Skip to content

Commit

Permalink
Implement proxy to observer adapter.
Browse files Browse the repository at this point in the history
  • Loading branch information
andreibancioiu committed Aug 5, 2024
1 parent 90ec53c commit 866d7f6
Showing 1 changed file with 187 additions and 0 deletions.
187 changes: 187 additions & 0 deletions systemtests/proxyToObserverAdapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// This is a one-file HTTP forwarder: captures requests destined to the MultiversX Observer API and transforms them into requests to the MultiversX Proxy API.
// It handles GET and POST requests used by the MultiversX Rosetta implementation.
package main

import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"os"
"time"

"github.com/gin-gonic/gin"
"github.com/urfave/cli"
)

var (
cliFlagProxyUrl = cli.StringFlag{
Name: "proxy",
Required: true,
}

cliFlagShard = cli.UintFlag{
Name: "shard",
Required: true,
}

cliFlagSleep = cli.UintFlag{
Name: "sleep",
Required: true,
}
)

func main() {
app := cli.NewApp()
app.Action = startAdapter
app.Flags = []cli.Flag{
cliFlagProxyUrl,
cliFlagShard,
cliFlagSleep,
}

err := app.Run(os.Args)
if err != nil {
log.Fatal(err.Error())
os.Exit(1)
}
}

func startAdapter(ctx *cli.Context) {
adapter := adapter{
proxyUrl: ctx.GlobalString(cliFlagProxyUrl.Name),
shard: ctx.GlobalUint(cliFlagShard.Name),
sleepDuration: ctx.GlobalUint(cliFlagSleep.Name),
}

router := gin.Default()
router.GET("/node/status", adapter.getNodeStatus)
router.GET("/node/epoch-start/:epoch", adapter.getEpochStart)
router.GET("/block/by-nonce/:nonce", adapter.getBlockByNonce)
router.GET("/address/:address/esdt/:token", adapter.getAccountESDT)
router.GET("/address/:address", adapter.getAccount)
router.POST("/transaction/send", adapter.sendTransaction)
router.Run(":8080")

Check failure on line 66 in systemtests/proxyToObserverAdapter.go

View workflow job for this annotation

GitHub Actions / golangci linter

Error return value of `router.Run` is not checked (errcheck)
}

type adapter struct {
shard uint
proxyUrl string
sleepDuration uint
}

func (adapter *adapter) getNodeStatus(c *gin.Context) {
url := fmt.Sprintf("network/status/%d", adapter.shard)

adapter.forwardGetRequest(c, url, func(response map[string]interface{}) {
data := response["data"].(map[string]interface{})
data["metrics"] = data["status"]
data["metrics"].(map[string]interface{})["erd_app_version"] = "v1.2.3"
data["metrics"].(map[string]interface{})["erd_public_key_block_sign"] = "00"
delete(data, "status")
})
}

func (adapter *adapter) getEpochStart(c *gin.Context) {
epoch := c.Param("epoch")
url := fmt.Sprintf("network/epoch-start/%d/by-epoch/%s", adapter.shard, epoch)
adapter.forwardGetRequest(c, url, nil)
}

func (adapter *adapter) getBlockByNonce(c *gin.Context) {
nonce := c.Param("nonce")
url := fmt.Sprintf("block/%d/by-nonce/%s", adapter.shard, nonce)
adapter.forwardGetRequest(c, url, nil)
}

func (adapter *adapter) getAccountESDT(c *gin.Context) {
address := c.Param("address")
token := c.Param("token")
url := fmt.Sprintf("address/%s/esdt/%s", address, token)
adapter.forwardGetRequest(c, url, nil)
}

func (adapter *adapter) getAccount(c *gin.Context) {
address := c.Param("address")
url := fmt.Sprintf("address/%s", address)
adapter.forwardGetRequest(c, url, nil)
}

func (adapter *adapter) sendTransaction(c *gin.Context) {
url := fmt.Sprintf("transaction/send")
adapter.forwardPostRequest(c, url)
}

func (adapter *adapter) forwardGetRequest(c *gin.Context, urlPath string, mutateResponse func(map[string]interface{})) {
// Delay GET requests, to avoid reaching the rate limit.
adapter.sleep()

urlObject := url.URL{
Path: urlPath,
RawQuery: c.Request.URL.Query().Encode(),
}

url := fmt.Sprintf("%s/%s", adapter.proxyUrl, urlObject.String())

rawResponse, err := http.Get(url)
if err != nil {
log.Printf("Error fetching %s: %v", url, err)
adapter.emitInternalServerError(c, err)
return
}
defer rawResponse.Body.Close()

var response map[string]interface{}
if err := json.NewDecoder(rawResponse.Body).Decode(&response); err != nil {
log.Printf("Error decoding response: %v", err)
adapter.emitInternalServerError(c, err)
return
}

if mutateResponse != nil {
mutateResponse(response)
}

c.JSON(rawResponse.StatusCode, response)
}

func (adapter *adapter) forwardPostRequest(c *gin.Context, urlPath string) {
url := fmt.Sprintf("%s/%s", adapter.proxyUrl, urlPath)

var data map[string]interface{}
if err := c.BindJSON(&data); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Bad request"})
return
}

jsonData, err := json.Marshal(data)
if err != nil {
adapter.emitInternalServerError(c, err)
return
}

rawResponse, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
adapter.emitInternalServerError(c, err)
return
}
defer rawResponse.Body.Close()

var response map[string]interface{}
if err := json.NewDecoder(rawResponse.Body).Decode(&response); err != nil {
adapter.emitInternalServerError(c, err)
return
}

c.JSON(rawResponse.StatusCode, response)
}

func (adapter *adapter) sleep() {
time.Sleep(time.Duration(adapter.sleepDuration) * time.Millisecond)
}

func (adapter *adapter) emitInternalServerError(c *gin.Context, err error) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}

0 comments on commit 866d7f6

Please sign in to comment.