From 7f3a6e0c042250987adf936abd72a98378510837 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 14 Mar 2018 20:22:06 +0100 Subject: [PATCH 1/3] Options -> AppState --- client/keys.go | 4 +- examples/basecoin/cmd/basecli/main.go | 3 +- examples/basecoin/cmd/basecoind/main.go | 1 + server/init.go | 79 +++++-------------------- server/start.go | 16 ++--- 5 files changed, 27 insertions(+), 76 deletions(-) diff --git a/client/keys.go b/client/keys.go index 7e150de7a2..47eb0b9c95 100644 --- a/client/keys.go +++ b/client/keys.go @@ -6,8 +6,8 @@ import ( dbm "github.com/tendermint/tmlibs/db" ) -// TODO explain what the keybase is used for -// GetKeyBase initializes a keybase based on the configuration +// GetKeyBase initializes a keybase based on the given db. +// The KeyBase manages all activity requiring access to a key. func GetKeyBase(db dbm.DB) keys.Keybase { keybase := keys.New( db, diff --git a/examples/basecoin/cmd/basecli/main.go b/examples/basecoin/cmd/basecli/main.go index 7f7377b483..5260a90f7f 100644 --- a/examples/basecoin/cmd/basecli/main.go +++ b/examples/basecoin/cmd/basecli/main.go @@ -13,13 +13,14 @@ import ( "github.com/cosmos/cosmos-sdk/client/lcd" "github.com/cosmos/cosmos-sdk/client/rpc" "github.com/cosmos/cosmos-sdk/client/tx" - coolcmd "github.com/cosmos/cosmos-sdk/examples/basecoin/x/cool/commands" + "github.com/cosmos/cosmos-sdk/version" authcmd "github.com/cosmos/cosmos-sdk/x/auth/commands" bankcmd "github.com/cosmos/cosmos-sdk/x/bank/commands" "github.com/cosmos/cosmos-sdk/examples/basecoin/app" "github.com/cosmos/cosmos-sdk/examples/basecoin/types" + coolcmd "github.com/cosmos/cosmos-sdk/examples/basecoin/x/cool/commands" ) // gaiacliCmd is the entry point for this binary diff --git a/examples/basecoin/cmd/basecoind/main.go b/examples/basecoin/cmd/basecoind/main.go index 44ea00fbba..56dfcc02f2 100644 --- a/examples/basecoin/cmd/basecoind/main.go +++ b/examples/basecoin/cmd/basecoind/main.go @@ -59,6 +59,7 @@ func generateApp(rootDir string, logger log.Logger) (abci.Application, error) { } func main() { + // TODO: set logger through CLI logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)). With("module", "main") diff --git a/server/init.go b/server/init.go index f2134abcd0..e506720f8c 100644 --- a/server/init.go +++ b/server/init.go @@ -2,30 +2,24 @@ package server import ( "encoding/json" - "fmt" "io/ioutil" "github.com/spf13/cobra" - "github.com/tendermint/go-crypto/keys" - "github.com/tendermint/go-crypto/keys/words" cmn "github.com/tendermint/tmlibs/common" - dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" cfg "github.com/tendermint/tendermint/config" tmtypes "github.com/tendermint/tendermint/types" - - sdk "github.com/cosmos/cosmos-sdk/types" ) // InitCmd will initialize all files for tendermint, -// along with proper app_options. +// along with proper app_state. // The application can pass in a function to generate -// proper options. And may want to use GenerateCoinKey +// proper state. And may want to use GenerateCoinKey // to create default account(s). -func InitCmd(gen GenOptions, logger log.Logger) *cobra.Command { +func InitCmd(gen GenAppState, logger log.Logger) *cobra.Command { cmd := initCmd{ gen: gen, logger: logger, @@ -37,39 +31,14 @@ func InitCmd(gen GenOptions, logger log.Logger) *cobra.Command { } } -// GenOptions can parse command-line and flag to -// generate default app_options for the genesis file. +// GenAppState can parse command-line and flag to +// generate default app_state for the genesis file. // This is application-specific -type GenOptions func(args []string) (json.RawMessage, error) - -// GenerateCoinKey returns the address of a public key, -// along with the secret phrase to recover the private key. -// You can give coins to this address and return the recovery -// phrase to the user to access them. -func GenerateCoinKey() (sdk.Address, string, error) { - // construct an in-memory key store - codec, err := words.LoadCodec("english") - if err != nil { - return nil, "", err - } - keybase := keys.New( - dbm.NewMemDB(), - codec, - ) - - // generate a private key, with recovery phrase - info, secret, err := keybase.Create("name", "pass", keys.AlgoEd25519) - if err != nil { - return nil, "", err - } - - addr := info.PubKey.Address() - return addr, secret, nil -} +type GenAppState func(args []string) (json.RawMessage, error) type initCmd struct { - gen GenOptions - logger log.Logger + genAppState GenAppState + logger log.Logger } func (c initCmd) run(cmd *cobra.Command, args []string) error { @@ -85,19 +54,19 @@ func (c initCmd) run(cmd *cobra.Command, args []string) error { } // no app_options, leave like tendermint - if c.gen == nil { + if c.genAppState == nil { return nil } - // Now, we want to add the custom app_options - options, err := c.gen(args) + // Now, we want to add the custom app_state + appState, err := c.genAppState(args) if err != nil { return err } // And add them to the genesis file genFile := config.GenesisFile() - return addGenesisOptions(genFile, options) + return addGenesisState(genFile, appState) } // This was copied from tendermint/cmd/tendermint/commands/init.go @@ -141,7 +110,7 @@ func (c initCmd) initTendermintFiles(config *cfg.Config) error { // so we can add one line. type GenesisDoc map[string]json.RawMessage -func addGenesisOptions(filename string, options json.RawMessage) error { +func addGenesisState(filename string, appState json.RawMessage) error { bz, err := ioutil.ReadFile(filename) if err != nil { return err @@ -153,7 +122,7 @@ func addGenesisOptions(filename string, options json.RawMessage) error { return err } - doc["app_state"] = options + doc["app_state"] = appState out, err := json.MarshalIndent(doc, "", " ") if err != nil { return err @@ -161,23 +130,3 @@ func addGenesisOptions(filename string, options json.RawMessage) error { return ioutil.WriteFile(filename, out, 0600) } - -// GetGenesisJSON returns a new tendermint genesis with Basecoin app_options -// that grant a large amount of "mycoin" to a single address -// TODO: A better UX for generating genesis files -func GetGenesisJSON(pubkey, chainID, denom, addr string, options string) string { - return fmt.Sprintf(`{ - "accounts": [{ - "address": "%s", - "coins": [ - { - "denom": "%s", - "amount": 9007199254740992 - } - ] - }], - "plugin_options": [ - "coin/issuer", {"app": "sigs", "addr": "%s"}%s - ] -}`, addr, denom, addr, options) -} diff --git a/server/start.go b/server/start.go index 1424c81532..530a3a3e4b 100644 --- a/server/start.go +++ b/server/start.go @@ -23,14 +23,14 @@ const ( // appGenerator lets us lazily initialize app, using home dir // and other flags (?) to start -type appGenerator func(string, log.Logger) (abci.Application, error) +type appCreator func(string, log.Logger) (abci.Application, error) // StartCmd runs the service passed in, either // stand-alone, or in-process with tendermint -func StartCmd(app appGenerator, logger log.Logger) *cobra.Command { +func StartCmd(app appCreator, logger log.Logger) *cobra.Command { start := startCmd{ - app: app, - logger: logger, + appCreator: appCreator, + logger: logger, } cmd := &cobra.Command{ Use: "start", @@ -48,8 +48,8 @@ func StartCmd(app appGenerator, logger log.Logger) *cobra.Command { } type startCmd struct { - app appGenerator - logger log.Logger + appCreator appCreator + logger log.Logger } func (s startCmd) run(cmd *cobra.Command, args []string) error { @@ -65,7 +65,7 @@ func (s startCmd) startStandAlone() error { // Generate the app in the proper dir addr := viper.GetString(flagAddress) home := viper.GetString("home") - app, err := s.app(home, s.logger) + app, err := s.appCreator(home, s.logger) if err != nil { return err } @@ -92,7 +92,7 @@ func (s startCmd) startInProcess() error { } home := cfg.RootDir - app, err := s.app(home, s.logger) + app, err := s.appCreator(home, s.logger) if err != nil { return err } From d807d32f8a980a96062441e34b3119d9426e3d01 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 15 Mar 2018 03:16:54 +0100 Subject: [PATCH 2/3] client/lcd: wip use in-proc tm and lcd for tests --- client/lcd/lcd_test.go | 65 ++++++++++++++++++++++----- client/lcd/main_test.go | 24 ++++++++++ client/lcd/root.go | 32 ++++++++----- examples/basecoin/cmd/basecli/main.go | 4 ++ x/bank/rest/root.go | 9 ++-- x/bank/rest/sendtx.go | 10 +---- 6 files changed, 113 insertions(+), 31 deletions(-) create mode 100644 client/lcd/main_test.go diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 9d27248d70..55ddcfe09c 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net" "net/http" "os" "path/filepath" @@ -12,18 +13,18 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - cryptoKeys "github.com/tendermint/go-crypto/keys" - "github.com/tendermint/tendermint/p2p" - ctypes "github.com/tendermint/tendermint/rpc/core/types" - tmtypes "github.com/tendermint/tendermint/types" - - "github.com/cosmos/cosmos-sdk/client" + "github.com/chain/core/config" + client "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/tests" - "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/wire" + auth "github.com/cosmos/cosmos-sdk/x/auth/rest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + p2p "github.com/tendermint/go-p2p" + "github.com/tendermint/mintdb/types" + "github.com/tendermint/tendermint/proxy" + "github.com/tendermint/tmlibs/log" ) func TestKeys(t *testing.T) { @@ -299,9 +300,53 @@ func setupEnvironment(t *testing.T) (kill func(), port string, seed string) { cmdNode.Process.Wait() os.Remove(dir) } +} + +// strt TM and the LCD in process, listening on their respective sockets +func startTMAndLCD(t *testing.T) (kill func(), port string, seed string) { + + // make the keybase and its key ... + + startTM(cfg, genDoc, app) + startLCD(cdc, listenAddr, logger) + + kill = func() { + // TODO: cleanup + // TODO: it would be great if TM could run without + // persiting anything in the first place + } return kill, port, seed } +// Create & start in-process tendermint node with memdb +// and in-process abci application. +// TODO: need to clean up the WAL dir or enable it to be not persistent +func startTM(cfg *config.Config, genDoc types.GenesisDoc, app abci.Application) (*Node, error) { + genDocProvider := func() (*types.GenesisDoc, error) { return genDoc, nil } + dbProvider := func() (*dbm.DB, error) { return dbm.NewMemDB(), nil } + n, err := node.NewNode(cfg, + privVal, + proxy.NewLocalClientCreator(app), + genDocProvider, + dbProvider, + logger.With("module", "node")) + if err != nil { + return nil, err + } + + err = n.Start() + if err != nil { + return nil, err + } + return n, err +} + +// start the LCD. note this blocks! +func startLCD(cdc *wire.Codec, listenAddr string, logger log.Logger) (net.Listener, error) { + handler := createHandler(cdc) + return StartHTTPServer(listenAddr, handler, logger) +} + func request(t *testing.T, port, method, path string, payload []byte) (*http.Response, string) { var res *http.Response var err error diff --git a/client/lcd/main_test.go b/client/lcd/main_test.go new file mode 100644 index 0000000000..4043098217 --- /dev/null +++ b/client/lcd/main_test.go @@ -0,0 +1,24 @@ +package client_test + +import ( + "os" + "testing" + + nm "github.com/tendermint/tendermint/node" +) + +var node *nm.Node + +// See https://golang.org/pkg/testing/#hdr-Main +// for more details +func TestMain(m *testing.M) { + // start a basecoind node and LCD server in the background to test against + + // run all the tests against a single server instance + code := m.Run() + + // tear down + + // + os.Exit(code) +} diff --git a/client/lcd/root.go b/client/lcd/root.go index 8476d16d5e..b0ffa2f6da 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -2,10 +2,12 @@ package lcd import ( "net/http" + "os" "github.com/gorilla/mux" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/tendermint/tmlibs/log" client "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/client/keys" @@ -18,8 +20,8 @@ import ( ) const ( - flagBind = "bind" - flagCORS = "cors" + flagListenAddr = "laddr" + flagCORS = "cors" ) // ServeCommand will generate a long-running rest server @@ -29,25 +31,35 @@ func ServeCommand(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "rest-server", Short: "Start LCD (light-client daemon), a local REST server", - RunE: startRESTServer(cdc), + RunE: startRESTServerFn(cdc), } - // TODO: handle unix sockets also? - cmd.Flags().StringP(flagBind, "b", "localhost:1317", "Interface and port that server binds to") + cmd.Flags().StringP(flagListenAddr, "a", "tcp://localhost:1317", "Address for server to listen on") cmd.Flags().String(flagCORS, "", "Set to domains that can make CORS requests (* for all)") cmd.Flags().StringP(client.FlagChainID, "c", "", "ID of chain we connect to") cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to") return cmd } -func startRESTServer(cdc *wire.Codec) func(cmd *cobra.Command, args []string) error { +func startRESTServerFn(cdc *wire.Codec) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { - bind := viper.GetString(flagBind) - r := initRouter(cdc) - return http.ListenAndServe(bind, r) + listenAddr := viper.GetString(flagListenAddr) + handler := createHandler(cdc) + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)). + With("module", "rest-server") + listener, err := StartHTTPServer(listenAddr, handler, logger) + if err != nil { + return err + } + + // Wait forever and cleanup + cmn.TrapSignal(func() { + err := listener.Close() + logger.Error("Error closing listener", "err", err) + }) } } -func initRouter(cdc *wire.Codec) http.Handler { +func createHandler(cdc *wire.Codec) http.Handler { r := mux.NewRouter() r.HandleFunc("/version", version.VersionRequestHandler).Methods("GET") diff --git a/examples/basecoin/cmd/basecli/main.go b/examples/basecoin/cmd/basecli/main.go index 5260a90f7f..5f7b337900 100644 --- a/examples/basecoin/cmd/basecli/main.go +++ b/examples/basecoin/cmd/basecli/main.go @@ -42,6 +42,10 @@ func main() { // get the codec cdc := app.MakeCodec() + // TODO: setup keybase, viper object, etc. to be passed into + // the below functions and eliminate global vars, like we do + // with the cdc + // add standard rpc, and tx commands rpc.AddCommands(basecliCmd) basecliCmd.AddCommand(client.LineBreak) diff --git a/x/bank/rest/root.go b/x/bank/rest/root.go index 77bb991cb0..4534482a93 100644 --- a/x/bank/rest/root.go +++ b/x/bank/rest/root.go @@ -1,11 +1,14 @@ package rest import ( - "github.com/cosmos/cosmos-sdk/wire" "github.com/gorilla/mux" + + keys "github.com/tendermint/go-crypto/keys" + + "github.com/cosmos/cosmos-sdk/wire" ) // RegisterRoutes - Central function to define routes that get registered by the main application -func RegisterRoutes(r *mux.Router, cdc *wire.Codec) { - r.HandleFunc("/accounts/{address}/send", SendRequestHandler(cdc)).Methods("POST") +func RegisterRoutes(r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { + r.HandleFunc("/accounts/{address}/send", SendRequestHandler(cdc, kb)).Methods("POST") } diff --git a/x/bank/rest/sendtx.go b/x/bank/rest/sendtx.go index 407a53420e..4b1c8f12a0 100644 --- a/x/bank/rest/sendtx.go +++ b/x/bank/rest/sendtx.go @@ -7,6 +7,7 @@ import ( "net/http" "github.com/gorilla/mux" + "github.com/tendermint/go-crypto/keys" "github.com/cosmos/cosmos-sdk/client/builder" "github.com/cosmos/cosmos-sdk/client/keys" @@ -26,7 +27,7 @@ type sendBody struct { } // SendRequestHandler - http request handler to send coins to a address -func SendRequestHandler(cdc *wire.Codec) func(http.ResponseWriter, *http.Request) { +func SendRequestHandler(cdc *wire.Codec, kb keys.Keybase) func(http.ResponseWriter, *http.Request) { c := commands.Commander{cdc} return func(w http.ResponseWriter, r *http.Request) { // collect data @@ -47,13 +48,6 @@ func SendRequestHandler(cdc *wire.Codec) func(http.ResponseWriter, *http.Request return } - kb, err := keys.GetKeyBase() - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - return - } - info, err := kb.Get(m.LocalAccountName) if err != nil { w.WriteHeader(http.StatusUnauthorized) From 64852138b6aedc4bd3c8dabc4b9ce3385734700f Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 18 Mar 2018 01:42:18 +0100 Subject: [PATCH 3/3] fix the tests. includes some hacks --- client/builder/builder.go | 1 + client/lcd/helpers.go | 69 +++++++++++ client/lcd/lcd_test.go | 252 +++++++++++++++++++++++--------------- client/lcd/main_test.go | 18 ++- client/lcd/root.go | 15 ++- server/init.go | 4 +- server/key.go | 34 +++++ server/start.go | 2 +- server/start_test.go | 6 +- types/tx_msg.go | 2 - x/auth/ante.go | 7 ++ x/bank/rest/sendtx.go | 5 +- 12 files changed, 301 insertions(+), 114 deletions(-) create mode 100644 client/lcd/helpers.go create mode 100644 server/key.go diff --git a/client/builder/builder.go b/client/builder/builder.go index 69c95fa3b6..2fb1c824e8 100644 --- a/client/builder/builder.go +++ b/client/builder/builder.go @@ -106,6 +106,7 @@ func SignAndBuild(name, passphrase string, msg sdk.Msg, cdc *wire.Codec) ([]byte // sign and build bz := signMsg.Bytes() + sig, pubkey, err := keybase.Sign(name, passphrase, bz) if err != nil { return nil, err diff --git a/client/lcd/helpers.go b/client/lcd/helpers.go new file mode 100644 index 0000000000..71278fca33 --- /dev/null +++ b/client/lcd/helpers.go @@ -0,0 +1,69 @@ +package lcd + +// NOTE: COPIED VERBATIM FROM tendermint/tendermint/rpc/test/helpers.go + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + cmn "github.com/tendermint/tmlibs/common" + + cfg "github.com/tendermint/tendermint/config" + ctypes "github.com/tendermint/tendermint/rpc/core/types" + rpcclient "github.com/tendermint/tendermint/rpc/lib/client" +) + +var globalConfig *cfg.Config + +func waitForRPC() { + laddr := GetConfig().RPC.ListenAddress + fmt.Println("LADDR", laddr) + client := rpcclient.NewJSONRPCClient(laddr) + result := new(ctypes.ResultStatus) + for { + _, err := client.Call("status", map[string]interface{}{}, result) + if err == nil { + return + } + } +} + +// f**ing long, but unique for each test +func makePathname() string { + // get path + p, err := os.Getwd() + if err != nil { + panic(err) + } + // fmt.Println(p) + sep := string(filepath.Separator) + return strings.Replace(p, sep, "_", -1) +} + +func randPort() int { + return int(cmn.RandUint16()/2 + 10000) +} + +func makeAddrs() (string, string, string) { + start := randPort() + return fmt.Sprintf("tcp://0.0.0.0:%d", start), + fmt.Sprintf("tcp://0.0.0.0:%d", start+1), + fmt.Sprintf("tcp://0.0.0.0:%d", start+2) +} + +// GetConfig returns a config for the test cases as a singleton +func GetConfig() *cfg.Config { + if globalConfig == nil { + pathname := makePathname() + globalConfig = cfg.ResetTestRoot(pathname) + + // and we use random ports to run in parallel + tm, rpc, _ := makeAddrs() + globalConfig.P2P.ListenAddress = tm + globalConfig.RPC.ListenAddress = rpc + globalConfig.TxIndex.IndexTags = "app.creator" // see kvstore application + } + return globalConfig +} diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 55ddcfe09c..99c040f1a3 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -8,50 +8,76 @@ import ( "net" "net/http" "os" - "path/filepath" "regexp" "testing" "time" - "github.com/chain/core/config" - client "github.com/cosmos/cosmos-sdk/client" - keys "github.com/cosmos/cosmos-sdk/client/keys" - "github.com/cosmos/cosmos-sdk/tests" - "github.com/cosmos/cosmos-sdk/wire" - auth "github.com/cosmos/cosmos-sdk/x/auth/rest" + "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - p2p "github.com/tendermint/go-p2p" - "github.com/tendermint/mintdb/types" + + abci "github.com/tendermint/abci/types" + cryptoKeys "github.com/tendermint/go-crypto/keys" + tmcfg "github.com/tendermint/tendermint/config" + nm "github.com/tendermint/tendermint/node" + p2p "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/proxy" + ctypes "github.com/tendermint/tendermint/rpc/core/types" + tmrpc "github.com/tendermint/tendermint/rpc/lib/server" + tmtypes "github.com/tendermint/tendermint/types" + dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" + + client "github.com/cosmos/cosmos-sdk/client" + keys "github.com/cosmos/cosmos-sdk/client/keys" + bapp "github.com/cosmos/cosmos-sdk/examples/basecoin/app" + btypes "github.com/cosmos/cosmos-sdk/examples/basecoin/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" +) + +var ( + coinDenom = "mycoin" + coinAmount = int64(10000000) + + // XXX bad globals + port string // XXX: but it's the int ... + name string = "test" + password string = "0123456789" + seed string + sendAddr string ) func TestKeys(t *testing.T) { - kill, port, _ := setupEnvironment(t) - defer kill() // empty keys - res, body := request(t, port, "GET", "/keys", nil) - require.Equal(t, http.StatusOK, res.StatusCode, body) - assert.Equal(t, "[]", body, "Expected an empty array") + // XXX: the test comes with a key setup + /* + res, body := request(t, port, "GET", "/keys", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + assert.Equal(t, "[]", body, "Expected an empty array") + */ // get seed - res, body = request(t, port, "GET", "/keys/seed", nil) + res, body := request(t, port, "GET", "/keys/seed", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - seed := body + newSeed := body reg, err := regexp.Compile(`([a-z]+ ){12}`) require.Nil(t, err) match := reg.MatchString(seed) assert.True(t, match, "Returned seed has wrong foramt", seed) + newName := "test_newname" + newPassword := "0987654321" + // add key - var jsonStr = []byte(`{"name":"test_fail", "password":"1234567890"}`) + var jsonStr = []byte(fmt.Sprintf(`{"name":"test_fail", "password":"%s"}`, password)) res, body = request(t, port, "POST", "/keys", jsonStr) assert.Equal(t, http.StatusBadRequest, res.StatusCode, "Account creation should require a seed") - jsonStr = []byte(fmt.Sprintf(`{"name":"test", "password":"1234567890", "seed": "%s"}`, seed)) + jsonStr = []byte(fmt.Sprintf(`{"name":"%s", "password":"%s", "seed": "%s"}`, newName, newPassword, newSeed)) res, body = request(t, port, "POST", "/keys", jsonStr) assert.Equal(t, http.StatusOK, res.StatusCode, body) @@ -61,41 +87,42 @@ func TestKeys(t *testing.T) { // existing keys res, body = request(t, port, "GET", "/keys", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var m [1]keys.KeyOutput + var m [2]keys.KeyOutput err = json.Unmarshal([]byte(body), &m) require.Nil(t, err) - assert.Equal(t, m[0].Name, "test", "Did not serve keys name correctly") - assert.Equal(t, m[0].Address, addr, "Did not serve keys Address correctly") + assert.Equal(t, m[0].Name, name, "Did not serve keys name correctly") + assert.Equal(t, m[0].Address, sendAddr, "Did not serve keys Address correctly") + assert.Equal(t, m[1].Name, newName, "Did not serve keys name correctly") + assert.Equal(t, m[1].Address, addr, "Did not serve keys Address correctly") // select key - res, body = request(t, port, "GET", "/keys/test", nil) + keyEndpoint := fmt.Sprintf("/keys/%s", newName) + res, body = request(t, port, "GET", keyEndpoint, nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var m2 keys.KeyOutput err = json.Unmarshal([]byte(body), &m2) require.Nil(t, err) - assert.Equal(t, "test", m2.Name, "Did not serve keys name correctly") + assert.Equal(t, newName, m2.Name, "Did not serve keys name correctly") assert.Equal(t, addr, m2.Address, "Did not serve keys Address correctly") // update key - jsonStr = []byte(`{"old_password":"1234567890", "new_password":"12345678901"}`) - res, body = request(t, port, "PUT", "/keys/test", jsonStr) + jsonStr = []byte(fmt.Sprintf(`{"old_password":"%s", "new_password":"12345678901"}`, newPassword)) + res, body = request(t, port, "PUT", keyEndpoint, jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) // here it should say unauthorized as we changed the password before - res, body = request(t, port, "PUT", "/keys/test", jsonStr) + res, body = request(t, port, "PUT", keyEndpoint, jsonStr) require.Equal(t, http.StatusUnauthorized, res.StatusCode, body) // delete key jsonStr = []byte(`{"password":"12345678901"}`) - res, body = request(t, port, "DELETE", "/keys/test", jsonStr) + res, body = request(t, port, "DELETE", keyEndpoint, jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) } func TestVersion(t *testing.T) { - kill, port, _ := setupEnvironment(t) - defer kill() // node info res, body := request(t, port, "GET", "/version", nil) @@ -108,8 +135,6 @@ func TestVersion(t *testing.T) { } func TestNodeStatus(t *testing.T) { - kill, port, _ := setupEnvironment(t) - defer kill() // node info res, body := request(t, port, "GET", "/node_info", nil) @@ -131,8 +156,6 @@ func TestNodeStatus(t *testing.T) { } func TestBlock(t *testing.T) { - kill, port, _ := setupEnvironment(t) - defer kill() time.Sleep(time.Second * 2) // TODO: LOL -> wait for blocks @@ -163,10 +186,6 @@ func TestBlock(t *testing.T) { } func TestValidators(t *testing.T) { - kill, port, _ := setupEnvironment(t) - defer kill() - - time.Sleep(time.Second * 2) // TODO: LOL -> wait for blocks var resultVals ctypes.ResultValidators @@ -195,17 +214,13 @@ func TestValidators(t *testing.T) { } func TestCoinSend(t *testing.T) { - kill, port, seed := setupEnvironment(t) - defer kill() - - time.Sleep(time.Second * 2) // TO // query empty res, body := request(t, port, "GET", "/accounts/8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6", nil) require.Equal(t, http.StatusNoContent, res.StatusCode, body) // create TX - addr, receiveAddr, resultTx := doSend(t, port, seed) + receiveAddr, resultTx := doSend(t, port, seed) time.Sleep(time.Second * 2) // T @@ -214,7 +229,7 @@ func TestCoinSend(t *testing.T) { assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) // query sender - res, body = request(t, port, "GET", "/accounts/"+addr, nil) + res, body = request(t, port, "GET", "/accounts/"+sendAddr, nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var m auth.BaseAccount @@ -222,8 +237,8 @@ func TestCoinSend(t *testing.T) { require.Nil(t, err) coins := m.Coins mycoins := coins[0] - assert.Equal(t, "mycoin", mycoins.Denom) - assert.Equal(t, int64(9007199254740991), mycoins.Amount) + assert.Equal(t, coinDenom, mycoins.Denom) + assert.Equal(t, coinAmount-1, mycoins.Amount) // query receiver res, body = request(t, port, "GET", "/accounts/"+receiveAddr, nil) @@ -233,13 +248,11 @@ func TestCoinSend(t *testing.T) { require.Nil(t, err) coins = m.Coins mycoins = coins[0] - assert.Equal(t, "mycoin", mycoins.Denom) + assert.Equal(t, coinDenom, mycoins.Denom) assert.Equal(t, int64(1), mycoins.Amount) } func TestTxs(t *testing.T) { - kill, port, seed := setupEnvironment(t) - defer kill() // TODO: re-enable once we can get txs by tag @@ -254,7 +267,7 @@ func TestTxs(t *testing.T) { // assert.Equal(t, "[]", body) // create TX - _, _, resultTx := doSend(t, port, seed) + _, resultTx := doSend(t, port, seed) time.Sleep(time.Second * 2) // TO @@ -278,53 +291,85 @@ func TestTxs(t *testing.T) { //__________________________________________________________ // helpers -// TODO/XXX: We should be spawning what we need in process, not shelling out -func setupEnvironment(t *testing.T) (kill func(), port string, seed string) { - dir, err := ioutil.TempDir("", "tmp-basecoin-") - require.Nil(t, err) - - seed = tests.TestInitBasecoin(t, dir) - // get chain ID - bz, err := ioutil.ReadFile(filepath.Join(dir, "config", "genesis.json")) - require.Nil(t, err) - var gen tmtypes.GenesisDoc - err = json.Unmarshal(bz, &gen) - require.Nil(t, err) - cmdNode := tests.StartNodeServerForTest(t, dir) - cmdLCD, port := tests.StartLCDServerForTest(t, dir, gen.ChainID) - - kill = func() { - cmdLCD.Process.Kill() - cmdLCD.Process.Wait() - cmdNode.Process.Kill() - cmdNode.Process.Wait() - os.Remove(dir) - } -} - // strt TM and the LCD in process, listening on their respective sockets -func startTMAndLCD(t *testing.T) (kill func(), port string, seed string) { +func startTMAndLCD() (*nm.Node, net.Listener, error) { - // make the keybase and its key ... - - startTM(cfg, genDoc, app) - startLCD(cdc, listenAddr, logger) - - kill = func() { - // TODO: cleanup - // TODO: it would be great if TM could run without - // persiting anything in the first place + kb, err := keys.GetKeyBase() // dbm.NewMemDB()) // :( + if err != nil { + return nil, nil, err } - return kill, port, seed + var info cryptoKeys.Info + info, seed, err = kb.Create(name, password, cryptoKeys.AlgoEd25519) // XXX global seed + if err != nil { + return nil, nil, err + } + + pubKey := info.PubKey + sendAddr = pubKey.Address().String() // XXX global + + config := GetConfig() + config.Consensus.TimeoutCommit = 1000 + config.Consensus.SkipTimeoutCommit = false + + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + logger = log.NewFilter(logger, log.AllowError()) + privValidatorFile := config.PrivValidatorFile() + privVal := tmtypes.LoadOrGenPrivValidatorFS(privValidatorFile) + app := bapp.NewBasecoinApp(logger, dbm.NewMemDB()) + + genesisFile := config.GenesisFile() + genDoc, err := tmtypes.GenesisDocFromFile(genesisFile) + if err != nil { + return nil, nil, err + } + + coins := sdk.Coins{{coinDenom, coinAmount}} + appState := btypes.GenesisState{ + Accounts: []*btypes.GenesisAccount{ + { + Name: "tester", + Address: pubKey.Address(), + Coins: coins, + }, + }, + } + stateBytes, err := json.Marshal(appState) + if err != nil { + return nil, nil, err + } + genDoc.AppState = stateBytes + + cdc := wire.NewCodec() + + // LCD listen address + port = fmt.Sprintf("%d", 17377) // XXX + listenAddr := fmt.Sprintf("tcp://localhost:%s", port) // XXX + + // XXX: need to set this so LCD knows the tendermint node address! + viper.Set(client.FlagNode, config.RPC.ListenAddress) + viper.Set(client.FlagChainID, genDoc.ChainID) + + node, err := startTM(config, logger, genDoc, privVal, app) + if err != nil { + return nil, nil, err + } + lcd, err := startLCD(cdc, logger, listenAddr) + if err != nil { + return nil, nil, err + } + + time.Sleep(time.Second * 2) + + return node, lcd, nil } // Create & start in-process tendermint node with memdb // and in-process abci application. // TODO: need to clean up the WAL dir or enable it to be not persistent -func startTM(cfg *config.Config, genDoc types.GenesisDoc, app abci.Application) (*Node, error) { - genDocProvider := func() (*types.GenesisDoc, error) { return genDoc, nil } - dbProvider := func() (*dbm.DB, error) { return dbm.NewMemDB(), nil } - n, err := node.NewNode(cfg, +func startTM(cfg *tmcfg.Config, logger log.Logger, genDoc *tmtypes.GenesisDoc, privVal tmtypes.PrivValidator, app abci.Application) (*nm.Node, error) { + genDocProvider := func() (*tmtypes.GenesisDoc, error) { return genDoc, nil } + dbProvider := func(*nm.DBContext) (dbm.DB, error) { return dbm.NewMemDB(), nil } + n, err := nm.NewNode(cfg, privVal, proxy.NewLocalClientCreator(app), genDocProvider, @@ -338,13 +383,18 @@ func startTM(cfg *config.Config, genDoc types.GenesisDoc, app abci.Application) if err != nil { return nil, err } + + // wait for rpc + waitForRPC() + + logger.Info("Tendermint running!") return n, err } // start the LCD. note this blocks! -func startLCD(cdc *wire.Codec, listenAddr string, logger log.Logger) (net.Listener, error) { +func startLCD(cdc *wire.Codec, logger log.Logger, listenAddr string) (net.Listener, error) { handler := createHandler(cdc) - return StartHTTPServer(listenAddr, handler, logger) + return tmrpc.StartHTTPServer(listenAddr, handler, logger) } func request(t *testing.T, port, method, path string, payload []byte) (*http.Response, string) { @@ -363,13 +413,7 @@ func request(t *testing.T, port, method, path string, payload []byte) (*http.Res return res, string(output) } -func doSend(t *testing.T, port, seed string) (sendAddr string, receiveAddr string, resultTx ctypes.ResultBroadcastTxCommit) { - // create account from seed who has keys - var jsonStr = []byte(fmt.Sprintf(`{"name":"test", "password":"1234567890", "seed": "%s"}`, seed)) - res, body := request(t, port, "POST", "/keys", jsonStr) - - assert.Equal(t, http.StatusOK, res.StatusCode, body) - sendAddr = body +func doSend(t *testing.T, port, seed string) (receiveAddr string, resultTx ctypes.ResultBroadcastTxCommit) { // create receive address kb := client.MockKeyBase() @@ -377,13 +421,23 @@ func doSend(t *testing.T, port, seed string) (sendAddr string, receiveAddr strin require.Nil(t, err) receiveAddr = receiveInfo.PubKey.Address().String() + // get the account to get the sequence + res, body := request(t, port, "GET", "/accounts/"+sendAddr, nil) + // require.Equal(t, http.StatusOK, res.StatusCode, body) + acc := auth.BaseAccount{} + err = json.Unmarshal([]byte(body), &acc) + require.Nil(t, err) + fmt.Println("BODY", body) + fmt.Println("ACC", acc) + sequence := acc.Sequence + // send - jsonStr = []byte(`{ "name":"test", "password":"1234567890", "amount":[{ "denom": "mycoin", "amount": 1 }] }`) + jsonStr := []byte(fmt.Sprintf(`{ "name":"%s", "password":"%s", "sequence":%d, "amount":[{ "denom": "%s", "amount": 1 }] }`, name, password, sequence, coinDenom)) res, body = request(t, port, "POST", "/accounts/"+receiveAddr+"/send", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) err = json.Unmarshal([]byte(body), &resultTx) require.Nil(t, err) - return sendAddr, receiveAddr, resultTx + return receiveAddr, resultTx } diff --git a/client/lcd/main_test.go b/client/lcd/main_test.go index 4043098217..9f0e2bd4f0 100644 --- a/client/lcd/main_test.go +++ b/client/lcd/main_test.go @@ -1,6 +1,7 @@ -package client_test +package lcd import ( + "fmt" "os" "testing" @@ -15,10 +16,23 @@ func TestMain(m *testing.M) { // start a basecoind node and LCD server in the background to test against // run all the tests against a single server instance + node, lcd, err := startTMAndLCD() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + code := m.Run() // tear down + // TODO: cleanup + // TODO: it would be great if TM could run without + // persiting anything in the first place + node.Stop() + node.Wait() + + // just a listener ... + lcd.Close() - // os.Exit(code) } diff --git a/client/lcd/root.go b/client/lcd/root.go index b0ffa2f6da..7f18af59dc 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -9,6 +9,9 @@ import ( "github.com/spf13/viper" "github.com/tendermint/tmlibs/log" + tmserver "github.com/tendermint/tendermint/rpc/lib/server" + cmn "github.com/tendermint/tmlibs/common" + client "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/client/keys" rpc "github.com/cosmos/cosmos-sdk/client/rpc" @@ -46,7 +49,7 @@ func startRESTServerFn(cdc *wire.Codec) func(cmd *cobra.Command, args []string) handler := createHandler(cdc) logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)). With("module", "rest-server") - listener, err := StartHTTPServer(listenAddr, handler, logger) + listener, err := tmserver.StartHTTPServer(listenAddr, handler, logger) if err != nil { return err } @@ -56,6 +59,7 @@ func startRESTServerFn(cdc *wire.Codec) func(cmd *cobra.Command, args []string) err := listener.Close() logger.Error("Error closing listener", "err", err) }) + return nil } } @@ -63,11 +67,16 @@ func createHandler(cdc *wire.Codec) http.Handler { r := mux.NewRouter() r.HandleFunc("/version", version.VersionRequestHandler).Methods("GET") + kb, err := keys.GetKeyBase() //XXX + if err != nil { + panic(err) + } + // TODO make more functional? aka r = keys.RegisterRoutes(r) keys.RegisterRoutes(r) rpc.RegisterRoutes(r) tx.RegisterRoutes(r, cdc) - auth.RegisterRoutes(r, cdc, "main") // TODO should use a variable not just a string - bank.RegisterRoutes(r, cdc) + auth.RegisterRoutes(r, cdc, "main") + bank.RegisterRoutes(r, cdc, kb) return r } diff --git a/server/init.go b/server/init.go index e506720f8c..12e330dbc8 100644 --- a/server/init.go +++ b/server/init.go @@ -21,8 +21,8 @@ import ( // to create default account(s). func InitCmd(gen GenAppState, logger log.Logger) *cobra.Command { cmd := initCmd{ - gen: gen, - logger: logger, + genAppState: gen, + logger: logger, } return &cobra.Command{ Use: "init", diff --git a/server/key.go b/server/key.go new file mode 100644 index 0000000000..aed1f9d1ff --- /dev/null +++ b/server/key.go @@ -0,0 +1,34 @@ +package server + +import ( + "github.com/tendermint/go-crypto/keys" + "github.com/tendermint/go-crypto/keys/words" + dbm "github.com/tendermint/tmlibs/db" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GenerateCoinKey returns the address of a public key, +// along with the secret phrase to recover the private key. +// You can give coins to this address and return the recovery +// phrase to the user to access them. +func GenerateCoinKey() (sdk.Address, string, error) { + // construct an in-memory key store + codec, err := words.LoadCodec("english") + if err != nil { + return nil, "", err + } + keybase := keys.New( + dbm.NewMemDB(), + codec, + ) + + // generate a private key, with recovery phrase + info, secret, err := keybase.Create("name", "pass", keys.AlgoEd25519) + if err != nil { + return nil, "", err + } + + addr := info.PubKey.Address() + return addr, secret, nil +} diff --git a/server/start.go b/server/start.go index 530a3a3e4b..c8d9fc4d1d 100644 --- a/server/start.go +++ b/server/start.go @@ -29,7 +29,7 @@ type appCreator func(string, log.Logger) (abci.Application, error) // stand-alone, or in-process with tendermint func StartCmd(app appCreator, logger log.Logger) *cobra.Command { start := startCmd{ - appCreator: appCreator, + appCreator: app, logger: logger, } cmd := &cobra.Command{ diff --git a/server/start_test.go b/server/start_test.go index de396c46ff..4934e84022 100644 --- a/server/start_test.go +++ b/server/start_test.go @@ -27,8 +27,7 @@ func TestStartStandAlone(t *testing.T) { startCmd.Flags().Set(flagAddress, FreeTCPAddr(t)) // set to a new free address timeout := time.Duration(3) * time.Second - ch := RunOrTimeout(startCmd, timeout, t) - close(ch) + RunOrTimeout(startCmd, timeout, t) } func TestStartWithTendermint(t *testing.T) { @@ -50,6 +49,5 @@ func TestStartWithTendermint(t *testing.T) { //a, _ := startCmd.Flags().GetString(flagAddress) //panic(a) - ch := RunOrTimeout(startCmd, timeout, t) - close(ch) + RunOrTimeout(startCmd, timeout, t) } diff --git a/types/tx_msg.go b/types/tx_msg.go index e4af1d8040..b81d2ddf98 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -2,7 +2,6 @@ package types import ( "encoding/json" - "fmt" ) // Transactions messages must fulfill the Msg @@ -125,7 +124,6 @@ type StdSignDoc struct { // StdSignBytes returns the bytes to sign for a transaction. // TODO: change the API to just take a chainID and StdTx ? func StdSignBytes(chainID string, sequences []int64, fee StdFee, msg Msg) []byte { - fmt.Println("FEE BYTES BABY", fee, string(fee.Bytes())) bz, err := json.Marshal(StdSignDoc{ ChainID: chainID, Sequences: sequences, diff --git a/x/auth/ante.go b/x/auth/ante.go index 08bb185778..c9509251dc 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -5,6 +5,7 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/spf13/viper" ) // NewAnteHandler returns an AnteHandler that checks @@ -46,6 +47,12 @@ func NewAnteHandler(accountMapper sdk.AccountMapper) sdk.AnteHandler { sequences[i] = sigs[i].Sequence } fee := stdTx.Fee + chainID := ctx.ChainID() + // XXX: major hack; need to get ChainID + // into the app right away (#565) + if chainID == "" { + chainID = viper.GetString("chain-id") + } signBytes := sdk.StdSignBytes(ctx.ChainID(), sequences, fee, msg) // Check sig and nonce and collect signer accounts. diff --git a/x/bank/rest/sendtx.go b/x/bank/rest/sendtx.go index 4b1c8f12a0..85b9dc4d50 100644 --- a/x/bank/rest/sendtx.go +++ b/x/bank/rest/sendtx.go @@ -7,10 +7,11 @@ import ( "net/http" "github.com/gorilla/mux" + "github.com/spf13/viper" "github.com/tendermint/go-crypto/keys" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/builder" - "github.com/cosmos/cosmos-sdk/client/keys" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/bank/commands" @@ -72,6 +73,8 @@ func SendRequestHandler(cdc *wire.Codec, kb keys.Keybase) func(http.ResponseWrit } // sign + // XXX: OMG + viper.Set(client.FlagSequence, m.Sequence) txBytes, err := builder.SignAndBuild(m.LocalAccountName, m.Password, msg, c.Cdc) if err != nil { w.WriteHeader(http.StatusUnauthorized)