Merge pull request #497 from cosmos/feature/sdk2-gaia-cli

REVIEW: Full-fledged gaia cli
This commit is contained in:
Ethan Buchman 2018-03-01 00:21:49 -05:00 committed by GitHub
commit 5494a4ed83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 2489 additions and 455 deletions

View File

@ -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

View File

@ -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
@ -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),
@ -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
@ -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,
)

38
client/flags.go Normal file
View File

@ -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
client/helpers.go Normal file
View File

@ -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
client/input.go Normal file
View File

@ -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
client/keys.go Normal file
View File

@ -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())
}

119
client/keys/README.md Normal file
View File

@ -0,0 +1,119 @@
# Keys CLI
**WARNING: out-of-date and parts are wrong.... please update**
This is as much an example how to expose cobra/viper, as for a cli itself
(I think this code is overkill for what go-keys needs). But please look at
the commands, and give feedback and changes.
`RootCmd` calls some initialization functions (`cobra.OnInitialize` and `RootCmd.PersistentPreRunE`) which serve to connect environmental variables and cobra flags, as well as load the config file. It also validates the flags registered on root and creates the cryptomanager, which will be used by all subcommands.
## Help info
```
# keys help
Keys allows you to manage your local keystore for tendermint.
These keys may be in any format supported by go-crypto and can be
used by light-clients, full nodes, or any other application that
needs to sign with a private key.
Usage:
keys [command]
Available Commands:
get Get details of one key
list List all keys
new Create a new public/private key pair
serve Run the key manager as an http server
update Change the password for a private key
Flags:
--keydir string Directory to store private keys (subdir of root) (default "keys")
-o, --output string Output format (text|json) (default "text")
-r, --root string root directory for config and data (default "/Users/ethan/.tlc")
Use "keys [command] --help" for more information about a command.
```
## Getting the config file
The first step is to load in root, by checking the following in order:
* -r, --root command line flag
* TM_ROOT environmental variable
* default ($HOME/.tlc evaluated at runtime)
Once the `rootDir` is established, the script looks for a config file named `keys.{json,toml,yaml,hcl}` in that directory and parses it. These values will provide defaults for flags of the same name.
There is an example config file for testing out locally, which writes keys to `./.mykeys`. You can
## Getting/Setting variables
When we want to get the value of a user-defined variable (eg. `output`), we can call `viper.GetString("output")`, which will do the following checks, until it finds a match:
* Is `--output` command line flag present?
* Is `TM_OUTPUT` environmental variable set?
* Was a config file found and does it have an `output` variable?
* Is there a default set on the command line flag?
If no variable is set and there was no default, we get back "".
This setup allows us to have powerful command line flags, but use env variables or config files (local or 12-factor style) to avoid passing these arguments every time.
## Nesting structures
Sometimes we don't just need key-value pairs, but actually a multi-level config file, like
```
[mail]
from = "no-reply@example.com"
server = "mail.example.com"
port = 567
password = "XXXXXX"
```
This CLI is too simple to warant such a structure, but I think eg. tendermint could benefit from such an approach. Here are some pointers:
* [Accessing nested keys from config files](https://github.com/spf13/viper#accessing-nested-keys)
* [Overriding nested values with envvars](https://www.netlify.com/blog/2016/09/06/creating-a-microservice-boilerplate-in-go/#nested-config-values) - the mentioned outstanding PR is already merged into master!
* Overriding nested values with cli flags? (use `--log_config.level=info` ??)
I'd love to see an example of this fully worked out in a more complex CLI.
## Have your cake and eat it too
It's easy to render data different ways. Some better for viewing, some better for importing to other programs. You can just add some global (persistent) flags to control the output formatting, and everyone gets what they want.
```
# keys list -e hex
All keys:
betty d0789984492b1674e276b590d56b7ae077f81adc
john b77f4720b220d1411a649b6c7f1151eb6b1c226a
# keys list -e btc
All keys:
betty 3uTF4r29CbtnzsNHZoPSYsE4BDwH
john 3ZGp2Md35iw4XVtRvZDUaAEkCUZP
# keys list -e b64 -o json
[
{
"name": "betty",
"address": "0HiZhEkrFnTidrWQ1Wt64Hf4Gtw=",
"pubkey": {
"type": "secp256k1",
"data": "F83WvhT0KwttSoqQqd_0_r2ztUUaQix5EXdO8AZyREoV31Og780NW59HsqTAb2O4hZ-w-j0Z-4b2IjfdqqfhVQ=="
}
},
{
"name": "john",
"address": "t39HILIg0UEaZJtsfxFR62scImo=",
"pubkey": {
"type": "ed25519",
"data": "t1LFmbg_8UTwj-n1wkqmnTp6NfaOivokEhlYySlGYCY="
}
}
]
```

122
client/keys/add.go Normal file
View File

@ -0,0 +1,122 @@
package keys
import (
"fmt"
"github.com/cosmos/cosmos-sdk/client"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/go-crypto/keys"
"github.com/tendermint/tmlibs/cli"
)
const (
flagType = "type"
flagRecover = "recover"
flagNoBackup = "no-backup"
flagDryRun = "dry-run"
)
func addKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "add <name>",
Short: "Create a new key, or import from seed",
Long: `Add a public/private key pair to the key store.
If you select --seed/-s you can recover a key from the seed
phrase, otherwise, a new key will be generated.`,
RunE: runAddCmd,
}
cmd.Flags().StringP(flagType, "t", "ed25519", "Type of private key (ed25519|secp256k1|ledger)")
cmd.Flags().Bool(flagRecover, false, "Provide seed phrase to recover existing key instead of creating")
cmd.Flags().Bool(flagNoBackup, false, "Don't print out seed phrase (if others are watching the terminal)")
cmd.Flags().Bool(flagDryRun, false, "Perform action, but don't add key to local keystore")
return cmd
}
func runAddCmd(cmd *cobra.Command, args []string) error {
var kb keys.Keybase
var err error
var name, pass string
buf := client.BufferStdin()
if viper.GetBool(flagDryRun) {
// we throw this away, so don't enforce args,
// we want to get a new random seed phrase quickly
kb = client.MockKeyBase()
pass = "throwing-this-key-away"
name = "inmemorykey"
} else {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a name for the key")
}
name = args[0]
kb, err = GetKeyBase()
if err != nil {
return err
}
pass, err = client.GetCheckPassword(
"Enter a passphrase for your key:",
"Repeat the passphrase:", buf)
if err != nil {
return err
}
}
if viper.GetBool(flagRecover) {
seed, err := client.GetSeed(
"Enter your recovery seed phrase:", buf)
if err != nil {
return err
}
info, err := kb.Recover(name, pass, seed)
if err != nil {
return err
}
// print out results without the seed phrase
viper.Set(flagNoBackup, true)
printCreate(info, "")
} else {
algo := keys.CryptoAlgo(viper.GetString(flagType))
info, seed, err := kb.Create(name, pass, algo)
if err != nil {
return err
}
printCreate(info, seed)
}
return nil
}
// addOutput lets us json format the data
type addOutput struct {
Key keys.Info `json:"key"`
Seed string `json:"seed"`
}
func printCreate(info keys.Info, seed string) {
output := viper.Get(cli.OutputFlag)
switch output {
case "text":
printInfo(info)
// print seed unless requested not to.
if !viper.GetBool(flagNoBackup) {
fmt.Println("**Important** write this seed phrase in a safe place.")
fmt.Println("It is the only way to recover your account if you ever forget your password.")
fmt.Println()
fmt.Println(seed)
}
case "json":
out := addOutput{Key: info}
if !viper.GetBool(flagNoBackup) {
out.Seed = seed
}
json, err := MarshalJSON(out)
if err != nil {
panic(err) // really shouldn't happen...
}
fmt.Println(string(json))
default:
panic(fmt.Sprintf("I can't speak: %s", output))
}
}

45
client/keys/delete.go Normal file
View File

@ -0,0 +1,45 @@
package keys
import (
"fmt"
"github.com/cosmos/cosmos-sdk/client"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
func deleteKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "delete <name>",
Short: "Delete the given key",
RunE: runDeleteCmd,
}
return cmd
}
func runDeleteCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a name for the key")
}
name := args[0]
buf := client.BufferStdin()
oldpass, err := client.GetPassword(
"DANGER - enter password to permanently delete key:", buf)
if err != nil {
return err
}
kb, err := GetKeyBase()
if err != nil {
return err
}
err = kb.Delete(name, oldpass)
if err != nil {
return err
}
fmt.Println("Password deleted forever (uh oh!)")
return nil
}

25
client/keys/list.go Normal file
View File

@ -0,0 +1,25 @@
package keys
import "github.com/spf13/cobra"
// listKeysCmd represents the list command
var listKeysCmd = &cobra.Command{
Use: "list",
Short: "List all keys",
Long: `Return a list of all public keys stored by this key manager
along with their associated name and address.`,
RunE: runListCmd,
}
func runListCmd(cmd *cobra.Command, args []string) error {
kb, err := GetKeyBase()
if err != nil {
return err
}
infos, err := kb.List()
if err == nil {
printInfos(infos)
}
return err
}

