Merge pull request #176 from tendermint/feature/cli-cleanup-aka-confuse-rigel
Feature/cli cleanup aka confuse rigel
This commit is contained in:
commit
d885413469
7
Makefile
7
Makefile
|
@ -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
|
||||
|
|
17
app/app.go
17
app/app.go
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package commands
|
||||
package auto
|
||||
|
||||
import (
|
||||
"os"
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package proofs
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
const (
|
||||
heightFlag = "height"
|
||||
)
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "query",
|
||||
Short: "Get and store merkle proofs for blockchain data",
|
||||
Long: `Proofs allows you to validate data and merkle proofs.
|
||||
|
||||
These proofs tie the data to a checkpoint, which is managed by "seeds".
|
||||
Here we can validate these proofs and import/export them to prove specific
|
||||
data to other peers as needed.
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.Flags().Int(heightFlag, 0, "Height to query (skip to use latest block)")
|
||||
}
|
|
@ -0,0 +1,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())
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -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, ""),
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package seeds
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "seeds",
|
||||
Short: "Verify seeds from your local store",
|
||||
Long: `Seeds allows you to inspect and update the validator set for the chain.
|
||||
|
||||
Since all security in a PoS system is based on having the correct validator
|
||||
set, it is important to inspect the seeds to maintain the security, which
|
||||
is used to verify all header and merkle proofs.
|
||||
`,
|
||||
}
|
|
@ -0,0 +1,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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
},
|
||||
}
|
|
@ -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)))
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"))
|
||||
|
|
|
@ -8,8 +8,6 @@ import (
|
|||
"path"
|
||||
"strings"
|
||||
|
||||
//"github.com/pkg/errors"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/go-crypto"
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
hash: 6eb1119dccf2ab4d0adb870a14cb4408047119be53c8ec4afeaa281bd1d2b457
|
||||
updated: 2017-06-28T13:09:42.542992443+02:00
|
||||
hash: 2fec08220d5d8cbc791523583b85f3fb68e3d65ead6802198d9c879a9e295b46
|
||||
updated: 2017-07-18T21:21:05.336445544+02:00
|
||||
imports:
|
||||
- name: github.com/bgentry/speakeasy
|
||||
version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd
|
||||
|
@ -111,6 +111,7 @@ imports:
|
|||
- example/dummy
|
||||
- server
|
||||
- types
|
||||
- version
|
||||
- name: github.com/tendermint/ed25519
|
||||
version: 1f52c6f8b8a5c7908aff4497c186af344b428925
|
||||
subpackages:
|
||||
|
@ -125,6 +126,7 @@ imports:
|
|||
- keys/server
|
||||
- keys/server/types
|
||||
- keys/storage/filestorage
|
||||
- keys/storage/memstorage
|
||||
- keys/wordlist
|
||||
- name: github.com/tendermint/go-wire
|
||||
version: 5f88da3dbc1a72844e6dfaf274ce87f851d488eb
|
||||
|
@ -132,17 +134,11 @@ imports:
|
|||
- data
|
||||
- data/base58
|
||||
- name: github.com/tendermint/light-client
|
||||
version: 489b726d8b358dbd9d8f6a15d18e8b9fe0a39269
|
||||
version: d63415027075bc5d74a98a718393b59b5c4279a5
|
||||
subpackages:
|
||||
- certifiers
|
||||
- certifiers/client
|
||||
- certifiers/files
|
||||
- commands
|
||||
- commands/proofs
|
||||
- commands/proxy
|
||||
- commands/rpc
|
||||
- commands/seeds
|
||||
- commands/txs
|
||||
- proofs
|
||||
- name: github.com/tendermint/merkleeyes
|
||||
version: 102aaf5a8ffda1846413fb22805a94def2045b9f
|
||||
|
@ -151,7 +147,7 @@ imports:
|
|||
- client
|
||||
- iavl
|
||||
- name: github.com/tendermint/tendermint
|
||||
version: 3065059da7bb57714f08c7a6fcb97e4b36be0194
|
||||
version: 695ad5fe2d70ec7b6fcfe0b46a73cc1b2d55e0ac
|
||||
subpackages:
|
||||
- blockchain
|
||||
- cmd/tendermint/commands
|
||||
|
|
|
@ -22,13 +22,12 @@ import:
|
|||
subpackages:
|
||||
- data
|
||||
- package: github.com/tendermint/light-client
|
||||
version: develop
|
||||
version: unstable
|
||||
subpackages:
|
||||
- commands
|
||||
- commands/proofs
|
||||
- commands/seeds
|
||||
- commands/txs
|
||||
- proofs
|
||||
- certifiers
|
||||
- certifiers/client
|
||||
- certifiers/files
|
||||
- package: github.com/tendermint/merkleeyes
|
||||
version: develop
|
||||
subpackages:
|
||||
|
|
|
@ -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")
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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())
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue