Merge pull request #176 from tendermint/feature/cli-cleanup-aka-confuse-rigel

Feature/cli cleanup aka confuse rigel
This commit is contained in:
Ethan Frey 2017-07-20 15:37:52 +02:00 committed by GitHub
commit d885413469
74 changed files with 3005 additions and 547 deletions

View File

@ -19,7 +19,8 @@ dist:
benchmark:
@go test -bench=. ./modules/...
test: test_unit test_cli test_tutorial
#test: test_unit test_cli test_tutorial
test: test_unit test_cli
test_unit:
@go test `glide novendor`
@ -27,7 +28,11 @@ 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/roles.sh
./tests/cli/counter.sh
./tests/cli/restart.sh
# @./tests/cli/ibc.sh

View File

@ -16,6 +16,7 @@ import (
"github.com/tendermint/basecoin/modules/coin"
"github.com/tendermint/basecoin/modules/fee"
"github.com/tendermint/basecoin/modules/nonce"
"github.com/tendermint/basecoin/modules/roles"
"github.com/tendermint/basecoin/stack"
sm "github.com/tendermint/basecoin/state"
"github.com/tendermint/basecoin/version"
@ -56,14 +57,19 @@ func NewBasecoin(handler basecoin.Handler, eyesCli *eyes.Client, logger log.Logg
// DefaultHandler - placeholder to just handle sendtx
func DefaultHandler(feeDenom string) basecoin.Handler {
// use the default stack
h := coin.NewHandler()
d := stack.NewDispatcher(stack.WrapHandler(h))
c := coin.NewHandler()
r := roles.NewHandler()
d := stack.NewDispatcher(
stack.WrapHandler(c),
stack.WrapHandler(r),
)
return stack.New(
base.Logger{},
stack.Recovery{},
auth.Signatures{},
base.Chain{},
nonce.ReplayCheck{},
roles.NewMiddleware(),
fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank),
).Use(d)
}
@ -139,7 +145,9 @@ func (app *Basecoin) CheckTx(txBytes []byte) abci.Result {
return errors.Result(err)
}
// TODO: can we abstract this setup and commit logic??
// we also need to discard error changes, so we don't increment checktx
// sequence on error, but not delivertx
cache := app.cacheState.CacheWrap()
ctx := stack.NewContext(
app.state.GetChainID(),
app.height,
@ -147,11 +155,12 @@ func (app *Basecoin) CheckTx(txBytes []byte) abci.Result {
)
// checktx generally shouldn't touch the state, but we don't care
// here on the framework level, since the cacheState is thrown away next block
res, err := app.handler.CheckTx(ctx, app.cacheState, tx)
res, err := app.handler.CheckTx(ctx, cache, tx)
if err != nil {
return errors.Result(err)
}
cache.CacheSync()
return res.ToABCI()
}

View File

@ -7,10 +7,12 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/basecoin/modules/coin"
eyescli "github.com/tendermint/merkleeyes/client"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin/modules/coin"
)
const genesisFilepath = "./testdata/genesis.json"

View File

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

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

@ -0,0 +1,141 @@
/*
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
}
// ParseActor parses an address of form:
// [<chain>:][<app>:]<hex address>
// into a basecoin.Actor.
// If app is not specified or "", then assume auth.NameSigs
func ParseActor(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
}
// ParseActors takes a comma-separated list of actors and parses them into
// a slice
func ParseActors(key string) (signers []basecoin.Actor, err error) {
var act basecoin.Actor
for _, k := range strings.Split(key, ",") {
act, err = ParseActor(k)
if err != nil {
return
}
signers = append(signers, act)
}
return
}
// GetOneArg makes sure there is exactly one positional argument
func GetOneArg(args []string, argname string) (string, error) {
if len(args) == 0 {
return "", errors.Errorf("Missing required argument [%s]", argname)
}
if len(args) > 1 {
return "", errors.Errorf("Only accepts one argument [%s]", argname)
}
return args[0], nil
}

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/light-client/certifiers"
"github.com/tendermint/light-client/certifiers/files"
"github.com/tendermint/tmlibs/cli"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tendermint/types"
)
var (
dirPerm = os.FileMode(0700)
)
//nolint
const (
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,118 @@
package proofs
import (
"fmt"
"github.com/pkg/errors"
"github.com/spf13/viper"
wire "github.com/tendermint/go-wire"
"github.com/tendermint/go-wire/data"
lc "github.com/tendermint/light-client"
"github.com/tendermint/light-client/proofs"
"github.com/tendermint/tendermint/rpc/client"
"github.com/tendermint/basecoin/client/commands"
)
// 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,47 @@
package proofs
import (
"github.com/spf13/cobra"
"github.com/tendermint/go-wire/data"
"github.com/tendermint/light-client/proofs"
"github.com/tendermint/basecoin/client/commands"
)
// KeyQueryCmd - CLI command to query a state by key with proof
var KeyQueryCmd = &cobra.Command{
Use: "key [key]",
Short: "Handle proofs for state of abci app",
Long: `This will look up a given key in the abci app, verify the proof,
and output it as hex.
If you want json output, use an app-specific command that knows key and value structure.`,
RunE: commands.RequireInit(keyQueryCmd),
}
// Note: we cannot yse GetAndParseAppProof here, as we don't use go-wire to
// parse the object, but rather return the raw bytes
func keyQueryCmd(cmd *cobra.Command, args []string) error {
// parse cli
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,52 @@
package proofs
import (
"github.com/spf13/cobra"
"github.com/tendermint/light-client/proofs"
"github.com/tendermint/basecoin/client/commands"
)
//nolint TODO add description
var TxPresenters = proofs.NewPresenters()
// TxQueryCmd - CLI command to query a transaction with proof
var TxQueryCmd = &cobra.Command{
Use: "tx [txhash]",
Short: "Handle proofs of commited txs",
Long: `Proofs allows you to validate abci state with merkle proofs.
These proofs tie the data to a checkpoint, which is managed by "seeds".
Here we can validate these proofs and import/export them to prove specific
data to other peers as needed.
`,
RunE: commands.RequireInit(txQueryCmd),
}
func txQueryCmd(cmd *cobra.Command, args []string) error {
// parse cli
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,110 @@
package proxy
import (
"net/http"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
certclient "github.com/tendermint/light-client/certifiers/client"
"github.com/tendermint/tendermint/rpc/client"
"github.com/tendermint/tendermint/rpc/core"
rpc "github.com/tendermint/tendermint/rpc/lib/server"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
"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,67 @@
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"
certclient "github.com/tendermint/light-client/certifiers/client"
"github.com/tendermint/tendermint/rpc/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,76 @@
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,44 @@
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,59 @@
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,73 @@
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,44 @@
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,245 @@
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"
wire "github.com/tendermint/go-wire"
"github.com/tendermint/go-wire/data"
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(FlagName)
manager := keycmd.GetKeyManager()
info, _ := manager.Get(name) // error -> empty pubkey
return info.PubKey
}
// GetSignerAct returns the address of the signer of the tx
// (as we still only support single sig)
func GetSignerAct() (res basecoin.Actor) {
// this could be much cooler with multisig...
signer := GetSigner()
if !signer.Empty() {
res = auth.SigPerm(signer.Address())
}
return res
}
// DoTx is a helper function for the lazy :)
//
// It uses only public functions and goes through the standard sequence of
// wrapping the tx with middleware layers, signing it, either preparing it,
// or posting it and displaying the result.
//
// If you want a non-standard flow, just call the various functions directly.
// eg. if you already set the middleware layers in your code, or want to
// output in another format.
func DoTx(tx basecoin.Tx) (err error) {
tx, err = Middleware.Wrap(tx)
if err != nil {
return err
}
err = SignTx(tx)
if err != nil {
return err
}
bres, err := PrepareOrPostTx(tx)
if err != nil {
return err
}
if bres == nil {
return nil // successful prep, nothing left to do
}
return OutputTx(bres) // print response of the post
}
// SignTx will validate the tx, and signs it if it is wrapping a Signable.
// Modifies tx in place, and returns an error if it should sign but couldn't
func SignTx(tx basecoin.Tx) error {
// validate tx client-side
err := tx.ValidateBasic()
if err != nil {
return err
}
name := viper.GetString(FlagName)
manager := keycmd.GetKeyManager()
if sign, ok := tx.Unwrap().(keys.Signable); ok {
// TODO: allow us not to sign? if so then what use?
if name == "" {
return errors.New("--name is required to sign tx")
}
err = signTx(manager, sign, name)
}
return err
}
// PrepareOrPostTx checks the flags to decide to prepare the tx for future
// multisig, or to post it to the node. Returns error on any failure.
// If no error and the result is nil, it means it already wrote to file,
// no post, no need to do more.
func PrepareOrPostTx(tx basecoin.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
wrote, err := PrepareTx(tx)
// error in prep
if err != nil {
return nil, err
}
// successfully wrote the tx!
if wrote {
return nil, nil
}
// or try to post it
return PostTx(tx)
}
// PrepareTx checks for FlagPrepare and if set, write the tx as json
// to the specified location for later multi-sig. Returns true if it
// handled the tx (no futher work required), false if it did nothing
// (and we should post the tx)
func PrepareTx(tx basecoin.Tx) (bool, error) {
prep := viper.GetString(FlagPrepare)
if prep == "" {
return false, nil
}
js, err := data.ToJSON(tx)
if err != nil {
return false, err
}
err = writeOutput(prep, js)
if err != nil {
return false, err
}
return true, nil
}
// PostTx does all work once we construct a proper struct
// it validates the data, signs if needed, transforms to bytes,
// and posts to the node.
func PostTx(tx basecoin.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
packet := wire.BinaryBytes(tx)
// post the bytes
node := commands.GetNode()
return node.BroadcastTxCommit(packet)
}
// OutputTx validates if success and prints the tx result to stdout
func OutputTx(res *ctypes.ResultBroadcastTxCommit) error {
if res.CheckTx.IsErr() {
return errors.Errorf("CheckTx: (%d): %s", res.CheckTx.Code, res.CheckTx.Log)
}
if res.DeliverTx.IsErr() {
return errors.Errorf("DeliverTx: (%d): %s", res.DeliverTx.Code, res.DeliverTx.Log)
}
js, err := json.MarshalIndent(res, "", " ")
if err != nil {
return err
}
fmt.Println(string(js))
return nil
}
func signTx(manager keys.Manager, tx keys.Signable, name string) error {
prompt := fmt.Sprintf("Please enter passphrase for %s: ", name)
pass, err := getPassword(prompt)
if err != nil {
return err
}
return manager.Sign(name, pass, tx)
}
// if we read from non-tty, we just need to init the buffer reader once,
// in case we try to read multiple passwords
var buf *bufio.Reader
func inputIsTty() bool {
return isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd())
}
func stdinPassword() (string, error) {
if buf == nil {
buf = bufio.NewReader(os.Stdin)
}
pass, err := buf.ReadString('\n')
if err != nil {
return "", err
}
return strings.TrimSpace(pass), nil
}
func getPassword(prompt string) (pass string, err error) {
if inputIsTty() {
pass, err = speakeasy.Ask(prompt)
} else {
pass, err = stdinPassword()
}
return
}
func writeOutput(file string, d []byte) error {
var writer io.Writer
if file == "-" {
writer = os.Stdout
} else {
f, err := os.Create(file)
if err != nil {
return errors.WithStack(err)
}
defer f.Close()
writer = f
}
_, err := writer.Write(d)
// this returns nil if err == nil
return errors.WithStack(err)
}
func readInput(file string) ([]byte, error) {
var reader io.Reader
// get the input stream
if file == "" || file == "-" {
reader = os.Stdin
} else {
f, err := os.Open(file)
if err != nil {
return nil, errors.WithStack(err)
}
defer f.Close()
reader = f
}
// and read it all!
data, err := ioutil.ReadAll(reader)
return data, errors.WithStack(err)
}

View File

@ -0,0 +1,20 @@
package txs
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

@ -0,0 +1,62 @@
package txs
import (
"encoding/json"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/basecoin"
)
// nolint
const (
FlagName = "name"
FlagIn = "in"
FlagPrepare = "prepare"
)
// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
Use: "tx",
Short: "Post tx from json input",
RunE: doRawTx,
}
func init() {
RootCmd.PersistentFlags().String(FlagName, "", "name to sign the tx")
// TODO: prepare needs to override the SignAndPost somehow to SignAndSave
RootCmd.PersistentFlags().String(FlagPrepare, "", "file to store prepared tx")
RootCmd.Flags().String(FlagIn, "", "file with tx in json format")
}
func doRawTx(cmd *cobra.Command, args []string) error {
raw, err := readInput(viper.GetString(FlagIn))
if err != nil {
return err
}
// parse the input
var tx basecoin.Tx
err = json.Unmarshal(raw, &tx)
if err != nil {
return errors.WithStack(err)
}
// sign it
err = SignTx(tx)
if err != nil {
return err
}
// otherwise, post it and display response
bres, err := PrepareOrPostTx(tx)
if err != nil {
return err
}
if bres == nil {
return nil // successful prep, nothing left to do
}
return OutputTx(bres) // print response of the post
}

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

@ -0,0 +1,17 @@
package commands
import (
"fmt"
"github.com/spf13/cobra"
"github.com/tendermint/basecoin/version"
)
// VersionCmd - command to show the application version
var VersionCmd = &cobra.Command{
Use: "version",
Short: "Show version info",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(version.Version)
},
}

View File

@ -1,220 +0,0 @@
package commands
import (
"encoding/hex"
"fmt"
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/light-client/commands"
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"
"github.com/tendermint/basecoin/modules/base"
"github.com/tendermint/basecoin/modules/coin"
"github.com/tendermint/basecoin/modules/fee"
"github.com/tendermint/basecoin/modules/nonce"
)
//-------------------------
// SendTx
// SendTxCmd is CLI command to send tokens between basecoin accounts
var SendTxCmd = &cobra.Command{
Use: "send",
Short: "send tokens from one account to another",
RunE: commands.RequireInit(doSendTx),
}
//nolint
const (
FlagTo = "to"
FlagAmount = "amount"
FlagFee = "fee"
FlagGas = "gas"
FlagExpires = "expires"
FlagSequence = "sequence"
)
func init() {
flags := SendTxCmd.Flags()
flags.String(FlagTo, "", "Destination address for the bits")
flags.String(FlagAmount, "", "Coins to send in the format <amt><coin>,<amt><coin>...")
flags.String(FlagFee, "0mycoin", "Coins for the transaction fee of the format <amt><coin>")
flags.Uint64(FlagGas, 0, "Amount of gas for this transaction")
flags.Uint64(FlagExpires, 0, "Block height at which this tx expires")
flags.Int(FlagSequence, -1, "Sequence number for this transaction")
}
// doSendTx is an example of how to make a tx
func doSendTx(cmd *cobra.Command, args []string) error {
// load data from json or flags
var tx basecoin.Tx
found, err := txcmd.LoadJSON(&tx)
if err != nil {
return err
}
if !found {
tx, err = readSendTxFlags()
}
if err != nil {
return err
}
// TODO: make this more flexible for middleware
tx, err = WrapFeeTx(tx)
if err != nil {
return err
}
tx, err = WrapNonceTx(tx)
if err != nil {
return err
}
tx, err = WrapChainTx(tx)
if err != nil {
return err
}
// Note: this is single sig (no multi sig yet)
stx := auth.NewSig(tx)
// Sign if needed and post. This it the work-horse
bres, err := txcmd.SignAndPostTx(stx)
if err != nil {
return err
}
if err = ValidateResult(bres); err != nil {
return err
}
// Output result
return txcmd.OutputTx(bres)
}
// 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
}
// WrapNonceTx grabs the sequence number from the flag and wraps
// the tx with this nonce. Grabs the permission from the signer,
// as we still only support single sig on the cli
func WrapNonceTx(tx basecoin.Tx) (res basecoin.Tx, err error) {
//add the nonce tx layer to the tx
seq := viper.GetInt(FlagSequence)
if seq < 0 {
return res, fmt.Errorf("sequence must be greater than 0")
}
signers := []basecoin.Actor{GetSignerAct()}
res = nonce.NewTx(uint32(seq), signers, tx)
return
}
// WrapFeeTx checks for FlagFee and if present wraps the tx with a
// FeeTx of the given amount, paid by the signer
func WrapFeeTx(tx basecoin.Tx) (res basecoin.Tx, err error) {
//parse the fee and amounts into coin types
toll, err := coin.ParseCoin(viper.GetString(FlagFee))
if err != nil {
return res, err
}
// if no fee, do nothing, otherwise wrap it
if toll.IsZero() {
return tx, nil
}
res = fee.NewFee(tx, toll, GetSignerAct())
return
}
// WrapChainTx will wrap the tx with a ChainTx from the standard flags
func WrapChainTx(tx basecoin.Tx) (res basecoin.Tx, err error) {
expires := viper.GetInt64(FlagExpires)
chain := commands.GetChainID()
if chain == "" {
return res, errors.New("No chain-id provided")
}
res = base.NewChainTx(chain, uint64(expires), tx)
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
}
func readSendTxFlags() (tx basecoin.Tx, err error) {
// parse to address
chain, to, err := parseChainAddress(viper.GetString(FlagTo))
if err != nil {
return tx, err
}
toAddr := auth.SigPerm(to)
toAddr.ChainID = chain
amountCoins, err := coin.ParseCoins(viper.GetString(FlagAmount))
if err != nil {
return tx, err
}
// craft the inputs and outputs
ins := []coin.TxInput{{
Address: GetSignerAct(),
Coins: amountCoins,
}}
outs := []coin.TxOutput{{
Address: toAddr,
Coins: amountCoins,
}}
return coin.NewSendTx(ins, outs), nil
}
func parseChainAddress(toFlag string) (string, []byte, error) {
var toHex string
var chainPrefix string
spl := strings.Split(toFlag, "/")
switch len(spl) {
case 1:
toHex = spl[0]
case 2:
chainPrefix = spl[0]
toHex = spl[1]
default:
return "", nil, errors.Errorf("To address has too many slashes")
}
// convert destination address to bytes
to, err := hex.DecodeString(cmn.StripHex(toHex))
if err != nil {
return "", nil, errors.Errorf("To address is invalid hex: %v\n", err)
}
return chainPrefix, to, nil
}
/** TODO copied from basecoin cli - put in common somewhere? **/
// ParseHexFlag parses a flag string to byte array
func ParseHexFlag(flag string) ([]byte, error) {
return hex.DecodeString(cmn.StripHex(viper.GetString(flag)))
}

View File

@ -1,86 +0,0 @@
package commands
import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/tendermint/basecoin"
wire "github.com/tendermint/go-wire"
lc "github.com/tendermint/light-client"
lcmd "github.com/tendermint/light-client/commands"
proofcmd "github.com/tendermint/light-client/commands/proofs"
"github.com/tendermint/light-client/proofs"
"github.com/tendermint/basecoin/modules/auth"
"github.com/tendermint/basecoin/modules/coin"
"github.com/tendermint/basecoin/modules/nonce"
"github.com/tendermint/basecoin/stack"
)
// AccountQueryCmd - command to query an account
var AccountQueryCmd = &cobra.Command{
Use: "account [address]",
Short: "Get details of an account, with proof",
RunE: lcmd.RequireInit(doAccountQuery),
}
func doAccountQuery(cmd *cobra.Command, args []string) error {
addr, err := proofcmd.ParseHexKey(args, "address")
if err != nil {
return err
}
key := stack.PrefixedKey(coin.NameCoin, auth.SigPerm(addr).Bytes())
acc := coin.Account{}
proof, err := proofcmd.GetAndParseAppProof(key, &acc)
if lc.IsNoDataErr(err) {
return errors.Errorf("Account bytes are empty for address %X ", addr)
} else if err != nil {
return err
}
return proofcmd.OutputProof(acc, proof.BlockHeight())
}
// NonceQueryCmd - command to query an nonce account
var NonceQueryCmd = &cobra.Command{
Use: "nonce [address]",
Short: "Get details of a nonce sequence number, with proof",
RunE: lcmd.RequireInit(doNonceQuery),
}
func doNonceQuery(cmd *cobra.Command, args []string) error {
addr, err := proofcmd.ParseHexKey(args, "address")
if err != nil {
return err
}
act := []basecoin.Actor{basecoin.NewActor(
auth.NameSigs,
addr,
)}
key := stack.PrefixedKey(nonce.NameNonce, nonce.GetSeqKey(act))
var seq uint32
proof, err := proofcmd.GetAndParseAppProof(key, &seq)
if lc.IsNoDataErr(err) {
return errors.Errorf("Sequence is empty for address %X ", addr)
} else if err != nil {
return err
}
return proofcmd.OutputProof(seq, proof.BlockHeight())
}
// 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

@ -6,16 +6,21 @@ 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"
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"
coincmd "github.com/tendermint/basecoin/cmd/basecoin/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"
feecmd "github.com/tendermint/basecoin/modules/fee/commands"
noncecmd "github.com/tendermint/basecoin/modules/nonce/commands"
rolecmd "github.com/tendermint/basecoin/modules/roles/commands"
)
// BaseCli - main basecoin client command
@ -36,17 +41,30 @@ func main() {
// Prepare queries
proofs.RootCmd.AddCommand(
// These are default parsers, but optional in your app (you can remove key)
proofs.TxCmd,
proofs.KeyCmd,
bcmd.AccountQueryCmd,
bcmd.NonceQueryCmd,
proofs.TxQueryCmd,
proofs.KeyQueryCmd,
coincmd.AccountQueryCmd,
noncecmd.NonceQueryCmd,
rolecmd.RoleQueryCmd,
)
proofs.TxPresenters.Register("base", txcmd.BaseTxPresenter{})
// set up the middleware
txcmd.Middleware = txcmd.Wrappers{
feecmd.FeeWrapper{},
rolecmd.RoleWrapper{},
noncecmd.NonceWrapper{},
basecmd.ChainWrapper{},
authcmd.SigWrapper{},
}
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
bcmd.SendTxCmd,
coincmd.SendTxCmd,
// this enables creating roles
rolecmd.CreateRoleTxCmd,
)
// Set up the various commands to use
@ -57,10 +75,10 @@ func main() {
seeds.RootCmd,
rpccmd.RootCmd,
proofs.RootCmd,
txs.RootCmd,
txcmd.RootCmd,
proxy.RootCmd,
coincmd.VersionCmd,
bcmd.AutoCompleteCmd,
commands.VersionCmd,
auto.AutoCompleteCmd,
)
cmd := cli.PrepareMainCmd(BaseCli, "BC", os.ExpandEnv("$HOME/.basecli"))

View File