29
client/keys/root.go Normal file
View File

@ -0,0 +1,29 @@
package keys
import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/spf13/cobra"
)
// Commands registers a sub-tree of commands to interact with
// local private key storage.
func Commands() *cobra.Command {
cmd := &cobra.Command{
Use: "keys",
Short: "Add or view local private keys",
Long: `Keys allows you to manage your local keystore for tendermint.
These keys may be in any format supported by go-crypto and can be
used by light-clients, full nodes, or any other application that
needs to sign with a private key.`,
}
cmd.AddCommand(
addKeyCommand(),
listKeysCmd,
showKeysCmd,
client.LineBreak,
deleteKeyCommand(),
updateKeyCommand(),
)
return cmd
}

32
client/keys/show.go Normal file
View File

@ -0,0 +1,32 @@
package keys
import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
var showKeysCmd = &cobra.Command{
Use: "show <name>",
Short: "Show key info for the given name",
Long: `Return public details of one local key.`,
RunE: runShowCmd,
}
func runShowCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a name for the key")
}
name := args[0]
kb, err := GetKeyBase()
if err != nil {
return err
}
info, err := kb.Get(name)
if err == nil {
printInfo(info)
}
return err
}

50
client/keys/update.go Normal file
View File

@ -0,0 +1,50 @@
package keys
import (
"fmt"
"github.com/cosmos/cosmos-sdk/client"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
func updateKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "update <name>",
Short: "Change the password used to protect private key",
RunE: runUpdateCmd,
}
return cmd
}
func runUpdateCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a name for the key")
}
name := args[0]
buf := client.BufferStdin()
oldpass, err := client.GetPassword(
"Enter the current passphrase:", buf)
if err != nil {
return err
}
newpass, err := client.GetCheckPassword(
"Enter the new passphrase:",
"Repeat the new passphrase:", buf)
if err != nil {
return err
}
kb, err := GetKeyBase()
if err != nil {
return err
}
err = kb.Update(name, oldpass, newpass)
if err != nil {
return err
}
fmt.Println("Password successfully updated!")
return nil
}

68
client/keys/utils.go Normal file
View File

@ -0,0 +1,68 @@
package keys
import (
"fmt"
"github.com/spf13/viper"
keys "github.com/tendermint/go-crypto/keys"
"github.com/tendermint/tmlibs/cli"
dbm "github.com/tendermint/tmlibs/db"
"github.com/cosmos/cosmos-sdk/client"
)
// KeyDBName is the directory under root where we store the keys
const KeyDBName = "keys"
var (
// keybase is used to make GetKeyBase a singleton
keybase keys.Keybase
)
// GetKeyBase initializes a keybase based on the configuration
func GetKeyBase() (keys.Keybase, error) {
if keybase == nil {
rootDir := viper.GetString(cli.HomeFlag)
db, err := dbm.NewGoLevelDB(KeyDBName, rootDir)
if err != nil {
return nil, err
}
keybase = client.GetKeyBase(db)
}
return keybase, nil
}
func printInfo(info keys.Info) {
switch viper.Get(cli.OutputFlag) {
case "text":
addr := info.PubKey.Address().String()
sep := "\t\t"
if len(info.Name) > 7 {
sep = "\t"
}
fmt.Printf("%s%s%s\n", info.Name, sep, addr)
case "json":
json, err := MarshalJSON(info)
if err != nil {
panic(err) // really shouldn't happen...
}
fmt.Println(string(json))
}
}
func printInfos(infos []keys.Info) {
switch viper.Get(cli.OutputFlag) {
case "text":
fmt.Println("All keys:")
for _, i := range infos {
printInfo(i)
}
case "json":
json, err := MarshalJSON(infos)
if err != nil {
panic(err) // really shouldn't happen...
}
fmt.Println(string(json))
}
}

17
client/keys/wire.go Normal file
View File

@ -0,0 +1,17 @@
package keys
import (
crypto "github.com/tendermint/go-crypto"
wire "github.com/tendermint/go-wire"
)
var cdc *wire.Codec
func init() {
cdc = wire.NewCodec()
crypto.RegisterWire(cdc)
}
func MarshalJSON(o interface{}) ([]byte, error) {
return cdc.MarshalJSON(o)
}

36
client/lcd/root.go Normal file
View File

@ -0,0 +1,36 @@
package lcd
import (
"errors"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
)
const (
flagBind = "bind"
flagCORS = "cors"
)
// XXX: remove this when not needed
func todoNotImplemented(_ *cobra.Command, _ []string) error {
return errors.New("TODO: Command not yet implemented")
}
// ServeCommand will generate a long-running rest server
// (aka Light Client Daemon) that exposes functionality similar
// to the cli, but over rest
func ServeCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "serve",
Short: "Start LCD (light-client daemon), a local REST server",
RunE: todoNotImplemented,
}
// TODO: handle unix sockets also?
cmd.Flags().StringP(flagBind, "b", "localhost:1317", "Interface and port that server binds to")
cmd.Flags().String(flagCORS, "", "Set to domains that can make CORS requests (* for all)")
cmd.Flags().StringP(client.FlagChainID, "c", "", "ID of chain we connect to")
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
return cmd
}

66
client/rpc/block.go Normal file
View File

@ -0,0 +1,66 @@
package rpc
import (
"fmt"
"strconv"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
tmwire "github.com/tendermint/tendermint/wire"
)
const (
flagSelect = "select"
)
func blockCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "block [height]",
Short: "Get verified data for a the block at given height",
RunE: getBlock,
}
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
// TODO: change this to false when we can
cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses")
cmd.Flags().StringSlice(flagSelect, []string{"header", "tx"}, "Fields to return (header|txs|results)")
return cmd
}
func getBlock(cmd *cobra.Command, args []string) error {
var height *int64
// optional height
if len(args) > 0 {
h, err := strconv.Atoi(args[0])
if err != nil {
return err
}
if h > 0 {
tmp := int64(h)
height = &tmp
}
}
// get the node
node, err := client.GetNode()
if err != nil {
return err
}
// TODO: actually honor the --select flag!
// header -> BlockchainInfo
// header, tx -> Block
// results -> BlockResults
res, err := node.Block(height)
if err != nil {
return err
}
output, err := tmwire.MarshalJSON(res)
// output, err := json.MarshalIndent(res, " ", "")
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}

44
client/rpc/root.go Normal file
View File

@ -0,0 +1,44 @@
package rpc
import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
)
const (
// one of the following should be provided to verify the connection
flagGenesis = "genesis"
flagCommit = "commit"
flagValHash = "validator-set"
)
// XXX: remove this when not needed
func todoNotImplemented(_ *cobra.Command, _ []string) error {
return errors.New("TODO: Command not yet implemented")
}
// AddCommands adds a number of rpc-related subcommands
func AddCommands(cmd *cobra.Command) {
cmd.AddCommand(
initClientCommand(),
statusCommand(),
blockCommand(),
validatorCommand(),
)
}
func initClientCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "init",
Short: "Initialize light client",
RunE: todoNotImplemented,
}
cmd.Flags().StringP(client.FlagChainID, "c", "", "ID of chain we connect to")
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
cmd.Flags().String(flagGenesis, "", "Genesis file to verify header validity")
cmd.Flags().String(flagCommit, "", "File with trusted and signed header")
cmd.Flags().String(flagValHash, "", "Hash of trusted validator set (hex-encoded)")
return cmd
}

40
client/rpc/status.go Normal file
View File

@ -0,0 +1,40 @@
package rpc
import (
"fmt"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
tmwire "github.com/tendermint/tendermint/wire"
)
func statusCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "status",
Short: "Query remote node for status",
RunE: checkStatus,
}
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
return cmd
}
func checkStatus(cmd *cobra.Command, args []string) error {
// get the node
node, err := client.GetNode()
if err != nil {
return err
}
res, err := node.Status()
if err != nil {
return err
}
output, err := tmwire.MarshalJSON(res)
// output, err := json.MarshalIndent(res, " ", "")
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}

57
client/rpc/validators.go Normal file
View File

@ -0,0 +1,57 @@
package rpc
import (
"fmt"
"strconv"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
tmwire "github.com/tendermint/tendermint/wire"
)
func validatorCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "validatorset <height>",
Short: "Get the full validator set at given height",
RunE: getValidators,
}
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
// TODO: change this to false when we can
cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses")
return cmd
}
func getValidators(cmd *cobra.Command, args []string) error {
var height *int64
// optional height
if len(args) > 0 {
h, err := strconv.Atoi(args[0])
if err != nil {
return err
}
if h > 0 {
tmp := int64(h)
height = &tmp
}
}
// get the node
node, err := client.GetNode()
if err != nil {
return err
}
res, err := node.Validators(height)
if err != nil {
return err
}
output, err := tmwire.MarshalJSON(res)
// output, err := json.MarshalIndent(res, " ", "")
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}

20
client/tx/root.go Normal file
View File

