Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7c8d0e1
FEAT: Start caching hyperblock with lookup keys
mariusmihaic Nov 21, 2025
30728a4
FEAT: Use templates to store cachable blocks
mariusmihaic Nov 21, 2025
6d65d6b
FEAT: Add part of time cacher
mariusmihaic Nov 24, 2025
93f69bf
FEAT: Test block processor cache
mariusmihaic Nov 24, 2025
498274b
CLN: Refactor solution
mariusmihaic Nov 24, 2025
c201a97
FEAT: Extend test to look for keys + lookup keys
mariusmihaic Nov 24, 2025
77c182a
FEAT: Inject timed cache in block processor
mariusmihaic Nov 24, 2025
9ec89c4
FEAT: Config cache duration + test get cached hyper block
mariusmihaic Nov 24, 2025
e82423b
FEAT: Test cached block by hash/nonce
mariusmihaic Nov 24, 2025
cf1a7a8
CLN: After self review
mariusmihaic Nov 25, 2025
9fc0379
FIX: After review pt 1
mariusmihaic Dec 2, 2025
acb543e
FIX: After review pt 2
mariusmihaic Dec 3, 2025
40e3866
Merge pull request #509 from multiversx/MX-17334-cache-block-response
AdoAdoAdo Dec 8, 2025
2c3e733
FEAT: Go core with get proxy block with exec res
mariusmihaic Dec 12, 2025
60e9629
Merge pull request #510 from multiversx/test-proxy-get-block-with-exe…
miiu96 Dec 22, 2025
2176395
process status fixes
miiu96 Dec 22, 2025
9391344
Merge pull request #511 from multiversx/fix-process-status
miiu96 Dec 22, 2025
2b5a701
enable epochs v2
sstanculeanu Jan 21, 2026
e54757a
Merge pull request #513 from multiversx/enable-epochs-v2
sstanculeanu Jan 21, 2026
94f5826
fix description
miiu96 Feb 13, 2026
8338035
fix
miiu96 Feb 13, 2026
5ae9db4
Merge pull request #515 from multiversx/fix-api-description
miiu96 Feb 13, 2026
6a6ed53
enable-rounds proxy route
axenteoctavian Feb 17, 2026
98c50f4
Merge pull request #516 from multiversx/enable-rounds-api-route
axenteoctavian Feb 17, 2026
5b40dc8
update docker image
miiu96 May 5, 2026
7590cc3
update go version
miiu96 May 7, 2026
69cf259
update
miiu96 May 7, 2026
ba75600
Merge pull request #517 from multiversx/update-docker-image
miiu96 Jul 14, 2026
d436f81
extra checks body size
miiu96 Jul 24, 2026
008ef2a
changed to 10 mb
miiu96 Aug 19, 2026
8851857
fixes after review
miiu96 Aug 19, 2026
990acd4
Merge pull request #518 from multiversx/extra-check-body-size
miiu96 Aug 19, 2026
6dacdd9
updates
miiu96 Aug 19, 2026
1d2aeee
Merge pull request #519 from multiversx/tags-for-modules
miiu96 Aug 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/deploy-docker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:

steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Extract metadata (tags, labels) for Docker
id: meta
Expand Down Expand Up @@ -45,5 +45,7 @@ jobs:
file: ./docker/Dockerfile
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
provenance: mode=max
sbom: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
8 changes: 4 additions & 4 deletions .github/workflows/pr-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ jobs:
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.23.6
uses: actions/setup-go@v5
- name: Set up Go 1.x
uses: actions/setup-go@v6
with:
go-version: 1.23.6
go-version: 1.26.2
id: go

- name: Check out code into the Go module directory
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Get dependencies
run: |
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/pr-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ jobs:
name: Unit
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.23.6
uses: actions/setup-go@v5
- name: Set up Go 1.x
uses: actions/setup-go@v6
with:
go-version: 1.23.6
go-version: 1.26.2
id: go

- name: Check out code
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Get dependencies
run: |
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ For more details, go [here](https://docs.multiversx.com/sdk-and-tools/proxy/).
- `/v1.0/network/direct-staked-info` (GET) --> returns the list of direct staked values
- `/v1.0/network/delegated-info` (GET) --> returns the list of delegated values
- `/v1.0/network/enable-epochs` (GET) --> returns the activation epochs metric
- `/v1.0/network/enable-epochs-v2` (GET) --> returns the newer version of activation epochs
- `/v1.0/network/enable-rounds` (GET) --> returns the activation rounds metric
### node

- `/v1.0/node/heartbeatstatus` (GET) --> returns the heartbeat data from an observer from any shard. Has a cache to avoid many requests
Expand Down
41 changes: 41 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package api

import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"io"
"net/http"
"reflect"
"time"
Expand Down Expand Up @@ -39,8 +42,12 @@ func CreateServer(
rateLimitTimeWindowInSeconds int,
isProfileModeActivated bool,
shouldStartSwaggerUI bool,
maxRequestBodySize int64,
) (*http.Server, error) {
ws := gin.Default()
if maxRequestBodySize > 0 {
ws.Use(maxRequestBodySizeMiddleware(maxRequestBodySize))
}
ws.Use(cors.Default())

err := registerValidators()
Expand All @@ -61,6 +68,40 @@ func CreateServer(
return httpServer, nil
}

func maxRequestBodySizeMiddleware(maxSize int64) gin.HandlerFunc {
return func(c *gin.Context) {
if c.Request.Body == nil {
c.Next()
return
}

bodyBytes, err := io.ReadAll(http.MaxBytesReader(c.Writer, c.Request.Body, maxSize))
if err != nil {
if maxBytesErr, ok := errors.AsType[*http.MaxBytesError](err); ok {
printMessage := fmt.Sprintf("request body exceeded the size limit of %d bytes", maxBytesErr.Limit)
log.Warn(printMessage, "path", c.Request.RequestURI)
c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, data.GenericAPIResponse{
Data: nil,
Error: printMessage,
Code: data.ReturnCodeRequestError,
})
return
}

log.Warn("error reading request body", "error", err, "path", c.Request.RequestURI)
c.AbortWithStatusJSON(http.StatusBadRequest, data.GenericAPIResponse{
Data: nil,
Error: err.Error(),
Code: data.ReturnCodeRequestError,
})
return
}

c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
c.Next()
}
}

func registerValidators() error {
validators := []validatorInput{
{Name: "skValidator", Validator: skValidator},
Expand Down
118 changes: 118 additions & 0 deletions api/api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package api

import (
"bytes"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"

"github.com/gin-gonic/gin"
"github.com/multiversx/mx-chain-proxy-go/api/middleware"
"github.com/stretchr/testify/require"
)

func TestMaxRequestBodySizeMiddleware_OversizedBodyShouldErr(t *testing.T) {
t.Parallel()

ws := gin.New()
ws.Use(maxRequestBodySizeMiddleware(1 << 20))
ws.POST("/test", func(c *gin.Context) {
_, err := io.ReadAll(c.Request.Body)
if err != nil {
c.Status(http.StatusRequestEntityTooLarge)
return
}
c.Status(http.StatusOK)
})

hugeBody := make([]byte, 2*1024*1024)
for i := range hugeBody {
hugeBody[i] = 'a'
}

req := httptest.NewRequest(http.MethodPost, "/test", bytes.NewReader(hugeBody))
resp := httptest.NewRecorder()
ws.ServeHTTP(resp, req)

require.Equal(t, http.StatusRequestEntityTooLarge, resp.Code)
}

func TestMaxRequestBodySizeMiddleware_NormalBodyShouldPass(t *testing.T) {
t.Parallel()

ws := gin.New()
ws.Use(maxRequestBodySizeMiddleware(1 << 20))
ws.POST("/test", func(c *gin.Context) {
body, err := io.ReadAll(c.Request.Body)
if err != nil {
c.Status(http.StatusInternalServerError)
return
}
if string(body) != `{"key":"value"}` {
c.Status(http.StatusBadRequest)
return
}
c.Status(http.StatusOK)
})

body := []byte(`{"key":"value"}`)
req := httptest.NewRequest(http.MethodPost, "/test", bytes.NewReader(body))
resp := httptest.NewRecorder()
ws.ServeHTTP(resp, req)

require.Equal(t, http.StatusOK, resp.Code)
}

func TestMaxRequestBodySizeMiddleware_LoggingDisabled_ValidJSONWithExcessWhitespaceShouldBeRejected(t *testing.T) {
t.Parallel()

validJSON := `{"valid":"request"}`
limit := int64(len(validJSON))

ws := gin.New()
ws.Use(maxRequestBodySizeMiddleware(limit))
ws.POST("/test", func(c *gin.Context) {
var payload map[string]string
if err := c.ShouldBindJSON(&payload); err != nil {
c.Status(http.StatusBadRequest)
return
}
c.Status(http.StatusOK)
})

body := validJSON + strings.Repeat(" ", 10)
req := httptest.NewRequest(http.MethodPost, "/test", strings.NewReader(body))
resp := httptest.NewRecorder()
ws.ServeHTTP(resp, req)

require.Equal(t, http.StatusRequestEntityTooLarge, resp.Code)
}

func TestMaxRequestBodySizeMiddleware_LoggingEnabled_ValidJSONWithExcessWhitespaceShouldBeRejected(t *testing.T) {
t.Parallel()

validJSON := `{"valid":"request"}`
limit := int64(len(validJSON))

ws := gin.New()
ws.Use(maxRequestBodySizeMiddleware(limit))
ws.Use(middleware.NewResponseLoggerMiddleware(10 * time.Second).MiddlewareHandlerFunc())
ws.POST("/test", func(c *gin.Context) {
var payload map[string]string
if err := c.ShouldBindJSON(&payload); err != nil {
c.Status(http.StatusBadRequest)
return
}
c.Status(http.StatusOK)
})

body := validJSON + strings.Repeat(" ", 10)
req := httptest.NewRequest(http.MethodPost, "/test", strings.NewReader(body))
resp := httptest.NewRecorder()
ws.ServeHTTP(resp, req)

require.Equal(t, http.StatusRequestEntityTooLarge, resp.Code)
}
22 changes: 22 additions & 0 deletions api/groups/baseNetworkGroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ func NewNetworkGroup(facadeHandler data.FacadeHandler) (*networkGroup, error) {
{Path: "/esdt/non-fungible-tokens", Handler: ng.getEsdtHandlerFunc(data.NonFungibleTokens), Method: http.MethodGet},
{Path: "/esdt/supply/:token", Handler: ng.getESDTSupply, Method: http.MethodGet},
{Path: "/enable-epochs", Handler: ng.getEnableEpochs, Method: http.MethodGet},
{Path: "/enable-epochs-v2", Handler: ng.getEnableEpochsV2, Method: http.MethodGet},
{Path: "/enable-rounds", Handler: ng.getEnableRounds, Method: http.MethodGet},
{Path: "/direct-staked-info", Handler: ng.getDirectStakedInfo, Method: http.MethodGet},
{Path: "/delegated-info", Handler: ng.getDelegatedInfo, Method: http.MethodGet},
{Path: "/ratings", Handler: ng.getRatingsConfig, Method: http.MethodGet},
Expand Down Expand Up @@ -144,6 +146,26 @@ func (group *networkGroup) getEnableEpochs(c *gin.Context) {
c.JSON(http.StatusOK, enableEpochsMetrics)
}

func (group *networkGroup) getEnableEpochsV2(c *gin.Context) {
enableEpochsMetrics, err := group.facade.GetEnableEpochsMetricsV2()
if err != nil {
shared.RespondWith(c, http.StatusInternalServerError, nil, err.Error(), data.ReturnCodeInternalError)
return
}

c.JSON(http.StatusOK, enableEpochsMetrics)
}

func (group *networkGroup) getEnableRounds(c *gin.Context) {
enableRoundsMetrics, err := group.facade.GetEnableRoundsMetrics()
if err != nil {
shared.RespondWith(c, http.StatusInternalServerError, nil, err.Error(), data.ReturnCodeInternalError)
return
}

c.JSON(http.StatusOK, enableRoundsMetrics)
}

func (group *networkGroup) getESDTSupply(c *gin.Context) {
tokenIdentifier := c.Param("token")
if tokenIdentifier == "" {
Expand Down
Loading
Loading