Merge pull request #174 from tendermint/feature/train-wreck-light-client

Moved all commands from light-client into basecoin
This commit is contained in:
Ethan Frey 2017-07-18 22:31:47 +02:00 committed by GitHub
commit b486ed35ab
40 changed files with 1913 additions and 203 deletions

View File

@ -27,6 +27,9 @@ test_unit:
test_cli: tests/cli/shunit2
# sudo apt-get install jq
./tests/cli/keys.sh
./tests/cli/rpc.sh
./tests/cli/init.sh
./tests/cli/basictx.sh
./tests/cli/counter.sh
./tests/cli/restart.sh

View File

@ -1,4 +1,4 @@
package commands
package auto
import (
"os"

116
client/commands/common.go Normal file
View File

@ -0,0 +1,116 @@
/*
Package commands contains any general setup/helpers valid for all subcommands
*/
package commands
import (
"encoding/hex"
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/light-client/certifiers"
"github.com/tendermint/light-client/certifiers/client"
"github.com/tendermint/light-client/certifiers/files"
"github.com/tendermint/tmlibs/cli"
cmn "github.com/tendermint/tmlibs/common"
rpcclient "github.com/tendermint/tendermint/rpc/client"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/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 rpcclient.NewHTTP(viper.GetString(NodeFlag), "/websocket")
}
// GetProviders creates a trusted (local) seed provider and a remote
// provider based on configuration.
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
}
// GetCertifier constructs a dynamic certifier from the config info
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
}
// ParseAddress parses an address of form:
// [<chain>:][<app>:]<hex address>
// into a basecoin.Actor.
// If app is not specified or "", then assume auth.NameSigs
func ParseAddress(input string) (res basecoin.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 = basecoin.Actor{
ChainID: chain,
App: app,
Address: addr,
}
return
}

346
client/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()
}
}

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/client/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
}

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)")
}

View File

@ -0,0 +1,46 @@
package proofs
import (
"github.com/spf13/cobra"
"github.com/tendermint/go-wire/data"
"github.com/tendermint/basecoin/client/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())
}

View File

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

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/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"
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, ""),
}
}

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/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
}

View File

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

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

View File

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

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/client/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)
}

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/client/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
}

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.
`,
}

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/client/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
}

View File

@ -0,0 +1,42 @@
package seeds
import (
"fmt"
"github.com/spf13/cobra"
"github.com/tendermint/light-client/certifiers"
"github.com/tendermint/basecoin/client/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
}

View File

@ -0,0 +1,188 @@
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"
crypto "github.com/tendermint/go-crypto"
keycmd "github.com/tendermint/go-crypto/cmd"
"github.com/tendermint/go-crypto/keys"
lc "github.com/tendermint/light-client"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/client/commands"
"github.com/tendermint/basecoin/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(NameFlag)
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 basecoin.Actor) {
// this could be much cooler with multisig...
signer := GetSigner()
if !signer.Empty() {
res = auth.SigPerm(signer.Address())
}
return res
}
// 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
}

View File

@ -0,0 +1,34 @@
package txs
import (
"github.com/pkg/errors"
wire "github.com/tendermint/go-wire"
"github.com/tendermint/light-client/proofs"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/tendermint/basecoin"
)
// BaseTxPresenter this decodes all basecoin tx
type BaseTxPresenter struct {
proofs.RawPresenter // this handles MakeKey as hex bytes
}
// ParseData - unmarshal raw bytes to a basecoin tx
func (BaseTxPresenter) ParseData(raw []byte) (interface{}, error) {
var tx basecoin.Tx
err := wire.ReadBinaryBytes(raw, &tx)
return tx, err
}
// ValidateResult returns an appropriate error if the server rejected the
// tx in CheckTx or DeliverTx
func ValidateResult(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)
}
return nil
}

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

@ -0,0 +1,45 @@
package txs
import (
"github.com/spf13/pflag"
"github.com/tendermint/basecoin"
)
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(basecoin.Tx) (basecoin.Tx, 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 basecoin.Tx) (basecoin.Tx, 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)
}
}

View File

@ -1,111 +0,0 @@
package commands
import (
"encoding/hex"
"fmt"
"strings"
"github.com/pkg/errors"
"github.com/spf13/pflag"
txcmd "github.com/tendermint/light-client/commands/txs"
cmn "github.com/tendermint/tmlibs/common"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/modules/auth"
)
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(basecoin.Tx) (basecoin.Tx, 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 basecoin.Tx) (basecoin.Tx, 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)
}
}
// ValidateResult returns an appropriate error if the server rejected the
// tx in CheckTx or DeliverTx
func ValidateResult(res *ctypes.ResultBroadcastTxCommit) error {
if res.CheckTx.IsErr() {
return fmt.Errorf("CheckTx: (%d): %s", res.CheckTx.Code, res.CheckTx.Log)
}
if res.DeliverTx.IsErr() {
return fmt.Errorf("DeliverTx: (%d): %s", res.DeliverTx.Code, res.DeliverTx.Log)
}
return nil
}
// ParseAddress parses an address of form:
// [<chain>:][<app>:]<hex address>
// into a basecoin.Actor.
// If app is not specified or "", then assume auth.NameSigs
func ParseAddress(input string) (res basecoin.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 = basecoin.Actor{
ChainID: chain,
App: app,
Address: addr,
}
return
}
// GetSignerAct returns the address of the signer of the tx
// (as we still only support single sig)
func GetSignerAct() (res basecoin.Actor) {
// this could be much cooler with multisig...
signer := txcmd.GetSigner()
if !signer.Empty() {
res = auth.SigPerm(signer.Address())
}
return res
}

View File

@ -1,20 +0,0 @@
package commands
import (
wire "github.com/tendermint/go-wire"
"github.com/tendermint/light-client/proofs"
"github.com/tendermint/basecoin"
)
// BaseTxPresenter this decodes all basecoin tx
type BaseTxPresenter struct {
proofs.RawPresenter // this handles MakeKey as hex bytes
}
// ParseData - unmarshal raw bytes to a basecoin tx
func (BaseTxPresenter) ParseData(raw []byte) (interface{}, error) {
var tx basecoin.Tx
err := wire.ReadBinaryBytes(raw, &tx)
return tx, err
}

View File

@ -8,15 +8,15 @@ 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/tmlibs/cli"
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"
"github.com/tendermint/basecoin/client/commands"
"github.com/tendermint/basecoin/client/commands/auto"
"github.com/tendermint/basecoin/client/commands/proofs"
"github.com/tendermint/basecoin/client/commands/proxy"
rpccmd "github.com/tendermint/basecoin/client/commands/rpc"
"github.com/tendermint/basecoin/client/commands/seeds"
txcmd "github.com/tendermint/basecoin/client/commands/txs"
authcmd "github.com/tendermint/basecoin/modules/auth/commands"
basecmd "github.com/tendermint/basecoin/modules/base/commands"
coincmd "github.com/tendermint/basecoin/modules/coin/commands"
@ -56,19 +56,19 @@ func main() {
coincmd.AccountQueryCmd,
noncecmd.NonceQueryCmd,
)
proofs.TxPresenters.Register("base", txcmd.BaseTxPresenter{})
// set up the middleware
bcmd.Middleware = bcmd.Wrappers{
txcmd.Middleware = txcmd.Wrappers{
feecmd.FeeWrapper{},
noncecmd.NonceWrapper{},
basecmd.ChainWrapper{},
authcmd.SigWrapper{},
}
bcmd.Middleware.Register(txs.RootCmd.PersistentFlags())
txcmd.Middleware.Register(txcmd.RootCmd.PersistentFlags())
// you will always want this for the base send command
proofs.TxPresenters.Register("base", bcmd.BaseTxPresenter{})
txs.RootCmd.AddCommand(
txcmd.RootCmd.AddCommand(
// This is the default transaction, optional in your app
coincmd.SendTxCmd,
)
@ -81,10 +81,10 @@ func main() {
seeds.RootCmd,
rpccmd.RootCmd,
proofs.RootCmd,
txs.RootCmd,
txcmd.RootCmd,
proxy.RootCmd,
VersionCmd,
bcmd.AutoCompleteCmd,
auto.AutoCompleteCmd,
)
cmd := cli.PrepareMainCmd(BaseCli, "BC", os.ExpandEnv("$HOME/.basecli"))

View File

@ -4,10 +4,8 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
txcmd "github.com/tendermint/light-client/commands/txs"
"github.com/tendermint/basecoin"
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"
txcmd "github.com/tendermint/basecoin/client/commands/txs"
"github.com/tendermint/basecoin/docs/guide/counter/plugins/counter"
"github.com/tendermint/basecoin/modules/coin"
)
@ -43,7 +41,7 @@ func counterTx(cmd *cobra.Command, args []string) error {
return err
}
tx, err = bcmd.Middleware.Wrap(tx)
tx, err = txcmd.Middleware.Wrap(tx)
if err != nil {
return err
}
@ -53,7 +51,7 @@ func counterTx(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
if err = bcmd.ValidateResult(bres); err != nil {
if err = txcmd.ValidateResult(bres); err != nil {
return err
}

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/client/commands/proofs"
"github.com/tendermint/basecoin/docs/guide/counter/plugins/counter"
"github.com/tendermint/basecoin/stack"

View File

@ -6,14 +6,13 @@ 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/tmlibs/cli"
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"
"github.com/tendermint/basecoin/client/commands"
"github.com/tendermint/basecoin/client/commands/proofs"
"github.com/tendermint/basecoin/client/commands/proxy"
"github.com/tendermint/basecoin/client/commands/seeds"
txcmd "github.com/tendermint/basecoin/client/commands/txs"
bcount "github.com/tendermint/basecoin/docs/guide/counter/cmd/countercli/commands"
authcmd "github.com/tendermint/basecoin/modules/auth/commands"
basecmd "github.com/tendermint/basecoin/modules/base/commands"
@ -50,17 +49,17 @@ func main() {
)
// set up the middleware
bcmd.Middleware = bcmd.Wrappers{
txcmd.Middleware = txcmd.Wrappers{
feecmd.FeeWrapper{},
noncecmd.NonceWrapper{},
basecmd.ChainWrapper{},
authcmd.SigWrapper{},
}
bcmd.Middleware.Register(txs.RootCmd.PersistentFlags())
txcmd.Middleware.Register(txcmd.RootCmd.PersistentFlags())
// Prepare transactions
proofs.TxPresenters.Register("base", bcmd.BaseTxPresenter{})
txs.RootCmd.AddCommand(
proofs.TxPresenters.Register("base", txcmd.BaseTxPresenter{})
txcmd.RootCmd.AddCommand(
// This is the default transaction, optional in your app
coincmd.SendTxCmd,
@ -75,7 +74,7 @@ func main() {
keycmd.RootCmd,
seeds.RootCmd,
proofs.RootCmd,
txs.RootCmd,
txcmd.RootCmd,
proxy.RootCmd,
)

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

@ -5,7 +5,7 @@ import (
"github.com/spf13/viper"
"github.com/tendermint/basecoin"
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"
txcmd "github.com/tendermint/basecoin/client/commands/txs"
"github.com/tendermint/basecoin/modules/auth"
)
@ -17,7 +17,7 @@ const (
// SigWrapper wraps a tx with a signature layer to hold pubkey sigs
type SigWrapper struct{}
var _ bcmd.Wrapper = SigWrapper{}
var _ txcmd.Wrapper = SigWrapper{}
// Wrap will wrap the tx with OneSig or MultiSig depending on flags
func (SigWrapper) Wrap(tx basecoin.Tx) (res basecoin.Tx, err error) {

View File

@ -6,10 +6,10 @@ import (
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/tendermint/light-client/commands"
"github.com/tendermint/basecoin/client/commands"
"github.com/tendermint/basecoin"
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"
txcmd "github.com/tendermint/basecoin/client/commands/txs"
"github.com/tendermint/basecoin/modules/base"
)
@ -21,7 +21,7 @@ const (
// ChainWrapper wraps a tx with an chain info and optional expiration
type ChainWrapper struct{}
var _ bcmd.Wrapper = ChainWrapper{}
var _ txcmd.Wrapper = ChainWrapper{}
// Wrap will wrap the tx with a ChainTx from the standard flags
func (ChainWrapper) Wrap(tx basecoin.Tx) (res basecoin.Tx, err error) {

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/client/commands"
proofcmd "github.com/tendermint/basecoin/client/commands/proofs"
"github.com/tendermint/basecoin/modules/auth"
"github.com/tendermint/basecoin/modules/coin"

View File

@ -4,11 +4,9 @@ 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"
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"
"github.com/tendermint/basecoin/client/commands"
txcmd "github.com/tendermint/basecoin/client/commands/txs"
"github.com/tendermint/basecoin/modules/coin"
)
@ -47,7 +45,7 @@ func doSendTx(cmd *cobra.Command, args []string) error {
return err
}
tx, err = bcmd.Middleware.Wrap(tx)
tx, err = txcmd.Middleware.Wrap(tx)
if err != nil {
return err
}
@ -57,7 +55,7 @@ func doSendTx(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
if err = bcmd.ValidateResult(bres); err != nil {
if err = txcmd.ValidateResult(bres); err != nil {
return err
}
@ -67,7 +65,7 @@ func doSendTx(cmd *cobra.Command, args []string) error {
func readSendTxFlags() (tx basecoin.Tx, err error) {
// parse to address
toAddr, err := bcmd.ParseAddress(viper.GetString(FlagTo))
toAddr, err := commands.ParseAddress(viper.GetString(FlagTo))
if err != nil {
return tx, err
}
@ -98,7 +96,7 @@ func readSendTxFlags() (tx basecoin.Tx, err error) {
func readFromAddr() (basecoin.Actor, error) {
from := viper.GetString(FlagFrom)
if from == "" {
return bcmd.GetSignerAct(), nil
return txcmd.GetSignerAct(), nil
}
return bcmd.ParseAddress(from)
return commands.ParseAddress(from)
}

View File

@ -5,7 +5,8 @@ import (
"github.com/spf13/viper"
"github.com/tendermint/basecoin"
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"
"github.com/tendermint/basecoin/client/commands"
txcmd "github.com/tendermint/basecoin/client/commands/txs"
"github.com/tendermint/basecoin/modules/coin"
"github.com/tendermint/basecoin/modules/fee"
)
@ -19,7 +20,7 @@ const (
// FeeWrapper wraps a tx with an optional fee payment
type FeeWrapper struct{}
var _ bcmd.Wrapper = FeeWrapper{}
var _ txcmd.Wrapper = FeeWrapper{}
// Wrap checks for FlagFee and if present wraps the tx with a
// FeeTx of the given amount, paid by the signer
@ -52,7 +53,7 @@ func (FeeWrapper) Register(fs *pflag.FlagSet) {
func readPayer() (basecoin.Actor, error) {
payer := viper.GetString(FlagPayer)
if payer == "" {
return bcmd.GetSignerAct(), nil
return txcmd.GetSignerAct(), nil
}
return bcmd.ParseAddress(payer)
return commands.ParseAddress(payer)
}

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/client/commands"
proofcmd "github.com/tendermint/basecoin/client/commands/proofs"
"github.com/tendermint/basecoin/modules/nonce"
"github.com/tendermint/basecoin/stack"

View File

@ -8,7 +8,8 @@ import (
"github.com/spf13/viper"
"github.com/tendermint/basecoin"
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"
"github.com/tendermint/basecoin/client/commands"
txcmd "github.com/tendermint/basecoin/client/commands/txs"
"github.com/tendermint/basecoin/modules/nonce"
)
@ -21,7 +22,7 @@ const (
// NonceWrapper wraps a tx with a nonce
type NonceWrapper struct{}
var _ bcmd.Wrapper = NonceWrapper{}
var _ txcmd.Wrapper = NonceWrapper{}
// Wrap grabs the sequence number from the flag and wraps
// the tx with this nonce. Grabs the permission from the signer,
@ -49,7 +50,7 @@ func (NonceWrapper) Register(fs *pflag.FlagSet) {
func readNonceKey() ([]basecoin.Actor, error) {
nonce := viper.GetString(FlagNonceKey)
if nonce == "" {
return []basecoin.Actor{bcmd.GetSignerAct()}, nil
return []basecoin.Actor{txcmd.GetSignerAct()}, nil
}
return parseActors(nonce)
}
@ -57,7 +58,7 @@ func readNonceKey() ([]basecoin.Actor, error) {
func parseActors(key string) (signers []basecoin.Actor, err error) {
var act basecoin.Actor
for _, k := range strings.Split(key, ",") {
act, err = bcmd.ParseAddress(k)
act, err = commands.ParseAddress(k)
if err != nil {
return
}

108
tests/cli/init.sh Executable file
View File

@ -0,0 +1,108 @@
#!/bin/bash
CLIENT_EXE=basecli
SERVER_EXE=basecoin
oneTimeSetUp() {
BASE=~/.bc_init_test
rm -rf "$BASE"
mkdir -p "$BASE"
SERVER="${BASE}/server"
SERVER_LOG="${BASE}/${SERVER_EXE}.log"
HEX="deadbeef1234deadbeef1234deadbeef1234aaaa"
${SERVER_EXE} init ${HEX} --home="$SERVER" >> "$SERVER_LOG"
if ! assertTrue $?; then return 1; fi
GENESIS_FILE=${SERVER}/genesis.json
CHAIN_ID=$(cat ${GENESIS_FILE} | jq .chain_id | tr -d \")
printf "starting ${SERVER_EXE}...\n"
${SERVER_EXE} start --home="$SERVER" >> "$SERVER_LOG" 2>&1 &
sleep 5
PID_SERVER=$!
disown
if ! ps $PID_SERVER >/dev/null; then
echo "**STARTUP FAILED**"
cat $SERVER_LOG
return 1
fi
}
oneTimeTearDown() {
printf "\nstopping ${SERVER_EXE}..."
kill -9 $PID_SERVER >/dev/null 2>&1
sleep 1
}
test01goodInit() {
export BCHOME=${BASE}/client-01
assertFalse "ls ${BCHOME} 2>/dev/null >&2"
echo y | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null
assertTrue "initialized light-client" $?
checkDir $BCHOME 3
}
test02badInit() {
export BCHOME=${BASE}/client-02
assertFalse "ls ${BCHOME} 2>/dev/null >&2"
# no node where we go
echo y | ${CLIENT_EXE} init --node=tcp://localhost:9999 --chain-id="${CHAIN_ID}" > /dev/null 2>&1
assertFalse "invalid init" $?
# dir there, but empty...
checkDir $BCHOME 0
# try with invalid chain id
echo y | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="bad-chain-id" > /dev/null 2>&1
assertFalse "invalid init" $?
checkDir $BCHOME 0
# reject the response
echo n | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null 2>&1
assertFalse "invalid init" $?
checkDir $BCHOME 0
}
test03noDoubleInit() {
export BCHOME=${BASE}/client-03
assertFalse "ls ${BCHOME} 2>/dev/null >&2"
# init properly
echo y | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null 2>&1
assertTrue "initialized light-client" $?
checkDir $BCHOME 3
# try again, and we get an error
echo y | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null 2>&1
assertFalse "warning on re-init" $?
checkDir $BCHOME 3
# unless we --force-reset
echo y | ${CLIENT_EXE} init --force-reset --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null 2>&1
assertTrue "re-initialized light-client" $?
checkDir $BCHOME 3
}
test04acceptGenesisFile() {
export BCHOME=${BASE}/client-04
assertFalse "ls ${BCHOME} 2>/dev/null >&2"
# init properly
${CLIENT_EXE} init --node=tcp://localhost:46657 --genesis=${GENESIS_FILE} > /dev/null 2>&1
assertTrue "initialized light-client" $?
checkDir $BCHOME 3
}
# XXX Ex: checkDir $DIR $FILES
# Makes sure directory exists and has the given number of files
checkDir() {
assertTrue "ls ${1} 2>/dev/null >&2"
assertEquals "no files created" "$2" $(ls $1 | wc -l)
}
# load and run these tests with shunit2!
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
. $DIR/shunit2

29
tests/cli/keys.sh Executable file
View File

@ -0,0 +1,29 @@
#!/bin/bash
CLIENT_EXE=basecli
oneTimeSetUp() {
PASS=qwertyuiop
export BCHOME=$HOME/.bc_keys_test
${CLIENT_EXE} reset_all
assertTrue $?
}
newKey(){
assertNotNull "keyname required" "$1"
KEYPASS=${2:-qwertyuiop}
echo $KEYPASS | ${CLIENT_EXE} keys new $1 >/dev/null 2>&1
assertTrue "created $1" $?
}
testMakeKeys() {
USER=demouser
assertFalse "already user $USER" "${CLIENT_EXE} keys get $USER"
newKey $USER
assertTrue "no user $USER" "${CLIENT_EXE} keys get $USER"
}
# load and run these tests with shunit2!
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
. $DIR/shunit2

130
tests/cli/rpc.sh Executable file
View File

@ -0,0 +1,130 @@
#!/bin/bash
CLIENT_EXE=basecli
SERVER_EXE=basecoin
oneTimeSetUp() {
BASE=~/.bc_init_test
rm -rf "$BASE"
mkdir -p "$BASE"
SERVER="${BASE}/server"
SERVER_LOG="${BASE}/${SERVER_EXE}.log"
HEX="deadbeef1234deadbeef1234deadbeef1234aaaa"
${SERVER_EXE} init ${HEX} --home="$SERVER" >> "$SERVER_LOG"
if ! assertTrue $?; then return 1; fi
GENESIS_FILE=${SERVER}/genesis.json
CHAIN_ID=$(cat ${GENESIS_FILE} | jq .chain_id | tr -d \")
printf "starting ${SERVER_EXE}...\n"
${SERVER_EXE} start --home="$SERVER" >> "$SERVER_LOG" 2>&1 &
sleep 5
PID_SERVER=$!
disown
if ! ps $PID_SERVER >/dev/null; then
echo "**STARTUP FAILED**"
cat $SERVER_LOG
return 1
fi
# this sets the base for all client queries in the tests
export BCHOME=${BASE}/client
${CLIENT_EXE} init --node=tcp://localhost:46657 --genesis=${GENESIS_FILE} > /dev/null 2>&1
if ! assertTrue "initialized light-client" "$?"; then
return 1
fi
}
oneTimeTearDown() {
printf "\nstopping ${SERVER_EXE}..."
kill -9 $PID_SERVER >/dev/null 2>&1
sleep 1
}
test01getInsecure() {
GENESIS=$(${CLIENT_EXE} rpc genesis)
assertTrue "get genesis" "$?"
MYCHAIN=$(echo ${GENESIS} | jq .genesis.chain_id | tr -d \")
assertEquals "genesis chain matches" "${CHAIN_ID}" "${MYCHAIN}"
STATUS=$(${CLIENT_EXE} rpc status)
assertTrue "get status" "$?"
SHEIGHT=$(echo ${STATUS} | jq .latest_block_height)
assertTrue "parsed status" "$?"
assertNotNull "has a height" "${SHEIGHT}"
VALS=$(${CLIENT_EXE} rpc validators)
assertTrue "get validators" "$?"
VHEIGHT=$(echo ${VALS} | jq .block_height)
assertTrue "parsed validators" "$?"
assertTrue "sensible heights: $SHEIGHT / $VHEIGHT" "test $VHEIGHT -ge $SHEIGHT"
VCNT=$(echo ${VALS} | jq '.validators | length')
assertEquals "one validator" "1" "$VCNT"
INFO=$(${CLIENT_EXE} rpc info)
assertTrue "get info" "$?"
DATA=$(echo $INFO | jq .response.data)
assertEquals "basecoin info" '"Basecoin v0.6.1"' "$DATA"
}
test02getSecure() {
HEIGHT=$(${CLIENT_EXE} rpc status | jq .latest_block_height)
assertTrue "get status" "$?"
# check block produces something reasonable
assertFalse "missing height" "${CLIENT_EXE} rpc block"
BLOCK=$(${CLIENT_EXE} rpc block --height=$HEIGHT)
assertTrue "get block" "$?"
MHEIGHT=$(echo $BLOCK | jq .block_meta.header.height)
assertEquals "meta height" "${HEIGHT}" "${MHEIGHT}"
BHEIGHT=$(echo $BLOCK | jq .block.header.height)
assertEquals "meta height" "${HEIGHT}" "${BHEIGHT}"
# check commit produces something reasonable
assertFalse "missing height" "${CLIENT_EXE} rpc commit"
let "CHEIGHT = $HEIGHT - 1"
COMMIT=$(${CLIENT_EXE} rpc commit --height=$CHEIGHT)
assertTrue "get commit" "$?"
HHEIGHT=$(echo $COMMIT | jq .header.height)
assertEquals "commit height" "${CHEIGHT}" "${HHEIGHT}"
assertEquals "canonical" "true" $(echo $COMMIT | jq .canonical)
BSIG=$(echo $BLOCK | jq .block.last_commit)
CSIG=$(echo $COMMIT | jq .commit)
assertEquals "block and commit" "$BSIG" "$CSIG"
# now let's get some headers
# assertFalse "missing height" "${CLIENT_EXE} rpc headers"
HEADERS=$(${CLIENT_EXE} rpc headers --min=$CHEIGHT --max=$HEIGHT)
assertTrue "get headers" "$?"
assertEquals "proper height" "$HEIGHT" $(echo $HEADERS | jq '.last_height')
assertEquals "two headers" "2" $(echo $HEADERS | jq '.block_metas | length')
# should we check these headers?
CHEAD=$(echo $COMMIT | jq .header)
# most recent first, so the commit header is second....
HHEAD=$(echo $HEADERS | jq .block_metas[1].header)
assertEquals "commit and header" "$CHEAD" "$HHEAD"
}
test03waiting() {
START=$(${CLIENT_EXE} rpc status | jq .latest_block_height)
assertTrue "get status" "$?"
let "NEXT = $START + 5"
assertFalse "no args" "${CLIENT_EXE} rpc wait"
assertFalse "too long" "${CLIENT_EXE} rpc wait --height=1234"
assertTrue "normal wait" "${CLIENT_EXE} rpc wait --height=$NEXT"
STEP=$(${CLIENT_EXE} rpc status | jq .latest_block_height)
assertEquals "wait until height" "$NEXT" "$STEP"
let "NEXT = $STEP + 3"
assertTrue "${CLIENT_EXE} rpc wait --delta=3"
STEP=$(${CLIENT_EXE} rpc status | jq .latest_block_height)
assertEquals "wait for delta" "$NEXT" "$STEP"
}
# load and run these tests with shunit2!
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
. $DIR/shunit2