Moved all commands from light-client into basecoin

This commit is contained in:
Ethan Frey 2017-07-18 21:23:11 +02:00
parent f41aed4945
commit a9e4a94402
29 changed files with 1474 additions and 36 deletions

View File

@ -8,7 +8,7 @@ import (
"github.com/pkg/errors"
"github.com/spf13/pflag"
txcmd "github.com/tendermint/light-client/commands/txs"
txcmd "github.com/tendermint/basecoin/commands/txs"
cmn "github.com/tendermint/tmlibs/common"
ctypes "github.com/tendermint/tendermint/rpc/core/types"

View File

@ -8,12 +8,12 @@ import (
"github.com/tendermint/abci/version"
keycmd "github.com/tendermint/go-crypto/cmd"
"github.com/tendermint/light-client/commands"
"github.com/tendermint/light-client/commands/proofs"
"github.com/tendermint/light-client/commands/proxy"
rpccmd "github.com/tendermint/light-client/commands/rpc"
"github.com/tendermint/light-client/commands/seeds"
"github.com/tendermint/light-client/commands/txs"
"github.com/tendermint/basecoin/commands"
"github.com/tendermint/basecoin/commands/proofs"
"github.com/tendermint/basecoin/commands/proxy"
rpccmd "github.com/tendermint/basecoin/commands/rpc"
"github.com/tendermint/basecoin/commands/seeds"
"github.com/tendermint/basecoin/commands/txs"
"github.com/tendermint/tmlibs/cli"
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"

73
commands/common.go Normal file
View File

@ -0,0 +1,73 @@
/*
Package commands contains any general setup/helpers valid for all subcommands
*/
package commands
import (
"errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/tmlibs/cli"
rpcclient "github.com/tendermint/tendermint/rpc/client"
"github.com/tendermint/light-client/certifiers"
"github.com/tendermint/light-client/certifiers/client"
"github.com/tendermint/light-client/certifiers/files"
)
var (
trustedProv certifiers.Provider
sourceProv certifiers.Provider
)
const (
ChainFlag = "chain-id"
NodeFlag = "node"
)
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")
}
func GetChainID() string {
return viper.GetString(ChainFlag)
}
func GetNode() rpcclient.Client {
return rpcclient.NewHTTP(viper.GetString(NodeFlag), "/websocket")
}
func GetProviders() (trusted certifiers.Provider, source certifiers.Provider) {
if trustedProv == nil || sourceProv == nil {
// initialize provider with files stored in homedir
rootDir := viper.GetString(cli.HomeFlag)
trustedProv = certifiers.NewCacheProvider(
certifiers.NewMemStoreProvider(),
files.NewProvider(rootDir),
)
node := viper.GetString(NodeFlag)
sourceProv = client.NewHTTP(node)
}
return trustedProv, sourceProv
}
func GetCertifier() (*certifiers.InquiringCertifier, error) {
// load up the latest store....
trust, source := GetProviders()
// this gets the most recent verified seed
seed, err := certifiers.LatestSeed(trust)
if certifiers.IsSeedNotFoundErr(err) {
return nil, errors.New("Please run init first to establish a root of trust")
}
if err != nil {
return nil, err
}
cert := certifiers.NewInquiring(
viper.GetString(ChainFlag), seed.Validators, trust, source)
return cert, nil
}

346
commands/init.go Normal file
View File

@ -0,0 +1,346 @@
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/tmlibs/cli"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tendermint/types"
"github.com/tendermint/light-client/certifiers"
"github.com/tendermint/light-client/certifiers/files"
)
var (
dirPerm = os.FileMode(0700)
)
const (
SeedFlag = "seed"
HashFlag = "valhash"
GenesisFlag = "genesis"
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(SeedFlag, "", "Seed 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 = initSeed()
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 {
// 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 initSeed() (err error) {
// create a provider....
trust, source := GetProviders()
// load a seed file, or get data from the provider
var seed certifiers.Seed
seedFile := viper.GetString(SeedFlag)
if seedFile == "" {
fmt.Println("Loading validator set from tendermint rpc...")
seed, err = certifiers.LatestSeed(source)
} else {
fmt.Printf("Loading validators from file %s\n", seedFile)
seed, err = certifiers.LoadSeed(seedFile)
}
// can't load the seed? abort!
if err != nil {
return err
}
// make sure it is a proper seed
err = seed.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, seed.Hash()) {
err = errors.Errorf("Seed hash doesn't match expectation: %X", seed.Hash())
}
} else {
err = validateHash(seed)
}
if err != nil {
return err
}
// if accepted, store seed as current state
trust.StoreSeed(seed)
return nil
}
func validateHash(seed certifiers.Seed) 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", seed.Hash())
fmt.Println("Is this valid (y/n)?")
valid := askForConfirmation()
if !valid {
return errors.New("Invalid validator hash, try init with proper seed 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()
}
}

119
commands/proofs/get.go Normal file
View File

@ -0,0 +1,119 @@
package proofs
import (
"fmt"
"github.com/pkg/errors"
"github.com/spf13/viper"
wire "github.com/tendermint/go-wire"
"github.com/tendermint/go-wire/data"
"github.com/tendermint/tendermint/rpc/client"
lc "github.com/tendermint/light-client"
"github.com/tendermint/basecoin/commands"
"github.com/tendermint/light-client/proofs"
)
// GetAndParseAppProof does most of the work of the query commands, but is quite
// opinionated, so if you want more control, set up the items and call GetProof
// directly. Notably, it always uses go-wire.ReadBinaryBytes to deserialize,
// and Height and Node from standard flags.
//
// It will try to get the proof for the given key. If it is successful,
// it will return the proof and also unserialize proof.Data into the data
// argument (so pass in a pointer to the appropriate struct)
func GetAndParseAppProof(key []byte, data interface{}) (lc.Proof, error) {
height := GetHeight()
node := commands.GetNode()
prover := proofs.NewAppProver(node)
proof, err := GetProof(node, prover, key, height)
if err != nil {
return proof, err
}
err = wire.ReadBinaryBytes(proof.Data(), data)
return proof, err
}
// GetProof performs the get command directly from the proof (not from the CLI)
func GetProof(node client.Client, prover lc.Prover, key []byte, height int) (proof lc.Proof, err error) {
proof, err = prover.Get(key, uint64(height))
if err != nil {
return
}
ph := int(proof.BlockHeight())
// here is the certifier, root of all knowledge
cert, err := commands.GetCertifier()
if err != nil {
return
}
// get and validate a signed header for this proof
// 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, ph, nil)
commit, err := node.Commit(ph)
if err != nil {
return
}
check := lc.Checkpoint{
Header: commit.Header,
Commit: commit.Commit,
}
err = cert.Certify(check)
if err != nil {
return
}
// validate the proof against the certified header to ensure data integrity
err = proof.Validate(check)
if err != nil {
return
}
return proof, err
}
// 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)
}
func GetHeight() int {
return viper.GetInt(heightFlag)
}
type proof struct {
Height uint64 `json:"height"`
Data interface{} `json:"data"`
}
// 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(info interface{}, height uint64) error {
wrap := proof{height, info}
res, err := data.ToJSON(wrap)
if err != nil {
return err
}
fmt.Println(string(res))
return nil
}

23
commands/proofs/root.go Normal file
View File

@ -0,0 +1,23 @@
package proofs
import "github.com/spf13/cobra"
const (
heightFlag = "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.Flags().Int(heightFlag, 0, "Height to query (skip to use latest block)")
}

46
commands/proofs/state.go Normal file
View File

@ -0,0 +1,46 @@
package proofs
import (
"github.com/spf13/cobra"
"github.com/tendermint/go-wire/data"
"github.com/tendermint/basecoin/commands"
"github.com/tendermint/light-client/proofs"
)
var KeyCmd = &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(doKeyQuery),
}
// Note: we cannot yse GetAndParseAppProof here, as we don't use go-wire to
// parse the object, but rather return the raw bytes
func doKeyQuery(cmd *cobra.Command, args []string) error {
// parse cli
height := GetHeight()
key, err := ParseHexKey(args, "key")
if err != nil {
return err
}
// get the proof -> this will be used by all prover commands
node := commands.GetNode()
prover := proofs.NewAppProver(node)
proof, err := GetProof(node, prover, key, height)
if err != nil {
return err
}
// state just returns raw hex....
info := data.Bytes(proof.Data())
// we can reuse this output for other commands for text/json
// unless they do something special like store a file to disk
return OutputProof(info, proof.BlockHeight())
}

49
commands/proofs/tx.go Normal file
View File

@ -0,0 +1,49 @@
package proofs
import (
"github.com/spf13/cobra"
"github.com/tendermint/basecoin/commands"
"github.com/tendermint/light-client/proofs"
)
var TxPresenters = proofs.NewPresenters()
var TxCmd = &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(doTxQuery),
}
func doTxQuery(cmd *cobra.Command, args []string) error {
// parse cli
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()
prover := proofs.NewTxProver(node)
proof, err := GetProof(node, prover, bkey, height)
if err != nil {
return err
}
// auto-determine which tx it was, over all registered tx types
info, err := TxPresenters.BruteForce(proof.Data())
if err != nil {
return err
}
// we can reuse this output for other commands for text/json
// unless they do something special like store a file to disk
return OutputProof(info, proof.BlockHeight())
}

111
commands/proxy/root.go Normal file
View File

@ -0,0 +1,111 @@
package proxy
import (
"net/http"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/tendermint/rpc/client"
"github.com/tendermint/tendermint/rpc/core"
rpc "github.com/tendermint/tendermint/rpc/lib/server"
certclient "github.com/tendermint/light-client/certifiers/client"
"github.com/tendermint/basecoin/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"
wsEndpoint = "/websocket"
)
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
c := commands.GetNode()
cert, err := commands.GetCertifier()
if err != nil {
return err
}
sc := certclient.Wrap(c, cert)
sc.Start()
r := routes(sc)
// 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(viper.GetString(bindFlag), mux, logger)
if err != nil {
return err
}
cmn.TrapSignal(func() {
// TODO: close up shop
})
return nil
}
// First step, proxy with no checks....
func routes(c client.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, ""),
}
}

49
commands/rpc/helpers.go Normal file
View File

@ -0,0 +1,49 @@
package rpc
import (
"fmt"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/basecoin/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
}

66
commands/rpc/insecure.go Normal file
View File

@ -0,0 +1,66 @@
package rpc
import (
"github.com/spf13/cobra"
"github.com/tendermint/basecoin/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()
if err != nil {
return err
}
return printResult(validators)
}

65
commands/rpc/root.go Normal file
View File

@ -0,0 +1,65 @@
package rpc
import (
"fmt"
"github.com/spf13/cobra"
"github.com/tendermint/go-wire/data"
"github.com/tendermint/tendermint/rpc/client"
certclient "github.com/tendermint/light-client/certifiers/client"
"github.com/tendermint/basecoin/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() (client.Client, error) {
// First, connect a client
c := commands.GetNode()
cert, err := commands.GetCertifier()
if err != nil {
return nil, err
}
sc := certclient.Wrap(c, cert)
return sc, 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
}

75
commands/rpc/secure.go Normal file
View File

@ -0,0 +1,75 @@
package rpc
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/basecoin/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)
}

43
commands/seeds/export.go Normal file
View File

@ -0,0 +1,43 @@
package seeds
import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/basecoin/commands"
)
var exportCmd = &cobra.Command{
Use: "export <file>",
Short: "Export selected seeds to given file",
Long: `Exports the most recent seed to a binary file.
If desired, you can select by an older height or validator hash.
`,
RunE: commands.RequireInit(exportSeed),
SilenceUsage: true,
}
func init() {
exportCmd.Flags().Int(heightFlag, 0, "Show the seed with closest height to this")
exportCmd.Flags().String(hashFlag, "", "Show the seed matching the validator hash")
RootCmd.AddCommand(exportCmd)
}
func exportSeed(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)
seed, err := loadSeed(trust, h, hash, "")
if err != nil {
return err
}
// now get the output file and write it
return seed.Write(path)
}

57
commands/seeds/import.go Normal file
View File