@ -0,0 +1,20 @@
package tx
import (
"github.com/spf13/cobra"
wire "github.com/tendermint/go-wire"
)
// type used to pass around the provided cdc
type commander struct {
cdc *wire.Codec
}
// AddCommands adds a number of tx-query related subcommands
func AddCommands(cmd *cobra.Command, cdc *wire.Codec) {
cmdr := commander{cdc}
cmd.AddCommand(
SearchTxCmd(cmdr),
QueryTxCmd(cmdr),
)
}

80
client/tx/search.go Normal file
View File

@ -0,0 +1,80 @@
package tx
import (
"errors"
"fmt"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/client"
wire "github.com/tendermint/go-wire"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
)
const (
flagTags = "tag"
flagAny = "any"
)
// default client command to search through tagged transactions
func SearchTxCmd(cmdr commander) *cobra.Command {
cmd := &cobra.Command{
Use: "txs",
Short: "Search for all transactions that match the given tags",
RunE: cmdr.searchTxCmd,
}
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
// TODO: change this to false once proofs built in
cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses")
cmd.Flags().StringSlice(flagTags, nil, "Tags that must match (may provide multiple)")
cmd.Flags().Bool(flagAny, false, "Return transactions that match ANY tag, rather than ALL")
return cmd
}
func (c commander) searchTxCmd(cmd *cobra.Command, args []string) error {
tags := viper.GetStringSlice(flagTags)
if len(tags) == 0 {
return errors.New("Must declare at least one tag to search")
}
// XXX: implement ANY
query := strings.Join(tags, " AND ")
// get the node
node, err := client.GetNode()
if err != nil {
return err
}
prove := !viper.GetBool(client.FlagTrustNode)
res, err := node.TxSearch(query, prove)
if err != nil {
return err
}
info, err := formatTxResults(c.cdc, res)
if err != nil {
return err
}
output, err := c.cdc.MarshalJSON(info)
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}
func formatTxResults(cdc *wire.Codec, res []*ctypes.ResultTx) ([]txInfo, error) {
var err error
out := make([]txInfo, len(res))
for i := range res {
out[i], err = formatTxResult(cdc, res[i])
if err != nil {
return nil, err
}
}
return out, nil
}

99
client/tx/tx.go Normal file
View File

@ -0,0 +1,99 @@
package tx
import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/client"
sdk "github.com/cosmos/cosmos-sdk/types"
abci "github.com/tendermint/abci/types"
wire "github.com/tendermint/go-wire"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
)
// Get the default command for a tx query
func QueryTxCmd(cmdr commander) *cobra.Command {
cmd := &cobra.Command{
Use: "tx [hash]",
Short: "Matches this txhash over all committed blocks",
RunE: cmdr.queryTxCmd,
}
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
// TODO: change this to false when we can
cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses")
return cmd
}
// command to query for a transaction
func (c commander) queryTxCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a tx hash")
}
// find the key to look up the account
hexStr := args[0]
hash, err := hex.DecodeString(hexStr)
if err != nil {
return err
}
// get the node
node, err := client.GetNode()
if err != nil {
return err
}
prove := !viper.GetBool(client.FlagTrustNode)
res, err := node.Tx(hash, prove)
if err != nil {
return err
}
info, err := formatTxResult(c.cdc, res)
if err != nil {
return err
}
output, err := json.MarshalIndent(info, "", " ")
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}
func formatTxResult(cdc *wire.Codec, res *ctypes.ResultTx) (txInfo, error) {
// TODO: verify the proof if requested
tx, err := parseTx(cdc, res.Tx)
if err != nil {
return txInfo{}, err
}
info := txInfo{
Height: res.Height,
Tx: tx,
Result: res.TxResult,
}
return info, nil
}
// txInfo is used to prepare info to display
type txInfo struct {
Height int64 `json:"height"`
Tx sdk.Tx `json:"tx"`
Result abci.ResponseDeliverTx `json:"result"`
}
func parseTx(cdc *wire.Codec, txBytes []byte) (sdk.Tx, error) {
var tx sdk.StdTx
err := cdc.UnmarshalBinary(txBytes, &tx)
if err != nil {
return nil, err
}
return tx, nil
}

View File

@ -40,7 +40,7 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp {
// create your application object
var app = &BasecoinApp{
BaseApp: bam.NewBaseApp(appName, logger, db),
cdc: MakeTxCodec(),
cdc: MakeCodec(),
capKeyMainStore: sdk.NewKVStoreKey("main"),
capKeyIBCStore: sdk.NewKVStoreKey("ibc"),
}
@ -71,10 +71,11 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp {
}
// custom tx codec
func MakeTxCodec() *wire.Codec {
func MakeCodec() *wire.Codec {
cdc := wire.NewCodec()
crypto.RegisterWire(cdc) // Register crypto.[PubKey,PrivKey,Signature] types.
cdc.RegisterInterface((*sdk.Msg)(nil), nil)
bank.RegisterWire(cdc) // Register bank.[SendMsg,IssueMsg] types.
crypto.RegisterWire(cdc) // Register crypto.[PubKey,PrivKey,Signature] types.
return cdc
}

View File

@ -53,14 +53,21 @@ func TestSendMsg(t *testing.T) {
Signature: sig,
}})
// just marshal/unmarshal!
cdc := MakeCodec()
txBytes, err := cdc.MarshalBinary(tx)
require.NoError(t, err)
// Run a Check
res := bapp.Check(tx)
assert.Equal(t, sdk.CodeUnrecognizedAddress, res.Code, res.Log)
cres := bapp.CheckTx(txBytes)
assert.Equal(t, sdk.CodeUnrecognizedAddress,
sdk.CodeType(cres.Code), cres.Log)
// Simulate a Block
bapp.BeginBlock(abci.RequestBeginBlock{})
res = bapp.Deliver(tx)
assert.Equal(t, sdk.CodeUnrecognizedAddress, res.Code, res.Log)
dres := bapp.DeliverTx(txBytes)
assert.Equal(t, sdk.CodeUnrecognizedAddress,
sdk.CodeType(dres.Code), dres.Log)
}
func TestGenesis(t *testing.T) {

View File

@ -0,0 +1,71 @@
package main
import (
"errors"
"os"
"github.com/spf13/cobra"
"github.com/tendermint/tmlibs/cli"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/client/lcd"
"github.com/cosmos/cosmos-sdk/client/rpc"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/version"
authcmd "github.com/cosmos/cosmos-sdk/x/auth/commands"
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/commands"
"github.com/cosmos/cosmos-sdk/examples/basecoin/app"
"github.com/cosmos/cosmos-sdk/examples/basecoin/types"
)
// gaiacliCmd is the entry point for this binary
var (
basecliCmd = &cobra.Command{
Use: "basecli",
Short: "Basecoin light-client",
}
)
func todoNotImplemented(_ *cobra.Command, _ []string) error {
return errors.New("TODO: Command not yet implemented")
}
func main() {
// disable sorting
cobra.EnableCommandSorting = false
// get the codec
cdc := app.MakeCodec()
// add standard rpc, and tx commands
rpc.AddCommands(basecliCmd)
basecliCmd.AddCommand(client.LineBreak)
tx.AddCommands(basecliCmd, cdc)
basecliCmd.AddCommand(client.LineBreak)
// add query/post commands (custom to binary)
basecliCmd.AddCommand(
client.GetCommands(
authcmd.GetAccountCmd("main", cdc, types.GetParseAccount(cdc)),
)...)
basecliCmd.AddCommand(
client.PostCommands(
bankcmd.SendTxCmd(cdc),
)...)
// add proxy, version and key info
basecliCmd.AddCommand(
client.LineBreak,
lcd.ServeCommand(),
keys.Commands(),
client.LineBreak,
version.VersionCmd,
)
// prepare and add flags
executor := cli.PrepareMainCmd(basecliCmd, "BC", os.ExpandEnv("$HOME/.basecli"))
executor.Execute()
}

View File

@ -1,27 +1,76 @@
package main
import (
"encoding/json"
"fmt"
"os"
"github.com/spf13/cobra"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/tmlibs/cli"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/examples/basecoin/app"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/version"
)
func main() {
fmt.Println("This is temporary, for unblocking our build process.")
return
// basecoindCmd is the entry point for this binary
var (
basecoindCmd = &cobra.Command{
Use: "gaiad",
Short: "Gaia Daemon (server)",
}
)
// TODO CREATE CLI
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "main")
db, err := dbm.NewGoLevelDB("basecoind", "data")
// defaultOptions sets up the app_options for the
// default genesis file
func defaultOptions(args []string) (json.RawMessage, error) {
addr, secret, err := server.GenerateCoinKey()
if err != nil {
fmt.Println(err)
os.Exit(1)
return nil, err
}
fmt.Println("Secret phrase to access coins:")
fmt.Println(secret)
opts := fmt.Sprintf(`{
"accounts": [{
"address": "%s",
"coins": [
{
"denom": "mycoin",
"amount": 9007199254740992
}
]
}]
}`, addr)
return json.RawMessage(opts), nil
}
func generateApp(rootDir string, logger log.Logger) (abci.Application, error) {
db, err := dbm.NewGoLevelDB("basecoin", rootDir)
if err != nil {
return nil, err
}
bapp := app.NewBasecoinApp(logger, db)
baseapp.RunForever(bapp)
return bapp, nil
}
func main() {
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).
With("module", "main")
basecoindCmd.AddCommand(
server.InitCmd(defaultOptions, logger),
server.StartCmd(generateApp, logger),
server.UnsafeResetAllCmd(logger),
version.VersionCmd,
)
// prepare and add flags
rootDir := os.ExpandEnv("$HOME/.basecoind")
executor := cli.PrepareBaseCmd(basecoindCmd, "BC", rootDir)
executor.Execute()
}

View File

@ -4,6 +4,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
crypto "github.com/tendermint/go-crypto"
wire "github.com/tendermint/go-wire"
)
var _ sdk.Account = (*AppAccount)(nil)
@ -15,13 +16,22 @@ var _ sdk.Account = (*AppAccount)(nil)
// auth.AccountStore uses the flexible go-wire library.
type AppAccount struct {
auth.BaseAccount
Name string
Name string `json:"name"`
}
// nolint
func (acc AppAccount) GetName() string { return acc.Name }
func (acc *AppAccount) SetName(name string) { acc.Name = name }
// Get the ParseAccount function for the custom AppAccount
func GetParseAccount(cdc *wire.Codec) sdk.ParseAccount {
return func(accBytes []byte) (res sdk.Account, err error) {
acct := new(AppAccount)
err = cdc.UnmarshalBinary(accBytes, acct)
return acct, err
}
}
//___________________________________________________________________________________
// State to Unmarshal

View File

@ -1,131 +0,0 @@
package main
import "github.com/spf13/cobra"
const (
// these are needed for every init
flagChainID = "chain-id"
flagNode = "node"
// one of the following should be provided to verify the connection
flagGenesis = "genesis"
flagCommit = "commit"
flagValHash = "validator-set"
flagSelect = "select"
flagTags = "tag"
flagAny = "any"
flagBind = "bind"
flagCORS = "cors"
flagTrustNode = "trust-node"
// this is for signing
flagName = "name"
)
var (
statusCmd = &cobra.Command{
Use: "status",
Short: "Query remote node for status",
RunE: todoNotImplemented,
}
)
// AddClientCommands returns a sub-tree of all basic client commands
//
// Call AddGetCommand and AddPostCommand to add custom txs and queries
func AddClientCommands(cmd *cobra.Command) {
cmd.AddCommand(
initClientCommand(),
statusCmd,
blockCommand(),
validatorCommand(),
lineBreak,
txSearchCommand(),
txCommand(),
lineBreak,
)
}
// GetCommands adds common flags to query commands
func GetCommands(cmds ...*cobra.Command) []*cobra.Command {
for _, c := range cmds {
c.Flags().Bool(flagTrustNode, false, "Don't verify proofs for responses")
}
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(flagPassword, "", "Password to use the named private key")
}
return cmds
}
func initClientCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "init",
Short: "Initialize light client",
RunE: todoNotImplemented,
}
cmd.Flags().StringP(flagChainID, "c", "", "ID of chain we connect to")
cmd.Flags().StringP(flagNode, "n", "tcp://localhost:46657", "Node to connect to")
cmd.Flags().String(flagGenesis, "", "Genesis file to verify header validity")
cmd.Flags().String(flagCommit, "", "File with trusted and signed header")
cmd.Flags().String(flagValHash, "", "Hash of trusted validator set (hex-encoded)")
return cmd
}
func blockCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "block <height>",
Short: "Get verified data for a the block at given height",
RunE: todoNotImplemented,
}
cmd.Flags().StringSlice(flagSelect, []string{"header", "tx"}, "Fields to return (header|txs|results)")
return cmd
}
func validatorCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "validatorset <height>",
Short: "Get the full validator set at given height",
RunE: todoNotImplemented,
}
return cmd
}
func serveCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "serve",
Short: "Start LCD (light-client daemon), a local REST server",
RunE: todoNotImplemented,
}
// TODO: handle unix sockets also?
cmd.Flags().StringP(flagBind, "b", "localhost:1317", "Interface and port that server binds to")
cmd.Flags().String(flagCORS, "", "Set to domains that can make CORS requests (* for all)")
return cmd
}
func txSearchCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "txs",
Short: "Search for all transactions that match the given tags",
RunE: todoNotImplemented,
}
cmd.Flags().StringSlice(flagTags, nil, "Tags that must match (may provide multiple)")
cmd.Flags().Bool(flagAny, false, "Return transactions that match ANY tag, rather than ALL")
return cmd
}
func txCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "tx <hash>",
Short: "Matches this txhash over all committed blocks",
RunE: todoNotImplemented,
}
return cmd
}

View File

@ -1,77 +0,0 @@
package main
import "github.com/spf13/cobra"
const (
flagPassword = "password"
flagNewPassword = "new-password"
flagType = "type"
flagSeed = "seed"
flagDryRun = "dry-run"
)
var (
listKeysCmd = &cobra.Command{
Use: "list",
Short: "List all locally availably keys",
RunE: todoNotImplemented,
}
showKeysCmd = &cobra.Command{
Use: "show <name>",
Short: "Show key info for the given name",
RunE: todoNotImplemented,
}
)
// KeyCommands registers a sub-tree of commands to interact with
// local private key storage.
func KeyCommands() *cobra.Command {
cmd := &cobra.Command{
Use: "keys",
Short: "Add or view local private keys",
}
cmd.AddCommand(
addKeyCommand(),
listKeysCmd,
showKeysCmd,
lineBreak,
deleteKeyCommand(),
updateKeyCommand(),
)
return cmd
}
func addKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "add <name>",
Short: "Create a new key, or import from seed",
RunE: todoNotImplemented,
}
cmd.Flags().StringP(flagPassword, "p", "", "Password to encrypt private key")
cmd.Flags().StringP(flagType, "t", "ed25519", "Type of private key (ed25519|secp256k1|ledger)")
cmd.Flags().StringP(flagSeed, "s", "", "Provide seed phrase to recover existing key instead of creating")
cmd.Flags().Bool(flagDryRun, false, "Perform action, but don't add key to local keystore")
return cmd
}
func updateKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "update <name>",
Short: "Change the password used to protect private key",
RunE: todoNotImplemented,
}
cmd.Flags().StringP(flagPassword, "p", "", "Current password to decrypt key")
cmd.Flags().String(flagNewPassword, "", "New password to use to protect key")
return cmd
}
func deleteKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "delete <name>",
Short: "Delete the given key",
RunE: todoNotImplemented,
}
cmd.Flags().StringP(flagPassword, "p", "", "Password of existing key to delete")
return cmd
}

View File

@ -1,75 +0,0 @@
package main
import (
"errors"
"os"
"github.com/spf13/cobra"
"github.com/tendermint/tmlibs/cli"
)
const (
flagTo = "to"
flagAmount = "amount"
flagFee = "fee"
)
// gaiacliCmd is the entry point for this binary
var (
gaiacliCmd = &cobra.Command{
Use: "gaiacli",
Short: "Gaia light-client",
}
lineBreak = &cobra.Command{Run: func(*cobra.Command, []string) {}}
getAccountCmd = &cobra.Command{
Use: "account <address>",
Short: "Query account balance",
RunE: todoNotImplemented,
}
)
func todoNotImplemented(_ *cobra.Command, _ []string) error {
return errors.New("TODO: Command not yet implemented")
}
func postSendCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "send",
Short: "Create and sign a send tx",
RunE: todoNotImplemented,
}
cmd.Flags().String(flagTo, "", "Address to send coins")
cmd.Flags().String(flagAmount, "", "Amount of coins to send")
cmd.Flags().String(flagFee, "", "Fee to pay along with transaction")
return cmd
}
func main() {
// disable sorting
cobra.EnableCommandSorting = false
// generic client commands
AddClientCommands(gaiacliCmd)
// query commands (custom to binary)
gaiacliCmd.AddCommand(
GetCommands(getAccountCmd)...)
// post tx commands (custom to binary)
gaiacliCmd.AddCommand(
PostCommands(postSendCommand())...)
// add proxy, version and key info
gaiacliCmd.AddCommand(
lineBreak,
serveCommand(),
KeyCommands(),
lineBreak,
VersionCmd,
)
// prepare and add flags
executor := cli.PrepareBaseCmd(gaiacliCmd, "GA", os.ExpandEnv("$HOME/.gaiacli"))
executor.Execute()
}

View File

@ -1,26 +0,0 @@
package main
import (
"fmt"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/version"
)
var (
// VersionCmd prints out the current sdk version
VersionCmd = &cobra.Command{
Use: "version",
Short: "Print the app version",
Run: doVersionCmd,
}
)
func doVersionCmd(cmd *cobra.Command, args []string) {
v := version.Version
if version.GitCommit != "" {
v = v + " " + version.GitCommit
}
fmt.Println(v)
}

View File

