Moved all commands from light-client into basecoin
This commit is contained in:
parent
f41aed4945
commit
a9e4a94402
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)")
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -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, ""),
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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.
|
||||
`,
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue