client/rest, modules/coin/rest: moved code around
After offline emails and a video call with @ethanfrey, a goal was decided to move things around i.e: - [X] Move /build/send and /query/account to modules/coin/rest Due to that move, there is a lot of overlap between needed code and utils so extracted common code to make https://github.com/tendermint/tmlibs/pull/33 so make sure to pull in that commit into your tmlibs tree. After code review feedback: client/rest, modules/coin/rest: FoutputProof, PrepareSendTx helper * Extract OutputProof to FoutputProof helper that can be used in modules/coin/rest/handlers.go as proofs.FoutputProof * Revert r.HandleFunc("/tx", doPostTx).Methods("POST") which was erraneously deleted * Use function signatures from "tendermint/tmblibs/common"
This commit is contained in:
parent
67f25f54ed
commit
1a45755027
|
@ -5,4 +5,4 @@ merkleeyes.db
|
||||||
build
|
build
|
||||||
shunit2
|
shunit2
|
||||||
docs/guide/*.sh
|
docs/guide/*.sh
|
||||||
|
keys/
|
||||||
|
|
|
@ -2,6 +2,8 @@ package proofs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
@ -111,15 +113,22 @@ type proof struct {
|
||||||
Data interface{} `json:"data"`
|
Data interface{} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OutputProof prints the proof to stdout
|
// FoutputProof writes the output of wrapping height and info
|
||||||
// reuse this for printing proofs and we should enhance this for text/json,
|
// in the form {"data": <the_data>, "height": <the_height>}
|
||||||
// better presentation of height
|
// to the provider io.Writer
|
||||||
func OutputProof(info interface{}, height uint64) error {
|
func FoutputProof(w io.Writer, v interface{}, height uint64) error {
|
||||||
wrap := proof{height, info}
|
wrap := &proof{height, v}
|
||||||
res, err := data.ToJSON(wrap)
|
blob, err := data.ToJSON(wrap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println(string(res))
|
_, err = fmt.Fprintf(w, "%s\n", blob)
|
||||||
return nil
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -1,25 +1,15 @@
|
||||||
package rest
|
package rest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/tendermint/basecoin"
|
"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"
|
keysutils "github.com/tendermint/go-crypto/cmd"
|
||||||
keys "github.com/tendermint/go-crypto/keys"
|
keys "github.com/tendermint/go-crypto/keys"
|
||||||
lightclient "github.com/tendermint/light-client"
|
"github.com/tendermint/tmlibs/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Keys struct {
|
type Keys struct {
|
||||||
|
@ -42,19 +32,19 @@ func (k *Keys) GenerateKey(w http.ResponseWriter, r *http.Request) {
|
||||||
ckReq := &CreateKeyRequest{
|
ckReq := &CreateKeyRequest{
|
||||||
Algo: k.algo,
|
Algo: k.algo,
|
||||||
}
|
}
|
||||||
if err := parseRequestJSON(r, ckReq); err != nil {
|
if err := common.ParseRequestAndValidateJSON(r, ckReq); err != nil {
|
||||||
writeError(w, err)
|
common.WriteError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
key, seed, err := k.manager.Create(ckReq.Name, ckReq.Passphrase, ckReq.Algo)
|
key, seed, err := k.manager.Create(ckReq.Name, ckReq.Passphrase, ckReq.Algo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(w, err)
|
common.WriteError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
res := &CreateKeyResponse{Key: key, Seed: seed}
|
res := &CreateKeyResponse{Key: key, Seed: seed}
|
||||||
writeSuccess(w, res)
|
common.WriteSuccess(w, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *Keys) GetKey(w http.ResponseWriter, r *http.Request) {
|
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"]
|
name := query["name"]
|
||||||
key, err := k.manager.Get(name)
|
key, err := k.manager.Get(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(w, err)
|
common.WriteError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writeSuccess(w, &key)
|
common.WriteSuccess(w, &key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *Keys) ListKeys(w http.ResponseWriter, r *http.Request) {
|
func (k *Keys) ListKeys(w http.ResponseWriter, r *http.Request) {
|
||||||
keys, err := k.manager.List()
|
keys, err := k.manager.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(w, err)
|
common.WriteError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writeSuccess(w, keys)
|
common.WriteSuccess(w, keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -83,52 +73,52 @@ var (
|
||||||
|
|
||||||
func (k *Keys) UpdateKey(w http.ResponseWriter, r *http.Request) {
|
func (k *Keys) UpdateKey(w http.ResponseWriter, r *http.Request) {
|
||||||
uReq := new(UpdateKeyRequest)
|
uReq := new(UpdateKeyRequest)
|
||||||
if err := parseRequestJSON(r, uReq); err != nil {
|
if err := common.ParseRequestAndValidateJSON(r, uReq); err != nil {
|
||||||
writeError(w, err)
|
common.WriteError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
query := mux.Vars(r)
|
query := mux.Vars(r)
|
||||||
name := query["name"]
|
name := query["name"]
|
||||||
if name != uReq.Name {
|
if name != uReq.Name {
|
||||||
writeError(w, errNonMatchingPathAndJSONKeyNames)
|
common.WriteError(w, errNonMatchingPathAndJSONKeyNames)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := k.manager.Update(uReq.Name, uReq.OldPass, uReq.NewPass); err != nil {
|
if err := k.manager.Update(uReq.Name, uReq.OldPass, uReq.NewPass); err != nil {
|
||||||
writeError(w, err)
|
common.WriteError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err := k.manager.Get(uReq.Name)
|
key, err := k.manager.Get(uReq.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(w, err)
|
common.WriteError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writeSuccess(w, &key)
|
common.WriteSuccess(w, &key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *Keys) DeleteKey(w http.ResponseWriter, r *http.Request) {
|
func (k *Keys) DeleteKey(w http.ResponseWriter, r *http.Request) {
|
||||||
dReq := new(DeleteKeyRequest)
|
dReq := new(DeleteKeyRequest)
|
||||||
if err := parseRequestJSON(r, dReq); err != nil {
|
if err := common.ParseRequestAndValidateJSON(r, dReq); err != nil {
|
||||||
writeError(w, err)
|
common.WriteError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
query := mux.Vars(r)
|
query := mux.Vars(r)
|
||||||
name := query["name"]
|
name := query["name"]
|
||||||
if name != dReq.Name {
|
if name != dReq.Name {
|
||||||
writeError(w, errNonMatchingPathAndJSONKeyNames)
|
common.WriteError(w, errNonMatchingPathAndJSONKeyNames)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := k.manager.Delete(dReq.Name, dReq.Passphrase); err != nil {
|
if err := k.manager.Delete(dReq.Name, dReq.Passphrase); err != nil {
|
||||||
writeError(w, err)
|
common.WriteError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := &ErrorResponse{Success: true}
|
resp := &common.ErrorResponse{Success: true}
|
||||||
writeSuccess(w, resp)
|
common.WriteSuccess(w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *Keys) Register(r *mux.Router) {
|
func (k *Keys) Register(r *mux.Router) {
|
||||||
|
@ -145,140 +135,38 @@ type Context struct {
|
||||||
|
|
||||||
func (ctx *Context) RegisterHandlers(r *mux.Router) error {
|
func (ctx *Context) RegisterHandlers(r *mux.Router) error {
|
||||||
ctx.Keys.Register(r)
|
ctx.Keys.Register(r)
|
||||||
r.HandleFunc("/build/send", doSend).Methods("POST")
|
|
||||||
r.HandleFunc("/sign", doSign).Methods("POST")
|
r.HandleFunc("/sign", doSign).Methods("POST")
|
||||||
r.HandleFunc("/tx", doPostTx).Methods("POST")
|
r.HandleFunc("/tx", doPostTx).Methods("POST")
|
||||||
r.HandleFunc("/query/account/{signature}", doAccountQuery).Methods("GET")
|
|
||||||
|
|
||||||
return nil
|
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) {
|
func doPostTx(w http.ResponseWriter, r *http.Request) {
|
||||||
tx := new(basecoin.Tx)
|
tx := new(basecoin.Tx)
|
||||||
if err := parseRequestJSON(r, tx); err != nil {
|
if err := common.ParseRequestAndValidateJSON(r, tx); err != nil {
|
||||||
writeError(w, err)
|
common.WriteError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
commit, err := PostTx(*tx)
|
commit, err := PostTx(*tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(w, err)
|
common.WriteError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writeSuccess(w, commit)
|
common.WriteSuccess(w, commit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func doSign(w http.ResponseWriter, r *http.Request) {
|
func doSign(w http.ResponseWriter, r *http.Request) {
|
||||||
sr := new(SignRequest)
|
sr := new(SignRequest)
|
||||||
if err := parseRequestJSON(r, sr); err != nil {
|
if err := common.ParseRequestAndValidateJSON(r, sr); err != nil {
|
||||||
writeError(w, err)
|
common.WriteError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tx := sr.Tx
|
tx := sr.Tx
|
||||||
if err := SignTx(sr.Name, sr.Password, tx); err != nil {
|
if err := SignTx(sr.Name, sr.Password, tx); err != nil {
|
||||||
writeError(w, err)
|
common.WriteError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writeSuccess(w, tx)
|
common.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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,9 @@
|
||||||
package rest
|
package rest
|
||||||
|
|
||||||
import (
|
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"
|
||||||
"github.com/tendermint/basecoin/modules/coin"
|
"github.com/tendermint/basecoin/modules/coin"
|
||||||
"github.com/tendermint/go-crypto/keys"
|
"github.com/tendermint/go-crypto/keys"
|
||||||
data "github.com/tendermint/go-wire/data"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CreateKeyRequest struct {
|
type CreateKeyRequest struct {
|
||||||
|
@ -40,16 +32,6 @@ type SignRequest struct {
|
||||||
Tx basecoin.Tx `json:"tx" validate:"required"`
|
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 {
|
type CreateKeyResponse struct {
|
||||||
Key keys.Info `json:"key,omitempty"`
|
Key keys.Info `json:"key,omitempty"`
|
||||||
Seed string `json:"seed_phrase,omitempty"`
|
Seed string `json:"seed_phrase,omitempty"`
|
||||||
|
@ -68,48 +50,3 @@ type SendInput struct {
|
||||||
From *basecoin.Actor `json:"from"`
|
From *basecoin.Actor `json:"from"`
|
||||||
Amount coin.Coins `json:"amount"`
|
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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/tendermint/basecoin/client/commands"
|
"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.`,
|
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() {
|
func main() {
|
||||||
commands.AddBasicFlags(srvCli)
|
commands.AddBasicFlags(srvCli)
|
||||||
|
|
||||||
flagset := serveCmd.Flags()
|
|
||||||
flagset.IntVar(&port, "port", 8998, "the port to run the server on")
|
|
||||||
|
|
||||||
srvCli.AddCommand(
|
srvCli.AddCommand(
|
||||||
commands.InitCmd,
|
commands.InitCmd,
|
||||||
serveCmd,
|
rest.ServeCmd,
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: Decide whether to use $HOME/.basecli for compatibility
|
// TODO: Decide whether to use $HOME/.basecli for compatibility
|
||||||
|
@ -47,20 +32,3 @@ func main() {
|
||||||
log.Fatal(err)
|
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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -173,7 +173,7 @@ imports:
|
||||||
- types
|
- types
|
||||||
- version
|
- version
|
||||||
- name: github.com/tendermint/tmlibs
|
- name: github.com/tendermint/tmlibs
|
||||||
version: 2f6f3e6aa70bb19b70a6e73210273fa127041070
|
version: 75372988e737a9f672c0e7f6308042620bd3e151
|
||||||
subpackages:
|
subpackages:
|
||||||
- autofile
|
- autofile
|
||||||
- cli
|
- cli
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
Loading…
Reference in New Issue