@ -1,44 +0,0 @@
package main
import (
"errors"
"os"
"github.com/spf13/cobra"
"github.com/tendermint/tmlibs/cli"
"github.com/cosmos/cosmos-sdk/baseapp"
)
const (
flagTo = "to"
flagAmount = "amount"
flagFee = "fee"
)
// gaiadCmd is the entry point for this binary
var (
gaiadCmd = &cobra.Command{
Use: "gaiad",
Short: "Gaia Daemon (server)",
}
)
func todoNotImplemented(_ *cobra.Command, _ []string) error {
return errors.New("TODO: Command not yet implemented")
}
func main() {
// TODO: set this to something real
var node baseapp.BaseApp
AddNodeCommands(gaiadCmd, node)
gaiadCmd.AddCommand(
VersionCmd,
)
// prepare and add flags
executor := cli.PrepareBaseCmd(gaiadCmd, "GA", os.ExpandEnv("$HOME/.gaiad"))
executor.Execute()
}

View File

@ -1,47 +0,0 @@
package main
import (
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/baseapp"
)
const (
flagWithTendermint = "with-tendermint"
)
var (
initNodeCmd = &cobra.Command{
Use: "init <flags???>",
Short: "Initialize full node",
RunE: todoNotImplemented,
}
resetNodeCmd = &cobra.Command{
Use: "unsafe_reset_all",
Short: "Reset full node data (danger, must resync)",
RunE: todoNotImplemented,
}
)
// AddNodeCommands registers all commands to interact
// with a local full-node as subcommands of the argument.
//
// Accept an application it should start
func AddNodeCommands(cmd *cobra.Command, node baseapp.BaseApp) {
cmd.AddCommand(
initNodeCmd,
startNodeCmd(node),
resetNodeCmd,
)
}
func startNodeCmd(node baseapp.BaseApp) *cobra.Command {
cmd := &cobra.Command{
Use: "start",
Short: "Run the full node",
RunE: todoNotImplemented,
}
cmd.Flags().Bool(flagWithTendermint, true, "run abci app embedded in-process with tendermint")
return cmd
}

76
glide.lock generated
View File

@ -1,6 +1,8 @@
hash: 2b4ad3bf1489a7cb5e62c6cb4c1fa976d4ae21993743e4968418c4e81925fb99
updated: 2018-02-19T17:13:04.368106064Z
hash: fa45c8a4f5512ed730f793b93d4876bdc604a1333a5a1f938c98a0f7dd55f22e
updated: 2018-02-23T19:47:25.630102+01:00
imports:
- name: github.com/bgentry/speakeasy
version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd
- name: github.com/btcsuite/btcd
version: 50de9da05b50eb15658bb350f6ea24368a111ab7
subpackages:
@ -9,6 +11,8 @@ imports:
version: 346938d642f2ec3594ed81d874461961cd0faa76
subpackages:
- spew
- name: github.com/ebuchman/fail-test
version: 95f809107225be108efcf10a3509e4ea6ceef3c4
- name: github.com/fsnotify/fsnotify
version: c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9
- name: github.com/go-kit/kit
@ -40,6 +44,8 @@ imports:
- ptypes/timestamp
- name: github.com/golang/snappy
version: 553a641470496b2327abcac10b36396bd98e45c9
- name: github.com/gorilla/websocket
version: ea4d1f681babbce9545c9c5f3d5194a789c89f5b
- name: github.com/hashicorp/hcl
version: 23c074d0eceb2b8a5bfdbb271ab780cde70f05a8
subpackages:
@ -51,6 +57,8 @@ imports:
- json/parser
- json/scanner
- json/token
- name: github.com/howeyc/crc16
version: 96a97a1abb579c7ff1a8ffa77f2e72d1c314b57f
- name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- name: github.com/jmhodges/levigo
@ -59,12 +67,16 @@ imports:
version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0
- name: github.com/magiconair/properties
version: 49d762b9817ba1c2e9d0c69183c2b4a8b8f1d934
- name: github.com/mattn/go-isatty
version: 0360b2af4f38e8d38c7fce2a9f4e702702d73a39
- name: github.com/mitchellh/mapstructure
version: b4575eea38cca1123ec2dc90c26529b5c5acfcff
- name: github.com/pelletier/go-toml
version: acdc4509485b587f5e675510c4f2c63e90ff68a8
- name: github.com/pkg/errors
version: 645ef00459ed84a119197bfb8d8205042c6df63d
- name: github.com/rcrowley/go-metrics
version: 1f30fe9094a513ce4c700b9a54458bbb0c96996c
- name: github.com/rigelrozanski/common
version: f691f115798593d783b9999b1263c2f4ffecc439
- name: github.com/spf13/afero
@ -97,8 +109,11 @@ imports:
- leveldb/table
- leveldb/util
- name: github.com/tendermint/abci
version: c960c5275617ef141c92c3d7fc65a396c97662df
version: 68592f4d8ee34e97db94b7a7976b1309efdb7eb9
subpackages:
- client
- example/code
- example/dummy
- server
- types
- name: github.com/tendermint/ed25519
@ -108,21 +123,72 @@ imports:
- extra25519
- name: github.com/tendermint/go-crypto
version: 4fc3055dbd17aa1203d0abc64b9293f378da22ec
subpackages:
- keys
- keys/bcrypt
- keys/words
- keys/words/wordlist
- name: github.com/tendermint/go-wire
version: 5d7845f24b843c914cf571dad2ca13c91cf70f0d
- name: github.com/tendermint/iavl
version: 1a59ec0c82dc940c25339dd7c834df5cb76a95cb
- name: github.com/tendermint/tmlibs
version: a0f652dc2e131be86fc8d9e4e2beec9831a8a6ec
- name: github.com/tendermint/tendermint
version: 6947e118f54e4df24f5e2c79bcdd66199e54d885
subpackages:
- blockchain
- cmd/tendermint/commands
- config
- consensus
- consensus/types
- evidence
- lite
- lite/client
- lite/errors
- lite/files
- lite/proxy
- mempool
- node
- p2p
- p2p/conn
- p2p/pex
- p2p/trust
- p2p/upnp
- proxy
- rpc/client
- rpc/core
- rpc/core/types
- rpc/grpc
- rpc/lib
- rpc/lib/client
- rpc/lib/server
- rpc/lib/types
- state
- state/txindex
- state/txindex/kv
- state/txindex/null
- types
- version
- wire
- name: github.com/tendermint/tmlibs
version: 1b9b5652a199ab0be2e781393fb275b66377309d
subpackages:
- autofile
- cli
- cli/flags
- clist
- common
- db
- flowrate
- log
- merkle
- pubsub
- pubsub/query
- name: golang.org/x/crypto
version: 1875d0a70c90e57f11972aefd42276df65e895b9
subpackages:
- blowfish
- curve25519
- nacl/box
- nacl/secretbox
- openpgp/armor
- openpgp/errors

View File

@ -4,6 +4,10 @@ import:
version: ^1.0.0
subpackages:
- proto
- package: github.com/bgentry/speakeasy
version: ^0.1.0
- package: github.com/mattn/go-isatty
version: ~0.0.3
- package: github.com/pkg/errors
version: ^0.8.0
- package: github.com/rigelrozanski/common
@ -25,6 +29,14 @@ import:
- db
- log
- merkle
- package: github.com/tendermint/tendermint
version: breaking/wire-sdk2
subpackages:
- cmd/tendermint/commands
- config
- lite
- rpc/client
- types
- package: golang.org/x/crypto
subpackages:
- ripemd160

122
mock/app.go Normal file
View File

@ -0,0 +1,122 @@
package mock
import (
"encoding/json"
"fmt"
abci "github.com/tendermint/abci/types"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
bam "github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// NewApp creates a simple mock kvstore app for testing.
// It should work similar to a real app.
// Make sure rootDir is empty before running the test,
// in order to guarantee consistent results
func NewApp(rootDir string, logger log.Logger) (abci.Application, error) {
db, err := dbm.NewGoLevelDB("mock", rootDir)
if err != nil {
return nil, err
}
// Capabilities key to access the main KVStore.
capKeyMainStore := sdk.NewKVStoreKey("main")
// Create BaseApp.
baseApp := bam.NewBaseApp("kvstore", logger, db)
// Set mounts for BaseApp's MultiStore.
baseApp.MountStoresIAVL(capKeyMainStore)
// Set Tx decoder
baseApp.SetTxDecoder(decodeTx)
baseApp.SetInitChainer(InitChainer(capKeyMainStore))
// Set a handler Route.
baseApp.Router().AddRoute("kvstore", KVStoreHandler(capKeyMainStore))
// Load latest version.
if err := baseApp.LoadLatestVersion(capKeyMainStore); err != nil {
return nil, err
}
return baseApp, nil
}
// KVStoreHandler is a simple handler that takes kvstoreTx and writes
// them to the db
func KVStoreHandler(storeKey sdk.StoreKey) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
dTx, ok := msg.(kvstoreTx)
if !ok {
panic("KVStoreHandler should only receive kvstoreTx")
}
// tx is already unmarshalled
key := dTx.key
value := dTx.value
store := ctx.KVStore(storeKey)
store.Set(key, value)
return sdk.Result{
Code: 0,
Log: fmt.Sprintf("set %s=%s", key, value),
}
}
}
// basic KV structure
type KV struct {
Key string `json:"key"`
Value string `json:"value"`
}
// What Genesis JSON is formatted as
type GenesisJSON struct {
Values []KV `json:"values"`
}
// InitChainer returns a function that can initialize the chain
// with key/value pairs
func InitChainer(key sdk.StoreKey) func(sdk.Context, abci.RequestInitChain) abci.ResponseInitChain {
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
stateJSON := req.AppStateBytes
genesisState := new(GenesisJSON)
err := json.Unmarshal(stateJSON, genesisState)
if err != nil {
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
// return sdk.ErrGenesisParse("").TraceCause(err, "")
}
for _, val := range genesisState.Values {
store := ctx.KVStore(key)
store.Set([]byte(val.Key), []byte(val.Value))
}
return abci.ResponseInitChain{}
}
}
// GenInitOptions can be passed into InitCmd,
// returns a static string of a few key-values that can be parsed
// by InitChainer
func GenInitOptions(args []string) (json.RawMessage, error) {
opts := []byte(`{
"values": [
{
"key": "hello",
"value": "goodbye"
},
{
"key": "foo",
"value": "bar"
}
]
}`)
return opts, nil
}

75
mock/app_test.go Normal file
View File

@ -0,0 +1,75 @@
package mock
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types"
)
// TestInitApp makes sure we can initialize this thing without an error
func TestInitApp(t *testing.T) {
// set up an app
app, closer, err := SetupApp()
// closer may need to be run, even when error in later stage
if closer != nil {
defer closer()
}
require.NoError(t, err)
// initialize it future-way
opts, err := GenInitOptions(nil)
require.NoError(t, err)
req := abci.RequestInitChain{AppStateBytes: opts}
app.InitChain(req)
app.Commit()
// XXX test failing
// make sure we can query these values
query := abci.RequestQuery{
Path: "/main/key",
Data: []byte("foo"),
}
qres := app.Query(query)
require.Equal(t, uint32(0), qres.Code, qres.Log)
assert.Equal(t, []byte("bar"), qres.Value)
}
// TextDeliverTx ensures we can write a tx
func TestDeliverTx(t *testing.T) {
// set up an app
app, closer, err := SetupApp()
// closer may need to be run, even when error in later stage
if closer != nil {
defer closer()
}
require.NoError(t, err)
key := "my-special-key"
value := "top-secret-data!!"
tx := NewTx(key, value)
txBytes := tx.GetSignBytes()
header := abci.Header{
AppHash: []byte("apphash"),
Height: 1,
}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
dres := app.DeliverTx(txBytes)
require.Equal(t, uint32(0), dres.Code, dres.Log)
app.EndBlock(abci.RequestEndBlock{})
cres := app.Commit()
require.NotEmpty(t, cres.Data)
// make sure we can query these values
query := abci.RequestQuery{
Path: "/main/key",
Data: []byte(key),
}
qres := app.Query(query)
require.Equal(t, uint32(0), qres.Code, qres.Log)
assert.Equal(t, []byte(value), qres.Value)
}

27
mock/helpers.go Normal file
View File

@ -0,0 +1,27 @@
package mock
import (
"io/ioutil"
"os"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/tmlibs/log"
)
// SetupApp returns an application as well as a clean-up function
// to be used to quickly setup a test case with an app
func SetupApp() (abci.Application, func(), error) {
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).
With("module", "mock")
rootDir, err := ioutil.TempDir("", "mock-sdk")
if err != nil {
return nil, nil, err
}
cleanup := func() {
os.RemoveAll(rootDir)
}
app, err := NewApp(rootDir, logger)
return app, cleanup, err
}

89
mock/tx.go Normal file
View File

@ -0,0 +1,89 @@
//nolint
package mock
import (
"bytes"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
crypto "github.com/tendermint/go-crypto"
)
// An sdk.Tx which is its own sdk.Msg.
type kvstoreTx struct {
key []byte
value []byte
bytes []byte
}
var _ sdk.Tx = kvstoreTx{}
func NewTx(key, value string) kvstoreTx {
bytes := fmt.Sprintf("%s=%s", key, value)
return kvstoreTx{
key: []byte(key),
value: []byte(value),
bytes: []byte(bytes),
}
}
func (tx kvstoreTx) Get(key interface{}) (value interface{}) {
switch k := key.(type) {
case string:
switch k {
case "key":
return tx.key
case "value":
return tx.value
}
}
return nil
}
func (tx kvstoreTx) Type() string {
return "kvstore"
}
func (tx kvstoreTx) GetMsg() sdk.Msg {
return tx
}
func (tx kvstoreTx) GetSignBytes() []byte {
return tx.bytes
}
// Should the app be calling this? Or only handlers?
func (tx kvstoreTx) ValidateBasic() sdk.Error {
return nil
}
func (tx kvstoreTx) GetSigners() []crypto.Address {
return nil
}
func (tx kvstoreTx) GetSignatures() []sdk.StdSignature {
return nil
}
func (tx kvstoreTx) GetFeePayer() crypto.Address {
return nil
}
// takes raw transaction bytes and decodes them into an sdk.Tx. An sdk.Tx has
// all the signatures and can be used to authenticate.
func decodeTx(txBytes []byte) (sdk.Tx, sdk.Error) {
var tx sdk.Tx
split := bytes.Split(txBytes, []byte("="))
if len(split) == 1 {
k := split[0]
tx = kvstoreTx{k, k, txBytes}
} else if len(split) == 2 {
k, v := split[0], split[1]
tx = kvstoreTx{k, v, txBytes}
} else {
return nil, sdk.ErrTxParse("too many =")
}
return tx, nil
}

182
server/init.go Normal file
View File

@ -0,0 +1,182 @@
package server
import (
"encoding/json"
"fmt"
"io/ioutil"
"github.com/spf13/cobra"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-crypto/keys"
"github.com/tendermint/go-crypto/keys/words"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
cfg "github.com/tendermint/tendermint/config"
tmtypes "github.com/tendermint/tendermint/types"
)
// InitCmd will initialize all files for tendermint,
// along with proper app_options.
// The application can pass in a function to generate
// proper options. And may want to use GenerateCoinKey
// to create default account(s).
func InitCmd(gen GenOptions, logger log.Logger) *cobra.Command {
cmd := initCmd{
gen: gen,
logger: logger,
}
return &cobra.Command{
Use: "init",
Short: "Initialize genesis files",
RunE: cmd.run,
}
}
// GenOptions can parse command-line and flag to
// generate default app_options for the genesis file.
// This is application-specific
type GenOptions func(args []string) (json.RawMessage, error)
// GenerateCoinKey returns the address of a public key,
// along with the secret phrase to recover the private key.
// You can give coins to this address and return the recovery
// phrase to the user to access them.
func GenerateCoinKey() (crypto.Address, string, error) {
// construct an in-memory key store
codec, err := words.LoadCodec("english")
if err != nil {
return nil, "", err
}
keybase := keys.New(
dbm.NewMemDB(),
codec,
)
// generate a private key, with recovery phrase
info, secret, err := keybase.Create("name", "pass", keys.AlgoEd25519)
if err != nil {
return nil, "", err
}
addr := info.PubKey.Address()
return addr, secret, nil
}
type initCmd struct {
gen GenOptions
logger log.Logger
}
func (c initCmd) run(cmd *cobra.Command, args []string) error {
// Run the basic tendermint initialization,
// set up a default genesis with no app_options
config, err := tcmd.ParseConfig()
if err != nil {
return err
}
err = c.initTendermintFiles(config)
if err != nil {
return err
}
// no app_options, leave like tendermint
if c.gen == nil {
return nil
}
// Now, we want to add the custom app_options
options, err := c.gen(args)
if err != nil {
return err
}
// And add them to the genesis file
genFile := config.GenesisFile()
return addGenesisOptions(genFile, options)
}
// This was copied from tendermint/cmd/tendermint/commands/init.go
// so we could pass in the config and the logger.
func (c initCmd) initTendermintFiles(config *cfg.Config) error {
// private validator
privValFile := config.PrivValidatorFile()
var privValidator *tmtypes.PrivValidatorFS
if cmn.FileExists(privValFile) {
privValidator = tmtypes.LoadPrivValidatorFS(privValFile)
c.logger.Info("Found private validator", "path", privValFile)
} else {
privValidator = tmtypes.GenPrivValidatorFS(privValFile)
privValidator.Save()
c.logger.Info("Generated private validator", "path", privValFile)
}
// genesis file
genFile := config.GenesisFile()
if cmn.FileExists(genFile) {
c.logger.Info("Found genesis file", "path", genFile)
} else {
genDoc := tmtypes.GenesisDoc{
ChainID: cmn.Fmt("test-chain-%v", cmn.RandStr(6)),
}
genDoc.Validators = []tmtypes.GenesisValidator{{
PubKey: privValidator.GetPubKey(),
Power: 10,
}}
if err := genDoc.SaveAs(genFile); err != nil {
return err
}
c.logger.Info("Generated genesis file", "path", genFile)
}
return nil
}
// GenesisDoc involves some tendermint-specific structures we don't
// want to parse, so we just grab it into a raw object format,
// so we can add one line.
type GenesisDoc map[string]json.RawMessage
func addGenesisOptions(filename string, options json.RawMessage) error {
bz, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
var doc GenesisDoc
err = json.Unmarshal(bz, &doc)
if err != nil {
return err
}
doc["app_state"] = options
out, err := json.MarshalIndent(doc, "", " ")
if err != nil {
return err
}
return ioutil.WriteFile(filename, out, 0600)
}
// GetGenesisJSON returns a new tendermint genesis with Basecoin app_options
// that grant a large amount of "mycoin" to a single address
// TODO: A better UX for generating genesis files
func GetGenesisJSON(pubkey, chainID, denom, addr string, options string) string {
return fmt.Sprintf(`{
"accounts": [{
"address": "%s",
"coins": [
{
"denom": "%s",
"amount": 9007199254740992
}
]
}],
"plugin_options": [
"coin/issuer", {"app": "sigs", "addr": "%s"}%s
]
}`, addr, denom, addr, options)
}

