move things to _attic
This commit is contained in:
@@ -1,33 +0,0 @@
|
||||
package auto
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// AutoCompleteCmd - command to generate bash autocompletions
|
||||
var AutoCompleteCmd = &cobra.Command{
|
||||
Use: "complete",
|
||||
Short: "generate bash autocompletions",
|
||||
RunE: doAutoComplete,
|
||||
}
|
||||
|
||||
// nolint - flags
|
||||
const (
|
||||
FlagOutput = "file"
|
||||
)
|
||||
|
||||
func init() {
|
||||
AutoCompleteCmd.Flags().String(FlagOutput, "", "file to output bash autocompletion")
|
||||
AutoCompleteCmd.MarkFlagFilename(FlagOutput)
|
||||
}
|
||||
|
||||
func doAutoComplete(cmd *cobra.Command, args []string) error {
|
||||
output := viper.GetString(FlagOutput)
|
||||
if output == "" {
|
||||
return cmd.Root().GenBashCompletion(os.Stdout)
|
||||
}
|
||||
return cmd.Root().GenBashCompletionFile(output)
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package commits
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/light-client/certifiers/files"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
var exportCmd = &cobra.Command{
|
||||
Use: "export <file>",
|
||||
Short: "Export selected commits to given file",
|
||||
Long: `Exports the most recent commit to a binary file.
|
||||
If desired, you can select by an older height or validator hash.
|
||||
`,
|
||||
RunE: commands.RequireInit(exportCommit),
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
exportCmd.Flags().Int(heightFlag, 0, "Show the commit with closest height to this")
|
||||
exportCmd.Flags().String(hashFlag, "", "Show the commit matching the validator hash")
|
||||
RootCmd.AddCommand(exportCmd)
|
||||
}
|
||||
|
||||
func exportCommit(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 || len(args[0]) == 0 {
|
||||
return errors.New("You must provide a filepath to output")
|
||||
}
|
||||
path := args[0]
|
||||
|
||||
// load the seed as specified
|
||||
trust, _ := commands.GetProviders()
|
||||
h := viper.GetInt(heightFlag)
|
||||
hash := viper.GetString(hashFlag)
|
||||
fc, err := loadCommit(trust, h, hash, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// now get the output file and write it
|
||||
return files.SaveFullCommitJSON(fc, path)
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package commits
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/light-client/certifiers/files"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
const (
|
||||
dryFlag = "dry-run"
|
||||
)
|
||||
|
||||
var importCmd = &cobra.Command{
|
||||
Use: "import <file>",
|
||||
Short: "Imports a new commit from the given file",
|
||||
Long: `Validate this file and update to the given commit if secure.`,
|
||||
RunE: commands.RequireInit(importCommit),
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
importCmd.Flags().Bool(dryFlag, false, "Test the import fully, but do not import")
|
||||
RootCmd.AddCommand(importCmd)
|
||||
}
|
||||
|
||||
func importCommit(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 || len(args[0]) == 0 {
|
||||
return errors.New("You must provide an input file")
|
||||
}
|
||||
|
||||
// prepare the certifier
|
||||
cert, err := commands.GetCertifier()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// parse the input file
|
||||
path := args[0]
|
||||
fc, err := files.LoadFullCommitJSON(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// just do simple checks in --dry-run
|
||||
if viper.GetBool(dryFlag) {
|
||||
fmt.Printf("Testing commit %d/%X\n", fc.Height(), fc.ValidatorsHash())
|
||||
err = fc.ValidateBasic(cert.ChainID())
|
||||
} else {
|
||||
fmt.Printf("Importing commit %d/%X\n", fc.Height(), fc.ValidatorsHash())
|
||||
err = cert.Update(fc)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package commits
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "commits",
|
||||
Short: "Verify commits from your local store",
|
||||
Long: `Commits allows you to inspect and update the validator set for the chain.
|
||||
|
||||
Since all security in a PoS system is based on having the correct validator
|
||||
set, it is important to inspect the commits to maintain the security, which
|
||||
is used to verify all header and merkle proofs.
|
||||
`,
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package commits
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/light-client/certifiers"
|
||||
"github.com/tendermint/light-client/certifiers/files"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
const (
|
||||
heightFlag = "height"
|
||||
hashFlag = "hash"
|
||||
fileFlag = "file"
|
||||
)
|
||||
|
||||
var showCmd = &cobra.Command{
|
||||
Use: "show",
|
||||
Short: "Show the details of one selected commit",
|
||||
Long: `Shows the most recent downloaded key by default.
|
||||
If desired, you can select by height, validator hash, or a file.
|
||||
`,
|
||||
RunE: commands.RequireInit(showCommit),
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
showCmd.Flags().Int(heightFlag, 0, "Show the commit with closest height to this")
|
||||
showCmd.Flags().String(hashFlag, "", "Show the commit matching the validator hash")
|
||||
showCmd.Flags().String(fileFlag, "", "Show the commit stored in the given file")
|
||||
RootCmd.AddCommand(showCmd)
|
||||
}
|
||||
|
||||
func loadCommit(p certifiers.Provider, h int, hash, file string) (fc certifiers.FullCommit, err error) {
|
||||
// load the commit from the proper place
|
||||
if h != 0 {
|
||||
fc, err = p.GetByHeight(h)
|
||||
} else if hash != "" {
|
||||
var vhash []byte
|
||||
vhash, err = hex.DecodeString(hash)
|
||||
if err == nil {
|
||||
fc, err = p.GetByHash(vhash)
|
||||
}
|
||||
} else if file != "" {
|
||||
fc, err = files.LoadFullCommitJSON(file)
|
||||
} else {
|
||||
// default is latest commit
|
||||
fc, err = p.LatestCommit()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func showCommit(cmd *cobra.Command, args []string) error {
|
||||
trust, _ := commands.GetProviders()
|
||||
|
||||
h := viper.GetInt(heightFlag)
|
||||
hash := viper.GetString(hashFlag)
|
||||
file := viper.GetString(fileFlag)
|
||||
fc, err := loadCommit(trust, h, hash, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// now render it!
|
||||
data, err := json.MarshalIndent(fc, "", " ")
|
||||
fmt.Println(string(data))
|
||||
return err
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package commits
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/light-client/certifiers"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
var updateCmd = &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Update commit to current height if possible",
|
||||
RunE: commands.RequireInit(updateCommit),
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
updateCmd.Flags().Int(heightFlag, 0, "Update to this height, not latest")
|
||||
RootCmd.AddCommand(updateCmd)
|
||||
}
|
||||
|
||||
func updateCommit(cmd *cobra.Command, args []string) error {
|
||||
cert, err := commands.GetCertifier()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h := viper.GetInt(heightFlag)
|
||||
var fc certifiers.FullCommit
|
||||
if h <= 0 {
|
||||
// get the lastest from our source
|
||||
fc, err = cert.Source.LatestCommit()
|
||||
} else {
|
||||
fc, err = cert.Source.GetByHeight(h)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// let the certifier do it's magic to update....
|
||||
fmt.Printf("Trying to update to height: %d...\n", fc.Height())
|
||||
err = cert.Update(fc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Success!")
|
||||
return nil
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
/*
|
||||
Package commands contains any general setup/helpers valid for all subcommands
|
||||
*/
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/light-client/certifiers"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/modules/auth"
|
||||
)
|
||||
|
||||
var (
|
||||
trustedProv certifiers.Provider
|
||||
sourceProv certifiers.Provider
|
||||
)
|
||||
|
||||
const (
|
||||
ChainFlag = "chain-id"
|
||||
NodeFlag = "node"
|
||||
)
|
||||
|
||||
// AddBasicFlags adds --node and --chain-id, which we need for everything
|
||||
func AddBasicFlags(cmd *cobra.Command) {
|
||||
cmd.PersistentFlags().String(ChainFlag, "", "Chain ID of tendermint node")
|
||||
cmd.PersistentFlags().String(NodeFlag, "", "<host>:<port> to tendermint rpc interface for this chain")
|
||||
}
|
||||
|
||||
// GetChainID reads ChainID from the flags
|
||||
func GetChainID() string {
|
||||
return viper.GetString(ChainFlag)
|
||||
}
|
||||
|
||||
// GetNode prepares a simple rpc.Client from the flags
|
||||
func GetNode() rpcclient.Client {
|
||||
return client.GetNode(viper.GetString(NodeFlag))
|
||||
}
|
||||
|
||||
// GetSourceProvider returns a provider pointing to an rpc handler
|
||||
func GetSourceProvider() certifiers.Provider {
|
||||
if sourceProv == nil {
|
||||
node := viper.GetString(NodeFlag)
|
||||
sourceProv = client.GetRPCProvider(node)
|
||||
}
|
||||
return sourceProv
|
||||
}
|
||||
|
||||
// GetTrustedProvider returns a reference to a local store with cache
|
||||
func GetTrustedProvider() certifiers.Provider {
|
||||
if trustedProv == nil {
|
||||
rootDir := viper.GetString(cli.HomeFlag)
|
||||
trustedProv = client.GetLocalProvider(rootDir)
|
||||
}
|
||||
return trustedProv
|
||||
}
|
||||
|
||||
// GetProviders creates a trusted (local) seed provider and a remote
|
||||
// provider based on configuration.
|
||||
func GetProviders() (trusted certifiers.Provider, source certifiers.Provider) {
|
||||
return GetTrustedProvider(), GetSourceProvider()
|
||||
}
|
||||
|
||||
// GetCertifier constructs a dynamic certifier from the config info
|
||||
func GetCertifier() (*certifiers.Inquiring, error) {
|
||||
// load up the latest store....
|
||||
trust := GetTrustedProvider()
|
||||
source := GetSourceProvider()
|
||||
chainID := GetChainID()
|
||||
return client.GetCertifier(chainID, trust, source)
|
||||
}
|
||||
|
||||
// ParseActor parses an address of form:
|
||||
// [<chain>:][<app>:]<hex address>
|
||||
// into a sdk.Actor.
|
||||
// If app is not specified or "", then assume auth.NameSigs
|
||||
func ParseActor(input string) (res sdk.Actor, err error) {
|
||||
chain, app := "", auth.NameSigs
|
||||
input = strings.TrimSpace(input)
|
||||
spl := strings.SplitN(input, ":", 3)
|
||||
|
||||
if len(spl) == 3 {
|
||||
chain = spl[0]
|
||||
spl = spl[1:]
|
||||
}
|
||||
if len(spl) == 2 {
|
||||
if spl[0] != "" {
|
||||
app = spl[0]
|
||||
}
|
||||
spl = spl[1:]
|
||||
}
|
||||
|
||||
addr, err := hex.DecodeString(cmn.StripHex(spl[0]))
|
||||
if err != nil {
|
||||
return res, errors.Errorf("Address is invalid hex: %v\n", err)
|
||||
}
|
||||
res = sdk.Actor{
|
||||
ChainID: chain,
|
||||
App: app,
|
||||
Address: addr,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ParseActors takes a comma-separated list of actors and parses them into
|
||||
// a slice
|
||||
func ParseActors(key string) (signers []sdk.Actor, err error) {
|
||||
var act sdk.Actor
|
||||
for _, k := range strings.Split(key, ",") {
|
||||
act, err = ParseActor(k)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
signers = append(signers, act)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetOneArg makes sure there is exactly one positional argument
|
||||
func GetOneArg(args []string, argname string) (string, error) {
|
||||
if len(args) == 0 {
|
||||
return "", errors.Errorf("Missing required argument [%s]", argname)
|
||||
}
|
||||
if len(args) > 1 {
|
||||
return "", errors.Errorf("Only accepts one argument [%s]", argname)
|
||||
}
|
||||
return args[0], nil
|
||||
}
|
||||
|
||||
// ParseHexFlag takes a flag name and parses the viper contents as hex
|
||||
func ParseHexFlag(flag string) ([]byte, error) {
|
||||
arg := viper.GetString(flag)
|
||||
if arg == "" {
|
||||
return nil, errors.Errorf("No such flag: %s", flag)
|
||||
}
|
||||
value, err := hex.DecodeString(cmn.StripHex(arg))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("Cannot parse %s", flag))
|
||||
}
|
||||
return value, nil
|
||||
|
||||
}
|
||||
@@ -1,352 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/light-client/certifiers"
|
||||
"github.com/tendermint/light-client/certifiers/files"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
var (
|
||||
dirPerm = os.FileMode(0700)
|
||||
)
|
||||
|
||||
//nolint
|
||||
const (
|
||||
CommitFlag = "commit"
|
||||
HashFlag = "valhash"
|
||||
GenesisFlag = "genesis"
|
||||
FlagTrustNode = "trust-node"
|
||||
|
||||
ConfigFile = "config.toml"
|
||||
)
|
||||
|
||||
// InitCmd will initialize the basecli store
|
||||
var InitCmd = &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Initialize the light client for a new chain",
|
||||
RunE: runInit,
|
||||
}
|
||||
|
||||
var ResetCmd = &cobra.Command{
|
||||
Use: "reset_all",
|
||||
Short: "DANGEROUS: Wipe out all client data, including keys",
|
||||
RunE: runResetAll,
|
||||
}
|
||||
|
||||
func init() {
|
||||
InitCmd.Flags().Bool("force-reset", false, "Wipe clean an existing client store, except for keys")
|
||||
InitCmd.Flags().String(CommitFlag, "", "Commit file to import (optional)")
|
||||
InitCmd.Flags().String(HashFlag, "", "Trusted validator hash (must match to accept)")
|
||||
InitCmd.Flags().String(GenesisFlag, "", "Genesis file with chainid and validators (optional)")
|
||||
}
|
||||
|
||||
func runInit(cmd *cobra.Command, args []string) error {
|
||||
root := viper.GetString(cli.HomeFlag)
|
||||
if viper.GetBool("force-reset") {
|
||||
resetRoot(root, true)
|
||||
}
|
||||
|
||||
// make sure we don't have an existing client initialized
|
||||
inited, err := WasInited(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if inited {
|
||||
return errors.Errorf("%s already is initialized, --force-reset if you really want to wipe it out", root)
|
||||
}
|
||||
|
||||
// clean up dir if init fails
|
||||
err = doInit(cmd, root)
|
||||
if err != nil {
|
||||
resetRoot(root, true)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// doInit actually creates all the files, on error, we should revert it all
|
||||
func doInit(cmd *cobra.Command, root string) error {
|
||||
// read the genesis file if present, and populate --chain-id and --valhash
|
||||
err := checkGenesis(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = initConfigFile(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = initTrust()
|
||||
return err
|
||||
}
|
||||
|
||||
func runResetAll(cmd *cobra.Command, args []string) error {
|
||||
root := viper.GetString(cli.HomeFlag)
|
||||
resetRoot(root, false)
|
||||
return nil
|
||||
}
|
||||
|
||||
func resetRoot(root string, saveKeys bool) {
|
||||
tmp := filepath.Join(os.TempDir(), cmn.RandStr(16))
|
||||
keys := filepath.Join(root, "keys")
|
||||
if saveKeys {
|
||||
os.Rename(keys, tmp)
|
||||
}
|
||||
os.RemoveAll(root)
|
||||
if saveKeys {
|
||||
os.Mkdir(root, 0700)
|
||||
os.Rename(tmp, keys)
|
||||
}
|
||||
}
|
||||
|
||||
type Runable func(cmd *cobra.Command, args []string) error
|
||||
|
||||
// Any commands that require and init'ed basecoin directory
|
||||
// should wrap their RunE command with RequireInit
|
||||
// to make sure that the client is initialized.
|
||||
//
|
||||
// This cannot be called during PersistentPreRun,
|
||||
// as they are called from the most specific command first, and root last,
|
||||
// and the root command sets up viper, which is needed to find the home dir.
|
||||
func RequireInit(run Runable) Runable {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
// otherwise, run the wrappped command
|
||||
if viper.GetBool(FlagTrustNode) {
|
||||
return run(cmd, args)
|
||||
}
|
||||
|
||||
// first check if we were Init'ed and if not, return an error
|
||||
root := viper.GetString(cli.HomeFlag)
|
||||
init, err := WasInited(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !init {
|
||||
return errors.Errorf("You must run '%s init' first", cmd.Root().Name())
|
||||
}
|
||||
|
||||
// otherwise, run the wrappped command
|
||||
return run(cmd, args)
|
||||
}
|
||||
}
|
||||
|
||||
// WasInited returns true if a basecoin was previously initialized
|
||||
// in this directory. Important to ensure proper behavior.
|
||||
//
|
||||
// Returns error if we have filesystem errors
|
||||
func WasInited(root string) (bool, error) {
|
||||
// make sure there is a directory here in any case
|
||||
os.MkdirAll(root, dirPerm)
|
||||
|
||||
// check if there is a config.toml file
|
||||
cfgFile := filepath.Join(root, "config.toml")
|
||||
_, err := os.Stat(cfgFile)
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
// check if there are non-empty checkpoints and validators dirs
|
||||
dirs := []string{
|
||||
filepath.Join(root, files.CheckDir),
|
||||
filepath.Join(root, files.ValDir),
|
||||
}
|
||||
// if any of these dirs is empty, then we have no data
|
||||
for _, d := range dirs {
|
||||
empty, err := isEmpty(d)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if empty {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// looks like we have everything
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func checkGenesis(cmd *cobra.Command) error {
|
||||
genesis := viper.GetString(GenesisFlag)
|
||||
if genesis == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
doc, err := types.GenesisDocFromFile(genesis)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.Set(ChainFlag, doc.ChainID)
|
||||
hash := doc.ValidatorHash()
|
||||
hexHash := hex.EncodeToString(hash)
|
||||
flags.Set(HashFlag, hexHash)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isEmpty returns false if we can read files in this dir.
|
||||
// if it doesn't exist, read issues, etc... return true
|
||||
//
|
||||
// TODO: should we handle errors otherwise?
|
||||
func isEmpty(dir string) (bool, error) {
|
||||
// check if we can read the directory, missing is fine, other error is not
|
||||
d, err := os.Open(dir)
|
||||
if os.IsNotExist(err) {
|
||||
return true, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
defer d.Close()
|
||||
|
||||
// read to see if any (at least one) files here...
|
||||
files, err := d.Readdirnames(1)
|
||||
if err == io.EOF {
|
||||
return true, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
empty := len(files) == 0
|
||||
return empty, nil
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Chain string `toml:"chain-id,omitempty"`
|
||||
Node string `toml:"node,omitempty"`
|
||||
Output string `toml:"output,omitempty"`
|
||||
Encoding string `toml:"encoding,omitempty"`
|
||||
}
|
||||
|
||||
func setConfig(flags *pflag.FlagSet, f string, v *string) {
|
||||
if flags.Changed(f) {
|
||||
*v = viper.GetString(f)
|
||||
}
|
||||
}
|
||||
|
||||
func initConfigFile(cmd *cobra.Command) error {
|
||||
flags := cmd.Flags()
|
||||
var cfg Config
|
||||
|
||||
required := []string{ChainFlag, NodeFlag}
|
||||
for _, f := range required {
|
||||
if !flags.Changed(f) {
|
||||
return errors.Errorf(`"--%s" required`, f)
|
||||
}
|
||||
}
|
||||
|
||||
setConfig(flags, ChainFlag, &cfg.Chain)
|
||||
setConfig(flags, NodeFlag, &cfg.Node)
|
||||
setConfig(flags, cli.OutputFlag, &cfg.Output)
|
||||
setConfig(flags, cli.EncodingFlag, &cfg.Encoding)
|
||||
|
||||
out, err := os.Create(filepath.Join(viper.GetString(cli.HomeFlag), ConfigFile))
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// save the config file
|
||||
err = toml.NewEncoder(out).Encode(cfg)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initTrust() (err error) {
|
||||
// create a provider....
|
||||
trust, source := GetProviders()
|
||||
|
||||
// load a commit file, or get data from the provider
|
||||
var fc certifiers.FullCommit
|
||||
commitFile := viper.GetString(CommitFlag)
|
||||
if commitFile == "" {
|
||||
fmt.Println("Loading validator set from tendermint rpc...")
|
||||
fc, err = source.LatestCommit()
|
||||
} else {
|
||||
fmt.Printf("Loading validators from file %s\n", commitFile)
|
||||
fc, err = files.LoadFullCommit(commitFile)
|
||||
}
|
||||
// can't load the commit? abort!
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make sure it is a proper commit
|
||||
err = fc.ValidateBasic(viper.GetString(ChainFlag))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// validate hash interactively or not
|
||||
hash := viper.GetString(HashFlag)
|
||||
if hash != "" {
|
||||
var hashb []byte
|
||||
hashb, err = hex.DecodeString(hash)
|
||||
if err == nil && !bytes.Equal(hashb, fc.ValidatorsHash()) {
|
||||
err = errors.Errorf("Validator hash doesn't match expectation: %X", fc.ValidatorsHash())
|
||||
}
|
||||
} else {
|
||||
err = validateHash(fc)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if accepted, store commit as current state
|
||||
trust.StoreCommit(fc)
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateHash(fc certifiers.FullCommit) error {
|
||||
// ask the user to verify the validator hash
|
||||
fmt.Println("\nImportant: if this is incorrect, all interaction with the chain will be insecure!")
|
||||
fmt.Printf(" Given validator hash valid: %X\n", fc.ValidatorsHash())
|
||||
fmt.Println("Is this valid (y/n)?")
|
||||
valid := askForConfirmation()
|
||||
if !valid {
|
||||
return errors.New("Invalid validator hash, try init with proper commit later")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func askForConfirmation() bool {
|
||||
var resp string
|
||||
_, err := fmt.Scanln(&resp)
|
||||
if err != nil {
|
||||
fmt.Println("Please type yes or no and then press enter:")
|
||||
return askForConfirmation()
|
||||
}
|
||||
resp = strings.ToLower(resp)
|
||||
if resp == "y" || resp == "yes" {
|
||||
return true
|
||||
} else if resp == "n" || resp == "no" {
|
||||
return false
|
||||
} else {
|
||||
fmt.Println("Please type yes or no and then press enter:")
|
||||
return askForConfirmation()
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
# Keys CLI
|
||||
|
||||
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="
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright © 2017 Ethan Frey
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package keys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// deleteCmd represents the delete command
|
||||
var deleteCmd = &cobra.Command{
|
||||
Use: "delete [name]",
|
||||
Short: "DANGER: Delete a private key from your system",
|
||||
RunE: runDeleteCmd,
|
||||
}
|
||||
|
||||
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]
|
||||
|
||||
oldpass, err := getPassword("DANGER - enter password to permanently delete key:")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = GetKeyManager().Delete(name, oldpass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Password deleted forever (uh oh!)")
|
||||
return nil
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright © 2017 Ethan Frey
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package keys
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// getCmd represents the get command
|
||||
var getCmd = &cobra.Command{
|
||||
Use: "get [name]",
|
||||
Short: "Get details of one key",
|
||||
Long: `Return public details of one local key.`,
|
||||
RunE: runGetCmd,
|
||||
}
|
||||
|
||||
func runGetCmd(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]
|
||||
|
||||
info, err := GetKeyManager().Get(name)
|
||||
if err == nil {
|
||||
printInfo(info)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
// Copyright © 2017 Ethan Frey
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package keys
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
// listCmd represents the list command
|
||||
var listCmd = &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 {
|
||||
infos, err := GetKeyManager().List()
|
||||
if err == nil {
|
||||
printInfos(infos)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
// Copyright © 2017 Ethan Frey
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package keys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tendermint/go-crypto/keys"
|
||||
"github.com/tendermint/go-wire/data"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
flagType = "type"
|
||||
flagNoBackup = "no-backup"
|
||||
)
|
||||
|
||||
// newCmd represents the new command
|
||||
var newCmd = &cobra.Command{
|
||||
Use: "new [name]",
|
||||
Short: "Create a new public/private key pair",
|
||||
Long: `Add a public/private key pair to the key store.
|
||||
The password muts be entered in the terminal and not
|
||||
passed as a command line argument for security.`,
|
||||
RunE: runNewCmd,
|
||||
}
|
||||
|
||||
func init() {
|
||||
newCmd.Flags().StringP(flagType, "t", "ed25519", "Type of key (ed25519|secp256k1|ledger")
|
||||
newCmd.Flags().Bool(flagNoBackup, false, "Don't print out seed phrase (if others are watching the terminal)")
|
||||
}
|
||||
|
||||
func runNewCmd(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]
|
||||
algo := viper.GetString(flagType)
|
||||
|
||||
pass, err := getCheckPassword("Enter a passphrase:", "Repeat the passphrase:")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, seed, err := GetKeyManager().Create(name, pass, algo)
|
||||
if err == nil {
|
||||
printCreate(info, seed)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type NewOutput struct {
|
||||
Key keys.Info `json:"key"`
|
||||
Seed string `json:"seed"`
|
||||
}
|
||||
|
||||
func printCreate(info keys.Info, seed string) {
|
||||
switch viper.Get(cli.OutputFlag) {
|
||||
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.\n")
|
||||
fmt.Println(seed)
|
||||
}
|
||||
case "json":
|
||||
out := NewOutput{Key: info}
|
||||
if !viper.GetBool(flagNoBackup) {
|
||||
out.Seed = seed
|
||||
}
|
||||
json, err := data.ToJSON(out)
|
||||
if err != nil {
|
||||
panic(err) // really shouldn't happen...
|
||||
}
|
||||
fmt.Println(string(json))
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
// Copyright © 2017 Ethan Frey
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package keys
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// recoverCmd represents the recover command
|
||||
var recoverCmd = &cobra.Command{
|
||||
Use: "recover [name]",
|
||||
Short: "Recover a private key from a seed phrase",
|
||||
Long: `Recover a private key from a seed phrase.
|
||||
|
||||
I really hope you wrote this down when you created the new key.
|
||||
The seed is only displayed on creation, never again.
|
||||
|
||||
You can also use this to copy a key between multiple testnets,
|
||||
simply by "recovering" the key in the other nets you want to copy
|
||||
to. Of course, it has no coins on the other nets, just the same address.`,
|
||||
RunE: runRecoverCmd,
|
||||
}
|
||||
|
||||
func runRecoverCmd(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]
|
||||
|
||||
pass, err := getPassword("Enter the new passphrase:")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// not really a password... huh?
|
||||
seed, err := getSeed("Enter your recovery seed phrase:")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := GetKeyManager().Recover(name, pass, seed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printInfo(info)
|
||||
return nil
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// Copyright © 2017 Ethan Frey
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package keys
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
keys "github.com/tendermint/go-crypto/keys"
|
||||
)
|
||||
|
||||
var (
|
||||
manager keys.Manager
|
||||
)
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "keys",
|
||||
Short: "Key manager for tendermint clients",
|
||||
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.`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(getCmd)
|
||||
RootCmd.AddCommand(listCmd)
|
||||
RootCmd.AddCommand(newCmd)
|
||||
RootCmd.AddCommand(updateCmd)
|
||||
RootCmd.AddCommand(deleteCmd)
|
||||
RootCmd.AddCommand(recoverCmd)
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
// Copyright © 2017 Ethan Frey
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package keys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// updateCmd represents the update command
|
||||
var updateCmd = &cobra.Command{
|
||||
Use: "update [name]",
|
||||
Short: "Change the password for a private key",
|
||||
RunE: runUpdateCmd,
|
||||
}
|
||||
|
||||
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]
|
||||
|
||||
oldpass, err := getPassword("Enter the current passphrase:")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newpass, err := getCheckPassword("Enter the new passphrase:", "Repeat the new passphrase:")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = GetKeyManager().Update(name, oldpass, newpass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Password successfully updated!")
|
||||
return nil
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/bgentry/speakeasy"
|
||||
isatty "github.com/mattn/go-isatty"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
keys "github.com/tendermint/go-crypto/keys"
|
||||
data "github.com/tendermint/go-wire/data"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
)
|
||||
|
||||
const MinPassLength = 10
|
||||
|
||||
// GetKeyManager initializes a key manager based on the configuration
|
||||
func GetKeyManager() keys.Manager {
|
||||
if manager == nil {
|
||||
rootDir := viper.GetString(cli.HomeFlag)
|
||||
manager = client.GetKeyManager(rootDir)
|
||||
}
|
||||
return manager
|
||||
}
|
||||
|
||||
// if we read from non-tty, we just need to init the buffer reader once,
|
||||
// in case we try to read multiple passwords (eg. update)
|
||||
var buf *bufio.Reader
|
||||
|
||||
func inputIsTty() bool {
|
||||
return isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd())
|
||||
}
|
||||
|
||||
func stdinPassword() (string, error) {
|
||||
if buf == nil {
|
||||
buf = bufio.NewReader(os.Stdin)
|
||||
}
|
||||
pass, err := buf.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(pass), nil
|
||||
}
|
||||
|
||||
func getPassword(prompt string) (pass string, err error) {
|
||||
if inputIsTty() {
|
||||
pass, err = speakeasy.Ask(prompt)
|
||||
} else {
|
||||
pass, err = stdinPassword()
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(pass) < MinPassLength {
|
||||
return "", errors.Errorf("Password must be at least %d characters", MinPassLength)
|
||||
}
|
||||
return pass, nil
|
||||
}
|
||||
|
||||
func getSeed(prompt string) (seed string, err error) {
|
||||
if inputIsTty() {
|
||||
fmt.Println(prompt)
|
||||
}
|
||||
seed, err = stdinPassword()
|
||||
seed = strings.TrimSpace(seed)
|
||||
return
|
||||
}
|
||||
|
||||
func getCheckPassword(prompt, prompt2 string) (string, error) {
|
||||
// simple read on no-tty
|
||||
if !inputIsTty() {
|
||||
return getPassword(prompt)
|
||||
}
|
||||
|
||||
// TODO: own function???
|
||||
pass, err := getPassword(prompt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pass2, err := getPassword(prompt2)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if pass != pass2 {
|
||||
return "", errors.New("Passphrases don't match")
|
||||
}
|
||||
return pass, nil
|
||||
}
|
||||
|
||||
func printInfo(info keys.Info) {
|
||||
switch viper.Get(cli.OutputFlag) {
|
||||
case "text":
|
||||
addr, err := data.ToText(info.Address)
|
||||
if err != nil {
|
||||
panic(err) // really shouldn't happen...
|
||||
}
|
||||
sep := "\t\t"
|
||||
if len(info.Name) > 7 {
|
||||
sep = "\t"
|
||||
}
|
||||
fmt.Printf("%s%s%s\n", info.Name, sep, addr)
|
||||
case "json":
|
||||
json, err := data.ToJSON(info)
|
||||
if err != nil {
|
||||
panic(err) // really shouldn't happen...
|
||||
}
|
||||
fmt.Println(string(json))
|
||||
}
|
||||
}
|
||||
|
||||
func printInfos(infos keys.Infos) {
|
||||
switch viper.Get(cli.OutputFlag) {
|
||||
case "text":
|
||||
fmt.Println("All keys:")
|
||||
for _, i := range infos {
|
||||
printInfo(i)
|
||||
}
|
||||
case "json":
|
||||
json, err := data.ToJSON(infos)
|
||||
if err != nil {
|
||||
panic(err) // really shouldn't happen...
|
||||
}
|
||||
fmt.Println(string(json))
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "proxy",
|
||||
Short: "Run proxy server, verifying tendermint rpc",
|
||||
Long: `This node will run a secure proxy to a tendermint rpc server.
|
||||
|
||||
All calls that can be tracked back to a block header by a proof
|
||||
will be verified before passing them back to the caller. Other that
|
||||
that it will present the same interface as a full tendermint node,
|
||||
just with added trust and running locally.`,
|
||||
RunE: commands.RequireInit(runProxy),
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
const (
|
||||
bindFlag = "serve"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RootCmd.Flags().String(bindFlag, ":8888", "Serve the proxy on the given port")
|
||||
}
|
||||
|
||||
// TODO: pass in a proper logger
|
||||
var logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||
|
||||
func init() {
|
||||
logger = logger.With("module", "main")
|
||||
logger = log.NewFilter(logger, log.AllowInfo())
|
||||
}
|
||||
|
||||
func runProxy(cmd *cobra.Command, args []string) error {
|
||||
// First, connect a client
|
||||
node := commands.GetNode()
|
||||
bind := viper.GetString(bindFlag)
|
||||
cert, err := commands.GetCertifier()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sc := client.SecureClient(node, cert)
|
||||
|
||||
err = client.StartProxy(sc, bind, logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmn.TrapSignal(func() {
|
||||
// TODO: close up shop
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
wire "github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/go-wire/data"
|
||||
"github.com/tendermint/iavl"
|
||||
"github.com/tendermint/light-client/proofs"
|
||||
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
// GetParsed does most of the work of the query commands, but is quite
|
||||
// opinionated, so if you want more control about parsing, call Get
|
||||
// directly.
|
||||
//
|
||||
// It will try to get the proof for the given key. If it is successful,
|
||||
// it will return the height and also unserialize proof.Data into the data
|
||||
// argument (so pass in a pointer to the appropriate struct)
|
||||
func GetParsed(key []byte, data interface{}, height int, prove bool) (uint64, error) {
|
||||
bs, h, err := Get(key, height, prove)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = wire.ReadBinaryBytes(bs, data)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// Get queries the given key and returns the value stored there and the
|
||||
// height we checked at.
|
||||
//
|
||||
// If prove is true (and why shouldn't it be?),
|
||||
// the data is fully verified before returning. If prove is false,
|
||||
// we just repeat whatever any (potentially malicious) node gives us.
|
||||
// Only use that if you are running the full node yourself,
|
||||
// and it is localhost or you have a secure connection (not HTTP)
|
||||
func Get(key []byte, height int, prove bool) (data.Bytes, uint64, error) {
|
||||
if height < 0 {
|
||||
return nil, 0, fmt.Errorf("Height cannot be negative")
|
||||
}
|
||||
|
||||
if !prove {
|
||||
node := commands.GetNode()
|
||||
resp, err := node.ABCIQueryWithOptions("/key", key,
|
||||
rpcclient.ABCIQueryOptions{Trusted: true, Height: uint64(height)})
|
||||
return data.Bytes(resp.Value), resp.Height, err
|
||||
}
|
||||
val, h, _, err := GetWithProof(key, height)
|
||||
return val, h, err
|
||||
}
|
||||
|
||||
// GetWithProof returns the values stored under a given key at the named
|
||||
// height as in Get. Additionally, it will return a validated merkle
|
||||
// proof for the key-value pair if it exists, and all checks pass.
|
||||
func GetWithProof(key []byte, height int) (data.Bytes, uint64, iavl.KeyProof, error) {
|
||||
node := commands.GetNode()
|
||||
cert, err := commands.GetCertifier()
|
||||
if err != nil {
|
||||
return nil, 0, nil, err
|
||||
}
|
||||
return client.GetWithProof(key, height, node, cert)
|
||||
}
|
||||
|
||||
// ParseHexKey parses the key flag as hex and converts to bytes or returns error
|
||||
// argname is used to customize the error message
|
||||
func ParseHexKey(args []string, argname string) ([]byte, error) {
|
||||
if len(args) == 0 {
|
||||
return nil, errors.Errorf("Missing required argument [%s]", argname)
|
||||
}
|
||||
if len(args) > 1 {
|
||||
return nil, errors.Errorf("Only accepts one argument [%s]", argname)
|
||||
}
|
||||
rawkey := args[0]
|
||||
if rawkey == "" {
|
||||
return nil, errors.Errorf("[%s] argument must be non-empty ", argname)
|
||||
}
|
||||
// with tx, we always just parse key as hex and use to lookup
|
||||
return proofs.ParseHexKey(rawkey)
|
||||
}
|
||||
|
||||
// GetHeight reads the viper config for the query height
|
||||
func GetHeight() int {
|
||||
return viper.GetInt(FlagHeight)
|
||||
}
|
||||
|
||||
type proof struct {
|
||||
Height uint64 `json:"height"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// FoutputProof writes the output of wrapping height and info
|
||||
// in the form {"data": <the_data>, "height": <the_height>}
|
||||
// to the provider io.Writer
|
||||
func FoutputProof(w io.Writer, v interface{}, height uint64) error {
|
||||
wrap := &proof{height, v}
|
||||
blob, err := data.ToJSON(wrap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintf(w, "%s\n", blob)
|
||||
return err
|
||||
}
|
||||
|
||||
// OutputProof prints the proof to stdout
|
||||
// reuse this for printing proofs and we should enhance this for text/json,
|
||||
// better presentation of height
|
||||
func OutputProof(data interface{}, height uint64) error {
|
||||
return FoutputProof(os.Stdout, data, height)
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
// nolint
|
||||
const (
|
||||
FlagHeight = "height"
|
||||
)
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "query",
|
||||
Short: "Get and store merkle proofs for blockchain data",
|
||||
Long: `Proofs allows you to validate data and merkle proofs.
|
||||
|
||||
These proofs tie the data to a checkpoint, which is managed by "seeds".
|
||||
Here we can validate these proofs and import/export them to prove specific
|
||||
data to other peers as needed.
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.PersistentFlags().Int(FlagHeight, 0, "Height to query (skip to use latest block)")
|
||||
RootCmd.PersistentFlags().Bool(commands.FlagTrustNode, false,
|
||||
"DANGEROUS: blindly trust all results from the server")
|
||||
RootCmd.PersistentFlags().MarkHidden(commands.FlagTrustNode)
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
// KeyQueryCmd - CLI command to query a state by key with proof
|
||||
var KeyQueryCmd = &cobra.Command{
|
||||
Use: "key [key]",
|
||||
Short: "Handle proofs for state of abci app",
|
||||
Long: `This will look up a given key in the abci app, verify the proof,
|
||||
and output it as hex.
|
||||
|
||||
If you want json output, use an app-specific command that knows key and value structure.`,
|
||||
RunE: commands.RequireInit(keyQueryCmd),
|
||||
}
|
||||
|
||||
// Note: we cannot yse GetAndParseAppProof here, as we don't use go-wire to
|
||||
// parse the object, but rather return the raw bytes
|
||||
func keyQueryCmd(cmd *cobra.Command, args []string) error {
|
||||
// parse cli
|
||||
key, err := ParseHexKey(args, "key")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
prove := !viper.GetBool(commands.FlagTrustNode)
|
||||
|
||||
val, h, err := Get(key, GetHeight(), prove)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return OutputProof(val, h)
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
wire "github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
// TxQueryCmd - CLI command to query a transaction with proof
|
||||
var TxQueryCmd = &cobra.Command{
|
||||
Use: "tx [txhash]",
|
||||
Short: "Handle proofs of commited txs",
|
||||
Long: `Proofs allows you to validate abci state with merkle proofs.
|
||||
|
||||
These proofs tie the data to a checkpoint, which is managed by "seeds".
|
||||
Here we can validate these proofs and import/export them to prove specific
|
||||
data to other peers as needed.
|
||||
`,
|
||||
RunE: commands.RequireInit(txQueryCmd),
|
||||
}
|
||||
|
||||
func txQueryCmd(cmd *cobra.Command, args []string) error {
|
||||
// parse cli
|
||||
// TODO: when querying historical heights is allowed... pass it
|
||||
// height := GetHeight()
|
||||
bkey, err := ParseHexKey(args, "txhash")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get the proof -> this will be used by all prover commands
|
||||
node := commands.GetNode()
|
||||
prove := !viper.GetBool(commands.FlagTrustNode)
|
||||
res, err := node.Tx(bkey, prove)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// no checks if we don't get a proof
|
||||
if !prove {
|
||||
return showTx(res.Height, res.Tx)
|
||||
}
|
||||
|
||||
cert, err := commands.GetCertifier()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
check, err := client.GetCertifiedCommit(res.Height, node, cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = res.Proof.Validate(check.Header.DataHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// note that we return res.Proof.Data, not res.Tx,
|
||||
// as res.Proof.Validate only verifies res.Proof.Data
|
||||
return showTx(res.Height, res.Proof.Data)
|
||||
}
|
||||
|
||||
// showTx parses anything that was previously registered as interface{}
|
||||
func showTx(h int, tx types.Tx) error {
|
||||
var info interface{}
|
||||
err := wire.ReadBinaryBytes(tx, &info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return OutputProof(info, uint64(h))
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
|
||||
"github.com/tendermint/tendermint/rpc/client"
|
||||
)
|
||||
|
||||
var waitCmd = &cobra.Command{
|
||||
Use: "wait",
|
||||
Short: "Wait until a given height, or number of new blocks",
|
||||
RunE: commands.RequireInit(runWait),
|
||||
}
|
||||
|
||||
func init() {
|
||||
waitCmd.Flags().Int(FlagHeight, -1, "wait for block height")
|
||||
waitCmd.Flags().Int(FlagDelta, -1, "wait for given number of nodes")
|
||||
}
|
||||
|
||||
func runWait(cmd *cobra.Command, args []string) error {
|
||||
c := commands.GetNode()
|
||||
h := viper.GetInt(FlagHeight)
|
||||
if h == -1 {
|
||||
// read from delta
|
||||
d := viper.GetInt(FlagDelta)
|
||||
if d == -1 {
|
||||
return errors.New("Must set --height or --delta")
|
||||
}
|
||||
status, err := c.Status()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h = status.LatestBlockHeight + d
|
||||
}
|
||||
|
||||
// now wait
|
||||
err := client.WaitForHeight(c, h, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Chain now at height %d\n", h)
|
||||
return nil
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
var statusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Query the status of the node",
|
||||
RunE: commands.RequireInit(runStatus),
|
||||
}
|
||||
|
||||
func runStatus(cmd *cobra.Command, args []string) error {
|
||||
c := commands.GetNode()
|
||||
status, err := c.Status()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printResult(status)
|
||||
}
|
||||
|
||||
var infoCmd = &cobra.Command{
|
||||
Use: "info",
|
||||
Short: "Query info on the abci app",
|
||||
RunE: commands.RequireInit(runInfo),
|
||||
}
|
||||
|
||||
func runInfo(cmd *cobra.Command, args []string) error {
|
||||
c := commands.GetNode()
|
||||
info, err := c.ABCIInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printResult(info)
|
||||
}
|
||||
|
||||
var genesisCmd = &cobra.Command{
|
||||
Use: "genesis",
|
||||
Short: "Query the genesis of the node",
|
||||
RunE: commands.RequireInit(runGenesis),
|
||||
}
|
||||
|
||||
func runGenesis(cmd *cobra.Command, args []string) error {
|
||||
c := commands.GetNode()
|
||||
genesis, err := c.Genesis()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printResult(genesis)
|
||||
}
|
||||
|
||||
var validatorsCmd = &cobra.Command{
|
||||
Use: "validators",
|
||||
Short: "Query the validators of the node",
|
||||
RunE: commands.RequireInit(runValidators),
|
||||
}
|
||||
|
||||
func runValidators(cmd *cobra.Command, args []string) error {
|
||||
c := commands.GetNode()
|
||||
validators, err := c.Validators(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printResult(validators)
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/tendermint/go-wire/data"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
const (
|
||||
FlagDelta = "delta"
|
||||
FlagHeight = "height"
|
||||
FlagMax = "max"
|
||||
FlagMin = "min"
|
||||
)
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "rpc",
|
||||
Short: "Query the tendermint rpc, validating everything with a proof",
|
||||
}
|
||||
|
||||
// TODO: add support for subscribing to events????
|
||||
func init() {
|
||||
RootCmd.AddCommand(
|
||||
statusCmd,
|
||||
infoCmd,
|
||||
genesisCmd,
|
||||
validatorsCmd,
|
||||
blockCmd,
|
||||
commitCmd,
|
||||
headersCmd,
|
||||
waitCmd,
|
||||
)
|
||||
}
|
||||
|
||||
func getSecureNode() (rpcclient.Client, error) {
|
||||
// First, connect a client
|
||||
c := commands.GetNode()
|
||||
cert, err := commands.GetCertifier()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.SecureClient(c, cert), nil
|
||||
}
|
||||
|
||||
// printResult just writes the struct to the console, returns an error if it can't
|
||||
func printResult(res interface{}) error {
|
||||
// TODO: handle text mode
|
||||
// switch viper.Get(cli.OutputFlag) {
|
||||
// case "text":
|
||||
// case "json":
|
||||
json, err := data.ToJSON(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(json))
|
||||
return nil
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
)
|
||||
|
||||
func init() {
|
||||
blockCmd.Flags().Int(FlagHeight, -1, "block height")
|
||||
commitCmd.Flags().Int(FlagHeight, -1, "block height")
|
||||
headersCmd.Flags().Int(FlagMin, -1, "minimum block height")
|
||||
headersCmd.Flags().Int(FlagMax, -1, "maximum block height")
|
||||
}
|
||||
|
||||
var blockCmd = &cobra.Command{
|
||||
Use: "block",
|
||||
Short: "Get a validated block at a given height",
|
||||
RunE: commands.RequireInit(runBlock),
|
||||
}
|
||||
|
||||
func runBlock(cmd *cobra.Command, args []string) error {
|
||||
c, err := getSecureNode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h := viper.GetInt(FlagHeight)
|
||||
block, err := c.Block(&h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printResult(block)
|
||||
}
|
||||
|
||||
var commitCmd = &cobra.Command{
|
||||
Use: "commit",
|
||||
Short: "Get the header and commit signature at a given height",
|
||||
RunE: commands.RequireInit(runCommit),
|
||||
}
|
||||
|
||||
func runCommit(cmd *cobra.Command, args []string) error {
|
||||
c, err := getSecureNode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h := viper.GetInt(FlagHeight)
|
||||
commit, err := c.Commit(&h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printResult(commit)
|
||||
}
|
||||
|
||||
var headersCmd = &cobra.Command{
|
||||
Use: "headers",
|
||||
Short: "Get all headers in the given height range",
|
||||
RunE: commands.RequireInit(runHeaders),
|
||||
}
|
||||
|
||||
func runHeaders(cmd *cobra.Command, args []string) error {
|
||||
c, err := getSecureNode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
min := viper.GetInt(FlagMin)
|
||||
max := viper.GetInt(FlagMax)
|
||||
headers, err := c.BlockchainInfo(min, max)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printResult(headers)
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
package txs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/bgentry/speakeasy"
|
||||
isatty "github.com/mattn/go-isatty"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/go-crypto/keys"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/go-wire/data"
|
||||
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
keycmd "github.com/cosmos/cosmos-sdk/client/commands/keys"
|
||||
"github.com/cosmos/cosmos-sdk/modules/auth"
|
||||
)
|
||||
|
||||
// Validatable represents anything that can be Validated
|
||||
type Validatable interface {
|
||||
ValidateBasic() error
|
||||
}
|
||||
|
||||
// GetSigner returns the pub key that will sign the tx
|
||||
// returns empty key if no name provided
|
||||
func GetSigner() crypto.PubKey {
|
||||
name := viper.GetString(FlagName)
|
||||
manager := keycmd.GetKeyManager()
|
||||
info, _ := manager.Get(name) // error -> empty pubkey
|
||||
return info.PubKey
|
||||
}
|
||||
|
||||
// GetSignerAct returns the address of the signer of the tx
|
||||
// (as we still only support single sig)
|
||||
func GetSignerAct() (res sdk.Actor) {
|
||||
// this could be much cooler with multisig...
|
||||
signer := GetSigner()
|
||||
if !signer.Empty() {
|
||||
res = auth.SigPerm(signer.Address())
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// DoTx is a helper function for the lazy :)
|
||||
//
|
||||
// It uses only public functions and goes through the standard sequence of
|
||||
// wrapping the tx with middleware layers, signing it, either preparing it,
|
||||
// or posting it and displaying the result.
|
||||
//
|
||||
// If you want a non-standard flow, just call the various functions directly.
|
||||
// eg. if you already set the middleware layers in your code, or want to
|
||||
// output in another format.
|
||||
func DoTx(tx interface{}) (err error) {
|
||||
tx, err = Middleware.Wrap(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = SignTx(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bres, err := PrepareOrPostTx(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bres == nil {
|
||||
return nil // successful prep, nothing left to do
|
||||
}
|
||||
return OutputTx(bres) // print response of the post
|
||||
|
||||
}
|
||||
|
||||
// SignTx will validate the tx, and signs it if it is wrapping a Signable.
|
||||
// Modifies tx in place, and returns an error if it should sign but couldn't
|
||||
func SignTx(tx interface{}) (err error) {
|
||||
// TODO: validate tx client-side
|
||||
// err := tx.ValidateBasic()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// abort early if we don't want to sign
|
||||
if viper.GetBool(FlagNoSign) {
|
||||
return nil
|
||||
}
|
||||
|
||||
name := viper.GetString(FlagName)
|
||||
manager := keycmd.GetKeyManager()
|
||||
|
||||
if sign, ok := tx.(keys.Signable); ok {
|
||||
// TODO: allow us not to sign? if so then what use?
|
||||
if name == "" {
|
||||
return errors.New("--name is required to sign tx")
|
||||
}
|
||||
err = signTx(manager, sign, name)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// PrepareOrPostTx checks the flags to decide to prepare the tx for future
|
||||
// multisig, or to post it to the node. Returns error on any failure.
|
||||
// If no error and the result is nil, it means it already wrote to file,
|
||||
// no post, no need to do more.
|
||||
func PrepareOrPostTx(tx interface{}) (*ctypes.ResultBroadcastTxCommit, error) {
|
||||
wrote, err := PrepareTx(tx)
|
||||
// error in prep
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// successfully wrote the tx!
|
||||
if wrote {
|
||||
return nil, nil
|
||||
}
|
||||
// or try to post it
|
||||
return PostTx(tx)
|
||||
}
|
||||
|
||||
// PrepareTx checks for FlagPrepare and if set, write the tx as json
|
||||
// to the specified location for later multi-sig. Returns true if it
|
||||
// handled the tx (no futher work required), false if it did nothing
|
||||
// (and we should post the tx)
|
||||
func PrepareTx(tx interface{}) (bool, error) {
|
||||
prep := viper.GetString(FlagPrepare)
|
||||
if prep == "" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
js, err := data.ToJSON(tx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
err = writeOutput(prep, js)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// PostTx does all work once we construct a proper struct
|
||||
// it validates the data, signs if needed, transforms to bytes,
|
||||
// and posts to the node.
|
||||
func PostTx(tx interface{}) (*ctypes.ResultBroadcastTxCommit, error) {
|
||||
packet := wire.BinaryBytes(tx)
|
||||
// post the bytes
|
||||
node := commands.GetNode()
|
||||
return node.BroadcastTxCommit(packet)
|
||||
}
|
||||
|
||||
// OutputTx validates if success and prints the tx result to stdout
|
||||
func OutputTx(res *ctypes.ResultBroadcastTxCommit) error {
|
||||
if res.CheckTx.IsErr() {
|
||||
return errors.Errorf("CheckTx: (%d): %s", res.CheckTx.Code, res.CheckTx.Log)
|
||||
}
|
||||
if res.DeliverTx.IsErr() {
|
||||
return errors.Errorf("DeliverTx: (%d): %s", res.DeliverTx.Code, res.DeliverTx.Log)
|
||||
}
|
||||
js, err := json.MarshalIndent(res, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(js))
|
||||
return nil
|
||||
}
|
||||
|
||||
func signTx(manager keys.Manager, tx keys.Signable, name string) error {
|
||||
prompt := fmt.Sprintf("Please enter passphrase for %s: ", name)
|
||||
pass, err := getPassword(prompt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return manager.Sign(name, pass, tx)
|
||||
}
|
||||
|
||||
// if we read from non-tty, we just need to init the buffer reader once,
|
||||
// in case we try to read multiple passwords
|
||||
var buf *bufio.Reader
|
||||
|
||||
func inputIsTty() bool {
|
||||
return isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd())
|
||||
}
|
||||
|
||||
func stdinPassword() (string, error) {
|
||||
if buf == nil {
|
||||
buf = bufio.NewReader(os.Stdin)
|
||||
}
|
||||
pass, err := buf.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(pass), nil
|
||||
}
|
||||
|
||||
func getPassword(prompt string) (pass string, err error) {
|
||||
if inputIsTty() {
|
||||
pass, err = speakeasy.Ask(prompt)
|
||||
} else {
|
||||
pass, err = stdinPassword()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func writeOutput(file string, d []byte) error {
|
||||
var writer io.Writer
|
||||
if file == "-" {
|
||||
writer = os.Stdout
|
||||
} else {
|
||||
f, err := os.Create(file)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
defer f.Close()
|
||||
writer = f
|
||||
}
|
||||
|
||||
_, err := writer.Write(d)
|
||||
// this returns nil if err == nil
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
func readInput(file string) ([]byte, error) {
|
||||
var reader io.Reader
|
||||
// get the input stream
|
||||
if file == "" || file == "-" {
|
||||
reader = os.Stdin
|
||||
} else {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
defer f.Close()
|
||||
reader = f
|
||||
}
|
||||
|
||||
// and read it all!
|
||||
data, err := ioutil.ReadAll(reader)
|
||||
return data, errors.WithStack(err)
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package txs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// nolint
|
||||
const (
|
||||
FlagName = "name"
|
||||
FlagNoSign = "no-sign"
|
||||
FlagIn = "in"
|
||||
FlagPrepare = "prepare"
|
||||
)
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "tx",
|
||||
Short: "Post tx from json input",
|
||||
RunE: doRawTx,
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.PersistentFlags().String(FlagName, "", "name to sign the tx")
|
||||
RootCmd.PersistentFlags().Bool(FlagNoSign, false, "don't add a signature")
|
||||
RootCmd.PersistentFlags().String(FlagPrepare, "", "file to store prepared tx")
|
||||
RootCmd.Flags().String(FlagIn, "", "file with tx in json format")
|
||||
}
|
||||
|
||||
func doRawTx(cmd *cobra.Command, args []string) error {
|
||||
raw, err := readInput(viper.GetString(FlagIn))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// parse the input
|
||||
var tx interface{}
|
||||
err = json.Unmarshal(raw, &tx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
// sign it
|
||||
err = SignTx(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// otherwise, post it and display response
|
||||
bres, err := PrepareOrPostTx(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bres == nil {
|
||||
return nil // successful prep, nothing left to do
|
||||
}
|
||||
return OutputTx(bres) // print response of the post
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package txs
|
||||
|
||||
import (
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var (
|
||||
// Middleware must be set in main.go to defined the wrappers we should apply
|
||||
Middleware Wrapper
|
||||
)
|
||||
|
||||
// Wrapper defines the information needed for each middleware package that
|
||||
// wraps the data. They should read all configuration out of bounds via viper.
|
||||
type Wrapper interface {
|
||||
Wrap(interface{}) (interface{}, error)
|
||||
Register(*pflag.FlagSet)
|
||||
}
|
||||
|
||||
// Wrappers combines a list of wrapper middlewares.
|
||||
// The first one is the inner-most layer, eg. Fee, Nonce, Chain, Auth
|
||||
type Wrappers []Wrapper
|
||||
|
||||
var _ Wrapper = Wrappers{}
|
||||
|
||||
// Wrap applies the wrappers to the passed in tx in order,
|
||||
// aborting on the first error
|
||||
func (ws Wrappers) Wrap(tx interface{}) (interface{}, error) {
|
||||
var err error
|
||||
for _, w := range ws {
|
||||
tx, err = w.Wrap(tx)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return tx, err
|
||||
}
|
||||
|
||||
// Register adds any needed flags to the command
|
||||
func (ws Wrappers) Register(fs *pflag.FlagSet) {
|
||||
for _, w := range ws {
|
||||
w.Register(fs)
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/version"
|
||||
)
|
||||
|
||||
// CommitHash should be filled by linker flags
|
||||
var CommitHash = ""
|
||||
|
||||
// VersionCmd - command to show the application version
|
||||
var VersionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Show version info",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("%s-%s\n", version.Version, CommitHash)
|
||||
},
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/tendermint/light-client/certifiers"
|
||||
certclient "github.com/tendermint/light-client/certifiers/client"
|
||||
certerr "github.com/tendermint/light-client/certifiers/errors"
|
||||
"github.com/tendermint/light-client/certifiers/files"
|
||||
|
||||
"github.com/tendermint/light-client/proofs"
|
||||
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
)
|
||||
|
||||
// GetNode prepares a simple rpc.Client for the given endpoint
|
||||
func GetNode(url string) rpcclient.Client {
|
||||
return rpcclient.NewHTTP(url, "/websocket")
|
||||
}
|
||||
|
||||
// GetRPCProvider retuns a certifier compatible data source using
|
||||
// tendermint RPC
|
||||
func GetRPCProvider(url string) certifiers.Provider {
|
||||
return certclient.NewHTTPProvider(url)
|
||||
}
|
||||
|
||||
// GetLocalProvider returns a reference to a file store of headers
|
||||
// wrapped with an in-memory cache
|
||||
func GetLocalProvider(dir string) certifiers.Provider {
|
||||
return certifiers.NewCacheProvider(
|
||||
certifiers.NewMemStoreProvider(),
|
||||
files.NewProvider(dir),
|
||||
)
|
||||
}
|
||||
|
||||
// GetCertifier initializes an inquiring certifier given a fixed chainID
|
||||
// and a local source of trusted data with at least one seed
|
||||
func GetCertifier(chainID string, trust certifiers.Provider,
|
||||
source certifiers.Provider) (*certifiers.Inquiring, error) {
|
||||
|
||||
// this gets the most recent verified commit
|
||||
fc, err := trust.LatestCommit()
|
||||
if certerr.IsCommitNotFoundErr(err) {
|
||||
return nil, errors.New("Please run init first to establish a root of trust")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert := certifiers.NewInquiring(chainID, fc, trust, source)
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// SecureClient uses a given certifier to wrap an connection to an untrusted
|
||||
// host and return a cryptographically secure rpc client.
|
||||
func SecureClient(c rpcclient.Client, cert *certifiers.Inquiring) rpcclient.Client {
|
||||
return proofs.Wrap(c, cert)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
//--------------------------------------------
|
||||
|
||||
var errNoData = fmt.Errorf("No data returned for query")
|
||||
|
||||
// IsNoDataErr checks whether an error is due to a query returning empty data
|
||||
func IsNoDataErr(err error) bool {
|
||||
return errors.Cause(err) == errNoData
|
||||
}
|
||||
|
||||
func ErrNoData() error {
|
||||
return errors.WithStack(errNoData)
|
||||
}
|
||||
|
||||
//--------------------------------------------
|
||||
@@ -1,18 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestErrorNoData(t *testing.T) {
|
||||
e1 := ErrNoData()
|
||||
e1.Error()
|
||||
assert.True(t, IsNoDataErr(e1))
|
||||
|
||||
e2 := errors.New("foobar")
|
||||
assert.False(t, IsNoDataErr(e2))
|
||||
assert.False(t, IsNoDataErr(nil))
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package client
|
||||
|
||||
/*
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/tendermint/go-crypto/keys"
|
||||
"github.com/tendermint/go-crypto/keys/cryptostore"
|
||||
"github.com/tendermint/go-crypto/keys/storage/filestorage"
|
||||
)
|
||||
|
||||
// KeySubdir is the directory name under root where we store the keys
|
||||
const KeySubdir = "keys"
|
||||
|
||||
// GetKeyManager initializes a key manager based on the configuration
|
||||
func GetKeyManager(rootDir string) keys.Manager {
|
||||
keyDir := filepath.Join(rootDir, KeySubdir)
|
||||
// TODO: smarter loading??? with language and fallback?
|
||||
codec := keys.MustLoadCodec("english")
|
||||
|
||||
// and construct the key manager
|
||||
manager := cryptostore.New(
|
||||
cryptostore.SecretBox,
|
||||
filestorage.New(keyDir),
|
||||
codec,
|
||||
)
|
||||
return manager
|
||||
}
|
||||
*/
|
||||
@@ -1,68 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
"github.com/tendermint/tendermint/rpc/core"
|
||||
rpc "github.com/tendermint/tendermint/rpc/lib/server"
|
||||
)
|
||||
|
||||
const (
|
||||
wsEndpoint = "/websocket"
|
||||
)
|
||||
|
||||
// StartProxy will start the websocket manager on the client,
|
||||
// set up the rpc routes to proxy via the given client,
|
||||
// and start up an http/rpc server on the location given by bind (eg. :1234)
|
||||
func StartProxy(c rpcclient.Client, bind string, logger log.Logger) error {
|
||||
c.Start()
|
||||
r := RPCRoutes(c)
|
||||
|
||||
// build the handler...
|
||||
mux := http.NewServeMux()
|
||||
rpc.RegisterRPCFuncs(mux, r, logger)
|
||||
wm := rpc.NewWebsocketManager(r, c)
|
||||
wm.SetLogger(logger)
|
||||
core.SetLogger(logger)
|
||||
mux.HandleFunc(wsEndpoint, wm.WebsocketHandler)
|
||||
|
||||
_, err := rpc.StartHTTPServer(bind, mux, logger)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// RPCRoutes just routes everything to the given client, as if it were
|
||||
// a tendermint fullnode.
|
||||
//
|
||||
// if we want security, the client must implement it as a secure client
|
||||
func RPCRoutes(c rpcclient.Client) map[string]*rpc.RPCFunc {
|
||||
|
||||
return map[string]*rpc.RPCFunc{
|
||||
// Subscribe/unsubscribe are reserved for websocket events.
|
||||
// We can just use the core tendermint impl, which uses the
|
||||
// EventSwitch we registered in NewWebsocketManager above
|
||||
"subscribe": rpc.NewWSRPCFunc(core.Subscribe, "event"),
|
||||
"unsubscribe": rpc.NewWSRPCFunc(core.Unsubscribe, "event"),
|
||||
|
||||
// info API
|
||||
"status": rpc.NewRPCFunc(c.Status, ""),
|
||||
"blockchain": rpc.NewRPCFunc(c.BlockchainInfo, "minHeight,maxHeight"),
|
||||
"genesis": rpc.NewRPCFunc(c.Genesis, ""),
|
||||
"block": rpc.NewRPCFunc(c.Block, "height"),
|
||||
"commit": rpc.NewRPCFunc(c.Commit, "height"),
|
||||
"tx": rpc.NewRPCFunc(c.Tx, "hash,prove"),
|
||||
"validators": rpc.NewRPCFunc(c.Validators, ""),
|
||||
|
||||
// broadcast API
|
||||
"broadcast_tx_commit": rpc.NewRPCFunc(c.BroadcastTxCommit, "tx"),
|
||||
"broadcast_tx_sync": rpc.NewRPCFunc(c.BroadcastTxSync, "tx"),
|
||||
"broadcast_tx_async": rpc.NewRPCFunc(c.BroadcastTxAsync, "tx"),
|
||||
|
||||
// abci API
|
||||
"abci_query": rpc.NewRPCFunc(c.ABCIQuery, "path,data,prove"),
|
||||
"abci_info": rpc.NewRPCFunc(c.ABCIInfo, ""),
|
||||
}
|
||||
}
|
||||
-116
@@ -1,116 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/tendermint/go-wire/data"
|
||||
"github.com/tendermint/iavl"
|
||||
"github.com/tendermint/light-client/certifiers"
|
||||
certerr "github.com/tendermint/light-client/certifiers/errors"
|
||||
|
||||
"github.com/tendermint/tendermint/rpc/client"
|
||||
)
|
||||
|
||||
// GetWithProof will query the key on the given node, and verify it has
|
||||
// a valid proof, as defined by the certifier.
|
||||
//
|
||||
// If there is any error in checking, returns an error.
|
||||
// If val is non-empty, proof should be KeyExistsProof
|
||||
// If val is empty, proof should be KeyMissingProof
|
||||
func GetWithProof(key []byte, reqHeight int, node client.Client,
|
||||
cert certifiers.Certifier) (
|
||||
val data.Bytes, height uint64, proof iavl.KeyProof, err error) {
|
||||
|
||||
if reqHeight < 0 {
|
||||
err = errors.Errorf("Height cannot be negative")
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := node.ABCIQueryWithOptions("/key", key,
|
||||
client.ABCIQueryOptions{Height: uint64(reqHeight)})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// make sure the proof is the proper height
|
||||
if !resp.Code.IsOK() {
|
||||
err = errors.Errorf("Query error %d: %s", resp.Code, resp.Code.String())
|
||||
return
|
||||
}
|
||||
if len(resp.Key) == 0 || len(resp.Proof) == 0 {
|
||||
err = ErrNoData()
|
||||
return
|
||||
}
|
||||
if resp.Height == 0 {
|
||||
err = errors.New("Height returned is zero")
|
||||
return
|
||||
}
|
||||
|
||||
// AppHash for height H is in header H+1
|
||||
var commit *certifiers.Commit
|
||||
commit, err = GetCertifiedCommit(int(resp.Height+1), node, cert)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(resp.Value) > 0 {
|
||||
// The key was found, construct a proof of existence.
|
||||
var eproof *iavl.KeyExistsProof
|
||||
eproof, err = iavl.ReadKeyExistsProof(resp.Proof)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "Error reading proof")
|
||||
return
|
||||
}
|
||||
|
||||
// Validate the proof against the certified header to ensure data integrity.
|
||||
err = eproof.Verify(resp.Key, resp.Value, commit.Header.AppHash)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "Couldn't verify proof")
|
||||
return
|
||||
}
|
||||
val = data.Bytes(resp.Value)
|
||||
proof = eproof
|
||||
} else {
|
||||
// The key wasn't found, construct a proof of non-existence.
|
||||
var aproof *iavl.KeyAbsentProof
|
||||
aproof, err = iavl.ReadKeyAbsentProof(resp.Proof)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "Error reading proof")
|
||||
return
|
||||
}
|
||||
// Validate the proof against the certified header to ensure data integrity.
|
||||
err = aproof.Verify(resp.Key, nil, commit.Header.AppHash)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "Couldn't verify proof")
|
||||
return
|
||||
}
|
||||
err = ErrNoData()
|
||||
proof = aproof
|
||||
}
|
||||
|
||||
height = resp.Height
|
||||
return
|
||||
}
|
||||
|
||||
// GetCertifiedCommit gets the signed header for a given height
|
||||
// and certifies it. Returns error if unable to get a proven header.
|
||||
func GetCertifiedCommit(h int, node client.Client,
|
||||
cert certifiers.Certifier) (empty *certifiers.Commit, err error) {
|
||||
|
||||
// FIXME: cannot use cert.GetByHeight for now, as it also requires
|
||||
// Validators and will fail on querying tendermint for non-current height.
|
||||
// When this is supported, we should use it instead...
|
||||
client.WaitForHeight(node, h, nil)
|
||||
cresp, err := node.Commit(&h)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
commit := certifiers.CommitFromResult(cresp)
|
||||
|
||||
// validate downloaded checkpoint with our request and trust store.
|
||||
if commit.Height() != h {
|
||||
return empty, certerr.ErrHeightMismatch(h, commit.Height())
|
||||
}
|
||||
err = cert.Certify(commit)
|
||||
return commit, nil
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/light-client/certifiers"
|
||||
certclient "github.com/tendermint/light-client/certifiers/client"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
nm "github.com/tendermint/tendermint/node"
|
||||
"github.com/tendermint/tendermint/rpc/client"
|
||||
rpctest "github.com/tendermint/tendermint/rpc/test"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
|
||||
sdkapp "github.com/cosmos/cosmos-sdk/app"
|
||||
"github.com/cosmos/cosmos-sdk/modules/eyes"
|
||||
)
|
||||
|
||||
var node *nm.Node
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
logger := log.TestingLogger()
|
||||
store, err := sdkapp.MockStoreApp("query", logger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
app := sdkapp.NewBaseApp(store, eyes.NewHandler(), nil)
|
||||
|
||||
node = rpctest.StartTendermint(app)
|
||||
|
||||
code := m.Run()
|
||||
|
||||
node.Stop()
|
||||
node.Wait()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func TestAppProofs(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
cl := client.NewLocal(node)
|
||||
client.WaitForHeight(cl, 1, nil)
|
||||
|
||||
k := []byte("my-key")
|
||||
v := []byte("my-value")
|
||||
|
||||
tx := eyes.SetTx{Key: k, Value: v}.Wrap()
|
||||
btx := wire.BinaryBytes(tx)
|
||||
br, err := cl.BroadcastTxCommit(btx)
|
||||
require.NoError(err, "%+v", err)
|
||||
require.EqualValues(0, br.CheckTx.Code, "%#v", br.CheckTx)
|
||||
require.EqualValues(0, br.DeliverTx.Code)
|
||||
brh := br.Height
|
||||
|
||||
// This sets up our trust on the node based on some past point.
|
||||
source := certclient.NewProvider(cl)
|
||||
seed, err := source.GetByHeight(br.Height - 2)
|
||||
require.NoError(err, "%+v", err)
|
||||
cert := certifiers.NewStatic("my-chain", seed.Validators)
|
||||
|
||||
client.WaitForHeight(cl, 3, nil)
|
||||
latest, err := source.LatestCommit()
|
||||
require.NoError(err, "%+v", err)
|
||||
rootHash := latest.Header.AppHash
|
||||
|
||||
// Test existing key.
|
||||
var data eyes.Data
|
||||
|
||||
// verify a query before the tx block has no data (and valid non-exist proof)
|
||||
bs, height, proof, err := GetWithProof(k, brh-1, cl, cert)
|
||||
require.NotNil(err)
|
||||
require.True(IsNoDataErr(err))
|
||||
require.Nil(bs)
|
||||
|
||||
// but given that block it is good
|
||||
bs, height, proof, err = GetWithProof(k, brh, cl, cert)
|
||||
require.NoError(err, "%+v", err)
|
||||
require.NotNil(proof)
|
||||
require.True(height >= uint64(latest.Header.Height))
|
||||
|
||||
// Alexis there is a bug here, somehow the above code gives us rootHash = nil
|
||||
// and proof.Verify doesn't care, while proofNotExists.Verify fails.
|
||||
// I am hacking this in to make it pass, but please investigate further.
|
||||
rootHash = proof.Root()
|
||||
|
||||
err = wire.ReadBinaryBytes(bs, &data)
|
||||
require.NoError(err, "%+v", err)
|
||||
assert.EqualValues(v, data.Value)
|
||||
err = proof.Verify(k, bs, rootHash)
|
||||
assert.NoError(err, "%+v", err)
|
||||
|
||||
// Test non-existing key.
|
||||
missing := []byte("my-missing-key")
|
||||
bs, _, proof, err = GetWithProof(missing, 0, cl, cert)
|
||||
require.True(IsNoDataErr(err))
|
||||
require.Nil(bs)
|
||||
require.NotNil(proof)
|
||||
err = proof.Verify(missing, nil, rootHash)
|
||||
assert.NoError(err, "%+v", err)
|
||||
err = proof.Verify(k, nil, rootHash)
|
||||
assert.Error(err)
|
||||
}
|
||||
|
||||
func TestTxProofs(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
cl := client.NewLocal(node)
|
||||
client.WaitForHeight(cl, 1, nil)
|
||||
|
||||
tx := eyes.NewSetTx([]byte("key-a"), []byte("value-a"))
|
||||
|
||||
btx := types.Tx(wire.BinaryBytes(tx))
|
||||
br, err := cl.BroadcastTxCommit(btx)
|
||||
require.NoError(err, "%+v", err)
|
||||
require.EqualValues(0, br.CheckTx.Code, "%#v", br.CheckTx)
|
||||
require.EqualValues(0, br.DeliverTx.Code)
|
||||
fmt.Printf("tx height: %d\n", br.Height)
|
||||
|
||||
source := certclient.NewProvider(cl)
|
||||
seed, err := source.GetByHeight(br.Height - 2)
|
||||
require.NoError(err, "%+v", err)
|
||||
cert := certifiers.NewStatic("my-chain", seed.Validators)
|
||||
|
||||
// First let's make sure a bogus transaction hash returns a valid non-existence proof.
|
||||
key := types.Tx([]byte("bogus")).Hash()
|
||||
res, err := cl.Tx(key, true)
|
||||
require.NotNil(err)
|
||||
require.Contains(err.Error(), "not found")
|
||||
|
||||
// Now let's check with the real tx hash.
|
||||
key = btx.Hash()
|
||||
res, err = cl.Tx(key, true)
|
||||
require.NoError(err, "%+v", err)
|
||||
require.NotNil(res)
|
||||
err = res.Proof.Validate(key)
|
||||
assert.NoError(err, "%+v", err)
|
||||
|
||||
commit, err := GetCertifiedCommit(int(br.Height), cl, cert)
|
||||
require.Nil(err, "%+v", err)
|
||||
require.Equal(res.Proof.RootHash, commit.Header.DataHash)
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
## basecoin-server
|
||||
|
||||
### Proxy server
|
||||
This package exposes access to key management i.e
|
||||
- creating
|
||||
- listing
|
||||
- updating
|
||||
- deleting
|
||||
|
||||
The HTTP handlers can be embedded in a larger server that
|
||||
does things like signing transactions and posting them to a
|
||||
Tendermint chain (which requires domain-knowledge of the transaction
|
||||
types and is out of scope of this generic app).
|
||||
|
||||
### Key Management
|
||||
We expose a couple of methods for safely managing your keychain.
|
||||
If you are embedding this in a larger server, you will typically
|
||||
want to mount all these paths /keys.
|
||||
|
||||
HTTP Method | Route | Description
|
||||
---|---|---
|
||||
POST|/|Requires a name and passphrase to create a brand new key
|
||||
GET|/|Retrieves the list of all available key names, along with their public key and address
|
||||
GET|/{name} | Updates the passphrase for the given key. It requires you to correctly provide the current passphrase, as well as a new one.
|
||||
DELETE|/{name} | Permanently delete this private key. It requires you to correctly provide the current passphrase.
|
||||
@@ -1,192 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
keys "github.com/tendermint/go-crypto/keys"
|
||||
"github.com/tendermint/tmlibs/common"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
keycmd "github.com/cosmos/cosmos-sdk/client/commands/keys"
|
||||
)
|
||||
|
||||
type Keys struct {
|
||||
algo string
|
||||
manager keys.Manager
|
||||
}
|
||||
|
||||
func DefaultKeysManager() keys.Manager {
|
||||
return keycmd.GetKeyManager()
|
||||
}
|
||||
|
||||
func NewDefaultKeysManager(algo string) *Keys {
|
||||
return New(DefaultKeysManager(), algo)
|
||||
}
|
||||
|
||||
func New(manager keys.Manager, algo string) *Keys {
|
||||
return &Keys{
|
||||
algo: algo,
|
||||
manager: manager,
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Keys) GenerateKey(w http.ResponseWriter, r *http.Request) {
|
||||
ckReq := &CreateKeyRequest{
|
||||
Algo: k.algo,
|
||||
}
|
||||
if err := common.ParseRequestAndValidateJSON(r, ckReq); err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
key, seed, err := k.manager.Create(ckReq.Name, ckReq.Passphrase, ckReq.Algo)
|
||||
if err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
res := &CreateKeyResponse{Key: key, Seed: seed}
|
||||
common.WriteSuccess(w, res)
|
||||
}
|
||||
|
||||
func (k *Keys) GetKey(w http.ResponseWriter, r *http.Request) {
|
||||
query := mux.Vars(r)
|
||||
name := query["name"]
|
||||
key, err := k.manager.Get(name)
|
||||
if err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
common.WriteSuccess(w, &key)
|
||||
}
|
||||
|
||||
func (k *Keys) ListKeys(w http.ResponseWriter, r *http.Request) {
|
||||
keys, err := k.manager.List()
|
||||
if err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
common.WriteSuccess(w, keys)
|
||||
}
|
||||
|
||||
var (
|
||||
errNonMatchingPathAndJSONKeyNames = errors.New("path and json key names don't match")
|
||||
)
|
||||
|
||||
func (k *Keys) UpdateKey(w http.ResponseWriter, r *http.Request) {
|
||||
uReq := new(UpdateKeyRequest)
|
||||
if err := common.ParseRequestAndValidateJSON(r, uReq); err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
query := mux.Vars(r)
|
||||
name := query["name"]
|
||||
if name != uReq.Name {
|
||||
common.WriteError(w, errNonMatchingPathAndJSONKeyNames)
|
||||
return
|
||||
}
|
||||
|
||||
if err := k.manager.Update(uReq.Name, uReq.OldPass, uReq.NewPass); err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
key, err := k.manager.Get(uReq.Name)
|
||||
if err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
common.WriteSuccess(w, &key)
|
||||
}
|
||||
|
||||
func (k *Keys) DeleteKey(w http.ResponseWriter, r *http.Request) {
|
||||
dReq := new(DeleteKeyRequest)
|
||||
if err := common.ParseRequestAndValidateJSON(r, dReq); err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
query := mux.Vars(r)
|
||||
name := query["name"]
|
||||
if name != dReq.Name {
|
||||
common.WriteError(w, errNonMatchingPathAndJSONKeyNames)
|
||||
return
|
||||
}
|
||||
|
||||
if err := k.manager.Delete(dReq.Name, dReq.Passphrase); err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp := &common.ErrorResponse{Success: true}
|
||||
common.WriteSuccess(w, resp)
|
||||
}
|
||||
|
||||
func doPostTx(w http.ResponseWriter, r *http.Request) {
|
||||
tx := new(sdk.Tx)
|
||||
if err := common.ParseRequestAndValidateJSON(r, tx); err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
commit, err := PostTx(*tx)
|
||||
if err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
common.WriteSuccess(w, commit)
|
||||
}
|
||||
|
||||
func doSign(w http.ResponseWriter, r *http.Request) {
|
||||
sr := new(SignRequest)
|
||||
if err := common.ParseRequestAndValidateJSON(r, sr); err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
tx := sr.Tx
|
||||
if err := SignTx(sr.Name, sr.Password, tx); err != nil {
|
||||
common.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
common.WriteSuccess(w, tx)
|
||||
}
|
||||
|
||||
// mux.Router registrars
|
||||
|
||||
// RegisterPostTx is a mux.Router handler that exposes POST
|
||||
// method access to post a transaction to the blockchain.
|
||||
func RegisterPostTx(r *mux.Router) error {
|
||||
r.HandleFunc("/tx", doPostTx).Methods("POST")
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterAllCRUD is a convenience method to register all
|
||||
// CRUD for keys to allow access by methods and routes:
|
||||
// POST: /keys
|
||||
// GET: /keys
|
||||
// GET: /keys/{name}
|
||||
// POST, PUT: /keys/{name}
|
||||
// DELETE: /keys/{name}
|
||||
func (k *Keys) RegisterAllCRUD(r *mux.Router) error {
|
||||
r.HandleFunc("/keys", k.GenerateKey).Methods("POST")
|
||||
r.HandleFunc("/keys", k.ListKeys).Methods("GET")
|
||||
r.HandleFunc("/keys/{name}", k.GetKey).Methods("GET")
|
||||
r.HandleFunc("/keys/{name}", k.UpdateKey).Methods("POST", "PUT")
|
||||
r.HandleFunc("/keys/{name}", k.DeleteKey).Methods("DELETE")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterSignTx is a mux.Router handler that
|
||||
// exposes POST method access to sign a transaction.
|
||||
func RegisterSignTx(r *mux.Router) error {
|
||||
r.HandleFunc("/sign", doSign).Methods("POST")
|
||||
return nil
|
||||
}
|
||||
|
||||
// End of mux.Router registrars
|
||||
@@ -1,29 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/tendermint/go-crypto/keys"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/client/commands"
|
||||
keycmd "github.com/cosmos/cosmos-sdk/client/commands/keys"
|
||||
)
|
||||
|
||||
// PostTx is same as a tx
|
||||
func PostTx(tx sdk.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
|
||||
packet := wire.BinaryBytes(tx)
|
||||
// post the bytes
|
||||
node := commands.GetNode()
|
||||
return node.BroadcastTxCommit(packet)
|
||||
}
|
||||
|
||||
// SignTx will modify the tx in-place, adding a signature if possible
|
||||
func SignTx(name, pass string, tx sdk.Tx) error {
|
||||
if sign, ok := tx.Unwrap().(keys.Signable); ok {
|
||||
manager := keycmd.GetKeyManager()
|
||||
return manager.Sign(name, pass, sign)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk"
|
||||
"github.com/cosmos/cosmos-sdk/modules/coin"
|
||||
"github.com/tendermint/go-crypto/keys"
|
||||
)
|
||||
|
||||
type CreateKeyRequest struct {
|
||||
Name string `json:"name,omitempty" validate:"required,min=3,printascii"`
|
||||
Passphrase string `json:"password,omitempty" validate:"required,min=10"`
|
||||
|
||||
// Algo is the requested algorithm to create the key
|
||||
Algo string `json:"algo,omitempty"`
|
||||
}
|
||||
|
||||
type DeleteKeyRequest struct {
|
||||
Name string `json:"name,omitempty" validate:"required,min=3,printascii"`
|
||||
Passphrase string `json:"password,omitempty" validate:"required,min=10"`
|
||||
}
|
||||
|
||||
type UpdateKeyRequest struct {
|
||||
Name string `json:"name,omitempty" validate:"required,min=3,printascii"`
|
||||
OldPass string `json:"password,omitempty" validate:"required,min=10"`
|
||||
NewPass string `json:"new_passphrase,omitempty" validate:"required,min=10"`
|
||||
}
|
||||
|
||||
type SignRequest struct {
|
||||
Name string `json:"name,omitempty" validate:"required,min=3,printascii"`
|
||||
Password string `json:"password,omitempty" validate:"required,min=10"`
|
||||
|
||||
Tx sdk.Tx `json:"tx" validate:"required"`
|
||||
}
|
||||
|
||||
type CreateKeyResponse struct {
|
||||
Key keys.Info `json:"key,omitempty"`
|
||||
Seed string `json:"seed_phrase,omitempty"`
|
||||
}
|
||||
|
||||
// SendInput is the request to send an amount from one actor to another.
|
||||
// Note: Not using the `validator:""` tags here because SendInput has
|
||||
// many fields so it would be nice to figure out all the invalid
|
||||
// inputs and report them back to the caller, in one shot.
|
||||
type SendInput struct {
|
||||
Fees *coin.Coin `json:"fees"`
|
||||
Multi bool `json:"multi,omitempty"`
|
||||
Sequence uint32 `json:"sequence"`
|
||||
|
||||
To *sdk.Actor `json:"to"`
|
||||
From *sdk.Actor `json:"from"`
|
||||
Amount coin.Coins `json:"amount"`
|
||||
}
|
||||
Reference in New Issue
Block a user