@ -0,0 +1,57 @@
package seeds
import (
"fmt"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/light-client/certifiers"
"github.com/tendermint/basecoin/commands"
)
const (
dryFlag = "dry-run"
)
var importCmd = &cobra.Command{
Use: "import <file>",
Short: "Imports a new seed from the given file",
Long: `Validate this file and update to the given seed if secure.`,
RunE: commands.RequireInit(importSeed),
SilenceUsage: true,
}
func init() {
importCmd.Flags().Bool(dryFlag, false, "Test the import fully, but do not import")
RootCmd.AddCommand(importCmd)
}
func importSeed(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]
seed, err := certifiers.LoadSeed(path)
if err != nil {
return err
}
// just do simple checks in --dry-run
if viper.GetBool(dryFlag) {
fmt.Printf("Testing seed %d/%X\n", seed.Height(), seed.Hash())
err = seed.ValidateBasic(cert.ChainID())
} else {
fmt.Printf("Importing seed %d/%X\n", seed.Height(), seed.Hash())
err = cert.Update(seed.Checkpoint, seed.Validators)
}
return err
}

15
commands/seeds/root.go Normal file
View File

@ -0,0 +1,15 @@
package seeds
import "github.com/spf13/cobra"
// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
Use: "seeds",
Short: "Verify seeds from your local store",
Long: `Seeds 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 seeds to maintain the security, which
is used to verify all header and merkle proofs.
`,
}

71
commands/seeds/show.go Normal file
View File

@ -0,0 +1,71 @@
package seeds
import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/light-client/certifiers"
"github.com/tendermint/basecoin/commands"
)
const (
heightFlag = "height"
hashFlag = "hash"
fileFlag = "file"
)
var showCmd = &cobra.Command{
Use: "show",
Short: "Show the details of one selected seed",
Long: `Shows the most recent downloaded key by default.
If desired, you can select by height, validator hash, or a file.
`,
RunE: commands.RequireInit(showSeed),
SilenceUsage: true,
}
func init() {
showCmd.Flags().Int(heightFlag, 0, "Show the seed with closest height to this")
showCmd.Flags().String(hashFlag, "", "Show the seed matching the validator hash")
showCmd.Flags().String(fileFlag, "", "Show the seed stored in the given file")
RootCmd.AddCommand(showCmd)
}
func loadSeed(p certifiers.Provider, h int, hash, file string) (seed certifiers.Seed, err error) {
// load the seed from the proper place
if h != 0 {
seed, err = p.GetByHeight(h)
} else if hash != "" {
var vhash []byte
vhash, err = hex.DecodeString(hash)
if err == nil {
seed, err = p.GetByHash(vhash)
}
} else if file != "" {
seed, err = certifiers.LoadSeed(file)
} else {
// default is latest seed
seed, err = certifiers.LatestSeed(p)
}
return
}
func showSeed(cmd *cobra.Command, args []string) error {
trust, _ := commands.GetProviders()
h := viper.GetInt(heightFlag)
hash := viper.GetString(hashFlag)
file := viper.GetString(fileFlag)
seed, err := loadSeed(trust, h, hash, file)
if err != nil {
return err
}
// now render it!
data, err := json.MarshalIndent(seed, "", " ")
fmt.Println(string(data))
return err
}

42
commands/seeds/update.go Normal file
View File

@ -0,0 +1,42 @@
package seeds
import (
"fmt"
"github.com/spf13/cobra"
"github.com/tendermint/light-client/certifiers"
"github.com/tendermint/basecoin/commands"
)
var updateCmd = &cobra.Command{
Use: "update",
Short: "Update seed to current chain state if possible",
RunE: commands.RequireInit(updateSeed),
SilenceUsage: true,
}
func init() {
RootCmd.AddCommand(updateCmd)
}
func updateSeed(cmd *cobra.Command, args []string) error {
cert, err := commands.GetCertifier()
if err != nil {
return err
}
// get the lastest from our source
seed, err := certifiers.LatestSeed(cert.SeedSource)
if err != nil {
return err
}
fmt.Printf("Trying to update to height: %d...\n", seed.Height())
// let the certifier do it's magic to update....
err = cert.Update(seed.Checkpoint, seed.Validators)
if err != nil {
return err
}
fmt.Println("Success!")
return nil
}

174
commands/txs/helpers.go Normal file
View File

@ -0,0 +1,174 @@
package txs
import (
"bufio"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"github.com/bgentry/speakeasy"
"github.com/mattn/go-isatty"
"github.com/pkg/errors"
"github.com/spf13/viper"
"github.com/tendermint/basecoin/commands"
crypto "github.com/tendermint/go-crypto"
keycmd "github.com/tendermint/go-crypto/cmd"
"github.com/tendermint/go-crypto/keys"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
lc "github.com/tendermint/light-client"
)
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(NameFlag)
manager := keycmd.GetKeyManager()
info, _ := manager.Get(name) // error -> empty pubkey
return info.PubKey
}
// Sign if it is Signable, otherwise, just convert it to bytes
func Sign(tx interface{}) (packet []byte, err error) {
name := viper.GetString(NameFlag)
manager := keycmd.GetKeyManager()
if sign, ok := tx.(keys.Signable); ok {
if name == "" {
return nil, errors.New("--name is required to sign tx")
}
packet, err = signTx(manager, sign, name)
} else if val, ok := tx.(lc.Value); ok {
packet = val.Bytes()
} else {
err = errors.Errorf("Reader returned invalid tx type: %#v\n", tx)
}
return
}
// SignAndPostTx 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 SignAndPostTx(tx Validatable) (*ctypes.ResultBroadcastTxCommit, error) {
// validate tx client-side
err := tx.ValidateBasic()
if err != nil {
return nil, err
}
// sign the tx if needed
packet, err := Sign(tx)
if err != nil {
return nil, err
}
// post the bytes
node := commands.GetNode()
return node.BroadcastTxCommit(packet)
}
// LoadJSON will read a json file from disk if --input is passed in
// template is a pointer to a struct that can hold the expected data (&MyTx{})
//
// If not data is provided, returns (false, nil)
// If data is provided and passes, returns (true, nil)
// If data is provided but not parsable, returns (true, err)
func LoadJSON(template interface{}) (bool, error) {
input := viper.GetString(InputFlag)
if input == "" {
return false, nil
}
// load the input
raw, err := readInput(input)
if err != nil {
return true, err
}
// parse the input
err = json.Unmarshal(raw, template)
if err != nil {
return true, err
}
return true, nil
}
// OutputTx prints the tx result to stdout
// TODO: something other than raw json?
func OutputTx(res *ctypes.ResultBroadcastTxCommit) error {
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) ([]byte, error) {
prompt := fmt.Sprintf("Please enter passphrase for %s: ", name)
pass, err := getPassword(prompt)
if err != nil {
return nil, err
}
err = manager.Sign(name, pass, tx)
if err != nil {
return nil, err
}
return tx.TxBytes()
}
func readInput(file string) ([]byte, error) {
var reader io.Reader
// get the input stream
if file == "-" {
reader = os.Stdin
} else {
f, err := os.Open(file)
if err != nil {
return nil, err
}
defer f.Close()
reader = f
}
// and read it all!
data, err := ioutil.ReadAll(reader)
return data, errors.WithStack(err)
}
// 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
}

19
commands/txs/root.go Normal file
View File

@ -0,0 +1,19 @@
package txs
import "github.com/spf13/cobra"
const (
NameFlag = "name"
InputFlag = "input"
)
// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
Use: "tx",
Short: "Create and post transactions to the node",
}
func init() {
RootCmd.PersistentFlags().String(NameFlag, "", "name to sign the tx")
RootCmd.PersistentFlags().String(InputFlag, "", "file with tx in json format")
}

View File

@ -4,7 +4,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
txcmd "github.com/tendermint/light-client/commands/txs"
txcmd "github.com/tendermint/basecoin/commands/txs"
"github.com/tendermint/basecoin"
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"

View File

@ -3,7 +3,7 @@ package commands
import (
"github.com/spf13/cobra"
proofcmd "github.com/tendermint/light-client/commands/proofs"
proofcmd "github.com/tendermint/basecoin/commands/proofs"
"github.com/tendermint/basecoin/docs/guide/counter/plugins/counter"
"github.com/tendermint/basecoin/stack"

View File

@ -6,11 +6,11 @@ import (
"github.com/spf13/cobra"
keycmd "github.com/tendermint/go-crypto/cmd"
"github.com/tendermint/light-client/commands"
"github.com/tendermint/light-client/commands/proofs"
"github.com/tendermint/light-client/commands/proxy"
"github.com/tendermint/light-client/commands/seeds"
"github.com/tendermint/light-client/commands/txs"
"github.com/tendermint/basecoin/commands"
"github.com/tendermint/basecoin/commands/proofs"
"github.com/tendermint/basecoin/commands/proxy"
"github.com/tendermint/basecoin/commands/seeds"
"github.com/tendermint/basecoin/commands/txs"
"github.com/tendermint/tmlibs/cli"
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"

16
glide.lock generated
View File

@ -1,5 +1,5 @@
hash: 6eb1119dccf2ab4d0adb870a14cb4408047119be53c8ec4afeaa281bd1d2b457
updated: 2017-06-28T13:09:42.542992443+02:00
hash: 2fec08220d5d8cbc791523583b85f3fb68e3d65ead6802198d9c879a9e295b46
updated: 2017-07-18T21:21:05.336445544+02:00
imports:
- name: github.com/bgentry/speakeasy
version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd
@ -111,6 +111,7 @@ imports:
- example/dummy
- server
- types
- version
- name: github.com/tendermint/ed25519
version: 1f52c6f8b8a5c7908aff4497c186af344b428925
subpackages:
@ -125,6 +126,7 @@ imports:
- keys/server
- keys/server/types
- keys/storage/filestorage
- keys/storage/memstorage
- keys/wordlist
- name: github.com/tendermint/go-wire
version: 5f88da3dbc1a72844e6dfaf274ce87f851d488eb
@ -132,17 +134,11 @@ imports:
- data
- data/base58
- name: github.com/tendermint/light-client
version: 489b726d8b358dbd9d8f6a15d18e8b9fe0a39269
version: d63415027075bc5d74a98a718393b59b5c4279a5
subpackages:
- certifiers
- certifiers/client
- certifiers/files
- commands
- commands/proofs
- commands/proxy
- commands/rpc
- commands/seeds
- commands/txs
- proofs
- name: github.com/tendermint/merkleeyes
version: 102aaf5a8ffda1846413fb22805a94def2045b9f
@ -151,7 +147,7 @@ imports:
- client
- iavl
- name: github.com/tendermint/tendermint
version: 3065059da7bb57714f08c7a6fcb97e4b36be0194
version: 695ad5fe2d70ec7b6fcfe0b46a73cc1b2d55e0ac
subpackages:
- blockchain
- cmd/tendermint/commands

View File

@ -22,13 +22,12 @@ import:
subpackages:
- data
- package: github.com/tendermint/light-client
version: develop
version: unstable
subpackages:
- commands
- commands/proofs
- commands/seeds
- commands/txs
- proofs
- certifiers
- certifiers/client
- certifiers/files
- package: github.com/tendermint/merkleeyes
version: develop
subpackages:

View File

@ -6,7 +6,7 @@ import (
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/tendermint/light-client/commands"
"github.com/tendermint/basecoin/commands"
"github.com/tendermint/basecoin"
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"

View File

@ -5,8 +5,8 @@ import (
"github.com/spf13/cobra"
lc "github.com/tendermint/light-client"
lcmd "github.com/tendermint/light-client/commands"
proofcmd "github.com/tendermint/light-client/commands/proofs"
lcmd "github.com/tendermint/basecoin/commands"
proofcmd "github.com/tendermint/basecoin/commands/proofs"
"github.com/tendermint/basecoin/modules/auth"
"github.com/tendermint/basecoin/modules/coin"

View File

@ -4,8 +4,8 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/light-client/commands"
txcmd "github.com/tendermint/light-client/commands/txs"
"github.com/tendermint/basecoin/commands"
txcmd "github.com/tendermint/basecoin/commands/txs"
"github.com/tendermint/basecoin"
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"

View File

@ -7,8 +7,8 @@ import (
"github.com/spf13/cobra"
lc "github.com/tendermint/light-client"
lcmd "github.com/tendermint/light-client/commands"
proofcmd "github.com/tendermint/light-client/commands/proofs"
lcmd "github.com/tendermint/basecoin/commands"
proofcmd "github.com/tendermint/basecoin/commands/proofs"
"github.com/tendermint/basecoin/modules/nonce"
"github.com/tendermint/basecoin/stack"