From 3f7ac60ec00bcf54e33285ba5143c331fd65df7f Mon Sep 17 00:00:00 2001 From: Luca Moser <moser.luca@gmail.com> Date: Wed, 15 Jan 2020 10:56:35 +0100 Subject: [PATCH] Adds a client lib as a wrapper for the HTTP API (#118) * :art: moves all webapi into one * :art: adds public key log * :art: changes txRequest to getTrytes * :art: rename packages * :pencil: adds comments * :art: changes status to error * :recycle: removes duration from API - getTrytes converts trits to trytes - set default spammer TPS to 1 Fix: Allow starting a node with gossip disabled (#97) * fix: remove selection flag and use gossip plugin * Upgrade hive.go feat: improve logging feat: improve analysis status chore: remove unused packages (#99) Fix: Use docker specific config (#100) * Use docker specific config * Format JSON :heavy_minus_sign: removes status :art: adds omitempty :lipstick: updates style import :sparkles: adds getNeighbors API :sparkles: adds getTransaction :heavy_minus_sign: removes addEndpoint * ports glumb plugin from Hornet to GoShimmer * :construction: WIP * implements HTTP API wrapper client lib * removes empty lines * clean line in autopeering * normalize client lib imports Co-authored-by: Angelo Capossele <angelocapossele@gmail.com> --- client/lib.go | 229 ++++++++++++++++++++++ main.go | 1 + plugins/graph/README.md | 9 + plugins/webapi/broadcastData/plugin.go | 12 +- plugins/webapi/findTransactions/plugin.go | 10 +- plugins/webapi/getNeighbors/plugin.go | 22 +-- plugins/webapi/getTransactions/plugin.go | 23 +-- plugins/webapi/getTrytes/plugin.go | 10 +- plugins/webapi/gtta/plugin.go | 4 +- plugins/webapi/spammer/plugin.go | 10 +- 10 files changed, 286 insertions(+), 44 deletions(-) create mode 100644 client/lib.go create mode 100644 plugins/graph/README.md diff --git a/client/lib.go b/client/lib.go new file mode 100644 index 00000000..4bc52b4b --- /dev/null +++ b/client/lib.go @@ -0,0 +1,229 @@ +// Implements a very simple wrapper for GoShimmer's HTTP API . +package shimmer + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/iotaledger/goshimmer/packages/errors" + webapi_broadcastData "github.com/iotaledger/goshimmer/plugins/webapi/broadcastData" + webapi_findTransactions "github.com/iotaledger/goshimmer/plugins/webapi/findTransactions" + webapi_getNeighbors "github.com/iotaledger/goshimmer/plugins/webapi/getNeighbors" + webapi_getTransactions "github.com/iotaledger/goshimmer/plugins/webapi/getTransactions" + webapi_getTrytes "github.com/iotaledger/goshimmer/plugins/webapi/getTrytes" + webapi_gtta "github.com/iotaledger/goshimmer/plugins/webapi/gtta" + webapi_spammer "github.com/iotaledger/goshimmer/plugins/webapi/spammer" + "github.com/iotaledger/iota.go/consts" + "github.com/iotaledger/iota.go/guards" + "github.com/iotaledger/iota.go/trinary" +) + +var ( + ErrBadRequest = errors.New("bad request") + ErrInternalServerError = errors.New("internal server error") + ErrNotFound = errors.New("not found") + ErrUnknownError = errors.New("unknown error") +) + +const ( + routeBroadcastData = "broadcastData" + routeGetTrytes = "getTrytes" + routeGetTransactions = "getTransactions" + routeFindTransactions = "findTransactions" + routeGetNeighbors = "getNeighbors" + routeGetTransactionsToApprove = "getTransactionsToApprove" + routeSpammer = "spammer" + + contentTypeJSON = "application/json" +) + +func NewShimmerAPI(node string) *ShimmerAPI { + return &ShimmerAPI{node: node} +} + +type ShimmerAPI struct { + http.Client + node string +} + +type errorresponse struct { + Error string `json:"error"` +} + +func interpretBody(res *http.Response, decodeTo interface{}) error { + resBody, err := ioutil.ReadAll(res.Body) + if err != nil { + return errors.Wrap(err, "unable to read response body") + } + defer res.Body.Close() + + if res.StatusCode == http.StatusOK { + return json.Unmarshal(resBody, decodeTo) + } + + errRes := &errorresponse{} + if err := json.Unmarshal(resBody, errRes); err != nil { + return errors.Wrap(err, "unable to read error from response body") + } + + switch res.StatusCode { + case http.StatusInternalServerError: + return errors.Wrap(ErrInternalServerError, errRes.Error) + case http.StatusNotFound: + return errors.Wrap(ErrNotFound, errRes.Error) + case http.StatusBadRequest: + return errors.Wrap(ErrBadRequest, errRes.Error) + } + return errors.Wrap(ErrUnknownError, errRes.Error) +} + +func (api *ShimmerAPI) BroadcastData(targetAddress trinary.Trytes, data trinary.Trytes) (trinary.Hash, error) { + if !guards.IsHash(targetAddress) { + return "", errors.Wrapf(consts.ErrInvalidHash, "invalid address: %s", targetAddress) + } + if !guards.IsTrytes(data) { + return "", errors.Wrapf(consts.ErrInvalidTrytes, "invalid trytes: %s", data) + } + + reqBytes, err := json.Marshal(&webapi_broadcastData.Request{Address: targetAddress, Data: data}) + if err != nil { + return "", err + } + + res, err := api.Post(fmt.Sprintf("%s/%s", api.node, routeBroadcastData), contentTypeJSON, bytes.NewReader(reqBytes)) + if err != nil { + return "", err + } + + resObj := &webapi_broadcastData.Response{} + if err := interpretBody(res, resObj); err != nil { + return "", err + } + + return resObj.Hash, nil +} + +func (api *ShimmerAPI) GetTrytes(txHashes trinary.Hashes) ([]trinary.Trytes, error) { + for _, hash := range txHashes { + if !guards.IsTrytes(hash) { + return nil, errors.Wrapf(consts.ErrInvalidHash, "invalid hash: %s", hash) + } + } + + reqBytes, err := json.Marshal(&webapi_getTrytes.Request{Hashes: txHashes}) + if err != nil { + return nil, err + } + + res, err := api.Post(fmt.Sprintf("%s/%s", api.node, routeGetTrytes), contentTypeJSON, bytes.NewReader(reqBytes)) + if err != nil { + return nil, err + } + + resObj := &webapi_getTrytes.Response{} + if err := interpretBody(res, resObj); err != nil { + return nil, err + } + + return resObj.Trytes, nil +} + +func (api *ShimmerAPI) GetTransactions(txHashes trinary.Hashes) ([]webapi_getTransactions.Transaction, error) { + for _, hash := range txHashes { + if !guards.IsTrytes(hash) { + return nil, errors.Wrapf(consts.ErrInvalidHash, "invalid hash: %s", hash) + } + } + + reqBytes, err := json.Marshal(&webapi_getTransactions.Request{Hashes: txHashes}) + if err != nil { + return nil, err + } + + res, err := api.Post(fmt.Sprintf("%s/%s", api.node, routeGetTransactions), contentTypeJSON, bytes.NewReader(reqBytes)) + if err != nil { + return nil, err + } + + resObj := &webapi_getTransactions.Response{} + if err := interpretBody(res, resObj); err != nil { + return nil, err + } + + return resObj.Transactions, nil +} + +func (api *ShimmerAPI) FindTransactions(query *webapi_findTransactions.Request) ([]trinary.Hashes, error) { + for _, hash := range query.Addresses { + if !guards.IsTrytes(hash) { + return nil, errors.Wrapf(consts.ErrInvalidHash, "invalid hash: %s", hash) + } + } + + reqBytes, err := json.Marshal(&query) + if err != nil { + return nil, err + } + + res, err := api.Post(fmt.Sprintf("%s/%s", api.node, routeFindTransactions), contentTypeJSON, bytes.NewReader(reqBytes)) + if err != nil { + return nil, err + } + + resObj := &webapi_findTransactions.Response{} + if err := interpretBody(res, resObj); err != nil { + return nil, err + } + + return resObj.Transactions, nil +} + +func (api *ShimmerAPI) GetNeighbors() (*webapi_getNeighbors.Response, error) { + res, err := api.Get(fmt.Sprintf("%s/%s", api.node, routeGetNeighbors)) + if err != nil { + return nil, err + } + + resObj := &webapi_getNeighbors.Response{} + if err := interpretBody(res, resObj); err != nil { + return nil, err + } + + return resObj, nil +} + +func (api *ShimmerAPI) GetTips() (*webapi_gtta.Response, error) { + res, err := api.Get(fmt.Sprintf("%s/%s", api.node, routeGetTransactionsToApprove)) + if err != nil { + return nil, err + } + + resObj := &webapi_gtta.Response{} + if err := interpretBody(res, resObj); err != nil { + return nil, err + } + + return resObj, nil +} + +func (api *ShimmerAPI) ToggleSpammer(enable bool) (*webapi_spammer.Response, error) { + res, err := api.Get(fmt.Sprintf("%s/%s?cmd=%s", api.node, routeSpammer, func() string { + if enable { + return "start" + } + return "stop" + }())) + if err != nil { + return nil, err + } + + resObj := &webapi_spammer.Response{} + if err := interpretBody(res, resObj); err != nil { + return nil, err + } + + return resObj, nil +} diff --git a/main.go b/main.go index 7822849a..f7d202d1 100644 --- a/main.go +++ b/main.go @@ -61,6 +61,7 @@ func main() { webapi_getTransactions.PLUGIN, webapi_findTransactions.PLUGIN, webapi_getNeighbors.PLUGIN, + webapi_spammer.PLUGIN, ui.PLUGIN, webauth.PLUGIN, diff --git a/plugins/graph/README.md b/plugins/graph/README.md new file mode 100644 index 00000000..da7c4ca7 --- /dev/null +++ b/plugins/graph/README.md @@ -0,0 +1,9 @@ +# How to install this plugin +- Run the following commands in this folder (or set `graph.socketioPath` and `graph.webrootPath` in your config if you want to use another path) + +```bash +git clone https://github.com/glumb/IOTAtangle.git +cd IOTAtangle && git reset --hard 07bba77a296a2d06277cdae56aa963abeeb5f66e +cd ../ +git clone https://github.com/socketio/socket.io-client.git +``` \ No newline at end of file diff --git a/plugins/webapi/broadcastData/plugin.go b/plugins/webapi/broadcastData/plugin.go index ef857882..0ed2562f 100644 --- a/plugins/webapi/broadcastData/plugin.go +++ b/plugins/webapi/broadcastData/plugin.go @@ -30,12 +30,14 @@ func configure(plugin *node.Plugin) { // broadcasts it to the node's neighbors. It returns the transaction hash if successful. func broadcastData(c echo.Context) error { - var request webRequest + var request Request if err := c.Bind(&request); err != nil { log.Info(err.Error()) return requestFailed(c, err.Error()) } + log.Debug("Received - address:", request.Address, " data:", request.Data) + tx := value_transaction.New() tx.SetHead(true) tx.SetTail(true) @@ -76,23 +78,23 @@ func broadcastData(c echo.Context) error { } func requestSuccessful(c echo.Context, txHash string) error { - return c.JSON(http.StatusCreated, webResponse{ + return c.JSON(http.StatusCreated, Response{ Hash: txHash, }) } func requestFailed(c echo.Context, message string) error { - return c.JSON(http.StatusBadRequest, webResponse{ + return c.JSON(http.StatusBadRequest, Response{ Error: message, }) } -type webResponse struct { +type Response struct { Hash string `json:"hash,omitempty"` Error string `json:"error,omitempty"` } -type webRequest struct { +type Request struct { Address string `json:"address"` Data string `json:"data"` } diff --git a/plugins/webapi/findTransactions/plugin.go b/plugins/webapi/findTransactions/plugin.go index cf0f1239..9c4fc937 100644 --- a/plugins/webapi/findTransactions/plugin.go +++ b/plugins/webapi/findTransactions/plugin.go @@ -25,7 +25,7 @@ func configure(plugin *node.Plugin) { // the value at the index of that address is empty. func findTransactions(c echo.Context) error { - var request webRequest + var request Request if err := c.Bind(&request); err != nil { log.Info(err.Error()) @@ -46,22 +46,22 @@ func findTransactions(c echo.Context) error { } func requestSuccessful(c echo.Context, txHashes [][]trinary.Trytes) error { - return c.JSON(http.StatusOK, webResponse{ + return c.JSON(http.StatusOK, Response{ Transactions: txHashes, }) } func requestFailed(c echo.Context, message string) error { - return c.JSON(http.StatusNotFound, webResponse{ + return c.JSON(http.StatusNotFound, Response{ Error: message, }) } -type webResponse struct { +type Response struct { Transactions [][]trinary.Trytes `json:"transactions,omitempty"` //string Error string `json:"error,omitempty"` } -type webRequest struct { +type Request struct { Addresses []string `json:"addresses"` } diff --git a/plugins/webapi/getNeighbors/plugin.go b/plugins/webapi/getNeighbors/plugin.go index 01457705..097793e1 100644 --- a/plugins/webapi/getNeighbors/plugin.go +++ b/plugins/webapi/getNeighbors/plugin.go @@ -24,8 +24,8 @@ func configure(plugin *node.Plugin) { // getNeighbors returns the chosen and accepted neighbors of the node func getNeighbors(c echo.Context) error { - chosen := []neighbor{} - accepted := []neighbor{} + chosen := []Neighbor{} + accepted := []Neighbor{} if autopeering.Selection == nil { return requestFailed(c, "Neighbor Selection is not enabled") @@ -33,7 +33,7 @@ func getNeighbors(c echo.Context) error { for _, peer := range autopeering.Selection.GetOutgoingNeighbors() { - n := neighbor{ + n := Neighbor{ ID: peer.ID().String(), PublicKey: base64.StdEncoding.EncodeToString(peer.PublicKey()), } @@ -41,7 +41,7 @@ func getNeighbors(c echo.Context) error { chosen = append(chosen, n) } for _, peer := range autopeering.Selection.GetIncomingNeighbors() { - n := neighbor{ + n := Neighbor{ ID: peer.ID().String(), PublicKey: base64.StdEncoding.EncodeToString(peer.PublicKey()), } @@ -52,26 +52,26 @@ func getNeighbors(c echo.Context) error { } -func requestSuccessful(c echo.Context, chosen, accepted []neighbor) error { - return c.JSON(http.StatusOK, webResponse{ +func requestSuccessful(c echo.Context, chosen, accepted []Neighbor) error { + return c.JSON(http.StatusOK, Response{ Chosen: chosen, Accepted: accepted, }) } func requestFailed(c echo.Context, message string) error { - return c.JSON(http.StatusNotFound, webResponse{ + return c.JSON(http.StatusNotFound, Response{ Error: message, }) } -type webResponse struct { - Chosen []neighbor `json:"chosen"` - Accepted []neighbor `json:"accepted"` +type Response struct { + Chosen []Neighbor `json:"chosen"` + Accepted []Neighbor `json:"accepted"` Error string `json:"error,omitempty"` } -type neighbor struct { +type Neighbor struct { ID string `json:"id"` // comparable node identifier PublicKey string `json:"publicKey"` // public key used to verify signatures Services []peerService diff --git a/plugins/webapi/getTransactions/plugin.go b/plugins/webapi/getTransactions/plugin.go index 2dfb696f..448243f9 100644 --- a/plugins/webapi/getTransactions/plugin.go +++ b/plugins/webapi/getTransactions/plugin.go @@ -25,13 +25,14 @@ func configure(plugin *node.Plugin) { // the value at the index of that transaction hash is empty. func getTransactions(c echo.Context) error { - var request webRequest - result := []transaction{} + var request Request + result := []Transaction{} if err := c.Bind(&request); err != nil { log.Info(err.Error()) return requestFailed(c, err.Error()) } + log.Debug("Received:", request.Hashes) for _, hash := range request.Hashes { @@ -40,7 +41,7 @@ func getTransactions(c echo.Context) error { return requestFailed(c, err.Error()) } if tx != nil { - t := transaction{ + t := Transaction{ Hash: tx.GetHash(), WeightMagnitude: tx.GetWeightMagnitude(), TrunkTransactionHash: tx.GetTrunkTransactionHash(), @@ -56,7 +57,7 @@ func getTransactions(c echo.Context) error { result = append(result, t) } else { //tx not found - result = append(result, transaction{}) + result = append(result, Transaction{}) } } @@ -64,28 +65,28 @@ func getTransactions(c echo.Context) error { return requestSuccessful(c, result) } -func requestSuccessful(c echo.Context, txs []transaction) error { - return c.JSON(http.StatusOK, webResponse{ +func requestSuccessful(c echo.Context, txs []Transaction) error { + return c.JSON(http.StatusOK, Response{ Transactions: txs, }) } func requestFailed(c echo.Context, message string) error { - return c.JSON(http.StatusNotFound, webResponse{ + return c.JSON(http.StatusNotFound, Response{ Error: message, }) } -type webResponse struct { - Transactions []transaction `json:"transaction,omitempty"` +type Response struct { + Transactions []Transaction `json:"transaction,omitempty"` Error string `json:"error,omitempty"` } -type webRequest struct { +type Request struct { Hashes []string `json:"hashes"` } -type transaction struct { +type Transaction struct { Hash trinary.Trytes `json:"hash,omitempty"` WeightMagnitude int `json:"weightMagnitude,omitempty"` TrunkTransactionHash trinary.Trytes `json:"trunkTransactionHash,omitempty"` diff --git a/plugins/webapi/getTrytes/plugin.go b/plugins/webapi/getTrytes/plugin.go index 4dac6f1b..8c9a8e25 100644 --- a/plugins/webapi/getTrytes/plugin.go +++ b/plugins/webapi/getTrytes/plugin.go @@ -25,7 +25,7 @@ func configure(plugin *node.Plugin) { // the value at the index of that transaction hash is empty. func getTrytes(c echo.Context) error { - var request webRequest + var request Request result := []trinary.Trytes{} if err := c.Bind(&request); err != nil { log.Info(err.Error()) @@ -56,22 +56,22 @@ func getTrytes(c echo.Context) error { } func requestSuccessful(c echo.Context, txTrytes []trinary.Trytes) error { - return c.JSON(http.StatusOK, webResponse{ + return c.JSON(http.StatusOK, Response{ Trytes: txTrytes, }) } func requestFailed(c echo.Context, message string) error { - return c.JSON(http.StatusNotFound, webResponse{ + return c.JSON(http.StatusNotFound, Response{ Error: message, }) } -type webResponse struct { +type Response struct { Trytes []trinary.Trytes `json:"trytes,omitempty"` //string Error string `json:"error,omitempty"` } -type webRequest struct { +type Request struct { Hashes []string `json:"hashes"` } diff --git a/plugins/webapi/gtta/plugin.go b/plugins/webapi/gtta/plugin.go index 3234ddc6..61217f8e 100644 --- a/plugins/webapi/gtta/plugin.go +++ b/plugins/webapi/gtta/plugin.go @@ -19,13 +19,13 @@ func Handler(c echo.Context) error { branchTransactionHash := tipselection.GetRandomTip() trunkTransactionHash := tipselection.GetRandomTip() - return c.JSON(http.StatusOK, webResponse{ + return c.JSON(http.StatusOK, Response{ BranchTransaction: branchTransactionHash, TrunkTransaction: trunkTransactionHash, }) } -type webResponse struct { +type Response struct { BranchTransaction trinary.Trytes `json:"branchTransaction"` TrunkTransaction trinary.Trytes `json:"trunkTransaction"` } diff --git a/plugins/webapi/spammer/plugin.go b/plugins/webapi/spammer/plugin.go index fe29875d..97b9056a 100644 --- a/plugins/webapi/spammer/plugin.go +++ b/plugins/webapi/spammer/plugin.go @@ -17,7 +17,7 @@ func configure(plugin *node.Plugin) { func WebApiHandler(c echo.Context) error { - var request webRequest + var request Request if err := c.Bind(&request); err != nil { return requestFailed(c, err.Error()) } @@ -44,22 +44,22 @@ func WebApiHandler(c echo.Context) error { } func requestSuccessful(c echo.Context, message string) error { - return c.JSON(http.StatusOK, webResponse{ + return c.JSON(http.StatusOK, Response{ Message: message, }) } func requestFailed(c echo.Context, message string) error { - return c.JSON(http.StatusNotFound, webResponse{ + return c.JSON(http.StatusNotFound, Response{ Message: message, }) } -type webResponse struct { +type Response struct { Message string `json:"message"` } -type webRequest struct { +type Request struct { Cmd string `json:"cmd"` Tps uint `json:"tps"` } -- GitLab