diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 024e2bbd9..e2428c273 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -127,8 +127,8 @@ func TestVersion(t *testing.T) { } func TestNodeStatus(t *testing.T) { - ch := server.StartServer(t) - defer close(ch) + _, _ = startServer(t) + // TODO need to kill server after prepareClient(t) cdc := app.MakeCodec() @@ -153,8 +153,8 @@ func TestNodeStatus(t *testing.T) { } func TestBlock(t *testing.T) { - ch := server.StartServer(t) - defer close(ch) + _, _ = startServer(t) + // TODO need to kill server after prepareClient(t) cdc := app.MakeCodec() @@ -184,9 +184,8 @@ func TestBlock(t *testing.T) { } func TestValidators(t *testing.T) { - ch := server.StartServer(t) - defer close(ch) - + _, _ = startServer(t) + // TODO need to kill server after prepareClient(t) cdc := app.MakeCodec() r := initRouter(cdc) @@ -214,6 +213,67 @@ func TestValidators(t *testing.T) { require.Equal(t, http.StatusNotFound, res.Code) } +func TestCoinSend(t *testing.T) { + addr, seed := startServer(t) + // TODO need to kill server after + prepareClient(t) + cdc := app.MakeCodec() + r := initRouter(cdc) + + // query empty + res := request(t, r, "GET", "/accounts/1234567890123456789012345678901234567890", nil) + require.Equal(t, http.StatusNoContent, res.Code, res.Body.String()) + + // query + res = request(t, r, "GET", "/accounts/"+addr.String(), nil) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + + assert.Equal(t, `{ + "coins": [ + { + "denom": "mycoin", + "amount": 9007199254740992 + } + ] + }`, res.Body.String()) + + // create account for default coins + var jsonStr = []byte(fmt.Sprintf(`{"name":"test", "password":"1234567890", "seed": "%s"}`, seed)) + res = request(t, r, "POST", "/keys", jsonStr) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + + // create random account + res = request(t, r, "GET", "/keys/seed", nil) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + receiveSeed := res.Body.String() + + jsonStr = []byte(fmt.Sprintf(`{"name":"receive", "password":"1234567890", "seed": "%s"}`, receiveSeed)) + res = request(t, r, "POST", "/keys", jsonStr) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + receiveAddr := res.Body.String() + + // send + jsonStr = []byte(`{"name":"test", "password":"1234567890", "amount":[{ + "denom": "mycoin", + "amount": 1 + }]}`) + res = request(t, r, "POST", "/accounts/"+receiveAddr+"/send", jsonStr) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + + // check if received + res = request(t, r, "GET", "/accounts/"+receiveAddr, nil) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + + assert.Equal(t, `{ + "coins": [ + { + "denom": "mycoin", + "amount": 1 + } + ] + }`, res.Body.String()) +} + //__________________________________________________________ // helpers @@ -226,6 +286,8 @@ func prepareClient(t *testing.T) { app := baseapp.NewBaseApp(t.Name(), defaultLogger(), db) viper.Set(client.FlagNode, "localhost:46657") + _ = client.GetKeyBase(db) + header := abci.Header{Height: 1} app.BeginBlock(abci.RequestBeginBlock{Header: header}) app.Commit() @@ -244,11 +306,31 @@ func setupViper() func() { } } -func startServer(t *testing.T) { +// from baseoind.main +func defaultOptions(addr string) func(args []string) (json.RawMessage, error) { + return func(args []string) (json.RawMessage, error) { + opts := fmt.Sprintf(`{ + "accounts": [{ + "address": "%s", + "coins": [ + { + "denom": "mycoin", + "amount": 9007199254740992 + } + ] + }] + }`, addr) + return json.RawMessage(opts), nil + } +} + +func startServer(t *testing.T) (types.Address, string) { defer setupViper()() // init server - initCmd := server.InitCmd(mock.GenInitOptions, log.NewNopLogger()) - err := initCmd.RunE(nil, nil) + addr, secret, err := server.GenerateCoinKey() + require.NoError(t, err) + initCmd := server.InitCmd(defaultOptions(addr.String()), log.NewNopLogger()) + err = initCmd.RunE(nil, nil) require.NoError(t, err) // start server @@ -258,6 +340,8 @@ func startServer(t *testing.T) { err = runOrTimeout(startCmd, timeout) require.NoError(t, err) + + return addr, secret } // copied from server/start_test.go @@ -281,16 +365,6 @@ func runOrTimeout(cmd *cobra.Command, timeout time.Duration) error { } } -func createKey(t *testing.T, r http.Handler) string { - var jsonStr = []byte(`{"name":"test", "password":"1234567890"}`) - res := request(t, r, "POST", "/keys", jsonStr) - - assert.Equal(t, http.StatusOK, res.Code, res.Body.String()) - - addr := res.Body.String() - return addr -} - func request(t *testing.T, r http.Handler, method string, path string, payload []byte) *httptest.ResponseRecorder { req, err := http.NewRequest(method, path, bytes.NewBuffer(payload)) require.Nil(t, err) diff --git a/client/lcd/root.go b/client/lcd/root.go index a692ebd59..7be79ced0 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -13,6 +13,8 @@ import ( tx "github.com/cosmos/cosmos-sdk/client/tx" version "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/wire" + auth "github.com/cosmos/cosmos-sdk/x/auth/rest" + bank "github.com/cosmos/cosmos-sdk/x/bank/rest" ) const ( @@ -52,5 +54,7 @@ func initRouter(cdc *wire.Codec) http.Handler { keys.RegisterRoutes(r) rpc.RegisterRoutes(r) tx.RegisterRoutes(r, cdc) + auth.RegisterRoutes(r, cdc, "main") + bank.RegisterRoutes(r, cdc) return r } diff --git a/x/auth/commands/account.go b/x/auth/commands/account.go index e9e1a5438..b6eb51896 100644 --- a/x/auth/commands/account.go +++ b/x/auth/commands/account.go @@ -16,10 +16,10 @@ import ( // GetAccountCmd for the auth.BaseAccount type func GetAccountCmdDefault(storeName string, cdc *wire.Codec) *cobra.Command { - return GetAccountCmd(storeName, cdc, getParseAccount(cdc)) + return GetAccountCmd(storeName, cdc, GetParseAccount(cdc)) } -func getParseAccount(cdc *wire.Codec) sdk.ParseAccount { +func GetParseAccount(cdc *wire.Codec) sdk.ParseAccount { return func(accBytes []byte) (sdk.Account, error) { acct := new(auth.BaseAccount) err := cdc.UnmarshalBinary(accBytes, &acct) diff --git a/x/auth/rest/query.go b/x/auth/rest/query.go new file mode 100644 index 000000000..1536e692a --- /dev/null +++ b/x/auth/rest/query.go @@ -0,0 +1,66 @@ +package rest + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "net/http" + + "github.com/gorilla/mux" + + "github.com/cosmos/cosmos-sdk/client" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" +) + +type commander struct { + storeName string + cdc *wire.Codec + parser sdk.ParseAccount +} + +func QueryAccountRequestHandler(storeName string, cdc *wire.Codec, parser sdk.ParseAccount) func(http.ResponseWriter, *http.Request) { + c := commander{storeName, cdc, parser} + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + addr := vars["address"] + bz, err := hex.DecodeString(addr) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + key := sdk.Address(bz) + + res, err := client.Query(key, c.storeName) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Could't query account. Error: %s", err.Error()))) + return + } + + // the query will return empty if there is no data for this account + if len(res) == 0 { + w.WriteHeader(http.StatusNoContent) + return + } + + // parse out the value + account, err := c.parser(res) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Could't parse query result. Result: %s. Error: %s", res, err.Error()))) + return + } + + // print out whole account + output, err := json.MarshalIndent(account, "", " ") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Could't marshall query result. Error: %s", err.Error()))) + return + } + + w.Write(output) + } +} diff --git a/x/auth/rest/root.go b/x/auth/rest/root.go new file mode 100644 index 000000000..c6d6fd9ed --- /dev/null +++ b/x/auth/rest/root.go @@ -0,0 +1,11 @@ +package rest + +import ( + "github.com/cosmos/cosmos-sdk/wire" + auth "github.com/cosmos/cosmos-sdk/x/auth/commands" + "github.com/gorilla/mux" +) + +func RegisterRoutes(r *mux.Router, cdc *wire.Codec, storeName string) { + r.HandleFunc("/accounts/{address}", QueryAccountRequestHandler(storeName, cdc, auth.GetParseAccount(cdc))).Methods("GET") +} diff --git a/x/bank/commands/sendtx.go b/x/bank/commands/sendtx.go index 7e6dd463d..5b692acec 100644 --- a/x/bank/commands/sendtx.go +++ b/x/bank/commands/sendtx.go @@ -10,8 +10,8 @@ import ( "github.com/cosmos/cosmos-sdk/client/builder" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" - "github.com/cosmos/cosmos-sdk/x/bank" + cryptokeys "github.com/tendermint/go-crypto/keys" ) const ( @@ -20,8 +20,8 @@ const ( ) // SendTxCommand will create a send tx and sign it with the given key -func SendTxCmd(cdc *wire.Codec) *cobra.Command { - cmdr := commander{cdc} +func SendTxCmd(Cdc *wire.Codec) *cobra.Command { + cmdr := Commander{Cdc} cmd := &cobra.Command{ Use: "send", Short: "Create and sign a send tx", @@ -32,18 +32,32 @@ func SendTxCmd(cdc *wire.Codec) *cobra.Command { return cmd } -type commander struct { - cdc *wire.Codec +type Commander struct { + Cdc *wire.Codec } -func (c commander) sendTxCmd(cmd *cobra.Command, args []string) error { - +func (c Commander) sendTxCmd(cmd *cobra.Command, args []string) error { // get the from address from, err := builder.GetFromAddress() if err != nil { return err } + // parse coins + amount := viper.GetString(flagAmount) + coins, err := sdk.ParseCoins(amount) + if err != nil { + return err + } + + // parse destination address + dest := viper.GetString(flagTo) + bz, err := hex.DecodeString(dest) + if err != nil { + return err + } + to := sdk.Address(bz) + // build send msg msg, err := buildMsg(from) if err != nil { @@ -60,25 +74,32 @@ func (c commander) sendTxCmd(cmd *cobra.Command, args []string) error { return nil } -func buildMsg(from sdk.Address) (sdk.Msg, error) { - - // parse coins - amount := viper.GetString(flagAmount) - coins, err := sdk.ParseCoins(amount) - if err != nil { - return nil, err - } - - // parse destination address - dest := viper.GetString(flagTo) - bz, err := hex.DecodeString(dest) - if err != nil { - return nil, err - } - to := sdk.Address(bz) - +func BuildMsg(from sdk.Address, to sdk.Address, coins sdk.Coins) sdk.Msg { input := bank.NewInput(from, coins) output := bank.NewOutput(to, coins) msg := bank.NewSendMsg([]bank.Input{input}, []bank.Output{output}) - return msg, nil + return msg +} + +func (c Commander) SignMessage(msg sdk.Msg, kb cryptokeys.Keybase, accountName string, password string) ([]byte, error) { + // sign and build + bz := msg.GetSignBytes() + sig, pubkey, err := kb.Sign(accountName, password, bz) + if err != nil { + return nil, err + } + sigs := []sdk.StdSignature{{ + PubKey: pubkey, + Signature: sig, + Sequence: viper.GetInt64(flagSequence), + }} + + // marshal bytes + tx := sdk.NewStdTx(msg, sigs) + + txBytes, err := c.Cdc.MarshalBinary(tx) + if err != nil { + return nil, err + } + return txBytes, nil } diff --git a/x/bank/rest/root.go b/x/bank/rest/root.go new file mode 100644 index 000000000..67c05ab11 --- /dev/null +++ b/x/bank/rest/root.go @@ -0,0 +1,10 @@ +package rest + +import ( + "github.com/cosmos/cosmos-sdk/wire" + "github.com/gorilla/mux" +) + +func RegisterRoutes(r *mux.Router, cdc *wire.Codec) { + r.HandleFunc("/accounts/{address}/send", SendRequestHandler(cdc)).Methods("POST") +} diff --git a/x/bank/rest/sendtx.go b/x/bank/rest/sendtx.go new file mode 100644 index 000000000..7ce99dadd --- /dev/null +++ b/x/bank/rest/sendtx.go @@ -0,0 +1,96 @@ +package rest + +import ( + "encoding/hex" + "encoding/json" + "net/http" + + "github.com/gorilla/mux" + + "github.com/cosmos/cosmos-sdk/client" + "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" +) + +type SendBody struct { + // fees is not used currently + // Fees sdk.Coin `json="fees"` + Amount sdk.Coins `json="amount"` + LocalAccountName string `json="account"` + Password string `json="password"` +} + +func SendRequestHandler(cdc *wire.Codec) func(http.ResponseWriter, *http.Request) { + c := commands.Commander{cdc} + return func(w http.ResponseWriter, r *http.Request) { + // collect data + vars := mux.Vars(r) + address := vars["address"] + + var m SendBody + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&m) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + 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) + w.Write([]byte(err.Error())) + return + } + + bz, err := hex.DecodeString(address) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + to := sdk.Address(bz) + + // build + msg := commands.BuildMsg(info.Address(), to, m.Amount) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + // sign + txBytes, err := c.SignMessage(msg, kb, m.LocalAccountName, m.Password) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(err.Error())) + return + } + + // send + res, err := client.BroadcastTx(txBytes) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + output, err := json.MarshalIndent(res, "", " ") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Write(output) + } +}