├── .github
└── workflows
│ ├── codeql-analysis.yml
│ ├── deploy-docker.yaml
│ ├── pr-build.yml
│ └── pr-tests.yml
├── .gitignore
├── Makefile
├── README.md
├── api
├── api.go
├── apiHandler.go
├── errors.go
├── errors
│ └── errors.go
├── groups
│ ├── baseAboutGroup.go
│ ├── baseAboutGroup_test.go
│ ├── baseAccountsGroup.go
│ ├── baseAccountsGroup_test.go
│ ├── baseActionsGroup.go
│ ├── baseActionsGroup_test.go
│ ├── baseBlockGroup.go
│ ├── baseBlockGroup_test.go
│ ├── baseBlocksGroup.go
│ ├── baseBlocksGroup_test.go
│ ├── baseGroup.go
│ ├── baseGroup_test.go
│ ├── baseHyperBlockGroup.go
│ ├── baseHyperBlockGroup_test.go
│ ├── baseInternalGroup.go
│ ├── baseInternalGroup_test.go
│ ├── baseNetworkGroup.go
│ ├── baseNetworkGroup_test.go
│ ├── baseNodeGroup.go
│ ├── baseNodeGroup_test.go
│ ├── baseProofGroup.go
│ ├── baseProofGroup_test.go
│ ├── baseStatusGroup.go
│ ├── baseStatusGroup_test.go
│ ├── baseTransactionGroup.go
│ ├── baseTransactionGroup_test.go
│ ├── baseValidatorGroup.go
│ ├── baseValidatorGroup_test.go
│ ├── baseVmValuesGroup.go
│ ├── baseVmValuesGroup_test.go
│ ├── common_test.go
│ ├── errors.go
│ ├── interface.go
│ ├── urlParams.go
│ ├── urlParams_test.go
│ └── v_next
│ │ ├── accountsGroupV_next.go
│ │ └── interface.go
├── middleware
│ ├── errors.go
│ ├── interface.go
│ ├── metricsMiddleware.go
│ ├── metricsMiddleware_test.go
│ ├── rateLimiter.go
│ ├── rateLimiter_test.go
│ ├── responseLogger.go
│ └── responseLogger_test.go
├── mock
│ ├── facadeStub.go
│ └── statusMetricsExtractor.go
└── shared
│ └── shared.go
├── assets
└── overview.png
├── cmd
└── proxy
│ ├── config
│ ├── apiConfig
│ │ ├── credentials.toml
│ │ ├── v1_0.toml
│ │ └── v_next.toml
│ ├── config.toml
│ └── swagger
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── index.css
│ │ ├── index.html
│ │ ├── mvx-icon.png
│ │ ├── oauth2-redirect.html
│ │ ├── openapi.json
│ │ ├── swagger-initializer.js
│ │ ├── swagger-ui-bundle.js
│ │ ├── swagger-ui-es-bundle-core.js
│ │ ├── swagger-ui-es-bundle.js
│ │ ├── swagger-ui-standalone-preset.js
│ │ ├── swagger-ui.css
│ │ └── swagger-ui.js
│ └── main.go
├── common
├── constants.go
├── interface.go
├── options.go
└── options_test.go
├── config
└── config.go
├── data
├── aboutInfo.go
├── account.go
├── api.go
├── auctionList.go
├── block.go
├── blockInfo.go
├── blocks.go
├── closableComponentsHolder.go
├── constants.go
├── database.go
├── duration.go
├── errors.go
├── esdt.go
├── esdt_test.go
├── metrics.go
├── mock
│ └── pubKeyConverterMock.go
├── nodeStatus.go
├── observer.go
├── proof.go
├── transaction.go
├── transaction_test.go
└── vmValues.go
├── docker
└── Dockerfile
├── facade
├── baseFacade.go
├── baseFacade_test.go
├── errors.go
├── interface.go
├── mock
│ ├── aboutInfoProcessorStub.go
│ ├── accountProccessorStub.go
│ ├── actionsProcessorStub.go
│ ├── blockProcessorStub.go
│ ├── blocksProcessorStub.go
│ ├── esdtSuppliesProcessorStub.go
│ ├── faucetProcessorStub.go
│ ├── nodeGroupProcessorStub.go
│ ├── nodeStatusProcessorStub.go
│ ├── proofProcessorStub.go
│ ├── scQueryServiceStub.go
│ ├── statusProcessorStub.go
│ ├── transactionProcessorStub.go
│ └── validatorStatisticsProcessorStub.go
└── versions
│ ├── proxyFacadeV1_0.go
│ └── proxyFacadeV_next.go
├── faucet
├── errors.go
├── mock
│ ├── addressContainerMock.go
│ ├── pubKeyConverterMock.go
│ └── shardCoordinatorMock.go
├── privateKeysLoader.go
└── privateKeysLoader_test.go
├── go.mod
├── go.sum
├── metrics
├── statusMetrics.go
└── statusMetrics_test.go
├── observer
├── availabilityCommon
│ ├── availabilityProvider.go
│ └── availabilityProvider_test.go
├── baseNodeProvider.go
├── baseNodeProvider_test.go
├── circularQueueNodesProvider.go
├── circularQueueNodesProvider_test.go
├── disabledNodesProvider.go
├── errors.go
├── holder
│ ├── nodesHolder.go
│ └── nodesHolder_test.go
├── interface.go
├── mapCounters
│ ├── mapCounter.go
│ ├── mapCounter_test.go
│ ├── mapCountersHolder.go
│ └── mapCountersHolder_test.go
├── nodesProviderFactory.go
├── nodesProviderFactory_test.go
├── simpleNodesProvider.go
├── simpleNodesProvider_test.go
└── testdata
│ └── config.toml
├── process
├── aboutInfoProcessor.go
├── aboutInfoProcessor_test.go
├── accountProcessor.go
├── accountProcessor_test.go
├── baseProcessor.go
├── baseProcessor_test.go
├── blockProcessor.go
├── blockProcessor_test.go
├── blocksProcessor.go
├── blocksProcessor_test.go
├── cache
│ ├── errors.go
│ ├── export_test.go
│ ├── genericApiResponseMemCacher.go
│ ├── genericApiResponseMemCacher_test.go
│ ├── heartbeatMemCacher.go
│ ├── heartbeatMemCacher_test.go
│ ├── validatorStatsMemCacher.go
│ └── validatorStatsMemCacher_test.go
├── database
│ ├── common.go
│ ├── errors.go
│ └── queries.go
├── disabled
│ └── epochStartNotifier.go
├── economicMetrics.go
├── economicMetrics_test.go
├── errors.go
├── esdtSupplyProcessor.go
├── esdtSupplyProcessor_test.go
├── export_test.go
├── factory
│ ├── disabledFaucetProcessor.go
│ ├── faucetProcessorFactory.go
│ ├── interface.go
│ └── transactionProcessorFactory.go
├── faucetProcessor.go
├── faucetProcessor_test.go
├── hyperblockBuilder.go
├── hyperblockBuilder_test.go
├── interface.go
├── logsevents
│ ├── errors.go
│ ├── logsMerger.go
│ └── logsMerger_test.go
├── mock
│ ├── addressContainerMock.go
│ ├── genericApiResponseCacherMock.go
│ ├── heartbeatCacherMock.go
│ ├── httpClientMock.go
│ ├── keygenMock.go
│ ├── loggerStub.go
│ ├── observersProviderStub.go
│ ├── privKeysLoaderStub.go
│ ├── processorStub.go
│ ├── pubKeyConverterMock.go
│ ├── scQueryServiceStub.go
│ ├── shardCoordinatorMock.go
│ ├── singleSignerStub.go
│ ├── statusMetricsProviderStub.go
│ ├── transactionCostHandlerStub.go
│ └── valStatsCacherMock.go
├── nodeGroupProcessor.go
├── nodeGroupProcessor_test.go
├── nodeStatusProcessor.go
├── nodeStatusProcessor_test.go
├── numShardsProcessor.go
├── numShardsProcessor_test.go
├── proofProcessor.go
├── proofProcessor_test.go
├── scQueryProcessor.go
├── scQueryProcessor_test.go
├── statusProcessor.go
├── statusProcessor_test.go
├── testdata
│ ├── executingSCCall.json
│ ├── finishedFailedComplexScenario1.json
│ ├── finishedFailedComplexScenario2.json
│ ├── finishedFailedComplexScenario3.json
│ ├── finishedFailedRelayedTxIntraShard.json
│ ├── finishedFailedRelayedTxMoveBalanceReturnMessage.json
│ ├── finishedFailedRelayedTxUnexecutable.json
│ ├── finishedFailedRelayedTxWithSCCall.json
│ ├── finishedFailedSCCall.json
│ ├── finishedFailedSCDeployWithTransfer.json
│ ├── finishedFailedSCR.json
│ ├── finishedInvalidBuiltinFunction.json
│ ├── finishedOKMoveBalance.json
│ ├── finishedOKRelayedTxCrossShard.json
│ ├── finishedOKRelayedTxIntraShard.json
│ ├── finishedOKRelayedTxWithSCCall.json
│ ├── finishedOKRelayedV2TxIntraShard.json
│ ├── finishedOKRewardTx.json
│ ├── finishedOKSCCall.json
│ ├── finishedOKSCDeploy.json
│ ├── finishedOKSCDeployWithTransfer.json
│ ├── finishedOkRelayedTxCrossShardMoveBalance.json
│ ├── malformedRelayedTxIntraShard.json
│ ├── malformedRelayedV2TxIntraShard.json
│ ├── pendingNewMoveBalance.json
│ ├── pendingNewSCCall.json
│ ├── relayedV3MoveBalanceOk.json
│ └── transactionWithScrs.json
├── transactionProcessor.go
├── transactionProcessor_test.go
├── txcost
│ ├── converters.go
│ ├── converters_test.go
│ ├── errors.go
│ ├── gasUsed.go
│ ├── gasUsed_test.go
│ ├── transactionCostProcessor.go
│ └── transactionCostProcessor_test.go
├── v1_0
│ └── doc.go
├── v_next
│ ├── accountProcessorV_next.go
│ └── doc.go
├── validatorAuctionProcessor.go
├── validatorAuctionProcessor_test.go
├── validatorStatisticsProcessor.go
└── validatorStatisticsProcessor_test.go
├── rosetta
└── README.md
├── testing
└── testHttpServer.go
└── versions
├── errors.go
├── factory
├── apiConfigParser.go
├── apiConfigParser_test.go
├── errors.go
├── interface.go
├── testdata
│ └── vx_x.toml
└── versionedFacadeCreator.go
└── versionsRegistry.go
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | pull_request:
5 | branches: [feat/*, rc/*]
6 | types: [opened, ready_for_review]
7 | push:
8 | workflow_dispatch:
9 |
10 | permissions:
11 | security-events: write
12 |
13 | jobs:
14 | analyze:
15 | name: Analyze
16 | runs-on: ubuntu-latest
17 |
18 | strategy:
19 | fail-fast: false
20 | matrix:
21 | language: ['go']
22 | # Learn more...
23 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
24 |
25 | steps:
26 | - name: Checkout repository
27 | uses: actions/checkout@v2
28 | with:
29 | # We must fetch at least the immediate parents so that if this is
30 | # a pull request then we can checkout the head.
31 | fetch-depth: 2
32 |
33 |
34 | # Initializes the CodeQL tools for scanning.
35 | - name: Initialize CodeQL
36 | uses: github/codeql-action/init@v1
37 | with:
38 | languages: ${{ matrix.language }}
39 |
40 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
41 | # If this step fails, then you should remove it and run the build manually (see below)
42 | - name: Autobuild
43 | uses: github/codeql-action/autobuild@v1
44 |
45 | # ℹ️ Command-line programs to run using the OS shell.
46 | # 📚 https://git.io/JvXDl
47 |
48 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
49 | # and modify them (or add more) to build your code if your project
50 | # uses a compiled language
51 |
52 | #- run: |
53 | # make bootstrap
54 | # make release
55 |
56 | - name: Perform CodeQL Analysis
57 | uses: github/codeql-action/analyze@v1
58 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-docker.yaml:
--------------------------------------------------------------------------------
1 | env:
2 | IMAGE_NODE: chain-proxy
3 | REGISTRY_HOSTNAME: multiversx
4 |
5 | name: Build Docker image & push
6 |
7 | on:
8 | release:
9 | types: [published]
10 | pull_request:
11 | workflow_dispatch:
12 |
13 | jobs:
14 | build-docker-image:
15 | runs-on: ubuntu-22.04
16 |
17 | steps:
18 | - name: Check out code into the Go module directory
19 | uses: actions/checkout@v4
20 |
21 | - name: Extract metadata (tags, labels) for Docker
22 | id: meta
23 | uses: docker/metadata-action@v5
24 | with:
25 | images: ${{ env.REGISTRY_HOSTNAME }}/${{ env.IMAGE_NODE }}
26 |
27 | - name: Set up QEMU for ARM64
28 | uses: docker/setup-qemu-action@v3
29 |
30 | - name: Set up Docker Buildx
31 | uses: docker/setup-buildx-action@v3
32 |
33 | - name: Log into Docker Hub
34 | if: github.event_name != 'pull_request'
35 | uses: docker/login-action@v3
36 | with:
37 | username: ${{ secrets.DOCKERHUB_USERNAME }}
38 | password: ${{ secrets.DOCKERHUB_TOKEN }}
39 |
40 | - name: Build and push image to Docker Hub
41 | id: push
42 | uses: docker/build-push-action@v6
43 | with:
44 | context: .
45 | file: ./docker/Dockerfile
46 | platforms: linux/amd64,linux/arm64
47 | push: ${{ github.event_name != 'pull_request' }}
48 | tags: ${{ steps.meta.outputs.tags }}
49 | labels: ${{ steps.meta.outputs.labels }}
50 |
--------------------------------------------------------------------------------
/.github/workflows/pr-build.yml:
--------------------------------------------------------------------------------
1 | name: Go
2 |
3 | on:
4 | pull_request:
5 | branches: [feat/*, rc/*]
6 | types: [opened, ready_for_review]
7 | push:
8 | workflow_dispatch:
9 |
10 | jobs:
11 | build:
12 | name: Build
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Set up Go 1.20.7
16 | uses: actions/setup-go@v2
17 | with:
18 | go-version: 1.20.7
19 | id: go
20 |
21 | - name: Check out code into the Go module directory
22 | uses: actions/checkout@v3
23 |
24 | - name: Get dependencies
25 | run: |
26 | go get -v -t -d ./...
27 | if [ -f Gopkg.toml ]; then
28 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
29 | dep ensure
30 | fi
31 | - name: Build
32 | run: make build
33 |
--------------------------------------------------------------------------------
/.github/workflows/pr-tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches: [ master, rc/*, feat/* ]
6 | pull_request:
7 | branches: [ master, rc/*, feat/* ]
8 |
9 | jobs:
10 | test:
11 | name: Unit
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Set up Go 1.20.7
15 | uses: actions/setup-go@v2
16 | with:
17 | go-version: 1.20.7
18 | id: go
19 |
20 | - name: Check out code
21 | uses: actions/checkout@v3
22 |
23 | - name: Get dependencies
24 | run: |
25 | go get -v -t -d ./...
26 | - name: Unit tests
27 | run: make test
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, build with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | .idea/
15 | vendor/
16 | cmd/proxy/proxy
17 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | SHELL := $(shell which bash)
2 |
3 | .DEFAULT_GOAL := help
4 |
5 | .PHONY: clean-test test build run
6 |
7 | help:
8 | @echo -e ""
9 | @echo -e "Make commands:"
10 | @grep -E '^[a-zA-Z_-]+:.*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":"}; {printf "\t\033[36m%-30s\033[0m\n", $$1}'
11 | @echo -e ""
12 |
13 | # #########################
14 | # Base commands
15 | # #########################
16 |
17 | cmd_dir = cmd/proxy
18 | binary = proxy
19 |
20 | clean-test:
21 | go clean -testcache
22 |
23 | test: clean-test
24 | go test ./...
25 |
26 | build:
27 | cd ${cmd_dir} && \
28 | go build -v \
29 | -o ${binary} \
30 | -ldflags="-X main.appVersion=$(shell git describe --tags --long --dirty) -X main.commitID=$(shell git rev-parse HEAD)"
31 |
32 | run: build
33 | cd ${cmd_dir} && \
34 | ./${binary} --log-level="*:DEBUG"
35 |
--------------------------------------------------------------------------------
/api/errors.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import "errors"
4 |
5 | // ErrNilGroupHandler signals that a nil group handler has been provided
6 | var ErrNilGroupHandler = errors.New("nil group handler")
7 |
8 | // ErrGroupAlreadyRegistered signals that the provided group has already been registered
9 | var ErrGroupAlreadyRegistered = errors.New("group already registered")
10 |
11 | // ErrGroupDoesNotExist signals that the called group does not exist
12 | var ErrGroupDoesNotExist = errors.New("group does not exist")
13 |
14 | // ErrNilFacade signals that a nil facade has been provided
15 | var ErrNilFacade = errors.New("nil facade")
16 |
--------------------------------------------------------------------------------
/api/groups/baseAboutGroup.go:
--------------------------------------------------------------------------------
1 | package groups
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gin-gonic/gin"
7 | "github.com/multiversx/mx-chain-proxy-go/api/shared"
8 | "github.com/multiversx/mx-chain-proxy-go/data"
9 | )
10 |
11 | type aboutGroup struct {
12 | facade AboutFacadeHandler
13 | *baseGroup
14 | }
15 |
16 | // NewAboutGroup returns a new instance of aboutGroup
17 | func NewAboutGroup(facadeHandler data.FacadeHandler) (*aboutGroup, error) {
18 | facade, ok := facadeHandler.(AboutFacadeHandler)
19 | if !ok {
20 | return nil, ErrWrongTypeAssertion
21 | }
22 | ag := &aboutGroup{
23 | facade: facade,
24 | baseGroup: &baseGroup{},
25 | }
26 |
27 | baseRoutesHandlers := []*data.EndpointHandlerData{
28 | {Path: "", Handler: ag.getAboutInfo, Method: http.MethodGet},
29 | {Path: "/nodes-versions", Handler: ag.getNodesVersions, Method: http.MethodGet},
30 | }
31 | ag.baseGroup.endpoints = baseRoutesHandlers
32 |
33 | return ag, nil
34 | }
35 |
36 | func (ag *aboutGroup) getAboutInfo(c *gin.Context) {
37 | aboutInfo, err := ag.facade.GetAboutInfo()
38 | if err != nil {
39 | shared.RespondWith(c, http.StatusInternalServerError, nil, err.Error(), data.ReturnCodeInternalError)
40 | return
41 | }
42 |
43 | c.JSON(http.StatusOK, aboutInfo)
44 | }
45 |
46 | func (ag *aboutGroup) getNodesVersions(c *gin.Context) {
47 | nodesVersions, err := ag.facade.GetNodesVersions()
48 | if err != nil {
49 | shared.RespondWith(c, http.StatusInternalServerError, nil, err.Error(), data.ReturnCodeInternalError)
50 | return
51 | }
52 |
53 | c.JSON(http.StatusOK, nodesVersions)
54 | }
55 |
--------------------------------------------------------------------------------
/api/groups/baseActionsGroup.go:
--------------------------------------------------------------------------------
1 | package groups
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gin-gonic/gin"
7 | "github.com/multiversx/mx-chain-proxy-go/api/shared"
8 | "github.com/multiversx/mx-chain-proxy-go/data"
9 | )
10 |
11 | type actionsGroup struct {
12 | facade ActionsFacadeHandler
13 | *baseGroup
14 | }
15 |
16 | // NewActionsGroup returns a new instance of actionGroup
17 | func NewActionsGroup(facadeHandler data.FacadeHandler) (*actionsGroup, error) {
18 | facade, ok := facadeHandler.(ActionsFacadeHandler)
19 | if !ok {
20 | return nil, ErrWrongTypeAssertion
21 | }
22 |
23 | ng := &actionsGroup{
24 | facade: facade,
25 | baseGroup: &baseGroup{},
26 | }
27 |
28 | baseRoutesHandlers := []*data.EndpointHandlerData{
29 | {Path: "/reload-observers", Handler: ng.updateObservers, Method: http.MethodPost},
30 | {Path: "/reload-full-history-observers", Handler: ng.updateFullHistoryObservers, Method: http.MethodPost},
31 | }
32 | ng.baseGroup.endpoints = baseRoutesHandlers
33 |
34 | return ng, nil
35 | }
36 |
37 | func (group *actionsGroup) updateObservers(c *gin.Context) {
38 | result := group.facade.ReloadObservers()
39 | group.handleUpdateResponding(result, c)
40 | }
41 |
42 | func (group *actionsGroup) updateFullHistoryObservers(c *gin.Context) {
43 | result := group.facade.ReloadFullHistoryObservers()
44 | group.handleUpdateResponding(result, c)
45 | }
46 |
47 | func (group *actionsGroup) handleUpdateResponding(result data.NodesReloadResponse, c *gin.Context) {
48 | if result.Error != "" {
49 | httpCode := http.StatusInternalServerError
50 | internalCode := data.ReturnCodeInternalError
51 | if !result.OkRequest {
52 | httpCode = http.StatusBadRequest
53 | internalCode = data.ReturnCodeRequestError
54 | }
55 |
56 | shared.RespondWith(c, httpCode, result.Description, result.Error, internalCode)
57 | return
58 | }
59 |
60 | shared.RespondWith(c, http.StatusOK, result.Description, "", data.ReturnCodeSuccess)
61 | }
62 |
--------------------------------------------------------------------------------
/api/groups/baseBlocksGroup.go:
--------------------------------------------------------------------------------
1 | package groups
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gin-gonic/gin"
7 | apiErrors "github.com/multiversx/mx-chain-proxy-go/api/errors"
8 | "github.com/multiversx/mx-chain-proxy-go/api/shared"
9 | "github.com/multiversx/mx-chain-proxy-go/data"
10 | )
11 |
12 | type blocksGroup struct {
13 | facade BlocksFacadeHandler
14 | *baseGroup
15 | }
16 |
17 | func NewBlocksGroup(facadeHandler data.FacadeHandler) (*blocksGroup, error) {
18 | facade, ok := facadeHandler.(BlocksFacadeHandler)
19 | if !ok {
20 | return nil, ErrWrongTypeAssertion
21 | }
22 |
23 | bbg := &blocksGroup{
24 | facade: facade,
25 | baseGroup: &baseGroup{},
26 | }
27 | baseRoutesHandlers := []*data.EndpointHandlerData{
28 | {Path: "/by-round/:round", Handler: bbg.byRoundHandler, Method: http.MethodGet},
29 | }
30 | bbg.baseGroup.endpoints = baseRoutesHandlers
31 |
32 | return bbg, nil
33 | }
34 |
35 | func (bbp *blocksGroup) byRoundHandler(c *gin.Context) {
36 | round, err := shared.FetchRoundFromRequest(c)
37 | if err != nil {
38 | shared.RespondWithBadRequest(c, apiErrors.ErrCannotParseRound.Error())
39 | return
40 | }
41 |
42 | options, err := parseBlockQueryOptions(c)
43 | if err != nil {
44 | shared.RespondWithValidationError(c, apiErrors.ErrBadUrlParams, err)
45 | return
46 | }
47 |
48 | blockByRoundResponse, err := bbp.facade.GetBlocksByRound(round, options)
49 | if err != nil {
50 | shared.RespondWith(c, http.StatusInternalServerError, nil, err.Error(), data.ReturnCodeInternalError)
51 | return
52 | }
53 |
54 | c.JSON(http.StatusOK, blockByRoundResponse)
55 | }
56 |
--------------------------------------------------------------------------------
/api/groups/baseGroup_test.go:
--------------------------------------------------------------------------------
1 | package groups
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/gin-gonic/gin"
7 | "github.com/multiversx/mx-chain-proxy-go/data"
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestCrudOperationsBaseGroup(t *testing.T) {
12 | t.Parallel()
13 |
14 | ginHandler := func(c *gin.Context) {}
15 | hd0 := &data.EndpointHandlerData{
16 | Path: "path0",
17 | Handler: ginHandler,
18 | Method: "GET",
19 | }
20 | hd1 := &data.EndpointHandlerData{
21 | Path: "path1",
22 | Handler: ginHandler,
23 | Method: "GET",
24 | }
25 | hd2 := &data.EndpointHandlerData{
26 | Path: "path2",
27 | Handler: ginHandler,
28 | Method: "GET",
29 | }
30 | hd3 := &data.EndpointHandlerData{
31 | Path: "path3",
32 | Handler: ginHandler,
33 | Method: "GET",
34 | }
35 | hd4 := &data.EndpointHandlerData{
36 | Path: "path4",
37 | Handler: ginHandler,
38 | Method: "GET",
39 | }
40 |
41 | bg := &baseGroup{
42 | endpoints: []*data.EndpointHandlerData{hd0, hd1, hd2},
43 | }
44 |
45 | // ensure the order is kept
46 | assert.Equal(t, hd0.Path, bg.endpoints[0].Path)
47 | assert.Equal(t, hd1.Path, bg.endpoints[1].Path)
48 | assert.Equal(t, hd2.Path, bg.endpoints[2].Path)
49 |
50 | err := bg.UpdateEndpoint(hd0.Path, *hd3)
51 | assert.NoError(t, err)
52 | assert.Equal(t, 3, len(bg.endpoints))
53 |
54 | // ensure the order
55 | assert.Equal(t, hd3.Path, bg.endpoints[0].Path)
56 | assert.Equal(t, hd1.Path, bg.endpoints[1].Path)
57 | assert.Equal(t, hd2.Path, bg.endpoints[2].Path)
58 |
59 | err = bg.AddEndpoint(hd4.Path, *hd4)
60 | assert.NoError(t, err)
61 | assert.Equal(t, 4, len(bg.endpoints))
62 |
63 | // ensure the order
64 | assert.Equal(t, hd3.Path, bg.endpoints[0].Path)
65 | assert.Equal(t, hd1.Path, bg.endpoints[1].Path)
66 | assert.Equal(t, hd2.Path, bg.endpoints[2].Path)
67 | assert.Equal(t, hd4.Path, bg.endpoints[3].Path)
68 |
69 | err = bg.RemoveEndpoint(hd2.Path)
70 | assert.NoError(t, err)
71 | assert.Equal(t, 3, len(bg.endpoints))
72 |
73 | // ensure the order
74 | assert.Equal(t, hd3.Path, bg.endpoints[0].Path)
75 | assert.Equal(t, hd1.Path, bg.endpoints[1].Path)
76 | assert.Equal(t, hd4.Path, bg.endpoints[2].Path)
77 | }
78 |
--------------------------------------------------------------------------------
/api/groups/baseHyperBlockGroup.go:
--------------------------------------------------------------------------------
1 | package groups
2 |
3 | import (
4 | "encoding/hex"
5 | "net/http"
6 |
7 | "github.com/gin-gonic/gin"
8 | apiErrors "github.com/multiversx/mx-chain-proxy-go/api/errors"
9 | "github.com/multiversx/mx-chain-proxy-go/api/shared"
10 | "github.com/multiversx/mx-chain-proxy-go/data"
11 | )
12 |
13 | type hyperBlockGroup struct {
14 | facade HyperBlockFacadeHandler
15 | *baseGroup
16 | }
17 |
18 | // NewHyperBlockGroup returns a new instance of hyperBlockGroup
19 | func NewHyperBlockGroup(facadeHandler data.FacadeHandler) (*hyperBlockGroup, error) {
20 | facade, ok := facadeHandler.(HyperBlockFacadeHandler)
21 | if !ok {
22 | return nil, ErrWrongTypeAssertion
23 | }
24 |
25 | hbg := &hyperBlockGroup{
26 | facade: facade,
27 | baseGroup: &baseGroup{},
28 | }
29 |
30 | baseRoutesHandlers := []*data.EndpointHandlerData{
31 | {Path: "/by-hash/:hash", Handler: hbg.hyperBlockByHashHandler, Method: http.MethodGet},
32 | {Path: "/by-nonce/:nonce", Handler: hbg.hyperBlockByNonceHandler, Method: http.MethodGet},
33 | }
34 | hbg.baseGroup.endpoints = baseRoutesHandlers
35 |
36 | return hbg, nil
37 | }
38 |
39 | // hyperBlockByHashHandler handles "by-hash" requests
40 | func (group *hyperBlockGroup) hyperBlockByHashHandler(c *gin.Context) {
41 | hash := c.Param("hash")
42 | _, err := hex.DecodeString(hash)
43 | if err != nil {
44 | shared.RespondWithBadRequest(c, apiErrors.ErrInvalidBlockHashParam.Error())
45 | return
46 | }
47 |
48 | options, err := parseHyperblockQueryOptions(c)
49 | if err != nil {
50 | shared.RespondWithValidationError(c, apiErrors.ErrBadUrlParams, err)
51 | return
52 | }
53 |
54 | blockByHashResponse, err := group.facade.GetHyperBlockByHash(hash, options)
55 | if err != nil {
56 | shared.RespondWith(c, http.StatusInternalServerError, nil, err.Error(), data.ReturnCodeInternalError)
57 | return
58 | }
59 |
60 | c.JSON(http.StatusOK, blockByHashResponse)
61 | }
62 |
63 | // hyperBlockByNonceHandler handles "by-nonce" requests
64 | func (group *hyperBlockGroup) hyperBlockByNonceHandler(c *gin.Context) {
65 | nonce, err := shared.FetchNonceFromRequest(c)
66 | if err != nil {
67 | shared.RespondWithBadRequest(c, apiErrors.ErrCannotParseNonce.Error())
68 | return
69 | }
70 |
71 | options, err := parseHyperblockQueryOptions(c)
72 | if err != nil {
73 | shared.RespondWithValidationError(c, apiErrors.ErrBadUrlParams, err)
74 | return
75 | }
76 |
77 | blockByNonceResponse, err := group.facade.GetHyperBlockByNonce(nonce, options)
78 | if err != nil {
79 | shared.RespondWith(c, http.StatusInternalServerError, nil, err.Error(), data.ReturnCodeInternalError)
80 | return
81 | }
82 |
83 | c.JSON(http.StatusOK, blockByNonceResponse)
84 | }
85 |
--------------------------------------------------------------------------------
/api/groups/baseNodeGroup.go:
--------------------------------------------------------------------------------
1 | package groups
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gin-gonic/gin"
7 | apiErrors "github.com/multiversx/mx-chain-proxy-go/api/errors"
8 | "github.com/multiversx/mx-chain-proxy-go/api/shared"
9 | "github.com/multiversx/mx-chain-proxy-go/data"
10 | )
11 |
12 | type nodeGroup struct {
13 | facade NodeFacadeHandler
14 | *baseGroup
15 | }
16 |
17 | // NewNodeGroup returns a new instance of nodeGroup
18 | func NewNodeGroup(facadeHandler data.FacadeHandler) (*nodeGroup, error) {
19 | facade, ok := facadeHandler.(NodeFacadeHandler)
20 | if !ok {
21 | return nil, ErrWrongTypeAssertion
22 | }
23 |
24 | ng := &nodeGroup{
25 | facade: facade,
26 | baseGroup: &baseGroup{},
27 | }
28 |
29 | baseRoutesHandlers := []*data.EndpointHandlerData{
30 | {Path: "/heartbeatstatus", Handler: ng.getHeartbeatData, Method: http.MethodGet},
31 | {Path: "/old-storage-token/:token/nonce/:nonce", Handler: ng.isOldStorageForToken, Method: http.MethodGet},
32 | {Path: "/waiting-epochs-left/:key", Handler: ng.waitingEpochsLeft, Method: http.MethodGet},
33 | }
34 | ng.baseGroup.endpoints = baseRoutesHandlers
35 |
36 | return ng, nil
37 | }
38 |
39 | // getHeartbeatData will expose heartbeat status from an observer (if any available) in json format
40 | func (group *nodeGroup) getHeartbeatData(c *gin.Context) {
41 | heartbeatResults, err := group.facade.GetHeartbeatData()
42 | if err != nil {
43 | shared.RespondWith(c, http.StatusInternalServerError, nil, err.Error(), data.ReturnCodeInternalError)
44 | return
45 | }
46 |
47 | shared.RespondWith(c, http.StatusOK, gin.H{"heartbeats": heartbeatResults.Heartbeats}, "", data.ReturnCodeSuccess)
48 | }
49 |
50 | func (group *nodeGroup) isOldStorageForToken(c *gin.Context) {
51 | // TODO: when the old storage tokens liquidity issue is solved on the protocol, mark this endpoint as deprecated
52 | // and remove the processing code
53 | token := c.Param("token")
54 | nonce, err := shared.FetchNonceFromRequest(c)
55 | if err != nil {
56 | shared.RespondWith(
57 | c,
58 | http.StatusBadRequest,
59 | nil,
60 | apiErrors.ErrCannotParseNonce.Error(),
61 | data.ReturnCodeRequestError,
62 | )
63 | return
64 | }
65 | isOldStorage, err := group.facade.IsOldStorageForToken(token, nonce)
66 | if err != nil {
67 | shared.RespondWith(c, http.StatusInternalServerError, nil, err.Error(), data.ReturnCodeInternalError)
68 | return
69 | }
70 |
71 | shared.RespondWith(c, http.StatusOK, gin.H{"isOldStorage": isOldStorage}, "", data.ReturnCodeSuccess)
72 | }
73 |
74 | func (group *nodeGroup) waitingEpochsLeft(c *gin.Context) {
75 | publicKey := c.Param("key")
76 | response, err := group.facade.GetWaitingEpochsLeftForPublicKey(publicKey)
77 | if err != nil {
78 | shared.RespondWith(c, http.StatusInternalServerError, nil, err.Error(), data.ReturnCodeInternalError)
79 | return
80 | }
81 |
82 | shared.RespondWith(c, http.StatusOK, response.Data, "", data.ReturnCodeSuccess)
83 | }
84 |
--------------------------------------------------------------------------------
/api/groups/baseStatusGroup.go:
--------------------------------------------------------------------------------
1 | package groups
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gin-gonic/gin"
7 | "github.com/multiversx/mx-chain-proxy-go/api/shared"
8 | "github.com/multiversx/mx-chain-proxy-go/data"
9 | )
10 |
11 | type statusGroup struct {
12 | facade StatusFacadeHandler
13 | *baseGroup
14 | }
15 |
16 | // NewStatusGroup returns a new instance of statusGroup
17 | func NewStatusGroup(facadeHandler data.FacadeHandler) (*statusGroup, error) {
18 | facade, ok := facadeHandler.(StatusFacadeHandler)
19 | if !ok {
20 | return nil, ErrWrongTypeAssertion
21 | }
22 |
23 | ng := &statusGroup{
24 | facade: facade,
25 | baseGroup: &baseGroup{},
26 | }
27 |
28 | baseRoutesHandlers := []*data.EndpointHandlerData{
29 | {Path: "/metrics", Handler: ng.getMetrics, Method: http.MethodGet},
30 | {Path: "/prometheus-metrics", Handler: ng.getPrometheusMetrics, Method: http.MethodGet},
31 | }
32 | ng.baseGroup.endpoints = baseRoutesHandlers
33 |
34 | return ng, nil
35 | }
36 |
37 | // getMetrics will expose endpoints statistics in json format
38 | func (group *statusGroup) getMetrics(c *gin.Context) {
39 | metricsResults := group.facade.GetMetrics()
40 |
41 | shared.RespondWith(c, http.StatusOK, gin.H{"metrics": metricsResults}, "", data.ReturnCodeSuccess)
42 | }
43 |
44 | // getPrometheusMetrics will expose proxy metrics in prometheus format
45 | func (group *statusGroup) getPrometheusMetrics(c *gin.Context) {
46 | metricsResults := group.facade.GetMetricsForPrometheus()
47 |
48 | c.String(http.StatusOK, metricsResults)
49 | }
50 |
--------------------------------------------------------------------------------
/api/groups/baseStatusGroup_test.go:
--------------------------------------------------------------------------------
1 | package groups_test
2 |
3 | import (
4 | "io/ioutil"
5 | "net/http"
6 | "net/http/httptest"
7 | "testing"
8 |
9 | "github.com/multiversx/mx-chain-proxy-go/api/groups"
10 | "github.com/multiversx/mx-chain-proxy-go/api/mock"
11 | "github.com/multiversx/mx-chain-proxy-go/data"
12 | "github.com/stretchr/testify/require"
13 | )
14 |
15 | type statusMetricsResponse struct {
16 | Data struct {
17 | Metrics map[string]*data.EndpointMetrics `json:"metrics"`
18 | }
19 | Error string `json:"error"`
20 | Code string `json:"code"`
21 | }
22 |
23 | const statusPath = "/status"
24 |
25 | func TestNewStatusGroup_WrongFacadeShouldErr(t *testing.T) {
26 | t.Parallel()
27 |
28 | wrongFacade := &mock.WrongFacade{}
29 | group, err := groups.NewStatusGroup(wrongFacade)
30 | require.Nil(t, group)
31 | require.Equal(t, groups.ErrWrongTypeAssertion, err)
32 | }
33 |
34 | func TestGetMetrics_ShouldWork(t *testing.T) {
35 | t.Parallel()
36 |
37 | expectedMetrics := map[string]*data.EndpointMetrics{
38 | "/network/config": {
39 | NumRequests: 5,
40 | NumErrors: 3,
41 | TotalResponseTime: 100,
42 | LowestResponseTime: 20,
43 | HighestResponseTime: 50,
44 | },
45 | }
46 | facade := &mock.FacadeStub{
47 | GetMetricsCalled: func() map[string]*data.EndpointMetrics {
48 | return expectedMetrics
49 | },
50 | }
51 |
52 | statusGroup, err := groups.NewStatusGroup(facade)
53 | require.NoError(t, err)
54 | ws := startProxyServer(statusGroup, statusPath)
55 |
56 | req, _ := http.NewRequest("GET", "/status/metrics", nil)
57 | resp := httptest.NewRecorder()
58 | ws.ServeHTTP(resp, req)
59 |
60 | var apiResp statusMetricsResponse
61 | loadResponse(resp.Body, &apiResp)
62 | require.Equal(t, http.StatusOK, resp.Code)
63 |
64 | require.Equal(t, expectedMetrics, apiResp.Data.Metrics)
65 | }
66 |
67 | func TestGetPrometheusMetrics_ShouldWork(t *testing.T) {
68 | t.Parallel()
69 |
70 | expectedMetrics := `num_requests{endpoint="/network/config"} 37`
71 | facade := &mock.FacadeStub{
72 | GetPrometheusMetricsCalled: func() string {
73 | return expectedMetrics
74 | },
75 | }
76 |
77 | statusGroup, err := groups.NewStatusGroup(facade)
78 | require.NoError(t, err)
79 | ws := startProxyServer(statusGroup, statusPath)
80 |
81 | req, _ := http.NewRequest("GET", "/status/prometheus-metrics", nil)
82 | resp := httptest.NewRecorder()
83 | ws.ServeHTTP(resp, req)
84 |
85 | bodyBytes, err := ioutil.ReadAll(resp.Body)
86 | require.NoError(t, err)
87 |
88 | require.Equal(t, http.StatusOK, resp.Code)
89 | require.Equal(t, expectedMetrics, string(bodyBytes))
90 | }
91 |
--------------------------------------------------------------------------------
/api/groups/baseValidatorGroup.go:
--------------------------------------------------------------------------------
1 | package groups
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gin-gonic/gin"
7 | "github.com/multiversx/mx-chain-proxy-go/api/shared"
8 | "github.com/multiversx/mx-chain-proxy-go/data"
9 | )
10 |
11 | type validatorGroup struct {
12 | facade ValidatorFacadeHandler
13 | *baseGroup
14 | }
15 |
16 | // NewValidatorGroup returns a new instance of validatorGroup
17 | func NewValidatorGroup(facadeHandler data.FacadeHandler) (*validatorGroup, error) {
18 | facade, ok := facadeHandler.(ValidatorFacadeHandler)
19 | if !ok {
20 | return nil, ErrWrongTypeAssertion
21 | }
22 |
23 | vg := &validatorGroup{
24 | facade: facade,
25 | baseGroup: &baseGroup{},
26 | }
27 |
28 | baseRoutesHandlers := []*data.EndpointHandlerData{
29 | {Path: "/statistics", Handler: vg.statistics, Method: http.MethodGet},
30 | {Path: "/auction", Handler: vg.auctionList, Method: http.MethodGet},
31 | }
32 | vg.baseGroup.endpoints = baseRoutesHandlers
33 |
34 | return vg, nil
35 | }
36 |
37 | // statistics returns the validator statistics
38 | func (group *validatorGroup) statistics(c *gin.Context) {
39 | validatorStatistics, err := group.facade.ValidatorStatistics()
40 | if err != nil {
41 | shared.RespondWith(c, http.StatusBadRequest, nil, err.Error(), data.ReturnCodeRequestError)
42 | return
43 | }
44 |
45 | shared.RespondWith(c, http.StatusOK, gin.H{"statistics": validatorStatistics}, "", data.ReturnCodeSuccess)
46 | }
47 |
48 | func (group *validatorGroup) auctionList(c *gin.Context) {
49 | auctionList, err := group.facade.AuctionList()
50 | if err != nil {
51 | shared.RespondWith(c, http.StatusBadRequest, nil, err.Error(), data.ReturnCodeRequestError)
52 | return
53 | }
54 |
55 | shared.RespondWith(c, http.StatusOK, gin.H{"auctionList": auctionList}, "", data.ReturnCodeSuccess)
56 | }
57 |
--------------------------------------------------------------------------------
/api/groups/common_test.go:
--------------------------------------------------------------------------------
1 | package groups_test
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io"
7 |
8 | "github.com/gin-contrib/cors"
9 | "github.com/gin-gonic/gin"
10 | "github.com/multiversx/mx-chain-proxy-go/data"
11 | )
12 |
13 | var emptyGinHandler = func(_ *gin.Context) {}
14 |
15 | func init() {
16 | gin.SetMode(gin.TestMode)
17 | }
18 |
19 | func startProxyServer(group data.GroupHandler, path string) *gin.Engine {
20 | ws := gin.New()
21 | ws.Use(cors.Default())
22 | routes := ws.Group(path)
23 | group.RegisterRoutes(routes, data.ApiRoutesConfig{}, emptyGinHandler, emptyGinHandler, emptyGinHandler)
24 | return ws
25 | }
26 |
27 | func loadResponse(rsp io.Reader, destination interface{}) {
28 | jsonParser := json.NewDecoder(rsp)
29 | err := jsonParser.Decode(destination)
30 | if err != nil {
31 | fmt.Println(err.Error())
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/api/groups/errors.go:
--------------------------------------------------------------------------------
1 | package groups
2 |
3 | import "errors"
4 |
5 | // ErrNilGinHandler signals that a nil gin handler has been provided
6 | var ErrNilGinHandler = errors.New("nil gin handler")
7 |
8 | // ErrEndpointAlreadyRegistered signals that the provided endpoint path already exists
9 | var ErrEndpointAlreadyRegistered = errors.New("endpoint already registered")
10 |
11 | // ErrHandlerDoesNotExist signals that the requested handler does not exist
12 | var ErrHandlerDoesNotExist = errors.New("handler does not exist")
13 |
14 | // ErrWrongTypeAssertion signals that a wrong type assertion issue was found during the execution
15 | var ErrWrongTypeAssertion = errors.New("wrong type assertion")
16 |
17 | // ErrForcedShardIDCannotBeProvided signals that the forced shard id cannot be provided for a different address other than the system account address
18 | var ErrForcedShardIDCannotBeProvided = errors.New("forced shard id parameter can only be provided for system accounts")
19 |
--------------------------------------------------------------------------------
/api/groups/v_next/accountsGroupV_next.go:
--------------------------------------------------------------------------------
1 | package v_next
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/gin-gonic/gin"
8 | "github.com/multiversx/mx-chain-core-go/core/check"
9 | "github.com/multiversx/mx-chain-proxy-go/api/errors"
10 | "github.com/multiversx/mx-chain-proxy-go/api/groups"
11 | "github.com/multiversx/mx-chain-proxy-go/api/shared"
12 | "github.com/multiversx/mx-chain-proxy-go/data"
13 | )
14 |
15 | type accountsGroupV_next struct {
16 | baseAccountsGroup data.GroupHandler
17 | facade AccountsFacadeHandlerV_next
18 | }
19 |
20 | // NewAccountsGroupV_next returns a new instance of accountsGroupV_next
21 | func NewAccountsGroupV_next(baseAccountsGroup data.GroupHandler, facadeHandler data.FacadeHandler) (*accountsGroupV_next, error) {
22 | if check.IfNil(baseAccountsGroup) {
23 | return nil, fmt.Errorf("nil base accounts group for v_next")
24 | }
25 |
26 | facade, ok := facadeHandler.(AccountsFacadeHandlerV_next)
27 | if !ok {
28 | return nil, groups.ErrWrongTypeAssertion
29 | }
30 |
31 | ag := &accountsGroupV_next{
32 | baseAccountsGroup: baseAccountsGroup,
33 | facade: facade,
34 | }
35 |
36 | err := ag.baseAccountsGroup.UpdateEndpoint("/:address/shard", data.EndpointHandlerData{
37 | Handler: ag.GetShardForAccountV_next,
38 | Method: http.MethodGet,
39 | })
40 | if err != nil {
41 | return nil, err
42 | }
43 |
44 | err = ag.baseAccountsGroup.RemoveEndpoint("/:address/nonce")
45 | if err != nil {
46 | return nil, err
47 | }
48 |
49 | err = ag.baseAccountsGroup.AddEndpoint("/:address/new-endpoint", data.EndpointHandlerData{
50 | Handler: ag.NewEndpoint,
51 | Method: http.MethodGet,
52 | })
53 | if err != nil {
54 | return nil, err
55 | }
56 |
57 | return ag, nil
58 | }
59 |
60 | // NewEndpoint is an example of a new endpoint added in the version v_next
61 | func (ag *accountsGroupV_next) NewEndpoint(c *gin.Context) {
62 | res := ag.facade.NextEndpointHandler()
63 | c.JSON(http.StatusOK, &data.GenericAPIResponse{
64 | Data: res,
65 | Error: "",
66 | Code: data.ReturnCodeSuccess,
67 | })
68 | }
69 |
70 | // GetShardForAccountV_next is an example of an updated endpoint in the version v_next
71 | func (ag *accountsGroupV_next) GetShardForAccountV_next(c *gin.Context) {
72 | addr := c.Param("address")
73 | if addr == "" {
74 | shared.RespondWith(
75 | c,
76 | http.StatusBadRequest,
77 | nil,
78 | fmt.Sprintf("%v: %v", errors.ErrComputeShardForAddress, errors.ErrEmptyAddress),
79 | data.ReturnCodeRequestError,
80 | )
81 | return
82 | }
83 |
84 | shardID, err := ag.facade.GetShardIDForAddressV_next(addr, 0)
85 | if err != nil {
86 | shared.RespondWith(
87 | c,
88 | http.StatusInternalServerError,
89 | nil,
90 | fmt.Sprintf("%s: %s", errors.ErrComputeShardForAddress.Error(), err.Error()),
91 | data.ReturnCodeInternalError,
92 | )
93 | return
94 | }
95 |
96 | shared.RespondWith(c, http.StatusOK, gin.H{"shardID": shardID}, "", data.ReturnCodeSuccess)
97 | }
98 |
99 | // Group returns the base accounts group
100 | func (ag *accountsGroupV_next) Group() data.GroupHandler {
101 | return ag.baseAccountsGroup
102 | }
103 |
--------------------------------------------------------------------------------
/api/groups/v_next/interface.go:
--------------------------------------------------------------------------------
1 | package v_next
2 |
3 | import "github.com/multiversx/mx-chain-proxy-go/data"
4 |
5 | // AccountsFacadeHandlerV_next interface defines methods that can be used from facade context variable
6 | type AccountsFacadeHandlerV_next interface {
7 | GetAccount(address string) (*data.AccountModel, error)
8 | GetTransactions(address string) ([]data.DatabaseTransaction, error)
9 | GetShardIDForAddressV_next(address string, additional int) (uint32, error)
10 | GetValueForKey(address string, key string) (string, error)
11 | NextEndpointHandler() string
12 | }
13 |
--------------------------------------------------------------------------------
/api/middleware/errors.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import "errors"
4 |
5 | // ErrNilLimitsMapForEndpoints signals that a nil limits map has been provided
6 | var ErrNilLimitsMapForEndpoints = errors.New("nil limits map")
7 |
8 | // ErrNilStatusMetricsExtractor signals that a nil status metrics extractor has been provided
9 | var ErrNilStatusMetricsExtractor = errors.New("nil status metrics extractor")
10 |
--------------------------------------------------------------------------------
/api/middleware/interface.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/gin-gonic/gin"
7 | )
8 |
9 | // RateLimiterHandler defines the actions that an implementation of rate limiter handler should do
10 | type RateLimiterHandler interface {
11 | MiddlewareProcessor
12 | ResetMap(version string)
13 | }
14 |
15 | // StatusMetricsExtractor defines what a status metrics extractor should do
16 | type StatusMetricsExtractor interface {
17 | AddRequestData(path string, withError bool, duration time.Duration)
18 | IsInterfaceNil() bool
19 | }
20 |
21 | // MiddlewareProcessor defines a processor used internally by the web server when processing requests
22 | type MiddlewareProcessor interface {
23 | MiddlewareHandlerFunc() gin.HandlerFunc
24 | IsInterfaceNil() bool
25 | }
26 |
--------------------------------------------------------------------------------
/api/middleware/metricsMiddleware.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "bytes"
5 | "net/http"
6 | "time"
7 |
8 | "github.com/gin-gonic/gin"
9 | "github.com/multiversx/mx-chain-core-go/core/check"
10 | )
11 |
12 | type metricsMiddleware struct {
13 | statusMetricsExtractor StatusMetricsExtractor
14 | }
15 |
16 | // NewMetricsMiddleware returns a new instance of metricsMiddleware
17 | func NewMetricsMiddleware(statusMetricsExtractor StatusMetricsExtractor) (*metricsMiddleware, error) {
18 | if check.IfNil(statusMetricsExtractor) {
19 | return nil, ErrNilStatusMetricsExtractor
20 | }
21 |
22 | mm := &metricsMiddleware{
23 | statusMetricsExtractor: statusMetricsExtractor,
24 | }
25 |
26 | return mm, nil
27 | }
28 |
29 | // MiddlewareHandlerFunc logs updated data in regards to endpoints' durations statistics
30 | func (mm *metricsMiddleware) MiddlewareHandlerFunc() gin.HandlerFunc {
31 | return func(c *gin.Context) {
32 | t := time.Now()
33 |
34 | bw := &bodyWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
35 | c.Writer = bw
36 |
37 | c.Next()
38 |
39 | duration := time.Since(t)
40 | status := c.Writer.Status()
41 |
42 | withError := status != http.StatusOK
43 |
44 | mm.statusMetricsExtractor.AddRequestData(c.FullPath(), withError, duration)
45 | }
46 | }
47 |
48 | // IsInterfaceNil returns true if there is no value under the interface
49 | func (mm *metricsMiddleware) IsInterfaceNil() bool {
50 | return mm == nil
51 | }
52 |
--------------------------------------------------------------------------------
/api/middleware/metricsMiddleware_test.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "testing"
7 | "time"
8 |
9 | "github.com/gin-contrib/cors"
10 | "github.com/gin-gonic/gin"
11 | "github.com/multiversx/mx-chain-proxy-go/api/groups"
12 | apiMock "github.com/multiversx/mx-chain-proxy-go/api/mock"
13 | "github.com/multiversx/mx-chain-proxy-go/common"
14 | "github.com/multiversx/mx-chain-proxy-go/data"
15 | "github.com/stretchr/testify/require"
16 | )
17 |
18 | var emptyGinHandler = func(_ *gin.Context) {}
19 |
20 | func startApiServerMetrics(handler groups.AccountsFacadeHandler, metricsMiddleware *metricsMiddleware) *gin.Engine {
21 | ws := gin.New()
22 | ws.Use(cors.Default())
23 | ws.Use(metricsMiddleware.MiddlewareHandlerFunc())
24 | accGr, _ := groups.NewAccountsGroup(handler)
25 |
26 | group := ws.Group("/address")
27 | accGr.RegisterRoutes(group, data.ApiRoutesConfig{}, emptyGinHandler, emptyGinHandler, emptyGinHandler)
28 | return ws
29 | }
30 |
31 | func TestNewMetricsMiddleware(t *testing.T) {
32 | t.Parallel()
33 |
34 | t.Run("nil status metrics exporter - should err", func(t *testing.T) {
35 | t.Parallel()
36 |
37 | mm, err := NewMetricsMiddleware(nil)
38 | require.Nil(t, mm)
39 | require.Equal(t, ErrNilStatusMetricsExtractor, err)
40 | })
41 |
42 | t.Run("should work", func(t *testing.T) {
43 | t.Parallel()
44 |
45 | mm, err := NewMetricsMiddleware(&apiMock.StatusMetricsExporterStub{})
46 | require.NoError(t, err)
47 | require.NotNil(t, mm)
48 | })
49 | }
50 |
51 | func TestMetricsMiddleware_MiddlewareHandlerFunc(t *testing.T) {
52 | t.Parallel()
53 |
54 | type receivedRequestData struct {
55 | path string
56 | withError bool
57 | duration time.Duration
58 | }
59 | receivedData := make([]*receivedRequestData, 0)
60 | mm, err := NewMetricsMiddleware(&apiMock.StatusMetricsExporterStub{
61 | AddRequestDataCalled: func(path string, withError bool, duration time.Duration) {
62 | receivedData = append(receivedData, &receivedRequestData{
63 | path: path,
64 | withError: withError,
65 | duration: duration,
66 | })
67 | },
68 | })
69 | require.NoError(t, err)
70 |
71 | facade := &apiMock.FacadeStub{
72 | GetAccountHandler: func(address string, _ common.AccountQueryOptions) (*data.AccountModel, error) {
73 | return &data.AccountModel{
74 | Account: data.Account{
75 | Address: address,
76 | Nonce: 1,
77 | Balance: "100",
78 | },
79 | }, nil
80 | },
81 | }
82 |
83 | ws := startApiServerMetrics(facade, mm)
84 |
85 | resp := httptest.NewRecorder()
86 | context, _ := gin.CreateTestContext(resp)
87 | req, _ := http.NewRequestWithContext(context, "GET", "/address/test", nil)
88 | ws.ServeHTTP(resp, req)
89 |
90 | require.Len(t, receivedData, 1)
91 | require.Equal(t, "/address/:address", receivedData[0].path)
92 | require.False(t, receivedData[0].withError)
93 | }
94 |
--------------------------------------------------------------------------------
/api/middleware/rateLimiter.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "sync"
7 | "time"
8 |
9 | "github.com/gin-gonic/gin"
10 | "github.com/multiversx/mx-chain-proxy-go/data"
11 | )
12 |
13 | // ReturnCodeRequestError defines a request which hasn't been executed successfully due to a bad request received
14 | const ReturnCodeRequestError string = "bad_request"
15 |
16 | type rateLimiter struct {
17 | requestsMap map[string]uint64
18 | mutRequestsMap sync.RWMutex
19 | limits map[string]uint64
20 | countDuration time.Duration
21 | }
22 |
23 | // NewRateLimiter returns a new instance of rateLimiter
24 | func NewRateLimiter(limits map[string]uint64, countDuration time.Duration) (*rateLimiter, error) {
25 | if limits == nil {
26 | return nil, ErrNilLimitsMapForEndpoints
27 | }
28 | return &rateLimiter{
29 | requestsMap: make(map[string]uint64),
30 | limits: limits,
31 | countDuration: countDuration,
32 | }, nil
33 | }
34 |
35 | // MiddlewareHandlerFunc returns the gin middleware for limiting the number of requests for a given endpoint
36 | func (rl *rateLimiter) MiddlewareHandlerFunc() gin.HandlerFunc {
37 | return func(c *gin.Context) {
38 | endpoint := c.FullPath()
39 |
40 | limitForEndpoint, isEndpointLimited := rl.limits[endpoint]
41 | if !isEndpointLimited {
42 | return
43 | }
44 |
45 | clientIP := c.ClientIP()
46 | key := fmt.Sprintf("%s_%s", endpoint, clientIP)
47 |
48 | numRequests := rl.addInRequestsMap(key)
49 | if numRequests >= limitForEndpoint {
50 | printMessage := fmt.Sprintf("your IP exceeded the limit of %d requests in %v for this endpoint", limitForEndpoint, rl.countDuration)
51 | c.AbortWithStatusJSON(http.StatusTooManyRequests, data.GenericAPIResponse{
52 | Data: nil,
53 | Error: printMessage,
54 | Code: data.ReturnCode(ReturnCodeRequestError),
55 | })
56 | }
57 | }
58 | }
59 |
60 | func (rl *rateLimiter) addInRequestsMap(key string) uint64 {
61 | rl.mutRequestsMap.Lock()
62 | defer rl.mutRequestsMap.Unlock()
63 |
64 | _, ok := rl.requestsMap[key]
65 | if !ok {
66 | rl.requestsMap[key] = 1
67 | return 1
68 | }
69 |
70 | rl.requestsMap[key]++
71 |
72 | return rl.requestsMap[key]
73 | }
74 |
75 | // ResetMap has to be called from outside at a given interval so the requests map will be cleaned and older restrictions
76 | // would be erased
77 | func (rl *rateLimiter) ResetMap(version string) {
78 | rl.mutRequestsMap.Lock()
79 | rl.requestsMap = make(map[string]uint64)
80 | rl.mutRequestsMap.Unlock()
81 |
82 | log.Info("rate limiter map has been reset", "version", version, "time", time.Now())
83 | }
84 |
85 | // IsInterfaceNil returns true if there is no value under the interface
86 | func (rl *rateLimiter) IsInterfaceNil() bool {
87 | return rl == nil
88 | }
89 |
--------------------------------------------------------------------------------
/api/mock/statusMetricsExtractor.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // StatusMetricsExporterStub -
8 | type StatusMetricsExporterStub struct {
9 | AddRequestDataCalled func(path string, withError bool, duration time.Duration)
10 | }
11 |
12 | // AddRequestData -
13 | func (s *StatusMetricsExporterStub) AddRequestData(path string, withError bool, duration time.Duration) {
14 | if s.AddRequestDataCalled != nil {
15 | s.AddRequestDataCalled(path, withError, duration)
16 | }
17 | }
18 |
19 | // IsInterfaceNil -
20 | func (s *StatusMetricsExporterStub) IsInterfaceNil() bool {
21 | return s == nil
22 | }
23 |
--------------------------------------------------------------------------------
/assets/overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multiversx/mx-chain-proxy-go/ac88c1e1a5aa5a07113fa3dd37637d638f2a319a/assets/overview.png
--------------------------------------------------------------------------------
/cmd/proxy/config/apiConfig/credentials.toml:
--------------------------------------------------------------------------------
1 | # Credentials holds the list of username-pair pairs that allow access to certain endpoints.
2 | # The password represents the hashed value. In this examples, the password are the hashed usernames.
3 | # Please change these example values as they are just placeholders.
4 | # Example credentials:
5 | # Credentials = [
6 | # { Username = "example", Password = "hashed password" },
7 | # { Username = "example2", Password = "hashed password" }
8 | # ]
9 |
10 | [Hasher]
11 | Type = "sha256"
12 |
--------------------------------------------------------------------------------
/cmd/proxy/config/swagger/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multiversx/mx-chain-proxy-go/ac88c1e1a5aa5a07113fa3dd37637d638f2a319a/cmd/proxy/config/swagger/favicon-16x16.png
--------------------------------------------------------------------------------
/cmd/proxy/config/swagger/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multiversx/mx-chain-proxy-go/ac88c1e1a5aa5a07113fa3dd37637d638f2a319a/cmd/proxy/config/swagger/favicon-32x32.png
--------------------------------------------------------------------------------
/cmd/proxy/config/swagger/index.css:
--------------------------------------------------------------------------------
1 | html {
2 | box-sizing: border-box;
3 | overflow: -moz-scrollbars-vertical;
4 | overflow-y: scroll;
5 | }
6 |
7 | *,
8 | *:before,
9 | *:after {
10 | box-sizing: inherit;
11 | }
12 |
13 | body {
14 | margin: 0;
15 | background: #fafafa;
16 | }
17 |
--------------------------------------------------------------------------------
/cmd/proxy/config/swagger/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | MultiversX Gateway
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/cmd/proxy/config/swagger/mvx-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multiversx/mx-chain-proxy-go/ac88c1e1a5aa5a07113fa3dd37637d638f2a319a/cmd/proxy/config/swagger/mvx-icon.png
--------------------------------------------------------------------------------
/cmd/proxy/config/swagger/oauth2-redirect.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Swagger UI: OAuth2 Redirect
5 |
6 |
7 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/cmd/proxy/config/swagger/swagger-initializer.js:
--------------------------------------------------------------------------------
1 | window.onload = function() {
2 | //
3 |
4 | // Custom plugin to hide the API definition URL
5 | const HideInfoUrlPartsPlugin = () => {
6 | return {
7 | wrapComponents: {
8 | InfoUrl: () => () => null
9 | }
10 | }
11 | }
12 |
13 | // the following lines will be replaced by docker/configurator, when it runs in a docker-container
14 | window.ui = SwaggerUIBundle({
15 | url: "openapi.json",
16 | dom_id: '#swagger-ui',
17 | deepLinking: true,
18 | presets: [
19 | SwaggerUIBundle.presets.apis,
20 | SwaggerUIStandalonePreset.slice(1)
21 | ],
22 | plugins: [
23 | SwaggerUIBundle.plugins.DownloadUrl,
24 | HideInfoUrlPartsPlugin
25 | ],
26 | layout: "StandaloneLayout"
27 | });
28 |
29 | //
30 | };
31 |
--------------------------------------------------------------------------------
/common/constants.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | // UnVersionedAppString defines the default string, when the binary was build without setting the app flag
4 | const UnVersionedAppString = "undefined"
5 |
6 | // UndefinedCommitString defines the default string, when the binary was build without setting the commit flag
7 | const UndefinedCommitString = "undefined"
8 |
9 | // OutputFormat represents the format type returned by api
10 | type OutputFormat uint8
11 |
12 | const (
13 | // Internal output format returns struct directly, will be serialized into JSON by gin
14 | Internal OutputFormat = 0
15 |
16 | // Proto output format returns the bytes of the proto object
17 | Proto OutputFormat = 1
18 | )
19 |
--------------------------------------------------------------------------------
/common/interface.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | // Coordinator defines what a shard state coordinator should hold
4 | type Coordinator interface {
5 | NumberOfShards() uint32
6 | ComputeId(address []byte) uint32
7 | SelfId() uint32
8 | SameShard(firstAddress, secondAddress []byte) bool
9 | CommunicationIdentifier(destShardID uint32) string
10 | IsInterfaceNil() bool
11 | }
12 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-proxy-go/data"
5 | )
6 |
7 | // GeneralSettingsConfig will hold the general settings for a node
8 | type GeneralSettingsConfig struct {
9 | ServerPort int
10 | RequestTimeoutSec int
11 | HeartbeatCacheValidityDurationSec int
12 | ValStatsCacheValidityDurationSec int
13 | EconomicsMetricsCacheValidityDurationSec int
14 | FaucetValue string
15 | RateLimitWindowDurationSeconds int
16 | BalancedObservers bool
17 | BalancedFullHistoryNodes bool
18 | AllowEntireTxPoolFetch bool
19 | NumShardsTimeoutInSec int
20 | TimeBetweenNodesRequestsInSec int
21 | }
22 |
23 | // Config will hold the whole config file's data
24 | type Config struct {
25 | GeneralSettings GeneralSettingsConfig
26 | AddressPubkeyConverter PubkeyConfig
27 | Marshalizer TypeConfig
28 | Hasher TypeConfig
29 | ApiLogging ApiLoggingConfig
30 | Observers []*data.NodeData
31 | FullHistoryNodes []*data.NodeData
32 | }
33 |
34 | // TypeConfig will map the string type configuration
35 | type TypeConfig struct {
36 | Type string
37 | }
38 |
39 | // PubkeyConfig will map the public key configuration
40 | type PubkeyConfig struct {
41 | Length int
42 | Type string
43 | SignatureLength int
44 | }
45 |
46 | // ApiLoggingConfig holds the configuration related to API requests logging
47 | type ApiLoggingConfig struct {
48 | LoggingEnabled bool
49 | ThresholdInMicroSeconds int
50 | }
51 |
52 | // CredentialsConfig holds the credential pairs
53 | type CredentialsConfig struct {
54 | Credentials []data.Credential
55 | Hasher TypeConfig
56 | }
57 |
--------------------------------------------------------------------------------
/data/aboutInfo.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | // AboutInfo defines the structure needed for exposing app info
4 | type AboutInfo struct {
5 | AppVersion string `json:"appVersion"`
6 | CommitID string `json:"commitID"`
7 | }
8 |
9 | // NodesVersionProxyResponseData maps the response data for the proxy's nodes version endpoint
10 | type NodesVersionProxyResponseData struct {
11 | Versions map[uint32][]string `json:"versions"`
12 | }
13 |
14 | // NodeVersionAPIResponse maps the format to be used when fetching the node version from API
15 | type NodeVersionAPIResponse struct {
16 | Data struct {
17 | Metrics struct {
18 | Version string `json:"erd_app_version"`
19 | } `json:"metrics"`
20 | } `json:"data"`
21 | Error string `json:"error"`
22 | Code string `json:"code"`
23 | }
24 |
--------------------------------------------------------------------------------
/data/account.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import "github.com/multiversx/mx-chain-core-go/data/validator"
4 |
5 | // AccountModel defines an account model (with associated information)
6 | type AccountModel struct {
7 | Account Account `json:"account"`
8 | BlockInfo BlockInfo `json:"blockInfo"`
9 | }
10 |
11 | // AccountsModel defines the model of the accounts response
12 | type AccountsModel struct {
13 | Accounts map[string]*Account `json:"accounts"`
14 | }
15 |
16 | // Account defines the data structure for an account
17 | type Account struct {
18 | Address string `json:"address"`
19 | Nonce uint64 `json:"nonce"`
20 | Balance string `json:"balance"`
21 | Username string `json:"username"`
22 | Code string `json:"code"`
23 | CodeHash []byte `json:"codeHash"`
24 | RootHash []byte `json:"rootHash"`
25 | CodeMetadata []byte `json:"codeMetadata"`
26 | DeveloperReward string `json:"developerReward"`
27 | OwnerAddress string `json:"ownerAddress"`
28 | Pairs map[string]string `json:"pairs,omitempty"`
29 | }
30 |
31 | // ValidatorApiResponse represents the data which is fetched from each validator for returning it in API call
32 | type ValidatorApiResponse = validator.ValidatorStatistics
33 |
34 | // ValidatorStatisticsResponse respects the format the validator statistics are received from the observers
35 | type ValidatorStatisticsResponse struct {
36 | Statistics map[string]*ValidatorApiResponse `json:"statistics"`
37 | }
38 |
39 | // ValidatorStatisticsApiResponse respects the format the validator statistics are received from the observers
40 | type ValidatorStatisticsApiResponse struct {
41 | Data ValidatorStatisticsResponse `json:"data"`
42 | Error string `json:"error"`
43 | Code string `json:"code"`
44 | }
45 |
46 | // AccountApiResponse defines a wrapped account that the node respond with
47 | type AccountApiResponse struct {
48 | Data AccountModel `json:"data"`
49 | Error string `json:"error"`
50 | Code string `json:"code"`
51 | }
52 |
53 | // AccountsApiResponse defines the response that will be returned by the node when requesting multiple accounts
54 | type AccountsApiResponse struct {
55 | Data AccountsModel `json:"data"`
56 | Error string `json:"error"`
57 | Code string `json:"code"`
58 | }
59 |
60 | // AccountKeyValueResponseData follows the format of the data field on an account key-value response
61 | type AccountKeyValueResponseData struct {
62 | Value string `json:"value"`
63 | }
64 |
65 | // AccountKeyValueResponse defines the response for a request for a value of a key for an account
66 | type AccountKeyValueResponse struct {
67 | Data AccountKeyValueResponseData `json:"data"`
68 | Error string `json:"error"`
69 | Code string `json:"code"`
70 | }
71 |
--------------------------------------------------------------------------------
/data/auctionList.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | // AuctionNode holds data needed for a node in auction to respond to API calls
4 | type AuctionNode struct {
5 | BlsKey string `json:"blsKey"`
6 | Qualified bool `json:"qualified"`
7 | }
8 |
9 | // AuctionListValidatorAPIResponse holds the data needed for an auction node validator for responding to API calls
10 | type AuctionListValidatorAPIResponse struct {
11 | Owner string `json:"owner"`
12 | NumStakedNodes int64 `json:"numStakedNodes"`
13 | TotalTopUp string `json:"totalTopUp"`
14 | TopUpPerNode string `json:"topUpPerNode"`
15 | QualifiedTopUp string `json:"qualifiedTopUp"`
16 | Nodes []*AuctionNode `json:"nodes"`
17 | }
18 |
19 | // AuctionListResponse respects the format the auction list api response received from the observers
20 | type AuctionListResponse struct {
21 | AuctionListValidators []*AuctionListValidatorAPIResponse `json:"auctionList"`
22 | }
23 |
24 | // AuctionListAPIResponse respects the format the auction list received from the observers
25 | type AuctionListAPIResponse struct {
26 | Data AuctionListResponse `json:"data"`
27 | Error string `json:"error"`
28 | Code string `json:"code"`
29 | }
30 |
--------------------------------------------------------------------------------
/data/block.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-core-go/data/alteredAccount"
5 | "github.com/multiversx/mx-chain-core-go/data/api"
6 | )
7 |
8 | // BlockApiResponse is a response holding a block
9 | type BlockApiResponse struct {
10 | Data BlockApiResponsePayload `json:"data"`
11 | Error string `json:"error"`
12 | Code ReturnCode `json:"code"`
13 | }
14 |
15 | // BlockApiResponsePayload wraps a block
16 | type BlockApiResponsePayload struct {
17 | Block api.Block `json:"block"`
18 | }
19 |
20 | // HyperblockApiResponse is a response holding a hyperblock
21 | type HyperblockApiResponse struct {
22 | Data HyperblockApiResponsePayload `json:"data"`
23 | Error string `json:"error"`
24 | Code ReturnCode `json:"code"`
25 | }
26 |
27 | // NewHyperblockApiResponse creates a HyperblockApiResponse
28 | func NewHyperblockApiResponse(hyperblock api.Hyperblock) *HyperblockApiResponse {
29 | return &HyperblockApiResponse{
30 | Data: HyperblockApiResponsePayload{
31 | Hyperblock: hyperblock,
32 | },
33 | Code: ReturnCodeSuccess,
34 | }
35 | }
36 |
37 | // HyperblockApiResponsePayload wraps a hyperblock
38 | type HyperblockApiResponsePayload struct {
39 | Hyperblock api.Hyperblock `json:"hyperblock"`
40 | }
41 |
42 | // InternalBlockApiResponse is a response holding an internal block
43 | type InternalBlockApiResponse struct {
44 | Data InternalBlockApiResponsePayload `json:"data"`
45 | Error string `json:"error"`
46 | Code ReturnCode `json:"code"`
47 | }
48 |
49 | // InternalBlockApiResponsePayload wraps a internal generic block
50 | type InternalBlockApiResponsePayload struct {
51 | Block interface{} `json:"block"`
52 | }
53 |
54 | // ValidatorsInfoApiResponse is a response holding validators info
55 | type ValidatorsInfoApiResponse struct {
56 | Data InternalStartOfEpochValidators `json:"data"`
57 | Error string `json:"error"`
58 | Code ReturnCode `json:"code"`
59 | }
60 |
61 | // InternalBlockApiResponsePayload wraps a internal generic validators info
62 | type InternalStartOfEpochValidators struct {
63 | ValidatorsInfo interface{} `json:"validators"`
64 | }
65 |
66 | // InternalMiniBlockApiResponse is a response holding an internal miniblock
67 | type InternalMiniBlockApiResponse struct {
68 | Data InternalMiniBlockApiResponsePayload `json:"data"`
69 | Error string `json:"error"`
70 | Code ReturnCode `json:"code"`
71 | }
72 |
73 | // InternalMiniBlockApiResponsePayload wraps an internal miniblock
74 | type InternalMiniBlockApiResponsePayload struct {
75 | MiniBlock interface{} `json:"miniblock"`
76 | }
77 |
78 | // AlteredAccountsApiResponse is a response holding a altered accounts
79 | type AlteredAccountsApiResponse struct {
80 | Data AlteredAccountsPayload `json:"data"`
81 | Error string `json:"error"`
82 | Code ReturnCode `json:"code"`
83 | }
84 |
85 | // AlteredAccountsPayload wraps altered accounts payload
86 | type AlteredAccountsPayload struct {
87 | Accounts []*alteredAccount.AlteredAccount `json:"accounts"`
88 | }
89 |
--------------------------------------------------------------------------------
/data/blockInfo.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | // BlockInfo defines the data structure for the block at which an resource (e.g. Account object) is fetched from the Network
4 | type BlockInfo struct {
5 | Nonce uint64 `json:"nonce"`
6 | Hash string `json:"hash"`
7 | RootHash string `json:"rootHash"`
8 | }
9 |
--------------------------------------------------------------------------------
/data/blocks.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import "github.com/multiversx/mx-chain-core-go/data/api"
4 |
5 | // BlocksApiResponse is a response holding(possibly) multiple block
6 | type BlocksApiResponse struct {
7 | Data BlocksApiResponsePayload `json:"data"`
8 | Error string `json:"error"`
9 | Code ReturnCode `json:"code"`
10 | }
11 |
12 | // BlocksApiResponsePayload wraps a block
13 | type BlocksApiResponsePayload struct {
14 | Blocks []*api.Block `json:"blocks"`
15 | }
16 |
--------------------------------------------------------------------------------
/data/closableComponentsHolder.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "sync"
5 |
6 | logger "github.com/multiversx/mx-chain-logger-go"
7 | )
8 |
9 | // closableComponent defines the behaviour of a component that is closable
10 | type closableComponent interface {
11 | Close() error
12 | }
13 |
14 | var log = logger.GetOrCreate("data")
15 |
16 | // ClosableComponentsHandler is a structure that holds a list of closable components and closes them when needed
17 | type ClosableComponentsHandler struct {
18 | components []closableComponent
19 | sync.Mutex
20 | }
21 |
22 | // NewClosableComponentsHandler will return a new instance of closableComponentsHandler
23 | func NewClosableComponentsHandler() *ClosableComponentsHandler {
24 | return &ClosableComponentsHandler{
25 | components: make([]closableComponent, 0),
26 | }
27 | }
28 |
29 | // Add will add one or more components to the internal closable components slice
30 | func (cch *ClosableComponentsHandler) Add(components ...closableComponent) {
31 | cch.Lock()
32 | cch.components = append(cch.components, components...)
33 | cch.Unlock()
34 | }
35 |
36 | // Close will handle the closing of all the components from the internal slice
37 | func (cch *ClosableComponentsHandler) Close() {
38 | cch.Lock()
39 | defer cch.Unlock()
40 |
41 | for _, component := range cch.components {
42 | log.LogIfError(component.Close())
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/data/constants.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import "github.com/multiversx/mx-chain-core-go/data/transaction"
4 |
5 | // TxStatusUnknown defines the response that should be received from an observer when transaction status is unknown
6 | const TxStatusUnknown transaction.TxStatus = "unknown"
7 |
--------------------------------------------------------------------------------
/data/database.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "math/big"
5 |
6 | "github.com/multiversx/mx-chain-es-indexer-go/data"
7 | )
8 |
9 | // DatabaseTransaction extends indexer.Transaction with the 'hash' field that is not ignored in json schema
10 | type DatabaseTransaction struct {
11 | Hash string `json:"hash"`
12 | Fee string `json:"fee"`
13 | data.Transaction
14 | }
15 |
16 | // CalculateFee calculates transaction fee using gasPrice and gasUsed
17 | func (dt *DatabaseTransaction) CalculateFee() string {
18 | gasPrice := big.NewInt(0).SetUint64(dt.GasPrice)
19 | gasUsed := big.NewInt(0).SetUint64(dt.GasUsed)
20 | fee := big.NewInt(0).Mul(gasPrice, gasUsed)
21 |
22 | return fee.String()
23 | }
24 |
--------------------------------------------------------------------------------
/data/duration.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "time"
7 | )
8 |
9 | // Duration is a wrapper of the original Duration struct
10 | // that has JSON marshal and unmarshal capabilities
11 | // golang issue: https://github.com/golang/go/issues/10275
12 | type Duration struct {
13 | time.Duration
14 | }
15 |
16 | // MarshalJSON is called when a json marshal is triggered on this field
17 | func (d Duration) MarshalJSON() ([]byte, error) {
18 | return json.Marshal(d.String())
19 | }
20 |
21 | // UnmarshalJSON is called when a json unmarshal is triggered on this field
22 | func (d *Duration) UnmarshalJSON(b []byte) error {
23 | var v interface{}
24 | if err := json.Unmarshal(b, &v); err != nil {
25 | return err
26 | }
27 | switch value := v.(type) {
28 | case float64:
29 | d.Duration = time.Duration(value)
30 | return nil
31 | case string:
32 | var err error
33 | d.Duration, err = time.ParseDuration(value)
34 | if err != nil {
35 | return err
36 | }
37 | return nil
38 | default:
39 | return errors.New("invalid duration")
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/data/errors.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import "errors"
4 |
5 | // ErrNilTransaction signals that a nil transaction has been provided
6 | var ErrNilTransaction = errors.New("nil transaction")
7 |
8 | // ErrNilPubKeyConverter signals that a nil pub key converter has been provided
9 | var ErrNilPubKeyConverter = errors.New("nil pub key converter")
10 |
--------------------------------------------------------------------------------
/data/esdt.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | const (
4 | FungibleTokens = "fungible-tokens"
5 | SemiFungibleTokens = "semi-fungible-tokens"
6 | NonFungibleTokens = "non-fungible-tokens"
7 | )
8 |
9 | // ValidTokenTypes holds a slice containing the valid esdt token types
10 | var ValidTokenTypes = []string{FungibleTokens, SemiFungibleTokens, NonFungibleTokens}
11 |
12 | // ESDTSupplyResponse is a response holding esdt supply
13 | type ESDTSupplyResponse struct {
14 | Data ESDTSupply `json:"data"`
15 | Error string `json:"error"`
16 | Code ReturnCode `json:"code"`
17 | }
18 |
19 | // ESDTSupply is a DTO holding esdt supply
20 | type ESDTSupply struct {
21 | Supply string `json:"supply"`
22 | Minted string `json:"minted"`
23 | Burned string `json:"burned"`
24 | InitialMinted string `json:"initialMinted"`
25 | RecomputedSupply bool `json:"recomputedSupply"`
26 | }
27 |
28 | // IsValidEsdtPath returns true if the provided path is a valid esdt token type
29 | func IsValidEsdtPath(path string) bool {
30 | for _, tokenType := range ValidTokenTypes {
31 | if tokenType == path {
32 | return true
33 | }
34 | }
35 |
36 | return false
37 | }
38 |
--------------------------------------------------------------------------------
/data/esdt_test.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func TestIsValidEsdtPath(t *testing.T) {
10 | testCases := []struct {
11 | input string
12 | output bool
13 | }{
14 | {
15 | input: FungibleTokens,
16 | output: true,
17 | },
18 | {
19 | input: SemiFungibleTokens,
20 | output: true,
21 | },
22 | {
23 | input: NonFungibleTokens,
24 | output: true,
25 | },
26 | {
27 | input: "invalid token type",
28 | output: false,
29 | },
30 | {
31 | input: "",
32 | output: false,
33 | },
34 | }
35 |
36 | for _, tc := range testCases {
37 | res := IsValidEsdtPath(tc.input)
38 | require.Equal(t, tc.output, res)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/data/metrics.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import "time"
4 |
5 | // EndpointMetrics holds statistics about the requests for a specific endpoint
6 | type EndpointMetrics struct {
7 | NumRequests uint64 `json:"num_requests"`
8 | NumErrors uint64 `json:"num_errors"`
9 | TotalResponseTime time.Duration `json:"total_response_time"`
10 | LowestResponseTime time.Duration `json:"lowest_response_time"`
11 | HighestResponseTime time.Duration `json:"highest_response_time"`
12 | }
13 |
--------------------------------------------------------------------------------
/data/mock/pubKeyConverterMock.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import (
4 | "encoding/hex"
5 |
6 | "github.com/multiversx/mx-chain-core-go/core"
7 | )
8 |
9 | // PubKeyConverterMock -
10 | type PubKeyConverterMock struct {
11 | len int
12 | }
13 |
14 | // Decode -
15 | func (pcm *PubKeyConverterMock) Decode(humanReadable string) ([]byte, error) {
16 | return hex.DecodeString(humanReadable)
17 | }
18 |
19 | // Encode -
20 | func (pcm *PubKeyConverterMock) Encode(pkBytes []byte) (string, error) {
21 | return hex.EncodeToString(pkBytes), nil
22 | }
23 |
24 | // EncodeSlice -
25 | func (pcm *PubKeyConverterMock) EncodeSlice(pkBytesSlice [][]byte) ([]string, error) {
26 | results := make([]string, 0)
27 | for _, pk := range pkBytesSlice {
28 | results = append(results, hex.EncodeToString(pk))
29 | }
30 |
31 | return results, nil
32 | }
33 |
34 | // SilentEncode -
35 | func (pcm *PubKeyConverterMock) SilentEncode(pkBytes []byte, _ core.Logger) string {
36 | return hex.EncodeToString(pkBytes)
37 | }
38 |
39 | // Len -
40 | func (pcm *PubKeyConverterMock) Len() int {
41 | return pcm.len
42 | }
43 |
44 | // IsInterfaceNil -
45 | func (pcm *PubKeyConverterMock) IsInterfaceNil() bool {
46 | return pcm == nil
47 | }
48 |
--------------------------------------------------------------------------------
/data/observer.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | // NodeData holds an observer data
4 | type NodeData struct {
5 | ShardId uint32
6 | Address string
7 | IsSynced bool
8 | IsFallback bool
9 | IsSnapshotless bool
10 | }
11 |
12 | // NodesReloadResponse is a DTO that holds details about nodes reloading
13 | type NodesReloadResponse struct {
14 | OkRequest bool
15 | Description string
16 | Error string
17 | }
18 |
19 | // NodeType is a type which identifies the type of a node (observer or full history)
20 | type NodeType string
21 |
22 | const (
23 | // Observer identifies a node which is a regular observer
24 | Observer NodeType = "observer"
25 |
26 | // FullHistoryNode identifier a node that has full history mode enabled
27 | FullHistoryNode NodeType = "full history"
28 | )
29 |
30 | // ObserverDataAvailabilityType represents the type to be used for the observers' data availability
31 | type ObserverDataAvailabilityType string
32 |
33 | const (
34 | // AvailabilityAll mean that the observer can be used for both real-time and historical requests
35 | AvailabilityAll ObserverDataAvailabilityType = "all"
36 |
37 | // AvailabilityRecent means that the observer can be used only for recent data
38 | AvailabilityRecent ObserverDataAvailabilityType = "recent"
39 | )
40 |
--------------------------------------------------------------------------------
/data/proof.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | // VerifyProofRequest represents the parameters needed to verify a Merkle proof
4 | type VerifyProofRequest struct {
5 | RootHash string `json:"roothash"`
6 | Address string `json:"address"`
7 | Proof []string `json:"proof"`
8 | }
9 |
--------------------------------------------------------------------------------
/data/transaction_test.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "encoding/hex"
5 | "testing"
6 |
7 | "github.com/multiversx/mx-chain-proxy-go/data/mock"
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func TestNewTransactionWrapper_NilTransactionShouldErr(t *testing.T) {
12 | t.Parallel()
13 |
14 | tw, err := NewTransactionWrapper(nil, &mock.PubKeyConverterMock{})
15 | require.Nil(t, tw)
16 | require.Equal(t, ErrNilTransaction, err)
17 | }
18 |
19 | func TestNewTransactionWrapper_NilPubKeyConverterShouldErr(t *testing.T) {
20 | t.Parallel()
21 |
22 | tx := Transaction{Nonce: 5}
23 | tw, err := NewTransactionWrapper(&tx, nil)
24 | require.Nil(t, tw)
25 | require.Equal(t, ErrNilPubKeyConverter, err)
26 | }
27 |
28 | func TestNewTransactionWrapper_ShouldWork(t *testing.T) {
29 | t.Parallel()
30 |
31 | tx := Transaction{Nonce: 5}
32 | tw, err := NewTransactionWrapper(&tx, &mock.PubKeyConverterMock{})
33 | require.NotNil(t, tw)
34 | require.NoError(t, err)
35 | }
36 |
37 | func TestTransactionWrapper_Getters(t *testing.T) {
38 | t.Parallel()
39 |
40 | data := "data"
41 | gasLimit := uint64(37)
42 | gasPrice := uint64(5)
43 | rcvr, _ := hex.DecodeString("receiver")
44 |
45 | tx := Transaction{
46 | Nonce: 0,
47 | Value: "",
48 | Receiver: hex.EncodeToString(rcvr),
49 | Sender: "",
50 | GasPrice: gasPrice,
51 | GasLimit: gasLimit,
52 | Data: []byte(data),
53 | Signature: "",
54 | }
55 | tw, _ := NewTransactionWrapper(&tx, &mock.PubKeyConverterMock{})
56 | require.NotNil(t, tw)
57 |
58 | require.Equal(t, []byte(data), tw.GetData())
59 | require.Equal(t, gasLimit, tw.GetGasLimit())
60 | require.Equal(t, gasPrice, tw.GetGasPrice())
61 | require.Equal(t, rcvr, tw.GetRcvAddr())
62 | }
63 |
--------------------------------------------------------------------------------
/data/vmValues.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-core-go/core"
5 | "github.com/multiversx/mx-chain-core-go/data/vm"
6 | )
7 |
8 | // VmValuesResponseData follows the format of the data field in an API response for a VM values query
9 | type VmValuesResponseData struct {
10 | Data *vm.VMOutputApi `json:"data"`
11 | BlockInfo BlockInfo `json:"blockInfo"`
12 | }
13 |
14 | // ResponseVmValue defines a wrapper over string containing returned data in hex format
15 | type ResponseVmValue struct {
16 | Data VmValuesResponseData `json:"data"`
17 | Error string `json:"error"`
18 | Code string `json:"code"`
19 | }
20 |
21 | // VmValueRequest defines the request struct for values available in a VM
22 | type VmValueRequest struct {
23 | Address string `json:"scAddress"`
24 | FuncName string `json:"funcName"`
25 | CallerAddr string `json:"caller"`
26 | CallValue string `json:"value"`
27 | SameScState bool `json:"sameScState"`
28 | ShouldBeSynced bool `json:"shouldBeSynced"`
29 | Args []string `json:"args"`
30 | }
31 |
32 | // SCQuery represents a prepared query for executing a function of the smart contract
33 | type SCQuery struct {
34 | ScAddress string
35 | FuncName string
36 | CallerAddr string
37 | CallValue string
38 | SameScState bool `json:"sameScState"`
39 | ShouldBeSynced bool `json:"shouldBeSynced"`
40 | Arguments [][]byte
41 | BlockNonce core.OptionalUint64
42 | BlockHash []byte
43 | }
44 |
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.20.7 AS builder
2 | LABEL maintainer="multiversx"
3 |
4 | WORKDIR /mx-chain-proxy-go
5 | COPY . .
6 |
7 | # Proxy
8 | WORKDIR /mx-chain-proxy-go/cmd/proxy
9 | RUN go build -ldflags="-X main.appVersion=$(git describe --tags --long --dirty) -X main.commitID=$(git rev-parse HEAD)"
10 |
11 | # ===== SECOND STAGE ======
12 | FROM ubuntu:22.04
13 | RUN apt-get update -y && apt-get upgrade -y
14 |
15 | COPY --from=builder /mx-chain-proxy-go/cmd/proxy /mx-chain-proxy-go/cmd/proxy
16 |
17 | WORKDIR /mx-chain-proxy-go/cmd/proxy/
18 | EXPOSE 8080
19 | ENTRYPOINT ["./proxy"]
20 |
--------------------------------------------------------------------------------
/facade/errors.go:
--------------------------------------------------------------------------------
1 | package facade
2 |
3 | import "github.com/pkg/errors"
4 |
5 | // ErrNilActionsProcessor signals that a nil actions processor has been provided
6 | var ErrNilActionsProcessor = errors.New("nil actions processor provided")
7 |
8 | // ErrNilAccountProcessor signals that a nil account processor has been provided
9 | var ErrNilAccountProcessor = errors.New("nil account processor provided")
10 |
11 | // ErrNilTransactionProcessor signals that a nil transaction processor has been provided
12 | var ErrNilTransactionProcessor = errors.New("nil transaction processor provided")
13 |
14 | // ErrNilSCQueryService signals that a nil smart contracts query service has been provided
15 | var ErrNilSCQueryService = errors.New("nil smart contracts query service provided")
16 |
17 | // ErrNilNodeGroupProcessor signals that a nil node group processor has been provided
18 | var ErrNilNodeGroupProcessor = errors.New("nil node group processor provided")
19 |
20 | // ErrNilValidatorStatisticsProcessor signals that a nil validator statistics processor has been provided
21 | var ErrNilValidatorStatisticsProcessor = errors.New("nil validator statistics processor provided")
22 |
23 | // ErrNilFaucetProcessor signals that a nil faucet processor has been provided
24 | var ErrNilFaucetProcessor = errors.New("nil faucet processor provided")
25 |
26 | // ErrNilNodeStatusProcessor signals that a nil node status processor has been provided
27 | var ErrNilNodeStatusProcessor = errors.New("nil node status processor provided")
28 |
29 | // ErrNilBlockProcessor signals that a nil block processor has been provided
30 | var ErrNilBlockProcessor = errors.New("nil block processor provided")
31 |
32 | // ErrNilBlocksProcessor signals that a nil blocks processor has been provided
33 | var ErrNilBlocksProcessor = errors.New("nil blocks processor provided")
34 |
35 | // ErrNilProofProcessor signals that a nil proof processor has been provided
36 | var ErrNilProofProcessor = errors.New("nil proof processor provided")
37 |
38 | // ErrNilESDTSuppliesProcessor signals that a nil esdt supplies processor has been provided
39 | var ErrNilESDTSuppliesProcessor = errors.New("nil esdt supplies processor")
40 |
41 | // ErrNilStatusProcessor signals that a nil status processor has been provided
42 | var ErrNilStatusProcessor = errors.New("nil status processor")
43 |
44 | // ErrNilAboutInfoProcessor signals that a nil about info processor has been provided
45 | var ErrNilAboutInfoProcessor = errors.New("nil about info processor")
46 |
--------------------------------------------------------------------------------
/facade/mock/aboutInfoProcessorStub.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import "github.com/multiversx/mx-chain-proxy-go/data"
4 |
5 | // AboutInfoProcessorStub -
6 | type AboutInfoProcessorStub struct {
7 | GetAboutInfoCalled func() *data.GenericAPIResponse
8 | GetNodesVersionsCalled func() (*data.GenericAPIResponse, error)
9 | }
10 |
11 | // GetAboutInfo -
12 | func (stub *AboutInfoProcessorStub) GetAboutInfo() *data.GenericAPIResponse {
13 | if stub.GetAboutInfoCalled != nil {
14 | return stub.GetAboutInfoCalled()
15 | }
16 |
17 | return nil
18 | }
19 |
20 | // GetNodesVersions -
21 | func (stub *AboutInfoProcessorStub) GetNodesVersions() (*data.GenericAPIResponse, error) {
22 | if stub.GetNodesVersionsCalled != nil {
23 | return stub.GetNodesVersionsCalled()
24 | }
25 |
26 | return nil, nil
27 | }
28 |
--------------------------------------------------------------------------------
/facade/mock/actionsProcessorStub.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-proxy-go/data"
5 | )
6 |
7 | // ActionsProcessorStub -
8 | type ActionsProcessorStub struct {
9 | ReloadObserversCalled func() data.NodesReloadResponse
10 | ReloadFullHistoryObserversCalled func() data.NodesReloadResponse
11 | }
12 |
13 | // ReloadObservers -
14 | func (a *ActionsProcessorStub) ReloadObservers() data.NodesReloadResponse {
15 | if a.ReloadObserversCalled != nil {
16 | return a.ReloadObserversCalled()
17 | }
18 |
19 | return data.NodesReloadResponse{}
20 | }
21 |
22 | // ReloadFullHistoryObservers -
23 | func (a *ActionsProcessorStub) ReloadFullHistoryObservers() data.NodesReloadResponse {
24 | if a.ReloadFullHistoryObserversCalled != nil {
25 | return a.ReloadFullHistoryObserversCalled()
26 | }
27 |
28 | return data.NodesReloadResponse{}
29 | }
30 |
--------------------------------------------------------------------------------
/facade/mock/blocksProcessorStub.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-proxy-go/common"
5 | "github.com/multiversx/mx-chain-proxy-go/data"
6 | )
7 |
8 | // BlocksProcessorStub -
9 | type BlocksProcessorStub struct {
10 | GetBlocksByRoundCalled func(round uint64, options common.BlockQueryOptions) (*data.BlocksApiResponse, error)
11 | }
12 |
13 | // GetBlocksByRound -
14 | func (bps *BlocksProcessorStub) GetBlocksByRound(round uint64, options common.BlockQueryOptions) (*data.BlocksApiResponse, error) {
15 | if bps.GetBlocksByRoundCalled != nil {
16 | return bps.GetBlocksByRoundCalled(round, options)
17 | }
18 | return nil, nil
19 | }
20 |
--------------------------------------------------------------------------------
/facade/mock/esdtSuppliesProcessorStub.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import "github.com/multiversx/mx-chain-proxy-go/data"
4 |
5 | // ESDTSuppliesProcessorStub -
6 | type ESDTSuppliesProcessorStub struct {
7 | GetESDTSupplyCalled func(token string) (*data.ESDTSupplyResponse, error)
8 | }
9 |
10 | // GetESDTSupply -
11 | func (e *ESDTSuppliesProcessorStub) GetESDTSupply(token string) (*data.ESDTSupplyResponse, error) {
12 | if e.GetESDTSupplyCalled != nil {
13 | return e.GetESDTSupplyCalled(token)
14 | }
15 |
16 | return nil, nil
17 | }
18 |
--------------------------------------------------------------------------------
/facade/mock/faucetProcessorStub.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import (
4 | "math/big"
5 |
6 | crypto "github.com/multiversx/mx-chain-crypto-go"
7 | "github.com/multiversx/mx-chain-proxy-go/data"
8 | )
9 |
10 | type FaucetProcessorStub struct {
11 | IsEnabledCalled func() bool
12 | GenerateTxForSendUserFundsCalled func(senderSk crypto.PrivateKey, senderPk string, senderNonce uint64,
13 | receiver string, value *big.Int, networkConfig *data.NetworkConfig) (*data.Transaction, error)
14 | SenderDetailsFromPemCalled func(receiver string) (crypto.PrivateKey, string, error)
15 | }
16 |
17 | func (fps *FaucetProcessorStub) IsEnabled() bool {
18 | if fps.IsEnabledCalled != nil {
19 | return fps.IsEnabledCalled()
20 | }
21 |
22 | return true
23 | }
24 |
25 | func (fps *FaucetProcessorStub) SenderDetailsFromPem(receiver string) (crypto.PrivateKey, string, error) {
26 | return fps.SenderDetailsFromPemCalled(receiver)
27 | }
28 |
29 | func (fps *FaucetProcessorStub) GenerateTxForSendUserFunds(
30 | senderSk crypto.PrivateKey,
31 | senderPk string,
32 | senderNonce uint64,
33 | receiver string,
34 | value *big.Int,
35 | networkConfig *data.NetworkConfig,
36 | ) (*data.Transaction, error) {
37 | return fps.GenerateTxForSendUserFundsCalled(senderSk, senderPk, senderNonce, receiver, value, networkConfig)
38 | }
39 |
--------------------------------------------------------------------------------
/facade/mock/nodeGroupProcessorStub.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import "github.com/multiversx/mx-chain-proxy-go/data"
4 |
5 | // NodeGroupProcessorStub represents a stub implementation of a NodeGroupProcessor
6 | type NodeGroupProcessorStub struct {
7 | GetHeartbeatDataCalled func() (*data.HeartbeatResponse, error)
8 | IsOldStorageForTokenCalled func(tokenID string, nonce uint64) (bool, error)
9 | GetWaitingEpochsLeftForPublicKeyCalled func(publicKey string) (*data.WaitingEpochsLeftApiResponse, error)
10 | }
11 |
12 | // IsOldStorageForToken -
13 | func (hbps *NodeGroupProcessorStub) IsOldStorageForToken(tokenID string, nonce uint64) (bool, error) {
14 | return hbps.IsOldStorageForTokenCalled(tokenID, nonce)
15 | }
16 |
17 | // GetHeartbeatData -
18 | func (hbps *NodeGroupProcessorStub) GetHeartbeatData() (*data.HeartbeatResponse, error) {
19 | return hbps.GetHeartbeatDataCalled()
20 | }
21 |
22 | // GetWaitingEpochsLeftForPublicKey -
23 | func (hbps *NodeGroupProcessorStub) GetWaitingEpochsLeftForPublicKey(publicKey string) (*data.WaitingEpochsLeftApiResponse, error) {
24 | if hbps.GetWaitingEpochsLeftForPublicKeyCalled != nil {
25 | return hbps.GetWaitingEpochsLeftForPublicKeyCalled(publicKey)
26 | }
27 | return &data.WaitingEpochsLeftApiResponse{}, nil
28 | }
29 |
--------------------------------------------------------------------------------
/facade/mock/proofProcessorStub.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import "github.com/multiversx/mx-chain-proxy-go/data"
4 |
5 | // ProofProcessorStub -
6 | type ProofProcessorStub struct {
7 | GetProofCalled func(string, string) (*data.GenericAPIResponse, error)
8 | GetProofDataTrieCalled func(string, string, string) (*data.GenericAPIResponse, error)
9 | GetProofCurrentRootHashCalled func(string) (*data.GenericAPIResponse, error)
10 | VerifyProofCalled func(string, string, []string) (*data.GenericAPIResponse, error)
11 | }
12 |
13 | // GetProof -
14 | func (pp *ProofProcessorStub) GetProof(rootHash string, address string) (*data.GenericAPIResponse, error) {
15 | if pp.GetProofCalled != nil {
16 | return pp.GetProofCalled(rootHash, address)
17 | }
18 |
19 | return nil, nil
20 | }
21 |
22 | // GetProofDataTrie -
23 | func (pp *ProofProcessorStub) GetProofDataTrie(rootHash string, address string, key string) (*data.GenericAPIResponse, error) {
24 | if pp.GetProofDataTrieCalled != nil {
25 | return pp.GetProofDataTrieCalled(rootHash, address, key)
26 | }
27 |
28 | return nil, nil
29 | }
30 |
31 | // GetProofCurrentRootHash -
32 | func (pp *ProofProcessorStub) GetProofCurrentRootHash(address string) (*data.GenericAPIResponse, error) {
33 | if pp.GetProofCurrentRootHashCalled != nil {
34 | return pp.GetProofCurrentRootHashCalled(address)
35 | }
36 |
37 | return nil, nil
38 | }
39 |
40 | // VerifyProof -
41 | func (pp *ProofProcessorStub) VerifyProof(rootHash string, address string, proof []string) (*data.GenericAPIResponse, error) {
42 | if pp.VerifyProofCalled != nil {
43 | return pp.VerifyProofCalled(rootHash, address, proof)
44 | }
45 |
46 | return nil, nil
47 | }
48 |
--------------------------------------------------------------------------------
/facade/mock/scQueryServiceStub.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-core-go/data/vm"
5 | "github.com/multiversx/mx-chain-proxy-go/data"
6 | )
7 |
8 | // SCQueryServiceStub -
9 | type SCQueryServiceStub struct {
10 | ExecuteQueryCalled func(*data.SCQuery) (*vm.VMOutputApi, data.BlockInfo, error)
11 | }
12 |
13 | // ExecuteQuery -
14 | func (serviceStub *SCQueryServiceStub) ExecuteQuery(query *data.SCQuery) (*vm.VMOutputApi, data.BlockInfo, error) {
15 | return serviceStub.ExecuteQueryCalled(query)
16 | }
17 |
--------------------------------------------------------------------------------
/facade/mock/statusProcessorStub.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-proxy-go/data"
5 | )
6 |
7 | // StatusProcessorStub -
8 | type StatusProcessorStub struct {
9 | GetMetricsCalled func() map[string]*data.EndpointMetrics
10 | GetMetricsForPrometheusCalled func() string
11 | }
12 |
13 | // GetMetricsForPrometheus -
14 | func (s *StatusProcessorStub) GetMetricsForPrometheus() string {
15 | if s.GetMetricsForPrometheusCalled != nil {
16 | return s.GetMetricsForPrometheusCalled()
17 | }
18 |
19 | return ""
20 | }
21 |
22 | // GetMetrics -
23 | func (s *StatusProcessorStub) GetMetrics() map[string]*data.EndpointMetrics {
24 | if s.GetMetricsCalled != nil {
25 | return s.GetMetricsCalled()
26 | }
27 |
28 | return nil
29 | }
30 |
--------------------------------------------------------------------------------
/facade/mock/validatorStatisticsProcessorStub.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import "github.com/multiversx/mx-chain-proxy-go/data"
4 |
5 | // ValidatorStatisticsProcessorStub -
6 | type ValidatorStatisticsProcessorStub struct {
7 | GetValidatorStatisticsCalled func() (*data.ValidatorStatisticsResponse, error)
8 | }
9 |
10 | // GetValidatorStatistics -
11 | func (v *ValidatorStatisticsProcessorStub) GetValidatorStatistics() (*data.ValidatorStatisticsResponse, error) {
12 | return v.GetValidatorStatisticsCalled()
13 | }
14 |
15 | // GetAuctionList -
16 | func (v *ValidatorStatisticsProcessorStub) GetAuctionList() (*data.AuctionListResponse, error) {
17 | return nil, nil
18 | }
19 |
--------------------------------------------------------------------------------
/facade/versions/proxyFacadeV1_0.go:
--------------------------------------------------------------------------------
1 | package versions
2 |
3 | import "github.com/multiversx/mx-chain-proxy-go/facade"
4 |
5 | // ProxyFacadeV1_0 is the facade that corresponds to the version v1.0
6 | type ProxyFacadeV1_0 struct {
7 | *facade.ProxyFacade
8 | }
9 |
--------------------------------------------------------------------------------
/facade/versions/proxyFacadeV_next.go:
--------------------------------------------------------------------------------
1 | package versions
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-proxy-go/facade"
5 | "github.com/multiversx/mx-chain-proxy-go/process/v_next"
6 | )
7 |
8 | // ProxyFacadeV_next is the facade that corresponds to the version v_next
9 | type ProxyFacadeV_next struct {
10 | AccountsProcessor v_next.AccountProcessorV_next
11 | *facade.ProxyFacade
12 | }
13 |
14 | // GetShardIDForAddressV_next is an example function that demonstrates how to add a new custom handler for a modified api endpoint
15 | func (epf *ProxyFacadeV_next) GetShardIDForAddressV_next(address string, additionalField int) (uint32, error) {
16 | return epf.AccountsProcessor.GetShardIDForAddress(address, additionalField)
17 | }
18 |
19 | // NextEndpointHandler is an example function that demonstrates how to add a new custom handler for a new API endpoint
20 | func (epf *ProxyFacadeV_next) NextEndpointHandler() string {
21 | return epf.AccountsProcessor.NextEndpointHandler()
22 | }
23 |
--------------------------------------------------------------------------------
/faucet/errors.go:
--------------------------------------------------------------------------------
1 | package faucet
2 |
3 | import "errors"
4 |
5 | // ErrNilShardCoordinator signals that the provided shard coordinator is nil
6 | var ErrNilShardCoordinator = errors.New("nil shard coordinator")
7 |
8 | // ErrFaucetPemFileDoesNotExist signals that the faucet pem file does not exist
9 | var ErrFaucetPemFileDoesNotExist = errors.New("faucet pem file does not exist")
10 |
11 | // ErrNilPubKeyConverter signals that the provided pub key converter is nil
12 | var ErrNilPubKeyConverter = errors.New("nil pub key converter")
13 |
--------------------------------------------------------------------------------
/faucet/mock/addressContainerMock.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | type AddressContainerMock struct {
4 | BytesField []byte
5 | }
6 |
7 | func (adr *AddressContainerMock) Bytes() []byte {
8 | return adr.BytesField
9 | }
10 |
11 | func (adr *AddressContainerMock) IsInterfaceNil() bool {
12 | return adr == nil
13 | }
14 |
--------------------------------------------------------------------------------
/faucet/mock/pubKeyConverterMock.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import (
4 | "encoding/hex"
5 |
6 | "github.com/multiversx/mx-chain-core-go/core"
7 | )
8 |
9 | // PubKeyConverterMock -
10 | type PubKeyConverterMock struct {
11 | len int
12 | }
13 |
14 | // Decode -
15 | func (pcm *PubKeyConverterMock) Decode(humanReadable string) ([]byte, error) {
16 | return hex.DecodeString(humanReadable)
17 | }
18 |
19 | // Encode -
20 | func (pcm *PubKeyConverterMock) Encode(pkBytes []byte) (string, error) {
21 | return hex.EncodeToString(pkBytes), nil
22 | }
23 |
24 | // EncodeSlice -
25 | func (pcm *PubKeyConverterMock) EncodeSlice(pkBytesSlice [][]byte) ([]string, error) {
26 | results := make([]string, 0)
27 | for _, pk := range pkBytesSlice {
28 | results = append(results, hex.EncodeToString(pk))
29 | }
30 |
31 | return results, nil
32 | }
33 |
34 | // SilentEncode -
35 | func (pcm *PubKeyConverterMock) SilentEncode(pkBytes []byte, _ core.Logger) string {
36 | return hex.EncodeToString(pkBytes)
37 | }
38 |
39 | // Len -
40 | func (pcm *PubKeyConverterMock) Len() int {
41 | return pcm.len
42 | }
43 |
44 | // IsInterfaceNil -
45 | func (pcm *PubKeyConverterMock) IsInterfaceNil() bool {
46 | return pcm == nil
47 | }
48 |
--------------------------------------------------------------------------------
/faucet/mock/shardCoordinatorMock.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | type ShardCoordinatorMock struct {
4 | }
5 |
6 | func (scm *ShardCoordinatorMock) NumberOfShards() uint32 {
7 | panic("implement me")
8 | }
9 |
10 | func (scm *ShardCoordinatorMock) ComputeId(_ []byte) uint32 {
11 | return uint32(1)
12 | }
13 |
14 | func (scm *ShardCoordinatorMock) SetSelfId(_ uint32) error {
15 | panic("implement me")
16 | }
17 |
18 | func (scm *ShardCoordinatorMock) SelfId() uint32 {
19 | return 0
20 | }
21 |
22 | func (scm *ShardCoordinatorMock) SameShard(_, _ []byte) bool {
23 | return true
24 | }
25 |
26 | func (scm *ShardCoordinatorMock) CommunicationIdentifier(_ uint32) string {
27 | return "0_1"
28 | }
29 |
30 | // IsInterfaceNil returns true if there is no value under the interface
31 | func (scm *ShardCoordinatorMock) IsInterfaceNil() bool {
32 | return scm == nil
33 | }
34 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/multiversx/mx-chain-proxy-go
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/gin-contrib/cors v1.4.0
7 | github.com/gin-contrib/pprof v1.4.0
8 | github.com/gin-contrib/static v0.0.1
9 | github.com/gin-gonic/gin v1.10.0
10 | github.com/multiversx/mx-chain-core-go v1.3.0
11 | github.com/multiversx/mx-chain-crypto-go v1.2.12
12 | github.com/multiversx/mx-chain-es-indexer-go v1.8.0
13 | github.com/multiversx/mx-chain-logger-go v1.0.15
14 | github.com/pkg/errors v0.9.1
15 | github.com/stretchr/testify v1.10.0
16 | github.com/urfave/cli v1.22.16
17 | gopkg.in/go-playground/validator.v8 v8.18.2
18 | )
19 |
20 | require (
21 | github.com/btcsuite/btcd/btcutil v1.1.3 // indirect
22 | github.com/bytedance/sonic v1.11.6 // indirect
23 | github.com/bytedance/sonic/loader v0.1.1 // indirect
24 | github.com/cloudwego/base64x v0.1.4 // indirect
25 | github.com/cloudwego/iasm v0.2.0 // indirect
26 | github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
27 | github.com/davecgh/go-spew v1.1.1 // indirect
28 | github.com/denisbrodbeck/machineid v1.0.1 // indirect
29 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect
30 | github.com/gin-contrib/sse v0.1.0 // indirect
31 | github.com/go-playground/locales v0.14.1 // indirect
32 | github.com/go-playground/universal-translator v0.18.1 // indirect
33 | github.com/go-playground/validator/v10 v10.20.0 // indirect
34 | github.com/goccy/go-json v0.10.2 // indirect
35 | github.com/gogo/protobuf v1.3.2 // indirect
36 | github.com/golang/protobuf v1.5.3 // indirect
37 | github.com/json-iterator/go v1.1.12 // indirect
38 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect
39 | github.com/leodido/go-urn v1.4.0 // indirect
40 | github.com/mattn/go-isatty v0.0.20 // indirect
41 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
42 | github.com/modern-go/reflect2 v1.0.2 // indirect
43 | github.com/mr-tron/base58 v1.2.0 // indirect
44 | github.com/pelletier/go-toml v1.9.3 // indirect
45 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect
46 | github.com/pmezard/go-difflib v1.0.0 // indirect
47 | github.com/russross/blackfriday/v2 v2.1.0 // indirect
48 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
49 | github.com/ugorji/go/codec v1.2.12 // indirect
50 | golang.org/x/arch v0.8.0 // indirect
51 | golang.org/x/crypto v0.31.0 // indirect
52 | golang.org/x/net v0.33.0 // indirect
53 | golang.org/x/sys v0.28.0 // indirect
54 | golang.org/x/text v0.21.0 // indirect
55 | google.golang.org/protobuf v1.36.3 // indirect
56 | gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
57 | gopkg.in/yaml.v3 v3.0.1 // indirect
58 | )
59 |
--------------------------------------------------------------------------------
/metrics/statusMetrics.go:
--------------------------------------------------------------------------------
1 | package metrics
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "sync"
7 | "time"
8 |
9 | "github.com/multiversx/mx-chain-proxy-go/data"
10 | )
11 |
12 | // statusMetrics will handle displaying at /status/metrics all collected metrics
13 | type statusMetrics struct {
14 | endpointMetrics map[string]*data.EndpointMetrics
15 | mutEndpointsOperations sync.RWMutex
16 | }
17 |
18 | // NewStatusMetrics will return an instance of the struct
19 | func NewStatusMetrics() *statusMetrics {
20 | return &statusMetrics{
21 | endpointMetrics: make(map[string]*data.EndpointMetrics),
22 | }
23 | }
24 |
25 | // AddRequestData will add the received data to the metrics map
26 | func (sm *statusMetrics) AddRequestData(path string, withError bool, duration time.Duration) {
27 | // TODO: refactor this by using a buffered channel that receives new request data and stores them into the map
28 | // from time to time
29 |
30 | sm.mutEndpointsOperations.Lock()
31 | defer sm.mutEndpointsOperations.Unlock()
32 |
33 | currentData := sm.endpointMetrics[path]
34 | withErrorIncrementalStep := uint64(0)
35 | if withError {
36 | withErrorIncrementalStep = 1
37 | }
38 | if currentData == nil {
39 | sm.endpointMetrics[path] = &data.EndpointMetrics{
40 | NumRequests: 1,
41 | NumErrors: withErrorIncrementalStep,
42 | TotalResponseTime: duration,
43 | LowestResponseTime: duration,
44 | HighestResponseTime: duration,
45 | }
46 |
47 | return
48 | }
49 |
50 | currentData.NumRequests++
51 | currentData.NumErrors += withErrorIncrementalStep
52 | if duration < currentData.LowestResponseTime {
53 | currentData.LowestResponseTime = duration
54 | }
55 | if duration > currentData.HighestResponseTime {
56 | currentData.HighestResponseTime = duration
57 | }
58 | currentData.TotalResponseTime += duration
59 | }
60 |
61 | // GetAll returns the metrics map
62 | func (sm *statusMetrics) GetAll() map[string]*data.EndpointMetrics {
63 | sm.mutEndpointsOperations.RLock()
64 | defer sm.mutEndpointsOperations.RUnlock()
65 |
66 | newMap := make(map[string]*data.EndpointMetrics)
67 | for key, value := range sm.endpointMetrics {
68 | newMap[key] = value
69 | }
70 |
71 | return newMap
72 | }
73 |
74 | // GetMetricsForPrometheus returns the metrics in a prometheus format
75 | func (sm *statusMetrics) GetMetricsForPrometheus() string {
76 | metricsMap := sm.GetAll()
77 |
78 | stringBuilder := strings.Builder{}
79 |
80 | for endpointPath, endpointData := range metricsMap {
81 | stringBuilder.WriteString(fmt.Sprintf("num_requests{endpoint=\"%s\"} %d\n", endpointPath, endpointData.NumRequests))
82 | stringBuilder.WriteString(fmt.Sprintf("num_errors{endpoint=\"%s\"} %d\n", endpointPath, endpointData.NumErrors))
83 | stringBuilder.WriteString(fmt.Sprintf("total_response_time_ns{endpoint=\"%s\"} %d\n", endpointPath, endpointData.TotalResponseTime))
84 | stringBuilder.WriteString(fmt.Sprintf("highest_response_time_ns{endpoint=\"%s\"} %d\n", endpointPath, endpointData.HighestResponseTime))
85 | stringBuilder.WriteString(fmt.Sprintf("lowest_response_time_ns{endpoint=\"%s\"} %d\n", endpointPath, endpointData.LowestResponseTime))
86 | }
87 |
88 | return stringBuilder.String()
89 | }
90 |
91 | // IsInterfaceNil returns true if there is no value under the interface
92 | func (sm *statusMetrics) IsInterfaceNil() bool {
93 | return sm == nil
94 | }
95 |
--------------------------------------------------------------------------------
/observer/availabilityCommon/availabilityProvider.go:
--------------------------------------------------------------------------------
1 | package availabilityCommon
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-proxy-go/common"
5 | "github.com/multiversx/mx-chain-proxy-go/data"
6 | )
7 |
8 | // AvailabilityProvider is a stateless component that aims to group common operations regarding observers' data availability
9 | type AvailabilityProvider struct {
10 | }
11 |
12 | // AvailabilityForAccountQueryOptions returns the availability needed for the provided query options
13 | func (ap *AvailabilityProvider) AvailabilityForAccountQueryOptions(options common.AccountQueryOptions) data.ObserverDataAvailabilityType {
14 | availability := data.AvailabilityRecent
15 | if options.AreHistoricalCoordinatesSet() {
16 | availability = data.AvailabilityAll
17 | }
18 | return availability
19 | }
20 |
21 | // AvailabilityForVmQuery returns the availability needed for the provided query options
22 | func (ap *AvailabilityProvider) AvailabilityForVmQuery(query *data.SCQuery) data.ObserverDataAvailabilityType {
23 | availability := data.AvailabilityRecent
24 | if query.BlockNonce.HasValue || len(query.BlockHash) > 0 {
25 | availability = data.AvailabilityAll
26 | }
27 | return availability
28 | }
29 |
30 | // IsNodeValid returns true if the provided node is valid based on the availability
31 | func (ap *AvailabilityProvider) IsNodeValid(node *data.NodeData, availability data.ObserverDataAvailabilityType) bool {
32 | isInvalidSnapshotlessNode := availability == data.AvailabilityRecent && !node.IsSnapshotless
33 | isInvalidRegularNode := availability == data.AvailabilityAll && node.IsSnapshotless
34 | isInvalidNode := isInvalidSnapshotlessNode || isInvalidRegularNode
35 | return !isInvalidNode
36 | }
37 |
38 | // GetDescriptionForAvailability returns a short description string about the provided availability
39 | func (ap *AvailabilityProvider) GetDescriptionForAvailability(availability data.ObserverDataAvailabilityType) string {
40 | switch availability {
41 | case data.AvailabilityAll:
42 | return "regular nodes"
43 | case data.AvailabilityRecent:
44 | return "snapshotless nodes"
45 | default:
46 | return "N/A"
47 | }
48 | }
49 |
50 | // GetAllAvailabilityTypes returns all data availability types
51 | func (ap *AvailabilityProvider) GetAllAvailabilityTypes() []data.ObserverDataAvailabilityType {
52 | return []data.ObserverDataAvailabilityType{data.AvailabilityAll, data.AvailabilityRecent}
53 | }
54 |
--------------------------------------------------------------------------------
/observer/availabilityCommon/availabilityProvider_test.go:
--------------------------------------------------------------------------------
1 | package availabilityCommon
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/multiversx/mx-chain-core-go/core"
7 | "github.com/multiversx/mx-chain-proxy-go/common"
8 | "github.com/multiversx/mx-chain-proxy-go/data"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | func TestAvailabilityForAccountQueryOptions(t *testing.T) {
13 | t.Parallel()
14 |
15 | ap := &AvailabilityProvider{}
16 |
17 | // Test with historical coordinates set
18 | options := common.AccountQueryOptions{BlockHash: []byte("hash")}
19 | require.Equal(t, data.AvailabilityAll, ap.AvailabilityForAccountQueryOptions(options))
20 |
21 | // Test without historical coordinates set
22 | options = common.AccountQueryOptions{}
23 | require.Equal(t, data.AvailabilityRecent, ap.AvailabilityForAccountQueryOptions(options))
24 | }
25 |
26 | func TestAvailabilityForVmQuery(t *testing.T) {
27 | t.Parallel()
28 |
29 | ap := &AvailabilityProvider{}
30 |
31 | // Test with BlockNonce set
32 | query := &data.SCQuery{BlockNonce: core.OptionalUint64{HasValue: true, Value: 37}}
33 | require.Equal(t, data.AvailabilityAll, ap.AvailabilityForVmQuery(query))
34 |
35 | // Test without BlockNonce set but with BlockHash
36 | query = &data.SCQuery{BlockHash: []byte("hash")}
37 | require.Equal(t, data.AvailabilityAll, ap.AvailabilityForVmQuery(query))
38 |
39 | // Test without BlockNonce and BlockHash
40 | query = &data.SCQuery{}
41 | require.Equal(t, data.AvailabilityRecent, ap.AvailabilityForVmQuery(query))
42 | }
43 |
44 | func TestIsNodeValid(t *testing.T) {
45 | t.Parallel()
46 |
47 | ap := &AvailabilityProvider{}
48 |
49 | // Test with AvailabilityRecent and snapshotless node
50 | node := &data.NodeData{IsSnapshotless: true}
51 | require.True(t, ap.IsNodeValid(node, data.AvailabilityRecent))
52 |
53 | // Test with AvailabilityRecent and regular node
54 | node = &data.NodeData{}
55 | require.False(t, ap.IsNodeValid(node, data.AvailabilityRecent))
56 |
57 | // Test with AvailabilityAll and regular node
58 | node = &data.NodeData{}
59 | require.True(t, ap.IsNodeValid(node, data.AvailabilityAll))
60 |
61 | // Test with AvailabilityAll and Snapshotless node
62 | node = &data.NodeData{IsSnapshotless: true}
63 | require.False(t, ap.IsNodeValid(node, data.AvailabilityAll))
64 | }
65 |
66 | func TestGetDescriptionForAvailability(t *testing.T) {
67 | t.Parallel()
68 |
69 | ap := &AvailabilityProvider{}
70 |
71 | require.Equal(t, "regular nodes", ap.GetDescriptionForAvailability(data.AvailabilityAll))
72 | require.Equal(t, "snapshotless nodes", ap.GetDescriptionForAvailability(data.AvailabilityRecent))
73 | require.Equal(t, "N/A", ap.GetDescriptionForAvailability("invalid")) // Invalid value
74 | }
75 |
76 | func TestAvailabilityProvider_GetAllAvailabilityTypes(t *testing.T) {
77 | t.Parallel()
78 |
79 | ap := &AvailabilityProvider{}
80 | require.Equal(t, []data.ObserverDataAvailabilityType{data.AvailabilityAll, data.AvailabilityRecent}, ap.GetAllAvailabilityTypes())
81 | }
82 |
--------------------------------------------------------------------------------
/observer/circularQueueNodesProvider.go:
--------------------------------------------------------------------------------
1 | package observer
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-proxy-go/data"
5 | "github.com/multiversx/mx-chain-proxy-go/observer/mapCounters"
6 | )
7 |
8 | // circularQueueNodesProvider will handle the providing of observers in a circular queue way, guaranteeing the
9 | // balancing of them
10 | type circularQueueNodesProvider struct {
11 | *baseNodeProvider
12 | positionsHolder CounterMapsHolder
13 | }
14 |
15 | // NewCircularQueueNodesProvider returns a new instance of circularQueueNodesProvider
16 | func NewCircularQueueNodesProvider(
17 | observers []*data.NodeData,
18 | configurationFilePath string,
19 | numberOfShards uint32,
20 | ) (*circularQueueNodesProvider, error) {
21 | bop := &baseNodeProvider{
22 | configurationFilePath: configurationFilePath,
23 | numOfShards: numberOfShards,
24 | }
25 |
26 | err := bop.initNodes(observers)
27 | if err != nil {
28 | return nil, err
29 | }
30 |
31 | return &circularQueueNodesProvider{
32 | baseNodeProvider: bop,
33 | positionsHolder: mapCounters.NewMapCountersHolder(),
34 | }, nil
35 | }
36 |
37 | // GetNodesByShardId will return a slice of observers for the given shard
38 | func (cqnp *circularQueueNodesProvider) GetNodesByShardId(shardId uint32, dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error) {
39 | cqnp.mutNodes.Lock()
40 | defer cqnp.mutNodes.Unlock()
41 |
42 | syncedNodesForShard, err := cqnp.getSyncedNodesForShardUnprotected(shardId, dataAvailability)
43 | if err != nil {
44 | return nil, err
45 | }
46 |
47 | position, err := cqnp.positionsHolder.ComputeShardPosition(dataAvailability, shardId, uint32(len(syncedNodesForShard)))
48 | if err != nil {
49 | return nil, err
50 | }
51 |
52 | sliceToRet := append(syncedNodesForShard[position:], syncedNodesForShard[:position]...)
53 |
54 | return sliceToRet, nil
55 | }
56 |
57 | // GetAllNodes will return a slice containing all observers
58 | func (cqnp *circularQueueNodesProvider) GetAllNodes(dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error) {
59 | cqnp.mutNodes.Lock()
60 | defer cqnp.mutNodes.Unlock()
61 |
62 | allNodes, err := cqnp.getSyncedNodesUnprotected(dataAvailability)
63 | if err != nil {
64 | return nil, err
65 | }
66 |
67 | position, err := cqnp.positionsHolder.ComputeAllNodesPosition(dataAvailability, uint32(len(allNodes)))
68 | if err != nil {
69 | return nil, err
70 | }
71 |
72 | sliceToRet := append(allNodes[position:], allNodes[:position]...)
73 |
74 | return sliceToRet, nil
75 | }
76 |
77 | // IsInterfaceNil returns true if there is no value under the interface
78 | func (cqnp *circularQueueNodesProvider) IsInterfaceNil() bool {
79 | return cqnp == nil
80 | }
81 |
--------------------------------------------------------------------------------
/observer/disabledNodesProvider.go:
--------------------------------------------------------------------------------
1 | package observer
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/multiversx/mx-chain-proxy-go/data"
7 | )
8 |
9 | type disabledNodesProvider struct {
10 | returnMessage string
11 | }
12 |
13 | func NewDisabledNodesProvider(returnMessage string) *disabledNodesProvider {
14 | returnMessageToUse := "not implemented"
15 | if returnMessage != "" {
16 | returnMessageToUse = returnMessage
17 | }
18 | return &disabledNodesProvider{
19 | returnMessage: returnMessageToUse,
20 | }
21 | }
22 |
23 | // UpdateNodesBasedOnSyncState won't do anything as this is a disabled component
24 | func (d *disabledNodesProvider) UpdateNodesBasedOnSyncState(_ []*data.NodeData) {
25 | }
26 |
27 | // GetAllNodesWithSyncState returns an empty slice
28 | func (d *disabledNodesProvider) GetAllNodesWithSyncState() []*data.NodeData {
29 | return make([]*data.NodeData, 0)
30 | }
31 |
32 | // GetNodesByShardId returns the desired return message as an error
33 | func (d *disabledNodesProvider) GetNodesByShardId(_ uint32, _ data.ObserverDataAvailabilityType) ([]*data.NodeData, error) {
34 | return nil, errors.New(d.returnMessage)
35 | }
36 |
37 | // GetAllNodes returns the desired return message as an error
38 | func (d *disabledNodesProvider) GetAllNodes(_ data.ObserverDataAvailabilityType) ([]*data.NodeData, error) {
39 | return nil, errors.New(d.returnMessage)
40 | }
41 |
42 | // ReloadNodes return the desired return message as an error
43 | func (d *disabledNodesProvider) ReloadNodes(_ data.NodeType) data.NodesReloadResponse {
44 | return data.NodesReloadResponse{Description: "disabled nodes provider", Error: d.returnMessage}
45 | }
46 |
47 | // PrintNodesInShards does nothing as it is disabled
48 | func (d *disabledNodesProvider) PrintNodesInShards() {
49 | }
50 |
51 | // IsInterfaceNil returns true if there is no value under the interface
52 | func (d *disabledNodesProvider) IsInterfaceNil() bool {
53 | return d == nil
54 | }
55 |
--------------------------------------------------------------------------------
/observer/errors.go:
--------------------------------------------------------------------------------
1 | package observer
2 |
3 | import "errors"
4 |
5 | // ErrEmptyObserversList signals that the list of observers is empty
6 | var ErrEmptyObserversList = errors.New("empty observers list")
7 |
8 | // ErrShardNotAvailable signals that the specified shard ID cannot be found in internal maps
9 | var ErrShardNotAvailable = errors.New("the specified shard ID does not exist in proxy's configuration")
10 |
11 | // ErrInvalidShard signals that an invalid shard has been provided
12 | var ErrInvalidShard = errors.New("invalid shard")
13 |
--------------------------------------------------------------------------------
/observer/interface.go:
--------------------------------------------------------------------------------
1 | package observer
2 |
3 | import "github.com/multiversx/mx-chain-proxy-go/data"
4 |
5 | // NodesProviderHandler defines what a nodes provider should be able to do
6 | type NodesProviderHandler interface {
7 | GetNodesByShardId(shardId uint32, dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error)
8 | GetAllNodes(dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error)
9 | UpdateNodesBasedOnSyncState(nodesWithSyncStatus []*data.NodeData)
10 | GetAllNodesWithSyncState() []*data.NodeData
11 | ReloadNodes(nodesType data.NodeType) data.NodesReloadResponse
12 | PrintNodesInShards()
13 | IsInterfaceNil() bool
14 | }
15 |
16 | // NodesHolder defines the actions of a component that is able to hold nodes
17 | type NodesHolder interface {
18 | UpdateNodes(nodesWithSyncStatus []*data.NodeData)
19 | PrintNodesInShards()
20 | GetSyncedNodes(shardID uint32) []*data.NodeData
21 | GetSyncedFallbackNodes(shardID uint32) []*data.NodeData
22 | GetOutOfSyncNodes(shardID uint32) []*data.NodeData
23 | GetOutOfSyncFallbackNodes(shardID uint32) []*data.NodeData
24 | Count() int
25 | IsInterfaceNil() bool
26 | }
27 |
28 | // CounterMapsHolder defines the actions to be implemented by a component that can hold multiple counter maps
29 | type CounterMapsHolder interface {
30 | ComputeShardPosition(availability data.ObserverDataAvailabilityType, shardID uint32, numNodes uint32) (uint32, error)
31 | ComputeAllNodesPosition(availability data.ObserverDataAvailabilityType, numNodes uint32) (uint32, error)
32 | IsInterfaceNil() bool
33 | }
34 |
--------------------------------------------------------------------------------
/observer/mapCounters/mapCounter.go:
--------------------------------------------------------------------------------
1 | package mapCounters
2 |
3 | import "sync"
4 |
5 | type mapCounter struct {
6 | positions map[uint32]uint32
7 | allNodesCount uint32
8 | allNodesPosition uint32
9 | mut sync.RWMutex
10 | }
11 |
12 | // newMapCounter returns a new instance of a mapCounter
13 | func newMapCounter() *mapCounter {
14 | return &mapCounter{
15 | positions: make(map[uint32]uint32),
16 | allNodesPosition: 0,
17 | }
18 | }
19 |
20 | func (mc *mapCounter) computePositionForShard(shardID uint32, numNodes uint32) uint32 {
21 | mc.mut.Lock()
22 | defer mc.mut.Unlock()
23 |
24 | mc.initShardPositionIfNeededUnprotected(shardID)
25 |
26 | mc.positions[shardID]++
27 | mc.positions[shardID] %= numNodes
28 |
29 | return mc.positions[shardID]
30 | }
31 |
32 | func (mc *mapCounter) computePositionForAllNodes(numNodes uint32) uint32 {
33 | mc.mut.Lock()
34 | defer mc.mut.Unlock()
35 |
36 | mc.initAllNodesPositionIfNeededUnprotected(numNodes)
37 |
38 | mc.allNodesPosition++
39 | mc.allNodesPosition %= numNodes
40 |
41 | return mc.allNodesPosition
42 | }
43 |
44 | func (mc *mapCounter) initShardPositionIfNeededUnprotected(shardID uint32) {
45 | _, shardExists := mc.positions[shardID]
46 | if !shardExists {
47 | mc.positions[shardID] = 0
48 | }
49 | }
50 |
51 | func (mc *mapCounter) initAllNodesPositionIfNeededUnprotected(numNodes uint32) {
52 | if numNodes != mc.allNodesCount {
53 | mc.allNodesCount = numNodes
54 | mc.allNodesPosition = 0
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/observer/mapCounters/mapCountersHolder.go:
--------------------------------------------------------------------------------
1 | package mapCounters
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/multiversx/mx-chain-proxy-go/data"
7 | "github.com/multiversx/mx-chain-proxy-go/observer/availabilityCommon"
8 | )
9 |
10 | var (
11 | errInvalidAvailability = errors.New("invalid data availability type")
12 | errNumNodesMustBeGreaterThanZero = errors.New("the number of nodes must be greater than 0")
13 | )
14 |
15 | // mapCountersHolder handles multiple counters map based on the data availability
16 | type mapCountersHolder struct {
17 | countersMap map[data.ObserverDataAvailabilityType]*mapCounter
18 | }
19 |
20 | // NewMapCountersHolder populates the initial map and returns a new instance of mapCountersHolder
21 | func NewMapCountersHolder() *mapCountersHolder {
22 | availabilityProvider := availabilityCommon.AvailabilityProvider{}
23 | dataAvailabilityTypes := availabilityProvider.GetAllAvailabilityTypes()
24 |
25 | countersMap := make(map[data.ObserverDataAvailabilityType]*mapCounter, len(dataAvailabilityTypes))
26 | for _, availability := range dataAvailabilityTypes {
27 | countersMap[availability] = newMapCounter()
28 | }
29 |
30 | return &mapCountersHolder{
31 | countersMap: countersMap,
32 | }
33 | }
34 |
35 | // ComputeShardPosition returns the shard position based on the availability and the shard
36 | func (mch *mapCountersHolder) ComputeShardPosition(availability data.ObserverDataAvailabilityType, shardID uint32, numNodes uint32) (uint32, error) {
37 | if numNodes == 0 {
38 | return 0, errNumNodesMustBeGreaterThanZero
39 | }
40 | counterMap, exists := mch.countersMap[availability]
41 | if !exists {
42 | return 0, errInvalidAvailability
43 | }
44 |
45 | position := counterMap.computePositionForShard(shardID, numNodes)
46 | return position, nil
47 | }
48 |
49 | // ComputeAllNodesPosition returns the all nodes position based on the availability
50 | func (mch *mapCountersHolder) ComputeAllNodesPosition(availability data.ObserverDataAvailabilityType, numNodes uint32) (uint32, error) {
51 | if numNodes == 0 {
52 | return 0, errNumNodesMustBeGreaterThanZero
53 | }
54 | counterMap, exists := mch.countersMap[availability]
55 | if !exists {
56 | return 0, errInvalidAvailability
57 | }
58 |
59 | position := counterMap.computePositionForAllNodes(numNodes)
60 | return position, nil
61 | }
62 |
63 | // IsInterfaceNil returns true if there is no value under the interface
64 | func (mch *mapCountersHolder) IsInterfaceNil() bool {
65 | return mch == nil
66 | }
67 |
--------------------------------------------------------------------------------
/observer/nodesProviderFactory.go:
--------------------------------------------------------------------------------
1 | package observer
2 |
3 | import (
4 | logger "github.com/multiversx/mx-chain-logger-go"
5 | "github.com/multiversx/mx-chain-proxy-go/config"
6 | )
7 |
8 | var log = logger.GetOrCreate("observer")
9 |
10 | // nodesProviderFactory handles the creation of an nodes provider based on config
11 | type nodesProviderFactory struct {
12 | cfg config.Config
13 | configurationFilePath string
14 | numberOfShards uint32
15 | }
16 |
17 | // NewNodesProviderFactory returns a new instance of nodesProviderFactory
18 | func NewNodesProviderFactory(cfg config.Config, configurationFilePath string, numberOfShards uint32) (*nodesProviderFactory, error) {
19 | return &nodesProviderFactory{
20 | cfg: cfg,
21 | configurationFilePath: configurationFilePath,
22 | numberOfShards: numberOfShards,
23 | }, nil
24 | }
25 |
26 | // CreateObservers will create and return an object of type NodesProviderHandler based on a flag
27 | func (npf *nodesProviderFactory) CreateObservers() (NodesProviderHandler, error) {
28 | if npf.cfg.GeneralSettings.BalancedObservers {
29 | return NewCircularQueueNodesProvider(
30 | npf.cfg.Observers,
31 | npf.configurationFilePath,
32 | npf.numberOfShards)
33 | }
34 |
35 | return NewSimpleNodesProvider(
36 | npf.cfg.Observers,
37 | npf.configurationFilePath,
38 | npf.numberOfShards)
39 | }
40 |
41 | // CreateFullHistoryNodes will create and return an object of type NodesProviderHandler based on a flag
42 | func (npf *nodesProviderFactory) CreateFullHistoryNodes() (NodesProviderHandler, error) {
43 | if npf.cfg.GeneralSettings.BalancedFullHistoryNodes {
44 | nodesProviderHandler, err := NewCircularQueueNodesProvider(
45 | npf.cfg.FullHistoryNodes,
46 | npf.configurationFilePath,
47 | npf.numberOfShards)
48 | if err != nil {
49 | return getDisabledFullHistoryNodesProviderIfNeeded(err)
50 | }
51 |
52 | return nodesProviderHandler, nil
53 | }
54 |
55 | nodesProviderHandler, err := NewSimpleNodesProvider(
56 | npf.cfg.FullHistoryNodes,
57 | npf.configurationFilePath,
58 | npf.numberOfShards)
59 | if err != nil {
60 | return getDisabledFullHistoryNodesProviderIfNeeded(err)
61 | }
62 |
63 | return nodesProviderHandler, nil
64 | }
65 |
66 | func getDisabledFullHistoryNodesProviderIfNeeded(err error) (NodesProviderHandler, error) {
67 | if err == ErrEmptyObserversList {
68 | log.Warn("no configuration found for full history nodes. Calls to endpoints specific to full history nodes " +
69 | "will return an error")
70 | return NewDisabledNodesProvider("full history nodes not supported"), nil
71 | }
72 |
73 | return nil, err
74 | }
75 |
--------------------------------------------------------------------------------
/observer/nodesProviderFactory_test.go:
--------------------------------------------------------------------------------
1 | package observer
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/multiversx/mx-chain-proxy-go/config"
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestNewObserversProviderFactory_ShouldWork(t *testing.T) {
11 | t.Parallel()
12 |
13 | opf, err := NewNodesProviderFactory(config.Config{}, "path", 2)
14 | assert.Nil(t, err)
15 | assert.NotNil(t, opf)
16 | }
17 |
18 | func TestObserversProviderFactory_CreateShouldReturnSimple(t *testing.T) {
19 | t.Parallel()
20 |
21 | cfg := getDummyConfig()
22 | cfg.GeneralSettings.BalancedObservers = false
23 |
24 | opf, _ := NewNodesProviderFactory(cfg, "path", 2)
25 | op, err := opf.CreateObservers()
26 | assert.Nil(t, err)
27 | _, ok := op.(*simpleNodesProvider)
28 | assert.True(t, ok)
29 | }
30 |
31 | func TestObserversProviderFactory_CreateShouldReturnCircularQueue(t *testing.T) {
32 | t.Parallel()
33 |
34 | cfg := getDummyConfig()
35 | cfg.GeneralSettings.BalancedObservers = true
36 |
37 | opf, _ := NewNodesProviderFactory(cfg, "path", 2)
38 | op, err := opf.CreateObservers()
39 | assert.Nil(t, err)
40 | _, ok := op.(*circularQueueNodesProvider)
41 | assert.True(t, ok)
42 | }
43 |
--------------------------------------------------------------------------------
/observer/simpleNodesProvider.go:
--------------------------------------------------------------------------------
1 | package observer
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-proxy-go/data"
5 | )
6 |
7 | // simpleNodesProvider will handle the providing of observers in a simple way, in the order in which they were
8 | // provided in the config file.
9 | type simpleNodesProvider struct {
10 | *baseNodeProvider
11 | }
12 |
13 | // NewSimpleNodesProvider will return a new instance of simpleNodesProvider
14 | func NewSimpleNodesProvider(
15 | observers []*data.NodeData,
16 | configurationFilePath string,
17 | numberOfShards uint32,
18 | ) (*simpleNodesProvider, error) {
19 | bop := &baseNodeProvider{
20 | configurationFilePath: configurationFilePath,
21 | numOfShards: numberOfShards,
22 | }
23 |
24 | err := bop.initNodes(observers)
25 | if err != nil {
26 | return nil, err
27 | }
28 |
29 | return &simpleNodesProvider{
30 | baseNodeProvider: bop,
31 | }, nil
32 | }
33 |
34 | // GetNodesByShardId will return a slice of the nodes for the given shard
35 | func (snp *simpleNodesProvider) GetNodesByShardId(shardId uint32, dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error) {
36 | snp.mutNodes.RLock()
37 | defer snp.mutNodes.RUnlock()
38 |
39 | return snp.getSyncedNodesForShardUnprotected(shardId, dataAvailability)
40 | }
41 |
42 | // GetAllNodes will return a slice containing all the nodes
43 | func (snp *simpleNodesProvider) GetAllNodes(dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error) {
44 | snp.mutNodes.RLock()
45 | defer snp.mutNodes.RUnlock()
46 |
47 | return snp.getSyncedNodesUnprotected(dataAvailability)
48 | }
49 |
50 | // IsInterfaceNil returns true if there is no value under the interface
51 | func (snp *simpleNodesProvider) IsInterfaceNil() bool {
52 | return snp == nil
53 | }
54 |
--------------------------------------------------------------------------------
/observer/testdata/config.toml:
--------------------------------------------------------------------------------
1 | # GeneralSettings section of the proxy server
2 | [GeneralSettings]
3 | # NumberOfShards represents the total number of shards from the network (excluding metachain)
4 | NumberOfShards = 3
5 |
6 | # List of Observers. If you want to define a metachain observer (needed for validator statistics route) use
7 | # shard id 4294967295
8 | [[Observers]]
9 | ShardId = 0
10 | Address = "observer-shard-0"
11 |
12 | [[Observers]]
13 | ShardId = 1
14 | Address = "observer-shard-1"
15 |
16 | [[Observers]]
17 | ShardId = 2
18 | Address = "observer-shard-2"
19 |
20 | [[Observers]]
21 | ShardId = 4294967295
22 | Address = "observer-shard-4294967295"
23 |
24 | [[FullHistoryNodes]]
25 | ShardId = 0
26 | Address = "full-history-observer-shard-0"
27 |
28 | [[FullHistoryNodes]]
29 | ShardId = 1
30 | Address = "full-history-observer-shard-1"
31 |
32 | [[FullHistoryNodes]]
33 | ShardId = 4294967295
34 | Address = "full-history-observer-shard-4294967295"
35 |
--------------------------------------------------------------------------------
/process/aboutInfoProcessor.go:
--------------------------------------------------------------------------------
1 | package process
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/multiversx/mx-chain-core-go/core/check"
8 | "github.com/multiversx/mx-chain-proxy-go/common"
9 | "github.com/multiversx/mx-chain-proxy-go/data"
10 | )
11 |
12 | const shortHashSize = 7
13 |
14 | type aboutProcessor struct {
15 | baseProc Processor
16 | commitID string
17 | appVersion string
18 | }
19 |
20 | // NewAboutProcessor creates a new instance of about processor
21 | func NewAboutProcessor(baseProc Processor, appVersion string, commit string) (*aboutProcessor, error) {
22 | if check.IfNil(baseProc) {
23 | return nil, ErrNilCoreProcessor
24 | }
25 | if len(appVersion) == 0 {
26 | return nil, ErrEmptyAppVersionString
27 | }
28 | if len(commit) == 0 {
29 | return nil, ErrEmptyCommitString
30 | }
31 |
32 | return &aboutProcessor{
33 | baseProc: baseProc,
34 | commitID: commit,
35 | appVersion: appVersion,
36 | }, nil
37 | }
38 |
39 | // GetAboutInfo will return the app info parameters
40 | func (ap *aboutProcessor) GetAboutInfo() *data.GenericAPIResponse {
41 | commit := ap.commitID
42 | if ap.commitID != common.UndefinedCommitString {
43 | if len(commit) >= shortHashSize {
44 | commit = commit[:shortHashSize]
45 | }
46 | }
47 |
48 | aboutInfo := &data.AboutInfo{
49 | AppVersion: ap.appVersion,
50 | CommitID: commit,
51 | }
52 |
53 | resp := &data.GenericAPIResponse{
54 | Data: aboutInfo,
55 | Error: "",
56 | Code: data.ReturnCodeSuccess,
57 | }
58 |
59 | return resp
60 | }
61 |
62 | // GetNodesVersions will return the versions of the nodes behind proxy
63 | func (ap *aboutProcessor) GetNodesVersions() (*data.GenericAPIResponse, error) {
64 | versionsMap := make(map[uint32][]string)
65 | allObservers, err := ap.baseProc.GetAllObservers(data.AvailabilityRecent)
66 | if err != nil {
67 | return nil, err
68 | }
69 |
70 | for _, observer := range allObservers {
71 | nodeVersion, err := ap.getNodeAppVersion(observer.Address)
72 | if err != nil {
73 | return nil, err
74 | }
75 |
76 | versionsMap[observer.ShardId] = append(versionsMap[observer.ShardId], nodeVersion)
77 | }
78 |
79 | return &data.GenericAPIResponse{
80 | Data: data.NodesVersionProxyResponseData{
81 | Versions: versionsMap,
82 | },
83 | Error: "",
84 | Code: data.ReturnCodeSuccess,
85 | }, nil
86 | }
87 |
88 | func (ap *aboutProcessor) getNodeAppVersion(observerAddress string) (string, error) {
89 | var versionResponse data.NodeVersionAPIResponse
90 | code, err := ap.baseProc.CallGetRestEndPoint(observerAddress, NodeStatusPath, &versionResponse)
91 | if code != http.StatusOK {
92 | return "", fmt.Errorf("invalid return code %d", code)
93 | }
94 |
95 | if err != nil {
96 | return "", err
97 | }
98 |
99 | if len(versionResponse.Error) > 0 {
100 | return "", fmt.Errorf("%w while extracting the app version", err)
101 | }
102 |
103 | return versionResponse.Data.Metrics.Version, nil
104 | }
105 |
--------------------------------------------------------------------------------
/process/blocksProcessor.go:
--------------------------------------------------------------------------------
1 | package process
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/multiversx/mx-chain-core-go/core/check"
7 | "github.com/multiversx/mx-chain-core-go/data/api"
8 | "github.com/multiversx/mx-chain-proxy-go/common"
9 | "github.com/multiversx/mx-chain-proxy-go/data"
10 | )
11 |
12 | const (
13 | blockByRoundPath = "/block/by-round"
14 | )
15 |
16 | // BlocksProcessor handles blocks retrieving from all shards
17 | type BlocksProcessor struct {
18 | proc Processor
19 | }
20 |
21 | // NewBlocksProcessor creates a new block processor
22 | func NewBlocksProcessor(proc Processor) (*BlocksProcessor, error) {
23 | if check.IfNil(proc) {
24 | return nil, ErrNilCoreProcessor
25 | }
26 |
27 | return &BlocksProcessor{
28 | proc: proc,
29 | }, nil
30 | }
31 |
32 | // GetBlocksByRound return all blocks(from all shards) by a specific round. For each shard, a block is requested
33 | // (from only one observer) and added in a slice of blocks => should have max blocks = no of shards.
34 | // If there are more observers in a shard which can be queried for a block by round, we get the block from
35 | // the first one which responds (no sanity checks are performed)
36 | func (bp *BlocksProcessor) GetBlocksByRound(round uint64, options common.BlockQueryOptions) (*data.BlocksApiResponse, error) {
37 | shardIDs := bp.proc.GetShardIDs()
38 | ret := &data.BlocksApiResponse{
39 | Data: data.BlocksApiResponsePayload{
40 | Blocks: make([]*api.Block, 0, len(shardIDs)),
41 | },
42 | }
43 |
44 | path := common.BuildUrlWithBlockQueryOptions(fmt.Sprintf("%s/%d", blockByRoundPath, round), options)
45 |
46 | for _, shardID := range shardIDs {
47 | observers, err := bp.proc.GetObservers(shardID, data.AvailabilityAll)
48 | if err != nil {
49 | return nil, err
50 | }
51 |
52 | for _, observer := range observers {
53 | block, err := bp.getBlockFromObserver(observer, path)
54 | if err != nil {
55 | log.Error("block request failed", "shard id", observer.ShardId, "observer", observer.Address, "error", err.Error())
56 | continue
57 | }
58 |
59 | log.Info("block requested successfully", "shard id", observer.ShardId, "observer", observer.Address, "round", round)
60 | ret.Data.Blocks = append(ret.Data.Blocks, block)
61 | break
62 | }
63 | }
64 |
65 | return ret, nil
66 | }
67 |
68 | func (bp *BlocksProcessor) getBlockFromObserver(observer *data.NodeData, path string) (*api.Block, error) {
69 | var response data.BlockApiResponse
70 |
71 | _, err := bp.proc.CallGetRestEndPoint(observer.Address, path, &response)
72 | if err != nil {
73 | return nil, err
74 | }
75 |
76 | return &response.Data.Block, nil
77 | }
78 |
--------------------------------------------------------------------------------
/process/cache/errors.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import "errors"
4 |
5 | // ErrNilHeartbeatsInCache signals that the heartbeats response stored in cache is nil
6 | var ErrNilHeartbeatsInCache = errors.New("nil heartbeat response in cache")
7 |
8 | // ErrNilHeartbeatsToStoreInCache signals that the provided heartbeats response is nil
9 | var ErrNilHeartbeatsToStoreInCache = errors.New("nil heartbeat response to store in cache")
10 |
11 | // ErrNilValidatorStatsInCache signals that the heartbeats response stored in cache is nil
12 | var ErrNilValidatorStatsInCache = errors.New("nil validator statistics response in cache")
13 |
14 | // ErrNilValidatorStatsToStoreInCache signals that the provided validator statistics is nil
15 | var ErrNilValidatorStatsToStoreInCache = errors.New("nil validator statistics to store in cache")
16 |
17 | // ErrNilGenericApiResponseInCache signals that the generic api response stored in cache is nil
18 | var ErrNilGenericApiResponseInCache = errors.New("nil generic api response in cache")
19 |
20 | // ErrNilGenericApiResponseToStoreInCache signals that the provided generic api response is nil
21 | var ErrNilGenericApiResponseToStoreInCache = errors.New("nil generic api response to store in cache")
22 |
--------------------------------------------------------------------------------
/process/cache/export_test.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import "github.com/multiversx/mx-chain-proxy-go/data"
4 |
5 | func (hmc *HeartbeatMemoryCacher) GetStoredHbts() []data.PubKeyHeartbeat {
6 | hmc.mutHeartbeats.RLock()
7 | defer hmc.mutHeartbeats.RUnlock()
8 |
9 | return hmc.storedHeartbeats
10 | }
11 |
12 | func (hmc *HeartbeatMemoryCacher) SetStoredHbts(hbts []data.PubKeyHeartbeat) {
13 | hmc.mutHeartbeats.Lock()
14 | hmc.storedHeartbeats = hbts
15 | hmc.mutHeartbeats.Unlock()
16 | }
17 |
18 | func (vsmc *validatorsStatsMemoryCacher) GetStoredValStats() map[string]*data.ValidatorApiResponse {
19 | vsmc.mutValidatorsStatss.RLock()
20 | defer vsmc.mutValidatorsStatss.RUnlock()
21 |
22 | return vsmc.storedValidatorsStats
23 | }
24 |
25 | func (vsmc *validatorsStatsMemoryCacher) SetStoredValStats(valStats map[string]*data.ValidatorApiResponse) {
26 | vsmc.mutValidatorsStatss.Lock()
27 | vsmc.storedValidatorsStats = valStats
28 | vsmc.mutValidatorsStatss.Unlock()
29 | }
30 |
31 | func (garmc *genericApiResponseMemoryCacher) GetGenericApiResponse() *data.GenericAPIResponse {
32 | garmc.mutGenericApiResponse.RLock()
33 | defer garmc.mutGenericApiResponse.RUnlock()
34 |
35 | return garmc.storedResponse
36 | }
37 |
38 | func (garmc *genericApiResponseMemoryCacher) SetGenericApiResponse(response *data.GenericAPIResponse) {
39 | garmc.mutGenericApiResponse.Lock()
40 | garmc.storedResponse = response
41 | garmc.mutGenericApiResponse.Unlock()
42 | }
43 |
--------------------------------------------------------------------------------
/process/cache/genericApiResponseMemCacher.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "sync"
5 |
6 | "github.com/multiversx/mx-chain-proxy-go/data"
7 | )
8 |
9 | // genericApiResponseMemoryCacher will handle caching the ValidatorsStatss response
10 | type genericApiResponseMemoryCacher struct {
11 | storedResponse *data.GenericAPIResponse
12 | mutGenericApiResponse sync.RWMutex
13 | }
14 |
15 | // NewGenericApiResponseMemoryCacher will return a new instance of genericApiResponseMemoryCacher
16 | func NewGenericApiResponseMemoryCacher() *genericApiResponseMemoryCacher {
17 | return &genericApiResponseMemoryCacher{
18 | storedResponse: nil,
19 | mutGenericApiResponse: sync.RWMutex{},
20 | }
21 | }
22 |
23 | // Load will return the generic api response stored in cache (if found)
24 | func (garmc *genericApiResponseMemoryCacher) Load() (*data.GenericAPIResponse, error) {
25 | garmc.mutGenericApiResponse.RLock()
26 | defer garmc.mutGenericApiResponse.RUnlock()
27 |
28 | if garmc.storedResponse == nil {
29 | return nil, ErrNilGenericApiResponseInCache
30 | }
31 |
32 | return garmc.storedResponse, nil
33 | }
34 |
35 | // Store will update the generic api response response in cache
36 | func (garmc *genericApiResponseMemoryCacher) Store(genericApiResponse *data.GenericAPIResponse) {
37 | garmc.mutGenericApiResponse.Lock()
38 | garmc.storedResponse = genericApiResponse
39 | garmc.mutGenericApiResponse.Unlock()
40 | }
41 |
42 | // IsInterfaceNil will return true if there is no value under the interface
43 | func (garmc *genericApiResponseMemoryCacher) IsInterfaceNil() bool {
44 | return garmc == nil
45 | }
46 |
--------------------------------------------------------------------------------
/process/cache/genericApiResponseMemCacher_test.go:
--------------------------------------------------------------------------------
1 | package cache_test
2 |
3 | import (
4 | "sync"
5 | "testing"
6 | "time"
7 |
8 | "github.com/multiversx/mx-chain-proxy-go/data"
9 | "github.com/multiversx/mx-chain-proxy-go/process/cache"
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | func TestNewGenericApiResponseMemoryCacher(t *testing.T) {
14 | t.Parallel()
15 |
16 | mc := cache.NewGenericApiResponseMemoryCacher()
17 | assert.NotNil(t, mc)
18 | assert.False(t, mc.IsInterfaceNil())
19 | }
20 |
21 | func TestGenericApiResponseMemoryCacher_StoreNilValStatsShouldNotPanic(t *testing.T) {
22 | t.Parallel()
23 |
24 | defer func() {
25 | r := recover()
26 | assert.Nil(t, r)
27 | }()
28 | mc := cache.NewGenericApiResponseMemoryCacher()
29 |
30 | mc.Store(nil)
31 | }
32 |
33 | func TestGenericApiResponseMemoryCacher_StoreShouldWork(t *testing.T) {
34 | t.Parallel()
35 |
36 | mc := cache.NewGenericApiResponseMemoryCacher()
37 | apiResp := &data.GenericAPIResponse{
38 | Data: "test data",
39 | }
40 |
41 | mc.Store(apiResp)
42 | assert.Equal(t, apiResp, mc.GetGenericApiResponse())
43 | }
44 |
45 | func TestGenericApiResponseMemoryCacher_LoadNilStoredShouldErr(t *testing.T) {
46 | t.Parallel()
47 |
48 | mc := cache.NewGenericApiResponseMemoryCacher()
49 |
50 | apiResp, err := mc.Load()
51 | assert.Nil(t, apiResp)
52 | assert.Equal(t, cache.ErrNilGenericApiResponseInCache, err)
53 | }
54 |
55 | func TestGenericApiResponseMemoryCacher_LoadShouldWork(t *testing.T) {
56 | t.Parallel()
57 |
58 | mc := cache.NewGenericApiResponseMemoryCacher()
59 | apiResp := &data.GenericAPIResponse{
60 | Data: "test data 2",
61 | }
62 |
63 | mc.SetGenericApiResponse(apiResp)
64 |
65 | restoredApiResp, err := mc.Load()
66 | assert.NoError(t, err)
67 | assert.Equal(t, apiResp, restoredApiResp)
68 | }
69 |
70 | func TestGenericApiResponseMemoryCacher_ConcurrencySafe(t *testing.T) {
71 | t.Parallel()
72 |
73 | // here we should test if parallel accesses to the cache component leads to a race condition
74 | // if the mutex from the component is removed then test should fail when run with -race flag
75 | mc := cache.NewGenericApiResponseMemoryCacher()
76 | genericApiRespToStore := &data.GenericAPIResponse{
77 | Data: "test data concurrent test",
78 | }
79 |
80 | wg := sync.WaitGroup{}
81 | wg.Add(2)
82 |
83 | stopGoRoutinesEvent1 := time.After(1000 * time.Millisecond)
84 | stopGoRoutinesEvent2 := time.After(1100 * time.Millisecond)
85 |
86 | go func() {
87 | for {
88 | select {
89 | case <-stopGoRoutinesEvent1:
90 | wg.Done()
91 | break
92 | default:
93 | mc.Store(genericApiRespToStore)
94 | time.Sleep(5 * time.Millisecond)
95 | }
96 | }
97 | }()
98 |
99 | go func() {
100 | for {
101 | select {
102 | case <-stopGoRoutinesEvent2:
103 | wg.Done()
104 | break
105 | default:
106 | _, _ = mc.Load()
107 | time.Sleep(5 * time.Millisecond)
108 | }
109 | }
110 | }()
111 |
112 | wg.Wait()
113 | }
114 |
--------------------------------------------------------------------------------
/process/cache/heartbeatMemCacher.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "sync"
5 |
6 | "github.com/multiversx/mx-chain-proxy-go/data"
7 | )
8 |
9 | // HeartbeatMemoryCacher will handle caching the heartbeats response
10 | type HeartbeatMemoryCacher struct {
11 | storedHeartbeats []data.PubKeyHeartbeat
12 | mutHeartbeats sync.RWMutex
13 | }
14 |
15 | // NewHeartbeatMemoryCacher will return a new instance of HeartbeatMemoryCacher
16 | func NewHeartbeatMemoryCacher() *HeartbeatMemoryCacher {
17 | return &HeartbeatMemoryCacher{
18 | storedHeartbeats: nil,
19 | mutHeartbeats: sync.RWMutex{},
20 | }
21 | }
22 |
23 | // LoadHeartbeats will return the heartbeats response stored in cache (if found)
24 | func (hmc *HeartbeatMemoryCacher) LoadHeartbeats() (*data.HeartbeatResponse, error) {
25 | hmc.mutHeartbeats.RLock()
26 | defer hmc.mutHeartbeats.RUnlock()
27 |
28 | if hmc.storedHeartbeats == nil {
29 | return nil, ErrNilHeartbeatsInCache
30 | }
31 |
32 | return &data.HeartbeatResponse{Heartbeats: hmc.storedHeartbeats}, nil
33 | }
34 |
35 | // StoreHeartbeats will update the stored heartbeats response in cache
36 | func (hmc *HeartbeatMemoryCacher) StoreHeartbeats(hbts *data.HeartbeatResponse) error {
37 | if hbts == nil {
38 | return ErrNilHeartbeatsToStoreInCache
39 | }
40 |
41 | hmc.mutHeartbeats.Lock()
42 | hmc.storedHeartbeats = hbts.Heartbeats
43 | hmc.mutHeartbeats.Unlock()
44 |
45 | return nil
46 | }
47 |
48 | // IsInterfaceNil will return true if there is no value under the interface
49 | func (hmc *HeartbeatMemoryCacher) IsInterfaceNil() bool {
50 | return hmc == nil
51 | }
52 |
--------------------------------------------------------------------------------
/process/cache/heartbeatMemCacher_test.go:
--------------------------------------------------------------------------------
1 | package cache_test
2 |
3 | import (
4 | "sync"
5 | "testing"
6 | "time"
7 |
8 | "github.com/multiversx/mx-chain-proxy-go/data"
9 | "github.com/multiversx/mx-chain-proxy-go/process/cache"
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | func TestNewHeartbeatMemoryCacher(t *testing.T) {
14 | t.Parallel()
15 |
16 | mc := cache.NewHeartbeatMemoryCacher()
17 | assert.NotNil(t, mc)
18 | assert.False(t, mc.IsInterfaceNil())
19 | }
20 |
21 | func TestHeartbeatMemoryCacher_StoreHeartbeatsNilHbtsShouldErr(t *testing.T) {
22 | t.Parallel()
23 |
24 | mc := cache.NewHeartbeatMemoryCacher()
25 |
26 | err := mc.StoreHeartbeats(nil)
27 | assert.Equal(t, cache.ErrNilHeartbeatsToStoreInCache, err)
28 | }
29 |
30 | func TestHeartbeatMemoryCacher_StoreHeartbeatsShouldWork(t *testing.T) {
31 | t.Parallel()
32 |
33 | mc := cache.NewHeartbeatMemoryCacher()
34 | hbts := []data.PubKeyHeartbeat{
35 | {
36 | NodeDisplayName: "node1",
37 | },
38 | {
39 | NodeDisplayName: "node2",
40 | },
41 | }
42 | hbtsResp := data.HeartbeatResponse{Heartbeats: hbts}
43 | err := mc.StoreHeartbeats(&hbtsResp)
44 |
45 | assert.Nil(t, err)
46 | assert.Equal(t, hbts, mc.GetStoredHbts())
47 | }
48 |
49 | func TestHeartbeatMemoryCacher_LoadHeartbeatsNilStoredHbtsShouldErr(t *testing.T) {
50 | t.Parallel()
51 |
52 | mc := cache.NewHeartbeatMemoryCacher()
53 |
54 | hbts, err := mc.LoadHeartbeats()
55 | assert.Nil(t, hbts)
56 | assert.Equal(t, cache.ErrNilHeartbeatsInCache, err)
57 | }
58 |
59 | func TestHeartbeatMemoryCacher_LoadHeartbeatsShouldWork(t *testing.T) {
60 | t.Parallel()
61 |
62 | mc := cache.NewHeartbeatMemoryCacher()
63 | hbts := []data.PubKeyHeartbeat{
64 | {
65 | NodeDisplayName: "node1",
66 | },
67 | {
68 | NodeDisplayName: "node2",
69 | },
70 | }
71 |
72 | mc.SetStoredHbts(hbts)
73 |
74 | restoredHbtsResp, err := mc.LoadHeartbeats()
75 | assert.Nil(t, err)
76 | assert.Equal(t, hbts, restoredHbtsResp.Heartbeats)
77 | }
78 |
79 | func TestHeartbeatMemoryCacher_ConcurrencySafe(t *testing.T) {
80 | t.Parallel()
81 |
82 | // here we should test if parallel accesses to the cache component leads to a race condition
83 | // if the mutex from the component is removed then test should fail when run with -race flag
84 | mc := cache.NewHeartbeatMemoryCacher()
85 | hbtsToStore := data.HeartbeatResponse{Heartbeats: []data.PubKeyHeartbeat{{NodeDisplayName: "node1"}}}
86 |
87 | wg := sync.WaitGroup{}
88 | wg.Add(2)
89 |
90 | stopGoRoutinesEvent1 := time.After(1000 * time.Millisecond)
91 | stopGoRoutinesEvent2 := time.After(1100 * time.Millisecond)
92 |
93 | go func() {
94 | for {
95 | select {
96 | case <-stopGoRoutinesEvent1:
97 | wg.Done()
98 | break
99 | default:
100 | _ = mc.StoreHeartbeats(&hbtsToStore)
101 | time.Sleep(5 * time.Millisecond)
102 | }
103 | }
104 | }()
105 |
106 | go func() {
107 | for {
108 | select {
109 | case <-stopGoRoutinesEvent2:
110 | wg.Done()
111 | break
112 | default:
113 | _, _ = mc.LoadHeartbeats()
114 | time.Sleep(5 * time.Millisecond)
115 | }
116 | }
117 | }()
118 |
119 | wg.Wait()
120 | }
121 |
--------------------------------------------------------------------------------
/process/cache/validatorStatsMemCacher.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "sync"
5 |
6 | "github.com/multiversx/mx-chain-proxy-go/data"
7 | )
8 |
9 | // validatorsStatsMemoryCacher will handle caching the ValidatorsStatss response
10 | type validatorsStatsMemoryCacher struct {
11 | storedValidatorsStats map[string]*data.ValidatorApiResponse
12 | mutValidatorsStatss sync.RWMutex
13 | }
14 |
15 | // NewValidatorsStatsMemoryCacher will return a new instance of validatorsStatsMemoryCacher
16 | func NewValidatorsStatsMemoryCacher() *validatorsStatsMemoryCacher {
17 | return &validatorsStatsMemoryCacher{
18 | storedValidatorsStats: nil,
19 | mutValidatorsStatss: sync.RWMutex{},
20 | }
21 | }
22 |
23 | // LoadValStats will return the ValidatorsStats response stored in cache (if found)
24 | func (vsmc *validatorsStatsMemoryCacher) LoadValStats() (map[string]*data.ValidatorApiResponse, error) {
25 | vsmc.mutValidatorsStatss.RLock()
26 | defer vsmc.mutValidatorsStatss.RUnlock()
27 |
28 | if vsmc.storedValidatorsStats == nil {
29 | return nil, ErrNilValidatorStatsInCache
30 | }
31 |
32 | return vsmc.storedValidatorsStats, nil
33 | }
34 |
35 | // StoreValStats will update the stored ValidatorsStatss response in cache
36 | func (vsmc *validatorsStatsMemoryCacher) StoreValStats(valStats map[string]*data.ValidatorApiResponse) error {
37 | if valStats == nil {
38 | return ErrNilValidatorStatsToStoreInCache
39 | }
40 |
41 | vsmc.mutValidatorsStatss.Lock()
42 | vsmc.storedValidatorsStats = valStats
43 | vsmc.mutValidatorsStatss.Unlock()
44 |
45 | return nil
46 | }
47 |
48 | // IsInterfaceNil will return true if there is no value under the interface
49 | func (vsmc *validatorsStatsMemoryCacher) IsInterfaceNil() bool {
50 | return vsmc == nil
51 | }
52 |
--------------------------------------------------------------------------------
/process/cache/validatorStatsMemCacher_test.go:
--------------------------------------------------------------------------------
1 | package cache_test
2 |
3 | import (
4 | "sync"
5 | "testing"
6 | "time"
7 |
8 | "github.com/multiversx/mx-chain-proxy-go/data"
9 | "github.com/multiversx/mx-chain-proxy-go/process/cache"
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | func TestNewValidatorsStatsMemoryCacher(t *testing.T) {
14 | t.Parallel()
15 |
16 | mc := cache.NewValidatorsStatsMemoryCacher()
17 | assert.NotNil(t, mc)
18 | assert.False(t, mc.IsInterfaceNil())
19 | }
20 |
21 | func TestValidatorsStatsMemoryCacher_StoreNilValStatsShouldErr(t *testing.T) {
22 | t.Parallel()
23 |
24 | mc := cache.NewValidatorsStatsMemoryCacher()
25 |
26 | err := mc.StoreValStats(nil)
27 | assert.Equal(t, cache.ErrNilValidatorStatsToStoreInCache, err)
28 | }
29 |
30 | func TestValidatorsStatsMemoryCacher_StoreShouldWork(t *testing.T) {
31 | t.Parallel()
32 |
33 | mc := cache.NewValidatorsStatsMemoryCacher()
34 | valStats := map[string]*data.ValidatorApiResponse{
35 | "pubk1": {TempRating: 0.5},
36 | }
37 | err := mc.StoreValStats(valStats)
38 |
39 | assert.Nil(t, err)
40 | assert.Equal(t, valStats, mc.GetStoredValStats())
41 | }
42 |
43 | func TestValidatorsStatsMemoryCacher_LoadNilStoredValStatsShouldErr(t *testing.T) {
44 | t.Parallel()
45 |
46 | mc := cache.NewValidatorsStatsMemoryCacher()
47 |
48 | valStats, err := mc.LoadValStats()
49 | assert.Nil(t, valStats)
50 | assert.Equal(t, cache.ErrNilValidatorStatsInCache, err)
51 | }
52 |
53 | func TestValidatorsStatsMemoryCacher_LoadShouldWork(t *testing.T) {
54 | t.Parallel()
55 |
56 | mc := cache.NewValidatorsStatsMemoryCacher()
57 | valStats := map[string]*data.ValidatorApiResponse{
58 | "pubk1": {TempRating: 50.5},
59 | "pubk2": {TempRating: 50.6},
60 | }
61 |
62 | mc.SetStoredValStats(valStats)
63 |
64 | restoredValStatsResp, err := mc.LoadValStats()
65 | assert.NoError(t, err)
66 | assert.Equal(t, valStats, restoredValStatsResp)
67 | }
68 |
69 | func TestValidatorsStatsMemoryCacher_ConcurrencySafe(t *testing.T) {
70 | t.Parallel()
71 |
72 | // here we should test if parallel accesses to the cache component leads to a race condition
73 | // if the mutex from the component is removed then test should fail when run with -race flag
74 | mc := cache.NewValidatorsStatsMemoryCacher()
75 | valStatsToStore := map[string]*data.ValidatorApiResponse{
76 | "pubk1": {TempRating: 50.5},
77 | "pubk2": {TempRating: 50.6},
78 | }
79 |
80 | wg := sync.WaitGroup{}
81 | wg.Add(2)
82 |
83 | stopGoRoutinesEvent1 := time.After(1000 * time.Millisecond)
84 | stopGoRoutinesEvent2 := time.After(1100 * time.Millisecond)
85 |
86 | go func() {
87 | for {
88 | select {
89 | case <-stopGoRoutinesEvent1:
90 | wg.Done()
91 | break
92 | default:
93 | _ = mc.StoreValStats(valStatsToStore)
94 | time.Sleep(5 * time.Millisecond)
95 | }
96 | }
97 | }()
98 |
99 | go func() {
100 | for {
101 | select {
102 | case <-stopGoRoutinesEvent2:
103 | wg.Done()
104 | break
105 | default:
106 | _, _ = mc.LoadValStats()
107 | time.Sleep(5 * time.Millisecond)
108 | }
109 | }
110 | }()
111 |
112 | wg.Wait()
113 | }
114 |
--------------------------------------------------------------------------------
/process/database/common.go:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 |
7 | dataIndexer "github.com/multiversx/mx-chain-es-indexer-go/data"
8 | "github.com/multiversx/mx-chain-proxy-go/data"
9 | )
10 |
11 | func convertObjectToBlock(obj object) (*dataIndexer.Block, string, error) {
12 | h1 := obj["hits"].(object)["hits"].([]interface{})
13 | if len(h1) == 0 {
14 | return nil, "", errCannotFindBlockInDb
15 | }
16 | h2 := h1[0].(object)["_source"]
17 |
18 | h3 := h1[0].(object)["_id"]
19 | blockHash := fmt.Sprint(h3)
20 |
21 | marshalizedBlock, _ := json.Marshal(h2)
22 | var block dataIndexer.Block
23 | err := json.Unmarshal(marshalizedBlock, &block)
24 | if err != nil {
25 | return nil, "", errCannotUnmarshalBlock
26 | }
27 |
28 | return &block, blockHash, nil
29 | }
30 |
31 | func convertObjectToTransactions(obj object) ([]data.DatabaseTransaction, error) {
32 | hits, ok := obj["hits"].(object)
33 | if !ok {
34 | return nil, errCannotGetTxsFromBody
35 | }
36 |
37 | txs := make([]data.DatabaseTransaction, 0)
38 | for _, h1 := range hits["hits"].([]interface{}) {
39 | h2 := h1.(object)["_source"]
40 |
41 | var tx data.DatabaseTransaction
42 | marshalizedTx, _ := json.Marshal(h2)
43 | err := json.Unmarshal(marshalizedTx, &tx)
44 | if err != nil {
45 | continue
46 | }
47 |
48 | h3 := h1.(object)["_id"]
49 | txHash := fmt.Sprint(h3)
50 | tx.Hash = txHash
51 | tx.Fee = tx.CalculateFee()
52 | txs = append(txs, tx)
53 | }
54 | return txs, nil
55 | }
56 |
--------------------------------------------------------------------------------
/process/database/errors.go:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | import "errors"
4 |
5 | var errCannotFindBlockInDb = errors.New("cannot find blocks in database")
6 | var errCannotUnmarshalBlock = errors.New("cannot unmarshal block")
7 | var errCannotGetTxsFromBody = errors.New("cannot get transactions from decoded body")
8 |
--------------------------------------------------------------------------------
/process/database/queries.go:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | )
8 |
9 | type object = map[string]interface{}
10 |
11 | func encodeQuery(query object) (bytes.Buffer, error) {
12 | var buff bytes.Buffer
13 | if err := json.NewEncoder(&buff).Encode(query); err != nil {
14 | return bytes.Buffer{}, fmt.Errorf("error encoding query: %w", err)
15 | }
16 |
17 | return buff, nil
18 | }
19 |
20 | func blockByNonceAndShardIDQuery(nonce uint64, shardID uint32) object {
21 | return object{
22 | "query": object{
23 | "bool": object{
24 | "must": []interface{}{
25 | object{
26 | "match": object{
27 | "nonce": fmt.Sprintf("%d", nonce),
28 | },
29 | },
30 | object{
31 | "match": object{
32 | "shardId": fmt.Sprintf("%d", shardID),
33 | },
34 | },
35 | },
36 | },
37 | },
38 | }
39 | }
40 |
41 | func blockByHashQuery(hash string) object {
42 | return object{
43 | "query": object{
44 | "match": object{
45 | "_id": hash,
46 | },
47 | },
48 | }
49 | }
50 |
51 | func txsByMiniblockHashQuery(hash string) object {
52 | return object{
53 | "query": object{
54 | "match": object{
55 | "miniBlockHash": hash,
56 | },
57 | },
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/process/disabled/epochStartNotifier.go:
--------------------------------------------------------------------------------
1 | package disabled
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-core-go/core"
5 | "github.com/multiversx/mx-chain-core-go/data"
6 | )
7 |
8 | // EpochStartNotifier represents a disabled struct that implements the EpochStartNotifier interface
9 | type EpochStartNotifier struct {
10 | }
11 |
12 | // RegisterNotifyHandler won't do anything as this is a disabled component
13 | func (e *EpochStartNotifier) RegisterNotifyHandler(_ core.EpochSubscriberHandler) {
14 | }
15 |
16 | // CurrentEpoch returns 0 as this is a disabled component
17 | func (e *EpochStartNotifier) CurrentEpoch() uint32 {
18 | return 0
19 | }
20 |
21 | // CheckEpoch won't do anything as this a disabled component
22 | func (e *EpochStartNotifier) CheckEpoch(_ data.HeaderHandler) {
23 | }
24 |
25 | // IsInterfaceNil returns true if there is no value under the interface
26 | func (e *EpochStartNotifier) IsInterfaceNil() bool {
27 | return e == nil
28 | }
29 |
--------------------------------------------------------------------------------
/process/economicMetrics.go:
--------------------------------------------------------------------------------
1 | package process
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/multiversx/mx-chain-core-go/core"
8 | "github.com/multiversx/mx-chain-proxy-go/data"
9 | )
10 |
11 | // EconomicsDataPath represents the path where an observer exposes his economics data
12 | const EconomicsDataPath = "/network/economics"
13 |
14 | const thresholdCountConsecutiveFails = 10
15 |
16 | // GetEconomicsDataMetrics will return the economic metrics from cache
17 | func (nsp *NodeStatusProcessor) GetEconomicsDataMetrics() (*data.GenericAPIResponse, error) {
18 | return nsp.economicMetricsCacher.Load()
19 | }
20 |
21 | func (nsp *NodeStatusProcessor) getEconomicsDataMetricsFromApi() (*data.GenericAPIResponse, error) {
22 | metaObservers, err := nsp.proc.GetObservers(core.MetachainShardId, data.AvailabilityRecent)
23 | if err != nil {
24 | return nil, err
25 | }
26 |
27 | return nsp.getEconomicsDataMetrics(metaObservers)
28 | }
29 |
30 | func (nsp *NodeStatusProcessor) getEconomicsDataMetrics(observers []*data.NodeData) (*data.GenericAPIResponse, error) {
31 | responseNetworkMetrics := data.GenericAPIResponse{}
32 | for _, observer := range observers {
33 |
34 | _, err := nsp.proc.CallGetRestEndPoint(observer.Address, EconomicsDataPath, &responseNetworkMetrics)
35 | if err != nil {
36 | log.Error("economics data request", "observer", observer.Address, "error", err.Error())
37 | continue
38 | }
39 |
40 | log.Info("economics data request", "shard id", observer.ShardId, "observer", observer.Address)
41 | return &responseNetworkMetrics, nil
42 | }
43 |
44 | return nil, WrapObserversError(responseNetworkMetrics.Error)
45 | }
46 |
47 | // StartCacheUpdate will update the economic metrics cache at a given time
48 | func (nsp *NodeStatusProcessor) StartCacheUpdate() {
49 | if nsp.cancelFunc != nil {
50 | log.Error("NodeStatusProcessor - cache update already started")
51 | return
52 | }
53 |
54 | var ctx context.Context
55 | ctx, nsp.cancelFunc = context.WithCancel(context.Background())
56 |
57 | go func(ctx context.Context) {
58 | timer := time.NewTimer(nsp.cacheValidityDuration)
59 | defer timer.Stop()
60 |
61 | countConsecutiveFails := 0
62 | nsp.handleCacheUpdate(&countConsecutiveFails)
63 |
64 | for {
65 | timer.Reset(nsp.cacheValidityDuration)
66 |
67 | select {
68 | case <-timer.C:
69 | nsp.handleCacheUpdate(&countConsecutiveFails)
70 |
71 | case <-ctx.Done():
72 | log.Debug("finishing NodeStatusProcessor cache update...")
73 | return
74 | }
75 | }
76 | }(ctx)
77 | }
78 |
79 | func (nsp *NodeStatusProcessor) handleCacheUpdate(countConsecutiveFails *int) {
80 | economicMetrics, err := nsp.getEconomicsDataMetricsFromApi()
81 | if err != nil {
82 | *countConsecutiveFails++
83 | log.Warn("economic metrics: get from API", "error", err.Error())
84 | }
85 |
86 | if *countConsecutiveFails >= thresholdCountConsecutiveFails {
87 | nsp.economicMetricsCacher.Store(nil)
88 | }
89 |
90 | if economicMetrics != nil {
91 | *countConsecutiveFails = 0
92 | nsp.economicMetricsCacher.Store(economicMetrics)
93 | }
94 | }
95 |
96 | // Close will handle the closing of the cache update go routine
97 | func (nsp *NodeStatusProcessor) Close() error {
98 | if nsp.cancelFunc != nil {
99 | nsp.cancelFunc()
100 | }
101 |
102 | return nil
103 | }
104 |
--------------------------------------------------------------------------------
/process/export_test.go:
--------------------------------------------------------------------------------
1 | package process
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/multiversx/mx-chain-core-go/data/transaction"
7 | proxyData "github.com/multiversx/mx-chain-proxy-go/data"
8 | )
9 |
10 | // SetDelayForCheckingNodesSyncState -
11 | func (bp *BaseProcessor) SetDelayForCheckingNodesSyncState(delay time.Duration) {
12 | bp.delayForCheckingNodesSyncState = delay
13 | }
14 |
15 | // SetNodeStatusFetcher -
16 | func (bp *BaseProcessor) SetNodeStatusFetcher(fetcher func(url string) (*proxyData.NodeStatusAPIResponse, int, error)) {
17 | bp.nodeStatusFetcher = fetcher
18 | }
19 |
20 | // ComputeTokenStorageKey -
21 | func ComputeTokenStorageKey(tokenID string, nonce uint64) string {
22 | return computeTokenStorageKey(tokenID, nonce)
23 | }
24 |
25 | // GetShortHashSize -
26 | func GetShortHashSize() int {
27 | return shortHashSize
28 | }
29 |
30 | // ComputeTransactionStatus -
31 | func (tp *TransactionProcessor) ComputeTransactionStatus(tx *transaction.ApiTransactionResult, withResults bool) *proxyData.ProcessStatusResponse {
32 | return tp.computeTransactionStatus(tx, withResults)
33 | }
34 |
35 | // CheckIfFailed -
36 | func CheckIfFailed(logs []*transaction.ApiLogs) (bool, string) {
37 | return checkIfFailed(logs)
38 | }
39 |
--------------------------------------------------------------------------------
/process/factory/disabledFaucetProcessor.go:
--------------------------------------------------------------------------------
1 | package factory
2 |
3 | import (
4 | "errors"
5 | "math/big"
6 |
7 | "github.com/multiversx/mx-chain-crypto-go"
8 | "github.com/multiversx/mx-chain-proxy-go/data"
9 | )
10 |
11 | var errNotEnabled = errors.New("faucet not enabled")
12 |
13 | type disabledFaucetProcessor struct {
14 | }
15 |
16 | // IsEnabled will return false
17 | func (d *disabledFaucetProcessor) IsEnabled() bool {
18 | return false
19 | }
20 |
21 | // SenderDetailsFromPem will return an error that signals that faucet is not enabled
22 | func (d *disabledFaucetProcessor) SenderDetailsFromPem(_ string) (crypto.PrivateKey, string, error) {
23 | return nil, "", errNotEnabled
24 | }
25 |
26 | // GenerateTxForSendUserFunds will return an error that signals that faucet is not enabled
27 | func (d *disabledFaucetProcessor) GenerateTxForSendUserFunds(
28 | _ crypto.PrivateKey,
29 | _ string,
30 | _ uint64,
31 | _ string,
32 | _ *big.Int,
33 | _ *data.NetworkConfig,
34 | ) (*data.Transaction, error) {
35 | return nil, errNotEnabled
36 | }
37 |
--------------------------------------------------------------------------------
/process/factory/faucetProcessorFactory.go:
--------------------------------------------------------------------------------
1 | package factory
2 |
3 | import (
4 | "math/big"
5 |
6 | "github.com/multiversx/mx-chain-core-go/core"
7 | logger "github.com/multiversx/mx-chain-logger-go"
8 | "github.com/multiversx/mx-chain-proxy-go/common"
9 | "github.com/multiversx/mx-chain-proxy-go/facade"
10 | "github.com/multiversx/mx-chain-proxy-go/faucet"
11 | "github.com/multiversx/mx-chain-proxy-go/process"
12 | )
13 |
14 | var log = logger.GetOrCreate("process/factory")
15 |
16 | // CreateFaucetProcessor will return the faucet processor needed for current settings
17 | func CreateFaucetProcessor(
18 | baseProc Processor,
19 | shardCoordinator common.Coordinator,
20 | defaultFaucetValue *big.Int,
21 | pubKeyConverter core.PubkeyConverter,
22 | pemFileLocation string,
23 | ) (facade.FaucetProcessor, error) {
24 | if defaultFaucetValue.Cmp(big.NewInt(0)) == 0 {
25 | log.Info("faucet is disabled")
26 | return &disabledFaucetProcessor{}, nil
27 | }
28 |
29 | log.Info("faucet is enabled", "pem file location", pemFileLocation)
30 | privKeysLoader, err := faucet.NewPrivateKeysLoader(shardCoordinator, pemFileLocation, pubKeyConverter)
31 | if err != nil {
32 | return nil, err
33 | }
34 |
35 | return process.NewFaucetProcessor(baseProc, privKeysLoader, defaultFaucetValue, pubKeyConverter)
36 | }
37 |
--------------------------------------------------------------------------------
/process/factory/interface.go:
--------------------------------------------------------------------------------
1 | package factory
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-core-go/core"
5 | "github.com/multiversx/mx-chain-crypto-go"
6 | "github.com/multiversx/mx-chain-proxy-go/common"
7 | "github.com/multiversx/mx-chain-proxy-go/data"
8 | "github.com/multiversx/mx-chain-proxy-go/observer"
9 | )
10 |
11 | // Processor defines what a processor should be able to do
12 | type Processor interface {
13 | ComputeShardId(addressBuff []byte) (uint32, error)
14 | CallGetRestEndPoint(address string, path string, value interface{}) (int, error)
15 | CallPostRestEndPoint(address string, path string, data interface{}, response interface{}) (int, error)
16 | GetObserversOnePerShard(dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error)
17 | GetShardIDs() []uint32
18 | GetFullHistoryNodesOnePerShard(dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error)
19 | GetObservers(shardID uint32, dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error)
20 | GetAllObservers(dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error)
21 | GetFullHistoryNodes(shardID uint32, dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error)
22 | GetAllFullHistoryNodes(dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error)
23 | GetShardCoordinator() common.Coordinator
24 | GetPubKeyConverter() core.PubkeyConverter
25 | GetObserverProvider() observer.NodesProviderHandler
26 | GetFullHistoryNodesProvider() observer.NodesProviderHandler
27 | IsInterfaceNil() bool
28 | }
29 |
30 | // PrivateKeysLoaderHandler defines what a component which handles loading of the private keys file should do
31 | type PrivateKeysLoaderHandler interface {
32 | PrivateKeysByShard() (map[uint32][]crypto.PrivateKey, error)
33 | }
34 |
--------------------------------------------------------------------------------
/process/factory/transactionProcessorFactory.go:
--------------------------------------------------------------------------------
1 | package factory
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-core-go/core"
5 | "github.com/multiversx/mx-chain-core-go/hashing"
6 | "github.com/multiversx/mx-chain-core-go/marshal"
7 | "github.com/multiversx/mx-chain-proxy-go/facade"
8 | "github.com/multiversx/mx-chain-proxy-go/process"
9 | "github.com/multiversx/mx-chain-proxy-go/process/logsevents"
10 | "github.com/multiversx/mx-chain-proxy-go/process/txcost"
11 | )
12 |
13 | // CreateTransactionProcessor will return the transaction processor needed for current settings
14 | func CreateTransactionProcessor(
15 | proc process.Processor,
16 | pubKeyConverter core.PubkeyConverter,
17 | hasher hashing.Hasher,
18 | marshalizer marshal.Marshalizer,
19 | allowEntireTxPoolFetch bool,
20 | ) (facade.TransactionProcessor, error) {
21 | newTxCostProcessor := func() (process.TransactionCostHandler, error) {
22 | return txcost.NewTransactionCostProcessor(
23 | proc,
24 | pubKeyConverter,
25 | )
26 | }
27 |
28 | logsMerger, err := logsevents.NewLogsMerger(hasher, &marshal.JsonMarshalizer{})
29 | if err != nil {
30 | return nil, err
31 | }
32 |
33 | return process.NewTransactionProcessor(
34 | proc,
35 | pubKeyConverter,
36 | hasher,
37 | marshalizer,
38 | newTxCostProcessor,
39 | logsMerger,
40 | allowEntireTxPoolFetch,
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/process/logsevents/errors.go:
--------------------------------------------------------------------------------
1 | package logsevents
2 |
3 | import "errors"
4 |
5 | // ErrNilHasher is raised when a valid hasher is expected but nil used
6 | var ErrNilHasher = errors.New("hasher is nil")
7 |
8 | // ErrNilMarshalizer is raised when a valid marshalizer is expected but nil used
9 | var ErrNilMarshalizer = errors.New("marshalizer is nil")
10 |
--------------------------------------------------------------------------------
/process/logsevents/logsMerger.go:
--------------------------------------------------------------------------------
1 | package logsevents
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-core-go/core"
5 | "github.com/multiversx/mx-chain-core-go/core/check"
6 | "github.com/multiversx/mx-chain-core-go/data/transaction"
7 | "github.com/multiversx/mx-chain-core-go/hashing"
8 | "github.com/multiversx/mx-chain-core-go/marshal"
9 | logger "github.com/multiversx/mx-chain-logger-go"
10 | )
11 |
12 | var log = logger.GetOrCreate("process/logsevents")
13 |
14 | type logsMerger struct {
15 | hasher hashing.Hasher
16 | marshalizer marshal.Marshalizer
17 | }
18 |
19 | // NewLogsMerger will create a new instance of logsMerger
20 | func NewLogsMerger(hasher hashing.Hasher, marshalizer marshal.Marshalizer) (*logsMerger, error) {
21 | if check.IfNil(hasher) {
22 | return nil, ErrNilHasher
23 | }
24 | if check.IfNil(marshalizer) {
25 | return nil, ErrNilMarshalizer
26 | }
27 |
28 | return &logsMerger{
29 | hasher: hasher,
30 | marshalizer: marshalizer,
31 | }, nil
32 | }
33 |
34 | // MergeLogEvents will merge events from provided logs
35 | func (lm *logsMerger) MergeLogEvents(logSource *transaction.ApiLogs, logDestination *transaction.ApiLogs) *transaction.ApiLogs {
36 | if logSource == nil {
37 | return logDestination
38 | }
39 |
40 | if logDestination == nil {
41 | return logSource
42 | }
43 |
44 | mergedEvents := make(map[string]*transaction.Events)
45 | lm.mergeEvents(mergedEvents, logSource)
46 | lm.mergeEvents(mergedEvents, logDestination)
47 |
48 | return &transaction.ApiLogs{
49 | Address: logSource.Address,
50 | Events: convertEventsMapInSlice(mergedEvents),
51 | }
52 | }
53 |
54 | func (lm *logsMerger) mergeEvents(mergedEvents map[string]*transaction.Events, apiLog *transaction.ApiLogs) {
55 | for _, event := range apiLog.Events {
56 | logHash, err := core.CalculateHash(lm.marshalizer, lm.hasher, event)
57 | if err != nil {
58 | log.Warn("logsMerger.mergeEvents cannot compute event hash", "error", err.Error())
59 | }
60 |
61 | mergedEvents[string(logHash)] = event
62 | }
63 | }
64 |
65 | func convertEventsMapInSlice(eventsMap map[string]*transaction.Events) []*transaction.Events {
66 | events := make([]*transaction.Events, 0, len(eventsMap))
67 | for _, eventLog := range eventsMap {
68 | events = append(events, eventLog)
69 | }
70 |
71 | return events
72 | }
73 |
74 | // IsInterfaceNil returns true if the value under the interface is nil
75 | func (lm *logsMerger) IsInterfaceNil() bool {
76 | return lm == nil
77 | }
78 |
--------------------------------------------------------------------------------
/process/logsevents/logsMerger_test.go:
--------------------------------------------------------------------------------
1 | package logsevents
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/multiversx/mx-chain-core-go/data/transaction"
7 | hasherFactory "github.com/multiversx/mx-chain-core-go/hashing/factory"
8 | marshalFactory "github.com/multiversx/mx-chain-core-go/marshal/factory"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | func TestNewLogsMerger(t *testing.T) {
13 | t.Parallel()
14 |
15 | hasher, _ := hasherFactory.NewHasher("blake2b")
16 | marshalizer, _ := marshalFactory.NewMarshalizer("json")
17 | lp, err := NewLogsMerger(nil, marshalizer)
18 | require.Nil(t, lp)
19 | require.Equal(t, ErrNilHasher, err)
20 |
21 | lp, err = NewLogsMerger(hasher, nil)
22 | require.Nil(t, lp)
23 | require.Equal(t, ErrNilMarshalizer, err)
24 |
25 | lp, err = NewLogsMerger(hasher, marshalizer)
26 | require.NotNil(t, lp)
27 | require.Nil(t, err)
28 | }
29 |
30 | func TestLogsMerger_MergeLogsNoLogsOnDst(t *testing.T) {
31 | t.Parallel()
32 |
33 | hasher, _ := hasherFactory.NewHasher("blake2b")
34 | marshalizer, _ := marshalFactory.NewMarshalizer("json")
35 | lp, _ := NewLogsMerger(hasher, marshalizer)
36 |
37 | sourceLog := &transaction.ApiLogs{
38 | Address: "addr1",
39 | Events: []*transaction.Events{
40 | {
41 | Data: []byte("data1"),
42 | },
43 | },
44 | }
45 |
46 | res := lp.MergeLogEvents(sourceLog, nil)
47 | require.Equal(t, sourceLog, res)
48 | }
49 |
50 | func TestLogsMerger_MergeLogsNoLogsOnSource(t *testing.T) {
51 | t.Parallel()
52 |
53 | hasher, _ := hasherFactory.NewHasher("blake2b")
54 | marshalizer, _ := marshalFactory.NewMarshalizer("json")
55 | lp, _ := NewLogsMerger(hasher, marshalizer)
56 |
57 | destinationLog := &transaction.ApiLogs{
58 | Address: "addr1",
59 | Events: []*transaction.Events{
60 | {
61 | Data: []byte("data1"),
62 | },
63 | },
64 | }
65 |
66 | res := lp.MergeLogEvents(nil, destinationLog)
67 | require.Equal(t, destinationLog, res)
68 | }
69 |
70 | func TestLogsMerger_MergeLogs(t *testing.T) {
71 | hasher, _ := hasherFactory.NewHasher("blake2b")
72 | marshalizer, _ := marshalFactory.NewMarshalizer("json")
73 | lp, _ := NewLogsMerger(hasher, marshalizer)
74 |
75 | sourceLog := &transaction.ApiLogs{
76 | Address: "addr1",
77 | Events: []*transaction.Events{
78 | {
79 | Data: []byte("data1"),
80 | },
81 | {
82 | Data: []byte("data2"),
83 | },
84 | },
85 | }
86 | destinationLog := &transaction.ApiLogs{
87 | Address: "addr1",
88 | Events: []*transaction.Events{
89 | {
90 | Data: []byte("data1"),
91 | },
92 | {
93 | Data: []byte("data3"),
94 | },
95 | },
96 | }
97 |
98 | res := lp.MergeLogEvents(sourceLog, destinationLog)
99 | require.Len(t, res.Events, 3)
100 | }
101 |
--------------------------------------------------------------------------------
/process/mock/addressContainerMock.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | type AddressContainerMock struct {
4 | BytesField []byte
5 | }
6 |
7 | func (adr *AddressContainerMock) Bytes() []byte {
8 | return adr.BytesField
9 | }
10 |
11 | func (adr *AddressContainerMock) IsInterfaceNil() bool {
12 | return adr == nil
13 | }
14 |
--------------------------------------------------------------------------------
/process/mock/genericApiResponseCacherMock.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import (
4 | "errors"
5 | "sync"
6 |
7 | "github.com/multiversx/mx-chain-proxy-go/data"
8 | )
9 |
10 | // GenericApiResponseCacherMock -
11 | type GenericApiResponseCacherMock struct {
12 | Data *data.GenericAPIResponse
13 | sync.RWMutex
14 | }
15 |
16 | // Load -
17 | func (g *GenericApiResponseCacherMock) Load() (*data.GenericAPIResponse, error) {
18 | g.RLock()
19 | defer g.RUnlock()
20 |
21 | if g.Data == nil {
22 | return nil, errors.New("nil data")
23 | }
24 |
25 | return g.Data, nil
26 | }
27 |
28 | // Store -
29 | func (g *GenericApiResponseCacherMock) Store(response *data.GenericAPIResponse) {
30 | g.Lock()
31 | g.Data = response
32 | g.Unlock()
33 | }
34 |
35 | // IsInterfaceNil -
36 | func (g *GenericApiResponseCacherMock) IsInterfaceNil() bool {
37 | return g == nil
38 | }
39 |
--------------------------------------------------------------------------------
/process/mock/heartbeatCacherMock.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/multiversx/mx-chain-proxy-go/data"
7 | )
8 |
9 | type HeartbeatCacherMock struct {
10 | Data *data.HeartbeatResponse
11 | }
12 |
13 | func (hcm *HeartbeatCacherMock) LoadHeartbeats() (*data.HeartbeatResponse, error) {
14 | if hcm.Data == nil {
15 | return nil, errors.New("nil Data")
16 | }
17 |
18 | return hcm.Data, nil
19 | }
20 |
21 | func (hcm *HeartbeatCacherMock) StoreHeartbeats(data *data.HeartbeatResponse) error {
22 | hcm.Data = data
23 | return nil
24 | }
25 |
26 | func (hcm *HeartbeatCacherMock) IsInterfaceNil() bool {
27 | return hcm == nil
28 | }
29 |
--------------------------------------------------------------------------------
/process/mock/httpClientMock.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import "net/http"
4 |
5 | // HttpClientMock -
6 | type HttpClientMock struct {
7 | DoCalled func(req *http.Request) (*http.Response, error)
8 | }
9 |
10 | // Do -
11 | func (mock *HttpClientMock) Do(req *http.Request) (*http.Response, error) {
12 | if mock.DoCalled != nil {
13 | return mock.DoCalled(req)
14 | }
15 | return &http.Response{}, nil
16 | }
17 |
--------------------------------------------------------------------------------
/process/mock/keygenMock.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import crypto "github.com/multiversx/mx-chain-crypto-go"
4 |
5 | type KeygenStub struct {
6 | GeneratePairCalled func() (crypto.PrivateKey, crypto.PublicKey)
7 | PrivateKeyFromByteArrayCalled func(b []byte) (crypto.PrivateKey, error)
8 | PublicKeyFromByteArrayCalled func(b []byte) (crypto.PublicKey, error)
9 | SuiteCalled func() crypto.Suite
10 | }
11 |
12 | func (kgs *KeygenStub) GeneratePair() (crypto.PrivateKey, crypto.PublicKey) {
13 | return kgs.GeneratePairCalled()
14 | }
15 |
16 | func (kgs *KeygenStub) PrivateKeyFromByteArray(b []byte) (crypto.PrivateKey, error) {
17 | return kgs.PrivateKeyFromByteArrayCalled(b)
18 | }
19 |
20 | func (kgs *KeygenStub) PublicKeyFromByteArray(b []byte) (crypto.PublicKey, error) {
21 | return kgs.PublicKeyFromByteArrayCalled(b)
22 | }
23 |
24 | func (kgs *KeygenStub) Suite() crypto.Suite {
25 | return kgs.SuiteCalled()
26 | }
27 |
28 | func (kgs *KeygenStub) IsInterfaceNil() bool {
29 | return kgs == nil
30 | }
31 |
--------------------------------------------------------------------------------
/process/mock/loggerStub.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import (
4 | logger "github.com/multiversx/mx-chain-logger-go"
5 | )
6 |
7 | // LoggerStub -
8 | type LoggerStub struct {
9 | TraceCalled func(message string, args ...interface{})
10 | DebugCalled func(message string, args ...interface{})
11 | InfoCalled func(message string, args ...interface{})
12 | WarnCalled func(message string, args ...interface{})
13 | ErrorCalled func(message string, args ...interface{})
14 | LogIfErrorCalled func(err error, args ...interface{})
15 | LogCalled func(logLevel logger.LogLevel, message string, args ...interface{})
16 | LogLineCalled func(line *logger.LogLine)
17 | SetLevelCalled func(logLevel logger.LogLevel)
18 | GetLevelCalled func() logger.LogLevel
19 | }
20 |
21 | // Trace -
22 | func (stub *LoggerStub) Trace(message string, args ...interface{}) {
23 | if stub.TraceCalled != nil {
24 | stub.TraceCalled(message, args...)
25 | }
26 | }
27 |
28 | // Debug -
29 | func (stub *LoggerStub) Debug(message string, args ...interface{}) {
30 | if stub.DebugCalled != nil {
31 | stub.DebugCalled(message, args...)
32 | }
33 | }
34 |
35 | // Info -
36 | func (stub *LoggerStub) Info(message string, args ...interface{}) {
37 | if stub.InfoCalled != nil {
38 | stub.InfoCalled(message, args...)
39 | }
40 | }
41 |
42 | // Warn -
43 | func (stub *LoggerStub) Warn(message string, args ...interface{}) {
44 | if stub.WarnCalled != nil {
45 | stub.WarnCalled(message, args...)
46 | }
47 | }
48 |
49 | // Error -
50 | func (stub *LoggerStub) Error(message string, args ...interface{}) {
51 | if stub.ErrorCalled != nil {
52 | stub.ErrorCalled(message, args...)
53 | }
54 | }
55 |
56 | // LogIfError -
57 | func (stub *LoggerStub) LogIfError(err error, args ...interface{}) {
58 | if stub.LogIfErrorCalled != nil {
59 | stub.LogIfErrorCalled(err, args...)
60 | }
61 | }
62 |
63 | // Log -
64 | func (stub *LoggerStub) Log(logLevel logger.LogLevel, message string, args ...interface{}) {
65 | if stub.LogCalled != nil {
66 | stub.LogCalled(logLevel, message, args...)
67 | }
68 | }
69 |
70 | // LogLine -
71 | func (stub *LoggerStub) LogLine(line *logger.LogLine) {
72 | if stub.LogLineCalled != nil {
73 | stub.LogLineCalled(line)
74 | }
75 | }
76 |
77 | // SetLevel -
78 | func (stub *LoggerStub) SetLevel(logLevel logger.LogLevel) {
79 | if stub.SetLevelCalled != nil {
80 | stub.SetLevelCalled(logLevel)
81 | }
82 | }
83 |
84 | // GetLevel -
85 | func (stub *LoggerStub) GetLevel() logger.LogLevel {
86 | if stub.GetLevelCalled != nil {
87 | return stub.GetLevelCalled()
88 | }
89 |
90 | return logger.LogNone
91 | }
92 |
93 | // IsInterfaceNil -
94 | func (stub *LoggerStub) IsInterfaceNil() bool {
95 | return stub == nil
96 | }
97 |
--------------------------------------------------------------------------------
/process/mock/observersProviderStub.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-proxy-go/data"
5 | )
6 |
7 | // ObserversProviderStub -
8 | type ObserversProviderStub struct {
9 | GetNodesByShardIdCalled func(shardId uint32, dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error)
10 | GetAllNodesCalled func(dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error)
11 | ReloadNodesCalled func(nodesType data.NodeType) data.NodesReloadResponse
12 | UpdateNodesBasedOnSyncStateCalled func(nodesWithSyncStatus []*data.NodeData)
13 | GetAllNodesWithSyncStateCalled func() []*data.NodeData
14 | PrintNodesInShardsCalled func()
15 | }
16 |
17 | // GetNodesByShardId -
18 | func (ops *ObserversProviderStub) GetNodesByShardId(shardId uint32, dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error) {
19 | if ops.GetNodesByShardIdCalled != nil {
20 | return ops.GetNodesByShardIdCalled(shardId, dataAvailability)
21 | }
22 |
23 | return []*data.NodeData{
24 | {
25 | Address: "address",
26 | ShardId: 0,
27 | },
28 | }, nil
29 | }
30 |
31 | // GetAllNodes -
32 | func (ops *ObserversProviderStub) GetAllNodes(dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error) {
33 | if ops.GetAllNodesCalled != nil {
34 | return ops.GetAllNodesCalled(dataAvailability)
35 | }
36 |
37 | return []*data.NodeData{
38 | {
39 | Address: "address",
40 | ShardId: 0,
41 | },
42 | }, nil
43 | }
44 |
45 | // UpdateNodesBasedOnSyncState -
46 | func (ops *ObserversProviderStub) UpdateNodesBasedOnSyncState(nodesWithSyncStatus []*data.NodeData) {
47 | if ops.UpdateNodesBasedOnSyncStateCalled != nil {
48 | ops.UpdateNodesBasedOnSyncStateCalled(nodesWithSyncStatus)
49 | }
50 | }
51 |
52 | // GetAllNodesWithSyncState -
53 | func (ops *ObserversProviderStub) GetAllNodesWithSyncState() []*data.NodeData {
54 | if ops.GetAllNodesWithSyncStateCalled != nil {
55 | return ops.GetAllNodesWithSyncStateCalled()
56 | }
57 |
58 | return make([]*data.NodeData, 0)
59 | }
60 |
61 | // ReloadNodes -
62 | func (ops *ObserversProviderStub) ReloadNodes(nodesType data.NodeType) data.NodesReloadResponse {
63 | if ops.ReloadNodesCalled != nil {
64 | return ops.ReloadNodesCalled(nodesType)
65 | }
66 |
67 | return data.NodesReloadResponse{}
68 | }
69 |
70 | // PrintNodesInShards -
71 | func (ops *ObserversProviderStub) PrintNodesInShards() {
72 | if ops.PrintNodesInShardsCalled != nil {
73 | ops.PrintNodesInShardsCalled()
74 | }
75 | }
76 |
77 | // IsInterfaceNil -
78 | func (ops *ObserversProviderStub) IsInterfaceNil() bool {
79 | return ops == nil
80 | }
81 |
--------------------------------------------------------------------------------
/process/mock/privKeysLoaderStub.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import crypto "github.com/multiversx/mx-chain-crypto-go"
4 |
5 | type PrivateKeysLoaderStub struct {
6 | PrivateKeysByShardCalled func() (map[uint32][]crypto.PrivateKey, error)
7 | }
8 |
9 | func (pkls *PrivateKeysLoaderStub) PrivateKeysByShard() (map[uint32][]crypto.PrivateKey, error) {
10 | return pkls.PrivateKeysByShardCalled()
11 | }
12 |
--------------------------------------------------------------------------------
/process/mock/pubKeyConverterMock.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import (
4 | "encoding/hex"
5 |
6 | "github.com/multiversx/mx-chain-core-go/core"
7 | )
8 |
9 | // PubKeyConverterMock -
10 | type PubKeyConverterMock struct {
11 | len int
12 | }
13 |
14 | // Decode -
15 | func (pcm *PubKeyConverterMock) Decode(humanReadable string) ([]byte, error) {
16 | return hex.DecodeString(humanReadable)
17 | }
18 |
19 | // Encode -
20 | func (pcm *PubKeyConverterMock) Encode(pkBytes []byte) (string, error) {
21 | return hex.EncodeToString(pkBytes), nil
22 | }
23 |
24 | // EncodeSlice -
25 | func (pcm *PubKeyConverterMock) EncodeSlice(pkBytesSlice [][]byte) ([]string, error) {
26 | results := make([]string, 0)
27 | for _, pk := range pkBytesSlice {
28 | results = append(results, hex.EncodeToString(pk))
29 | }
30 |
31 | return results, nil
32 | }
33 |
34 | // SilentEncode -
35 | func (pcm *PubKeyConverterMock) SilentEncode(pkBytes []byte, _ core.Logger) string {
36 | return hex.EncodeToString(pkBytes)
37 | }
38 |
39 | // Len -
40 | func (pcm *PubKeyConverterMock) Len() int {
41 | return pcm.len
42 | }
43 |
44 | // IsInterfaceNil -
45 | func (pcm *PubKeyConverterMock) IsInterfaceNil() bool {
46 | return pcm == nil
47 | }
48 |
--------------------------------------------------------------------------------
/process/mock/scQueryServiceStub.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-core-go/data/vm"
5 | "github.com/multiversx/mx-chain-proxy-go/data"
6 | )
7 |
8 | // SCQueryServiceStub is a stub
9 | type SCQueryServiceStub struct {
10 | ExecuteQueryCalled func(*data.SCQuery) (*vm.VMOutputApi, data.BlockInfo, error)
11 | }
12 |
13 | // ExecuteQuery is a stub
14 | func (serviceStub *SCQueryServiceStub) ExecuteQuery(query *data.SCQuery) (*vm.VMOutputApi, data.BlockInfo, error) {
15 | return serviceStub.ExecuteQueryCalled(query)
16 | }
17 |
18 | // IsInterfaceNil returns true if the value under the interface is nil
19 | func (serviceStub *SCQueryServiceStub) IsInterfaceNil() bool {
20 | return serviceStub == nil
21 | }
22 |
--------------------------------------------------------------------------------
/process/mock/shardCoordinatorMock.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | type ShardCoordinatorMock struct {
4 | NumShards uint32
5 | }
6 |
7 | func (scm *ShardCoordinatorMock) NumberOfShards() uint32 {
8 | return scm.NumShards
9 | }
10 |
11 | func (scm *ShardCoordinatorMock) ComputeId(_ []byte) uint32 {
12 | return uint32(1)
13 | }
14 |
15 | func (scm *ShardCoordinatorMock) SelfId() uint32 {
16 | return 0
17 | }
18 |
19 | func (scm *ShardCoordinatorMock) SameShard(_, _ []byte) bool {
20 | return true
21 | }
22 |
23 | func (scm *ShardCoordinatorMock) CommunicationIdentifier(_ uint32) string {
24 | return "0_1"
25 | }
26 |
27 | // IsInterfaceNil returns true if there is no value under the interface
28 | func (scm *ShardCoordinatorMock) IsInterfaceNil() bool {
29 | return scm == nil
30 | }
31 |
--------------------------------------------------------------------------------
/process/mock/singleSignerStub.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import crypto "github.com/multiversx/mx-chain-crypto-go"
4 |
5 | type SignerStub struct {
6 | SignCalled func(private crypto.PrivateKey, msg []byte) ([]byte, error)
7 | VerifyCalled func(public crypto.PublicKey, msg []byte, sig []byte) error
8 | }
9 |
10 | func (s *SignerStub) Sign(private crypto.PrivateKey, msg []byte) ([]byte, error) {
11 | return s.SignCalled(private, msg)
12 | }
13 |
14 | func (s *SignerStub) Verify(public crypto.PublicKey, msg []byte, sig []byte) error {
15 | return s.VerifyCalled(public, msg, sig)
16 | }
17 |
18 | // IsInterfaceNil returns true if there is no value under the interface
19 | func (s *SignerStub) IsInterfaceNil() bool {
20 | return s == nil
21 | }
22 |
--------------------------------------------------------------------------------
/process/mock/statusMetricsProviderStub.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-proxy-go/data"
5 | )
6 |
7 | // StatusMetricsProviderStub -
8 | type StatusMetricsProviderStub struct {
9 | GetAllCalled func() map[string]*data.EndpointMetrics
10 | GetMetricsForPrometheusCalled func() string
11 | }
12 |
13 | // GetMetricsForPrometheus -
14 | func (s *StatusMetricsProviderStub) GetMetricsForPrometheus() string {
15 | if s.GetMetricsForPrometheusCalled != nil {
16 | return s.GetMetricsForPrometheusCalled()
17 | }
18 |
19 | return ""
20 | }
21 |
22 | // GetAll -
23 | func (s *StatusMetricsProviderStub) GetAll() map[string]*data.EndpointMetrics {
24 | if s.GetAllCalled != nil {
25 | return s.GetAllCalled()
26 | }
27 |
28 | return make(map[string]*data.EndpointMetrics)
29 | }
30 |
31 | // IsInterfaceNil returns true if there is no value under the interface
32 | func (s *StatusMetricsProviderStub) IsInterfaceNil() bool {
33 | return s == nil
34 | }
35 |
--------------------------------------------------------------------------------
/process/mock/transactionCostHandlerStub.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import "github.com/multiversx/mx-chain-proxy-go/data"
4 |
5 | // TransactionCostHandlerStub -
6 | type TransactionCostHandlerStub struct {
7 | RezolveCostRequestCalled func(tx *data.Transaction) (*data.TxCostResponseData, error)
8 | }
9 |
10 | // ResolveCostRequest -
11 | func (tchs *TransactionCostHandlerStub) ResolveCostRequest(tx *data.Transaction) (*data.TxCostResponseData, error) {
12 | if tchs.RezolveCostRequestCalled != nil {
13 | return tchs.RezolveCostRequestCalled(tx)
14 | }
15 |
16 | return nil, nil
17 | }
18 |
--------------------------------------------------------------------------------
/process/mock/valStatsCacherMock.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/multiversx/mx-chain-proxy-go/data"
7 | )
8 |
9 | // ValStatsCacherMock --
10 | type ValStatsCacherMock struct {
11 | Data map[string]*data.ValidatorApiResponse
12 | }
13 |
14 | // LoadValStats --
15 | func (vscm *ValStatsCacherMock) LoadValStats() (map[string]*data.ValidatorApiResponse, error) {
16 | if vscm.Data == nil {
17 | return nil, errors.New("nil Data")
18 | }
19 |
20 | return vscm.Data, nil
21 | }
22 |
23 | // StoreValStats --
24 | func (vscm *ValStatsCacherMock) StoreValStats(valStats map[string]*data.ValidatorApiResponse) error {
25 | vscm.Data = valStats
26 | return nil
27 | }
28 |
29 | // IsInterfaceNil --
30 | func (vscm *ValStatsCacherMock) IsInterfaceNil() bool {
31 | return vscm == nil
32 | }
33 |
--------------------------------------------------------------------------------
/process/statusProcessor.go:
--------------------------------------------------------------------------------
1 | package process
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-core-go/core/check"
5 | "github.com/multiversx/mx-chain-proxy-go/data"
6 | )
7 |
8 | // StatusProcessor is able to process status requests
9 | type StatusProcessor struct {
10 | proc Processor
11 | statusMetricsProvider StatusMetricsProvider
12 | }
13 |
14 | // NewStatusProcessor creates a new instance of AccountProcessor
15 | func NewStatusProcessor(proc Processor, statusMetricsProvider StatusMetricsProvider) (*StatusProcessor, error) {
16 | if check.IfNil(proc) {
17 | return nil, ErrNilCoreProcessor
18 | }
19 | if check.IfNil(statusMetricsProvider) {
20 | return nil, ErrNilStatusMetricsProvider
21 | }
22 |
23 | return &StatusProcessor{
24 | proc: proc,
25 | statusMetricsProvider: statusMetricsProvider,
26 | }, nil
27 | }
28 |
29 | // GetMetrics returns the metrics for all the endpoints
30 | func (sp *StatusProcessor) GetMetrics() map[string]*data.EndpointMetrics {
31 | return sp.statusMetricsProvider.GetAll()
32 | }
33 |
34 | // GetMetricsForPrometheus returns the metrics in a prometheus format
35 | func (sp *StatusProcessor) GetMetricsForPrometheus() string {
36 | return sp.statusMetricsProvider.GetMetricsForPrometheus()
37 | }
38 |
--------------------------------------------------------------------------------
/process/statusProcessor_test.go:
--------------------------------------------------------------------------------
1 | package process
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/multiversx/mx-chain-proxy-go/data"
7 | "github.com/multiversx/mx-chain-proxy-go/process/mock"
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func TestNewStatusProcessor(t *testing.T) {
12 | t.Parallel()
13 |
14 | t.Run("nil base processor - should error", func(t *testing.T) {
15 | t.Parallel()
16 |
17 | sp, err := NewStatusProcessor(nil, &mock.StatusMetricsProviderStub{})
18 | require.Nil(t, sp)
19 | require.Equal(t, ErrNilCoreProcessor, err)
20 | })
21 |
22 | t.Run("nil status metric provider - should error", func(t *testing.T) {
23 | t.Parallel()
24 |
25 | sp, err := NewStatusProcessor(&mock.ProcessorStub{}, nil)
26 | require.Nil(t, sp)
27 | require.Equal(t, ErrNilStatusMetricsProvider, err)
28 | })
29 |
30 | t.Run("should work", func(t *testing.T) {
31 | t.Parallel()
32 |
33 | sp, err := NewStatusProcessor(&mock.ProcessorStub{}, &mock.StatusMetricsProviderStub{})
34 | require.NoError(t, err)
35 | require.NotNil(t, sp)
36 | })
37 | }
38 |
39 | func TestStatusProcessor_GetMetrics(t *testing.T) {
40 | t.Parallel()
41 |
42 | expectedMetrics := map[string]*data.EndpointMetrics{
43 | "endpoint0": {NumErrors: 5},
44 | "endpoint1": {NumErrors: 37},
45 | }
46 | statusProvider := &mock.StatusMetricsProviderStub{
47 | GetAllCalled: func() map[string]*data.EndpointMetrics {
48 | return expectedMetrics
49 | },
50 | }
51 | sp, err := NewStatusProcessor(&mock.ProcessorStub{}, statusProvider)
52 | require.NoError(t, err)
53 | require.NotNil(t, sp)
54 |
55 | metrics := sp.GetMetrics()
56 | require.NoError(t, err)
57 | require.Equal(t, expectedMetrics, metrics)
58 | }
59 |
60 | func TestStatusProcessor_GetMetricsForPrometheus(t *testing.T) {
61 | t.Parallel()
62 |
63 | expectedOutput := "metrics"
64 | statusProvider := &mock.StatusMetricsProviderStub{
65 | GetMetricsForPrometheusCalled: func() string {
66 | return expectedOutput
67 | },
68 | }
69 | sp, err := NewStatusProcessor(&mock.ProcessorStub{}, statusProvider)
70 | require.NoError(t, err)
71 | require.NotNil(t, sp)
72 |
73 | metrics := sp.GetMetricsForPrometheus()
74 | require.NoError(t, err)
75 | require.Equal(t, expectedOutput, metrics)
76 | }
77 |
--------------------------------------------------------------------------------
/process/testdata/finishedFailedSCCall.json:
--------------------------------------------------------------------------------
1 | {
2 | "transaction": {
3 | "type": "normal",
4 | "processingTypeOnSource": "BuiltInFunctionCall",
5 | "processingTypeOnDestination": "SCInvoking",
6 | "hash": "a4823050d2396540b17bd9290523973763142c9f655bb26cd9e33f359b6d73ad",
7 | "blockHash": "a4823050d2396540b17bd9290523973763142c9f655bb26cd9e33f359b6d73ad",
8 | "notarizedAtSourceInMetaNonce": 2000,
9 | "NotarizedAtSourceInMetaHash": "a4823050d2396540b17bd9290523973763142c9f655bb26cd9e33f359b6d73ad",
10 | "notarizedAtDestinationInMetaNonce": 2000,
11 | "notarizedAtDestinationInMetaHash": "a4823050d2396540b17bd9290523973763142c9f655bb26cd9e33f359b6d73ad",
12 | "miniblockType": "TxBlock",
13 | "miniblockHash": "a4823050d2396540b17bd9290523973763142c9f655bb26cd9e33f359b6d73ad",
14 | "hyperblockHash": "a4823050d2396540b17bd9290523973763142c9f655bb26cd9e33f359b6d73ad",
15 | "logs": {
16 | "address": "a4823050d2396540b17bd9290523973763142c9f655bb26cd9e33f359b6d73ad",
17 | "events": [
18 | {
19 | "address": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
20 | "identifier": "ESDTNFTTransfer",
21 | "topics": [
22 | "V0FSUC05YWIzMjI="
23 | ],
24 | "data": null
25 | },
26 | {
27 | "address": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
28 | "identifier": "signalError",
29 | "topics": [
30 | "PcsDekGLvCoXOQRSrs9OWLbpCiUYjTapCfGPuKVbdl8="
31 | ],
32 | "data": "QDY1Nzg2NTYzNzU3NDY5NmY2ZTIwNjY2MTY5NmM2NTY0"
33 | },
34 | {
35 | "address": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
36 | "identifier": "internalVMErrors",
37 | "topics": [
38 | "AAAAAAAAAAAFAAfCb7p+cp8llHG2CSInNKwxoB1Qdl8="
39 | ],
40 | "data": ""
41 | }
42 | ]
43 | },
44 | "status": "success",
45 | "tokens": [
46 | "TKN-9ab322-01"
47 | ],
48 | "esdtValues": [
49 | "1"
50 | ],
51 | "receivers": [
52 | "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty"
53 | ],
54 | "receiversShardIDs": [
55 | 1
56 | ],
57 | "operation": "ESDTNFTTransfer",
58 | "function": "call",
59 | "initiallyPaidFee": "407005000000000",
60 | "fee": "407005000000000",
61 | "chainID": "T",
62 | "version": 1,
63 | "options": 0
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/process/testdata/finishedFailedSCR.json:
--------------------------------------------------------------------------------
1 | {
2 | "transaction": {
3 | "type": "unsigned",
4 | "processingTypeOnSource": "MoveBalance",
5 | "processingTypeOnDestination": "MoveBalance",
6 | "hash": "7cfde9ad5ead518ec768607a3ac992763f5afdcf31e603fdd56418c7ffe19774",
7 | "nonce": 0,
8 | "round": 66,
9 | "epoch": 3,
10 | "value": "0",
11 | "receiver": "erd1u39p5ld7qjg5qz8tnj2zr2ntvqeskzryu3fnh343uux2xlxzk6dsq5zx75",
12 | "sender": "erd1ykqd64fxxpp4wsz0v7sjqem038wfpzlljhx4mhwx8w9lcxmdzcfszrp64a",
13 | "gasUsed": 50000,
14 | "previousTransactionHash": "6c9d9eaf8928257c8019c56d56a9c0273e6428de00b96a584077778a5128be6a",
15 | "originalTransactionHash": "6c9d9eaf8928257c8019c56d56a9c0273e6428de00b96a584077778a5128be6a",
16 | "returnMessage": "insufficient funds",
17 | "sourceShard": 1,
18 | "destinationShard": 1,
19 | "blockNonce": 66,
20 | "blockHash": "84bb2e631eec1c3665839f087e170de19bdaf287a83c6065fa97310f57b94e4c",
21 | "miniblockType": "SmartContractResultBlock",
22 | "miniblockHash": "9627ce0cafb78c5c109ce4fbb9011e8a07ac05057b89488348a05cd9a30d9717",
23 | "timestamp": 1717422998,
24 | "status": "success",
25 | "operation": "transfer",
26 | "fee": "0",
27 | "callType": "directCall",
28 | "options": 0
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/process/testdata/finishedInvalidBuiltinFunction.json:
--------------------------------------------------------------------------------
1 | {
2 | "transaction": {
3 | "type": "invalid",
4 | "processingTypeOnSource": "BuiltInFunctionCall",
5 | "processingTypeOnDestination": "BuiltInFunctionCall",
6 | "hash": "a4823050d2396540b17bd9290523973763142c9f655bb26cd9e33f359b6d73ad",
7 | "value": "0",
8 | "receiver": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
9 | "sender": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
10 | "gasPrice": 1000000000,
11 | "gasLimit": 550000000,
12 | "gasUsed": 550000000,
13 | "data": "TXVsdGlFU0RUTkZUVHJhbnNmZXJAN2EwMTY3ZGY3NmY1ZjllNzBkNjRjMjEwNjAxYTQ2NWE4Y2I0ZGE5YTQ0YjkwY2FkZDY4ZDA0YWIwNTIwYmQ3Y0AwM0A1NjRjNTMyZDMxMzE2NDMwNjQzMEAwNEAwMUA1NjRjNTMyZDMxMzE2NDMwNjQzMEAwNUAwMUA1NjRjNTMyZDMxMzE2NDMwNjQzMEAwNkAwMUA2NzY1NzQ1NTZjNzQ2OTZkNjE3NDY1NDE2ZTczNzc2NTcy",
14 | "signature": "ea1865ccf6d7cbc7312703ab3bd5fd780d286029c03d20cec5f0784ae1a04ab9556e1451be04a8a0e4cec03eaab16e782788a49fae9ef2b09f43f3d683235c07",
15 | "sourceShard": 0,
16 | "destinationShard": 0,
17 | "logs": {
18 | "address": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
19 | "events": [
20 | {
21 | "address": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
22 | "identifier": "signalError",
23 | "topics": [
24 | "geQz5PeDGewXN9wviXwiSXnafo8GYO6dOYXGHRviLjw="
25 | ],
26 | "data": "QDZlNjU3NzIwNGU0NjU0MjA2NDYxNzQ2MTIwNmY2ZTIwNzM2NTZlNjQ2NTcyMjA2NjZmNzIyMDc0NmY2YjY1NmUyMDU2NGM1MzJkMzEzMTY0MzA2NDMw"
27 | }
28 | ]
29 | },
30 | "status": "invalid",
31 | "tokens": [
32 | "TKN-11d0d0-04",
33 | "TKN-11d0d0-05",
34 | "TKN-11d0d0-06"
35 | ],
36 | "esdtValues": [
37 | "1",
38 | "1",
39 | "1"
40 | ],
41 | "receivers": [
42 | "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
43 | "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
44 | "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty"
45 | ],
46 | "receiversShardIDs": [
47 | 0,
48 | 0,
49 | 0
50 | ],
51 | "operation": "MultiESDTNFTTransfer",
52 | "initiallyPaidFee": "5852440000000000",
53 | "fee": "5852440000000000",
54 | "options": 0
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/process/testdata/finishedOKMoveBalance.json:
--------------------------------------------------------------------------------
1 | {
2 | "transaction": {
3 | "type": "normal",
4 | "processingTypeOnSource": "MoveBalance",
5 | "processingTypeOnDestination": "MoveBalance",
6 | "hash": "a4823050d2396540b17bd9290523973763142c9f655bb26cd9e33f359b6d73ad",
7 | "value": "1500000000000000000",
8 | "receiver": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
9 | "sender": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
10 | "gasPrice": 1000000000,
11 | "gasLimit": 50000,
12 | "gasUsed": 50000,
13 | "sourceShard": 2,
14 | "destinationShard": 1,
15 | "notarizedAtSourceInMetaNonce": 3000,
16 | "NotarizedAtSourceInMetaHash": "a4823050d2396540b17bd9290523973763142c9f655bb26cd9e33f359b6d73ad",
17 | "notarizedAtDestinationInMetaNonce": 3000,
18 | "notarizedAtDestinationInMetaHash": "a4823050d2396540b17bd9290523973763142c9f655bb26cd9e33f359b6d73ad",
19 | "miniblockType": "TxBlock",
20 | "miniblockHash": "a4823050d2396540b17bd9290523973763142c9f655bb26cd9e33f359b6d73ad",
21 | "status": "success",
22 | "operation": "transfer",
23 | "initiallyPaidFee": "50000000000000",
24 | "fee": "50000000000000",
25 | "chainID": "T",
26 | "version": 1,
27 | "options": 0
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/process/testdata/finishedOKRelayedV2TxIntraShard.json:
--------------------------------------------------------------------------------
1 | {
2 | "transaction": {
3 | "type": "normal",
4 | "processingTypeOnSource": "RelayedTxV2",
5 | "processingTypeOnDestination": "RelayedTxV2",
6 | "hash": "47845341c9bf9172fdb8ebc8a7db729ff91c66c8d708e042af7a0b3f6a3c337e",
7 | "nonce": 0,
8 | "round": 1590,
9 | "epoch": 7,
10 | "value": "0",
11 | "receiver": "erd1jrvq9emyfd97xs2kq53qedkz4sv40aayjt7tjcw9x6kjujvuv8uqnww788",
12 | "sender": "erd19al0qr2zcgu067sku2m86djxp5dexle6we2lrlm63z29q2yawfsqmnxk78",
13 | "data": "cmVsYXllZFR4VjJAYWY2ZjM2M2E4NWVkNmRjYWQwMDJkNjRhMzdlMmMyMmQwYWVjYTEzNWY0MWZlM2UwYjhhNDlkYzAwNWZkZDNhOEAwMUA3MjYxNmU2NDZmNmRAZDVjNDRiNDk5MTI3ZDA3ZWE5YTdhYTgyMDVlYjUxMmMyYzRmYzhjZTA2NzcwN2QxMWZkNmI2NmI0ZmEzYWQwMWRiNzk4ODY2YzEyMzM3Y2JjODYyZWMzMzllNzAzMzBlMGZkZGQyZTBjZTViNGUzOGYxYzdjZGM0ZTczYWFmMDU=",
14 | "notarizedAtSourceInMetaNonce": 1477,
15 | "NotarizedAtSourceInMetaHash": "20d21f4610e5c997a82ede2e9da9bb6ba4e806488829a5e88d74ec6f735979a5",
16 | "notarizedAtDestinationInMetaNonce": 1477,
17 | "notarizedAtDestinationInMetaHash": "20d21f4610e5c997a82ede2e9da9bb6ba4e806488829a5e88d74ec6f735979a5",
18 | "smartContractResults": [
19 | {
20 | "hash": "SCR-hash1",
21 | "receiver": "erd14ahnvw59a4ku45qz6e9r0ckz959wegf47s078c9c5jwuqp0a6w5qgfjy87",
22 | "sender": "erd1jrvq9emyfd97xs2kq53qedkz4sv40aayjt7tjcw9x6kjujvuv8uqnww788",
23 | "relayerAddress": "erd19al0qr2zcgu067sku2m86djxp5dexle6we2lrlm63z29q2yawfsqmnxk78",
24 | "data": "random",
25 | "operation": "transfer"
26 | }
27 | ],
28 | "status": "success",
29 | "receivers": [
30 | "erd14ahnvw59a4ku45qz6e9r0ckz959wegf47s078c9c5jwuqp0a6w5qgfjy87"
31 | ],
32 | "receiversShardIDs": [
33 | 0
34 | ],
35 | "operation": "transfer",
36 | "initiallyPaidFee": "481500000000000",
37 | "fee": "481500000000000",
38 | "isRelayed": true,
39 | "chainID": "1",
40 | "version": 1,
41 | "options": 0
42 | },
43 | "scrs": [
44 | {
45 | "type": "unsigned",
46 | "processingTypeOnSource": "MoveBalance",
47 | "processingTypeOnDestination": "MoveBalance",
48 | "hash": "SCR-hash1",
49 | "receiver": "erd14ahnvw59a4ku45qz6e9r0ckz959wegf47s078c9c5jwuqp0a6w5qgfjy87",
50 | "sender": "erd1jrvq9emyfd97xs2kq53qedkz4sv40aayjt7tjcw9x6kjujvuv8uqnww788",
51 | "data": "cmFuZG9t",
52 | "miniblockType": "SmartContractResultBlock",
53 | "callType": "directCall",
54 | "relayerAddress": "erd19al0qr2zcgu067sku2m86djxp5dexle6we2lrlm63z29q2yawfsqmnxk78"
55 | }
56 | ]
57 | }
--------------------------------------------------------------------------------
/process/testdata/finishedOKRewardTx.json:
--------------------------------------------------------------------------------
1 | {
2 | "transaction": {
3 | "type": "reward",
4 | "processingTypeOnSource": "MoveBalance",
5 | "processingTypeOnDestination": "MoveBalance",
6 | "hash": "191a0bf2d29559181da9d93bfd35f8b9f136122006d10b51096fec6be65fb25d",
7 | "value": "586698853111273957",
8 | "receiver": "erd1kkcvtdc6j235d9x830hlz8xc4vvz3q63mlhqykw5nq8f2t0tfx7sx4jhhj",
9 | "sender": "metachain",
10 | "gasUsed": 50000,
11 | "sourceShard": 4294967295,
12 | "destinationShard": 1,
13 | "blockNonce": 1093268,
14 | "blockHash": "4db7e2478663a19dea9ac29949c2482118448f0497aad8f0807eb3e02a60c76f",
15 | "notarizedAtSourceInMetaNonce": 1093987,
16 | "NotarizedAtSourceInMetaHash": "41eeb81ae7a0155f98de00bba25e667b3efa8cc0bf20a8131867e86f6b90d5ff",
17 | "notarizedAtDestinationInMetaNonce": 1093991,
18 | "notarizedAtDestinationInMetaHash": "d301328ed7ba3cbfb74257af110c383c99594c366783563407d67bb1a8c8b489",
19 | "status": "success",
20 | "operation": "transfer",
21 | "fee": "0",
22 | "options": 0
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/process/testdata/finishedOKSCDeploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "transaction": {
3 | "type": "normal",
4 | "processingTypeOnSource": "SCDeployment",
5 | "processingTypeOnDestination": "SCDeployment",
6 | "hash": "a4823050d2396540b17bd9290523973763142c9f655bb26cd9e33f359b6d73ad",
7 | "value": "0",
8 | "receiver": "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu",
9 | "sender": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
10 | "gasPrice": 1000000000,
11 | "gasLimit": 550000000,
12 | "gasUsed": 550000000,
13 | "data": "MDA2MTczNmQwMTAwMDAwMDAxMTUwNDYwMDM3ZjdmN2UwMTdmNjAwMjdmN2YwMTdlNjAwMTdlMDA2MDAwMDAwMjQyMDMwMzY1NmU3NjExNjk2ZTc0MzYzNDczNzQ2ZjcyNjE2NzY1NTM3NDZmNzI2NTAwMDAwMzY1NmU3NjEwNjk2ZTc0MzYzNDczNzQ2ZjcyNjE2NzY1NGM2ZjYxNjQwMDAxMDM2NTZlNzYwYjY5NmU3NDM2MzQ2NjY5NmU2OTczNjgwMDAyMDMwNDAzMDMwMzAzMDQwNTAxNzAwMTAxMDEwNTAzMDEwMDAyMDYwODAxN2YwMTQxYTA4ODA0MGIwNzJhMDQwNjZkNjU2ZDZmNzI3OTAyMDAwODYzNjE2YzZjNDI2MTYzNmIwMDAzMDY2MzYxNmM2YzRkNjUwMDA0MDk2ZTc1NmQ0MzYxNmM2YzY1NjQwMDA1MGEzZTAzMDIwMDBiMjIwMDQxODA4ODgwODAwMDQxMjA0MTgwODg4MDgwMDA0MTIwMTA4MTgwODA4MDAwNDIwMTdjMTA4MDgwODA4MDAwMWEwYjE2MDA0MTgwODg4MDgwMDA0MTIwMTA4MTgwODA4MDAwMTA4MjgwODA4MDAwMGIwYjI3MDEwMDQxODAwODBiMjAyYTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDJhQDA1MDBAMDUwMA==",
14 | "signature": "3e83a487b13bc46e9a80f67a552f878c21fe6ffb8c0a172b9807f2ddc3d8598a50a8569fc56c630b0e01af384962e201981beaa949b7a09a64d68ffb4aba2407",
15 | "sourceShard": 0,
16 | "destinationShard": 0,
17 | "logs": {
18 | "address": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
19 | "events": [
20 | {
21 | "address": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
22 | "identifier": "SCDeploy",
23 | "topics": [
24 | "AAAAAAAAAAAFAI/gqaFAmqjqwl6V7SHtxjqOsCioLjw="
25 | ],
26 | "data": null
27 | },
28 | {
29 | "address": "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu",
30 | "identifier": "writeLog",
31 | "topics": [
32 | "geQz5PeDGewXN9wviXwiSXnafo8GYO6dOYXGHRviLjw="
33 | ],
34 | "data": "QDZmNmI="
35 | }
36 | ]
37 | },
38 | "status": "success",
39 | "operation": "scDeploy",
40 | "initiallyPaidFee": "6384070000000000",
41 | "fee": "6384070000000000",
42 | "chainID": "1",
43 | "version": 1,
44 | "options": 0
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/process/testdata/finishedOKSCDeployWithTransfer.json:
--------------------------------------------------------------------------------
1 | {
2 | "transaction": {
3 | "type": "normal",
4 | "processingTypeOnSource": "SCInvoking",
5 | "processingTypeOnDestination": "SCInvoking",
6 | "hash": "a4823050d2396540b17bd9290523973763142c9f655bb26cd9e33f359b6d73ad",
7 | "receiver": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
8 | "sender": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
9 | "data": "ZG9fZGVwbG95QDAwMDAwMDAwMDAwMDAwMDAwNTAwRkIxNUE1MEZGNUQ2MEQyQUI5NjA3MTgyRDdCQjM2MjlEQThFNkQxOEEwMTJAQTlGNkE4MUFBN0RBNzY3OEE5NTg1NTgyOURBMjVFM0EyOTI3OUJDM0QwQjQ0QTg4RjI1MUMxNkFEM0JFQUNEQw==",
10 | "smartContractResults": [
11 | {
12 | "hash": "SCR-hash1",
13 | "receiver": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
14 | "sender": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
15 | "operation": "transfer"
16 | }
17 | ],
18 | "logs": {
19 | "address": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
20 | "events": [
21 | {
22 | "address": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
23 | "identifier": "transferValueOnly",
24 | "topics": [
25 | "AAAAAAAAAAAFAAc2sq9MjfurX+5zluCpV5y58QqNoBI=",
26 | "qfaoGqfadnipWFWCnaJeOiknm8PQtEqI8lHBatO+rNw=",
27 | "A+g="
28 | ],
29 | "data": null
30 | },
31 | {
32 | "address": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
33 | "identifier": "SCDeploy",
34 | "topics": [
35 | "AAAAAAAAAAAFAPiodj5xVGTYTcUG7NG4iLn/T/TUoBI=",
36 | "AAAAAAAAAAAFAAc2sq9MjfurX+5zluCpV5y58QqNoBI="
37 | ],
38 | "data": null
39 | },
40 | {
41 | "address": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
42 | "identifier": "writeLog",
43 | "topics": [
44 | "VuJjyDpv3q8GVEqm7SLrVgl7ySOB9Ya6HTmmeI4+oBI=",
45 | "QHRvbyBtdWNoIGdhcyBwcm92aWRlZCBmb3IgcHJvY2Vzc2luZzogZ2FzIHByb3ZpZGVkID0gNDk3NDE1MDAsIGdhcyB1c2VkID0gMTUxMzQyMA=="
46 | ],
47 | "data": "QDZmNmI="
48 | }
49 | ]
50 | },
51 | "status": "success",
52 | "operation": "transfer",
53 | "function": "do_deploy",
54 | "chainID": "T",
55 | "version": 2,
56 | "options": 0
57 | },
58 | "scrs": [
59 | {
60 | "type": "unsigned",
61 | "processingTypeOnSource": "MoveBalance",
62 | "processingTypeOnDestination": "MoveBalance",
63 | "hash": "SCR-hash1",
64 | "receiver": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
65 | "sender": "erd1adfmxhyczrl2t97yx92v5nywqyse0c7qh4xs0p4artg2utnu90pspgvqty",
66 | "status": "success",
67 | "operation": "transfer",
68 | "fee": "0",
69 | "callType": "directCall",
70 | "options": 0
71 | }
72 | ]
73 | }
74 |
--------------------------------------------------------------------------------
/process/testdata/malformedRelayedTxIntraShard.json:
--------------------------------------------------------------------------------
1 | {
2 | "transaction": {
3 | "type": "normal",
4 | "processingTypeOnSource": "RelayedTx",
5 | "processingTypeOnDestination": "RelayedTx",
6 | "hash": "a4823050d2396540b17bd9290523973763142c9f655bb26cd9e33f359b6d73ad",
7 | "receiver": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th",
8 | "sender": "erd1kyaqzaprcdnv4luvanah0gfxzzsnpaygsy6pytrexll2urtd05ts9vegu7",
9 | "gasPrice": 1000000000,
10 | "gasLimit": 11052000,
11 | "gasUsed": 11052000,
12 | "data": "cmVsYXllZFR4QDdiMjI2ZTZmNmU2MzY1MjIzYTM4MzUzODJjMjI3NjYxNmM3NTY1MjIzYTMxMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMzAzMDMwMmMyMjcyNjU2MzY1Njk3NjY1NzIyMjNhMjI3MzU0NmY0MjY0NDM1MDQ0NWE3MzcyMmY2YTRmN2EzNzY0MzY0NTZkNDU0YjQ1NzczOTQ5Njk0MjRlNDI0OTczNjU1NDY2MmI3MjY3MzE3NDY2NTI2MzNkMjIyYzIyNzM2NTZlNjQ2NTcyMjIzYTIyNDE1NDZjNDg0Yzc2Mzk2ZjY4NmU2MzYxNmQ0MzM4Nzc2NzM5NzA2NDUxNjgzODZiNzc3MDQ3NDIzNTZhNjk0OTQ5NmYzMzQ5NDg0YjU5NGU2MTY1NDUzZDIyMmMyMjY3NjE3MzUwNzI2OTYzNjUyMjNhMzEzMDMwMzAzMDMwMzAzMDMwMzAyYzIyNjc2MTczNGM2OTZkNjk3NDIyM2EzMTMwMzAzMDMwMzAzMDMwMmMyMjYzNjg2MTY5NmU0OTQ0MjIzYTIyNTI0MTNkM2QyMjJjMjI3NjY1NzI3MzY5NmY2ZTIyM2EzMTJjMjI3MzY5Njc2ZTYxNzQ3NTcyNjUyMjNhMjI1NjRjNGY1ODQ5NTI3YTZkNDY0ZTMwNTg1MDYxNTc3MDM4NWEzMDUwNjM3NzY5MzkzOTRmNTQyYjZiNmI3Nzc0Mzg1MTU3Mzc2NjM0NzU2ZDVhNjI3ODU0NmEzNzQyNzc0NTU5NTg2MzRiMzM0NjU2NGMzNTY3NDM2YTUyNzk3MTcwMzc2ZTYzNTg2YzU1NDM3Mzc3NmY0ZTZiNmQ2MzUyNzI1MjM5NTM0MjQxM2QzZDIyN2Q=",
13 | "notarizedAtSourceInMetaNonce":2171094,
14 | "NotarizedAtSourceInMetaHash":"02d38e178073d1af3b77c2dc87da50f8139e00b468c436fc04f22879acf5a45f",
15 | "notarizedAtDestinationInMetaNonce":2171094,
16 | "notarizedAtDestinationInMetaHash":"02d38e178073d1af3b77c2dc87da50f8139e00b468c436fc04f22879acf5a45f",
17 | "status": "success",
18 | "receivers": [
19 | "erd1kyaqzaprcdnv4luvanah0gfxzzsnpaygsy6pytrexll2urtd05ts9vegu7"
20 | ],
21 | "receiversShardIDs": [
22 | 1
23 | ],
24 | "operation": "transfer",
25 | "initiallyPaidFee": "1152000000000000",
26 | "fee": "1152000000000000",
27 | "isRelayed": true,
28 | "chainID": "D",
29 | "version": 1,
30 | "options": 0
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/process/testdata/malformedRelayedV2TxIntraShard.json:
--------------------------------------------------------------------------------
1 | {
2 | "transaction": {
3 | "type": "normal",
4 | "processingTypeOnSource": "RelayedTxV2",
5 | "processingTypeOnDestination": "RelayedTxV2",
6 | "hash": "47845341c9bf9172fdb8ebc8a7db729ff91c66c8d708e042af7a0b3f6a3c337e",
7 | "nonce": 0,
8 | "round": 1590,
9 | "epoch": 7,
10 | "value": "0",
11 | "receiver": "erd1jrvq9emyfd97xs2kq53qedkz4sv40aayjt7tjcw9x6kjujvuv8uqnww788",
12 | "sender": "erd19al0qr2zcgu067sku2m86djxp5dexle6we2lrlm63z29q2yawfsqmnxk78",
13 | "data": "cmVsYXllZFR4VjJAYWY2ZjM2M2E4NWVkNmRjYWQwMDJkNjRhMzdlMmMyMmQwYWVjYTEzNWY0MWZlM2UwYjhhNDlkYzAwNWZkZDNhOEAwMUA3MjYxNmU2NDZmNmRAZDVjNDRiNDk5MTI3ZDA3ZWE5YTdhYTgyMDVlYjUxMmMyYzRmYzhjZTA2NzcwN2QxMWZkNmI2NmI0ZmEzYWQwMWRiNzk4ODY2YzEyMzM3Y2JjODYyZWMzMzllNzAzMzBlMGZkZGQyZTBjZTViNGUzOGYxYzdjZGM0ZTczYWFmMDU=",
14 | "notarizedAtSourceInMetaNonce": 1477,
15 | "NotarizedAtSourceInMetaHash": "20d21f4610e5c997a82ede2e9da9bb6ba4e806488829a5e88d74ec6f735979a5",
16 | "notarizedAtDestinationInMetaNonce": 1477,
17 | "notarizedAtDestinationInMetaHash": "20d21f4610e5c997a82ede2e9da9bb6ba4e806488829a5e88d74ec6f735979a5",
18 | "smartContractResults": [
19 | {
20 | "hash": "SCR-hash1",
21 | "receiver": "erd14ahnvw59a4ku45qz6e9r0ckz959wegf47s078c9c5jwuqp0a6w5qgfjy87",
22 | "sender": "erd1jrvq9emyfd97xs2kq53qedkz4sv40aayjt7tjcw9x6kjujvuv8uqnww788",
23 | "relayerAddress": "erd19al0qr2zcgu067sku2m86djxp5dexle6we2lrlm63z29q2yawfsqmnxk78",
24 | "data": "random",
25 | "operation": "transfer"
26 | }
27 | ],
28 | "status": "success",
29 | "receivers": [
30 | "erd14ahnvw59a4ku45qz6e9r0ckz959wegf47s078c9c5jwuqp0a6w5qgfjy87"
31 | ],
32 | "receiversShardIDs": [
33 | 0
34 | ],
35 | "operation": "transfer",
36 | "initiallyPaidFee": "481500000000000",
37 | "fee": "481500000000000",
38 | "isRelayed": true,
39 | "chainID": "1",
40 | "version": 1,
41 | "options": 0
42 | }
43 | }
--------------------------------------------------------------------------------
/process/testdata/pendingNewMoveBalance.json:
--------------------------------------------------------------------------------
1 | {
2 | "transaction": {
3 | "type": "normal",
4 | "processingTypeOnSource": "MoveBalance",
5 | "processingTypeOnDestination": "MoveBalance",
6 | "hash": "a4823050d2396540b17bd9290523973763142c9f655bb26cd9e33f359b6d73ad",
7 | "receiver": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th",
8 | "sender": "erd1kyaqzaprcdnv4luvanah0gfxzzsnpaygsy6pytrexll2urtd05ts9vegu7",
9 | "value": "1500000000000000000",
10 | "gasPrice": 1000000000,
11 | "gasLimit": 50000,
12 | "gasUsed": 50000,
13 | "sourceShard": 2,
14 | "destinationShard": 1,
15 | "miniblockType": "TxBlock",
16 | "miniblockHash": "a4823050d2396540b17bd9290523973763142c9f655bb26cd9e33f359b6d73ad",
17 | "status": "pending",
18 | "operation": "transfer",
19 | "initiallyPaidFee": "50000000000000",
20 | "fee": "50000000000000",
21 | "chainID": "T",
22 | "version": 1,
23 | "options": 0
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/process/testdata/pendingNewSCCall.json:
--------------------------------------------------------------------------------
1 | {
2 | "transaction": {
3 | "type": "normal",
4 | "processingTypeOnSource": "MoveBalance",
5 | "processingTypeOnDestination": "SCInvoking",
6 | "hash": "a4823050d2396540b17bd9290523973763142c9f655bb26cd9e33f359b6d73ad",
7 | "receiver": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u",
8 | "sender": "erd1kyaqzaprcdnv4luvanah0gfxzzsnpaygsy6pytrexll2urtd05ts9vegu7",
9 | "value": "0",
10 | "gasPrice": 1000000000,
11 | "gasLimit": 40000000,
12 | "gasUsed": 1433000,
13 | "data": "",
14 | "sourceShard": 0,
15 | "destinationShard": 1,
16 | "miniblockType": "TxBlock",
17 | "miniblockHash": "a4823050d2396540b17bd9290523973763142c9f655bb26cd9e33f359b6d73ad",
18 | "status": "pending",
19 | "operation": "transfer",
20 | "function": "call",
21 | "initiallyPaidFee": "1818670000000000",
22 | "fee": "1433000000000000",
23 | "chainID": "T",
24 | "version": 1,
25 | "options": 0
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/process/txcost/converters.go:
--------------------------------------------------------------------------------
1 | package txcost
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/multiversx/mx-chain-proxy-go/data"
7 | )
8 |
9 | const argsSeparator = "@"
10 |
11 | func (tcp *transactionCostProcessor) computeShardID(addr string) (uint32, error) {
12 | senderBuff, err := tcp.pubKeyConverter.Decode(addr)
13 | if err != nil {
14 | return 0, err
15 | }
16 |
17 | return tcp.proc.ComputeShardId(senderBuff)
18 | }
19 |
20 | func (tcp *transactionCostProcessor) computeSenderAndReceiverShardID(sender, receiver string) (uint32, uint32, error) {
21 | senderShardID, err := tcp.computeShardID(sender)
22 | if err != nil {
23 | return 0, 0, err
24 | }
25 |
26 | receiverShardID, err := tcp.computeShardID(receiver)
27 | if err != nil {
28 | return 0, 0, err
29 | }
30 |
31 | return senderShardID, receiverShardID, nil
32 | }
33 |
34 | func convertSCRInTransaction(scr *data.ExtendedApiSmartContractResult, originalTx *data.Transaction) *data.Transaction {
35 | newDataField := removeLatestArgumentFromDataField(scr.Data)
36 |
37 | return &data.Transaction{
38 | Nonce: scr.Nonce,
39 | Value: scr.Value.String(),
40 | Receiver: scr.RcvAddr,
41 | Sender: scr.SndAddr,
42 | GasPrice: scr.GasPrice,
43 | GasLimit: scr.GasLimit,
44 | Data: []byte(newDataField),
45 | Signature: "",
46 | ChainID: originalTx.ChainID,
47 | Version: originalTx.Version,
48 | Options: originalTx.Options,
49 | }
50 | }
51 |
52 | func removeLatestArgumentFromDataField(dataField string) string {
53 | splitDataField := strings.Split(dataField, argsSeparator)
54 | newStr := splitDataField[:len(splitDataField)-1]
55 | if len(newStr) == 0 {
56 | return dataField
57 | }
58 |
59 | newDataField := strings.Join(newStr, argsSeparator)
60 |
61 | return newDataField
62 | }
63 |
--------------------------------------------------------------------------------
/process/txcost/converters_test.go:
--------------------------------------------------------------------------------
1 | package txcost
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func TestRemoveLatestArgumentFromDataField(t *testing.T) {
10 | t.Parallel()
11 |
12 | dataField := removeLatestArgumentFromDataField("function@arg1@arg2@arg3@shouldBeRemoved")
13 | require.Equal(t, "function@arg1@arg2@arg3", dataField)
14 |
15 | dataField = removeLatestArgumentFromDataField("@new@arg1@arg2@arg3@shouldBeRemoved")
16 | require.Equal(t, "@new@arg1@arg2@arg3", dataField)
17 |
18 | dataField = removeLatestArgumentFromDataField("1@2@3")
19 | require.Equal(t, "1@2", dataField)
20 |
21 | dataField = removeLatestArgumentFromDataField("")
22 | require.Equal(t, "", dataField)
23 |
24 | dataField = removeLatestArgumentFromDataField("the-field")
25 | require.Equal(t, "the-field", dataField)
26 | }
27 |
--------------------------------------------------------------------------------
/process/txcost/errors.go:
--------------------------------------------------------------------------------
1 | package txcost
2 |
3 | import "errors"
4 |
5 | // ErrNilPubKeyConverter signals that a nil pub key converter has been provided
6 | var ErrNilPubKeyConverter = errors.New("nil pub key converter provided")
7 |
8 | // ErrNilCoreProcessor signals that a nil core processor has been provided
9 | var ErrNilCoreProcessor = errors.New("nil core processor")
10 |
11 | // ErrSendingRequest signals that sending the request failed on all observers
12 | var ErrSendingRequest = errors.New("sending request error")
13 |
--------------------------------------------------------------------------------
/process/txcost/gasUsed.go:
--------------------------------------------------------------------------------
1 | package txcost
2 |
3 | import (
4 | "runtime/debug"
5 |
6 | "github.com/multiversx/mx-chain-proxy-go/data"
7 | )
8 |
9 | func (tcp *transactionCostProcessor) prepareGasUsed(senderShardID, receiverShardID uint32, res *data.TxCostResponseData) {
10 | extra := 0
11 | if senderShardID != receiverShardID {
12 | extra = 1
13 | }
14 |
15 | tcp.computeResponsesGasUsed(extra, res)
16 | }
17 |
18 | func (tcp *transactionCostProcessor) computeResponsesGasUsed(extra int, res *data.TxCostResponseData) {
19 | numResponses := len(tcp.responses)
20 |
21 | to := numResponses - 1 - extra
22 | gasUsed := uint64(0)
23 | for idx := 0; idx < to; idx++ {
24 | responseIndex := idx + extra
25 | if numResponses-1 < responseIndex || len(tcp.txsFromSCR)-1 < idx {
26 | log.Warn("transactionCostProcessor.computeResponsesGasUsed()", "stack", string(debug.Stack()))
27 |
28 | res.RetMessage = "something went wrong"
29 | res.TxCost = 0
30 | return
31 | }
32 |
33 | diff := tcp.responses[idx+extra].Data.TxCost - tcp.txsFromSCR[idx].GasLimit
34 | gasUsed += diff
35 | }
36 |
37 | gasForLastResponse := tcp.responses[numResponses-1].Data.TxCost
38 | gasUsed += gasForLastResponse
39 | res.TxCost = gasUsed
40 | }
41 |
--------------------------------------------------------------------------------
/process/txcost/gasUsed_test.go:
--------------------------------------------------------------------------------
1 | package txcost
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/multiversx/mx-chain-proxy-go/data"
7 | "github.com/multiversx/mx-chain-proxy-go/process/mock"
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func TestTransactionCostProcessor_IndexOutOfBounds(t *testing.T) {
12 | t.Parallel()
13 |
14 | coreProc := &mock.ProcessorStub{}
15 | newTxCostProcessor, _ := NewTransactionCostProcessor(
16 | coreProc, &mock.PubKeyConverterMock{})
17 | newTxCostProcessor.responses = append(newTxCostProcessor.responses, &data.ResponseTxCost{})
18 | newTxCostProcessor.responses = append(newTxCostProcessor.responses, &data.ResponseTxCost{})
19 | newTxCostProcessor.responses = append(newTxCostProcessor.responses, &data.ResponseTxCost{})
20 |
21 | res := &data.TxCostResponseData{}
22 | newTxCostProcessor.prepareGasUsed(0, 0, res)
23 | require.Equal(t, "something went wrong", res.RetMessage)
24 | }
25 |
26 | func TestTransactionCostProcessor_PrepareGasUsedShouldWork(t *testing.T) {
27 | t.Parallel()
28 |
29 | coreProc := &mock.ProcessorStub{}
30 | newTxCostProcessor, _ := NewTransactionCostProcessor(
31 | coreProc, &mock.PubKeyConverterMock{})
32 | newTxCostProcessor.responses = append(newTxCostProcessor.responses, &data.ResponseTxCost{
33 | Data: data.TxCostResponseData{
34 | TxCost: 500,
35 | },
36 | })
37 | newTxCostProcessor.responses = append(newTxCostProcessor.responses, &data.ResponseTxCost{
38 | Data: data.TxCostResponseData{
39 | TxCost: 1000,
40 | },
41 | })
42 | newTxCostProcessor.txsFromSCR = append(newTxCostProcessor.txsFromSCR, &data.Transaction{
43 | GasLimit: 200,
44 | })
45 |
46 | res := &data.TxCostResponseData{
47 | TxCost: 500,
48 | }
49 |
50 | expectedGas := uint64(1300)
51 | newTxCostProcessor.prepareGasUsed(0, 0, res)
52 | require.Equal(t, expectedGas, res.TxCost)
53 | require.Equal(t, "", res.RetMessage)
54 | }
55 |
--------------------------------------------------------------------------------
/process/v1_0/doc.go:
--------------------------------------------------------------------------------
1 | // Package v1.0 represents the processors needed for the version v1.0 of the API
2 | package v1_0
3 |
--------------------------------------------------------------------------------
/process/v_next/accountProcessorV_next.go:
--------------------------------------------------------------------------------
1 | package v_next
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-proxy-go/process"
5 | )
6 |
7 | // AccountProcessorV_next is the account processor for the version v_next
8 | type AccountProcessorV_next struct {
9 | *process.AccountProcessor
10 | }
11 |
12 | // GetShardIDForAddress is an example of an updated endpoint the version v_next
13 | func (ap *AccountProcessorV_next) GetShardIDForAddress(address string, additionalField int) (uint32, error) {
14 | return 37, nil
15 | }
16 |
17 | // NextEndpointHandler is an example of a new endpoint in the version v_next
18 | func (ap *AccountProcessorV_next) NextEndpointHandler() string {
19 | return "test"
20 | }
21 |
--------------------------------------------------------------------------------
/process/v_next/doc.go:
--------------------------------------------------------------------------------
1 | // Package v_next represents the processors needed for the example version v_next of the API
2 | package v_next
3 |
--------------------------------------------------------------------------------
/process/validatorAuctionProcessor.go:
--------------------------------------------------------------------------------
1 | package process
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-core-go/core"
5 | "github.com/multiversx/mx-chain-proxy-go/data"
6 | )
7 |
8 | // GetAuctionList returns the auction list from a metachain observer node
9 | func (vsp *ValidatorStatisticsProcessor) GetAuctionList() (*data.AuctionListResponse, error) {
10 | observers, errFetchObs := vsp.proc.GetObservers(core.MetachainShardId, data.AvailabilityRecent)
11 | if errFetchObs != nil {
12 | return nil, errFetchObs
13 | }
14 |
15 | var valStatsResponse data.AuctionListAPIResponse
16 | for _, observer := range observers {
17 | _, err := vsp.proc.CallGetRestEndPoint(observer.Address, auctionListPath, &valStatsResponse)
18 | if err == nil {
19 | log.Info("auction list fetched from API", "observer", observer.Address)
20 | return &valStatsResponse.Data, nil
21 | }
22 |
23 | log.Error("getAuctionListFromApi", "observer", observer.Address, "error", err)
24 | }
25 |
26 | return nil, ErrAuctionListNotAvailable
27 | }
28 |
--------------------------------------------------------------------------------
/rosetta/README.md:
--------------------------------------------------------------------------------
1 | ## Multiversx Rosetta
2 |
3 | Moved at [Multiversx/rosetta](https://github.com/multiversx/mx-chain-rosetta).
4 |
--------------------------------------------------------------------------------
/versions/errors.go:
--------------------------------------------------------------------------------
1 | package versions
2 |
3 | import "errors"
4 |
5 | // ErrNilFacadeHandler signals that a nil facade handler has been provided
6 | var ErrNilFacadeHandler = errors.New("nil facade handler")
7 |
8 | // ErrNilApiHandler signals that the provided api handler is nil
9 | var ErrNilApiHandler = errors.New("nil api handler")
10 |
11 | // ErrNoVersionIsSet signals that no version is provided in the environment
12 | var ErrNoVersionIsSet = errors.New("no version is set")
13 |
14 | // ErrVersionNotFound signals that a provided version does not exist
15 | var ErrVersionNotFound = errors.New("version not found")
16 |
--------------------------------------------------------------------------------
/versions/factory/apiConfigParser.go:
--------------------------------------------------------------------------------
1 | package factory
2 |
3 | import (
4 | "fmt"
5 | "path/filepath"
6 |
7 | "github.com/multiversx/mx-chain-core-go/core"
8 | "github.com/multiversx/mx-chain-proxy-go/data"
9 | )
10 |
11 | type apiConfigParser struct {
12 | baseDir string
13 | }
14 |
15 | // NewApiConfigParser returns a new instance of apiConfigParser
16 | func NewApiConfigParser(baseDir string) (*apiConfigParser, error) {
17 | err := checkDirectoryPath(baseDir)
18 | if err != nil {
19 | return nil, err
20 | }
21 |
22 | return &apiConfigParser{
23 | baseDir: baseDir,
24 | }, nil
25 | }
26 |
27 | // GetConfigForVersion will open the configuration file and load the api routes config
28 | func (acp *apiConfigParser) GetConfigForVersion(version string) (*data.ApiRoutesConfig, error) {
29 | filePath := filepath.Join(acp.baseDir, fmt.Sprintf("%s.toml", version))
30 | return loadApiConfig(filePath)
31 | }
32 |
33 | func checkDirectoryPath(baseDirectory string) error {
34 | file, err := core.OpenFile(baseDirectory)
35 | if err != nil {
36 | return err
37 | }
38 |
39 | fileStats, err := file.Stat()
40 | if err != nil {
41 | return err
42 | }
43 |
44 | if fileStats.IsDir() {
45 | return nil
46 | }
47 |
48 | return ErrNoDirectoryAtPath
49 | }
50 |
51 | func loadApiConfig(filepath string) (*data.ApiRoutesConfig, error) {
52 | cfg := &data.ApiRoutesConfig{}
53 | err := core.LoadTomlFile(cfg, filepath)
54 | if err != nil {
55 | return nil, err
56 | }
57 |
58 | return cfg, nil
59 | }
60 |
--------------------------------------------------------------------------------
/versions/factory/apiConfigParser_test.go:
--------------------------------------------------------------------------------
1 | package factory
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | const dirLocation = "testdata"
11 |
12 | func TestNewApiConfigParser_IncorrectPath(t *testing.T) {
13 | acp, err := NewApiConfigParser("wrong path")
14 | require.Nil(t, acp)
15 | require.Error(t, err)
16 | }
17 |
18 | func TestNewApiConfigParser_FileInsteadOfDirectory(t *testing.T) {
19 | acp, err := NewApiConfigParser(fmt.Sprintf("%s/%s", dirLocation, "vx_x.toml"))
20 | require.Error(t, err)
21 | require.Nil(t, acp)
22 | }
23 |
24 | func TestNewApiConfigParser(t *testing.T) {
25 | acp, err := NewApiConfigParser(dirLocation)
26 | require.NoError(t, err)
27 | require.NotNil(t, acp)
28 | }
29 |
30 | func TestApiConfigParser_GetConfigForVersionInvalidVersion(t *testing.T) {
31 | acp, _ := NewApiConfigParser(dirLocation)
32 |
33 | res, err := acp.GetConfigForVersion("wrong version")
34 | require.Nil(t, res)
35 | require.Error(t, err)
36 | }
37 |
38 | func TestApiConfigParser_GetConfigForVersion(t *testing.T) {
39 | acp, _ := NewApiConfigParser(dirLocation)
40 |
41 | res, err := acp.GetConfigForVersion("vx_x")
42 | require.NoError(t, err)
43 | require.NotNil(t, res)
44 |
45 | require.Equal(t, 1, len(res.APIPackages))
46 |
47 | endpointConfig, ok := res.APIPackages["testendpoint"]
48 | require.True(t, ok)
49 | require.Equal(t, 3, len(endpointConfig.Routes))
50 | }
51 |
--------------------------------------------------------------------------------
/versions/factory/errors.go:
--------------------------------------------------------------------------------
1 | package factory
2 |
3 | import "errors"
4 |
5 | // ErrNoDirectoryAtPath signals that the file is not a directory
6 | var ErrNoDirectoryAtPath = errors.New("no directory at the given path")
7 |
--------------------------------------------------------------------------------
/versions/factory/interface.go:
--------------------------------------------------------------------------------
1 | package factory
2 |
3 | import (
4 | "github.com/multiversx/mx-chain-proxy-go/data"
5 | )
6 |
7 | // ApiConfigParser defines the actions that an api config parser should be able to do
8 | type ApiConfigParser interface {
9 | GetConfigForVersion(version string) (*data.ApiRoutesConfig, error)
10 | }
11 |
--------------------------------------------------------------------------------
/versions/factory/testdata/vx_x.toml:
--------------------------------------------------------------------------------
1 | [APIPackages]
2 |
3 | [APIPackages.testendpoint]
4 | Routes = [
5 | { Name = "/test-secured", Open = true, Secured = true },
6 | { Name = "/test-unsecured", Open = true, Secured = false },
7 | { Name = "/test-closed", Open = false, Secured = false }
8 | ]
9 |
--------------------------------------------------------------------------------
/versions/versionsRegistry.go:
--------------------------------------------------------------------------------
1 | package versions
2 |
3 | import (
4 | "sync"
5 |
6 | "github.com/multiversx/mx-chain-proxy-go/data"
7 | )
8 |
9 | type versionsRegistry struct {
10 | versions map[string]*data.VersionData
11 | sync.RWMutex
12 | }
13 |
14 | // NewVersionsRegistry returns a new instance of versionsRegistry
15 | func NewVersionsRegistry() *versionsRegistry {
16 | return &versionsRegistry{
17 | versions: make(map[string]*data.VersionData),
18 | }
19 | }
20 |
21 | // AddVersion will add the version and its corresponding handler to the inner map
22 | func (vm *versionsRegistry) AddVersion(version string, versionData *data.VersionData) error {
23 | if versionData.Facade == nil {
24 | return ErrNilFacadeHandler
25 | }
26 | if versionData.ApiHandler == nil {
27 | return ErrNilApiHandler
28 | }
29 |
30 | vm.Lock()
31 | vm.versions[version] = versionData
32 | vm.Unlock()
33 |
34 | return nil
35 | }
36 |
37 | // GetAllVersions returns a slice containing all the versions in string format
38 | func (vm *versionsRegistry) GetAllVersions() (map[string]*data.VersionData, error) {
39 | vm.RLock()
40 | defer vm.RUnlock()
41 | if len(vm.versions) == 0 {
42 | return nil, ErrNoVersionIsSet
43 | }
44 |
45 | return vm.versions, nil
46 | }
47 |
48 | // IsInterfaceNil returns true if there is no value under the interface
49 | func (vm *versionsRegistry) IsInterfaceNil() bool {
50 | return vm == nil
51 | }
52 |
--------------------------------------------------------------------------------