Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

client/rest, modules/coin/rest: moved code around #197

Merged
merged 1 commit into from
Aug 3, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ merkleeyes.db
build
shunit2
docs/guide/*.sh

keys/
25 changes: 17 additions & 8 deletions client/commands/proofs/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package proofs

import (
"fmt"
"io"
"os"

"github.com/pkg/errors"
"github.com/spf13/viper"
Expand Down Expand Up @@ -111,15 +113,22 @@ type proof struct {
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)
// FoutputProof writes the output of wrapping height and info
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice!

I like the generalization here that lets it work for http.ResponseWriter as well.

// in the form {"data": <the_data>, "height": <the_height>}
// to the provider io.Writer
func FoutputProof(w io.Writer, v interface{}, height uint64) error {
wrap := &proof{height, v}
blob, err := data.ToJSON(wrap)
if err != nil {
return err
}
fmt.Println(string(res))
return nil
_, err = fmt.Fprintf(w, "%s\n", blob)
return err
}

// 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(data interface{}, height uint64) error {
return FoutputProof(os.Stdout, data, height)
}
47 changes: 47 additions & 0 deletions client/rest/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package rest

import (
"fmt"
"log"
"net/http"

"github.com/gorilla/mux"
"github.com/spf13/cobra"
"github.com/spf13/viper"

coinrest "github.com/tendermint/basecoin/modules/coin/rest"
)

var ServeCmd = &cobra.Command{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to move this command here.

Use: "serve",
Short: "Serve the light REST client for tendermint",
Long: "Access basecoin via REST",
RunE: serve,
}

const envPortFlag = "port"

func init() {
_ = ServeCmd.PersistentFlags().Int(envPortFlag, 8998, "the port to run the server on")
}

const defaultAlgo = "ed25519"

func serve(cmd *cobra.Command, args []string) error {
port := viper.GetInt(envPortFlag)
keysManager := DefaultKeysManager()
router := mux.NewRouter()
ctx := Context{
Keys: New(keysManager, defaultAlgo),
}
if err := ctx.RegisterHandlers(router); err != nil {
return err
}
if err := coinrest.RegisterHandlers(router); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than specifying the two packages where to register the handlers, please accept eg. a slice of RegisterHandler funcs, which can be set in main.go (at least indirectly). In a similar way how the cli allows one to register query functions.

https://github.com/tendermint/basecoin/blob/unstable/cmd/basecli/main.go#L43-L51

I think even ctx should be optional. A use case came up in the retreat where we want a baseserver that only allows queries. No need for key management or /tx or /send... That should be configurable without copying/forking large amounts of code.

return err
}

addr := fmt.Sprintf(":%d", port)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you used Int() above, you could use viper.GetInt(FlagPort) here.
This would allow us to set a BC_PORT variable (12-factor style) or add port = 12345 in the config file (typical unix style) to configure it and not just assume it comes from the command line.

Check out viper. It is pretty crazy. Maybe overkill but we decided on it for the cli here, so we can go all out.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, yeah I used viper.GetInt(flag) and it worked with cobra.PersistentFlags().Int(...), thanks for the advice!

log.Printf("Serving on %q", addr)
return http.ListenAndServe(addr, router)
}
170 changes: 29 additions & 141 deletions client/rest/handlers.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
package rest

import (
"fmt"
"net/http"
"strings"

"github.com/gorilla/mux"
"github.com/pkg/errors"

"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/client/commands"
"github.com/tendermint/basecoin/client/commands/proofs"
"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"
"github.com/tendermint/basecoin/stack"
keysutils "github.com/tendermint/go-crypto/cmd"
keys "github.com/tendermint/go-crypto/keys"
lightclient "github.com/tendermint/light-client"
"github.com/tendermint/tmlibs/common"
)

type Keys struct {
Expand All @@ -42,39 +32,39 @@ func (k *Keys) GenerateKey(w http.ResponseWriter, r *http.Request) {
ckReq := &CreateKeyRequest{
Algo: k.algo,
}
if err := parseRequestJSON(r, ckReq); err != nil {
writeError(w, err)
if err := common.ParseRequestAndValidateJSON(r, ckReq); err != nil {
common.WriteError(w, err)
return
}

key, seed, err := k.manager.Create(ckReq.Name, ckReq.Passphrase, ckReq.Algo)
if err != nil {
writeError(w, err)
common.WriteError(w, err)
return
}

res := &CreateKeyResponse{Key: key, Seed: seed}
writeSuccess(w, res)
common.WriteSuccess(w, res)
}

func (k *Keys) GetKey(w http.ResponseWriter, r *http.Request) {
query := mux.Vars(r)
name := query["name"]
key, err := k.manager.Get(name)
if err != nil {
writeError(w, err)
common.WriteError(w, err)
return
}
writeSuccess(w, &key)
common.WriteSuccess(w, &key)
}

func (k *Keys) ListKeys(w http.ResponseWriter, r *http.Request) {
keys, err := k.manager.List()
if err != nil {
writeError(w, err)
common.WriteError(w, err)
return
}
writeSuccess(w, keys)
common.WriteSuccess(w, keys)
}

var (
Expand All @@ -83,52 +73,52 @@ var (

func (k *Keys) UpdateKey(w http.ResponseWriter, r *http.Request) {
uReq := new(UpdateKeyRequest)
if err := parseRequestJSON(r, uReq); err != nil {
writeError(w, err)
if err := common.ParseRequestAndValidateJSON(r, uReq); err != nil {
common.WriteError(w, err)
return
}

query := mux.Vars(r)
name := query["name"]
if name != uReq.Name {
writeError(w, errNonMatchingPathAndJSONKeyNames)
common.WriteError(w, errNonMatchingPathAndJSONKeyNames)
return
}

if err := k.manager.Update(uReq.Name, uReq.OldPass, uReq.NewPass); err != nil {
writeError(w, err)
common.WriteError(w, err)
return
}

key, err := k.manager.Get(uReq.Name)
if err != nil {
writeError(w, err)
common.WriteError(w, err)
return
}
writeSuccess(w, &key)
common.WriteSuccess(w, &key)
}

func (k *Keys) DeleteKey(w http.ResponseWriter, r *http.Request) {
dReq := new(DeleteKeyRequest)
if err := parseRequestJSON(r, dReq); err != nil {
writeError(w, err)
if err := common.ParseRequestAndValidateJSON(r, dReq); err != nil {
common.WriteError(w, err)
return
}

query := mux.Vars(r)
name := query["name"]
if name != dReq.Name {
writeError(w, errNonMatchingPathAndJSONKeyNames)
common.WriteError(w, errNonMatchingPathAndJSONKeyNames)
return
}

if err := k.manager.Delete(dReq.Name, dReq.Passphrase); err != nil {
writeError(w, err)
common.WriteError(w, err)
return
}

resp := &ErrorResponse{Success: true}
writeSuccess(w, resp)
resp := &common.ErrorResponse{Success: true}
common.WriteSuccess(w, resp)
}

func (k *Keys) Register(r *mux.Router) {
Expand All @@ -145,140 +135,38 @@ type Context struct {

func (ctx *Context) RegisterHandlers(r *mux.Router) error {
ctx.Keys.Register(r)
r.HandleFunc("/build/send", doSend).Methods("POST")
r.HandleFunc("/sign", doSign).Methods("POST")
r.HandleFunc("/tx", doPostTx).Methods("POST")
r.HandleFunc("/query/account/{signature}", doAccountQuery).Methods("GET")

return nil
}

func extractAddress(signature string) (address string, err *ErrorResponse) {
// Expecting the signature of the form:
// sig:<ADDRESS>
splits := strings.Split(signature, ":")
if len(splits) < 2 {
return "", &ErrorResponse{
Error: `expecting the signature of the form "sig:<ADDRESS>"`,
Code: 406,
}
}
if splits[0] != "sigs" {
return "", &ErrorResponse{
Error: `expecting the signature of the form "sig:<ADDRESS>"`,
Code: 406,
}
}
return splits[1], nil
}

func doAccountQuery(w http.ResponseWriter, r *http.Request) {
query := mux.Vars(r)
signature := query["signature"]
address, errResp := extractAddress(signature)
if errResp != nil {
writeCode(w, errResp, errResp.Code)
return
}
actor, err := commands.ParseActor(address)
if err != nil {
writeError(w, err)
return
}
actor = coin.ChainAddr(actor)
key := stack.PrefixedKey(coin.NameCoin, actor.Bytes())
account := new(coin.Account)
proof, err := proofs.GetAndParseAppProof(key, account)
if lightclient.IsNoDataErr(err) {
err := fmt.Errorf("account bytes are empty for address: %q", address)
writeError(w, err)
return
} else if err != nil {
writeError(w, err)
return
}

if err := proofs.OutputProof(account, proof.BlockHeight()); err != nil {
writeError(w, err)
return
}
writeSuccess(w, account)
}

func doPostTx(w http.ResponseWriter, r *http.Request) {
tx := new(basecoin.Tx)
if err := parseRequestJSON(r, tx); err != nil {
writeError(w, err)
if err := common.ParseRequestAndValidateJSON(r, tx); err != nil {
common.WriteError(w, err)
return
}
commit, err := PostTx(*tx)
if err != nil {
writeError(w, err)
common.WriteError(w, err)
return
}

writeSuccess(w, commit)
common.WriteSuccess(w, commit)
}

func doSign(w http.ResponseWriter, r *http.Request) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is dead code now, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, it is used in "/sign"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I think I tagged the wrong function... scroll down to doSend. There are no references to it in the client/rest package.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, thanks. Yeah, that was moved to modules/coin/rest/handlers.go.

sr := new(SignRequest)
if err := parseRequestJSON(r, sr); err != nil {
writeError(w, err)
if err := common.ParseRequestAndValidateJSON(r, sr); err != nil {
common.WriteError(w, err)
return
}

tx := sr.Tx
if err := SignTx(sr.Name, sr.Password, tx); err != nil {
writeError(w, err)
return
}
writeSuccess(w, tx)
}

func doSend(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
si := new(SendInput)
if err := parseRequestJSON(r, si); err != nil {
writeError(w, err)
return
}

var errsList []string
if si.From == nil {
errsList = append(errsList, `"from" cannot be nil`)
}
if si.Sequence <= 0 {
errsList = append(errsList, `"sequence" must be > 0`)
}
if si.To == nil {
errsList = append(errsList, `"to" cannot be nil`)
}
if len(si.Amount) == 0 {
errsList = append(errsList, `"amount" cannot be empty`)
}
if len(errsList) > 0 {
err := &ErrorResponse{
Error: strings.Join(errsList, ", "),
Code: 406,
}
writeCode(w, err, 406)
common.WriteError(w, err)
return
}

tx := coin.NewSendOneTx(*si.From, *si.To, si.Amount)
// fees are optional
if si.Fees != nil && !si.Fees.IsZero() {
tx = fee.NewFee(tx, *si.Fees, *si.From)
}
// only add the actual signer to the nonce
signers := []basecoin.Actor{*si.From}
tx = nonce.NewTx(si.Sequence, signers, tx)
tx = base.NewChainTx(commands.GetChainID(), 0, tx)

if si.Multi {
tx = auth.NewMulti(tx).Wrap()
} else {
tx = auth.NewSig(tx).Wrap()
}
writeSuccess(w, tx)
common.WriteSuccess(w, tx)
}
Loading