@ -8,8 +8,6 @@ import (
"path"
"strings"
//"github.com/pkg/errors"
"github.com/spf13/viper"
"github.com/tendermint/go-crypto"

View File

@ -3,9 +3,10 @@ package main
import (
"os"
"github.com/tendermint/tmlibs/cli"
"github.com/tendermint/basecoin/app"
"github.com/tendermint/basecoin/cmd/basecoin/commands"
"github.com/tendermint/tmlibs/cli"
)
func main() {

View File

@ -4,12 +4,9 @@ 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/auth"
"github.com/tendermint/basecoin/modules/coin"
)
@ -34,54 +31,14 @@ func init() {
fs := CounterTxCmd.Flags()
fs.String(FlagCountFee, "", "Coins to send in the format <amt><coin>,<amt><coin>...")
fs.Bool(FlagValid, false, "Is count valid?")
fs.String(bcmd.FlagFee, "0mycoin", "Coins for the transaction fee of the format <amt><coin>")
fs.Int(bcmd.FlagSequence, -1, "Sequence number for this transaction")
}
// TODO: counterTx is very similar to the sendtx one,
// maybe we can pull out some common patterns?
func counterTx(cmd *cobra.Command, args []string) error {
// load data from json or flags
var tx basecoin.Tx
found, err := txcmd.LoadJSON(&tx)
tx, err := readCounterTxFlags()
if err != nil {
return err
}
if !found {
tx, err = readCounterTxFlags()
}
if err != nil {
return err
}
// TODO: make this more flexible for middleware
tx, err = bcmd.WrapFeeTx(tx)
if err != nil {
return err
}
tx, err = bcmd.WrapNonceTx(tx)
if err != nil {
return err
}
tx, err = bcmd.WrapChainTx(tx)
if err != nil {
return err
}
stx := auth.NewSig(tx)
// Sign if needed and post. This it the work-horse
bres, err := txcmd.SignAndPostTx(stx)
if err != nil {
return err
}
if err = bcmd.ValidateResult(bres); err != nil {
return err
}
// Output result
return txcmd.OutputTx(bres)
return txcmd.DoTx(tx)
}
func readCounterTxFlags() (tx basecoin.Tx, err error) {

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,15 +6,19 @@ 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"
coincmd "github.com/tendermint/basecoin/modules/coin/commands"
feecmd "github.com/tendermint/basecoin/modules/fee/commands"
noncecmd "github.com/tendermint/basecoin/modules/nonce/commands"
)
// BaseCli represents the base command when called without any subcommands
@ -35,19 +39,29 @@ func main() {
// Prepare queries
proofs.RootCmd.AddCommand(
// These are default parsers, optional in your app
proofs.TxCmd,
proofs.KeyCmd,
bcmd.AccountQueryCmd,
proofs.TxQueryCmd,
proofs.KeyQueryCmd,
coincmd.AccountQueryCmd,
noncecmd.NonceQueryCmd,
// XXX IMPORTANT: here is how you add custom query commands in your app
bcount.CounterQueryCmd,
)
// set up the middleware
txcmd.Middleware = txcmd.Wrappers{
feecmd.FeeWrapper{},
noncecmd.NonceWrapper{},
basecmd.ChainWrapper{},
authcmd.SigWrapper{},
}
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
bcmd.SendTxCmd,
coincmd.SendTxCmd,
// XXX IMPORTANT: here is how you add custom tx construction for your app
bcount.CounterTxCmd,
@ -60,7 +74,7 @@ func main() {
keycmd.RootCmd,
seeds.RootCmd,
proofs.RootCmd,
txs.RootCmd,
txcmd.RootCmd,
proxy.RootCmd,
)

View File

@ -5,27 +5,17 @@ import (
"fmt"
"reflect"
"github.com/pkg/errors"
abci "github.com/tendermint/abci/types"
)
var (
errDecoding = fmt.Errorf("Error decoding input")
errUnauthorized = fmt.Errorf("Unauthorized")
errInvalidSignature = fmt.Errorf("Invalid Signature")
errTooLarge = fmt.Errorf("Input size too large")
errNoSigners = fmt.Errorf("There are no signers")
errMissingSignature = fmt.Errorf("Signature missing")
errTooManySignatures = fmt.Errorf("Too many signatures")
errNoChain = fmt.Errorf("No chain id provided")
errTxEmpty = fmt.Errorf("The provided Tx is empty")
errWrongChain = fmt.Errorf("Wrong chain for tx")
errUnknownTxType = fmt.Errorf("Tx type unknown")
errInvalidFormat = fmt.Errorf("Invalid format")
errUnknownModule = fmt.Errorf("Unknown module")
errExpired = fmt.Errorf("Tx expired")
errUnknownKey = fmt.Errorf("Unknown key")
errDecoding = fmt.Errorf("Error decoding input")
errUnauthorized = fmt.Errorf("Unauthorized")
errTooLarge = fmt.Errorf("Input size too large")
errMissingSignature = fmt.Errorf("Signature missing")
errUnknownTxType = fmt.Errorf("Tx type unknown")
errInvalidFormat = fmt.Errorf("Invalid format")
errUnknownModule = fmt.Errorf("Unknown module")
internalErr = abci.CodeType_InternalError
encodingErr = abci.CodeType_EncodingError
@ -70,14 +60,6 @@ func IsUnknownModuleErr(err error) bool {
return IsSameError(errUnknownModule, err)
}
func ErrUnknownKey(mod string) TMError {
w := errors.Wrap(errUnknownKey, mod)
return WithCode(w, abci.CodeType_UnknownRequest)
}
func IsUnknownKeyErr(err error) bool {
return IsSameError(errUnknownKey, err)
}
func ErrInternal(msg string) TMError {
return New(msg, internalErr)
}
@ -104,10 +86,6 @@ func IsUnauthorizedErr(err error) bool {
return HasErrorCode(err, unauthorized)
}
func ErrNoSigners() TMError {
return WithCode(errNoSigners, unauthorized)
}
func ErrMissingSignature() TMError {
return WithCode(errMissingSignature, unauthorized)
}
@ -115,49 +93,9 @@ func IsMissingSignatureErr(err error) bool {
return IsSameError(errMissingSignature, err)
}
func ErrTooManySignatures() TMError {
return WithCode(errTooManySignatures, unauthorized)
}
func IsTooManySignaturesErr(err error) bool {
return IsSameError(errTooManySignatures, err)
}
func ErrInvalidSignature() TMError {
return WithCode(errInvalidSignature, unauthorized)
}
func IsInvalidSignatureErr(err error) bool {
return IsSameError(errInvalidSignature, err)
}
func ErrNoChain() TMError {
return WithCode(errNoChain, unauthorized)
}
func IsNoChainErr(err error) bool {
return IsSameError(errNoChain, err)
}
func ErrTxEmpty() TMError {
return WithCode(errTxEmpty, unauthorized)
}
func ErrWrongChain(chain string) TMError {
msg := errors.Wrap(errWrongChain, chain)
return WithCode(msg, unauthorized)
}
func IsWrongChainErr(err error) bool {
return IsSameError(errWrongChain, err)
}
func ErrTooLarge() TMError {
return WithCode(errTooLarge, encodingErr)
}
func IsTooLargeErr(err error) bool {
return IsSameError(errTooLarge, err)
}
func ErrExpired() TMError {
return WithCode(errExpired, unauthorized)
}
func IsExpiredErr(err error) bool {
return IsSameError(errExpired, err)
}

View File

@ -42,7 +42,6 @@ func TestErrorMatches(t *testing.T) {
{errUnauthorized, ErrUnauthorized(), true},
{errMissingSignature, ErrUnauthorized(), false},
{errMissingSignature, ErrMissingSignature(), true},
{errWrongChain, ErrWrongChain("hakz"), true},
{errUnknownTxType, ErrUnknownTxType(holder{}), true},
{errUnknownTxType, ErrUnknownTxType("some text here..."), true},
{errUnknownTxType, ErrUnknownTxType(demoTx{5}.Wrap()), true},
@ -66,13 +65,6 @@ func TestChecks(t *testing.T) {
{ErrDecoding(), IsDecodingErr, true},
{ErrUnauthorized(), IsDecodingErr, false},
{ErrUnauthorized(), IsUnauthorizedErr, true},
{ErrInvalidSignature(), IsInvalidSignatureErr, true},
// unauthorized includes InvalidSignature, but not visa versa
{ErrInvalidSignature(), IsUnauthorizedErr, true},
{ErrUnauthorized(), IsInvalidSignatureErr, false},
// make sure WrongChain works properly
{ErrWrongChain("fooz"), IsUnauthorizedErr, true},
{ErrWrongChain("barz"), IsWrongChainErr, true},
// make sure lots of things match InternalErr, but not everything
{ErrInternal("bad db connection"), IsInternalErr, true},
{Wrap(errors.New("wrapped")), IsInternalErr, true},

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

@ -0,0 +1,35 @@
package commands
import (
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/tendermint/basecoin"
txcmd "github.com/tendermint/basecoin/client/commands/txs"
"github.com/tendermint/basecoin/modules/auth"
)
//nolint
const (
FlagMulti = "multi"
)
// SigWrapper wraps a tx with a signature layer to hold pubkey sigs
type SigWrapper struct{}
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) {
if !viper.GetBool(FlagMulti) {
res = auth.NewSig(tx).Wrap()
} else {
res = auth.NewMulti(tx).Wrap()
}
return
}
// Register adds the sequence flags to the cli
func (SigWrapper) Register(fs *pflag.FlagSet) {
fs.Bool(FlagMulti, false, "Prepare the tx for multisig")
}

31
modules/auth/errors.go Normal file
View File

@ -0,0 +1,31 @@
//nolint
package auth
import (
"fmt"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin/errors"
)
var (
errInvalidSignature = fmt.Errorf("Invalid Signature") //move auth
errTooManySignatures = fmt.Errorf("Too many signatures") //move auth
unauthorized = abci.CodeType_Unauthorized
)
func ErrTooManySignatures() errors.TMError {
return errors.WithCode(errTooManySignatures, unauthorized)
}
func IsTooManySignaturesErr(err error) bool {
return errors.IsSameError(errTooManySignatures, err)
}
func ErrInvalidSignature() errors.TMError {
return errors.WithCode(errInvalidSignature, unauthorized)
}
func IsInvalidSignatureErr(err error) bool {
return errors.IsSameError(errInvalidSignature, err)
}

View File

@ -0,0 +1,29 @@
package auth
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tendermint/basecoin/errors"
)
func TestChecks(t *testing.T) {
// TODO: make sure the Is and Err methods match
assert := assert.New(t)
cases := []struct {
err error
check func(error) bool
match bool
}{
// unauthorized includes InvalidSignature, but not visa versa
{ErrInvalidSignature(), IsInvalidSignatureErr, true},
{ErrInvalidSignature(), errors.IsUnauthorizedErr, true},
}
for i, tc := range cases {
match := tc.check(tc.err)
assert.Equal(tc.match, match, "%d", i)
}
}

View File

@ -106,7 +106,7 @@ func (s *OneSig) Sign(pubkey crypto.PubKey, sig crypto.Signature) error {
return errors.ErrMissingSignature()
}
if !s.Empty() {
return errors.ErrTooManySignatures()
return ErrTooManySignatures()
}
// set the value once we are happy
s.Signed = signed
@ -121,7 +121,7 @@ func (s *OneSig) Signers() ([]crypto.PubKey, error) {
return nil, errors.ErrMissingSignature()
}
if !s.Pubkey.VerifyBytes(s.SignBytes(), s.Sig) {
return nil, errors.ErrInvalidSignature()
return nil, ErrInvalidSignature()
}
return []crypto.PubKey{s.Pubkey}, nil
}
@ -194,7 +194,7 @@ func (s *MultiSig) Signers() ([]crypto.PubKey, error) {
for i := range s.Sigs {
ms := s.Sigs[i]
if !ms.Pubkey.VerifyBytes(data, ms.Sig) {
return nil, errors.ErrInvalidSignature()
return nil, ErrInvalidSignature()
}
keys[i] = ms.Pubkey
}

View File

@ -6,7 +6,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/basecoin/stack"
crypto "github.com/tendermint/go-crypto"
keys "github.com/tendermint/go-crypto/keys"
"github.com/tendermint/go-crypto/keys/cryptostore"
@ -14,6 +13,7 @@ import (
wire "github.com/tendermint/go-wire"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/stack"
)
func checkSignBytes(t *testing.T, bytes []byte, expected string) {

View File

@ -2,7 +2,6 @@ package base
import (
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/errors"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/state"
)
@ -48,7 +47,7 @@ func (c Chain) checkChainTx(chainID string, height uint64, tx basecoin.Tx) (base
// make sure it is a chaintx
ctx, ok := tx.Unwrap().(ChainTx)
if !ok {
return tx, errors.ErrNoChain()
return tx, ErrNoChain()
}
// basic validation
@ -59,10 +58,10 @@ func (c Chain) checkChainTx(chainID string, height uint64, tx basecoin.Tx) (base
// compare against state
if ctx.ChainID != chainID {
return tx, errors.ErrWrongChain(ctx.ChainID)
return tx, ErrWrongChain(ctx.ChainID)
}
if ctx.ExpiresAt != 0 && ctx.ExpiresAt <= height {
return tx, errors.ErrExpired()
return tx, ErrExpired()
}
return ctx.Tx, nil
}

View File

@ -0,0 +1,39 @@
package commands
import (
"errors"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/client/commands"
txcmd "github.com/tendermint/basecoin/client/commands/txs"
"github.com/tendermint/basecoin/modules/base"
)
//nolint
const (
FlagExpires = "expires"
)
// ChainWrapper wraps a tx with an chain info and optional expiration
type ChainWrapper struct{}
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) {
expires := viper.GetInt64(FlagExpires)
chain := commands.GetChainID()
if chain == "" {
return res, errors.New("No chain-id provided")
}
res = base.NewChainTx(chain, uint64(expires), tx)
return
}
// Register adds the sequence flags to the cli
func (ChainWrapper) Register(fs *pflag.FlagSet) {
fs.Uint64(FlagExpires, 0, "Block height at which this tx expires")
}

37
modules/base/errors.go Normal file
View File

@ -0,0 +1,37 @@
//nolint
package base
import (
"fmt"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin/errors"
)
var (
errNoChain = fmt.Errorf("No chain id provided") //move base
errWrongChain = fmt.Errorf("Wrong chain for tx") //move base
errExpired = fmt.Errorf("Tx expired") //move base
unauthorized = abci.CodeType_Unauthorized
)
func ErrNoChain() errors.TMError {
return errors.WithCode(errNoChain, unauthorized)
}
func IsNoChainErr(err error) bool {
return errors.IsSameError(errNoChain, err)
}
func ErrWrongChain(chain string) errors.TMError {
return errors.WithMessage(chain, errWrongChain, unauthorized)
}
func IsWrongChainErr(err error) bool {
return errors.IsSameError(errWrongChain, err)
}
func ErrExpired() errors.TMError {
return errors.WithCode(errExpired, unauthorized)
}
func IsExpiredErr(err error) bool {
return errors.IsSameError(errExpired, err)
}

View File

@ -0,0 +1,45 @@
package base
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tendermint/basecoin/errors"
)
func TestErrorMatches(t *testing.T) {
assert := assert.New(t)
cases := []struct {
pattern, err error
match bool
}{
{errWrongChain, ErrWrongChain("hakz"), true},
}
for i, tc := range cases {
same := errors.IsSameError(tc.pattern, tc.err)
assert.Equal(tc.match, same, "%d: %#v / %#v", i, tc.pattern, tc.err)
}
}
func TestChecks(t *testing.T) {
// TODO: make sure the Is and Err methods match
assert := assert.New(t)
cases := []struct {
err error
check func(error) bool
match bool
}{
// make sure WrongChain works properly
{ErrWrongChain("fooz"), errors.IsUnauthorizedErr, true},
{ErrWrongChain("barz"), IsWrongChainErr, true},
}
for i, tc := range cases {
match := tc.check(tc.err)
assert.Equal(tc.match, match, "%d", i)
}
}

View File

@ -86,10 +86,10 @@ func (c ChainTx) Wrap() basecoin.Tx {
}
func (c ChainTx) ValidateBasic() error {
if c.ChainID == "" {
return errors.ErrNoChain()
return ErrNoChain()
}
if !chainPattern.MatchString(c.ChainID) {
return errors.ErrWrongChain(c.ChainID)
return ErrWrongChain(c.ChainID)
}
if c.Tx.Empty() {
return errors.ErrUnknownTxType(c.Tx)

View File

@ -7,10 +7,10 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/go-wire/data"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/stack"
)
func TestEncoding(t *testing.T) {

View File

@ -0,0 +1,42 @@
package commands
import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
lc "github.com/tendermint/light-client"
"github.com/tendermint/basecoin/client/commands"
proofcmd "github.com/tendermint/basecoin/client/commands/proofs"
"github.com/tendermint/basecoin/modules/coin"
"github.com/tendermint/basecoin/stack"
)
// AccountQueryCmd - command to query an account
var AccountQueryCmd = &cobra.Command{
Use: "account [address]",
Short: "Get details of an account, with proof",
RunE: commands.RequireInit(accountQueryCmd),
}
func accountQueryCmd(cmd *cobra.Command, args []string) error {
addr, err := commands.GetOneArg(args, "address")
if err != nil {
return err
}
act, err := commands.ParseActor(addr)
if err != nil {
return err
}
key := stack.PrefixedKey(coin.NameCoin, act.Bytes())
acc := coin.Account{}
proof, err := proofcmd.GetAndParseAppProof(key, &acc)
if lc.IsNoDataErr(err) {
return errors.Errorf("Account bytes are empty for address %X ", addr)
} else if err != nil {
return err
}
return proofcmd.OutputProof(acc, proof.BlockHeight())
}

View File

@ -0,0 +1,79 @@
package commands
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/client/commands"
txcmd "github.com/tendermint/basecoin/client/commands/txs"
"github.com/tendermint/basecoin/modules/coin"
)
// SendTxCmd is CLI command to send tokens between basecoin accounts
var SendTxCmd = &cobra.Command{
Use: "send",
Short: "send tokens from one account to another",
RunE: commands.RequireInit(sendTxCmd),
}
//nolint
const (
FlagTo = "to"
FlagAmount = "amount"
FlagFrom = "from"
)
func init() {
flags := SendTxCmd.Flags()
flags.String(FlagTo, "", "Destination address for the bits")
flags.String(FlagAmount, "", "Coins to send in the format <amt><coin>,<amt><coin>...")
flags.String(FlagFrom, "", "Address sending coins, if not first signer")
}
// sendTxCmd is an example of how to make a tx
func sendTxCmd(cmd *cobra.Command, args []string) error {
tx, err := readSendTxFlags()
if err != nil {
return err
}
return txcmd.DoTx(tx)
}
func readSendTxFlags() (tx basecoin.Tx, err error) {
// parse to address
toAddr, err := commands.ParseActor(viper.GetString(FlagTo))
if err != nil {
return tx, err
}
fromAddr, err := readFromAddr()
if err != nil {
return tx, err
}
amountCoins, err := coin.ParseCoins(viper.GetString(FlagAmount))
if err != nil {
return tx, err
}
// craft the inputs and outputs
ins := []coin.TxInput{{
Address: fromAddr,
Coins: amountCoins,
}}
outs := []coin.TxOutput{{
Address: toAddr,
Coins: amountCoins,
}}
return coin.NewSendTx(ins, outs), nil
}
func readFromAddr() (basecoin.Actor, error) {
from := viper.GetString(FlagFrom)
if from == "" {
return txcmd.GetSignerAct(), nil
}
return commands.ParseActor(from)
}

View File

@ -5,23 +5,23 @@ import (
"fmt"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin/errors"
)
var (
errNoAccount = fmt.Errorf("No such account")
errInsufficientFunds = fmt.Errorf("Insufficient Funds")
errNoInputs = fmt.Errorf("No Input Coins")
errNoOutputs = fmt.Errorf("No Output Coins")
errInvalidAddress = fmt.Errorf("Invalid Address")
errInvalidCoins = fmt.Errorf("Invalid Coins")
errInvalidSequence = fmt.Errorf("Invalid Sequence")
)
errInsufficientFunds = fmt.Errorf("Insufficient funds")
errNoInputs = fmt.Errorf("No input coins")
errNoOutputs = fmt.Errorf("No output coins")
errInvalidAddress = fmt.Errorf("Invalid address")
errInvalidCoins = fmt.Errorf("Invalid coins")
errUnknownKey = fmt.Errorf("Unknown key")
var (
invalidInput = abci.CodeType_BaseInvalidInput
invalidOutput = abci.CodeType_BaseInvalidOutput
unknownAddress = abci.CodeType_BaseUnknownAddress
unknownRequest = abci.CodeType_UnknownRequest
)
// here are some generic handlers to grab classes of errors based on code
@ -80,3 +80,10 @@ func ErrNoOutputs() errors.TMError {
func IsNoOutputsErr(err error) bool {
return errors.IsSameError(errNoOutputs, err)
}
func ErrUnknownKey(mod string) errors.TMError {
return errors.WithMessage(mod, errUnknownKey, unknownRequest)
}
func IsUnknownKeyErr(err error) bool {
return errors.IsSameError(errUnknownKey, err)
}

View File

@ -99,7 +99,7 @@ func (h Handler) SetOption(l log.Logger, store state.KVStore, module, key, value
return "Success", nil
}
return "", errors.ErrUnknownKey(key)
return "", ErrUnknownKey(key)
}
func checkTx(ctx basecoin.Context, tx basecoin.Tx) (send SendTx, err error) {

View File

@ -1,11 +1,11 @@
package coin
import (
"github.com/tendermint/basecoin/modules/auth"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-wire/data"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/modules/auth"
)
// AccountWithKey is a helper for tests, that includes and account

View File

@ -0,0 +1,59 @@
package commands
import (
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/tendermint/basecoin"
"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"
)
//nolint
const (
FlagFee = "fee"
FlagPayer = "payer"
)
// FeeWrapper wraps a tx with an optional fee payment
type FeeWrapper struct{}
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
func (FeeWrapper) Wrap(tx basecoin.Tx) (res basecoin.Tx, err error) {
//parse the fee and amounts into coin types
toll, err := coin.ParseCoin(viper.GetString(FlagFee))
if err != nil {
return res, err
}
// if no fee, do nothing, otherwise wrap it
if toll.IsZero() {
return tx, nil
}
payer, err := readPayer()
if err != nil {
return res, err
}
res = fee.NewFee(tx, toll, payer)
return
}
// Register adds the sequence flags to the cli
func (FeeWrapper) Register(fs *pflag.FlagSet) {
fs.String(FlagFee, "0mycoin", "Coins for the transaction fee of the format <amt><coin>")
fs.String(FlagPayer, "", "Account to pay fee if not current signer (for multisig)")
}
func readPayer() (basecoin.Actor, error) {
payer := viper.GetString(FlagPayer)
if payer == "" {
return txcmd.GetSignerAct(), nil
}
return commands.ParseActor(payer)
}

View File

@ -10,19 +10,21 @@ import (
)
var (
errInsufficientFees = fmt.Errorf("Insufficient Fees")
errInsufficientFees = fmt.Errorf("Insufficient fees")
errWrongFeeDenom = fmt.Errorf("Required fee denomination")
invalidInput = abci.CodeType_BaseInvalidInput
)
func ErrInsufficientFees() errors.TMError {
return errors.WithCode(errInsufficientFees, abci.CodeType_BaseInvalidInput)
return errors.WithCode(errInsufficientFees, invalidInput)
}
func IsInsufficientFeesErr(err error) bool {
return errors.IsSameError(errInsufficientFees, err)
}
func ErrWrongFeeDenom(denom string) errors.TMError {
return errors.WithMessage(denom, errWrongFeeDenom, abci.CodeType_BaseInvalidInput)
return errors.WithMessage(denom, errWrongFeeDenom, invalidInput)
}
func IsWrongFeeDenomErr(err error) bool {
return errors.IsSameError(errWrongFeeDenom, err)

View File

@ -0,0 +1,55 @@
package commands
import (
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
lc "github.com/tendermint/light-client"
"github.com/tendermint/basecoin"
"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"
)
// NonceQueryCmd - command to query an nonce account
var NonceQueryCmd = &cobra.Command{
Use: "nonce [address]",
Short: "Get details of a nonce sequence number, with proof",
RunE: commands.RequireInit(nonceQueryCmd),
}
func nonceQueryCmd(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("Missing required argument [address]")
}
addr := strings.Join(args, ",")
signers, err := commands.ParseActors(addr)
if err != nil {
return err
}
seq, proof, err := doNonceQuery(signers)
if err != nil {
return err
}
return proofcmd.OutputProof(seq, proof.BlockHeight())
}
func doNonceQuery(signers []basecoin.Actor) (sequence uint32, proof lc.Proof, err error) {
key := stack.PrefixedKey(nonce.NameNonce, nonce.GetSeqKey(signers))
proof, err = proofcmd.GetAndParseAppProof(key, &sequence)
if lc.IsNoDataErr(err) {
// no data, return sequence 0
return 0, proof, nil
}
return
}

View File

@ -0,0 +1,83 @@
package commands
import (
"fmt"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/client/commands"
txcmd "github.com/tendermint/basecoin/client/commands/txs"
"github.com/tendermint/basecoin/modules/nonce"
)
// nolint
const (
FlagSequence = "sequence"
FlagNonceKey = "nonce-key"
)
// NonceWrapper wraps a tx with a nonce
type NonceWrapper struct{}
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,
// as we still only support single sig on the cli
func (NonceWrapper) Wrap(tx basecoin.Tx) (res basecoin.Tx, err error) {
signers, err := readNonceKey()
if err != nil {
return res, err
}
seq, err := readSequence(signers)
if err != nil {
return res, err
}
res = nonce.NewTx(seq, signers, tx)
return
}
// Register adds the sequence flags to the cli
func (NonceWrapper) Register(fs *pflag.FlagSet) {
fs.Int(FlagSequence, -1, "Sequence number for this transaction")
fs.String(FlagNonceKey, "", "Set of comma-separated addresses for the nonce (for multisig)")
}
func readNonceKey() ([]basecoin.Actor, error) {
nonce := viper.GetString(FlagNonceKey)
if nonce == "" {
return []basecoin.Actor{txcmd.GetSignerAct()}, nil
}
return commands.ParseActors(nonce)
}
// read the sequence from the flag or query for it if flag is -1
func readSequence(signers []basecoin.Actor) (seq uint32, err error) {
//add the nonce tx layer to the tx
seqFlag := viper.GetInt(FlagSequence)
switch {
case seqFlag > 0:
seq = uint32(seqFlag)
case seqFlag == -1:
//autocalculation for default sequence
seq, _, err = doNonceQuery(signers)
if err != nil {
return
}
//increase the sequence by 1!
seq++
default:
err = fmt.Errorf("sequence must be either greater than 0, or -1 for autocalculation")
}
return
}

View File

@ -11,22 +11,31 @@ import (
var (
errNoNonce = fmt.Errorf("Tx doesn't contain nonce")
errNotMember = fmt.Errorf("nonce contains non-permissioned member")
errNotMember = fmt.Errorf("Nonce contains non-permissioned member")
errZeroSequence = fmt.Errorf("Sequence number cannot be zero")
errNoSigners = fmt.Errorf("There are no signers")
errTxEmpty = fmt.Errorf("The provided Tx is empty")
unauthorized = abci.CodeType_Unauthorized
badNonce = abci.CodeType_BadNonce
invalidInput = abci.CodeType_BaseInvalidInput
)
func ErrBadNonce(got, expected uint32) errors.TMError {
return errors.WithCode(fmt.Errorf("Bad nonce sequence, got %d, expected %d", got, expected), unauthorized)
return errors.WithCode(fmt.Errorf("Bad nonce sequence, got %d, expected %d", got, expected), badNonce)
}
func ErrNoNonce() errors.TMError {
return errors.WithCode(errNoNonce, unauthorized)
return errors.WithCode(errNoNonce, badNonce)
}
func ErrNotMember() errors.TMError {
return errors.WithCode(errNotMember, unauthorized)
}
func ErrZeroSequence() errors.TMError {
return errors.WithCode(errZeroSequence, unauthorized)
return errors.WithCode(errZeroSequence, invalidInput)
}
func ErrNoSigners() errors.TMError {
return errors.WithCode(errNoSigners, invalidInput)
}
func ErrTxEmpty() errors.TMError {
return errors.WithCode(errTxEmpty, invalidInput)
}

View File

@ -11,7 +11,6 @@ import (
"sort"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/errors"
"github.com/tendermint/basecoin/state"
)
@ -50,11 +49,11 @@ func (n Tx) Wrap() basecoin.Tx {
func (n Tx) ValidateBasic() error {
switch {
case n.Tx.Empty():
return errors.ErrTxEmpty()
return ErrTxEmpty()
case n.Sequence == 0:
return ErrZeroSequence()
case len(n.Signers) == 0:
return errors.ErrNoSigners()
return ErrNoSigners()
}
return n.Tx.ValidateBasic()
}

View File

@ -0,0 +1,40 @@
package commands
import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
lcmd "github.com/tendermint/basecoin/client/commands"
proofcmd "github.com/tendermint/basecoin/client/commands/proofs"
"github.com/tendermint/basecoin/modules/roles"
"github.com/tendermint/basecoin/stack"
)
// RoleQueryCmd - command to query a role
var RoleQueryCmd = &cobra.Command{
Use: "role [name]",
Short: "Get details of a role, with proof",
RunE: lcmd.RequireInit(roleQueryCmd),
}
func roleQueryCmd(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("Missing required argument [name]")
} else if len(args) > 1 {
return errors.New("Command only supports one name")
}
role, err := parseRole(args[0])
if err != nil {
return err
}
var res roles.Role
key := stack.PrefixedKey(roles.NameRole, role)
proof, err := proofcmd.GetAndParseAppProof(key, &res)
if err != nil {
return err
}
return proofcmd.OutputProof(res, proof.BlockHeight())
}

View File

@ -0,0 +1,65 @@
package commands
import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/client/commands"
txcmd "github.com/tendermint/basecoin/client/commands/txs"
"github.com/tendermint/basecoin/modules/roles"
)
// CreateRoleTxCmd is CLI command to send tokens between basecoin accounts
var CreateRoleTxCmd = &cobra.Command{
Use: "create-role",
Short: "Create a new role",
RunE: commands.RequireInit(createRoleTxCmd),
}
//nolint
const (
FlagRole = "role"
FlagMembers = "members"
FlagMinSigs = "min-sigs"
)
func init() {
flags := CreateRoleTxCmd.Flags()
flags.String(FlagRole, "", "Name of the role to create")
flags.String(FlagMembers, "", "Set of comma-separated addresses for this role")
flags.Int(FlagMinSigs, 0, "Minimum number of signatures needed to assume this role")
}
// createRoleTxCmd is an example of how to make a tx
func createRoleTxCmd(cmd *cobra.Command, args []string) error {
tx, err := readCreateRoleTxFlags()
if err != nil {
return err
}
return txcmd.DoTx(tx)
}
func readCreateRoleTxFlags() (tx basecoin.Tx, err error) {
role, err := parseRole(viper.GetString(FlagRole))
if err != nil {
return tx, err
}
sigs := viper.GetInt(FlagMinSigs)
if sigs < 1 {
return tx, errors.Errorf("--%s must be at least 1", FlagMinSigs)
}
signers, err := commands.ParseActors(viper.GetString(FlagMembers))
if err != nil {
return tx, err
}
if len(signers) == 0 {
return tx, errors.New("must specify at least one member")
}
tx = roles.NewCreateRoleTx(role, uint32(sigs), signers)
return tx, nil
}

View File

@ -0,0 +1,48 @@
package commands
import (
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/tendermint/basecoin"
txcmd "github.com/tendermint/basecoin/client/commands/txs"
"github.com/tendermint/basecoin/modules/roles"
)
// nolint
const (
FlagAssumeRole = "assume-role"
)
// RoleWrapper wraps a tx with 0, 1, or more roles
type RoleWrapper struct{}
var _ txcmd.Wrapper = RoleWrapper{}
// Wrap grabs the sequence number from the flag and wraps
// the tx with this nonce. Grabs the permission from the signer,
// as we still only support single sig on the cli
func (RoleWrapper) Wrap(tx basecoin.Tx) (basecoin.Tx, error) {
assume := viper.GetStringSlice(FlagAssumeRole)
// we wrap from inside-out, so we must wrap them in the reverse order,
// so they are applied in the order the user intended
for i := len(assume) - 1; i >= 0; i-- {
r, err := parseRole(assume[i])
if err != nil {
return tx, err
}
tx = roles.NewAssumeRoleTx(r, tx)
}
return tx, nil
}
// Register adds the sequence flags to the cli
func (RoleWrapper) Register(fs *pflag.FlagSet) {
fs.StringSlice(FlagAssumeRole, nil, "Roles to assume (can use multiple times)")
}
// parse role turns the string->byte... todo: support hex?
func parseRole(role string) ([]byte, error) {
return []byte(role), nil
}

View File

@ -5,6 +5,7 @@ import (
"fmt"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin/errors"
)
@ -16,54 +17,56 @@ var (
errNoMembers = fmt.Errorf("No members specified")
errTooManyMembers = fmt.Errorf("Too many members specified")
errNotEnoughMembers = fmt.Errorf("Not enough members specified")
unauthorized = abci.CodeType_Unauthorized
)
// TODO: codegen?
// ex: err-gen NoRole,"No such role",CodeType_Unauthorized
func ErrNoRole() errors.TMError {
return errors.WithCode(errNoRole, abci.CodeType_Unauthorized)
return errors.WithCode(errNoRole, unauthorized)
}
func IsNoRoleErr(err error) bool {
return errors.IsSameError(errNoRole, err)
}
func ErrRoleExists() errors.TMError {
return errors.WithCode(errRoleExists, abci.CodeType_Unauthorized)
return errors.WithCode(errRoleExists, unauthorized)
}
func IsRoleExistsErr(err error) bool {
return errors.IsSameError(errRoleExists, err)
}
func ErrNotMember() errors.TMError {
return errors.WithCode(errNotMember, abci.CodeType_Unauthorized)
return errors.WithCode(errNotMember, unauthorized)
}
func IsNotMemberErr(err error) bool {
return errors.IsSameError(errNotMember, err)
}
func ErrInsufficientSigs() errors.TMError {
return errors.WithCode(errInsufficientSigs, abci.CodeType_Unauthorized)
return errors.WithCode(errInsufficientSigs, unauthorized)
}
func IsInsufficientSigsErr(err error) bool {
return errors.IsSameError(errInsufficientSigs, err)
}
func ErrNoMembers() errors.TMError {
return errors.WithCode(errNoMembers, abci.CodeType_Unauthorized)
return errors.WithCode(errNoMembers, unauthorized)
}
func IsNoMembersErr(err error) bool {
return errors.IsSameError(errNoMembers, err)
}
func ErrTooManyMembers() errors.TMError {
return errors.WithCode(errTooManyMembers, abci.CodeType_Unauthorized)
return errors.WithCode(errTooManyMembers, unauthorized)
}
func IsTooManyMembersErr(err error) bool {
return errors.IsSameError(errTooManyMembers, err)
}
func ErrNotEnoughMembers() errors.TMError {
return errors.WithCode(errNotEnoughMembers, abci.CodeType_Unauthorized)
return errors.WithCode(errNotEnoughMembers, unauthorized)
}
func IsNotEnoughMembersErr(err error) bool {
return errors.IsSameError(errNotEnoughMembers, err)

View File

@ -4,6 +4,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/modules/roles"
"github.com/tendermint/basecoin/stack"

View File

@ -3,10 +3,11 @@ package roles
import (
"fmt"
wire "github.com/tendermint/go-wire"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/errors"
"github.com/tendermint/basecoin/state"
wire "github.com/tendermint/go-wire"
)
// NewPerm creates a role permission with the given label

View File

@ -6,11 +6,11 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/go-wire/data"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/state"
"github.com/tendermint/go-wire/data"
)
// writerMid is a middleware that writes the given bytes on CheckTx and DeliverTx

View File

@ -21,20 +21,20 @@ test00GetAccount() {
SENDER=$(getAddr $RICH)
RECV=$(getAddr $POOR)
assertFalse "requires arg" "${CLIENT_EXE} query account"
assertFalse "line=${LINENO}, requires arg" "${CLIENT_EXE} query account"
checkAccount $SENDER "9007199254740992"
ACCT2=$(${CLIENT_EXE} query account $RECV 2>/dev/null)
assertFalse "has no genesis account" $?
assertFalse "line=${LINENO}, has no genesis account" $?
}
test01SendTx() {
SENDER=$(getAddr $RICH)
RECV=$(getAddr $POOR)
assertFalse "missing dest" "${CLIENT_EXE} tx send --amount=992mycoin --sequence=1"
assertFalse "bad password" "echo foo | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH"
assertFalse "line=${LINENO}, missing dest" "${CLIENT_EXE} tx send --amount=992mycoin --sequence=1"
assertFalse "line=${LINENO}, bad password" "echo foo | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH"
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH)
txSucceeded $? "$TX" "$RECV"
HASH=$(echo $TX | jq .hash | tr -d \")
@ -53,7 +53,8 @@ test02SendTxWithFee() {
SENDER=$(getAddr $RICH)
RECV=$(getAddr $POOR)
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=90mycoin --fee=10mycoin --sequence=2 --to=$RECV --name=$RICH)
# Test to see if the auto-sequencing works, the sequence here should be calculated to be 2
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=90mycoin --fee=10mycoin --to=$RECV --name=$RICH)
txSucceeded $? "$TX" "$RECV"
HASH=$(echo $TX | jq .hash | tr -d \")
TX_HEIGHT=$(echo $TX | jq .height)
@ -67,7 +68,7 @@ test02SendTxWithFee() {
# assert replay protection
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=90mycoin --fee=10mycoin --sequence=2 --to=$RECV --name=$RICH 2>/dev/null)
assertFalse "replay: $TX" $?
assertFalse "line=${LINENO}, replay: $TX" $?
checkAccount $SENDER "9007199254739900"
checkAccount $RECV "1082"
@ -76,7 +77,7 @@ test02SendTxWithFee() {
if [ -n "$DEBUG" ]; then echo $NONCE; echo; fi
# TODO: note that cobra returns error code 0 on parse failure,
# so currently this check passes even if there is no nonce query command
if assertTrue "no nonce query" $?; then
if assertTrue "line=${LINENO}, no nonce query" $?; then
assertEquals "line=${LINENO}, proper nonce" "2" $(echo $NONCE | jq .data)
fi
}

View File

@ -138,10 +138,27 @@ checkAccount() {
return $?
}
# XXX Ex Usage: checkRole $ROLE $SIGS $NUM_SIGNERS
# Desc: Ensures this named role exists, and has the number of members and required signatures as above
checkRole() {
# make sure sender goes down
ROLE=$(${CLIENT_EXE} query role $1)
if ! assertTrue "line=${LINENO}, role must exist" $?; then
return 1
fi
if [ -n "$DEBUG" ]; then echo $ROLE; echo; fi
assertEquals "line=${LINENO}, proper sigs" "$2" $(echo $ROLE | jq .data.min_sigs)
assertEquals "line=${LINENO}, proper app" '"sigs"' $(echo $ROLE | jq '.data.signers[0].app' )
assertEquals "line=${LINENO}, proper signers" "$3" $(echo $ROLE | jq '.data.signers | length')
return $?
}
# XXX Ex Usage: txSucceeded $? "$TX" "$RECIEVER"
# Desc: Must be called right after the `tx` command, makes sure it got a success response
txSucceeded() {
if (assertTrue "sent tx ($3): $2" $1); then
if (assertTrue "line=${LINENO}, sent tx ($3): $2" $1); then
TX=$2
assertEquals "line=${LINENO}, good check ($3): $TX" "0" $(echo $TX | jq .check_tx.code)
assertEquals "line=${LINENO}, good deliver ($3): $TX" "0" $(echo $TX | jq .deliver_tx.code)
@ -171,13 +188,43 @@ checkSendTx() {
return $?
}
# XXX Ex Usage: toHex "my-name"
# converts the string into the hex representation of the bytes
toHex() {
echo -n $1 | od -A n -t x1 | sed 's/ //g' | tr 'a-f' 'A-F'
}
# XXX Ex Usage: checkRoleTx $HASH $HEIGHT $NAME $NUM_SIGNERS
# Desc: This looks up the tx by hash, and makes sure the height and type match
# and that the it refers to the proper role
checkRoleTx() {
TX=$(${CLIENT_EXE} query tx $1)
assertTrue "line=${LINENO}, found tx" $?
if [ -n "$DEBUG" ]; then echo $TX; echo; fi
assertEquals "line=${LINENO}, proper height" $2 $(echo $TX | jq .height)
assertEquals "line=${LINENO}, type=sigs/one" '"sigs/one"' $(echo $TX | jq .data.type)
CTX=$(echo $TX | jq .data.data.tx)
assertEquals "line=${LINENO}, type=chain/tx" '"chain/tx"' $(echo $CTX | jq .type)
NTX=$(echo $CTX | jq .data.tx)
assertEquals "line=${LINENO}, type=nonce" '"nonce"' $(echo $NTX | jq .type)
RTX=$(echo $NTX | jq .data.tx)
assertEquals "line=${LINENO}, type=role/create" '"role/create"' $(echo $RTX | jq .type)
HEXNAME=$(toHex $3)
assertEquals "line=${LINENO}, proper name" "\"$HEXNAME\"" $(echo $RTX | jq .data.role)
assertEquals "line=${LINENO}, proper num signers" "$4" $(echo $RTX | jq '.data.signers | length')
return $?
}
# XXX Ex Usage: checkSendFeeTx $HASH $HEIGHT $SENDER $AMOUNT $FEE
# Desc: This is like checkSendTx, but asserts a feetx wrapper with $FEE value.
# This looks up the tx by hash, and makes sure the height and type match
# and that the first input was from this sender for this amount
checkSendFeeTx() {
TX=$(${CLIENT_EXE} query tx $1)
assertTrue "found tx" $?
assertTrue "line=${LINENO}, found tx" $?
if [ -n "$DEBUG" ]; then echo $TX; echo; fi
assertEquals "line=${LINENO}, proper height" $2 $(echo $TX | jq .height)

View File

@ -33,9 +33,10 @@ test01SendTx() {
SENDER=$(getAddr $RICH)
RECV=$(getAddr $POOR)
assertFalse "Line=${LINENO}, missing dest" "${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 2>/dev/null"
assertFalse "Line=${LINENO}, bad password" "echo foo | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH 2>/dev/null"
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH)
# sequence should work well for first time also
assertFalse "Line=${LINENO}, missing dest" "${CLIENT_EXE} tx send --amount=992mycoin 2>/dev/null"
assertFalse "Line=${LINENO}, bad password" "echo foo | ${CLIENT_EXE} tx send --amount=992mycoin --to=$RECV --name=$RICH 2>/dev/null"
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=992mycoin --to=$RECV --name=$RICH)
txSucceeded $? "$TX" "$RECV"
HASH=$(echo $TX | jq .hash | tr -d \")
TX_HEIGHT=$(echo $TX | jq .height)
@ -104,7 +105,7 @@ test03AddCount() {
# make sure we cannot replay the counter, no state change
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx counter --countfee=10mycoin --sequence=2 --name=${RICH} --valid 2>/dev/null)
assertFalse "replay: $TX" $?
assertFalse "line=${LINENO}, replay: $TX" $?
checkCounter "2" "17"
checkAccount $SENDER "9007199254739979"
}

View File

@ -66,21 +66,21 @@ test00GetAccount() {
RECV_1=$(BC_HOME=${CLIENT_1} getAddr $POOR)
export BC_HOME=${CLIENT_1}
assertFalse "requires arg" "${CLIENT_EXE} query account 2>/dev/null"
assertFalse "has no genesis account" "${CLIENT_EXE} query account $RECV_1 2>/dev/null"
assertFalse "line=${LINENO}, requires arg" "${CLIENT_EXE} query account 2>/dev/null"
assertFalse "line=${LINENO}, has no genesis account" "${CLIENT_EXE} query account $RECV_1 2>/dev/null"
checkAccount $SENDER_1 "0" "9007199254740992"
export BC_HOME=${CLIENT_2}
SENDER_2=$(getAddr $RICH)
RECV_2=$(getAddr $POOR)
assertFalse "requires arg" "${CLIENT_EXE} query account 2>/dev/null"
assertFalse "has no genesis account" "${CLIENT_EXE} query account $RECV_2 2>/dev/null"
assertFalse "line=${LINENO}, requires arg" "${CLIENT_EXE} query account 2>/dev/null"
assertFalse "line=${LINENO}, has no genesis account" "${CLIENT_EXE} query account $RECV_2 2>/dev/null"
checkAccount $SENDER_2 "0" "9007199254740992"
# Make sure that they have different addresses on both chains (they are random keys)
assertNotEquals "sender keys must be different" "$SENDER_1" "$SENDER_2"
assertNotEquals "recipient keys must be different" "$RECV_1" "$RECV_2"
assertNotEquals "line=${LINENO}, sender keys must be different" "$SENDER_1" "$SENDER_2"
assertNotEquals "line=${LINENO}, recipient keys must be different" "$RECV_1" "$RECV_2"
}
test01SendIBCTx() {
@ -105,7 +105,7 @@ test01SendIBCTx() {
# Make sure nothing arrived - yet
waitForBlock ${PORT_1}
assertFalse "no relay running" "BC_HOME=${CLIENT_2} ${CLIENT_EXE} query account $RECV"
assertFalse "line=${LINENO}, no relay running" "BC_HOME=${CLIENT_2} ${CLIENT_EXE} query account $RECV"
# Start the relay and wait a few blocks...
# (already sent a tx on chain1, so use higher sequence)

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 "line=${LINENO}" $?; 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 "line=${LINENO}" "ls ${BCHOME} 2>/dev/null >&2"
echo y | ${CLIENT_EXE} init --node=tcp://localhost:46657 --chain-id="${CHAIN_ID}" > /dev/null
assertTrue "line=${LINENO}, initialized light-client" $?
checkDir $BCHOME 3
}
test02badInit() {
export BCHOME=${BASE}/client-02
assertFalse "line=${LINENO}" "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 "line=${LINENO}, 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 "line=${LINENO}, 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 "line=${LINENO}, invalid init" $?
checkDir $BCHOME 0
}
test03noDoubleInit() {
export BCHOME=${BASE}/client-03
assertFalse "line=${LINENO}" "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 "line=${LINENO}, 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 "line=${LINENO}, 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 "line=${LINENO}, re-initialized light-client" $?
checkDir $BCHOME 3
}
test04acceptGenesisFile() {
export BCHOME=${BASE}/client-04
assertFalse "line=${LINENO}" "ls ${BCHOME} 2>/dev/null >&2"
# init properly
${CLIENT_EXE} init --node=tcp://localhost:46657 --genesis=${GENESIS_FILE} > /dev/null 2>&1
assertTrue "line=${LINENO}, initialized light-client" $?
checkDir $BCHOME 3
}
# XXX Ex: checkDir $DIR $FILES
# Makes sure directory exists and has the given number of files
checkDir() {
assertTrue "line=${LINENO}" "ls ${1} 2>/dev/null >&2"
assertEquals "line=${LINENO}, 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 "line ${LINENO}" $?
}
newKey(){
assertNotNull "keyname required" "$1"
KEYPASS=${2:-qwertyuiop}
echo $KEYPASS | ${CLIENT_EXE} keys new $1 >/dev/null 2>&1
assertTrue "line ${LINENO}, created $1" $?
}
testMakeKeys() {
USER=demouser
assertFalse "line ${LINENO}, already user $USER" "${CLIENT_EXE} keys get $USER"
newKey $USER
assertTrue "line ${LINENO}, 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

93
tests/cli/roles.sh Executable file
View File

@ -0,0 +1,93 @@
#!/bin/bash
# These global variables are required for common.sh
SERVER_EXE=basecoin
CLIENT_EXE=basecli
ACCOUNTS=(jae ethan bucky rigel igor)
RICH=${ACCOUNTS[0]}
POOR=${ACCOUNTS[4]}
DUDE=${ACCOUNTS[2]}
oneTimeSetUp() {
if ! quickSetup .basecoin_test_roles roles-chain; then
exit 1;
fi
}
oneTimeTearDown() {
quickTearDown
}
test01SetupRole() {
ONE=$(getAddr $RICH)
TWO=$(getAddr $POOR)
THREE=$(getAddr $DUDE)
MEMBERS=${ONE},${TWO},${THREE}
SIGS=2
assertFalse "line=${LINENO}, missing min-sigs" "echo qwertyuiop | ${CLIENT_EXE} tx create-role --role=bank --members=${MEMBERS} --sequence=1 --name=$RICH"
assertFalse "line=${LINENO}, missing members" "echo qwertyuiop | ${CLIENT_EXE} tx create-role --role=bank --min-sigs=2 --sequence=1 --name=$RICH"
assertFalse "line=${LINENO}, missing role" "echo qwertyuiop | ${CLIENT_EXE} tx create-role --min-sigs=2 --members=${MEMBERS} --sequence=1 --name=$RICH"
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx create-role --role=bank --min-sigs=$SIGS --members=${MEMBERS} --sequence=1 --name=$RICH)
txSucceeded $? "$TX" "bank"
HASH=$(echo $TX | jq .hash | tr -d \")
TX_HEIGHT=$(echo $TX | jq .height)
checkRole bank $SIGS 3
# Make sure tx is indexed
checkRoleTx $HASH $TX_HEIGHT "bank" 3
}
test02SendTxToRole() {
SENDER=$(getAddr $RICH)
RECV=role:$(toHex bank)
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --fee=90mycoin --amount=10000mycoin --to=$RECV --sequence=2 --name=$RICH)
txSucceeded $? "$TX" "bank"
HASH=$(echo $TX | jq .hash | tr -d \")
TX_HEIGHT=$(echo $TX | jq .height)
# reduce by 10090
checkAccount $SENDER "9007199254730902"
checkAccount $RECV "10000"
checkSendFeeTx $HASH $TX_HEIGHT $SENDER "10000" "90"
}
test03SendMultiFromRole() {
ONE=$(getAddr $RICH)
TWO=$(getAddr $POOR)
THREE=$(getAddr $DUDE)
BANK=role:$(toHex bank)
# no money to start mr. poor...
assertFalse "line=${LINENO}, has no money yet" "${CLIENT_EXE} query account $TWO 2>/dev/null"
# let's try to send money from the role directly without multisig
FAIL=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=6000mycoin --from=$BANK --to=$TWO --sequence=1 --name=$POOR 2>/dev/null)
assertFalse "need to assume role" $?
FAIL=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=6000mycoin --from=$BANK --to=$TWO --sequence=1 --assume-role=bank --name=$POOR 2>/dev/null)
assertFalse "need two signatures" $?
# okay, begin a multisig transaction mr. poor...
TX_FILE=$BASE_DIR/tx.json
echo qwertyuiop | ${CLIENT_EXE} tx send --amount=6000mycoin --from=$BANK --to=$TWO --sequence=1 --assume-role=bank --name=$POOR --multi --prepare=$TX_FILE
assertTrue "line=${LINENO}, successfully prepare tx" $?
# and get some dude to sign it
FAIL=$(echo qwertyuiop | ${CLIENT_EXE} tx --in=$TX_FILE --name=$POOR 2>/dev/null)
assertFalse "line=${LINENO}, double signing doesn't get bank" $?
# and get some dude to sign it for the full access
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx --in=$TX_FILE --name=$DUDE)
txSucceeded $? "$TX" "multi-bank"
checkAccount $TWO "6000"
checkAccount $BANK "4000"
}
# Load common then run these tests with shunit2!
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory
. $DIR/common.sh
. $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 "line=${LINENO}" $?; 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 "line=${LINENO}, 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 "line=${LINENO}, get genesis" "$?"
MYCHAIN=$(echo ${GENESIS} | jq .genesis.chain_id | tr -d \")
assertEquals "line=${LINENO}, genesis chain matches" "${CHAIN_ID}" "${MYCHAIN}"
STATUS=$(${CLIENT_EXE} rpc status)
assertTrue "line=${LINENO}, get status" "$?"
SHEIGHT=$(echo ${STATUS} | jq .latest_block_height)
assertTrue "line=${LINENO}, parsed status" "$?"
assertNotNull "line=${LINENO}, has a height" "${SHEIGHT}"
VALS=$(${CLIENT_EXE} rpc validators)
assertTrue "line=${LINENO}, get validators" "$?"
VHEIGHT=$(echo ${VALS} | jq .block_height)
assertTrue "line=${LINENO}, parsed validators" "$?"
assertTrue "line=${LINENO}, sensible heights: $SHEIGHT / $VHEIGHT" "test $VHEIGHT -ge $SHEIGHT"
VCNT=$(echo ${VALS} | jq '.validators | length')
assertEquals "line=${LINENO}, one validator" "1" "$VCNT"
INFO=$(${CLIENT_EXE} rpc info)
assertTrue "line=${LINENO}, get info" "$?"
DATA=$(echo $INFO | jq .response.data)
assertEquals "line=${LINENO}, basecoin info" '"Basecoin v0.6.1"' "$DATA"
}
test02GetSecure() {
HEIGHT=$(${CLIENT_EXE} rpc status | jq .latest_block_height)
assertTrue "line=${LINENO}, get status" "$?"
# check block produces something reasonable
assertFalse "line=${LINENO}, missing height" "${CLIENT_EXE} rpc block"
BLOCK=$(${CLIENT_EXE} rpc block --height=$HEIGHT)
assertTrue "line=${LINENO}, get block" "$?"
MHEIGHT=$(echo $BLOCK | jq .block_meta.header.height)
assertEquals "line=${LINENO}, meta height" "${HEIGHT}" "${MHEIGHT}"
BHEIGHT=$(echo $BLOCK | jq .block.header.height)
assertEquals "line=${LINENO}, meta height" "${HEIGHT}" "${BHEIGHT}"
# check commit produces something reasonable
assertFalse "line=${LINENO}, missing height" "${CLIENT_EXE} rpc commit"
let "CHEIGHT = $HEIGHT - 1"
COMMIT=$(${CLIENT_EXE} rpc commit --height=$CHEIGHT)
assertTrue "line=${LINENO}, get commit" "$?"
HHEIGHT=$(echo $COMMIT | jq .header.height)
assertEquals "line=${LINENO}, commit height" "${CHEIGHT}" "${HHEIGHT}"
assertEquals "line=${LINENO}, canonical" "true" $(echo $COMMIT | jq .canonical)
BSIG=$(echo $BLOCK | jq .block.last_commit)
CSIG=$(echo $COMMIT | jq .commit)
assertEquals "line=${LINENO}, 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 "line=${LINENO}, get headers" "$?"
assertEquals "line=${LINENO}, proper height" "$HEIGHT" $(echo $HEADERS | jq '.last_height')
assertEquals "line=${LINENO}, 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 "line=${LINENO}, commit and header" "$CHEAD" "$HHEAD"
}
test03Waiting() {
START=$(${CLIENT_EXE} rpc status | jq .latest_block_height)
assertTrue "line=${LINENO}, get status" "$?"
let "NEXT = $START + 5"
assertFalse "line=${LINENO}, no args" "${CLIENT_EXE} rpc wait"
assertFalse "line=${LINENO}, too long" "${CLIENT_EXE} rpc wait --height=1234"
assertTrue "line=${LINENO}, normal wait" "${CLIENT_EXE} rpc wait --height=$NEXT"
STEP=$(${CLIENT_EXE} rpc status | jq .latest_block_height)
assertEquals "line=${LINENO}, wait until height" "$NEXT" "$STEP"
let "NEXT = $STEP + 3"
assertTrue "line=${LINENO}, ${CLIENT_EXE} rpc wait --delta=3"
STEP=$(${CLIENT_EXE} rpc status | jq .latest_block_height)
assertEquals "line=${LINENO}, 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