Skip to content
Snippets Groups Projects
Commit ed435f7d authored by Angelo Capossele's avatar Angelo Capossele Committed by Luca Moser
Browse files

Adds a HTTP API to query and create transactions (#95)

* :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

* :construction: WIP

* :construction: WIP

* :sparkles: adds txs per address

* :sparkles: adds findTransactions API
parent 33edf86c
No related branches found
No related tags found
No related merge requests found
......@@ -19,10 +19,13 @@ import (
"github.com/iotaledger/goshimmer/plugins/tipselection"
"github.com/iotaledger/goshimmer/plugins/ui"
"github.com/iotaledger/goshimmer/plugins/webapi"
webapi_gtta "github.com/iotaledger/goshimmer/plugins/webapi-gtta"
webapi_send_data "github.com/iotaledger/goshimmer/plugins/webapi-send-data"
webapi_spammer "github.com/iotaledger/goshimmer/plugins/webapi-spammer"
webapi_tx_request "github.com/iotaledger/goshimmer/plugins/webapi-tx-request"
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/goshimmer/plugins/webauth"
"github.com/iotaledger/goshimmer/plugins/zeromq"
"github.com/iotaledger/hive.go/node"
......@@ -53,9 +56,11 @@ func main() {
webapi.PLUGIN,
webapi_gtta.PLUGIN,
webapi_spammer.PLUGIN,
webapi_send_data.PLUGIN,
webapi_tx_request.PLUGIN,
webapi_spammer.PLUGIN,
webapi_broadcastData.PLUGIN,
webapi_getTrytes.PLUGIN,
webapi_getTransactions.PLUGIN,
webapi_findTransactions.PLUGIN,
webapi_getNeighbors.PLUGIN,
ui.PLUGIN,
webauth.PLUGIN,
......
......@@ -10,7 +10,7 @@ const (
ADDRESS_OFFSET = 0
VALUE_OFFSET = ADDRESS_END
TIMESTAMP_OFFSET = VALUE_END
SIGNATURE_MESSAGE_FRAGMENT_OFFSET = TIMESTAMP_SIZE
SIGNATURE_MESSAGE_FRAGMENT_OFFSET = TIMESTAMP_END
ADDRESS_SIZE = 243
VALUE_SIZE = 81
......
......@@ -100,6 +100,7 @@ func start(shutdownSignal <-chan struct{}) {
}
log.Infof("Auto Peering started: address=%s", srv.LocalAddr())
log.Debugf("Auto Peering server started: PubKey=%s", base64.StdEncoding.EncodeToString(local.GetInstance().PublicKey()))
<-shutdownSignal
log.Info("Stopping Auto Peering server ...")
......
......@@ -30,7 +30,6 @@ func StoreTransactionHashForAddressInDatabase(address *TxHashForAddress) error {
); err != nil {
return ErrDatabaseError.Derive(err, "failed to store tx for address in database")
}
return nil
}
......
......@@ -20,18 +20,18 @@ import (
func configure(plugin *node.Plugin) {
//webapi.Server.Static("ui", "plugins/ui/src")
webapi.AddEndpoint("ui", func(c echo.Context) error {
webapi.Server.GET("ui", func(c echo.Context) error {
return c.HTML(http.StatusOK, files["index.html"])
})
webapi.AddEndpoint("ui/**", staticFileServer)
webapi.Server.GET("ui/**", staticFileServer)
webapi.AddEndpoint("ws", upgrader)
webapi.AddEndpoint("loghistory", func(c echo.Context) error {
webapi.Server.GET("ws", upgrader)
webapi.Server.GET("loghistory", func(c echo.Context) error {
logMutex.RLock()
defer logMutex.RUnlock()
return c.JSON(http.StatusOK, logHistory)
})
webapi.AddEndpoint("tpsqueue", func(c echo.Context) error {
webapi.Server.GET("tpsqueue", func(c echo.Context) error {
tpsQueueMutex.RLock()
defer tpsQueueMutex.RUnlock()
return c.JSON(http.StatusOK, tpsQueue)
......
package webapi
import (
"github.com/labstack/echo"
)
func AddEndpoint(url string, handler func(c echo.Context) error) {
Server.GET(url, handler)
}
package webapi_send_data
package broadcastData
import (
"net/http"
"time"
"github.com/iotaledger/goshimmer/plugins/autopeering/local"
"github.com/iotaledger/goshimmer/packages/gossip"
"github.com/iotaledger/goshimmer/packages/model/meta_transaction"
"github.com/iotaledger/goshimmer/packages/model/value_transaction"
"github.com/iotaledger/goshimmer/plugins/autopeering/local"
"github.com/iotaledger/goshimmer/plugins/tipselection"
"github.com/iotaledger/goshimmer/plugins/webapi"
"github.com/iotaledger/hive.go/logger"
"github.com/iotaledger/hive.go/node"
"github.com/iotaledger/hive.go/typeutils"
"github.com/iotaledger/iota.go/address"
"github.com/iotaledger/iota.go/trinary"
"github.com/labstack/echo"
)
var PLUGIN = node.NewPlugin("WebAPI SendData Endpoint", node.Enabled, configure)
var PLUGIN = node.NewPlugin("WebAPI broadcastData Endpoint", node.Enabled, configure)
var log *logger.Logger
func configure(plugin *node.Plugin) {
log = logger.NewLogger("API-sendData")
webapi.AddEndpoint("sendData", SendDataHandler)
log = logger.NewLogger("API-broadcastData")
webapi.Server.POST("broadcastData", broadcastData)
}
func SendDataHandler(c echo.Context) error {
c.Set("requestStartTime", time.Now())
// broadcastData creates a data (0-value) transaction given an input of bytes and
// broadcasts it to the node's neighbors. It returns the transaction hash if successful.
func broadcastData(c echo.Context) error {
var request webRequest
if err := c.Bind(&request); err != nil {
log.Info(err.Error())
return requestFailed(c, err.Error())
}
log.Info("Received:", request.Data)
log.Debug("Received - address:", request.Address, " data:", request.Data)
tx := value_transaction.New()
tx.SetHead(true)
tx.SetTail(true)
buffer := make([]byte, 6561)
if len(request.Data) > 6561 {
return requestFailed(c, "data exceding 6561 byte limit")
buffer := make([]byte, 2187)
if len(request.Data) > 2187 {
log.Warn("Data exceeding 2187 byte limit -", len(request.Data))
return requestFailed(c, "Data exceeding 2187 byte limit")
}
copy(buffer, []byte(request.Data))
copy(buffer, typeutils.StringToBytes(request.Data))
trytes, err := trinary.BytesToTrytes(buffer)
if err != nil {
log.Info("Trytes conversion", err.Error())
log.Warn("Trytes conversion failed", err.Error())
return requestFailed(c, err.Error())
}
err = address.ValidAddress(request.Address)
if err != nil {
log.Warn("Invalid Address:", request.Address)
return requestFailed(c, err.Error())
}
tx.SetAddress(request.Address)
tx.SetSignatureMessageFragment(trytes)
tx.SetValue(0)
tx.SetBranchTransactionHash(tipselection.GetRandomTip())
tx.SetTrunkTransactionHash(tipselection.GetRandomTip())
tx.SetTimestamp(uint(time.Now().Unix()))
if err := tx.DoProofOfWork(meta_transaction.MIN_WEIGHT_MAGNITUDE); err != nil {
log.Warn("PoW failed", err)
return requestFailed(c, err.Error())
}
gossip.Events.TransactionReceived.Trigger(&gossip.TransactionReceivedEvent{Data: tx.GetBytes(), Peer: &local.GetInstance().Peer})
......@@ -65,25 +77,22 @@ func SendDataHandler(c echo.Context) error {
func requestSuccessful(c echo.Context, txHash string) error {
return c.JSON(http.StatusCreated, webResponse{
Duration: time.Since(c.Get("requestStartTime").(time.Time)).Nanoseconds() / 1e6,
TransactionHash: txHash,
Status: "OK",
Hash: txHash,
})
}
func requestFailed(c echo.Context, message string) error {
return c.JSON(http.StatusNotModified, webResponse{
Duration: time.Since(c.Get("requestStartTime").(time.Time)).Nanoseconds() / 1e6,
Status: message,
return c.JSON(http.StatusBadRequest, webResponse{
Error: message,
})
}
type webResponse struct {
Duration int64 `json:"duration"`
TransactionHash string `json:"transactionHash"`
Status string `json:"status"`
Hash string `json:"hash,omitempty"`
Error string `json:"error,omitempty"`
}
type webRequest struct {
Address string `json:"address"`
Data string `json:"data"`
}
package webapi_tx_request
package findTransactions
import (
"net/http"
"time"
"github.com/iotaledger/goshimmer/plugins/tangle"
"github.com/iotaledger/goshimmer/plugins/webapi"
"github.com/iotaledger/hive.go/logger"
"github.com/iotaledger/hive.go/node"
"github.com/iotaledger/iota.go/trinary"
"github.com/labstack/echo"
)
var PLUGIN = node.NewPlugin("WebAPI Transaction Request Endpoint", node.Enabled, configure)
var PLUGIN = node.NewPlugin("WebAPI findTransactions Endpoint", node.Enabled, configure)
var log *logger.Logger
func configure(plugin *node.Plugin) {
log = logger.NewLogger("API-TxRequest")
webapi.AddEndpoint("txRequest", Handler)
log = logger.NewLogger("API-findTransactions")
webapi.Server.GET("findTransactions", findTransactions)
}
func Handler(c echo.Context) error {
c.Set("requestStartTime", time.Now())
// findTransactions returns the array of transaction hashes for the
// given addresses (in the same order as the parameters).
// If a node doesn't have any transaction hash for a given address in its ledger,
// the value at the index of that address is empty.
func findTransactions(c echo.Context) error {
var request webRequest
if err := c.Bind(&request); err != nil {
log.Info(err.Error())
return requestFailed(c, err.Error())
}
log.Info("Received:", request.TransactionHash)
log.Debug("Received:", request.Addresses)
result := make([][]trinary.Trytes, len(request.Addresses))
tx, err := tangle.GetTransaction(request.TransactionHash)
for i, address := range request.Addresses {
txs, err := tangle.ReadTransactionHashesForAddressFromDatabase(address)
if err != nil {
return requestFailed(c, err.Error())
}
if tx == nil {
return requestFailed(c, "Tx not found")
result[i] = append(result[i], txs...)
}
return requestSuccessful(c, tx.GetBytes())
return requestSuccessful(c, result)
}
func requestSuccessful(c echo.Context, tx []byte) error {
func requestSuccessful(c echo.Context, txHashes [][]trinary.Trytes) error {
return c.JSON(http.StatusOK, webResponse{
Duration: time.Since(c.Get("requestStartTime").(time.Time)).Nanoseconds() / 1e6,
Transaction: tx,
Status: "OK",
Transactions: txHashes,
})
}
func requestFailed(c echo.Context, message string) error {
return c.JSON(http.StatusNotFound, webResponse{
Duration: time.Since(c.Get("requestStartTime").(time.Time)).Nanoseconds() / 1e6,
Status: message,
Error: message,
})
}
type webResponse struct {
Duration int64 `json:"duration"`
Transaction []byte `json:"transaction"`
Status string `json:"status"`
Transactions [][]trinary.Trytes `json:"transactions,omitempty"` //string
Error string `json:"error,omitempty"`
}
type webRequest struct {
TransactionHash string `json:"transactionHash"`
Addresses []string `json:"addresses"`
}
package getNeighbors
import (
"encoding/base64"
"net/http"
"github.com/iotaledger/goshimmer/packages/autopeering/peer"
"github.com/iotaledger/goshimmer/packages/autopeering/peer/service"
"github.com/iotaledger/goshimmer/plugins/autopeering"
"github.com/iotaledger/goshimmer/plugins/webapi"
"github.com/iotaledger/hive.go/logger"
"github.com/iotaledger/hive.go/node"
"github.com/labstack/echo"
)
var PLUGIN = node.NewPlugin("WebAPI getNeighbors Endpoint", node.Enabled, configure)
var log *logger.Logger
func configure(plugin *node.Plugin) {
log = logger.NewLogger("API-getNeighbors")
webapi.Server.GET("getNeighbors", getNeighbors)
}
// getNeighbors returns the chosen and accepted neighbors of the node
func getNeighbors(c echo.Context) error {
chosen := []neighbor{}
accepted := []neighbor{}
if autopeering.Selection == nil {
return requestFailed(c, "Neighbor Selection is not enabled")
}
for _, peer := range autopeering.Selection.GetOutgoingNeighbors() {
n := neighbor{
ID: peer.ID().String(),
PublicKey: base64.StdEncoding.EncodeToString(peer.PublicKey()),
}
n.Services = getServices(peer)
chosen = append(chosen, n)
}
for _, peer := range autopeering.Selection.GetIncomingNeighbors() {
n := neighbor{
ID: peer.ID().String(),
PublicKey: base64.StdEncoding.EncodeToString(peer.PublicKey()),
}
n.Services = getServices(peer)
accepted = append(accepted, n)
}
return requestSuccessful(c, chosen, accepted)
}
func requestSuccessful(c echo.Context, chosen, accepted []neighbor) error {
return c.JSON(http.StatusOK, webResponse{
Chosen: chosen,
Accepted: accepted,
})
}
func requestFailed(c echo.Context, message string) error {
return c.JSON(http.StatusNotFound, webResponse{
Error: message,
})
}
type webResponse struct {
Chosen []neighbor `json:"chosen"`
Accepted []neighbor `json:"accepted"`
Error string `json:"error,omitempty"`
}
type neighbor struct {
ID string `json:"id"` // comparable node identifier
PublicKey string `json:"publicKey"` // public key used to verify signatures
Services []peerService
}
type peerService struct {
ID string `json:"id"` // ID of the service
Address string `json:"address"` // network address of the service
}
func getServices(p *peer.Peer) []peerService {
services := []peerService{}
peeringService := p.Services().Get(service.PeeringKey)
if peeringService != nil {
services = append(services, peerService{
ID: "peering",
Address: peeringService.String(),
})
}
gossipService := p.Services().Get(service.GossipKey)
if gossipService != nil {
services = append(services, peerService{
ID: "gossip",
Address: gossipService.String(),
})
}
fpcService := p.Services().Get(service.FPCKey)
if fpcService != nil {
services = append(services, peerService{
ID: "FPC",
Address: fpcService.String(),
})
}
return services
}
package getTransactions
import (
"net/http"
"github.com/iotaledger/goshimmer/plugins/tangle"
"github.com/iotaledger/goshimmer/plugins/webapi"
"github.com/iotaledger/hive.go/logger"
"github.com/iotaledger/hive.go/node"
"github.com/iotaledger/iota.go/trinary"
"github.com/labstack/echo"
)
var PLUGIN = node.NewPlugin("WebAPI getTransaction Endpoint", node.Enabled, configure)
var log *logger.Logger
func configure(plugin *node.Plugin) {
log = logger.NewLogger("API-getTransactions")
webapi.Server.GET("getTransactions", getTransactions)
}
// getTransactions returns the array of transactions for the
// given transaction hashes (in the same order as the parameters).
// If a node doesn't have the transaction for a given transaction hash in its ledger,
// the value at the index of that transaction hash is empty.
func getTransactions(c echo.Context) error {
var request webRequest
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 {
tx, err := tangle.GetTransaction(hash)
if err != nil {
return requestFailed(c, err.Error())
}
if tx != nil {
t := transaction{
Hash: tx.GetHash(),
WeightMagnitude: tx.GetWeightMagnitude(),
TrunkTransactionHash: tx.GetTrunkTransactionHash(),
BranchTransactionHash: tx.GetBranchTransactionHash(),
Head: tx.IsHead(),
Tail: tx.IsTail(),
Nonce: tx.GetNonce(),
Address: tx.GetAddress(),
Value: tx.GetValue(),
Timestamp: tx.GetTimestamp(),
SignatureMessageFragment: tx.GetSignatureMessageFragment(),
}
result = append(result, t)
} else {
//tx not found
result = append(result, transaction{})
}
}
return requestSuccessful(c, result)
}
func requestSuccessful(c echo.Context, txs []transaction) error {
return c.JSON(http.StatusOK, webResponse{
Transactions: txs,
})
}
func requestFailed(c echo.Context, message string) error {
return c.JSON(http.StatusNotFound, webResponse{
Error: message,
})
}
type webResponse struct {
Transactions []transaction `json:"transaction,omitempty"`
Error string `json:"error,omitempty"`
}
type webRequest struct {
Hashes []string `json:"hashes"`
}
type transaction struct {
Hash trinary.Trytes `json:"hash,omitempty"`
WeightMagnitude int `json:"weightMagnitude,omitempty"`
TrunkTransactionHash trinary.Trytes `json:"trunkTransactionHash,omitempty"`
BranchTransactionHash trinary.Trytes `json:"branchTransactionHash,omitempty"`
Head bool `json:"head,omitempty"`
Tail bool `json:"tail,omitempty"`
Nonce trinary.Trytes `json:"nonce,omitempty"`
Address trinary.Trytes `json:"address,omitempty"`
Value int64 `json:"value,omitempty"`
Timestamp uint `json:"timestamp,omitempty"`
SignatureMessageFragment trinary.Trytes `json:"signatureMessageFragment,omitempty"`
}
package getTrytes
import (
"net/http"
"github.com/iotaledger/goshimmer/plugins/tangle"
"github.com/iotaledger/goshimmer/plugins/webapi"
"github.com/iotaledger/hive.go/logger"
"github.com/iotaledger/hive.go/node"
"github.com/iotaledger/iota.go/trinary"
"github.com/labstack/echo"
)
var PLUGIN = node.NewPlugin("WebAPI getTrytes Endpoint", node.Enabled, configure)
var log *logger.Logger
func configure(plugin *node.Plugin) {
log = logger.NewLogger("API-getTrytes")
webapi.Server.GET("getTrytes", getTrytes)
}
// getTrytes returns the array of transaction trytes for the
// given transaction hashes (in the same order as the parameters).
// If a node doesn't have the trytes for a given transaction hash in its ledger,
// the value at the index of that transaction hash is empty.
func getTrytes(c echo.Context) error {
var request webRequest
result := []trinary.Trytes{}
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 {
tx, err := tangle.GetTransaction(hash)
if err != nil {
return requestFailed(c, err.Error())
}
if tx != nil {
trytes, err := trinary.TritsToTrytes(tx.GetTrits())
// Returns an error if len(tx.GetTrits())%3!=0
if err != nil {
return requestFailed(c, err.Error())
}
result = append(result, trytes)
} else {
//tx not found
result = append(result, "")
}
}
return requestSuccessful(c, result)
}
func requestSuccessful(c echo.Context, txTrytes []trinary.Trytes) error {
return c.JSON(http.StatusOK, webResponse{
Trytes: txTrytes,
})
}
func requestFailed(c echo.Context, message string) error {
return c.JSON(http.StatusNotFound, webResponse{
Error: message,
})
}
type webResponse struct {
Trytes []trinary.Trytes `json:"trytes,omitempty"` //string
Error string `json:"error,omitempty"`
}
type webRequest struct {
Hashes []string `json:"hashes"`
}
package webapi_gtta
package gtta
import (
"net/http"
"time"
"github.com/iotaledger/goshimmer/plugins/tipselection"
"github.com/iotaledger/goshimmer/plugins/webapi"
......@@ -11,25 +10,22 @@ import (
"github.com/labstack/echo"
)
var PLUGIN = node.NewPlugin("WebAPI GTTA Endpoint", node.Enabled, func(plugin *node.Plugin) {
webapi.AddEndpoint("getTransactionsToApprove", Handler)
var PLUGIN = node.NewPlugin("WebAPI GTTA Endpoint", node.Disabled, func(plugin *node.Plugin) {
webapi.Server.GET("getTransactionsToApprove", Handler)
})
func Handler(c echo.Context) error {
start := time.Now()
branchTransactionHash := tipselection.GetRandomTip()
trunkTransactionHash := tipselection.GetRandomTip()
return c.JSON(http.StatusOK, webResponse{
Duration: time.Since(start).Nanoseconds() / 1e6,
BranchTransaction: branchTransactionHash,
TrunkTransaction: trunkTransactionHash,
})
}
type webResponse struct {
Duration int64 `json:"duration"`
BranchTransaction trinary.Trytes `json:"branchTransaction"`
TrunkTransaction trinary.Trytes `json:"trunkTransaction"`
}
package webapi_spammer
package spammer
import (
"net/http"
"time"
"github.com/iotaledger/goshimmer/packages/transactionspammer"
"github.com/iotaledger/goshimmer/plugins/webapi"
......@@ -13,11 +12,10 @@ import (
var PLUGIN = node.NewPlugin("Spammer", node.Disabled, configure)
func configure(plugin *node.Plugin) {
webapi.AddEndpoint("spammer", WebApiHandler)
webapi.Server.GET("spammer", WebApiHandler)
}
func WebApiHandler(c echo.Context) error {
c.Set("requestStartTime", time.Now())
var request webRequest
if err := c.Bind(&request); err != nil {
......@@ -27,7 +25,7 @@ func WebApiHandler(c echo.Context) error {
switch request.Cmd {
case "start":
if request.Tps == 0 {
request.Tps = 1000
request.Tps = 1
}
transactionspammer.Stop()
......@@ -47,23 +45,17 @@ func WebApiHandler(c echo.Context) error {
func requestSuccessful(c echo.Context, message string) error {
return c.JSON(http.StatusOK, webResponse{
Duration: time.Since(c.Get("requestStartTime").(time.Time)).Nanoseconds() / 1e6,
Status: "success",
Message: message,
})
}
func requestFailed(c echo.Context, message string) error {
return c.JSON(http.StatusOK, webResponse{
Duration: time.Since(c.Get("requestStartTime").(time.Time)).Nanoseconds() / 1e6,
Status: "failed",
return c.JSON(http.StatusNotFound, webResponse{
Message: message,
})
}
type webResponse struct {
Duration int64 `json:"duration"`
Status string `json:"status"`
Message string `json:"message"`
}
......
......@@ -41,7 +41,7 @@ func configure(plugin *node.Plugin) {
func run(plugin *node.Plugin) {
daemon.BackgroundWorker("webauth", func(shutdownSignal <-chan struct{}) {
webapi.AddEndpoint("login", func(c echo.Context) error {
webapi.Server.GET("login", func(c echo.Context) error {
username := c.FormValue("username")
password := c.FormValue("password")
uiUser := os.Getenv("UI_USER")
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment