├── .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 | --------------------------------------------------------------------------------