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