Merge pull request #497 from cosmos/feature/sdk2-gaia-cli
REVIEW: Full-fledged gaia cli
This commit is contained in:
commit
5494a4ed83
3
Makefile
3
Makefile
@ -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
|
||||
|
||||
@ -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
38
client/flags.go
Normal 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
71
client/helpers.go
Normal 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
92
client/input.go
Normal 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
23
client/keys.go
Normal 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
119
client/keys/README.md
Normal 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
122
client/keys/add.go
Normal 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
45
client/keys/delete.go
Normal 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
25
client/keys/list.go
Normal 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
29
client/keys/root.go
Normal 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
32
client/keys/show.go
Normal 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
50
client/keys/update.go
Normal 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
68
client/keys/utils.go
Normal 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
17
client/keys/wire.go
Normal 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
36
client/lcd/root.go
Normal 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
66
client/rpc/block.go
Normal 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
44
client/rpc/root.go
Normal 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
40
client/rpc/status.go
Normal 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
57
client/rpc/validators.go
Normal 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
20
client/tx/root.go
Normal 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
80
client/tx/search.go
Normal 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
99
client/tx/tx.go
Normal 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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
71
examples/basecoin/cmd/basecli/main.go
Normal file
71
examples/basecoin/cmd/basecli/main.go
Normal 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()
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
@ -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
76
glide.lock
generated
@ -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
|
||||
|
||||
12
glide.yaml
12
glide.yaml
@ -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
122
mock/app.go
Normal 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
75
mock/app_test.go
Normal 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
27
mock/helpers.go
Normal 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
89
mock/tx.go
Normal 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
182
server/init.go
Normal 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
36
server/init_test.go
Normal 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
31
server/reset.go
Normal 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
119
server/start.go
Normal 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
72
server/start_test.go
Normal 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
69
tests/check_basecli.sh
Executable 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
|
||||
|
||||
@ -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
14
tools/glide.lock
generated
@ -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: []
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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"`
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
82
x/auth/commands/account.go
Normal file
82
x/auth/commands/account.go
Normal 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
129
x/bank/commands/sendtx.go
Normal 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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user