36
server/init_test.go Normal file
View File

@ -0,0 +1,36 @@
package server
import (
"io/ioutil"
"os"
"testing"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"github.com/tendermint/tmlibs/log"
"github.com/cosmos/cosmos-sdk/mock"
)
// setupViper creates a homedir to run inside,
// and returns a cleanup function to defer
func setupViper() func() {
rootDir, err := ioutil.TempDir("", "mock-sdk-cmd")
if err != nil {
panic(err) // fuck it!
}
viper.Set("home", rootDir)
return func() {
os.RemoveAll(rootDir)
}
}
func TestInit(t *testing.T) {
defer setupViper()()
logger := log.NewNopLogger()
cmd := InitCmd(mock.GenInitOptions, logger)
err := cmd.RunE(nil, nil)
require.NoError(t, err)
}

31
server/reset.go Normal file
View File

@ -0,0 +1,31 @@
package server
import (
"github.com/spf13/cobra"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tmlibs/log"
)
// UnsafeResetAllCmd - extension of the tendermint command, resets initialization
func UnsafeResetAllCmd(logger log.Logger) *cobra.Command {
cmd := resetAll{logger}
return &cobra.Command{
Use: "unsafe_reset_all",
Short: "Reset all blockchain data",
RunE: cmd.run,
}
}
type resetAll struct {
logger log.Logger
}
func (r resetAll) run(cmd *cobra.Command, args []string) error {
cfg, err := tcmd.ParseConfig()
if err != nil {
return err
}
tcmd.ResetAll(cfg.DBDir(), cfg.PrivValidatorFile(), r.logger)
return nil
}

119
server/start.go Normal file
View File

@ -0,0 +1,119 @@
package server
import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/abci/server"
abci "github.com/tendermint/abci/types"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tendermint/node"
"github.com/tendermint/tendermint/proxy"
"github.com/tendermint/tendermint/types"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
)
const (
flagWithTendermint = "with-tendermint"
flagAddress = "address"
)
// appGenerator lets us lazily initialize app, using home dir
// and other flags (?) to start
type appGenerator func(string, log.Logger) (abci.Application, error)
// StartCmd runs the service passed in, either
// stand-alone, or in-process with tendermint
func StartCmd(app appGenerator, logger log.Logger) *cobra.Command {
start := startCmd{
app: app,
logger: logger,
}
cmd := &cobra.Command{
Use: "start",
Short: "Run the full node",
RunE: start.run,
}
// basic flags for abci app
cmd.Flags().Bool(flagWithTendermint, true, "run abci app embedded in-process with tendermint")
cmd.Flags().String(flagAddress, "tcp://0.0.0.0:46658", "Listen address")
// AddNodeFlags adds support for all
// tendermint-specific command line options
tcmd.AddNodeFlags(cmd)
return cmd
}
type startCmd struct {
app appGenerator
logger log.Logger
}
func (s startCmd) run(cmd *cobra.Command, args []string) error {
if !viper.GetBool(flagWithTendermint) {
s.logger.Info("Starting ABCI without Tendermint")
return s.startStandAlone()
}
s.logger.Info("Starting ABCI with Tendermint")
return s.startInProcess()
}
func (s startCmd) startStandAlone() error {
// Generate the app in the proper dir
addr := viper.GetString(flagAddress)
home := viper.GetString("home")
app, err := s.app(home, s.logger)
if err != nil {
return err
}
svr, err := server.NewServer(addr, "socket", app)
if err != nil {
return errors.Errorf("Error creating listener: %v\n", err)
}
svr.SetLogger(s.logger.With("module", "abci-server"))
svr.Start()
// Wait forever
cmn.TrapSignal(func() {
// Cleanup
svr.Stop()
})
return nil
}
func (s startCmd) startInProcess() error {
cfg, err := tcmd.ParseConfig()
if err != nil {
return err
}
home := cfg.RootDir
app, err := s.app(home, s.logger)
if err != nil {
return err
}
// Create & start tendermint node
n, err := node.NewNode(cfg,
types.LoadOrGenPrivValidatorFS(cfg.PrivValidatorFile()),
proxy.NewLocalClientCreator(app),
node.DefaultGenesisDocProviderFunc(cfg),
node.DefaultDBProvider,
s.logger.With("module", "node"))
if err != nil {
return err
}
err = n.Start()
if err != nil {
return err
}
// Trap signal, run forever.
n.RunForever()
return nil
}

72
server/start_test.go Normal file
View File

@ -0,0 +1,72 @@
package server
import (
"fmt"
"os"
"testing"
"time"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/mock"
"github.com/tendermint/tmlibs/log"
)
func TestStartStandAlone(t *testing.T) {
defer setupViper()()
logger := log.NewNopLogger()
initCmd := InitCmd(mock.GenInitOptions, logger)
err := initCmd.RunE(nil, nil)
require.NoError(t, err)
// set up app and start up
viper.Set(flagWithTendermint, false)
viper.Set(flagAddress, "localhost:11122")
startCmd := StartCmd(mock.NewApp, logger)
timeout := time.Duration(3) * time.Second
err = runOrTimeout(startCmd, timeout)
require.NoError(t, err)
}
func TestStartWithTendermint(t *testing.T) {
defer setupViper()()
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).
With("module", "mock-cmd")
// logger := log.NewNopLogger()
initCmd := InitCmd(mock.GenInitOptions, logger)
err := initCmd.RunE(nil, nil)
require.NoError(t, err)
// set up app and start up
viper.Set(flagWithTendermint, true)
startCmd := StartCmd(mock.NewApp, logger)
timeout := time.Duration(3) * time.Second
err = runOrTimeout(startCmd, timeout)
require.NoError(t, err)
}
func runOrTimeout(cmd *cobra.Command, timeout time.Duration) error {
done := make(chan error)
go func(out chan<- error) {
// this should NOT exit
err := cmd.RunE(nil, nil)
if err != nil {
out <- err
}
out <- fmt.Errorf("start died for unknown reasons")
}(done)
timer := time.NewTimer(timeout)
select {
case err := <-done:
return err
case <-timer.C:
return nil
}
}

69
tests/check_basecli.sh Executable file
View File

@ -0,0 +1,69 @@
#!/bin/sh
# Note: Bucky, I know you want to kill bash tests.
# Please show me how to do an alternative to this.
# I would rather get code running before I leave than
# fight trying to invent some new test harness that
# no one else will understand.
#
# Thus, I leave this as an exercise to the reader to
# port into a non-bash version. And I don't do it proper...
# just automate my manual tests
# WARNING!!!
rm -rf ~/.basecoind ~/.basecli
cd $GOPATH/src/github.com/cosmos/cosmos-sdk
# make get_vendor_deps
make build
# init stuff
SEED=`./build/basecoind init | tail -1`
PASS='some-silly-123'
(echo $PASS; echo $SEED) | ./build/basecli keys add demo --recover
ADDR=`./build/basecli keys show demo | cut -f3`
echo "Recovered seed for demo:" $ADDR
# start up server
./build/basecoind start > ~/.basecoind/basecoind.log 2>&1 &
sleep 5
PID_SERVER=$!
# query original state
TO='ABCAFE00DEADBEEF00CAFE00DEADBEEF00CAFE00'
echo; echo "My account:" $ADDR
./build/basecli account $ADDR
echo; echo "Empty account:" $TO
./build/basecli account $TO
# send some money
TX=`echo $PASS | ./build/basecli send --to=$TO --amount=1000mycoin --name=demo --seq=0`
echo; echo "SendTx"; echo $TX
HASH=`echo $TX | cut -d' ' -f6`
echo "tx hash:" $HASH
# let some blocks come up....
./build/basecli status | jq .latest_block_height
sleep 2
./build/basecli status | jq .latest_block_height
# balances change
echo; echo "My account went down"
./build/basecli account $ADDR
echo; echo "Empty account got some cash"
./build/basecli account $TO
# query original tx
echo; echo "View tx"
./build/basecli tx $HASH
# wait a bit then dump out some blockchain state
sleep 10
./build/basecli status --trace
./build/basecli block --trace
./build/basecli validatorset --trace
# shutdown, but add a sleep if you want to manually run some cli scripts
# against this server before it goes away
# sleep 120
kill $PID_SERVER

