Merge pull request #197 from orijtech/baseserver-repackaging

client/rest, modules/coin/rest: moved code around
This commit is contained in:
Ethan Frey 2017-08-03 14:05:23 +02:00 committed by GitHub
commit cdaee322bc
8 changed files with 219 additions and 247 deletions

2
.gitignore vendored
View File

@ -5,4 +5,4 @@ merkleeyes.db
build
shunit2
docs/guide/*.sh
keys/

View File

@ -2,6 +2,8 @@ package proofs
import (
"fmt"
"io"
"os"
"github.com/pkg/errors"
"github.com/spf13/viper"
@ -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
// 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
client/rest/cmd.go Normal file
View File

@ -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{
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 {
return err
}
addr := fmt.Sprintf(":%d", port)
log.Printf("Serving on %q", addr)
return http.ListenAndServe(addr, router)
}

View File

@ -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 {
@ -42,19 +32,19 @@ 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) {
@ -62,19 +52,19 @@ func (k *Keys) GetKey(w http.ResponseWriter, r *http.Request) {
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 (
@ -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) {
@ -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) {
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)
common.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)
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)
}

View File

@ -1,17 +1,9 @@
package rest
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/pkg/errors"
"gopkg.in/go-playground/validator.v9"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/modules/coin"
"github.com/tendermint/go-crypto/keys"
data "github.com/tendermint/go-wire/data"
)
type CreateKeyRequest struct {
@ -40,16 +32,6 @@ type SignRequest struct {
Tx basecoin.Tx `json:"tx" validate:"required"`
}
type ErrorResponse struct {
Success bool `json:"success,omitempty"`
// Error is the error message if Success is false
Error string `json:"error,omitempty"`
// Code is set if Success is false
Code int `json:"code,omitempty"`
}
type CreateKeyResponse struct {
Key keys.Info `json:"key,omitempty"`
Seed string `json:"seed_phrase,omitempty"`
@ -68,48 +50,3 @@ type SendInput struct {
From *basecoin.Actor `json:"from"`
Amount coin.Coins `json:"amount"`
}
// Validators
var theValidator = validator.New()
func validate(req interface{}) error {
return errors.Wrap(theValidator.Struct(req), "Validate")
}
// Helpers
func parseRequestJSON(r *http.Request, save interface{}) error {
defer r.Body.Close()
slurp, err := ioutil.ReadAll(r.Body)
if err != nil {
return errors.Wrap(err, "Read Request")
}
if err := json.Unmarshal(slurp, save); err != nil {
return errors.Wrap(err, "Parse")
}
return validate(save)
}
func writeSuccess(w http.ResponseWriter, data interface{}) {
writeCode(w, data, 200)
}
func writeCode(w http.ResponseWriter, out interface{}, code int) {
blob, err := data.ToJSON(out)
if err != nil {
writeError(w, err)
} else {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
w.Write(blob)
}
}
func writeError(w http.ResponseWriter, err error) {
resp := &ErrorResponse{
Code: 406,
Error: err.Error(),
}
writeCode(w, resp, 406)
}

View File

@ -1,12 +1,9 @@
package main
import (
"fmt"
"log"
"net/http"
"os"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
"github.com/tendermint/basecoin/client/commands"
@ -20,24 +17,12 @@ var srvCli = &cobra.Command{
Long: `Baseserver presents a nice (not raw hex) interface to the basecoin blockchain structure.`,
}
var serveCmd = &cobra.Command{
Use: "serve",
Short: "Serve the light REST client for tendermint",
Long: "Access basecoin via REST",
RunE: serve,
}
var port int
func main() {
commands.AddBasicFlags(srvCli)
flagset := serveCmd.Flags()
flagset.IntVar(&port, "port", 8998, "the port to run the server on")
srvCli.AddCommand(
commands.InitCmd,
serveCmd,
rest.ServeCmd,
)
// TODO: Decide whether to use $HOME/.basecli for compatibility
@ -47,20 +32,3 @@ func main() {
log.Fatal(err)
}
}
const defaultAlgo = "ed25519"
func serve(cmd *cobra.Command, args []string) error {
keysManager := rest.DefaultKeysManager()
router := mux.NewRouter()
ctx := rest.Context{
Keys: rest.New(keysManager, defaultAlgo),
}
if err := ctx.RegisterHandlers(router); err != nil {
return err
}
addr := fmt.Sprintf(":%d", port)
log.Printf("Serving on %q", addr)
return http.ListenAndServe(addr, router)
}

2
glide.lock generated
View File

@ -173,7 +173,7 @@ imports:
- types
- version
- name: github.com/tendermint/tmlibs
version: 2f6f3e6aa70bb19b70a6e73210273fa127041070
version: 75372988e737a9f672c0e7f6308042620bd3e151
subpackages:
- autofile
- cli

View File

@ -0,0 +1,123 @@
package rest
import (
"fmt"
"net/http"
"strings"
"github.com/gorilla/mux"
"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"
lightclient "github.com/tendermint/light-client"
"github.com/tendermint/tmlibs/common"
)
// SendInput is the request to send an amount from one actor to another.
// Note: Not using the `validator:""` tags here because SendInput has
// many fields so it would be nice to figure out all the invalid
// inputs and report them back to the caller, in one shot.
type SendInput struct {
Fees *coin.Coin `json:"fees"`
Multi bool `json:"multi,omitempty"`
Sequence uint32 `json:"sequence"`
To *basecoin.Actor `json:"to"`
From *basecoin.Actor `json:"from"`
Amount coin.Coins `json:"amount"`
}
func RegisterHandlers(r *mux.Router) error {
r.HandleFunc("/build/send", doSend).Methods("POST")
r.HandleFunc("/query/account/{signature}", doQueryAccount).Methods("GET")
return nil
}
// doQueryAccount is the HTTP handlerfunc to query an account
// It expects a query string with
func doQueryAccount(w http.ResponseWriter, r *http.Request) {
query := mux.Vars(r)
signature := query["signature"]
actor, err := commands.ParseActor(signature)
if err != nil {
common.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", signature)
common.WriteError(w, err)
return
} else if err != nil {
common.WriteError(w, err)
return
}
if err := proofs.FoutputProof(w, account, proof.BlockHeight()); err != nil {
common.WriteError(w, err)
}
}
func PrepareSendTx(si *SendInput) basecoin.Tx {
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()
}
return tx
}
func doSend(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
si := new(SendInput)
if err := common.ParseRequestAndValidateJSON(r, si); err != nil {
common.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 {
code := http.StatusBadRequest
err := &common.ErrorResponse{
Err: strings.Join(errsList, ", "),
Code: code,
}
common.WriteCode(w, err, code)
return
}
tx := PrepareSendTx(si)
common.WriteSuccess(w, tx)
}