move things to _attic

This commit is contained in:
Ethan Buchman
2018-01-06 15:35:22 -05:00
parent c5aeef3f2f
commit e45ad068fb
196 changed files with 2 additions and 3 deletions
-33
View File
@@ -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)
}
-46
View File
@@ -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)
}
-59
View File
@@ -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
}
-15
View File
@@ -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.
`,
}
-74
View File
@@ -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
}
-52
View File
@@ -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
}
-154
View File
@@ -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
}
-352
View File
@@ -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()
}
}
-117
View File
@@ -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="
}
}
]
```
-49
View File
@@ -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
}
-42
View File
@@ -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
}
-34
View File
@@ -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
}
-94
View File
@@ -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))
}
}
-61
View File
@@ -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
}
-44
View File
@@ -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)
}
-53
View File
@@ -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
}
-131
View File
@@ -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))
}
}
-66
View File
@@ -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
}
-121
View File
@@ -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)
}
-30
View File
@@ -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)
}
-36
View File
@@ -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)
}
-76
View File
@@ -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))
}
-49
View File
@@ -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
}
-67
View File
@@ -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)
}
-64
View File
@@ -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
}
-76
View File
@@ -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)
}
-250
View File
@@ -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)
}
-61
View File
@@ -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
}
-43
View File
@@ -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)
}
}
-21
View File
@@ -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)
},
}
-57
View File
@@ -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)
}
-22
View File
@@ -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)
}
//--------------------------------------------
-18
View File
@@ -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))
}
-30
View File
@@ -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
}
*/
-68
View File
@@ -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
View File
@@ -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
}
-148
View File
@@ -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)
}
-25
View File
@@ -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.
-192
View File
@@ -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
-29
View File
@@ -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
}
-52
View File
@@ -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"`
}