View File

@ -40,10 +40,10 @@ install: get_vendor_deps
@echo "$(ansi_grn)Installing tools$(ansi_end)"
@echo "$(ansi_yel)Install go-vendorinstall$(ansi_end)"
go build -o bin/go-vendorinstall go-vendorinstall/*.go
@echo "$(ansi_yel)Install gometalinter.v2$(ansi_end)"
GOBIN=$(CURDIR)/bin ./bin/go-vendorinstall gopkg.in/alecthomas/gometalinter.v2
GOBIN=$(CURDIR)/bin ./bin/go-vendorinstall github.com/alecthomas/gometalinter
@echo "$(ansi_yel)Install shelldown$(ansi_end)"
GOBIN=$(CURDIR)/bin ./bin/go-vendorinstall github.com/rigelrozanski/shelldown/cmd/shelldown

14
tools/glide.lock generated
View File

@ -1,8 +1,8 @@
hash: a163b1c4806024cfc9062db75a0abed285ec40461243e59af0e147db2c4bf0ce
updated: 2018-01-15T19:02:49.834182027-08:00
hash: 934ad5be72c9c240e8555eb6e1b2319840266c04c0fa9e024008cf841c0cee65
updated: 2018-02-23T19:33:08.596187+01:00
imports:
- name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- name: github.com/alecthomas/gometalinter
version: 46cc1ea3778b247666c2949669a3333c532fa9c6
- name: github.com/rigelrozanski/common
version: f691f115798593d783b9999b1263c2f4ffecc439
- name: github.com/rigelrozanski/shelldown
@ -12,7 +12,7 @@ imports:
- name: github.com/spf13/cobra
version: 7b2c5ac9fc04fc5efafb60700713d4fa609b777b
- name: github.com/spf13/pflag
version: 97afa5e7ca8a08a383cb259e06636b5e2cc7897f
- name: gopkg.in/alecthomas/gometalinter.v2
version: 88d47c66988c5a5cb3945925da47c883800a94df
version: e57e3eeb33f795204c1ca35f56c44f83227c6e66
- name: github.com/spf13/viper
version: 25b30aa063fc18e48662b86996252eabdcf2f0c7
testImports: []

View File

@ -1,6 +1,13 @@
package: github.com/cosmos/cosmos-sdk/tools
import:
- package: github.com/alecthomas/gometalinter
version: ^2.0.5
- package: github.com/rigelrozanski/shelldown
subpackages:
- cmd/shelldown
- package: gopkg.in/alecthomas/gometalinter.v2
- package: github.com/spf13/pflag
version: v1.0.0
- package: github.com/spf13/cobra
version: v0.0.1
- package: github.com/spf13/viper
version: ^1.0.0

View File

@ -30,3 +30,6 @@ type AccountMapper interface {
GetAccount(ctx Context, addr crypto.Address) Account
SetAccount(ctx Context, acc Account)
}
// Application function variable used to unmarshal account
type ParseAccount func([]byte) (Account, error)

View File

@ -4,7 +4,7 @@ import crypto "github.com/tendermint/go-crypto"
// Standard Signature
type StdSignature struct {
crypto.PubKey // optional
crypto.Signature
Sequence int64
crypto.PubKey `json:"pub_key"` // optional
crypto.Signature `json:"signature"`
Sequence int64 `json:"sequence"`
}

View File

@ -52,8 +52,8 @@ var _ Tx = (*StdTx)(nil)
// StdTx is a standard way to wrap a Msg with Signatures.
// NOTE: the first signature is the FeePayer (Signatures must not be nil).
type StdTx struct {
Msg
Signatures []StdSignature
Msg `json:"msg"`
Signatures []StdSignature `json:"signatures"`
}
func NewStdTx(msg Msg, sigs []StdSignature) StdTx {

View File

@ -1,11 +1,9 @@
package main
package version
import (
"fmt"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/version"
)
var (
@ -18,9 +16,9 @@ var (
)
func doVersionCmd(cmd *cobra.Command, args []string) {
v := version.Version
if version.GitCommit != "" {
v = v + " " + version.GitCommit
v := Version
if GitCommit != "" {
v = v + " " + GitCommit
}
fmt.Println(v)
}

View File

@ -0,0 +1,82 @@
package commands
import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/pkg/errors"
"github.com/spf13/cobra"
crypto "github.com/tendermint/go-crypto"
wire "github.com/tendermint/go-wire"
"github.com/cosmos/cosmos-sdk/client"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
)
// GetAccountCmd for the auth.BaseAccount type
func GetAccountCmdDefault(storeName string, cdc *wire.Codec) *cobra.Command {
return GetAccountCmd(storeName, cdc, getParseAccount(cdc))
}
func getParseAccount(cdc *wire.Codec) sdk.ParseAccount {
return func(accBytes []byte) (sdk.Account, error) {
acct := new(auth.BaseAccount)
err := cdc.UnmarshalBinary(accBytes, acct)
return acct, err
}
}
// GetAccountCmd returns a query account that will display the
// state of the account at a given address
func GetAccountCmd(storeName string, cdc *wire.Codec, parser sdk.ParseAccount) *cobra.Command {
cmdr := commander{
storeName,
cdc,
parser,
}
return &cobra.Command{
Use: "account <address>",
Short: "Query account balance",
RunE: cmdr.getAccountCmd,
}
}
type commander struct {
storeName string
cdc *wire.Codec
parser sdk.ParseAccount
}
func (c commander) getAccountCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide an account name")
}
// find the key to look up the account
addr := args[0]
bz, err := hex.DecodeString(addr)
if err != nil {
return err
}
key := crypto.Address(bz)
res, err := client.Query(key, c.storeName)
// parse out the value
account, err := c.parser(res)
if err != nil {
return err
}
// print out whole account
output, err := json.MarshalIndent(account, "", " ")
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}

129
x/bank/commands/sendtx.go Normal file
View File

@ -0,0 +1,129 @@
package commands
import (
"encoding/hex"
"fmt"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
crypto "github.com/tendermint/go-crypto"
wire "github.com/tendermint/go-wire"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/keys"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank"
)
const (
flagTo = "to"
flagAmount = "amount"
flagFee = "fee"
flagSequence = "seq"
)
// SendTxCommand will create a send tx and sign it with the given key
func SendTxCmd(cdc *wire.Codec) *cobra.Command {
cmdr := commander{cdc}
cmd := &cobra.Command{
Use: "send",
Short: "Create and sign a send tx",
RunE: cmdr.sendTxCmd,
}
cmd.Flags().String(flagTo, "", "Address to send coins")
cmd.Flags().String(flagAmount, "", "Amount of coins to send")
cmd.Flags().String(flagFee, "", "Fee to pay along with transaction")
cmd.Flags().Int64(flagSequence, 0, "Sequence number to sign the tx")
return cmd
}
type commander struct {
cdc *wire.Codec
}
func (c commander) sendTxCmd(cmd *cobra.Command, args []string) error {
txBytes, err := c.buildTx()
if err != nil {
return err
}
res, err := client.BroadcastTx(txBytes)
if err != nil {
return err
}
fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String())
return nil
}
func (c commander) buildTx() ([]byte, error) {
keybase, err := keys.GetKeyBase()
if err != nil {
return nil, err
}
name := viper.GetString(client.FlagName)
info, err := keybase.Get(name)
if err != nil {
return nil, errors.Errorf("No key for: %s", name)
}
from := info.PubKey.Address()
msg, err := buildMsg(from)
if err != nil {
return nil, err
}
// sign and build
bz := msg.GetSignBytes()
buf := client.BufferStdin()
prompt := fmt.Sprintf("Password to sign with '%s':", name)
passphrase, err := client.GetPassword(prompt, buf)
if err != nil {
return nil, err
}
sig, pubkey, err := keybase.Sign(name, passphrase, bz)
if err != nil {
return nil, err
}
sigs := []sdk.StdSignature{{
PubKey: pubkey,
Signature: sig,
Sequence: viper.GetInt64(flagSequence),
}}
// marshal bytes
tx := sdk.NewStdTx(msg, sigs)
txBytes, err := c.cdc.MarshalBinary(tx)
if err != nil {
return nil, err
}
return txBytes, nil
}
func buildMsg(from crypto.Address) (sdk.Msg, error) {
// parse coins
amount := viper.GetString(flagAmount)
coins, err := sdk.ParseCoins(amount)
if err != nil {
return nil, err
}
// parse destination address
dest := viper.GetString(flagTo)
bz, err := hex.DecodeString(dest)
if err != nil {
return nil, err
}
to := crypto.Address(bz)
input := bank.NewInput(from, coins)
output := bank.NewOutput(to, coins)
msg := bank.NewSendMsg([]bank.Input{input}, []bank.Output{output})
return msg, nil
}

View File

@ -39,6 +39,7 @@ func handleSendMsg(ctx sdk.Context, ck CoinKeeper, msg SendMsg) sdk.Result {
}
}
// TODO: add some tags so we can search it!
return sdk.Result{} // TODO
}