diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1967eba0589..1852d8881b0 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -19,3 +19,12 @@ Based on the [Contributing Guidelines](https://github.com/multiversx/mx-chain-go - was the PR targeted to the correct branch? - if this is a larger feature that probably needs more than one PR, is there a `feat` branch created? - if this is a `feat` branch merging, do all satellite projects have a proper tag inside `go.mod`? + +## Triggering Tests from Comments +To re-run integration tests with custom branches, add a comment in this format: + +``` +Run Tests: +mx-chain-simulator-go: feat-sub-second-round-2 +mx-chain-testing-suite: fix_ci +``` \ No newline at end of file diff --git a/.github/workflows/build_and_run_chain_simulator_and_execute_system_test.yml b/.github/workflows/build_and_run_chain_simulator_and_execute_system_test.yml index 6c3ac00f3f6..2f578e1514b 100644 --- a/.github/workflows/build_and_run_chain_simulator_and_execute_system_test.yml +++ b/.github/workflows/build_and_run_chain_simulator_and_execute_system_test.yml @@ -6,9 +6,6 @@ on: - 'main' - 'master' - 'rc/*' - workflow_dispatch: - issue_comment: - types: [created] permissions: issues: write @@ -18,9 +15,7 @@ permissions: jobs: build-and-test: if: | - github.event_name == 'pull_request' || - (github.event_name == 'issue_comment' && contains(github.event.comment.body, 'Run Tests:')) || - github.event_name == 'workflow_dispatch' + github.event_name == 'pull_request' runs-on: [self-hosted, Linux, X64] env: @@ -67,11 +62,18 @@ jobs: with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | + const issue_number = context.issue.number; + + if (!issue_number) { + core.error('Could not determine PR number from pull_request event'); + return; + } + // Get all comments const comments = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, - issue_number: context.issue.number, + issue_number: issue_number, }); // Find the last comment that contains 'Run Tests:' @@ -104,7 +106,7 @@ jobs: } else { core.info('No comment containing "Run Tests:" was found. Using default branch settings.'); } - + - name: Print Target Branches run: | @@ -210,7 +212,7 @@ jobs: # Wait for the initialization to complete - look for multiple possible success patterns INIT_COMPLETED=false RETRY_COUNT=0 - MAX_RETRIES=60 # Increase timeout to 60 seconds + MAX_RETRIES=120 echo "Waiting for ChainSimulator initialization..." while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do @@ -287,56 +289,38 @@ jobs: run: | set +e pytest mx-chain-testing-suite/scenarios/ --html=report.html --self-contained-html --continue-on-collection-errors - INITIAL_PYTEST_EXIT_CODE=$? + PYTEST_EXIT_CODE=$? set -e - - echo "Initial pytest exit code: $INITIAL_PYTEST_EXIT_CODE" - - # Parse the HTML report to determine actual test status - FINAL_PYTEST_EXIT_CODE=$INITIAL_PYTEST_EXIT_CODE - + + echo "Pytest exit code: $PYTEST_EXIT_CODE" + if [ -f "report.html" ]; then echo "Report generated successfully." - - # Extract test summary from HTML report using multiple parsing approaches - FAILED_TESTS=$(grep -E '[0-9]+ Failed|Failed.*[0-9]+' report.html | head -1 | grep -o '[0-9]\+' | head -1 || echo "0") - ERROR_TESTS=$(grep -E '[0-9]+ Errors|Errors.*[0-9]+' report.html | head -1 | grep -o '[0-9]\+' | head -1 || echo "0") - RERUN_TESTS=$(grep -E '[0-9]+ Reruns|Reruns.*[0-9]+' report.html | head -1 | grep -o '[0-9]\+' | head -1 || echo "0") - - # Fallback: check for "0 Failed" pattern directly - if grep -q "0 Failed" report.html && [ "$INITIAL_PYTEST_EXIT_CODE" -ne 0 ]; then - echo "Detected '0 Failed' pattern in report despite non-zero exit code - likely successful reruns" - FAILED_TESTS=0 - fi - + + # Extract test metrics from HTML checkbox spans + FAILED_TESTS=$(grep -oP '\d+(?= Failed,)' report.html | head -1 || echo "0") + ERROR_TESTS=$(grep -oP '\d+(?= Errors,)' report.html | head -1 || echo "0") + RERUN_TESTS=$(grep -oP '\d+(?= Reruns)' report.html | head -1 || echo "0") + PASSED_TESTS=$(grep -oP '\d+(?= Passed,)' report.html | head -1 || echo "0") + echo "Failed tests: $FAILED_TESTS" echo "Error tests: $ERROR_TESTS" echo "Rerun tests: $RERUN_TESTS" - - # Determine final status based on actual test results - if [ "$FAILED_TESTS" -eq 0 ] && [ "$ERROR_TESTS" -eq 0 ]; then - if [ "$RERUN_TESTS" -gt 0 ]; then - echo "āœ… All tests passed after $RERUN_TESTS reruns - considering as SUCCESS" - else - echo "āœ… All tests passed on first attempt" - fi - FINAL_PYTEST_EXIT_CODE=0 - else - echo "āŒ Tests have genuine failures: $FAILED_TESTS failed, $ERROR_TESTS errors" - FINAL_PYTEST_EXIT_CODE=1 - fi - - # Move report to reports directory + echo "Passed tests: $PASSED_TESTS" + + echo "FAILED_TESTS=$FAILED_TESTS" >> $GITHUB_ENV + echo "ERROR_TESTS=$ERROR_TESTS" >> $GITHUB_ENV + echo "RERUN_TESTS=$RERUN_TESTS" >> $GITHUB_ENV + echo "PASSED_TESTS=$PASSED_TESTS" >> $GITHUB_ENV + mkdir -p ./reports mv report.html ./reports/ else - echo "āŒ Report not found - using original exit code" - FINAL_PYTEST_EXIT_CODE=$INITIAL_PYTEST_EXIT_CODE + echo "Report not found" fi - - echo "PYTEST_EXIT_CODE=$FINAL_PYTEST_EXIT_CODE" >> $GITHUB_ENV - echo "Final pytest exit code: $FINAL_PYTEST_EXIT_CODE" + echo "PYTEST_EXIT_CODE=$PYTEST_EXIT_CODE" >> $GITHUB_ENV + - name: Stage report for R2 run: | mkdir -p r2_upload @@ -453,9 +437,16 @@ jobs: const exitCode = process.env.PYTEST_EXIT_CODE; const simulatorCommitHash = process.env.SIMULATOR_COMMIT_HASH || 'N/A'; + const testingSuiteCommitHash = process.env.CURRENT_COMMIT_HASH || 'N/A'; const r2Url = process.env.R2_REPORT_URL || 'N/A'; const issue_number = context.issue.number; + + if (!issue_number) { + console.log('Could not determine PR number'); + return; + } + const owner = context.repo.owner; const repo = context.repo.repo; const ts = process.env.TS || process.env.TIMESTAMP; @@ -475,8 +466,9 @@ jobs: - **Current Branch:** \`${currentBranch}\` - **mx-chain-go Target Branch:** \`${goTargetBranch}\` - **mx-chain-simulator-go Target Branch:** \`${simulatorTargetBranch}\` - - **mx-chain-testing-suite Target Branch:** \`${testingSuiteTargetBranch}\` - **mx-chain-simulator-go Commit Hash:** \`${simulatorCommitHash}\` + - **mx-chain-testing-suite Target Branch:** \`${testingSuiteTargetBranch}\` + - **mx-chain-testing-suite Commit Hash:** \`${testingSuiteCommitHash}\` šŸš€ **Environment Variables:** - **TIMESTAMP:** \`${ts}\` @@ -504,13 +496,22 @@ jobs: SIMULATOR_COMMIT_HASH: ${{ env.SIMULATOR_COMMIT_HASH }} TS: ${{ env.TS }} PYTEST_EXIT_CODE: ${{ env.PYTEST_EXIT_CODE }} + FAILED_TESTS: ${{ env.FAILED_TESTS }} + ERROR_TESTS: ${{ env.ERROR_TESTS }} + RERUN_TESTS: ${{ env.RERUN_TESTS }} + PASSED_TESTS: ${{ env.PASSED_TESTS }} run: | status="āŒ FAILED" [ "${PYTEST_EXIT_CODE}" = "0" ] && status="āœ… PASSED" repo="${GITHUB_REPOSITORY##*/}" base="${{ github.base_ref }}" + pr_url="https://github.com/${{ github.repository }}/pull/${{ github.event.pull_request.number }}" msg="*MX-CHAIN-GO ChainSimulator CI – ${status}*\ + \n============================================\ + \n${PASSED_TESTS:-0} PASSED | ${FAILED_TESTS:-0} FAILED | ${ERROR_TESTS:-0} ERRORS | ${RERUN_TESTS:-0} RERUNS\ + \n============================================\ + \n• *PR:* <${pr_url}|#${{ github.event.pull_request.number }}>\ \n• *Report:* <${R2_REPORT_URL:-N/A}|HTML>\ \n• *Repository:* \`${repo}\`\ \n• *Branch:* \`${BRANCH_NAME}\` → \`${base:-main}\`\ @@ -532,7 +533,7 @@ jobs: else echo "Tests passed successfully." fi - + - name: Cleanup Workspace and Processes if: always() diff --git a/api/errors/errors.go b/api/errors/errors.go index 88ebeeec1c2..3cd1a412cc4 100644 --- a/api/errors/errors.go +++ b/api/errors/errors.go @@ -94,6 +94,9 @@ var ErrGetTransaction = errors.New("getting transaction failed") // ErrGetSmartContractResults signals an error happening when trying to fetch smart contract results var ErrGetSmartContractResults = errors.New("getting smart contract results failed") +// ErrGetVirtualNonce signals an error happening when trying to get the virtual nonce of an account +var ErrGetVirtualNonce = errors.New("getting virtual nonce of account failed") + // ErrGetBlock signals an error happening when trying to fetch a block var ErrGetBlock = errors.New("getting block failed") diff --git a/api/gin/webServer.go b/api/gin/webServer.go index f7228373979..2769fe56a15 100644 --- a/api/gin/webServer.go +++ b/api/gin/webServer.go @@ -12,19 +12,21 @@ import ( "github.com/gin-gonic/gin" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/marshal" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/multiversx/mx-chain-go/api/errors" "github.com/multiversx/mx-chain-go/api/groups" "github.com/multiversx/mx-chain-go/api/middleware" "github.com/multiversx/mx-chain-go/api/shared" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/facade" - logger "github.com/multiversx/mx-chain-logger-go" - "github.com/prometheus/client_golang/prometheus/promhttp" ) var log = logger.GetOrCreate("api/gin") const prometheusMetricsRoute = "/debug/metrics/prometheus" +const readHeaderTimeout = 10 * time.Second // ArgsNewWebServer holds the arguments needed to create a new instance of webServer type ArgsNewWebServer struct { @@ -126,7 +128,11 @@ func (ws *webServer) StartHttpServer() error { ws.registerRoutes(engine) - server := &http.Server{Addr: ws.facade.RestApiInterface(), Handler: engine} + server := &http.Server{ + Addr: ws.facade.RestApiInterface(), + Handler: engine, + ReadHeaderTimeout: readHeaderTimeout, + } log.Debug("creating gin web sever", "interface", ws.facade.RestApiInterface()) ws.httpServer, err = NewHttpServer(server) if err != nil { diff --git a/api/groups/networkGroup.go b/api/groups/networkGroup.go index 8a8e7854f9d..f69915c5089 100644 --- a/api/groups/networkGroup.go +++ b/api/groups/networkGroup.go @@ -22,6 +22,8 @@ const ( getStatusPath = "/status" economicsPath = "/economics" enableEpochsPath = "/enable-epochs" + enableEpochsV2Path = "/enable-epochs-v2" + enableRoundsPath = "/enable-rounds" getESDTsPath = "/esdts" getFFTsPath = "/esdt/fungible-tokens" getSFTsPath = "/esdt/semi-fungible-tokens" @@ -99,6 +101,16 @@ func NewNetworkGroup(facade networkFacadeHandler) (*networkGroup, error) { Method: http.MethodGet, Handler: ng.getEnableEpochs, }, + { + Path: enableEpochsV2Path, + Method: http.MethodGet, + Handler: ng.getEnableEpochsV2, + }, + { + Path: enableRoundsPath, + Method: http.MethodGet, + Handler: ng.getEnableRounds, + }, { Path: getESDTsPath, Method: http.MethodGet, @@ -210,6 +222,34 @@ func (ng *networkGroup) getEnableEpochs(c *gin.Context) { ) } +// getEnableEpochsV2 returns all enable epoch flags with their activation epochs +func (ng *networkGroup) getEnableEpochsV2(c *gin.Context) { + enableEpochsMetrics := ng.getFacade().StatusMetrics().EnableEpochsMetricsV2() + + c.JSON( + http.StatusOK, + shared.GenericAPIResponse{ + Data: gin.H{"enableEpochs": enableEpochsMetrics}, + Error: "", + Code: shared.ReturnCodeSuccess, + }, + ) +} + +// getEnableRounds returns metrics related to the activation rounds of the network +func (ng *networkGroup) getEnableRounds(c *gin.Context) { + enableRoundsMetrics := ng.getFacade().StatusMetrics().EnableRoundsMetrics() + + c.JSON( + http.StatusOK, + shared.GenericAPIResponse{ + Data: gin.H{"enableRounds": enableRoundsMetrics}, + Error: "", + Code: shared.ReturnCodeSuccess, + }, + ) +} + // getNetworkStatus returns metrics related to the network status (shard specific) func (ng *networkGroup) getNetworkStatus(c *gin.Context) { networkMetrics, err := ng.getFacade().StatusMetrics().NetworkMetrics() diff --git a/api/groups/networkGroup_test.go b/api/groups/networkGroup_test.go index c809a632b56..9e9f5b22ab8 100644 --- a/api/groups/networkGroup_test.go +++ b/api/groups/networkGroup_test.go @@ -8,10 +8,12 @@ import ( "math/big" "net/http" "net/http/httptest" + "reflect" "strconv" "strings" "testing" + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data/api" apiErrors "github.com/multiversx/mx-chain-go/api/errors" "github.com/multiversx/mx-chain-go/api/groups" @@ -22,10 +24,21 @@ import ( "github.com/multiversx/mx-chain-go/node/external" "github.com/multiversx/mx-chain-go/statusHandler" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +type testStatusMetricsHandler interface { + core.AppStatusHandler + external.StatusMetricsHandler +} + +func createNetworkStatusMetrics() testStatusMetricsHandler { + sm, _ := statusHandler.NewStatusMetrics(&enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) + return sm +} + func TestNewNetworkGroup(t *testing.T) { t.Parallel() @@ -93,7 +106,7 @@ type gasConfigsData struct { func TestNetworkConfigMetrics_ShouldWork(t *testing.T) { t.Parallel() - statusMetricsProvider := statusHandler.NewStatusMetrics() + statusMetricsProvider := createNetworkStatusMetrics() key := common.MetricMinGasLimit val := uint64(37) statusMetricsProvider.SetUInt64Value(key, val) @@ -179,7 +192,7 @@ func TestGetNetworkStatus_ShouldReturnErrorIfFacadeReturnsError(t *testing.T) { func TestNetworkConfigMetrics_GasLimitGuardedTxShouldWork(t *testing.T) { t.Parallel() - statusMetricsProvider := statusHandler.NewStatusMetrics() + statusMetricsProvider := createNetworkStatusMetrics() key := common.MetricExtraGasLimitGuardedTx val := uint64(37) statusMetricsProvider.SetUInt64Value(key, val) @@ -209,7 +222,7 @@ func TestNetworkConfigMetrics_GasLimitGuardedTxShouldWork(t *testing.T) { func TestNetworkConfigMetrics_GasLimitRelayedTxShouldWork(t *testing.T) { t.Parallel() - statusMetricsProvider := statusHandler.NewStatusMetrics() + statusMetricsProvider := createNetworkStatusMetrics() key := common.MetricExtraGasLimitRelayedTx val := uint64(123) statusMetricsProvider.SetUInt64Value(key, val) @@ -239,7 +252,7 @@ func TestNetworkConfigMetrics_GasLimitRelayedTxShouldWork(t *testing.T) { func TestNetworkStatusMetrics_ShouldWork(t *testing.T) { t.Parallel() - statusMetricsProvider := statusHandler.NewStatusMetrics() + statusMetricsProvider := createNetworkStatusMetrics() key := common.MetricEpochNumber val := uint64(37) statusMetricsProvider.SetUInt64Value(key, val) @@ -267,7 +280,7 @@ func TestNetworkStatusMetrics_ShouldWork(t *testing.T) { } func TestEconomicsMetrics_ShouldWork(t *testing.T) { - statusMetricsProvider := statusHandler.NewStatusMetrics() + statusMetricsProvider := createNetworkStatusMetrics() key := common.MetricTotalSupply val := "12345" statusMetricsProvider.SetStringValue(key, val) @@ -333,7 +346,7 @@ func TestGetEconomicValues_ShouldReturnErrorIfFacadeReturnsError(t *testing.T) { } func TestEconomicsMetrics_CannotGetStakeValues(t *testing.T) { - statusMetricsProvider := statusHandler.NewStatusMetrics() + statusMetricsProvider := createNetworkStatusMetrics() key := common.MetricTotalSupply val := "12345" statusMetricsProvider.SetStringValue(key, val) @@ -595,7 +608,7 @@ func TestGetEnableEpochs_ShouldReturnErrorIfFacadeReturnsError(t *testing.T) { func TestGetEnableEpochs_ShouldWork(t *testing.T) { t.Parallel() - statusMetrics := statusHandler.NewStatusMetrics() + statusMetrics := createNetworkStatusMetrics() key := common.MetricScDeployEnableEpoch val := uint64(4) statusMetrics.SetUInt64Value(key, val) @@ -622,6 +635,87 @@ func TestGetEnableEpochs_ShouldWork(t *testing.T) { assert.True(t, keyAndValueFoundInResponse) } +func TestGetEnableEpochsV2_ShouldWork(t *testing.T) { + t.Parallel() + + allEpochsMap := make(map[string]uint32) + typeOfCfg := reflect.TypeOf(config.EnableEpochs{}) + for i := 0; i < typeOfCfg.NumField(); i++ { + field := typeOfCfg.Field(i) + if field.Name == "MaxNodesChangeEnableEpoch" || + field.Name == "BLSMultiSignerEnableEpoch" { + // slices, ignoring + continue + } + allEpochsMap[field.Name] = uint32(i) + } + + statusMetrics, _ := statusHandler.NewStatusMetrics(&enableEpochsHandlerMock.EnableEpochsHandlerStub{ + GetAllEnableEpochsCalled: func() map[string]uint32 { + return allEpochsMap + }, + }, &testscommon.EnableRoundsHandlerStub{}) + + facade := mock.FacadeStub{} + facade.StatusMetricsHandler = func() external.StatusMetricsHandler { + return statusMetrics + } + + networkGroup, err := groups.NewNetworkGroup(&facade) + require.NoError(t, err) + + ws := startWebServer(networkGroup, "network", getNetworkRoutesConfig()) + + req, _ := http.NewRequest("GET", "/network/enable-epochs-v2", nil) + resp := httptest.NewRecorder() + ws.ServeHTTP(resp, req) + + respBytes, _ := io.ReadAll(resp.Body) + respStr := string(respBytes) + assert.Equal(t, resp.Code, http.StatusOK) + + keyAndValueFoundInResponse := strings.Contains(respStr, "SupernovaEnableEpoch") && + strings.Contains(respStr, strconv.FormatUint(uint64(allEpochsMap["SupernovaEnableEpoch"]), 10)) + assert.True(t, keyAndValueFoundInResponse) +} + +func TestGetEnableRounds_ShouldWork(t *testing.T) { + t.Parallel() + + expectedRounds := map[string]uint64{ + "round1": 100, + "round2": 200, + } + + facade := mock.FacadeStub{ + StatusMetricsHandler: func() external.StatusMetricsHandler { + return &testscommon.StatusMetricsStub{ + EnableRoundsMetricsCalled: func() map[string]uint64 { + return expectedRounds + }, + } + }, + } + + networkGroup, err := groups.NewNetworkGroup(&facade) + require.NoError(t, err) + + ws := startWebServer(networkGroup, "network", getNetworkRoutesConfig()) + + req, _ := http.NewRequest("GET", "/network/enable-rounds", nil) + resp := httptest.NewRecorder() + ws.ServeHTTP(resp, req) + + respBytes, _ := io.ReadAll(resp.Body) + respStr := string(respBytes) + assert.Equal(t, http.StatusOK, resp.Code) + + keyAndValueFoundInResponse := strings.Contains(respStr, "round1") && strings.Contains(respStr, "100") && + strings.Contains(respStr, "round2") && strings.Contains(respStr, "200") + assert.True(t, keyAndValueFoundInResponse) + assert.True(t, strings.Contains(respStr, "enableRounds")) +} + func TestGetESDTTotalSupply_InternalError(t *testing.T) { t.Parallel() @@ -1043,6 +1137,7 @@ func getNetworkRoutesConfig() config.ApiRoutesConfig { {Name: "/esdts", Open: true}, {Name: "/total-staked", Open: true}, {Name: "/enable-epochs", Open: true}, + {Name: "/enable-epochs-v2", Open: true}, {Name: "/direct-staked-info", Open: true}, {Name: "/delegated-info", Open: true}, {Name: "/esdt/supply/:token", Open: true}, @@ -1050,6 +1145,7 @@ func getNetworkRoutesConfig() config.ApiRoutesConfig { {Name: "/genesis-balances", Open: true}, {Name: "/ratings", Open: true}, {Name: "/gas-configs", Open: true}, + {Name: "/enable-rounds", Open: true}, }, }, }, diff --git a/api/groups/nodeGroup_test.go b/api/groups/nodeGroup_test.go index 4bc6e6c738e..bf0fddde4db 100644 --- a/api/groups/nodeGroup_test.go +++ b/api/groups/nodeGroup_test.go @@ -25,10 +25,21 @@ import ( "github.com/multiversx/mx-chain-go/node/external" "github.com/multiversx/mx-chain-go/statusHandler" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +type testNodeStatusMetricsHandler interface { + core.AppStatusHandler + external.StatusMetricsHandler +} + +func createNodeStatusMetrics() testNodeStatusMetricsHandler { + sm, _ := statusHandler.NewStatusMetrics(&enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) + return sm +} + func TestNewNodeGroup(t *testing.T) { t.Parallel() @@ -259,7 +270,7 @@ func TestBootstrapStatus_ShouldReturnErrorIfFacadeReturnsError(t *testing.T) { } func TestBootstrapStatusMetrics_ShouldWork(t *testing.T) { - statusMetricsProvider := statusHandler.NewStatusMetrics() + statusMetricsProvider := createNodeStatusMetrics() statusMetricsProvider.SetUInt64Value(common.MetricTrieSyncNumReceivedBytes, uint64(100)) statusMetricsProvider.SetUInt64Value(common.MetricTrieSyncNumProcessedNodes, uint64(150)) @@ -349,7 +360,7 @@ func TestNodeGroup_GetConnectedPeersRatings(t *testing.T) { } func TestStatusMetrics_ShouldDisplayNonP2pMetrics(t *testing.T) { - statusMetricsProvider := statusHandler.NewStatusMetrics() + statusMetricsProvider := createNodeStatusMetrics() key := "test-details-key" val := "test-details-value" statusMetricsProvider.SetStringValue(key, val) @@ -381,7 +392,7 @@ func TestStatusMetrics_ShouldDisplayNonP2pMetrics(t *testing.T) { } func TestP2PStatusMetrics_ShouldDisplayNonP2pMetrics(t *testing.T) { - statusMetricsProvider := statusHandler.NewStatusMetrics() + statusMetricsProvider := createNodeStatusMetrics() key := "test-details-key" val := "test-details-value" statusMetricsProvider.SetStringValue(key, val) @@ -687,7 +698,7 @@ func TestPrometheusMetrics_ShouldReturnErrorIfFacadeReturnsError(t *testing.T) { } func TestPrometheusMetrics_ShouldWork(t *testing.T) { - statusMetricsProvider := statusHandler.NewStatusMetrics() + statusMetricsProvider := createNodeStatusMetrics() key := "test-key" val := uint64(37) statusMetricsProvider.SetUInt64Value(key, val) @@ -998,7 +1009,7 @@ func TestNodeGroup_UpdateFacade(t *testing.T) { t.Run("should work", func(t *testing.T) { t.Parallel() - statusMetricsProvider := statusHandler.NewStatusMetrics() + statusMetricsProvider := createNodeStatusMetrics() key := "test-key" val := uint64(37) statusMetricsProvider.SetUInt64Value(key, val) diff --git a/api/groups/transactionGroup.go b/api/groups/transactionGroup.go index b434be5dbb5..521bfab583c 100644 --- a/api/groups/transactionGroup.go +++ b/api/groups/transactionGroup.go @@ -37,14 +37,19 @@ const ( getTransactionPath = "/:txhash" getScrsByTxHashPath = "/scrs-by-tx-hash/:txhash" getTransactionsPool = "/pool" - - queryParamWithResults = "withResults" - queryParamCheckSignature = "checkSignature" - queryParamSender = "by-sender" - queryParamFields = "fields" - queryParamLastNonce = "last-nonce" - queryParamNonceGaps = "nonce-gaps" - queryParameterScrHash = "scrHash" + getSelectedTransactionsPath = "/pool/simulate-selection" + getVirtualNoncePath = "/pool/:address/virtual-nonce" + + queryParamWithResults = "withResults" + queryParamCheckSignature = "checkSignature" + queryParamSender = "by-sender" + queryParamFields = "fields" + queryParamLastNonce = "last-nonce" + queryParamNonceGaps = "nonce-gaps" + queryParameterScrHash = "scrHash" + queryParameterWithSender = "withSender" + queryParameterWithRelayer = "withRelayer" + queryParameterWithNonce = "withNonce" ) // transactionFacadeHandler defines the methods to be implemented by a facade for transaction requests @@ -61,6 +66,8 @@ type transactionFacadeHandler interface { GetTransactionsPoolForSender(sender, fields string) (*common.TransactionsPoolForSenderApiResponse, error) GetLastPoolNonceForSender(sender string) (uint64, error) GetTransactionsPoolNonceGapsForSender(sender string) (*common.TransactionsPoolNonceGapsForSenderApiResponse, error) + GetSelectedTransactions(requestedFields string) (*common.TransactionsSelectionSimulationResult, error) + GetVirtualNonce(address string) (*common.VirtualNonceOfAccountResponse, error) ComputeTransactionGasLimit(tx *transaction.Transaction) (*transaction.CostResponse, error) EncodeAddressPubkey(pk []byte) (string, error) GetThrottlerForEndpoint(endpoint string) (core.Throttler, bool) @@ -134,6 +141,28 @@ func NewTransactionGroup(facade transactionFacadeHandler) (*transactionGroup, er }, }, }, + { + Path: getSelectedTransactionsPath, + Method: http.MethodGet, + Handler: tg.simulateTransactionsSelection, + AdditionalMiddlewares: []shared.AdditionalMiddleware{ + { + Middleware: middleware.CreateEndpointThrottlerFromFacade(getSelectedTransactionsPath, facade), + Position: shared.Before, + }, + }, + }, + { + Path: getVirtualNoncePath, + Method: http.MethodGet, + Handler: tg.getVirtualNonceByAddress, + AdditionalMiddlewares: []shared.AdditionalMiddleware{ + { + Middleware: middleware.CreateEndpointThrottlerFromFacade(getVirtualNoncePath, facade), + Position: shared.Before, + }, + }, + }, { Path: sendMultiplePath, Method: http.MethodPost, @@ -494,6 +523,45 @@ func (tg *transactionGroup) getScrsByTxHash(c *gin.Context) { ) } +func (tg *transactionGroup) getVirtualNonceByAddress(c *gin.Context) { + address := c.Param("address") + if address == "" { + c.JSON( + http.StatusBadRequest, + shared.GenericAPIResponse{ + Data: nil, + Error: fmt.Sprintf("%s: %s", errors.ErrValidation.Error(), errors.ErrEmptyAddress.Error()), + Code: shared.ReturnCodeRequestError, + }, + ) + return + } + + start := time.Now() + virtualNonce, err := tg.getFacade().GetVirtualNonce(address) + if err != nil { + c.JSON( + http.StatusInternalServerError, + shared.GenericAPIResponse{ + Data: nil, + Error: fmt.Sprintf("%s: %s", errors.ErrGetVirtualNonce.Error(), err.Error()), + Code: shared.ReturnCodeInternalError, + }, + ) + return + } + logging.LogAPIActionDurationIfNeeded(start, "API call: GetVirtualNonce") + + c.JSON( + http.StatusOK, + shared.GenericAPIResponse{ + Data: gin.H{"virtualNonce": virtualNonce}, + Error: "", + Code: shared.ReturnCodeSuccess, + }, + ) +} + // getTransaction returns transaction details for a given txhash func (tg *transactionGroup) getTransaction(c *gin.Context) { txhash := c.Param("txhash") @@ -773,6 +841,35 @@ func (tg *transactionGroup) getTransactionsPoolNonceGapsForSender(sender string, ) } +// simulateTransactionsSelection simulates a selection and returns the requested fields of each selected transaction +func (tg *transactionGroup) simulateTransactionsSelection(c *gin.Context) { + start := time.Now() + + selectionSimulationFields := getQueryParameterFields(c) + transactions, err := tg.getFacade().GetSelectedTransactions(selectionSimulationFields) + logging.LogAPIActionDurationIfNeeded(start, "API call: GetSelectedTransactions") + if err != nil { + c.JSON( + http.StatusInternalServerError, + shared.GenericAPIResponse{ + Data: nil, + Error: err.Error(), + Code: shared.ReturnCodeInternalError, + }, + ) + return + } + + c.JSON( + http.StatusOK, + shared.GenericAPIResponse{ + Data: gin.H{"transactions": transactions}, + Error: "", + Code: shared.ReturnCodeSuccess, + }, + ) +} + func (tg *transactionGroup) createTransaction(receivedTx *transaction.FrontendTransaction) (*transaction.Transaction, []byte, error) { txArgs := &external.ArgsCreateTransaction{ Nonce: receivedTx.Nonce, diff --git a/api/groups/transactionGroup_test.go b/api/groups/transactionGroup_test.go index 9f603412ae0..e3b6aba5485 100644 --- a/api/groups/transactionGroup_test.go +++ b/api/groups/transactionGroup_test.go @@ -1005,6 +1005,112 @@ func testTxPoolWithInvalidQuery(query string, expectedErr error) func(t *testing } } +func TestTransactionsGroup_GetSelectedTransactions(t *testing.T) { + t.Parallel() + + t.Run("GetSelectedTransactions should error", func(t *testing.T) { + t.Parallel() + + facade := &mock.FacadeStub{ + GetSelectedTransactionsCalled: func(fields string) (*common.TransactionsSelectionSimulationResult, error) { + return nil, expectedErr + }, + } + + testTransactionsGroup( + t, + facade, + "/transaction/pool/simulate-selection", + "GET", + nil, + http.StatusInternalServerError, + expectedErr, + ) + }) + + t.Run("GetSelectedTransactions should work", func(t *testing.T) { + t.Parallel() + + expectedTransactions := []common.Transaction{ + { + TxFields: map[string]interface{}{ + "hash": "txHash1", + }, + }, + { + TxFields: map[string]interface{}{ + "hash": "txHash2", + }, + }, + } + expectedResult := &common.TransactionsSelectionSimulationResult{ + Transactions: expectedTransactions, + } + + facade := &mock.FacadeStub{ + GetSelectedTransactionsCalled: func(fields string) (*common.TransactionsSelectionSimulationResult, error) { + return expectedResult, nil + }, + } + + loadTransactionGroupResponse( + t, + facade, + "/transaction/pool/simulate-selection", + "GET", + nil, + expectedResult, + ) + }) +} + +func TestTransactionsGroup_GetVirtualNonce(t *testing.T) { + t.Parallel() + + t.Run("GetVirtualNonce should error", func(t *testing.T) { + t.Parallel() + + facade := &mock.FacadeStub{ + GetVirtualNonceCalled: func(address string) (*common.VirtualNonceOfAccountResponse, error) { + return nil, expectedErr + }, + } + + testTransactionsGroup( + t, + facade, + "/transaction/pool/address/virtual-nonce", + "GET", + nil, + http.StatusInternalServerError, + expectedErr, + ) + }) + + t.Run("GetVirtualNonce should work", func(t *testing.T) { + t.Parallel() + + expectedResult := &common.VirtualNonceOfAccountResponse{ + VirtualNonce: 10, + } + + facade := &mock.FacadeStub{ + GetVirtualNonceCalled: func(address string) (*common.VirtualNonceOfAccountResponse, error) { + return expectedResult, nil + }, + } + + loadTransactionGroupResponse( + t, + facade, + "/transaction/pool/alice/virtual-nonce", + "GET", + nil, + expectedResult, + ) + }) +} + func TestTransactionsGroup_UpdateFacade(t *testing.T) { t.Parallel() @@ -1193,6 +1299,8 @@ func getTransactionRoutesConfig() config.ApiRoutesConfig { {Name: "/:txhash/status", Open: true}, {Name: "/simulate", Open: true}, {Name: "/scrs-by-tx-hash/:txhash", Open: true}, + {Name: "/pool/simulate-selection", Open: true}, + {Name: "/pool/:address/virtual-nonce", Open: true}, }, }, }, diff --git a/api/groups/validatorGroup_test.go b/api/groups/validatorGroup_test.go index 0bbd1ebf742..103fb3d986f 100644 --- a/api/groups/validatorGroup_test.go +++ b/api/groups/validatorGroup_test.go @@ -34,7 +34,7 @@ func TestNewValidatorGroup(t *testing.T) { }) } -// ValidatorStatisticsResponse is the response for the validator statistics endpoint. +// validatorStatisticsResponse is the response for the validator statistics endpoint. type validatorStatisticsResponse struct { Result map[string]*validator.ValidatorStatistics `json:"statistics"` Error string `json:"error"` diff --git a/api/middleware/sourceThrottler.go b/api/middleware/sourceThrottler.go index fe451988046..368978bf72b 100644 --- a/api/middleware/sourceThrottler.go +++ b/api/middleware/sourceThrottler.go @@ -10,7 +10,7 @@ import ( "github.com/multiversx/mx-chain-go/api/shared" ) -// globalThrottler is a middleware limiter used to limit total number of requests originating from the same source +// sourceThrottler is a middleware limiter used to limit total number of requests originating from the same source type sourceThrottler struct { mutRequests sync.Mutex sourceRequests map[string]uint32 diff --git a/api/mock/facadeStub.go b/api/mock/facadeStub.go index 000c95dafc3..3058ce4fbd4 100644 --- a/api/mock/facadeStub.go +++ b/api/mock/facadeStub.go @@ -2,9 +2,10 @@ package mock import ( "encoding/hex" - "github.com/multiversx/mx-chain-core-go/data/smartContractResult" "math/big" + "github.com/multiversx/mx-chain-core-go/data/smartContractResult" + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data/alteredAccount" "github.com/multiversx/mx-chain-core-go/data/api" @@ -85,6 +86,7 @@ type FacadeStub struct { GetTransactionsPoolForSenderCalled func(sender, fields string) (*common.TransactionsPoolForSenderApiResponse, error) GetLastPoolNonceForSenderCalled func(sender string) (uint64, error) GetTransactionsPoolNonceGapsForSenderCalled func(sender string) (*common.TransactionsPoolNonceGapsForSenderApiResponse, error) + GetSelectedTransactionsCalled func(fields string) (*common.TransactionsSelectionSimulationResult, error) GetGasConfigsCalled func() (map[string]map[string]uint64, error) RestApiInterfaceCalled func() string RestAPIServerDebugModeCalled func() bool @@ -100,6 +102,7 @@ type FacadeStub struct { P2PPrometheusMetricsEnabledCalled func() bool AuctionListHandler func() ([]*common.AuctionListValidatorAPIResponse, error) GetSCRsByTxHashCalled func(txHash string, scrHash string) ([]*transaction.ApiSmartContractResult, error) + GetVirtualNonceCalled func(address string) (*common.VirtualNonceOfAccountResponse, error) SimulateSCRExecutionCostCalled func(scr *smartContractResult.SmartContractResult) (*transaction.CostResponse, error) } @@ -673,6 +676,15 @@ func (f *FacadeStub) GetTransactionsPoolNonceGapsForSender(sender string) (*comm return nil, nil } +// GetSelectedTransactions - +func (f *FacadeStub) GetSelectedTransactions(fields string) (*common.TransactionsSelectionSimulationResult, error) { + if f.GetSelectedTransactionsCalled != nil { + return f.GetSelectedTransactionsCalled(fields) + } + + return nil, nil +} + // GetGasConfigs - func (f *FacadeStub) GetGasConfigs() (map[string]map[string]uint64, error) { if f.GetGasConfigsCalled != nil { @@ -758,6 +770,15 @@ func (f *FacadeStub) GetWaitingEpochsLeftForPublicKey(publicKey string) (uint32, return 0, nil } +// GetVirtualNonce - +func (f *FacadeStub) GetVirtualNonce(address string) (*common.VirtualNonceOfAccountResponse, error) { + if f.GetVirtualNonceCalled != nil { + return f.GetVirtualNonceCalled(address) + } + + return nil, nil +} + // P2PPrometheusMetricsEnabled - func (f *FacadeStub) P2PPrometheusMetricsEnabled() bool { if f.P2PPrometheusMetricsEnabledCalled != nil { diff --git a/api/shared/interface.go b/api/shared/interface.go index f33ff91746c..fa07ebfb309 100644 --- a/api/shared/interface.go +++ b/api/shared/interface.go @@ -131,6 +131,7 @@ type FacadeHandler interface { GetTransactionsPoolForSender(sender, fields string) (*common.TransactionsPoolForSenderApiResponse, error) GetLastPoolNonceForSender(sender string) (uint64, error) GetTransactionsPoolNonceGapsForSender(sender string) (*common.TransactionsPoolNonceGapsForSenderApiResponse, error) + GetSelectedTransactions(fields string) (*common.TransactionsSelectionSimulationResult, error) IsDataTrieMigrated(address string, options api.AccountQueryOptions) (bool, error) GetManagedKeysCount() int GetManagedKeys() []string @@ -139,6 +140,7 @@ type FacadeHandler interface { GetWaitingManagedKeys() ([]string, error) GetWaitingEpochsLeftForPublicKey(publicKey string) (uint32, error) GetSCRsByTxHash(txHash string, scrHash string) ([]*transaction.ApiSmartContractResult, error) + GetVirtualNonce(address string) (*common.VirtualNonceOfAccountResponse, error) P2PPrometheusMetricsEnabled() bool IsInterfaceNil() bool } diff --git a/cmd/node/config/api.toml b/cmd/node/config/api.toml index 947733b740f..88a4b5646ac 100644 --- a/cmd/node/config/api.toml +++ b/cmd/node/config/api.toml @@ -131,6 +131,12 @@ # /network/enable-epochs will return metrics related to activation epochs { Name = "/enable-epochs", Open = true }, + # /network/enable-epochs-v2 will return metrics related to activation epochs in the same format as in toml + { Name = "/enable-epochs-v2", Open = true }, + + # /network/enable-rounds will return metrics related to activation rounds + { Name = "/enable-rounds", Open = true }, + # /network/esdts will return all the issued esdts on the protocol { Name = "/esdts", Open = true }, @@ -230,6 +236,12 @@ # /transaction/scrs-by-tx-hash/:txhash will return the smart contract results generated by the provided transaction hash { Name = "/scrs-by-tx-hash/:txhash", Open = true }, + + # transaction/pool/simulate-selection will simulate a selection, and it will return each selected transaction with the requested fields + { Name = "/pool/simulate-selection", Open = true}, # should be false on public configs + + # transaction/pool/:address/virtual-nonce will return the virtual nonce of the account + { Name = "/pool/:address/virtual-nonce", Open = true}, ] [APIPackages.block] diff --git a/cmd/node/config/config.toml b/cmd/node/config/config.toml index ed93fbb82a2..7a2c3c78f41 100644 --- a/cmd/node/config/config.toml +++ b/cmd/node/config/config.toml @@ -27,29 +27,87 @@ # GenesisMaxNumberOfShards represents the maximum number of shards to be created at genesis (excluding metaChain shard) GenesisMaxNumberOfShards = 3 - # MaxConsecutiveRoundsOfRatingDecrease represents the max number of consecutive rounds in which a block is not proposed - # on a shard and the validators' rating could be decreased. If, for instance, a shard gets stuck for more rounds - # than this value, then the validators rating decrease should stop so they won't get jailed - MaxConsecutiveRoundsOfRatingDecrease = 600 - # SyncProcessTimeInMillis is the value in milliseconds used when processing blocks while synchronizing blocks SyncProcessTimeInMillis = 12000 + # SyncProcessTimeSupernovaInMillis is the value in milliseconds used when processing blocks while synchronizing blocks after Supernova + SyncProcessTimeSupernovaInMillis = 6000 + # SetGuardianEpochsDelay represents the delay in epochs between the execution time of the SetGuardian transaction and # the activation of the configured guardian. # Make sure that this is greater than the unbonding period! SetGuardianEpochsDelay = 2 # TODO: for mainnet should be 20, 2 is just for testing + # MaxProposalNonceGap defines the maximum gap allowed between the highest finalized nonce and lowest proposed nonce + MaxProposalNonceGap = 20 + # ChainParametersByEpoch defines chain operation configurable values that can be modified based on epochs ChainParametersByEpoch = [ - { EnableEpoch = 0, RoundDuration = 6000, ShardConsensusGroupSize = 7, ShardMinNumNodes = 10, MetachainConsensusGroupSize = 10, MetachainMinNumNodes = 10, Hysteresis = 0.2, Adaptivity = false }, - { EnableEpoch = 1, RoundDuration = 6000, ShardConsensusGroupSize = 10, ShardMinNumNodes = 10, MetachainConsensusGroupSize = 10, MetachainMinNumNodes = 10, Hysteresis = 0.2, Adaptivity = false } + { EnableEpoch = 0, RoundDuration = 6000, RoundsPerEpoch = 200, MinRoundsBetweenEpochs = 20, ShardConsensusGroupSize = 7, ShardMinNumNodes = 10, MetachainConsensusGroupSize = 10, MetachainMinNumNodes = 10, Hysteresis = 0.2, Adaptivity = false, Offset = 0 }, + { EnableEpoch = 1, RoundDuration = 6000, RoundsPerEpoch = 200, MinRoundsBetweenEpochs = 20, ShardConsensusGroupSize = 10, ShardMinNumNodes = 10, MetachainConsensusGroupSize = 10, MetachainMinNumNodes = 10, Hysteresis = 0.2, Adaptivity = false, Offset = 0 }, + { EnableEpoch = 2, RoundDuration = 600, RoundsPerEpoch = 2000, MinRoundsBetweenEpochs = 50, ShardConsensusGroupSize = 10, ShardMinNumNodes = 10, MetachainConsensusGroupSize = 10, MetachainMinNumNodes = 10, Hysteresis = 0.2, Adaptivity = false, Offset = 2 } ] # EpochChangeGracePeriodEnableEpoch represents the configuration of different grace periods for epoch change with their activation epochs EpochChangeGracePeriodByEpoch = [ {EnableEpoch = 0, GracePeriodInRounds = 1 }, {EnableEpoch = 1, GracePeriodInRounds = 10 }, # Andromeda epoch comes with a longer grace period + {EnableEpoch = 2, GracePeriodInRounds = 100 }, # Supernova epoch comes with an even longer grace period due to round duration reduction + ] + + # ProcessConfigsByEpoch represents the configuration of process configuration parameters by epoch + ProcessConfigsByEpoch = [ + { EnableEpoch = 0, MaxMetaNoncesBehind = 15, MaxMetaNoncesBehindForGlobalStuck = 30, MaxShardNoncesBehind = 15 }, + { EnableEpoch = 2, MaxMetaNoncesBehind = 75, MaxMetaNoncesBehindForGlobalStuck = 120, MaxShardNoncesBehind = 75 }, + ] + + ProcessConfigsByRound = [ + { + EnableRound = 0, + MaxRoundsWithoutNewBlockReceived = 10, + MaxRoundsWithoutCommittedBlock = 10, + RoundModulusTriggerWhenSyncIsStuck = 20, + MaxSyncWithErrorsAllowed = 10, + MaxRoundsToKeepUnprocessedMiniBlocks = 300, + MaxRoundsToKeepUnprocessedTransactions = 300, + # MaxConsecutiveRoundsOfRatingDecrease represents the max number of consecutive rounds in which a block is not proposed + # on a shard and the validators' rating could be decreased. If, for instance, a shard gets stuck for more rounds + # than this value, then the validators rating decrease should stop so they won't get jailed + MaxConsecutiveRoundsOfRatingDecrease = 600, + # MaxRoundsOfInactivityAccepted defines the number of rounds missed by a main or higher level backup machine before + # the current machine will take over and propose/sign blocks. Used in both single-key and multi-key modes. + MaxRoundsOfInactivityAccepted = 3, + MaxBlockProcessingTimeMs = 6000, # this is only used after Supernova + NumHeadersToRequestInAdvance = 10 + }, + { + EnableRound = 440, + MaxRoundsWithoutNewBlockReceived = 100, + MaxRoundsWithoutCommittedBlock = 100, + RoundModulusTriggerWhenSyncIsStuck = 200, + MaxSyncWithErrorsAllowed = 100, + MaxRoundsToKeepUnprocessedMiniBlocks = 3000, + MaxRoundsToKeepUnprocessedTransactions = 3000, + MaxConsecutiveRoundsOfRatingDecrease = 6000, + MaxRoundsOfInactivityAccepted = 30, + MaxBlockProcessingTimeMs = 6000, + NumHeadersToRequestInAdvance = 10 + } + ] + + EpochStartConfigsByEpoch = [ + { EnableEpoch = 0, GracePeriodRounds = 25, ExtraDelayForRequestBlockInfoInMilliseconds = 3000 }, + { EnableEpoch = 2, GracePeriodRounds = 250, ExtraDelayForRequestBlockInfoInMilliseconds = 220 }, + ] + + EpochStartConfigsByRound = [ + { EnableRound = 0, MaxRoundsWithoutCommittedStartInEpochBlock = 50 }, + { EnableRound = 440, MaxRoundsWithoutCommittedStartInEpochBlock = 500 }, + ] + + ConsensusConfigsByEpoch = [ + { EnableEpoch = 0, NumRoundsToWaitBeforeSignalingChronologyStuck = 10 }, + { EnableEpoch = 2, NumRoundsToWaitBeforeSignalingChronologyStuck = 100 }, ] [HardwareRequirements] @@ -58,14 +116,13 @@ [Versions] DefaultVersion = "default" VersionsByEpochs = [ - { StartEpoch = 0, Version = "*" }, + { StartEpoch = 0, StartRound = 0, Version = "*"}, # The value of StartEpoch parameter for version 2 should be the same with the ScheduledMiniBlocksEnableEpoch flag from enableEpoch.toml file - { StartEpoch = 1, Version = "2" }, + { StartEpoch = 1, StartRound = 0, Version = "2" }, + + # TODO The value of StartEpoch parameter for version 3 should be the same with the SupernovaEnableEpoch flag from enableEpoch.toml file and StartRound the same with the SupernovaEnableRound flag from enableRounds.toml + { StartEpoch = 2, StartRound = 440, Version = "3" }, ] - [Versions.Cache] - Name = "VersionsCache" - Capacity = 100 - Type = "LRU" [StoragePruning] # If the Enabled flag is set to false, then the storers won't divide epochs into separate dbs @@ -171,6 +228,8 @@ BatchDelaySeconds = 2 MaxBatchSize = 100 MaxOpenFiles = 10 + ShardIDProviderType = "BinarySplit" + NumShards = 4 [BootstrapStorage] [BootstrapStorage.Cache] @@ -197,6 +256,8 @@ BatchDelaySeconds = 2 MaxBatchSize = 100 MaxOpenFiles = 10 + ShardIDProviderType = "BinarySplit" + NumShards = 4 [ProofsStorage] [ProofsStorage.Cache] @@ -211,6 +272,19 @@ MaxBatchSize = 100 MaxOpenFiles = 10 +[ExecutionResultsStorage] + [ExecutionResultsStorage.Cache] + Name = "ExecutionResultsStorage" + Capacity = 1000 + Type = "SizeLRU" + SizeInBytes = 20971520 #20MB + [ExecutionResultsStorage.DB] + FilePath = "ExecutionResults" + Type = "LvlDBSerial" + BatchDelaySeconds = 2 + MaxBatchSize = 100 + MaxOpenFiles = 10 + [TxStorage] [TxStorage.Cache] Name = "TxStorage" @@ -223,6 +297,8 @@ BatchDelaySeconds = 2 MaxBatchSize = 30000 MaxOpenFiles = 10 + ShardIDProviderType = "BinarySplit" + NumShards = 4 [UnsignedTransactionStorage] [UnsignedTransactionStorage.Cache] @@ -434,6 +510,24 @@ Type = "TxCache" Shards = 16 +[TxCacheBounds] + MaxNumBytesPerSenderUpperBound = 33_554_432 #32MB + MaxTrackedBlocks = 100 + PropagationGracePeriodMs = 200 + +[TxCacheSelection] + SelectionMaxNumTxs = 30000 + SelectionLoopDurationCheckInterval = 10 #chunk (of transactions) size + SelectionGasBandwidthIncreasePercent = 400 + SelectionGasBandwidthIncreaseScheduledPercent = 260 + SelectionGasRequested = 10_000_000_000 + +[AOTSelection] + # Ahead-of-Time transaction selection - pre-selects transactions before consensus round + Enabled = true + CacheSize = 10 # Number of pre-selection results to cache + SelectionTimeoutMs = 150 # Max time for AOT selection in milliseconds + [TrieNodesChunksDataPool] Name = "TrieNodesDataPool" Capacity = 400 @@ -476,6 +570,22 @@ SizeInBytes = 31457280 #30MB Shards = 4 +[ExecutedMiniBlocksCache] + Name = "ExecutedMiniBlocksCache" + Capacity = 500 + Type = "SizeLRU" + SizeInBytes = 52428800 # 50MB + +[PostProcessTransactionsCache] + Name = "PostProcessTransactionsCache" + Capacity = 250000 + Type = "SizeLRU" + SizeInBytes = 104857600 # 100MB + +[HeaderBodyCacheConfig] + Capacity = 1000 + + #PublicKeyPeerId represents the main cache used to map MultiversX block signing public keys to their associated peer id's. [PublicKeyPeerId] Name = "PublicKeyPeerId" @@ -515,10 +625,6 @@ TopRatedCacheCapacity = 5000 BadRatedCacheCapacity = 5000 -[PoolsCleanersConfig] - MaxRoundsToKeepUnprocessedMiniBlocks = 300 # max number of rounds unprocessed miniblocks are kept in pool - MaxRoundsToKeepUnprocessedTransactions = 300 # max number of rounds unprocessed transactions are kept in pool - [TrieSyncStorage] Capacity = 300000 SizeInBytes = 104857600 #100MB @@ -533,74 +639,153 @@ [Antiflood] Enabled = true - NumConcurrentResolverJobs = 50 - NumConcurrentResolvingTrieNodesJobs = 3 - [Antiflood.FastReacting] - IntervalInSeconds = 1 - ReservedPercent = 20.0 - [Antiflood.FastReacting.PeerMaxInput] - BaseMessagesPerInterval = 280 - TotalSizePerInterval = 4194304 #4MB/s - [Antiflood.FastReacting.PeerMaxInput.IncreaseFactor] - Threshold = 10 #if consensus size will exceed this value, then - Factor = 1.0 #increase the base value with [factor*consensus size] - [Antiflood.FastReacting.BlackList] - ThresholdNumMessagesPerInterval = 1000 - ThresholdSizePerInterval = 8388608 #8MB/s - NumFloodingRounds = 10 - PeerBanDurationInSeconds = 300 - - [Antiflood.SlowReacting] - IntervalInSeconds = 30 - ReservedPercent = 20.0 - [Antiflood.SlowReacting.PeerMaxInput] - BaseMessagesPerInterval = 6000 - TotalSizePerInterval = 18874368 # 18MB/interval - [Antiflood.SlowReacting.PeerMaxInput.IncreaseFactor] - Threshold = 10 #if consensus size will exceed this value, then - Factor = 0.0 #increase the base value with [factor*consensus size] - [Antiflood.SlowReacting.BlackList] - ThresholdNumMessagesPerInterval = 10000 - ThresholdSizePerInterval = 37748736 # 36MB/interval - NumFloodingRounds = 2 - PeerBanDurationInSeconds = 3600 - - [Antiflood.OutOfSpecs] - IntervalInSeconds = 1 - ReservedPercent = 0.0 - [Antiflood.OutOfSpecs.PeerMaxInput] - BaseMessagesPerInterval = 2000 - TotalSizePerInterval = 10485760 # 10MB/interval - [Antiflood.OutOfSpecs.PeerMaxInput.IncreaseFactor] - Threshold = 0 #if consensus size will exceed this value, then - Factor = 0.0 #increase the base value with [factor*consensus size] - [Antiflood.OutOfSpecs.BlackList] - ThresholdNumMessagesPerInterval = 3600 - ThresholdSizePerInterval = 12582912 # 12MB/interval - NumFloodingRounds = 2 - PeerBanDurationInSeconds = 3600 - - [Antiflood.PeerMaxOutput] - BaseMessagesPerInterval = 150 - TotalSizePerInterval = 2097152 #2MB/s - - [Antiflood.Cache] - Name = "Antiflood" - Capacity = 7000 - Type = "LRU" - [Antiflood.Topic] - DefaultMaxMessagesPerSec = 15000 - MaxMessages = [{ Topic = "shardBlocks*", NumMessagesPerSec = 30 }, - { Topic = "metachainBlocks", NumMessagesPerSec = 30 }] - - [Antiflood.TxAccumulator] - # MaxAllowedTimeInMilliseconds is used as a time frame in which the node gathers transactions. - # After this period, collected transactions will be sent on the p2p topics - MaxAllowedTimeInMilliseconds = 250 - # MaxDeviationTimeInMilliseconds represents the time in miliseconds that will cause the effectual time frame be - # less than the specified max value. This is used to create desynchronizations between senders as to not - # clutter the network exactly in the same moment - MaxDeviationTimeInMilliseconds = 25 + + [[Antiflood.ConfigsByRound]] + Round = 0 + NumConcurrentResolverJobs = 50 + NumConcurrentResolvingTrieNodesJobs = 3 + + [Antiflood.ConfigsByRound.FastReacting] + IntervalInSeconds = 1 + ReservedPercent = 20.0 + [Antiflood.ConfigsByRound.FastReacting.PeerMaxInput] + BaseMessagesPerInterval = 280 + TotalSizePerInterval = 4194304 #4MB/s + [Antiflood.ConfigsByRound.FastReacting.PeerMaxInput.IncreaseFactor] + Threshold = 10 #if consensus size will exceed this value, then + Factor = 1.0 #increase the base value with [factor*consensus size] + [Antiflood.ConfigsByRound.FastReacting.BlackList] + ThresholdNumMessagesPerInterval = 1000 + ThresholdSizePerInterval = 8388608 #8MB/s + NumFloodingRounds = 10 + PeerBanDurationInSeconds = 300 + + [Antiflood.ConfigsByRound.SlowReacting] + IntervalInSeconds = 30 + ReservedPercent = 20.0 + [Antiflood.ConfigsByRound.SlowReacting.PeerMaxInput] + BaseMessagesPerInterval = 6000 + TotalSizePerInterval = 18874368 # 18MB/interval + [Antiflood.ConfigsByRound.SlowReacting.PeerMaxInput.IncreaseFactor] + Threshold = 10 #if consensus size will exceed this value, then + Factor = 0.0 #increase the base value with [factor*consensus size] + [Antiflood.ConfigsByRound.SlowReacting.BlackList] + ThresholdNumMessagesPerInterval = 10000 + ThresholdSizePerInterval = 37748736 # 36MB/interval + NumFloodingRounds = 2 + PeerBanDurationInSeconds = 3600 + + [Antiflood.ConfigsByRound.OutOfSpecs] + IntervalInSeconds = 1 + ReservedPercent = 0.0 + [Antiflood.ConfigsByRound.OutOfSpecs.PeerMaxInput] + BaseMessagesPerInterval = 2000 + TotalSizePerInterval = 10485760 # 10MB/interval + [Antiflood.ConfigsByRound.OutOfSpecs.PeerMaxInput.IncreaseFactor] + Threshold = 0 #if consensus size will exceed this value, then + Factor = 0.0 #increase the base value with [factor*consensus size] + [Antiflood.ConfigsByRound.OutOfSpecs.BlackList] + ThresholdNumMessagesPerInterval = 3600 + ThresholdSizePerInterval = 12582912 # 12MB/interval + NumFloodingRounds = 2 + PeerBanDurationInSeconds = 3600 + + [Antiflood.ConfigsByRound.PeerMaxOutput] + [Antiflood.ConfigsByRound.PeerMaxOutput.PeerMaxInput] + BaseMessagesPerInterval = 150 + TotalSizePerInterval = 2097152 #2MB/s + + [Antiflood.ConfigsByRound.Cache] + Name = "Antiflood" + Capacity = 7000 + Type = "LRU" + [Antiflood.ConfigsByRound.Topic] + DefaultMaxMessagesPerSec = 15000 + MaxMessages = [{ Topic = "shardBlocks*", NumMessagesPerSec = 30 }, + { Topic = "metachainBlocks", NumMessagesPerSec = 30 }] + + [Antiflood.ConfigsByRound.TxAccumulator] + # MaxAllowedTimeInMilliseconds is used as a time frame in which the node gathers transactions. + # After this period, collected transactions will be sent on the p2p topics + MaxAllowedTimeInMilliseconds = 250 + # MaxDeviationTimeInMilliseconds represents the time in miliseconds that will cause the effectual time frame be + # less than the specified max value. This is used to create desynchronizations between senders as to not + # clutter the network exactly in the same moment + MaxDeviationTimeInMilliseconds = 25 + + [[Antiflood.ConfigsByRound]] + Round = 440 + NumConcurrentResolverJobs = 50 # TODO: analyse for meta on bootstrap there was a constant used with value 10 + NumConcurrentResolvingTrieNodesJobs = 3 + + [Antiflood.ConfigsByRound.FastReacting] + IntervalInSeconds = 1 + ReservedPercent = 20.0 + [Antiflood.ConfigsByRound.FastReacting.PeerMaxInput] + BaseMessagesPerInterval = 280 + TotalSizePerInterval = 4194304 #4MB/s + [Antiflood.ConfigsByRound.FastReacting.PeerMaxInput.IncreaseFactor] + Threshold = 10 #if consensus size will exceed this value, then + Factor = 1.0 #increase the base value with [factor*consensus size] + [Antiflood.ConfigsByRound.FastReacting.BlackList] + ThresholdNumMessagesPerInterval = 10000 + ThresholdSizePerInterval = 12388608 #12MB/s + NumFloodingRounds = 100 + PeerBanDurationInSeconds = 300 + + [Antiflood.ConfigsByRound.SlowReacting] + IntervalInSeconds = 30 + ReservedPercent = 20.0 + [Antiflood.ConfigsByRound.SlowReacting.PeerMaxInput] + BaseMessagesPerInterval = 6000 + TotalSizePerInterval = 18874368 # 18MB/interval + [Antiflood.ConfigsByRound.SlowReacting.PeerMaxInput.IncreaseFactor] + Threshold = 10 #if consensus size will exceed this value, then + Factor = 0.0 #increase the base value with [factor*consensus size] + [Antiflood.ConfigsByRound.SlowReacting.BlackList] + ThresholdNumMessagesPerInterval = 100000 + ThresholdSizePerInterval = 60748736 # 60MB/interval + NumFloodingRounds = 20 + PeerBanDurationInSeconds = 3600 + + [Antiflood.ConfigsByRound.OutOfSpecs] + IntervalInSeconds = 1 + ReservedPercent = 0.0 + [Antiflood.ConfigsByRound.OutOfSpecs.PeerMaxInput] + BaseMessagesPerInterval = 20000 + TotalSizePerInterval = 104857600 # 100MB/interval + [Antiflood.ConfigsByRound.OutOfSpecs.PeerMaxInput.IncreaseFactor] + Threshold = 0 #if consensus size will exceed this value, then + Factor = 0.0 #increase the base value with [factor*consensus size] + [Antiflood.ConfigsByRound.OutOfSpecs.BlackList] + ThresholdNumMessagesPerInterval = 36000 + ThresholdSizePerInterval = 18582912 # 18MB/interval + NumFloodingRounds = 20 + PeerBanDurationInSeconds = 3600 + + [Antiflood.ConfigsByRound.PeerMaxOutput] + [Antiflood.ConfigsByRound.PeerMaxOutput.PeerMaxInput] + BaseMessagesPerInterval = 150 + TotalSizePerInterval = 2097152 #2MB/s + + [Antiflood.ConfigsByRound.Cache] + Name = "Antiflood" + Capacity = 7000 + Type = "LRU" + [Antiflood.ConfigsByRound.Topic] + DefaultMaxMessagesPerSec = 15000 + MaxMessages = [{ Topic = "shardBlocks*", NumMessagesPerSec = 30 }, + { Topic = "metachainBlocks", NumMessagesPerSec = 30 }] + + [Antiflood.ConfigsByRound.TxAccumulator] + # MaxAllowedTimeInMilliseconds is used as a time frame in which the node gathers transactions. + # After this period, collected transactions will be sent on the p2p topics + MaxAllowedTimeInMilliseconds = 80 + # MaxDeviationTimeInMilliseconds represents the time in miliseconds that will cause the effectual time frame be + # less than the specified max value. This is used to create desynchronizations between senders as to not + # clutter the network exactly in the same moment + MaxDeviationTimeInMilliseconds = 25 + # Antiflood config ends here for Supernova [WebServerAntiflood] WebServerAntifloodEnabled = true @@ -664,8 +849,6 @@ [EpochStartConfig] GenesisEpoch = 0 - MinRoundsBetweenEpochs = 20 - RoundsPerEpoch = 200 # Min and Max ShuffledOutRestartThreshold represents the minimum and maximum duration of an epoch (in percentage) after a node which # has been shuffled out has to restart its process in order to start in a new shard MinShuffledOutRestartThreshold = 0.05 @@ -673,7 +856,7 @@ MinNumConnectedPeersToStart = 2 MinNumOfPeersToConsiderBlockValid = 2 - ExtraDelayForRequestBlockInfoInMilliseconds = 3000 + ExtraDelayForRequestBlockInfoInMilliseconds = 220 # ResourceStats, if enabled, will output in a folder called "stats" # resource statistics. For example: number of active go routines, memory allocation, number of GC sweeps, etc. @@ -696,6 +879,9 @@ TimeoutMilliseconds = 100 SyncPeriodSeconds = 3600 Version = 0 # Setting 0 means 'use default value' + # OutOfBoundsThreshold defines the threshold in milliseconds for checking out of bound + # limit in ntp syncer + OutOfBoundsThreshold = 120 [StateTriesConfig] SnapshotsEnabled = true @@ -717,6 +903,7 @@ [BlockSizeThrottleConfig] MinSizeInBytes = 104857 # 104857 is 10% from 1MB MaxSizeInBytes = 943718 # 943718 is 90% from 1MB + MaxExecResSizeInBytes = 104857 # 104857 is 10% from 1MB [VirtualMachine] [VirtualMachine.Execution] @@ -834,6 +1021,10 @@ NumRequestsThreshold = 9 NumResolveFailureThreshold = 3 DebugLineExpiration = 10 #Will remove the debug line after a `DebugLineExpiration` number of prints + [Debug.InterceptorResolver.BroadcastStatistics] + Enabled = false + # "intercepted tx", "intercepted reward tx", "intercepted unsigned tx", "intercepted header", ... + Messages = ["intercepted tx"] [Debug.Antiflood] Enabled = true CacheSize = 10000 @@ -906,6 +1097,8 @@ BatchDelaySeconds = 2 MaxBatchSize = 20000 MaxOpenFiles = 10 + ShardIDProviderType = "BinarySplit" + NumShards = 4 [DbLookupExtensions.EpochByHashStorageConfig.Cache] Name = "DbLookupExtensions.EpochByHashStorage" Capacity = 20000 @@ -916,6 +1109,8 @@ BatchDelaySeconds = 2 MaxBatchSize = 20000 MaxOpenFiles = 10 + ShardIDProviderType = "BinarySplit" + NumShards = 4 [DbLookupExtensions.ResultsHashesByTxHashStorageConfig.Cache] Name = "DbLookupExtensions.ResultsHashesByTxHashStorage" Capacity = 20000 @@ -991,11 +1186,17 @@ Type = "SizeLRU" SizeInBytes = 314572800 #300MB -[Redundancy] - # MaxRoundsOfInactivityAccepted defines the number of rounds missed by a main or higher level backup machine before - # the current machine will take over and propose/sign blocks. Used in both single-key and multi-key modes. - MaxRoundsOfInactivityAccepted = 3 - [InterceptedDataVerifier] + EnableCaching = true + CacheSpanInSec = 30 + CacheExpiryInSec = 30 + +[DirectSentTransactions] CacheSpanInSec = 30 CacheExpiryInSec = 30 + +[ExecutionResultInclusionEstimator] + # Safety margin = 110 -> 110% -> 10% buffer + SafetyMargin = 110 + # Maximum execution results a single block may carry (0 = unlimited) + MaxResultsPerBlock = 20 diff --git a/cmd/node/config/economics.toml b/cmd/node/config/economics.toml index 932c9528d99..e096dc50d1b 100644 --- a/cmd/node/config/economics.toml +++ b/cmd/node/config/economics.toml @@ -55,9 +55,12 @@ GasLimitSettings = [ {EnableEpoch = 0, MaxGasLimitPerBlock = "1500000000", MaxGasLimitPerMiniBlock = "1500000000", MaxGasLimitPerMetaBlock = "15000000000", MaxGasLimitPerMetaMiniBlock = "15000000000", MaxGasLimitPerTx = "1500000000", MinGasLimit = "50000", ExtraGasLimitGuardedTx = "50000", MaxGasHigherFactorAccepted = "10"}, {EnableEpoch = 1, MaxGasLimitPerBlock = "1500000000", MaxGasLimitPerMiniBlock = "250000000", MaxGasLimitPerMetaBlock = "15000000000", MaxGasLimitPerMetaMiniBlock = "250000000", MaxGasLimitPerTx = "600000000", MinGasLimit = "50000", ExtraGasLimitGuardedTx = "50000", MaxGasHigherFactorAccepted = "2"}, + {EnableEpoch = 2, MaxGasLimitPerBlock = "600000000", MaxGasLimitPerMiniBlock = "250000000", MaxGasLimitPerMetaBlock = "12000000000", MaxGasLimitPerMetaMiniBlock = "250000000", MaxGasLimitPerTx = "600000000", MinGasLimit = "50000", ExtraGasLimitGuardedTx = "50000", MaxGasHigherFactorAccepted = "2"}, ] - MinGasPrice = "1000000000" #will yield min tx fee of 0.00005 eGLD - GasPriceModifier = 0.01 - GasPerDataByte = "1500" - DataLimitForBaseCalc = "10000" - MaxGasPriceSetGuardian = "2000000000" + MinGasPrice = "1000000000" #will yield min tx fee of 0.00005 eGLD + GasPriceModifier = 0.01 + GasPerDataByte = "1500" + DataLimitForBaseCalc = "10000" + MaxGasPriceSetGuardian = "2000000000" + BlockCapacityOverestimationFactor = 200 # 200% + PercentDecreaseLimitsStep = 10 # 10% diff --git a/cmd/node/config/enableEpochs.toml b/cmd/node/config/enableEpochs.toml index 95a9f51aaa3..d854e942182 100644 --- a/cmd/node/config/enableEpochs.toml +++ b/cmd/node/config/enableEpochs.toml @@ -166,9 +166,6 @@ # StorageAPICostOptimizationEnableEpoch represents the epoch when new storage helper functions are enabled and cost is reduced in Wasm VM StorageAPICostOptimizationEnableEpoch = 1 - # TransformToMultiShardCreateEnableEpoch represents the epoch when the new function on esdt system sc is enabled to transfer create role into multishard - TransformToMultiShardCreateEnableEpoch = 1 - # ESDTRegisterAndSetAllRolesEnableEpoch represents the epoch when new function to register tickerID and set all roles is enabled ESDTRegisterAndSetAllRolesEnableEpoch = 1 @@ -348,6 +345,9 @@ # AndromedaEnableEpoch represents the epoch when the equivalent messages and fix order for consensus features are enabled AndromedaEnableEpoch = 1 + # SupernovaEnableEpoch represents the epoch when sub-second finality will be enabled + SupernovaEnableEpoch = 2 + # CheckBuiltInCallOnTransferValueAndFailEnableRound represents the ROUND when the check on transfer value fix is activated CheckBuiltInCallOnTransferValueAndFailEnableRound = 1 diff --git a/cmd/node/config/enableRounds.toml b/cmd/node/config/enableRounds.toml index 6258483fb13..d8676ffe3d8 100644 --- a/cmd/node/config/enableRounds.toml +++ b/cmd/node/config/enableRounds.toml @@ -11,3 +11,7 @@ [RoundActivations.DisableAsyncCallV1] Options = [] Round = "0" + + [RoundActivations.SupernovaEnableRound] + Options = [] + Round = "440" diff --git a/cmd/node/config/external.toml b/cmd/node/config/external.toml index 73bc78bae55..b5e11f73646 100644 --- a/cmd/node/config/external.toml +++ b/cmd/node/config/external.toml @@ -13,7 +13,7 @@ Password = "" # EnabledIndexes represents a slice of indexes that will be enabled for indexing. Full list is: # ["rating", "transactions", "blocks", "validators", "miniblocks", "rounds", "accounts", "accountshistory", "receipts", "scresults", "accountsesdt", "accountsesdthistory", "epochinfo", "scdeploys", "tokens", "tags", "logs", "delegators", "operations", "esdts", "events"] - EnabledIndexes = ["rating", "transactions", "blocks", "validators", "miniblocks", "rounds", "accounts", "accountshistory", "receipts", "scresults", "accountsesdt", "accountsesdthistory", "epochinfo", "scdeploys", "tokens", "tags", "logs", "delegators", "operations", "esdts", "events"] + EnabledIndexes = ["rating", "transactions", "blocks", "validators", "miniblocks", "rounds", "accounts", "accountshistory", "receipts", "scresults", "accountsesdt", "accountsesdthistory", "epochinfo", "scdeploys", "tokens", "tags", "logs", "delegators", "operations", "esdts", "events", "executionresults"] # EventNotifierConnector defines settings needed to configure and launch the event notifier component # HTTP event notifier connector integration will be DEPRECATED in the following iterations diff --git a/cmd/node/config/prefs.toml b/cmd/node/config/prefs.toml index 8f3a2343a79..d24ce017010 100644 --- a/cmd/node/config/prefs.toml +++ b/cmd/node/config/prefs.toml @@ -12,8 +12,8 @@ # In multikey mode, all bls keys not mentioned in NamedIdentity section will use this one as default Identity = "" - # RedundancyLevel represents the level of redundancy used by the node (-1 = disabled, 0 = main instance (default), - # 1 = first backup, 2 = second backup, etc.) + # RedundancyLevel represents the level of redundancy used by the node + # (0 = main instance (default), 1 = first backup, 2 = second backup, etc.) RedundancyLevel = 0 # FullArchive, if enabled, will make the node able to respond to requests from past, old epochs. diff --git a/cmd/node/config/ratings.toml b/cmd/node/config/ratings.toml index c9cdf1c4be4..c0bf4470766 100644 --- a/cmd/node/config/ratings.toml +++ b/cmd/node/config/ratings.toml @@ -43,6 +43,13 @@ ProposerDecreaseFactor = -4.0 ValidatorDecreaseFactor = -4.0 ConsecutiveMissedBlocksPenalty = 1.50 + [[ShardChain.RatingStepsByEpoch]] + EnableEpoch = 2 + HoursToMaxRatingFromStartRating = 55 + ProposerValidatorImportance = 1.0 + ProposerDecreaseFactor = -4.0 + ValidatorDecreaseFactor = -4.0 + ConsecutiveMissedBlocksPenalty = 1.04 [MetaChain] [[MetaChain.RatingStepsByEpoch]] @@ -52,6 +59,13 @@ ProposerDecreaseFactor = -4.0 ValidatorDecreaseFactor = -4.0 ConsecutiveMissedBlocksPenalty = 1.50 + [[MetaChain.RatingStepsByEpoch]] + EnableEpoch = 2 + HoursToMaxRatingFromStartRating = 55 + ProposerValidatorImportance = 1.0 + ProposerDecreaseFactor = -4.0 + ValidatorDecreaseFactor = -4.0 + ConsecutiveMissedBlocksPenalty = 1.04 [PeerHonesty] #this value will be multiplied with the current value for a public key each DecayUpdateIntervalInSeconds seconds diff --git a/cmd/node/config/systemSmartContractsConfig.toml b/cmd/node/config/systemSmartContractsConfig.toml index dc1c2a2910b..525dbb84a64 100644 --- a/cmd/node/config/systemSmartContractsConfig.toml +++ b/cmd/node/config/systemSmartContractsConfig.toml @@ -2,7 +2,8 @@ GenesisNodePrice = "2500000000000000000000" #2.5K eGLD MinStakeValue = "100000000000000000000" #100 eGLD MinUnstakeTokensValue = "10000000000000000000" #10eGLD - UnBondPeriod = 250 + UnBondPeriod = 250 # period as number of rounds + UnBondPeriodSupernova = 2500 # period as number of rounds UnBondPeriodInEpochs = 3 MinStepValue = "100000000000000000000" NumRoundsWithoutBleed = 100 diff --git a/cmd/node/factory/interface.go b/cmd/node/factory/interface.go index 8f90ce3ee89..95e37c70c74 100644 --- a/cmd/node/factory/interface.go +++ b/cmd/node/factory/interface.go @@ -23,20 +23,20 @@ type HeaderSigVerifierHandler interface { // HeaderIntegrityVerifierHandler is the interface needed to check that a header's integrity is correct type HeaderIntegrityVerifierHandler interface { Verify(header data.HeaderHandler) error - GetVersion(epoch uint32) string + GetVersion(epoch uint32, round uint64) string IsInterfaceNil() bool } // HeaderVersionHandler handles the header version type HeaderVersionHandler interface { - GetVersion(epoch uint32) string + GetVersion(epoch uint32, round uint64) string Verify(hdr data.HeaderHandler) error IsInterfaceNil() bool } // VersionedHeaderFactory creates versioned headers type VersionedHeaderFactory interface { - Create(epoch uint32) data.HeaderHandler + Create(epoch uint32, round uint64) data.HeaderHandler IsInterfaceNil() bool } diff --git a/cmd/termui/CLI.md b/cmd/termui/CLI.md index 26717e22d8d..8330e84e6ab 100644 --- a/cmd/termui/CLI.md +++ b/cmd/termui/CLI.md @@ -19,7 +19,7 @@ GLOBAL OPTIONS: --log-level level(s) This flag specifies the logger level(s). It can contain multiple comma-separated value. For example, if set to *:INFO the logs for all packages will have the INFO level. However, if set to *:INFO,api:DEBUG the logs for all packages will have the INFO level, excepting the api package which will receive a DEBUG log level. (default: "*:INFO ") --log-correlation Boolean option for enabling log correlation elements. --log-logger-name Boolean option for logger name in the logs. - --interval value This flag specifies the duration in milliseconds until new data is fetched from the node (default: 1000) + --interval value This flag specifies the duration in milliseconds until new data is fetched from the node (default: 100) --use-wss Will use wss instead of ws when creating the web socket --help, -h show help --version, -v print the version diff --git a/cmd/termui/main.go b/cmd/termui/main.go index b9c4084649b..5bbdd8da788 100644 --- a/cmd/termui/main.go +++ b/cmd/termui/main.go @@ -73,7 +73,7 @@ VERSION: fetchIntervalInMilliseconds = cli.IntFlag{ Name: "interval", Usage: "This flag specifies the duration in milliseconds until new data is fetched from the node", - Value: 1000, + Value: 100, Destination: &argsConfig.interval, } diff --git a/cmd/termui/presenter/blockInfoGetters.go b/cmd/termui/presenter/blockInfoGetters.go index bc50da54041..39b0f42c383 100644 --- a/cmd/termui/presenter/blockInfoGetters.go +++ b/cmd/termui/presenter/blockInfoGetters.go @@ -56,3 +56,43 @@ func (psh *PresenterStatusHandler) GetBlockSize() uint64 { func (psh *PresenterStatusHandler) GetHighestFinalBlock() uint64 { return psh.getFromCacheAsUint64(common.MetricHighestFinalBlock) } + +// GetDurationProposedBlockReceivedOrSentFromRoundStart returns the metrics containing time taken to receive a proposed block body since the round started +func (psh *PresenterStatusHandler) GetDurationProposedBlockReceivedOrSentFromRoundStart() uint64 { + return psh.getFromCacheAsUint64(common.MetricReceivedOrSentProposedBlock) +} + +// GetDurationProofReceivedFromProposedBlockReceivedOrSent returns the metrics containing time taken to receive signatures for a block since the block was received +func (psh *PresenterStatusHandler) GetDurationProofReceivedFromProposedBlockReceivedOrSent() uint64 { + return psh.getFromCacheAsUint64(common.MetricReceivedProof) +} + +// GetAvgDurationProposedBlockReceivedOrSentFromRoundStart returns the average received proposed block body metric +func (psh *PresenterStatusHandler) GetAvgDurationProposedBlockReceivedOrSentFromRoundStart() uint64 { + return psh.getFromCacheAsUint64(common.MetricAvgReceivedOrSentProposedBlock) +} + +// GetAvgDurationProofReceivedFromProposedBlockReceivedOrSent returns the average received signatures metric from the time of block body received +func (psh *PresenterStatusHandler) GetAvgDurationProofReceivedFromProposedBlockReceivedOrSent() uint64 { + return psh.getFromCacheAsUint64(common.MetricAvgReceivedProof) +} + +// GetNumTrackedBlocks returns how many blocks are currently tracked by txPool +func (psh *PresenterStatusHandler) GetNumTrackedBlocks() uint64 { + return psh.getFromCacheAsUint64(common.MetricNumTrackedBlocks) +} + +// GetNumTrackedAccounts returns how many accounts are currently tracked by txPool +func (psh *PresenterStatusHandler) GetNumTrackedAccounts() uint64 { + return psh.getFromCacheAsUint64(common.MetricNumTrackedAccounts) +} + +// GetDeltaHeaderNonceLastExecutionResultNonce returns the difference between the current header nonce and the last execution result nonce. Should be 1 in normal conditions. +func (psh *PresenterStatusHandler) GetDeltaHeaderNonceLastExecutionResultNonce() uint64 { + return psh.getFromCacheAsUint64(common.MetricDeltaHeaderNonceLastExecutionResultNonce) +} + +// GetRejectedExecutionResults returns how many execution results were rejected by the Inclusion Estimator. Should be zero in normal conditions. +func (psh *PresenterStatusHandler) GetRejectedExecutionResults() uint64 { + return psh.getFromCacheAsUint64(common.MetricNumInclusionEstimationRejected) +} diff --git a/cmd/termui/presenter/blockInfoGetters_test.go b/cmd/termui/presenter/blockInfoGetters_test.go index 4a7b4f4b3a7..8e248c659da 100644 --- a/cmd/termui/presenter/blockInfoGetters_test.go +++ b/cmd/termui/presenter/blockInfoGetters_test.go @@ -125,18 +125,91 @@ func TestPresenterStatusHandler_GetCurrentRoundTimestamp(t *testing.T) { assert.Equal(t, currentRoundTimestamp, result) } -func TestPresenterStatusHandler_GetBlockSize(t *testing.T) { +func TestPresenterStatusHandler_GetBlockReceived(t *testing.T) { t.Parallel() - miniBlocksSize := uint64(100) - headerSize := uint64(50) + proposedBlockMs := uint64(100) + presenterStatusHandler := NewPresenterStatusHandler() - presenterStatusHandler.SetUInt64Value(common.MetricMiniBlocksSize, miniBlocksSize) - presenterStatusHandler.SetUInt64Value(common.MetricHeaderSize, headerSize) - result := presenterStatusHandler.GetBlockSize() + presenterStatusHandler.SetUInt64Value(common.MetricReceivedOrSentProposedBlock, proposedBlockMs) + result := presenterStatusHandler.GetDurationProposedBlockReceivedOrSentFromRoundStart() + assert.Equal(t, proposedBlockMs, result) +} - blockExpectedSize := miniBlocksSize + headerSize - assert.Equal(t, blockExpectedSize, result) +func TestPresenterStatusHandler_GetAvgBlockReceived(t *testing.T) { + t.Parallel() + + proposedBlockMs := uint64(100) + + presenterStatusHandler := NewPresenterStatusHandler() + presenterStatusHandler.SetUInt64Value(common.MetricAvgReceivedOrSentProposedBlock, proposedBlockMs) + result := presenterStatusHandler.GetAvgDurationProposedBlockReceivedOrSentFromRoundStart() + assert.Equal(t, proposedBlockMs, result) +} + +func TestPresenterStatusHandler_GetProofReceived(t *testing.T) { + t.Parallel() + + proofMs := uint64(100) + + presenterStatusHandler := NewPresenterStatusHandler() + presenterStatusHandler.SetUInt64Value(common.MetricReceivedProof, proofMs) + result := presenterStatusHandler.GetDurationProofReceivedFromProposedBlockReceivedOrSent() + assert.Equal(t, proofMs, result) +} +func TestPresenterStatusHandler_GetAvgProofReceived(t *testing.T) { + t.Parallel() + + proofMs := uint64(100) + + presenterStatusHandler := NewPresenterStatusHandler() + presenterStatusHandler.SetUInt64Value(common.MetricAvgReceivedProof, proofMs) + result := presenterStatusHandler.GetAvgDurationProofReceivedFromProposedBlockReceivedOrSent() + assert.Equal(t, proofMs, result) +} + +func TestPresenterStatusHandler_GetNumTrackedBlocks(t *testing.T) { + t.Parallel() + + numTrackedBlocks := uint64(100) + presenterStatusHandler := NewPresenterStatusHandler() + presenterStatusHandler.SetUInt64Value(common.MetricNumTrackedBlocks, numTrackedBlocks) + result := presenterStatusHandler.GetNumTrackedBlocks() + + assert.Equal(t, numTrackedBlocks, result) +} + +func TestPresenterStatusHandler_GetNumTrackedAccounts(t *testing.T) { + t.Parallel() + + numTrackedAccounts := uint64(100) + presenterStatusHandler := NewPresenterStatusHandler() + presenterStatusHandler.SetUInt64Value(common.MetricNumTrackedAccounts, numTrackedAccounts) + result := presenterStatusHandler.GetNumTrackedAccounts() + + assert.Equal(t, numTrackedAccounts, result) +} + +func TestPresenterStatusHandler_GetDeltaHeaderNonceLastExecutionResultNonce(t *testing.T) { + t.Parallel() + + delta := uint64(1) + presenterStatusHandler := NewPresenterStatusHandler() + presenterStatusHandler.SetUInt64Value(common.MetricDeltaHeaderNonceLastExecutionResultNonce, delta) + result := presenterStatusHandler.GetDeltaHeaderNonceLastExecutionResultNonce() + + assert.Equal(t, delta, result) +} + +func TestPresenterStatusHandler_GetInclusionEstimationRejected(t *testing.T) { + t.Parallel() + + numRejected := uint64(10) + presenterStatusHandler := NewPresenterStatusHandler() + presenterStatusHandler.SetUInt64Value(common.MetricNumInclusionEstimationRejected, numRejected) + result := presenterStatusHandler.GetRejectedExecutionResults() + + assert.Equal(t, numRejected, result) } func TestPresenterStatusHandler_GetHighestFinalBlock(t *testing.T) { @@ -149,3 +222,17 @@ func TestPresenterStatusHandler_GetHighestFinalBlock(t *testing.T) { assert.Equal(t, highestFinalBlockNonce, result) } + +func TestPresenterStatusHandler_GetBlockSize(t *testing.T) { + t.Parallel() + + miniBlocksSize := uint64(100) + headerSize := uint64(50) + presenterStatusHandler := NewPresenterStatusHandler() + presenterStatusHandler.SetUInt64Value(common.MetricMiniBlocksSize, miniBlocksSize) + presenterStatusHandler.SetUInt64Value(common.MetricHeaderSize, headerSize) + result := presenterStatusHandler.GetBlockSize() + + blockExpectedSize := miniBlocksSize + headerSize + assert.Equal(t, blockExpectedSize, result) +} diff --git a/cmd/termui/presenter/chainInfoGetters.go b/cmd/termui/presenter/chainInfoGetters.go index e701dbc8557..771f86f9eae 100644 --- a/cmd/termui/presenter/chainInfoGetters.go +++ b/cmd/termui/presenter/chainInfoGetters.go @@ -13,6 +13,16 @@ func (psh *PresenterStatusHandler) GetNonce() uint64 { return psh.getFromCacheAsUint64(common.MetricNonce) } +// GetLastExecutedNonce will return current last executed nonce of node +func (psh *PresenterStatusHandler) GetLastExecutedNonce() uint64 { + return psh.getFromCacheAsUint64(common.MetricLastExecutedNonce) +} + +// GetProposedNonce will return current proposed nonce of node +func (psh *PresenterStatusHandler) GetProposedNonce() uint64 { + return psh.getFromCacheAsUint64(common.MetricProposedNonce) +} + // GetIsSyncing will return state of the node func (psh *PresenterStatusHandler) GetIsSyncing() uint64 { return psh.getFromCacheAsUint64(common.MetricIsSyncing) @@ -72,25 +82,14 @@ func (psh *PresenterStatusHandler) CalculateTimeToSynchronize(numMillisecondsRef currentSynchronizedRound := psh.GetSynchronizedRound() - numSynchronizationSpeedHistory := len(psh.synchronizationSpeedHistory) - - sum := uint64(0) - for i := 0; i < len(psh.synchronizationSpeedHistory); i++ { - sum += psh.synchronizationSpeedHistory[i] - } - - speed := float64(0) - if numSynchronizationSpeedHistory > 0 { - speed = float64(sum*1000) / float64(numSynchronizationSpeedHistory*numMillisecondsRefreshTime) - } - + speed := psh.calculateSpeedFromSpeedHistory(numMillisecondsRefreshTime) currentRound := psh.GetCurrentRound() if currentRound < currentSynchronizedRound || speed == 0 { - return "" + return "Estimating..." } remainingRoundsToSynchronize := currentRound - currentSynchronizedRound - timeEstimationSeconds := float64(remainingRoundsToSynchronize) / speed + timeEstimationSeconds := float64(remainingRoundsToSynchronize) / float64(speed) remainingTime := core.SecondsToHourMinSec(int(timeEstimationSeconds)) return remainingTime @@ -100,47 +99,37 @@ func (psh *PresenterStatusHandler) CalculateTimeToSynchronize(numMillisecondsRef // how many blocks per second are synchronized func (psh *PresenterStatusHandler) CalculateSynchronizationSpeed(numMillisecondsRefreshTime int) uint64 { currentSynchronizedRound := psh.GetSynchronizedRound() - if psh.oldRound == 0 { - psh.oldRound = currentSynchronizedRound - return 0 - } - - roundsPerSecond := int64(currentSynchronizedRound - psh.oldRound) - if roundsPerSecond < 0 { - roundsPerSecond = 0 - } if len(psh.synchronizationSpeedHistory) >= maxSpeedHistorySaved { - psh.synchronizationSpeedHistory = psh.synchronizationSpeedHistory[1:len(psh.synchronizationSpeedHistory)] + psh.synchronizationSpeedHistory = psh.synchronizationSpeedHistory[1:] } - psh.synchronizationSpeedHistory = append(psh.synchronizationSpeedHistory, uint64(roundsPerSecond)) + psh.synchronizationSpeedHistory = append(psh.synchronizationSpeedHistory, currentSynchronizedRound) - psh.oldRound = currentSynchronizedRound + speed := psh.calculateSpeedFromSpeedHistory(numMillisecondsRefreshTime) - numSyncedBlocks := uint64(0) - cumulatedTime := uint64(0) + return speed +} + +func (psh *PresenterStatusHandler) calculateSpeedFromSpeedHistory(numMillisecondsRefreshTime int) uint64 { lastIndex := len(psh.synchronizationSpeedHistory) - 1 - millisecondsInASecond := uint64(1000) - for { - if lastIndex < 0 { - break - } - if cumulatedTime >= millisecondsInASecond { - break - } + firstIndex := 0 - numSyncedBlocks += psh.synchronizationSpeedHistory[lastIndex] - lastIndex-- - cumulatedTime += uint64(numMillisecondsRefreshTime) + if lastIndex <= firstIndex { + return 0 } - if cumulatedTime == 0 || numSyncedBlocks == 0 { + + numSyncedBlocks := psh.synchronizationSpeedHistory[lastIndex] - psh.synchronizationSpeedHistory[firstIndex] + cumulatedTimeMs := uint64((lastIndex - firstIndex) * numMillisecondsRefreshTime) + + if cumulatedTimeMs == 0 || numSyncedBlocks == 0 { return 0 } - timeAdjustment := float64(millisecondsInASecond) / float64(cumulatedTime) - syncedBlocksAdjustment := timeAdjustment * float64(numSyncedBlocks) + numMillisecondsInASecond := 1000.0 + speed := (float64(numSyncedBlocks) / float64(cumulatedTimeMs)) * numMillisecondsInASecond + + return uint64(speed) - return uint64(syncedBlocksAdjustment) } // GetNumTxProcessed will return number of processed transactions since node starts diff --git a/cmd/termui/presenter/chainInfoGetters_test.go b/cmd/termui/presenter/chainInfoGetters_test.go index e4faa42b1f3..a2a7755fccc 100644 --- a/cmd/termui/presenter/chainInfoGetters_test.go +++ b/cmd/termui/presenter/chainInfoGetters_test.go @@ -22,6 +22,28 @@ func TestPresenterStatusHandler_GetNonce(t *testing.T) { assert.Equal(t, nonce, result) } +func TestPresenterStatusHandler_GetLastExecutedNonce(t *testing.T) { + t.Parallel() + + nonce := uint64(900) + presenterStatusHandler := NewPresenterStatusHandler() + presenterStatusHandler.SetUInt64Value(common.MetricLastExecutedNonce, nonce) + result := presenterStatusHandler.GetLastExecutedNonce() + + assert.Equal(t, nonce, result) +} + +func TestPresenterStatusHandler_GetProposedNonce(t *testing.T) { + t.Parallel() + + nonce := uint64(1000) + presenterStatusHandler := NewPresenterStatusHandler() + presenterStatusHandler.SetUInt64Value(common.MetricProposedNonce, nonce) + result := presenterStatusHandler.GetProposedNonce() + + assert.Equal(t, nonce, result) +} + func TestPresenterStatusHandler_GetIsSyncing(t *testing.T) { t.Parallel() @@ -132,7 +154,11 @@ func TestPresenterStatusHandler_CalculateTimeToSynchronize(t *testing.T) { time.Sleep(time.Second) presenterStatusHandler.SetUInt64Value(common.MetricSynchronizedRound, currentBlockNonce) presenterStatusHandler.SetUInt64Value(common.MetricCurrentRound, probableHighestNonce) - presenterStatusHandler.synchronizationSpeedHistory = append(presenterStatusHandler.synchronizationSpeedHistory, synchronizationSpeed) + presenterStatusHandler.synchronizationSpeedHistory = []uint64{ + currentBlockNonce, + currentBlockNonce + synchronizationSpeed, + } + synchronizationEstimation := presenterStatusHandler.CalculateTimeToSynchronize(1000) // Node needs to synchronize 190 blocks and synchronization speed is 10 blocks/s diff --git a/cmd/termui/presenter/presenterStatusHandler.go b/cmd/termui/presenter/presenterStatusHandler.go index 1722eedbcb4..a5a00166ffe 100644 --- a/cmd/termui/presenter/presenterStatusHandler.go +++ b/cmd/termui/presenter/presenterStatusHandler.go @@ -15,7 +15,6 @@ type PresenterStatusHandler struct { mutPresenterMap sync.RWMutex logLines []string mutLogLineWrite sync.RWMutex - oldRound uint64 synchronizationSpeedHistory []uint64 totalRewardsOld *big.Float } diff --git a/cmd/termui/provider/logProvider.go b/cmd/termui/provider/logProvider.go index 300d689182b..514280562a5 100644 --- a/cmd/termui/provider/logProvider.go +++ b/cmd/termui/provider/logProvider.go @@ -11,7 +11,7 @@ import ( "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/node" - "github.com/multiversx/mx-chain-logger-go" + logger "github.com/multiversx/mx-chain-logger-go" ) var formatter = logger.PlainFormatter{} @@ -48,14 +48,18 @@ func InitLogHandler(args LogHandlerArgs) error { var err error scheme := ws + if args.UseWss { scheme = wss } + nodeURL := getNodeUrlPath(args.NodeURL) + go func() { for { - webSocket, err = openWebSocket(scheme, args.NodeURL) + webSocket, err = openWebSocket(scheme, nodeURL) + if err != nil { - _, _ = args.Presenter.Write([]byte(fmt.Sprintf("termui websocket error, retrying in %v...", retryDuration))) + _, _ = args.Presenter.Write([]byte(fmt.Sprintf("termui websocket error %s, retrying in %v...", err, retryDuration))) time.Sleep(retryDuration) continue } @@ -75,12 +79,17 @@ func InitLogHandler(args LogHandlerArgs) error { return nil } +func getNodeUrlPath(nodeURL string) string { + return strings.TrimPrefix(strings.TrimPrefix(nodeURL, "http://"), "https://") +} + func openWebSocket(scheme string, address string) (*websocket.Conn, error) { u := url.URL{ Scheme: scheme, Host: address, Path: "/log", } + conn, _, err := websocket.DefaultDialer.Dial(u.String(), nil) if err != nil { return nil, err diff --git a/cmd/termui/view/interface.go b/cmd/termui/view/interface.go index 608dd2e1222..214755580db 100644 --- a/cmd/termui/view/interface.go +++ b/cmd/termui/view/interface.go @@ -18,9 +18,19 @@ type Presenter interface { GetCountConsensusAcceptedBlocks() uint64 GetCountLeader() uint64 GetCountAcceptedBlocks() uint64 + GetDurationProposedBlockReceivedOrSentFromRoundStart() uint64 + GetDurationProofReceivedFromProposedBlockReceivedOrSent() uint64 + GetAvgDurationProposedBlockReceivedOrSentFromRoundStart() uint64 + GetAvgDurationProofReceivedFromProposedBlockReceivedOrSent() uint64 + GetNumTrackedBlocks() uint64 + GetNumTrackedAccounts() uint64 + GetRejectedExecutionResults() uint64 + GetDeltaHeaderNonceLastExecutionResultNonce() uint64 GetIsSyncing() uint64 GetTxPoolLoad() uint64 GetNonce() uint64 + GetProposedNonce() uint64 + GetLastExecutedNonce() uint64 GetProbableHighestNonce() uint64 GetSynchronizedRound() uint64 GetRoundTime() uint64 diff --git a/cmd/termui/view/termuic/processCloser.go b/cmd/termui/view/termuic/processCloser.go index e29bfee71ff..2941f17366f 100644 --- a/cmd/termui/view/termuic/processCloser.go +++ b/cmd/termui/view/termuic/processCloser.go @@ -6,7 +6,7 @@ import ( "syscall" ) -// StopApplication is used to stop application when Ctrl+C is pressed +// stopApplication is used to stop application when Ctrl+C is pressed func stopApplication() { if p, err := os.FindProcess(os.Getpid()); err != nil { return diff --git a/cmd/termui/view/termuic/termuiRenders/drawableContainer.go b/cmd/termui/view/termuic/termuiRenders/drawableContainer.go index f21472b2185..9d971d28237 100644 --- a/cmd/termui/view/termuic/termuiRenders/drawableContainer.go +++ b/cmd/termui/view/termuic/termuiRenders/drawableContainer.go @@ -4,7 +4,7 @@ import ( "github.com/gizak/termui/v3" ) -const topHeight = 22 +const topHeight = 24 // DrawableContainer defines a container of drawable object with position and dimensions type DrawableContainer struct { diff --git a/cmd/termui/view/termuic/termuiRenders/widgetsRender.go b/cmd/termui/view/termuic/termuiRenders/widgetsRender.go index 2f39b000e9f..6669f2536bf 100644 --- a/cmd/termui/view/termuic/termuiRenders/widgetsRender.go +++ b/cmd/termui/view/termuic/termuiRenders/widgetsRender.go @@ -11,6 +11,8 @@ import ( "github.com/multiversx/mx-chain-go/common" ) +const conversionFactorToSeconds = 1e9 + const ( statusSyncing = "currently syncing" statusSynchronized = "synchronized" @@ -90,12 +92,15 @@ func (wr *WidgetsRender) setGrid() { colMemoryLoad := ui.NewCol(1.0/2, wr.memoryLoad) gridRight := ui.NewGrid() + + progressBarElementRatio := 3.0 / topHeight + blockSectionHeightRatio := 1 - 4*progressBarElementRatio gridRight.Set( - ui.NewRow(10.0/22, wr.blockInfo), - ui.NewRow(3.0/22, colCpuLoad, colMemoryLoad), - ui.NewRow(3.0/22, wr.epochLoad), - ui.NewRow(3.0/22, wr.networkBytesInEpoch), - ui.NewRow(3.0/22, colNetworkSent, colNetworkRecv), + ui.NewRow(blockSectionHeightRatio, wr.blockInfo), + ui.NewRow(progressBarElementRatio, colCpuLoad, colMemoryLoad), + ui.NewRow(progressBarElementRatio, wr.epochLoad), + ui.NewRow(progressBarElementRatio, wr.networkBytesInEpoch), + ui.NewRow(progressBarElementRatio, colNetworkSent, colNetworkRecv), ) gridBottom := ui.NewGrid() @@ -115,13 +120,36 @@ func (wr *WidgetsRender) RefreshData(numMillisecondsRefreshTime int) { wr.prepareLoads() } +func (wr *WidgetsRender) getShardIdStr() string { + currentRound := wr.presenter.GetCurrentRound() + nodesProcessed := wr.presenter.GetTrieSyncNumProcessedNodes() + isNodeSyncingTrie := nodesProcessed != 0 + + synchronizedRound := wr.presenter.GetSynchronizedRound() + isRoundNotSynchronized := synchronizedRound < currentRound + + isNodeSyncing := isNodeSyncingTrie || isRoundNotSynchronized || currentRound == 0 + + if isNodeSyncing { + return statusNotApplicable + } + + shardID := wr.presenter.GetShardId() + + shardIdStr := fmt.Sprintf("%d", shardID) + if shardID == uint64(core.MetachainShardId) { + shardIdStr = "meta" + } + + return shardIdStr +} + func (wr *WidgetsRender) prepareInstanceInfo() { // 8 rows and one column numRows := 8 rows := make([][]string, numRows) nodeName := wr.presenter.GetNodeName() - shardId := wr.presenter.GetShardId() instanceType := wr.presenter.GetNodeType() peerType := wr.presenter.GetPeerType() peerSubType := wr.presenter.GetPeerSubType() @@ -134,15 +162,12 @@ func (wr *WidgetsRender) prepareInstanceInfo() { if peerSubType == core.FullHistoryObserver.String() { nodeTypeAndListDisplay += " - full archive" } - shardIdStr := fmt.Sprintf("%d", shardId) - if shardId == uint64(core.MetachainShardId) { - shardIdStr = "meta" - } + wr.instanceInfo.RowStyles[0] = ui.NewStyle(ui.ColorYellow) rows[0] = []string{ fmt.Sprintf("Node name: %s (Shard %s - %s)", nodeName, - shardIdStr, + wr.getShardIdStr(), nodeTypeAndListDisplay, ), } @@ -258,7 +283,7 @@ func (wr *WidgetsRender) prepareChainInfo(numMillisecondsRefreshTime int) { synchronizedRound, currentRound)} consensusRoundTime := wr.presenter.GetRoundTime() - rows[7] = []string{fmt.Sprintf("Consensus round time: %ds", consensusRoundTime)} + rows[7] = []string{fmt.Sprintf("Consensus round time: %dms", consensusRoundTime)} numConnectedPeers := wr.presenter.GetNumConnectedPeers() numLiveValidators := wr.presenter.GetLiveValidatorNodes() @@ -294,51 +319,89 @@ func computeRedundancyStr(redundancyLevel int64, redundancyIsMainActive string) } func (wr *WidgetsRender) prepareBlockInfo() { - // 7 rows and one column - numRows := 8 + // 10 rows and one column + numRows := 10 rows := make([][]string, numRows) - currentBlockHeight := wr.presenter.GetNonce() + currentBlockHeight := wr.presenter.GetProposedNonce() blockSize := wr.presenter.GetBlockSize() - rows[0] = []string{fmt.Sprintf("Current block height: %d, size: %s", currentBlockHeight, core.ConvertBytes(blockSize))} + lastExecutedNonce := wr.presenter.GetLastExecutedNonce() + lastNotarizedExecutedNonce := wr.presenter.GetNonce() - numTransactionInBlock := wr.presenter.GetNumTxInBlock() - rows[1] = []string{fmt.Sprintf("Num transactions in block: %d", numTransactionInBlock)} + if currentBlockHeight == 0 { + currentBlockHeight = lastNotarizedExecutedNonce + } + + if lastExecutedNonce == 0 { + lastExecutedNonce = currentBlockHeight + } + + rows[0] = []string{fmt.Sprintf("Current block height: %d, size: %s, last executed nonce: %d, last notarized executed nonce: %d", currentBlockHeight, core.ConvertBytes(blockSize), lastExecutedNonce, lastNotarizedExecutedNonce)} + numTransactionInBlock := wr.presenter.GetNumTxInBlock() numMiniBlocks := wr.presenter.GetNumMiniBlocks() - rows[2] = []string{fmt.Sprintf("Num miniblocks in block: %d", numMiniBlocks)} + rows[1] = []string{fmt.Sprintf("Num transactions in block: %d | Num miniblocks in block: %d", + numTransactionInBlock, + numMiniBlocks, + )} currentBlockHash := wr.presenter.GetCurrentBlockHash() - rows[3] = []string{fmt.Sprintf("Current block hash: %s", currentBlockHash)} + rows[2] = []string{fmt.Sprintf("Current block hash: %s", currentBlockHash)} crossCheckBlockHeight := wr.presenter.GetCrossCheckBlockHeight() - rows[4] = []string{fmt.Sprintf("Cross check: %s", crossCheckBlockHeight)} + rows[3] = []string{fmt.Sprintf("Cross check: %s", crossCheckBlockHeight)} shardId := wr.presenter.GetShardId() if shardId != uint64(core.MetachainShardId) { highestFinalBlock := wr.presenter.GetHighestFinalBlock() - rows[4][0] += fmt.Sprintf(", final nonce: %d", highestFinalBlock) + rows[3][0] += fmt.Sprintf(", final nonce: %d", highestFinalBlock) } consensusState := wr.presenter.GetConsensusState() - rows[5] = []string{fmt.Sprintf("Consensus state: %s", consensusState)} + rows[4] = []string{fmt.Sprintf("Consensus state: %s", consensusState)} syncStatus := wr.presenter.GetIsSyncing() switch syncStatus { case 1: - rows[6] = []string{"Consensus round state: N/A (syncing)"} + rows[4][0] += " | Consensus round state: N/A (syncing)" case 0: instanceType := wr.presenter.GetNodeType() if instanceType == string(core.NodeTypeObserver) { - rows[6] = []string{fmt.Sprintf("Consensus round state: N/A (%s)", string(core.NodeTypeObserver))} + rows[4][0] += fmt.Sprintf(" | Consensus round state: N/A (%s)", string(core.NodeTypeObserver)) } else { consensusRoundState := wr.presenter.GetConsensusRoundState() - rows[6] = []string{fmt.Sprintf("Consensus round state: %s", consensusRoundState)} + rows[4][0] += fmt.Sprintf(" | Consensus round state: %s", consensusRoundState) } } + durationStartRoundToSentOrReceivedBlock := float64(wr.presenter.GetDurationProposedBlockReceivedOrSentFromRoundStart()) / conversionFactorToSeconds + durationSentOrReceivedBlockToReceivedProof := float64(wr.presenter.GetDurationProofReceivedFromProposedBlockReceivedOrSent()) / conversionFactorToSeconds + + rows[5] = []string{ + fmt.Sprintf("Received proposed block: %.6f sec | Received proof: %.6f sec", + durationStartRoundToSentOrReceivedBlock, + durationSentOrReceivedBlockToReceivedProof), + } + + durationStartRoundToSentOrReceivedBlock = float64(wr.presenter.GetAvgDurationProposedBlockReceivedOrSentFromRoundStart()) / conversionFactorToSeconds + durationSentOrReceivedBlockToReceivedProof = float64(wr.presenter.GetAvgDurationProofReceivedFromProposedBlockReceivedOrSent()) / conversionFactorToSeconds + + rows[6] = []string{ + fmt.Sprintf("Avg Received proposed block: %.6f sec | Avg Received proof: %.6f sec", + durationStartRoundToSentOrReceivedBlock, + durationSentOrReceivedBlockToReceivedProof), + } + + rows[7] = []string{ + fmt.Sprintf("Delta header nonce - last execution result nonce: %d | Rejected execution results: %d", + wr.presenter.GetDeltaHeaderNonceLastExecutionResultNonce(), + wr.presenter.GetRejectedExecutionResults()), + } + currentRoundTimestamp := wr.presenter.GetCurrentRoundTimestamp() - rows[7] = []string{fmt.Sprintf("Current round timestamp: %d", currentRoundTimestamp)} + rows[8] = []string{fmt.Sprintf("Current round timestamp: %d", currentRoundTimestamp)} + + rows[9] = []string{fmt.Sprintf("Num tracked blocks: %d, Num tracked accounts: %d", wr.presenter.GetNumTrackedBlocks(), wr.presenter.GetNumTrackedAccounts())} wr.blockInfo.Title = "Block info:" wr.blockInfo.RowSeparator = false diff --git a/cmd/termui/view/termuic/termuiRenders/widgetsRender_test.go b/cmd/termui/view/termuic/termuiRenders/widgetsRender_test.go index d6a818c7665..a73b6f49dd8 100644 --- a/cmd/termui/view/termuic/termuiRenders/widgetsRender_test.go +++ b/cmd/termui/view/termuic/termuiRenders/widgetsRender_test.go @@ -4,7 +4,11 @@ import ( "fmt" "testing" + "github.com/gizak/termui/v3/widgets" + "github.com/multiversx/mx-chain-go/cmd/termui/presenter" + "github.com/multiversx/mx-chain-go/common" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestWidgetsRender_prepareLogLines(t *testing.T) { @@ -43,3 +47,51 @@ func TestWidgetsRender_prepareLogLines(t *testing.T) { assert.Equal(t, 8, len(result)) }) } + +func TestPrepareBlockInfo(t *testing.T) { + presenterStatusHandler := presenter.NewPresenterStatusHandler() + presenterStatusHandler.SetUInt64Value(common.MetricNonce, 38) + presenterStatusHandler.SetUInt64Value(common.MetricLastExecutedNonce, 40) + presenterStatusHandler.SetUInt64Value(common.MetricProposedNonce, 42) + presenterStatusHandler.SetUInt64Value(common.MetricMiniBlocksSize, 2000) + presenterStatusHandler.SetUInt64Value(common.MetricHeaderSize, 48) + presenterStatusHandler.SetUInt64Value(common.MetricNumTxInBlock, 5) + presenterStatusHandler.SetUInt64Value(common.MetricNumMiniBlocks, 2) + presenterStatusHandler.SetStringValue(common.MetricCurrentBlockHash, "hash") + presenterStatusHandler.SetStringValue(common.MetricCrossCheckBlockHeight, "cross123") + presenterStatusHandler.SetUInt64Value(common.MetricHighestFinalBlock, 99) + presenterStatusHandler.SetUInt64Value(common.MetricShardId, 0) // not metachain + presenterStatusHandler.SetStringValue(common.MetricConsensusState, "ProposedBlock") + presenterStatusHandler.SetUInt64Value(common.MetricIsSyncing, 0) + presenterStatusHandler.SetStringValue(common.MetricConsensusRoundState, "Success") + presenterStatusHandler.SetUInt64Value(common.MetricCurrentRoundTimestamp, 123456) + presenterStatusHandler.SetUInt64Value(common.MetricReceivedOrSentProposedBlock, 2_000_000) // ns + presenterStatusHandler.SetUInt64Value(common.MetricReceivedProof, 3_000_000) + presenterStatusHandler.SetUInt64Value(common.MetricAvgReceivedOrSentProposedBlock, 1_000_000) + presenterStatusHandler.SetUInt64Value(common.MetricAvgReceivedProof, 1_500_000) + presenterStatusHandler.SetUInt64Value(common.MetricNumTrackedBlocks, 100) + presenterStatusHandler.SetUInt64Value(common.MetricDeltaHeaderNonceLastExecutionResultNonce, 4) + presenterStatusHandler.SetUInt64Value(common.MetricNumInclusionEstimationRejected, 10) + + wr := &WidgetsRender{ + presenter: presenterStatusHandler, + blockInfo: &widgets.Table{}, + } + + wr.prepareBlockInfo() + + require.Equal(t, "Block info:", wr.blockInfo.Title) + require.False(t, wr.blockInfo.RowSeparator) + require.Len(t, wr.blockInfo.Rows, 10) + + // Example: check one row + require.Contains(t, wr.blockInfo.Rows[0][0], fmt.Sprintf("Current block height: %d, size: %s, last executed nonce: %d, last notarized executed nonce: %d", 42, "2.00 KB", 40, 38)) + require.Contains(t, wr.blockInfo.Rows[2][0], "hash") + require.Contains(t, wr.blockInfo.Rows[4][0], "Consensus state: ProposedBlock | Consensus round state: Success") + require.Contains(t, wr.blockInfo.Rows[5][0], "Received proposed block: 0.002000 sec | Received proof: 0.003000 sec") + require.Contains(t, wr.blockInfo.Rows[6][0], "Avg Received proposed block: 0.001000 sec | Avg Received proof: 0.001500 sec") + require.Contains(t, wr.blockInfo.Rows[7][0], "Delta header nonce - last execution result nonce: 4 | Rejected execution results: 10") + require.Contains(t, wr.blockInfo.Rows[8][0], "Current round timestamp: 123456") + require.Contains(t, wr.blockInfo.Rows[3][0], "Cross check: cross123, final nonce: 99") + require.Contains(t, wr.blockInfo.Rows[9][0], "Num tracked blocks: 100") +} diff --git a/common/channels.go b/common/channels.go index ba240d76b7b..7cd38dfbc15 100644 --- a/common/channels.go +++ b/common/channels.go @@ -16,3 +16,16 @@ func CloseKeyValueHolderChan(ch chan core.KeyValueHolder) { close(ch) } } + +// EmptyUint64Channel will return the number of reads from the channel +func EmptyUint64Channel(ch chan uint64) int { + nrReads := 0 + for { + select { + case <-ch: + nrReads++ + default: + return nrReads + } + } +} diff --git a/common/channels_test.go b/common/channels_test.go index 4e2828e2d6a..2a8dc612bc4 100644 --- a/common/channels_test.go +++ b/common/channels_test.go @@ -36,3 +36,40 @@ func didTriggerHappen(ch chan struct{}) bool { return false } } + +func TestEmptyUint64Channel_EmptyChannel(t *testing.T) { + t.Parallel() + + ch := make(chan uint64, 5) + nrReads := EmptyUint64Channel(ch) + + require.Equal(t, 0, nrReads) +} + +func TestEmptyUint64Channel_ChannelWithValues(t *testing.T) { + t.Parallel() + + ch := make(chan uint64, 5) + ch <- 1 + ch <- 2 + ch <- 3 + + nrReads := EmptyUint64Channel(ch) + + require.Equal(t, 3, nrReads) + require.Equal(t, 0, len(ch)) +} + +func TestEmptyUint64Channel_FullChannel(t *testing.T) { + t.Parallel() + + ch := make(chan uint64, 3) + ch <- 10 + ch <- 20 + ch <- 30 + + nrReads := EmptyUint64Channel(ch) + + require.Equal(t, 3, nrReads) + require.Equal(t, 0, len(ch)) +} diff --git a/common/common.go b/common/common.go index 6641777b54a..cf6601328d5 100644 --- a/common/common.go +++ b/common/common.go @@ -2,15 +2,22 @@ package common import ( "encoding/hex" + "encoding/json" "fmt" + "math/big" "math/bits" + "reflect" "strconv" "strings" "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" - "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/errors" logger "github.com/multiversx/mx-chain-logger-go" + + "github.com/multiversx/mx-chain-go/config" ) const ( @@ -21,12 +28,36 @@ const ( nonceIndex = 0 ) +type executionResultHandler interface { + GetMiniBlockHeadersHandlers() []data.MiniBlockHeaderHandler +} + type chainParametersHandler interface { CurrentChainParameters() config.ChainParametersByEpochConfig ChainParametersForEpoch(epoch uint32) (config.ChainParametersByEpochConfig, error) IsInterfaceNil() bool } +// PrepareLogEventsKey will prepare logs key for cacher +func PrepareLogEventsKey(headerHash []byte) []byte { + return append([]byte("logs"), headerHash...) +} + +// PrepareOrderedTxHashesKey will prepare transactions execution order key for cacher +func PrepareOrderedTxHashesKey(headerHash []byte) []byte { + return append([]byte("execution"), headerHash...) +} + +// PrepareHeaderGasDataKey will prepare header gas data key for cacher +func PrepareHeaderGasDataKey(headerHash []byte) []byte { + return append([]byte("gas"), headerHash...) +} + +// PrepareUnexecutableTxHashesKey will prepare unexecutable transaction hashes key for cacher +func PrepareUnexecutableTxHashesKey(headerHash []byte) []byte { + return append([]byte("unexecutable"), headerHash...) +} + // IsValidRelayedTxV3 returns true if the provided transaction is a valid transaction of type relayed v3 func IsValidRelayedTxV3(tx data.TransactionHandler) bool { relayedTx, isRelayedV3 := tx.(data.RelayedTransactionHandler) @@ -50,6 +81,23 @@ func IsRelayedTxV3(tx data.TransactionHandler) bool { return hasRelayer || hasRelayerSignature } +// IsAsyncExecutionEnabledForEpochAndRound returns true if both Supernova epochs and Supernova rounds are enabled for the provided epoch and round +func IsAsyncExecutionEnabledForEpochAndRound( + enableEpochsHandler EnableEpochsHandler, + enableRoundsHandler EnableRoundsHandler, + epoch uint32, + round uint64, +) bool { + return enableEpochsHandler.IsFlagEnabledInEpoch(SupernovaFlag, epoch) && + enableRoundsHandler.IsFlagEnabledInRound(SupernovaRoundFlag, round) +} + +// IsAsyncExecutionEnabled returns true if both Supernova epochs and Supernova rounds are enabled +func IsAsyncExecutionEnabled(enableEpochsHandler EnableEpochsHandler, enableRoundsHandler EnableRoundsHandler) bool { + return enableEpochsHandler.IsFlagEnabled(SupernovaFlag) && + enableRoundsHandler.IsFlagEnabled(SupernovaRoundFlag) +} + // IsEpochChangeBlockForFlagActivation returns true if the provided header is the first one after the specified flag's activation func IsEpochChangeBlockForFlagActivation(header data.HeaderHandler, enableEpochsHandler EnableEpochsHandler, flag core.EnableEpochFlag) bool { isStartOfEpochBlock := header.IsStartOfEpochBlock() @@ -204,7 +252,367 @@ func GetNonceAndShardFromKey(nonceShardKey []byte) (uint64, uint32, error) { } // ConvertTimeStampSecToMs will convert unix timestamp from seconds to milliseconds -// TODO: this has to be handled properly when round timestamp granularity will be changed to milliseconds func ConvertTimeStampSecToMs(timeStamp uint64) uint64 { return timeStamp * 1000 } + +func convertTimeStampMsToSec(timeStamp uint64) uint64 { + return timeStamp / 1000 +} + +// GetHeaderTimestamps will return timestamps as seconds and milliseconds based on supernova round activation +func GetHeaderTimestamps( + header data.HeaderHandler, + enableEpochsHandler EnableEpochsHandler, +) (uint64, uint64, error) { + if check.IfNil(header) { + return 0, 0, ErrNilHeaderHandler + } + + headerTimestamp := header.GetTimeStamp() + return PrepareTimestampBasedOnHeaderData(headerTimestamp, header.GetEpoch(), enableEpochsHandler) +} + +// PrepareTimestampBasedOnHeaderData will prepare timestamp based on the provided data +func PrepareTimestampBasedOnHeaderData(headerTimestamp uint64, headerEpoch uint32, enableEpochsHandler EnableEpochsHandler) (uint64, uint64, error) { + if check.IfNil(enableEpochsHandler) { + return 0, 0, errors.ErrNilEnableEpochsHandler + } + timestampSec := headerTimestamp + timestampMs := headerTimestamp + + if !enableEpochsHandler.IsFlagEnabledInEpoch(SupernovaFlag, headerEpoch) { + timestampMs = ConvertTimeStampSecToMs(headerTimestamp) + return timestampSec, timestampMs, nil + } + + // reduce block timestamp (which now comes as milliseconds) to seconds to keep backwards compatibility + // from now on timestampMs will be used for milliseconds granularity + timestampSec = convertTimeStampMsToSec(headerTimestamp) + + return timestampSec, timestampMs, nil +} + +// PrettifyStruct returns a JSON string representation of a struct, converting byte slices to hex +// and formatting big number values into readable strings. Useful for logging or debugging. +func PrettifyStruct(x interface{}) (string, error) { + if x == nil { + return "nil", nil + } + + val := reflect.ValueOf(x) + result := prettifyValue(val, val.Type()) + + jsonBytes, err := json.Marshal(result) + if err != nil { + return "", err + } + return string(jsonBytes), nil +} + +// prettifyValue recursively converts a reflect.Value into a representation suitable for JSON serialization, +// handling pointers, slices, structs, and special formatting for big numeric types. +func prettifyValue(val reflect.Value, typ reflect.Type) interface{} { + if bigValue, isBig := prettifyBigNumbers(val); isBig { + return bigValue + } + + if val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil + } + val = val.Elem() + typ = val.Type() + } + + switch val.Kind() { + case reflect.Struct: + return prettifyStructFields(val, typ) + case reflect.Slice, reflect.Array: + return prettifySliceOrArray(val) + default: + return val.Interface() + } +} + +func prettifyStructFields(val reflect.Value, typ reflect.Type) map[string]interface{} { + out := make(map[string]interface{}) + for i := 0; i < val.NumField(); i++ { + field := val.Field(i) + fieldType := typ.Field(i) + + name := fieldType.Tag.Get("json") + if name == "" { + name = fieldType.Name + } else { + name = strings.Split(name, ",")[0] + } + + if fieldType.PkgPath != "" { + out[name] = "" + continue + } + + if field.Kind() == reflect.Slice && field.Type() == reflect.TypeOf([]byte{}) { + out[name] = fmt.Sprintf("%x", field.Bytes()) + } else { + out[name] = prettifyValue(field, field.Type()) + } + } + return out +} + +func prettifySliceOrArray(val reflect.Value) interface{} { + if val.Type().Elem().Kind() == reflect.Uint8 { + b := make([]byte, val.Len()) + for i := 0; i < val.Len(); i++ { + b[i] = byte(val.Index(i).Uint()) + } + return fmt.Sprintf("%x", b) + } + + out := make([]interface{}, val.Len()) + for i := 0; i < val.Len(); i++ { + out[i] = prettifyValue(val.Index(i), val.Index(i).Type()) + } + return out +} + +func prettifyBigNumbers(val reflect.Value) (string, bool) { + if val.CanInterface() { + switch v := val.Interface().(type) { + case *big.Int: + if v != nil { + return v.String(), true + } + case big.Int: + return v.String(), true + case *big.Float: + if v != nil { + return v.Text('g', -1), true + } + case big.Float: + return v.Text('g', -1), true + case *big.Rat: + if v != nil { + return v.RatString(), true + } + case big.Rat: + return v.RatString(), true + } + } + return "", false +} + +// GetLastBaseExecutionResultHandler extracts the BaseExecutionResultHandler from the provided header, based on its type +func GetLastBaseExecutionResultHandler(header data.HeaderHandler) (data.BaseExecutionResultHandler, error) { + if check.IfNil(header) { + return nil, ErrNilHeaderHandler + } + + lastExecResultsHandler := header.GetLastExecutionResultHandler() + return ExtractBaseExecutionResultHandler(lastExecResultsHandler) +} + +// GetOrCreateLastExecutionResultForPrevHeader extracts base execution result from +// header if header v3. Otherwise, it will create last execution result based +// on the provided header +func GetOrCreateLastExecutionResultForPrevHeader( + prevHeader data.HeaderHandler, + prevHeaderHash []byte, +) (data.BaseExecutionResultHandler, error) { + if prevHeader.IsHeaderV3() { + return ExtractBaseExecutionResultHandler(prevHeader.GetLastExecutionResultHandler()) + } + + lastExecResult, err := CreateLastExecutionResultFromPrevHeader(prevHeader, prevHeaderHash) + if err != nil { + return nil, err + } + + return ExtractBaseExecutionResultHandler(lastExecResult) +} + +func isValidHeaderBeforeV3(header data.HeaderHandler) error { + _, isHeaderV2 := header.(*block.HeaderV2) + if isHeaderV2 { + return nil + } + + _, isHeaderV1 := header.(*block.Header) + if !isHeaderV1 { + return ErrWrongTypeAssertion + } + + return nil +} + +// CreateLastExecutionResultFromPrevHeader creates a LastExecutionResultInfo object from the given previous header +func CreateLastExecutionResultFromPrevHeader(prevHeader data.HeaderHandler, prevHeaderHash []byte) (data.LastExecutionResultHandler, error) { + if check.IfNil(prevHeader) { + return nil, ErrNilHeaderHandler + } + if len(prevHeaderHash) == 0 { + return nil, ErrInvalidHeaderHash + } + + if prevHeader.GetShardID() != core.MetachainShardId { + err := isValidHeaderBeforeV3(prevHeader) + if err != nil { + return nil, err + } + + return &block.ExecutionResultInfo{ + NotarizedInRound: prevHeader.GetRound(), + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: prevHeaderHash, + HeaderNonce: prevHeader.GetNonce(), + HeaderRound: prevHeader.GetRound(), + HeaderEpoch: prevHeader.GetEpoch(), + RootHash: prevHeader.GetRootHash(), + GasUsed: 0, // we don't have this information in previous header + }, + }, nil + } + + prevMetaHeader, ok := prevHeader.(data.MetaHeaderHandler) + if !ok { + return nil, ErrWrongTypeAssertion + } + + return &block.MetaExecutionResultInfo{ + NotarizedInRound: prevHeader.GetRound(), + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: prevHeaderHash, + HeaderNonce: prevMetaHeader.GetNonce(), + HeaderRound: prevMetaHeader.GetRound(), + HeaderEpoch: prevMetaHeader.GetEpoch(), + RootHash: prevMetaHeader.GetRootHash(), + GasUsed: 0, // we don't have this information in previous header + }, + ValidatorStatsRootHash: prevMetaHeader.GetValidatorStatsRootHash(), + AccumulatedFeesInEpoch: prevMetaHeader.GetAccumulatedFeesInEpoch(), + DevFeesInEpoch: prevMetaHeader.GetDevFeesInEpoch(), + }, + }, nil +} + +// ExtractBaseExecutionResultHandler extracts the base execution result handler from a last execution result handler +func ExtractBaseExecutionResultHandler(lastExecResultsHandler data.LastExecutionResultHandler) (data.BaseExecutionResultHandler, error) { + if check.IfNil(lastExecResultsHandler) { + log.Error("ExtractBaseExecutionResultHandler: nil exec") + return nil, ErrNilLastExecutionResultHandler + } + + var baseExecutionResultsHandler data.BaseExecutionResultHandler + var ok bool + switch executionResultsHandlerType := lastExecResultsHandler.(type) { + case data.LastMetaExecutionResultHandler: + metaBaseExecutionResults := executionResultsHandlerType.GetExecutionResultHandler() + if check.IfNil(metaBaseExecutionResults) { + return nil, ErrNilBaseExecutionResult + } + baseExecutionResultsHandler, ok = metaBaseExecutionResults.(data.BaseExecutionResultHandler) + if !ok { + return nil, ErrWrongTypeAssertion + } + case data.LastShardExecutionResultHandler: + baseExecutionResultsHandler = executionResultsHandlerType.GetExecutionResultHandler() + default: + return nil, fmt.Errorf("%w: unsupported execution result handler type", ErrWrongTypeAssertion) + } + + if check.IfNil(baseExecutionResultsHandler) { + return nil, ErrNilBaseExecutionResult + } + + return baseExecutionResultsHandler, nil +} + +// GetMiniBlocksHeaderHandlersFromExecResult returns miniblock handlers based on execution result +func GetMiniBlocksHeaderHandlersFromExecResult( + baseExecResult data.BaseExecutionResultHandler, +) ([]data.MiniBlockHeaderHandler, error) { + if check.IfNil(baseExecResult) { + return nil, ErrNilBaseExecutionResult + } + + execResult, ok := baseExecResult.(executionResultHandler) + if !ok { + return nil, ErrWrongTypeAssertion + } + + return execResult.GetMiniBlockHeadersHandlers(), nil +} + +// GetLastExecutionResultNonce returns last execution result nonce if header v3 enabled, otherwise it returns provided header nonce +func GetLastExecutionResultNonce( + header data.HeaderHandler, +) uint64 { + nonce := header.GetNonce() + + if !header.IsHeaderV3() { + return nonce + } + + lastExecutionResult, err := GetLastBaseExecutionResultHandler(header) + if err != nil { + return nonce + } + + return lastExecutionResult.GetHeaderNonce() +} + +// GetFirstExecutionResultNonce returns first execution result nonce if header v3 enabled, otherwise it returns provided header nonce. +// For header v3, it returns first execution result if there are any, otherwise it returns last execution results on the header +func GetFirstExecutionResultNonce( + header data.HeaderHandler, +) uint64 { + nonce := header.GetNonce() + + if !header.IsHeaderV3() { + return nonce + } + + if len(header.GetExecutionResultsHandlers()) > 0 { + firstExecResult := header.GetExecutionResultsHandlers()[0] + return firstExecResult.GetHeaderNonce() + } + + return GetLastExecutionResultNonce(header) +} + +// GetMiniBlockHeadersFromExecResult returns mb headers from meta header if v3, otherwise, returns mini block headers +func GetMiniBlockHeadersFromExecResult(header data.HeaderHandler) ([]data.MiniBlockHeaderHandler, error) { + if !header.IsHeaderV3() { + return header.GetMiniBlockHeaderHandlers(), nil + } + + mbHeaderHandlers := make([]data.MiniBlockHeaderHandler, 0) + for _, execResult := range header.GetExecutionResultsHandlers() { + mbHeaders, err := GetMiniBlocksHeaderHandlersFromExecResult(execResult) + if err != nil { + return nil, fmt.Errorf("%w in GetMiniBlockHeadersFromExecResult.GetMiniBlocksHeaderHandlersFromExecResult", err) + } + + mbHeaderHandlers = append(mbHeaderHandlers, mbHeaders...) + } + + return mbHeaderHandlers, nil +} + +// GetFeePayer returns the address that pays the fee for this transaction. +// For relayed v3 transactions, the fee payer is the relayer; otherwise it is the sender. +func GetFeePayer(tx data.TransactionHandler) []byte { + if check.IfNil(tx) { + return nil + } + + relayedTx, ok := tx.(data.RelayedTransactionHandler) + if ok && len(relayedTx.GetRelayerAddr()) > 0 { + return relayedTx.GetRelayerAddr() + } + + return tx.GetSndAddr() +} diff --git a/common/commonCachedData.go b/common/commonCachedData.go new file mode 100644 index 00000000000..adbf7a4c2c7 --- /dev/null +++ b/common/commonCachedData.go @@ -0,0 +1,175 @@ +package common + +import ( + "encoding/hex" + "fmt" + + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/data/outport" + "github.com/multiversx/mx-chain-core-go/marshal" + logger "github.com/multiversx/mx-chain-logger-go" + + "github.com/multiversx/mx-chain-go/errors" + "github.com/multiversx/mx-chain-go/storage" +) + +var log = logger.GetOrCreate("common") + +// GetCachedIntermediateTxs will return from cache intermediate transactions +func GetCachedIntermediateTxs(cache storage.Cacher, headerHash []byte) (map[block.Type]map[string]data.TransactionHandler, error) { + if check.IfNil(cache) { + return nil, errors.ErrNilCacher + } + + cachedIntermediateTxs, ok := cache.Get(headerHash) + if !ok { + log.Warn("intermediateTxs not found in dataPool", "hash", headerHash) + return nil, fmt.Errorf("%w for header %s", ErrMissingCachedTransactions, hex.EncodeToString(headerHash)) + } + + cachedIntermediateTxsMap, ok := cachedIntermediateTxs.(map[block.Type]map[string]data.TransactionHandler) + if !ok { + return nil, fmt.Errorf("%w for cached intermediate transaction %s", ErrWrongTypeAssertion, hex.EncodeToString(headerHash)) + } + + return cachedIntermediateTxsMap, nil +} + +// GetCachedLogs will return the cached log events from provided cache +func GetCachedLogs(cache storage.Cacher, headerHash []byte) ([]data.LogDataHandler, error) { + if check.IfNil(cache) { + return nil, errors.ErrNilCacher + } + + logsKey := PrepareLogEventsKey(headerHash) + cachedLogs, ok := cache.Get(logsKey) + if !ok { + log.Warn("logs not found in dataPool", "hash", headerHash) + return nil, fmt.Errorf("%w for header %s", ErrMissingCachedLogs, hex.EncodeToString(headerHash)) + } + cachedLogsSlice, ok := cachedLogs.([]data.LogDataHandler) + if !ok { + return nil, fmt.Errorf("%w for cached logs %s", ErrWrongTypeAssertion, hex.EncodeToString(headerHash)) + } + + return cachedLogsSlice, nil +} + +// GetCachedOrderedTxHashes wil return the cached ordered tx hashes from the provided cache +func GetCachedOrderedTxHashes(cache storage.Cacher, headerHash []byte) ([][]byte, error) { + if check.IfNil(cache) { + return nil, errors.ErrNilCacher + } + + orderedTxHashesKey := PrepareOrderedTxHashesKey(headerHash) + cachedData, ok := cache.Get(orderedTxHashesKey) + if !ok { + log.Warn("orderedTxHashes not found in dataPool", "hash", headerHash) + return nil, fmt.Errorf("%w for header %s", ErrMissingOrderedTxHashes, hex.EncodeToString(headerHash)) + } + + cachedDataSlice, ok := cachedData.([][]byte) + if !ok { + return nil, fmt.Errorf("%w for cached logs %s", ErrWrongTypeAssertion, hex.EncodeToString(headerHash)) + } + + return cachedDataSlice, nil +} + +// GetCachedUnexecutableTxHashes will return the cached unexecutable tx hashes from the provided cache +func GetCachedUnexecutableTxHashes(cache storage.Cacher, headerHash []byte) ([][]byte, error) { + if check.IfNil(cache) { + return nil, errors.ErrNilCacher + } + + unexecutableTxHashesKey := PrepareUnexecutableTxHashesKey(headerHash) + cachedData, ok := cache.Get(unexecutableTxHashesKey) + if !ok { + log.Warn("unexecutableTxHashes not found in dataPool", "hash", headerHash) + return nil, fmt.Errorf("%w for header %s", ErrMissingUnexecutableTxHash, hex.EncodeToString(headerHash)) + } + + cachedDataSlice, ok := cachedData.([][]byte) + if !ok { + return nil, fmt.Errorf("%w for cached unexecutable txs %s", ErrWrongTypeAssertion, hex.EncodeToString(headerHash)) + } + + return cachedDataSlice, nil +} + +// GetCachedMbs will return the cached miniblocks from provided cache +func GetCachedMbs(cache storage.Cacher, marshaller marshal.Marshalizer, headerHash []byte) ([]*block.MiniBlock, error) { + if check.IfNil(cache) { + return nil, errors.ErrNilCacher + } + + cachedIntraMBs, ok := cache.Get(headerHash) + if !ok { + log.Warn("intra miniblocks not found in dataPool", "hash", headerHash) + return nil, fmt.Errorf("%w for header %s", ErrMissingMiniBlock, hex.EncodeToString(headerHash)) + } + + miniBlocks, ok := cachedIntraMBs.([]*block.MiniBlock) + if !ok { + return nil, fmt.Errorf("%w for GetCachedMbs", ErrWrongTypeAssertion) + } + + return miniBlocks, nil +} + +// GetCachedBody will return the block body based from provided cache based on the execution result +func GetCachedBody(cache storage.Cacher, marshaller marshal.Marshalizer, baseExecResult data.BaseExecutionResultHandler) (*block.Body, error) { + if check.IfNil(cache) { + return nil, errors.ErrNilCacher + } + + miniBlockHeaderHandlers, err := GetMiniBlocksHeaderHandlersFromExecResult(baseExecResult) + if err != nil { + return nil, err + } + + var miniBlocks block.MiniBlockSlice + for _, miniBlockHeaderHandler := range miniBlockHeaderHandlers { + mbHash := miniBlockHeaderHandler.GetHash() + cachedMiniBlock, found := cache.Get(mbHash) + if !found { + log.Warn("mini block from execution result not cached after execution", + "mini block hash", mbHash) + return nil, ErrMissingMiniBlock + } + + cachedMiniBlockBytes := cachedMiniBlock.([]byte) + + miniBlock := &block.MiniBlock{} + err = marshaller.Unmarshal(miniBlock, cachedMiniBlockBytes) + if err != nil { + return nil, err + } + + miniBlocks = append(miniBlocks, miniBlock) + } + + return &block.Body{MiniBlocks: miniBlocks}, nil +} + +// GetCacheHeaderGasData will return the cached header gas data from the provided cache +func GetCacheHeaderGasData(cache storage.Cacher, headerHash []byte) (*outport.HeaderGasConsumption, error) { + if check.IfNil(cache) { + return nil, errors.ErrNilCacher + } + + cacheHeaderGasDataI, ok := cache.Get(PrepareHeaderGasDataKey(headerHash)) + if !ok { + log.Warn("header gas data not found in dataPool", "hash", headerHash) + return nil, fmt.Errorf("%w for header %s", ErrMissingHeaderGasData, hex.EncodeToString(headerHash)) + } + + cacheHeaderGasData, ok := cacheHeaderGasDataI.(*outport.HeaderGasConsumption) + if !ok { + return nil, fmt.Errorf("%w for GetCacheHeaderGasData", ErrWrongTypeAssertion) + } + + return cacheHeaderGasData, nil +} diff --git a/common/commonCachedData_test.go b/common/commonCachedData_test.go new file mode 100644 index 00000000000..e8697c9c2b3 --- /dev/null +++ b/common/commonCachedData_test.go @@ -0,0 +1,381 @@ +package common + +import ( + "errors" + "testing" + + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/data/outport" + "github.com/multiversx/mx-chain-core-go/data/receipt" + "github.com/multiversx/mx-chain-core-go/data/smartContractResult" + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-go/testscommon/cache" + "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" + "github.com/stretchr/testify/require" +) + +func TestGetIntermediateTxs(t *testing.T) { + t.Parallel() + + t.Run("getIntermediateTxs cannot find in cache", func(t *testing.T) { + t.Parallel() + + cacher := cache.NewCacherMock() + + headerHash := []byte("h") + + _, err := GetCachedIntermediateTxs(cacher, headerHash) + require.True(t, errors.Is(err, ErrMissingCachedTransactions)) + }) + + t.Run("getIntermediateTxs wrong type in cache should return empty maps", func(t *testing.T) { + t.Parallel() + + cacher := cache.NewCacherMock() + + headerHash := []byte("h") + cacher.Put(headerHash, []byte("wrong"), 0) + + _, err := GetCachedIntermediateTxs(cacher, headerHash) + require.True(t, errors.Is(err, ErrWrongTypeAssertion)) + }) + + t.Run("getIntermediateTxs should work", func(t *testing.T) { + t.Parallel() + + cachedIntermediateTxsMap := map[block.Type]map[string]data.TransactionHandler{} + cachedIntermediateTxsMap[block.SmartContractResultBlock] = map[string]data.TransactionHandler{ + "h1": &smartContractResult.SmartContractResult{}, + } + cachedIntermediateTxsMap[block.ReceiptBlock] = map[string]data.TransactionHandler{ + "r1": &receipt.Receipt{}, + } + + cacher := cache.NewCacherMock() + + headerHash := []byte("h") + cacher.Put(headerHash, cachedIntermediateTxsMap, 0) + + results, err := GetCachedIntermediateTxs(cacher, headerHash) + require.NoError(t, err) + require.Len(t, results[block.SmartContractResultBlock], 1) + require.Len(t, results[block.ReceiptBlock], 1) + require.Equal(t, cachedIntermediateTxsMap[block.SmartContractResultBlock], results[block.SmartContractResultBlock]) + require.Equal(t, cachedIntermediateTxsMap[block.ReceiptBlock], results[block.ReceiptBlock]) + }) + +} + +func TestGetLogs(t *testing.T) { + t.Parallel() + + t.Run("getLogs cannot find in cache", func(t *testing.T) { + t.Parallel() + + cacher := cache.NewCacherMock() + + headerHash := []byte("h") + + _, err := GetCachedLogs(cacher, headerHash) + require.True(t, errors.Is(err, ErrMissingCachedLogs)) + }) + + t.Run("getLogs wrong type in cache should return empty slice", func(t *testing.T) { + t.Parallel() + + cacher := cache.NewCacherMock() + + headerHash := []byte("h") + logsKey := PrepareLogEventsKey(headerHash) + cacher.Put(logsKey, "wrong type", 0) + + _, err := GetCachedLogs(cacher, headerHash) + require.True(t, errors.Is(err, ErrWrongTypeAssertion)) + }) + + t.Run("getLogs should work", func(t *testing.T) { + t.Parallel() + + cacher := cache.NewCacherMock() + + headerHash := []byte("h") + expectedLogs := []data.LogDataHandler{ + &transaction.LogData{ + Log: &transaction.Log{}, + TxHash: "t1", + }, + &transaction.LogData{ + Log: &transaction.Log{}, + TxHash: "t2", + }, + } + logsKey := PrepareLogEventsKey(headerHash) + + cacher.Put(logsKey, expectedLogs, 0) + + logs, err := GetCachedLogs(cacher, headerHash) + require.Nil(t, err) + require.Len(t, logs, 2) + require.Equal(t, expectedLogs, logs) + }) + +} + +func TestGetIntraMbs(t *testing.T) { + t.Parallel() + + t.Run("getIntraMbs cannot find in cache", func(t *testing.T) { + t.Parallel() + + cacher := cache.NewCacherMock() + marshaller := &marshallerMock.MarshalizerMock{} + + headerHash := []byte("h") + + _, err := GetCachedMbs(cacher, marshaller, headerHash) + require.True(t, errors.Is(err, ErrMissingMiniBlock)) + }) + + t.Run("getIntraMbs should work", func(t *testing.T) { + t.Parallel() + + cacher := cache.NewCacherMock() + marshaller := &marshallerMock.MarshalizerMock{} + + headerHash := []byte("h") + expectedMbs := []*block.MiniBlock{ + {SenderShardID: 0}, + {SenderShardID: 0}, + } + + cacher.Put(headerHash, expectedMbs, 0) + + intraMBs, err := GetCachedMbs(cacher, marshaller, headerHash) + require.Nil(t, err) + require.Equal(t, expectedMbs, intraMBs) + }) + +} + +func TestGetBody(t *testing.T) { + t.Parallel() + + t.Run("cannot get mb headers should error", func(t *testing.T) { + t.Parallel() + + executionResult := &block.BaseExecutionResult{} + + marshaller := &marshallerMock.MarshalizerMock{} + cacher := cache.NewCacherMock() + + _, err := GetCachedBody(cacher, marshaller, executionResult) + require.NotNil(t, err) + }) + + t.Run("missing miniblock should error", func(t *testing.T) { + t.Parallel() + + mb1 := &block.MiniBlock{ + SenderShardID: 1, + } + h1 := []byte("h1") + h2 := []byte("h2") + + executionResult := &block.ExecutionResult{ + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: h1, + }, + { + Hash: h2, + }, + }, + } + + marshaller := &marshallerMock.MarshalizerMock{} + mb1Bytes, _ := marshaller.Marshal(mb1) + + cacher := cache.NewCacherMock() + cacher.Put(h1, mb1Bytes, 0) + + _, err := GetCachedBody(cacher, marshaller, executionResult) + require.Equal(t, ErrMissingMiniBlock, err) + }) + + t.Run("marshaller error", func(t *testing.T) { + t.Parallel() + + h1 := []byte("h1") + h2 := []byte("h2") + + executionResult := &block.ExecutionResult{ + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: h1, + }, + { + Hash: h2, + }, + }, + } + + marshaller := &marshallerMock.MarshalizerMock{} + + cacher := cache.NewCacherMock() + cacher.Put(h1, []byte("wrong"), 0) + + _, err := GetCachedBody(cacher, marshaller, executionResult) + require.NotNil(t, err) + }) + + t.Run("getBody should work", func(t *testing.T) { + t.Parallel() + + mb1 := &block.MiniBlock{ + SenderShardID: 1, + } + mb2 := &block.MiniBlock{ + SenderShardID: 2, + } + h1 := []byte("h1") + h2 := []byte("h2") + + executionResult := &block.ExecutionResult{ + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: h1, + }, + { + Hash: h2, + }, + }, + } + + marshaller := &marshallerMock.MarshalizerMock{} + mb1Bytes, _ := marshaller.Marshal(mb1) + mb2Bytes, _ := marshaller.Marshal(mb2) + + cacher := cache.NewCacherMock() + cacher.Put(h1, mb1Bytes, 0) + cacher.Put(h2, mb2Bytes, 0) + + res, err := GetCachedBody(cacher, marshaller, executionResult) + require.Nil(t, err) + require.Equal(t, &block.Body{MiniBlocks: []*block.MiniBlock{mb1, mb2}}, res) + }) +} + +func TestGetCachedOrderedTxHashes(t *testing.T) { + t.Parallel() + + t.Run("cannot find in cache should error", func(t *testing.T) { + t.Parallel() + + cacher := cache.NewCacherMock() + + headerHash := []byte("h") + + _, err := GetCachedOrderedTxHashes(cacher, headerHash) + require.True(t, errors.Is(err, ErrMissingOrderedTxHashes)) + }) + + t.Run("wrong type in cache should error", func(t *testing.T) { + cacher := cache.NewCacherMock() + + headerHash := []byte("h") + cacher.Put(PrepareOrderedTxHashesKey(headerHash), []byte("a"), 0) + + _, err := GetCachedOrderedTxHashes(cacher, headerHash) + require.True(t, errors.Is(err, ErrWrongTypeAssertion)) + }) + + t.Run("should work", func(t *testing.T) { + cacher := cache.NewCacherMock() + + headerHash := []byte("h") + hashes := [][]byte{[]byte("a"), []byte("b"), []byte("c")} + cacher.Put(PrepareOrderedTxHashesKey(headerHash), hashes, 0) + + res, err := GetCachedOrderedTxHashes(cacher, headerHash) + require.Nil(t, err) + require.Equal(t, hashes, res) + }) +} + +func TestGetCachedUnexecutableTxHashes(t *testing.T) { + t.Parallel() + + t.Run("cannot find in cache should error", func(t *testing.T) { + t.Parallel() + + cacher := cache.NewCacherMock() + + headerHash := []byte("h") + + _, err := GetCachedUnexecutableTxHashes(cacher, headerHash) + require.True(t, errors.Is(err, ErrMissingUnexecutableTxHash)) + }) + + t.Run("wrong type in cache should error", func(t *testing.T) { + cacher := cache.NewCacherMock() + + headerHash := []byte("h") + cacher.Put(PrepareUnexecutableTxHashesKey(headerHash), []byte("a"), 0) + + _, err := GetCachedUnexecutableTxHashes(cacher, headerHash) + require.True(t, errors.Is(err, ErrWrongTypeAssertion)) + }) + + t.Run("should work", func(t *testing.T) { + cacher := cache.NewCacherMock() + + headerHash := []byte("h") + hashes := [][]byte{[]byte("a"), []byte("b"), []byte("c")} + cacher.Put(PrepareUnexecutableTxHashesKey(headerHash), hashes, 0) + + res, err := GetCachedUnexecutableTxHashes(cacher, headerHash) + require.Nil(t, err) + require.Equal(t, hashes, res) + }) + +} + +func TestGetCachedHeaderGasData(t *testing.T) { + t.Parallel() + + t.Run("cannot find in cache should error", func(t *testing.T) { + t.Parallel() + + cacher := cache.NewCacherMock() + + headerHash := []byte("h") + + _, err := GetCacheHeaderGasData(cacher, headerHash) + require.True(t, errors.Is(err, ErrMissingHeaderGasData)) + }) + + t.Run("wrong type in cache should error", func(t *testing.T) { + cacher := cache.NewCacherMock() + + headerHash := []byte("h") + cacher.Put(PrepareHeaderGasDataKey(headerHash), []byte("a"), 0) + + _, err := GetCacheHeaderGasData(cacher, headerHash) + require.True(t, errors.Is(err, ErrWrongTypeAssertion)) + }) + + t.Run("should work", func(t *testing.T) { + cacher := cache.NewCacherMock() + + headerHash := []byte("h") + headerGasData := &outport.HeaderGasConsumption{ + GasRefunded: 100, + } + cacher.Put(PrepareHeaderGasDataKey(headerHash), headerGasData, 0) + + res, err := GetCacheHeaderGasData(cacher, headerHash) + require.Nil(t, err) + require.Equal(t, headerGasData, res) + }) +} diff --git a/common/common_test.go b/common/common_test.go index 22dd024821e..289d8fb1868 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -1,20 +1,24 @@ package common_test import ( + "encoding/json" "errors" "math/big" "testing" "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/smartContractResult" "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" + commonErrors "github.com/multiversx/mx-chain-go/errors" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/chainParameters" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" - "github.com/stretchr/testify/require" ) var testFlag = core.EnableEpochFlag("test flag") @@ -259,3 +263,883 @@ func TestConsesusGroupSizeForShardAndEpoch(t *testing.T) { require.Equal(t, int(groupSize), size) }) } + +func TestGetHeaderTimestamps(t *testing.T) { + t.Parallel() + + t.Run("nil checks", func(t *testing.T) { + t.Parallel() + + header := &block.Header{ + Epoch: 2, + TimeStamp: 123, + } + + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag != common.SupernovaFlag + }, + } + + _, _, err := common.GetHeaderTimestamps(nil, enableEpochsHandler) + require.Equal(t, common.ErrNilHeaderHandler, err) + + _, _, err = common.GetHeaderTimestamps(header, nil) + require.Equal(t, commonErrors.ErrNilEnableEpochsHandler, err) + }) + + t.Run("before supernova epoch activation", func(t *testing.T) { + t.Parallel() + + header := &block.Header{ + Epoch: 2, + TimeStamp: 123, + } + + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag != common.SupernovaFlag + }, + } + + timestampSec, timestampMs, _ := common.GetHeaderTimestamps(header, enableEpochsHandler) + require.Equal(t, uint64(123), timestampSec) + require.Equal(t, uint64(123000), timestampMs) + }) + + t.Run("after supernova epoch activation", func(t *testing.T) { + t.Parallel() + + header := &block.Header{ + Epoch: 2, + TimeStamp: 1234567, // as milliseconds + } + + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag + }, + } + + timestampSec, timestampMs, _ := common.GetHeaderTimestamps(header, enableEpochsHandler) + require.Equal(t, uint64(1234), timestampSec) + require.Equal(t, uint64(1234567), timestampMs) + }) +} + +// Structures for testing prettify functions +type inner struct { + Bytes []byte `json:"bytes"` + Big *big.Int `json:"big"` + Float big.Float `json:"float"` + Rat *big.Rat `json:"rat"` + private string // unexported +} + +type testStruct struct { + InnerVal inner `json:"innerVal"` + ByteArray [4]byte `json:"byteArray"` + IntArray []int `json:"intArray"` + FloatSlice []*big.Float `json:"floatSlice"` + NilPtr *big.Int `json:"nilPtr"` +} + +func TestPrettifyStruct(t *testing.T) { + t.Parallel() + + t.Run("should return 'nil' for nil struct", func(t *testing.T) { + t.Parallel() + result, _ := common.PrettifyStruct(nil) + require.Equal(t, "nil", result) + }) + + t.Run("with simple struct type", func(t *testing.T) { + t.Parallel() + + type testStruct struct { + Field1 string + Field2 int + } + + ts := &testStruct{ + Field1: "value1", + Field2: 42, + } + + expected := `{"Field1":"value1","Field2":42}` + result, _ := common.PrettifyStruct(ts) + require.Equal(t, expected, result) + }) + + t.Run("with array of simple struct type", func(t *testing.T) { + t.Parallel() + + type testStruct struct { + Field1 string + Field2 int + } + + ts := &testStruct{ + Field1: "value1", + Field2: 42, + } + + ts1 := &testStruct{ + Field1: "value2", + Field2: 84, + } + tsArray := []*testStruct{ts, ts1} + expected := `[{"Field1":"value1","Field2":42},{"Field1":"value2","Field2":84}]` + result, _ := common.PrettifyStruct(tsArray) + + require.Equal(t, expected, result) + }) + + t.Run("with complex struct type", func(t *testing.T) { + t.Parallel() + v := testStruct{ + InnerVal: inner{ + Bytes: []byte("some-bytes"), + Big: big.NewInt(42_000_000), + Float: *big.NewFloat(123.456), + Rat: big.NewRat(355, 113), + private: "should-not-be-visible", + }, + ByteArray: [4]byte{'t', 'e', 's', 't'}, + IntArray: []int{10, 20, 30}, + FloatSlice: []*big.Float{big.NewFloat(0.1), big.NewFloat(0.2)}, + NilPtr: nil, + } + + out, err := common.PrettifyStruct(v) + require.NoError(t, err) + expected := `{"byteArray":"74657374","floatSlice":["0.1","0.2"],"innerVal":{"big":"42000000","bytes":"736f6d652d6279746573","float":"123.456","private":"\u003cunexported\u003e","rat":"355/113"},"intArray":[10,20,30],"nilPtr":null}` + require.Equal(t, expected, out) + }) + + t.Run("with minimal headers", func(t *testing.T) { + t.Parallel() + + hdr := &block.Header{ + Nonce: 2, + Round: 2, + PrevHash: []byte("prevHash"), + PrevRandSeed: []byte("prevRandSeed"), + Signature: []byte("signature"), + PubKeysBitmap: []byte("00110"), + ShardID: 0, + RootHash: []byte("rootHash"), + MiniBlockHeaders: []block.MiniBlockHeader{}, + } + + hdrv2 := &block.HeaderV2{ + Header: hdr, + ScheduledGasProvided: 0, + } + var h data.HeaderHandler + h = hdrv2 + prettified, _ := common.PrettifyStruct(h) + expected := `{"header":{"accumulatedFees":null,"blockBodyType":0,"chainID":"","developerFees":null,"epoch":0,"epochStartMetaHash":"","leaderSignature":"","metaBlockHashes":[],"miniBlockHeaders":[],"nonce":2,"peerChanges":[],"prevHash":"7072657648617368","prevRandSeed":"7072657652616e6453656564","pubKeysBitmap":"3030313130","randSeed":"","receiptsHash":"","reserved":"","rootHash":"726f6f7448617368","round":2,"shardID":0,"signature":"7369676e6174757265","softwareVersion":"","timeStamp":0,"txCount":0},"scheduledAccumulatedFees":null,"scheduledDeveloperFees":null,"scheduledGasPenalized":0,"scheduledGasProvided":0,"scheduledGasRefunded":0,"scheduledRootHash":""}` + require.Equal(t, expected, prettified) + + metaHeader := &block.MetaBlock{ + Nonce: 2, + Round: 2, + PrevHash: []byte("prevHash"), + PrevRandSeed: []byte("prevRandSeed"), + Signature: []byte("signature"), + PubKeysBitmap: []byte("00110"), + RootHash: []byte("rootHash"), + ShardInfo: []block.ShardData{ + { + ShardID: 0, + TxCount: 100, + }, + }, + } + h = metaHeader + prettified, _ = common.PrettifyStruct(h) + expected = `{"accumulatedFees":null,"accumulatedFeesInEpoch":null,"chainID":"","devFeesInEpoch":null,"developerFees":null,"epoch":0,"epochStart":{"economics":{"nodePrice":null,"prevEpochStartHash":"","prevEpochStartRound":0,"rewardsForProtocolSustainability":null,"rewardsPerBlock":null,"totalNewlyMinted":null,"totalSupply":null,"totalToDistribute":null},"lastFinalizedHeaders":[]},"leaderSignature":"","miniBlockHeaders":[],"nonce":2,"peerInfo":[],"prevHash":"7072657648617368","prevRandSeed":"7072657652616e6453656564","pubKeysBitmap":"3030313130","randSeed":"","receiptsHash":"","reserved":"","rootHash":"726f6f7448617368","round":2,"shardInfo":[{"accumulatedFees":null,"developerFees":null,"epoch":0,"headerHash":"","lastIncludedMetaNonce":0,"nonce":0,"numPendingMiniBlocks":0,"prevHash":"","prevRandSeed":"","pubKeysBitmap":"","round":0,"shardID":0,"shardMiniBlockHeaders":[],"signature":"","txCount":100}],"signature":"7369676e6174757265","softwareVersion":"","timeStamp":0,"txCount":0,"validatorStatsRootHash":""}` + require.Equal(t, expected, prettified) + }) + + t.Run("with complete headers", func(t *testing.T) { + t.Parallel() + + headerV2 := `{"header":{"nonce":481,"prevHash":"nNqMnj/cTiZYVMq2WW8bh9vhiN69D/AIIm7wLn/nm+0=","prevRandSeed":"sG4G+2bvTXI/htmsnJAxfQXd9oTZe5KNZ5W766kCFBbFxl7B9yZ7YCiTyzkO5X2T","randSeed":"ACmbkLg73NmmZ8sGHFhe3MYtdK46wbKIUt+ivJsdHJld82HIAvnzjF0ezqUOdsOW","shardID":0,"timeStamp":1752768353,"round":0,"epoch":5,"blockBodyType":0,"leaderSignature":"aqchjZKetNFlMalYh+sgxcOdTxDABP7iIz5wOo5qirDI7BnhkuSQtDXhloNNZDqD","miniBlockHeaders":[{"hash":"JZ0x61Ds1sC7788L5sd3HM4CdcCsQxImNnwDY9sseUA=","senderShardID":4294967295,"receiverShardID":0,"txCount":5,"type":255,"reserved":"IAQ="},{"hash":"bvoV+3E/L3glspLncniBWc5Y913oZf9xLVdcbCdQ2R4=","senderShardID":4294967295,"receiverShardID":4294967280,"txCount":6,"type":60,"reserved":"IAU="},{"hash":"h4DQEYG9V7cCfWwC7n3HR7JRRXyxxP3wi/1aQYwaIWM=","senderShardID":4294967295,"receiverShardID":4294967280,"txCount":5,"type":60,"reserved":"IAQ="}],"peerChanges":null,"rootHash":"1vyoepMXFURwMyBaRW5bEfPVJnaD9R1VLo/w6edvSO8=","metaBlockHashes":["aZiUegDAPYMjxFoDZifKS1OtucE82KjcBcV7JOAnKrk="],"txCount":16,"epochStartMetaHash":"aZiUegDAPYMjxFoDZifKS1OtucE82KjcBcV7JOAnKrk=","receiptsHash":"DldRwCblQ7Loqy6wYJnaodHl30d3j3eH+qtFzfEv46g=","chainID":"bG9jYWxuZXQ=","softwareVersion":"Mg==","accumulatedFees":0,"developerFees":0},"scheduledRootHash":"SZRu3iHeUgmPfL99TQapZNOcqKSWYmp5rMrAuOMbmu0=","scheduledAccumulatedFees":0,"scheduledDeveloperFees":0,"scheduledGasProvided":0,"scheduledGasPenalized":0,"scheduledGasRefunded":0}` + metablock := `{"nonce":184,"epoch":2,"round":0,"timeStamp":1752766553,"shardInfo":[{"headerHash":"N4Be23RIX4Hdb/IX8N9Rn9IVrDwNv0x/aRBG3DeZ59s=","shardMiniBlockHeaders":[{"hash":"SQnnrD2Cv9UbULqY2vrdsP9pKzVrp9lUgMaKf8N/VQs=","senderShardID":0,"receiverShardID":0,"txCount":100,"type":0}],"prevRandSeed":"AtTtjVgLLCR1vcN5lhMgKAXSQ+uGgQJQAGCIRXpRur2WgyOFWVwGsvB0XNr5tT2D","round":205,"prevHash":"5DzInuk8HiY/x21RCIAaLnmEp2pNcj3GFhjV/D0ugeo=","nonce":182,"accumulatedFees":5000000000000000,"developerFees":0,"numPendingMiniBlocks":1,"lastIncludedMetaNonce":181,"shardID":0,"txCount":100,"epoch":2}],"peerInfo":null,"leaderSignature":"MaAFUyniShBNVbL01Mf5WJOAh0ypTKcjFtQ4E+wODRWpUWjb1/icT07eeEK5n7oT","prevHash":"p5RrqnclvenWpggjZazqDuNMSh/BAKXUUZOW4Ty3R80=","prevRandSeed":"n+jWtdpAJrz1G8YyxNtn6aKuMSwrpVhwzhaHVbEsTIJe0i5N3gzl73QWxaHWjUiU","randSeed":"ek0OGMLItkHOwp/AtNtM8jup4ZKUgXw2xPpMEvARqWUo+xiMai7K0Zt5n/EKl0QL","rootHash":"SA2azL3/LsUqsfofORRsha0dXjBlshUEVJELa9uBTuQ=","validatorStatsRootHash":"YWE259eQYLgZ94BeQE5Ur9/IuGlrQj3K3NMb/FnsYus=","miniBlockHeaders":null,"receiptsHash":"DldRwCblQ7Loqy6wYJnaodHl30d3j3eH+qtFzfEv46g=","epochStart":{"lastFinalizedHeaders":null,"economics":{"prevEpochStartRound":0}},"chainID":"bG9jYWxuZXQ=","softwareVersion":"Mg==","accumulatedFees":0,"accumulatedFeesInEpoch":5050000000000000,"developerFees":0,"devFeesInEpoch":0,"txCount":100}` + + header := &block.HeaderV2{} + require.NoError(t, json.Unmarshal([]byte(headerV2), header)) + prettified, err := common.PrettifyStruct(header) + require.NoError(t, err) + t.Log("HeaderV2", prettified) + + meta := &block.MetaBlock{} + require.NoError(t, json.Unmarshal([]byte(metablock), meta)) + prettified, err = common.PrettifyStruct(meta) + require.NoError(t, err) + t.Log("MetaBlock", prettified) + + }) +} + +func TestGetLastBaseExecutionResultHandler(t *testing.T) { + t.Parallel() + + t.Run("nil header, should return error", func(t *testing.T) { + t.Parallel() + + var header data.HeaderHandler + result, err := common.GetLastBaseExecutionResultHandler(header) + require.Nil(t, result) + require.Equal(t, common.ErrNilHeaderHandler, err) + }) + t.Run("nil last execution result (wrong header), should return error", func(t *testing.T) { + t.Parallel() + + result, err := common.GetLastBaseExecutionResultHandler(&block.Header{}) + require.Nil(t, result) + require.Equal(t, common.ErrNilLastExecutionResultHandler, err) + }) + t.Run("valid LastMetaExecutionResultHandler, should return handler", func(t *testing.T) { + t.Parallel() + + baseMetaExecutionResultsHandler := &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash"), + HeaderNonce: 100, + HeaderRound: 200, + RootHash: []byte("rootHash"), + }, + } + + header := &block.MetaBlockV3{ + LastExecutionResult: &block.MetaExecutionResultInfo{ + NotarizedInRound: 201, + ExecutionResult: baseMetaExecutionResultsHandler, + }, + } + + result, err := common.GetLastBaseExecutionResultHandler(header) + require.NotNil(t, result) + require.Nil(t, err) + require.Equal(t, baseMetaExecutionResultsHandler, result) + }) + t.Run("nil internal BaseMetaExecutionResultHandler, should return error", func(t *testing.T) { + t.Parallel() + + header := &block.MetaBlockV3{ + LastExecutionResult: &block.MetaExecutionResultInfo{ + NotarizedInRound: 201, + ExecutionResult: nil, + }, + } + + result, err := common.GetLastBaseExecutionResultHandler(header) + require.Nil(t, result) + require.Equal(t, common.ErrNilBaseExecutionResult, err) + }) + t.Run("valid LastShardExecutionResultHandler, should return handler", func(t *testing.T) { + t.Parallel() + + baseExecutionResults := &block.BaseExecutionResult{ + HeaderHash: []byte("hash"), + HeaderNonce: 100, + HeaderRound: 200, + RootHash: []byte("rootHash"), + } + header := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + NotarizedInRound: 201, + ExecutionResult: baseExecutionResults, + }, + } + + result, err := common.GetLastBaseExecutionResultHandler(header) + require.NotNil(t, result) + require.Nil(t, err) + require.Equal(t, baseExecutionResults, result) + }) + + t.Run("nil base execution result, should return error", func(t *testing.T) { + t.Parallel() + + var baseExecutionResultsHandler *block.BaseExecutionResult + header := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + NotarizedInRound: 201, + ExecutionResult: baseExecutionResultsHandler, + }, + } + + result, err := common.GetLastBaseExecutionResultHandler(header) + require.Nil(t, result) + require.Equal(t, common.ErrNilBaseExecutionResult, err) + }) +} + +func TestGetMiniBlockHeaderHandlersFromExecResults(t *testing.T) { + t.Parallel() + + t.Run("should fail if nil base execution result", func(t *testing.T) { + t.Parallel() + + retExecResult, err := common.GetMiniBlocksHeaderHandlersFromExecResult(nil) + require.Equal(t, common.ErrNilBaseExecutionResult, err) + require.Nil(t, retExecResult) + }) + + t.Run("should fail if wrong type for shard", func(t *testing.T) { + t.Parallel() + + execResult := &block.BaseExecutionResult{} + + retExecResult, err := common.GetMiniBlocksHeaderHandlersFromExecResult(execResult) + require.Equal(t, common.ErrWrongTypeAssertion, err) + require.Nil(t, retExecResult) + }) + + t.Run("should work for shard", func(t *testing.T) { + t.Parallel() + + mbh1 := block.MiniBlockHeader{ + Hash: []byte("hash1"), + } + mbh2 := block.MiniBlockHeader{ + Hash: []byte("hash1"), + } + + miniBlockHeaders := []block.MiniBlockHeader{ + mbh1, + mbh2, + } + + execResult := &block.ExecutionResult{ + MiniBlockHeaders: miniBlockHeaders, + } + + expMiniBlockHandlers := []data.MiniBlockHeaderHandler{ + &mbh1, + &mbh2, + } + + retExecResult, err := common.GetMiniBlocksHeaderHandlersFromExecResult(execResult) + require.Nil(t, err) + require.Equal(t, expMiniBlockHandlers, retExecResult) + }) + + t.Run("should work for meta", func(t *testing.T) { + t.Parallel() + + mbh1 := block.MiniBlockHeader{ + Hash: []byte("hash1"), + } + mbh2 := block.MiniBlockHeader{ + Hash: []byte("hash1"), + } + + miniBlockHeaders := []block.MiniBlockHeader{ + mbh1, + mbh2, + } + + execResult := &block.MetaExecutionResult{ + MiniBlockHeaders: miniBlockHeaders, + } + + expMiniBlockHandlers := []data.MiniBlockHeaderHandler{ + &mbh1, + &mbh2, + } + + retExecResult, err := common.GetMiniBlocksHeaderHandlersFromExecResult(execResult) + require.Nil(t, err) + require.Equal(t, expMiniBlockHandlers, retExecResult) + }) +} + +func TestPrepareLogEventsKey(t *testing.T) { + t.Parallel() + + logs := common.PrepareLogEventsKey([]byte("LogsX")) + require.Equal(t, "logsLogsX", string(logs)) +} + +func TestGetMiniBlockHeadersFromExecResult(t *testing.T) { + t.Parallel() + + t.Run("meta header v1", func(t *testing.T) { + mbHeaders := []block.MiniBlockHeader{ + { + Hash: []byte("hash1"), + }, + } + metaBlock := &block.MetaBlock{ + MiniBlockHeaders: mbHeaders, + } + + res, err := common.GetMiniBlockHeadersFromExecResult(metaBlock) + require.Nil(t, err) + require.Equal(t, []data.MiniBlockHeaderHandler{&mbHeaders[0]}, res) + }) + t.Run("meta header v3", func(t *testing.T) { + metaBlock := &block.MetaBlockV3{ + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("hash1"), + }, + }, + ExecutionResults: []*block.MetaExecutionResult{ + { + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("hash2"), + }, + }, + }, + { + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("hash3"), + }, + }, + }, + }, + } + + expectedRes := []data.MiniBlockHeaderHandler{ + &metaBlock.ExecutionResults[0].MiniBlockHeaders[0], + &metaBlock.ExecutionResults[1].MiniBlockHeaders[0], + } + res, err := common.GetMiniBlockHeadersFromExecResult(metaBlock) + require.Nil(t, err) + require.Equal(t, expectedRes, res) + }) +} + +func Test_CreateLastExecutionResultFromPrevHeader(t *testing.T) { + t.Parallel() + + t.Run("nil prevHeader", func(t *testing.T) { + t.Parallel() + + lastExecutionResult, err := common.CreateLastExecutionResultFromPrevHeader(nil, []byte("prevHeaderHash")) + require.Equal(t, common.ErrNilHeaderHandler, err) + require.Nil(t, lastExecutionResult) + }) + t.Run("nil prevHeaderHash", func(t *testing.T) { + t.Parallel() + + prevHeader := createDummyPrevShardHeaderV2() + lastExecutionResult, err := common.CreateLastExecutionResultFromPrevHeader(prevHeader, nil) + require.Equal(t, common.ErrInvalidHeaderHash, err) + require.Nil(t, lastExecutionResult) + }) + t.Run("valid shard prevHeader type", func(t *testing.T) { + t.Parallel() + + prevHeaderHash := []byte("prevHeaderHash") + prevHeader := createDummyPrevShardHeaderV2() + expectedLastExecutionResult := &block.ExecutionResultInfo{ + NotarizedInRound: prevHeader.GetRound(), + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: prevHeaderHash, + HeaderNonce: prevHeader.GetNonce(), + HeaderRound: prevHeader.GetRound(), + RootHash: prevHeader.GetRootHash(), + }, + } + + lastExecutionResultHandler, err := common.CreateLastExecutionResultFromPrevHeader(prevHeader, prevHeaderHash) + lastExecutionResultInfo := lastExecutionResultHandler.(*block.ExecutionResultInfo) + require.NoError(t, err) + require.NotNil(t, lastExecutionResultInfo) + require.Equal(t, expectedLastExecutionResult, lastExecutionResultInfo) + }) + t.Run("invalid metaChain prevHeader type", func(t *testing.T) { + t.Parallel() + + prevHeaderHash := []byte("prevHeaderHash") + prevMetaHeader := createDummyInvalidMetaHeader() + lastExecutionResultHandler, err := common.CreateLastExecutionResultFromPrevHeader(prevMetaHeader, prevHeaderHash) + require.Equal(t, common.ErrWrongTypeAssertion, err) + require.Nil(t, lastExecutionResultHandler) + }) + t.Run("valid metaChain prevHeader type", func(t *testing.T) { + t.Parallel() + + prevHeaderHash := []byte("prevHeaderHash") + prevMetaHeader := createDummyPrevMetaHeader() + expectedLastExecutionResult := &block.MetaExecutionResultInfo{ + NotarizedInRound: prevMetaHeader.GetRound(), + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: prevHeaderHash, + HeaderNonce: prevMetaHeader.GetNonce(), + HeaderRound: prevMetaHeader.GetRound(), + RootHash: prevMetaHeader.GetRootHash(), + }, + ValidatorStatsRootHash: prevMetaHeader.GetValidatorStatsRootHash(), + AccumulatedFeesInEpoch: prevMetaHeader.GetAccumulatedFeesInEpoch(), + DevFeesInEpoch: prevMetaHeader.GetDevFeesInEpoch(), + }, + } + + lastExecutionResultHandler, err := common.CreateLastExecutionResultFromPrevHeader(prevMetaHeader, prevHeaderHash) + lastExecutionResultInfo, ok := lastExecutionResultHandler.(*block.MetaExecutionResultInfo) + require.True(t, ok) + require.NoError(t, err) + require.NotNil(t, lastExecutionResultInfo) + require.Equal(t, expectedLastExecutionResult, lastExecutionResultInfo) + }) +} + +func TestGetOrCreateLastExecutionResultForPrevHeader(t *testing.T) { + t.Parallel() + + t.Run("should fail if nil base execution result", func(t *testing.T) { + t.Parallel() + + header := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{}, + } + headerHash := []byte("headerHash1") + + execResult, err := common.GetOrCreateLastExecutionResultForPrevHeader(header, headerHash) + require.Equal(t, common.ErrNilBaseExecutionResult, err) + + require.Equal(t, nil, execResult) + }) + + t.Run("should work for header v3", func(t *testing.T) { + t.Parallel() + + baseExecResult := &block.BaseExecutionResult{ + HeaderNonce: 1, + HeaderHash: []byte("headerHash2"), + } + lastExecRes := &block.ExecutionResultInfo{ + ExecutionResult: baseExecResult, + } + + header := &block.HeaderV3{ + LastExecutionResult: lastExecRes, + } + headerHash := []byte("headerHash1") + + execResult, err := common.GetOrCreateLastExecutionResultForPrevHeader(header, headerHash) + require.Nil(t, err) + + require.Equal(t, baseExecResult, execResult) + }) + + t.Run("should work for header v2", func(t *testing.T) { + t.Parallel() + + headerHash := []byte("headerHash1") + + expBaseExecResult := &block.BaseExecutionResult{ + HeaderNonce: 10, + HeaderHash: headerHash, + } + + header := &block.HeaderV2{ + Header: &block.Header{ + Nonce: 10, + }, + } + + execResult, err := common.GetOrCreateLastExecutionResultForPrevHeader(header, headerHash) + require.Nil(t, err) + + require.Equal(t, expBaseExecResult, execResult) + }) + + t.Run("should work for header v1", func(t *testing.T) { + t.Parallel() + + headerHash := []byte("headerHash1") + + expBaseExecResult := &block.BaseExecutionResult{ + HeaderNonce: 10, + HeaderHash: headerHash, + } + + header := &block.Header{ + Nonce: 10, + } + + execResult, err := common.GetOrCreateLastExecutionResultForPrevHeader(header, headerHash) + require.Nil(t, err) + + require.Equal(t, expBaseExecResult, execResult) + }) +} + +func TestGetFirstExecutionResultNonce(t *testing.T) { + t.Parallel() + + t.Run("return header nonce if not header v3", func(t *testing.T) { + t.Parallel() + + header := &block.Header{ + Nonce: 2, + } + + retNonce := common.GetFirstExecutionResultNonce(header) + require.Equal(t, uint64(2), retNonce) + }) + + t.Run("return first execution results on block", func(t *testing.T) { + t.Parallel() + + lastExecRes := &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 3, + HeaderHash: []byte("headerHash2"), + }, + } + + execRes1 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 1, + }, + } + execRes2 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 2, + }, + } + execRes3 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 3, + }, + } + + header := &block.HeaderV3{ + ExecutionResults: []*block.ExecutionResult{ + execRes1, + execRes2, + execRes3, + }, + LastExecutionResult: lastExecRes, + } + + retNonce := common.GetFirstExecutionResultNonce(header) + require.Equal(t, uint64(1), retNonce) + }) + + t.Run("return from last execution result if not execution results on block", func(t *testing.T) { + t.Parallel() + + nonce := uint64(1) + baseExecResult := &block.BaseExecutionResult{ + HeaderNonce: nonce, + HeaderHash: []byte("headerHash2"), + } + lastExecRes := &block.ExecutionResultInfo{ + ExecutionResult: baseExecResult, + } + + header := &block.HeaderV3{ + LastExecutionResult: lastExecRes, + } + + retNonce := common.GetFirstExecutionResultNonce(header) + require.Equal(t, nonce, retNonce) + }) +} + +func Test_ExtractBaseExecutionResultHandler(t *testing.T) { + t.Parallel() + + t.Run("in case of nil lastExecResultsHandler should return ErrNilLastExecutionResultHandler", func(t *testing.T) { + t.Parallel() + + baseExecRes, err := common.ExtractBaseExecutionResultHandler(nil) + require.Nil(t, baseExecRes) + require.Equal(t, common.ErrNilLastExecutionResultHandler, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + expectedBaseExecResult := &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + RootHash: []byte("rootHash"), + }, + ValidatorStatsRootHash: []byte("valStatsRootHash"), + } + baseExecRes, err := common.ExtractBaseExecutionResultHandler(&block.MetaExecutionResultInfo{ + ExecutionResult: expectedBaseExecResult, + }) + require.Nil(t, err) + require.Equal(t, expectedBaseExecResult, baseExecRes) + }) + + t.Run("in case of nil ExecutionResult on MetaExecutionResultInfo should return ErrNilBaseExecutionResult", func(t *testing.T) { + t.Parallel() + + baseExecRes, err := common.ExtractBaseExecutionResultHandler(&block.MetaExecutionResultInfo{ + ExecutionResult: nil, + }) + require.Nil(t, baseExecRes) + require.Equal(t, common.ErrNilBaseExecutionResult, err) + }) + + t.Run("in case of wrong base execution result should return unsupported execution result handler type", func(t *testing.T) { + t.Parallel() + + expectedBaseExecResult := &block.BaseExecutionResult{} + baseExecRes, err := common.ExtractBaseExecutionResultHandler(&block.ExecutionResult{ + BaseExecutionResult: expectedBaseExecResult, + }) + require.ErrorContains(t, err, "unsupported execution result handler type") + require.Nil(t, baseExecRes) + }) + + t.Run("should work in case of ExecutionResultInfo type", func(t *testing.T) { + t.Parallel() + + expectedBaseExecResult := &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash"), + HeaderNonce: 10, + HeaderEpoch: 2, + } + baseExecRes, err := common.ExtractBaseExecutionResultHandler(&block.ExecutionResultInfo{ + ExecutionResult: expectedBaseExecResult, + }) + require.Nil(t, err) + require.Equal(t, expectedBaseExecResult, baseExecRes) + }) + + t.Run("should return ErrNilBaseExecutionResult in case of nil ExecutionResult on ExecutionResultInfo", func(t *testing.T) { + t.Parallel() + + _, err := common.ExtractBaseExecutionResultHandler(&block.ExecutionResultInfo{ + ExecutionResult: nil, + }) + require.Equal(t, common.ErrNilBaseExecutionResult, err) + }) +} + +func Test_GetOrCreateLastExecutionResultForPrevHeader(t *testing.T) { + t.Parallel() + + t.Run("should work in case of headerV3", func(t *testing.T) { + t.Parallel() + + baseExecResult := &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash"), + RootHash: []byte("rootHash"), + HeaderNonce: 10, + HeaderEpoch: 2, + } + prevHeader := block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: baseExecResult, + }, + } + lastExecResult, err := common.GetOrCreateLastExecutionResultForPrevHeader(&prevHeader, nil) + require.Nil(t, err) + require.Equal(t, baseExecResult, lastExecResult) + }) + + t.Run("should work in case of other header type", func(t *testing.T) { + t.Parallel() + + prevHeader := block.HeaderV2{ + Header: &block.Header{ + Nonce: 2, + RootHash: []byte("rootHash"), + }, + } + lastExecResult, err := common.GetOrCreateLastExecutionResultForPrevHeader(&prevHeader, []byte("prevHash")) + require.Nil(t, err) + require.Equal(t, []byte("rootHash"), lastExecResult.GetRootHash()) + require.Equal(t, uint64(2), lastExecResult.GetHeaderNonce()) + }) + + t.Run("propagate error in case creating last execution result for prev header fails", func(t *testing.T) { + t.Parallel() + + prevHeader := block.HeaderV2{ + Header: &block.Header{ + Nonce: 2, + RootHash: []byte("rootHash"), + }, + } + _, err := common.GetOrCreateLastExecutionResultForPrevHeader(&prevHeader, nil) + require.Equal(t, err, common.ErrInvalidHeaderHash) + }) +} + +func Test_GetLastExecutionResultNonce(t *testing.T) { + t.Parallel() + + t.Run("should work in case it is not headerV3", func(t *testing.T) { + t.Parallel() + + header := block.HeaderV2{ + Header: &block.Header{ + Nonce: 2, + }, + } + + nonce := common.GetLastExecutionResultNonce(&header) + require.Equal(t, uint64(2), nonce) + }) + + t.Run("should work in case of other header type", func(t *testing.T) { + t.Parallel() + + header := block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 2, + }, + }, + } + + nonce := common.GetLastExecutionResultNonce(&header) + require.Equal(t, uint64(2), nonce) + }) +} + +func createDummyPrevShardHeaderV2() *block.HeaderV2 { + return &block.HeaderV2{ + Header: &block.Header{ + Nonce: 1, + Round: 2, + RootHash: []byte("prevRootHash"), + }, + } +} +func createDummyInvalidMetaHeader() data.HeaderHandler { + return &block.HeaderV2{ + Header: &block.Header{ + Nonce: 1, + Round: 2, + RootHash: []byte("prevRootHash"), + ShardID: core.MetachainShardId, + }, + } +} +func createDummyPrevMetaHeader() *block.MetaBlock { + return &block.MetaBlock{ + Nonce: 4, + Round: 5, + RootHash: []byte("prevRootHash"), + ValidatorStatsRootHash: []byte("prevValidatorStatsRootHash"), + DevFeesInEpoch: big.NewInt(300), + AccumulatedFeesInEpoch: big.NewInt(400), + } +} + +func TestPrepareUnexecutableTxHashesKey(t *testing.T) { + t.Parallel() + + hash := []byte("hash") + require.Equal(t, []byte("unexecutablehash"), common.PrepareUnexecutableTxHashesKey(hash)) +} diff --git a/common/configs/antifloodConfigs.go b/common/configs/antifloodConfigs.go new file mode 100644 index 00000000000..ffe09dc58b0 --- /dev/null +++ b/common/configs/antifloodConfigs.go @@ -0,0 +1,200 @@ +package configs + +import ( + "errors" + "fmt" + "sort" + + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/process" +) + +const minBanDuration = 1 // second +const minMessages = 1 +const minTotalSize = 1 //1Byte +const maxPercentReserved = 90.0 +const minPercentReserved = 0.0 + +// ErrEmptyAntifloodConfigsByRound signals that an empty antiflood configs has been provided +var ErrEmptyAntifloodConfigsByRound = errors.New("empty antiflood configs") + +type antifloodConfigs struct { + orderedConfigsByRound []config.AntifloodConfigByRound + isEnabled bool + roundNotifier process.RoundNotifier +} + +// NewAntifloodConfigsHandler creates a new instance of antiflood configs handler +func NewAntifloodConfigsHandler( + antifoodConfig config.AntifloodConfig, + roundNotifier process.RoundNotifier, +) (*antifloodConfigs, error) { + if check.IfNil(roundNotifier) { + return nil, process.ErrNilRoundNotifier + } + err := checkAnifloodConfigsByRound(antifoodConfig.ConfigsByRound) + if err != nil { + return nil, err + } + + ac := &antifloodConfigs{ + orderedConfigsByRound: make([]config.AntifloodConfigByRound, len(antifoodConfig.ConfigsByRound)), + roundNotifier: roundNotifier, + isEnabled: antifoodConfig.Enabled, + } + + copy(ac.orderedConfigsByRound, antifoodConfig.ConfigsByRound) + sort.SliceStable(ac.orderedConfigsByRound, func(i, j int) bool { + return ac.orderedConfigsByRound[i].Round < ac.orderedConfigsByRound[j].Round + }) + + return ac, nil +} + +func checkAnifloodConfigsByRound(configs []config.AntifloodConfigByRound) error { + if len(configs) == 0 { + return ErrEmptyAntifloodConfigsByRound + } + + // check for duplicated configs + seen := make(map[uint64]struct{}) + for _, cfg := range configs { + _, exists := seen[cfg.Round] + if exists { + return ErrDuplicatedRoundConfig + } + seen[cfg.Round] = struct{}{} + + err := checkAntifloodConfig(cfg) + if err != nil { + return err + } + } + + _, exists := seen[0] + if !exists { + return ErrMissingRoundZeroConfig + } + + return nil +} + +func checkAntifloodConfig(conf config.AntifloodConfigByRound) error { + err := checkFloodPreventerConfig(conf.FastReacting) + if err != nil { + return err + } + + err = checkFloodPreventerConfig(conf.SlowReacting) + if err != nil { + return err + } + + err = checkFloodPreventerConfig(conf.OutOfSpecs) + if err != nil { + return err + } + + return nil +} + +func checkFloodPreventerConfig(conf config.FloodPreventerConfig) error { + if conf.BlackList.ThresholdNumMessagesPerInterval == 0 { + return fmt.Errorf("%w, thresholdNumReceivedFlood == 0", process.ErrInvalidValue) + } + if conf.BlackList.ThresholdSizePerInterval == 0 { + return fmt.Errorf("%w, thresholdSizeReceivedFlood == 0", process.ErrInvalidValue) + } + if conf.BlackList.PeerBanDurationInSeconds < minBanDuration { + return fmt.Errorf("%w for peerBanDurationInSeconds", process.ErrInvalidValue) + } + if conf.BlackList.NumFloodingRounds == 0 { + return fmt.Errorf("%w, numFloodingRounds", process.ErrInvalidValue) + } + + if conf.PeerMaxInput.BaseMessagesPerInterval < minMessages { + return fmt.Errorf("%w, maxMessagesPerPeer: provided %d, minimum %d", + process.ErrInvalidValue, + conf.PeerMaxInput.BaseMessagesPerInterval, + minMessages, + ) + } + if conf.PeerMaxInput.TotalSizePerInterval < minTotalSize { + return fmt.Errorf("%w, maxTotalSizePerPeer: provided %d, minimum %d", + process.ErrInvalidValue, + conf.PeerMaxInput.TotalSizePerInterval, + minTotalSize, + ) + } + if conf.ReservedPercent > maxPercentReserved { + return fmt.Errorf("%w, percentReserved: provided %0.3f, maximum %0.3f", + process.ErrInvalidValue, + conf.ReservedPercent, + maxPercentReserved, + ) + } + if conf.ReservedPercent < minPercentReserved { + return fmt.Errorf("%w, percentReserved: provided %0.3f, minimum %0.3f", + process.ErrInvalidValue, + conf.ReservedPercent, + minPercentReserved, + ) + } + if conf.PeerMaxInput.IncreaseFactor.Factor < 0 { + return fmt.Errorf("%w, increaseFactor is negative: provided %0.3f", + process.ErrInvalidValue, + conf.PeerMaxInput.IncreaseFactor.Factor, + ) + } + + return nil +} + +// IsEnabled returns true if antiflood is enabled +func (ac *antifloodConfigs) IsEnabled() bool { + return ac.isEnabled +} + +// GetCurrentConfig returns antiflood config based on the current round +func (ac *antifloodConfigs) GetCurrentConfig() config.AntifloodConfigByRound { + return ac.getCurrentConfig() +} + +func (ac *antifloodConfigs) getCurrentConfig() config.AntifloodConfigByRound { + currentRound := ac.roundNotifier.CurrentRound() + + for i := len(ac.orderedConfigsByRound) - 1; i >= 0; i-- { + if ac.orderedConfigsByRound[i].Round <= currentRound { + return ac.orderedConfigsByRound[i] + } + } + + // this should not happen, there is already a check in the constructor for missing config + return config.AntifloodConfigByRound{} +} + +// GetFloodPreventerConfigByType returns flood preventer config by type +func (ac *antifloodConfigs) GetFloodPreventerConfigByType(configType common.FloodPreventerType) config.FloodPreventerConfig { + currentConfig := ac.getCurrentConfig() + + switch configType { + case common.FastReacting: + return currentConfig.FastReacting + case common.SlowReacting: + return currentConfig.SlowReacting + case common.OutOfSpecs: + return currentConfig.OutOfSpecs + case common.Output: + return currentConfig.PeerMaxOutput + default: + // this case should not happen + return config.FloodPreventerConfig{} + } +} + +// IsInterfaceNil checks if the instance is nil +func (ac *antifloodConfigs) IsInterfaceNil() bool { + return ac == nil +} diff --git a/common/configs/antifloodConfigs_test.go b/common/configs/antifloodConfigs_test.go new file mode 100644 index 00000000000..4b038bf18fe --- /dev/null +++ b/common/configs/antifloodConfigs_test.go @@ -0,0 +1,246 @@ +package configs_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/common/configs" + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" + "github.com/stretchr/testify/require" +) + +func getAntifloodConfigsByRound() []config.AntifloodConfigByRound { + defaultAntifloodConfig := testscommon.GetDefaultAntifloodConfig() + return defaultAntifloodConfig.ConfigsByRound +} + +func TestNewAntifloodConfigsHandler(t *testing.T) { + t.Parallel() + + validConfig := config.AntifloodConfig{ + Enabled: true, + ConfigsByRound: getAntifloodConfigsByRound(), + } + + t.Run("should return error for empty configs by round", func(t *testing.T) { + t.Parallel() + + afConf := validConfig + afConf.ConfigsByRound = nil + + handler, err := configs.NewAntifloodConfigsHandler(afConf, &epochNotifier.RoundNotifierStub{}) + require.Nil(t, handler) + require.Equal(t, configs.ErrEmptyAntifloodConfigsByRound, err) + }) + + t.Run("should return error for duplicated round config", func(t *testing.T) { + t.Parallel() + + afConf := validConfig + afConf.ConfigsByRound = append(afConf.ConfigsByRound, afConf.ConfigsByRound[0]) + + handler, err := configs.NewAntifloodConfigsHandler(afConf, &epochNotifier.RoundNotifierStub{}) + require.Nil(t, handler) + require.Equal(t, configs.ErrDuplicatedRoundConfig, err) + }) + + t.Run("should return error for missing round zero config", func(t *testing.T) { + t.Parallel() + + afConf := validConfig + afConf.ConfigsByRound = []config.AntifloodConfigByRound{ + getAntifloodConfigsByRound()[1], + } + + handler, err := configs.NewAntifloodConfigsHandler(afConf, &epochNotifier.RoundNotifierStub{}) + require.Nil(t, handler) + require.Equal(t, configs.ErrMissingRoundZeroConfig, err) + }) + + t.Run("should return error for invalid flood preventer config", func(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + mutate func(*config.FloodPreventerConfig) + expectedErr string + }{ + { + name: "zero ThresholdNumMessagesPerInterval", + mutate: func(c *config.FloodPreventerConfig) { + c.BlackList.ThresholdNumMessagesPerInterval = 0 + }, + expectedErr: "thresholdNumReceivedFlood == 0", + }, + { + name: "zero ThresholdSizePerInterval", + mutate: func(c *config.FloodPreventerConfig) { + c.BlackList.ThresholdSizePerInterval = 0 + }, + expectedErr: "thresholdSizeReceivedFlood == 0", + }, + { + name: "invalid PeerBanDurationInSeconds", + mutate: func(c *config.FloodPreventerConfig) { + c.BlackList.PeerBanDurationInSeconds = 0 + }, + expectedErr: "for peerBanDurationInSeconds", + }, + { + name: "zero NumFloodingRounds", + mutate: func(c *config.FloodPreventerConfig) { + c.BlackList.NumFloodingRounds = 0 + }, + expectedErr: "numFloodingRounds", + }, + { + name: "invalid BaseMessagesPerInterval", + mutate: func(c *config.FloodPreventerConfig) { + c.PeerMaxInput.BaseMessagesPerInterval = 0 + }, + expectedErr: "maxMessagesPerPeer", + }, + { + name: "invalid TotalSizePerInterval", + mutate: func(c *config.FloodPreventerConfig) { + c.PeerMaxInput.TotalSizePerInterval = 0 + }, + expectedErr: "maxTotalSizePerPeer", + }, + { + name: "ReservedPercent greater than max", + mutate: func(c *config.FloodPreventerConfig) { + c.ReservedPercent = 91.0 + }, + expectedErr: "percentReserved", + }, + { + name: "ReservedPercent less than min", + mutate: func(c *config.FloodPreventerConfig) { + c.ReservedPercent = -1.0 + }, + expectedErr: "percentReserved", + }, + { + name: "IncreaseFactor negative", + mutate: func(c *config.FloodPreventerConfig) { + c.PeerMaxInput.IncreaseFactor.Factor = -0.5 + }, + expectedErr: "increaseFactor is negative", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + afConf := config.AntifloodConfig{ + ConfigsByRound: getAntifloodConfigsByRound(), + } + + // Mutate FastReacting config of the first round + tc.mutate(&afConf.ConfigsByRound[0].FastReacting) + + handler, err := configs.NewAntifloodConfigsHandler(afConf, &epochNotifier.RoundNotifierStub{}) + require.Nil(t, handler) + require.ErrorIs(t, err, process.ErrInvalidValue) + require.True(t, strings.Contains(err.Error(), tc.expectedErr), fmt.Sprintf("expected error to contain '%s', got '%s'", tc.expectedErr, err.Error())) + }) + } + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + afConf := validConfig + + handler, err := configs.NewAntifloodConfigsHandler(afConf, &epochNotifier.RoundNotifierStub{}) + require.NotNil(t, handler) + require.NoError(t, err) + require.False(t, handler.IsInterfaceNil()) + + require.True(t, handler.IsEnabled()) + }) +} + +func TestAntifloodConfigs_GetCurrentConfig(t *testing.T) { + t.Parallel() + + t.Run("first config", func(t *testing.T) { + t.Parallel() + + afConf := config.AntifloodConfig{ + Enabled: true, + ConfigsByRound: getAntifloodConfigsByRound(), + } + + roundNotifier := &epochNotifier.RoundNotifierStub{ + CurrentRoundCalled: func() uint64 { + return 60 + }, + } + handler, _ := configs.NewAntifloodConfigsHandler(afConf, roundNotifier) + + currentConfig := handler.GetCurrentConfig() + + // check some configs vars + require.Equal(t, uint64(0), currentConfig.Round) + require.Equal(t, float32(50), currentConfig.FastReacting.ReservedPercent) + }) + + t.Run("latest config", func(t *testing.T) { + t.Parallel() + + afConf := config.AntifloodConfig{ + Enabled: true, + ConfigsByRound: getAntifloodConfigsByRound(), + } + + roundNotifier := &epochNotifier.RoundNotifierStub{ + CurrentRoundCalled: func() uint64 { + return 120 + }, + } + handler, _ := configs.NewAntifloodConfigsHandler(afConf, roundNotifier) + + currentConfig := handler.GetCurrentConfig() + + // check some configs vars + require.Equal(t, uint64(100), currentConfig.Round) + require.Equal(t, float32(60), currentConfig.FastReacting.ReservedPercent) + }) +} + +func TestAntifloodConfigs_GetFloodPreventerConfigByType(t *testing.T) { + t.Parallel() + + conf := config.AntifloodConfig{ + Enabled: true, + ConfigsByRound: getAntifloodConfigsByRound(), + } + + roundNotifier := &epochNotifier.RoundNotifierStub{ + CurrentRoundCalled: func() uint64 { + return 120 + }, + } + handler, _ := configs.NewAntifloodConfigsHandler(conf, roundNotifier) + + fastReacting := handler.GetFloodPreventerConfigByType(common.FastReacting) + require.Equal(t, conf.ConfigsByRound[1].FastReacting, fastReacting) + + slowReacting := handler.GetFloodPreventerConfigByType(common.SlowReacting) + require.Equal(t, conf.ConfigsByRound[1].SlowReacting, slowReacting) + + outOfSpecs := handler.GetFloodPreventerConfigByType(common.OutOfSpecs) + require.Equal(t, conf.ConfigsByRound[1].OutOfSpecs, outOfSpecs) + + peerOutput := handler.GetFloodPreventerConfigByType(common.Output) + require.Equal(t, conf.ConfigsByRound[1].PeerMaxOutput, peerOutput) + + other := handler.GetFloodPreventerConfigByType(common.FloodPreventerType("other")) + require.Equal(t, config.FloodPreventerConfig{}, other) // empty config +} diff --git a/common/configs/commonConfigs.go b/common/configs/commonConfigs.go new file mode 100644 index 00000000000..2fb04c473bb --- /dev/null +++ b/common/configs/commonConfigs.go @@ -0,0 +1,190 @@ +package configs + +import ( + "errors" + "sort" + + "github.com/multiversx/mx-chain-go/config" +) + +const ( + defaultGracePeriodRounds = 25 + defaultExtraDelayForRequestBlockInfoInMs = 3000 + defaultMaxRoundsWithoutCommittedStartInEpochBlock = 50 + defaultNumRoundsToWaitBeforeSignalingChronologyStuck = 10 +) + +// ErrEmptyCommonConfigsByEpoch signals that an empty common configs by epoch has been provided +var ErrEmptyCommonConfigsByEpoch = errors.New("empty common configs by epoch") + +// ErrEmptyCommonConfigsByRound signals that an empty common configs by round has been provided +var ErrEmptyCommonConfigsByRound = errors.New("empty common configs by round") + +type commonConfigs struct { + orderedEpochStartConfigByEpoch []config.EpochStartConfigByEpoch + orderedEpochStartConfigByRound []config.EpochStartConfigByRound + orderedConsensusConfigByEpoch []config.ConsensusConfigByEpoch +} + +// NewCommonConfigsHandler creates a new process configs by epoch component +func NewCommonConfigsHandler( + configsByEpoch []config.EpochStartConfigByEpoch, + configsByRound []config.EpochStartConfigByRound, + consensusConfigByEpoch []config.ConsensusConfigByEpoch, +) (*commonConfigs, error) { + err := checkCommonConfigsByEpoch(configsByEpoch) + if err != nil { + return nil, err + } + err = checkConsensusConfigsByEpoch(consensusConfigByEpoch) + if err != nil { + return nil, err + } + + err = checkCommonConfigsByRound(configsByRound) + if err != nil { + return nil, err + } + + cc := &commonConfigs{ + orderedEpochStartConfigByEpoch: make([]config.EpochStartConfigByEpoch, len(configsByEpoch)), + orderedEpochStartConfigByRound: make([]config.EpochStartConfigByRound, len(configsByRound)), + orderedConsensusConfigByEpoch: make([]config.ConsensusConfigByEpoch, len(consensusConfigByEpoch)), + } + + // sort the config values in ascending order + copy(cc.orderedEpochStartConfigByEpoch, configsByEpoch) + sort.SliceStable(cc.orderedEpochStartConfigByEpoch, func(i, j int) bool { + return cc.orderedEpochStartConfigByEpoch[i].EnableEpoch < cc.orderedEpochStartConfigByEpoch[j].EnableEpoch + }) + + copy(cc.orderedEpochStartConfigByRound, configsByRound) + sort.SliceStable(cc.orderedEpochStartConfigByRound, func(i, j int) bool { + return cc.orderedEpochStartConfigByRound[i].EnableRound < cc.orderedEpochStartConfigByRound[j].EnableRound + }) + + copy(cc.orderedConsensusConfigByEpoch, consensusConfigByEpoch) + sort.SliceStable(cc.orderedConsensusConfigByEpoch, func(i, j int) bool { + return cc.orderedConsensusConfigByEpoch[i].EnableEpoch < cc.orderedConsensusConfigByEpoch[j].EnableEpoch + }) + + return cc, nil +} + +func checkCommonConfigsByEpoch(configsByEpoch []config.EpochStartConfigByEpoch) error { + if len(configsByEpoch) == 0 { + return ErrEmptyCommonConfigsByEpoch + } + + // check for duplicated configs + seen := make(map[uint32]struct{}) + for _, cfg := range configsByEpoch { + _, exists := seen[cfg.EnableEpoch] + if exists { + return ErrDuplicatedEpochConfig + } + seen[cfg.EnableEpoch] = struct{}{} + } + + _, exists := seen[0] + if !exists { + return ErrMissingEpochZeroConfig + } + + return nil +} + +func checkCommonConfigsByRound(configsByRound []config.EpochStartConfigByRound) error { + if len(configsByRound) == 0 { + return ErrEmptyCommonConfigsByRound + } + + // check for duplicated configs + seen := make(map[uint64]struct{}) + for _, cfg := range configsByRound { + _, exists := seen[cfg.EnableRound] + if exists { + return ErrDuplicatedRoundConfig + } + seen[cfg.EnableRound] = struct{}{} + } + + _, exists := seen[0] + if !exists { + return ErrMissingRoundZeroConfig + } + + return nil +} + +func checkConsensusConfigsByEpoch(configsByEpoch []config.ConsensusConfigByEpoch) error { + if len(configsByEpoch) == 0 { + return ErrEmptyCommonConfigsByEpoch + } + + // check for duplicated configs + seen := make(map[uint32]struct{}) + for _, cfg := range configsByEpoch { + _, exists := seen[cfg.EnableEpoch] + if exists { + return ErrDuplicatedEpochConfig + } + seen[cfg.EnableEpoch] = struct{}{} + } + + _, exists := seen[0] + if !exists { + return ErrMissingEpochZeroConfig + } + + return nil +} + +// GetGracePeriodRoundsByEpoch returns the grace period rounds by epoch +func (cc *commonConfigs) GetGracePeriodRoundsByEpoch(epoch uint32) uint32 { + for i := len(cc.orderedEpochStartConfigByEpoch) - 1; i >= 0; i-- { + if cc.orderedEpochStartConfigByEpoch[i].EnableEpoch <= epoch { + return cc.orderedEpochStartConfigByEpoch[i].GracePeriodRounds + } + } + + return defaultGracePeriodRounds // this should not happen +} + +// GetExtraDelayForRequestBlockInfoInMs returns the extra delay for request block info by epoch +func (cc *commonConfigs) GetExtraDelayForRequestBlockInfoInMs(epoch uint32) uint32 { + for i := len(cc.orderedEpochStartConfigByEpoch) - 1; i >= 0; i-- { + if cc.orderedEpochStartConfigByEpoch[i].EnableEpoch <= epoch { + return cc.orderedEpochStartConfigByEpoch[i].ExtraDelayForRequestBlockInfoInMilliseconds + } + } + + return defaultExtraDelayForRequestBlockInfoInMs // this should not happen +} + +// GetMaxRoundsWithoutCommittedStartInEpochBlockInRound returns max rounds without commited start in epoch block +func (cc *commonConfigs) GetMaxRoundsWithoutCommittedStartInEpochBlockInRound(round uint64) uint32 { + for i := len(cc.orderedEpochStartConfigByRound) - 1; i >= 0; i-- { + if cc.orderedEpochStartConfigByRound[i].EnableRound <= round { + return cc.orderedEpochStartConfigByRound[i].MaxRoundsWithoutCommittedStartInEpochBlock + } + } + + return defaultMaxRoundsWithoutCommittedStartInEpochBlock // this should not happen +} + +// GetNumRoundsToWaitBeforeSignalingChronologyStuck returns number of rounds to wait before signaling chronology stuck +func (cc *commonConfigs) GetNumRoundsToWaitBeforeSignalingChronologyStuck(epoch uint32) uint32 { + for i := len(cc.orderedConsensusConfigByEpoch) - 1; i >= 0; i-- { + if cc.orderedConsensusConfigByEpoch[i].EnableEpoch <= epoch { + return cc.orderedConsensusConfigByEpoch[i].NumRoundsToWaitBeforeSignalingChronologyStuck + } + } + + return defaultNumRoundsToWaitBeforeSignalingChronologyStuck // this should not happen +} + +// IsInterfaceNil checks if the instance is nil +func (cc *commonConfigs) IsInterfaceNil() bool { + return cc == nil +} diff --git a/common/configs/commonConfigs_test.go b/common/configs/commonConfigs_test.go new file mode 100644 index 00000000000..5d86baf3599 --- /dev/null +++ b/common/configs/commonConfigs_test.go @@ -0,0 +1,136 @@ +package configs_test + +import ( + "testing" + + "github.com/multiversx/mx-chain-go/common/configs" + "github.com/multiversx/mx-chain-go/config" + "github.com/stretchr/testify/require" +) + +func TestNewCommonConfigsHandler(t *testing.T) { + t.Parallel() + + t.Run("should return error for empty config by epoch", func(t *testing.T) { + t.Parallel() + + pce, err := configs.NewCommonConfigsHandler(nil, []config.EpochStartConfigByRound{}, []config.ConsensusConfigByEpoch{}) + require.Nil(t, pce) + require.Equal(t, configs.ErrEmptyCommonConfigsByEpoch, err) + }) + + t.Run("should return error for empty config by round", func(t *testing.T) { + t.Parallel() + + pce, err := configs.NewCommonConfigsHandler([]config.EpochStartConfigByEpoch{{EnableEpoch: 0}}, nil, []config.ConsensusConfigByEpoch{{EnableEpoch: 0}}) + require.Nil(t, pce) + require.Equal(t, configs.ErrEmptyCommonConfigsByRound, err) + }) + + t.Run("should return error for duplicated epoch configs", func(t *testing.T) { + t.Parallel() + + conf := []config.EpochStartConfigByEpoch{ + {EnableEpoch: 0, GracePeriodRounds: 1}, + {EnableEpoch: 0, GracePeriodRounds: 2}, + } + pce, err := configs.NewCommonConfigsHandler(conf, []config.EpochStartConfigByRound{}, []config.ConsensusConfigByEpoch{}) + require.Nil(t, pce) + require.Equal(t, configs.ErrDuplicatedEpochConfig, err) + }) + + t.Run("should return error for missing epoch 0 config", func(t *testing.T) { + t.Parallel() + + conf := []config.EpochStartConfigByEpoch{ + {EnableEpoch: 1, GracePeriodRounds: 1}, + {EnableEpoch: 2, GracePeriodRounds: 2}, + } + pce, err := configs.NewCommonConfigsHandler(conf, []config.EpochStartConfigByRound{}, []config.ConsensusConfigByEpoch{}) + require.Nil(t, pce) + require.Equal(t, configs.ErrMissingEpochZeroConfig, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + conf := []config.EpochStartConfigByEpoch{ + {EnableEpoch: 0, GracePeriodRounds: 0}, + {EnableEpoch: 2, GracePeriodRounds: 2}, + {EnableEpoch: 1, GracePeriodRounds: 1}, + } + confByRound := []config.EpochStartConfigByRound{ + {EnableRound: 0, MaxRoundsWithoutCommittedStartInEpochBlock: 10}, + {EnableRound: 1, MaxRoundsWithoutCommittedStartInEpochBlock: 11}, + } + consensusConf := []config.ConsensusConfigByEpoch{ + {EnableEpoch: 0, NumRoundsToWaitBeforeSignalingChronologyStuck: 10}, + {EnableEpoch: 1, NumRoundsToWaitBeforeSignalingChronologyStuck: 11}, + } + + pce, err := configs.NewCommonConfigsHandler(conf, confByRound, consensusConf) + require.NotNil(t, pce) + require.NoError(t, err) + require.False(t, pce.IsInterfaceNil()) + + require.Equal(t, uint32(0), pce.GetOrderedEpochStartConfigByEpoch(0).GracePeriodRounds) + require.Equal(t, uint32(1), pce.GetOrderedEpochStartConfigByEpoch(1).GracePeriodRounds) + require.Equal(t, uint32(2), pce.GetOrderedEpochStartConfigByEpoch(2).GracePeriodRounds) + }) +} + +func TestCommonConfigsByEpoch_Getters(t *testing.T) { + t.Parallel() + + conf := []config.EpochStartConfigByEpoch{ + {EnableEpoch: 0, GracePeriodRounds: 10, ExtraDelayForRequestBlockInfoInMilliseconds: 20}, + {EnableEpoch: 1, GracePeriodRounds: 11, ExtraDelayForRequestBlockInfoInMilliseconds: 21}, + {EnableEpoch: 2, GracePeriodRounds: 12, ExtraDelayForRequestBlockInfoInMilliseconds: 22}, + } + + confByRound := []config.EpochStartConfigByRound{ + {EnableRound: 0, MaxRoundsWithoutCommittedStartInEpochBlock: 30}, + {EnableRound: 1, MaxRoundsWithoutCommittedStartInEpochBlock: 31}, + } + + consensusConf := []config.ConsensusConfigByEpoch{ + {EnableEpoch: 0, NumRoundsToWaitBeforeSignalingChronologyStuck: 10}, + {EnableEpoch: 1, NumRoundsToWaitBeforeSignalingChronologyStuck: 11}, + } + + t.Run("get grace period rounds by epoch", func(t *testing.T) { + t.Parallel() + + cc, _ := configs.NewCommonConfigsHandler(conf, confByRound, consensusConf) + + gracePeriodRounds := cc.GetGracePeriodRoundsByEpoch(0) + require.Equal(t, uint32(10), gracePeriodRounds) + + gracePeriodRounds = cc.GetGracePeriodRoundsByEpoch(1) + require.Equal(t, uint32(11), gracePeriodRounds) + }) + + t.Run("get extra delay for request block info", func(t *testing.T) { + t.Parallel() + + cc, _ := configs.NewCommonConfigsHandler(conf, confByRound, consensusConf) + + extraDelayForRequests := cc.GetExtraDelayForRequestBlockInfoInMs(0) + require.Equal(t, uint32(20), extraDelayForRequests) + + extraDelayForRequests = cc.GetExtraDelayForRequestBlockInfoInMs(1) + require.Equal(t, uint32(21), extraDelayForRequests) + }) + + t.Run("get max rounds without commited start in epoch block by roud", func(t *testing.T) { + t.Parallel() + + cc, _ := configs.NewCommonConfigsHandler(conf, confByRound, consensusConf) + + maxRoundsWithoutCommitedStartInEpochBlock := cc.GetMaxRoundsWithoutCommittedStartInEpochBlockInRound(0) + require.Equal(t, uint32(30), maxRoundsWithoutCommitedStartInEpochBlock) + + maxRoundsWithoutCommitedStartInEpochBlock = cc.GetMaxRoundsWithoutCommittedStartInEpochBlockInRound(1) + require.Equal(t, uint32(31), maxRoundsWithoutCommitedStartInEpochBlock) + }) +} diff --git a/common/configs/consts.go b/common/configs/consts.go new file mode 100644 index 00000000000..69d11040f37 --- /dev/null +++ b/common/configs/consts.go @@ -0,0 +1,22 @@ +package configs + +const ( + minRoundsToKeepUnprocessedData = uint64(1) + minBlockProcessingTimeMs = uint32(1) +) + +const ( + defaultMaxMetaNoncesBehind = 15 + defaultMaxMetaNoncesBehindForGlobalStuck = 30 + defaultMaxShardNoncesBehind = 15 + defaultMaxRoundsWithoutNewBlockReceived = 10 + defaultMaxRoundsWithoutCommittedBlock = 10 + defaultRoundModulusTriggerWhenSyncIsStuck = 20 + defaultMaxSyncWithErrorsAllowed = 20 + defaultMaxRoundsToKeepUnprocessedMiniBlocks = 3000 + defaultMaxRoundsToKeepUnprocessedTransactions = 3000 + defaultMaxConsecutiveRoundsOfRatingDecrease = 600 + defaultMaxRoundsOfInactivityAccepted = 3 + defaultMaxBlockProcessingTimeMs = 100 + defaultNumHeadersToRequestInAdvance = 10 +) diff --git a/common/configs/dto/dto.go b/common/configs/dto/dto.go new file mode 100644 index 00000000000..137b2ed3148 --- /dev/null +++ b/common/configs/dto/dto.go @@ -0,0 +1,17 @@ +package dto + +// ConfigVariable defines a config variable name +type ConfigVariable string + +const ( + // NumFloodingRoundsFastReacting defines variable name for NumFloodingRoundsFastReacting + NumFloodingRoundsFastReacting ConfigVariable = "NumFloodingRoundsFastReacting" + // NumFloodingRoundsSlowReacting defines variable name for NumFloodingRoundsSlowReacting + NumFloodingRoundsSlowReacting ConfigVariable = "NumFloodingRoundsSlowReacting" + // NumFloodingRoundsOutOfSpecs defines variable name for NumFloodingRoundsOutOfSpecs + NumFloodingRoundsOutOfSpecs ConfigVariable = "NumFloodingRoundsOutOfSpecs" + // MaxConsecutiveRoundsOfRatingDecrease defines variable name for MaxConsecutiveRoundsOfRatingDecrease + MaxConsecutiveRoundsOfRatingDecrease ConfigVariable = "MaxConsecutiveRoundsOfRatingDecrease" + // MaxRoundsOfInactivityAccepted defines variable name for MaxRoundsOfInactivityAccepted + MaxRoundsOfInactivityAccepted ConfigVariable = "MaxRoundsOfInactivityAccepted" +) diff --git a/common/configs/errors.go b/common/configs/errors.go new file mode 100644 index 00000000000..f3fb1c31040 --- /dev/null +++ b/common/configs/errors.go @@ -0,0 +1,21 @@ +package configs + +import "errors" + +// ErrEmptyProcessConfigsByEpoch signals that an empty process configs by epoch has been provided +var ErrEmptyProcessConfigsByEpoch = errors.New("empty process configs by epoch") + +// ErrEmptyProcessConfigsByRound signals that an empty process configs by round has been provided +var ErrEmptyProcessConfigsByRound = errors.New("empty process configs by round") + +// ErrDuplicatedEpochConfig signals that a duplicated config section has been provided +var ErrDuplicatedEpochConfig = errors.New("duplicated epoch config") + +// ErrDuplicatedRoundConfig signals that a duplicated config section has been provided +var ErrDuplicatedRoundConfig = errors.New("duplicated round config") + +// ErrMissingEpochZeroConfig signals that epoch zero configuration is missing +var ErrMissingEpochZeroConfig = errors.New("missing configuration for epoch 0") + +// ErrMissingRoundZeroConfig signals that base round configuration is missing +var ErrMissingRoundZeroConfig = errors.New("missing base configuration for round") diff --git a/common/configs/export_test.go b/common/configs/export_test.go new file mode 100644 index 00000000000..e38bd700758 --- /dev/null +++ b/common/configs/export_test.go @@ -0,0 +1,21 @@ +package configs + +import "github.com/multiversx/mx-chain-go/config" + +// GetOrderedConfigsByEpoch - +func (pce *processConfigsByEpoch) GetOrderedConfigsByEpoch(epoch uint32) config.ProcessConfigByEpoch { + if len(pce.orderedConfigByEpoch) == 0 { + return config.ProcessConfigByEpoch{} + } + + return pce.orderedConfigByEpoch[epoch] +} + +// GetOrderedEpochStartConfigByEpoch - +func (cc *commonConfigs) GetOrderedEpochStartConfigByEpoch(epoch uint32) config.EpochStartConfigByEpoch { + if len(cc.orderedEpochStartConfigByEpoch) == 0 { + return config.EpochStartConfigByEpoch{} + } + + return cc.orderedEpochStartConfigByEpoch[epoch] +} diff --git a/common/configs/processConfigs.go b/common/configs/processConfigs.go new file mode 100644 index 00000000000..cdc20eaae85 --- /dev/null +++ b/common/configs/processConfigs.go @@ -0,0 +1,316 @@ +package configs + +import ( + "fmt" + "sort" + "time" + + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/common/configs/dto" + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/process" + logger "github.com/multiversx/mx-chain-logger-go" +) + +var log = logger.GetOrCreate("processConfigs") + +type configByRoundSelector[T any] func(config.ProcessConfigByRound) T +type configVariableHandler struct { + valueSelector configByRoundSelector[uint64] + defaultValue uint64 +} + +// processConfigsByEpoch holds the process configuration for epoch changes +type processConfigsByEpoch struct { + orderedConfigByEpoch []config.ProcessConfigByEpoch + orderedConfigByRound []config.ProcessConfigByRound + roundNotifier process.RoundNotifier + variablesMap map[dto.ConfigVariable]configVariableHandler +} + +// NewProcessConfigsHandler creates a new process configs by epoch component +func NewProcessConfigsHandler( + configsByEpoch []config.ProcessConfigByEpoch, + configsByRound []config.ProcessConfigByRound, + roundNotifier process.RoundNotifier, +) (*processConfigsByEpoch, error) { + if check.IfNil(roundNotifier) { + return nil, fmt.Errorf("%w in NewProcessConfigsHandler", process.ErrNilRoundNotifier) + } + + err := checkConfigsByEpoch(configsByEpoch) + if err != nil { + return nil, err + } + + err = checkConfigsByRound(configsByRound) + if err != nil { + return nil, err + } + + pce := &processConfigsByEpoch{ + orderedConfigByEpoch: make([]config.ProcessConfigByEpoch, len(configsByEpoch)), + orderedConfigByRound: make([]config.ProcessConfigByRound, len(configsByRound)), + roundNotifier: roundNotifier, + variablesMap: initCfgVarMap(), + } + + // sort the config values in ascending order + copy(pce.orderedConfigByEpoch, configsByEpoch) + sort.SliceStable(pce.orderedConfigByEpoch, func(i, j int) bool { + return pce.orderedConfigByEpoch[i].EnableEpoch < pce.orderedConfigByEpoch[j].EnableEpoch + }) + + copy(pce.orderedConfigByRound, configsByRound) + sort.SliceStable(pce.orderedConfigByRound, func(i, j int) bool { + return pce.orderedConfigByRound[i].EnableRound < pce.orderedConfigByRound[j].EnableRound + }) + + return pce, nil +} + +// TODO: consider extracting common functionality into a base component +func checkConfigsByEpoch(configsByEpoch []config.ProcessConfigByEpoch) error { + if len(configsByEpoch) == 0 { + return ErrEmptyProcessConfigsByEpoch + } + + // check for duplicated configs + seen := make(map[uint32]struct{}) + for _, cfg := range configsByEpoch { + _, exists := seen[cfg.EnableEpoch] + if exists { + return ErrDuplicatedEpochConfig + } + seen[cfg.EnableEpoch] = struct{}{} + } + + _, exists := seen[0] + if !exists { + return ErrMissingEpochZeroConfig + } + + return nil +} + +func checkConfigsByRound(configsByRound []config.ProcessConfigByRound) error { + if len(configsByRound) == 0 { + return ErrEmptyProcessConfigsByRound + } + + // check for duplicated configs + seen := make(map[uint64]struct{}) + for _, cfg := range configsByRound { + _, exists := seen[cfg.EnableRound] + if exists { + return ErrDuplicatedRoundConfig + } + err := checkRoundConfigValues(cfg) + if err != nil { + return fmt.Errorf("%w in checkConfigsByRound for enable round: %d", err, cfg.EnableRound) + } + + seen[cfg.EnableRound] = struct{}{} + } + + _, exists := seen[0] + if !exists { + return ErrMissingRoundZeroConfig + } + + return nil +} + +func checkRoundConfigValues(cfg config.ProcessConfigByRound) error { + if cfg.MaxRoundsToKeepUnprocessedTransactions < minRoundsToKeepUnprocessedData { + return fmt.Errorf("%w for MaxRoundsToKeepUnprocessedTransactions, received %d, min expected %d", + process.ErrInvalidValue, cfg.MaxRoundsToKeepUnprocessedTransactions, minRoundsToKeepUnprocessedData) + } + if cfg.MaxRoundsToKeepUnprocessedMiniBlocks < minRoundsToKeepUnprocessedData { + return fmt.Errorf("%w for MaxRoundsToKeepUnprocessedMiniBlocks, received %d, min expected %d", + process.ErrInvalidValue, cfg.MaxRoundsToKeepUnprocessedMiniBlocks, minRoundsToKeepUnprocessedData) + } + if cfg.MaxConsecutiveRoundsOfRatingDecrease == 0 { + return process.ErrZeroMaxConsecutiveRoundsOfRatingDecrease + } + if cfg.MaxBlockProcessingTimeMs < minBlockProcessingTimeMs { + return fmt.Errorf("%w for MaxBlockProcessingTimeMs, received %d, min expected %d", + process.ErrInvalidValue, cfg.MaxBlockProcessingTimeMs, minBlockProcessingTimeMs) + } + + return nil +} + +// TODO: We should probably get rid of all these functions and use only GetValue func, similar to how enableEpochsHandler works + +// GetMaxMetaNoncesBehindByEpoch returns the max meta nonces behind by epoch +func (pce *processConfigsByEpoch) GetMaxMetaNoncesBehindByEpoch(epoch uint32) uint32 { + for i := len(pce.orderedConfigByEpoch) - 1; i >= 0; i-- { + if pce.orderedConfigByEpoch[i].EnableEpoch <= epoch { + return pce.orderedConfigByEpoch[i].MaxMetaNoncesBehind + } + } + + return defaultMaxMetaNoncesBehind // this should not happen +} + +// GetMaxMetaNoncesBehindForGlobalStuckByEpoch returns the max meta nonces behind for global stuck by epoch +func (pce *processConfigsByEpoch) GetMaxMetaNoncesBehindForGlobalStuckByEpoch(epoch uint32) uint32 { + for i := len(pce.orderedConfigByEpoch) - 1; i >= 0; i-- { + if pce.orderedConfigByEpoch[i].EnableEpoch <= epoch { + return pce.orderedConfigByEpoch[i].MaxMetaNoncesBehindForGlobalStuck + } + } + + return defaultMaxMetaNoncesBehindForGlobalStuck // this should not happen +} + +// GetMaxShardNoncesBehindByEpoch returns the max meta nonces behind for global stuck by epoch +func (pce *processConfigsByEpoch) GetMaxShardNoncesBehindByEpoch(epoch uint32) uint32 { + for i := len(pce.orderedConfigByEpoch) - 1; i >= 0; i-- { + if pce.orderedConfigByEpoch[i].EnableEpoch <= epoch { + return pce.orderedConfigByEpoch[i].MaxShardNoncesBehind + } + } + + return defaultMaxShardNoncesBehind // this should not happen +} + +func getConfigValueByRound[T any]( + configs []config.ProcessConfigByRound, + round uint64, + selector configByRoundSelector[T], + defaultValue T, +) T { + for i := len(configs) - 1; i >= 0; i-- { + if configs[i].EnableRound <= round { + return selector(configs[i]) + } + } + + return defaultValue +} + +// GetMaxRoundsWithoutNewBlockReceivedByRound returns max rounds without new block received by epoch +func (pce *processConfigsByEpoch) GetMaxRoundsWithoutNewBlockReceivedByRound(round uint64) uint32 { + return getConfigValueByRound( + pce.orderedConfigByRound, + round, + func(cfg config.ProcessConfigByRound) uint32 { + return cfg.MaxRoundsWithoutNewBlockReceived + }, + defaultMaxRoundsWithoutNewBlockReceived, + ) +} + +// GetMaxRoundsWithoutCommittedBlock returns max rounds without commited block +func (pce *processConfigsByEpoch) GetMaxRoundsWithoutCommittedBlock(round uint64) uint32 { + return getConfigValueByRound( + pce.orderedConfigByRound, + round, + func(cfg config.ProcessConfigByRound) uint32 { + return cfg.MaxRoundsWithoutCommittedBlock + }, + defaultMaxRoundsWithoutCommittedBlock, + ) +} + +// GetRoundModulusTriggerWhenSyncIsStuck returns round modulus when sync is stuck +func (pce *processConfigsByEpoch) GetRoundModulusTriggerWhenSyncIsStuck(round uint64) uint32 { + return getConfigValueByRound( + pce.orderedConfigByRound, + round, + func(cfg config.ProcessConfigByRound) uint32 { + return cfg.RoundModulusTriggerWhenSyncIsStuck + }, + defaultRoundModulusTriggerWhenSyncIsStuck, + ) +} + +// GetMaxSyncWithErrorsAllowed returns max allowed sync errors until an error event is triggered +func (pce *processConfigsByEpoch) GetMaxSyncWithErrorsAllowed(round uint64) uint32 { + return getConfigValueByRound( + pce.orderedConfigByRound, + round, + func(cfg config.ProcessConfigByRound) uint32 { + return cfg.MaxSyncWithErrorsAllowed + }, + defaultMaxSyncWithErrorsAllowed, + ) +} + +// GetMaxRoundsToKeepUnprocessedMiniBlocks returns max rounds to keep unprocessed mini blocks based on round +func (pce *processConfigsByEpoch) GetMaxRoundsToKeepUnprocessedMiniBlocks(round uint64) uint64 { + return getConfigValueByRound( + pce.orderedConfigByRound, + round, + func(cfg config.ProcessConfigByRound) uint64 { + return cfg.MaxRoundsToKeepUnprocessedMiniBlocks + }, + defaultMaxRoundsToKeepUnprocessedMiniBlocks, + ) +} + +// GetMaxRoundsToKeepUnprocessedTransactions returns max rounds to keep unprocessed transaction blocks based on round +func (pce *processConfigsByEpoch) GetMaxRoundsToKeepUnprocessedTransactions(round uint64) uint64 { + return getConfigValueByRound( + pce.orderedConfigByRound, + round, + func(cfg config.ProcessConfigByRound) uint64 { + return cfg.MaxRoundsToKeepUnprocessedTransactions + }, + defaultMaxRoundsToKeepUnprocessedTransactions, + ) +} + +// GetMaxBlockProcessingTime returns max block processing time +func (pce *processConfigsByEpoch) GetMaxBlockProcessingTime(round uint64) time.Duration { + return getConfigValueByRound( + pce.orderedConfigByRound, + round, + func(cfg config.ProcessConfigByRound) time.Duration { + return time.Duration(cfg.MaxBlockProcessingTimeMs) * time.Millisecond + }, + defaultMaxBlockProcessingTimeMs, + ) +} + +// GetNumHeadersToRequestInAdvance returns the number of headers to request in advance based on round +func (pce *processConfigsByEpoch) GetNumHeadersToRequestInAdvance(round uint64) uint64 { + return getConfigValueByRound( + pce.orderedConfigByRound, + round, + func(cfg config.ProcessConfigByRound) uint64 { + return cfg.NumHeadersToRequestInAdvance + }, + defaultNumHeadersToRequestInAdvance, + ) +} + +// GetValue returns the value of the provided variable for the current round +func (pce *processConfigsByEpoch) GetValue(variable dto.ConfigVariable) uint64 { + return pce.getValueByRound(variable, pce.roundNotifier.CurrentRound()) +} + +func (pce *processConfigsByEpoch) getValueByRound( + variable dto.ConfigVariable, + round uint64, +) uint64 { + cfgVarHandler, ok := pce.variablesMap[variable] + if !ok { + log.Warn("processConfigsByEpoch.getValueByRound: variable not found in pce.variablesMap", "variable", variable) + return 0 + } + + return getConfigValueByRound( + pce.orderedConfigByRound, + round, + cfgVarHandler.valueSelector, + cfgVarHandler.defaultValue, + ) +} + +// IsInterfaceNil checks if the instance is nil +func (pce *processConfigsByEpoch) IsInterfaceNil() bool { + return pce == nil +} diff --git a/common/configs/processConfigs_test.go b/common/configs/processConfigs_test.go new file mode 100644 index 00000000000..e9d2b005051 --- /dev/null +++ b/common/configs/processConfigs_test.go @@ -0,0 +1,300 @@ +package configs_test + +import ( + "strings" + "testing" + "time" + + "github.com/multiversx/mx-chain-go/common/configs" + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" + "github.com/stretchr/testify/require" +) + +func getConfigsByRound() []config.ProcessConfigByRound { + return []config.ProcessConfigByRound{ + { + EnableRound: 0, + MaxRoundsWithoutNewBlockReceived: 10, + MaxRoundsToKeepUnprocessedMiniBlocks: 1, + MaxRoundsToKeepUnprocessedTransactions: 1, + NumFloodingRoundsSlowReacting: 2, + NumFloodingRoundsFastReacting: 3, + NumFloodingRoundsOutOfSpecs: 4, + MaxConsecutiveRoundsOfRatingDecrease: 600, + MaxBlockProcessingTimeMs: 1000, + }, + { + EnableRound: 1, + MaxRoundsWithoutNewBlockReceived: 11, + MaxRoundsToKeepUnprocessedMiniBlocks: 1, + MaxRoundsToKeepUnprocessedTransactions: 1, + NumFloodingRoundsSlowReacting: 20, + NumFloodingRoundsFastReacting: 30, + NumFloodingRoundsOutOfSpecs: 40, + MaxConsecutiveRoundsOfRatingDecrease: 6000, + MaxBlockProcessingTimeMs: 1000, + }, + } +} + +func TestNewProcessConfigsByEpoch(t *testing.T) { + t.Parallel() + + t.Run("should return error for empty config by epoch", func(t *testing.T) { + t.Parallel() + + pce, err := configs.NewProcessConfigsHandler(nil, nil, &epochNotifier.RoundNotifierStub{}) + require.Nil(t, pce) + require.Equal(t, configs.ErrEmptyProcessConfigsByEpoch, err) + }) + + t.Run("should return error for empty config by round", func(t *testing.T) { + t.Parallel() + + pce, err := configs.NewProcessConfigsHandler([]config.ProcessConfigByEpoch{}, nil, &epochNotifier.RoundNotifierStub{}) + require.Nil(t, pce) + require.Equal(t, configs.ErrEmptyProcessConfigsByEpoch, err) + }) + + t.Run("should return error for duplicated epoch configs", func(t *testing.T) { + t.Parallel() + + conf := []config.ProcessConfigByEpoch{ + {EnableEpoch: 0, MaxMetaNoncesBehind: 15}, + {EnableEpoch: 0, MaxMetaNoncesBehind: 30}, + } + pce, err := configs.NewProcessConfigsHandler(conf, []config.ProcessConfigByRound{}, &epochNotifier.RoundNotifierStub{}) + require.Nil(t, pce) + require.Equal(t, configs.ErrDuplicatedEpochConfig, err) + }) + + t.Run("should return error for missing epoch 0 config", func(t *testing.T) { + t.Parallel() + + conf := []config.ProcessConfigByEpoch{ + {EnableEpoch: 1, MaxMetaNoncesBehind: 15}, + {EnableEpoch: 2, MaxMetaNoncesBehind: 30}, + } + pce, err := configs.NewProcessConfigsHandler(conf, []config.ProcessConfigByRound{}, &epochNotifier.RoundNotifierStub{}) + require.Nil(t, pce) + require.Equal(t, configs.ErrMissingEpochZeroConfig, err) + }) + + t.Run("should return error for zero MaxRoundsToKeepUnprocessedTransactions value", func(t *testing.T) { + t.Parallel() + + confByEpoch := []config.ProcessConfigByEpoch{ + {EnableEpoch: 0, MaxMetaNoncesBehind: 15}, + } + confByRound := getConfigsByRound() + confByRound[0].MaxRoundsToKeepUnprocessedTransactions = 0 + + pce, err := configs.NewProcessConfigsHandler(confByEpoch, confByRound, &epochNotifier.RoundNotifierStub{}) + require.Nil(t, pce) + require.ErrorIs(t, err, process.ErrInvalidValue) + require.True(t, strings.Contains(err.Error(), "MaxRoundsToKeepUnprocessedTransactions")) + }) + + t.Run("should return error for zero MaxRoundsToKeepUnprocessedMiniBlocks value", func(t *testing.T) { + t.Parallel() + + confByEpoch := []config.ProcessConfigByEpoch{ + {EnableEpoch: 0, MaxMetaNoncesBehind: 15}, + } + confByRound := getConfigsByRound() + confByRound[0].MaxRoundsToKeepUnprocessedMiniBlocks = 0 + + pce, err := configs.NewProcessConfigsHandler(confByEpoch, confByRound, &epochNotifier.RoundNotifierStub{}) + require.Nil(t, pce) + require.ErrorIs(t, err, process.ErrInvalidValue) + require.True(t, strings.Contains(err.Error(), "MaxRoundsToKeepUnprocessedMiniBlocks")) + }) + + t.Run("should return error for invalid max consecutive rounds of rating decrease value", func(t *testing.T) { + t.Parallel() + + confByEpoch := []config.ProcessConfigByEpoch{ + {EnableEpoch: 0, MaxMetaNoncesBehind: 15}, + } + confByRound := getConfigsByRound() + confByRound[0].MaxConsecutiveRoundsOfRatingDecrease = 0 + + pce, err := configs.NewProcessConfigsHandler(confByEpoch, confByRound, &epochNotifier.RoundNotifierStub{}) + require.Nil(t, pce) + require.ErrorIs(t, err, process.ErrZeroMaxConsecutiveRoundsOfRatingDecrease) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + conf := []config.ProcessConfigByEpoch{ + {EnableEpoch: 0, MaxMetaNoncesBehind: 15}, + {EnableEpoch: 2, MaxMetaNoncesBehind: 30}, + {EnableEpoch: 1, MaxMetaNoncesBehind: 45}, + } + confByRound := getConfigsByRound() + + pce, err := configs.NewProcessConfigsHandler(conf, confByRound, &epochNotifier.RoundNotifierStub{}) + require.NotNil(t, pce) + require.NoError(t, err) + require.False(t, pce.IsInterfaceNil()) + + require.Equal(t, uint32(15), pce.GetOrderedConfigsByEpoch(0).MaxMetaNoncesBehind) + require.Equal(t, uint32(45), pce.GetOrderedConfigsByEpoch(1).MaxMetaNoncesBehind) + require.Equal(t, uint32(30), pce.GetOrderedConfigsByEpoch(2).MaxMetaNoncesBehind) + }) +} + +func TestProcessConfigsByEpoch_Getters(t *testing.T) { + t.Parallel() + + conf := []config.ProcessConfigByEpoch{ + {EnableEpoch: 0, + MaxMetaNoncesBehind: 10, + MaxMetaNoncesBehindForGlobalStuck: 11, + MaxShardNoncesBehind: 12, + }, + {EnableEpoch: 1, + MaxMetaNoncesBehind: 20, + MaxMetaNoncesBehindForGlobalStuck: 21, + MaxShardNoncesBehind: 22, + }, + } + + confByRound := []config.ProcessConfigByRound{ + {EnableRound: 0, + MaxRoundsWithoutNewBlockReceived: 10, + MaxRoundsWithoutCommittedBlock: 20, + MaxSyncWithErrorsAllowed: 30, + MaxRoundsToKeepUnprocessedTransactions: 50, + MaxRoundsToKeepUnprocessedMiniBlocks: 60, + NumFloodingRoundsSlowReacting: 2, + NumFloodingRoundsFastReacting: 3, + NumFloodingRoundsOutOfSpecs: 4, + MaxConsecutiveRoundsOfRatingDecrease: 600, + MaxBlockProcessingTimeMs: 1000, + }, + {EnableRound: 1, + MaxRoundsWithoutNewBlockReceived: 11, + MaxRoundsWithoutCommittedBlock: 21, + MaxSyncWithErrorsAllowed: 31, + MaxRoundsToKeepUnprocessedTransactions: 500, + MaxRoundsToKeepUnprocessedMiniBlocks: 600, + NumFloodingRoundsSlowReacting: 20, + NumFloodingRoundsFastReacting: 30, + NumFloodingRoundsOutOfSpecs: 40, + MaxConsecutiveRoundsOfRatingDecrease: 6000, + MaxBlockProcessingTimeMs: 2000, + }, + } + + t.Run("get max meta nonces behind", func(t *testing.T) { + t.Parallel() + + pce, _ := configs.NewProcessConfigsHandler(conf, confByRound, &epochNotifier.RoundNotifierStub{}) + + maxMetaNoncesBehind := pce.GetMaxMetaNoncesBehindByEpoch(0) + require.Equal(t, uint32(10), maxMetaNoncesBehind) + + maxMetaNoncesBehind = pce.GetMaxMetaNoncesBehindByEpoch(1) + require.Equal(t, uint32(20), maxMetaNoncesBehind) + }) + + t.Run("get max meta nonces behind for global stuck", func(t *testing.T) { + t.Parallel() + + pce, _ := configs.NewProcessConfigsHandler(conf, confByRound, &epochNotifier.RoundNotifierStub{}) + + maxMetaNoncesBehindForGlobalStuck := pce.GetMaxMetaNoncesBehindForGlobalStuckByEpoch(0) + require.Equal(t, uint32(11), maxMetaNoncesBehindForGlobalStuck) + + maxMetaNoncesBehindForGlobalStuck = pce.GetMaxMetaNoncesBehindForGlobalStuckByEpoch(1) + require.Equal(t, uint32(21), maxMetaNoncesBehindForGlobalStuck) + }) + + t.Run("get max shard nonces behind", func(t *testing.T) { + t.Parallel() + + pce, _ := configs.NewProcessConfigsHandler(conf, confByRound, &epochNotifier.RoundNotifierStub{}) + + maxShardNoncesBehind := pce.GetMaxShardNoncesBehindByEpoch(0) + require.Equal(t, uint32(12), maxShardNoncesBehind) + + maxShardNoncesBehind = pce.GetMaxShardNoncesBehindByEpoch(1) + require.Equal(t, uint32(22), maxShardNoncesBehind) + }) + + t.Run("get max rounds without new block received", func(t *testing.T) { + t.Parallel() + + pce, _ := configs.NewProcessConfigsHandler(conf, confByRound, &epochNotifier.RoundNotifierStub{}) + + maxRoundsWithoutNewBlockReceived := pce.GetMaxRoundsWithoutNewBlockReceivedByRound(0) + require.Equal(t, uint32(10), maxRoundsWithoutNewBlockReceived) + + maxRoundsWithoutNewBlockReceived = pce.GetMaxRoundsWithoutNewBlockReceivedByRound(1) + require.Equal(t, uint32(11), maxRoundsWithoutNewBlockReceived) + }) + + t.Run("get max rounds without committed block", func(t *testing.T) { + t.Parallel() + + pce, _ := configs.NewProcessConfigsHandler(conf, confByRound, &epochNotifier.RoundNotifierStub{}) + + maxRoundsWithoutCommittedBlock := pce.GetMaxRoundsWithoutCommittedBlock(0) + require.Equal(t, uint32(20), maxRoundsWithoutCommittedBlock) + + maxRoundsWithoutCommittedBlock = pce.GetMaxRoundsWithoutCommittedBlock(1) + require.Equal(t, uint32(21), maxRoundsWithoutCommittedBlock) + }) + + t.Run("get max allowed sync with errors", func(t *testing.T) { + t.Parallel() + + pce, _ := configs.NewProcessConfigsHandler(conf, confByRound, &epochNotifier.RoundNotifierStub{}) + + maxSyncWithErrorsAllowed := pce.GetMaxSyncWithErrorsAllowed(0) + require.Equal(t, uint32(30), maxSyncWithErrorsAllowed) + + maxSyncWithErrorsAllowed = pce.GetMaxSyncWithErrorsAllowed(1) + require.Equal(t, uint32(31), maxSyncWithErrorsAllowed) + }) + + t.Run("get max rounds to keep unprocessed transactions", func(t *testing.T) { + t.Parallel() + + pce, _ := configs.NewProcessConfigsHandler(conf, confByRound, &epochNotifier.RoundNotifierStub{}) + + res := pce.GetMaxRoundsToKeepUnprocessedTransactions(0) + require.Equal(t, uint64(50), res) + + res = pce.GetMaxRoundsToKeepUnprocessedTransactions(1) + require.Equal(t, uint64(500), res) + }) + + t.Run("get max block processing time", func(t *testing.T) { + t.Parallel() + + pce, _ := configs.NewProcessConfigsHandler(conf, confByRound, &epochNotifier.RoundNotifierStub{}) + + res := pce.GetMaxBlockProcessingTime(0) + require.Equal(t, time.Duration(1000)*time.Millisecond, res) + + res = pce.GetMaxBlockProcessingTime(1) + require.Equal(t, time.Duration(2000)*time.Millisecond, res) + }) + + t.Run("get max rounds to keep unprocessed mini blocks", func(t *testing.T) { + t.Parallel() + + pce, _ := configs.NewProcessConfigsHandler(conf, confByRound, &epochNotifier.RoundNotifierStub{}) + + res := pce.GetMaxRoundsToKeepUnprocessedMiniBlocks(0) + require.Equal(t, uint64(60), res) + + res = pce.GetMaxRoundsToKeepUnprocessedMiniBlocks(1) + require.Equal(t, uint64(600), res) + }) +} diff --git a/common/configs/vars.go b/common/configs/vars.go new file mode 100644 index 00000000000..71c6c2f065a --- /dev/null +++ b/common/configs/vars.go @@ -0,0 +1,23 @@ +package configs + +import ( + "github.com/multiversx/mx-chain-go/common/configs/dto" + "github.com/multiversx/mx-chain-go/config" +) + +func initCfgVarMap() map[dto.ConfigVariable]configVariableHandler { + return map[dto.ConfigVariable]configVariableHandler{ + dto.MaxConsecutiveRoundsOfRatingDecrease: { + valueSelector: func(cfg config.ProcessConfigByRound) uint64 { + return cfg.MaxConsecutiveRoundsOfRatingDecrease + }, + defaultValue: defaultMaxConsecutiveRoundsOfRatingDecrease, + }, + dto.MaxRoundsOfInactivityAccepted: { + valueSelector: func(cfg config.ProcessConfigByRound) uint64 { + return cfg.MaxRoundsOfInactivityAccepted + }, + defaultValue: defaultMaxRoundsOfInactivityAccepted, + }, + } +} diff --git a/common/consensus/common.go b/common/consensus/common.go new file mode 100644 index 00000000000..dc200fb6749 --- /dev/null +++ b/common/consensus/common.go @@ -0,0 +1,24 @@ +package consensus + +import ( + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/consensus" + logger "github.com/multiversx/mx-chain-logger-go" +) + +var log = logger.GetOrCreate("common/consensus") + +// ShouldConsiderSelfKeyInConsensus returns true if current machine is the main one, or it is a backup machine but the main +// machine failed +func ShouldConsiderSelfKeyInConsensus(redundancyHandler consensus.NodeRedundancyHandler) bool { + if check.IfNil(redundancyHandler) { + log.Warn("redundancy handler is nil") + return false + } + + isMainMachine := !redundancyHandler.IsRedundancyNode() + if isMainMachine { + return true + } + return !redundancyHandler.IsMainMachineActive() +} diff --git a/common/consensus/common_test.go b/common/consensus/common_test.go new file mode 100644 index 00000000000..b4ffc476efb --- /dev/null +++ b/common/consensus/common_test.go @@ -0,0 +1,39 @@ +package consensus_test + +import ( + "testing" + + "github.com/multiversx/mx-chain-go/common/consensus" + consensusMock "github.com/multiversx/mx-chain-go/consensus/mock" + "github.com/stretchr/testify/assert" +) + +func Test_ShouldConsiderSelfKeyInConsensus(t *testing.T) { + t.Parallel() + + // nil handler + assert.False(t, consensus.ShouldConsiderSelfKeyInConsensus(nil)) + + // main machine + assert.True(t, consensus.ShouldConsiderSelfKeyInConsensus( + &consensusMock.NodeRedundancyHandlerStub{ + IsRedundancyNodeCalled: func() bool { return false }, + }, + )) + + // backup with active main + assert.False(t, consensus.ShouldConsiderSelfKeyInConsensus( + &consensusMock.NodeRedundancyHandlerStub{ + IsRedundancyNodeCalled: func() bool { return true }, + IsMainMachineActiveCalled: func() bool { return true }, + }, + )) + + // backup with inactive main + assert.True(t, consensus.ShouldConsiderSelfKeyInConsensus( + &consensusMock.NodeRedundancyHandlerStub{ + IsRedundancyNodeCalled: func() bool { return true }, + IsMainMachineActiveCalled: func() bool { return false }, + }, + )) +} diff --git a/common/constants.go b/common/constants.go index 32430f4e72c..2cf06247bc0 100644 --- a/common/constants.go +++ b/common/constants.go @@ -103,6 +103,12 @@ const MetricCurrentRound = "erd_current_round" // MetricNonce is the metric for monitoring the nonce of a node const MetricNonce = "erd_nonce" +// MetricLastExecutedNonce is the metric for monitoring the last executed nonce of a node +const MetricLastExecutedNonce = "erd_last_executed_nonce" + +// MetricProposedNonce is the metric for monitoring the proposed nonce of a node +const MetricProposedNonce = "erd_proposed_nonce" + // MetricBlockTimestamp is the metric for monitoring the timestamp of the last synchronized block const MetricBlockTimestamp = "erd_block_timestamp" @@ -315,6 +321,36 @@ const MetricNoncesPassedInCurrentEpoch = "erd_nonces_passed_in_current_epoch" // 100 meaning that the block has been received in the last moment of the round) const MetricReceivedProposedBlock = "erd_consensus_received_proposed_block" +// MetricReceivedOrSentProposedBlock is the metric that specifies the delay in nanoseconds from the start of the current round until +// the time the proposed block body has been sent or has reached the current node. +const MetricReceivedOrSentProposedBlock = "erd_consensus_received_or_sent_proposed_block" + +// MetricReceivedProof is the metric that specifies the delay in nanoseconds between the time the proposed block has been sent +// or has reached the current node until the proof was received. +const MetricReceivedProof = "erd_consensus_received_proof" + +// MetricAvgReceivedOrSentProposedBlock is the metric that specifies the average delay in nanoseconds from the start of the round until +// the time the proposed block has been sent or has reached the current node. +const MetricAvgReceivedOrSentProposedBlock = "erd_consensus_average_received_or_sent_proposed_block" + +// MetricAvgReceivedProof is the metric that specifies the average delay in nanoseconds between the time the proposed block +// has been sent or has reached the current node until proof was received. +const MetricAvgReceivedProof = "erd_consensus_average_received_sent_proof" + +// MetricNumTrackedBlocks is the metric that specifies how many blocks are tracked by the txPool +const MetricNumTrackedBlocks = "erd_num_tracked_blocks" + +// MetricNumTrackedAccounts is the metric that specifies how many accounts are tracked by the txPool +const MetricNumTrackedAccounts = "erd_num_tracked_accounts" + +// MetricNumInclusionEstimationRejected is the metric that specifies how many execution results were rejected by the +// inclusion estimation process +const MetricNumInclusionEstimationRejected = "erd_num_inclusion_estimation_rejected" + +// MetricDeltaHeaderNonceLastExecutionResultNonce is the metric that specifies the delta between the header nonce and +// the last execution result nonce +const MetricDeltaHeaderNonceLastExecutionResultNonce = "erd_delta_header_nonce_last_execution_result_nonce" + // MetricCreatedProposedBlock is the metric that specifies the percent of the block subround used for header and body // creation (0 meaning that the block was created in no-time and 100 meaning that the block creation used all the // subround spare duration) @@ -593,9 +629,6 @@ const ( // MetricStorageAPICostOptimizationEnableEpoch represents the epoch when storage api cost optimization feature is enabled MetricStorageAPICostOptimizationEnableEpoch = "erd_storage_api_cost_optimization_enable_epoch" - // MetricTransformToMultiShardCreateEnableEpoch represents the epoch when transform to multi shard create functionality is enabled - MetricTransformToMultiShardCreateEnableEpoch = "erd_transform_to_multi_shard_create_enable_epoch" - // MetricESDTRegisterAndSetAllRolesEnableEpoch represents the epoch when esdt register and set all roles functionality is enabled MetricESDTRegisterAndSetAllRolesEnableEpoch = "erd_esdt_register_and_set_all_roles_enable_epoch" @@ -779,6 +812,12 @@ const ( // MetricRelayedTransactionsV1V2DisableEpoch represents the epoch when relayed transactions v1 and v2 are disabled MetricRelayedTransactionsV1V2DisableEpoch = "erd_relayed_transactions_v1_v2_disable_epoch" + // MetricTailInflationEnableEpoch represents the epoch when tail inflation is enabled + MetricTailInflationEnableEpoch = "erd_tail_inflation_enable_epoch" + + // MetricSupernovaEnableEpoch represents the epoch when supernova is enabled + MetricSupernovaEnableEpoch = "erd_supernova_enable_epoch" + // MetricEpochEnable represents the epoch when the max nodes change configuration is applied MetricEpochEnable = "erd_epoch_enable" @@ -900,6 +939,8 @@ const ( NetStatisticsOrder // OldDatabaseCleanOrder defines the order in which oldDatabaseCleaner component is notified of a start of epoch event OldDatabaseCleanOrder + // EpochTxBroadcastDebug defines the order in which epochTxBroadcastDebug is notifier of a start of epoch event + EpochTxBroadcastDebug = 9 ) // NodeState specifies what type of state a node could have @@ -980,15 +1021,15 @@ const MaxSoftwareVersionLengthInBytes = 10 // ExtraDelayForBroadcastBlockInfo represents the number of seconds to wait since a block has been broadcast and the // moment when its components, like mini blocks and transactions, would be broadcast too -const ExtraDelayForBroadcastBlockInfo = 1 * time.Second +const ExtraDelayForBroadcastBlockInfo = 120 * time.Millisecond // ExtraDelayBetweenBroadcastMbsAndTxs represents the number of seconds to wait since miniblocks have been broadcast // and the moment when theirs transactions would be broadcast too -const ExtraDelayBetweenBroadcastMbsAndTxs = 1 * time.Second +const ExtraDelayBetweenBroadcastMbsAndTxs = 100 * time.Millisecond // ExtraDelayForRequestBlockInfo represents the number of seconds to wait since a block has been received and the // moment when its components, like mini blocks and transactions, would be requested too if they are still missing -const ExtraDelayForRequestBlockInfo = ExtraDelayForBroadcastBlockInfo + ExtraDelayBetweenBroadcastMbsAndTxs + time.Second +const ExtraDelayForRequestBlockInfo = ExtraDelayForBroadcastBlockInfo + ExtraDelayBetweenBroadcastMbsAndTxs // CommitMaxTime represents max time accepted for a commit action, after which a warn message is displayed const CommitMaxTime = 3 * time.Second @@ -996,6 +1037,9 @@ const CommitMaxTime = 3 * time.Second // PutInStorerMaxTime represents max time accepted for a put action, after which a warn message is displayed const PutInStorerMaxTime = time.Second +// PutInStorerMaxTimeSupernova represents max time accepted for a put action with supernova activated, after which a warn message is displayed +const PutInStorerMaxTimeSupernova = 600 * time.Millisecond + // DefaultUnstakedEpoch represents the default epoch that is set for a validator that has not unstaked yet const DefaultUnstakedEpoch = math.MaxUint32 @@ -1026,10 +1070,6 @@ const NotSetDestinationShardID = "disabled" // considering that constant below is set to 3 const AdditionalScrForEachScCallOrSpecialTx = 3 -// MaxRoundsWithoutCommittedStartInEpochBlock defines the maximum rounds to wait for start in epoch block to be committed, -// before a special action to be applied -const MaxRoundsWithoutCommittedStartInEpochBlock = 50 - // DefaultResolversIdentifier represents the identifier that is used in conjunction with regular resolvers // (that makes the node run properly) const DefaultResolversIdentifier = "default resolver" @@ -1282,6 +1322,7 @@ const ( RelayedTransactionsV3Flag core.EnableEpochFlag = "RelayedTransactionsV3Flag" RelayedTransactionsV3FixESDTTransferFlag core.EnableEpochFlag = "RelayedTransactionsV3FixESDTTransferFlag" AndromedaFlag core.EnableEpochFlag = "AndromedaFlag" + SupernovaFlag core.EnableEpochFlag = "SupernovaFlag" CheckBuiltInCallOnTransferValueAndFailExecutionFlag core.EnableEpochFlag = "CheckBuiltInCallOnTransferValueAndFailExecutionFlag" MaskInternalDependenciesErrorsFlag core.EnableEpochFlag = "MaskInternalDependenciesErrorsFlag" FixBackTransferOPCODEFlag core.EnableEpochFlag = "FixBackTransferOPCODEFlag" @@ -1292,3 +1333,29 @@ const ( RelayedTransactionsV1V2DisableFlag core.EnableEpochFlag = "RelayedTransactionsV1V2DisableFlag" // all new flags must be added to createAllFlagsMap method, as part of enableEpochsHandler allFlagsDefined ) + +// EnableRoundFlag defines a flag specific to the enableRounds config +type EnableRoundFlag string + +// EnableRound flag definitions +const ( + DisableAsyncCallV1Flag EnableRoundFlag = "DisableAsyncCallV1" + SupernovaRoundFlag EnableRoundFlag = "SupernovaEnableRound" +) + +// HashSize defines const for the hash length +const HashSize = 32 + +// FloodPreventerType defines the type of flood preventer +type FloodPreventerType string + +const ( + // FastReacting defines fast reacting flood preventer type + FastReacting FloodPreventerType = "fast_reacting" + // SlowReacting defines slow reacting flood preventer type + SlowReacting FloodPreventerType = "slow_reacting" + // OutOfSpecs defines out of specs flood preventer type + OutOfSpecs FloodPreventerType = "out_of_specs" + // Output defines output flood preventer type + Output FloodPreventerType = "output" +) diff --git a/common/crypto/interface.go b/common/crypto/interface.go index 527d8019ec6..1ba6176e8e6 100644 --- a/common/crypto/interface.go +++ b/common/crypto/interface.go @@ -4,6 +4,6 @@ import crypto "github.com/multiversx/mx-chain-crypto-go" // MultiSignerContainer defines the container for different versioned multiSigner instances type MultiSignerContainer interface { - GetMultiSigner(epoch uint32) (crypto.MultiSigner, error) + GetMultiSigner(epoch uint32) (crypto.MultiSignerV2, error) IsInterfaceNil() bool } diff --git a/common/disabled/processStatusHandler.go b/common/disabled/processStatusHandler.go index 036075099af..a6cd2f2bab4 100644 --- a/common/disabled/processStatusHandler.go +++ b/common/disabled/processStatusHandler.go @@ -13,6 +13,9 @@ func NewProcessStatusHandler() *processStatusHandler { // SetBusy does nothing func (psh *processStatusHandler) SetBusy(_ string) {} +// TrySetBusy returns true +func (psh *processStatusHandler) TrySetBusy(_ string) bool { return true } + // SetIdle does nothing func (psh *processStatusHandler) SetIdle() {} diff --git a/common/disabled/processStatusHandler_test.go b/common/disabled/processStatusHandler_test.go index a8860da4605..6bda4d4769e 100644 --- a/common/disabled/processStatusHandler_test.go +++ b/common/disabled/processStatusHandler_test.go @@ -21,6 +21,7 @@ func TestProcessStatusHandler_MethodsShouldNotPanic(t *testing.T) { psh := NewProcessStatusHandler() assert.False(t, check.IfNil(psh)) psh.SetBusy("") + assert.True(t, psh.TrySetBusy("")) psh.SetIdle() assert.True(t, psh.IsIdle()) } diff --git a/common/dtos.go b/common/dtos.go index 3c3ef9e7008..95356bf2a0e 100644 --- a/common/dtos.go +++ b/common/dtos.go @@ -42,6 +42,24 @@ type TransactionsPoolNonceGapsForSenderApiResponse struct { Gaps []NonceGapApiResponse `json:"gaps"` } +// TransactionsSelectionSimulationResult represents a struct that holds the data to be returned when simulating a selection +type TransactionsSelectionSimulationResult struct { + Transactions []Transaction `json:"transactions"` +} + +// VirtualNonceOfAccountResponse represents a struct that holds the data to be returned when requesting the virtual nonce of an account +type VirtualNonceOfAccountResponse struct { + VirtualNonce uint64 `json:"virtualNonce"` + LatestCommittedBlock LatestCommittedBlockResponse `json:"latestCommittedBlock"` +} + +// LatestCommittedBlockResponse represents a struct that holds the data to be returned when requesting the latest committed block +type LatestCommittedBlockResponse struct { + Nonce uint64 `json:"nonce"` + Hash string `json:"hash"` + RootHash string `json:"rootHash"` +} + // DelegationDataAPI will be used when requesting the genesis balances from API type DelegationDataAPI struct { Address string `json:"address"` @@ -92,3 +110,9 @@ type AuctionListValidatorAPIResponse struct { QualifiedTopUp string `json:"qualifiedTopUp"` Nodes []*AuctionNode `json:"nodes"` } + +// LastExecutionResultForInclusion is a lightweight summary of the last notarized execution result EIE requires. +type LastExecutionResultForInclusion struct { + NotarizedInRound uint64 + ProposedInRound uint64 +} diff --git a/common/enablers/enableEpochsHandler.go b/common/enablers/enableEpochsHandler.go index 4468b2fb7e3..564d330becf 100644 --- a/common/enablers/enableEpochsHandler.go +++ b/common/enablers/enableEpochsHandler.go @@ -18,8 +18,9 @@ var log = logger.GetOrCreate("common/enablers") type flagEnabledInEpoch = func(epoch uint32) bool type flagHandler struct { - isActiveInEpoch flagEnabledInEpoch - activationEpoch uint32 + isActiveInEpoch flagEnabledInEpoch + activationEpoch uint32 + activationEpochName string } type enableEpochsHandler struct { @@ -52,812 +53,954 @@ func (handler *enableEpochsHandler) createAllFlagsMap() { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.SCDeployEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.SCDeployEnableEpoch, + activationEpoch: handler.enableEpochsConfig.SCDeployEnableEpoch, + activationEpochName: "SCDeployEnableEpoch", }, common.BuiltInFunctionsFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.BuiltInFunctionsEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.BuiltInFunctionsEnableEpoch, + activationEpoch: handler.enableEpochsConfig.BuiltInFunctionsEnableEpoch, + activationEpochName: "BuiltInFunctionsEnableEpoch", }, common.RelayedTransactionsFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.RelayedTransactionsEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.RelayedTransactionsEnableEpoch, + activationEpoch: handler.enableEpochsConfig.RelayedTransactionsEnableEpoch, + activationEpochName: "RelayedTransactionsEnableEpoch", }, common.PenalizedTooMuchGasFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.PenalizedTooMuchGasEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.PenalizedTooMuchGasEnableEpoch, + activationEpoch: handler.enableEpochsConfig.PenalizedTooMuchGasEnableEpoch, + activationEpochName: "PenalizedTooMuchGasEnableEpoch", }, common.SwitchJailWaitingFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.SwitchJailWaitingEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.SwitchJailWaitingEnableEpoch, + activationEpoch: handler.enableEpochsConfig.SwitchJailWaitingEnableEpoch, + activationEpochName: "SwitchJailWaitingEnableEpoch", }, common.BelowSignedThresholdFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.BelowSignedThresholdEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.BelowSignedThresholdEnableEpoch, + activationEpoch: handler.enableEpochsConfig.BelowSignedThresholdEnableEpoch, + activationEpochName: "BelowSignedThresholdEnableEpoch", }, common.SwitchHysteresisForMinNodesFlagInSpecificEpochOnly: { isActiveInEpoch: func(epoch uint32) bool { return epoch == handler.enableEpochsConfig.SwitchHysteresisForMinNodesEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.SwitchHysteresisForMinNodesEnableEpoch, + activationEpoch: handler.enableEpochsConfig.SwitchHysteresisForMinNodesEnableEpoch, + activationEpochName: "SwitchHysteresisForMinNodesEnableEpoch", }, common.TransactionSignedWithTxHashFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.TransactionSignedWithTxHashEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.TransactionSignedWithTxHashEnableEpoch, + activationEpoch: handler.enableEpochsConfig.TransactionSignedWithTxHashEnableEpoch, + activationEpochName: "TransactionSignedWithTxHashEnableEpoch", }, common.MetaProtectionFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.MetaProtectionEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.MetaProtectionEnableEpoch, + activationEpoch: handler.enableEpochsConfig.MetaProtectionEnableEpoch, + activationEpochName: "MetaProtectionEnableEpoch", }, common.AheadOfTimeGasUsageFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.AheadOfTimeGasUsageEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.AheadOfTimeGasUsageEnableEpoch, + activationEpoch: handler.enableEpochsConfig.AheadOfTimeGasUsageEnableEpoch, + activationEpochName: "AheadOfTimeGasUsageEnableEpoch", }, common.GasPriceModifierFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.GasPriceModifierEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.GasPriceModifierEnableEpoch, + activationEpoch: handler.enableEpochsConfig.GasPriceModifierEnableEpoch, + activationEpochName: "GasPriceModifierEnableEpoch", }, common.RepairCallbackFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.RepairCallbackEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.RepairCallbackEnableEpoch, + activationEpoch: handler.enableEpochsConfig.RepairCallbackEnableEpoch, + activationEpochName: "RepairCallbackEnableEpoch", }, common.ReturnDataToLastTransferFlagAfterEpoch: { isActiveInEpoch: func(epoch uint32) bool { return epoch > handler.enableEpochsConfig.ReturnDataToLastTransferEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ReturnDataToLastTransferEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ReturnDataToLastTransferEnableEpoch, + activationEpochName: "ReturnDataToLastTransferEnableEpoch", }, common.SenderInOutTransferFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.SenderInOutTransferEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.SenderInOutTransferEnableEpoch, + activationEpoch: handler.enableEpochsConfig.SenderInOutTransferEnableEpoch, + activationEpochName: "SenderInOutTransferEnableEpoch", }, common.StakeFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.StakeEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.StakeEnableEpoch, + activationEpoch: handler.enableEpochsConfig.StakeEnableEpoch, + activationEpochName: "StakeEnableEpoch", }, common.StakingV2Flag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.StakingV2EnableEpoch }, - activationEpoch: handler.enableEpochsConfig.StakingV2EnableEpoch, + activationEpoch: handler.enableEpochsConfig.StakingV2EnableEpoch, + activationEpochName: "StakingV2EnableEpoch", }, common.StakingV2OwnerFlagInSpecificEpochOnly: { isActiveInEpoch: func(epoch uint32) bool { return epoch == handler.enableEpochsConfig.StakingV2EnableEpoch }, - activationEpoch: handler.enableEpochsConfig.StakingV2EnableEpoch, + activationEpoch: handler.enableEpochsConfig.StakingV2EnableEpoch, + activationEpochName: "StakingV2EnableEpoch", }, common.StakingV2FlagAfterEpoch: { isActiveInEpoch: func(epoch uint32) bool { return epoch > handler.enableEpochsConfig.StakingV2EnableEpoch }, - activationEpoch: handler.enableEpochsConfig.StakingV2EnableEpoch, + activationEpoch: handler.enableEpochsConfig.StakingV2EnableEpoch, + activationEpochName: "StakingV2EnableEpoch", }, common.DoubleKeyProtectionFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.DoubleKeyProtectionEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.DoubleKeyProtectionEnableEpoch, + activationEpoch: handler.enableEpochsConfig.DoubleKeyProtectionEnableEpoch, + activationEpochName: "DoubleKeyProtectionEnableEpoch", }, common.ESDTFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.ESDTEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ESDTEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ESDTEnableEpoch, + activationEpochName: "ESDTEnableEpoch", }, common.ESDTFlagInSpecificEpochOnly: { isActiveInEpoch: func(epoch uint32) bool { return epoch == handler.enableEpochsConfig.ESDTEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ESDTEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ESDTEnableEpoch, + activationEpochName: "ESDTEnableEpoch", }, common.GovernanceFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.GovernanceEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.GovernanceEnableEpoch, + activationEpoch: handler.enableEpochsConfig.GovernanceEnableEpoch, + activationEpochName: "GovernanceEnableEpoch", }, common.GovernanceFlagInSpecificEpochOnly: { isActiveInEpoch: func(epoch uint32) bool { return epoch == handler.enableEpochsConfig.GovernanceEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.GovernanceEnableEpoch, + activationEpoch: handler.enableEpochsConfig.GovernanceEnableEpoch, + activationEpochName: "GovernanceEnableEpoch", }, common.GovernanceDisableProposeFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.GovernanceDisableProposeEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.GovernanceDisableProposeEnableEpoch, + activationEpoch: handler.enableEpochsConfig.GovernanceDisableProposeEnableEpoch, + activationEpochName: "GovernanceDisableProposeEnableEpoch", }, common.GovernanceFixesFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.GovernanceFixesEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.GovernanceFixesEnableEpoch, + activationEpoch: handler.enableEpochsConfig.GovernanceFixesEnableEpoch, + activationEpochName: "GovernanceFixesEnableEpoch", }, common.DelegationManagerFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.DelegationManagerEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.DelegationManagerEnableEpoch, + activationEpoch: handler.enableEpochsConfig.DelegationManagerEnableEpoch, + activationEpochName: "DelegationManagerEnableEpoch", }, common.DelegationSmartContractFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.DelegationSmartContractEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.DelegationSmartContractEnableEpoch, + activationEpoch: handler.enableEpochsConfig.DelegationSmartContractEnableEpoch, + activationEpochName: "DelegationSmartContractEnableEpoch", }, common.DelegationSmartContractFlagInSpecificEpochOnly: { isActiveInEpoch: func(epoch uint32) bool { return epoch == handler.enableEpochsConfig.DelegationSmartContractEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.DelegationSmartContractEnableEpoch, + activationEpoch: handler.enableEpochsConfig.DelegationSmartContractEnableEpoch, + activationEpochName: "DelegationSmartContractEnableEpoch", }, common.CorrectLastUnJailedFlagInSpecificEpochOnly: { isActiveInEpoch: func(epoch uint32) bool { return epoch == handler.enableEpochsConfig.CorrectLastUnjailedEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.CorrectLastUnjailedEnableEpoch, + activationEpoch: handler.enableEpochsConfig.CorrectLastUnjailedEnableEpoch, + activationEpochName: "CorrectLastUnjailedEnableEpoch", }, common.CorrectLastUnJailedFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.CorrectLastUnjailedEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.CorrectLastUnjailedEnableEpoch, + activationEpoch: handler.enableEpochsConfig.CorrectLastUnjailedEnableEpoch, + activationEpochName: "CorrectLastUnjailedEnableEpoch", }, common.RelayedTransactionsV2Flag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.RelayedTransactionsV2EnableEpoch }, - activationEpoch: handler.enableEpochsConfig.RelayedTransactionsV2EnableEpoch, + activationEpoch: handler.enableEpochsConfig.RelayedTransactionsV2EnableEpoch, + activationEpochName: "RelayedTransactionsV2EnableEpoch", }, common.UnBondTokensV2Flag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.UnbondTokensV2EnableEpoch }, - activationEpoch: handler.enableEpochsConfig.UnbondTokensV2EnableEpoch, + activationEpoch: handler.enableEpochsConfig.UnbondTokensV2EnableEpoch, + activationEpochName: "UnbondTokensV2EnableEpoch", }, common.SaveJailedAlwaysFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.SaveJailedAlwaysEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.SaveJailedAlwaysEnableEpoch, + activationEpoch: handler.enableEpochsConfig.SaveJailedAlwaysEnableEpoch, + activationEpochName: "SaveJailedAlwaysEnableEpoch", }, common.ReDelegateBelowMinCheckFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.ReDelegateBelowMinCheckEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ReDelegateBelowMinCheckEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ReDelegateBelowMinCheckEnableEpoch, + activationEpochName: "ReDelegateBelowMinCheckEnableEpoch", }, common.ValidatorToDelegationFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.ValidatorToDelegationEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ValidatorToDelegationEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ValidatorToDelegationEnableEpoch, + activationEpochName: "ValidatorToDelegationEnableEpoch", }, common.IncrementSCRNonceInMultiTransferFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.IncrementSCRNonceInMultiTransferEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.IncrementSCRNonceInMultiTransferEnableEpoch, + activationEpoch: handler.enableEpochsConfig.IncrementSCRNonceInMultiTransferEnableEpoch, + activationEpochName: "IncrementSCRNonceInMultiTransferEnableEpoch", }, common.ESDTMultiTransferFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.ESDTMultiTransferEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ESDTMultiTransferEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ESDTMultiTransferEnableEpoch, + activationEpochName: "ESDTMultiTransferEnableEpoch", }, common.ESDTNFTImprovementV1Flag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.ESDTMultiTransferEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ESDTMultiTransferEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ESDTMultiTransferEnableEpoch, + activationEpochName: "ESDTMultiTransferEnableEpoch", }, common.GlobalMintBurnFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch < handler.enableEpochsConfig.GlobalMintBurnDisableEpoch }, - activationEpoch: handler.enableEpochsConfig.GlobalMintBurnDisableEpoch, + activationEpoch: handler.enableEpochsConfig.GlobalMintBurnDisableEpoch, + activationEpochName: "GlobalMintBurnDisableEpoch", }, common.ESDTTransferRoleFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.ESDTTransferRoleEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ESDTTransferRoleEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ESDTTransferRoleEnableEpoch, + activationEpochName: "ESDTTransferRoleEnableEpoch", }, common.ComputeRewardCheckpointFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.ComputeRewardCheckpointEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ComputeRewardCheckpointEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ComputeRewardCheckpointEnableEpoch, + activationEpochName: "ComputeRewardCheckpointEnableEpoch", }, common.SCRSizeInvariantCheckFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.SCRSizeInvariantCheckEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.SCRSizeInvariantCheckEnableEpoch, + activationEpoch: handler.enableEpochsConfig.SCRSizeInvariantCheckEnableEpoch, + activationEpochName: "SCRSizeInvariantCheckEnableEpoch", }, common.BackwardCompSaveKeyValueFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch < handler.enableEpochsConfig.BackwardCompSaveKeyValueEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.BackwardCompSaveKeyValueEnableEpoch, + activationEpoch: handler.enableEpochsConfig.BackwardCompSaveKeyValueEnableEpoch, + activationEpochName: "BackwardCompSaveKeyValueEnableEpoch", }, common.ESDTNFTCreateOnMultiShardFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.ESDTNFTCreateOnMultiShardEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ESDTNFTCreateOnMultiShardEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ESDTNFTCreateOnMultiShardEnableEpoch, + activationEpochName: "ESDTNFTCreateOnMultiShardEnableEpoch", }, common.MetaESDTSetFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.MetaESDTSetEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.MetaESDTSetEnableEpoch, + activationEpoch: handler.enableEpochsConfig.MetaESDTSetEnableEpoch, + activationEpochName: "MetaESDTSetEnableEpoch", }, common.AddTokensToDelegationFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.AddTokensToDelegationEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.AddTokensToDelegationEnableEpoch, + activationEpoch: handler.enableEpochsConfig.AddTokensToDelegationEnableEpoch, + activationEpochName: "AddTokensToDelegationEnableEpoch", }, common.MultiESDTTransferFixOnCallBackFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.MultiESDTTransferFixOnCallBackOnEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.MultiESDTTransferFixOnCallBackOnEnableEpoch, + activationEpoch: handler.enableEpochsConfig.MultiESDTTransferFixOnCallBackOnEnableEpoch, + activationEpochName: "MultiESDTTransferFixOnCallBackOnEnableEpoch", }, common.OptimizeGasUsedInCrossMiniBlocksFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.OptimizeGasUsedInCrossMiniBlocksEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.OptimizeGasUsedInCrossMiniBlocksEnableEpoch, + activationEpoch: handler.enableEpochsConfig.OptimizeGasUsedInCrossMiniBlocksEnableEpoch, + activationEpochName: "OptimizeGasUsedInCrossMiniBlocksEnableEpoch", }, common.CorrectFirstQueuedFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.CorrectFirstQueuedEpoch }, - activationEpoch: handler.enableEpochsConfig.CorrectFirstQueuedEpoch, + activationEpoch: handler.enableEpochsConfig.CorrectFirstQueuedEpoch, + activationEpochName: "CorrectFirstQueuedEpoch", }, common.DeleteDelegatorAfterClaimRewardsFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.DeleteDelegatorAfterClaimRewardsEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.DeleteDelegatorAfterClaimRewardsEnableEpoch, + activationEpoch: handler.enableEpochsConfig.DeleteDelegatorAfterClaimRewardsEnableEpoch, + activationEpochName: "DeleteDelegatorAfterClaimRewardsEnableEpoch", }, common.RemoveNonUpdatedStorageFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.RemoveNonUpdatedStorageEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.RemoveNonUpdatedStorageEnableEpoch, + activationEpoch: handler.enableEpochsConfig.RemoveNonUpdatedStorageEnableEpoch, + activationEpochName: "RemoveNonUpdatedStorageEnableEpoch", }, common.OptimizeNFTStoreFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.OptimizeNFTStoreEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.OptimizeNFTStoreEnableEpoch, + activationEpoch: handler.enableEpochsConfig.OptimizeNFTStoreEnableEpoch, + activationEpochName: "OptimizeNFTStoreEnableEpoch", }, common.SaveToSystemAccountFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.OptimizeNFTStoreEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.OptimizeNFTStoreEnableEpoch, + activationEpoch: handler.enableEpochsConfig.OptimizeNFTStoreEnableEpoch, + activationEpochName: "OptimizeNFTStoreEnableEpoch", }, common.CheckFrozenCollectionFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.OptimizeNFTStoreEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.OptimizeNFTStoreEnableEpoch, + activationEpoch: handler.enableEpochsConfig.OptimizeNFTStoreEnableEpoch, + activationEpochName: "OptimizeNFTStoreEnableEpoch", }, common.ValueLengthCheckFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.OptimizeNFTStoreEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.OptimizeNFTStoreEnableEpoch, + activationEpoch: handler.enableEpochsConfig.OptimizeNFTStoreEnableEpoch, + activationEpochName: "OptimizeNFTStoreEnableEpoch", }, common.CheckTransferFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.OptimizeNFTStoreEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.OptimizeNFTStoreEnableEpoch, + activationEpoch: handler.enableEpochsConfig.OptimizeNFTStoreEnableEpoch, + activationEpochName: "OptimizeNFTStoreEnableEpoch", }, common.CreateNFTThroughExecByCallerFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.CreateNFTThroughExecByCallerEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.CreateNFTThroughExecByCallerEnableEpoch, + activationEpoch: handler.enableEpochsConfig.CreateNFTThroughExecByCallerEnableEpoch, + activationEpochName: "CreateNFTThroughExecByCallerEnableEpoch", }, common.StopDecreasingValidatorRatingWhenStuckFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.StopDecreasingValidatorRatingWhenStuckEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.StopDecreasingValidatorRatingWhenStuckEnableEpoch, + activationEpoch: handler.enableEpochsConfig.StopDecreasingValidatorRatingWhenStuckEnableEpoch, + activationEpochName: "StopDecreasingValidatorRatingWhenStuckEnableEpoch", }, common.FrontRunningProtectionFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.FrontRunningProtectionEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.FrontRunningProtectionEnableEpoch, + activationEpoch: handler.enableEpochsConfig.FrontRunningProtectionEnableEpoch, + activationEpochName: "FrontRunningProtectionEnableEpoch", }, common.PayableBySCFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.IsPayableBySCEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.IsPayableBySCEnableEpoch, + activationEpoch: handler.enableEpochsConfig.IsPayableBySCEnableEpoch, + activationEpochName: "IsPayableBySCEnableEpoch", }, common.CleanUpInformativeSCRsFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.CleanUpInformativeSCRsEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.CleanUpInformativeSCRsEnableEpoch, + activationEpoch: handler.enableEpochsConfig.CleanUpInformativeSCRsEnableEpoch, + activationEpochName: "CleanUpInformativeSCRsEnableEpoch", }, common.StorageAPICostOptimizationFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.StorageAPICostOptimizationEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.StorageAPICostOptimizationEnableEpoch, + activationEpoch: handler.enableEpochsConfig.StorageAPICostOptimizationEnableEpoch, + activationEpochName: "StorageAPICostOptimizationEnableEpoch", }, common.ESDTRegisterAndSetAllRolesFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.ESDTRegisterAndSetAllRolesEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ESDTRegisterAndSetAllRolesEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ESDTRegisterAndSetAllRolesEnableEpoch, + activationEpochName: "ESDTRegisterAndSetAllRolesEnableEpoch", }, common.ScheduledMiniBlocksFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.ScheduledMiniBlocksEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ScheduledMiniBlocksEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ScheduledMiniBlocksEnableEpoch, + activationEpochName: "ScheduledMiniBlocksEnableEpoch", }, common.CorrectJailedNotUnStakedEmptyQueueFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.CorrectJailedNotUnstakedEmptyQueueEpoch }, - activationEpoch: handler.enableEpochsConfig.CorrectJailedNotUnstakedEmptyQueueEpoch, + activationEpoch: handler.enableEpochsConfig.CorrectJailedNotUnstakedEmptyQueueEpoch, + activationEpochName: "CorrectJailedNotUnstakedEmptyQueueEpoch", }, common.DoNotReturnOldBlockInBlockchainHookFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.DoNotReturnOldBlockInBlockchainHookEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.DoNotReturnOldBlockInBlockchainHookEnableEpoch, + activationEpoch: handler.enableEpochsConfig.DoNotReturnOldBlockInBlockchainHookEnableEpoch, + activationEpochName: "DoNotReturnOldBlockInBlockchainHookEnableEpoch", }, common.AddFailedRelayedTxToInvalidMBsFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch < handler.enableEpochsConfig.AddFailedRelayedTxToInvalidMBsDisableEpoch }, - activationEpoch: handler.enableEpochsConfig.AddFailedRelayedTxToInvalidMBsDisableEpoch, + activationEpoch: handler.enableEpochsConfig.AddFailedRelayedTxToInvalidMBsDisableEpoch, + activationEpochName: "AddFailedRelayedTxToInvalidMBsDisableEpoch", }, common.SCRSizeInvariantOnBuiltInResultFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.SCRSizeInvariantOnBuiltInResultEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.SCRSizeInvariantOnBuiltInResultEnableEpoch, + activationEpoch: handler.enableEpochsConfig.SCRSizeInvariantOnBuiltInResultEnableEpoch, + activationEpochName: "SCRSizeInvariantOnBuiltInResultEnableEpoch", }, common.CheckCorrectTokenIDForTransferRoleFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.CheckCorrectTokenIDForTransferRoleEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.CheckCorrectTokenIDForTransferRoleEnableEpoch, + activationEpoch: handler.enableEpochsConfig.CheckCorrectTokenIDForTransferRoleEnableEpoch, + activationEpochName: "CheckCorrectTokenIDForTransferRoleEnableEpoch", }, common.FailExecutionOnEveryAPIErrorFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.FailExecutionOnEveryAPIErrorEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.FailExecutionOnEveryAPIErrorEnableEpoch, + activationEpoch: handler.enableEpochsConfig.FailExecutionOnEveryAPIErrorEnableEpoch, + activationEpochName: "FailExecutionOnEveryAPIErrorEnableEpoch", }, common.MiniBlockPartialExecutionFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.MiniBlockPartialExecutionEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.MiniBlockPartialExecutionEnableEpoch, + activationEpoch: handler.enableEpochsConfig.MiniBlockPartialExecutionEnableEpoch, + activationEpochName: "MiniBlockPartialExecutionEnableEpoch", }, common.ManagedCryptoAPIsFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.ManagedCryptoAPIsEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ManagedCryptoAPIsEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ManagedCryptoAPIsEnableEpoch, + activationEpochName: "ManagedCryptoAPIsEnableEpoch", }, common.ESDTMetadataContinuousCleanupFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.ESDTMetadataContinuousCleanupEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ESDTMetadataContinuousCleanupEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ESDTMetadataContinuousCleanupEnableEpoch, + activationEpochName: "ESDTMetadataContinuousCleanupEnableEpoch", }, common.FixAsyncCallbackCheckFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.ESDTMetadataContinuousCleanupEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ESDTMetadataContinuousCleanupEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ESDTMetadataContinuousCleanupEnableEpoch, + activationEpochName: "ESDTMetadataContinuousCleanupEnableEpoch", }, common.SendAlwaysFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.ESDTMetadataContinuousCleanupEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ESDTMetadataContinuousCleanupEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ESDTMetadataContinuousCleanupEnableEpoch, + activationEpochName: "ESDTMetadataContinuousCleanupEnableEpoch", }, common.ChangeDelegationOwnerFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.ESDTMetadataContinuousCleanupEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ESDTMetadataContinuousCleanupEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ESDTMetadataContinuousCleanupEnableEpoch, + activationEpochName: "ESDTMetadataContinuousCleanupEnableEpoch", }, common.DisableExecByCallerFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.DisableExecByCallerEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.DisableExecByCallerEnableEpoch, + activationEpoch: handler.enableEpochsConfig.DisableExecByCallerEnableEpoch, + activationEpochName: "DisableExecByCallerEnableEpoch", }, common.RefactorContextFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.RefactorContextEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.RefactorContextEnableEpoch, + activationEpoch: handler.enableEpochsConfig.RefactorContextEnableEpoch, + activationEpochName: "RefactorContextEnableEpoch", }, common.CheckFunctionArgumentFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.CheckFunctionArgumentEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.CheckFunctionArgumentEnableEpoch, + activationEpoch: handler.enableEpochsConfig.CheckFunctionArgumentEnableEpoch, + activationEpochName: "CheckFunctionArgumentEnableEpoch", }, common.CheckExecuteOnReadOnlyFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.CheckExecuteOnReadOnlyEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.CheckExecuteOnReadOnlyEnableEpoch, + activationEpoch: handler.enableEpochsConfig.CheckExecuteOnReadOnlyEnableEpoch, + activationEpochName: "CheckExecuteOnReadOnlyEnableEpoch", }, common.SetSenderInEeiOutputTransferFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.SetSenderInEeiOutputTransferEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.SetSenderInEeiOutputTransferEnableEpoch, + activationEpoch: handler.enableEpochsConfig.SetSenderInEeiOutputTransferEnableEpoch, + activationEpochName: "SetSenderInEeiOutputTransferEnableEpoch", }, common.RefactorPeersMiniBlocksFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.RefactorPeersMiniBlocksEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.RefactorPeersMiniBlocksEnableEpoch, + activationEpoch: handler.enableEpochsConfig.RefactorPeersMiniBlocksEnableEpoch, + activationEpochName: "RefactorPeersMiniBlocksEnableEpoch", }, common.SCProcessorV2Flag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.SCProcessorV2EnableEpoch }, - activationEpoch: handler.enableEpochsConfig.SCProcessorV2EnableEpoch, + activationEpoch: handler.enableEpochsConfig.SCProcessorV2EnableEpoch, + activationEpochName: "SCProcessorV2EnableEpoch", }, common.FixAsyncCallBackArgsListFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.FixAsyncCallBackArgsListEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.FixAsyncCallBackArgsListEnableEpoch, + activationEpoch: handler.enableEpochsConfig.FixAsyncCallBackArgsListEnableEpoch, + activationEpochName: "FixAsyncCallBackArgsListEnableEpoch", }, common.FixOldTokenLiquidityFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.FixOldTokenLiquidityEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.FixOldTokenLiquidityEnableEpoch, + activationEpoch: handler.enableEpochsConfig.FixOldTokenLiquidityEnableEpoch, + activationEpochName: "FixOldTokenLiquidityEnableEpoch", }, common.RuntimeMemStoreLimitFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.RuntimeMemStoreLimitEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.RuntimeMemStoreLimitEnableEpoch, + activationEpoch: handler.enableEpochsConfig.RuntimeMemStoreLimitEnableEpoch, + activationEpochName: "RuntimeMemStoreLimitEnableEpoch", }, common.RuntimeCodeSizeFixFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.RuntimeCodeSizeFixEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.RuntimeCodeSizeFixEnableEpoch, + activationEpoch: handler.enableEpochsConfig.RuntimeCodeSizeFixEnableEpoch, + activationEpochName: "RuntimeCodeSizeFixEnableEpoch", }, common.MaxBlockchainHookCountersFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.MaxBlockchainHookCountersEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.MaxBlockchainHookCountersEnableEpoch, + activationEpoch: handler.enableEpochsConfig.MaxBlockchainHookCountersEnableEpoch, + activationEpochName: "MaxBlockchainHookCountersEnableEpoch", }, common.WipeSingleNFTLiquidityDecreaseFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.WipeSingleNFTLiquidityDecreaseEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.WipeSingleNFTLiquidityDecreaseEnableEpoch, + activationEpoch: handler.enableEpochsConfig.WipeSingleNFTLiquidityDecreaseEnableEpoch, + activationEpochName: "WipeSingleNFTLiquidityDecreaseEnableEpoch", }, common.AlwaysSaveTokenMetaDataFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.AlwaysSaveTokenMetaDataEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.AlwaysSaveTokenMetaDataEnableEpoch, + activationEpoch: handler.enableEpochsConfig.AlwaysSaveTokenMetaDataEnableEpoch, + activationEpochName: "AlwaysSaveTokenMetaDataEnableEpoch", }, common.SetGuardianFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.SetGuardianEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.SetGuardianEnableEpoch, + activationEpoch: handler.enableEpochsConfig.SetGuardianEnableEpoch, + activationEpochName: "SetGuardianEnableEpoch", }, common.RelayedNonceFixFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.RelayedNonceFixEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.RelayedNonceFixEnableEpoch, + activationEpoch: handler.enableEpochsConfig.RelayedNonceFixEnableEpoch, + activationEpochName: "RelayedNonceFixEnableEpoch", }, common.ConsistentTokensValuesLengthCheckFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.ConsistentTokensValuesLengthCheckEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ConsistentTokensValuesLengthCheckEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ConsistentTokensValuesLengthCheckEnableEpoch, + activationEpochName: "ConsistentTokensValuesLengthCheckEnableEpoch", }, common.KeepExecOrderOnCreatedSCRsFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.KeepExecOrderOnCreatedSCRsEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.KeepExecOrderOnCreatedSCRsEnableEpoch, + activationEpoch: handler.enableEpochsConfig.KeepExecOrderOnCreatedSCRsEnableEpoch, + activationEpochName: "KeepExecOrderOnCreatedSCRsEnableEpoch", }, common.MultiClaimOnDelegationFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.MultiClaimOnDelegationEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.MultiClaimOnDelegationEnableEpoch, + activationEpoch: handler.enableEpochsConfig.MultiClaimOnDelegationEnableEpoch, + activationEpochName: "MultiClaimOnDelegationEnableEpoch", }, common.ChangeUsernameFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.ChangeUsernameEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ChangeUsernameEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ChangeUsernameEnableEpoch, + activationEpochName: "ChangeUsernameEnableEpoch", }, common.AutoBalanceDataTriesFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.AutoBalanceDataTriesEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.AutoBalanceDataTriesEnableEpoch, + activationEpoch: handler.enableEpochsConfig.AutoBalanceDataTriesEnableEpoch, + activationEpochName: "AutoBalanceDataTriesEnableEpoch", }, common.MigrateDataTrieFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.MigrateDataTrieEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.MigrateDataTrieEnableEpoch, + activationEpoch: handler.enableEpochsConfig.MigrateDataTrieEnableEpoch, + activationEpochName: "MigrateDataTrieEnableEpoch", }, common.FixDelegationChangeOwnerOnAccountFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.FixDelegationChangeOwnerOnAccountEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.FixDelegationChangeOwnerOnAccountEnableEpoch, + activationEpoch: handler.enableEpochsConfig.FixDelegationChangeOwnerOnAccountEnableEpoch, + activationEpochName: "FixDelegationChangeOwnerOnAccountEnableEpoch", }, common.FixOOGReturnCodeFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.FixOOGReturnCodeEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.FixOOGReturnCodeEnableEpoch, + activationEpoch: handler.enableEpochsConfig.FixOOGReturnCodeEnableEpoch, + activationEpochName: "FixOOGReturnCodeEnableEpoch", }, common.DeterministicSortOnValidatorsInfoFixFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.DeterministicSortOnValidatorsInfoEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.DeterministicSortOnValidatorsInfoEnableEpoch, + activationEpoch: handler.enableEpochsConfig.DeterministicSortOnValidatorsInfoEnableEpoch, + activationEpochName: "DeterministicSortOnValidatorsInfoEnableEpoch", }, common.DynamicGasCostForDataTrieStorageLoadFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.DynamicGasCostForDataTrieStorageLoadEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.DynamicGasCostForDataTrieStorageLoadEnableEpoch, + activationEpoch: handler.enableEpochsConfig.DynamicGasCostForDataTrieStorageLoadEnableEpoch, + activationEpochName: "DynamicGasCostForDataTrieStorageLoadEnableEpoch", }, common.ScToScLogEventFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.ScToScLogEventEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ScToScLogEventEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ScToScLogEventEnableEpoch, + activationEpochName: "ScToScLogEventEnableEpoch", }, common.BlockGasAndFeesReCheckFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.BlockGasAndFeesReCheckEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.BlockGasAndFeesReCheckEnableEpoch, + activationEpoch: handler.enableEpochsConfig.BlockGasAndFeesReCheckEnableEpoch, + activationEpochName: "BlockGasAndFeesReCheckEnableEpoch", }, common.BalanceWaitingListsFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.BalanceWaitingListsEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.BalanceWaitingListsEnableEpoch, + activationEpoch: handler.enableEpochsConfig.BalanceWaitingListsEnableEpoch, + activationEpochName: "BalanceWaitingListsEnableEpoch", }, common.NFTStopCreateFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.NFTStopCreateEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.NFTStopCreateEnableEpoch, + activationEpoch: handler.enableEpochsConfig.NFTStopCreateEnableEpoch, + activationEpochName: "NFTStopCreateEnableEpoch", }, common.FixGasRemainingForSaveKeyValueFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.FixGasRemainingForSaveKeyValueBuiltinFunctionEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.FixGasRemainingForSaveKeyValueBuiltinFunctionEnableEpoch, + activationEpoch: handler.enableEpochsConfig.FixGasRemainingForSaveKeyValueBuiltinFunctionEnableEpoch, + activationEpochName: "FixGasRemainingForSaveKeyValueBuiltinFunctionEnableEpoch", }, common.IsChangeOwnerAddressCrossShardThroughSCFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.ChangeOwnerAddressCrossShardThroughSCEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ChangeOwnerAddressCrossShardThroughSCEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ChangeOwnerAddressCrossShardThroughSCEnableEpoch, + activationEpochName: "ChangeOwnerAddressCrossShardThroughSCEnableEpoch", }, common.CurrentRandomnessOnSortingFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.CurrentRandomnessOnSortingEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.CurrentRandomnessOnSortingEnableEpoch, + activationEpoch: handler.enableEpochsConfig.CurrentRandomnessOnSortingEnableEpoch, + activationEpochName: "CurrentRandomnessOnSortingEnableEpoch", }, common.StakeLimitsFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.StakeLimitsEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.StakeLimitsEnableEpoch, + activationEpoch: handler.enableEpochsConfig.StakeLimitsEnableEpoch, + activationEpochName: "StakeLimitsEnableEpoch", }, common.StakingV4Step1Flag: { isActiveInEpoch: func(epoch uint32) bool { return epoch == handler.enableEpochsConfig.StakingV4Step1EnableEpoch }, - activationEpoch: handler.enableEpochsConfig.StakingV4Step1EnableEpoch, + activationEpoch: handler.enableEpochsConfig.StakingV4Step1EnableEpoch, + activationEpochName: "StakingV4Step1EnableEpoch", }, common.StakingV4Step2Flag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.StakingV4Step2EnableEpoch }, - activationEpoch: handler.enableEpochsConfig.StakingV4Step2EnableEpoch, + activationEpoch: handler.enableEpochsConfig.StakingV4Step2EnableEpoch, + activationEpochName: "StakingV4Step2EnableEpoch", }, common.StakingV4Step3Flag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.StakingV4Step3EnableEpoch }, - activationEpoch: handler.enableEpochsConfig.StakingV4Step3EnableEpoch, + activationEpoch: handler.enableEpochsConfig.StakingV4Step3EnableEpoch, + activationEpochName: "StakingV4Step3EnableEpoch", }, common.CleanupAuctionOnLowWaitingListFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.CleanupAuctionOnLowWaitingListEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.CleanupAuctionOnLowWaitingListEnableEpoch, + activationEpoch: handler.enableEpochsConfig.CleanupAuctionOnLowWaitingListEnableEpoch, + activationEpochName: "CleanupAuctionOnLowWaitingListEnableEpoch", }, common.StakingV4StartedFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.StakingV4Step1EnableEpoch }, - activationEpoch: handler.enableEpochsConfig.StakingV4Step1EnableEpoch, + activationEpoch: handler.enableEpochsConfig.StakingV4Step1EnableEpoch, + activationEpochName: "StakingV4Step1EnableEpoch", }, common.AlwaysMergeContextsInEEIFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.AlwaysMergeContextsInEEIEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.AlwaysMergeContextsInEEIEnableEpoch, + activationEpoch: handler.enableEpochsConfig.AlwaysMergeContextsInEEIEnableEpoch, + activationEpochName: "AlwaysMergeContextsInEEIEnableEpoch", }, common.UseGasBoundedShouldFailExecutionFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.UseGasBoundedShouldFailExecutionEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.UseGasBoundedShouldFailExecutionEnableEpoch, + activationEpoch: handler.enableEpochsConfig.UseGasBoundedShouldFailExecutionEnableEpoch, + activationEpochName: "UseGasBoundedShouldFailExecutionEnableEpoch", }, common.DynamicESDTFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.DynamicESDTEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.DynamicESDTEnableEpoch, + activationEpoch: handler.enableEpochsConfig.DynamicESDTEnableEpoch, + activationEpochName: "DynamicESDTEnableEpoch", }, common.EGLDInESDTMultiTransferFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.EGLDInMultiTransferEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.EGLDInMultiTransferEnableEpoch, + activationEpoch: handler.enableEpochsConfig.EGLDInMultiTransferEnableEpoch, + activationEpochName: "EGLDInMultiTransferEnableEpoch", }, common.CryptoOpcodesV2Flag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.CryptoOpcodesV2EnableEpoch }, - activationEpoch: handler.enableEpochsConfig.CryptoOpcodesV2EnableEpoch, + activationEpoch: handler.enableEpochsConfig.CryptoOpcodesV2EnableEpoch, + activationEpochName: "CryptoOpcodesV2EnableEpoch", }, common.UnJailCleanupFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.UnJailCleanupEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.UnJailCleanupEnableEpoch, + activationEpoch: handler.enableEpochsConfig.UnJailCleanupEnableEpoch, + activationEpochName: "UnJailCleanupEnableEpoch", }, common.FixRelayedBaseCostFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.FixRelayedBaseCostEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.FixRelayedBaseCostEnableEpoch, + activationEpoch: handler.enableEpochsConfig.FixRelayedBaseCostEnableEpoch, + activationEpochName: "FixRelayedBaseCostEnableEpoch", }, common.MultiESDTNFTTransferAndExecuteByUserFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.MultiESDTNFTTransferAndExecuteByUserEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.MultiESDTNFTTransferAndExecuteByUserEnableEpoch, + activationEpoch: handler.enableEpochsConfig.MultiESDTNFTTransferAndExecuteByUserEnableEpoch, + activationEpochName: "MultiESDTNFTTransferAndExecuteByUserEnableEpoch", }, common.FixRelayedMoveBalanceToNonPayableSCFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.FixRelayedMoveBalanceToNonPayableSCEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.FixRelayedMoveBalanceToNonPayableSCEnableEpoch, + activationEpoch: handler.enableEpochsConfig.FixRelayedMoveBalanceToNonPayableSCEnableEpoch, + activationEpochName: "FixRelayedMoveBalanceToNonPayableSCEnableEpoch", }, common.RelayedTransactionsV3Flag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.RelayedTransactionsV3EnableEpoch }, - activationEpoch: handler.enableEpochsConfig.RelayedTransactionsV3EnableEpoch, + activationEpoch: handler.enableEpochsConfig.RelayedTransactionsV3EnableEpoch, + activationEpochName: "RelayedTransactionsV3EnableEpoch", }, common.RelayedTransactionsV3FixESDTTransferFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.RelayedTransactionsV3FixESDTTransferEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.RelayedTransactionsV3FixESDTTransferEnableEpoch, + activationEpoch: handler.enableEpochsConfig.RelayedTransactionsV3FixESDTTransferEnableEpoch, + activationEpochName: "RelayedTransactionsV3FixESDTTransferEnableEpoch", }, common.AndromedaFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.AndromedaEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.AndromedaEnableEpoch, + activationEpoch: handler.enableEpochsConfig.AndromedaEnableEpoch, + activationEpochName: "AndromedaEnableEpoch", + }, + common.SupernovaFlag: { + isActiveInEpoch: func(epoch uint32) bool { + return epoch >= handler.enableEpochsConfig.SupernovaEnableEpoch + }, + activationEpoch: handler.enableEpochsConfig.SupernovaEnableEpoch, + activationEpochName: "SupernovaEnableEpoch", }, // TODO: move it to activation round common.CheckBuiltInCallOnTransferValueAndFailExecutionFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.CheckBuiltInCallOnTransferValueAndFailEnableRound }, - activationEpoch: handler.enableEpochsConfig.CheckBuiltInCallOnTransferValueAndFailEnableRound, + activationEpoch: handler.enableEpochsConfig.CheckBuiltInCallOnTransferValueAndFailEnableRound, + activationEpochName: "CheckBuiltInCallOnTransferValueAndFailEnableRound", }, common.AutomaticActivationOfNodesDisableFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.AutomaticActivationOfNodesDisableEpoch }, - activationEpoch: handler.enableEpochsConfig.AutomaticActivationOfNodesDisableEpoch, + activationEpoch: handler.enableEpochsConfig.AutomaticActivationOfNodesDisableEpoch, + activationEpochName: "AutomaticActivationOfNodesDisableEpoch", }, common.MaskInternalDependenciesErrorsFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.MaskVMInternalDependenciesErrorsEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.MaskVMInternalDependenciesErrorsEnableEpoch, + activationEpoch: handler.enableEpochsConfig.MaskVMInternalDependenciesErrorsEnableEpoch, + activationEpochName: "MaskVMInternalDependenciesErrorsEnableEpoch", }, common.FixBackTransferOPCODEFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.FixBackTransferOPCODEEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.FixBackTransferOPCODEEnableEpoch, + activationEpoch: handler.enableEpochsConfig.FixBackTransferOPCODEEnableEpoch, + activationEpochName: "FixBackTransferOPCODEEnableEpoch", }, common.ValidationOnGobDecodeFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.ValidationOnGobDecodeEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.ValidationOnGobDecodeEnableEpoch, + activationEpoch: handler.enableEpochsConfig.ValidationOnGobDecodeEnableEpoch, + activationEpochName: "ValidationOnGobDecodeEnableEpoch", }, common.BarnardOpcodesFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.BarnardOpcodesEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.BarnardOpcodesEnableEpoch, + activationEpoch: handler.enableEpochsConfig.BarnardOpcodesEnableEpoch, + activationEpochName: "BarnardOpcodesEnableEpoch", }, common.FixGetBalanceFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.FixGetBalanceEnableEpoch }, - activationEpoch: handler.enableEpochsConfig.FixGetBalanceEnableEpoch, + activationEpoch: handler.enableEpochsConfig.FixGetBalanceEnableEpoch, + activationEpochName: "FixGetBalanceEnableEpoch", }, common.RelayedTransactionsV1V2DisableFlag: { isActiveInEpoch: func(epoch uint32) bool { return epoch >= handler.enableEpochsConfig.RelayedTransactionsV1V2DisableEpoch }, - activationEpoch: handler.enableEpochsConfig.RelayedTransactionsV1V2DisableEpoch, + activationEpoch: handler.enableEpochsConfig.RelayedTransactionsV1V2DisableEpoch, + activationEpochName: "RelayedTransactionsV1V2DisableEpoch", }, } } @@ -937,6 +1080,15 @@ func (handler *enableEpochsHandler) StakingV4Step1EnableEpoch() uint32 { return handler.enableEpochsConfig.StakingV4Step1EnableEpoch } +// GetAllEnableEpochs returns all flags with their activation epochs +func (handler *enableEpochsHandler) GetAllEnableEpochs() map[string]uint32 { + result := make(map[string]uint32, len(handler.allFlagsDefined)) + for _, fh := range handler.allFlagsDefined { + result[fh.activationEpochName] = fh.activationEpoch + } + return result +} + // IsInterfaceNil returns true if there is no value under the interface func (handler *enableEpochsHandler) IsInterfaceNil() bool { return handler == nil diff --git a/common/enablers/enableEpochsHandler_test.go b/common/enablers/enableEpochsHandler_test.go index 5f93bfadf91..ae4c01dfa17 100644 --- a/common/enablers/enableEpochsHandler_test.go +++ b/common/enablers/enableEpochsHandler_test.go @@ -1,7 +1,9 @@ package enablers import ( + "fmt" "math" + "reflect" "testing" "github.com/multiversx/mx-chain-core-go/core/check" @@ -71,7 +73,6 @@ func createEnableEpochsConfig() config.EnableEpochs { IsPayableBySCEnableEpoch: 52, CleanUpInformativeSCRsEnableEpoch: 53, StorageAPICostOptimizationEnableEpoch: 54, - TransformToMultiShardCreateEnableEpoch: 55, ESDTRegisterAndSetAllRolesEnableEpoch: 56, ScheduledMiniBlocksEnableEpoch: 57, CorrectJailedNotUnstakedEmptyQueueEpoch: 58, @@ -135,6 +136,7 @@ func createEnableEpochsConfig() config.EnableEpochs { BarnardOpcodesEnableEpoch: 116, AutomaticActivationOfNodesDisableEpoch: 117, RelayedTransactionsV1V2DisableEpoch: 118, + SupernovaEnableEpoch: 119, } } @@ -342,6 +344,7 @@ func TestEnableEpochsHandler_IsFlagEnabled(t *testing.T) { require.True(t, handler.IsFlagEnabled(common.AndromedaFlag)) require.True(t, handler.IsFlagEnabled(common.DynamicESDTFlag)) require.True(t, handler.IsFlagEnabled(common.RelayedTransactionsV1V2DisableFlag)) + require.True(t, handler.IsFlagEnabled(common.SupernovaFlag)) } func TestEnableEpochsHandler_GetActivationEpoch(t *testing.T) { @@ -478,6 +481,32 @@ func TestEnableEpochsHandler_GetActivationEpoch(t *testing.T) { require.Equal(t, cfg.AutomaticActivationOfNodesDisableEpoch, handler.GetActivationEpoch(common.AutomaticActivationOfNodesDisableFlag)) require.Equal(t, cfg.FixGetBalanceEnableEpoch, handler.GetActivationEpoch(common.FixGetBalanceFlag)) require.Equal(t, cfg.RelayedTransactionsV1V2DisableEpoch, handler.GetActivationEpoch(common.RelayedTransactionsV1V2DisableFlag)) + require.Equal(t, cfg.SupernovaEnableEpoch, handler.GetActivationEpoch(common.SupernovaFlag)) +} + +func TestEnableEpochsHandler_GetAllEnableEpochs(t *testing.T) { + t.Parallel() + + cfg := createEnableEpochsConfig() + handler, _ := NewEnableEpochsHandler(cfg, &epochNotifier.EpochNotifierStub{}) + require.NotNil(t, handler) + + allEpochs := handler.GetAllEnableEpochs() + typeOfCfg := reflect.TypeOf(cfg) + valueOfCfg := reflect.ValueOf(cfg) + for i := 0; i < typeOfCfg.NumField(); i++ { + field := typeOfCfg.Field(i) + value := valueOfCfg.Field(i) + if field.Name == "MaxNodesChangeEnableEpoch" || + field.Name == "BLSMultiSignerEnableEpoch" { + // slices, ignoring + continue + } + + epoch, exists := allEpochs[field.Name] + require.True(t, exists, fmt.Sprintf("could not find: %s", field.Name)) + require.Equal(t, value.Uint(), uint64(epoch)) + } } func TestEnableEpochsHandler_IsInterfaceNil(t *testing.T) { diff --git a/common/enablers/enableRoundsHandler.go b/common/enablers/enableRoundsHandler.go index 29e55dd2b57..12e6d1f717e 100644 --- a/common/enablers/enableRoundsHandler.go +++ b/common/enablers/enableRoundsHandler.go @@ -2,10 +2,12 @@ package enablers import ( "fmt" + "runtime/debug" "strconv" "strings" + "sync" - "github.com/multiversx/mx-chain-core-go/core/atomic" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/process" ) @@ -13,27 +15,34 @@ import ( const ( conversionBase = 10 bitConversionSize = 64 - - disableAsyncCallV1 = "DisableAsyncCallV1" ) +type roundFlag struct { + round uint64 + options []string +} + +type flagEnabledInRound = func(round uint64) bool + +type roundFlagHandler struct { + isActiveInRound flagEnabledInRound + activationRound uint64 +} + type enableRoundsHandler struct { - *roundFlagsHolder + allFlagsDefined map[common.EnableRoundFlag]roundFlagHandler + currentRound uint64 + roundMut sync.RWMutex } // NewEnableRoundsHandler creates a new enable rounds handler instance -func NewEnableRoundsHandler(args config.RoundConfig, roundNotifier process.RoundNotifier) (*enableRoundsHandler, error) { - disableAsyncCallV1, err := getRoundConfig(args, disableAsyncCallV1) +func NewEnableRoundsHandler(roundsConfig config.RoundConfig, roundNotifier process.RoundNotifier) (*enableRoundsHandler, error) { + handler := &enableRoundsHandler{} + err := handler.createAllFlagsMap(roundsConfig) if err != nil { return nil, err } - handler := &enableRoundsHandler{ - roundFlagsHolder: &roundFlagsHolder{ - disableAsyncCallV1: disableAsyncCallV1, - }, - } - roundNotifier.RegisterNotifyHandler(handler) return handler, nil @@ -56,15 +65,119 @@ func getRoundConfig(args config.RoundConfig, configName string) (*roundFlag, err "options", fmt.Sprintf("[%s]", strings.Join(activationRound.Options, ", "))) return &roundFlag{ - Flag: &atomic.Flag{}, round: round, options: activationRound.Options, }, nil } +func (handler *enableRoundsHandler) createAllFlagsMap(conf config.RoundConfig) error { + disableAsyncCallV1, err := getRoundConfig(conf, string(common.DisableAsyncCallV1Flag)) + if err != nil { + return fmt.Errorf("%w while trying to get round config for %s", err, string(common.DisableAsyncCallV1Flag)) + } + + supernovaEnableRound, err := getRoundConfig(conf, string(common.SupernovaRoundFlag)) + if err != nil { + return fmt.Errorf("%w while trying to get round config for %s", err, string(common.SupernovaRoundFlag)) + } + + handler.allFlagsDefined = map[common.EnableRoundFlag]roundFlagHandler{ + common.DisableAsyncCallV1Flag: { + isActiveInRound: func(round uint64) bool { + return round >= disableAsyncCallV1.round + }, + activationRound: disableAsyncCallV1.round, + }, + common.SupernovaRoundFlag: { + isActiveInRound: func(round uint64) bool { + return round >= supernovaEnableRound.round + }, + activationRound: supernovaEnableRound.round, + }, + } + + return nil +} + // RoundConfirmed is called whenever a new round is confirmed -func (handler *enableRoundsHandler) RoundConfirmed(round uint64, timestamp uint64) { - handler.disableAsyncCallV1.SetValue(handler.disableAsyncCallV1.round <= round) +func (handler *enableRoundsHandler) RoundConfirmed(round uint64, _ uint64) { + handler.roundMut.Lock() + handler.currentRound = round + handler.roundMut.Unlock() +} + +// GetCurrentRound returns the current round +func (handler *enableRoundsHandler) GetCurrentRound() uint64 { + handler.roundMut.RLock() + currentRound := handler.currentRound + handler.roundMut.RUnlock() + + return currentRound +} + +// IsFlagDefined returns true if the provided flag is enabled in the current round +func (handler *enableRoundsHandler) IsFlagDefined(flag common.EnableRoundFlag) bool { + _, found := handler.allFlagsDefined[flag] + if found { + return true + } + + log.Warn("flag is not defined", + "flag", flag, + "stack trace", string(debug.Stack()), + ) + + return false +} + +// IsFlagEnabled returns true if the provided flag is enabled in the current round +func (handler *enableRoundsHandler) IsFlagEnabled(flag common.EnableRoundFlag) bool { + handler.roundMut.RLock() + currentRound := handler.currentRound + handler.roundMut.RUnlock() + + return handler.IsFlagEnabledInRound(flag, currentRound) +} + +// IsFlagEnabledInRound returns true if the provided flag is enabled in the provided round +func (handler *enableRoundsHandler) IsFlagEnabledInRound(flag common.EnableRoundFlag, round uint64) bool { + fh, found := handler.allFlagsDefined[flag] + if !found { + log.Warn("IsFlagEnabledInRound: got unknown flag", + "flag", flag, + "round", round, + "stack trace", string(debug.Stack()), + ) + + return false + } + + return fh.isActiveInRound(round) +} + +// GetActivationRound returns the activation round of the provided flag +func (handler *enableRoundsHandler) GetActivationRound(flag common.EnableRoundFlag) uint64 { + fh, found := handler.allFlagsDefined[flag] + if !found { + log.Warn("GetActivationRound: got unknown flag", + "flag", flag, + "stack trace", string(debug.Stack()), + ) + + return 0 + } + + return fh.activationRound +} + +// GetAllEnableRounds returns a map of all enable round flags with their activation rounds +func (handler *enableRoundsHandler) GetAllEnableRounds() map[string]uint64 { + result := make(map[string]uint64, len(handler.allFlagsDefined)) + for flag, flagHandler := range handler.allFlagsDefined { + result[string(flag)] = flagHandler.activationRound + } + + return result } // IsInterfaceNil returns true if there is no value under the interface diff --git a/common/enablers/enableRoundsHandler_test.go b/common/enablers/enableRoundsHandler_test.go index 0af63708198..79dbf7cccf0 100644 --- a/common/enablers/enableRoundsHandler_test.go +++ b/common/enablers/enableRoundsHandler_test.go @@ -2,15 +2,33 @@ package enablers import ( "errors" + "math" "strings" "testing" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +func createEnableRoundsConfig() config.RoundConfig { + return config.RoundConfig{ + RoundActivations: map[string]config.ActivationRoundByName{ + string(common.DisableAsyncCallV1Flag): { + Round: "1", + Options: nil, + }, + string(common.SupernovaRoundFlag): { + Round: "2", + Options: nil, + }, + }, + } +} + func TestNewEnableRoundsHandler(t *testing.T) { t.Parallel() @@ -24,14 +42,19 @@ func TestNewEnableRoundsHandler(t *testing.T) { assert.True(t, check.IfNil(handler)) assert.True(t, errors.Is(err, errMissingRoundActivation)) - assert.True(t, strings.Contains(err.Error(), disableAsyncCallV1)) + assert.True(t, strings.Contains(err.Error(), string(common.DisableAsyncCallV1Flag))) }) + t.Run("invalid round string", func(t *testing.T) { t.Parallel() cfg := config.RoundConfig{ RoundActivations: map[string]config.ActivationRoundByName{ - disableAsyncCallV1: { + string(common.DisableAsyncCallV1Flag): { + Round: "0", + Options: []string{"string 1", "string 2"}, + }, + string(common.SupernovaRoundFlag): { Round: "[invalid round]", Options: []string{"string 1", "string 2"}, }, @@ -45,12 +68,17 @@ func TestNewEnableRoundsHandler(t *testing.T) { assert.True(t, strings.Contains(err.Error(), "invalid syntax while trying to convert")) assert.True(t, strings.Contains(err.Error(), "[invalid round]")) }) + t.Run("should work: round 0", func(t *testing.T) { t.Parallel() cfg := config.RoundConfig{ RoundActivations: map[string]config.ActivationRoundByName{ - disableAsyncCallV1: { + string(common.DisableAsyncCallV1Flag): { + Round: "0", + Options: []string{"string 1", "string 2"}, + }, + string(common.SupernovaRoundFlag): { Round: "0", Options: []string{"string 1", "string 2"}, }, @@ -58,80 +86,98 @@ func TestNewEnableRoundsHandler(t *testing.T) { } handler, err := NewEnableRoundsHandler(cfg, &epochNotifier.RoundNotifierStub{}) - - assert.False(t, check.IfNil(handler)) assert.Nil(t, err) + assert.False(t, check.IfNil(handler)) }) + t.Run("should work: round non-zero", func(t *testing.T) { t.Parallel() - cfg := config.RoundConfig{ - RoundActivations: map[string]config.ActivationRoundByName{ - disableAsyncCallV1: { - Round: "445", - Options: nil, - }, - }, - } - + cfg := createEnableRoundsConfig() handler, err := NewEnableRoundsHandler(cfg, &epochNotifier.RoundNotifierStub{}) - - assert.False(t, check.IfNil(handler)) assert.Nil(t, err) + assert.False(t, check.IfNil(handler)) }) } -func TestFlagsHolder_DisableAsyncCallV1Enabled(t *testing.T) { +func TestEnableRoundsHandler_GetCurrentRound(t *testing.T) { t.Parallel() - t.Run("should work: config round 0", func(t *testing.T) { - t.Parallel() + cfg := createEnableRoundsConfig() + handler, _ := NewEnableRoundsHandler(cfg, &epochNotifier.RoundNotifierStub{}) + require.NotNil(t, handler) - cfg := config.RoundConfig{ - RoundActivations: map[string]config.ActivationRoundByName{ - disableAsyncCallV1: { - Round: "0", - Options: nil, - }, - }, - } + currentRound := uint64(123) + handler.RoundConfirmed(currentRound, 0) - handler, _ := NewEnableRoundsHandler(cfg, &epochNotifier.RoundNotifierStub{}) - assert.True(t, handler.IsDisableAsyncCallV1Enabled()) // check round not called + require.Equal(t, currentRound, handler.GetCurrentRound()) +} - handler.RoundConfirmed(0, 0) - assert.True(t, handler.IsDisableAsyncCallV1Enabled()) +func TestEnableRoundsHandler_IsFlagDefined(t *testing.T) { + t.Parallel() - handler.RoundConfirmed(1, 0) - assert.True(t, handler.IsDisableAsyncCallV1Enabled()) - }) - t.Run("should work: config round 1", func(t *testing.T) { - t.Parallel() + cfg := createEnableRoundsConfig() + handler, _ := NewEnableRoundsHandler(cfg, &epochNotifier.RoundNotifierStub{}) + require.NotNil(t, handler) - cfg := config.RoundConfig{ - RoundActivations: map[string]config.ActivationRoundByName{ - disableAsyncCallV1: { - Round: "1", - Options: nil, - }, - }, - } + require.True(t, handler.IsFlagDefined(common.SupernovaRoundFlag)) + require.False(t, handler.IsFlagDefined("new flag")) +} + +func TestEnableRoundsHandler_IsFlagEnabledInRound(t *testing.T) { + t.Parallel() - handler, _ := NewEnableRoundsHandler(cfg, &epochNotifier.RoundNotifierStub{}) - assert.False(t, handler.IsDisableAsyncCallV1Enabled()) // check round not called - handler.RoundConfirmed(0, 0) - assert.False(t, handler.IsDisableAsyncCallV1Enabled()) + cfg := createEnableRoundsConfig() + handler, _ := NewEnableRoundsHandler(cfg, &epochNotifier.RoundNotifierStub{}) + require.NotNil(t, handler) - handler.RoundConfirmed(1, 0) - assert.True(t, handler.IsDisableAsyncCallV1Enabled()) + require.False(t, handler.IsFlagEnabledInRound("not defined", 10)) - handler.RoundConfirmed(2, 0) - assert.True(t, handler.IsDisableAsyncCallV1Enabled()) + require.True(t, handler.IsFlagEnabledInRound(common.SupernovaRoundFlag, 2)) + require.True(t, handler.IsFlagEnabledInRound(common.SupernovaRoundFlag, 3)) + require.False(t, handler.IsFlagEnabledInRound(common.SupernovaRoundFlag, 1)) +} - handler.RoundConfirmed(0, 0) - assert.False(t, handler.IsDisableAsyncCallV1Enabled()) +func TestEnableRoundsHandler_IsFlagEnabled(t *testing.T) { + t.Parallel() - handler.RoundConfirmed(2, 0) - assert.True(t, handler.IsDisableAsyncCallV1Enabled()) - }) + cfg := createEnableRoundsConfig() + handler, _ := NewEnableRoundsHandler(cfg, &epochNotifier.RoundNotifierStub{}) + require.NotNil(t, handler) + + handler.RoundConfirmed(0, 0) + + require.False(t, handler.IsFlagEnabled(common.DisableAsyncCallV1Flag)) + require.False(t, handler.IsFlagEnabled(common.SupernovaRoundFlag)) + + handler.RoundConfirmed(math.MaxUint32, 0) + + require.True(t, handler.IsFlagEnabled(common.DisableAsyncCallV1Flag)) + require.True(t, handler.IsFlagEnabled(common.SupernovaRoundFlag)) +} + +func TestEnableRoundsHandler_GetAllEnableRounds(t *testing.T) { + t.Parallel() + + cfg := createEnableRoundsConfig() + handler, _ := NewEnableRoundsHandler(cfg, &epochNotifier.RoundNotifierStub{}) + require.NotNil(t, handler) + + result := handler.GetAllEnableRounds() + require.Equal(t, 2, len(result)) + require.Equal(t, uint64(1), result[string(common.DisableAsyncCallV1Flag)]) + require.Equal(t, uint64(2), result[string(common.SupernovaRoundFlag)]) +} + +func TestEnableRoundsHandler_GetActivationRound(t *testing.T) { + t.Parallel() + + cfg := createEnableRoundsConfig() + handler, _ := NewEnableRoundsHandler(cfg, &epochNotifier.RoundNotifierStub{}) + require.NotNil(t, handler) + + require.Equal(t, uint64(0), handler.GetActivationRound("not defined")) + + require.Equal(t, uint64(1), handler.GetActivationRound(common.DisableAsyncCallV1Flag)) + require.Equal(t, uint64(2), handler.GetActivationRound(common.SupernovaRoundFlag)) } diff --git a/common/enablers/roundFlag.go b/common/enablers/roundFlag.go deleted file mode 100644 index 2d8e90b18f7..00000000000 --- a/common/enablers/roundFlag.go +++ /dev/null @@ -1,11 +0,0 @@ -package enablers - -import ( - "github.com/multiversx/mx-chain-core-go/core/atomic" -) - -type roundFlag struct { - *atomic.Flag - round uint64 - options []string -} diff --git a/common/enablers/roundFlags.go b/common/enablers/roundFlags.go deleted file mode 100644 index b2501abccbc..00000000000 --- a/common/enablers/roundFlags.go +++ /dev/null @@ -1,10 +0,0 @@ -package enablers - -type roundFlagsHolder struct { - disableAsyncCallV1 *roundFlag -} - -// IsDisableAsyncCallV1Enabled returns true for the round where async calls will be disabled in processor v1 -func (holder *roundFlagsHolder) IsDisableAsyncCallV1Enabled() bool { - return holder.disableAsyncCallV1.IsSet() -} diff --git a/common/errors.go b/common/errors.go index 3be75377813..c436b184d86 100644 --- a/common/errors.go +++ b/common/errors.go @@ -23,6 +23,12 @@ var ErrAlreadyExistingEquivalentProof = errors.New("already existing equivalent // ErrNilHeaderHandler signals that a nil header handler has been provided var ErrNilHeaderHandler = errors.New("nil header handler") +// ErrNilHeaderHash is raised when a nil header hash is provided +var ErrNilHeaderHash = errors.New("header hash is nil") + +// ErrInvalidHeaderHash signals that an invalid header hash has been provided +var ErrInvalidHeaderHash = errors.New("invalid header hash") + // ErrNotEnoughSignatures defines the error for not enough signatures var ErrNotEnoughSignatures = errors.New("not enough signatures") @@ -34,3 +40,39 @@ var ErrInvalidHashShardKey = errors.New("invalid hash shard key") // ErrInvalidNonceShardKey signals that the provided nonce-shard key is invalid var ErrInvalidNonceShardKey = errors.New("invalid nonce shard key") + +// ErrNilCommonConfigsHandler signals that a nil common configs handler has been provided +var ErrNilCommonConfigsHandler = errors.New("nil common configs handler") + +// ErrWrongTypeAssertion signals that a type assertion failed +var ErrWrongTypeAssertion = errors.New("wrong type assertion") + +// ErrNilBaseExecutionResult signals that a nil base execution result has been provided +var ErrNilBaseExecutionResult = errors.New("nil base execution result") + +// ErrNilLastExecutionResultHandler signals that a nil last execution result handler has been provided +var ErrNilLastExecutionResultHandler = errors.New("nil last execution result handler") + +// ErrMissingHeader signals that header of the block is missing +var ErrMissingHeader = errors.New("missing header") + +// ErrMissingMiniBlock signals that mini block is missing +var ErrMissingMiniBlock = errors.New("missing mini block") + +// ErrMissingCachedTransactions signals that cached transactions are missing +var ErrMissingCachedTransactions = errors.New("missing cached transactions") + +// ErrMissingCachedLogs signals that cached logs events are missing +var ErrMissingCachedLogs = errors.New("missing cached logs") + +// ErrMissingOrderedTxHashes signals that ordered tx hashes are missing +var ErrMissingOrderedTxHashes = errors.New("missing ordered tx hashes") + +// ErrInvalidHeader signals that an invalid header has been provided +var ErrInvalidHeader = errors.New("invalid header") + +// ErrMissingUnexecutableTxHash signals that unexecutable tx hashes are missing +var ErrMissingUnexecutableTxHash = errors.New("missing unexecutable tx hash") + +// ErrMissingHeaderGasData signal that header gas data is missing +var ErrMissingHeaderGasData = errors.New("missing header gas data") diff --git a/common/export_test.go b/common/export_test.go index 53f25e26285..6154fc4d057 100644 --- a/common/export_test.go +++ b/common/export_test.go @@ -2,6 +2,12 @@ package common import "time" +// MinRoundDurationMS - +var MinRoundDurationMS = minRoundDurationMS + +// MinRoundDurationSec - +var MinRoundDurationSec = minRoundDurationSec + // NewTimeoutHandlerWithHandlerFunc - func NewTimeoutHandlerWithHandlerFunc(timeout time.Duration, handler func() time.Time) *timeoutHandler { th := &timeoutHandler{ @@ -12,3 +18,12 @@ func NewTimeoutHandlerWithHandlerFunc(timeout time.Duration, handler func() time return th } + +// TimeDurationToUnix - +func TimeDurationToUnix( + duration time.Duration, + enableEpochsHandler EnableEpochsHandler, + epoch uint32, +) int64 { + return timeDurationToUnix(duration, enableEpochsHandler, epoch) +} diff --git a/common/forking/genericEpochNotifier.go b/common/forking/genericEpochNotifier.go index a87a25ac527..c501bed7dda 100644 --- a/common/forking/genericEpochNotifier.go +++ b/common/forking/genericEpochNotifier.go @@ -36,7 +36,7 @@ func (gen *genericEpochNotifier) CheckEpoch(header data.HeaderHandler) { } gen.mutData.Lock() - epoch := header.GetEpoch() + epoch := getEpoch(header) timestamp := header.GetTimeStamp() shouldSkipHeader := gen.wasInitialized && gen.currentEpoch == epoch if shouldSkipHeader { @@ -65,6 +65,25 @@ func (gen *genericEpochNotifier) CheckEpoch(header data.HeaderHandler) { } } +func getEpoch(header data.HeaderHandler) uint32 { + epoch := header.GetEpoch() + if !header.IsHeaderV3() { + return epoch + } + + metaBlock, isMeta := header.(data.MetaHeaderHandler) + if !isMeta { + return epoch + } + + if !metaBlock.IsEpochChangeProposed() { + return epoch + } + + // If it is epoch start proposed meta header, we should use next epoch + return epoch + 1 +} + // RegisterNotifyHandler will register the provided handler to be called whenever a new epoch has changed func (gen *genericEpochNotifier) RegisterNotifyHandler(handler vmcommon.EpochSubscriberHandler) { if check.IfNil(handler) { diff --git a/common/forking/genericEpochNotifier_test.go b/common/forking/genericEpochNotifier_test.go index a0a649c098c..96a84f0289a 100644 --- a/common/forking/genericEpochNotifier_test.go +++ b/common/forking/genericEpochNotifier_test.go @@ -11,6 +11,7 @@ import ( "github.com/multiversx/mx-chain-go/common/mock" "github.com/multiversx/mx-chain-go/testscommon" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNewGenericEpochNotifier(t *testing.T) { @@ -184,3 +185,12 @@ func TestGenericEpochNotifier_ConcurrentOperations(t *testing.T) { wg.Wait() } + +func TestGetEpoch(t *testing.T) { + t.Parallel() + + require.Equal(t, uint32(5), getEpoch(&block.MetaBlock{Epoch: 5})) + require.Equal(t, uint32(5), getEpoch(&block.HeaderV3{Epoch: 5})) + require.Equal(t, uint32(5), getEpoch(&block.MetaBlockV3{Epoch: 5, EpochChangeProposed: false})) + require.Equal(t, uint32(6), getEpoch(&block.MetaBlockV3{Epoch: 5, EpochChangeProposed: true})) +} diff --git a/common/holders/errors.go b/common/holders/errors.go new file mode 100644 index 00000000000..371b2acc2f5 --- /dev/null +++ b/common/holders/errors.go @@ -0,0 +1,5 @@ +package holders + +import "errors" + +var errNilHaveTimeForSelectionFunc = errors.New("nil have time for selection function") diff --git a/common/holders/txSelectionOptions.go b/common/holders/txSelectionOptions.go new file mode 100644 index 00000000000..a5298768bea --- /dev/null +++ b/common/holders/txSelectionOptions.go @@ -0,0 +1,46 @@ +package holders + +type txSelectionOptions struct { + gasRequested uint64 + maxNumTxs int + loopDurationCheckInterval int + haveTimeForSelection func() bool +} + +// NewTxSelectionOptions returns a new instance of a selectionOptions struct +func NewTxSelectionOptions(gasRequested uint64, maxNumTxs int, loopDurationCheckInterval int, haveTimeForSelection func() bool) (*txSelectionOptions, error) { + if haveTimeForSelection == nil { + return nil, errNilHaveTimeForSelectionFunc + } + return &txSelectionOptions{ + gasRequested: gasRequested, + maxNumTxs: maxNumTxs, + haveTimeForSelection: haveTimeForSelection, + loopDurationCheckInterval: loopDurationCheckInterval, + }, nil +} + +// GetGasRequested returns a selection constraint parameter (for gas) +func (options *txSelectionOptions) GetGasRequested() uint64 { + return options.gasRequested +} + +// GetMaxNumTxs returns a selection constraint parameter (for number of transactions) +func (options *txSelectionOptions) GetMaxNumTxs() int { + return options.maxNumTxs +} + +// HaveTimeForSelection returns true if there is any time left for the selection (related to selection duration) +func (options *txSelectionOptions) HaveTimeForSelection() bool { + return options.haveTimeForSelection() +} + +// GetLoopDurationCheckInterval returns a selection constraint parameter (related to selection duration) +func (options *txSelectionOptions) GetLoopDurationCheckInterval() int { + return options.loopDurationCheckInterval +} + +// IsInterfaceNil returns true if there is no value under the interface +func (options *txSelectionOptions) IsInterfaceNil() bool { + return options == nil +} diff --git a/common/holders/txSelectionOptionsAPI.go b/common/holders/txSelectionOptionsAPI.go new file mode 100644 index 00000000000..5daaad33194 --- /dev/null +++ b/common/holders/txSelectionOptionsAPI.go @@ -0,0 +1,19 @@ +package holders + +type txSelectionOptionsAPI struct { + *txSelectionOptions + requestedFields string +} + +// NewTxSelectionOptionsAPI returns a new instance of a selectionOptionsAPI struct +func NewTxSelectionOptionsAPI(options *txSelectionOptions, requestedFields string) *txSelectionOptionsAPI { + return &txSelectionOptionsAPI{ + txSelectionOptions: options, + requestedFields: requestedFields, + } +} + +// GetRequestedFields returns a selection query parameter for the selection simulation endpoint (the requested fields of the transaction) +func (selectionOptions *txSelectionOptionsAPI) GetRequestedFields() string { + return selectionOptions.requestedFields +} diff --git a/common/holders/txSelectionOptionsAPI_test.go b/common/holders/txSelectionOptionsAPI_test.go new file mode 100644 index 00000000000..024d7a09d52 --- /dev/null +++ b/common/holders/txSelectionOptionsAPI_test.go @@ -0,0 +1,20 @@ +package holders + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewTxSelectionOptionsAPI(t *testing.T) { + t.Parallel() + + options, _ := NewTxSelectionOptions(10_000_000_000, 30_000, 10, haveTimeTrue) + optionsAPI := NewTxSelectionOptionsAPI(options, "hash,nonce") + + require.Equal(t, uint64(10_000_000_000), optionsAPI.GetGasRequested()) + require.Equal(t, 30_000, optionsAPI.GetMaxNumTxs()) + require.Equal(t, 10, optionsAPI.GetLoopDurationCheckInterval()) + require.Equal(t, "hash,nonce", optionsAPI.GetRequestedFields()) + require.True(t, optionsAPI.HaveTimeForSelection()) +} diff --git a/common/holders/txSelectionOptions_test.go b/common/holders/txSelectionOptions_test.go new file mode 100644 index 00000000000..9528c96e93c --- /dev/null +++ b/common/holders/txSelectionOptions_test.go @@ -0,0 +1,27 @@ +package holders + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func haveTimeTrue() bool { + return true +} + +func TestNewTxSelectionOptions(t *testing.T) { + t.Parallel() + + _, err := NewTxSelectionOptions(10_000_000_000, 30_000, 10, nil) + require.Equal(t, errNilHaveTimeForSelectionFunc, err) + + options, err := NewTxSelectionOptions(10_000_000_000, 30_000, 10, haveTimeTrue) + require.NoError(t, err) + + assert.Equal(t, uint64(10_000_000_000), options.GetGasRequested()) + assert.Equal(t, 30_000, options.GetMaxNumTxs()) + assert.Equal(t, 10, options.GetLoopDurationCheckInterval()) + assert.True(t, options.HaveTimeForSelection()) +} diff --git a/common/interface.go b/common/interface.go index c7f3059ff6f..9a7c51536ef 100644 --- a/common/interface.go +++ b/common/interface.go @@ -2,6 +2,7 @@ package common import ( "context" + "math/big" "time" "github.com/multiversx/mx-chain-core-go/core" @@ -9,6 +10,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data/block" crypto "github.com/multiversx/mx-chain-crypto-go" + "github.com/multiversx/mx-chain-go/common/configs/dto" "github.com/multiversx/mx-chain-go/config" ) @@ -189,6 +191,7 @@ type SnapshotStatisticsHandler interface { AddTrieStats(handler TrieStatisticsHandler, trieType TrieType) GetSnapshotDuration() int64 GetSnapshotNumNodes() uint64 + IncrementThrottlerWaits() IsInterfaceNil() bool } @@ -252,6 +255,7 @@ type StateStatisticsHandler interface { // able to tell if the node is idle or processing/committing a block type ProcessStatusHandler interface { SetBusy(reason string) + TrySetBusy(reason string) bool SetIdle() IsIdle() bool IsInterfaceNil() bool @@ -280,6 +284,21 @@ type RootHashHolder interface { IsInterfaceNil() bool } +// TxSelectionOptions holds transactions selection options (parameters) +type TxSelectionOptions interface { + GetGasRequested() uint64 + GetMaxNumTxs() int + HaveTimeForSelection() bool + GetLoopDurationCheckInterval() int + IsInterfaceNil() bool +} + +// TxSelectionOptionsAPI holds transactions selection options (parameters) for the API call +type TxSelectionOptionsAPI interface { + TxSelectionOptions + GetRequestedFields() string +} + // GasScheduleNotifierAPI defines the behavior of the gas schedule notifier components that is used for api type GasScheduleNotifierAPI interface { core.GasScheduleNotifier @@ -306,6 +325,20 @@ type EnableEpochsHandler interface { IsFlagEnabled(flag core.EnableEpochFlag) bool IsFlagEnabledInEpoch(flag core.EnableEpochFlag, epoch uint32) bool GetActivationEpoch(flag core.EnableEpochFlag) uint32 + GetAllEnableEpochs() map[string]uint32 + + IsInterfaceNil() bool +} + +// EnableRoundsHandler defines the operations of an entity that manages round activation flags +type EnableRoundsHandler interface { + RoundConfirmed(round uint64, timestamp uint64) + GetCurrentRound() uint64 + IsFlagDefined(flag EnableRoundFlag) bool + IsFlagEnabled(flag EnableRoundFlag) bool + IsFlagEnabledInRound(flag EnableRoundFlag, round uint64) bool + GetActivationRound(flag EnableRoundFlag) uint64 + GetAllEnableRounds() map[string]uint64 IsInterfaceNil() bool } @@ -389,6 +422,7 @@ type ChainParametersSubscriptionHandler interface { // HeadersPool defines what a headers pool structure can perform type HeadersPool interface { GetHeaderByHash(hash []byte) (data.HeaderHandler, error) + IsInterfaceNil() bool } // FieldsSizeChecker defines the behavior of a fields size checker common component @@ -425,3 +459,71 @@ type TrieLeavesRetriever interface { GetLeaves(numLeaves int, iteratorState [][]byte, leavesParser TrieLeafParser, ctx context.Context) (map[string]string, [][]byte, error) IsInterfaceNil() bool } + +// AccountNonceAndBalanceProvider provides the nonce and balance of accounts +type AccountNonceAndBalanceProvider interface { + GetAccountNonceAndBalance(accountKey []byte) (uint64, *big.Int, bool, error) + GetRootHash() ([]byte, error) + IsInterfaceNil() bool +} + +// AccountNonceProvider provides the nonce of accounts +type AccountNonceProvider interface { + GetAccountNonce(accountKey []byte) (uint64, bool, error) + GetRootHash() ([]byte, error) + IsInterfaceNil() bool +} + +// ChainParametersHandler defines the actions that need to be done by a component that can handle chain parameters +type ChainParametersHandler interface { + CurrentChainParameters() config.ChainParametersByEpochConfig + AllChainParameters() []config.ChainParametersByEpochConfig + ChainParametersForEpoch(epoch uint32) (config.ChainParametersByEpochConfig, error) + IsInterfaceNil() bool +} + +// ProcessConfigsHandler defines the behavior of a component that can return the process configs for a specific epoch or round +type ProcessConfigsHandler interface { + GetMaxMetaNoncesBehindByEpoch(epoch uint32) uint32 + GetMaxMetaNoncesBehindForGlobalStuckByEpoch(epoch uint32) uint32 + GetMaxShardNoncesBehindByEpoch(epoch uint32) uint32 + + GetMaxRoundsWithoutNewBlockReceivedByRound(round uint64) uint32 + GetMaxRoundsWithoutCommittedBlock(round uint64) uint32 + GetRoundModulusTriggerWhenSyncIsStuck(round uint64) uint32 + GetMaxSyncWithErrorsAllowed(round uint64) uint32 + GetMaxRoundsToKeepUnprocessedTransactions(round uint64) uint64 + GetMaxRoundsToKeepUnprocessedMiniBlocks(round uint64) uint64 + GetMaxBlockProcessingTime(round uint64) time.Duration + GetNumHeadersToRequestInAdvance(round uint64) uint64 + + GetValue(variable dto.ConfigVariable) uint64 + + IsInterfaceNil() bool +} + +// CommonConfigsHandler defines the behavior of a component that can return epoch start configurations by epoch or by round +type CommonConfigsHandler interface { + GetGracePeriodRoundsByEpoch(epoch uint32) uint32 + GetExtraDelayForRequestBlockInfoInMs(epoch uint32) uint32 + GetMaxRoundsWithoutCommittedStartInEpochBlockInRound(round uint64) uint32 + GetNumRoundsToWaitBeforeSignalingChronologyStuck(epoch uint32) uint32 + + IsInterfaceNil() bool +} + +// AntifloodConfigsHandler defines the behavior of a component that can return antiflood config by round +type AntifloodConfigsHandler interface { + GetCurrentConfig() config.AntifloodConfigByRound + GetFloodPreventerConfigByType(configType FloodPreventerType) config.FloodPreventerConfig + IsEnabled() bool + IsInterfaceNil() bool +} + +// AOTSelectionPreempter defines the interface for preempting AOT transaction selection +type AOTSelectionPreempter interface { + // CancelOngoingSelection aborts ongoing AOT selection if one is in progress + // Called before OnProposed/OnExecuted to avoid conflicts + CancelOngoingSelection() + IsInterfaceNil() bool +} diff --git a/common/reflectcommon/structFieldsUpdate_test.go b/common/reflectcommon/structFieldsUpdate_test.go index 27a30ea9d00..d9cad99a83f 100644 --- a/common/reflectcommon/structFieldsUpdate_test.go +++ b/common/reflectcommon/structFieldsUpdate_test.go @@ -384,20 +384,6 @@ func TestAdaptStructureValueBasedOnPath(t *testing.T) { require.Equal(t, expectedNewValue, cfg.StoragePruning.FullArchiveNumActivePersisters) }) - t.Run("should work and override int32 value", func(t *testing.T) { - t.Parallel() - - path := "Antiflood.NumConcurrentResolverJobs" - cfg := &config.Config{} - cfg.Antiflood.NumConcurrentResolverJobs = int32(50) - expectedNewValue := int32(37) - - err := AdaptStructureValueBasedOnPath(cfg, path, expectedNewValue) - require.NoError(t, err) - - require.Equal(t, expectedNewValue, cfg.Antiflood.NumConcurrentResolverJobs) - }) - t.Run("should work and override string value on multiple levels depth", func(t *testing.T) { t.Parallel() diff --git a/common/statistics/stateStatistics.go b/common/statistics/stateStatistics.go index 5804d3dff78..4e2fc8fbe35 100644 --- a/common/statistics/stateStatistics.go +++ b/common/statistics/stateStatistics.go @@ -84,7 +84,7 @@ func (ss *stateStatistics) Persister(epoch uint32) uint64 { return ss.numPersister[epoch] } -// IncrWitePersister will increment persister write counter +// IncrWritePersister will increment persister write counter func (ss *stateStatistics) IncrWritePersister(epoch uint32) { ss.mutPersisters.Lock() defer ss.mutPersisters.Unlock() diff --git a/common/unixTime.go b/common/unixTime.go new file mode 100644 index 00000000000..7ea0d70eb5d --- /dev/null +++ b/common/unixTime.go @@ -0,0 +1,120 @@ +package common + +import ( + "time" + + "github.com/multiversx/mx-chain-go/errors" +) + +const ( + genesisEpoch = 0 + + numMillisecondsInSec = 1000 + + // NumberOfSecondsInDay defines the number of seconds in a day + NumberOfSecondsInDay = 86400 + // NumberOfMillisecondsInDay defines the number of milliseconds in a day + NumberOfMillisecondsInDay = NumberOfSecondsInDay * numMillisecondsInSec +) + +const ( + minRoundDurationMS = 200 + minRoundDurationSec = 1 +) + +// GetGenesisUnixTimestampFromStartTime returns genesis unix timestamp based on the +// provided time +func GetGenesisUnixTimestampFromStartTime( + t time.Time, + enableEpochsHandler EnableEpochsHandler, +) int64 { + if enableEpochsHandler.IsFlagEnabledInEpoch(SupernovaFlag, genesisEpoch) { + return t.UnixMilli() + } + + return t.Unix() +} + +// GetGenesisStartTimeFromUnixTimestamp returns genesis time based on the provided +// unix timestamp as seconds +func GetGenesisStartTimeFromUnixTimestamp( + unixTimestampAsSeconds int64, + enableEpochsHandler EnableEpochsHandler, +) time.Time { + if enableEpochsHandler.IsFlagEnabledInEpoch(SupernovaFlag, genesisEpoch) { + return time.UnixMilli(unixTimestampAsSeconds * numMillisecondsInSec) + } + + return time.Unix(unixTimestampAsSeconds, 0) +} + +// CheckRoundDuration checks round duration based on current configuration +func CheckRoundDuration( + roundDuration uint64, + enableEpochsHandler EnableEpochsHandler, +) error { + if enableEpochsHandler.IsFlagEnabled(SupernovaFlag) { + return checkRoundDurationMilliSec(roundDuration) + } + + return checkRoundDurationSec(roundDuration) +} + +func checkRoundDurationSec(roundDuration uint64) error { + roundDurationSec := roundDuration / numMillisecondsInSec + if roundDurationSec < minRoundDurationSec { + return errors.ErrInvalidRoundDuration + } + + return nil +} + +func checkRoundDurationMilliSec(roundDuration uint64) error { + if roundDuration < minRoundDurationMS { + return errors.ErrInvalidRoundDuration + } + + return nil +} + +// ComputeRoundsPerDay computes the rounds per day based on current configuration +func ComputeRoundsPerDay( + roundTime time.Duration, + enableEpochsHandler EnableEpochsHandler, + epoch uint32, +) uint64 { + unitsInDay := getUnitsPerDay(enableEpochsHandler, epoch) + + return uint64(unitsInDay) / uint64(timeDurationToUnix(roundTime, enableEpochsHandler, epoch)) +} + +// timeDurationToUnix converts duration time to unix based on current configuration +func timeDurationToUnix( + duration time.Duration, + enableEpochsHandler EnableEpochsHandler, + epoch uint32, +) int64 { + if enableEpochsHandler.IsFlagEnabledInEpoch(SupernovaFlag, epoch) { + return duration.Milliseconds() + } + + return int64(duration.Seconds()) +} + +func getUnitsPerDay( + enableEpochsHandler EnableEpochsHandler, + epoch uint32, +) int { + if enableEpochsHandler.IsFlagEnabledInEpoch(SupernovaFlag, epoch) { + return NumberOfMillisecondsInDay + } + + return NumberOfSecondsInDay +} + +// RoundToNearestMinute rounds the given time to the nearest minute +func RoundToNearestMinute( + t time.Time, +) time.Time { + return t.Add(1 * time.Minute).Round(time.Minute) +} diff --git a/common/unixTime_test.go b/common/unixTime_test.go new file mode 100644 index 00000000000..6d6a75f9775 --- /dev/null +++ b/common/unixTime_test.go @@ -0,0 +1,256 @@ +package common_test + +import ( + "testing" + "time" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/errors" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" + "github.com/stretchr/testify/require" +) + +func TestTimeToUnix(t *testing.T) { + t.Parallel() + + t.Run("with supernova flag enabled", func(t *testing.T) { + t.Parallel() + + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag && epoch == 0 + }, + } + + tt := time.Now() + + res := common.GetGenesisUnixTimestampFromStartTime(tt, enableEpochsHandler) + require.Equal(t, tt.UnixMilli(), res) + }) + + t.Run("without supernova flag enabled", func(t *testing.T) { + t.Parallel() + + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return flag != common.SupernovaFlag + }, + } + + tt := time.Now() + + res := common.GetGenesisUnixTimestampFromStartTime(tt, enableEpochsHandler) + require.Equal(t, tt.Unix(), res) + }) +} + +func TestTimeToUnixInEpoch(t *testing.T) { + t.Parallel() + + t.Run("with supernova flag enabled", func(t *testing.T) { + t.Parallel() + + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag && epoch == 0 + }, + } + + tt := time.Now().Truncate(time.Second) + + unixTimeStamp := common.GetGenesisUnixTimestampFromStartTime(tt, enableEpochsHandler) + require.Equal(t, tt.UnixMilli(), unixTimeStamp) + + // should not work properly to get start time from unix timestamp as milliseconds + timeFromUnixTimeStamp := common.GetGenesisStartTimeFromUnixTimestamp(unixTimeStamp, enableEpochsHandler) + require.NotEqual(t, tt, timeFromUnixTimeStamp) + }) + + t.Run("without supernova flag enabled", func(t *testing.T) { + t.Parallel() + + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag != common.SupernovaFlag && epoch == 0 + }, + } + + tt := time.Now().Truncate(time.Second) + + unixTimeStamp := common.GetGenesisUnixTimestampFromStartTime(tt, enableEpochsHandler) + require.Equal(t, tt.Unix(), unixTimeStamp) + + timeFromUnixTimeStamp := common.GetGenesisStartTimeFromUnixTimestamp(unixTimeStamp, enableEpochsHandler) + require.Equal(t, tt, timeFromUnixTimeStamp) + }) +} + +func TestTimeDurationToUnix(t *testing.T) { + t.Parallel() + + expEpoch := uint32(2) + + t.Run("with supernova flag enabled", func(t *testing.T) { + t.Parallel() + + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag && epoch == expEpoch + }, + } + + dur := 200 * time.Millisecond + + unix := common.TimeDurationToUnix(dur, enableEpochsHandler, expEpoch) + require.Equal(t, int64(200), unix) + + dur = 2 * time.Second + + unix = common.TimeDurationToUnix(dur, enableEpochsHandler, expEpoch) + require.Equal(t, int64(2000), unix) + }) + + t.Run("without supernova flag enabled", func(t *testing.T) { + t.Parallel() + + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag != common.SupernovaFlag && epoch == expEpoch + }, + } + + dur := 200 * time.Millisecond + + unix := common.TimeDurationToUnix(dur, enableEpochsHandler, expEpoch) + require.Equal(t, int64(0), unix) + + dur = 2 * time.Second + + unix = common.TimeDurationToUnix(dur, enableEpochsHandler, expEpoch) + require.Equal(t, int64(2), unix) + }) +} + +func TestCheckRoundDuration(t *testing.T) { + t.Parallel() + + t.Run("with supernova flag enabled", func(t *testing.T) { + t.Parallel() + + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return flag == common.SupernovaFlag + }, + } + + err := common.CheckRoundDuration(uint64(common.MinRoundDurationMS-1), enableEpochsHandler) + require.Equal(t, errors.ErrInvalidRoundDuration, err) + + err = common.CheckRoundDuration(uint64(0), enableEpochsHandler) + require.Equal(t, errors.ErrInvalidRoundDuration, err) + + err = common.CheckRoundDuration(uint64(common.MinRoundDurationMS), enableEpochsHandler) + require.Nil(t, err) + }) + + t.Run("without supernova flag enabled", func(t *testing.T) { + t.Parallel() + + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return flag != common.SupernovaFlag + }, + } + + err := common.CheckRoundDuration(uint64(common.MinRoundDurationMS), enableEpochsHandler) + require.Equal(t, errors.ErrInvalidRoundDuration, err) + + err = common.CheckRoundDuration(uint64(common.MinRoundDurationSec*1000-1), enableEpochsHandler) + require.Equal(t, errors.ErrInvalidRoundDuration, err) + + err = common.CheckRoundDuration(uint64(common.MinRoundDurationSec*1000), enableEpochsHandler) + require.Nil(t, err) + }) +} + +func TestComputeRoundsPerDay(t *testing.T) { + t.Parallel() + + expEpoch := uint32(2) + + t.Run("with supernova flag enabled", func(t *testing.T) { + t.Parallel() + + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag && epoch == expEpoch + }, + } + + dur := 600 * time.Millisecond + + numRounds := common.ComputeRoundsPerDay(dur, enableEpochsHandler, expEpoch) + expNumRounds := uint64(common.NumberOfMillisecondsInDay) / 600 + require.Equal(t, expNumRounds, numRounds) + }) + + t.Run("without supernova flag enabled", func(t *testing.T) { + t.Parallel() + + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag != common.SupernovaFlag && epoch == expEpoch + }, + } + + dur := 6 * time.Second + + numRounds := common.ComputeRoundsPerDay(dur, enableEpochsHandler, expEpoch) + expNumRounds := uint64(common.NumberOfSecondsInDay) / 6 + require.Equal(t, expNumRounds, numRounds) + }) +} + +func TestRoundToNearestMinute(t *testing.T) { + tests := []struct { + name string + input time.Time + expected time.Time + }{ + { + name: "Round up at 30 seconds", + input: time.Date(2025, 6, 13, 16, 8, 30, 0, time.UTC), + expected: time.Date(2025, 6, 13, 16, 10, 0, 0, time.UTC), // 16:08:30 + 1m = 16:09:30 -> rounds to 16:10:00 + }, + { + name: "Round down at 29 seconds", + input: time.Date(2025, 6, 13, 16, 8, 29, 999999999, time.UTC), + expected: time.Date(2025, 6, 13, 16, 9, 0, 0, time.UTC), // 16:08:29.999 + 1m = 16:09:29.999 -> rounds to 16:09:00 + }, + { + name: "Exact minute", + input: time.Date(2025, 6, 13, 16, 8, 0, 0, time.UTC), + expected: time.Date(2025, 6, 13, 16, 9, 0, 0, time.UTC), // 16:08:00 + 1m = 16:09:00 -> rounds to 16:09:00 + }, + { + name: "Round up at 45 seconds", + input: time.Date(2025, 6, 13, 16, 8, 45, 500000000, time.UTC), + expected: time.Date(2025, 6, 13, 16, 10, 0, 0, time.UTC), // 16:08:45.5 + 1m = 16:09:45.5 -> rounds to 16:10:00 + }, + { + name: "Different timezone", + input: time.Date(2025, 6, 13, 16, 8, 30, 0, time.FixedZone("EST", -5*60*60)), + expected: time.Date(2025, 6, 13, 16, 10, 0, 0, time.FixedZone("EST", -5*60*60)), // 16:08:30 + 1m = 16:09:30 -> rounds to 16:10:00 + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := common.RoundToNearestMinute(tt.input) + if !result.Equal(tt.expected) { + t.Errorf("RoundToNearestMinute(%v) = %v; want %v", tt.input, result, tt.expected) + } + }) + } +} diff --git a/config/config.go b/config/config.go index 0ad08a34111..80c13872b34 100644 --- a/config/config.go +++ b/config/config.go @@ -13,6 +13,29 @@ type CacheConfig struct { Shards uint32 } +// TxCacheBoundsConfig will map the transactions cache bounds config +type TxCacheBoundsConfig struct { + MaxNumBytesPerSenderUpperBound uint32 + MaxTrackedBlocks uint32 + PropagationGracePeriodMs uint32 +} + +// TxCacheSelectionConfig will map the mempool selection config +type TxCacheSelectionConfig struct { + SelectionGasBandwidthIncreasePercent uint32 + SelectionGasBandwidthIncreaseScheduledPercent uint32 + SelectionGasRequested uint64 + SelectionMaxNumTxs int + SelectionLoopDurationCheckInterval int +} + +// AOTSelectionConfig will map the ahead-of-time transaction selection config +type AOTSelectionConfig struct { + Enabled bool + CacheSize int + SelectionTimeoutMs int +} + // HeadersPoolConfig will map the headers cache configuration type HeadersPoolConfig struct { MaxHeadersPerShard int @@ -25,6 +48,13 @@ type ProofsPoolConfig struct { BucketSize int } +// ExecutionResultInclusionEstimatorConfig will map the EIE configuration - supplied at construction, read-only thereafter. +// TODO add also max estimated block gas capacity +type ExecutionResultInclusionEstimatorConfig struct { + SafetyMargin uint64 + MaxResultsPerBlock uint64 +} + // DBConfig will map the database configuration type DBConfig struct { FilePath string @@ -78,11 +108,12 @@ type ConsensusConfig struct { // NTPConfig will hold the configuration for NTP queries type NTPConfig struct { - Hosts []string - Port int - TimeoutMilliseconds int - SyncPeriodSeconds int - Version int + Hosts []string + Port int + TimeoutMilliseconds int + SyncPeriodSeconds int + Version int + OutOfBoundsThreshold int } // EvictionWaitingListConfig will hold the configuration for the EvictionWaitingList @@ -94,8 +125,6 @@ type EvictionWaitingListConfig struct { // EpochStartConfig will hold the configuration of EpochStart settings type EpochStartConfig struct { - MinRoundsBetweenEpochs int64 - RoundsPerEpoch int64 MinShuffledOutRestartThreshold float64 MaxShuffledOutRestartThreshold float64 MinNumConnectedPeersToStart int @@ -106,8 +135,9 @@ type EpochStartConfig struct { // BlockSizeThrottleConfig will hold the configuration for adaptive block size throttle type BlockSizeThrottleConfig struct { - MinSizeInBytes uint32 - MaxSizeInBytes uint32 + MinSizeInBytes uint32 + MaxSizeInBytes uint32 + MaxExecResSizeInBytes uint32 } // SoftwareVersionConfig will hold the configuration for software version checker @@ -165,39 +195,48 @@ type Config struct { SmartContractsStorageSimulate StorageConfig StateAccessesStorage StorageConfig - BootstrapStorage StorageConfig - MetaBlockStorage StorageConfig - ProofsStorage StorageConfig + ExecutionResultInclusionEstimator ExecutionResultInclusionEstimatorConfig + + BootstrapStorage StorageConfig + MetaBlockStorage StorageConfig + ProofsStorage StorageConfig + ExecutionResultsStorage StorageConfig - AccountsTrieStorage StorageConfig - PeerAccountsTrieStorage StorageConfig - EvictionWaitingList EvictionWaitingListConfig - StateTriesConfig StateTriesConfig + AccountsTrieStorage StorageConfig + PeerAccountsTrieStorage StorageConfig + EvictionWaitingList EvictionWaitingListConfig + StateTriesConfig StateTriesConfig StateAccessesCollectorConfig StateAccessesCollectorConfig - TrieStorageManagerConfig TrieStorageManagerConfig - TrieLeavesRetrieverConfig TrieLeavesRetrieverConfig - BadBlocksCache CacheConfig - - TxBlockBodyDataPool CacheConfig - PeerBlockBodyDataPool CacheConfig - TxDataPool CacheConfig - UnsignedTransactionDataPool CacheConfig - RewardTransactionDataPool CacheConfig - TrieNodesChunksDataPool CacheConfig - WhiteListPool CacheConfig - WhiteListerVerifiedTxs CacheConfig - SmartContractDataPool CacheConfig - ValidatorInfoPool CacheConfig - TrieSyncStorage TrieSyncStorageConfig - EpochStartConfig EpochStartConfig - AddressPubkeyConverter PubkeyConfig - ValidatorPubkeyConverter PubkeyConfig - Hasher TypeConfig - MultisigHasher TypeConfig - Marshalizer MarshalizerConfig - VmMarshalizer TypeConfig - TxSignMarshalizer TypeConfig - TxSignHasher TypeConfig + TrieStorageManagerConfig TrieStorageManagerConfig + TrieLeavesRetrieverConfig TrieLeavesRetrieverConfig + BadBlocksCache CacheConfig + + TxBlockBodyDataPool CacheConfig + PeerBlockBodyDataPool CacheConfig + TxDataPool CacheConfig + TxCacheBounds TxCacheBoundsConfig + TxCacheSelection TxCacheSelectionConfig + AOTSelection AOTSelectionConfig + UnsignedTransactionDataPool CacheConfig + RewardTransactionDataPool CacheConfig + TrieNodesChunksDataPool CacheConfig + WhiteListPool CacheConfig + WhiteListerVerifiedTxs CacheConfig + SmartContractDataPool CacheConfig + ValidatorInfoPool CacheConfig + ExecutedMiniBlocksCache CacheConfig + PostProcessTransactionsCache CacheConfig + HeaderBodyCacheConfig HeaderBodyCacheConfig + TrieSyncStorage TrieSyncStorageConfig + EpochStartConfig EpochStartConfig + AddressPubkeyConverter PubkeyConfig + ValidatorPubkeyConverter PubkeyConfig + Hasher TypeConfig + MultisigHasher TypeConfig + Marshalizer MarshalizerConfig + VmMarshalizer TypeConfig + TxSignMarshalizer TypeConfig + TxSignHasher TypeConfig PublicKeyShardId CacheConfig PublicKeyPeerId CacheConfig @@ -236,11 +275,10 @@ type Config struct { Requesters RequesterConfig VMOutputCacher CacheConfig - PeersRatingConfig PeersRatingConfig - PoolsCleanersConfig PoolsCleanersConfig - Redundancy RedundancyConfig + PeersRatingConfig PeersRatingConfig InterceptedDataVerifier InterceptedDataVerifierConfig + DirectSentTransactions DirectSentTransactionsConfig } // PeersRatingConfig will hold settings related to peers rating @@ -267,6 +305,11 @@ type StoragePruningConfig struct { FullArchiveNumActivePersisters uint32 } +// HeaderBodyCacheConfig will hold settings related with header body cache +type HeaderBodyCacheConfig struct { + Capacity int +} + // ResourceStatsConfig will hold all resource stats settings type ResourceStatsConfig struct { Enabled bool @@ -297,20 +340,104 @@ type EpochChangeGracePeriodByEpoch struct { GracePeriodInRounds uint32 } +// ConsensusConfigByEpoch defines consensus configuration parameters by epoch +type ConsensusConfigByEpoch struct { + EnableEpoch uint32 + + NumRoundsToWaitBeforeSignalingChronologyStuck uint32 +} + +// EpochStartConfigByEpoch defines epoch start configuration parameters by epoch +type EpochStartConfigByEpoch struct { + EnableEpoch uint32 + + GracePeriodRounds uint32 + ExtraDelayForRequestBlockInfoInMilliseconds uint32 +} + +// EpochStartConfigByRound defines epoch start configuration parameters by round +type EpochStartConfigByRound struct { + EnableRound uint64 + + // MaxRoundsWithoutCommittedStartInEpochBlock defines the maximum rounds to wait for start in epoch block to be committed, + // before a special action to be applied + MaxRoundsWithoutCommittedStartInEpochBlock uint32 +} + +// ProcessConfigByEpoch defines process configuration parameters by epoch +type ProcessConfigByEpoch struct { + EnableEpoch uint32 + + // MaxMetaNoncesBehind defines the maximum difference between the current meta block nonce and the processed meta block + // nonce before a shard is considered stuck + MaxMetaNoncesBehind uint32 + + // MaxMetaNoncesBehindForGlobalStuck defines the maximum difference between the current meta block nonce and the processed + // meta block nonce for any shard, where the chain is considered stuck and enters recovery + + MaxMetaNoncesBehindForGlobalStuck uint32 + + // MaxShardNoncesBehind defines the maximum difference between the current shard block nonce and the last notarized + // shard block nonce by meta, before meta is considered stuck + MaxShardNoncesBehind uint32 +} + +// ProcessConfigByRound defines process configuration parameters by round +type ProcessConfigByRound struct { + EnableRound uint64 + + // MaxRoundsWithoutNewBlockReceived defines the maximum number of rounds to wait for a new block to be received, + // before a special action to be applied + MaxRoundsWithoutNewBlockReceived uint32 + + // MaxRoundsWithoutCommittedBlock defines the maximum rounds to wait for a new block to be committed, + // before a special action to be applied + MaxRoundsWithoutCommittedBlock uint32 + + // RoundModulusTriggerWhenSyncIsStuck defines a round modulus on which a trigger for an action when sync is stuck will be released + RoundModulusTriggerWhenSyncIsStuck uint32 + + // MaxSyncWithErrorsAllowed defines the maximum allowed number of sync with errors, + // before a special action to be applied + MaxSyncWithErrorsAllowed uint32 + + // Max number of rounds unprocessed miniblocks are kept in pool + MaxRoundsToKeepUnprocessedMiniBlocks uint64 + + // Max number of rounds unprocessed transactions are kept in pool + MaxRoundsToKeepUnprocessedTransactions uint64 + + NumFloodingRoundsFastReacting uint32 + NumFloodingRoundsSlowReacting uint32 + NumFloodingRoundsOutOfSpecs uint32 + + MaxConsecutiveRoundsOfRatingDecrease uint64 + MaxRoundsOfInactivityAccepted uint64 + MaxBlockProcessingTimeMs uint32 + NumHeadersToRequestInAdvance uint64 +} + // GeneralSettingsConfig will hold the general settings for a node type GeneralSettingsConfig struct { - StatusPollingIntervalSec int - MaxComputableRounds uint64 - MaxConsecutiveRoundsOfRatingDecrease uint64 - StartInEpochEnabled bool - ChainID string - MinTransactionVersion uint32 - GenesisString string - GenesisMaxNumberOfShards uint32 - SyncProcessTimeInMillis uint32 - SetGuardianEpochsDelay uint32 - ChainParametersByEpoch []ChainParametersByEpochConfig - EpochChangeGracePeriodByEpoch []EpochChangeGracePeriodByEpoch + StatusPollingIntervalSec int + // TODO: add config per epoch for supernova + MaxComputableRounds uint64 + StartInEpochEnabled bool + ChainID string + MinTransactionVersion uint32 + GenesisString string + GenesisMaxNumberOfShards uint32 + SyncProcessTimeInMillis uint32 + SyncProcessTimeSupernovaInMillis uint32 + SetGuardianEpochsDelay uint32 + MaxProposalNonceGap uint64 + ChainParametersByEpoch []ChainParametersByEpochConfig + EpochChangeGracePeriodByEpoch []EpochChangeGracePeriodByEpoch + ProcessConfigsByEpoch []ProcessConfigByEpoch + ProcessConfigsByRound []ProcessConfigByRound `toml:"ProcessConfigsByRound"` + EpochStartConfigsByEpoch []EpochStartConfigByEpoch + EpochStartConfigsByRound []EpochStartConfigByRound + ConsensusConfigsByEpoch []ConsensusConfigByEpoch } // HardwareRequirementsConfig will hold the hardware requirements config @@ -323,6 +450,7 @@ type FacadeConfig struct { RestApiInterface string PprofEnabled bool P2PPrometheusMetricsEnabled bool + TxCacheSelectionConfig TxCacheSelectionConfig } // StateTriesConfig will hold information about state tries @@ -395,13 +523,19 @@ type TxAccumulatorConfig struct { // AntifloodConfig will hold all p2p antiflood parameters type AntifloodConfig struct { - Enabled bool + Enabled bool + ConfigsByRound []AntifloodConfigByRound +} + +// AntifloodConfigByRound will hold antiflood parameters by round +type AntifloodConfigByRound struct { + Round uint64 NumConcurrentResolverJobs int32 NumConcurrentResolvingTrieNodesJobs int32 OutOfSpecs FloodPreventerConfig FastReacting FloodPreventerConfig SlowReacting FloodPreventerConfig - PeerMaxOutput AntifloodLimitsConfig + PeerMaxOutput FloodPreventerConfig Cache CacheConfig Topic TopicAntifloodConfig TxAccumulator TxAccumulatorConfig @@ -536,6 +670,13 @@ type InterceptorResolverDebugConfig struct { NumRequestsThreshold int NumResolveFailureThreshold int DebugLineExpiration int + BroadcastStatistics BroadcastStatisticsConfig +} + +// BroadcastStatisticsConfig holds configuration for broadcast statistics collection +type BroadcastStatisticsConfig struct { + Enabled bool + Messages []string } // AntifloodDebugConfig will hold the antiflood debug configuration @@ -593,6 +734,7 @@ type RouteConfig struct { // VersionByEpochs represents a version entry that will be applied between the provided epochs type VersionByEpochs struct { StartEpoch uint32 + StartRound uint64 Version string } @@ -600,7 +742,6 @@ type VersionByEpochs struct { type VersionsConfig struct { DefaultVersion string VersionsByEpochs []VersionByEpochs - Cache CacheConfig } // Configs is a holder for the node configuration parameters @@ -672,20 +813,11 @@ type RequesterConfig struct { NumFullHistoryPeers uint32 } -// PoolsCleanersConfig represents the config options to be used by the pools cleaners -type PoolsCleanersConfig struct { - MaxRoundsToKeepUnprocessedMiniBlocks int64 - MaxRoundsToKeepUnprocessedTransactions int64 -} - -// RedundancyConfig represents the config options to be used when setting the redundancy configuration -type RedundancyConfig struct { - MaxRoundsOfInactivityAccepted int -} - // ChainParametersByEpochConfig holds chain parameters that are configurable based on epochs type ChainParametersByEpochConfig struct { RoundDuration uint64 + RoundsPerEpoch int64 + MinRoundsBetweenEpochs int64 Hysteresis float32 EnableEpoch uint32 ShardConsensusGroupSize uint32 @@ -693,6 +825,7 @@ type ChainParametersByEpochConfig struct { MetachainConsensusGroupSize uint32 MetachainMinNumNodes uint32 Adaptivity bool + Offset uint16 } // IndexBroadcastDelay holds a pair of starting consensus index and the delay the nodes should wait before broadcasting final info @@ -703,6 +836,13 @@ type IndexBroadcastDelay struct { // InterceptedDataVerifierConfig holds the configuration for the intercepted data verifier type InterceptedDataVerifierConfig struct { + EnableCaching bool + CacheSpanInSec uint64 + CacheExpiryInSec uint64 +} + +// DirectSentTransactionsConfig holds the configuration for the direct-sent transactions +type DirectSentTransactionsConfig struct { CacheSpanInSec uint64 CacheExpiryInSec uint64 } diff --git a/config/economicsConfig.go b/config/economicsConfig.go index 1c5275093f6..ab51ce21404 100644 --- a/config/economicsConfig.go +++ b/config/economicsConfig.go @@ -59,11 +59,13 @@ type GasLimitSetting struct { // FeeSettings will hold economics fee settings type FeeSettings struct { - GasLimitSettings []GasLimitSetting - GasPerDataByte string - MinGasPrice string - GasPriceModifier float64 - MaxGasPriceSetGuardian string + GasLimitSettings []GasLimitSetting + GasPerDataByte string + MinGasPrice string + GasPriceModifier float64 + MaxGasPriceSetGuardian string + BlockCapacityOverestimationFactor uint64 + PercentDecreaseLimitsStep uint64 } // EconomicsConfig will hold economics config diff --git a/config/epochConfig.go b/config/epochConfig.go index 25a34c5476e..81d312ead36 100644 --- a/config/epochConfig.go +++ b/config/epochConfig.go @@ -70,7 +70,6 @@ type EnableEpochs struct { IsPayableBySCEnableEpoch uint32 CleanUpInformativeSCRsEnableEpoch uint32 StorageAPICostOptimizationEnableEpoch uint32 - TransformToMultiShardCreateEnableEpoch uint32 ESDTRegisterAndSetAllRolesEnableEpoch uint32 DoNotReturnOldBlockInBlockchainHookEnableEpoch uint32 AddFailedRelayedTxToInvalidMBsDisableEpoch uint32 @@ -127,6 +126,7 @@ type EnableEpochs struct { RelayedTransactionsV3EnableEpoch uint32 RelayedTransactionsV3FixESDTTransferEnableEpoch uint32 AndromedaEnableEpoch uint32 + SupernovaEnableEpoch uint32 CheckBuiltInCallOnTransferValueAndFailEnableRound uint32 MaskVMInternalDependenciesErrorsEnableEpoch uint32 FixBackTransferOPCODEEnableEpoch uint32 diff --git a/config/systemSmartContractsConfig.go b/config/systemSmartContractsConfig.go index 9ce67ce3208..8dd2cef3e82 100644 --- a/config/systemSmartContractsConfig.go +++ b/config/systemSmartContractsConfig.go @@ -18,6 +18,7 @@ type StakingSystemSCConfig struct { UnJailValue string MinStepValue string UnBondPeriod uint64 + UnBondPeriodSupernova uint64 UnBondPeriodInEpochs uint32 NumRoundsWithoutBleed uint64 MaximumPercentageToBleed float64 diff --git a/config/tomlConfig_test.go b/config/tomlConfig_test.go index 5c625a7378b..1450e50f5d1 100644 --- a/config/tomlConfig_test.go +++ b/config/tomlConfig_test.go @@ -59,6 +59,22 @@ func TestTomlParser(t *testing.T) { MetachainConsensusGroupSize: 5, Hysteresis: 0.0, Adaptivity: false, + Offset: 2, + }, + }, + ProcessConfigsByRound: []ProcessConfigByRound{ + { + EnableRound: 1, + MaxRoundsWithoutNewBlockReceived: 2, + MaxRoundsWithoutCommittedBlock: 3, + RoundModulusTriggerWhenSyncIsStuck: 4, + MaxSyncWithErrorsAllowed: 5, + MaxRoundsToKeepUnprocessedMiniBlocks: 6, + MaxRoundsToKeepUnprocessedTransactions: 7, + MaxConsecutiveRoundsOfRatingDecrease: 11, + MaxRoundsOfInactivityAccepted: 12, + MaxBlockProcessingTimeMs: 13, + NumHeadersToRequestInAdvance: 14, }, }, }, @@ -159,15 +175,219 @@ func TestTomlParser(t *testing.T) { MaxStateTrieLevelInMemory: 38, MaxPeerTrieLevelInMemory: 39, }, - Redundancy: RedundancyConfig{ - MaxRoundsOfInactivityAccepted: 3, + TxCacheBounds: TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: 33_554_432, + MaxTrackedBlocks: 100, + PropagationGracePeriodMs: 200, + }, + TxCacheSelection: TxCacheSelectionConfig{ + SelectionGasBandwidthIncreasePercent: 400, + SelectionGasBandwidthIncreaseScheduledPercent: 260, + SelectionGasRequested: 10_000_000_000, + SelectionMaxNumTxs: 30000, + SelectionLoopDurationCheckInterval: 10, + }, + Antiflood: AntifloodConfig{ + Enabled: true, + ConfigsByRound: []AntifloodConfigByRound{ + { + Round: 0, + NumConcurrentResolverJobs: 50, + NumConcurrentResolvingTrieNodesJobs: 3, + FastReacting: FloodPreventerConfig{ + IntervalInSeconds: 1, + ReservedPercent: 20.0, + PeerMaxInput: AntifloodLimitsConfig{ + BaseMessagesPerInterval: 280, + TotalSizePerInterval: 4194304, + IncreaseFactor: IncreaseFactorConfig{ + Threshold: 10, + Factor: 1.0, + }, + }, + BlackList: BlackListConfig{ + ThresholdNumMessagesPerInterval: 1000, + ThresholdSizePerInterval: 8388608, + NumFloodingRounds: 10, + PeerBanDurationInSeconds: 300, + }, + }, + SlowReacting: FloodPreventerConfig{ + IntervalInSeconds: 30, + ReservedPercent: 20.0, + PeerMaxInput: AntifloodLimitsConfig{ + BaseMessagesPerInterval: 6000, + TotalSizePerInterval: 18874368, + IncreaseFactor: IncreaseFactorConfig{ + Threshold: 10, + Factor: 0.0, + }, + }, + BlackList: BlackListConfig{ + ThresholdNumMessagesPerInterval: 10000, + ThresholdSizePerInterval: 37748736, + NumFloodingRounds: 2, + PeerBanDurationInSeconds: 3600, + }, + }, + OutOfSpecs: FloodPreventerConfig{ + IntervalInSeconds: 1, + ReservedPercent: 0.0, + PeerMaxInput: AntifloodLimitsConfig{ + BaseMessagesPerInterval: 2000, + TotalSizePerInterval: 10485760, + IncreaseFactor: IncreaseFactorConfig{ + Threshold: 0, + Factor: 0.0, + }, + }, + BlackList: BlackListConfig{ + ThresholdNumMessagesPerInterval: 3600, + ThresholdSizePerInterval: 12582912, + NumFloodingRounds: 2, + PeerBanDurationInSeconds: 3600, + }, + }, + PeerMaxOutput: FloodPreventerConfig{ + PeerMaxInput: AntifloodLimitsConfig{ + BaseMessagesPerInterval: 150, + TotalSizePerInterval: 2097152, + }, + }, + Cache: CacheConfig{ + Name: "Antiflood", + Capacity: 7000, + Type: "LRU", + }, + Topic: TopicAntifloodConfig{ + DefaultMaxMessagesPerSec: 15000, + MaxMessages: []TopicMaxMessagesConfig{ + { + Topic: "shardBlocks*", + NumMessagesPerSec: 30, + }, + { + Topic: "metachainBlocks", + NumMessagesPerSec: 30, + }, + }, + }, + TxAccumulator: TxAccumulatorConfig{ + MaxAllowedTimeInMilliseconds: 250, + MaxDeviationTimeInMilliseconds: 25, + }, + }, + { + Round: 440, + NumConcurrentResolverJobs: 50, + NumConcurrentResolvingTrieNodesJobs: 3, + FastReacting: FloodPreventerConfig{ + IntervalInSeconds: 1, + ReservedPercent: 20.0, + PeerMaxInput: AntifloodLimitsConfig{ + BaseMessagesPerInterval: 280, + TotalSizePerInterval: 4194304, + IncreaseFactor: IncreaseFactorConfig{ + Threshold: 10, + Factor: 1.0, + }, + }, + BlackList: BlackListConfig{ + ThresholdNumMessagesPerInterval: 10000, + ThresholdSizePerInterval: 12388608, + NumFloodingRounds: 100, + PeerBanDurationInSeconds: 300, + }, + }, + SlowReacting: FloodPreventerConfig{ + IntervalInSeconds: 30, + ReservedPercent: 20.0, + PeerMaxInput: AntifloodLimitsConfig{ + BaseMessagesPerInterval: 6000, + TotalSizePerInterval: 18874368, + IncreaseFactor: IncreaseFactorConfig{ + Threshold: 10, + Factor: 0.0, + }, + }, + BlackList: BlackListConfig{ + ThresholdNumMessagesPerInterval: 100000, + ThresholdSizePerInterval: 60748736, + NumFloodingRounds: 20, + PeerBanDurationInSeconds: 3600, + }, + }, + OutOfSpecs: FloodPreventerConfig{ + IntervalInSeconds: 1, + ReservedPercent: 0.0, + PeerMaxInput: AntifloodLimitsConfig{ + BaseMessagesPerInterval: 20000, + TotalSizePerInterval: 104857600, + IncreaseFactor: IncreaseFactorConfig{ + Threshold: 0, + Factor: 0.0, + }, + }, + BlackList: BlackListConfig{ + ThresholdNumMessagesPerInterval: 36000, + ThresholdSizePerInterval: 18582912, + NumFloodingRounds: 20, + PeerBanDurationInSeconds: 3600, + }, + }, + PeerMaxOutput: FloodPreventerConfig{ + PeerMaxInput: AntifloodLimitsConfig{ + BaseMessagesPerInterval: 150, + TotalSizePerInterval: 2097152, + }, + }, + Cache: CacheConfig{ + Name: "Antiflood", + Capacity: 7000, + Type: "LRU", + }, + Topic: TopicAntifloodConfig{ + DefaultMaxMessagesPerSec: 15000, + MaxMessages: []TopicMaxMessagesConfig{ + { + Topic: "shardBlocks*", + NumMessagesPerSec: 30, + }, + { + Topic: "metachainBlocks", + NumMessagesPerSec: 30, + }, + }, + }, + TxAccumulator: TxAccumulatorConfig{ + MaxAllowedTimeInMilliseconds: 80, + MaxDeviationTimeInMilliseconds: 25, + }, + }, + }, }, } testString := ` [GeneralSettings] ChainParametersByEpoch = [ - { EnableEpoch = 0, RoundDuration = 4000, ShardConsensusGroupSize = 3, ShardMinNumNodes = 4, MetachainConsensusGroupSize = 5, MetachainMinNumNodes = 6, Hysteresis = 0.0, Adaptivity = false } + { EnableEpoch = 0, RoundDuration = 4000, ShardConsensusGroupSize = 3, ShardMinNumNodes = 4, MetachainConsensusGroupSize = 5, MetachainMinNumNodes = 6, Hysteresis = 0.0, Adaptivity = false, Offset = 2 } + ] + ProcessConfigsByRound = [ + { + EnableRound = 1, + MaxRoundsWithoutNewBlockReceived = 2, + MaxRoundsWithoutCommittedBlock = 3, + RoundModulusTriggerWhenSyncIsStuck = 4, + MaxSyncWithErrorsAllowed = 5, + MaxRoundsToKeepUnprocessedMiniBlocks = 6, + MaxRoundsToKeepUnprocessedTransactions = 7, + MaxConsecutiveRoundsOfRatingDecrease = 11, + MaxRoundsOfInactivityAccepted = 12, + MaxBlockProcessingTimeMs = 13, + NumHeadersToRequestInAdvance = 14 + } ] + [MiniBlocksStorage] [MiniBlocksStorage.Cache] Capacity = ` + strconv.Itoa(txBlockBodyStorageSize) + ` @@ -214,6 +434,157 @@ func TestTomlParser(t *testing.T) { [Consensus] Type = "` + consensusType + `" +[TxCacheBounds] + MaxNumBytesPerSenderUpperBound = 33_554_432 + MaxTrackedBlocks = 100 + PropagationGracePeriodMs = 200 + +[TxCacheSelection] + SelectionMaxNumTxs = 30000 + SelectionGasRequested = 10_000_000_000 + SelectionGasBandwidthIncreasePercent = 400 + SelectionGasBandwidthIncreaseScheduledPercent = 260 + SelectionLoopDurationCheckInterval = 10 + +[Antiflood] + Enabled = true + + [[Antiflood.ConfigsByRound]] + Round = 0 + NumConcurrentResolverJobs = 50 + NumConcurrentResolvingTrieNodesJobs = 3 + + [Antiflood.ConfigsByRound.FastReacting] + IntervalInSeconds = 1 + ReservedPercent = 20.0 + [Antiflood.ConfigsByRound.FastReacting.PeerMaxInput] + BaseMessagesPerInterval = 280 + TotalSizePerInterval = 4194304 + [Antiflood.ConfigsByRound.FastReacting.PeerMaxInput.IncreaseFactor] + Threshold = 10 + Factor = 1.0 + [Antiflood.ConfigsByRound.FastReacting.BlackList] + ThresholdNumMessagesPerInterval = 1000 + ThresholdSizePerInterval = 8388608 + NumFloodingRounds = 10 + PeerBanDurationInSeconds = 300 + + [Antiflood.ConfigsByRound.SlowReacting] + IntervalInSeconds = 30 + ReservedPercent = 20.0 + [Antiflood.ConfigsByRound.SlowReacting.PeerMaxInput] + BaseMessagesPerInterval = 6000 + TotalSizePerInterval = 18874368 + [Antiflood.ConfigsByRound.SlowReacting.PeerMaxInput.IncreaseFactor] + Threshold = 10 + Factor = 0.0 + [Antiflood.ConfigsByRound.SlowReacting.BlackList] + ThresholdNumMessagesPerInterval = 10000 + ThresholdSizePerInterval = 37748736 + NumFloodingRounds = 2 + PeerBanDurationInSeconds = 3600 + + [Antiflood.ConfigsByRound.OutOfSpecs] + IntervalInSeconds = 1 + ReservedPercent = 0.0 + [Antiflood.ConfigsByRound.OutOfSpecs.PeerMaxInput] + BaseMessagesPerInterval = 2000 + TotalSizePerInterval = 10485760 + [Antiflood.ConfigsByRound.OutOfSpecs.PeerMaxInput.IncreaseFactor] + Threshold = 0 + Factor = 0.0 + [Antiflood.ConfigsByRound.OutOfSpecs.BlackList] + ThresholdNumMessagesPerInterval = 3600 + ThresholdSizePerInterval = 12582912 + NumFloodingRounds = 2 + PeerBanDurationInSeconds = 3600 + + [Antiflood.ConfigsByRound.PeerMaxOutput] + [Antiflood.ConfigsByRound.PeerMaxOutput.PeerMaxInput] + BaseMessagesPerInterval = 150 + TotalSizePerInterval = 2097152 #2MB/s + + [Antiflood.ConfigsByRound.Cache] + Name = "Antiflood" + Capacity = 7000 + Type = "LRU" + [Antiflood.ConfigsByRound.Topic] + DefaultMaxMessagesPerSec = 15000 + MaxMessages = [{ Topic = "shardBlocks*", NumMessagesPerSec = 30 }, + { Topic = "metachainBlocks", NumMessagesPerSec = 30 }] + + [Antiflood.ConfigsByRound.TxAccumulator] + MaxAllowedTimeInMilliseconds = 250 + MaxDeviationTimeInMilliseconds = 25 + + [[Antiflood.ConfigsByRound]] + Round = 440 + NumConcurrentResolverJobs = 50 + NumConcurrentResolvingTrieNodesJobs = 3 + + [Antiflood.ConfigsByRound.FastReacting] + IntervalInSeconds = 1 + ReservedPercent = 20.0 + [Antiflood.ConfigsByRound.FastReacting.PeerMaxInput] + BaseMessagesPerInterval = 280 + TotalSizePerInterval = 4194304 + [Antiflood.ConfigsByRound.FastReacting.PeerMaxInput.IncreaseFactor] + Threshold = 10 + Factor = 1.0 + [Antiflood.ConfigsByRound.FastReacting.BlackList] + ThresholdNumMessagesPerInterval = 10000 + ThresholdSizePerInterval = 12388608 + NumFloodingRounds = 100 + PeerBanDurationInSeconds = 300 + + [Antiflood.ConfigsByRound.SlowReacting] + IntervalInSeconds = 30 + ReservedPercent = 20.0 + [Antiflood.ConfigsByRound.SlowReacting.PeerMaxInput] + BaseMessagesPerInterval = 6000 + TotalSizePerInterval = 18874368 + [Antiflood.ConfigsByRound.SlowReacting.PeerMaxInput.IncreaseFactor] + Threshold = 10 + Factor = 0.0 + [Antiflood.ConfigsByRound.SlowReacting.BlackList] + ThresholdNumMessagesPerInterval = 100000 + ThresholdSizePerInterval = 60748736 + NumFloodingRounds = 20 + PeerBanDurationInSeconds = 3600 + + [Antiflood.ConfigsByRound.OutOfSpecs] + IntervalInSeconds = 1 + ReservedPercent = 0.0 + [Antiflood.ConfigsByRound.OutOfSpecs.PeerMaxInput] + BaseMessagesPerInterval = 20000 + TotalSizePerInterval = 104857600 + [Antiflood.ConfigsByRound.OutOfSpecs.PeerMaxInput.IncreaseFactor] + Threshold = 0 + Factor = 0.0 + [Antiflood.ConfigsByRound.OutOfSpecs.BlackList] + ThresholdNumMessagesPerInterval = 36000 + ThresholdSizePerInterval = 18582912 + NumFloodingRounds = 20 + PeerBanDurationInSeconds = 3600 + + [Antiflood.ConfigsByRound.PeerMaxOutput] + [Antiflood.ConfigsByRound.PeerMaxOutput.PeerMaxInput] + BaseMessagesPerInterval = 150 + TotalSizePerInterval = 2097152 + + [Antiflood.ConfigsByRound.Cache] + Name = "Antiflood" + Capacity = 7000 + Type = "LRU" + [Antiflood.ConfigsByRound.Topic] + DefaultMaxMessagesPerSec = 15000 + MaxMessages = [{ Topic = "shardBlocks*", NumMessagesPerSec = 30 }, + { Topic = "metachainBlocks", NumMessagesPerSec = 30 }] + + [Antiflood.ConfigsByRound.TxAccumulator] + MaxAllowedTimeInMilliseconds = 80 + MaxDeviationTimeInMilliseconds = 25 + [VirtualMachine] [VirtualMachine.Execution] TimeOutForSCExecutionInMilliseconds = 10000 # 10 seconds = 10000 milliseconds @@ -263,11 +634,6 @@ func TestTomlParser(t *testing.T) { PeerStatePruningEnabled = true MaxStateTrieLevelInMemory = 38 MaxPeerTrieLevelInMemory = 39 - -[Redundancy] - # MaxRoundsOfInactivityAccepted defines the number of rounds missed by a main or higher level backup machine before - # the current machine will take over and propose/sign blocks. Used in both single-key and multi-key modes. - MaxRoundsOfInactivityAccepted = 3 ` cfg := Config{} @@ -760,9 +1126,6 @@ func TestEnableEpochConfig(t *testing.T) { # StorageAPICostOptimizationEnableEpoch represents the epoch when new storage helper functions are enabled and cost is reduced in Wasm VM StorageAPICostOptimizationEnableEpoch = 54 - # TransformToMultiShardCreateEnableEpoch represents the epoch when the new function on esdt system sc is enabled to transfer create role into multishard - TransformToMultiShardCreateEnableEpoch = 55 - # ESDTRegisterAndSetAllRolesEnableEpoch represents the epoch when new function to register tickerID and set all roles is enabled ESDTRegisterAndSetAllRolesEnableEpoch = 56 @@ -944,6 +1307,9 @@ func TestEnableEpochConfig(t *testing.T) { # RelayedTransactionsV1V2DisableEpoch represents the epoch when relayed transactions v1 and v2 are disabled RelayedTransactionsV1V2DisableEpoch = 113 + # SupernovaEnableEpoch represents the epoch when sub-second finality will be enabled + SupernovaEnableEpoch = 114 + # MaxNodesChangeEnableEpoch holds configuration for changing the maximum number of nodes and the enabling epoch MaxNodesChangeEnableEpoch = [ { EpochEnable = 44, MaxNumNodes = 2169, NodesToShufflePerShard = 80 }, @@ -1018,7 +1384,6 @@ func TestEnableEpochConfig(t *testing.T) { IsPayableBySCEnableEpoch: 52, CleanUpInformativeSCRsEnableEpoch: 53, StorageAPICostOptimizationEnableEpoch: 54, - TransformToMultiShardCreateEnableEpoch: 55, ESDTRegisterAndSetAllRolesEnableEpoch: 56, ScheduledMiniBlocksEnableEpoch: 57, CorrectJailedNotUnstakedEmptyQueueEpoch: 58, @@ -1077,6 +1442,7 @@ func TestEnableEpochConfig(t *testing.T) { AutomaticActivationOfNodesDisableEpoch: 111, FixGetBalanceEnableEpoch: 112, RelayedTransactionsV1V2DisableEpoch: 113, + SupernovaEnableEpoch: 114, MaxNodesChangeEnableEpoch: []MaxNodesChangeConfig{ { EpochEnable: 44, @@ -1123,3 +1489,35 @@ func TestEnableEpochConfig(t *testing.T) { assert.Nil(t, err) assert.Equal(t, expectedCfg, cfg) } + +func TestEnableRoundsConfig(t *testing.T) { + testString := ` +[RoundActivations] + [RoundActivations.DisableAsyncCallV1] + Options = [] + Round = "0" + + [RoundActivations.SupernovaEnableRound] + Options = [] + Round = "75" +` + + expectedCfg := RoundConfig{ + RoundActivations: map[string]ActivationRoundByName{ + "DisableAsyncCallV1": { + Round: "0", + Options: []string{}, + }, + "SupernovaEnableRound": { + Round: "75", + Options: []string{}, + }, + }, + } + cfg := RoundConfig{} + + err := toml.Unmarshal([]byte(testString), &cfg) + + assert.Nil(t, err) + assert.Equal(t, expectedCfg, cfg) +} diff --git a/consensus/broadcast/commonMessenger.go b/consensus/broadcast/commonMessenger.go index e28c39defec..98f439a27d0 100644 --- a/consensus/broadcast/commonMessenger.go +++ b/consensus/broadcast/commonMessenger.go @@ -171,6 +171,7 @@ func (cm *commonMessenger) BroadcastBlockData( time.Sleep(extraDelayForBroadcast) if len(miniBlocks) > 0 { + // TODO: check if we need to pack also miniblock into smaller chunks err := cm.BroadcastMiniBlocks(miniBlocks, pkBytes) if err != nil { log.Warn("commonMessenger.BroadcastBlockData: broadcast miniblocks", "error", err.Error()) diff --git a/consensus/broadcast/delayedBroadcast.go b/consensus/broadcast/delayedBroadcast.go index 9f67dcbc248..ea382b326de 100644 --- a/consensus/broadcast/delayedBroadcast.go +++ b/consensus/broadcast/delayedBroadcast.go @@ -10,7 +10,6 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" - "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/consensus" @@ -26,11 +25,21 @@ import ( const prefixHeaderAlarm = "header_" const prefixDelayDataAlarm = "delay_" const sizeHeadersCache = 1000 // 1000 hashes in cache +const sizeProcessedMetaHeadersCache = 1000 + +type shardDataHandler interface { + GetHeaderHash() []byte + GetRound() uint64 + GetShardID() uint32 +} // ArgsDelayedBlockBroadcaster holds the arguments to create a delayed block broadcaster type ArgsDelayedBlockBroadcaster struct { InterceptorsContainer process.InterceptorsContainer HeadersSubscriber consensus.HeadersPoolSubscriber + HeadersPool consensus.HeadersPoolGetter + ProofsPool consensus.EquivalentProofsPool + EnableEpochsHandler common.EnableEpochsHandler ShardCoordinator sharding.Coordinator LeaderCacheSize uint32 ValidatorCacheSize uint32 @@ -46,8 +55,8 @@ type timersScheduler interface { } type headerDataForValidator struct { - round uint64 - prevRandSeed []byte + round uint64 + headerHash []byte } type delayedBlockBroadcaster struct { @@ -55,6 +64,9 @@ type delayedBlockBroadcaster struct { interceptorsContainer process.InterceptorsContainer shardCoordinator sharding.Coordinator headersSubscriber consensus.HeadersPoolSubscriber + headersPool consensus.HeadersPoolGetter + proofsPool consensus.EquivalentProofsPool + enableEpochsHandler common.EnableEpochsHandler valHeaderBroadcastData []*shared.ValidatorHeaderBroadcastData valBroadcastData []*shared.DelayedBroadcastData delayedBroadcastData []*shared.DelayedBroadcastData @@ -66,6 +78,7 @@ type delayedBlockBroadcaster struct { broadcastHeader func(header data.HeaderHandler, pkBytes []byte) error broadcastConsensusMessage func(message *consensus.Message) error cacheHeaders storage.Cacher + cacheProcessedMetaHeaders storage.Cacher mutHeadersCache sync.RWMutex } @@ -80,6 +93,15 @@ func NewDelayedBlockBroadcaster(args *ArgsDelayedBlockBroadcaster) (*delayedBloc if check.IfNil(args.HeadersSubscriber) { return nil, spos.ErrNilHeadersSubscriber } + if check.IfNil(args.HeadersPool) { + return nil, spos.ErrNilHeadersPool + } + if check.IfNil(args.ProofsPool) { + return nil, spos.ErrNilEquivalentProofPool + } + if check.IfNil(args.EnableEpochsHandler) { + return nil, spos.ErrNilEnableEpochsHandler + } if check.IfNil(args.AlarmScheduler) { return nil, spos.ErrNilAlarmScheduler } @@ -89,11 +111,19 @@ func NewDelayedBlockBroadcaster(args *ArgsDelayedBlockBroadcaster) (*delayedBloc return nil, err } + cacheProcessedMetaHeaders, err := cache.NewLRUCache(sizeProcessedMetaHeadersCache) + if err != nil { + return nil, err + } + dbb := &delayedBlockBroadcaster{ alarm: args.AlarmScheduler, shardCoordinator: args.ShardCoordinator, interceptorsContainer: args.InterceptorsContainer, headersSubscriber: args.HeadersSubscriber, + headersPool: args.HeadersPool, + proofsPool: args.ProofsPool, + enableEpochsHandler: args.EnableEpochsHandler, valHeaderBroadcastData: make([]*shared.ValidatorHeaderBroadcastData, 0), valBroadcastData: make([]*shared.DelayedBroadcastData, 0), delayedBroadcastData: make([]*shared.DelayedBroadcastData, 0), @@ -101,10 +131,12 @@ func NewDelayedBlockBroadcaster(args *ArgsDelayedBlockBroadcaster) (*delayedBloc maxValidatorDelayCacheSize: args.ValidatorCacheSize, mutDataForBroadcast: sync.RWMutex{}, cacheHeaders: cacheHeaders, + cacheProcessedMetaHeaders: cacheProcessedMetaHeaders, mutHeadersCache: sync.RWMutex{}, } dbb.headersSubscriber.RegisterHandler(dbb.headerReceived) + dbb.proofsPool.RegisterHandler(dbb.receivedProof) err = dbb.registerHeaderInterceptorCallback(dbb.interceptedHeader) if err != nil { return nil, err @@ -261,36 +293,75 @@ func (dbb *delayedBlockBroadcaster) Close() { } func (dbb *delayedBlockBroadcaster) headerReceived(headerHandler data.HeaderHandler, headerHash []byte) { + if headerHandler.GetShardID() != core.MetachainShardId { + return + } + + if common.IsProofsFlagEnabledForHeader(dbb.enableEpochsHandler, headerHandler) { + if !dbb.proofsPool.HasProof(headerHandler.GetShardID(), headerHash) { + return + } + } + + dbb.processMetachainHeaderBroadcast(headerHandler, headerHash) +} + +func (dbb *delayedBlockBroadcaster) receivedProof(proof data.HeaderProofHandler) { + if check.IfNil(proof) { + return + } + if proof.GetHeaderShardId() != core.MetachainShardId { + return + } + + headerHash := proof.GetHeaderHash() + header, err := dbb.headersPool.GetHeaderByHash(headerHash) + if err != nil { + log.Trace("delayedBlockBroadcaster.receivedProof: header not found in pool, will be handled by headerReceived", + "headerHash", headerHash, + ) + return + } + + dbb.processMetachainHeaderBroadcast(header, headerHash) +} + +func (dbb *delayedBlockBroadcaster) processMetachainHeaderBroadcast(headerHandler data.HeaderHandler, headerHash []byte) { + has, _ := dbb.cacheProcessedMetaHeaders.HasOrAdd(headerHash, struct{}{}, 0) + if has { + log.Trace("delayedBlockBroadcaster.processMetachainHeaderBroadcast: already processed, skipping", + "headerHash", headerHash, + ) + return + } + dbb.mutDataForBroadcast.RLock() defer dbb.mutDataForBroadcast.RUnlock() if len(dbb.delayedBroadcastData) == 0 && len(dbb.valBroadcastData) == 0 { return } - if headerHandler.GetShardID() != core.MetachainShardId { - return - } headerHashes, dataForValidators, err := getShardDataFromMetaChainBlock( headerHandler, dbb.shardCoordinator.SelfId(), ) if err != nil { - log.Error("delayedBlockBroadcaster.headerReceived", "error", err.Error(), + log.Error("delayedBlockBroadcaster.processMetachainHeaderBroadcast", "error", err.Error(), "headerHash", headerHash, ) return } if len(headerHashes) == 0 { - log.Trace("delayedBlockBroadcaster.headerReceived: header received with no shardData for current shard", + log.Trace("delayedBlockBroadcaster.processMetachainHeaderBroadcast: no shardData for current shard", "headerHash", headerHash, ) return } - log.Trace("delayedBlockBroadcaster.headerReceived", "nbHeaderHashes", len(headerHashes)) + log.Trace("delayedBlockBroadcaster.processMetachainHeaderBroadcast", "nbHeaderHashes", len(headerHashes)) for i := range headerHashes { - log.Trace("delayedBlockBroadcaster.headerReceived", "headerHash", headerHashes[i]) + log.Trace("delayedBlockBroadcaster.processMetachainHeaderBroadcast", "headerHash", headerHashes[i]) } go dbb.scheduleValidatorBroadcast(dataForValidators) @@ -348,7 +419,7 @@ func (dbb *delayedBlockBroadcaster) scheduleValidatorBroadcast(dataForValidators for i := range dataForValidators { log.Trace("delayedBlockBroadcaster.scheduleValidatorBroadcast", "round", dataForValidators[i].round, - "prevRandSeed", dataForValidators[i].prevRandSeed, + "headerHash", dataForValidators[i].headerHash, ) } @@ -363,9 +434,9 @@ func (dbb *delayedBlockBroadcaster) scheduleValidatorBroadcast(dataForValidators for _, headerData := range dataForValidators { for _, broadcastData := range dbb.valBroadcastData { sameRound := headerData.round == broadcastData.Header.GetRound() - samePrevRandomness := bytes.Equal(headerData.prevRandSeed, broadcastData.Header.GetPrevRandSeed()) - if sameRound && samePrevRandomness { - duration := validatorDelayPerOrder*time.Duration(broadcastData.Order) + common.ExtraDelayForBroadcastBlockInfo + sameHeaderHash := bytes.Equal(headerData.headerHash, broadcastData.HeaderHash) + if sameRound && sameHeaderHash { + duration := common.ExtraDelayForBroadcastBlockInfo alarmID := prefixDelayDataAlarm + hex.EncodeToString(broadcastData.HeaderHash) alarmsToAdd = append(alarmsToAdd, alarmParams{ @@ -376,7 +447,7 @@ func (dbb *delayedBlockBroadcaster) scheduleValidatorBroadcast(dataForValidators "headerHash", broadcastData.HeaderHash, "alarmID", alarmID, "round", headerData.round, - "prevRandSeed", headerData.prevRandSeed, + "headerHash", headerData.headerHash, "consensusOrder", broadcastData.Order, ) } @@ -503,20 +574,25 @@ func getShardDataFromMetaChainBlock( headerHandler data.HeaderHandler, shardID uint32, ) ([][]byte, []*headerDataForValidator, error) { - metaHeader, ok := headerHandler.(*block.MetaBlock) - if !ok { - return nil, nil, spos.ErrInvalidMetaHeader + if check.IfNil(headerHandler) { + return nil, nil, spos.ErrNilHeader } dataForValidators := make([]*headerDataForValidator, 0) shardHeaderHashes := make([][]byte, 0) - shardsInfo := metaHeader.GetShardInfo() + + var shardsInfo []shardDataHandler + shardsInfo, err := getShardInfoHandlers(headerHandler) + if err != nil { + return nil, nil, err + } + for _, shardInfo := range shardsInfo { - if shardInfo.ShardID == shardID { - shardHeaderHashes = append(shardHeaderHashes, shardInfo.HeaderHash) + if shardInfo.GetShardID() == shardID { + shardHeaderHashes = append(shardHeaderHashes, shardInfo.GetHeaderHash()) headerData := &headerDataForValidator{ - round: shardInfo.GetRound(), - prevRandSeed: shardInfo.GetPrevRandSeed(), + round: shardInfo.GetRound(), + headerHash: shardInfo.GetHeaderHash(), } dataForValidators = append(dataForValidators, headerData) } @@ -524,6 +600,43 @@ func getShardDataFromMetaChainBlock( return shardHeaderHashes, dataForValidators, nil } +func getShardInfoHandlers( + headerHandler data.HeaderHandler, +) ([]shardDataHandler, error) { + metaBlock, ok := headerHandler.(data.MetaHeaderHandler) + if !ok { + return nil, spos.ErrInvalidMetaHeader + } + + if metaBlock.IsHeaderV3() { + // the alarm was registered with the header hash keys based on the proposal block + // but the data to be broadcasted was set based on execution results. + // here, we fetch the shard info proposal only for header hash key, so we have to + // fetch based on shard infos proposal (which was associated with the data to broadcast) + // and not based on shard infos for execution + return getShardInfoProposalHandlers(metaBlock) + } + + shardsInfo := make([]shardDataHandler, 0) + for _, shardInfo := range metaBlock.GetShardInfoHandlers() { + shardsInfo = append(shardsInfo, shardInfo) + } + + return shardsInfo, nil +} + +func getShardInfoProposalHandlers( + metaBlock data.MetaHeaderHandler, +) ([]shardDataHandler, error) { + shardsInfo := make([]shardDataHandler, 0) + + for _, shardInfo := range metaBlock.GetShardInfoProposalHandlers() { + shardsInfo = append(shardsInfo, shardInfo) + } + + return shardsInfo, nil +} + func (dbb *delayedBlockBroadcaster) registerHeaderInterceptorCallback( cb func(topic string, hash []byte, data interface{}), ) error { diff --git a/consensus/broadcast/delayedBroadcast_test.go b/consensus/broadcast/delayedBroadcast_test.go index 961bc0efcc9..2ee5b40b8ff 100644 --- a/consensus/broadcast/delayedBroadcast_test.go +++ b/consensus/broadcast/delayedBroadcast_test.go @@ -26,6 +26,8 @@ import ( "github.com/multiversx/mx-chain-go/consensus/spos" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/testscommon" + dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/pool" ) @@ -137,6 +139,9 @@ func createDefaultDelayedBroadcasterArgs() *broadcast.ArgsDelayedBlockBroadcaste ShardCoordinator: &mock.ShardCoordinatorMock{}, InterceptorsContainer: interceptorsContainer, HeadersSubscriber: headersSubscriber, + HeadersPool: headersSubscriber, + ProofsPool: &dataRetrieverMock.ProofsPoolMock{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, LeaderCacheSize: 2, ValidatorCacheSize: 2, AlarmScheduler: alarm.NewAlarmScheduler(), @@ -185,6 +190,36 @@ func TestNewDelayedBlockBroadcaster_NilAlarmSchedulerShouldErr(t *testing.T) { require.Nil(t, dbb) } +func TestNewDelayedBlockBroadcaster_NilHeadersPoolShouldErr(t *testing.T) { + t.Parallel() + + delayBroadcasterArgs := createDefaultDelayedBroadcasterArgs() + delayBroadcasterArgs.HeadersPool = nil + dbb, err := broadcast.NewDelayedBlockBroadcaster(delayBroadcasterArgs) + require.Equal(t, spos.ErrNilHeadersPool, err) + require.Nil(t, dbb) +} + +func TestNewDelayedBlockBroadcaster_NilProofsPoolShouldErr(t *testing.T) { + t.Parallel() + + delayBroadcasterArgs := createDefaultDelayedBroadcasterArgs() + delayBroadcasterArgs.ProofsPool = nil + dbb, err := broadcast.NewDelayedBlockBroadcaster(delayBroadcasterArgs) + require.Equal(t, spos.ErrNilEquivalentProofPool, err) + require.Nil(t, dbb) +} + +func TestNewDelayedBlockBroadcaster_NilEnableEpochsHandlerShouldErr(t *testing.T) { + t.Parallel() + + delayBroadcasterArgs := createDefaultDelayedBroadcasterArgs() + delayBroadcasterArgs.EnableEpochsHandler = nil + dbb, err := broadcast.NewDelayedBlockBroadcaster(delayBroadcasterArgs) + require.Equal(t, spos.ErrNilEnableEpochsHandler, err) + require.Nil(t, dbb) +} + func TestNewDelayedBlockBroadcasterOK(t *testing.T) { t.Parallel() @@ -194,6 +229,237 @@ func TestNewDelayedBlockBroadcasterOK(t *testing.T) { require.NotNil(t, dbb) } +func TestDelayedBlockBroadcaster_HeaderReceivedProofsEnabled_ShouldNotBroadcastWithoutProof(t *testing.T) { + t.Parallel() + + mbBroadcastCalled := atomic.Flag{} + delayBroadcasterArgs := createDefaultDelayedBroadcasterArgs() + delayBroadcasterArgs.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.AndromedaFlag + }, + } + delayBroadcasterArgs.ProofsPool = &dataRetrieverMock.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return false // no proof available + }, + } + + dbb, err := broadcast.NewDelayedBlockBroadcaster(delayBroadcasterArgs) + require.Nil(t, err) + + err = dbb.SetBroadcastHandlers( + func(mbData map[uint32][]byte, pk []byte) error { + mbBroadcastCalled.SetValue(true) + return nil + }, + func(txData map[string][][]byte, pk []byte) error { return nil }, + func(header data.HeaderHandler, pk []byte) error { return nil }, + func(message *consensus.Message) error { return nil }, + ) + require.Nil(t, err) + + headerHash, _, miniblocksData, transactionsData := createDelayData("1") + delayedData := broadcast.CreateDelayBroadcastDataForLeader(headerHash, miniblocksData, transactionsData) + _ = dbb.SetLeaderData(delayedData) + + metaBlock := createMetaBlock() + metaBlock.Nonce = 1 // nonce > 0 so proofs flag applies + + dbb.HeaderReceived(metaBlock, []byte("meta hash")) + time.Sleep(common.ExtraDelayForBroadcastBlockInfo + common.ExtraDelayBetweenBroadcastMbsAndTxs + 100*time.Millisecond) + + assert.False(t, mbBroadcastCalled.IsSet(), "should NOT broadcast when proof is missing and proofs flag enabled") +} + +func TestDelayedBlockBroadcaster_ReceivedProof_HeaderNotInPoolShouldNotBroadcast(t *testing.T) { + t.Parallel() + + mbBroadcastCalled := atomic.Flag{} + delayBroadcasterArgs := createDefaultDelayedBroadcasterArgs() + delayBroadcasterArgs.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.AndromedaFlag + }, + } + delayBroadcasterArgs.HeadersPool = &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, errors.New("not found") + }, + } + + dbb, err := broadcast.NewDelayedBlockBroadcaster(delayBroadcasterArgs) + require.Nil(t, err) + + err = dbb.SetBroadcastHandlers( + func(mbData map[uint32][]byte, pk []byte) error { + mbBroadcastCalled.SetValue(true) + return nil + }, + func(txData map[string][][]byte, pk []byte) error { return nil }, + func(header data.HeaderHandler, pk []byte) error { return nil }, + func(message *consensus.Message) error { return nil }, + ) + require.Nil(t, err) + + headerHash, _, miniblocksData, transactionsData := createDelayData("1") + delayedData := broadcast.CreateDelayBroadcastDataForLeader(headerHash, miniblocksData, transactionsData) + _ = dbb.SetLeaderData(delayedData) + + proof := &block.HeaderProof{ + HeaderHash: []byte("meta hash"), + HeaderShardId: core.MetachainShardId, + HeaderNonce: 1, + } + dbb.ReceivedProof(proof) + time.Sleep(common.ExtraDelayForBroadcastBlockInfo + common.ExtraDelayBetweenBroadcastMbsAndTxs + 100*time.Millisecond) + + assert.False(t, mbBroadcastCalled.IsSet(), "should NOT broadcast when header is not in pool") +} + +func TestDelayedBlockBroadcaster_ReceivedProof_NonMetaShouldBeIgnored(t *testing.T) { + t.Parallel() + + mbBroadcastCalled := atomic.Flag{} + delayBroadcasterArgs := createDefaultDelayedBroadcasterArgs() + dbb, err := broadcast.NewDelayedBlockBroadcaster(delayBroadcasterArgs) + require.Nil(t, err) + + err = dbb.SetBroadcastHandlers( + func(mbData map[uint32][]byte, pk []byte) error { + mbBroadcastCalled.SetValue(true) + return nil + }, + func(txData map[string][][]byte, pk []byte) error { return nil }, + func(header data.HeaderHandler, pk []byte) error { return nil }, + func(message *consensus.Message) error { return nil }, + ) + require.Nil(t, err) + + proof := &block.HeaderProof{ + HeaderHash: []byte("shard hash"), + HeaderShardId: 0, // not metachain + } + dbb.ReceivedProof(proof) + time.Sleep(50 * time.Millisecond) + + assert.False(t, mbBroadcastCalled.IsSet(), "should NOT broadcast for non-metachain proofs") +} + +func TestDelayedBlockBroadcaster_ReceivedProof_NilProofShouldNotPanic(t *testing.T) { + t.Parallel() + + delayBroadcasterArgs := createDefaultDelayedBroadcasterArgs() + dbb, err := broadcast.NewDelayedBlockBroadcaster(delayBroadcasterArgs) + require.Nil(t, err) + + require.NotPanics(t, func() { + dbb.ReceivedProof(nil) + }) +} + +func TestDelayedBlockBroadcaster_HeaderReceivedProofsDisabled_ShouldBroadcastImmediately(t *testing.T) { + t.Parallel() + + mbBroadcastCalled := atomic.Flag{} + + delayBroadcasterArgs := createDefaultDelayedBroadcasterArgs() + // default EnableEpochsHandlerStub returns false for IsFlagEnabledInEpoch -> proofs disabled + dbb, err := broadcast.NewDelayedBlockBroadcaster(delayBroadcasterArgs) + require.Nil(t, err) + + err = dbb.SetBroadcastHandlers( + func(mbData map[uint32][]byte, pk []byte) error { + mbBroadcastCalled.SetValue(true) + return nil + }, + func(txData map[string][][]byte, pk []byte) error { return nil }, + func(header data.HeaderHandler, pk []byte) error { return nil }, + func(message *consensus.Message) error { return nil }, + ) + require.Nil(t, err) + + headerHash := []byte("shard0 headerHash") + miniblocksData := map[uint32][]byte{1: []byte("miniblock data")} + transactionsData := map[string][][]byte{"txBlockBodies_0_1": {[]byte("tx0")}} + delayedData := broadcast.CreateDelayBroadcastDataForLeader(headerHash, miniblocksData, transactionsData) + _ = dbb.SetLeaderData(delayedData) + + metaBlock := createMetaBlock() + dbb.HeaderReceived(metaBlock, []byte("meta hash")) + time.Sleep(common.ExtraDelayForBroadcastBlockInfo + common.ExtraDelayBetweenBroadcastMbsAndTxs + 100*time.Millisecond) + + assert.True(t, mbBroadcastCalled.IsSet(), "should broadcast when proofs flag is disabled (backwards compat)") +} + +func TestDelayedBlockBroadcaster_HeaderArrivesFirst_ThenProofTriggersBroadcast(t *testing.T) { + t.Parallel() + + mbBroadcastCalled := atomic.Flag{} + proofAvailable := atomic.Flag{} + + delayBroadcasterArgs := createDefaultDelayedBroadcasterArgs() + delayBroadcasterArgs.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.AndromedaFlag + }, + } + delayBroadcasterArgs.ProofsPool = &dataRetrieverMock.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return proofAvailable.IsSet() + }, + } + + metaBlock := createMetaBlock() + metaBlock.Nonce = 1 + metaHash := []byte("meta hash") + + delayBroadcasterArgs.HeadersPool = &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, metaHash) { + return metaBlock, nil + } + return nil, errors.New("not found") + }, + } + + dbb, err := broadcast.NewDelayedBlockBroadcaster(delayBroadcasterArgs) + require.Nil(t, err) + + err = dbb.SetBroadcastHandlers( + func(mbData map[uint32][]byte, pk []byte) error { + mbBroadcastCalled.SetValue(true) + return nil + }, + func(txData map[string][][]byte, pk []byte) error { return nil }, + func(header data.HeaderHandler, pk []byte) error { return nil }, + func(message *consensus.Message) error { return nil }, + ) + require.Nil(t, err) + + headerHash := []byte("shard0 headerHash") + miniblocksData := map[uint32][]byte{1: []byte("miniblock data")} + transactionsData := map[string][][]byte{"txBlockBodies_0_1": {[]byte("tx0")}} + delayedData := broadcast.CreateDelayBroadcastDataForLeader(headerHash, miniblocksData, transactionsData) + _ = dbb.SetLeaderData(delayedData) + + // Step 1: header arrives, no proof yet -> should NOT broadcast + dbb.HeaderReceived(metaBlock, metaHash) + time.Sleep(common.ExtraDelayForBroadcastBlockInfo + common.ExtraDelayBetweenBroadcastMbsAndTxs + 100*time.Millisecond) + assert.False(t, mbBroadcastCalled.IsSet(), "should not broadcast before proof arrives") + + // Step 2: proof arrives -> should trigger broadcast via headerReceived delegation + proofAvailable.SetValue(true) + proof := &block.HeaderProof{ + HeaderHash: metaHash, + HeaderShardId: core.MetachainShardId, + HeaderNonce: 1, + } + dbb.ReceivedProof(proof) + time.Sleep(common.ExtraDelayForBroadcastBlockInfo + common.ExtraDelayBetweenBroadcastMbsAndTxs + 100*time.Millisecond) + assert.True(t, mbBroadcastCalled.IsSet(), "should broadcast after proof arrives") +} + func TestDelayedBlockBroadcaster_HeaderReceivedNoDelayedDataRegistered(t *testing.T) { t.Parallel() @@ -387,7 +653,7 @@ func TestDelayedBlockBroadcaster_HeaderReceivedWithoutSignaturesForShardShouldNo time.Sleep(sleepTime) logOutputStr := observer.getBufferStr() - expectedLogMsg := "delayedBlockBroadcaster.headerReceived: header received with no shardData for current shard" + expectedLogMsg := "delayedBlockBroadcaster.processMetachainHeaderBroadcast: no shardData for current shard" require.Contains(t, logOutputStr, expectedLogMsg) require.Contains(t, logOutputStr, fmt.Sprintf("headerHash = %s", hex.EncodeToString(headerHash))) @@ -1177,8 +1443,8 @@ func TestDelayedBlockBroadcaster_ScheduleValidatorBroadcastDifferentHeaderRoundS require.Equal(t, 1, len(vbd)) hdfv := &broadcast.HeaderDataForValidator{ - Round: vArgs.header.GetRound() + 1, - PrevRandSeed: vArgs.header.GetPrevRandSeed(), + Round: vArgs.header.GetRound() + 1, + HeaderHash: vArgs.headerHash, } dbb.ScheduleValidatorBroadcast([]*broadcast.HeaderDataForValidator{hdfv}) @@ -1236,12 +1502,12 @@ func TestDelayedBlockBroadcaster_ScheduleValidatorBroadcastDifferentPrevRandShou vbd := dbb.GetValidatorBroadcastData() require.Equal(t, 1, len(vbd)) - differentPrevRandSeed := make([]byte, len(vArgs.header.GetPrevRandSeed())) - copy(differentPrevRandSeed, vArgs.header.GetPrevRandSeed()) - differentPrevRandSeed[0] = ^differentPrevRandSeed[0] + differentHeaderHash := make([]byte, len(vArgs.header.GetPrevRandSeed())) + copy(differentHeaderHash, vArgs.header.GetPrevRandSeed()) + differentHeaderHash[0] = ^differentHeaderHash[0] hdfv := &broadcast.HeaderDataForValidator{ - Round: vArgs.header.GetRound(), - PrevRandSeed: differentPrevRandSeed, + Round: vArgs.header.GetRound(), + HeaderHash: differentHeaderHash, } dbb.ScheduleValidatorBroadcast([]*broadcast.HeaderDataForValidator{hdfv}) @@ -1300,8 +1566,8 @@ func TestDelayedBlockBroadcaster_ScheduleValidatorBroadcastSameRoundAndPrevRandS require.Equal(t, 1, len(vbd)) hdfv := &broadcast.HeaderDataForValidator{ - Round: vArgs.header.GetRound(), - PrevRandSeed: vArgs.header.GetPrevRandSeed(), + Round: vArgs.header.GetRound(), + HeaderHash: vArgs.headerHash, } dbb.ScheduleValidatorBroadcast([]*broadcast.HeaderDataForValidator{hdfv}) @@ -1595,36 +1861,151 @@ func TestDelayedBlockBroadcaster_BroadcastBlockDataFailedBroadcast(t *testing.T) require.Contains(t, logOutputStr, errTxs) } -func TestDelayedBlockBroadcaster_GetShardDataFromMetaChainBlockInvalidMetaHandler(t *testing.T) { +func TestDelayedBlockBroadcaster_GetShardDataFromMetaChainBlock(t *testing.T) { + t.Parallel() + shardID := uint32(0) - _, _, err := broadcast.GetShardDataFromMetaChainBlock(nil, shardID) - require.NotNil(t, err) - require.Equal(t, spos.ErrInvalidMetaHeader, err) -} + t.Run("nil header", func(t *testing.T) { + t.Parallel() + + headerHashes, dataForValidators, err := broadcast.GetShardDataFromMetaChainBlock(nil, shardID) + require.NotNil(t, err) + require.Nil(t, headerHashes) + require.Empty(t, dataForValidators) + + require.Equal(t, spos.ErrNilHeader, err) + }) + + t.Run("shard header, should fail", func(t *testing.T) { + t.Parallel() + + headerHashes, dataForValidators, err := broadcast.GetShardDataFromMetaChainBlock(&block.HeaderV3{}, shardID) + require.NotNil(t, err) + require.Nil(t, headerHashes) + require.Empty(t, dataForValidators) + + require.Equal(t, spos.ErrInvalidMetaHeader, err) + }) + + t.Run("should work before header v3", func(t *testing.T) { + t.Parallel() + + headerHash0 := []byte("headerHash0") + headerRound0 := uint64(2) + shardID := uint32(1) + + metaHeader := &block.MetaBlock{ + Nonce: 5, + Round: 1, + ShardInfo: []block.ShardData{ + { + HeaderHash: headerHash0, + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("miniblock hash"), + ReceiverShardID: 1, + SenderShardID: 0, + TxCount: 2, + }, + }, + Round: headerRound0, + ShardID: shardID, + }, + }, + } -func TestDelayedBlockBroadcaster_GetShardDataFromMetaChainBlock(t *testing.T) { - metaHeader := createMetaBlock() - shardID := uint32(0) + expHeaderHashes := [][]byte{ + headerHash0, + } - expHeaderHashes := make([][]byte, 0) - valData := make([]*broadcast.HeaderDataForValidator, 0) + expValData := []*broadcast.HeaderDataForValidator{ + { + Round: headerRound0, + HeaderHash: headerHash0, + }, + } - for _, shInfo := range metaHeader.ShardInfo { - if shInfo.ShardID != shardID { - continue + headerHashes, dfv, err := broadcast.GetShardDataFromMetaChainBlock(metaHeader, shardID) + require.Nil(t, err) + require.Equal(t, expHeaderHashes, headerHashes) + require.Equal(t, expValData, dfv) + }) + + t.Run("should work with meta header v3", func(t *testing.T) { + t.Parallel() + + headerHashP0 := []byte("headerHashP0") + headerRoundP0 := uint64(2) + headerHashP1 := []byte("headerHashP1") + headerRoundP1 := uint64(3) + headerHashP2 := []byte("headerHashP2") + headerRoundP2 := uint64(4) + + shardID := uint32(1) + + metaHeader := &block.MetaBlockV3{ + Nonce: 5, + Round: 1, + ShardInfo: []block.ShardData{ + { + HeaderHash: []byte("headerHash"), // this should not be referenced + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("miniblock hash"), + ReceiverShardID: 1, + SenderShardID: 0, + TxCount: 2, + }, + }, + }, + }, + ShardInfoProposal: []block.ShardDataProposal{ + { + HeaderHash: headerHashP0, + Round: headerRoundP0, + ShardID: shardID, + }, + { + HeaderHash: headerHashP1, + Round: headerRoundP1, + ShardID: shardID, + }, + { + HeaderHash: headerHashP2, + Round: headerRoundP2, + ShardID: shardID, + }, + }, } - valData = append(valData, &broadcast.HeaderDataForValidator{ - Round: shInfo.Round, - PrevRandSeed: shInfo.PrevRandSeed, - }) - expHeaderHashes = append(expHeaderHashes, shInfo.HeaderHash) - } - headerHashes, dfv, err := broadcast.GetShardDataFromMetaChainBlock(metaHeader, shardID) - require.Nil(t, err) - require.Equal(t, expHeaderHashes, headerHashes) - require.Equal(t, valData, dfv) + expHeaderHashes := [][]byte{ + headerHashP0, + headerHashP1, + headerHashP2, + } + + expValData := []*broadcast.HeaderDataForValidator{ + { + Round: headerRoundP0, + HeaderHash: headerHashP0, + }, + { + Round: headerRoundP1, + HeaderHash: headerHashP1, + }, + { + Round: headerRoundP2, + HeaderHash: headerHashP2, + }, + } + + headerHashes, dfv, err := broadcast.GetShardDataFromMetaChainBlock(metaHeader, shardID) + require.Nil(t, err) + + require.Equal(t, expHeaderHashes, headerHashes) + require.Equal(t, expValData, dfv) + }) } func TestDelayedBlockBroadcaster_InterceptedMiniBlockForNotSetValDataShouldBroadcast(t *testing.T) { @@ -1670,8 +2051,8 @@ func TestDelayedBlockBroadcaster_InterceptedMiniBlockForNotSetValDataShouldBroad vbd := dbb.GetValidatorBroadcastData() require.Equal(t, 1, len(vbd)) hdfv := &broadcast.HeaderDataForValidator{ - Round: vArgs.header.GetRound(), - PrevRandSeed: vArgs.header.GetPrevRandSeed(), + Round: vArgs.header.GetRound(), + HeaderHash: vArgs.headerHash, } dbb.InterceptedMiniBlockData("txBlockBodies_0_1", []byte("some other miniBlock hash"), &block.MiniBlock{}) @@ -1740,8 +2121,8 @@ func TestDelayedBlockBroadcaster_InterceptedMiniBlockOutOfManyForSetValDataShoul require.Equal(t, 1, len(vbd)) hdfv := &broadcast.HeaderDataForValidator{ - Round: vArgs.header.GetRound(), - PrevRandSeed: vArgs.header.GetPrevRandSeed(), + Round: vArgs.header.GetRound(), + HeaderHash: vArgs.headerHash, } dbb.ScheduleValidatorBroadcast([]*broadcast.HeaderDataForValidator{hdfv}) @@ -1805,8 +2186,8 @@ func TestDelayedBlockBroadcaster_InterceptedMiniBlockFinalForSetValDataShouldNot require.Equal(t, 1, len(vbd)) hdfv := &broadcast.HeaderDataForValidator{ - Round: vArgs.header.GetRound(), - PrevRandSeed: vArgs.header.GetPrevRandSeed(), + Round: vArgs.header.GetRound(), + HeaderHash: vArgs.headerHash, } dbb.ScheduleValidatorBroadcast([]*broadcast.HeaderDataForValidator{hdfv}) @@ -1871,8 +2252,8 @@ func TestDelayedBlockBroadcaster_Close(t *testing.T) { require.Equal(t, 1, len(vbd)) hdfv := &broadcast.HeaderDataForValidator{ - Round: vArgs.header.GetRound(), - PrevRandSeed: vArgs.header.GetPrevRandSeed(), + Round: vArgs.header.GetRound(), + HeaderHash: vArgs.headerHash, } dbb.ScheduleValidatorBroadcast([]*broadcast.HeaderDataForValidator{hdfv}) diff --git a/consensus/broadcast/export.go b/consensus/broadcast/export.go index 5351003e38f..7addb7a4c48 100644 --- a/consensus/broadcast/export.go +++ b/consensus/broadcast/export.go @@ -14,8 +14,8 @@ import ( // HeaderDataForValidator - type HeaderDataForValidator struct { - Round uint64 - PrevRandSeed []byte + Round uint64 + HeaderHash []byte } // ExtractMetaMiniBlocksAndTransactions - @@ -81,6 +81,12 @@ func (dbb *delayedBlockBroadcaster) HeaderReceived(headerHandler data.HeaderHand dbb.headerReceived(headerHandler, hash) } +// ReceivedProof is the callback registered on the proofs pool +// to be called when a proof is added to the proofs pool +func (dbb *delayedBlockBroadcaster) ReceivedProof(proof data.HeaderProofHandler) { + dbb.receivedProof(proof) +} + // GetValidatorBroadcastData returns the set validator delayed broadcast data func (dbb *delayedBlockBroadcaster) GetValidatorBroadcastData() []*shared.DelayedBroadcastData { dbb.mutDataForBroadcast.RLock() @@ -121,8 +127,8 @@ func (dbb *delayedBlockBroadcaster) ScheduleValidatorBroadcast(dataForValidators dfv := make([]*headerDataForValidator, len(dataForValidators)) for i, d := range dataForValidators { convDfv := &headerDataForValidator{ - round: d.Round, - prevRandSeed: d.PrevRandSeed, + round: d.Round, + headerHash: d.HeaderHash, } dfv[i] = convDfv } @@ -149,8 +155,8 @@ func GetShardDataFromMetaChainBlock( dfv := make([]*HeaderDataForValidator, len(dataForValidators)) for i, d := range dataForValidators { convDfv := &HeaderDataForValidator{ - Round: d.round, - PrevRandSeed: d.prevRandSeed, + Round: d.round, + HeaderHash: d.headerHash, } dfv[i] = convDfv } diff --git a/consensus/broadcast/export_test.go b/consensus/broadcast/export_test.go deleted file mode 100644 index 646dfa9b161..00000000000 --- a/consensus/broadcast/export_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package broadcast - -import ( - "github.com/multiversx/mx-chain-core-go/marshal" -) - -// SetMarshalizerMeta sets the unexported marshaller -func (mcm *metaChainMessenger) SetMarshalizerMeta( - m marshal.Marshalizer, -) { - mcm.marshalizer = m -} diff --git a/consensus/broadcast/metaChainMessenger_test.go b/consensus/broadcast/metaChainMessenger_test.go index 7f188e48c59..13c647b7844 100644 --- a/consensus/broadcast/metaChainMessenger_test.go +++ b/consensus/broadcast/metaChainMessenger_test.go @@ -9,6 +9,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -346,10 +347,16 @@ func TestMetaChainMessenger_PrepareBroadcastHeaderValidator(t *testing.T) { require.NotNil(t, mcm) mcm.PrepareBroadcastHeaderValidator(nil, make(map[uint32][]byte), make(map[string][][]byte), 0, make([]byte, 0)) }) + t.Run("Err on core.CalculateHash", func(t *testing.T) { t.Parallel() args := createDefaultMetaChainArgs() + args.Marshalizer = &testscommon.MarshallerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + return nil, errors.New("some err") + }, + } delayedBroadcaster := &consensusMock.DelayedBroadcasterMock{ SetHeaderForValidatorCalled: func(vData *shared.ValidatorHeaderBroadcastData) error { require.Fail(t, "SetHeaderForValidator should not be called") @@ -361,9 +368,10 @@ func TestMetaChainMessenger_PrepareBroadcastHeaderValidator(t *testing.T) { header := &block.Header{} mcm, _ := broadcast.NewMetaChainMessenger(args) require.NotNil(t, mcm) - mcm.SetMarshalizerMeta(nil) + mcm.PrepareBroadcastHeaderValidator(header, make(map[uint32][]byte), make(map[string][][]byte), 0, make([]byte, 0)) }) + t.Run("Err on SetHeaderForValidator", func(t *testing.T) { t.Parallel() diff --git a/consensus/broadcast/shardChainMessenger.go b/consensus/broadcast/shardChainMessenger.go index 233055bc9f8..d7bbb8afd76 100644 --- a/consensus/broadcast/shardChainMessenger.go +++ b/consensus/broadcast/shardChainMessenger.go @@ -180,6 +180,7 @@ func (scm *shardChainMessenger) prepareDataToBroadcast( return nil, err } + // TODO: check if miniblocks and txs are set in a deterministic way (check if there are map iterations that can generate non-deterministic results) metaMiniBlocks, metaTransactions := scm.extractMetaMiniBlocksAndTransactions(miniBlocks, transactions) dtb := &dataToBroadcast{ diff --git a/consensus/broadcast/shardChainMessenger_test.go b/consensus/broadcast/shardChainMessenger_test.go index 7846ba12b0d..0b0d55d62b1 100644 --- a/consensus/broadcast/shardChainMessenger_test.go +++ b/consensus/broadcast/shardChainMessenger_test.go @@ -26,6 +26,8 @@ import ( "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/factory" "github.com/multiversx/mx-chain-go/testscommon" + dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" "github.com/multiversx/mx-chain-go/testscommon/p2pmocks" ) @@ -572,6 +574,9 @@ func TestShardChainMessenger_BroadcastBlockDataLeaderShouldTriggerWaitingDelayed argsDelayedBroadcaster := broadcast.ArgsDelayedBlockBroadcaster{ InterceptorsContainer: args.InterceptorsContainer, HeadersSubscriber: args.HeadersSubscriber, + HeadersPool: &pool.HeadersPoolStub{}, + ProofsPool: &dataRetrieverMock.ProofsPoolMock{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, ShardCoordinator: args.ShardCoordinator, LeaderCacheSize: args.MaxDelayCacheSize, ValidatorCacheSize: args.MaxDelayCacheSize, diff --git a/consensus/chronology/argChronology.go b/consensus/chronology/argChronology.go deleted file mode 100644 index 79b012e55b6..00000000000 --- a/consensus/chronology/argChronology.go +++ /dev/null @@ -1,18 +0,0 @@ -package chronology - -import ( - "time" - - "github.com/multiversx/mx-chain-core-go/core" - "github.com/multiversx/mx-chain-go/consensus" - "github.com/multiversx/mx-chain-go/ntp" -) - -// ArgChronology holds all dependencies required by the chronology component -type ArgChronology struct { - GenesisTime time.Time - RoundHandler consensus.RoundHandler - SyncTimer ntp.SyncTimer - Watchdog core.WatchdogTimer - AppStatusHandler core.AppStatusHandler -} diff --git a/consensus/chronology/chronology.go b/consensus/chronology/chronology.go index f4dc90604b7..cf3f4288a91 100644 --- a/consensus/chronology/chronology.go +++ b/consensus/chronology/chronology.go @@ -10,10 +10,11 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/core/closing" "github.com/multiversx/mx-chain-core-go/display" - "github.com/multiversx/mx-chain-logger-go" + logger "github.com/multiversx/mx-chain-logger-go" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/consensus" + "github.com/multiversx/mx-chain-go/errors" "github.com/multiversx/mx-chain-go/ntp" ) @@ -25,9 +26,20 @@ var log = logger.GetOrCreate("consensus/chronology") // srBeforeStartRound defines the state which exist before the start of the round const srBeforeStartRound = -1 -const numRoundsToWaitBeforeSignalingChronologyStuck = 10 const chronologyAlarmID = "chronology" +// ArgChronology holds all dependencies required by the chronology component +type ArgChronology struct { + GenesisTime time.Time + RoundHandler consensus.RoundHandler + SyncTimer ntp.SyncTimer + Watchdog core.WatchdogTimer + AppStatusHandler core.AppStatusHandler + EnableEpochsHandler common.EnableEpochsHandler + EnableRoundsHandler common.EnableRoundsHandler + ConfigsHandler common.CommonConfigsHandler +} + // chronology defines the data needed by the chronology type chronology struct { genesisTime time.Time @@ -43,23 +55,29 @@ type chronology struct { appStatusHandler core.AppStatusHandler cancelFunc func() - watchdog core.WatchdogTimer + watchdog core.WatchdogTimer + enableEpochsHandler common.EnableEpochsHandler + enableRoundsHandler common.EnableRoundsHandler + configsHandler common.CommonConfigsHandler + supernovaTransitionDone bool } // NewChronology creates a new chronology object func NewChronology(arg ArgChronology) (*chronology, error) { - err := checkNewChronologyParams(arg) if err != nil { return nil, err } chr := chronology{ - genesisTime: arg.GenesisTime, - roundHandler: arg.RoundHandler, - syncTimer: arg.SyncTimer, - appStatusHandler: arg.AppStatusHandler, - watchdog: arg.Watchdog, + genesisTime: arg.GenesisTime, + roundHandler: arg.RoundHandler, + syncTimer: arg.SyncTimer, + appStatusHandler: arg.AppStatusHandler, + watchdog: arg.Watchdog, + enableEpochsHandler: arg.EnableEpochsHandler, + enableRoundsHandler: arg.EnableRoundsHandler, + configsHandler: arg.ConfigsHandler, } chr.subroundId = srBeforeStartRound @@ -71,7 +89,6 @@ func NewChronology(arg ArgChronology) (*chronology, error) { } func checkNewChronologyParams(arg ArgChronology) error { - if check.IfNil(arg.RoundHandler) { return ErrNilRoundHandler } @@ -84,6 +101,15 @@ func checkNewChronologyParams(arg ArgChronology) error { if check.IfNil(arg.AppStatusHandler) { return ErrNilAppStatusHandler } + if check.IfNil(arg.EnableEpochsHandler) { + return errors.ErrNilEnableEpochsHandler + } + if check.IfNil(arg.EnableRoundsHandler) { + return errors.ErrNilEnableRoundsHandler + } + if check.IfNil(arg.ConfigsHandler) { + return common.ErrNilCommonConfigsHandler + } return nil } @@ -111,7 +137,8 @@ func (chr *chronology) RemoveAllSubrounds() { // StartRounds actually starts the chronology and calls the DoWork() method of the subroundHandlers loaded func (chr *chronology) StartRounds() { - watchdogAlarmDuration := chr.roundHandler.TimeDuration() * numRoundsToWaitBeforeSignalingChronologyStuck + alarmDurationRounds := chr.getNumRoundsToWaitBeforeSignalingChronologyStuck() + watchdogAlarmDuration := chr.roundHandler.TimeDuration() * time.Duration(alarmDurationRounds) chr.watchdog.SetDefault(watchdogAlarmDuration, chronologyAlarmID) var ctx context.Context @@ -119,6 +146,11 @@ func (chr *chronology) StartRounds() { go chr.startRounds(ctx) } +func (chr *chronology) getNumRoundsToWaitBeforeSignalingChronologyStuck() uint32 { + supernovaActivationEpoch := chr.enableEpochsHandler.GetActivationEpoch(common.SupernovaFlag) + return chr.configsHandler.GetNumRoundsToWaitBeforeSignalingChronologyStuck(supernovaActivationEpoch) +} + func (chr *chronology) startRounds(ctx context.Context) { // force a round update to initialize the round roundHandlerWithRevert := chr.roundHandler.(consensus.RoundHandlerConsensusSwitch) @@ -169,7 +201,8 @@ func (chr *chronology) updateRound() { if oldRoundIndex != chr.roundHandler.Index() { chr.watchdog.Reset(chronologyAlarmID) - msg := fmt.Sprintf("ROUND %d BEGINS (%d)", chr.roundHandler.Index(), chr.roundHandler.TimeStamp().Unix()) + + msg := fmt.Sprintf("ROUND %d BEGINS (%d)", chr.roundHandler.Index(), chr.roundHandler.TimeStamp().UnixMilli()) log.Debug(display.Headline(msg, chr.syncTimer.FormattedCurrentTime(), "#")) logger.SetCorrelationRound(chr.roundHandler.Index()) @@ -177,6 +210,14 @@ func (chr *chronology) updateRound() { } } +func (chr *chronology) getRoundUnixTimeStamp() int64 { + if chr.enableEpochsHandler.IsFlagEnabled(common.SupernovaFlag) { + return chr.roundHandler.TimeStamp().UnixMilli() + } + + return chr.roundHandler.TimeStamp().Unix() +} + // initRound is called when a new round begins, and it does the necessary initialization func (chr *chronology) initRound() { chr.subroundId = srBeforeStartRound @@ -187,13 +228,43 @@ func (chr *chronology) initRound() { if hasSubroundsAndGenesisTimePassed { chr.subroundId = chr.subroundHandlers[0].Current() - chr.appStatusHandler.SetUInt64Value(common.MetricCurrentRound, uint64(chr.roundHandler.Index())) - chr.appStatusHandler.SetUInt64Value(common.MetricCurrentRoundTimestamp, uint64(chr.roundHandler.TimeStamp().Unix())) + + roundIndex := uint64(chr.roundHandler.Index()) + chr.appStatusHandler.SetUInt64Value(common.MetricCurrentRound, roundIndex) + chr.appStatusHandler.SetUInt64Value(common.MetricCurrentRoundTimestamp, uint64(chr.getRoundUnixTimeStamp())) + + chr.handleSupernovaTransitionIfNeeded() } chr.mutSubrounds.RUnlock() } +func (chr *chronology) handleSupernovaTransitionIfNeeded() { + if chr.supernovaTransitionDone { + return + } + + if !chr.enableEpochsHandler.IsFlagEnabled(common.SupernovaFlag) { + return + } + + roundIndex := uint64(chr.roundHandler.Index()) + supernovaActivationRound := chr.enableRoundsHandler.GetActivationRound(common.SupernovaRoundFlag) + if supernovaActivationRound > roundIndex { + return + } + + chr.appStatusHandler.SetUInt64Value(common.MetricRoundDuration, uint64(chr.roundHandler.TimeDuration().Milliseconds())) + + // update time duration on each subround + // TODO: analyze if we should consider modifying startTime and endTime as well for each subround after Supernova + for _, subroundHandler := range chr.subroundHandlers { + subroundHandler.SetBaseDuration(chr.roundHandler.TimeDuration()) + } + + chr.supernovaTransitionDone = true +} + // loadSubroundHandler returns the implementation of SubroundHandler given by the subroundId func (chr *chronology) loadSubroundHandler(subroundId int) consensus.SubroundHandler { chr.mutSubrounds.RLock() diff --git a/consensus/chronology/chronology_test.go b/consensus/chronology/chronology_test.go index f7a9b70adb5..c409d8dce87 100644 --- a/consensus/chronology/chronology_test.go +++ b/consensus/chronology/chronology_test.go @@ -1,17 +1,24 @@ package chronology_test import ( + "sync/atomic" "testing" "time" + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/consensus" "github.com/multiversx/mx-chain-go/consensus/chronology" "github.com/multiversx/mx-chain-go/consensus/mock" + "github.com/multiversx/mx-chain-go/errors" + "github.com/multiversx/mx-chain-go/testscommon" consensusMocks "github.com/multiversx/mx-chain-go/testscommon/consensus" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" + "github.com/multiversx/mx-chain-go/testscommon/round" statusHandlerMock "github.com/multiversx/mx-chain-go/testscommon/statusHandler" ) @@ -76,6 +83,39 @@ func TestChronology_NewChronologyNilAppStatusHandlerShouldFail(t *testing.T) { assert.Equal(t, err, chronology.ErrNilAppStatusHandler) } +func TestChronology_NewChronologyNilEnableEpochsHandlerShouldFail(t *testing.T) { + t.Parallel() + + arg := getDefaultChronologyArg() + arg.EnableEpochsHandler = nil + chr, err := chronology.NewChronology(arg) + + assert.Nil(t, chr) + assert.Equal(t, err, errors.ErrNilEnableEpochsHandler) +} + +func TestChronology_NewChronologyNilEnableRoundsHandlerShouldFail(t *testing.T) { + t.Parallel() + + arg := getDefaultChronologyArg() + arg.EnableRoundsHandler = nil + chr, err := chronology.NewChronology(arg) + + assert.Nil(t, chr) + assert.Equal(t, err, errors.ErrNilEnableRoundsHandler) +} + +func TestChronology_NewChronologyNilConfigsHandlerShouldFail(t *testing.T) { + t.Parallel() + + arg := getDefaultChronologyArg() + arg.ConfigsHandler = nil + chr, err := chronology.NewChronology(arg) + + assert.Nil(t, chr) + assert.Equal(t, err, common.ErrNilCommonConfigsHandler) +} + func TestChronology_NewChronologyShouldWork(t *testing.T) { t.Parallel() @@ -118,7 +158,7 @@ func TestChronology_StartRoundShouldReturnWhenRoundIndexIsNegative(t *testing.T) t.Parallel() arg := getDefaultChronologyArg() - roundHandlerMock := &consensusMocks.RoundHandlerMock{} + roundHandlerMock := &round.RoundHandlerMock{} roundHandlerMock.IndexCalled = func() int64 { return -1 } @@ -152,7 +192,7 @@ func TestChronology_StartRoundShouldReturnWhenDoWorkReturnsFalse(t *testing.T) { t.Parallel() arg := getDefaultChronologyArg() - roundHandlerMock := &consensusMocks.RoundHandlerMock{} + roundHandlerMock := &round.RoundHandlerMock{} roundHandlerMock.UpdateRound(roundHandlerMock.TimeStamp(), roundHandlerMock.TimeStamp().Add(roundHandlerMock.TimeDuration())) arg.RoundHandler = roundHandlerMock chr, _ := chronology.NewChronology(arg) @@ -169,7 +209,7 @@ func TestChronology_StartRoundShouldWork(t *testing.T) { t.Parallel() arg := getDefaultChronologyArg() - roundHandlerMock := &consensusMocks.RoundHandlerMock{} + roundHandlerMock := &round.RoundHandlerMock{} roundHandlerMock.UpdateRound(roundHandlerMock.TimeStamp(), roundHandlerMock.TimeStamp().Add(roundHandlerMock.TimeDuration())) arg.RoundHandler = roundHandlerMock chr, _ := chronology.NewChronology(arg) @@ -189,13 +229,36 @@ func TestChronology_UpdateRoundShouldInitRound(t *testing.T) { t.Parallel() arg := getDefaultChronologyArg() + arg.EnableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + GetActivationRoundCalled: func(flag common.EnableRoundFlag) uint64 { + return 2 + }, + } + arg.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return flag == common.SupernovaFlag + }, + } chr, _ := chronology.NewChronology(arg) srm := initSubroundHandlerMock() + wasSetBaseDurationCalled := false + srm.SetBaseDurationCalled = func(baseDuration time.Duration) { + wasSetBaseDurationCalled = true + } chr.AddSubround(srm) + + // first call, supernova not yet active chr.UpdateRound() + require.False(t, wasSetBaseDurationCalled) + // second call, supernova activation round + chr.UpdateRound() assert.Equal(t, srm.Current(), chr.SubroundId()) + require.True(t, wasSetBaseDurationCalled) + + // third call, coverage only after supernova + chr.UpdateRound() } func TestChronology_LoadSubroundHandlerShouldReturnNilWhenSubroundHandlerNotExists(t *testing.T) { @@ -222,7 +285,7 @@ func TestChronology_InitRoundShouldNotSetSubroundWhenRoundIndexIsNegative(t *tes t.Parallel() arg := getDefaultChronologyArg() - roundHandlerMock := &consensusMocks.RoundHandlerMock{} + roundHandlerMock := &round.RoundHandlerMock{} arg.RoundHandler = roundHandlerMock arg.GenesisTime = arg.SyncTimer.CurrentTime() chr, _ := chronology.NewChronology(arg) @@ -243,7 +306,7 @@ func TestChronology_InitRoundShouldSetSubroundWhenRoundIndexIsPositive(t *testin t.Parallel() arg := getDefaultChronologyArg() - roundHandlerMock := &consensusMocks.RoundHandlerMock{} + roundHandlerMock := &round.RoundHandlerMock{} roundHandlerMock.UpdateRound(roundHandlerMock.TimeStamp(), roundHandlerMock.TimeStamp().Add(roundHandlerMock.TimeDuration())) arg.RoundHandler = roundHandlerMock arg.GenesisTime = arg.SyncTimer.CurrentTime() @@ -260,7 +323,7 @@ func TestChronology_StartRoundShouldNotUpdateRoundWhenCurrentRoundIsNotFinished( t.Parallel() arg := getDefaultChronologyArg() - roundHandlerMock := &consensusMocks.RoundHandlerMock{} + roundHandlerMock := &round.RoundHandlerMock{} arg.RoundHandler = roundHandlerMock arg.GenesisTime = arg.SyncTimer.CurrentTime() chr, _ := chronology.NewChronology(arg) @@ -274,7 +337,7 @@ func TestChronology_StartRoundShouldNotUpdateRoundWhenCurrentRoundIsNotFinished( func TestChronology_StartRoundShouldUpdateRoundWhenCurrentRoundIsFinished(t *testing.T) { t.Parallel() arg := getDefaultChronologyArg() - roundHandlerMock := &consensusMocks.RoundHandlerMock{} + roundHandlerMock := &round.RoundHandlerMock{} arg.RoundHandler = roundHandlerMock arg.GenesisTime = arg.SyncTimer.CurrentTime() chr, _ := chronology.NewChronology(arg) @@ -317,11 +380,14 @@ func TestChronology_CheckIfStatusHandlerWorks(t *testing.T) { func getDefaultChronologyArg() chronology.ArgChronology { return chronology.ArgChronology{ - GenesisTime: time.Now(), - RoundHandler: &consensusMocks.RoundHandlerMock{}, - SyncTimer: &consensusMocks.SyncTimerMock{}, - AppStatusHandler: statusHandlerMock.NewAppStatusHandlerMock(), - Watchdog: &mock.WatchdogMock{}, + GenesisTime: time.Now(), + RoundHandler: &round.RoundHandlerMock{}, + SyncTimer: &consensusMocks.SyncTimerMock{}, + AppStatusHandler: statusHandlerMock.NewAppStatusHandlerMock(), + Watchdog: &mock.WatchdogMock{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + ConfigsHandler: testscommon.GetDefaultCommonConfigsHandler(), } } @@ -373,29 +439,62 @@ func TestChronology_Close(t *testing.T) { func TestChronology_StartRounds(t *testing.T) { t.Parallel() - arg := getDefaultChronologyArg() + t.Run("before supernova", func(t *testing.T) { + t.Parallel() + + arg := getDefaultChronologyArg() + + chr, err := chronology.NewChronology(arg) + require.Nil(t, err) + doneFuncCalled := false + + ctx := &mock.ContextMock{ + DoneFunc: func() <-chan struct{} { + done := make(chan struct{}) + close(done) + doneFuncCalled = true + return done + }, + } + chr.StartRoundsTest(ctx) + assert.True(t, doneFuncCalled) + }) - chr, err := chronology.NewChronology(arg) - require.Nil(t, err) - doneFuncCalled := false - - ctx := &mock.ContextMock{ - DoneFunc: func() <-chan struct{} { - done := make(chan struct{}) - close(done) - doneFuncCalled = true - return done - }, - } - chr.StartRoundsTest(ctx) - assert.True(t, doneFuncCalled) + t.Run("with goroutine call, after supernova", func(t *testing.T) { + t.Parallel() + + arg := getDefaultChronologyArg() + arg.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return flag == common.SupernovaFlag + }, + } + + updateRoundCalled := &atomic.Bool{} + updateRoundCalled.Store(false) + + arg.RoundHandler = &round.RoundHandlerMock{ + UpdateRoundCalled: func(t1, t2 time.Time) { + updateRoundCalled.Store(true) + }, + } + + chr, err := chronology.NewChronology(arg) + require.Nil(t, err) + + chr.StartRounds() + + time.Sleep(5 * time.Millisecond) + + require.True(t, updateRoundCalled.Load()) + }) } func TestChronology_StartRoundsShouldWork(t *testing.T) { t.Parallel() arg := getDefaultChronologyArg() - roundHandlerMock := &consensusMocks.RoundHandlerMock{} + roundHandlerMock := &round.RoundHandlerMock{} roundHandlerMock.UpdateRound(roundHandlerMock.TimeStamp(), roundHandlerMock.TimeStamp().Add(roundHandlerMock.TimeDuration())) arg.RoundHandler = roundHandlerMock chr, _ := chronology.NewChronology(arg) diff --git a/consensus/interface.go b/consensus/interface.go index 27e2916110a..266d2ff296f 100644 --- a/consensus/interface.go +++ b/consensus/interface.go @@ -23,6 +23,7 @@ type RoundHandler interface { TimeStamp() time.Time TimeDuration() time.Duration RemainingTime(startTime time.Time, maxTime time.Duration) time.Duration + GetTimeStampForRound(round uint64) uint64 IsInterfaceNil() bool } @@ -46,6 +47,8 @@ type SubroundHandler interface { StartTime() int64 // EndTime returns the top limit time, in the roundHandler time, of the current subround EndTime() int64 + // SetBaseDuration sets the base duration + SetBaseDuration(baseDuration time.Duration) // Name returns the name of the current roundHandler Name() string // ConsensusChannel returns the consensus channel @@ -112,6 +115,12 @@ type HeadersPoolSubscriber interface { IsInterfaceNil() bool } +// HeadersPoolGetter can retrieve a header by its hash from the headers pool +type HeadersPoolGetter interface { + GetHeaderByHash(hash []byte) (data.HeaderHandler, error) + IsInterfaceNil() bool +} + // PeerHonestyHandler defines the behaivour of a component able to handle/monitor the peer honesty of nodes which are // participating in consensus type PeerHonestyHandler interface { @@ -189,6 +198,7 @@ type SigningHandler interface { AggregateSigs(bitmap []byte, epoch uint32) ([]byte, error) SetAggregatedSig([]byte) error Verify(msg []byte, bitmap []byte, epoch uint32) error + GetPubKeysFromBytes(pubKeysBytes [][]byte) ([]crypto.PublicKey, error) IsInterfaceNil() bool } @@ -212,6 +222,7 @@ type EquivalentProofsPool interface { GetProof(shardID uint32, headerHash []byte) (data.HeaderProofHandler, error) GetProofByNonce(headerNonce uint64, shardID uint32) (data.HeaderProofHandler, error) HasProof(shardID uint32, headerHash []byte) bool + RegisterHandler(handler func(headerProof data.HeaderProofHandler)) IsInterfaceNil() bool } diff --git a/consensus/mock/subroundHandlerMock.go b/consensus/mock/subroundHandlerMock.go index ca37ff42ecf..13003caf029 100644 --- a/consensus/mock/subroundHandlerMock.go +++ b/consensus/mock/subroundHandlerMock.go @@ -2,6 +2,7 @@ package mock import ( "context" + "time" "github.com/multiversx/mx-chain-go/consensus" ) @@ -14,6 +15,7 @@ type SubroundHandlerMock struct { CurrentCalled func() int StartTimeCalled func() int64 EndTimeCalled func() int64 + SetBaseDurationCalled func(baseDuration time.Duration) NameCalled func() string JobCalled func() bool CheckCalled func() bool @@ -50,6 +52,13 @@ func (srm *SubroundHandlerMock) EndTime() int64 { return srm.EndTimeCalled() } +// SetBaseDuration - +func (srm *SubroundHandlerMock) SetBaseDuration(baseDuration time.Duration) { + if srm.SetBaseDurationCalled != nil { + srm.SetBaseDurationCalled(baseDuration) + } +} + // Name - func (srm *SubroundHandlerMock) Name() string { return srm.NameCalled() diff --git a/consensus/round/round.go b/consensus/round/round.go index e4ed2e07d1d..e8dfe6f6d7e 100644 --- a/consensus/round/round.go +++ b/consensus/round/round.go @@ -6,59 +6,153 @@ import ( "time" "github.com/multiversx/mx-chain-core-go/core/check" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/consensus" + "github.com/multiversx/mx-chain-go/errors" "github.com/multiversx/mx-chain-go/ntp" ) +var log = logger.GetOrCreate("consensus/round") + var _ consensus.RoundHandler = (*round)(nil) +// ArgsRound defines the arguments needed to create a new round handler component +type ArgsRound struct { + GenesisTimeStamp time.Time + SupernovaGenesisTimeStamp time.Time + CurrentTimeStamp time.Time + RoundTimeDuration time.Duration + SupernovaTimeDuration time.Duration + SyncTimer ntp.SyncTimer + StartRound int64 + SupernovaStartRound int64 + EnableRoundsHandler common.EnableRoundsHandler + ImportDBMode bool +} + // round defines the data needed by the roundHandler type round struct { - index int64 // represents the index of the round in the current chronology (current time - genesis time) / round duration - timeStamp time.Time // represents the start time of the round in the current chronology genesis time + round index * round duration - timeDuration time.Duration // represents the duration of the round in current chronology - syncTimer ntp.SyncTimer - startRound int64 + index int64 // represents the index of the round in the current chronology (current time - genesis time) / round duration + timeStamp time.Time // represents the start time of the round in the current chronology genesis time + round index * round duration + supernovaGenesisTimeStamp time.Time // time duration between genesis and the time duration change + timeDuration time.Duration // represents the duration of the round in current chronology + supernovaTimeDuration time.Duration + genesisTimeStamp time.Time + syncTimer ntp.SyncTimer + startRound int64 + supernovaStartRound int64 + importDBMode bool *sync.RWMutex + + enableRoundsHandler common.EnableRoundsHandler } // NewRound defines a new round object -func NewRound( - genesisTimeStamp time.Time, - currentTimeStamp time.Time, - roundTimeDuration time.Duration, - syncTimer ntp.SyncTimer, - startRound int64, -) (*round, error) { +func NewRound(args ArgsRound) (*round, error) { + log.Debug("creating round handler..") - if check.IfNil(syncTimer) { + if check.IfNil(args.SyncTimer) { return nil, ErrNilSyncTimer } + if check.IfNil(args.EnableRoundsHandler) { + return nil, errors.ErrNilEnableRoundsHandler + } rnd := round{ - timeDuration: roundTimeDuration, - timeStamp: genesisTimeStamp, - syncTimer: syncTimer, - startRound: startRound, - RWMutex: &sync.RWMutex{}, + timeDuration: args.RoundTimeDuration, + supernovaTimeDuration: args.SupernovaTimeDuration, + timeStamp: args.GenesisTimeStamp, + supernovaGenesisTimeStamp: args.SupernovaGenesisTimeStamp, + syncTimer: args.SyncTimer, + startRound: args.StartRound, + supernovaStartRound: args.SupernovaStartRound, + genesisTimeStamp: args.GenesisTimeStamp, + RWMutex: &sync.RWMutex{}, + enableRoundsHandler: args.EnableRoundsHandler, + importDBMode: args.ImportDBMode, } - rnd.UpdateRound(genesisTimeStamp, currentTimeStamp) + rnd.UpdateRound(args.GenesisTimeStamp, args.CurrentTimeStamp) + + log.Debug("updated initial round..") + return &rnd, nil } // UpdateRound updates the index and the time stamp of the round depending on the genesis time and the current time given func (rnd *round) UpdateRound(genesisTimeStamp time.Time, currentTimeStamp time.Time) { + baseTimeStamp := rnd.supernovaGenesisTimeStamp + roundDuration := rnd.supernovaTimeDuration + startRound := rnd.supernovaStartRound + + supernovaActivated := rnd.isSupernovaActivated(currentTimeStamp) + + if !supernovaActivated { + baseTimeStamp = genesisTimeStamp + roundDuration = rnd.timeDuration + startRound = rnd.startRound + } + + rnd.updateRound(baseTimeStamp, currentTimeStamp, startRound, roundDuration) +} + +func (rnd *round) isSupernovaRoundActivated() bool { + rnd.RLock() + index := rnd.index + rnd.RUnlock() + + if index < 0 { + return false + } + + return rnd.enableRoundsHandler.IsFlagEnabledInRound(common.SupernovaRoundFlag, uint64(index)) +} + +func (rnd *round) isSupernovaActivated(currentTimeStamp time.Time) bool { + supernovaActivated := rnd.isSupernovaRoundActivated() + if supernovaActivated { + return supernovaActivated + } + + currentTimeAfterSupernova := currentTimeStamp.UnixMilli() >= rnd.supernovaGenesisTimeStamp.UnixMilli() + + if currentTimeAfterSupernova && !rnd.importDBMode { + log.Debug("isSupernovaActivated: force set supernovaActivated", + "currentTimeAfterSupernova", currentTimeAfterSupernova, + ) + supernovaActivated = true + } + + return supernovaActivated +} + +func (rnd *round) updateRound( + genesisTimeStamp time.Time, + currentTimeStamp time.Time, + startRound int64, + roundDuration time.Duration, +) { delta := currentTimeStamp.Sub(genesisTimeStamp).Nanoseconds() - index := int64(math.Floor(float64(delta)/float64(rnd.timeDuration.Nanoseconds()))) + rnd.startRound + index := int64(math.Floor(float64(delta)/float64(roundDuration.Nanoseconds()))) + startRound rnd.Lock() if rnd.index != index { rnd.index = index - rnd.timeStamp = genesisTimeStamp.Add(time.Duration((index - rnd.startRound) * rnd.timeDuration.Nanoseconds())) + rnd.timeStamp = genesisTimeStamp.Add(time.Duration((index - startRound) * roundDuration.Nanoseconds())) } + + log.Trace("round.updateRound", + "delta", delta, + "index", index, + "startRound", startRound, + "genesisTimeStamp", genesisTimeStamp.UnixMilli(), + "rnd.timeStamp", rnd.timeStamp.UnixMilli(), + "currentTimeStamp", currentTimeStamp.UnixMilli(), + ) + rnd.Unlock() } @@ -88,6 +182,14 @@ func (rnd *round) TimeStamp() time.Time { // TimeDuration returns the duration of the round func (rnd *round) TimeDuration() time.Duration { + return rnd.getTimeDuration() +} + +func (rnd *round) getTimeDuration() time.Duration { + if rnd.isSupernovaRoundActivated() { + return rnd.supernovaTimeDuration + } + return rnd.timeDuration } @@ -103,12 +205,27 @@ func (rnd *round) RemainingTime(startTime time.Time, maxTime time.Duration) time // RevertOneRound reverts the round index and time stamp by one round, used in case of a transition to new consensus func (rnd *round) RevertOneRound() { + timeDuration := rnd.getTimeDuration() + rnd.Lock() + rnd.index-- - rnd.timeStamp = rnd.timeStamp.Add(-rnd.timeDuration) + rnd.timeStamp = rnd.timeStamp.Add(-timeDuration) + rnd.Unlock() } +// GetTimeStampForRound returns unix milliseconds timestamp for the specified round +func (rnd *round) GetTimeStampForRound(round uint64) uint64 { + if int64(round) <= rnd.supernovaStartRound { + roundTimeStampMs := rnd.genesisTimeStamp.Add(time.Duration(int64(round)-rnd.startRound) * rnd.timeDuration).UnixMilli() + return uint64(roundTimeStampMs) + } + + roundTimeStampMs := rnd.supernovaGenesisTimeStamp.Add(time.Duration(int64(round)-rnd.supernovaStartRound) * rnd.supernovaTimeDuration).UnixMilli() + return uint64(roundTimeStampMs) +} + // IsInterfaceNil returns true if there is no value under the interface func (rnd *round) IsInterfaceNil() bool { return rnd == nil diff --git a/consensus/round/round_test.go b/consensus/round/round_test.go index e0ece36b572..3aa2611e515 100644 --- a/consensus/round/round_test.go +++ b/consensus/round/round_test.go @@ -1,13 +1,18 @@ package round_test import ( + "sync" "testing" "time" + "github.com/multiversx/mx-chain-core-go/core/atomic" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/consensus/round" + "github.com/multiversx/mx-chain-go/errors" + "github.com/multiversx/mx-chain-go/testscommon" consensusMocks "github.com/multiversx/mx-chain-go/testscommon/consensus" "github.com/stretchr/testify/assert" @@ -15,28 +20,55 @@ import ( const roundTimeDuration = 10 * time.Millisecond -func TestRound_NewRoundShouldErrNilSyncTimer(t *testing.T) { +func createDefaultRoundArgs() round.ArgsRound { + genesisTime := time.Now() + return round.ArgsRound{ + GenesisTimeStamp: genesisTime, + SupernovaGenesisTimeStamp: genesisTime, + CurrentTimeStamp: genesisTime, + RoundTimeDuration: roundTimeDuration, + SupernovaTimeDuration: roundTimeDuration, + SyncTimer: &consensusMocks.SyncTimerMock{}, + StartRound: 0, + SupernovaStartRound: 0, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + } +} + +func TestRound_NewRound(t *testing.T) { t.Parallel() - genesisTime := time.Now() + t.Run("nil sync timer", func(t *testing.T) { + t.Parallel() - rnd, err := round.NewRound(genesisTime, genesisTime, roundTimeDuration, nil, 0) + args := createDefaultRoundArgs() + args.SyncTimer = nil + rnd, err := round.NewRound(args) - assert.Nil(t, rnd) - assert.Equal(t, round.ErrNilSyncTimer, err) -} + assert.Nil(t, rnd) + assert.Equal(t, round.ErrNilSyncTimer, err) + }) -func TestRound_NewRoundShouldWork(t *testing.T) { - t.Parallel() + t.Run("nil enable rounds handler", func(t *testing.T) { + t.Parallel() - genesisTime := time.Now() + args := createDefaultRoundArgs() + args.EnableRoundsHandler = nil + rnd, err := round.NewRound(args) - syncTimerMock := &consensusMocks.SyncTimerMock{} + assert.Nil(t, rnd) + assert.Equal(t, errors.ErrNilEnableRoundsHandler, err) + }) - rnd, err := round.NewRound(genesisTime, genesisTime, roundTimeDuration, syncTimerMock, 0) + t.Run("should work", func(t *testing.T) { + t.Parallel() - assert.Nil(t, err) - assert.False(t, check.IfNil(rnd)) + args := createDefaultRoundArgs() + rnd, err := round.NewRound(args) + + assert.Nil(t, err) + assert.False(t, check.IfNil(rnd)) + }) } func TestRound_UpdateRoundShouldNotChangeAnything(t *testing.T) { @@ -44,9 +76,11 @@ func TestRound_UpdateRoundShouldNotChangeAnything(t *testing.T) { genesisTime := time.Now() - syncTimerMock := &consensusMocks.SyncTimerMock{} + args := createDefaultRoundArgs() + args.GenesisTimeStamp = genesisTime + args.SupernovaGenesisTimeStamp = genesisTime.Add(10 * roundTimeDuration) - rnd, _ := round.NewRound(genesisTime, genesisTime, roundTimeDuration, syncTimerMock, 0) + rnd, _ := round.NewRound(args) oldIndex := rnd.Index() oldTimeStamp := rnd.TimeStamp() @@ -62,16 +96,207 @@ func TestRound_UpdateRoundShouldNotChangeAnything(t *testing.T) { func TestRound_UpdateRoundShouldAdvanceOneRound(t *testing.T) { t.Parallel() - genesisTime := time.Now() + t.Run("before supernova", func(t *testing.T) { + t.Parallel() - syncTimerMock := &consensusMocks.SyncTimerMock{} + genesisTime := time.Now() - rnd, _ := round.NewRound(genesisTime, genesisTime, roundTimeDuration, syncTimerMock, 0) - oldIndex := rnd.Index() - rnd.UpdateRound(genesisTime, genesisTime.Add(roundTimeDuration)) - newIndex := rnd.Index() + args := createDefaultRoundArgs() + args.EnableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return flag != common.SupernovaRoundFlag + }, + } + + args.GenesisTimeStamp = genesisTime + args.SupernovaGenesisTimeStamp = genesisTime.Add(10 * roundTimeDuration) + + rnd, _ := round.NewRound(args) + oldIndex := rnd.Index() + rnd.UpdateRound(genesisTime, genesisTime.Add(roundTimeDuration)) + newIndex := rnd.Index() + + assert.Equal(t, oldIndex, newIndex-1) + }) + + t.Run("after supernova, with flag activated", func(t *testing.T) { + t.Parallel() + + genesisTime := time.Now() + + roundDuration := 10 * time.Millisecond + + supernovaRoundDuration := 5 * time.Millisecond + supernovaStartRond := int64(5) + supernovaGenesisTime := genesisTime.Add(time.Duration(supernovaStartRond) * roundDuration) + + args := createDefaultRoundArgs() + args.RoundTimeDuration = roundDuration + args.SupernovaTimeDuration = supernovaRoundDuration + args.EnableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return flag == common.SupernovaRoundFlag && round >= uint64(supernovaStartRond) + }, + } + + args.SupernovaStartRound = supernovaStartRond + args.GenesisTimeStamp = genesisTime + + args.SupernovaGenesisTimeStamp = genesisTime.Add(5 * roundDuration) + + rnd, _ := round.NewRound(args) + + rnd.UpdateRound(genesisTime, genesisTime.Add(roundTimeDuration)) + rnd.UpdateRound(genesisTime, genesisTime.Add(2*roundTimeDuration)) + rnd.UpdateRound(genesisTime, genesisTime.Add(3*roundTimeDuration)) + rnd.UpdateRound(genesisTime, genesisTime.Add(4*roundTimeDuration)) + + index0 := rnd.Index() + timestamp0 := rnd.TimeStamp() + assert.Equal(t, roundDuration, rnd.TimeDuration()) + + rnd.UpdateRound(genesisTime, supernovaGenesisTime) + + index1 := rnd.Index() + timestamp1 := rnd.TimeStamp() + + diffTime := timestamp1.Sub(timestamp0) + + assert.Equal(t, supernovaRoundDuration, rnd.TimeDuration()) + assert.Equal(t, index0, index1-1) + assert.Equal(t, int64(5), index1) + assert.Equal(t, roundDuration, diffTime) + + rnd.UpdateRound(genesisTime, supernovaGenesisTime.Add(supernovaRoundDuration)) + + index2 := rnd.Index() + timestamp2 := rnd.TimeStamp() + + diffTime2 := timestamp2.Sub(timestamp1) - assert.Equal(t, oldIndex, newIndex-1) + assert.Equal(t, supernovaRoundDuration, rnd.TimeDuration()) + assert.Equal(t, index1, index2-1) + assert.Equal(t, int64(6), index2) + assert.Equal(t, supernovaRoundDuration, diffTime2) + }) + + t.Run("after supernova, without flag activated at start, but in supernova genesis time", func(t *testing.T) { + t.Parallel() + + genesisTime := time.Now() + + roundDuration := 10 * time.Millisecond + + supernovaRoundDuration := 5 * time.Millisecond + supernovaStartRond := int64(5) + supernovaGenesisTime := genesisTime.Add(time.Duration(supernovaStartRond) * roundDuration) + + args := createDefaultRoundArgs() + args.RoundTimeDuration = roundDuration + args.SupernovaTimeDuration = supernovaRoundDuration + args.EnableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return flag == common.SupernovaRoundFlag && round >= uint64(supernovaStartRond) + }, + } + + args.SupernovaStartRound = supernovaStartRond + args.GenesisTimeStamp = genesisTime + args.SupernovaGenesisTimeStamp = supernovaGenesisTime + + args.CurrentTimeStamp = supernovaGenesisTime + + rnd, _ := round.NewRound(args) + + assert.Equal(t, supernovaGenesisTime.UnixMilli(), rnd.TimeStamp().UnixMilli()) + index0 := rnd.Index() + assert.Equal(t, supernovaRoundDuration, rnd.TimeDuration()) + assert.Equal(t, int64(5), index0) + }) + + t.Run("after supernova, without flag activated at start, but after supernova genesis time", func(t *testing.T) { + t.Parallel() + + genesisTime := time.Now() + + roundDuration := 10 * time.Millisecond + + supernovaRoundDuration := 5 * time.Millisecond + supernovaStartRond := int64(5) + supernovaGenesisTime := genesisTime.Add(time.Duration(supernovaStartRond) * roundDuration) + + args := createDefaultRoundArgs() + args.RoundTimeDuration = roundDuration + args.SupernovaTimeDuration = supernovaRoundDuration + args.EnableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return flag == common.SupernovaRoundFlag && round >= uint64(supernovaStartRond) + }, + } + + args.SupernovaStartRound = supernovaStartRond + args.GenesisTimeStamp = genesisTime + args.SupernovaGenesisTimeStamp = supernovaGenesisTime + + currentTime := supernovaGenesisTime.Add(supernovaRoundDuration) + args.CurrentTimeStamp = currentTime + + rnd, _ := round.NewRound(args) + + assert.Equal(t, supernovaRoundDuration, rnd.TimeDuration()) + assert.Equal(t, int64(6), rnd.Index()) + assert.Equal(t, currentTime.UnixMilli(), rnd.TimeStamp().UnixMilli()) + + rnd.UpdateRound(genesisTime, supernovaGenesisTime.Add(2*supernovaRoundDuration)) + + assert.Equal(t, supernovaRoundDuration, rnd.TimeDuration()) + assert.Equal(t, int64(7), rnd.Index()) + assert.Equal(t, currentTime.Add(supernovaRoundDuration).UnixMilli(), rnd.TimeStamp().UnixMilli()) + }) + + t.Run("after supernova genesis time, in import db mode", func(t *testing.T) { + t.Parallel() + + genesisTime := time.Now() + + roundDuration := 10 * time.Millisecond + + supernovaRoundDuration := 5 * time.Millisecond + supernovaStartRond := int64(5) + supernovaGenesisTime := genesisTime.Add(time.Duration(supernovaStartRond) * roundDuration) + + args := createDefaultRoundArgs() + args.RoundTimeDuration = roundDuration + args.SupernovaTimeDuration = supernovaRoundDuration + args.EnableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return flag != common.SupernovaRoundFlag && round >= uint64(supernovaStartRond) + }, + } + args.ImportDBMode = true + + args.SupernovaStartRound = supernovaStartRond + args.GenesisTimeStamp = genesisTime + args.SupernovaGenesisTimeStamp = supernovaGenesisTime + + // set current time 2 rounds ahead + currentTime := genesisTime.Add(roundDuration * 2) + + args.CurrentTimeStamp = currentTime + + rnd, _ := round.NewRound(args) + + assert.Equal(t, roundDuration, rnd.TimeDuration()) + assert.Equal(t, int64(2), rnd.Index()) + + rnd.UpdateRound(genesisTime, genesisTime.Add(roundDuration*6)) + + // if in import db and supernova enable flag not enabled, do not activate + // supernova even if supernova genesis time was reached + + assert.Equal(t, roundDuration, rnd.TimeDuration()) + assert.Equal(t, int64(6), rnd.Index()) + }) } func TestRound_IndexShouldReturnFirstIndex(t *testing.T) { @@ -79,9 +304,10 @@ func TestRound_IndexShouldReturnFirstIndex(t *testing.T) { genesisTime := time.Now() - syncTimerMock := &consensusMocks.SyncTimerMock{} + args := createDefaultRoundArgs() + args.GenesisTimeStamp = genesisTime - rnd, _ := round.NewRound(genesisTime, genesisTime, roundTimeDuration, syncTimerMock, 0) + rnd, _ := round.NewRound(args) rnd.UpdateRound(genesisTime, genesisTime.Add(roundTimeDuration/2)) index := rnd.Index() @@ -93,26 +319,142 @@ func TestRound_TimeStampShouldReturnTimeStampOfTheNextRound(t *testing.T) { genesisTime := time.Now() - syncTimerMock := &consensusMocks.SyncTimerMock{} + args := createDefaultRoundArgs() + args.GenesisTimeStamp = genesisTime + args.SupernovaGenesisTimeStamp = genesisTime.Add(10 * roundTimeDuration) - rnd, _ := round.NewRound(genesisTime, genesisTime, roundTimeDuration, syncTimerMock, 0) + rnd, _ := round.NewRound(args) rnd.UpdateRound(genesisTime, genesisTime.Add(roundTimeDuration+roundTimeDuration/2)) timeStamp := rnd.TimeStamp() assert.Equal(t, genesisTime.Add(roundTimeDuration), timeStamp) } +func TestRound_UpdateRoundWithTimeDurationChange(t *testing.T) { + t.Parallel() + + t.Run("with transition to supernova in epoch", func(t *testing.T) { + t.Parallel() + + genesisTime := time.Now() + + args := createDefaultRoundArgs() + args.GenesisTimeStamp = genesisTime + args.SupernovaGenesisTimeStamp = genesisTime.Add(10 * roundTimeDuration) + + rnd, _ := round.NewRound(args) + + oldIndex := rnd.Index() + rnd.UpdateRound(genesisTime, genesisTime.Add(roundTimeDuration)) + + newIndex := rnd.Index() + assert.Equal(t, oldIndex, newIndex-1) + assert.Equal(t, int64(1), newIndex) + + oldIndex = rnd.Index() + rnd.UpdateRound(genesisTime, genesisTime.Add(2*roundTimeDuration)) + + newIndex = rnd.Index() + assert.Equal(t, oldIndex, newIndex-1) + assert.Equal(t, int64(2), newIndex) + + oldIndex = rnd.Index() + rnd.UpdateRound(genesisTime, genesisTime.Add(3*roundTimeDuration)) + + newIndex = rnd.Index() + assert.Equal(t, oldIndex, newIndex-1) + assert.Equal(t, int64(3), newIndex) + }) + + t.Run("with transition to supernova in round", func(t *testing.T) { + t.Parallel() + + genesisTime := time.Now() + + flag := &atomic.Flag{} + + args := createDefaultRoundArgs() + + args.GenesisTimeStamp = genesisTime + supernovaGenesisTime := genesisTime.Add(2 * roundTimeDuration) + args.SupernovaGenesisTimeStamp = supernovaGenesisTime + + args.RoundTimeDuration = 10 * time.Millisecond + newRoundTimeDuration := 5 * time.Millisecond + args.SupernovaTimeDuration = newRoundTimeDuration + + args.StartRound = 0 + args.SupernovaStartRound = 2 + + args.EnableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledCalled: func(_ common.EnableRoundFlag) bool { + return flag.IsSet() + }, + } + rnd, _ := round.NewRound(args) + + oldIndex := rnd.Index() + rnd.UpdateRound(genesisTime, genesisTime.Add(roundTimeDuration)) + + newIndex := rnd.Index() + assert.Equal(t, oldIndex, newIndex-1) + assert.Equal(t, int64(1), newIndex) + + oldIndex = rnd.Index() + rnd.UpdateRound(genesisTime, genesisTime.Add(2*roundTimeDuration)) + + newIndex = rnd.Index() + assert.Equal(t, oldIndex, newIndex-1) + assert.Equal(t, int64(2), newIndex) + + flag.SetValue(true) + + oldIndex = rnd.Index() + rnd.UpdateRound(supernovaGenesisTime, supernovaGenesisTime.Add(newRoundTimeDuration)) + + newIndex = rnd.Index() + assert.Equal(t, oldIndex, newIndex-1) + assert.Equal(t, int64(3), newIndex) + }) +} + func TestRound_TimeDurationShouldReturnTheDurationOfOneRound(t *testing.T) { t.Parallel() - genesisTime := time.Now() + t.Run("before supernova", func(t *testing.T) { + t.Parallel() - syncTimerMock := &consensusMocks.SyncTimerMock{} + genesisTime := time.Now() + + args := createDefaultRoundArgs() + args.GenesisTimeStamp = genesisTime + + rnd, _ := round.NewRound(args) + timeDuration := rnd.TimeDuration() + + assert.Equal(t, roundTimeDuration, timeDuration) + }) - rnd, _ := round.NewRound(genesisTime, genesisTime, roundTimeDuration, syncTimerMock, 0) - timeDuration := rnd.TimeDuration() + t.Run("after supernova", func(t *testing.T) { + t.Parallel() - assert.Equal(t, roundTimeDuration, timeDuration) + genesisTime := time.Now() + + args := createDefaultRoundArgs() + args.EnableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return flag == common.SupernovaRoundFlag + }, + } + + args.SupernovaGenesisTimeStamp = genesisTime + args.SupernovaTimeDuration = roundTimeDuration + + rnd, _ := round.NewRound(args) + timeDuration := rnd.TimeDuration() + + assert.Equal(t, roundTimeDuration, timeDuration) + }) } func TestRound_RemainingTimeInCurrentRoundShouldReturnPositiveValue(t *testing.T) { @@ -128,7 +470,12 @@ func TestRound_RemainingTimeInCurrentRoundShouldReturnPositiveValue(t *testing.T return time.Unix(0, timeElapsed) } - rnd, _ := round.NewRound(genesisTime, genesisTime, roundTimeDuration, syncTimerMock, 0) + args := createDefaultRoundArgs() + args.GenesisTimeStamp = genesisTime + args.CurrentTimeStamp = genesisTime + args.SyncTimer = syncTimerMock + + rnd, _ := round.NewRound(args) remainingTime := rnd.RemainingTime(rnd.TimeStamp(), roundTimeDuration) @@ -149,7 +496,12 @@ func TestRound_RemainingTimeInCurrentRoundShouldReturnNegativeValue(t *testing.T return time.Unix(0, timeElapsed) } - rnd, _ := round.NewRound(genesisTime, genesisTime, roundTimeDuration, syncTimerMock, 0) + args := createDefaultRoundArgs() + args.GenesisTimeStamp = genesisTime + args.CurrentTimeStamp = genesisTime + args.SyncTimer = syncTimerMock + + rnd, _ := round.NewRound(args) remainingTime := rnd.RemainingTime(rnd.TimeStamp(), roundTimeDuration) @@ -165,7 +517,14 @@ func TestRound_RevertOneRound(t *testing.T) { syncTimerMock := &consensusMocks.SyncTimerMock{} startRound := int64(10) - rnd, _ := round.NewRound(genesisTime, genesisTime, roundTimeDuration, syncTimerMock, startRound) + + args := createDefaultRoundArgs() + args.GenesisTimeStamp = genesisTime + args.SupernovaGenesisTimeStamp = genesisTime.Add(10 * roundTimeDuration) + args.SyncTimer = syncTimerMock + args.StartRound = startRound + + rnd, _ := round.NewRound(args) index := rnd.Index() require.Equal(t, startRound, index) @@ -177,17 +536,205 @@ func TestRound_RevertOneRound(t *testing.T) { func TestRound_BeforeGenesis(t *testing.T) { t.Parallel() + t.Run("without supernova activated", func(t *testing.T) { + t.Parallel() + + genesisTime := time.Now() + + syncTimerMock := &consensusMocks.SyncTimerMock{} + + startRound := int64(-1) + args := createDefaultRoundArgs() + args.GenesisTimeStamp = genesisTime + args.SupernovaGenesisTimeStamp = genesisTime.Add(10 * roundTimeDuration) + args.SyncTimer = syncTimerMock + args.StartRound = startRound + + rnd, _ := round.NewRound(args) + require.True(t, rnd.BeforeGenesis()) + + time.Sleep(roundTimeDuration * 2) + currentTime := time.Now() + + rnd.UpdateRound(genesisTime, currentTime) + require.False(t, rnd.BeforeGenesis()) + }) + + t.Run("with supernova activated", func(t *testing.T) { + t.Parallel() + + roundTimeDuration := 10 * time.Millisecond + supernovaRoundTimeDuration := 5 * time.Millisecond + + initTime := time.Now() + genesisTime := initTime.Add(roundTimeDuration * 20) + + syncTimerMock := &consensusMocks.SyncTimerMock{} + + supernovaStartRound := int64(10) + + startRound := int64(0) + args := createDefaultRoundArgs() + args.GenesisTimeStamp = genesisTime + args.SupernovaGenesisTimeStamp = genesisTime.Add(10 * roundTimeDuration) + args.SyncTimer = syncTimerMock + args.StartRound = startRound + args.RoundTimeDuration = roundTimeDuration + args.SupernovaTimeDuration = supernovaRoundTimeDuration + args.SupernovaStartRound = supernovaStartRound + args.EnableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return flag == common.SupernovaRoundFlag && round >= uint64(supernovaStartRound) + }, + } + + rnd, _ := round.NewRound(args) + require.True(t, rnd.BeforeGenesis()) + + time.Sleep(roundTimeDuration * 10) + rnd.UpdateRound(genesisTime, time.Now()) + require.True(t, rnd.BeforeGenesis()) + + time.Sleep(roundTimeDuration * 11) + rnd.UpdateRound(genesisTime, time.Now()) + require.False(t, rnd.BeforeGenesis()) + }) +} + +func TestRound_Concurrency(t *testing.T) { + t.Parallel() + + t.Run("before supernova", func(t *testing.T) { + t.Parallel() + + genesisTime := time.Now() + + args := createDefaultRoundArgs() + args.GenesisTimeStamp = genesisTime + + rnd, err := round.NewRound(args) + require.Nil(t, err) + + numOperations := 1000 + + wg := sync.WaitGroup{} + wg.Add(numOperations) + + for i := 0; i < numOperations; i++ { + go func(idx int) { + switch idx % 6 { + case 0: + _ = rnd.BeforeGenesis() + case 1: + _ = rnd.Index() + case 2: + _ = rnd.RemainingTime(rnd.TimeStamp(), roundTimeDuration) + case 3: + rnd.RevertOneRound() + case 4: + _ = rnd.TimeDuration() + case 5: + rnd.UpdateRound(genesisTime, time.Now()) + default: + assert.Fail(t, "should have not been called") + } + + wg.Done() + }(i) + } + + wg.Wait() + }) + + t.Run("after supernova", func(t *testing.T) { + t.Parallel() + + genesisTime := time.Now() + + args := createDefaultRoundArgs() + args.GenesisTimeStamp = genesisTime + args.SupernovaGenesisTimeStamp = genesisTime + args.SupernovaStartRound = 0 + + args.EnableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return flag == common.SupernovaRoundFlag && round > 0 + }, + } + + rnd, err := round.NewRound(args) + require.Nil(t, err) + + numOperations := 1000 + + wg := sync.WaitGroup{} + wg.Add(numOperations) + + for i := 0; i < numOperations; i++ { + go func(idx int) { + switch idx % 6 { + case 0: + _ = rnd.BeforeGenesis() + case 1: + _ = rnd.Index() + case 2: + _ = rnd.RemainingTime(rnd.TimeStamp(), roundTimeDuration) + case 3: + rnd.RevertOneRound() + case 4: + _ = rnd.TimeDuration() + case 5: + rnd.UpdateRound(genesisTime, time.Now()) + default: + assert.Fail(t, "should have not been called") + } + + wg.Done() + }(i) + } + + wg.Wait() + }) +} + +func TestRound_GetTimeStampForRound(t *testing.T) { + t.Parallel() + genesisTime := time.Now() + supernovaGenesisTimeStamp := genesisTime.Add(10 * roundTimeDuration) + + roundTimeDuration := 10 * time.Millisecond + supernovaRoundTimeDuration := 5 * time.Millisecond syncTimerMock := &consensusMocks.SyncTimerMock{} - startRound := int64(-1) - rnd, _ := round.NewRound(genesisTime, genesisTime, roundTimeDuration, syncTimerMock, startRound) + startRound := int64(0) + supernovaStartRound := int64(10) + + args := createDefaultRoundArgs() + args.GenesisTimeStamp = genesisTime + args.SupernovaGenesisTimeStamp = supernovaGenesisTimeStamp + args.SyncTimer = syncTimerMock + args.StartRound = startRound + args.RoundTimeDuration = roundTimeDuration + args.SupernovaTimeDuration = supernovaRoundTimeDuration + args.SupernovaStartRound = supernovaStartRound + rnd, _ := round.NewRound(args) require.True(t, rnd.BeforeGenesis()) - time.Sleep(roundTimeDuration * 2) - currentTime := time.Now() + roundTimeStamp := rnd.GetTimeStampForRound(0) + expRoundTimeStamp := genesisTime.Add(0 * roundTimeDuration) + require.Equal(t, uint64(expRoundTimeStamp.UnixMilli()), roundTimeStamp) + + roundTimeStamp = rnd.GetTimeStampForRound(10) + expRoundTimeStamp = genesisTime.Add(10 * roundTimeDuration) + require.Equal(t, uint64(expRoundTimeStamp.UnixMilli()), roundTimeStamp) + + roundTimeStamp = rnd.GetTimeStampForRound(20) + expRoundTimeStamp = supernovaGenesisTimeStamp.Add((20 - 10) * supernovaRoundTimeDuration) + require.Equal(t, uint64(expRoundTimeStamp.UnixMilli()), roundTimeStamp) - rnd.UpdateRound(genesisTime, currentTime) - require.False(t, rnd.BeforeGenesis()) + roundTimeStamp = rnd.GetTimeStampForRound(1000) + expRoundTimeStamp = supernovaGenesisTimeStamp.Add((1000 - 10) * supernovaRoundTimeDuration) + require.Equal(t, uint64(expRoundTimeStamp.UnixMilli()), roundTimeStamp) } diff --git a/consensus/spos/bls/ntpsync/ringBuffer.go b/consensus/spos/bls/ntpsync/ringBuffer.go new file mode 100644 index 00000000000..caf96922dfa --- /dev/null +++ b/consensus/spos/bls/ntpsync/ringBuffer.go @@ -0,0 +1,97 @@ +package ntpsync + +import ( + "fmt" + "sync" +) + +type entry struct { + nonce uint64 + hash string +} + +type ringBuffer struct { + mut sync.RWMutex + + buf []entry + capacity int + size int + index int + set map[string]struct{} // deduplication map +} + +func newNonceRingBuffer(cap int) *ringBuffer { + return &ringBuffer{ + buf: make([]entry, cap), + capacity: cap, + set: make(map[string]struct{}), + } +} + +// add adds a nonce if it's not already present. If full, it overwrites the oldest. +func (r *ringBuffer) add(nonce uint64, hash string) { + r.mut.Lock() + defer r.mut.Unlock() + + key := createEntryKey(nonce, hash) + if _, exists := r.set[key]; exists { + return + } + + // If overwriting the oldest entry, remove it from set + if r.size == r.capacity { + oldest := r.buf[r.index] + delete(r.set, createEntryKey(oldest.nonce, oldest.hash)) + } + + r.buf[r.index] = entry{ + nonce: nonce, + hash: hash, + } + r.set[key] = struct{}{} + + // update index and size + r.index = (r.index + 1) % r.capacity + if r.size < r.capacity { + r.size++ + } +} + +func createEntryKey(nonce uint64, hash string) string { + return fmt.Sprintf("%d-%s", nonce, hash) +} + +// last returns the last N nonces in chronological order +func (r *ringBuffer) last(n int) []uint64 { + r.mut.RLock() + defer r.mut.RUnlock() + + if n > r.size { + n = r.size + } + out := make([]uint64, n) + + start := (r.index - n + r.capacity) % r.capacity + for i := 0; i < n; i++ { + idx := (start + i) % r.capacity + out[i] = r.buf[idx].nonce + } + + return out +} + +func (r *ringBuffer) contains(nonce uint64, hash string) bool { + r.mut.RLock() + defer r.mut.RUnlock() + + _, exists := r.set[createEntryKey(nonce, hash)] + return exists +} + +// Size returns number of stored nonces +func (r *ringBuffer) len() int { + r.mut.RLock() + defer r.mut.RUnlock() + + return r.size +} diff --git a/consensus/spos/bls/ntpsync/ringBuffer_test.go b/consensus/spos/bls/ntpsync/ringBuffer_test.go new file mode 100644 index 00000000000..bf38da7dd7a --- /dev/null +++ b/consensus/spos/bls/ntpsync/ringBuffer_test.go @@ -0,0 +1,61 @@ +package ntpsync + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNonceRingBuffer_AddAndLen(t *testing.T) { + t.Parallel() + + rb := newNonceRingBuffer(3) + + rb.add(10, "") + rb.add(20, "") + rb.add(30, "") + require.Equal(t, 3, rb.len()) + + rb.add(20, "") + require.Equal(t, 3, rb.len()) + require.Equal(t, []uint64{10, 20, 30}, rb.last(5)) +} + +func TestNonceRingBuffer_OverwriteOldest(t *testing.T) { + t.Parallel() + + rb := newNonceRingBuffer(2) + + rb.add(1, "") + rb.add(2, "") + require.Equal(t, []uint64{1, 2}, rb.last(2)) + + // This should overwrite "1" + rb.add(3, "") + require.Equal(t, 2, rb.len()) + require.Equal(t, []uint64{2, 3}, rb.last(2)) + + // "1" must no longer exist + require.False(t, rb.contains(1, "")) + require.False(t, rb.contains(2, "h")) + require.False(t, rb.contains(3, "h")) + require.True(t, rb.contains(2, "")) + require.True(t, rb.contains(3, "")) +} + +func TestNonceRingBuffer_Last(t *testing.T) { + t.Parallel() + + rb := newNonceRingBuffer(5) + + rb.add(5, "") + rb.add(6, "") + rb.add(7, "") + + last := rb.last(2) + require.Equal(t, []uint64{6, 7}, last) + + // Requesting more than size should return everything + last = rb.last(10) + require.Equal(t, []uint64{5, 6, 7}, last) +} diff --git a/consensus/spos/bls/ntpsync/roundSyncController.go b/consensus/spos/bls/ntpsync/roundSyncController.go new file mode 100644 index 00000000000..64b5094f4f6 --- /dev/null +++ b/consensus/spos/bls/ntpsync/roundSyncController.go @@ -0,0 +1,110 @@ +package ntpsync + +import ( + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/consensus" + "github.com/multiversx/mx-chain-go/consensus/spos" + "github.com/multiversx/mx-chain-go/ntp" + logger "github.com/multiversx/mx-chain-logger-go" +) + +var log = logger.GetOrCreate("nonceSyncController") + +const ( + nonceBufferSize = 20 + numRequiredMissedHeadersToForceResync = 10 +) + +type nonceSyncController struct { + outOfRangeNonces *ringBuffer + deSyncedNonces *ringBuffer + syncer ntp.SyncTimer + selfShardID uint32 +} + +// NewNtpSyncController creates a new nonce sync controller which detects nonce desynchronization and triggers a forced NTP resync. +func NewNtpSyncController(proofsPool consensus.EquivalentProofsPool, syncer ntp.SyncTimer, selfShardID uint32) (*nonceSyncController, error) { + if check.IfNil(proofsPool) { + return nil, spos.ErrNilEquivalentProofPool + } + if check.IfNil(syncer) { + return nil, spos.ErrNilSyncTimer + } + + rsc := &nonceSyncController{ + outOfRangeNonces: newNonceRingBuffer(nonceBufferSize), + deSyncedNonces: newNonceRingBuffer(nonceBufferSize), + syncer: syncer, + selfShardID: selfShardID, + } + + proofsPool.RegisterHandler(rsc.receivedProof) + + return rsc, nil +} + +// AddOutOfRangeNonce records a consensus nonce that was outside the expected range. +// These nonces are later correlated with received valid proofs to detect NTP desynchronization +// and potentially trigger a forced time resync. +func (rsc *nonceSyncController) AddOutOfRangeNonce(nonce uint64, hash string) { + rsc.outOfRangeNonces.add(nonce, hash) +} + +// AddLeaderNonceAsOutOfRange will forcely add nonce as out of sync when self node as leader +// this will make sure that self proposed headers will not broke consecutivity while out of sync +func (rsc *nonceSyncController) AddLeaderNonceAsOutOfRange(nonce uint64, hash string) { + rsc.outOfRangeNonces.add(nonce, hash) + rsc.deSyncedNonces.add(nonce, hash) + rsc.tryResyncIfNeeded() +} + +func (rsc *nonceSyncController) receivedProof(headerProof data.HeaderProofHandler) { + if headerProof.GetHeaderShardId() != rsc.selfShardID { + return + } + + currNonce := headerProof.GetHeaderNonce() + hash := string(headerProof.GetHeaderHash()) + // this should probably not happen, but return early if we receive the same proof for this nonce so we don't trigger resync multiple times + if rsc.deSyncedNonces.contains(currNonce, hash) { + return + } + + if rsc.outOfRangeNonces.contains(currNonce, hash) { + rsc.deSyncedNonces.add(currNonce, hash) + rsc.tryResyncIfNeeded() + } +} + +func (rsc *nonceSyncController) tryResyncIfNeeded() { + if rsc.deSyncedNonces.len() < numRequiredMissedHeadersToForceResync { + return + } + + lastDeSyncedNonces := rsc.deSyncedNonces.last(numRequiredMissedHeadersToForceResync) + + if areNoncesInAscendingOrder(lastDeSyncedNonces) { + log.Debug("nonceSyncController: force ntp synchronization") + rsc.syncer.ForceSync() + } +} + +func areNoncesInAscendingOrder(nonces []uint64) bool { + if len(nonces) == 0 { + return false + } + + for i := 1; i < len(nonces); i++ { + if nonces[i] != nonces[i-1]+1 { + return false + } + } + + return true +} + +// IsInterfaceNil checks if the underlying pointer is nil +func (rsc *nonceSyncController) IsInterfaceNil() bool { + return rsc == nil +} diff --git a/consensus/spos/bls/ntpsync/roundSyncController_test.go b/consensus/spos/bls/ntpsync/roundSyncController_test.go new file mode 100644 index 00000000000..a99b0b965be --- /dev/null +++ b/consensus/spos/bls/ntpsync/roundSyncController_test.go @@ -0,0 +1,186 @@ +package ntpsync + +import ( + "math/rand" + "sync" + "testing" + + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/consensus/spos" + facadeMock "github.com/multiversx/mx-chain-go/facade/mock" + "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" + "github.com/stretchr/testify/require" +) + +func TestNewNonceSyncController(t *testing.T) { + t.Parallel() + + t.Run("nil proofs pool", func(t *testing.T) { + rsc, err := NewNtpSyncController(nil, &facadeMock.SyncTimerMock{}, 0) + require.Nil(t, rsc) + require.Equal(t, spos.ErrNilEquivalentProofPool, err) + }) + t.Run("nil sync timer", func(t *testing.T) { + rsc, err := NewNtpSyncController(&dataRetriever.ProofsPoolMock{}, nil, 0) + require.Nil(t, rsc) + require.Equal(t, spos.ErrNilSyncTimer, err) + }) + t.Run("should work", func(t *testing.T) { + rsc, err := NewNtpSyncController(&dataRetriever.ProofsPoolMock{}, &facadeMock.SyncTimerMock{}, 0) + require.NotNil(t, rsc) + require.False(t, rsc.IsInterfaceNil()) + require.Nil(t, err) + }) +} + +func TestHeaderTracker_ShouldForceNTPResync(t *testing.T) { + t.Parallel() + + t.Run("with multiple random calls", func(t *testing.T) { + t.Parallel() + + proofsPool := &dataRetriever.ProofsPoolMock{} + wasSyncCalled := false + syncer := &facadeMock.SyncTimerMock{ + ForceSyncCalled: func() { + wasSyncCalled = true + }, + } + + tracker, _ := NewNtpSyncController(proofsPool, syncer, 0) + + wg := sync.WaitGroup{} + + numIterations := 500 + + handlers := make([]func(), 0, numIterations) + + for i := 0; i < numIterations; i++ { + r := rand.Intn(1000) + 50 // to avoid interfering with the other samples + + if i%3 == 0 { + handlers = append(handlers, func() { + tracker.AddOutOfRangeNonce(uint64(r), "") + wg.Done() + }) + } + + if i%7 == 0 { + handlers = append(handlers, func() { + tracker.receivedProof(&block.HeaderProof{HeaderNonce: uint64(r)}) + wg.Done() + }) + } + } + + wg.Add(len(handlers)) + + for _, handler := range handlers { + go handler() + } + + wg.Wait() + + tracker.AddOutOfRangeNonce(1, "") + tracker.AddOutOfRangeNonce(0, "") + tracker.AddOutOfRangeNonce(2, "") + tracker.AddOutOfRangeNonce(3, "") + + // receive nonce ordered proofs for different shard and hash, which are not relevant + for i := 0; i <= 9; i++ { + tracker.receivedProof(&block.HeaderProof{HeaderNonce: uint64(i), HeaderShardId: 1}) + tracker.receivedProof(&block.HeaderProof{HeaderNonce: uint64(i), HeaderHash: []byte("h")}) + } + + tracker.receivedProof(&block.HeaderProof{HeaderNonce: 0}) + tracker.receivedProof(&block.HeaderProof{HeaderNonce: 1}) + tracker.receivedProof(&block.HeaderProof{HeaderNonce: 2}) + tracker.receivedProof(&block.HeaderProof{HeaderNonce: 3}) + + tracker.AddOutOfRangeNonce(4, "") + tracker.AddOutOfRangeNonce(5, "") + + tracker.receivedProof(&block.HeaderProof{HeaderNonce: 4}) + require.False(t, wasSyncCalled) + + tracker.AddOutOfRangeNonce(6, "") + tracker.AddOutOfRangeNonce(7, "") + tracker.AddOutOfRangeNonce(8, "") + tracker.AddOutOfRangeNonce(9, "") + tracker.AddOutOfRangeNonce(10, "") + + tracker.receivedProof(&block.HeaderProof{HeaderNonce: 5}) + tracker.receivedProof(&block.HeaderProof{HeaderNonce: 6}) + tracker.receivedProof(&block.HeaderProof{HeaderNonce: 7}) + tracker.receivedProof(&block.HeaderProof{HeaderNonce: 8}) + require.False(t, wasSyncCalled) + + // Nonces 0-9 are out of range with received proofs, should force sync + tracker.receivedProof(&block.HeaderProof{HeaderNonce: 9}) + require.True(t, wasSyncCalled) + + tracker.receivedProof(&block.HeaderProof{HeaderNonce: 10}) + require.True(t, wasSyncCalled) + + // Receive proof for the same header nonce, should not force resync again + wasSyncCalled = false + tracker.receivedProof(&block.HeaderProof{HeaderNonce: 10}) + require.False(t, wasSyncCalled) + + // Receive proof, but no out of range nonce, should not force resync + tracker.receivedProof(&block.HeaderProof{HeaderNonce: 11}) + require.False(t, wasSyncCalled) + }) + + t.Run("with self leader calls", func(t *testing.T) { + t.Parallel() + + proofsPool := &dataRetriever.ProofsPoolMock{} + wasSyncCalled := false + syncer := &facadeMock.SyncTimerMock{ + ForceSyncCalled: func() { + wasSyncCalled = true + }, + } + + tracker, _ := NewNtpSyncController(proofsPool, syncer, 0) + + for i := 0; i <= 7; i++ { + tracker.AddOutOfRangeNonce(uint64(i), "") + tracker.receivedProof(&block.HeaderProof{HeaderNonce: uint64(i)}) + } + + tracker.AddOutOfRangeNonce(9, "") + require.False(t, wasSyncCalled) + + // add for self leader, without receiving proof + tracker.AddLeaderNonceAsOutOfRange(8, "") + require.False(t, wasSyncCalled) + + tracker.receivedProof(&block.HeaderProof{HeaderNonce: 9}) + require.True(t, wasSyncCalled) + }) + + t.Run("only self leader calls", func(t *testing.T) { + t.Parallel() + + proofsPool := &dataRetriever.ProofsPoolMock{} + wasSyncCalled := false + syncer := &facadeMock.SyncTimerMock{ + ForceSyncCalled: func() { + wasSyncCalled = true + }, + } + + tracker, _ := NewNtpSyncController(proofsPool, syncer, 0) + + for i := 0; i <= 9; i++ { + tracker.AddLeaderNonceAsOutOfRange(uint64(i), "") + } + + // 10 consecutive leader proposed headers will trigger ntp force sync + // this a very unlikely scenario + + require.True(t, wasSyncCalled) + }) +} diff --git a/consensus/spos/bls/proxy/subroundsHandler.go b/consensus/spos/bls/proxy/subroundsHandler.go index 52b15c4ec9b..f9677b910f4 100644 --- a/consensus/spos/bls/proxy/subroundsHandler.go +++ b/consensus/spos/bls/proxy/subroundsHandler.go @@ -144,6 +144,7 @@ func (s *SubroundsHandler) Start(epoch uint32) error { func (s *SubroundsHandler) initSubroundsForEpoch(epoch uint32) error { var err error var fct subroundsFactory + if s.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, epoch) { if s.currentConsensusType == consensusV2 { return nil @@ -195,6 +196,7 @@ func (s *SubroundsHandler) initSubroundsForEpoch(epoch uint32) error { log.Debug("SubroundsHandler.initSubroundsForEpoch: reset consensus round state") s.worker.ResetConsensusRoundState() s.chronology.StartRounds() + return nil } diff --git a/consensus/spos/bls/proxy/subroundsHandler_test.go b/consensus/spos/bls/proxy/subroundsHandler_test.go index 8367968260e..1217473ff92 100644 --- a/consensus/spos/bls/proxy/subroundsHandler_test.go +++ b/consensus/spos/bls/proxy/subroundsHandler_test.go @@ -6,6 +6,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" crypto "github.com/multiversx/mx-chain-crypto-go" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" "github.com/stretchr/testify/require" mock2 "github.com/multiversx/mx-chain-go/consensus/mock" @@ -20,6 +21,7 @@ import ( epochNotifierMock "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" mock "github.com/multiversx/mx-chain-go/testscommon/epochstartmock" outportStub "github.com/multiversx/mx-chain-go/testscommon/outport" + "github.com/multiversx/mx-chain-go/testscommon/round" "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" "github.com/multiversx/mx-chain-go/testscommon/statusHandler" ) @@ -55,6 +57,7 @@ func getDefaultArgumentsSubroundHandler() (*SubroundsHandlerArgs, *spos.Consensu consensusCore.SetEpochStartNotifier(epochStartNotifier) consensusCore.SetBlockchain(&testscommon.ChainHandlerStub{}) consensusCore.SetBlockProcessor(&testscommon.BlockProcessorStub{}) + consensusCore.SetExecutionManager(&processMocks.ExecutionManagerMock{}) consensusCore.SetBootStrapper(&bootstrapperStubs.BootstrapperStub{}) consensusCore.SetBroadcastMessenger(&consensus.BroadcastMessengerMock{}) consensusCore.SetChronology(chronology) @@ -62,11 +65,11 @@ func getDefaultArgumentsSubroundHandler() (*SubroundsHandlerArgs, *spos.Consensu consensusCore.SetHasher(&testscommon.HasherStub{}) consensusCore.SetMarshalizer(&testscommon.MarshallerStub{}) consensusCore.SetMultiSignerContainer(&cryptoMocks.MultiSignerContainerStub{ - GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSigner, error) { + GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSignerV2, error) { return &cryptoMocks.MultisignerMock{}, nil }, }) - consensusCore.SetRoundHandler(&consensus.RoundHandlerMock{}) + consensusCore.SetRoundHandler(&round.RoundHandlerMock{}) consensusCore.SetShardCoordinator(&testscommon.ShardsCoordinatorMock{}) consensusCore.SetSyncTimer(&testscommon.SyncTimerStub{}) consensusCore.SetNodesCoordinator(&shardingMocks.NodesCoordinatorMock{}) @@ -79,6 +82,8 @@ func getDefaultArgumentsSubroundHandler() (*SubroundsHandlerArgs, *spos.Consensu consensusCore.SetPeerBlacklistHandler(&mock2.PeerBlacklistHandlerStub{}) consensusCore.SetSigningHandler(&consensus.SigningHandlerStub{}) consensusCore.SetEnableEpochsHandler(epochsEnable) + consensusCore.SetEnableRoundsHandler(&testscommon.EnableRoundsHandlerStub{}) + consensusCore.SetExecutionManager(&processMocks.ExecutionManagerMock{}) consensusCore.SetEquivalentProofsPool(&dataRetriever.ProofsPoolMock{}) consensusCore.SetEpochNotifier(epochNotifier) consensusCore.SetInvalidSignersCache(&consensus.InvalidSignersCacheMock{}) diff --git a/consensus/spos/bls/v1/blsSubroundsFactory.go b/consensus/spos/bls/v1/blsSubroundsFactory.go index 385d1603a5a..ffc0f30e20a 100644 --- a/consensus/spos/bls/v1/blsSubroundsFactory.go +++ b/consensus/spos/bls/v1/blsSubroundsFactory.go @@ -138,8 +138,9 @@ func (fct *factory) generateStartRoundSubround() error { -1, bls.SrStartRound, bls.SrBlock, - int64(float64(fct.getTimeDuration())*srStartStartTime), - int64(float64(fct.getTimeDuration())*srStartEndTime), + fct.getTimeDuration(), + srStartStartTime, + srStartEndTime, bls.GetSubroundName(bls.SrStartRound), fct.consensusState, fct.worker.GetConsensusStateChangedChannel(), @@ -180,8 +181,9 @@ func (fct *factory) generateBlockSubround() error { bls.SrStartRound, bls.SrBlock, bls.SrSignature, - int64(float64(fct.getTimeDuration())*srBlockStartTime), - int64(float64(fct.getTimeDuration())*srBlockEndTime), + fct.getTimeDuration(), + srBlockStartTime, + srBlockEndTime, bls.GetSubroundName(bls.SrBlock), fct.consensusState, fct.worker.GetConsensusStateChangedChannel(), @@ -218,8 +220,9 @@ func (fct *factory) generateSignatureSubround() error { bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(float64(fct.getTimeDuration())*srSignatureStartTime), - int64(float64(fct.getTimeDuration())*srSignatureEndTime), + fct.getTimeDuration(), + srSignatureStartTime, + srSignatureEndTime, bls.GetSubroundName(bls.SrSignature), fct.consensusState, fct.worker.GetConsensusStateChangedChannel(), @@ -254,8 +257,9 @@ func (fct *factory) generateEndRoundSubround() error { bls.SrSignature, bls.SrEndRound, -1, - int64(float64(fct.getTimeDuration())*srEndStartTime), - int64(float64(fct.getTimeDuration())*srEndEndTime), + fct.getTimeDuration(), + srEndStartTime, + srEndEndTime, bls.GetSubroundName(bls.SrEndRound), fct.consensusState, fct.worker.GetConsensusStateChangedChannel(), diff --git a/consensus/spos/bls/v1/blsSubroundsFactory_test.go b/consensus/spos/bls/v1/blsSubroundsFactory_test.go index 897e83d5593..ba538ac18b5 100644 --- a/consensus/spos/bls/v1/blsSubroundsFactory_test.go +++ b/consensus/spos/bls/v1/blsSubroundsFactory_test.go @@ -19,6 +19,7 @@ import ( consensusMock "github.com/multiversx/mx-chain-go/testscommon/consensus" "github.com/multiversx/mx-chain-go/testscommon/consensus/initializers" testscommonOutport "github.com/multiversx/mx-chain-go/testscommon/outport" + "github.com/multiversx/mx-chain-go/testscommon/round" "github.com/multiversx/mx-chain-go/testscommon/statusHandler" ) @@ -43,8 +44,8 @@ func executeStoredMessages() { func resetConsensusMessages() { } -func initRoundHandlerMock() *consensusMock.RoundHandlerMock { - return &consensusMock.RoundHandlerMock{ +func initRoundHandlerMock() *round.RoundHandlerMock { + return &round.RoundHandlerMock{ RoundIndex: 0, TimeStampCalled: func() time.Time { return time.Unix(0, 0) diff --git a/consensus/spos/bls/v1/subroundBlock.go b/consensus/spos/bls/v1/subroundBlock.go index bf6a0bf56c7..aa485392962 100644 --- a/consensus/spos/bls/v1/subroundBlock.go +++ b/consensus/spos/bls/v1/subroundBlock.go @@ -8,6 +8,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" + commonConsensus "github.com/multiversx/mx-chain-go/common/consensus" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/consensus" @@ -66,7 +67,7 @@ func checkNewSubroundBlockParams( // doBlockJob method does the job of the subround Block func (sr *subroundBlock) doBlockJob(ctx context.Context) bool { - isSelfLeader := sr.IsSelfLeaderInCurrentRound() && sr.ShouldConsiderSelfKeyInConsensus() + isSelfLeader := sr.IsSelfLeaderInCurrentRound() && commonConsensus.ShouldConsiderSelfKeyInConsensus(sr.NodeRedundancyHandler()) if !isSelfLeader && !sr.IsMultiKeyLeaderInCurrentRound() { // is NOT self leader in this round? return false } @@ -359,7 +360,7 @@ func (sr *subroundBlock) createHeader() (data.HeaderHandler, error) { return nil, err } - err = hdr.SetTimeStamp(uint64(sr.RoundHandler().TimeStamp().Unix())) + err = hdr.SetTimeStamp(sr.GetUnixTimestampForHeader(hdr.GetEpoch())) if err != nil { return nil, err } diff --git a/consensus/spos/bls/v1/subroundBlock_test.go b/consensus/spos/bls/v1/subroundBlock_test.go index d54a1b9b792..9ed34ea9d2c 100644 --- a/consensus/spos/bls/v1/subroundBlock_test.go +++ b/consensus/spos/bls/v1/subroundBlock_test.go @@ -22,6 +22,7 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/consensus/initializers" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" + "github.com/multiversx/mx-chain-go/testscommon/round" "github.com/multiversx/mx-chain-go/testscommon/statusHandler" ) @@ -31,8 +32,9 @@ func defaultSubroundForSRBlock(consensusState *spos.ConsensusState, ch chan bool bls.SrStartRound, bls.SrBlock, bls.SrSignature, - int64(5*roundTimeDuration/100), - int64(25*roundTimeDuration/100), + roundTimeDuration, + 0.05, + 0.25, "(BLOCK)", consensusState, ch, @@ -343,7 +345,7 @@ func TestSubroundBlock_DoBlockJob(t *testing.T) { }, } container.SetBroadcastMessenger(bm) - container.SetRoundHandler(&consensusMock.RoundHandlerMock{ + container.SetRoundHandler(&round.RoundHandlerMock{ RoundIndex: 1, }) r = sr.DoBlockJob() @@ -701,7 +703,7 @@ func TestSubroundBlock_ProcessReceivedBlockShouldReturnFalseWhenProcessBlockRetu return errors.New("error") } container.SetBlockProcessor(blockProcessorMock) - container.SetRoundHandler(&consensusMock.RoundHandlerMock{RoundIndex: 1}) + container.SetRoundHandler(&round.RoundHandlerMock{RoundIndex: 1}) assert.False(t, sr.ProcessReceivedBlock(cnsMsg)) } @@ -839,7 +841,7 @@ func TestSubroundBlock_HaveTimeInCurrentSubroundShouldReturnTrue(t *testing.T) { return time.Duration(remainingTime) > 0 } - roundHandlerMock := &consensusMock.RoundHandlerMock{} + roundHandlerMock := &round.RoundHandlerMock{} roundHandlerMock.TimeDurationCalled = func() time.Duration { return 4000 * time.Millisecond } @@ -869,7 +871,7 @@ func TestSubroundBlock_HaveTimeInCurrentSuboundShouldReturnFalse(t *testing.T) { return time.Duration(remainingTime) > 0 } - roundHandlerMock := &consensusMock.RoundHandlerMock{} + roundHandlerMock := &round.RoundHandlerMock{} roundHandlerMock.TimeDurationCalled = func() time.Duration { return 4000 * time.Millisecond } diff --git a/consensus/spos/bls/v1/subroundEndRound.go b/consensus/spos/bls/v1/subroundEndRound.go index c591c736aca..e622c8ccd68 100644 --- a/consensus/spos/bls/v1/subroundEndRound.go +++ b/consensus/spos/bls/v1/subroundEndRound.go @@ -11,6 +11,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/display" + commonConsensus "github.com/multiversx/mx-chain-go/common/consensus" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/consensus" @@ -594,7 +595,7 @@ func (sr *subroundEndRound) createAndBroadcastHeaderFinalInfo() { } func (sr *subroundEndRound) createAndBroadcastInvalidSigners(invalidSigners []byte) { - isSelfLeader := sr.IsSelfLeaderInCurrentRound() && sr.ShouldConsiderSelfKeyInConsensus() + isSelfLeader := sr.IsSelfLeaderInCurrentRound() && commonConsensus.ShouldConsiderSelfKeyInConsensus(sr.NodeRedundancyHandler()) if !(isSelfLeader || sr.IsMultiKeyLeaderInCurrentRound()) { return } @@ -815,12 +816,15 @@ func (sr *subroundEndRound) signBlockHeader() ([]byte, error) { func (sr *subroundEndRound) updateMetricsForLeader() { sr.appStatusHandler.Increment(common.MetricCountAcceptedBlocks) + + roundTimeStamp := sr.RoundHandler().TimeStamp() + timeSinceRound := time.Since(roundTimeStamp) sr.appStatusHandler.SetStringValue(common.MetricConsensusRoundState, - fmt.Sprintf("valid block produced in %f sec", time.Since(sr.RoundHandler().TimeStamp()).Seconds())) + fmt.Sprintf("valid block produced in %s", timeSinceRound.String())) } func (sr *subroundEndRound) broadcastBlockDataLeader() error { - miniBlocks, transactions, err := sr.BlockProcessor().MarshalizedDataToBroadcast(sr.GetHeader(), sr.GetBody()) + miniBlocks, transactions, err := sr.BlockProcessor().MarshalizedDataToBroadcast(sr.GetData(), sr.GetHeader(), sr.GetBody()) if err != nil { return err } @@ -915,7 +919,7 @@ func (sr *subroundEndRound) getIndexPkAndDataToBroadcast() (int, []byte, map[uin return -1, nil, nil, nil, err } - miniBlocks, transactions, err := sr.BlockProcessor().MarshalizedDataToBroadcast(sr.GetHeader(), sr.GetBody()) + miniBlocks, transactions, err := sr.BlockProcessor().MarshalizedDataToBroadcast(sr.GetData(), sr.GetHeader(), sr.GetBody()) if err != nil { return -1, nil, nil, nil, err } diff --git a/consensus/spos/bls/v1/subroundEndRound_test.go b/consensus/spos/bls/v1/subroundEndRound_test.go index 678f70b7d78..b11b4ec0e41 100644 --- a/consensus/spos/bls/v1/subroundEndRound_test.go +++ b/consensus/spos/bls/v1/subroundEndRound_test.go @@ -27,6 +27,7 @@ import ( consensusMocks "github.com/multiversx/mx-chain-go/testscommon/consensus" "github.com/multiversx/mx-chain-go/testscommon/consensus/initializers" "github.com/multiversx/mx-chain-go/testscommon/p2pmocks" + "github.com/multiversx/mx-chain-go/testscommon/round" "github.com/multiversx/mx-chain-go/testscommon/statusHandler" ) @@ -40,8 +41,9 @@ func initSubroundEndRoundWithContainer( bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -79,8 +81,9 @@ func TestNewSubroundEndRound(t *testing.T) { bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -164,8 +167,9 @@ func TestSubroundEndRound_NewSubroundEndRoundNilBlockChainShouldFail(t *testing. bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -200,8 +204,9 @@ func TestSubroundEndRound_NewSubroundEndRoundNilBlockProcessorShouldFail(t *test bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -236,8 +241,9 @@ func TestSubroundEndRound_NewSubroundEndRoundNilConsensusStateShouldFail(t *test bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -273,8 +279,9 @@ func TestSubroundEndRound_NewSubroundEndRoundNilMultiSignerContainerShouldFail(t bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -309,8 +316,9 @@ func TestSubroundEndRound_NewSubroundEndRoundNilRoundHandlerShouldFail(t *testin bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -345,8 +353,9 @@ func TestSubroundEndRound_NewSubroundEndRoundNilSyncTimerShouldFail(t *testing.T bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -381,8 +390,9 @@ func TestSubroundEndRound_NewSubroundEndRoundShouldWork(t *testing.T) { bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -460,7 +470,7 @@ func TestSubroundEndRound_DoEndRoundJobErrTimeIsOutShouldFail(t *testing.T) { sr.SetLeader("A") remainingTime := time.Millisecond - roundHandlerMock := &consensusMocks.RoundHandlerMock{ + roundHandlerMock := &round.RoundHandlerMock{ RemainingTimeCalled: func(startTime time.Time, maxTime time.Duration) time.Duration { return remainingTime }, @@ -505,7 +515,7 @@ func TestSubroundEndRound_DoEndRoundJobErrMarshalizedDataToBroadcastOK(t *testin container := consensusMocks.InitConsensusCore() bpm := consensusMocks.InitBlockProcessorMock(container.Marshalizer()) - bpm.MarshalizedDataToBroadcastCalled = func(header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) { + bpm.MarshalizedDataToBroadcastCalled = func(hash []byte, header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) { err = errors.New("error marshalized data to broadcast") return make(map[uint32][]byte), make(map[string][][]byte), err } @@ -541,7 +551,7 @@ func TestSubroundEndRound_DoEndRoundJobErrBroadcastMiniBlocksOK(t *testing.T) { container := consensusMocks.InitConsensusCore() bpm := consensusMocks.InitBlockProcessorMock(container.Marshalizer()) - bpm.MarshalizedDataToBroadcastCalled = func(header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) { + bpm.MarshalizedDataToBroadcastCalled = func(hash []byte, header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) { return make(map[uint32][]byte), make(map[string][][]byte), nil } container.SetBlockProcessor(bpm) @@ -578,7 +588,7 @@ func TestSubroundEndRound_DoEndRoundJobErrBroadcastTransactionsOK(t *testing.T) container := consensusMocks.InitConsensusCore() bpm := consensusMocks.InitBlockProcessorMock(container.Marshalizer()) - bpm.MarshalizedDataToBroadcastCalled = func(header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) { + bpm.MarshalizedDataToBroadcastCalled = func(hash []byte, header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) { return make(map[uint32][]byte), make(map[string][][]byte), nil } container.SetBlockProcessor(bpm) @@ -957,7 +967,7 @@ func TestSubroundEndRound_IsOutOfTimeShouldReturnTrue(t *testing.T) { // update roundHandler's mock, so it will calculate for real the duration container := consensusMocks.InitConsensusCore() - roundHandler := consensusMocks.RoundHandlerMock{RemainingTimeCalled: func(startTime time.Time, maxTime time.Duration) time.Duration { + roundHandler := round.RoundHandlerMock{RemainingTimeCalled: func(startTime time.Time, maxTime time.Duration) time.Duration { currentTime := time.Now() elapsedTime := currentTime.Sub(startTime) remainingTime := maxTime - elapsedTime @@ -1359,8 +1369,9 @@ func TestSubroundEndRound_ReceivedInvalidSignersInfo(t *testing.T) { bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -1731,8 +1742,9 @@ func TestSubroundEndRound_getMinConsensusGroupIndexOfManagedKeys(t *testing.T) { bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, diff --git a/consensus/spos/bls/v1/subroundSignature.go b/consensus/spos/bls/v1/subroundSignature.go index 1d71ac59420..360aba6e48a 100644 --- a/consensus/spos/bls/v1/subroundSignature.go +++ b/consensus/spos/bls/v1/subroundSignature.go @@ -8,6 +8,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + commonConsensus "github.com/multiversx/mx-chain-go/common/consensus" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/consensus" @@ -81,8 +82,8 @@ func (sr *subroundSignature) doSignatureJob(_ context.Context) bool { return false } - isSelfLeader := sr.IsSelfLeaderInCurrentRound() && sr.ShouldConsiderSelfKeyInConsensus() - isSelfInConsensusGroup := sr.IsNodeInConsensusGroup(sr.SelfPubKey()) && sr.ShouldConsiderSelfKeyInConsensus() + isSelfLeader := sr.IsSelfLeaderInCurrentRound() && commonConsensus.ShouldConsiderSelfKeyInConsensus(sr.NodeRedundancyHandler()) + isSelfInConsensusGroup := sr.IsNodeInConsensusGroup(sr.SelfPubKey()) && commonConsensus.ShouldConsiderSelfKeyInConsensus(sr.NodeRedundancyHandler()) if isSelfLeader || isSelfInConsensusGroup { selfIndex, err := sr.SelfConsensusGroupIndex() diff --git a/consensus/spos/bls/v1/subroundSignature_test.go b/consensus/spos/bls/v1/subroundSignature_test.go index a3708f8c326..03e5101cd5f 100644 --- a/consensus/spos/bls/v1/subroundSignature_test.go +++ b/consensus/spos/bls/v1/subroundSignature_test.go @@ -27,8 +27,9 @@ func initSubroundSignatureWithContainer(container *spos.ConsensusCore) v1.Subrou bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -65,8 +66,9 @@ func TestNewSubroundSignature(t *testing.T) { bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -142,8 +144,9 @@ func TestSubroundSignature_NewSubroundSignatureNilConsensusStateShouldFail(t *te bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -177,8 +180,9 @@ func TestSubroundSignature_NewSubroundSignatureNilHasherShouldFail(t *testing.T) bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -211,8 +215,9 @@ func TestSubroundSignature_NewSubroundSignatureNilMultiSignerContainerShouldFail bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -245,8 +250,9 @@ func TestSubroundSignature_NewSubroundSignatureNilRoundHandlerShouldFail(t *test bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -280,8 +286,9 @@ func TestSubroundSignature_NewSubroundSignatureNilSyncTimerShouldFail(t *testing bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -314,8 +321,9 @@ func TestSubroundSignature_NewSubroundSignatureShouldWork(t *testing.T) { bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -399,8 +407,9 @@ func TestSubroundSignature_DoSignatureJobWithMultikey(t *testing.T) { bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, diff --git a/consensus/spos/bls/v1/subroundStartRound.go b/consensus/spos/bls/v1/subroundStartRound.go index a47d9235cd2..734b04ebb2a 100644 --- a/consensus/spos/bls/v1/subroundStartRound.go +++ b/consensus/spos/bls/v1/subroundStartRound.go @@ -11,6 +11,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" outportcore "github.com/multiversx/mx-chain-core-go/data/outport" + "github.com/multiversx/mx-chain-go/common/consensus" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/consensus/spos" @@ -173,7 +174,7 @@ func (sr *subroundStartRound) initCurrentRound() bool { if sr.IsKeyManagedBySelf([]byte(leader)) { msg = " (my turn in multi-key)" } - if leader == sr.SelfPubKey() && sr.ShouldConsiderSelfKeyInConsensus() { + if leader == sr.SelfPubKey() && consensus.ShouldConsiderSelfKeyInConsensus(sr.NodeRedundancyHandler()) { msg = " (my turn)" } if len(msg) != 0 { @@ -192,7 +193,7 @@ func (sr *subroundStartRound) initCurrentRound() bool { sr.indexRoundIfNeeded(pubKeys) - isSingleKeyLeader := leader == sr.SelfPubKey() && sr.ShouldConsiderSelfKeyInConsensus() + isSingleKeyLeader := leader == sr.SelfPubKey() && consensus.ShouldConsiderSelfKeyInConsensus(sr.NodeRedundancyHandler()) isLeader := isSingleKeyLeader || sr.IsKeyManagedBySelf([]byte(leader)) isSelfInConsensus := sr.IsNodeInConsensusGroup(sr.SelfPubKey()) || numMultiKeysInConsensusGroup > 0 if !isSelfInConsensus { @@ -298,7 +299,7 @@ func (sr *subroundStartRound) indexRoundIfNeeded(pubKeys []string) { BlockWasProposed: false, ShardId: shardId, Epoch: epoch, - Timestamp: uint64(sr.GetRoundTimeStamp().Unix()), + Timestamp: sr.GetUnixTimestampForHeader(epoch), } roundsInfo := &outportcore.RoundsInfo{ ShardID: shardId, @@ -366,7 +367,8 @@ func (sr *subroundStartRound) EpochStartAction(hdr data.HeaderHandler) { func (sr *subroundStartRound) changeEpoch(currentEpoch uint32) { epochNodes, err := sr.NodesCoordinator().GetConsensusWhitelistedNodes(currentEpoch) if err != nil { - panic(fmt.Sprintf("consensus changing epoch failed with error %s", err.Error())) + log.Error("consensus changing epoch failed", "error", err.Error()) + return } sr.SetEligibleList(epochNodes) diff --git a/consensus/spos/bls/v1/subroundStartRound_test.go b/consensus/spos/bls/v1/subroundStartRound_test.go index e5b898930f5..8b7488e9824 100644 --- a/consensus/spos/bls/v1/subroundStartRound_test.go +++ b/consensus/spos/bls/v1/subroundStartRound_test.go @@ -57,8 +57,9 @@ func defaultSubround( -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, ch, @@ -101,8 +102,9 @@ func TestNewSubroundStartRound(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(START_ROUND)", consensusState, ch, @@ -553,8 +555,9 @@ func TestSubroundStartRound_InitCurrentRoundShouldMetrics(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(START_ROUND)", consensusState, ch, @@ -607,8 +610,9 @@ func TestSubroundStartRound_InitCurrentRoundShouldMetrics(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(START_ROUND)", consensusState, ch, @@ -661,8 +665,9 @@ func TestSubroundStartRound_InitCurrentRoundShouldMetrics(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(START_ROUND)", consensusState, ch, @@ -726,8 +731,9 @@ func TestSubroundStartRound_InitCurrentRoundShouldMetrics(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(START_ROUND)", consensusState, ch, @@ -794,8 +800,9 @@ func TestSubroundStartRound_InitCurrentRoundShouldMetrics(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(START_ROUND)", consensusState, ch, diff --git a/consensus/spos/bls/v2/benchmark_send_proof_test.go b/consensus/spos/bls/v2/benchmark_send_proof_test.go new file mode 100644 index 00000000000..a589e4ae683 --- /dev/null +++ b/consensus/spos/bls/v2/benchmark_send_proof_test.go @@ -0,0 +1,214 @@ +package v2_test + +import ( + "sort" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data/block" + crypto "github.com/multiversx/mx-chain-crypto-go" + "github.com/multiversx/mx-chain-crypto-go/signing" + "github.com/multiversx/mx-chain-crypto-go/signing/mcl" + mclMultisig "github.com/multiversx/mx-chain-crypto-go/signing/mcl/multisig" + "github.com/multiversx/mx-chain-crypto-go/signing/multisig" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/consensus/spos" + "github.com/multiversx/mx-chain-go/consensus/spos/bls" + v2 "github.com/multiversx/mx-chain-go/consensus/spos/bls/v2" + factoryCrypto "github.com/multiversx/mx-chain-go/factory/crypto" + "github.com/multiversx/mx-chain-go/storage/cache" + "github.com/multiversx/mx-chain-go/testscommon" + consensusMocks "github.com/multiversx/mx-chain-go/testscommon/consensus" + "github.com/multiversx/mx-chain-go/testscommon/consensus/initializers" + "github.com/multiversx/mx-chain-go/testscommon/cryptoMocks" + "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" + "github.com/multiversx/mx-chain-go/testscommon/statusHandler" +) + +const sendProofBenchmarkKeyCount = 400 + +// generateBLSKeyPairs generates the specified number of BLS key pairs +func generateBLSKeyPairs(kg crypto.KeyGenerator, count int) map[string]crypto.PrivateKey { + mapKeys := make(map[string]crypto.PrivateKey) + + for i := 0; i < count; i++ { + sk, pk := kg.GeneratePair() + pubKey, _ := pk.ToByteArray() + mapKeys[string(pubKey)] = sk + } + return mapKeys +} + +// sortedKeysFromMap returns sorted keys from a map of private keys +func sortedKeysFromMap(mapKeys map[string]crypto.PrivateKey) []string { + keys := make([]string, 0, len(mapKeys)) + for key := range mapKeys { + keys = append(keys, key) + } + sort.Strings(keys) + return keys +} + +// BenchmarkSubroundEndRound_SendProof400Keys benchmarks the sendProof method with 400 real BLS keys +func BenchmarkSubroundEndRound_SendProof400Keys(b *testing.B) { + benchmarkSendProof(b, sendProofBenchmarkKeyCount) +} + +func benchmarkSendProof(b *testing.B, numberOfKeys int) { + container := consensusMocks.InitConsensusCore() + + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.AndromedaFlag + }, + } + container.SetEnableEpochsHandler(enableEpochsHandler) + + // Set up real BLS cryptography + llSigner := &mclMultisig.BlsMultiSignerKOSK{} + suite := mcl.NewSuiteBLS12() + kg := signing.NewKeyGenerator(suite) + + multiSigHandler, err := multisig.NewBLSMultisig(llSigner, kg) + require.Nil(b, err) + + // Generate real BLS key pairs + mapKeys := generateBLSKeyPairs(kg, numberOfKeys) + keys := sortedKeysFromMap(mapKeys) + + keysHandlerMock := &testscommon.KeysHandlerStub{ + IsKeyManagedByCurrentNodeCalled: func(pkBytes []byte) bool { + _, exists := mapKeys[string(pkBytes)] + return exists + }, + GetHandledPrivateKeyCalled: func(pkBytes []byte) crypto.PrivateKey { + return mapKeys[string(pkBytes)] + }, + } + + pubKeysCache, err := cache.NewLRUCache(1000) + require.Nil(b, err) + + args := factoryCrypto.ArgsSigningHandler{ + PubKeys: keys, + MultiSignerContainer: &cryptoMocks.MultiSignerContainerStub{ + GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSignerV2, error) { + return multiSigHandler, nil + }, + }, + SingleSigner: &cryptoMocks.SingleSignerStub{}, + KeyGenerator: kg, + KeysHandler: keysHandlerMock, + PubKeysCache: pubKeysCache, + } + + signingHandler, err := factoryCrypto.NewSigningHandler(args) + require.Nil(b, err) + + container.SetSigningHandler(signingHandler) + + // Set up proofs pool to return false (no proof exists yet) + container.SetEquivalentProofsPool(&dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return false + }, + }) + + // Initialize consensus state with real keys + consensusState := initializers.InitConsensusStateWithArgsVerifySignature(keysHandlerMock, keys) + dataToBeSigned := []byte("block_header_hash_for_benchmark") + consensusState.Data = dataToBeSigned + + ch := make(chan bool, 1) + + sr, err := spos.NewSubround( + bls.SrSignature, + bls.SrEndRound, + -1, + roundTimeDuration, + 0.85, + 0.95, + "(END_ROUND)", + consensusState, + ch, + executeStoredMessages, + container, + chainID, + currentPid, + &statusHandler.AppStatusHandlerStub{}, + ) + require.Nil(b, err) + + // Set header + sr.SetHeader(&block.HeaderV2{ + Header: &block.Header{ + Nonce: 1, + Round: 1, + TimeStamp: 0, + ShardID: 0, + }, + }) + + // Set self pub key to be in consensus group (first key) + sr.SetSelfPubKey(keys[0]) + + srEndRound, err := v2.NewSubroundEndRound( + sr, + v2.ProcessingThresholdPercent, + &statusHandler.AppStatusHandlerStub{}, + &testscommon.SentSignatureTrackerStub{}, + &consensusMocks.SposWorkerMock{ + ConsensusMetricsCalled: func() spos.ConsensusMetricsHandler { + consensusMetrics, _ := spos.NewConsensusMetrics(&statusHandler.AppStatusHandlerStub{}) + return consensusMetrics + }, + }, + ) + require.Nil(b, err) + + // Create signature shares for all validators (simulating that all have signed) + for i := 0; i < len(keys); i++ { + _, err := signingHandler.CreateSignatureShareForPublicKey(dataToBeSigned, uint16(i), 0, []byte(keys[i])) + require.Nil(b, err) + err = srEndRound.SetJobDone(keys[i], bls.SrSignature, true) + require.Nil(b, err) + } + + b.ResetTimer() + b.StopTimer() + + for i := 0; i < b.N; i++ { + // Reset signature state for each iteration + for j := 0; j < len(keys); j++ { + _, _ = signingHandler.CreateSignatureShareForPublicKey(dataToBeSigned, uint16(j), 0, []byte(keys[j])) + } + + b.StartTimer() + proofSent, err := srEndRound.SendProof() + b.StopTimer() + + // The proof should be sent successfully (err may be ErrProofAlreadyPropagated on subsequent runs) + if err != nil && err != v2.ErrProofAlreadyPropagated && err != v2.ErrTimeOut { + require.Nil(b, err) + } + _ = proofSent + } +} + +// BenchmarkSubroundEndRound_SendProof63Keys benchmarks the sendProof method with 63 keys (standard consensus size) +func BenchmarkSubroundEndRound_SendProof63Keys(b *testing.B) { + benchmarkSendProof(b, 63) +} + +// BenchmarkSubroundEndRound_SendProof100Keys benchmarks the sendProof method with 100 keys +func BenchmarkSubroundEndRound_SendProof100Keys(b *testing.B) { + benchmarkSendProof(b, 100) +} + +// BenchmarkSubroundEndRound_SendProof200Keys benchmarks the sendProof method with 200 keys +func BenchmarkSubroundEndRound_SendProof200Keys(b *testing.B) { + benchmarkSendProof(b, 200) +} diff --git a/consensus/spos/bls/v2/benchmark_test.go b/consensus/spos/bls/v2/benchmark_test.go index 5fb3c3a253f..be659011a6d 100644 --- a/consensus/spos/bls/v2/benchmark_test.go +++ b/consensus/spos/bls/v2/benchmark_test.go @@ -19,6 +19,7 @@ import ( "github.com/multiversx/mx-chain-go/consensus/spos/bls" v2 "github.com/multiversx/mx-chain-go/consensus/spos/bls/v2" cryptoFactory "github.com/multiversx/mx-chain-go/factory/crypto" + "github.com/multiversx/mx-chain-go/storage/cache" "github.com/multiversx/mx-chain-go/testscommon" nodeMock "github.com/multiversx/mx-chain-go/testscommon/common" "github.com/multiversx/mx-chain-go/testscommon/consensus" @@ -73,15 +74,19 @@ func benchmarkSubroundSignatureDoSignatureJobForManagedKeys(b *testing.B, number }, } + pubKeysCache, err := cache.NewLRUCache(1000) + require.Nil(b, err) + args := cryptoFactory.ArgsSigningHandler{ PubKeys: initializers.CreateEligibleListFromMap(mapKeys), MultiSignerContainer: &cryptoMocks.MultiSignerContainerStub{ - GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSigner, error) { + GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSignerV2, error) { return multiSigHandler, nil }}, SingleSigner: &cryptoMocks.SingleSignerStub{}, KeyGenerator: kg, KeysHandler: keysHandlerMock, + PubKeysCache: pubKeysCache, } signingHandler, err := cryptoFactory.NewSigningHandler(args) require.Nil(b, err) @@ -94,8 +99,9 @@ func benchmarkSubroundSignatureDoSignatureJobForManagedKeys(b *testing.B, number bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, diff --git a/consensus/spos/bls/v2/benchmark_verify_signatures_test.go b/consensus/spos/bls/v2/benchmark_verify_signatures_test.go index 46d18d8460e..505a83dbcae 100644 --- a/consensus/spos/bls/v2/benchmark_verify_signatures_test.go +++ b/consensus/spos/bls/v2/benchmark_verify_signatures_test.go @@ -15,7 +15,7 @@ import ( "github.com/multiversx/mx-chain-crypto-go/signing/multisig" "github.com/multiversx/mx-chain-go/consensus/spos/bls" - dataRetrieverMocks "github.com/multiversx/mx-chain-go/dataRetriever/mock" + "github.com/multiversx/mx-chain-go/storage/cache" "github.com/multiversx/mx-chain-go/testscommon/consensus/initializers" "github.com/multiversx/mx-chain-go/common" @@ -87,16 +87,21 @@ func BenchmarkSubroundEndRound_VerifyNodesOnAggSigFailTime(b *testing.B) { }, } keys := createListFromMapKeys(mapKeys) + + pubKeysCache, err := cache.NewLRUCache(1000) + require.Nil(b, err) + args := factoryCrypto.ArgsSigningHandler{ PubKeys: keys, MultiSignerContainer: &cryptoMocks.MultiSignerContainerStub{ - GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSigner, error) { + GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSignerV2, error) { return multiSigHandler, nil }, }, SingleSigner: &cryptoMocks.SingleSignerStub{}, KeyGenerator: kg, KeysHandler: keysHandlerMock, + PubKeysCache: pubKeysCache, } signingHandler, err := factoryCrypto.NewSigningHandler(args) @@ -107,7 +112,7 @@ func BenchmarkSubroundEndRound_VerifyNodesOnAggSigFailTime(b *testing.B) { dataToBeSigned := []byte("message") consensusState.Data = dataToBeSigned - sr := initSubroundEndRoundWithContainerAndConsensusState(container, &statusHandler.AppStatusHandlerStub{}, consensusState, &dataRetrieverMocks.ThrottlerStub{}) + sr := initSubroundEndRoundWithContainerAndConsensusState(container, &statusHandler.AppStatusHandlerStub{}, consensusState) for i := 0; i < len(sr.ConsensusGroup()); i++ { _, err := sr.SigningHandler().CreateSignatureShareForPublicKey(dataToBeSigned, uint16(i), sr.EnableEpochsHandler().GetCurrentEpoch(), []byte(keys[i])) require.Nil(b, err) diff --git a/consensus/spos/bls/v2/blsSubroundsFactory.go b/consensus/spos/bls/v2/blsSubroundsFactory.go index a2b35dcefe3..6c76df939c1 100644 --- a/consensus/spos/bls/v2/blsSubroundsFactory.go +++ b/consensus/spos/bls/v2/blsSubroundsFactory.go @@ -8,6 +8,7 @@ import ( "github.com/multiversx/mx-chain-go/consensus/spos" "github.com/multiversx/mx-chain-go/consensus/spos/bls" + "github.com/multiversx/mx-chain-go/consensus/spos/bls/ntpsync" "github.com/multiversx/mx-chain-go/outport" ) @@ -146,8 +147,9 @@ func (fct *factory) generateStartRoundSubround() error { -1, bls.SrStartRound, bls.SrBlock, - int64(float64(fct.getTimeDuration())*srStartStartTime), - int64(float64(fct.getTimeDuration())*srStartEndTime), + fct.getTimeDuration(), + srStartStartTime, + srStartEndTime, bls.GetSubroundName(bls.SrStartRound), fct.consensusState, fct.worker.GetConsensusStateChangedChannel(), @@ -186,8 +188,9 @@ func (fct *factory) generateBlockSubround() error { bls.SrStartRound, bls.SrBlock, bls.SrSignature, - int64(float64(fct.getTimeDuration())*srBlockStartTime), - int64(float64(fct.getTimeDuration())*srBlockEndTime), + fct.getTimeDuration(), + srBlockStartTime, + srBlockEndTime, bls.GetSubroundName(bls.SrBlock), fct.consensusState, fct.worker.GetConsensusStateChangedChannel(), @@ -201,10 +204,20 @@ func (fct *factory) generateBlockSubround() error { return err } + syncController, err := ntpsync.NewNtpSyncController( + subround.EquivalentProofsPool(), + subround.ConsensusCoreHandler.SyncTimer(), + fct.consensusCore.ShardCoordinator().SelfId(), + ) + if err != nil { + return err + } + subroundBlockInstance, err := NewSubroundBlock( subround, processingThresholdPercent, fct.worker, + syncController, ) if err != nil { return err @@ -222,8 +235,9 @@ func (fct *factory) generateSignatureSubround() error { bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(float64(fct.getTimeDuration())*srSignatureStartTime), - int64(float64(fct.getTimeDuration())*srSignatureEndTime), + fct.getTimeDuration(), + srSignatureStartTime, + srSignatureEndTime, bls.GetSubroundName(bls.SrSignature), fct.consensusState, fct.worker.GetConsensusStateChangedChannel(), @@ -258,8 +272,9 @@ func (fct *factory) generateEndRoundSubround() error { bls.SrSignature, bls.SrEndRound, -1, - int64(float64(fct.getTimeDuration())*srEndStartTime), - int64(float64(fct.getTimeDuration())*srEndEndTime), + fct.getTimeDuration(), + srEndStartTime, + srEndEndTime, bls.GetSubroundName(bls.SrEndRound), fct.consensusState, fct.worker.GetConsensusStateChangedChannel(), @@ -279,7 +294,6 @@ func (fct *factory) generateEndRoundSubround() error { fct.appStatusHandler, fct.sentSignaturesTracker, fct.worker, - fct.signatureThrottler, ) if err != nil { return err diff --git a/consensus/spos/bls/v2/blsSubroundsFactory_test.go b/consensus/spos/bls/v2/blsSubroundsFactory_test.go index 5be8b6bdbcd..01a9d196399 100644 --- a/consensus/spos/bls/v2/blsSubroundsFactory_test.go +++ b/consensus/spos/bls/v2/blsSubroundsFactory_test.go @@ -7,10 +7,12 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" - "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/testscommon/round" + "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" + "github.com/multiversx/mx-chain-go/consensus" "github.com/multiversx/mx-chain-go/consensus/spos" "github.com/multiversx/mx-chain-go/consensus/spos/bls" @@ -34,8 +36,8 @@ const roundTimeDuration = 100 * time.Millisecond func executeStoredMessages() { } -func initRoundHandlerMock() *testscommonConsensus.RoundHandlerMock { - return &testscommonConsensus.RoundHandlerMock{ +func initRoundHandlerMock() *round.RoundHandlerMock { + return &round.RoundHandlerMock{ RoundIndex: 0, TimeStampCalled: func() time.Time { return time.Unix(0, 0) diff --git a/consensus/spos/bls/v2/constants.go b/consensus/spos/bls/v2/constants.go index 93856652b39..024718ab590 100644 --- a/consensus/spos/bls/v2/constants.go +++ b/consensus/spos/bls/v2/constants.go @@ -35,3 +35,11 @@ const srEndStartTime = 0.85 // srEndEndTime specifies the end time, from the total time of the round, of Subround End const srEndEndTime = 0.95 + +// competingBlockSignDelay is the fraction of the full round time to wait before signing +// a competing block for the same nonce, giving the previous block's proof time to arrive. +const competingBlockSignDelay = 0.5 + +// competingProofSendDelay is the fraction of the full round time to wait before sending +// a proof, giving the previous block's proof time to arrive. +const competingProofSendDelay = 0.25 diff --git a/consensus/spos/bls/v2/errors.go b/consensus/spos/bls/v2/errors.go index 545e84ac760..92935f5a8b4 100644 --- a/consensus/spos/bls/v2/errors.go +++ b/consensus/spos/bls/v2/errors.go @@ -10,3 +10,12 @@ var ErrTimeOut = errors.New("time is out") // ErrProofAlreadyPropagated signals that the proof was already propagated var ErrProofAlreadyPropagated = errors.New("proof already propagated") + +// ErrNilRoundSyncController signals that a nil round sync controller has been provided +var ErrNilRoundSyncController = errors.New("nil round sync controller") + +// ErrTooManyInvalidSigners signals that too many invalid signers were received +var ErrTooManyInvalidSigners = errors.New("too many invalid signers") + +// ErrValidSignatureFromInvalidSigner signals that a valid signature was received on invalid signers message +var ErrValidSignatureFromInvalidSigner = errors.New("valid signature from invalid sender") diff --git a/consensus/spos/bls/v2/export_test.go b/consensus/spos/bls/v2/export_test.go index 61193eb6193..58988e0d82f 100644 --- a/consensus/spos/bls/v2/export_test.go +++ b/consensus/spos/bls/v2/export_test.go @@ -226,6 +226,11 @@ func (sr *subroundBlock) GetLeaderForHeader(headerHandler data.HeaderHandler) ([ return sr.getLeaderForHeader(headerHandler) } +// UpdateConsensusMetricsProposedBlockReceivedOrSent updates the metrics containing time taken to receive a proposed block body since the round started +func (sr *subroundBlock) UpdateConsensusMetricsProposedBlockReceivedOrSent() { + sr.updateConsensusMetricsProposedBlockReceivedOrSent() +} + // subroundSignature // SubroundSignature defines an alias to the subroundSignature structure @@ -332,8 +337,8 @@ func (sr *subroundStartRound) IndexRoundIfNeeded(pubKeys []string) { } // SendSignatureForManagedKey calls the unexported sendSignatureForManagedKey function -func (sr *subroundSignature) SendSignatureForManagedKey(idx int, pk string) bool { - return sr.sendSignatureForManagedKey(idx, pk) +func (sr *subroundSignature) SendSignatureForManagedKey(ctx context.Context, idx int, pk string) bool { + return sr.sendSignatureForManagedKey(ctx, idx, pk) } // DoSignatureJobForManagedKeys calls the unexported doSignatureJobForManagedKeys function @@ -341,6 +346,21 @@ func (sr *subroundSignature) DoSignatureJobForManagedKeys(ctx context.Context) b return sr.doSignatureJobForManagedKeys(ctx) } +// WaitIfCompetingBlock calls the unexported waitIfCompetingBlock function +func (sr *subroundSignature) WaitIfCompetingBlock(ctx context.Context, pkBytes []byte, nonce uint64, currentHash []byte) bool { + return sr.waitIfCompetingBlock(ctx, pkBytes, nonce, currentHash) +} + +// WaitIfCompetingBlockForNode calls the unexported waitIfCompetingBlockForNode function +func (sr *subroundSignature) WaitIfCompetingBlockForNode(ctx context.Context, nonce uint64, currentHash []byte) bool { + return sr.waitIfCompetingBlockForNode(ctx, nonce, currentHash) +} + +// ShouldSendProof calls the unexported shouldSendProof function +func (sr *subroundEndRound) ShouldSendProof() bool { + return sr.shouldSendProof() +} + // ReceivedSignature method is called when a signature is received through the signature channel func (sr *subroundEndRound) ReceivedSignature(cnsDta *consensus.Message) bool { return sr.receivedSignature(context.Background(), cnsDta) @@ -360,3 +380,18 @@ func (sr *subroundEndRound) GetEquivalentProofSender() string { func (sr *subroundEndRound) SendProof() (bool, error) { return sr.sendProof() } + +// UpdateConsensusMetricsProof - +func (sr *subroundEndRound) UpdateConsensusMetricsProof() { + sr.updateConsensusMetricsProof() +} + +// UpdateNonceDeltaMetrics - +func (sr *subroundEndRound) UpdateNonceDeltaMetrics() { + sr.updateNonceDeltaMetrics() +} + +// PrepareBlockForExecution prepares the block for execution +func (sr *subroundBlock) PrepareBlockForExecution(header data.HeaderHandler, body data.BodyHandler) error { + return sr.prepareBlockForExecution(header, body) +} diff --git a/consensus/spos/bls/v2/subroundBlock.go b/consensus/spos/bls/v2/subroundBlock.go index af6cd839a9b..9c6ebeaae06 100644 --- a/consensus/spos/bls/v2/subroundBlock.go +++ b/consensus/spos/bls/v2/subroundBlock.go @@ -11,9 +11,11 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-go/common" + commonConsensus "github.com/multiversx/mx-chain-go/common/consensus" "github.com/multiversx/mx-chain-go/consensus" "github.com/multiversx/mx-chain-go/consensus/spos" "github.com/multiversx/mx-chain-go/consensus/spos/bls" + "github.com/multiversx/mx-chain-go/process/asyncExecution/cache" ) // maxAllowedSizeInBytes defines how many bytes are allowed as payload in a message @@ -26,6 +28,7 @@ type subroundBlock struct { processingThresholdPercentage int worker spos.WorkerHandler mutBlockProcessing sync.Mutex + syncController spos.NtpSyncControllerHandler } // NewSubroundBlock creates a subroundBlock object @@ -33,6 +36,7 @@ func NewSubroundBlock( baseSubround *spos.Subround, processingThresholdPercentage int, worker spos.WorkerHandler, + syncController spos.NtpSyncControllerHandler, ) (*subroundBlock, error) { err := checkNewSubroundBlockParams(baseSubround) if err != nil { @@ -42,11 +46,15 @@ func NewSubroundBlock( if check.IfNil(worker) { return nil, spos.ErrNilWorker } + if check.IfNil(syncController) { + return nil, ErrNilRoundSyncController + } srBlock := subroundBlock{ Subround: baseSubround, processingThresholdPercentage: processingThresholdPercentage, worker: worker, + syncController: syncController, } srBlock.Job = srBlock.doBlockJob @@ -89,6 +97,10 @@ func (sr *subroundBlock) doBlockJob(ctx context.Context) bool { if sr.IsSubroundFinished(sr.Current()) { return false } + if sr.HasProofForCompetingBlock() { + log.Debug("doBlockJob - competing block proof exists, skipping block proposal") + return false + } metricStatTime := time.Now() defer sr.computeSubroundProcessingMetric(metricStatTime, common.MetricCreatedProposedBlock) @@ -120,7 +132,13 @@ func (sr *subroundBlock) doBlockJob(ctx context.Context) bool { leader, errGetLeader := sr.GetLeader() if errGetLeader != nil { - log.Debug("doBlockJob.GetLeader", "error", errGetLeader) + printLogMessage(ctx, "doBlockJob.GetLeader", errGetLeader) + return false + } + + // check again there is no proof for competing block before sending the block + if sr.HasProofForCompetingBlock() { + log.Debug("doBlockJob - competing block proof exists, skipping block proposal") return false } @@ -129,6 +147,16 @@ func (sr *subroundBlock) doBlockJob(ctx context.Context) bool { return false } + err = sr.prepareBlockForExecution(header, body) + if err != nil { + printLogMessage(ctx, "doBlockJob.prepareBlockForExecution", err) + return false + } + + // placeholder for subroundBlock.doBlockJob script + + sr.updateConsensusMetricsProposedBlockReceivedOrSent() + err = sr.SetJobDone(leader, sr.Current(), true) if err != nil { log.Debug("doBlockJob.SetSelfJobDone", "error", err.Error()) @@ -137,11 +165,32 @@ func (sr *subroundBlock) doBlockJob(ctx context.Context) bool { // placeholder for subroundBlock.doBlockJob script - sr.ConsensusCoreHandler.ScheduledProcessor().StartScheduledProcessing(header, body, sr.GetRoundTimeStamp()) + if !header.IsHeaderV3() { + sr.ConsensusCoreHandler.ScheduledProcessor().StartScheduledProcessing(header, body, sr.GetRoundTimeStamp()) + } return true } +func (sr *subroundBlock) prepareBlockForExecution(header data.HeaderHandler, body data.BodyHandler) error { + if !header.IsHeaderV3() { + return nil + } + + // metachain does not need to select outgoing txs from txpool + if header.GetShardID() != core.MetachainShardId { + err := sr.BlockProcessor().OnProposedBlock(body, header, sr.GetData()) + if err != nil { + return err + } + } + return sr.ExecutionManager().AddPairForExecution(cache.HeaderBodyPair{ + Header: header, + Body: body, + HeaderHash: sr.GetData(), + }) +} + func (sr *subroundBlock) signBlockHeader(header data.HeaderHandler) ([]byte, error) { headerClone := header.ShallowClone() err := headerClone.SetLeaderSignature(nil) @@ -171,7 +220,7 @@ func printLogMessage(ctx context.Context, baseMessage string, err error) { log.Debug(baseMessage, "error", err.Error()) } -func (sr *subroundBlock) sendBlock(header data.HeaderHandler, body data.BodyHandler, _ string) bool { +func (sr *subroundBlock) sendBlock(header data.HeaderHandler, body data.BodyHandler, leader string) bool { marshalledBody, err := sr.Marshalizer().Marshal(body) if err != nil { log.Debug("sendBlock.Marshal: body", "error", err.Error()) @@ -185,10 +234,16 @@ func (sr *subroundBlock) sendBlock(header data.HeaderHandler, body data.BodyHand } sr.logBlockSize(marshalledBody, marshalledHeader) - if !sr.sendBlockBody(body, marshalledBody) || !sr.sendBlockHeader(header, marshalledHeader) { + headerHash := sr.Hasher().Compute(string(marshalledHeader)) + + if !sr.sendBlockBody(body, marshalledBody) || !sr.sendBlockHeader(header, headerHash) { return false } + sr.sendDirectSentTransactions(header, body, leader) + + sr.syncController.AddLeaderNonceAsOutOfRange(header.GetNonce(), string(headerHash)) + return true } @@ -208,15 +263,11 @@ func (sr *subroundBlock) createBlock(header data.HeaderHandler) (data.HeaderHand return sr.RoundHandler().RemainingTime(startTime, maxTime) > 0 } - finalHeader, blockBody, err := sr.BlockProcessor().CreateBlock( - header, - haveTimeInCurrentSubround, - ) - if err != nil { - return nil, nil, err + if header.IsHeaderV3() { + return sr.BlockProcessor().CreateBlockProposal(header, haveTimeInCurrentSubround) } - return finalHeader, blockBody, nil + return sr.BlockProcessor().CreateBlock(header, haveTimeInCurrentSubround) } // sendBlockBody method sends the proposed block body in the subround Block @@ -263,7 +314,7 @@ func (sr *subroundBlock) sendBlockBody( // sendBlockHeader method sends the proposed block header in the subround Block func (sr *subroundBlock) sendBlockHeader( headerHandler data.HeaderHandler, - marshalledHeader []byte, + headerHash []byte, ) bool { leader, errGetLeader := sr.GetLeader() if errGetLeader != nil { @@ -277,18 +328,49 @@ func (sr *subroundBlock) sendBlockHeader( return false } - headerHash := sr.Hasher().Compute(string(marshalledHeader)) - log.Debug("step 1: block header has been sent", "nonce", headerHandler.GetNonce(), - "hash", headerHash) + "hash", headerHash, + ) sr.SetData(headerHash) sr.SetHeader(headerHandler) + // log the header output for debugging purposes + headerOutput, err := common.PrettifyStruct(headerHandler) + if err == nil { + log.Debug("proposed header sent", "header", headerOutput) + } + return true } +func (sr *subroundBlock) sendDirectSentTransactions( + header data.HeaderHandler, + body data.BodyHandler, + leader string, +) { + if !header.IsHeaderV3() { + return + } + + mrsTxs := sr.BlockProcessor().ProposedDirectSentTransactionsToBroadcast(body) + if len(mrsTxs) == 0 { + return + } + + err := sr.BroadcastMessenger().BroadcastTransactions(mrsTxs, []byte(leader)) + if err != nil { + log.Warn("sendDirectSentTransactions.BroadcastTransactions", "error", err.Error()) + return + } + + log.Debug("proposed direct sent transactions sent") + for topic, txs := range mrsTxs { + log.Trace("on topic", "topic", topic, "txs", len(txs)) + } +} + func (sr *subroundBlock) getPrevHeaderAndHash() (data.HeaderHandler, []byte) { prevHeader := sr.Blockchain().GetCurrentBlockHeader() prevHeaderHash := sr.Blockchain().GetCurrentBlockHeaderHash() @@ -306,7 +388,7 @@ func (sr *subroundBlock) createHeader() (data.HeaderHandler, error) { prevRandSeed := prevHeader.GetRandSeed() round := uint64(sr.RoundHandler().Index()) - hdr, err := sr.BlockProcessor().CreateNewHeader(round, nonce) + hdr, err := sr.createHeaderBasedOnRound(round, nonce) if err != nil { return nil, err } @@ -331,7 +413,7 @@ func (sr *subroundBlock) createHeader() (data.HeaderHandler, error) { return nil, err } - err = hdr.SetTimeStamp(uint64(sr.RoundHandler().TimeStamp().Unix())) + err = hdr.SetTimeStamp(sr.GetUnixTimestampForHeader(hdr.GetEpoch())) if err != nil { return nil, err } @@ -354,8 +436,26 @@ func (sr *subroundBlock) createHeader() (data.HeaderHandler, error) { return hdr, nil } +func (sr *subroundBlock) createHeaderBasedOnRound(round uint64, nonce uint64) (data.HeaderHandler, error) { + isSupernovaActiveInRound := sr.EnableRoundsHandler().IsFlagEnabledInRound(common.SupernovaRoundFlag, round) + isSupernovaActiveInEpoch := sr.EnableEpochsHandler().IsFlagEnabled(common.SupernovaFlag) + if isSupernovaActiveInRound && !isSupernovaActiveInEpoch { + log.Warn("Supernova is active in round but not in epoch, creating header v2...") + } + + if isSupernovaActiveInRound && isSupernovaActiveInEpoch { + return sr.BlockProcessor().CreateNewHeaderProposal(round, nonce) + } + + return sr.BlockProcessor().CreateNewHeader(round, nonce) +} + // receivedBlockBody method is called when a block body is received through the block body channel func (sr *subroundBlock) receivedBlockBody(ctx context.Context, cnsDta *consensus.Message) bool { + if sr.IsSubroundFinished(sr.Current()) { + return false + } + node := string(cnsDta.PubKey) if !sr.IsNodeLeaderInCurrentRound(node) { // is NOT this node leader in current round? @@ -402,7 +502,8 @@ func (sr *subroundBlock) isHeaderForCurrentConsensus(header data.HeaderHandler) if header.GetShardID() != sr.ShardCoordinator().SelfId() { return false } - if header.GetRound() != uint64(sr.RoundHandler().Index()) { + if header.GetRound() != uint64(sr.GetRoundIndex()) { + sr.addOutOfRangeHeader(header) return false } @@ -421,6 +522,16 @@ func (sr *subroundBlock) isHeaderForCurrentConsensus(header data.HeaderHandler) return bytes.Equal(header.GetPrevRandSeed(), prevRandSeed) } +func (sr *subroundBlock) addOutOfRangeHeader(header data.HeaderHandler) { + hash, err := core.CalculateHash(sr.Marshalizer(), sr.Hasher(), header) + if err != nil { + log.Error("failed to calculate hash for out of range header", "err", err, "round", header.GetRound()) + return + } + + sr.syncController.AddOutOfRangeNonce(header.GetNonce(), string(hash)) +} + func (sr *subroundBlock) getLeaderForHeader(headerHandler data.HeaderHandler) ([]byte, error) { nc := sr.NodesCoordinator() @@ -449,11 +560,27 @@ func (sr *subroundBlock) getLeaderForHeader(headerHandler data.HeaderHandler) ([ func (sr *subroundBlock) receivedBlockHeader(headerHandler data.HeaderHandler) { if check.IfNil(headerHandler) { + log.Debug("subroundBlock.receivedBlockHeader - header is nil") return } log.Debug("subroundBlock.receivedBlockHeader", "nonce", headerHandler.GetNonce(), "round", headerHandler.GetRound()) if headerHandler.CheckFieldsForNil() != nil { + log.Debug("subroundBlock.receivedBlockHeader - header fields are nil") + return + } + + ok := sr.checkSupernovaHeader(headerHandler) + if !ok { + return + } + + if sr.IsSubroundFinished(sr.Current()) { + return + } + + if !sr.IsSubroundFinished(bls.SrStartRound) { + log.Debug("subroundBlock.receivedBlockHeader subroundStartRound not finished yet") return } @@ -525,6 +652,29 @@ func (sr *subroundBlock) receivedBlockHeader(headerHandler data.HeaderHandler) { spos.GetConsensusTopicID(sr.ShardCoordinator()), spos.LeaderPeerHonestyIncreaseFactor, ) + + // log the header output for debugging purposes + headerOutput, err := common.PrettifyStruct(headerHandler) + if err == nil { + log.Debug("proposed header received", "header", headerOutput) + } +} + +func (sr *subroundBlock) checkSupernovaHeader(headerHandler data.HeaderHandler) bool { + isSupernovaActive := common.IsAsyncExecutionEnabledForEpochAndRound(sr.EnableEpochsHandler(), sr.EnableRoundsHandler(), headerHandler.GetEpoch(), headerHandler.GetRound()) + isNotHeaderV3AfterSupernova := isSupernovaActive && !headerHandler.IsHeaderV3() + if isNotHeaderV3AfterSupernova { + log.Debug("checkSupernovaHeader received old header after supernova") + return false + } + + isHeaderV3BeforeSupernova := !isSupernovaActive && headerHandler.IsHeaderV3() + if isHeaderV3BeforeSupernova { + log.Debug("checkSupernovaHeader received header v3 before supernova") + return false + } + + return true } // CanProcessReceivedHeader method returns true if the received header can be processed and false otherwise @@ -533,7 +683,7 @@ func (sr *subroundBlock) CanProcessReceivedHeader(headerLeader string) bool { } func (sr *subroundBlock) shouldProcessBlock(headerLeader string) bool { - if sr.IsNodeSelf(headerLeader) { + if sr.IsNodeSelf(headerLeader) && commonConsensus.ShouldConsiderSelfKeyInConsensus(sr.NodeRedundancyHandler()) { return false } if sr.IsJobDone(headerLeader, sr.Current()) { @@ -559,6 +709,8 @@ func (sr *subroundBlock) processReceivedBlock( return false } + sr.updateConsensusMetricsProposedBlockReceivedOrSent() + sw := core.NewStopWatch() sw.Start("processReceivedBlock") @@ -611,11 +763,7 @@ func (sr *subroundBlock) processBlock( metricStatTime := time.Now() defer sr.computeSubroundProcessingMetric(metricStatTime, common.MetricProcessedProposedBlock) - err := sr.BlockProcessor().ProcessBlock( - sr.GetHeader(), - sr.GetBody(), - remainingTimeInCurrentRound, - ) + err := sr.processBlockBasedOnType(remainingTimeInCurrentRound) if roundIndex < sr.RoundHandler().Index() { log.Debug("canceled round, round index has been changed", @@ -640,11 +788,34 @@ func (sr *subroundBlock) processBlock( return false } - sr.ConsensusCoreHandler.ScheduledProcessor().StartScheduledProcessing(sr.GetHeader(), sr.GetBody(), sr.GetRoundTimeStamp()) + if !check.IfNil(sr.GetHeader()) && !sr.GetHeader().IsHeaderV3() { + sr.ConsensusCoreHandler.ScheduledProcessor().StartScheduledProcessing(sr.GetHeader(), sr.GetBody(), sr.GetRoundTimeStamp()) + } return true } +func (sr *subroundBlock) processBlockBasedOnType(haveTime func() time.Duration) error { + if sr.GetHeader().IsHeaderV3() { + err := sr.BlockProcessor().VerifyBlockProposal(sr.GetHeader(), sr.GetBody(), haveTime) + if err != nil { + return err + } + + return sr.ExecutionManager().AddPairForExecution(cache.HeaderBodyPair{ + Header: sr.GetHeader(), + Body: sr.GetBody(), + HeaderHash: sr.GetData(), + }) + } + + return sr.BlockProcessor().ProcessBlock( + sr.GetHeader(), + sr.GetBody(), + haveTime, + ) +} + func (sr *subroundBlock) printCancelRoundLogMessage(ctx context.Context, err error) { if common.IsContextDone(ctx) { log.Debug("canceled round as the context is closing") @@ -722,6 +893,14 @@ func (sr *subroundBlock) getRoundInLastCommittedBlock() int64 { return roundInLastCommittedBlock } +func (sr *subroundBlock) updateConsensusMetricsProposedBlockReceivedOrSent() { + + currentTime := sr.SyncTimer().CurrentTime() + bodyReceivedOrSentTime := currentTime.Sub(sr.RoundHandler().TimeStamp()).Nanoseconds() + + sr.worker.ConsensusMetrics().SetBlockReceivedOrSent(uint64(bodyReceivedOrSentTime)) +} + // IsInterfaceNil returns true if there is no value under the interface func (sr *subroundBlock) IsInterfaceNil() bool { return sr == nil diff --git a/consensus/spos/bls/v2/subroundBlock_test.go b/consensus/spos/bls/v2/subroundBlock_test.go index 12a82fd6529..ef0e5bddafc 100644 --- a/consensus/spos/bls/v2/subroundBlock_test.go +++ b/consensus/spos/bls/v2/subroundBlock_test.go @@ -19,12 +19,15 @@ import ( "github.com/multiversx/mx-chain-go/consensus/spos" "github.com/multiversx/mx-chain-go/consensus/spos/bls" v2 "github.com/multiversx/mx-chain-go/consensus/spos/bls/v2" + "github.com/multiversx/mx-chain-go/process/asyncExecution/cache" "github.com/multiversx/mx-chain-go/sharding/nodesCoordinator" "github.com/multiversx/mx-chain-go/testscommon" consensusMocks "github.com/multiversx/mx-chain-go/testscommon/consensus" "github.com/multiversx/mx-chain-go/testscommon/consensus/initializers" "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" + "github.com/multiversx/mx-chain-go/testscommon/round" "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" "github.com/multiversx/mx-chain-go/testscommon/statusHandler" ) @@ -37,8 +40,9 @@ func defaultSubroundForSRBlock(consensusState *spos.ConsensusState, ch chan bool bls.SrStartRound, bls.SrBlock, bls.SrSignature, - int64(5*roundTimeDuration/100), - int64(25*roundTimeDuration/100), + roundTimeDuration, + 0.05, + 0.25, "(BLOCK)", consensusState, ch, @@ -69,17 +73,30 @@ func defaultSubroundBlockFromSubround(sr *spos.Subround) (v2.SubroundBlock, erro srBlock, err := v2.NewSubroundBlock( sr, v2.ProcessingThresholdPercent, - &consensusMocks.SposWorkerMock{}, + &consensusMocks.SposWorkerMock{ + ConsensusMetricsCalled: func() spos.ConsensusMetricsHandler { + consensusMetrics, _ := spos.NewConsensusMetrics(sr.AppStatusHandler()) + return consensusMetrics + }, + }, + &consensusMocks.NtpSyncControllerMock{}, ) return srBlock, err } func defaultSubroundBlockWithoutErrorFromSubround(sr *spos.Subround) v2.SubroundBlock { + srBlock, _ := v2.NewSubroundBlock( sr, v2.ProcessingThresholdPercent, - &consensusMocks.SposWorkerMock{}, + &consensusMocks.SposWorkerMock{ + ConsensusMetricsCalled: func() spos.ConsensusMetricsHandler { + consensusMetrics, _ := spos.NewConsensusMetrics(sr.AppStatusHandler()) + return consensusMetrics + }, + }, + &consensusMocks.NtpSyncControllerMock{}, ) return srBlock @@ -161,6 +178,7 @@ func TestSubroundBlock_NewSubroundBlockNilSubroundShouldFail(t *testing.T) { nil, v2.ProcessingThresholdPercent, &consensusMocks.SposWorkerMock{}, + &consensusMocks.NtpSyncControllerMock{}, ) assert.Nil(t, srBlock) assert.Equal(t, spos.ErrNilSubround, err) @@ -315,11 +333,29 @@ func TestSubroundBlock_NewSubroundBlockNilWorkerShouldFail(t *testing.T) { sr, v2.ProcessingThresholdPercent, nil, + &consensusMocks.NtpSyncControllerMock{}, ) assert.Nil(t, srBlock) assert.Equal(t, spos.ErrNilWorker, err) } +func TestSubroundBlock_NewSubroundBlockNilRoundSyncController(t *testing.T) { + t.Parallel() + + container := consensusMocks.InitConsensusCore() + consensusState := initializers.InitConsensusState() + sr, _ := defaultSubroundForSRBlock(consensusState, make(chan bool, 1), container, &statusHandler.AppStatusHandlerStub{}) + + srBlock, err := v2.NewSubroundBlock( + sr, + v2.ProcessingThresholdPercent, + &consensusMocks.SposWorkerMock{}, + nil, + ) + require.Nil(t, srBlock) + require.Equal(t, v2.ErrNilRoundSyncController, err) +} + func TestSubroundBlock_NewSubroundBlockShouldWork(t *testing.T) { t.Parallel() container := consensusMocks.InitConsensusCore() @@ -567,7 +603,13 @@ func TestSubroundBlock_DoBlockJob(t *testing.T) { sr, _ := v2.NewSubroundBlock( baseSr, v2.ProcessingThresholdPercent, - &consensusMocks.SposWorkerMock{}, + &consensusMocks.SposWorkerMock{ + ConsensusMetricsCalled: func() spos.ConsensusMetricsHandler { + consensusMetrics, _ := spos.NewConsensusMetrics(baseSr.AppStatusHandler()) + return consensusMetrics + }, + }, + &consensusMocks.NtpSyncControllerMock{}, ) providedLeaderSignature := []byte("leader signature") @@ -612,7 +654,7 @@ func TestSubroundBlock_DoBlockJob(t *testing.T) { }, } container.SetBroadcastMessenger(bm) - container.SetRoundHandler(&consensusMocks.RoundHandlerMock{ + container.SetRoundHandler(&round.RoundHandlerMock{ RoundIndex: 1, }) container.SetEquivalentProofsPool(&dataRetriever.ProofsPoolMock{ @@ -623,16 +665,154 @@ func TestSubroundBlock_DoBlockJob(t *testing.T) { PubKeysBitmap: providedBitmap, }, nil }, + GetProofByNonceCalled: func(headerNonce uint64, shardID uint32) (data.HeaderProofHandler, error) { + return nil, fmt.Errorf("no proof for nonce") + }, }) r := sr.DoBlockJob() assert.True(t, r) assert.Equal(t, uint64(1), sr.GetHeader().GetNonce()) }) + t.Run("should work after supernova", func(t *testing.T) { + t.Parallel() + + providedSignature := []byte("provided signature") + providedBitmap := []byte("provided bitmap") + providedHash := []byte("provided hash") + providedMarshalledTx := []byte("provided marshalled tx") + providedHeader := &block.HeaderV2{ + Header: &block.Header{ + Signature: []byte("signature"), + PubKeysBitmap: []byte("bitmap"), + }, + } + + container := consensusMocks.InitConsensusCore() + chainHandler := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return providedHeader + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return providedHash + }, + } + container.SetBlockchain(chainHandler) + + consensusState := initializers.InitConsensusStateWithNodesCoordinator(container.NodesCoordinator()) + ch := make(chan bool, 1) + + baseSr, _ := defaultSubroundForSRBlock(consensusState, ch, container, &statusHandler.AppStatusHandlerStub{}) + sr, _ := v2.NewSubroundBlock( + baseSr, + v2.ProcessingThresholdPercent, + &consensusMocks.SposWorkerMock{ + ConsensusMetricsCalled: func() spos.ConsensusMetricsHandler { + consensusMetrics, _ := spos.NewConsensusMetrics(baseSr.AppStatusHandler()) + return consensusMetrics + }, + }, + &consensusMocks.NtpSyncControllerMock{}, + ) + + providedLeaderSignature := []byte("leader signature") + container.SetSigningHandler(&consensusMocks.SigningHandlerStub{ + CreateSignatureForPublicKeyCalled: func(message []byte, publicKeyBytes []byte) ([]byte, error) { + return providedLeaderSignature, nil + }, + VerifySignatureShareCalled: func(index uint16, sig []byte, msg []byte, epoch uint32) error { + assert.Fail(t, "should have not been called for leader") + return nil + }, + }) + container.SetRoundHandler(&testscommon.RoundHandlerMock{ + IndexCalled: func() int64 { + return 1 + }, + }) + enableRoundsHandler := &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return flag == common.SupernovaRoundFlag + }, + } + container.SetEnableRoundsHandler(enableRoundsHandler) + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return flag == common.SupernovaFlag + }, + } + container.SetEnableEpochsHandler(enableEpochsHandler) + + leader, err := sr.GetLeader() + require.Nil(t, err) + + sr.SetSelfPubKey(leader) + + wasBroadcastTransactionsCalled := false + bm := &consensusMocks.BroadcastMessengerMock{ + BroadcastConsensusMessageCalled: func(message *consensus.Message) error { + return nil + }, + BroadcastTransactionsCalled: func(m map[string][][]byte, bytes []byte) error { + wasBroadcastTransactionsCalled = true + return nil + }, + } + container.SetBroadcastMessenger(bm) + container.SetRoundHandler(&round.RoundHandlerMock{ + RoundIndex: 1, + }) + + container.SetEquivalentProofsPool(&dataRetriever.ProofsPoolMock{ + GetProofCalled: func(shardID uint32, headerHash []byte) (data.HeaderProofHandler, error) { + return &block.HeaderProof{ + HeaderHash: headerHash, + AggregatedSignature: providedSignature, + PubKeysBitmap: providedBitmap, + }, nil + }, + GetProofByNonceCalled: func(headerNonce uint64, shardID uint32) (data.HeaderProofHandler, error) { + return nil, fmt.Errorf("no proof for nonce") + }, + }) + wasCreateNewHeaderProposalCalled := false + wasCreateBlockProposalCalled := false + blockProcessor := &testscommon.BlockProcessorStub{ + CreateNewHeaderProposalCalled: func(round uint64, nonce uint64) (data.HeaderHandler, error) { + wasCreateNewHeaderProposalCalled = true + return &block.HeaderV3{}, nil + }, + CreateNewHeaderCalled: func(round uint64, nonce uint64) (data.HeaderHandler, error) { + require.Fail(t, "should have not been called") + return nil, nil + }, + CreateBlockProposalCalled: func(initialHdr data.HeaderHandler, haveTime func() bool) (data.HeaderHandler, data.BodyHandler, error) { + wasCreateBlockProposalCalled = true + return &block.HeaderV3{}, &block.Body{}, nil + }, + CreateBlockCalled: func(initialHdrData data.HeaderHandler, haveTime func() bool) (data.HeaderHandler, data.BodyHandler, error) { + require.Fail(t, "should have not been called") + return nil, nil, nil + }, + ProposedDirectSentTransactionsToBroadcastCalled: func(proposedBody data.BodyHandler) map[string][][]byte { + return map[string][][]byte{ + "topic": {providedMarshalledTx}, + } + }, + } + container.SetBlockProcessor(blockProcessor) + + r := sr.DoBlockJob() + require.True(t, r) + require.True(t, wasCreateNewHeaderProposalCalled) + assert.True(t, wasCreateBlockProposalCalled) + assert.True(t, wasBroadcastTransactionsCalled) + }) } func TestSubroundBlock_ReceivedBlock(t *testing.T) { t.Parallel() + container := consensusMocks.InitConsensusCore() sr := initSubroundBlock(nil, container, &statusHandler.AppStatusHandlerStub{}) blkBody := &block.Body{} @@ -655,7 +835,6 @@ func TestSubroundBlock_ReceivedBlock(t *testing.T) { currentPid, nil, ) - sr.SetBody(&block.Body{}) r := sr.ReceivedBlockBody(cnsMsg) assert.False(t, r) @@ -764,7 +943,7 @@ func TestSubroundBlock_ProcessReceivedBlockShouldReturnFalseWhenProcessBlockRetu return expectedErr } container.SetBlockProcessor(blockProcessorMock) - container.SetRoundHandler(&consensusMocks.RoundHandlerMock{RoundIndex: 1}) + container.SetRoundHandler(&round.RoundHandlerMock{RoundIndex: 1}) assert.False(t, sr.ProcessReceivedBlock(cnsMsg)) } @@ -902,7 +1081,7 @@ func TestSubroundBlock_HaveTimeInCurrentSubroundShouldReturnTrue(t *testing.T) { return time.Duration(remainingTime) > 0 } - roundHandlerMock := &consensusMocks.RoundHandlerMock{} + roundHandlerMock := &round.RoundHandlerMock{} roundHandlerMock.TimeDurationCalled = func() time.Duration { return 4000 * time.Millisecond } @@ -932,7 +1111,7 @@ func TestSubroundBlock_HaveTimeInCurrentSuboundShouldReturnFalse(t *testing.T) { return time.Duration(remainingTime) > 0 } - roundHandlerMock := &consensusMocks.RoundHandlerMock{} + roundHandlerMock := &round.RoundHandlerMock{} roundHandlerMock.TimeDurationCalled = func() time.Duration { return 4000 * time.Millisecond } @@ -1223,11 +1402,54 @@ func TestSubroundBlock_ReceivedBlockHeader(t *testing.T) { } container.SetBlockchain(blockchain) + sr.SetData(nil) + // nil header sr.ReceivedBlockHeader(nil) + require.Nil(t, sr.GetData()) + + // start round is not finished + sr.ReceivedBlockHeader(&testscommon.HeaderHandlerStub{}) + require.Nil(t, sr.GetData()) + sr.SetStatus(bls.SrStartRound, spos.SsFinished) + + // old header after supernova + container.SetEnableEpochsHandler(&enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return true + }, + }) + container.SetEnableRoundsHandler(&testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledCalled: func(flag common.EnableRoundFlag) bool { + return true + }, + }) + sr.ReceivedBlockHeader(&testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { + return false + }, + }) + require.Nil(t, sr.GetData()) + + // header v3 before supernova + container.SetEnableRoundsHandler(&testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledCalled: func(flag common.EnableRoundFlag) bool { + return false + }, + }) + sr.ReceivedBlockHeader(&testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { + return true + }, + }) + require.Nil(t, sr.GetData()) + + container.SetEnableEpochsHandler(&enableEpochsHandlerMock.EnableEpochsHandlerStub{}) + container.SetEnableRoundsHandler(&testscommon.EnableRoundsHandlerStub{}) // header not for current consensus sr.ReceivedBlockHeader(&testscommon.HeaderHandlerStub{}) + require.Nil(t, sr.GetData()) // nil fields on header sr.ReceivedBlockHeader(&testscommon.HeaderHandlerStub{ @@ -1235,9 +1457,11 @@ func TestSubroundBlock_ReceivedBlockHeader(t *testing.T) { return expectedErr }, }) + require.Nil(t, sr.GetData()) // header not for current consensus sr.ReceivedBlockHeader(&testscommon.HeaderHandlerStub{}) + require.Nil(t, sr.GetData()) headerForCurrentConsensus := &testscommon.HeaderHandlerStub{ GetShardIDCalled: func() uint32 { @@ -1259,6 +1483,7 @@ func TestSubroundBlock_ReceivedBlockHeader(t *testing.T) { defaultLeader := sr.Leader() sr.SetLeader(sr.SelfPubKey()) sr.ReceivedBlockHeader(headerForCurrentConsensus) + require.Nil(t, sr.GetData()) sr.SetLeader(defaultLeader) // consensus data already set @@ -1269,21 +1494,25 @@ func TestSubroundBlock_ReceivedBlockHeader(t *testing.T) { // header leader is not the current one sr.SetLeader("X") sr.ReceivedBlockHeader(headerForCurrentConsensus) + require.Nil(t, sr.GetData()) sr.SetLeader(defaultLeader) // header already received sr.SetHeader(&testscommon.HeaderHandlerStub{}) sr.ReceivedBlockHeader(headerForCurrentConsensus) + require.Nil(t, sr.GetData()) sr.SetHeader(nil) // self job already done _ = sr.SetJobDone(sr.SelfPubKey(), sr.Current(), true) sr.ReceivedBlockHeader(headerForCurrentConsensus) + require.Nil(t, sr.GetData()) _ = sr.SetJobDone(sr.SelfPubKey(), sr.Current(), false) // subround already finished sr.SetStatus(sr.Current(), spos.SsFinished) sr.ReceivedBlockHeader(headerForCurrentConsensus) + require.Nil(t, sr.GetData()) sr.SetStatus(sr.Current(), spos.SsNotFinished) // marshal error @@ -1293,10 +1522,115 @@ func TestSubroundBlock_ReceivedBlockHeader(t *testing.T) { }, }) sr.ReceivedBlockHeader(headerForCurrentConsensus) + require.Nil(t, sr.GetData()) container.SetMarshalizer(&testscommon.MarshallerStub{}) // should work sr.ReceivedBlockHeader(headerForCurrentConsensus) + _ = sr.SetJobDone(sr.SelfPubKey(), sr.Current(), false) + sr.SetStatus(sr.Current(), spos.SsNotFinished) + sr.SetHeader(nil) + sr.SetData(nil) + + // should work after supernova + wasVerifyBlockProposalCalled := false + container.SetBlockProcessor(&testscommon.BlockProcessorStub{ + VerifyBlockProposalCalled: func(headerHandler data.HeaderHandler, bodyHandler data.BodyHandler, haveTime func() time.Duration) error { + wasVerifyBlockProposalCalled = true + return nil + }, + }) + container.SetEnableEpochsHandler(&enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + }) + container.SetEnableRoundsHandler(&testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return true + }, + }) + headerForCurrentConsensus.IsHeaderV3Called = func() bool { + return true + } + sr.SetBody(&block.Body{}) + sr.ReceivedBlockHeader(headerForCurrentConsensus) + + require.True(t, wasVerifyBlockProposalCalled) +} + +func TestSubroundBlock_UpdateConsensusMetrics(t *testing.T) { + t.Parallel() + + now := time.Now() + container := consensusMocks.InitConsensusCore() + syncTimerMock := &consensusMocks.SyncTimerMock{ + CurrentTimeCalled: func() time.Time { + return now + }, + } + count := 0 + roundHandlerMock := testscommon.RoundHandlerMock{ + TimeStampCalled: func() time.Time { + defer func() { count++ }() + if count == 0 { + return now.Add(-500 * time.Nanosecond) + } + return now.Add(-200 * time.Nanosecond) + }, + } + container.SetSyncTimer(syncTimerMock) + container.SetRoundHandler(&roundHandlerMock) + + appStatusHandler := statusHandler.NewAppStatusHandlerMock() + + providedHeadr := &block.HeaderV2{ + Header: &block.Header{ + Signature: []byte("signature"), + PubKeysBitmap: []byte("bitmap"), + }, + } + providedHash := []byte("provided hash") + chainHandler := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return providedHeadr + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return providedHash + }, + } + container.SetBlockchain(chainHandler) + + consensusState := initializers.InitConsensusStateWithNodesCoordinator(container.NodesCoordinator()) + ch := make(chan bool, 1) + sr, _ := defaultSubroundForSRBlock(consensusState, ch, container, appStatusHandler) + + consensusMetrics, _ := spos.NewConsensusMetrics(sr.AppStatusHandler()) + srBlock, _ := v2.NewSubroundBlock( + sr, + v2.ProcessingThresholdPercent, + &consensusMocks.SposWorkerMock{ + ConsensusMetricsCalled: func() spos.ConsensusMetricsHandler { + return consensusMetrics + }, + }, + &consensusMocks.NtpSyncControllerMock{}, + ) + + consensusMetrics.ResetInstanceValues() + consensusMetrics.ResetAverages() + + srBlock.UpdateConsensusMetricsProposedBlockReceivedOrSent() + // instance value = 500; avg = 500 + assert.Equal(t, uint64(500), appStatusHandler.GetUint64(common.MetricReceivedOrSentProposedBlock), "MetricReceivedProof should be set") + assert.Equal(t, uint64(500), appStatusHandler.GetUint64(common.MetricAvgReceivedOrSentProposedBlock), "MetricAvgProofsReceived should be set") + + consensusMetrics.ResetInstanceValues() + + srBlock.UpdateConsensusMetricsProposedBlockReceivedOrSent() + // instance value = 200; avg = 500 + 200 / 2 = 350 + assert.Equal(t, uint64(200), appStatusHandler.GetUint64(common.MetricReceivedOrSentProposedBlock), "MetricReceivedProof should be set") + assert.Equal(t, uint64(350), appStatusHandler.GetUint64(common.MetricAvgReceivedOrSentProposedBlock), "MetricAvgProofsReceived should be set") } func TestSubroundBlock_GetLeaderForHeader(t *testing.T) { @@ -1347,13 +1681,158 @@ func TestSubroundBlock_GetLeaderForHeader(t *testing.T) { }) } +func TestSubroundBlock_prepareBlockForExecution(t *testing.T) { + t.Parallel() + + t.Run("non-HeaderV3 should return nil without calling any methods", func(t *testing.T) { + t.Parallel() + + container := consensusMocks.InitConsensusCore() + onProposedBlockCalled := false + addPairCalled := false + + container.SetBlockProcessor(&testscommon.BlockProcessorStub{ + OnProposedBlockCalled: func(_ data.BodyHandler, _ data.HeaderHandler, _ []byte) error { + onProposedBlockCalled = true + return nil + }, + }) + container.SetExecutionManager(&processMocks.ExecutionManagerMock{ + AddPairForExecutionCalled: func(_ cache.HeaderBodyPair) error { + addPairCalled = true + return nil + }, + }) + + sr := initSubroundBlock(nil, container, &statusHandler.AppStatusHandlerStub{}) + + err := sr.PrepareBlockForExecution(&testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { return false }, + }, &block.Body{}) + + require.Nil(t, err) + require.False(t, onProposedBlockCalled) + require.False(t, addPairCalled) + }) + + t.Run("HeaderV3 on shard should call OnProposedBlock before AddPairForExecution", func(t *testing.T) { + t.Parallel() + + container := consensusMocks.InitConsensusCore() + callOrder := make([]string, 0, 2) + + container.SetBlockProcessor(&testscommon.BlockProcessorStub{ + OnProposedBlockCalled: func(_ data.BodyHandler, _ data.HeaderHandler, _ []byte) error { + callOrder = append(callOrder, "OnProposedBlock") + return nil + }, + }) + container.SetExecutionManager(&processMocks.ExecutionManagerMock{ + AddPairForExecutionCalled: func(_ cache.HeaderBodyPair) error { + callOrder = append(callOrder, "AddPairForExecution") + return nil + }, + }) + + sr := initSubroundBlock(nil, container, &statusHandler.AppStatusHandlerStub{}) + + err := sr.PrepareBlockForExecution(&testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { return true }, + GetShardIDCalled: func() uint32 { return 0 }, + }, &block.Body{}) + + require.Nil(t, err) + require.Equal(t, []string{"OnProposedBlock", "AddPairForExecution"}, callOrder) + }) + + t.Run("HeaderV3 on metachain should only call AddPairForExecution", func(t *testing.T) { + t.Parallel() + + container := consensusMocks.InitConsensusCore() + onProposedBlockCalled := false + addPairCalled := false + + container.SetBlockProcessor(&testscommon.BlockProcessorStub{ + OnProposedBlockCalled: func(_ data.BodyHandler, _ data.HeaderHandler, _ []byte) error { + onProposedBlockCalled = true + return nil + }, + }) + container.SetExecutionManager(&processMocks.ExecutionManagerMock{ + AddPairForExecutionCalled: func(_ cache.HeaderBodyPair) error { + addPairCalled = true + return nil + }, + }) + + sr := initSubroundBlock(nil, container, &statusHandler.AppStatusHandlerStub{}) + + err := sr.PrepareBlockForExecution(&testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { return true }, + GetShardIDCalled: func() uint32 { return core.MetachainShardId }, + }, &block.Body{}) + + require.Nil(t, err) + require.False(t, onProposedBlockCalled) + require.True(t, addPairCalled) + }) + + t.Run("OnProposedBlock error should return error and not call AddPairForExecution", func(t *testing.T) { + t.Parallel() + + container := consensusMocks.InitConsensusCore() + addPairCalled := false + + container.SetBlockProcessor(&testscommon.BlockProcessorStub{ + OnProposedBlockCalled: func(_ data.BodyHandler, _ data.HeaderHandler, _ []byte) error { + return expectedErr + }, + }) + container.SetExecutionManager(&processMocks.ExecutionManagerMock{ + AddPairForExecutionCalled: func(_ cache.HeaderBodyPair) error { + addPairCalled = true + return nil + }, + }) + + sr := initSubroundBlock(nil, container, &statusHandler.AppStatusHandlerStub{}) + + err := sr.PrepareBlockForExecution(&testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { return true }, + GetShardIDCalled: func() uint32 { return 0 }, + }, &block.Body{}) + + require.Equal(t, expectedErr, err) + require.False(t, addPairCalled) + }) + + t.Run("AddPairForExecution error should return error", func(t *testing.T) { + t.Parallel() + + container := consensusMocks.InitConsensusCore() + container.SetExecutionManager(&processMocks.ExecutionManagerMock{ + AddPairForExecutionCalled: func(_ cache.HeaderBodyPair) error { + return expectedErr + }, + }) + + sr := initSubroundBlock(nil, container, &statusHandler.AppStatusHandlerStub{}) + + err := sr.PrepareBlockForExecution(&testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { return true }, + GetShardIDCalled: func() uint32 { return core.MetachainShardId }, + }, &block.Body{}) + + require.Equal(t, expectedErr, err) + }) +} + func TestSubroundBlock_IsInterfaceNil(t *testing.T) { t.Parallel() container := consensusMocks.InitConsensusCore() sr := initSubroundBlock(nil, container, nil) require.True(t, sr.IsInterfaceNil()) - sr = initSubroundBlock(nil, container, &statusHandler.AppStatusHandlerStub{}) require.False(t, sr.IsInterfaceNil()) } diff --git a/consensus/spos/bls/v2/subroundEndRound.go b/consensus/spos/bls/v2/subroundEndRound.go index 66c585d1b9b..2384bc15944 100644 --- a/consensus/spos/bls/v2/subroundEndRound.go +++ b/consensus/spos/bls/v2/subroundEndRound.go @@ -15,6 +15,8 @@ import ( "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/display" + commonConsensus "github.com/multiversx/mx-chain-go/common/consensus" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/consensus" "github.com/multiversx/mx-chain-go/consensus/spos" @@ -32,7 +34,6 @@ type subroundEndRound struct { mutProcessingEndRound sync.Mutex sentSignatureTracker spos.SentSignaturesTracker worker spos.WorkerHandler - signatureThrottler core.Throttler } // NewSubroundEndRound creates a subroundEndRound object @@ -42,7 +43,6 @@ func NewSubroundEndRound( appStatusHandler core.AppStatusHandler, sentSignatureTracker spos.SentSignaturesTracker, worker spos.WorkerHandler, - signatureThrottler core.Throttler, ) (*subroundEndRound, error) { err := checkNewSubroundEndRoundParams(baseSubround) if err != nil { @@ -57,9 +57,6 @@ func NewSubroundEndRound( if check.IfNil(worker) { return nil, spos.ErrNilWorker } - if check.IfNil(signatureThrottler) { - return nil, spos.ErrNilThrottler - } srEndRound := subroundEndRound{ Subround: baseSubround, @@ -68,7 +65,6 @@ func NewSubroundEndRound( mutProcessingEndRound: sync.Mutex{}, sentSignatureTracker: sentSignatureTracker, worker: worker, - signatureThrottler: signatureThrottler, } srEndRound.Job = srEndRound.doEndRoundJob srEndRound.Check = srEndRound.doEndRoundConsensusCheck @@ -118,7 +114,7 @@ func (sr *subroundEndRound) receivedProof(proof consensus.ProofHandler) { log.Debug("step 3: block header final info has been received", "PubKeysBitmap", proof.GetPubKeysBitmap(), "AggregateSignature", proof.GetAggregatedSignature(), - "HederHash", proof.GetHeaderHash()) + "HeaderHash", proof.GetHeaderHash()) sr.doEndRoundJobByNode() } @@ -147,6 +143,10 @@ func (sr *subroundEndRound) receivedInvalidSignersInfo(_ context.Context, cnsDta return false } + if !sr.IsNodeInConsensusGroup(string(cnsDta.PubKey)) { + return false + } + if len(cnsDta.InvalidSigners) == 0 { return false } @@ -181,6 +181,10 @@ func (sr *subroundEndRound) verifyInvalidSigners(invalidSigners []byte) ([]strin return nil, err } + if len(messages) > sr.ConsensusGroupSize() { + return nil, ErrTooManyInvalidSigners + } + pubKeys := make([]string, 0, len(messages)) for _, msg := range messages { pubKey, errVerify := sr.verifyInvalidSigner(msg) @@ -220,7 +224,7 @@ func (sr *subroundEndRound) verifyInvalidSigner(msg p2p.MessageP2P) (string, err return string(cnsMsg.PubKey), nil } - return "", nil + return "", ErrValidSignatureFromInvalidSigner } func (sr *subroundEndRound) applyBlacklistOnNode(peer core.PeerID) { @@ -257,30 +261,35 @@ func (sr *subroundEndRound) commitBlock() error { } func (sr *subroundEndRound) doEndRoundJobByNode() bool { - if sr.shouldSendProof() { + for sr.shouldSendProof() { if !sr.waitForSignalSync() { return false } + if sr.HasProofForCompetingBlock() { + log.Debug("doEndRoundJobByNode: competing block proof detected, aborting end round job") + return false + } + proofSent, err := sr.sendProof() - shouldWaitForMoreSignatures := errors.Is(err, spos.ErrInvalidNumSigShares) - // if not enough valid signatures were detected, wait a bit more - // either more signatures will be received, either proof from another participant - if shouldWaitForMoreSignatures { - return sr.doEndRoundJobByNode() + // Not enough valid signatures: wait for more or for a proof from another participant + if errors.Is(err, spos.ErrInvalidNumSigShares) { + continue } if proofSent { - err := sr.prepareBroadcastBlockData() - log.LogIfError(err) + errBroadcastBlockData := sr.prepareBroadcastBlockData() + log.LogIfError(errBroadcastBlockData) } + + break } return sr.finalizeConfirmedBlock() } func (sr *subroundEndRound) prepareBroadcastBlockData() error { - miniBlocks, transactions, err := sr.BlockProcessor().MarshalizedDataToBroadcast(sr.GetHeader(), sr.GetBody()) + miniBlocks, transactions, err := sr.BlockProcessor().MarshalizedDataToBroadcast(sr.GetData(), sr.GetHeader(), sr.GetBody()) if err != nil { return err } @@ -298,7 +307,8 @@ func (sr *subroundEndRound) waitForProof() bool { return true } - ctx, cancel := context.WithTimeout(context.Background(), sr.RoundHandler().TimeDuration()) + timeLeft := sr.RoundHandler().RemainingTime(sr.RoundHandler().TimeStamp(), sr.RoundHandler().TimeDuration()) + ctx, cancel := context.WithTimeout(context.Background(), timeLeft) defer cancel() for { @@ -307,6 +317,10 @@ func (sr *subroundEndRound) waitForProof() bool { if sr.EquivalentProofsPool().HasProof(shardID, headerHash) { return true } + if sr.HasProofForCompetingBlock() { + log.Debug("waitForProof: competing block proof detected, aborting wait") + return false + } case <-ctx.Done(): return false } @@ -318,10 +332,15 @@ func (sr *subroundEndRound) finalizeConfirmedBlock() bool { return false } - ok := sr.ScheduledProcessor().IsProcessedOKWithTimeout() - // placeholder for subroundEndRound.doEndRoundJobByLeader script - if !ok { - return false + sr.updateConsensusMetricsProof() + sr.updateNonceDeltaMetrics() + + if !sr.GetHeader().IsHeaderV3() { + ok := sr.ScheduledProcessor().IsProcessedOKWithTimeout() + // placeholder for subroundEndRound.doEndRoundJobByLeader script + if !ok { + return false + } } err := sr.commitBlock() @@ -331,6 +350,10 @@ func (sr *subroundEndRound) finalizeConfirmedBlock() bool { sr.SetStatus(sr.Current(), spos.SsFinished) + // Trigger AOT selection for next round after block commits + // This prepares transactions for the next block while state is consistent (post-OnExecuted) + sr.triggerAOTSelection() + sr.worker.DisplayStatistics() log.Debug("step 3: Body and Header have been committed") @@ -364,12 +387,9 @@ func (sr *subroundEndRound) sendProof() (bool, error) { return false, err } - roundHandler := sr.RoundHandler() - if roundHandler.RemainingTime(roundHandler.TimeStamp(), roundHandler.TimeDuration()) < 0 { - log.Debug("sendProof: time is out -> cancel broadcasting final info and header", - "round time stamp", roundHandler.TimeStamp(), - "current time", time.Now()) - return false, ErrTimeOut + // Re-check grace period after aggregation which may have been slow under CPU contention + if !sr.shouldSendProof() { + return false, nil } // broadcast header proof @@ -383,12 +403,28 @@ func (sr *subroundEndRound) sendProof() (bool, error) { } func (sr *subroundEndRound) shouldSendProof() bool { + // Allow proof sending with a grace period into the next round so the proof can + // reach nodes delaying before signing a competing block (equivocation prevention). + consensusRoundStart := sr.GetRoundTimeStamp() + roundDuration := sr.RoundHandler().TimeDuration() + graceDuration := time.Duration(float64(roundDuration) * competingProofSendDelay) + maxDuration := roundDuration + graceDuration + remaining := sr.RoundHandler().RemainingTime(consensusRoundStart, maxDuration) + if remaining <= 0 { + log.Debug("shouldSendProof: grace period expired, not sending proof", + "consensus round", sr.GetRoundIndex(), + "current round", sr.RoundHandler().Index()) + return false + } + if sr.EquivalentProofsPool().HasProof(sr.ShardCoordinator().SelfId(), sr.GetData()) { log.Debug("shouldSendProof: equivalent message already processed") return false } - return sr.IsSelfInConsensusGroup() + shouldSingleKeySendProof := sr.IsNodeInConsensusGroup(sr.SelfPubKey()) && commonConsensus.ShouldConsiderSelfKeyInConsensus(sr.NodeRedundancyHandler()) + shouldMultiKeySendProof := sr.IsMultiKeyInConsensusGroup() + return shouldSingleKeySendProof || shouldMultiKeySendProof } func (sr *subroundEndRound) aggregateSigsAndHandleInvalidSigners(bitmap []byte, sender string) ([]byte, []byte, error) { @@ -419,22 +455,6 @@ func (sr *subroundEndRound) aggregateSigsAndHandleInvalidSigners(bitmap []byte, return bitmap, sig, nil } -func (sr *subroundEndRound) checkGoRoutinesThrottler(ctx context.Context) error { - for { - if sr.signatureThrottler.CanProcess() { - break - } - - select { - case <-time.After(time.Millisecond): - continue - case <-ctx.Done(): - return spos.ErrTimeIsOut - } - } - return nil -} - // verifySignature implements parallel signature verification func (sr *subroundEndRound) verifySignature(i int, pk string, sigShare []byte) error { err := sr.SigningHandler().VerifySignatureShare(uint16(i), sigShare, sr.GetData(), sr.GetHeader().GetEpoch()) @@ -460,54 +480,99 @@ func (sr *subroundEndRound) verifySignature(i int, pk string, sigShare []byte) e return nil } +const maxParallelVerifications = 30 + func (sr *subroundEndRound) verifyNodesOnAggSigFail(ctx context.Context) ([]string, error) { - wg := &sync.WaitGroup{} - mutex := &sync.Mutex{} - invalidPubKeys := make([]string, 0) pubKeys := sr.ConsensusGroup() + invalidPubKeys := make([]string, 0) + verifiedValidPubKeys := make(map[string]struct{}) + var mu sync.Mutex if check.IfNil(sr.GetHeader()) { return nil, spos.ErrNilHeader } - for i, pk := range pubKeys { - isJobDone, err := sr.JobDone(pk, bls.SrSignature) - if err != nil || !isJobDone { - continue - } - - sigShare, err := sr.SigningHandler().SignatureShare(uint16(i)) - if err != nil { - return nil, err - } + // Create worker pool + workChan := make(chan struct { + index int + pubKey string + }, len(pubKeys)) - err = sr.checkGoRoutinesThrottler(ctx) - if err != nil { - return nil, err - } + var wg sync.WaitGroup - sr.signatureThrottler.StartProcessing() + // Start workers (bounded parallelism) + numWorkers := maxParallelVerifications + if len(pubKeys) < numWorkers { + numWorkers = len(pubKeys) + } + for i := 0; i < numWorkers; i++ { wg.Add(1) - - go func(i int, pk string, sigShare []byte) { - defer func() { - sr.signatureThrottler.EndProcessing() - wg.Done() - }() - errSigVerification := sr.verifySignature(i, pk, sigShare) - if errSigVerification != nil { - mutex.Lock() - invalidPubKeys = append(invalidPubKeys, pk) - mutex.Unlock() + go func() { + defer wg.Done() + for work := range workChan { + select { + case <-ctx.Done(): + return + default: + isJobDone, err := sr.JobDone(work.pubKey, bls.SrSignature) + if err != nil || !isJobDone { + continue + } + + sigShare, err := sr.SigningHandler().SignatureShare(uint16(work.index)) + if err != nil { + log.Debug("verifyNodesOnAggSigFail: failed to get signature share", + "public key", work.pubKey, + "error", err) + continue + } + + err = sr.verifySignature(work.index, work.pubKey, sigShare) + if err != nil { + mu.Lock() + invalidPubKeys = append(invalidPubKeys, work.pubKey) + mu.Unlock() + } else { + mu.Lock() + verifiedValidPubKeys[work.pubKey] = struct{}{} + mu.Unlock() + } + } } - }(i, pk, sigShare) + }() + } + + // Send work + for i, pk := range pubKeys { + workChan <- struct { + index int + pubKey string + }{i, pk} } + close(workChan) + wg.Wait() + sr.clearJobDoneForUnVerifiedSignatures(pubKeys, verifiedValidPubKeys) return invalidPubKeys, nil } +func (sr *subroundEndRound) clearJobDoneForUnVerifiedSignatures(pubKeys []string, verifiedValidPubKeys map[string]struct{}) { + for _, pk := range pubKeys { + isJobDone, err := sr.JobDone(pk, bls.SrSignature) + if err != nil || !isJobDone { + continue + } + + if _, wasVerified := verifiedValidPubKeys[pk]; !wasVerified { + log.Debug("verifyNodesOnAggSigFail: excluding unverified validator from aggregation", + "public key", pk) + _ = sr.SetJobDone(pk, bls.SrSignature, false) + } + } +} + func (sr *subroundEndRound) getFullMessagesForInvalidSigners(invalidPubKeys []string) ([]byte, error) { p2pMessages := make([]p2p.MessageP2P, 0) @@ -562,7 +627,7 @@ func (sr *subroundEndRound) handleInvalidSignersOnAggSigFail(sender string) ([]b } func (sr *subroundEndRound) computeAggSigOnValidNodes() ([]byte, []byte, error) { - threshold := sr.Threshold(bls.SrSignature) + threshold := sr.getThreshold() numValidSigShares := sr.ComputeSize(bls.SrSignature) if check.IfNil(sr.GetHeader()) { @@ -620,6 +685,12 @@ func (sr *subroundEndRound) createAndBroadcastProof( IsStartOfEpoch: sr.GetHeader().IsStartOfEpochBlock(), } + added := sr.EquivalentProofsPool().AddProof(headerProof) + if !added { + log.Debug("createAndBroadcastProof failed to add proof from self") + return ErrProofAlreadyPropagated + } + err := sr.BroadcastMessenger().BroadcastEquivalentProof(headerProof, []byte(sender)) if err != nil { return err @@ -667,7 +738,7 @@ func (sr *subroundEndRound) createAndBroadcastInvalidSigners( invalidSignersPubKeys []string, sender string, ) { - if !sr.ShouldConsiderSelfKeyInConsensus() && !sr.IsMultiKeyInConsensusGroup() { + if !commonConsensus.ShouldConsiderSelfKeyInConsensus(sr.NodeRedundancyHandler()) && !sr.IsMultiKeyInConsensusGroup() { return } @@ -705,8 +776,11 @@ func (sr *subroundEndRound) updateMetricsForLeader() { } sr.appStatusHandler.Increment(common.MetricCountAcceptedBlocks) + + roundTimeStamp := sr.RoundHandler().TimeStamp() + timeSinceRound := time.Since(roundTimeStamp) sr.appStatusHandler.SetStringValue(common.MetricConsensusRoundState, - fmt.Sprintf("valid block produced in %f sec", time.Since(sr.RoundHandler().TimeStamp()).Seconds())) + fmt.Sprintf("valid block produced in %s", timeSinceRound.String())) } // doEndRoundConsensusCheck method checks if the consensus is achieved @@ -778,7 +852,7 @@ func (sr *subroundEndRound) waitForSignalSync() bool { return true } - if sr.checkReceivedSignatures() { + if sr.checkReceivedSignaturesOrProof() { return true } @@ -787,9 +861,11 @@ func (sr *subroundEndRound) waitForSignalSync() bool { go sr.waitSignatures(ctx) timerBetweenStatusChecks := time.NewTimer(timeBetweenSignaturesChecks) + defer timerBetweenStatusChecks.Stop() remainingSRTime := sr.remainingTime() timeout := time.NewTimer(remainingSRTime) + defer timeout.Stop() for { select { case <-timerBetweenStatusChecks.C: @@ -798,7 +874,7 @@ func (sr *subroundEndRound) waitForSignalSync() bool { return true } - if sr.checkReceivedSignatures() { + if sr.checkReceivedSignaturesOrProof() { return true } timerBetweenStatusChecks.Reset(timeBetweenSignaturesChecks) @@ -816,11 +892,17 @@ func (sr *subroundEndRound) waitSignatures(ctx context.Context) { } sr.SetWaitingAllSignaturesTimeOut(true) + timer := time.NewTimer(remainingTime) + defer timer.Stop() + select { - case <-time.After(remainingTime): + case <-timer.C: case <-ctx.Done(): } - sr.ConsensusChannel() <- true + select { + case sr.ConsensusChannel() <- true: + default: + } } // maximum time to wait for signatures @@ -896,7 +978,7 @@ func (sr *subroundEndRound) receivedSignature(_ context.Context, cnsDta *consens return true } -func (sr *subroundEndRound) checkReceivedSignatures() bool { +func (sr *subroundEndRound) getThreshold() int { isTransitionBlock := common.IsEpochChangeBlockForFlagActivation(sr.GetHeader(), sr.EnableEpochsHandler(), common.AndromedaFlag) threshold := sr.Threshold(bls.SrSignature) @@ -910,16 +992,23 @@ func (sr *subroundEndRound) checkReceivedSignatures() bool { threshold = core.GetPBFTFallbackThreshold(sr.ConsensusGroupSize()) } - log.Warn("subroundEndRound.checkReceivedSignatures: fallback validation has been applied", + log.Warn("subroundEndRound.checkReceivedSignaturesOrProof: fallback validation has been applied", "minimum number of signatures required", threshold, "actual number of signatures received", sr.getNumOfSignaturesCollected(), ) } + return threshold +} + +func (sr *subroundEndRound) checkReceivedSignaturesOrProof() bool { + threshold := sr.getThreshold() + areSignaturesCollected, numSigs := sr.areSignaturesCollected(threshold) areAllSignaturesCollected := numSigs == sr.ConsensusGroupSize() + isProofReceived := sr.EquivalentProofsPool().HasProof(sr.ShardCoordinator().SelfId(), sr.GetData()) - isSignatureCollectionDone := areAllSignaturesCollected || (areSignaturesCollected && sr.GetWaitingAllSignaturesTimeOut()) + isSignatureCollectionDone := isProofReceived || areAllSignaturesCollected || (areSignaturesCollected && sr.GetWaitingAllSignaturesTimeOut()) isSelfJobDone := sr.IsSelfJobDone(bls.SrSignature) @@ -928,6 +1017,7 @@ func (sr *subroundEndRound) checkReceivedSignatures() bool { log.Debug("step 2: signatures collection done", "subround", sr.Name(), "signatures received", numSigs, + "is proof received", isProofReceived, "total signatures", len(sr.ConsensusGroup()), "threshold", threshold) @@ -960,6 +1050,26 @@ func (sr *subroundEndRound) getNumOfSignaturesCollected() int { return n } +func (sr *subroundEndRound) updateNonceDeltaMetrics() { + if !sr.GetHeader().IsHeaderV3() { + return + } + + lastExecutionResultHeaderNonce := common.GetLastExecutionResultNonce(sr.GetHeader()) + + sr.appStatusHandler.SetUInt64Value(common.MetricDeltaHeaderNonceLastExecutionResultNonce, + sr.GetHeader().GetNonce()-lastExecutionResultHeaderNonce) +} + +// updateConsensusMetricsProof sets the consensus metrics +func (sr *subroundEndRound) updateConsensusMetricsProof() { + + currentTime := sr.SyncTimer().CurrentTime() + metricsTime := currentTime.Sub(sr.RoundHandler().TimeStamp()).Nanoseconds() + + sr.worker.ConsensusMetrics().SetProofReceived(uint64(metricsTime)) +} + // areSignaturesCollected method checks if the signatures received from the nodes, belonging to the current // jobDone group, are more than the necessary given threshold func (sr *subroundEndRound) areSignaturesCollected(threshold int) (bool, int) { @@ -967,6 +1077,24 @@ func (sr *subroundEndRound) areSignaturesCollected(threshold int) (bool, int) { return n >= threshold, n } +// triggerAOTSelection triggers ahead-of-time transaction selection for the next block +// This is called after a block commits to prepare transactions for when this node becomes leader +func (sr *subroundEndRound) triggerAOTSelection() { + aotSelector := sr.AOTSelector() + if check.IfNil(aotSelector) { + return + } + + committedHeader := sr.GetHeader() + if check.IfNil(committedHeader) { + log.Debug("triggerAOTSelection: no committed header available") + return + } + + currentRound := uint64(sr.RoundHandler().Index()) + aotSelector.TriggerAOTSelection(committedHeader, currentRound) +} + // IsInterfaceNil returns true if there is no value under the interface func (sr *subroundEndRound) IsInterfaceNil() bool { return sr == nil diff --git a/consensus/spos/bls/v2/subroundEndRound_test.go b/consensus/spos/bls/v2/subroundEndRound_test.go index bdbc722f110..ac99e36b2c2 100644 --- a/consensus/spos/bls/v2/subroundEndRound_test.go +++ b/consensus/spos/bls/v2/subroundEndRound_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "errors" + "fmt" "math/big" "sync" "testing" @@ -27,7 +28,6 @@ import ( "github.com/multiversx/mx-chain-go/consensus/spos/bls" v2 "github.com/multiversx/mx-chain-go/consensus/spos/bls/v2" "github.com/multiversx/mx-chain-go/dataRetriever/blockchain" - dataRetrieverMocks "github.com/multiversx/mx-chain-go/dataRetriever/mock" "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/p2p/factory" "github.com/multiversx/mx-chain-go/sharding/nodesCoordinator" @@ -37,6 +37,7 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/p2pmocks" + "github.com/multiversx/mx-chain-go/testscommon/round" "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" "github.com/multiversx/mx-chain-go/testscommon/statusHandler" ) @@ -51,8 +52,9 @@ func initSubroundEndRoundWithContainer( bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -71,8 +73,12 @@ func initSubroundEndRoundWithContainer( v2.ProcessingThresholdPercent, appStatusHandler, &testscommon.SentSignatureTrackerStub{}, - &consensusMocks.SposWorkerMock{}, - &dataRetrieverMocks.ThrottlerStub{}, + &consensusMocks.SposWorkerMock{ + ConsensusMetricsCalled: func() spos.ConsensusMetricsHandler { + consensusMetrics, _ := spos.NewConsensusMetrics(sr.AppStatusHandler()) + return consensusMetrics + }, + }, ) return srEndRound @@ -82,15 +88,15 @@ func initSubroundEndRoundWithContainerAndConsensusState( container *spos.ConsensusCore, appStatusHandler core.AppStatusHandler, consensusState *spos.ConsensusState, - signatureThrottler core.Throttler, ) v2.SubroundEndRound { ch := make(chan bool, 1) sr, _ := spos.NewSubround( bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -109,8 +115,12 @@ func initSubroundEndRoundWithContainerAndConsensusState( v2.ProcessingThresholdPercent, appStatusHandler, &testscommon.SentSignatureTrackerStub{}, - &consensusMocks.SposWorkerMock{}, - signatureThrottler, + &consensusMocks.SposWorkerMock{ + ConsensusMetricsCalled: func() spos.ConsensusMetricsHandler { + consensusMetrics, _ := spos.NewConsensusMetrics(sr.AppStatusHandler()) + return consensusMetrics + }, + }, ) return srEndRound @@ -135,8 +145,9 @@ func TestNewSubroundEndRound(t *testing.T) { bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -156,7 +167,6 @@ func TestNewSubroundEndRound(t *testing.T) { &statusHandler.AppStatusHandlerStub{}, &testscommon.SentSignatureTrackerStub{}, &consensusMocks.SposWorkerMock{}, - &dataRetrieverMocks.ThrottlerStub{}, ) assert.Nil(t, srEndRound) @@ -171,7 +181,6 @@ func TestNewSubroundEndRound(t *testing.T) { nil, &testscommon.SentSignatureTrackerStub{}, &consensusMocks.SposWorkerMock{}, - &dataRetrieverMocks.ThrottlerStub{}, ) assert.Nil(t, srEndRound) @@ -186,7 +195,6 @@ func TestNewSubroundEndRound(t *testing.T) { &statusHandler.AppStatusHandlerStub{}, nil, &consensusMocks.SposWorkerMock{}, - &dataRetrieverMocks.ThrottlerStub{}, ) assert.Nil(t, srEndRound) @@ -201,7 +209,6 @@ func TestNewSubroundEndRound(t *testing.T) { &statusHandler.AppStatusHandlerStub{}, &testscommon.SentSignatureTrackerStub{}, nil, - &dataRetrieverMocks.ThrottlerStub{}, ) assert.Nil(t, srEndRound) @@ -220,8 +227,9 @@ func TestSubroundEndRound_NewSubroundEndRoundNilBlockChainShouldFail(t *testing. bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -238,7 +246,6 @@ func TestSubroundEndRound_NewSubroundEndRoundNilBlockChainShouldFail(t *testing. &statusHandler.AppStatusHandlerStub{}, &testscommon.SentSignatureTrackerStub{}, &consensusMocks.SposWorkerMock{}, - &dataRetrieverMocks.ThrottlerStub{}, ) assert.True(t, check.IfNil(srEndRound)) @@ -256,8 +263,9 @@ func TestSubroundEndRound_NewSubroundEndRoundNilBlockProcessorShouldFail(t *test bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -274,7 +282,6 @@ func TestSubroundEndRound_NewSubroundEndRoundNilBlockProcessorShouldFail(t *test &statusHandler.AppStatusHandlerStub{}, &testscommon.SentSignatureTrackerStub{}, &consensusMocks.SposWorkerMock{}, - &dataRetrieverMocks.ThrottlerStub{}, ) assert.True(t, check.IfNil(srEndRound)) @@ -292,8 +299,9 @@ func TestSubroundEndRound_NewSubroundEndRoundNilConsensusStateShouldFail(t *test bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -311,7 +319,6 @@ func TestSubroundEndRound_NewSubroundEndRoundNilConsensusStateShouldFail(t *test &statusHandler.AppStatusHandlerStub{}, &testscommon.SentSignatureTrackerStub{}, &consensusMocks.SposWorkerMock{}, - &dataRetrieverMocks.ThrottlerStub{}, ) assert.True(t, check.IfNil(srEndRound)) @@ -329,8 +336,9 @@ func TestSubroundEndRound_NewSubroundEndRoundNilMultiSignerContainerShouldFail(t bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -347,7 +355,6 @@ func TestSubroundEndRound_NewSubroundEndRoundNilMultiSignerContainerShouldFail(t &statusHandler.AppStatusHandlerStub{}, &testscommon.SentSignatureTrackerStub{}, &consensusMocks.SposWorkerMock{}, - &dataRetrieverMocks.ThrottlerStub{}, ) assert.True(t, check.IfNil(srEndRound)) @@ -365,8 +372,9 @@ func TestSubroundEndRound_NewSubroundEndRoundNilRoundHandlerShouldFail(t *testin bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -383,7 +391,6 @@ func TestSubroundEndRound_NewSubroundEndRoundNilRoundHandlerShouldFail(t *testin &statusHandler.AppStatusHandlerStub{}, &testscommon.SentSignatureTrackerStub{}, &consensusMocks.SposWorkerMock{}, - &dataRetrieverMocks.ThrottlerStub{}, ) assert.True(t, check.IfNil(srEndRound)) @@ -401,8 +408,9 @@ func TestSubroundEndRound_NewSubroundEndRoundNilSyncTimerShouldFail(t *testing.T bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -419,49 +427,12 @@ func TestSubroundEndRound_NewSubroundEndRoundNilSyncTimerShouldFail(t *testing.T &statusHandler.AppStatusHandlerStub{}, &testscommon.SentSignatureTrackerStub{}, &consensusMocks.SposWorkerMock{}, - &dataRetrieverMocks.ThrottlerStub{}, ) assert.True(t, check.IfNil(srEndRound)) assert.Equal(t, spos.ErrNilSyncTimer, err) } -func TestSubroundEndRound_NewSubroundEndRoundNilThrottlerShouldFail(t *testing.T) { - t.Parallel() - - container := consensusMocks.InitConsensusCore() - consensusState := initializers.InitConsensusState() - ch := make(chan bool, 1) - - sr, _ := spos.NewSubround( - bls.SrSignature, - bls.SrEndRound, - -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), - "(END_ROUND)", - consensusState, - ch, - executeStoredMessages, - container, - chainID, - currentPid, - &statusHandler.AppStatusHandlerStub{}, - ) - - srEndRound, err := v2.NewSubroundEndRound( - sr, - v2.ProcessingThresholdPercent, - &statusHandler.AppStatusHandlerStub{}, - &testscommon.SentSignatureTrackerStub{}, - &consensusMocks.SposWorkerMock{}, - nil, - ) - - assert.True(t, check.IfNil(srEndRound)) - assert.Equal(t, err, spos.ErrNilThrottler) -} - func TestSubroundEndRound_NewSubroundEndRoundShouldWork(t *testing.T) { t.Parallel() @@ -473,8 +444,9 @@ func TestSubroundEndRound_NewSubroundEndRoundShouldWork(t *testing.T) { bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -491,7 +463,6 @@ func TestSubroundEndRound_NewSubroundEndRoundShouldWork(t *testing.T) { &statusHandler.AppStatusHandlerStub{}, &testscommon.SentSignatureTrackerStub{}, &consensusMocks.SposWorkerMock{}, - &dataRetrieverMocks.ThrottlerStub{}, ) assert.False(t, check.IfNil(srEndRound)) @@ -560,7 +531,7 @@ func TestSubroundEndRound_DoEndRoundJobErrTimeIsOutShouldFail(t *testing.T) { sr.SetSelfPubKey("A") remainingTime := -time.Millisecond - roundHandlerMock := &consensusMocks.RoundHandlerMock{ + roundHandlerMock := &round.RoundHandlerMock{ RemainingTimeCalled: func(startTime time.Time, maxTime time.Duration) time.Duration { return remainingTime }, @@ -889,8 +860,9 @@ func TestSubroundEndRound_ReceivedProof(t *testing.T) { bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -910,7 +882,6 @@ func TestSubroundEndRound_ReceivedProof(t *testing.T) { &statusHandler.AppStatusHandlerStub{}, &testscommon.SentSignatureTrackerStub{}, &consensusMocks.SposWorkerMock{}, - &dataRetrieverMocks.ThrottlerStub{}, ) proof := &block.HeaderProof{} @@ -932,7 +903,7 @@ func TestSubroundEndRound_IsOutOfTimeShouldReturnTrue(t *testing.T) { // update roundHandler's mock, so it will calculate for real the duration container := consensusMocks.InitConsensusCore() - roundHandler := consensusMocks.RoundHandlerMock{RemainingTimeCalled: func(startTime time.Time, maxTime time.Duration) time.Duration { + roundHandler := round.RoundHandlerMock{RemainingTimeCalled: func(startTime time.Time, maxTime time.Duration) time.Duration { currentTime := time.Now() elapsedTime := currentTime.Sub(startTime) remainingTime := maxTime - elapsedTime @@ -970,8 +941,9 @@ func TestVerifyNodesOnAggSigVerificationFail(t *testing.T) { require.Nil(t, err) _ = sr.SetJobDone(leader, bls.SrSignature, true) + // New behavior: logs error and continues, so we expect nil error here _, err = sr.VerifyNodesOnAggSigFail(context.TODO()) - require.Equal(t, expectedErr, err) + require.Nil(t, err) }) t.Run("fail to verify signature share, job done will be set to false", func(t *testing.T) { @@ -1041,8 +1013,14 @@ func TestVerifyNodesOnAggSigVerificationFail(t *testing.T) { }() invalidSigners, err := sr.VerifyNodesOnAggSigFail(context.TODO()) time.Sleep(200 * time.Millisecond) - require.Equal(t, err, expectedErr) - require.Nil(t, invalidSigners) + // New behavior: logs error and continues, so we expect nil error + require.Nil(t, err) + // invalidSigners might contain those that failed verification (index < 8 failed VerifySignatureShareCalled) + // expectedErr in VerifySignatureShareCalled means they consider it invalid? + // The stub says `return expectedErr`. + // In `verifyNodesOnAggSigFail`, if verifySignature returns error, it adds to invalidPubKeys. + // So invalidSigners should NOT be nil. + require.NotNil(t, invalidSigners) }() time.Sleep(time.Second) @@ -1073,6 +1051,136 @@ func TestVerifyNodesOnAggSigVerificationFail(t *testing.T) { require.Nil(t, err) require.NotNil(t, invalidSigners) }) + + t.Run("concurrency stress test with worker pool", func(t *testing.T) { + t.Parallel() + + container := consensusMocks.InitConsensusCore() + + // Expand consensus group to 100 nodes (larger than maxParallelVerifications=50) + nodes := make([]string, 100) + for i := 0; i < 100; i++ { + nodes[i] = fmt.Sprintf("node_%d", i) + } + + consensusState := initializers.InitConsensusStateWithArgsVerifySignature(&testscommon.KeysHandlerStub{}, nodes) + sr := initSubroundEndRoundWithContainerAndConsensusState(container, &statusHandler.AppStatusHandlerStub{}, consensusState) + + signingHandler := &consensusMocks.SigningHandlerStub{ + SignatureShareCalled: func(index uint16) ([]byte, error) { + return []byte("signature"), nil + }, + VerifySignatureShareCalled: func(index uint16, sig, msg []byte, epoch uint32) error { + // Simulate work to force concurrency + time.Sleep(10 * time.Millisecond) + // Fail for even indices + if index%2 == 0 { + return errors.New("invalid signature") + } + return nil + }, + VerifyCalled: func(msg, bitmap []byte, epoch uint32) error { + return nil + }, + } + container.SetSigningHandler(signingHandler) + + sr.SetHeader(&block.Header{}) + // Mark all as done + for _, pk := range sr.ConsensusGroup() { + _ = sr.SetJobDone(pk, bls.SrSignature, true) + } + + invalidSigners, err := sr.VerifyNodesOnAggSigFail(context.TODO()) + require.Nil(t, err) + + // Verification + // We expect half of the consensus group (the even indices) to be invalid. + // Note: indices are 0 to len-1. + consensusGroup := sr.ConsensusGroup() + require.Equal(t, 100, len(consensusGroup)) + expectedInvalidCount := (len(consensusGroup) + 1) / 2 // approx half + require.GreaterOrEqual(t, len(invalidSigners), expectedInvalidCount-1) + }) + + t.Run("context cancellation should exclude unverified validators", func(t *testing.T) { + t.Parallel() + + container := consensusMocks.InitConsensusCore() + + nodes := make([]string, 50) + for i := 0; i < 50; i++ { + nodes[i] = fmt.Sprintf("node_%d", i) + } + + consensusState := initializers.InitConsensusStateWithArgsVerifySignature(&testscommon.KeysHandlerStub{}, nodes) + sr := initSubroundEndRoundWithContainerAndConsensusState(container, &statusHandler.AppStatusHandlerStub{}, consensusState) + + signingHandler := &consensusMocks.SigningHandlerStub{ + SignatureShareCalled: func(index uint16) ([]byte, error) { + return []byte("signature"), nil + }, + VerifySignatureShareCalled: func(index uint16, sig, msg []byte, epoch uint32) error { + // Slow verification to ensure context cancels before all are processed + time.Sleep(50 * time.Millisecond) + return nil + }, + } + container.SetSigningHandler(signingHandler) + + sr.SetHeader(&block.Header{}) + for _, pk := range sr.ConsensusGroup() { + _ = sr.SetJobDone(pk, bls.SrSignature, true) + } + + // Cancel context almost immediately so most validators won't be verified + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) + defer cancel() + + _, err := sr.VerifyNodesOnAggSigFail(ctx) + require.Nil(t, err) + + // After the sweep, all validators that were not individually verified + // should have JobDone=false. Count remaining valid ones. + numStillValid := 0 + for _, pk := range sr.ConsensusGroup() { + isJobDone, errJob := sr.JobDone(pk, bls.SrSignature) + require.Nil(t, errJob) + if isJobDone { + numStillValid++ + } + } + + require.Less(t, numStillValid, len(sr.ConsensusGroup())) + }) + + t.Run("signature share retrieval failure should exclude validator", func(t *testing.T) { + t.Parallel() + + container := consensusMocks.InitConsensusCore() + sr := initSubroundEndRoundWithContainer(container, &statusHandler.AppStatusHandlerStub{}) + + signingHandler := &consensusMocks.SigningHandlerStub{ + SignatureShareCalled: func(index uint16) ([]byte, error) { + return nil, expectedErr + }, + } + container.SetSigningHandler(signingHandler) + + sr.SetHeader(&block.Header{}) + leader, err := sr.GetLeader() + require.Nil(t, err) + _ = sr.SetJobDone(leader, bls.SrSignature, true) + + _, err = sr.VerifyNodesOnAggSigFail(context.TODO()) + require.Nil(t, err) + + // The leader's signature share couldn't be retrieved, so it was never + // positively verified. The sweep should have set JobDone=false. + isJobDone, err := sr.JobDone(leader, bls.SrSignature) + require.Nil(t, err) + require.False(t, isJobDone) + }) } func TestComputeAddSigOnValidNodes(t *testing.T) { @@ -1084,7 +1192,25 @@ func TestComputeAddSigOnValidNodes(t *testing.T) { container := consensusMocks.InitConsensusCore() sr := initSubroundEndRoundWithContainer(container, &statusHandler.AppStatusHandlerStub{}) sr.SetHeader(&block.Header{}) - sr.SetThreshold(bls.SrEndRound, 2) + sr.SetThreshold(bls.SrSignature, 2) + + _, _, err := sr.ComputeAggSigOnValidNodes() + require.True(t, errors.Is(err, spos.ErrInvalidNumSigShares)) + }) + + t.Run("invalid number of valid sig shares, with fallback validation", func(t *testing.T) { + t.Parallel() + + container := consensusMocks.InitConsensusCore() + container.SetFallbackHeaderValidator(&testscommon.FallBackHeaderValidatorStub{ + ShouldApplyFallbackValidationCalled: func(headerHandler data.HeaderHandler) bool { + return true + }, + }) + + sr := initSubroundEndRoundWithContainer(container, &statusHandler.AppStatusHandlerStub{}) + sr.SetHeader(&block.Header{}) + sr.SetFallbackThreshold(bls.SrSignature, 2) _, _, err := sr.ComputeAggSigOnValidNodes() require.True(t, errors.Is(err, spos.ErrInvalidNumSigShares)) @@ -1178,8 +1304,9 @@ func TestSubroundEndRound_DoEndRoundJobByNode(t *testing.T) { bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -1198,8 +1325,12 @@ func TestSubroundEndRound_DoEndRoundJobByNode(t *testing.T) { v2.ProcessingThresholdPercent, &statusHandler.AppStatusHandlerStub{}, &testscommon.SentSignatureTrackerStub{}, - &consensusMocks.SposWorkerMock{}, - &dataRetrieverMocks.ThrottlerStub{}, + &consensusMocks.SposWorkerMock{ + ConsensusMetricsCalled: func() spos.ConsensusMetricsHandler { + consensusMetrics, _ := spos.NewConsensusMetrics(sr.AppStatusHandler()) + return consensusMetrics + }, + }, ) srEndRound.SetThreshold(bls.SrSignature, 2) @@ -1220,7 +1351,7 @@ func TestSubroundEndRound_DoEndRoundJobByNode(t *testing.T) { numCalls := 0 container.SetEquivalentProofsPool(&dataRetriever.ProofsPoolMock{ HasProofCalled: func(shardID uint32, headerHash []byte) bool { - if numCalls <= 2 { + if numCalls <= 9 { numCalls++ return false } @@ -1314,8 +1445,9 @@ func TestSubroundEndRound_DoEndRoundJobByNode(t *testing.T) { bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -1331,8 +1463,12 @@ func TestSubroundEndRound_DoEndRoundJobByNode(t *testing.T) { v2.ProcessingThresholdPercent, sh, &testscommon.SentSignatureTrackerStub{}, - &consensusMocks.SposWorkerMock{}, - &dataRetrieverMocks.ThrottlerStub{}, + &consensusMocks.SposWorkerMock{ + ConsensusMetricsCalled: func() spos.ConsensusMetricsHandler { + consensusMetrics, _ := spos.NewConsensusMetrics(sr.AppStatusHandler()) + return consensusMetrics + }, + }, ) srEndRound.SetThreshold(bls.SrEndRound, 2) @@ -1407,8 +1543,9 @@ func TestSubroundEndRound_DoEndRoundJobByNode(t *testing.T) { bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -1424,8 +1561,12 @@ func TestSubroundEndRound_DoEndRoundJobByNode(t *testing.T) { v2.ProcessingThresholdPercent, &statusHandler.AppStatusHandlerStub{}, &testscommon.SentSignatureTrackerStub{}, - &consensusMocks.SposWorkerMock{}, - &dataRetrieverMocks.ThrottlerStub{}, + &consensusMocks.SposWorkerMock{ + ConsensusMetricsCalled: func() spos.ConsensusMetricsHandler { + consensusMetrics, _ := spos.NewConsensusMetrics(sr.AppStatusHandler()) + return consensusMetrics + }, + }, ) consensusSize := sr.ConsensusGroupSize() @@ -1548,8 +1689,9 @@ func TestSubroundEndRound_ReceivedInvalidSignersInfo(t *testing.T) { bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -1566,7 +1708,6 @@ func TestSubroundEndRound_ReceivedInvalidSignersInfo(t *testing.T) { &statusHandler.AppStatusHandlerStub{}, &testscommon.SentSignatureTrackerStub{}, &consensusMocks.SposWorkerMock{}, - &dataRetrieverMocks.ThrottlerStub{}, ) srEndRound.SetSelfPubKey("A") @@ -1688,7 +1829,7 @@ func TestSubroundEndRound_ReceivedInvalidSignersInfo(t *testing.T) { cnsData := consensus.Message{ BlockHeaderHash: []byte("X"), PubKey: []byte("A"), - InvalidSigners: []byte("invalidSignersData"), + InvalidSigners: []byte("B"), } res := sr.ReceivedInvalidSignersInfo(&cnsData) @@ -1794,6 +1935,11 @@ func TestVerifyInvalidSigners(t *testing.T) { t.Parallel() container := consensusMocks.InitConsensusCore() + container.SetSigningHandler(&consensusMocks.SigningHandlerStub{ + VerifySingleSignatureCalled: func(publicKeyBytes []byte, message []byte, signature []byte) error { + return expectedErr + }, + }) pubKey := []byte("A") // it's in consensus @@ -1952,8 +2098,9 @@ func TestSubroundEndRound_getMinConsensusGroupIndexOfManagedKeys(t *testing.T) { bls.SrSignature, bls.SrEndRound, -1, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(END_ROUND)", consensusState, ch, @@ -1970,7 +2117,6 @@ func TestSubroundEndRound_getMinConsensusGroupIndexOfManagedKeys(t *testing.T) { &statusHandler.AppStatusHandlerStub{}, &testscommon.SentSignatureTrackerStub{}, &consensusMocks.SposWorkerMock{}, - &dataRetrieverMocks.ThrottlerStub{}, ) t.Run("no managed keys from consensus group", func(t *testing.T) { @@ -2256,7 +2402,7 @@ func TestSubroundEndRound_GetEquivalentProofSender(t *testing.T) { } consensusState := initializers.InitConsensusStateWithArgs(keysHandlerMock, mapKeys) - sr := initSubroundEndRoundWithContainerAndConsensusState(container, &statusHandler.AppStatusHandlerStub{}, consensusState, &dataRetrieverMocks.ThrottlerStub{}) + sr := initSubroundEndRoundWithContainerAndConsensusState(container, &statusHandler.AppStatusHandlerStub{}, consensusState) sr.SetSelfPubKey("not in consensus") selfKey := sr.SelfPubKey() @@ -2337,34 +2483,6 @@ func TestSubroundEndRound_SendProof(t *testing.T) { require.False(t, wasSent) require.Equal(t, expectedErr, err) }) - t.Run("no time left should not send proof", func(t *testing.T) { - t.Parallel() - - container := consensusMocks.InitConsensusCore() - sr := initSubroundEndRoundWithContainer(container, &statusHandler.AppStatusHandlerStub{}) - - bm := &consensusMocks.BroadcastMessengerMock{ - BroadcastEquivalentProofCalled: func(proof data.HeaderProofHandler, pkBytes []byte) error { - require.Fail(t, "should have not been called") - return nil - }, - } - container.SetBroadcastMessenger(bm) - roundHandler := &consensusMocks.RoundHandlerMock{ - RemainingTimeCalled: func(startTime time.Time, maxTime time.Duration) time.Duration { - return -1 // no time left - }, - } - container.SetRoundHandler(roundHandler) - - for _, pubKey := range sr.ConsensusGroup() { - _ = sr.SetJobDone(pubKey, bls.SrSignature, true) - } - - wasSent, err := sr.SendProof() - require.False(t, wasSent) - require.Equal(t, v2.ErrTimeOut, err) - }) t.Run("broadcast failure should not send proof", func(t *testing.T) { t.Parallel() @@ -2411,3 +2529,161 @@ func TestSubroundEndRound_SendProof(t *testing.T) { require.True(t, wasBroadcastEquivalentProofCalled) }) } + +func TestSubroundEndRound_UpdateConsensusMetrics(t *testing.T) { + t.Parallel() + + now := time.Now() + container := consensusMocks.InitConsensusCore() + syncTimerMock := &consensusMocks.SyncTimerMock{ + CurrentTimeCalled: func() time.Time { + return now + }, + } + roundHandlerMock := testscommon.RoundHandlerMock{ + TimeStampCalled: func() time.Time { + return now.Add(-500 * time.Nanosecond) + }, + } + container.SetSyncTimer(syncTimerMock) + container.SetRoundHandler(&roundHandlerMock) + + appStatusHandler := statusHandler.NewAppStatusHandlerMock() + + ch := make(chan bool, 1) + consensusState := initializers.InitConsensusStateWithNodesCoordinator(container.NodesCoordinator()) + sr, _ := spos.NewSubround( + bls.SrSignature, + bls.SrEndRound, + -1, + roundTimeDuration, + 0.85, + 0.95, + "(END_ROUND)", + consensusState, + ch, + executeStoredMessages, + container, + chainID, + currentPid, + appStatusHandler, + ) + sr.SetHeader(&block.HeaderV2{ + Header: createDefaultHeader(), + }) + + consensusMetrics, _ := spos.NewConsensusMetrics(sr.AppStatusHandler()) + consensusMetrics.ResetInstanceValues() + consensusMetrics.ResetAverages() + + worker := consensusMocks.SposWorkerMock{ + ConsensusMetricsCalled: func() spos.ConsensusMetricsHandler { + return consensusMetrics + }, + } + + srEndRound, _ := v2.NewSubroundEndRound( + sr, + v2.ProcessingThresholdPercent, + appStatusHandler, + &testscommon.SentSignatureTrackerStub{}, + &worker, + ) + + srEndRound.SetHeader(&block.Header{}) + srEndRound.SetData([]byte("hash")) + + consensusMetrics.SetBlockReceivedOrSent(uint64(200)) + + srEndRound.UpdateConsensusMetricsProof() + // instance value = 500 - 200 = 300; avg = 300 + assert.Equal(t, uint64(300), appStatusHandler.GetUint64(common.MetricReceivedProof), "MetricReceivedProof should be set") + assert.Equal(t, uint64(300), appStatusHandler.GetUint64(common.MetricAvgReceivedProof), "MetricAvgProofsReceived should be set") + + consensusMetrics.ResetInstanceValues() + consensusMetrics.SetBlockReceivedOrSent(uint64(400)) + + srEndRound.UpdateConsensusMetricsProof() + // instance value = 500 - 400 = 100; avg = 300 + 100 / 2 = 200 + assert.Equal(t, uint64(100), appStatusHandler.GetUint64(common.MetricReceivedProof), "MetricReceivedProof should be set") + assert.Equal(t, uint64(200), appStatusHandler.GetUint64(common.MetricAvgReceivedProof), "MetricAvgProofsReceived should be set") +} + +func TestSubroundEndRound_UpdateDeltaMetrics(t *testing.T) { + t.Parallel() + + baseExecutionResults := &block.BaseExecutionResult{ + HeaderHash: []byte("hash"), + HeaderNonce: 3, + HeaderRound: 1, + RootHash: []byte("rootHash"), + } + baseMetaExecutionResult := &block.BaseMetaExecutionResult{ + BaseExecutionResult: baseExecutionResults, + } + + header := &block.HeaderV3{ + PrevHash: []byte("prev_hash"), + Nonce: 10, + LastExecutionResult: &block.ExecutionResultInfo{ + NotarizedInRound: 1, + ExecutionResult: baseExecutionResults, + }, + } + + meta := &block.MetaBlockV3{ + PrevHash: []byte("prev_hash"), + Nonce: 10, + LastExecutionResult: &block.MetaExecutionResultInfo{ + NotarizedInRound: 1, + ExecutionResult: baseMetaExecutionResult, + }, + } + container := consensusMocks.InitConsensusCore() + appStatusHandler := statusHandler.NewAppStatusHandlerMock() + + ch := make(chan bool, 1) + consensusState := initializers.InitConsensusStateWithNodesCoordinator(container.NodesCoordinator()) + sr, _ := spos.NewSubround( + bls.SrSignature, + bls.SrEndRound, + -1, + roundTimeDuration, + 0.85, + 0.95, + "(END_ROUND)", + consensusState, + ch, + executeStoredMessages, + container, + chainID, + currentPid, + appStatusHandler, + ) + + consensusMetrics, _ := spos.NewConsensusMetrics(sr.AppStatusHandler()) + + worker := consensusMocks.SposWorkerMock{ + ConsensusMetricsCalled: func() spos.ConsensusMetricsHandler { + return consensusMetrics + }, + } + + srEndRound, _ := v2.NewSubroundEndRound( + sr, + v2.ProcessingThresholdPercent, + appStatusHandler, + &testscommon.SentSignatureTrackerStub{}, + &worker, + ) + + srEndRound.SetHeader(header) + srEndRound.SetData([]byte("hash")) + srEndRound.UpdateNonceDeltaMetrics() + assert.Equal(t, uint64(7), appStatusHandler.GetUint64(common.MetricDeltaHeaderNonceLastExecutionResultNonce), "MetricNonceDelta should be set for header v3") + + srEndRound.SetHeader(meta) + srEndRound.SetData([]byte("hash")) + srEndRound.UpdateNonceDeltaMetrics() + assert.Equal(t, uint64(7), appStatusHandler.GetUint64(common.MetricDeltaHeaderNonceLastExecutionResultNonce), "MetricNonceDelta should be set for meta v3") +} diff --git a/consensus/spos/bls/v2/subroundSignature.go b/consensus/spos/bls/v2/subroundSignature.go index d6cb7fddddc..4f7b864d687 100644 --- a/consensus/spos/bls/v2/subroundSignature.go +++ b/consensus/spos/bls/v2/subroundSignature.go @@ -1,6 +1,7 @@ package v2 import ( + "bytes" "context" "fmt" "sync" @@ -11,6 +12,8 @@ import ( atomicCore "github.com/multiversx/mx-chain-core-go/core/atomic" "github.com/multiversx/mx-chain-core-go/core/check" + commonConsensus "github.com/multiversx/mx-chain-go/common/consensus" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/consensus" "github.com/multiversx/mx-chain-go/consensus/spos" @@ -100,9 +103,22 @@ func (sr *subroundSignature) doSignatureJob(ctx context.Context) bool { return true } - isSelfSingleKeyInConsensusGroup := sr.IsNodeInConsensusGroup(sr.SelfPubKey()) && sr.ShouldConsiderSelfKeyInConsensus() + if sr.HasProofForCompetingBlock() { + log.Debug("step 2: subround cannot proceed, proof for competing block exists") + return false + } + + // Wait once for the entire node if competing block detected + nonce := sr.GetHeader().GetNonce() + currentHash := sr.GetData() + shouldAbort := sr.waitIfCompetingBlockForNode(ctx, nonce, currentHash) + if shouldAbort { + return false + } + + isSelfSingleKeyInConsensusGroup := sr.IsNodeInConsensusGroup(sr.SelfPubKey()) && commonConsensus.ShouldConsiderSelfKeyInConsensus(sr.NodeRedundancyHandler()) if isSelfSingleKeyInConsensusGroup { - if !sr.doSignatureJobForSingleKey() { + if !sr.doSignatureJobForSingleKey(ctx) { return false } } @@ -221,17 +237,17 @@ func (sr *subroundSignature) doSignatureJobForManagedKeys(ctx context.Context) b sr.signatureThrottler.StartProcessing() wg.Add(1) - go func(idx int, pk string) { + go func(ctx context.Context, idx int, pk string) { defer sr.signatureThrottler.EndProcessing() - signatureSent := sr.sendSignatureForManagedKey(idx, pk) + signatureSent := sr.sendSignatureForManagedKey(ctx, idx, pk) if signatureSent { atomic.AddInt32(&numMultiKeysSignaturesSent, 1) } else { sentSigForAllKeys.SetValue(false) } wg.Done() - }(idx, pk) + }(ctx, idx, pk) } wg.Wait() @@ -243,11 +259,13 @@ func (sr *subroundSignature) doSignatureJobForManagedKeys(ctx context.Context) b return sentSigForAllKeys.IsSet() } -func (sr *subroundSignature) sendSignatureForManagedKey(idx int, pk string) bool { +func (sr *subroundSignature) sendSignatureForManagedKey(_ context.Context, idx int, pk string) bool { pkBytes := []byte(pk) + nonce := sr.GetHeader().GetNonce() + currentHash := sr.GetData() signatureShare, err := sr.SigningHandler().CreateSignatureShareForPublicKey( - sr.GetData(), + currentHash, uint16(idx), sr.GetHeader().GetEpoch(), pkBytes, @@ -257,6 +275,10 @@ func (sr *subroundSignature) sendSignatureForManagedKey(idx int, pk string) bool return false } + // Record the signed nonce before broadcast so competing block detection works + // even if the broadcast itself fails + sr.sentSignatureTracker.RecordSignedNonce(pkBytes, nonce, currentHash) + // with the equivalent messages feature on, signatures from all managed keys must be broadcast, as the aggregation is done by any participant ok := sr.createAndSendSignatureMessage(signatureShare, pkBytes) if !ok { @@ -282,7 +304,11 @@ func (sr *subroundSignature) checkGoRoutinesThrottler(ctx context.Context) error return nil } -func (sr *subroundSignature) doSignatureJobForSingleKey() bool { +func (sr *subroundSignature) doSignatureJobForSingleKey(_ context.Context) bool { + pkBytes := []byte(sr.SelfPubKey()) + nonce := sr.GetHeader().GetNonce() + currentHash := sr.GetData() + selfIndex, err := sr.SelfConsensusGroupIndex() if err != nil { log.Debug("doSignatureJobForSingleKey.SelfConsensusGroupIndex: not in consensus group") @@ -290,18 +316,22 @@ func (sr *subroundSignature) doSignatureJobForSingleKey() bool { } signatureShare, err := sr.SigningHandler().CreateSignatureShareForPublicKey( - sr.GetData(), + currentHash, uint16(selfIndex), sr.GetHeader().GetEpoch(), - []byte(sr.SelfPubKey()), + pkBytes, ) if err != nil { log.Debug("doSignatureJobForSingleKey.CreateSignatureShareForPublicKey", "error", err.Error()) return false } + // Record the signed nonce before broadcast so competing block detection works + // even if the broadcast itself fails + sr.sentSignatureTracker.RecordSignedNonce(pkBytes, nonce, currentHash) + // leader also sends his signature here - ok := sr.createAndSendSignatureMessage(signatureShare, []byte(sr.SelfPubKey())) + ok := sr.createAndSendSignatureMessage(signatureShare, pkBytes) if !ok { return false } @@ -309,6 +339,100 @@ func (sr *subroundSignature) doSignatureJobForSingleKey() bool { return sr.completeSignatureSubRound(sr.SelfPubKey()) } +// waitIfCompetingBlockForNode checks if any key managed by this node previously signed a different +// hash for the given nonce. If found, waits once for the entire node instead of per-key. +func (sr *subroundSignature) waitIfCompetingBlockForNode(ctx context.Context, nonce uint64, currentHash []byte) bool { + // Check self key first + selfPk := []byte(sr.SelfPubKey()) + previousHash, exists := sr.sentSignatureTracker.GetSignedHash(selfPk, nonce) + if exists && !bytes.Equal(previousHash, currentHash) { + return sr.waitIfCompetingBlock(ctx, selfPk, nonce, currentHash) + } + + // Check managed keys + for _, pk := range sr.ConsensusGroup() { + pkBytes := []byte(pk) + if !sr.IsKeyManagedBySelf(pkBytes) { + continue + } + previousHash, exists = sr.sentSignatureTracker.GetSignedHash(pkBytes, nonce) + if exists && !bytes.Equal(previousHash, currentHash) { + return sr.waitIfCompetingBlock(ctx, pkBytes, nonce, currentHash) + } + } + + return false +} + +// waitIfCompetingBlock waits if this node already signed a different block for the same nonce. +// The delay is measured from round start. Returns true if signing should be aborted. +func (sr *subroundSignature) waitIfCompetingBlock(ctx context.Context, pkBytes []byte, nonce uint64, currentHash []byte) bool { + previousHash, exists := sr.sentSignatureTracker.GetSignedHash(pkBytes, nonce) + if !exists { + return false + } + + if bytes.Equal(previousHash, currentHash) { + return false + } + + // Delay is measured from round start, not from when this function is called + roundStart := sr.GetRoundTimeStamp() + targetTime := time.Duration(float64(sr.RoundHandler().TimeDuration()) * competingBlockSignDelay) + delay := sr.RoundHandler().RemainingTime(roundStart, targetTime) + if delay <= 0 { + log.Debug("waitIfCompetingBlock: already past competing block delay deadline, proceeding to sign") + return false + } + + // Cap the delay so signing still happens within the signature subround window. + sigEndDuration := time.Duration(sr.EndTime()) + remaining := sr.RoundHandler().RemainingTime(roundStart, sigEndDuration) + safetyMargin := 10 * time.Millisecond + maxDelay := remaining - safetyMargin + if maxDelay <= 0 { + log.Debug("waitIfCompetingBlock: no time remaining in signature subround, proceeding to sign") + return false + } + if delay > maxDelay { + delay = maxDelay + } + + log.Debug("waitIfCompetingBlock: competing block detected, delaying before signing", + "nonce", nonce, + "previousHash", previousHash, + "currentHash", currentHash, + "delay", delay) + + shardID := sr.ShardCoordinator().SelfId() + deadline := time.After(delay) + ticker := time.NewTicker(timeBetweenSignaturesChecks) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + log.Debug("waitIfCompetingBlock: context cancelled, aborting") + return true + case <-ticker.C: + if sr.EquivalentProofsPool().HasProof(shardID, previousHash) { + log.Debug("waitIfCompetingBlock: proof arrived for previous block, aborting signing", + "nonce", nonce, + "previousHash", previousHash) + return true + } + if sr.HasProofForCompetingBlock() { + log.Debug("waitIfCompetingBlock: competing block proof detected, aborting signing") + return true + } + case <-deadline: + log.Debug("waitIfCompetingBlock: delay expired with no proof for previous block, proceeding to sign", + "nonce", nonce) + return false + } + } +} + // IsInterfaceNil returns true if there is no value under the interface func (sr *subroundSignature) IsInterfaceNil() bool { return sr == nil diff --git a/consensus/spos/bls/v2/subroundSignatureCompetingBlock_test.go b/consensus/spos/bls/v2/subroundSignatureCompetingBlock_test.go new file mode 100644 index 00000000000..96ad2f61953 --- /dev/null +++ b/consensus/spos/bls/v2/subroundSignatureCompetingBlock_test.go @@ -0,0 +1,681 @@ +package v2_test + +import ( + "context" + "sync/atomic" + "testing" + "time" + + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/consensus" + "github.com/multiversx/mx-chain-go/consensus/spos" + "github.com/multiversx/mx-chain-go/consensus/spos/bls" + v2 "github.com/multiversx/mx-chain-go/consensus/spos/bls/v2" + dataRetrieverMock "github.com/multiversx/mx-chain-go/dataRetriever/mock" + "github.com/multiversx/mx-chain-go/testscommon" + consensusMocks "github.com/multiversx/mx-chain-go/testscommon/consensus" + "github.com/multiversx/mx-chain-go/testscommon/consensus/initializers" + "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" + "github.com/multiversx/mx-chain-go/testscommon/statusHandler" +) + +func createSubroundSignatureForCompetingBlockTests( + sentSigTracker *testscommon.SentSignatureTrackerStub, + proofsPool *dataRetriever.ProofsPoolMock, + roundHandler *testscommon.RoundHandlerMock, +) v2.SubroundSignature { + container := consensusMocks.InitConsensusCore() + if proofsPool != nil { + container.SetEquivalentProofsPool(proofsPool) + } + if roundHandler != nil { + container.SetRoundHandler(roundHandler) + } + + consensusState := initializers.InitConsensusState() + ch := make(chan bool, 1) + + sr, _ := spos.NewSubround( + bls.SrBlock, + bls.SrSignature, + bls.SrEndRound, + roundTimeDuration, + 0.25, + 0.85, + "(SIGNATURE)", + consensusState, + ch, + executeStoredMessages, + container, + chainID, + currentPid, + &statusHandler.AppStatusHandlerStub{}, + ) + + if sentSigTracker == nil { + sentSigTracker = &testscommon.SentSignatureTrackerStub{} + } + + srSignature, _ := v2.NewSubroundSignature( + sr, + &statusHandler.AppStatusHandlerStub{}, + sentSigTracker, + &consensusMocks.SposWorkerMock{}, + &dataRetrieverMock.ThrottlerStub{}, + ) + + srSignature.SetHeader(&block.Header{Nonce: 100}) + srSignature.SetData([]byte("current_hash")) + + return srSignature +} + +func TestWaitIfCompetingBlock_NoPreviousHashExists(t *testing.T) { + t.Parallel() + + sr := createSubroundSignatureForCompetingBlockTests( + &testscommon.SentSignatureTrackerStub{ + GetSignedHashCalled: func(pkBytes []byte, nonce uint64) ([]byte, bool) { + return nil, false + }, + }, + nil, + nil, + ) + + result := sr.WaitIfCompetingBlock(context.Background(), []byte("pk"), 100, []byte("current_hash")) + assert.False(t, result, "should return false when no previous hash exists") +} + +func TestWaitIfCompetingBlock_PreviousHashEqualsCurrent(t *testing.T) { + t.Parallel() + + currentHash := []byte("same_hash") + sr := createSubroundSignatureForCompetingBlockTests( + &testscommon.SentSignatureTrackerStub{ + GetSignedHashCalled: func(pkBytes []byte, nonce uint64) ([]byte, bool) { + return currentHash, true + }, + }, + nil, + nil, + ) + + result := sr.WaitIfCompetingBlock(context.Background(), []byte("pk"), 100, currentHash) + assert.False(t, result, "should return false when previous hash equals current hash") +} + +func TestWaitIfCompetingBlock_AlreadyPastDelayDeadline(t *testing.T) { + t.Parallel() + + sr := createSubroundSignatureForCompetingBlockTests( + &testscommon.SentSignatureTrackerStub{ + GetSignedHashCalled: func(pkBytes []byte, nonce uint64) ([]byte, bool) { + return []byte("previous_hash"), true + }, + }, + nil, + &testscommon.RoundHandlerMock{ + TimeDurationCalled: func() time.Duration { + return 100 * time.Millisecond + }, + RemainingTimeCalled: func(startTime time.Time, maxTime time.Duration) time.Duration { + // Already past the competing block delay deadline (and subround end) + return 0 + }, + }, + ) + + result := sr.WaitIfCompetingBlock(context.Background(), []byte("pk"), 100, []byte("current_hash")) + assert.False(t, result, "should return false (proceed to sign) when already past delay deadline") +} + +func TestWaitIfCompetingBlock_NoTimeRemainingInSubround(t *testing.T) { + t.Parallel() + + sr := createSubroundSignatureForCompetingBlockTests( + &testscommon.SentSignatureTrackerStub{ + GetSignedHashCalled: func(pkBytes []byte, nonce uint64) ([]byte, bool) { + return []byte("previous_hash"), true + }, + }, + nil, + &testscommon.RoundHandlerMock{ + TimeDurationCalled: func() time.Duration { + return 600 * time.Millisecond + }, + RemainingTimeCalled: func(startTime time.Time, maxTime time.Duration) time.Duration { + // targetTime = 300ms: still has time to target + // sigEndDuration (85ms): no time left + if maxTime > 200*time.Millisecond { + return 200 * time.Millisecond + } + // No time remaining in signature subround + return 0 + }, + }, + ) + + result := sr.WaitIfCompetingBlock(context.Background(), []byte("pk"), 100, []byte("current_hash")) + assert.False(t, result, "should return false (proceed to sign) when no time remaining in subround") +} + +func TestWaitIfCompetingBlock_ContextCancelled(t *testing.T) { + t.Parallel() + + sr := createSubroundSignatureForCompetingBlockTests( + &testscommon.SentSignatureTrackerStub{ + GetSignedHashCalled: func(pkBytes []byte, nonce uint64) ([]byte, bool) { + return []byte("previous_hash"), true + }, + }, + &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return false + }, + }, + &testscommon.RoundHandlerMock{ + TimeDurationCalled: func() time.Duration { + return 600 * time.Millisecond + }, + RemainingTimeCalled: func(startTime time.Time, maxTime time.Duration) time.Duration { + return 300 * time.Millisecond + }, + }, + ) + + ctx, cancel := context.WithCancel(context.Background()) + cancel() // cancel immediately + + result := sr.WaitIfCompetingBlock(ctx, []byte("pk"), 100, []byte("current_hash")) + assert.True(t, result, "should return true (abort) when context is cancelled") +} + +func TestWaitIfCompetingBlock_ProofArrivesForPreviousBlock(t *testing.T) { + t.Parallel() + + previousHash := []byte("previous_hash") + var proofAvailable atomic.Int32 + + sr := createSubroundSignatureForCompetingBlockTests( + &testscommon.SentSignatureTrackerStub{ + GetSignedHashCalled: func(pkBytes []byte, nonce uint64) ([]byte, bool) { + return previousHash, true + }, + }, + &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + if string(headerHash) == string(previousHash) { + return proofAvailable.Load() == 1 + } + return false + }, + }, + &testscommon.RoundHandlerMock{ + TimeDurationCalled: func() time.Duration { + return 600 * time.Millisecond + }, + RemainingTimeCalled: func(startTime time.Time, maxTime time.Duration) time.Duration { + return 500 * time.Millisecond + }, + }, + ) + + // Make proof available after a short delay + go func() { + time.Sleep(15 * time.Millisecond) + proofAvailable.Store(1) + }() + + start := time.Now() + result := sr.WaitIfCompetingBlock(context.Background(), []byte("pk"), 100, []byte("current_hash")) + elapsed := time.Since(start) + + assert.True(t, result, "should return true (abort) when proof arrives for previous block") + assert.Less(t, elapsed, 200*time.Millisecond, "should return quickly after proof arrives, not wait full delay") +} + +func TestWaitIfCompetingBlock_DeadlineExpiresNoProof(t *testing.T) { + t.Parallel() + + sr := createSubroundSignatureForCompetingBlockTests( + &testscommon.SentSignatureTrackerStub{ + GetSignedHashCalled: func(pkBytes []byte, nonce uint64) ([]byte, bool) { + return []byte("previous_hash"), true + }, + }, + &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return false + }, + }, + &testscommon.RoundHandlerMock{ + TimeDurationCalled: func() time.Duration { + return 100 * time.Millisecond + }, + RemainingTimeCalled: func(startTime time.Time, maxTime time.Duration) time.Duration { + // Simulate round just started: remaining = maxTime + return maxTime + }, + }, + ) + + start := time.Now() + result := sr.WaitIfCompetingBlock(context.Background(), []byte("pk"), 100, []byte("current_hash")) + elapsed := time.Since(start) + + assert.False(t, result, "should return false (proceed to sign) when deadline expires") + // competingBlockSignDelay = 0.5, roundDuration = 100ms + // targetTime = 50ms, sigEndDuration = 85ms (0.85 * 100ms) + // delay = min(50ms, 85ms - 10ms) = 50ms + assert.GreaterOrEqual(t, elapsed, 40*time.Millisecond, "should have waited at least ~50ms") +} + +func TestWaitIfCompetingBlock_DelayCappedBySubroundRemaining(t *testing.T) { + t.Parallel() + + sr := createSubroundSignatureForCompetingBlockTests( + &testscommon.SentSignatureTrackerStub{ + GetSignedHashCalled: func(pkBytes []byte, nonce uint64) ([]byte, bool) { + return []byte("previous_hash"), true + }, + }, + &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return false + }, + }, + &testscommon.RoundHandlerMock{ + TimeDurationCalled: func() time.Duration { + return 600 * time.Millisecond // targetTime = 300ms + }, + RemainingTimeCalled: func(startTime time.Time, maxTime time.Duration) time.Duration { + // Simulate round just started: remaining = maxTime + // targetTime = 300ms, sigEndDuration = 85ms (0.85 * roundTimeDuration=100ms) + // delay = min(300ms, 85ms - 10ms) = 75ms + return maxTime + }, + }, + ) + + start := time.Now() + result := sr.WaitIfCompetingBlock(context.Background(), []byte("pk"), 100, []byte("current_hash")) + elapsed := time.Since(start) + + assert.False(t, result, "should return false (proceed to sign) after capped delay expires") + // delay should be capped to 75ms (sigEndDuration 85ms - 10ms safety), not full 300ms + assert.Less(t, elapsed, 150*time.Millisecond, "delay should be capped, not full 300ms") +} + +func TestWaitIfCompetingBlock_RecordSignedNonceCalledBeforeBroadcast(t *testing.T) { + t.Parallel() + + container := consensusMocks.InitConsensusCore() + container.SetSigningHandler(&consensusMocks.SigningHandlerStub{ + CreateSignatureShareForPublicKeyCalled: func(msg []byte, index uint16, epoch uint32, publicKeyBytes []byte) ([]byte, error) { + return []byte("SIG"), nil + }, + }) + container.SetBroadcastMessenger(&consensusMocks.BroadcastMessengerMock{ + BroadcastConsensusMessageCalled: func(message *consensus.Message) error { + return expectedErr // broadcast fails + }, + }) + + consensusState := initializers.InitConsensusStateWithKeysHandler( + &testscommon.KeysHandlerStub{ + IsKeyManagedByCurrentNodeCalled: func(pkBytes []byte) bool { + return true + }, + }, + ) + ch := make(chan bool, 1) + sr, _ := spos.NewSubround( + bls.SrBlock, + bls.SrSignature, + bls.SrEndRound, + roundTimeDuration, + 0.25, + 0.85, + "(SIGNATURE)", + consensusState, + ch, + executeStoredMessages, + container, + chainID, + currentPid, + &statusHandler.AppStatusHandlerStub{}, + ) + sr.SetHeader(&block.Header{Nonce: 100}) + + recordCalled := false + srSignature, _ := v2.NewSubroundSignature( + sr, + &statusHandler.AppStatusHandlerStub{}, + &testscommon.SentSignatureTrackerStub{ + RecordSignedNonceCalled: func(pkBytes []byte, nonce uint64, headerHash []byte) { + recordCalled = true + }, + }, + &consensusMocks.SposWorkerMock{}, + &dataRetrieverMock.ThrottlerStub{}, + ) + + // broadcast will fail but RecordSignedNonce should still be called + result := srSignature.SendSignatureForManagedKey(context.Background(), 0, "A") + assert.False(t, result, "should return false because broadcast failed") + assert.True(t, recordCalled, "RecordSignedNonce should be called before broadcast") +} + +func TestWaitIfCompetingBlockForNode_NoCompetingBlockForAnyKey(t *testing.T) { + t.Parallel() + + sr := createSubroundSignatureForCompetingBlockTests( + &testscommon.SentSignatureTrackerStub{ + GetSignedHashCalled: func(pkBytes []byte, nonce uint64) ([]byte, bool) { + return nil, false // no key has previously signed + }, + }, + nil, + nil, + ) + + result := sr.WaitIfCompetingBlockForNode(context.Background(), 100, []byte("current_hash")) + assert.False(t, result, "should return false when no key has a competing block") +} + +func TestWaitIfCompetingBlockForNode_SameHashForAllKeys(t *testing.T) { + t.Parallel() + + currentHash := []byte("current_hash") + sr := createSubroundSignatureForCompetingBlockTests( + &testscommon.SentSignatureTrackerStub{ + GetSignedHashCalled: func(pkBytes []byte, nonce uint64) ([]byte, bool) { + return currentHash, true // all keys signed the same hash + }, + }, + nil, + nil, + ) + + result := sr.WaitIfCompetingBlockForNode(context.Background(), 100, currentHash) + assert.False(t, result, "should return false when all keys signed the same hash") +} + +func TestWaitIfCompetingBlockForNode_SelfKeyHasCompetingBlock(t *testing.T) { + container := consensusMocks.InitConsensusCore() + container.SetRoundHandler(&testscommon.RoundHandlerMock{ + TimeDurationCalled: func() time.Duration { + return 100 * time.Millisecond + }, + RemainingTimeCalled: func(startTime time.Time, maxTime time.Duration) time.Duration { + return maxTime + }, + }) + + consensusState := initializers.InitConsensusState() + ch := make(chan bool, 1) + + sr, _ := spos.NewSubround( + bls.SrBlock, + bls.SrSignature, + bls.SrEndRound, + roundTimeDuration, + 0.25, + 0.85, + "(SIGNATURE)", + consensusState, + ch, + executeStoredMessages, + container, + chainID, + currentPid, + &statusHandler.AppStatusHandlerStub{}, + ) + + selfPk := sr.SelfPubKey() + + srSignature, _ := v2.NewSubroundSignature( + sr, + &statusHandler.AppStatusHandlerStub{}, + &testscommon.SentSignatureTrackerStub{ + GetSignedHashCalled: func(pkBytes []byte, nonce uint64) ([]byte, bool) { + if string(pkBytes) == selfPk { + return []byte("different_hash"), true + } + return nil, false + }, + }, + &consensusMocks.SposWorkerMock{}, + &dataRetrieverMock.ThrottlerStub{}, + ) + + srSignature.SetHeader(&block.Header{Nonce: 100}) + srSignature.SetData([]byte("current_hash")) + + start := time.Now() + result := srSignature.WaitIfCompetingBlockForNode(context.Background(), 100, []byte("current_hash")) + elapsed := time.Since(start) + + // Should have waited (delay from round start) and returned false (no proof arrived) + assert.False(t, result, "should return false after delay expires") + assert.GreaterOrEqual(t, elapsed, 40*time.Millisecond, "should have waited for competing block delay") +} + +func TestWaitIfCompetingBlockForNode_ManagedKeyHasCompetingBlock(t *testing.T) { + container := consensusMocks.InitConsensusCore() + container.SetRoundHandler(&testscommon.RoundHandlerMock{ + TimeDurationCalled: func() time.Duration { + return 100 * time.Millisecond + }, + RemainingTimeCalled: func(startTime time.Time, maxTime time.Duration) time.Duration { + return maxTime + }, + }) + + // Self key has no competing block, but a managed key does + consensusState := initializers.InitConsensusStateWithKeysHandler( + &testscommon.KeysHandlerStub{ + IsKeyManagedByCurrentNodeCalled: func(pkBytes []byte) bool { + // Mark the first consensus group member as managed + return string(pkBytes) == "A" + }, + }, + ) + ch := make(chan bool, 1) + + sr, _ := spos.NewSubround( + bls.SrBlock, + bls.SrSignature, + bls.SrEndRound, + roundTimeDuration, + 0.25, + 0.85, + "(SIGNATURE)", + consensusState, + ch, + executeStoredMessages, + container, + chainID, + currentPid, + &statusHandler.AppStatusHandlerStub{}, + ) + + selfPk := sr.SelfPubKey() + + srSignature, _ := v2.NewSubroundSignature( + sr, + &statusHandler.AppStatusHandlerStub{}, + &testscommon.SentSignatureTrackerStub{ + GetSignedHashCalled: func(pkBytes []byte, nonce uint64) ([]byte, bool) { + if string(pkBytes) == selfPk { + // Self key: no competing block + return nil, false + } + if string(pkBytes) == "A" { + // Managed key "A": has competing block + return []byte("old_hash"), true + } + return nil, false + }, + }, + &consensusMocks.SposWorkerMock{}, + &dataRetrieverMock.ThrottlerStub{}, + ) + + srSignature.SetHeader(&block.Header{Nonce: 100}) + srSignature.SetData([]byte("current_hash")) + + start := time.Now() + result := srSignature.WaitIfCompetingBlockForNode(context.Background(), 100, []byte("current_hash")) + elapsed := time.Since(start) + + // Managed key "A" has a competing block, so the node should wait + assert.False(t, result, "should return false after delay expires (no proof arrived)") + assert.GreaterOrEqual(t, elapsed, 40*time.Millisecond, "should have waited for competing block delay") +} + +func TestWaitIfCompetingBlockForNode_WaitsOnceNotPerKey(t *testing.T) { + t.Parallel() + + // This test verifies that waitIfCompetingBlockForNode returns after a single wait + // even when multiple keys have competing blocks - it should not wait per-key. + container := consensusMocks.InitConsensusCore() + container.SetRoundHandler(&testscommon.RoundHandlerMock{ + TimeDurationCalled: func() time.Duration { + return 100 * time.Millisecond + }, + RemainingTimeCalled: func(startTime time.Time, maxTime time.Duration) time.Duration { + return maxTime + }, + }) + + consensusState := initializers.InitConsensusState() + ch := make(chan bool, 1) + + sr, _ := spos.NewSubround( + bls.SrBlock, + bls.SrSignature, + bls.SrEndRound, + roundTimeDuration, + 0.25, + 0.85, + "(SIGNATURE)", + consensusState, + ch, + executeStoredMessages, + container, + chainID, + currentPid, + &statusHandler.AppStatusHandlerStub{}, + ) + + srSignature, _ := v2.NewSubroundSignature( + sr, + &statusHandler.AppStatusHandlerStub{}, + &testscommon.SentSignatureTrackerStub{ + GetSignedHashCalled: func(pkBytes []byte, nonce uint64) ([]byte, bool) { + // ALL keys have signed a different hash + return []byte("old_hash"), true + }, + }, + &consensusMocks.SposWorkerMock{}, + &dataRetrieverMock.ThrottlerStub{}, + ) + + srSignature.SetHeader(&block.Header{Nonce: 100}) + srSignature.SetData([]byte("current_hash")) + + start := time.Now() + result := srSignature.WaitIfCompetingBlockForNode(context.Background(), 100, []byte("current_hash")) + elapsed := time.Since(start) + + // Should return after ONE wait, not multiple + assert.False(t, result) + // targetTime = 50ms, sigEndDuration = 85ms, delay = min(50ms, 75ms) = 50ms + // Should only wait once (~50ms), not per-key + assert.Less(t, elapsed, 120*time.Millisecond, "should have waited only once, not per-key") +} + +func TestShouldSendProof_GracePeriodNotExpired(t *testing.T) { + t.Parallel() + + container := consensusMocks.InitConsensusCore() + container.SetEquivalentProofsPool(&dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return false + }, + }) + container.SetRoundHandler(&testscommon.RoundHandlerMock{ + TimeDurationCalled: func() time.Duration { + return 600 * time.Millisecond + }, + RemainingTimeCalled: func(startTime time.Time, maxTime time.Duration) time.Duration { + // positive remaining: grace period not expired + return 100 * time.Millisecond + }, + IndexCalled: func() int64 { + return 1 + }, + }) + + srEndRound := initSubroundEndRoundWithContainer(container, &statusHandler.AppStatusHandlerStub{}) + + // Set self as consensus member so the node is eligible to send proof + leader, err := srEndRound.GetLeader() + require.NoError(t, err) + srEndRound.SetSelfPubKey(leader) + + result := srEndRound.ShouldSendProof() + assert.True(t, result, "should return true when grace period has not expired and node is in consensus") +} + +func TestShouldSendProof_GracePeriodExpired(t *testing.T) { + t.Parallel() + + container := consensusMocks.InitConsensusCore() + container.SetRoundHandler(&testscommon.RoundHandlerMock{ + TimeDurationCalled: func() time.Duration { + return 600 * time.Millisecond + }, + RemainingTimeCalled: func(startTime time.Time, maxTime time.Duration) time.Duration { + // negative remaining: grace period expired + return -100 * time.Millisecond + }, + IndexCalled: func() int64 { + return 5 + }, + }) + + srEndRound := initSubroundEndRoundWithContainer(container, &statusHandler.AppStatusHandlerStub{}) + + result := srEndRound.ShouldSendProof() + assert.False(t, result, "should return false when grace period has expired") +} + +func TestShouldSendProof_ProofAlreadyExists(t *testing.T) { + t.Parallel() + + container := consensusMocks.InitConsensusCore() + container.SetEquivalentProofsPool(&dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true // proof already in pool + }, + }) + container.SetRoundHandler(&testscommon.RoundHandlerMock{ + TimeDurationCalled: func() time.Duration { + return 600 * time.Millisecond + }, + RemainingTimeCalled: func(startTime time.Time, maxTime time.Duration) time.Duration { + return 500 * time.Millisecond // grace period not expired + }, + }) + + srEndRound := initSubroundEndRoundWithContainer(container, &statusHandler.AppStatusHandlerStub{}) + + result := srEndRound.ShouldSendProof() + assert.False(t, result, "should return false when proof already exists in pool") +} diff --git a/consensus/spos/bls/v2/subroundSignature_test.go b/consensus/spos/bls/v2/subroundSignature_test.go index 40470f48f1d..05c26afd5f6 100644 --- a/consensus/spos/bls/v2/subroundSignature_test.go +++ b/consensus/spos/bls/v2/subroundSignature_test.go @@ -35,8 +35,9 @@ func initSubroundSignatureWithContainer(container *spos.ConsensusCore) v2.Subrou bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -74,8 +75,9 @@ func TestNewSubroundSignature(t *testing.T) { bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -170,8 +172,9 @@ func TestSubroundSignature_NewSubroundSignatureNilConsensusStateShouldFail(t *te bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -206,8 +209,9 @@ func TestSubroundSignature_NewSubroundSignatureNilHasherShouldFail(t *testing.T) bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -241,8 +245,9 @@ func TestSubroundSignature_NewSubroundSignatureNilMultiSignerContainerShouldFail bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -276,8 +281,9 @@ func TestSubroundSignature_NewSubroundSignatureNilRoundHandlerShouldFail(t *test bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -312,8 +318,9 @@ func TestSubroundSignature_NewSubroundSignatureNilSyncTimerShouldFail(t *testing bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -347,8 +354,9 @@ func TestSubroundSignature_NewSubroundSignatureNilAppStatusHandlerShouldFail(t * bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -382,8 +390,9 @@ func TestSubroundSignature_NewSubroundSignatureShouldWork(t *testing.T) { bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -516,8 +525,9 @@ func TestSubroundSignature_DoSignatureJob(t *testing.T) { bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -605,8 +615,9 @@ func TestSubroundSignature_SendSignature(t *testing.T) { bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -631,7 +642,7 @@ func TestSubroundSignature_SendSignature(t *testing.T) { &dataRetrieverMock.ThrottlerStub{}, ) - r := srSignature.SendSignatureForManagedKey(0, "a") + r := srSignature.SendSignatureForManagedKey(context.Background(), 0, "a") assert.False(t, r) }) @@ -672,8 +683,9 @@ func TestSubroundSignature_SendSignature(t *testing.T) { bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -698,7 +710,7 @@ func TestSubroundSignature_SendSignature(t *testing.T) { &dataRetrieverMock.ThrottlerStub{}, ) - r := srSignature.SendSignatureForManagedKey(1, "a") + r := srSignature.SendSignatureForManagedKey(context.Background(), 1, "a") assert.False(t, r) }) @@ -739,8 +751,9 @@ func TestSubroundSignature_SendSignature(t *testing.T) { bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -767,7 +780,7 @@ func TestSubroundSignature_SendSignature(t *testing.T) { &dataRetrieverMock.ThrottlerStub{}, ) - _ = srSignature.SendSignatureForManagedKey(1, "a") + _ = srSignature.SendSignatureForManagedKey(context.Background(), 1, "a") assert.True(t, varCalled) }) @@ -805,8 +818,9 @@ func TestSubroundSignature_DoSignatureJobForManagedKeys(t *testing.T) { bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, @@ -885,8 +899,9 @@ func TestSubroundSignature_DoSignatureJobForManagedKeys(t *testing.T) { bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(70*roundTimeDuration/100), - int64(85*roundTimeDuration/100), + roundTimeDuration, + 0.7, + 0.85, "(SIGNATURE)", consensusState, ch, diff --git a/consensus/spos/bls/v2/subroundStartRound.go b/consensus/spos/bls/v2/subroundStartRound.go index 4e3be13f5cd..ade857a645a 100644 --- a/consensus/spos/bls/v2/subroundStartRound.go +++ b/consensus/spos/bls/v2/subroundStartRound.go @@ -11,6 +11,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" outportcore "github.com/multiversx/mx-chain-core-go/data/outport" + "github.com/multiversx/mx-chain-go/consensus/spos/bls" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/consensus/spos" @@ -103,6 +104,8 @@ func (sr *subroundStartRound) doStartRoundJob(_ context.Context) bool { sr.worker.ResetConsensusMessages() sr.worker.ResetInvalidSignersCache() + sr.worker.ConsensusMetrics().ResetInstanceValues() + return true } @@ -129,6 +132,18 @@ func (sr *subroundStartRound) initCurrentRound() bool { return false } + currentHeader := sr.Blockchain().GetCurrentBlockHeader() + if !check.IfNil(currentHeader) && int64(currentHeader.GetRound()) == sr.RoundHandler().Index() { + log.Debug("initCurrentRound: header for current consensus already committed, setting all subrounds as finished") + + sr.SetStatus(sr.Current(), spos.SsFinished) + sr.SetStatus(bls.SrBlock, spos.SsFinished) + sr.SetStatus(bls.SrSignature, spos.SsFinished) + sr.SetStatus(bls.SrEndRound, spos.SsFinished) + + return true + } + sr.AppStatusHandler().SetStringValue(common.MetricConsensusRoundState, "") err := sr.generateNextConsensusGroup(sr.RoundHandler().Index()) @@ -268,13 +283,21 @@ func (sr *subroundStartRound) indexRoundIfNeeded(pubKeys []string) { round := sr.RoundHandler().Index() + headerTimestamp := sr.GetUnixTimestampForHeader(epoch) + timestampSec, timestampMs, err := common.PrepareTimestampBasedOnHeaderData(headerTimestamp, epoch, sr.EnableEpochsHandler()) + if err != nil { + log.Debug("subroundStartRound.indexRoundIfNeeded cannot prepare timestamp", "error", err.Error(), "epoch", epoch, "round", round) + return + } + roundInfo := &outportcore.RoundInfo{ Round: uint64(round), SignersIndexes: make([]uint64, 0), BlockWasProposed: false, ShardId: shardId, Epoch: epoch, - Timestamp: uint64(sr.GetRoundTimeStamp().Unix()), + Timestamp: timestampSec, + TimestampMs: timestampMs, } roundsInfo := &outportcore.RoundsInfo{ ShardID: shardId, @@ -342,9 +365,12 @@ func (sr *subroundStartRound) EpochStartAction(hdr data.HeaderHandler) { func (sr *subroundStartRound) changeEpoch(currentEpoch uint32) { epochNodes, err := sr.NodesCoordinator().GetConsensusWhitelistedNodes(currentEpoch) if err != nil { - panic(fmt.Sprintf("consensus changing epoch failed with error %s", err.Error())) + log.Error("consensus changing epoch failed", "error", err.Error()) + return } + sr.worker.ConsensusMetrics().ResetAverages() + sr.SetEligibleList(epochNodes) } diff --git a/consensus/spos/bls/v2/subroundStartRound_test.go b/consensus/spos/bls/v2/subroundStartRound_test.go index fa6853f0314..02a33ebcfba 100644 --- a/consensus/spos/bls/v2/subroundStartRound_test.go +++ b/consensus/spos/bls/v2/subroundStartRound_test.go @@ -15,6 +15,7 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/consensus" "github.com/multiversx/mx-chain-go/testscommon/consensus/initializers" "github.com/multiversx/mx-chain-go/testscommon/outport" + "github.com/multiversx/mx-chain-go/testscommon/statusHandler" "github.com/stretchr/testify/assert" @@ -25,7 +26,6 @@ import ( "github.com/multiversx/mx-chain-go/sharding/nodesCoordinator" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" - "github.com/multiversx/mx-chain-go/testscommon/statusHandler" ) var expErr = fmt.Errorf("expected error") @@ -35,7 +35,12 @@ func defaultSubroundStartRoundFromSubround(sr *spos.Subround) (v2.SubroundStartR sr, v2.ProcessingThresholdPercent, &testscommon.SentSignatureTrackerStub{}, - &consensus.SposWorkerMock{}, + &consensus.SposWorkerMock{ + ConsensusMetricsCalled: func() spos.ConsensusMetricsHandler { + consensusMetrics, _ := spos.NewConsensusMetrics(sr.AppStatusHandler()) + return consensusMetrics + }, + }, ) return startRound, err @@ -46,7 +51,12 @@ func defaultWithoutErrorSubroundStartRoundFromSubround(sr *spos.Subround) v2.Sub sr, v2.ProcessingThresholdPercent, &testscommon.SentSignatureTrackerStub{}, - &consensus.SposWorkerMock{}, + &consensus.SposWorkerMock{ + ConsensusMetricsCalled: func() spos.ConsensusMetricsHandler { + consensusMetrics, _ := spos.NewConsensusMetrics(sr.AppStatusHandler()) + return consensusMetrics + }, + }, ) return startRound @@ -62,8 +72,9 @@ func defaultSubround( -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, ch, @@ -71,7 +82,7 @@ func defaultSubround( container, chainID, currentPid, - &statusHandler.AppStatusHandlerStub{}, + statusHandler.NewAppStatusHandlerMock(), ) } @@ -83,7 +94,12 @@ func initSubroundStartRoundWithContainer(container spos.ConsensusCoreHandler) v2 sr, v2.ProcessingThresholdPercent, &testscommon.SentSignatureTrackerStub{}, - &consensus.SposWorkerMock{}, + &consensus.SposWorkerMock{ + ConsensusMetricsCalled: func() spos.ConsensusMetricsHandler { + consensusMetrics, _ := spos.NewConsensusMetrics(sr.AppStatusHandler()) + return consensusMetrics + }, + }, ) return srStartRound @@ -104,8 +120,9 @@ func TestNewSubroundStartRound(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(START_ROUND)", consensusState, ch, @@ -518,8 +535,9 @@ func TestSubroundStartRound_InitCurrentRoundShouldMetrics(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(START_ROUND)", consensusState, ch, @@ -570,8 +588,9 @@ func TestSubroundStartRound_InitCurrentRoundShouldMetrics(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(START_ROUND)", consensusState, ch, @@ -621,8 +640,9 @@ func TestSubroundStartRound_InitCurrentRoundShouldMetrics(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(START_ROUND)", consensusState, ch, @@ -683,8 +703,9 @@ func TestSubroundStartRound_InitCurrentRoundShouldMetrics(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(START_ROUND)", consensusState, ch, @@ -749,8 +770,9 @@ func TestSubroundStartRound_InitCurrentRoundShouldMetrics(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(START_ROUND)", consensusState, ch, @@ -781,8 +803,9 @@ func buildDefaultSubround(container spos.ConsensusCoreHandler) *spos.Subround { -1, bls.SrStartRound, bls.SrBlock, - int64(85*roundTimeDuration/100), - int64(95*roundTimeDuration/100), + roundTimeDuration, + 0.85, + 0.95, "(START_ROUND)", consensusState, ch, @@ -790,7 +813,7 @@ func buildDefaultSubround(container spos.ConsensusCoreHandler) *spos.Subround { container, chainID, currentPid, - &statusHandler.AppStatusHandlerStub{}, + statusHandler.NewAppStatusHandlerMock(), ) return sr @@ -1025,23 +1048,9 @@ func TestSubroundStartRound_IndexRoundIfNeededDifferentShardIdFail(t *testing.T) func TestSubroundStartRound_changeEpoch(t *testing.T) { t.Parallel() - expectPanic := func() { - if recover() == nil { - require.Fail(t, "expected panic") - } - } - - expectNoPanic := func() { - if recover() != nil { - require.Fail(t, "expected no panic") - } - } - - t.Run("error returned by nodes coordinator should error", func(t *testing.T) { + t.Run("error returned by nodes coordinator should not set eligible list", func(t *testing.T) { t.Parallel() - defer expectPanic() - container := consensus.InitConsensusCore() exErr := fmt.Errorf("expected error") container.SetNodesCoordinator( @@ -1057,15 +1066,22 @@ func TestSubroundStartRound_changeEpoch(t *testing.T) { sr, v2.ProcessingThresholdPercent, &testscommon.SentSignatureTrackerStub{}, - &consensus.SposWorkerMock{}, + &consensus.SposWorkerMock{ + ConsensusMetricsCalled: func() spos.ConsensusMetricsHandler { + consensusMetrics, _ := spos.NewConsensusMetrics(sr.AppStatusHandler()) + return consensusMetrics + }, + }, ) require.Nil(t, err) startRound.ChangeEpoch(1) + + require.False(t, startRound.IsNodeInEligibleList("aaa")) + require.False(t, startRound.IsNodeInEligibleList("bbb")) }) - t.Run("success - no panic", func(t *testing.T) { - t.Parallel() - defer expectNoPanic() + t.Run("should work", func(t *testing.T) { + t.Parallel() container := consensus.InitConsensusCore() expectedKeys := map[string]struct{}{ @@ -1086,10 +1102,19 @@ func TestSubroundStartRound_changeEpoch(t *testing.T) { sr, v2.ProcessingThresholdPercent, &testscommon.SentSignatureTrackerStub{}, - &consensus.SposWorkerMock{}, + &consensus.SposWorkerMock{ + ConsensusMetricsCalled: func() spos.ConsensusMetricsHandler { + consensusMetrics, _ := spos.NewConsensusMetrics(sr.AppStatusHandler()) + return consensusMetrics + }, + }, ) require.Nil(t, err) startRound.ChangeEpoch(1) + + require.True(t, startRound.IsNodeInEligibleList("aaa")) + require.True(t, startRound.IsNodeInEligibleList("bbb")) + require.False(t, startRound.IsNodeInEligibleList("ccc")) }) } @@ -1115,3 +1140,74 @@ func TestSubroundStartRound_GenerateNextConsensusGroupShouldReturnErr(t *testing assert.Equal(t, expErr, err2) } + +func TestSubroundStartRound_ConsensusMetricsResetAveragesShouldWork(t *testing.T) { + t.Parallel() + + container := consensus.InitConsensusCore() + + sr := buildDefaultSubround(container) + + consensusMetrics, _ := spos.NewConsensusMetrics(sr.AppStatusHandler()) + worker := consensus.SposWorkerMock{ + ConsensusMetricsCalled: func() spos.ConsensusMetricsHandler { + return consensusMetrics + }, + } + startRound, err := v2.NewSubroundStartRound( + sr, + v2.ProcessingThresholdPercent, + &testscommon.SentSignatureTrackerStub{}, + &worker, + ) + require.Nil(t, err) + + appStatusHandler := sr.AppStatusHandler().(*statusHandler.AppStatusHandlerMock) + cm := worker.ConsensusMetrics().(*spos.ConsensusMetrics) + + cm.SetBlockReceivedOrSent(uint64(150)) + cm.SetProofReceived(uint64(200)) + + assert.NotZero(t, appStatusHandler.GetUint64(common.MetricAvgReceivedOrSentProposedBlock)) + assert.NotZero(t, appStatusHandler.GetUint64(common.MetricAvgReceivedProof)) + + startRound.EpochStartAction(&testscommon.HeaderHandlerStub{EpochField: 2}) + + assert.Zero(t, appStatusHandler.GetUint64(common.MetricAvgReceivedOrSentProposedBlock)) + assert.Zero(t, appStatusHandler.GetUint64(common.MetricAvgReceivedProof)) +} + +func TestSubroundStartRound_ConsensusMetricsResetInstanceValuesShouldWork(t *testing.T) { + t.Parallel() + + container := consensus.InitConsensusCore() + + sr := buildDefaultSubround(container) + + consensusMetrics, _ := spos.NewConsensusMetrics(sr.AppStatusHandler()) + worker := consensus.SposWorkerMock{ + ConsensusMetricsCalled: func() spos.ConsensusMetricsHandler { + return consensusMetrics + }, + } + startRound, err := v2.NewSubroundStartRound( + sr, + v2.ProcessingThresholdPercent, + &testscommon.SentSignatureTrackerStub{}, + &worker, + ) + require.Nil(t, err) + cm := worker.ConsensusMetrics().(*spos.ConsensusMetrics) + appStatusHandler := sr.AppStatusHandler().(*statusHandler.AppStatusHandlerMock) + + cm.SetBlockReceivedOrSent(uint64(150)) + cm.SetProofReceived(uint64(200)) + + assert.Equal(t, uint64(150), appStatusHandler.GetUint64(common.MetricReceivedOrSentProposedBlock)) + assert.Equal(t, uint64(50), appStatusHandler.GetUint64(common.MetricReceivedProof)) + + startRound.DoStartRoundJob() + + assert.Equal(t, uint64(0), appStatusHandler.GetUint64(common.MetricReceivedOrSentProposedBlock)) + assert.Equal(t, uint64(0), appStatusHandler.GetUint64(common.MetricReceivedProof)) +} diff --git a/consensus/spos/consensusCore.go b/consensus/spos/consensusCore.go index c255d704822..30fbc4a0a06 100644 --- a/consensus/spos/consensusCore.go +++ b/consensus/spos/consensusCore.go @@ -20,6 +20,7 @@ import ( type ConsensusCore struct { blockChain data.ChainHandler blockProcessor process.BlockProcessor + executionManager process.ExecutionManager bootstrapper process.Bootstrapper broadcastMessenger consensus.BroadcastMessenger chronologyHandler consensus.ChronologyHandler @@ -41,15 +42,18 @@ type ConsensusCore struct { peerBlacklistHandler consensus.PeerBlacklistHandler signingHandler consensus.SigningHandler enableEpochsHandler common.EnableEpochsHandler + enableRoundsHandler common.EnableRoundsHandler equivalentProofsPool consensus.EquivalentProofsPool epochNotifier process.EpochNotifier invalidSignersCache InvalidSignersCache + aotSelector process.AOTTransactionSelector } // ConsensusCoreArgs store all arguments that are needed to create a ConsensusCore object type ConsensusCoreArgs struct { BlockChain data.ChainHandler BlockProcessor process.BlockProcessor + ExecutionManager process.ExecutionManager Bootstrapper process.Bootstrapper BroadcastMessenger consensus.BroadcastMessenger ChronologyHandler consensus.ChronologyHandler @@ -71,9 +75,11 @@ type ConsensusCoreArgs struct { PeerBlacklistHandler consensus.PeerBlacklistHandler SigningHandler consensus.SigningHandler EnableEpochsHandler common.EnableEpochsHandler + EnableRoundsHandler common.EnableRoundsHandler EquivalentProofsPool consensus.EquivalentProofsPool EpochNotifier process.EpochNotifier InvalidSignersCache InvalidSignersCache + AOTSelector process.AOTTransactionSelector } // NewConsensusCore creates a new ConsensusCore instance @@ -83,6 +89,7 @@ func NewConsensusCore( consensusCore := &ConsensusCore{ blockChain: args.BlockChain, blockProcessor: args.BlockProcessor, + executionManager: args.ExecutionManager, bootstrapper: args.Bootstrapper, broadcastMessenger: args.BroadcastMessenger, chronologyHandler: args.ChronologyHandler, @@ -104,9 +111,11 @@ func NewConsensusCore( peerBlacklistHandler: args.PeerBlacklistHandler, signingHandler: args.SigningHandler, enableEpochsHandler: args.EnableEpochsHandler, + enableRoundsHandler: args.EnableRoundsHandler, equivalentProofsPool: args.EquivalentProofsPool, epochNotifier: args.EpochNotifier, invalidSignersCache: args.InvalidSignersCache, + aotSelector: args.AOTSelector, } err := ValidateConsensusCore(consensusCore) @@ -132,6 +141,11 @@ func (cc *ConsensusCore) BlockProcessor() process.BlockProcessor { return cc.blockProcessor } +// ExecutionManager gets the ExecutionManager stored in the ConsensusCore +func (cc *ConsensusCore) ExecutionManager() process.ExecutionManager { + return cc.executionManager +} + // BootStrapper gets the Bootstrapper stored in the ConsensusCore func (cc *ConsensusCore) BootStrapper() process.Bootstrapper { return cc.bootstrapper @@ -237,6 +251,11 @@ func (cc *ConsensusCore) EnableEpochsHandler() common.EnableEpochsHandler { return cc.enableEpochsHandler } +// EnableRoundsHandler returns the enable rounds handler component +func (cc *ConsensusCore) EnableRoundsHandler() common.EnableRoundsHandler { + return cc.enableRoundsHandler +} + // EquivalentProofsPool returns the equivalent proofs component func (cc *ConsensusCore) EquivalentProofsPool() consensus.EquivalentProofsPool { return cc.equivalentProofsPool @@ -257,6 +276,11 @@ func (cc *ConsensusCore) SetBlockProcessor(blockProcessor process.BlockProcessor cc.blockProcessor = blockProcessor } +// SetExecutionManager sets execution manager +func (cc *ConsensusCore) SetExecutionManager(executionManager process.ExecutionManager) { + cc.executionManager = executionManager +} + // SetBootStrapper sets process bootstrapper func (cc *ConsensusCore) SetBootStrapper(bootstrapper process.Bootstrapper) { cc.bootstrapper = bootstrapper @@ -357,11 +381,16 @@ func (cc *ConsensusCore) SetSigningHandler(signingHandler consensus.SigningHandl cc.signingHandler = signingHandler } -// SetEnableEpochsHandler sets enable eopchs handler +// SetEnableEpochsHandler sets enable epochs handler func (cc *ConsensusCore) SetEnableEpochsHandler(enableEpochsHandler common.EnableEpochsHandler) { cc.enableEpochsHandler = enableEpochsHandler } +// SetEnableRoundsHandler sets enable rounds handler +func (cc *ConsensusCore) SetEnableRoundsHandler(enableRoundsHandler common.EnableRoundsHandler) { + cc.enableRoundsHandler = enableRoundsHandler +} + // SetEquivalentProofsPool sets equivalent proofs pool func (cc *ConsensusCore) SetEquivalentProofsPool(proofPool consensus.EquivalentProofsPool) { cc.equivalentProofsPool = proofPool @@ -377,6 +406,16 @@ func (cc *ConsensusCore) SetInvalidSignersCache(cache InvalidSignersCache) { cc.invalidSignersCache = cache } +// AOTSelector returns the AOT transaction selector +func (cc *ConsensusCore) AOTSelector() process.AOTTransactionSelector { + return cc.aotSelector +} + +// SetAOTSelector sets the AOT transaction selector +func (cc *ConsensusCore) SetAOTSelector(selector process.AOTTransactionSelector) { + cc.aotSelector = selector +} + // IsInterfaceNil returns true if there is no value under the interface func (cc *ConsensusCore) IsInterfaceNil() bool { return cc == nil diff --git a/consensus/spos/consensusCoreValidator.go b/consensus/spos/consensusCoreValidator.go index e3033fa24a9..d4ce7f43494 100644 --- a/consensus/spos/consensusCoreValidator.go +++ b/consensus/spos/consensusCoreValidator.go @@ -15,6 +15,9 @@ func ValidateConsensusCore(container ConsensusCoreHandler) error { if check.IfNil(container.BlockProcessor()) { return ErrNilBlockProcessor } + if check.IfNil(container.ExecutionManager()) { + return ErrNilExecutionManager + } if check.IfNil(container.BootStrapper()) { return ErrNilBootstrapper } @@ -79,6 +82,9 @@ func ValidateConsensusCore(container ConsensusCoreHandler) error { if check.IfNil(container.EnableEpochsHandler()) { return ErrNilEnableEpochsHandler } + if check.IfNil(container.EnableRoundsHandler()) { + return ErrNilEnableRoundsHandler + } if check.IfNil(container.EquivalentProofsPool()) { return ErrNilEquivalentProofPool } diff --git a/consensus/spos/consensusCoreValidator_test.go b/consensus/spos/consensusCoreValidator_test.go index f199cd0b7e5..e1b29f7e8d9 100644 --- a/consensus/spos/consensusCoreValidator_test.go +++ b/consensus/spos/consensusCoreValidator_test.go @@ -3,6 +3,7 @@ package spos_test import ( "testing" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" "github.com/stretchr/testify/assert" "github.com/multiversx/mx-chain-go/consensus/mock" @@ -16,6 +17,7 @@ import ( epochNotifierMock "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" epochstartmock "github.com/multiversx/mx-chain-go/testscommon/epochstartmock" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" + "github.com/multiversx/mx-chain-go/testscommon/round" "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" ) @@ -23,12 +25,13 @@ func initConsensusDataContainer() *spos.ConsensusCore { marshalizerMock := mock.MarshalizerMock{} blockChain := &testscommon.ChainHandlerStub{} blockProcessorMock := consensusMocks.InitBlockProcessorMock(marshalizerMock) + executionManager := &processMocks.ExecutionManagerMock{} bootstrapperMock := &bootstrapperStubs.BootstrapperStub{} broadcastMessengerMock := &consensusMocks.BroadcastMessengerMock{} chronologyHandlerMock := consensusMocks.InitChronologyHandlerMock() multiSignerMock := cryptoMocks.NewMultiSigner() hasherMock := &hashingMocks.HasherMock{} - roundHandlerMock := &consensusMocks.RoundHandlerMock{} + roundHandlerMock := &round.RoundHandlerMock{} epochStartSubscriber := &epochstartmock.EpochStartNotifierStub{} shardCoordinatorMock := mock.ShardCoordinatorMock{} syncTimerMock := &consensusMocks.SyncTimerMock{} @@ -44,6 +47,7 @@ func initConsensusDataContainer() *spos.ConsensusCore { multiSignerContainer := cryptoMocks.NewMultiSignerContainerMock(multiSignerMock) signingHandler := &consensusMocks.SigningHandlerStub{} enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{} + enableRoundsHandler := &testscommon.EnableRoundsHandlerStub{} proofsPool := &dataRetriever.ProofsPoolMock{} epochNotifier := &epochNotifierMock.EpochNotifierStub{} invalidSignersCache := &consensusMocks.InvalidSignersCacheMock{} @@ -51,6 +55,7 @@ func initConsensusDataContainer() *spos.ConsensusCore { consensusCore, _ := spos.NewConsensusCore(&spos.ConsensusCoreArgs{ BlockChain: blockChain, BlockProcessor: blockProcessorMock, + ExecutionManager: executionManager, Bootstrapper: bootstrapperMock, BroadcastMessenger: broadcastMessengerMock, ChronologyHandler: chronologyHandlerMock, @@ -72,6 +77,7 @@ func initConsensusDataContainer() *spos.ConsensusCore { PeerBlacklistHandler: peerBlacklistHandler, SigningHandler: signingHandler, EnableEpochsHandler: enableEpochsHandler, + EnableRoundsHandler: enableRoundsHandler, EquivalentProofsPool: proofsPool, EpochNotifier: epochNotifier, InvalidSignersCache: invalidSignersCache, @@ -110,6 +116,17 @@ func TestConsensusContainerValidator_ValidateNilProcessorShouldFail(t *testing.T assert.Equal(t, spos.ErrNilBlockProcessor, err) } +func TestConsensusContainerValidator_ValidateNilExecutionManagerShouldFail(t *testing.T) { + t.Parallel() + + container := initConsensusDataContainer() + container.SetExecutionManager(nil) + + err := spos.ValidateConsensusCore(container) + + assert.Equal(t, spos.ErrNilExecutionManager, err) +} + func TestConsensusContainerValidator_ValidateNilBootstrapperShouldFail(t *testing.T) { t.Parallel() @@ -297,6 +314,17 @@ func TestConsensusContainerValidator_ValidateNilEnableEpochsHandlerShouldFail(t assert.Equal(t, spos.ErrNilEnableEpochsHandler, err) } +func TestConsensusContainerValidator_ValidateNilEnableRoundsHandlerShouldFail(t *testing.T) { + t.Parallel() + + container := initConsensusDataContainer() + container.SetEnableRoundsHandler(nil) + + err := spos.ValidateConsensusCore(container) + + assert.Equal(t, spos.ErrNilEnableRoundsHandler, err) +} + func TestConsensusContainerValidator_ValidateNilBroadcastMessengerShouldFail(t *testing.T) { t.Parallel() diff --git a/consensus/spos/consensusCore_test.go b/consensus/spos/consensusCore_test.go index 1f44827f857..a874a11ccb0 100644 --- a/consensus/spos/consensusCore_test.go +++ b/consensus/spos/consensusCore_test.go @@ -18,6 +18,7 @@ func createDefaultConsensusCoreArgs() *spos.ConsensusCoreArgs { args := &spos.ConsensusCoreArgs{ BlockChain: consensusCoreMock.Blockchain(), BlockProcessor: consensusCoreMock.BlockProcessor(), + ExecutionManager: consensusCoreMock.ExecutionManager(), Bootstrapper: consensusCoreMock.BootStrapper(), BroadcastMessenger: consensusCoreMock.BroadcastMessenger(), ChronologyHandler: consensusCoreMock.Chronology(), @@ -39,6 +40,7 @@ func createDefaultConsensusCoreArgs() *spos.ConsensusCoreArgs { PeerBlacklistHandler: consensusCoreMock.PeerBlacklistHandler(), SigningHandler: consensusCoreMock.SigningHandler(), EnableEpochsHandler: consensusCoreMock.EnableEpochsHandler(), + EnableRoundsHandler: consensusCoreMock.EnableRoundsHandler(), EquivalentProofsPool: consensusCoreMock.EquivalentProofsPool(), EpochNotifier: consensusCoreMock.EpochNotifier(), InvalidSignersCache: &consensus.InvalidSignersCacheMock{}, @@ -74,6 +76,20 @@ func TestConsensusCore_WithNilBlockProcessorShouldFail(t *testing.T) { assert.Equal(t, spos.ErrNilBlockProcessor, err) } +func TestConsensusCore_WithNilExecutionManagerShouldFail(t *testing.T) { + t.Parallel() + + args := createDefaultConsensusCoreArgs() + args.ExecutionManager = nil + + consensusCore, err := spos.NewConsensusCore( + args, + ) + + assert.Nil(t, consensusCore) + assert.Equal(t, spos.ErrNilExecutionManager, err) +} + func TestConsensusCore_WithNilBootstrapperShouldFail(t *testing.T) { t.Parallel() @@ -352,6 +368,20 @@ func TestConsensusCore_WithNilEnableEpochsHandlerShouldFail(t *testing.T) { assert.Equal(t, spos.ErrNilEnableEpochsHandler, err) } +func TestConsensusCore_WithNilEnableRoundsHandlerShouldFail(t *testing.T) { + t.Parallel() + + args := createDefaultConsensusCoreArgs() + args.EnableRoundsHandler = nil + + consensusCore, err := spos.NewConsensusCore( + args, + ) + + assert.Nil(t, consensusCore) + assert.Equal(t, spos.ErrNilEnableRoundsHandler, err) +} + func TestConsensusCore_WithNilEpochStartRegistrationHandlerShouldFail(t *testing.T) { t.Parallel() diff --git a/consensus/spos/consensusMessageValidator.go b/consensus/spos/consensusMessageValidator.go index c2a63264c75..64801fd6fae 100644 --- a/consensus/spos/consensusMessageValidator.go +++ b/consensus/spos/consensusMessageValidator.go @@ -111,7 +111,7 @@ func checkArgsConsensusMessageValidator(args ArgsConsensusMessageValidator) erro } func (cmv *consensusMessageValidator) getPublicKeyBitmapSize() int { - sizeConsensus := cmv.consensusState.consensusGroupSize + sizeConsensus := cmv.consensusState.ConsensusGroupSize() bitmapSize := sizeConsensus / 8 if sizeConsensus%8 != 0 { bitmapSize++ diff --git a/consensus/spos/consensusMetrics.go b/consensus/spos/consensusMetrics.go new file mode 100644 index 00000000000..abde897dc41 --- /dev/null +++ b/consensus/spos/consensusMetrics.go @@ -0,0 +1,123 @@ +package spos + +import ( + "sync" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/common" +) + +// ConsensusMetrics keeps track of block delays and computes average delays +type ConsensusMetrics struct { + blockReceivedOrSentDelay uint64 + + blockReceivedDelaySum uint64 + blockReceivedCount uint64 + proofReceivedDelaySum uint64 + proofReceivedCount uint64 + + isBlockAlreadyReceived bool + appStatusHandler core.AppStatusHandler + mut sync.RWMutex +} + +// NewConsensusMetrics creates a new instance of ConsensusMetrics +func NewConsensusMetrics(appStatusHandler core.AppStatusHandler) (*ConsensusMetrics, error) { + if check.IfNil(appStatusHandler) { + return nil, ErrNilAppStatusHandler + } + return &ConsensusMetrics{appStatusHandler: appStatusHandler}, nil +} + +// ResetInstanceValues resets the instance values for the next round. +func (cm *ConsensusMetrics) ResetInstanceValues() { + cm.mut.Lock() + defer cm.mut.Unlock() + + cm.blockReceivedOrSentDelay = uint64(0) + cm.isBlockAlreadyReceived = false + cm.appStatusHandler.SetUInt64Value(common.MetricReceivedOrSentProposedBlock, uint64(0)) + cm.appStatusHandler.SetUInt64Value(common.MetricReceivedProof, uint64(0)) +} + +// ResetAverages resets the average calculations. It should be called on epoch change. +func (cm *ConsensusMetrics) ResetAverages() { + cm.mut.Lock() + defer cm.mut.Unlock() + + avgBlockReceivedDelay := avg(cm.blockReceivedDelaySum, cm.blockReceivedCount) + avgBlockSignedDelay := avg(cm.proofReceivedDelaySum, cm.proofReceivedCount) + log.Debug("Resetting consensus metrics averages on epoch change. Values before reset", "avgBlockReceivedDelay", avgBlockReceivedDelay, "avgBlockSignedDelaySum", avgBlockSignedDelay) + + cm.blockReceivedDelaySum = 0 + cm.blockReceivedCount = 0 + cm.proofReceivedDelaySum = 0 + cm.proofReceivedCount = 0 + + cm.appStatusHandler.SetUInt64Value(common.MetricAvgReceivedOrSentProposedBlock, uint64(0)) + cm.appStatusHandler.SetUInt64Value(common.MetricAvgReceivedProof, uint64(0)) +} + +// SetBlockReceivedOrSent sets the block body received delay and updates the metrics if both header and body have been received/sent. +func (cm *ConsensusMetrics) SetBlockReceivedOrSent(delayFromRoundStart uint64) { + cm.mut.Lock() + defer cm.mut.Unlock() + + cm.isBlockAlreadyReceived = true + cm.blockReceivedOrSentDelay = delayFromRoundStart + + cm.appStatusHandler.SetUInt64Value(common.MetricReceivedOrSentProposedBlock, delayFromRoundStart) + cm.updateAverages(common.MetricReceivedOrSentProposedBlock, delayFromRoundStart) +} + +// SetProofReceived sets the proof received delay and updates the metrics +func (cm *ConsensusMetrics) SetProofReceived(delayProofFromRoundStart uint64) { + cm.mut.Lock() + defer cm.mut.Unlock() + + if !cm.isBlockAlreadyReceived { + log.Debug("Block body not received yet, cannot compute proof received delay") + return + } + + if cm.blockReceivedOrSentDelay > delayProofFromRoundStart { + log.Debug("Proof received delay is smaller than block body received delay", + "blockReceivedOrSentDelay", cm.blockReceivedOrSentDelay, + "delayProofFromRoundStart", delayProofFromRoundStart) + return + } + + metricsTime := delayProofFromRoundStart - cm.blockReceivedOrSentDelay + cm.appStatusHandler.SetUInt64Value(common.MetricReceivedProof, metricsTime) + cm.updateAverages(common.MetricReceivedProof, metricsTime) +} + +func (cm *ConsensusMetrics) updateAverages(metric string, metricsTime uint64) { + switch metric { + case common.MetricReceivedOrSentProposedBlock: + cm.blockReceivedDelaySum += metricsTime + cm.blockReceivedCount++ + averageReceivedBlockDelay := avg(cm.blockReceivedDelaySum, cm.blockReceivedCount) + cm.appStatusHandler.SetUInt64Value(common.MetricAvgReceivedOrSentProposedBlock, averageReceivedBlockDelay) + log.Debug("Computed average block header and body received delay", "currentBlockReceivedDelay", metricsTime, "averageBlockReceivedDelay", averageReceivedBlockDelay) + case common.MetricReceivedProof: + cm.proofReceivedDelaySum += metricsTime + cm.proofReceivedCount++ + averageProofDelay := avg(cm.proofReceivedDelaySum, cm.proofReceivedCount) + cm.appStatusHandler.SetUInt64Value(common.MetricAvgReceivedProof, averageProofDelay) + log.Debug("Computed average signature received delay from block body received", "currentProofDelay", metricsTime, "averageProofDelay", averageProofDelay) + } +} + +func avg(sum, count uint64) uint64 { + if count == 0 { + return 0 + } + return sum / count +} + +// IsInterfaceNil returns true if the interface is nil +func (cm *ConsensusMetrics) IsInterfaceNil() bool { + return cm == nil +} diff --git a/consensus/spos/consensusMetrics_test.go b/consensus/spos/consensusMetrics_test.go new file mode 100644 index 00000000000..066a15d4695 --- /dev/null +++ b/consensus/spos/consensusMetrics_test.go @@ -0,0 +1,227 @@ +package spos + +import ( + "testing" + + "github.com/multiversx/mx-chain-go/common" + statusHandlerMock "github.com/multiversx/mx-chain-go/testscommon/statusHandler" + "github.com/stretchr/testify/assert" +) + +func TestConsensusMetrics_NewConsensusMetrics(t *testing.T) { + t.Parallel() + + t.Run("nil appStatusHandler", func(t *testing.T) { + t.Parallel() + cm, _ := NewConsensusMetrics(nil) + assert.Nil(t, cm) + assert.True(t, cm.IsInterfaceNil(), "NewConsensusMetrics(nil) should return nil") + }) + + t.Run("normal operation", func(t *testing.T) { + t.Parallel() + appStatusHandler := &statusHandlerMock.AppStatusHandlerMock{} + cm, _ := NewConsensusMetrics(appStatusHandler) + assert.NotNil(t, cm) + assert.False(t, cm.IsInterfaceNil(), "NewConsensusMetrics(non-nil) should return non-nil") + }) +} + +func TestConsensusMetrics_ResetAverages(t *testing.T) { + t.Parallel() + + t.Run("normal operation", func(t *testing.T) { + t.Parallel() + appStatusHandler := statusHandlerMock.NewAppStatusHandlerMock() + cm, _ := NewConsensusMetrics(appStatusHandler) + if cm == nil { + t.Errorf("NewConsensusMetrics() = nil, want non-nil") + return + } + + cm.blockReceivedDelaySum = 100 + cm.blockReceivedCount = 10 + cm.proofReceivedDelaySum = 300 + cm.proofReceivedCount = 20 + + cm.ResetAverages() + + assert.Equal(t, uint64(0), cm.blockReceivedDelaySum, "blockReceivedDelaySum should be reset to 0") + assert.Equal(t, uint64(0), cm.blockReceivedCount, "blockReceivedCount should be reset to 0") + assert.Equal(t, uint64(0), cm.proofReceivedDelaySum, "blockSignedDelaySum should be reset to 0") + assert.Equal(t, uint64(0), cm.proofReceivedCount, "blockSignedCount should be reset to 0") + }) + + t.Run("reset when sums and counts are zero", func(t *testing.T) { + t.Parallel() + appStatusHandler := statusHandlerMock.NewAppStatusHandlerMock() + cm, _ := NewConsensusMetrics(appStatusHandler) + if cm == nil { + t.Errorf("NewConsensusMetrics() = nil, want non-nil") + return + } + + cm.blockReceivedDelaySum = 0 + cm.blockReceivedCount = 0 + cm.proofReceivedDelaySum = 0 + cm.proofReceivedCount = 0 + + assert.NotPanics(t, func() { + cm.ResetAverages() + }) + }) +} + +func TestConsensusMetrics_resetInstanceValues(t *testing.T) { + t.Parallel() + + t.Run("normal operation", func(t *testing.T) { + t.Parallel() + appStatusHandler := statusHandlerMock.NewAppStatusHandlerMock() + + cm, _ := NewConsensusMetrics(appStatusHandler) + if cm == nil { + t.Errorf("NewConsensusMetrics() = nil, want non-nil") + return + } + + appStatusHandler.SetUInt64Value(common.MetricReceivedOrSentProposedBlock, 0) + appStatusHandler.SetUInt64Value(common.MetricReceivedProof, 0) + cm.blockReceivedOrSentDelay = 200 + + cm.ResetInstanceValues() + + assert.Equal(t, uint64(0), cm.blockReceivedOrSentDelay, "blockHeaderReceivedOrSentDelay should be reset to 0") + }) +} + +func TestConsensusMetrics_SetBlockReceivedOrSent(t *testing.T) { + t.Parallel() + + t.Run("should work", func(t *testing.T) { + t.Parallel() + appStatusHandler := statusHandlerMock.NewAppStatusHandlerMock() + cm, _ := NewConsensusMetrics(appStatusHandler) + + appStatusHandler.SetUInt64Value(common.MetricReceivedOrSentProposedBlock, 0) + + delay_100 := uint64(100) + cm.SetBlockReceivedOrSent(delay_100) + + assert.Equal(t, uint64(delay_100), cm.blockReceivedOrSentDelay, "blockHeaderReceivedOrSentDelay should be set correctly") + assert.Equal(t, uint64(delay_100), appStatusHandler.GetUint64(common.MetricReceivedOrSentProposedBlock), "blockReceivedDelay metric should be set correctly") + assert.Equal(t, uint64(delay_100), appStatusHandler.GetUint64(common.MetricAvgReceivedOrSentProposedBlock), "AvgReceivedProposedBlockBody should be set correctly") + assert.Equal(t, uint64(1), cm.blockReceivedCount, "blockReceivedCount should be incremented") + assert.Equal(t, uint64(delay_100), cm.blockReceivedDelaySum, "blockReceivedDelaySum should be updated correctly") + + delay_200 := uint64(200) + + cm.SetBlockReceivedOrSent(delay_200) + + assert.Equal(t, uint64(delay_200), cm.blockReceivedOrSentDelay, "blockHeaderReceivedOrSentDelay should be set correctly") + assert.Equal(t, uint64(delay_200), appStatusHandler.GetUint64(common.MetricReceivedOrSentProposedBlock), "blockReceivedDelay metric should be set correctly") + assert.Equal(t, uint64((delay_100+delay_200)/2), appStatusHandler.GetUint64(common.MetricAvgReceivedOrSentProposedBlock), "AvgReceivedProposedBlockBody should be set correctly") + assert.Equal(t, uint64(2), cm.blockReceivedCount, "blockReceivedCount should be incremented") + assert.Equal(t, uint64(delay_100+delay_200), cm.blockReceivedDelaySum, "blockReceivedDelaySum should be updated correctly") + + }) +} + +func TestConsensusMetrics_SetProof(t *testing.T) { + t.Parallel() + + t.Run("with no header or body received", func(t *testing.T) { + t.Parallel() + appStatusHandler := statusHandlerMock.NewAppStatusHandlerMock() + cm, _ := NewConsensusMetrics(appStatusHandler) + + appStatusHandler.SetUInt64Value(common.MetricReceivedOrSentProposedBlock, 0) + appStatusHandler.SetUInt64Value(common.MetricReceivedProof, 0) + + proofDelay := uint64(50) + + cm.SetProofReceived(proofDelay) + assert.Zero(t, appStatusHandler.GetUint64(common.MetricReceivedProof), "SetProofReceived should not be set when no header or body received") + }) + + t.Run("with body received first", func(t *testing.T) { + t.Parallel() + appStatusHandler := statusHandlerMock.NewAppStatusHandlerMock() + cm, _ := NewConsensusMetrics(appStatusHandler) + + appStatusHandler.SetUInt64Value(common.MetricReceivedOrSentProposedBlock, 0) + appStatusHandler.SetUInt64Value(common.MetricReceivedProof, 0) + + headerDelay := uint64(200) + proofDelay := uint64(250) + + cm.SetBlockReceivedOrSent(headerDelay) + cm.SetProofReceived(proofDelay) + + assert.Equal(t, headerDelay, appStatusHandler.GetUint64(common.MetricReceivedOrSentProposedBlock), "blockReceivedDelay metric should be updated correctly") + assert.Equal(t, proofDelay-headerDelay, appStatusHandler.GetUint64(common.MetricReceivedProof), "blockReceivedProof metric should be updated correctly") + }) + + t.Run("with proof delay smaller than header and body delay", func(t *testing.T) { + t.Parallel() + appStatusHandler := statusHandlerMock.NewAppStatusHandlerMock() + cm, _ := NewConsensusMetrics(appStatusHandler) + + appStatusHandler.SetUInt64Value(common.MetricReceivedOrSentProposedBlock, 0) + appStatusHandler.SetUInt64Value(common.MetricReceivedProof, 0) + + bodyDelay := uint64(200) + proofDelay := uint64(50) + + cm.SetBlockReceivedOrSent(bodyDelay) + cm.SetProofReceived(proofDelay) + + assert.Equal(t, bodyDelay, appStatusHandler.GetUint64(common.MetricReceivedOrSentProposedBlock), "blockReceivedDelay metric should be updated correctly") + assert.Zero(t, appStatusHandler.GetUint64(common.MetricReceivedProof), "blockReceivedProof metric should be updated correctly") + }) + +} + +func TestConsensusMetrics_UpdateAverages(t *testing.T) { + t.Parallel() + + appStatusHandler := statusHandlerMock.NewAppStatusHandlerMock() + cm, _ := NewConsensusMetrics(appStatusHandler) + + cm.blockReceivedDelaySum = 300 + cm.blockReceivedCount = 3 + cm.updateAverages(common.MetricReceivedOrSentProposedBlock, 500) + assert.Equal(t, uint64(800), cm.blockReceivedDelaySum, "blockReceivedDelaySum should be updated correctly") + assert.Equal(t, uint64(4), cm.blockReceivedCount, "blockReceivedCount should be updated correctly") + assert.Equal(t, uint64(200), appStatusHandler.GetUint64(common.MetricAvgReceivedOrSentProposedBlock), "AvgReceivedProposedBlockBody should be updated correctly") + + cm.proofReceivedDelaySum = 600 + cm.proofReceivedCount = 4 + cm.updateAverages(common.MetricReceivedProof, 400) + assert.Equal(t, uint64(1000), cm.proofReceivedDelaySum, "blockSignedDelaySum should be updated correctly") + assert.Equal(t, uint64(5), cm.proofReceivedCount, "blockSignedCount should be updated correctly") + assert.Equal(t, uint64(200), appStatusHandler.GetUint64(common.MetricAvgReceivedProof), "AvgReceivedSignatures should be updated correctly") +} + +func TestConsensusMetrics_IsProofSet(t *testing.T) { + t.Parallel() + + appStatusHandler := statusHandlerMock.NewAppStatusHandlerMock() + cm, _ := NewConsensusMetrics(appStatusHandler) + cm.ResetInstanceValues() + appStatusHandler.SetUInt64Value(common.MetricReceivedProof, 0) + + assert.False(t, isProofForCurrentConsensusSet(appStatusHandler), "isProofForCurrentConsensusSet should be false initially") + + cm.SetProofReceived(100) + assert.False(t, isProofForCurrentConsensusSet(appStatusHandler), "isProofForCurrentConsensusSet should be false after setting only proof") + + cm.SetBlockReceivedOrSent(200) + + cm.SetProofReceived(300) + assert.True(t, isProofForCurrentConsensusSet(appStatusHandler), "isProofForCurrentConsensusSet should be true after setting header, body and proof") +} + +func isProofForCurrentConsensusSet(appStatusHandler *statusHandlerMock.AppStatusHandlerMock) bool { + return appStatusHandler.GetUint64(common.MetricReceivedProof) > 0 +} diff --git a/consensus/spos/consensusState.go b/consensus/spos/consensusState.go index 476b90133e6..7c136ae8be4 100644 --- a/consensus/spos/consensusState.go +++ b/consensus/spos/consensusState.go @@ -7,6 +7,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" + commonConsensus "github.com/multiversx/mx-chain-go/common/consensus" logger "github.com/multiversx/mx-chain-logger-go" "github.com/multiversx/mx-chain-go/consensus" @@ -34,11 +35,11 @@ type ConsensusState struct { receivedMessagesWithSignature map[string]p2p.MessageP2P mutReceivedMessagesWithSignature sync.RWMutex - RoundIndex int64 - RoundTimeStamp time.Time - RoundCanceled bool - ExtendedCalled bool - WaitingAllSignaturesTimeOut bool + roundIndex int64 + roundTimeStamp time.Time + roundCanceled bool + extendedCalled bool + waitingAllSignaturesTimeOut bool processingBlock bool mutProcessingBlock sync.RWMutex @@ -48,6 +49,8 @@ type ConsensusState struct { *roundStatus mutState sync.RWMutex + + redundancyHandler consensus.NodeRedundancyHandler } // NewConsensusState creates a new ConsensusState object @@ -55,12 +58,14 @@ func NewConsensusState( roundConsensus *roundConsensus, roundThreshold *roundThreshold, roundStatus *roundStatus, + redundancyHandler consensus.NodeRedundancyHandler, ) *ConsensusState { cns := ConsensusState{ - roundConsensus: roundConsensus, - roundThreshold: roundThreshold, - roundStatus: roundStatus, + roundConsensus: roundConsensus, + roundThreshold: roundThreshold, + roundStatus: roundStatus, + redundancyHandler: redundancyHandler, } cns.ResetConsensusState() @@ -70,9 +75,12 @@ func NewConsensusState( // ResetConsensusRoundState method resets all the consensus round data (except messages received) func (cns *ConsensusState) ResetConsensusRoundState() { - cns.RoundCanceled = false - cns.ExtendedCalled = false - cns.WaitingAllSignaturesTimeOut = false + cns.mutState.Lock() + cns.roundCanceled = false + cns.extendedCalled = false + cns.waitingAllSignaturesTimeOut = false + cns.mutState.Unlock() + cns.ResetRoundStatus() cns.ResetRoundState() } @@ -146,11 +154,11 @@ func (cns *ConsensusState) IsNodeLeaderInCurrentRound(node string) bool { // GetLeader method gets the leader of the current round func (cns *ConsensusState) GetLeader() (string, error) { - if cns.consensusGroup == nil { + if cns.ConsensusGroup() == nil { return "", ErrNilConsensusGroup } - if len(cns.consensusGroup) == 0 { + if len(cns.ConsensusGroup()) == 0 { return "", ErrEmptyConsensusGroup } @@ -263,7 +271,7 @@ func (cns *ConsensusState) CanDoSubroundJob(currentSubroundId int) bool { // CanProcessReceivedMessage method returns true if the message received can be processed and false otherwise func (cns *ConsensusState) CanProcessReceivedMessage(cnsDta *consensus.Message, currentRoundIndex int64, currentSubroundId int) bool { - if cns.IsNodeSelf(string(cnsDta.PubKey)) { + if cns.IsNodeSelf(string(cnsDta.PubKey)) && commonConsensus.ShouldConsiderSelfKeyInConsensus(cns.redundancyHandler) { return false } @@ -361,7 +369,7 @@ func (cns *ConsensusState) IsLeaderJobDone(currentSubroundId int) bool { // IsMultiKeyJobDone method returns true if all the nodes controlled by this instance finished the current job for // the current subround and false otherwise func (cns *ConsensusState) IsMultiKeyJobDone(currentSubroundId int) bool { - for _, validator := range cns.consensusGroup { + for _, validator := range cns.ConsensusGroup() { if !cns.keysHandler.IsKeyManagedByCurrentNode([]byte(validator)) { continue } @@ -405,7 +413,7 @@ func (cns *ConsensusState) GetRoundCanceled() bool { cns.mutState.RLock() defer cns.mutState.RUnlock() - return cns.RoundCanceled + return cns.roundCanceled } // SetRoundCanceled sets the state of the current round @@ -413,7 +421,7 @@ func (cns *ConsensusState) SetRoundCanceled(roundCanceled bool) { cns.mutState.Lock() defer cns.mutState.Unlock() - cns.RoundCanceled = roundCanceled + cns.roundCanceled = roundCanceled } // GetRoundIndex returns the index of the current round @@ -421,7 +429,7 @@ func (cns *ConsensusState) GetRoundIndex() int64 { cns.mutState.RLock() defer cns.mutState.RUnlock() - return cns.RoundIndex + return cns.roundIndex } // SetRoundIndex sets the index of the current round @@ -429,27 +437,39 @@ func (cns *ConsensusState) SetRoundIndex(roundIndex int64) { cns.mutState.Lock() defer cns.mutState.Unlock() - cns.RoundIndex = roundIndex + cns.roundIndex = roundIndex } // GetRoundTimeStamp returns the time stamp of the current round func (cns *ConsensusState) GetRoundTimeStamp() time.Time { - return cns.RoundTimeStamp + cns.mutState.RLock() + defer cns.mutState.RUnlock() + + return cns.roundTimeStamp } // SetRoundTimeStamp sets the time stamp of the current round func (cns *ConsensusState) SetRoundTimeStamp(roundTimeStamp time.Time) { - cns.RoundTimeStamp = roundTimeStamp + cns.mutState.Lock() + defer cns.mutState.Unlock() + + cns.roundTimeStamp = roundTimeStamp } // GetExtendedCalled returns the state of the extended called func (cns *ConsensusState) GetExtendedCalled() bool { - return cns.ExtendedCalled + cns.mutState.RLock() + defer cns.mutState.RUnlock() + + return cns.extendedCalled } // SetExtendedCalled sets the state of the extended called func (cns *ConsensusState) SetExtendedCalled(extendedCalled bool) { - cns.ExtendedCalled = extendedCalled + cns.mutState.Lock() + defer cns.mutState.Unlock() + + cns.extendedCalled = extendedCalled } // GetBody returns the body of the current round @@ -489,7 +509,7 @@ func (cns *ConsensusState) GetWaitingAllSignaturesTimeOut() bool { cns.mutState.RLock() defer cns.mutState.RUnlock() - return cns.WaitingAllSignaturesTimeOut + return cns.waitingAllSignaturesTimeOut } // SetWaitingAllSignaturesTimeOut sets the state of the waiting all signatures time out @@ -497,7 +517,7 @@ func (cns *ConsensusState) SetWaitingAllSignaturesTimeOut(waitingAllSignaturesTi cns.mutState.Lock() defer cns.mutState.Unlock() - cns.WaitingAllSignaturesTimeOut = waitingAllSignaturesTimeOut + cns.waitingAllSignaturesTimeOut = waitingAllSignaturesTimeOut } // IsInterfaceNil returns true if there is no value under the interface diff --git a/consensus/spos/consensusState_test.go b/consensus/spos/consensusState_test.go index 62855a79ea9..9ecf258dc61 100644 --- a/consensus/spos/consensusState_test.go +++ b/consensus/spos/consensusState_test.go @@ -3,12 +3,14 @@ package spos_test import ( "bytes" "errors" + "sync" "testing" "time" p2pMessage "github.com/multiversx/mx-chain-communication-go/p2p/message" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/consensus/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -57,6 +59,7 @@ func internalInitConsensusStateWithKeysHandler(keysHandler consensus.KeysHandler rcns, rthr, rstatus, + &mock.NodeRedundancyHandlerStub{}, ) return cns @@ -78,9 +81,9 @@ func TestConsensusState_ResetConsensusStateShouldWork(t *testing.T) { cns.SetExtendedCalled(true) cns.SetWaitingAllSignaturesTimeOut(true) cns.ResetConsensusState() - assert.False(t, cns.RoundCanceled) + assert.False(t, cns.GetRoundCanceled()) assert.False(t, cns.GetExtendedCalled()) - assert.False(t, cns.WaitingAllSignaturesTimeOut) + assert.False(t, cns.GetWaitingAllSignaturesTimeOut()) } func TestConsensusState_IsNodeLeaderInCurrentRoundShouldReturnFalseWhenGetLeaderErr(t *testing.T) { @@ -422,11 +425,52 @@ func TestConsensusState_CanProcessReceivedMessageShouldReturnFalseWhenMessageIsR func TestConsensusState_CanProcessReceivedMessageShouldReturnFalseWhenMessageIsReceivedForOtherRound(t *testing.T) { t.Parallel() - cns := internalInitConsensusState() + eligibleList := []string{"1", "2", "3"} + + eligibleNodesPubKeys := make(map[string]struct{}) + for _, key := range eligibleList { + eligibleNodesPubKeys[key] = struct{}{} + } + + rcns, _ := spos.NewRoundConsensus( + eligibleNodesPubKeys, + 3, + "2", + &testscommon.KeysHandlerStub{}, + ) + + rcns.SetConsensusGroup(eligibleList) + rcns.SetLeader(eligibleList[0]) + rcns.ResetRoundState() + + rthr := spos.NewRoundThreshold() + + rthr.SetThreshold(bls.SrBlock, 1) + rthr.SetThreshold(bls.SrSignature, 3) + rthr.SetFallbackThreshold(bls.SrBlock, 1) + rthr.SetFallbackThreshold(bls.SrSignature, 2) + + rstatus := spos.NewRoundStatus() + rstatus.ResetRoundStatus() + + // force backup case for coverage + cns := spos.NewConsensusState( + rcns, + rthr, + rstatus, + &mock.NodeRedundancyHandlerStub{ + IsRedundancyNodeCalled: func() bool { + return true + }, + IsMainMachineActiveCalled: func() bool { + return false + }, + }, + ) cnsDta := &consensus.Message{ RoundIndex: 0, - PubKey: []byte("1"), + PubKey: []byte(cns.SelfPubKey()), } assert.False(t, cns.CanProcessReceivedMessage(cnsDta, 1, bls.SrBlock)) @@ -655,6 +699,71 @@ func TestConsensusState_GettersSetters(t *testing.T) { require.Equal(t, providedMsg, msg) } +func TestConsensusState_Concurrency(t *testing.T) { + t.Parallel() + + keysHandler := &testscommon.KeysHandlerStub{} + cns := internalInitConsensusStateWithKeysHandler(keysHandler) + + numOperations := 1000 + + wg := sync.WaitGroup{} + wg.Add(numOperations) + + for i := 0; i < numOperations; i++ { + go func(idx int) { + switch idx % 20 { + case 0: + cns.AddReceivedHeader(&block.HeaderV2{}) + case 1: + _ = cns.GetReceivedHeaders() + case 2: + cns.AddMessageWithSignature("", nil) + case 3: + _, _ = cns.GetMessageWithSignature("") + case 4: + _ = cns.IsNodeLeaderInCurrentRound("") + case 5: + _, _ = cns.GetLeader() + case 6: + cns.SetLeader("") + case 7: + _ = cns.IsJobDone("", 0) + case 8: + _ = cns.ConsensusGroup() + case 9: + cns.SetConsensusGroup([]string{}) + case 10: + cns.SetRoundIndex(0) + case 11: + _ = cns.ConsensusGroupSize() + case 12: + cns.SetConsensusGroupSize(0) + case 13: + _ = cns.GetRoundTimeStamp() + case 14: + cns.SetRoundTimeStamp(time.Now()) + case 15: + cns.SetRoundCanceled(true) + case 16: + _ = cns.GetRoundIndex() + case 17: + cns.SetStatus(0, spos.SsFinished) + case 18: + _ = cns.IsSelfJobDone(0) + case 19: + cns.ResetConsensusRoundState() + default: + assert.Fail(t, "should have not been called") + } + + wg.Done() + }(i) + } + + wg.Wait() +} + func TestConsensusState_IsInterfaceNil(t *testing.T) { t.Parallel() diff --git a/consensus/spos/errors.go b/consensus/spos/errors.go index d89a58865f3..02eb2b36dea 100644 --- a/consensus/spos/errors.go +++ b/consensus/spos/errors.go @@ -46,6 +46,9 @@ var ErrNilMessenger = errors.New("messenger is nil") // ErrNilBlockProcessor is raised when a valid block processor is expected but nil used var ErrNilBlockProcessor = errors.New("block processor is nil") +// ErrNilExecutionManager is raised when a valid execution manager is expected but nil used +var ErrNilExecutionManager = errors.New("execution manager is nil") + // ErrNilBootstrapper is raised when a valid block processor is expected but nil used var ErrNilBootstrapper = errors.New("bootstrapper is nil") @@ -55,6 +58,9 @@ var ErrNilBroadcastMessenger = errors.New("broadcast messenger is nil") // ErrNilHeadersSubscriber is raised when a valid headers subscriber is expected but nil is provided var ErrNilHeadersSubscriber = errors.New("headers subscriber is nil") +// ErrNilHeadersPool is raised when a valid headers pool is expected but nil is provided +var ErrNilHeadersPool = errors.New("headers pool is nil") + // ErrNilAlarmScheduler is raised when a valid alarm scheduler is expected but nil is provided var ErrNilAlarmScheduler = errors.New("alarm scheduler is nil") @@ -253,6 +259,9 @@ var ErrEquivalentMessageAlreadyReceived = errors.New("equivalent message already // ErrNilEnableEpochsHandler signals that a nil enable epochs handler has been provided var ErrNilEnableEpochsHandler = errors.New("nil enable epochs handler") +// ErrNilEnableRoundsHandler signals a nil enable rounds handler has been provided +var ErrNilEnableRoundsHandler = errors.New("nil enable rounds handler") + // ErrNilThrottler signals that a nil throttler has been provided var ErrNilThrottler = errors.New("nil throttler") @@ -265,12 +274,6 @@ var ErrNilEquivalentProofPool = errors.New("nil equivalent proof pool") // ErrNilHeaderProof signals that a nil header proof has been provided var ErrNilHeaderProof = errors.New("nil header proof") -// ErrHeaderProofNotExpected signals that a header proof was not expected -var ErrHeaderProofNotExpected = errors.New("header proof not expected") - -// ErrConsensusMessageNotExpected signals that a consensus message was not expected -var ErrConsensusMessageNotExpected = errors.New("consensus message not expected") - // ErrNilEpochNotifier signals that a nil epoch notifier has been provided var ErrNilEpochNotifier = errors.New("nil epoch notifier") diff --git a/consensus/spos/interface.go b/consensus/spos/interface.go index 1decc6aabc3..4c170171bfc 100644 --- a/consensus/spos/interface.go +++ b/consensus/spos/interface.go @@ -25,6 +25,7 @@ import ( type ConsensusCoreHandler interface { Blockchain() data.ChainHandler BlockProcessor() process.BlockProcessor + ExecutionManager() process.ExecutionManager BootStrapper() process.Bootstrapper BroadcastMessenger() consensus.BroadcastMessenger Chronology() consensus.ChronologyHandler @@ -46,9 +47,11 @@ type ConsensusCoreHandler interface { PeerBlacklistHandler() consensus.PeerBlacklistHandler SigningHandler() consensus.SigningHandler EnableEpochsHandler() common.EnableEpochsHandler + EnableRoundsHandler() common.EnableRoundsHandler EquivalentProofsPool() consensus.EquivalentProofsPool EpochNotifier() process.EpochNotifier InvalidSignersCache() InvalidSignersCache + AOTSelector() process.AOTTransactionSelector IsInterfaceNil() bool } @@ -127,6 +130,16 @@ type WorkerHandler interface { ResetInvalidSignersCache() // IsInterfaceNil returns true if there is no value under the interface IsInterfaceNil() bool + ConsensusMetrics() ConsensusMetricsHandler +} + +// ConsensusMetricsHandler handles the consensus metrics +type ConsensusMetricsHandler interface { + IsInterfaceNil() bool + ResetAverages() + ResetInstanceValues() + SetBlockReceivedOrSent(delayFromRoundStart uint64) + SetProofReceived(delayProofFromRoundStart uint64) } // PoolAdder adds data in a key-value pool @@ -163,6 +176,8 @@ type PeerBlackListCacher interface { type SentSignaturesTracker interface { StartRound() SignatureSent(pkBytes []byte) + RecordSignedNonce(pkBytes []byte, nonce uint64, headerHash []byte) + GetSignedHash(pkBytes []byte, nonce uint64) ([]byte, bool) IsInterfaceNil() bool } @@ -270,3 +285,13 @@ type InvalidSignersCache interface { Reset() IsInterfaceNil() bool } + +// NtpSyncControllerHandler detects round desynchronization and triggers a forced NTP resync. +// If the local clock drifts and the node repeatedly receives valid block proofs for rounds +// that fall outside the expected range, the handler identifies this pattern as a de-sync +// condition and initiates time resynchronization. +type NtpSyncControllerHandler interface { + AddOutOfRangeNonce(nonce uint64, hash string) + AddLeaderNonceAsOutOfRange(nonce uint64, hash string) + IsInterfaceNil() bool +} diff --git a/consensus/spos/roundConsensus.go b/consensus/spos/roundConsensus.go index 434adc6258d..75fcf246da9 100644 --- a/consensus/spos/roundConsensus.go +++ b/consensus/spos/roundConsensus.go @@ -44,6 +44,9 @@ func NewRoundConsensus( // ConsensusGroupIndex returns the index of given public key in the current consensus group func (rcns *roundConsensus) ConsensusGroupIndex(pubKey string) (int, error) { + rcns.mut.RLock() + defer rcns.mut.RUnlock() + for i, pk := range rcns.consensusGroup { if pk == pubKey { return i, nil @@ -54,7 +57,7 @@ func (rcns *roundConsensus) ConsensusGroupIndex(pubKey string) (int, error) { // SelfConsensusGroupIndex returns the index of self public key in current consensus group func (rcns *roundConsensus) SelfConsensusGroupIndex() (int, error) { - return rcns.ConsensusGroupIndex(rcns.selfPubKey) + return rcns.ConsensusGroupIndex(rcns.SelfPubKey()) } // SetEligibleList sets the eligible list ID's @@ -105,21 +108,33 @@ func (rcns *roundConsensus) SetLeader(leader string) { // ConsensusGroupSize returns the consensus group size func (rcns *roundConsensus) ConsensusGroupSize() int { + rcns.mut.RLock() + defer rcns.mut.RUnlock() + return rcns.consensusGroupSize } // SetConsensusGroupSize sets the consensus group size func (rcns *roundConsensus) SetConsensusGroupSize(consensusGroupSize int) { + rcns.mut.Lock() + defer rcns.mut.Unlock() + rcns.consensusGroupSize = consensusGroupSize } // SelfPubKey returns selfPubKey ID func (rcns *roundConsensus) SelfPubKey() string { + rcns.mut.RLock() + defer rcns.mut.RUnlock() + return rcns.selfPubKey } // SetSelfPubKey sets selfPubKey ID func (rcns *roundConsensus) SetSelfPubKey(selfPubKey string) { + rcns.mut.Lock() + defer rcns.mut.Unlock() + rcns.selfPubKey = selfPubKey } @@ -127,15 +142,21 @@ func (rcns *roundConsensus) SetSelfPubKey(selfPubKey string) { // in subround given by the subroundId parameter func (rcns *roundConsensus) JobDone(key string, subroundId int) (bool, error) { rcns.mut.RLock() + retCode, err := rcns.jobDone(key, subroundId) + rcns.mut.RUnlock() + + return retCode, err +} + +// this should run under mutex +func (rcns *roundConsensus) jobDone(key string, subroundId int) (bool, error) { currentRoundState := rcns.validatorRoundStates[key] if currentRoundState == nil { - rcns.mut.RUnlock() return false, ErrInvalidKey } retcode := currentRoundState.JobDone(subroundId) - rcns.mut.RUnlock() return retcode, nil } @@ -160,7 +181,7 @@ func (rcns *roundConsensus) SetJobDone(key string, subroundId int, value bool) e // SelfJobDone returns the self state of the action done in subround given by the subroundId parameter func (rcns *roundConsensus) SelfJobDone(subroundId int) (bool, error) { - return rcns.JobDone(rcns.selfPubKey, subroundId) + return rcns.JobDone(rcns.SelfPubKey(), subroundId) } // IsNodeInConsensusGroup method checks if the node is part of consensus group of the current round @@ -189,10 +210,13 @@ func (rcns *roundConsensus) IsNodeInEligibleList(node string) bool { // ComputeSize method returns the number of messages received from the nodes belonging to the current jobDone group // related to this subround func (rcns *roundConsensus) ComputeSize(subroundId int) int { + rcns.mut.RLock() + defer rcns.mut.RUnlock() + n := 0 for i := 0; i < len(rcns.consensusGroup); i++ { - isJobDone, err := rcns.JobDone(rcns.consensusGroup[i], subroundId) + isJobDone, err := rcns.jobDone(rcns.consensusGroup[i], subroundId) if err != nil { log.Debug("JobDone", "error", err.Error()) continue @@ -228,6 +252,9 @@ func (rcns *roundConsensus) ResetRoundState() { // IsMultiKeyInConsensusGroup method checks if one of the nodes which are controlled by this instance // is in consensus group in the current round func (rcns *roundConsensus) IsMultiKeyInConsensusGroup() bool { + rcns.mut.RLock() + defer rcns.mut.RUnlock() + for i := 0; i < len(rcns.consensusGroup); i++ { if rcns.IsKeyManagedBySelf([]byte(rcns.consensusGroup[i])) { return true diff --git a/consensus/spos/roundConsensus_test.go b/consensus/spos/roundConsensus_test.go index 36c8e5ad8ab..dfb9bff5b88 100644 --- a/consensus/spos/roundConsensus_test.go +++ b/consensus/spos/roundConsensus_test.go @@ -2,6 +2,7 @@ package spos_test import ( "bytes" + "sync" "testing" "github.com/multiversx/mx-chain-go/consensus" @@ -9,6 +10,7 @@ import ( "github.com/multiversx/mx-chain-go/consensus/spos/bls" "github.com/multiversx/mx-chain-go/testscommon" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func initRoundConsensus() *spos.RoundConsensus { @@ -37,15 +39,39 @@ func initRoundConsensusWithKeysHandler(keysHandler consensus.KeysHandler) *spos. return spos.NewRoundConsensusWrapper(rcns) } -func TestRoundConsensus_NewRoundConsensusShouldWork(t *testing.T) { +func TestRoundConsensus_NewRoundConsensus(t *testing.T) { t.Parallel() - rcns := *initRoundConsensus() + t.Run("nil keys handler, should fail", func(t *testing.T) { + t.Parallel() + + pubKeys := []string{"1", "2", "3"} + eligibleNodes := make(map[string]struct{}) - assert.NotNil(t, rcns) - assert.Equal(t, 3, len(rcns.ConsensusGroup())) - assert.Equal(t, "3", rcns.ConsensusGroup()[2]) - assert.Equal(t, "2", rcns.SelfPubKey()) + for i := range pubKeys { + eligibleNodes[pubKeys[i]] = struct{}{} + } + + rcns, err := spos.NewRoundConsensus( + eligibleNodes, + len(eligibleNodes), + "2", + nil, + ) + require.Nil(t, rcns) + require.Equal(t, spos.ErrNilKeysHandler, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + rcns := *initRoundConsensus() + + assert.NotNil(t, rcns) + assert.Equal(t, 3, len(rcns.ConsensusGroup())) + assert.Equal(t, "3", rcns.ConsensusGroup()[2]) + assert.Equal(t, "2", rcns.SelfPubKey()) + }) } func TestRoundConsensus_ConsensusGroupIndexFound(t *testing.T) { @@ -311,3 +337,59 @@ func TestRoundConsensus_IncrementRoundsWithoutReceivedMessages(t *testing.T) { roundConsensus.IncrementRoundsWithoutReceivedMessages(managedPkBytes) assert.True(t, wasCalled) } + +func TestRoundsConsensus_Concurrency(t *testing.T) { + t.Parallel() + + rcns := *initRoundConsensus() + + numOperations := 1000 + + wg := sync.WaitGroup{} + wg.Add(numOperations) + + for i := 0; i < numOperations; i++ { + go func(idx int) { + switch idx % 16 { + case 0: + _ = rcns.ComputeSize(0) + case 1: + _ = rcns.ConsensusGroup() + case 2: + rcns.SetConsensusGroup([]string{}) + case 3: + _ = rcns.ConsensusGroupSize() + case 4: + rcns.SetConsensusGroupSize(1) + case 5: + _ = rcns.EligibleList() + case 6: + _ = rcns.GetKeysHandler() + case 7: + _ = rcns.IsKeyManagedBySelf([]byte{}) + case 8: + _ = rcns.IsMultiKeyInConsensusGroup() + case 9: + _ = rcns.IsNodeInConsensusGroup("") + case 10: + _ = rcns.Leader() + case 11: + rcns.SetLeader("") + case 12: + _ = rcns.SelfPubKey() + case 13: + rcns.SetSelfPubKey("") + case 14: + _ = rcns.SetJobDone("key", 0, true) + case 15: + _, _ = rcns.SelfConsensusGroupIndex() + default: + assert.Fail(t, "should have not been called") + } + + wg.Done() + }(i) + } + + wg.Wait() +} diff --git a/consensus/spos/scheduledProcessor.go b/consensus/spos/scheduledProcessor.go index ecd2d44f433..67a354713c2 100644 --- a/consensus/spos/scheduledProcessor.go +++ b/consensus/spos/scheduledProcessor.go @@ -132,6 +132,11 @@ func (sp *scheduledProcessorWrapper) IsProcessedOKWithTimeout() bool { // StartScheduledProcessing starts the scheduled processing func (sp *scheduledProcessorWrapper) StartScheduledProcessing(header data.HeaderHandler, body data.BodyHandler, startTime time.Time) { + if check.IfNil(header) { + log.Debug("scheduledProcessorWrapper.StartScheduledProcessing: nil header provided") + return + } + if !header.HasScheduledSupport() { log.Debug("scheduled processing not supported") sp.setStatus(processingOK) diff --git a/consensus/spos/scheduledProcessor_test.go b/consensus/spos/scheduledProcessor_test.go index 0ec6ca3d5b2..74c0f262b98 100644 --- a/consensus/spos/scheduledProcessor_test.go +++ b/consensus/spos/scheduledProcessor_test.go @@ -13,6 +13,7 @@ import ( "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/consensus" + "github.com/multiversx/mx-chain-go/testscommon/round" "github.com/stretchr/testify/require" ) @@ -33,7 +34,7 @@ func TestNewScheduledProcessorWrapper_NilSyncTimerShouldErr(t *testing.T) { args := spos.ScheduledProcessorWrapperArgs{ SyncTimer: nil, Processor: &testscommon.BlockProcessorStub{}, - RoundTimeDurationHandler: &consensus.RoundHandlerMock{}, + RoundTimeDurationHandler: &round.RoundHandlerMock{}, } sp, err := spos.NewScheduledProcessorWrapper(args) @@ -47,7 +48,7 @@ func TestNewScheduledProcessorWrapper_NilBlockProcessorShouldErr(t *testing.T) { args := spos.ScheduledProcessorWrapperArgs{ SyncTimer: &consensus.SyncTimerMock{}, Processor: nil, - RoundTimeDurationHandler: &consensus.RoundHandlerMock{}, + RoundTimeDurationHandler: &round.RoundHandlerMock{}, } sp, err := spos.NewScheduledProcessorWrapper(args) @@ -75,7 +76,7 @@ func TestNewScheduledProcessorWrapper_NilBlockProcessorOK(t *testing.T) { args := spos.ScheduledProcessorWrapperArgs{ SyncTimer: &consensus.SyncTimerMock{}, Processor: &testscommon.BlockProcessorStub{}, - RoundTimeDurationHandler: &consensus.RoundHandlerMock{}, + RoundTimeDurationHandler: &round.RoundHandlerMock{}, } sp, err := spos.NewScheduledProcessorWrapper(args) @@ -95,7 +96,7 @@ func TestScheduledProcessorWrapper_IsProcessedOKEarlyExit(t *testing.T) { }, }, Processor: &testscommon.BlockProcessorStub{}, - RoundTimeDurationHandler: &consensus.RoundHandlerMock{}, + RoundTimeDurationHandler: &round.RoundHandlerMock{}, } sp, err := spos.NewScheduledProcessorWrapper(args) @@ -121,7 +122,7 @@ func defaultScheduledProcessorWrapperArgs() spos.ScheduledProcessorWrapperArgs { }, }, Processor: &testscommon.BlockProcessorStub{}, - RoundTimeDurationHandler: &consensus.RoundHandlerMock{}, + RoundTimeDurationHandler: &round.RoundHandlerMock{}, } } @@ -232,7 +233,7 @@ func TestScheduledProcessorWrapper_StatusGetterAndSetter(t *testing.T) { args := spos.ScheduledProcessorWrapperArgs{ SyncTimer: &consensus.SyncTimerMock{}, Processor: &testscommon.BlockProcessorStub{}, - RoundTimeDurationHandler: &consensus.RoundHandlerMock{}, + RoundTimeDurationHandler: &round.RoundHandlerMock{}, } sp, _ := spos.NewScheduledProcessorWrapper(args) @@ -260,7 +261,7 @@ func TestScheduledProcessorWrapper_StartScheduledProcessingHeaderV1ProcessingOK( return nil }, }, - RoundTimeDurationHandler: &consensus.RoundHandlerMock{}, + RoundTimeDurationHandler: &round.RoundHandlerMock{}, } sp, _ := spos.NewScheduledProcessorWrapper(args) @@ -286,7 +287,7 @@ func TestScheduledProcessorWrapper_StartScheduledProcessingHeaderV2ProcessingWit return errors.New("processing error") }, }, - RoundTimeDurationHandler: &consensus.RoundHandlerMock{}, + RoundTimeDurationHandler: &round.RoundHandlerMock{}, } sp, _ := spos.NewScheduledProcessorWrapper(args) @@ -314,7 +315,7 @@ func TestScheduledProcessorWrapper_StartScheduledProcessingHeaderV2ProcessingOK( return nil }, }, - RoundTimeDurationHandler: &consensus.RoundHandlerMock{}, + RoundTimeDurationHandler: &round.RoundHandlerMock{}, } sp, _ := spos.NewScheduledProcessorWrapper(args) @@ -353,7 +354,7 @@ func TestScheduledProcessorWrapper_StartScheduledProcessingHeaderV2ForceStopped( } }, }, - RoundTimeDurationHandler: &consensus.RoundHandlerMock{}, + RoundTimeDurationHandler: &round.RoundHandlerMock{}, } spw, err := spos.NewScheduledProcessorWrapper(args) @@ -389,7 +390,7 @@ func TestScheduledProcessorWrapper_StartScheduledProcessingHeaderV2ForceStopAfte return nil }, }, - RoundTimeDurationHandler: &consensus.RoundHandlerMock{}, + RoundTimeDurationHandler: &round.RoundHandlerMock{}, } spw, err := spos.NewScheduledProcessorWrapper(args) diff --git a/consensus/spos/sposFactory/sposFactory.go b/consensus/spos/sposFactory/sposFactory.go index 99f0cf682eb..6f2900c327b 100644 --- a/consensus/spos/sposFactory/sposFactory.go +++ b/consensus/spos/sposFactory/sposFactory.go @@ -7,6 +7,7 @@ import ( "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-crypto-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/consensus" "github.com/multiversx/mx-chain-go/consensus/broadcast" "github.com/multiversx/mx-chain-go/consensus/spos" @@ -33,6 +34,9 @@ func GetBroadcastMessenger( shardCoordinator sharding.Coordinator, peerSignatureHandler crypto.PeerSignatureHandler, headersSubscriber consensus.HeadersPoolSubscriber, + headersPool consensus.HeadersPoolGetter, + proofsPool consensus.EquivalentProofsPool, + enableEpochsHandler common.EnableEpochsHandler, interceptorsContainer process.InterceptorsContainer, alarmScheduler core.TimersScheduler, keysHandler consensus.KeysHandler, @@ -45,6 +49,9 @@ func GetBroadcastMessenger( dbbArgs := &broadcast.ArgsDelayedBlockBroadcaster{ InterceptorsContainer: interceptorsContainer, HeadersSubscriber: headersSubscriber, + HeadersPool: headersPool, + ProofsPool: proofsPool, + EnableEpochsHandler: enableEpochsHandler, ShardCoordinator: shardCoordinator, LeaderCacheSize: maxDelayCacheSize, ValidatorCacheSize: maxDelayCacheSize, diff --git a/consensus/spos/sposFactory/sposFactory_test.go b/consensus/spos/sposFactory/sposFactory_test.go index 1f122884530..fb479babd98 100644 --- a/consensus/spos/sposFactory/sposFactory_test.go +++ b/consensus/spos/sposFactory/sposFactory_test.go @@ -12,6 +12,8 @@ import ( "github.com/multiversx/mx-chain-go/consensus/spos" "github.com/multiversx/mx-chain-go/consensus/spos/sposFactory" "github.com/multiversx/mx-chain-go/testscommon" + dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" "github.com/multiversx/mx-chain-go/testscommon/p2pmocks" "github.com/multiversx/mx-chain-go/testscommon/pool" @@ -57,6 +59,9 @@ func TestGetBroadcastMessenger_ShardShouldWork(t *testing.T) { shardCoord, peerSigHandler, headersSubscriber, + headersSubscriber, + &dataRetrieverMock.ProofsPoolMock{}, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, interceptosContainer, alarmSchedulerStub, &testscommon.KeysHandlerStub{}, @@ -88,6 +93,9 @@ func TestGetBroadcastMessenger_MetachainShouldWork(t *testing.T) { shardCoord, peerSigHandler, headersSubscriber, + headersSubscriber, + &dataRetrieverMock.ProofsPoolMock{}, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, interceptosContainer, alarmSchedulerStub, &testscommon.KeysHandlerStub{}, @@ -111,6 +119,9 @@ func TestGetBroadcastMessenger_NilShardCoordinatorShouldErr(t *testing.T) { nil, nil, headersSubscriber, + headersSubscriber, + &dataRetrieverMock.ProofsPoolMock{}, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, interceptosContainer, alarmSchedulerStub, &testscommon.KeysHandlerStub{}, @@ -138,6 +149,9 @@ func TestGetBroadcastMessenger_InvalidShardIdShouldErr(t *testing.T) { shardCoord, nil, headersSubscriber, + headersSubscriber, + &dataRetrieverMock.ProofsPoolMock{}, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, interceptosContainer, alarmSchedulerStub, &testscommon.KeysHandlerStub{}, diff --git a/consensus/spos/subround.go b/consensus/spos/subround.go index 00b2c55fe6c..b1ae2750e6d 100644 --- a/consensus/spos/subround.go +++ b/consensus/spos/subround.go @@ -1,12 +1,16 @@ package spos import ( + "bytes" "context" + "sync" "time" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + commonConsensus "github.com/multiversx/mx-chain-go/common/consensus" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/consensus" ) @@ -25,14 +29,17 @@ type Subround struct { ConsensusCoreHandler ConsensusStateHandler - previous int - current int - next int - startTime int64 - endTime int64 - name string - chainID []byte - currentPid core.PeerID + previous int + current int + next int + mutDuration sync.RWMutex + startTimePercent float64 + endTimePercent float64 + startTime int64 + endTime int64 + name string + chainID []byte + currentPid core.PeerID consensusStateChangedChannel chan bool executeStoredMessages func() @@ -48,8 +55,9 @@ func NewSubround( previous int, current int, next int, - startTime int64, - endTime int64, + baseDuration time.Duration, + startTimePercent float64, + endTimePercent float64, name string, consensusState ConsensusStateHandler, consensusStateChangedChannel chan bool, @@ -71,12 +79,15 @@ func NewSubround( return nil, err } + startTime, endTime := computeStartAndEndTime(baseDuration, startTimePercent, endTimePercent) sr := Subround{ ConsensusCoreHandler: container, ConsensusStateHandler: consensusState, previous: previous, current: current, next: next, + startTimePercent: startTimePercent, + endTimePercent: endTimePercent, startTime: startTime, endTime: endTime, name: name, @@ -124,6 +135,12 @@ func checkNewSubroundParams( return nil } +func computeStartAndEndTime(baseDuration time.Duration, startTimePercent float64, endTimePercent float64) (int64, int64) { + startTime := int64(float64(baseDuration) * startTimePercent) + endTime := int64(float64(baseDuration) * endTimePercent) + return startTime, endTime +} + // DoWork method actually does the work of this Subround. First it tries to do the Job of the Subround then it will // Check the consensus. If the upper time limit of this Subround is reached, the Extend method will be called before // returning. If this method returns true the chronology will advance to the next Subround. @@ -177,14 +194,28 @@ func (sr *Subround) Next() int { // StartTime method returns the start time of the Subround func (sr *Subround) StartTime() int64 { + sr.mutDuration.RLock() + defer sr.mutDuration.RUnlock() + return sr.startTime } // EndTime method returns the upper time limit of the Subround func (sr *Subround) EndTime() int64 { + sr.mutDuration.RLock() + defer sr.mutDuration.RUnlock() + return sr.endTime } +// SetBaseDuration sets the base duration of the subround +func (sr *Subround) SetBaseDuration(baseDuration time.Duration) { + sr.mutDuration.Lock() + defer sr.mutDuration.Unlock() + + sr.startTime, sr.endTime = computeStartAndEndTime(baseDuration, sr.startTimePercent, sr.endTimePercent) +} + // Name method returns the name of the Subround func (sr *Subround) Name() string { return sr.name @@ -210,21 +241,34 @@ func (sr *Subround) ConsensusChannel() chan bool { return sr.consensusStateChangedChannel } -// GetAssociatedPid returns the associated PeerID to the provided public key bytes -func (sr *Subround) GetAssociatedPid(pkBytes []byte) core.PeerID { - return sr.GetKeysHandler().GetAssociatedPid(pkBytes) -} +// HasProofForCompetingBlock checks if there is a proof for a competing block in the equivalent proofs pool +func (sr *Subround) HasProofForCompetingBlock() bool { + prevBlock := sr.Blockchain().GetCurrentBlockHeader() + if check.IfNil(prevBlock) { + return false + } + competingBlockNonce := prevBlock.GetNonce() + 1 + proof, err := sr.EquivalentProofsPool().GetProofByNonce(competingBlockNonce, sr.ShardCoordinator().SelfId()) + if err != nil || check.IfNil(proof) { + return false + } -// ShouldConsiderSelfKeyInConsensus returns true if current machine is the main one, or it is a backup machine but the main -// machine failed -func (sr *Subround) ShouldConsiderSelfKeyInConsensus() bool { - isMainMachine := !sr.NodeRedundancyHandler().IsRedundancyNode() - if isMainMachine { + consensusBlockHash := sr.GetData() + if len(consensusBlockHash) == 0 { return true } - isMainMachineInactive := !sr.NodeRedundancyHandler().IsMainMachineActive() - return isMainMachineInactive + // proof for current consensus block does not count as competing + if bytes.Equal(proof.GetHeaderHash(), consensusBlockHash) { + return false + } + + return true +} + +// GetAssociatedPid returns the associated PeerID to the provided public key bytes +func (sr *Subround) GetAssociatedPid(pkBytes []byte) core.PeerID { + return sr.GetKeysHandler().GetAssociatedPid(pkBytes) } // IsSelfInConsensusGroup returns true is the current node is in consensus group in single @@ -241,7 +285,7 @@ func (sr *Subround) IsSelfLeader() bool { // IsSelfLeaderInCurrentRound method checks if the current node is leader in the current round func (sr *Subround) IsSelfLeaderInCurrentRound() bool { - return sr.IsNodeLeaderInCurrentRound(sr.SelfPubKey()) && sr.ShouldConsiderSelfKeyInConsensus() + return sr.IsNodeLeaderInCurrentRound(sr.SelfPubKey()) && commonConsensus.ShouldConsiderSelfKeyInConsensus(sr.NodeRedundancyHandler()) } // GetLeaderStartRoundMessage returns the leader start round message based on single key @@ -257,6 +301,17 @@ func (sr *Subround) GetLeaderStartRoundMessage() string { return "" } +// GetUnixTimestampForHeader returns unix timestamp in seconds or milliseconds based on epoch +func (sr *Subround) GetUnixTimestampForHeader( + headerEpoch uint32, +) uint64 { + if sr.EnableEpochsHandler().IsFlagEnabledInEpoch(common.SupernovaFlag, headerEpoch) { + return uint64(sr.RoundHandler().TimeStamp().UnixMilli()) + } + + return uint64(sr.RoundHandler().TimeStamp().Unix()) +} + // IsInterfaceNil returns true if there is no value under the interface func (sr *Subround) IsInterfaceNil() bool { return sr == nil diff --git a/consensus/spos/subround_test.go b/consensus/spos/subround_test.go index a07b3d460fd..a0645958e0f 100644 --- a/consensus/spos/subround_test.go +++ b/consensus/spos/subround_test.go @@ -3,21 +3,28 @@ package spos_test import ( "bytes" "context" + "errors" "sync" "testing" "time" "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + + "github.com/multiversx/mx-chain-go/consensus/mock" + "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/multiversx/mx-chain-go/consensus/mock" "github.com/multiversx/mx-chain-go/consensus/spos" "github.com/multiversx/mx-chain-go/consensus/spos/bls" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/consensus" "github.com/multiversx/mx-chain-go/testscommon/cryptoMocks" + "github.com/multiversx/mx-chain-go/testscommon/round" "github.com/multiversx/mx-chain-go/testscommon/statusHandler" ) @@ -87,6 +94,7 @@ func initConsensusState() *spos.ConsensusState { rcns, rthr, rstatus, + &mock.NodeRedundancyHandlerStub{}, ) cns.Data = []byte("X") @@ -104,8 +112,9 @@ func TestSubround_NewSubroundNilConsensusStateShouldFail(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", nil, ch, @@ -130,8 +139,9 @@ func TestSubround_NewSubroundNilChannelShouldFail(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, nil, @@ -157,8 +167,9 @@ func TestSubround_NewSubroundNilExecuteStoredMessagesShouldFail(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, ch, @@ -183,8 +194,9 @@ func TestSubround_NewSubroundNilContainerShouldFail(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, ch, @@ -211,8 +223,9 @@ func TestSubround_NilContainerBlockchainShouldFail(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, ch, @@ -239,8 +252,9 @@ func TestSubround_NilContainerBlockprocessorShouldFail(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, ch, @@ -267,8 +281,9 @@ func TestSubround_NilContainerBootstrapperShouldFail(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, ch, @@ -295,8 +310,9 @@ func TestSubround_NilContainerChronologyShouldFail(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, ch, @@ -323,8 +339,9 @@ func TestSubround_NilContainerHasherShouldFail(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, ch, @@ -351,8 +368,9 @@ func TestSubround_NilContainerMarshalizerShouldFail(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, ch, @@ -379,8 +397,9 @@ func TestSubround_NilContainerMultiSignerShouldFail(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, ch, @@ -407,8 +426,9 @@ func TestSubround_NilContainerRoundHandlerShouldFail(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, ch, @@ -435,8 +455,9 @@ func TestSubround_NilContainerShardCoordinatorShouldFail(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, ch, @@ -463,8 +484,9 @@ func TestSubround_NilContainerSyncTimerShouldFail(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, ch, @@ -491,8 +513,9 @@ func TestSubround_NilContainerValidatorGroupSelectorShouldFail(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, ch, @@ -517,8 +540,9 @@ func TestSubround_EmptyChainIDShouldFail(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, ch, @@ -543,8 +567,9 @@ func TestSubround_NewSubroundShouldWork(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, ch, @@ -578,8 +603,9 @@ func TestSubround_DoWorkShouldReturnFalseWhenJobFunctionIsNotSet(t *testing.T) { -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, ch, @@ -595,7 +621,7 @@ func TestSubround_DoWorkShouldReturnFalseWhenJobFunctionIsNotSet(t *testing.T) { } maxTime := time.Now().Add(100 * time.Millisecond) - roundHandlerMock := &consensus.RoundHandlerMock{} + roundHandlerMock := &round.RoundHandlerMock{} roundHandlerMock.RemainingTimeCalled = func(time.Time, time.Duration) time.Duration { return time.Until(maxTime) } @@ -616,8 +642,9 @@ func TestSubround_DoWorkShouldReturnFalseWhenCheckFunctionIsNotSet(t *testing.T) -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, ch, @@ -633,7 +660,7 @@ func TestSubround_DoWorkShouldReturnFalseWhenCheckFunctionIsNotSet(t *testing.T) sr.Check = nil maxTime := time.Now().Add(100 * time.Millisecond) - roundHandlerMock := &consensus.RoundHandlerMock{} + roundHandlerMock := &round.RoundHandlerMock{} roundHandlerMock.RemainingTimeCalled = func(time.Time, time.Duration) time.Duration { return time.Until(maxTime) } @@ -663,8 +690,9 @@ func testDoWork(t *testing.T, checkDone bool, shouldWork bool) { -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, ch, @@ -682,7 +710,7 @@ func testDoWork(t *testing.T, checkDone bool, shouldWork bool) { } maxTime := time.Now().Add(100 * time.Millisecond) - roundHandlerMock := &consensus.RoundHandlerMock{} + roundHandlerMock := &round.RoundHandlerMock{} roundHandlerMock.RemainingTimeCalled = func(time.Time, time.Duration) time.Duration { return time.Until(maxTime) } @@ -702,8 +730,9 @@ func TestSubround_DoWorkShouldReturnTrueWhenJobIsDoneAndConsensusIsDoneAfterAWhi -1, bls.SrStartRound, bls.SrBlock, - int64(0*roundTimeDuration/100), - int64(5*roundTimeDuration/100), + roundTimeDuration, + 0, + 0.05, "(START_ROUND)", consensusState, ch, @@ -729,7 +758,7 @@ func TestSubround_DoWorkShouldReturnTrueWhenJobIsDoneAndConsensusIsDoneAfterAWhi } maxTime := time.Now().Add(2000 * time.Millisecond) - roundHandlerMock := &consensus.RoundHandlerMock{} + roundHandlerMock := &round.RoundHandlerMock{} roundHandlerMock.RemainingTimeCalled = func(time.Time, time.Duration) time.Duration { return time.Until(maxTime) } @@ -760,8 +789,9 @@ func TestSubround_Previous(t *testing.T) { bls.SrStartRound, bls.SrBlock, bls.SrSignature, - int64(5*roundTimeDuration/100), - int64(25*roundTimeDuration/100), + roundTimeDuration, + 0.05, + 0.25, "(BLOCK)", consensusState, ch, @@ -792,8 +822,9 @@ func TestSubround_Current(t *testing.T) { bls.SrStartRound, bls.SrBlock, bls.SrSignature, - int64(5*roundTimeDuration/100), - int64(25*roundTimeDuration/100), + roundTimeDuration, + 0.05, + 0.25, "(BLOCK)", consensusState, ch, @@ -824,8 +855,9 @@ func TestSubround_Next(t *testing.T) { bls.SrStartRound, bls.SrBlock, bls.SrSignature, - int64(5*roundTimeDuration/100), - int64(25*roundTimeDuration/100), + roundTimeDuration, + 0.05, + 0.25, "(BLOCK)", consensusState, ch, @@ -856,8 +888,9 @@ func TestSubround_StartTime(t *testing.T) { bls.SrBlock, bls.SrSignature, bls.SrEndRound, - int64(25*roundTimeDuration/100), - int64(40*roundTimeDuration/100), + roundTimeDuration, + 0.25, + 0.4, "(SIGNATURE)", consensusState, ch, @@ -875,6 +908,10 @@ func TestSubround_StartTime(t *testing.T) { } assert.Equal(t, int64(25*roundTimeDuration/100), sr.StartTime()) + + newBase := roundTimeDuration * 2 + sr.SetBaseDuration(newBase) + assert.Equal(t, int64(25*newBase/100), sr.StartTime()) } func TestSubround_EndTime(t *testing.T) { @@ -888,8 +925,9 @@ func TestSubround_EndTime(t *testing.T) { bls.SrStartRound, bls.SrBlock, bls.SrSignature, - int64(5*roundTimeDuration/100), - int64(25*roundTimeDuration/100), + roundTimeDuration, + 0.05, + 0.25, "(BLOCK)", consensusState, ch, @@ -907,6 +945,10 @@ func TestSubround_EndTime(t *testing.T) { } assert.Equal(t, int64(25*roundTimeDuration/100), sr.EndTime()) + + newBase := roundTimeDuration * 2 + sr.SetBaseDuration(newBase) + assert.Equal(t, int64(25*newBase/100), sr.EndTime()) } func TestSubround_Name(t *testing.T) { @@ -920,8 +962,9 @@ func TestSubround_Name(t *testing.T) { bls.SrStartRound, bls.SrBlock, bls.SrSignature, - int64(5*roundTimeDuration/100), - int64(25*roundTimeDuration/100), + roundTimeDuration, + 0.05, + 0.25, "(BLOCK)", consensusState, ch, @@ -953,8 +996,9 @@ func TestSubround_GetAssociatedPid(t *testing.T) { bls.SrStartRound, bls.SrBlock, bls.SrSignature, - int64(5*roundTimeDuration/100), - int64(25*roundTimeDuration/100), + roundTimeDuration, + 0.05, + 0.25, "(BLOCK)", consensusState, ch, @@ -978,118 +1022,6 @@ func TestSubround_GetAssociatedPid(t *testing.T) { assert.True(t, wasCalled) } -func TestSubround_ShouldConsiderSelfKeyInConsensus(t *testing.T) { - t.Parallel() - - t.Run("is main machine active, should return true", func(t *testing.T) { - t.Parallel() - - consensusState := initConsensusState() - ch := make(chan bool, 1) - container := consensus.InitConsensusCore() - - redundancyHandler := &mock.NodeRedundancyHandlerStub{ - IsRedundancyNodeCalled: func() bool { - return false - }, - IsMainMachineActiveCalled: func() bool { - return true - }, - } - container.SetNodeRedundancyHandler(redundancyHandler) - - sr, _ := spos.NewSubround( - bls.SrStartRound, - bls.SrBlock, - bls.SrSignature, - int64(5*roundTimeDuration/100), - int64(25*roundTimeDuration/100), - "(BLOCK)", - consensusState, - ch, - executeStoredMessages, - container, - chainID, - currentPid, - &statusHandler.AppStatusHandlerStub{}, - ) - - require.True(t, sr.ShouldConsiderSelfKeyInConsensus()) - }) - - t.Run("is redundancy node machine active, should return true", func(t *testing.T) { - t.Parallel() - - consensusState := initConsensusState() - ch := make(chan bool, 1) - container := consensus.InitConsensusCore() - - redundancyHandler := &mock.NodeRedundancyHandlerStub{ - IsRedundancyNodeCalled: func() bool { - return true - }, - IsMainMachineActiveCalled: func() bool { - return false - }, - } - container.SetNodeRedundancyHandler(redundancyHandler) - - sr, _ := spos.NewSubround( - bls.SrStartRound, - bls.SrBlock, - bls.SrSignature, - int64(5*roundTimeDuration/100), - int64(25*roundTimeDuration/100), - "(BLOCK)", - consensusState, - ch, - executeStoredMessages, - container, - chainID, - currentPid, - &statusHandler.AppStatusHandlerStub{}, - ) - - require.True(t, sr.ShouldConsiderSelfKeyInConsensus()) - }) - - t.Run("is redundancy node machine but inactive, should return false", func(t *testing.T) { - t.Parallel() - - consensusState := initConsensusState() - ch := make(chan bool, 1) - container := consensus.InitConsensusCore() - - redundancyHandler := &mock.NodeRedundancyHandlerStub{ - IsRedundancyNodeCalled: func() bool { - return true - }, - IsMainMachineActiveCalled: func() bool { - return true - }, - } - container.SetNodeRedundancyHandler(redundancyHandler) - - sr, _ := spos.NewSubround( - bls.SrStartRound, - bls.SrBlock, - bls.SrSignature, - int64(5*roundTimeDuration/100), - int64(25*roundTimeDuration/100), - "(BLOCK)", - consensusState, - ch, - executeStoredMessages, - container, - chainID, - currentPid, - &statusHandler.AppStatusHandlerStub{}, - ) - - require.False(t, sr.ShouldConsiderSelfKeyInConsensus()) - }) -} - func TestSubround_GetLeaderStartRoundMessage(t *testing.T) { t.Parallel() @@ -1109,8 +1041,9 @@ func TestSubround_GetLeaderStartRoundMessage(t *testing.T) { bls.SrStartRound, bls.SrBlock, bls.SrSignature, - int64(5*roundTimeDuration/100), - int64(25*roundTimeDuration/100), + roundTimeDuration, + 0.05, + 0.25, "(BLOCK)", consensusState, ch, @@ -1141,8 +1074,9 @@ func TestSubround_GetLeaderStartRoundMessage(t *testing.T) { bls.SrStartRound, bls.SrBlock, bls.SrSignature, - int64(5*roundTimeDuration/100), - int64(25*roundTimeDuration/100), + roundTimeDuration, + 0.05, + 0.25, "(BLOCK)", consensusState, ch, @@ -1173,8 +1107,9 @@ func TestSubround_GetLeaderStartRoundMessage(t *testing.T) { bls.SrStartRound, bls.SrBlock, bls.SrSignature, - int64(5*roundTimeDuration/100), - int64(25*roundTimeDuration/100), + roundTimeDuration, + 0.05, + 0.25, "(BLOCK)", consensusState, ch, @@ -1209,8 +1144,9 @@ func TestSubround_IsSelfInConsensusGroup(t *testing.T) { bls.SrStartRound, bls.SrBlock, bls.SrSignature, - int64(5*roundTimeDuration/100), - int64(25*roundTimeDuration/100), + roundTimeDuration, + 0.05, + 0.25, "(BLOCK)", consensusState, ch, @@ -1235,8 +1171,9 @@ func TestSubround_IsSelfInConsensusGroup(t *testing.T) { bls.SrStartRound, bls.SrBlock, bls.SrSignature, - int64(5*roundTimeDuration/100), - int64(25*roundTimeDuration/100), + roundTimeDuration, + 0.05, + 0.25, "(BLOCK)", consensusState, ch, @@ -1271,8 +1208,9 @@ func TestSubround_IsSelfLeader(t *testing.T) { bls.SrStartRound, bls.SrBlock, bls.SrSignature, - int64(5*roundTimeDuration/100), - int64(25*roundTimeDuration/100), + roundTimeDuration, + 0.05, + 0.25, "(BLOCK)", consensusState, ch, @@ -1299,8 +1237,9 @@ func TestSubround_IsSelfLeader(t *testing.T) { bls.SrStartRound, bls.SrBlock, bls.SrSignature, - int64(5*roundTimeDuration/100), - int64(25*roundTimeDuration/100), + roundTimeDuration, + 0.05, + 0.25, "(BLOCK)", consensusState, ch, @@ -1317,6 +1256,82 @@ func TestSubround_IsSelfLeader(t *testing.T) { }) } +func TestSubround_Getters(t *testing.T) { + t.Parallel() + + consensusState := internalInitConsensusStateWithKeysHandler(&testscommon.KeysHandlerStub{}) + ch := make(chan bool, 1) + container := consensus.InitConsensusCore() + + providedAppStatusHandler := &statusHandler.AppStatusHandlerStub{} + sr, _ := spos.NewSubround( + bls.SrStartRound, + bls.SrBlock, + bls.SrSignature, + roundTimeDuration, + 0.05, + 0.25, + "(BLOCK)", + consensusState, + ch, + executeStoredMessages, + container, + chainID, + currentPid, + providedAppStatusHandler, + ) + require.Equal(t, chainID, sr.ChainID()) + require.Equal(t, currentPid, sr.CurrentPid()) + require.Equal(t, providedAppStatusHandler, sr.AppStatusHandler()) + require.Equal(t, ch, sr.ConsensusChannel()) +} + +func TestSubround_GetUnixTimestampForHeader(t *testing.T) { + t.Parallel() + + consensusState := internalInitConsensusStateWithKeysHandler(&testscommon.KeysHandlerStub{}) + ch := make(chan bool, 1) + container := consensus.InitConsensusCore() + isFlagEnabled := false + container.SetEnableEpochsHandler(&enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return isFlagEnabled + }, + }) + timestampBeforeSupernova := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) + timestampAfterSupernova := time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC) + container.SetRoundHandler(&testscommon.RoundHandlerMock{ + TimeStampCalled: func() time.Time { + if !isFlagEnabled { + return timestampBeforeSupernova + } + return timestampAfterSupernova + }, + }) + + providedAppStatusHandler := &statusHandler.AppStatusHandlerStub{} + sr, _ := spos.NewSubround( + bls.SrStartRound, + bls.SrBlock, + bls.SrSignature, + roundTimeDuration, + 0.05, + 0.25, + "(BLOCK)", + consensusState, + ch, + executeStoredMessages, + container, + chainID, + currentPid, + providedAppStatusHandler, + ) + require.Equal(t, uint64(timestampBeforeSupernova.Unix()), sr.GetUnixTimestampForHeader(0)) + + isFlagEnabled = true + require.Equal(t, uint64(timestampAfterSupernova.UnixMilli()), sr.GetUnixTimestampForHeader(0)) +} + func TestSubround_IsInterfaceNil(t *testing.T) { t.Parallel() @@ -1331,8 +1346,9 @@ func TestSubround_IsInterfaceNil(t *testing.T) { bls.SrStartRound, bls.SrBlock, bls.SrSignature, - int64(5*roundTimeDuration/100), - int64(25*roundTimeDuration/100), + roundTimeDuration, + 0.05, + 0.25, "(BLOCK)", consensusState, ch, @@ -1344,3 +1360,143 @@ func TestSubround_IsInterfaceNil(t *testing.T) { ) require.False(t, sr.IsInterfaceNil()) } + +func TestSubround_HasProofForCompetingBlock(t *testing.T) { + t.Parallel() + + prevBlockNonce := uint64(10) + shardID := uint32(0) + + createSubround := func( + blockchain data.ChainHandler, + proofsPool *dataRetriever.ProofsPoolMock, + ) *spos.Subround { + consensusState := internalInitConsensusStateWithKeysHandler(&testscommon.KeysHandlerStub{}) + ch := make(chan bool, 1) + container := consensus.InitConsensusCore() + container.SetBlockchain(blockchain) + container.SetEquivalentProofsPool(proofsPool) + container.SetShardCoordinator(&mock.ShardCoordinatorMock{ + ShardID: shardID, + }) + + sr, _ := spos.NewSubround( + bls.SrStartRound, + bls.SrBlock, + bls.SrSignature, + roundTimeDuration, + 0.05, + 0.25, + "(BLOCK)", + consensusState, + ch, + executeStoredMessages, + container, + chainID, + currentPid, + &statusHandler.AppStatusHandlerStub{}, + ) + return sr + } + + t.Run("nil previous block should return false", func(t *testing.T) { + t.Parallel() + + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return nil + }, + } + proofsPool := &dataRetriever.ProofsPoolMock{} + + sr := createSubround(blockchain, proofsPool) + + assert.False(t, sr.HasProofForCompetingBlock()) + }) + + t.Run("no proof at nonce should return false", func(t *testing.T) { + t.Parallel() + + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.Header{Nonce: prevBlockNonce} + }, + } + proofsPool := &dataRetriever.ProofsPoolMock{ + GetProofByNonceCalled: func(headerNonce uint64, shardId uint32) (data.HeaderProofHandler, error) { + return nil, errors.New("proof not found") + }, + } + + sr := createSubround(blockchain, proofsPool) + + assert.False(t, sr.HasProofForCompetingBlock()) + }) + + t.Run("proof exists but consensus data is empty should return true", func(t *testing.T) { + t.Parallel() + + competingBlockHash := []byte("competing_block_hash") + + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.Header{Nonce: prevBlockNonce} + }, + } + proofsPool := &dataRetriever.ProofsPoolMock{ + GetProofByNonceCalled: func(headerNonce uint64, shardId uint32) (data.HeaderProofHandler, error) { + return &block.HeaderProof{HeaderHash: competingBlockHash}, nil + }, + } + + sr := createSubround(blockchain, proofsPool) + sr.SetData(nil) + + assert.True(t, sr.HasProofForCompetingBlock()) + }) + + t.Run("proof for same block should return false", func(t *testing.T) { + t.Parallel() + + currentBlockHash := []byte("current_block_hash") + + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.Header{Nonce: prevBlockNonce} + }, + } + proofsPool := &dataRetriever.ProofsPoolMock{ + GetProofByNonceCalled: func(headerNonce uint64, shardId uint32) (data.HeaderProofHandler, error) { + return &block.HeaderProof{HeaderHash: currentBlockHash}, nil + }, + } + + sr := createSubround(blockchain, proofsPool) + sr.SetData(currentBlockHash) + + assert.False(t, sr.HasProofForCompetingBlock()) + }) + + t.Run("proof for different block should return true", func(t *testing.T) { + t.Parallel() + + currentBlockHash := []byte("current_block_hash") + competingBlockHash := []byte("competing_block_hash") + + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.Header{Nonce: prevBlockNonce} + }, + } + proofsPool := &dataRetriever.ProofsPoolMock{ + GetProofByNonceCalled: func(headerNonce uint64, shardId uint32) (data.HeaderProofHandler, error) { + return &block.HeaderProof{HeaderHash: competingBlockHash}, nil + }, + } + + sr := createSubround(blockchain, proofsPool) + sr.SetData(currentBlockHash) + + assert.True(t, sr.HasProofForCompetingBlock()) + }) +} diff --git a/consensus/spos/worker.go b/consensus/spos/worker.go index 54c493fd7ed..20556782702 100644 --- a/consensus/spos/worker.go +++ b/consensus/spos/worker.go @@ -18,6 +18,7 @@ import ( "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" crypto "github.com/multiversx/mx-chain-crypto-go" + commonConsensus "github.com/multiversx/mx-chain-go/common/consensus" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/consensus" @@ -59,6 +60,7 @@ type Worker struct { headerIntegrityVerifier process.HeaderIntegrityVerifier appStatusHandler core.AppStatusHandler enableEpochsHandler common.EnableEpochsHandler + enableRoundsHandler common.EnableRoundsHandler networkShardingCollector consensus.NetworkShardingCollector @@ -90,6 +92,7 @@ type Worker struct { closer core.SafeCloser invalidSignersCache InvalidSignersCache + consensusMetrics ConsensusMetricsHandler } // WorkerArgs holds the consensus worker arguments @@ -121,6 +124,7 @@ type WorkerArgs struct { PeerBlacklistHandler consensus.PeerBlacklistHandler EnableEpochsHandler common.EnableEpochsHandler InvalidSignersCache InvalidSignersCache + EnableRoundsHandler common.EnableRoundsHandler } // NewWorker creates a new Worker object @@ -148,6 +152,11 @@ func NewWorker(args *WorkerArgs) (*Worker, error) { return nil, err } + consensusMetrics, err := NewConsensusMetrics(args.AppStatusHandler) + if err != nil { + return nil, err + } + wrk := Worker{ consensusService: args.ConsensusService, blockChain: args.BlockChain, @@ -173,7 +182,9 @@ func NewWorker(args *WorkerArgs) (*Worker, error) { peerBlacklistHandler: args.PeerBlacklistHandler, closer: closing.NewSafeChanCloser(), enableEpochsHandler: args.EnableEpochsHandler, + enableRoundsHandler: args.EnableRoundsHandler, invalidSignersCache: args.InvalidSignersCache, + consensusMetrics: consensusMetrics, } wrk.consensusMessageValidator = consensusMessageValidatorObj @@ -302,7 +313,8 @@ func (wrk *Worker) addFutureHeaderToProcessIfNeeded(header data.HeaderHandler) { } isHeaderForNextRound := int64(header.GetRound()) == wrk.roundHandler.Index()+1 - if !isHeaderForNextRound { + isHeaderForCurrentRound := int64(header.GetRound()) == wrk.roundHandler.Index() + if !isHeaderForCurrentRound && !isHeaderForNextRound { return } @@ -725,7 +737,9 @@ func (wrk *Worker) computeRedundancyMetrics() (bool, string) { func (wrk *Worker) checkSelfState(cnsDta *consensus.Message) error { isMultiKeyManagedBySelf := wrk.consensusState.keysHandler.IsKeyManagedByCurrentNode(cnsDta.PubKey) - if wrk.consensusState.SelfPubKey() == string(cnsDta.PubKey) || isMultiKeyManagedBySelf { + isSelfKey := wrk.consensusState.SelfPubKey() == string(cnsDta.PubKey) + shouldConsiderSelfKeyInConsensus := isSelfKey && commonConsensus.ShouldConsiderSelfKeyInConsensus(wrk.nodeRedundancyHandler) + if shouldConsiderSelfKeyInConsensus || isMultiKeyManagedBySelf { return ErrMessageFromItself } @@ -855,11 +869,23 @@ func (wrk *Worker) Extend(subroundId int) { time.Sleep(time.Millisecond) } - wrk.scheduledProcessor.ForceStopScheduledExecutionBlocking() - wrk.blockProcessor.RevertCurrentBlock() + log.Debug("current block is reverted") + + if !wrk.isAsyncExecEnabled() { + wrk.scheduledProcessor.ForceStopScheduledExecutionBlocking() + wrk.blockProcessor.RevertCurrentBlock() + } + wrk.removeConsensusHeaderFromPool() +} - log.Debug("current block is reverted") +func (wrk *Worker) isAsyncExecEnabled() bool { + header := wrk.consensusState.GetHeader() + if !check.IfNil(header) { + return header.IsHeaderV3() + } + + return wrk.enableRoundsHandler.IsFlagEnabled(common.SupernovaRoundFlag) } func (wrk *Worker) removeConsensusHeaderFromPool() { @@ -988,3 +1014,8 @@ func emptyChannel(ch chan *consensus.Message) int { } } } + +// ConsensusMetrics returns the consensus metrics handler +func (wrk *Worker) ConsensusMetrics() ConsensusMetricsHandler { + return wrk.consensusMetrics +} diff --git a/consensus/spos/worker_test.go b/consensus/spos/worker_test.go index a144b88dcff..7f6a825ad89 100644 --- a/consensus/spos/worker_test.go +++ b/consensus/spos/worker_test.go @@ -34,6 +34,7 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" "github.com/multiversx/mx-chain-go/testscommon/p2pmocks" "github.com/multiversx/mx-chain-go/testscommon/processMocks" + "github.com/multiversx/mx-chain-go/testscommon/round" statusHandlerMock "github.com/multiversx/mx-chain-go/testscommon/statusHandler" ) @@ -123,6 +124,7 @@ func createDefaultWorkerArgs(appStatusHandler core.AppStatusHandler) *spos.Worke NodeRedundancyHandler: &mock.NodeRedundancyHandlerStub{}, PeerBlacklistHandler: &mock.PeerBlacklistHandlerStub{}, EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, InvalidSignersCache: &consensusMocks.InvalidSignersCacheMock{}, } @@ -149,8 +151,8 @@ func initWorker(appStatusHandler core.AppStatusHandler) *spos.Worker { return sposWorker } -func initRoundHandlerMock() *consensusMocks.RoundHandlerMock { - return &consensusMocks.RoundHandlerMock{ +func initRoundHandlerMock() *round.RoundHandlerMock { + return &round.RoundHandlerMock{ RoundIndex: 0, TimeStampCalled: func() time.Time { return time.Unix(0, 0) @@ -802,7 +804,7 @@ func testWorkerProcessReceivedMessageComputeReceivedProposedBlockMetric( }, }) - wrk.SetRoundHandler(&consensusMocks.RoundHandlerMock{ + wrk.SetRoundHandler(&round.RoundHandlerMock{ RoundIndex: 0, TimeDurationCalled: func() time.Duration { return roundDuration @@ -1161,7 +1163,7 @@ func TestWorker_ProcessReceivedMessageReceivedMessageIsFromSelfShouldRetNilAndNo func TestWorker_ProcessReceivedMessageWhenRoundIsCanceledShouldRetNilAndNotProcess(t *testing.T) { t.Parallel() wrk := *initWorker(&statusHandlerMock.AppStatusHandlerStub{}) - wrk.ConsensusState().RoundCanceled = true + wrk.ConsensusState().SetRoundCanceled(true) blk := &block.Body{} blkStr, _ := mock.MarshalizerMock{}.Marshal(blk) cnsMsg := consensus.NewConsensusMessage( @@ -1461,7 +1463,7 @@ func TestWorker_CheckSelfStateShouldErrMessageFromItself(t *testing.T) { func TestWorker_CheckSelfStateShouldErrRoundCanceled(t *testing.T) { t.Parallel() wrk := *initWorker(&statusHandlerMock.AppStatusHandlerStub{}) - wrk.ConsensusState().RoundCanceled = true + wrk.ConsensusState().SetRoundCanceled(true) cnsMsg := consensus.NewConsensusMessage( nil, nil, @@ -1688,6 +1690,7 @@ func TestWorker_ExecuteSignatureMessagesShouldNotExecuteWhenBlockIsNotFinished(t func TestWorker_ExecuteMessagesShouldExecute(t *testing.T) { t.Parallel() + wrk := *initWorker(&statusHandlerMock.AppStatusHandlerStub{}) wrk.StartWorking() blk := &block.Body{} @@ -1829,7 +1832,7 @@ func TestWorker_StoredHeadersExecution(t *testing.T) { roundIndex := &atomic.Int64{} roundIndex.Store(99) - roundHandler := &consensusMocks.RoundHandlerMock{ + roundHandler := &round.RoundHandlerMock{ IndexCalled: func() int64 { return roundIndex.Load() }, @@ -1872,7 +1875,7 @@ func TestWorker_StoredHeadersExecution(t *testing.T) { roundIndex := &atomic.Int64{} roundIndex.Store(99) - roundHandler := &consensusMocks.RoundHandlerMock{ + roundHandler := &round.RoundHandlerMock{ IndexCalled: func() int64 { return roundIndex.Load() }, @@ -1922,7 +1925,7 @@ func TestWorker_StoredHeadersExecution(t *testing.T) { roundIndex := &atomic.Int64{} roundIndex.Store(99) - roundHandler := &consensusMocks.RoundHandlerMock{ + roundHandler := &round.RoundHandlerMock{ IndexCalled: func() int64 { return roundIndex.Load() }, @@ -1975,7 +1978,7 @@ func TestWorker_ExtendShouldReturnWhenRoundIsCanceled(t *testing.T) { }, } wrk.SetBootstrapper(bootstrapperMock) - wrk.ConsensusState().RoundCanceled = true + wrk.ConsensusState().SetRoundCanceled(true) wrk.Extend(0) assert.False(t, executed) @@ -2394,3 +2397,30 @@ func TestWorker_ReceivedProof(t *testing.T) { require.True(t, wasHandlerCalled) }) } + +func TestWorker_ConsensusMetrics(t *testing.T) { + t.Parallel() + + workerArgs := createDefaultWorkerArgs(&statusHandlerMock.AppStatusHandlerStub{}) + wrk, _ := spos.NewWorker(workerArgs) + + metrics := wrk.ConsensusMetrics() + require.NotNil(t, metrics) +} + +func TestWorker_NewWorkerNilConsensusMetrics(t *testing.T) { + t.Parallel() + called := 0 + var typedNil core.AppStatusHandler = &statusHandlerMock.AppStatusHandlerStub{ + IsInterfaceNilCalled: func() bool { + called++ + return called > 1 + }, + } + + workerArgs := createDefaultWorkerArgs(typedNil) + worker, err := spos.NewWorker(workerArgs) + require.Nil(t, worker) + require.Error(t, err) // should come from NewConsensusMetrics + require.Equal(t, spos.ErrNilAppStatusHandler, err) +} diff --git a/dataRetriever/blockchain/baseBlockchain.go b/dataRetriever/blockchain/baseBlockchain.go index 5af565334fc..6b792cf4d61 100644 --- a/dataRetriever/blockchain/baseBlockchain.go +++ b/dataRetriever/blockchain/baseBlockchain.go @@ -6,16 +6,20 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/common" ) type baseBlockChain struct { - mut sync.RWMutex - appStatusHandler core.AppStatusHandler - genesisHeader data.HeaderHandler - genesisHeaderHash []byte - currentBlockHeader data.HeaderHandler - currentBlockHeaderHash []byte - finalBlockInfo *blockInfo + mut sync.RWMutex + appStatusHandler core.AppStatusHandler + genesisHeader data.HeaderHandler + genesisHeaderHash []byte + currentBlockHeader data.HeaderHandler + currentBlockHeaderHash []byte + finalBlockInfo *blockInfo + lastExecutedBlockInfo *blockInfo + lastExecutedBlockHeader data.HeaderHandler + lastExecutionResult data.BaseExecutionResultHandler } type blockInfo struct { @@ -100,3 +104,93 @@ func (bbc *baseBlockChain) GetFinalBlockInfo() (uint64, []byte, []byte) { return nonce, hash, rootHash } + +// GetLastExecutedBlockInfo returns the nonce, hash and rootHash associated with the last executed results +func (bbc *baseBlockChain) GetLastExecutedBlockInfo() (uint64, []byte, []byte) { + bbc.mut.RLock() + defer bbc.mut.RUnlock() + + nonce := bbc.lastExecutedBlockInfo.nonce + hash := bbc.lastExecutedBlockInfo.hash + rootHash := bbc.lastExecutedBlockInfo.committedRootHash + + return nonce, hash, rootHash +} + +// GetLastExecutedBlockHeader returns last executed block header pointer +func (bbc *baseBlockChain) GetLastExecutedBlockHeader() data.HeaderHandler { + bbc.mut.RLock() + defer bbc.mut.RUnlock() + + if check.IfNil(bbc.lastExecutedBlockHeader) { + return nil + } + + return bbc.lastExecutedBlockHeader.ShallowClone() +} + +// SetLastExecutedBlockHeaderAndRootHash sets header, hash and rootHash for last executed block +func (bbc *baseBlockChain) SetLastExecutedBlockHeaderAndRootHash( + header data.HeaderHandler, + headerHash []byte, + rootHash []byte, +) { + bbc.mut.Lock() + defer bbc.mut.Unlock() + + if check.IfNil(header) { + bbc.lastExecutedBlockHeader = nil + bbc.lastExecutedBlockInfo.nonce = 0 + bbc.lastExecutedBlockInfo.hash = nil + bbc.lastExecutedBlockInfo.committedRootHash = nil + return + } + + bbc.lastExecutedBlockInfo.nonce = header.GetNonce() + bbc.lastExecutedBlockInfo.hash = headerHash + bbc.lastExecutedBlockInfo.committedRootHash = rootHash + + bbc.lastExecutedBlockHeader = header.ShallowClone() + bbc.appStatusHandler.SetUInt64Value(common.MetricLastExecutedNonce, header.GetNonce()) +} + +// GetLastExecutionResult returns the last execution result +func (bbc *baseBlockChain) GetLastExecutionResult() data.BaseExecutionResultHandler { + bbc.mut.RLock() + defer bbc.mut.RUnlock() + + return bbc.lastExecutionResult +} + +// SetLastExecutionResult sets the last execution result +func (bbc *baseBlockChain) SetLastExecutionResult(result data.BaseExecutionResultHandler) { + bbc.mut.Lock() + defer bbc.mut.Unlock() + + bbc.lastExecutionResult = result +} + +func (bbc *baseBlockChain) setCurrentHeaderMetrics( + header data.HeaderHandler, +) { + if header.IsHeaderV3() { + bbc.setMetricsHeaderV3(header) + } else { + bbc.appStatusHandler.SetUInt64Value(common.MetricNonce, header.GetNonce()) + } + + bbc.appStatusHandler.SetUInt64Value(common.MetricSynchronizedRound, header.GetRound()) + bbc.appStatusHandler.SetUInt64Value(common.MetricBlockTimestamp, header.GetTimeStamp()) + bbc.appStatusHandler.SetUInt64Value(common.MetricBlockTimestampMs, header.GetTimeStamp()) +} + +func (bbc *baseBlockChain) setMetricsHeaderV3(header data.HeaderHandler) { + bbc.appStatusHandler.SetUInt64Value(common.MetricProposedNonce, header.GetNonce()) + + executionResult, err := common.GetLastBaseExecutionResultHandler(header) + if err != nil { + // any error returned here is intentionally ignored, as it is checked in other locations. + return + } + bbc.appStatusHandler.SetUInt64Value(common.MetricNonce, executionResult.GetHeaderNonce()) +} diff --git a/dataRetriever/blockchain/baseBlockchain_test.go b/dataRetriever/blockchain/baseBlockchain_test.go index 69a49304db0..09c642af05b 100644 --- a/dataRetriever/blockchain/baseBlockchain_test.go +++ b/dataRetriever/blockchain/baseBlockchain_test.go @@ -1,9 +1,13 @@ package blockchain import ( + "sync" "testing" + "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/mock" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/testscommon/statusHandler" "github.com/stretchr/testify/require" ) @@ -47,3 +51,192 @@ func TestBaseBlockchain_SetAndGetSetFinalBlockInfoWorksWithNilValues(t *testing. require.Nil(t, actualHash) require.Nil(t, actualRootHash) } + +func TestBaseBlockchain_SetAndGetLastExecutedBlockHeaderAndRootHash(t *testing.T) { + t.Parallel() + + t.Run("should set nil if header nil provided", func(t *testing.T) { + t.Parallel() + + base := &baseBlockChain{ + appStatusHandler: &mock.AppStatusHandlerStub{}, + finalBlockInfo: &blockInfo{}, + lastExecutedBlockInfo: &blockInfo{}, + } + + hash := []byte("hash") + rootHash := []byte("root-hash") + + base.SetLastExecutedBlockHeaderAndRootHash(nil, hash, rootHash) + + retHeader := base.GetLastExecutedBlockHeader() + + require.Nil(t, retHeader) + + retNonce, retHeaderHash, retRootHash := base.GetLastExecutedBlockInfo() + require.Zero(t, retNonce) + require.Nil(t, retHeaderHash) + require.Nil(t, retRootHash) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + base := &baseBlockChain{ + appStatusHandler: &mock.AppStatusHandlerStub{}, + finalBlockInfo: &blockInfo{}, + lastExecutedBlockInfo: &blockInfo{}, + } + + nonce := uint64(10) + hash := []byte("hash") + rootHash := []byte("root-hash") + + header1 := &block.HeaderV3{ + Nonce: nonce, + } + + base.SetLastExecutedBlockHeaderAndRootHash(header1, hash, rootHash) + + retHeader := base.GetLastExecutedBlockHeader() + + require.Equal(t, nonce, retHeader.GetNonce()) + + retNonce, retHeaderHash, retRootHash := base.GetLastExecutedBlockInfo() + require.Equal(t, nonce, retNonce) + require.Equal(t, hash, retHeaderHash) + require.Equal(t, rootHash, retRootHash) + }) +} + +func TestBaseBlockchain_SetAndGetLastExecutionResult(t *testing.T) { + t.Parallel() + + base := &baseBlockChain{ + appStatusHandler: &mock.AppStatusHandlerStub{}, + finalBlockInfo: &blockInfo{}, + lastExecutedBlockInfo: &blockInfo{}, + } + + nonce := uint64(10) + hash := []byte("hash") + rootHash := []byte("root-hash") + + execResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + RootHash: rootHash, + HeaderHash: hash, + HeaderNonce: nonce, + }, + } + + base.SetLastExecutionResult(execResult) + + retExecResult := base.GetLastExecutionResult() + + require.Equal(t, execResult, retExecResult) +} + +func TestBaseBlockchain_Concurrency(t *testing.T) { + t.Parallel() + + bc := &baseBlockChain{ + appStatusHandler: &mock.AppStatusHandlerStub{}, + finalBlockInfo: &blockInfo{}, + lastExecutedBlockInfo: &blockInfo{}, + } + + numCalls := 100 + + header := &block.HeaderV3{} + headerHash := []byte("headerHash") + rootHash := []byte("rootHash") + + execResult := &block.ExecutionResult{} + + var wg sync.WaitGroup + wg.Add(numCalls) + + for i := range numCalls { + go func(i int) { + switch i % 12 { + case 0: + _ = bc.GetCurrentBlockHeaderHash() + case 1: + _ = bc.GetGenesisHeader() + case 2: + _ = bc.GetGenesisHeaderHash() + case 3: + _ = bc.GetLastExecutedBlockHeader() + case 4: + _, _, _ = bc.GetFinalBlockInfo() + case 5: + _, _, _ = bc.GetLastExecutedBlockInfo() + case 6: + bc.SetLastExecutedBlockHeaderAndRootHash(header, headerHash, rootHash) + case 7: + bc.SetCurrentBlockHeaderHash(headerHash) + case 8: + bc.SetFinalBlockInfo(0, headerHash, rootHash) + case 9: + bc.SetGenesisHeaderHash(headerHash) + case 10: + bc.SetLastExecutionResult(execResult) + case 11: + _ = bc.GetLastExecutionResult() + default: + require.Fail(t, "should have not been called") + } + wg.Done() + }(i) + } + + wg.Wait() +} + +func TestBaseBlockchain_setMetrics(t *testing.T) { + t.Parallel() + + t.Run("set metrics header v1", func(t *testing.T) { + handler := statusHandler.NewAppStatusHandlerMock() + + bc := &baseBlockChain{ + appStatusHandler: handler, + } + + header := &block.Header{ + Nonce: 10, + Round: 11, + TimeStamp: 1234, + } + bc.setCurrentHeaderMetrics(header) + + require.Equal(t, header.Nonce, handler.GetUint64(common.MetricNonce)) + require.Equal(t, header.Round, handler.GetUint64(common.MetricSynchronizedRound)) + require.Equal(t, header.TimeStamp, handler.GetUint64(common.MetricBlockTimestampMs)) + }) + + t.Run("set metrics header v3", func(t *testing.T) { + handler := statusHandler.NewAppStatusHandlerMock() + + bc := &baseBlockChain{ + appStatusHandler: handler, + } + + header := &block.HeaderV3{ + Nonce: 10, + Round: 11, + TimestampMs: 1234000, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 9, + }, + }, + } + bc.setCurrentHeaderMetrics(header) + + require.Equal(t, uint64(9), handler.GetUint64(common.MetricNonce)) + require.Equal(t, header.Round, handler.GetUint64(common.MetricSynchronizedRound)) + require.Equal(t, header.TimestampMs, handler.GetUint64(common.MetricBlockTimestampMs)) + }) +} diff --git a/dataRetriever/blockchain/blockchain.go b/dataRetriever/blockchain/blockchain.go index 241385b379e..3e34d0f37e8 100644 --- a/dataRetriever/blockchain/blockchain.go +++ b/dataRetriever/blockchain/blockchain.go @@ -4,7 +4,6 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" - "github.com/multiversx/mx-chain-go/common" ) var _ data.ChainHandler = (*blockChain)(nil) @@ -24,8 +23,9 @@ func NewBlockChain(appStatusHandler core.AppStatusHandler) (*blockChain, error) } return &blockChain{ baseBlockChain: &baseBlockChain{ - appStatusHandler: appStatusHandler, - finalBlockInfo: &blockInfo{}, + appStatusHandler: appStatusHandler, + finalBlockInfo: &blockInfo{}, + lastExecutedBlockInfo: &blockInfo{}, }, }, nil } @@ -51,14 +51,17 @@ func (bc *blockChain) SetGenesisHeader(genesisBlock data.HeaderHandler) error { return nil } -// SetCurrentBlockHeaderAndRootHash sets current block header pointer and the root hash -func (bc *blockChain) SetCurrentBlockHeaderAndRootHash(header data.HeaderHandler, rootHash []byte) error { +// SetCurrentBlockHeader sets current block header pointer +func (bc *blockChain) SetCurrentBlockHeader(header data.HeaderHandler) error { + bc.mut.Lock() + defer bc.mut.Unlock() + + return bc.setCurrentBlockHeaderUnprotected(header) +} + +func (bc *blockChain) setCurrentBlockHeaderUnprotected(header data.HeaderHandler) error { if check.IfNil(header) { - bc.mut.Lock() bc.currentBlockHeader = nil - bc.currentBlockRootHash = nil - bc.mut.Unlock() - return nil } @@ -67,16 +70,25 @@ func (bc *blockChain) SetCurrentBlockHeaderAndRootHash(header data.HeaderHandler return data.ErrInvalidHeaderType } - bc.appStatusHandler.SetUInt64Value(common.MetricNonce, h.GetNonce()) - bc.appStatusHandler.SetUInt64Value(common.MetricSynchronizedRound, h.GetRound()) - bc.appStatusHandler.SetUInt64Value(common.MetricBlockTimestamp, h.GetTimeStamp()) - bc.appStatusHandler.SetUInt64Value(common.MetricBlockTimestampMs, common.ConvertTimeStampSecToMs(h.GetTimeStamp())) + bc.currentBlockHeader = h.ShallowClone() + + bc.setCurrentHeaderMetrics(header) + return nil +} + +// SetCurrentBlockHeaderAndRootHash sets current block header pointer and the root hash +func (bc *blockChain) SetCurrentBlockHeaderAndRootHash(header data.HeaderHandler, rootHash []byte) error { bc.mut.Lock() - bc.currentBlockHeader = h.ShallowClone() + defer bc.mut.Unlock() + + err := bc.setCurrentBlockHeaderUnprotected(header) + if err != nil { + return err + } + bc.currentBlockRootHash = make([]byte, len(rootHash)) copy(bc.currentBlockRootHash, rootHash) - bc.mut.Unlock() return nil } diff --git a/dataRetriever/blockchain/blockchain_test.go b/dataRetriever/blockchain/blockchain_test.go index 212d2755adc..cc235cf495a 100644 --- a/dataRetriever/blockchain/blockchain_test.go +++ b/dataRetriever/blockchain/blockchain_test.go @@ -2,6 +2,7 @@ package blockchain import ( "fmt" + "sync" "testing" "github.com/multiversx/mx-chain-core-go/core/check" @@ -9,6 +10,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/mock" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNewBlockChain_ShouldWork(t *testing.T) { @@ -93,3 +95,58 @@ func TestBlockChain_SettersInvalidValues(t *testing.T) { err = bc.SetCurrentBlockHeaderAndRootHash(&block.MetaBlock{}, []byte("root hash")) assert.Equal(t, err, data.ErrInvalidHeaderType) } + +func TestBlockChain_SetCurrentBlockHeader(t *testing.T) { + t.Parallel() + + bc, _ := NewBlockChain(&mock.AppStatusHandlerStub{}) + + hdr := &block.Header{ + Nonce: 4, + } + hdrHash := []byte("hash") + + bc.SetCurrentBlockHeaderHash(hdrHash) + + err := bc.SetCurrentBlockHeader(hdr) + assert.Nil(t, err) + + assert.Equal(t, hdr, bc.GetCurrentBlockHeader()) + assert.False(t, hdr == bc.GetCurrentBlockHeader()) + + assert.Equal(t, hdrHash, bc.GetCurrentBlockHeaderHash()) +} + +func TestBlockChain_Concurrency(t *testing.T) { + t.Parallel() + + bc, _ := NewBlockChain(&mock.AppStatusHandlerStub{}) + + numCalls := 100 + + header := &block.HeaderV3{} + rootHash := []byte("rootHash") + + var wg sync.WaitGroup + wg.Add(numCalls) + + for i := range numCalls { + go func(i int) { + switch i % 4 { + case 0: + _ = bc.GetCurrentBlockRootHash() + case 1: + _ = bc.SetCurrentBlockHeader(header) + case 2: + _ = bc.SetCurrentBlockHeaderAndRootHash(header, rootHash) + case 3: + _ = bc.SetGenesisHeader(header) + default: + require.Fail(t, "should have not been called") + } + wg.Done() + }(i) + } + + wg.Wait() +} diff --git a/dataRetriever/blockchain/errors.go b/dataRetriever/blockchain/errors.go index 280dc1a8bdd..41677e6140f 100644 --- a/dataRetriever/blockchain/errors.go +++ b/dataRetriever/blockchain/errors.go @@ -18,3 +18,6 @@ var ErrOperationNotPermitted = errors.New("operation not permitted") // ErrNilBlockChain signals that a nil blockchain has been provided var ErrNilBlockChain = errors.New("nil block chain") + +// ErrNonceDoesNotMatch signals the nonce in block is different from last executed nonce +var ErrNonceDoesNotMatch = errors.New("nonce does not match") diff --git a/dataRetriever/blockchain/metachain.go b/dataRetriever/blockchain/metachain.go index 269d5d949d5..362e6de0cb1 100644 --- a/dataRetriever/blockchain/metachain.go +++ b/dataRetriever/blockchain/metachain.go @@ -4,14 +4,10 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" - "github.com/multiversx/mx-chain-core-go/data/block" - "github.com/multiversx/mx-chain-go/common" ) var _ data.ChainHandler = (*metaChain)(nil) -// metaChain holds the block information for the beacon chain -// // The MetaChain also holds pointers to the Genesis block and the current block. type metaChain struct { *baseBlockChain @@ -26,8 +22,9 @@ func NewMetaChain(appStatusHandler core.AppStatusHandler) (*metaChain, error) { return &metaChain{ baseBlockChain: &baseBlockChain{ - appStatusHandler: appStatusHandler, - finalBlockInfo: &blockInfo{}, + appStatusHandler: appStatusHandler, + finalBlockInfo: &blockInfo{}, + lastExecutedBlockInfo: &blockInfo{}, }, }, nil } @@ -42,7 +39,7 @@ func (mc *metaChain) SetGenesisHeader(header data.HeaderHandler) error { return nil } - genBlock, ok := header.(*block.MetaBlock) + genBlock, ok := header.(data.MetaHeaderHandler) if !ok { return ErrWrongTypeInSet } @@ -53,32 +50,44 @@ func (mc *metaChain) SetGenesisHeader(header data.HeaderHandler) error { return nil } -// SetCurrentBlockHeaderAndRootHash sets current block header pointer and the root hash -func (mc *metaChain) SetCurrentBlockHeaderAndRootHash(header data.HeaderHandler, rootHash []byte) error { +// SetCurrentBlockHeader sets current block header pointer +func (mc *metaChain) SetCurrentBlockHeader(header data.HeaderHandler) error { + mc.mut.Lock() + defer mc.mut.Unlock() + + return mc.setCurrentBlockHeaderUnprotected(header) +} + +func (mc *metaChain) setCurrentBlockHeaderUnprotected(header data.HeaderHandler) error { if check.IfNil(header) { - mc.mut.Lock() mc.currentBlockHeader = nil - mc.currentBlockRootHash = nil - mc.mut.Unlock() - return nil } - currHead, ok := header.(*block.MetaBlock) + currHead, ok := header.(data.MetaHeaderHandler) if !ok { return ErrWrongTypeInSet } - mc.appStatusHandler.SetUInt64Value(common.MetricNonce, currHead.Nonce) - mc.appStatusHandler.SetUInt64Value(common.MetricSynchronizedRound, currHead.Round) - mc.appStatusHandler.SetUInt64Value(common.MetricBlockTimestamp, currHead.GetTimeStamp()) - mc.appStatusHandler.SetUInt64Value(common.MetricBlockTimestampMs, common.ConvertTimeStampSecToMs(currHead.GetTimeStamp())) + mc.currentBlockHeader = currHead.ShallowClone() + + mc.setCurrentHeaderMetrics(header) + return nil +} + +// SetCurrentBlockHeaderAndRootHash sets current block header pointer and the root hash +func (mc *metaChain) SetCurrentBlockHeaderAndRootHash(header data.HeaderHandler, rootHash []byte) error { mc.mut.Lock() - mc.currentBlockHeader = currHead.ShallowClone() + defer mc.mut.Unlock() + + err := mc.setCurrentBlockHeaderUnprotected(header) + if err != nil { + return err + } + mc.currentBlockRootHash = make([]byte, len(rootHash)) copy(mc.currentBlockRootHash, rootHash) - mc.mut.Unlock() return nil } diff --git a/dataRetriever/blockchain/metachain_test.go b/dataRetriever/blockchain/metachain_test.go index 684aa95477c..9ab96bb12f3 100644 --- a/dataRetriever/blockchain/metachain_test.go +++ b/dataRetriever/blockchain/metachain_test.go @@ -2,12 +2,14 @@ package blockchain import ( "fmt" + "sync" "testing" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/mock" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNewMetaChain_ShouldWork(t *testing.T) { @@ -93,3 +95,81 @@ func TestMetaChain_SettersInvalidValues(t *testing.T) { err = bc.SetCurrentBlockHeaderAndRootHash(&block.Header{}, []byte("root hash")) assert.Equal(t, err, ErrWrongTypeInSet) } + +func TestMetaChain_SetCurrentBlockHeader(t *testing.T) { + t.Parallel() + + bc, _ := NewMetaChain(&mock.AppStatusHandlerStub{}) + + t.Run("metablock v1", func(t *testing.T) { + t.Parallel() + + hdr := &block.MetaBlock{ + Nonce: 4, + } + hdrHash := []byte("hash") + + bc.SetCurrentBlockHeaderHash(hdrHash) + + err := bc.SetCurrentBlockHeader(hdr) + assert.Nil(t, err) + + assert.Equal(t, hdr, bc.GetCurrentBlockHeader()) + assert.False(t, hdr == bc.GetCurrentBlockHeader()) + + assert.Equal(t, hdrHash, bc.GetCurrentBlockHeaderHash()) + }) + + t.Run("metablock v3", func(t *testing.T) { + t.Parallel() + + hdr := &block.MetaBlockV3{ + Nonce: 4, + } + hdrHash := []byte("hash") + + bc.SetCurrentBlockHeaderHash(hdrHash) + + err := bc.SetCurrentBlockHeader(hdr) + assert.Nil(t, err) + + assert.Equal(t, hdr, bc.GetCurrentBlockHeader()) + assert.False(t, hdr == bc.GetCurrentBlockHeader()) + + assert.Equal(t, hdrHash, bc.GetCurrentBlockHeaderHash()) + }) +} + +func TestMetaChain_Concurrency(t *testing.T) { + t.Parallel() + + bc, _ := NewMetaChain(&mock.AppStatusHandlerStub{}) + + numCalls := 100 + + header := &block.MetaBlockV3{} + rootHash := []byte("rootHash") + + var wg sync.WaitGroup + wg.Add(numCalls) + + for i := range numCalls { + go func(i int) { + switch i % 4 { + case 0: + _ = bc.GetCurrentBlockRootHash() + case 1: + _ = bc.SetCurrentBlockHeader(header) + case 2: + _ = bc.SetCurrentBlockHeaderAndRootHash(header, rootHash) + case 3: + _ = bc.SetGenesisHeader(header) + default: + require.Fail(t, "should have not been called") + } + wg.Done() + }(i) + } + + wg.Wait() +} diff --git a/dataRetriever/dataPool/dataPool.go b/dataRetriever/dataPool/dataPool.go index be759b15b43..8b582efc3d3 100644 --- a/dataRetriever/dataPool/dataPool.go +++ b/dataRetriever/dataPool/dataPool.go @@ -12,21 +12,24 @@ var _ dataRetriever.PoolsHolder = (*dataPool)(nil) var log = logger.GetOrCreate("dataRetriever/dataPool") type dataPool struct { - transactions dataRetriever.ShardedDataCacherNotifier - unsignedTransactions dataRetriever.ShardedDataCacherNotifier - rewardTransactions dataRetriever.ShardedDataCacherNotifier - headers dataRetriever.HeadersPool - miniBlocks storage.Cacher - peerChangesBlocks storage.Cacher - trieNodes storage.Cacher - trieNodesChunks storage.Cacher - currBlockTxs dataRetriever.TransactionCacher - currEpochValidatorInfo dataRetriever.ValidatorInfoCacher - smartContracts storage.Cacher - peerAuthentications storage.Cacher - heartbeats storage.Cacher - validatorsInfo dataRetriever.ShardedDataCacherNotifier - proofs dataRetriever.ProofsPool + transactions dataRetriever.ShardedDataCacherNotifier + unsignedTransactions dataRetriever.ShardedDataCacherNotifier + rewardTransactions dataRetriever.ShardedDataCacherNotifier + headers dataRetriever.HeadersPool + miniBlocks storage.Cacher + peerChangesBlocks storage.Cacher + trieNodes storage.Cacher + trieNodesChunks storage.Cacher + currBlockTxs dataRetriever.TransactionCacher + currEpochValidatorInfo dataRetriever.ValidatorInfoCacher + smartContracts storage.Cacher + peerAuthentications storage.Cacher + heartbeats storage.Cacher + validatorsInfo dataRetriever.ShardedDataCacherNotifier + proofs dataRetriever.ProofsPool + executedMiniBlocks storage.Cacher + postProcessTransactions storage.Cacher + directSentTransactions storage.Cacher } // DataPoolArgs represents the data pool's constructor structure @@ -46,6 +49,9 @@ type DataPoolArgs struct { Heartbeats storage.Cacher ValidatorsInfo dataRetriever.ShardedDataCacherNotifier Proofs dataRetriever.ProofsPool + ExecutedMiniBlocks storage.Cacher + PostProcessTransactions storage.Cacher + DirectSentTransactions storage.Cacher } // NewDataPool creates a data pools holder object @@ -95,23 +101,35 @@ func NewDataPool(args DataPoolArgs) (*dataPool, error) { if check.IfNil(args.Proofs) { return nil, dataRetriever.ErrNilProofsPool } + if check.IfNil(args.ExecutedMiniBlocks) { + return nil, dataRetriever.ErrNilExecutedMiniBlocksCache + } + if check.IfNil(args.PostProcessTransactions) { + return nil, dataRetriever.ErrNilPostProcessTransactionsCache + } + if check.IfNil(args.DirectSentTransactions) { + return nil, dataRetriever.ErrNilDirectSentTransactionsCache + } return &dataPool{ - transactions: args.Transactions, - unsignedTransactions: args.UnsignedTransactions, - rewardTransactions: args.RewardTransactions, - headers: args.Headers, - miniBlocks: args.MiniBlocks, - peerChangesBlocks: args.PeerChangesBlocks, - trieNodes: args.TrieNodes, - trieNodesChunks: args.TrieNodesChunks, - currBlockTxs: args.CurrentBlockTransactions, - currEpochValidatorInfo: args.CurrentEpochValidatorInfo, - smartContracts: args.SmartContracts, - peerAuthentications: args.PeerAuthentications, - heartbeats: args.Heartbeats, - validatorsInfo: args.ValidatorsInfo, - proofs: args.Proofs, + transactions: args.Transactions, + unsignedTransactions: args.UnsignedTransactions, + rewardTransactions: args.RewardTransactions, + headers: args.Headers, + miniBlocks: args.MiniBlocks, + peerChangesBlocks: args.PeerChangesBlocks, + trieNodes: args.TrieNodes, + trieNodesChunks: args.TrieNodesChunks, + currBlockTxs: args.CurrentBlockTransactions, + currEpochValidatorInfo: args.CurrentEpochValidatorInfo, + smartContracts: args.SmartContracts, + peerAuthentications: args.PeerAuthentications, + heartbeats: args.Heartbeats, + validatorsInfo: args.ValidatorsInfo, + proofs: args.Proofs, + executedMiniBlocks: args.ExecutedMiniBlocks, + postProcessTransactions: args.PostProcessTransactions, + directSentTransactions: args.DirectSentTransactions, }, nil } @@ -190,6 +208,21 @@ func (dp *dataPool) Proofs() dataRetriever.ProofsPool { return dp.proofs } +// ExecutedMiniBlocks returns the holder for executed miniBlocks +func (dp *dataPool) ExecutedMiniBlocks() storage.Cacher { + return dp.executedMiniBlocks +} + +// DirectSentTransactions returns the holder for direct-sent transactions +func (dp *dataPool) DirectSentTransactions() storage.Cacher { + return dp.directSentTransactions +} + +// PostProcessTransactions returns the holder for post-process transactions +func (dp *dataPool) PostProcessTransactions() storage.Cacher { + return dp.postProcessTransactions +} + // Close closes all the components func (dp *dataPool) Close() error { var lastError error @@ -211,6 +244,15 @@ func (dp *dataPool) Close() error { } } + if !check.IfNil(dp.directSentTransactions) { + log.Debug("closing direct sent transactions pool....") + err := dp.directSentTransactions.Close() + if err != nil { + log.Error("failed to close direct sent transactions pool", "error", err.Error()) + lastError = err + } + } + return lastError } diff --git a/dataRetriever/dataPool/dataPool_test.go b/dataRetriever/dataPool/dataPool_test.go index 9a8f17181e3..43034dcbc01 100644 --- a/dataRetriever/dataPool/dataPool_test.go +++ b/dataRetriever/dataPool/dataPool_test.go @@ -34,6 +34,9 @@ func createMockDataPoolArgs() dataPool.DataPoolArgs { Heartbeats: cache.NewCacherStub(), ValidatorsInfo: testscommon.NewShardedDataStub(), Proofs: &dataRetrieverMocks.ProofsPoolMock{}, + ExecutedMiniBlocks: cache.NewCacherStub(), + PostProcessTransactions: cache.NewCacherStub(), + DirectSentTransactions: cache.NewCacherStub(), } } @@ -180,6 +183,39 @@ func TestNewDataPool_NilCurrBlockTransactionsShouldErr(t *testing.T) { require.Equal(t, dataRetriever.ErrNilCurrBlockTxs, err) } +func TestNewDataPool_NilExecutedMiniBlocksShouldErr(t *testing.T) { + t.Parallel() + + args := createMockDataPoolArgs() + args.ExecutedMiniBlocks = nil + tdp, err := dataPool.NewDataPool(args) + + require.Nil(t, tdp) + require.Equal(t, dataRetriever.ErrNilExecutedMiniBlocksCache, err) +} + +func TestNewDataPool_NilDirectSentTransactionsShouldErr(t *testing.T) { + t.Parallel() + + args := createMockDataPoolArgs() + args.DirectSentTransactions = nil + tdp, err := dataPool.NewDataPool(args) + + require.Nil(t, tdp) + require.Equal(t, dataRetriever.ErrNilDirectSentTransactionsCache, err) +} + +func TestNewDataPool_NilPostProcessTransactionsShouldErr(t *testing.T) { + t.Parallel() + + args := createMockDataPoolArgs() + args.PostProcessTransactions = nil + tdp, err := dataPool.NewDataPool(args) + + require.Nil(t, tdp) + require.Equal(t, dataRetriever.ErrNilPostProcessTransactionsCache, err) +} + func TestNewDataPool_NilCurrEpochValidatorInfoShouldErr(t *testing.T) { t.Parallel() @@ -214,6 +250,9 @@ func TestNewDataPool_OkValsShouldWork(t *testing.T) { assert.True(t, args.PeerAuthentications == tdp.PeerAuthentications()) assert.True(t, args.Heartbeats == tdp.Heartbeats()) assert.True(t, args.ValidatorsInfo == tdp.ValidatorsInfo()) + assert.True(t, args.ExecutedMiniBlocks == tdp.ExecutedMiniBlocks()) + assert.True(t, args.PostProcessTransactions == tdp.PostProcessTransactions()) + assert.True(t, args.DirectSentTransactions == tdp.DirectSentTransactions()) } func TestNewDataPool_Close(t *testing.T) { @@ -248,13 +287,28 @@ func TestNewDataPool_Close(t *testing.T) { err := tdp.Close() assert.Equal(t, expectedErr, err) }) - t.Run("both fail", func(t *testing.T) { + t.Run("direct sent transactions close returns error", func(t *testing.T) { + t.Parallel() + + args := createMockDataPoolArgs() + args.DirectSentTransactions = &cache.CacherStub{ + CloseCalled: func() error { + return expectedErr + }, + } + tdp, _ := dataPool.NewDataPool(args) + assert.NotNil(t, tdp) + err := tdp.Close() + assert.Equal(t, expectedErr, err) + }) + t.Run("all fail", func(t *testing.T) { t.Parallel() tnExpectedErr := errors.New("tn expected error") paExpectedErr := errors.New("pa expected error") + dsExpectedErr := errors.New("ds expected error") args := createMockDataPoolArgs() - tnCalled, paCalled := false, false + tnCalled, paCalled, dsCalled := false, false, false args.TrieNodes = &cache.CacherStub{ CloseCalled: func() error { tnCalled = true @@ -267,12 +321,19 @@ func TestNewDataPool_Close(t *testing.T) { return paExpectedErr }, } + args.DirectSentTransactions = &cache.CacherStub{ + CloseCalled: func() error { + dsCalled = true + return dsExpectedErr + }, + } tdp, _ := dataPool.NewDataPool(args) assert.NotNil(t, tdp) err := tdp.Close() - assert.Equal(t, paExpectedErr, err) + assert.Equal(t, dsExpectedErr, err) assert.True(t, tnCalled) assert.True(t, paCalled) + assert.True(t, dsCalled) }) t.Run("should work", func(t *testing.T) { t.Parallel() diff --git a/dataRetriever/dataPool/headersCache/headersCache.go b/dataRetriever/dataPool/headersCache/headersCache.go index 4b1ef31d8d9..c6013216c27 100644 --- a/dataRetriever/dataPool/headersCache/headersCache.go +++ b/dataRetriever/dataPool/headersCache/headersCache.go @@ -2,6 +2,8 @@ package headersCache import ( "bytes" + "encoding/hex" + "fmt" "time" "github.com/multiversx/mx-chain-core-go/core" @@ -176,17 +178,17 @@ func (cache *headersCache) removeHeaderFromNonceMap(headerInfo headerInfo, heade func (cache *headersCache) getHeaderByHash(hash []byte) (data.HeaderHandler, error) { info, ok := cache.headersByHash.getElement(hash) if !ok { - return nil, ErrHeaderNotFound + return nil, fmt.Errorf("%w: header hash: %s", ErrHeaderNotFound, hex.EncodeToString(hash)) } shard, ok := cache.headersNonceCache[info.headerShardId] if !ok { - return nil, ErrHeaderNotFound + return nil, fmt.Errorf("%w: header hash: %s", ErrHeaderNotFound, hex.EncodeToString(hash)) } headers := shard.getListOfHeaders(info.headerNonce) if headers.isEmpty() { - return nil, ErrHeaderNotFound + return nil, fmt.Errorf("%w: header hash: %s", ErrHeaderNotFound, hex.EncodeToString(hash)) } headers.timestamp = time.Now() @@ -196,7 +198,7 @@ func (cache *headersCache) getHeaderByHash(hash []byte) (data.HeaderHandler, err return header, nil } - return nil, ErrHeaderNotFound + return nil, fmt.Errorf("%w: header hash: %s", ErrHeaderNotFound, hex.EncodeToString(hash)) } func (cache *headersCache) getHeadersByNonceAndShardId(headerNonce uint64, shardId uint32) ([]headerDetails, bool) { diff --git a/dataRetriever/dataPool/headersCache/headersPool_test.go b/dataRetriever/dataPool/headersCache/headersPool_test.go index 2b2fb4cf3c6..d7c873ddb54 100644 --- a/dataRetriever/dataPool/headersCache/headersPool_test.go +++ b/dataRetriever/dataPool/headersCache/headersPool_test.go @@ -120,12 +120,12 @@ func Test_RemoveHeaderByHash(t *testing.T) { headersCacher.RemoveHeaderByHash(headerHash1) header, err := headersCacher.GetHeaderByHash(headerHash1) require.Nil(t, header) - require.Equal(t, headersCache.ErrHeaderNotFound, err) + require.ErrorIs(t, err, headersCache.ErrHeaderNotFound) headersCacher.RemoveHeaderByHash(headerHash2) header, err = headersCacher.GetHeaderByHash(headerHash2) require.Nil(t, header) - require.Equal(t, headersCache.ErrHeaderNotFound, err) + require.ErrorIs(t, err, headersCache.ErrHeaderNotFound) } func TestHeadersCacher_AddHeadersInCacheAndRemoveByNonceAndShardId(t *testing.T) { @@ -153,11 +153,11 @@ func TestHeadersCacher_AddHeadersInCacheAndRemoveByNonceAndShardId(t *testing.T) headersCacher.RemoveHeaderByNonceAndShardId(nonce, shardId) header, err := headersCacher.GetHeaderByHash(headerHash1) require.Nil(t, header) - require.Equal(t, headersCache.ErrHeaderNotFound, err) + require.ErrorIs(t, err, headersCache.ErrHeaderNotFound) header, err = headersCacher.GetHeaderByHash(headerHash2) require.Nil(t, header) - require.Equal(t, headersCache.ErrHeaderNotFound, err) + require.ErrorIs(t, err, headersCache.ErrHeaderNotFound) } func TestHeadersCacher_Eviction(t *testing.T) { @@ -455,7 +455,7 @@ func TestHeadersCacher_TestEvictionRemoveCorrectHeader(t *testing.T) { header, err = headersCacher.GetHeaderByHash(headersHashes[1]) require.Nil(t, header) - require.Equal(t, headersCache.ErrHeaderNotFound, err) + require.ErrorIs(t, err, headersCache.ErrHeaderNotFound) } func TestHeadersCacher_TestEvictionRemoveCorrectHeader2(t *testing.T) { @@ -489,7 +489,7 @@ func TestHeadersCacher_TestEvictionRemoveCorrectHeader2(t *testing.T) { header, err = headersCacher.GetHeaderByHash(headersHashes[1]) require.Nil(t, header) - require.Equal(t, headersCache.ErrHeaderNotFound, err) + require.ErrorIs(t, err, headersCache.ErrHeaderNotFound) for i := 2; i <= cacheSize; i++ { header, err := headersCacher.GetHeaderByHash(headersHashes[i]) diff --git a/dataRetriever/dataPool/proofsCache/export_test.go b/dataRetriever/dataPool/proofsCache/export_test.go index f6b0b007405..6ca7c5344ec 100644 --- a/dataRetriever/dataPool/proofsCache/export_test.go +++ b/dataRetriever/dataPool/proofsCache/export_test.go @@ -32,3 +32,13 @@ func (pc *proofsCache) AddProof(proof data.HeaderProofHandler) { func (pc *proofsCache) CleanupProofsBehindNonce(nonce uint64) { pc.cleanupProofsBehindNonce(nonce) } + +// GetProofByHash - +func (pc *proofsCache) GetProofByHash(headerHash []byte) (data.HeaderProofHandler, error) { + return pc.getProofByHash(headerHash) +} + +// GetProofByNonce - +func (pc *proofsCache) GetProofByNonce(headerNonce uint64) (data.HeaderProofHandler, error) { + return pc.getProofByNonce(headerNonce) +} diff --git a/dataRetriever/dataPool/proofsCache/proofsBucket.go b/dataRetriever/dataPool/proofsCache/proofsBucket.go index 91b5815f440..f6321e17293 100644 --- a/dataRetriever/dataPool/proofsCache/proofsBucket.go +++ b/dataRetriever/dataPool/proofsCache/proofsBucket.go @@ -17,10 +17,19 @@ func (p *proofNonceBucket) size() int { return len(p.proofsByNonce) } -func (p *proofNonceBucket) insert(proof data.HeaderProofHandler) { - p.proofsByNonce[proof.GetHeaderNonce()] = string(proof.GetHeaderHash()) +func (p *proofNonceBucket) insert(proof data.HeaderProofHandler) string { + nonce := proof.GetHeaderNonce() + newHash := string(proof.GetHeaderHash()) - if proof.GetHeaderNonce() > p.maxNonce { - p.maxNonce = proof.GetHeaderNonce() + oldHash, existed := p.proofsByNonce[nonce] + p.proofsByNonce[nonce] = newHash + + if nonce > p.maxNonce { + p.maxNonce = nonce + } + + if existed { + return oldHash } + return "" } diff --git a/dataRetriever/dataPool/proofsCache/proofsCache.go b/dataRetriever/dataPool/proofsCache/proofsCache.go index d885ffe8a41..b8aaaf38cd1 100644 --- a/dataRetriever/dataPool/proofsCache/proofsCache.go +++ b/dataRetriever/dataPool/proofsCache/proofsCache.go @@ -65,9 +65,15 @@ func (pc *proofsCache) addProof(proof data.HeaderProofHandler) { pc.mutProofsCache.Lock() defer pc.mutProofsCache.Unlock() - pc.insertProofByNonce(proof) + oldHash := pc.insertProofByNonce(proof) + newHash := string(proof.GetHeaderHash()) - pc.proofsByHash[string(proof.GetHeaderHash())] = proof + // Delete the old hash from proofsByHash if it's different from the new hash + if len(oldHash) != 0 && oldHash != newHash { + delete(pc.proofsByHash, oldHash) + } + + pc.proofsByHash[newHash] = proof } // getBucketKey will return bucket key as lower bound window value @@ -75,7 +81,7 @@ func (pc *proofsCache) getBucketKey(index uint64) uint64 { return (index / pc.bucketSize) * pc.bucketSize } -func (pc *proofsCache) insertProofByNonce(proof data.HeaderProofHandler) { +func (pc *proofsCache) insertProofByNonce(proof data.HeaderProofHandler) string { bucketKey := pc.getBucketKey(proof.GetHeaderNonce()) bucket, ok := pc.proofsByNonceBuckets[bucketKey] @@ -84,7 +90,7 @@ func (pc *proofsCache) insertProofByNonce(proof data.HeaderProofHandler) { pc.proofsByNonceBuckets[bucketKey] = bucket } - bucket.insert(proof) + return bucket.insert(proof) } func (pc *proofsCache) cleanupProofsBehindNonce(nonce uint64) { diff --git a/dataRetriever/dataPool/proofsCache/proofsPool_test.go b/dataRetriever/dataPool/proofsCache/proofsPool_test.go index efec39a85d6..d8cd88178eb 100644 --- a/dataRetriever/dataPool/proofsCache/proofsPool_test.go +++ b/dataRetriever/dataPool/proofsCache/proofsPool_test.go @@ -140,6 +140,39 @@ func TestProofsPool_Upsert(t *testing.T) { require.Equal(t, newProof1.GetPubKeysBitmap(), proof.GetPubKeysBitmap()) } +func TestProofsPool_UpsertMultipleHashes(t *testing.T) { + t.Parallel() + + pp := proofscache.NewProofsPool(3, 10) + + // Upsert 10 different proofs for the same nonce + // Each upsert should clean up the previous hash + for i := 0; i < 10; i++ { + proof := &block.HeaderProof{ + HeaderHash: []byte{byte(i)}, // Different hash each time + HeaderNonce: 5, // Same nonce + HeaderShardId: shardID, + } + ok := pp.UpsertProof(proof) + require.True(t, ok, "upsert %d should succeed", i) + } + + // All previous hashes (hash[0] through hash[8]) should be cleaned up + for i := 0; i < 9; i++ { + hasOldHash := pp.HasProof(shardID, []byte{byte(i)}) + require.False(t, hasOldHash, "old hash[%d] should be cleaned up", i) + } + + // Only the latest hash should be present + hasLatestHash := pp.HasProof(shardID, []byte{9}) + require.True(t, hasLatestHash, "latest hash[9] should be in pool") + + // Verify nonce 5 maps to the latest hash + proofByNonce, err := pp.GetProofByNonce(5, shardID) + require.Nil(t, err) + require.Equal(t, []byte{9}, proofByNonce.GetHeaderHash(), "nonce 5 should map to latest hash") +} + func TestProofsPool_IsProofEqual(t *testing.T) { t.Parallel() diff --git a/dataRetriever/errors.go b/dataRetriever/errors.go index f7d053567ec..c5b346f77e7 100644 --- a/dataRetriever/errors.go +++ b/dataRetriever/errors.go @@ -266,8 +266,23 @@ var ErrValidatorInfoNotFound = errors.New("validator info not found") // ErrNilProofsPool signals that a nil proofs pool has been provided var ErrNilProofsPool = errors.New("nil proofs pool") +// ErrNilExecutedMiniBlocksCache signals that a nil executed mini blocks cache has been provided +var ErrNilExecutedMiniBlocksCache = errors.New("nil executed mini blocks cache") + +// ErrNilPostProcessTransactionsCache signals that a nil post-process transactions cache has been provided +var ErrNilPostProcessTransactionsCache = errors.New("nil post process transactions cache") + +// ErrNilDirectSentTransactionsCache signals that a nil direct-sent transactions cache has been provided +var ErrNilDirectSentTransactionsCache = errors.New("nil direct sent transactions cache") + // ErrEquivalentProofsNotFound signals that no equivalent proof found var ErrEquivalentProofsNotFound = errors.New("equivalent proof not found") // ErrNilEnableEpochsHandler signals that a nil enable epochs handler has been provided var ErrNilEnableEpochsHandler = errors.New("nil enable epochs handler") + +// ErrBadMaxNumBytesPerSenderUpperBound signals a bad MaxNumBytesPerSenderUpperBound config +var ErrBadMaxNumBytesPerSenderUpperBound = errors.New("bad max number of bytes per sender upper bound") + +// ErrBadMaxTrackedBlocks signals a bad MaxTrackedBlocks config +var ErrBadMaxTrackedBlocks = errors.New("bad max tracked blocks") diff --git a/dataRetriever/factory/dataPoolFactory.go b/dataRetriever/factory/dataPoolFactory.go index e375ed2c785..8a403f40aa5 100644 --- a/dataRetriever/factory/dataPoolFactory.go +++ b/dataRetriever/factory/dataPoolFactory.go @@ -64,11 +64,12 @@ func NewDataPoolFromConfig(args ArgsDataPool) (dataRetriever.PoolsHolder, error) mainConfig := args.Config txPool, err := txpool.NewShardedTxPool(txpool.ArgShardedTxPool{ - Config: factory.GetCacherFromConfig(mainConfig.TxDataPool), - TxGasHandler: args.EconomicsData, - Marshalizer: args.Marshalizer, - NumberOfShards: args.ShardCoordinator.NumberOfShards(), - SelfShardID: args.ShardCoordinator.SelfId(), + Config: factory.GetCacherFromConfig(mainConfig.TxDataPool), + TxGasHandler: args.EconomicsData, + Marshalizer: args.Marshalizer, + NumberOfShards: args.ShardCoordinator.NumberOfShards(), + SelfShardID: args.ShardCoordinator.SelfId(), + TxCacheBoundsConfig: mainConfig.TxCacheBounds, }) if err != nil { return nil, fmt.Errorf("%w while creating the cache for the transactions", err) @@ -155,6 +156,26 @@ func NewDataPoolFromConfig(args ArgsDataPool) (dataRetriever.PoolsHolder, error) currBlockTransactions := dataPool.NewCurrentBlockTransactionsPool() currEpochValidatorInfo := dataPool.NewCurrentEpochValidatorInfoPool() + cacherCfg = factory.GetCacherFromConfig(mainConfig.ExecutedMiniBlocksCache) + executedMiniBlocksCache, err := storageunit.NewCache(cacherCfg) + if err != nil { + return nil, fmt.Errorf("%w while creating the cache for the executed mini blocks", err) + } + + cacherCfg = factory.GetCacherFromConfig(mainConfig.PostProcessTransactionsCache) + postProcessTransactionsCache, err := storageunit.NewCache(cacherCfg) + if err != nil { + return nil, fmt.Errorf("%w while creating the cache for the post process transactions", err) + } + + directSentTransactionsCache, err := cache.NewTimeCacher(cache.ArgTimeCacher{ + DefaultSpan: time.Duration(mainConfig.DirectSentTransactions.CacheSpanInSec) * time.Second, + CacheExpiry: time.Duration(mainConfig.DirectSentTransactions.CacheExpiryInSec) * time.Second, + }) + if err != nil { + return nil, fmt.Errorf("%w while creating the cache for the direct sent transactions", err) + } + dataPoolArgs := dataPool.DataPoolArgs{ Transactions: txPool, UnsignedTransactions: uTxPool, @@ -171,6 +192,9 @@ func NewDataPoolFromConfig(args ArgsDataPool) (dataRetriever.PoolsHolder, error) Heartbeats: heartbeatPool, ValidatorsInfo: validatorsInfo, Proofs: proofsPool, + ExecutedMiniBlocks: executedMiniBlocksCache, + PostProcessTransactions: postProcessTransactionsCache, + DirectSentTransactions: directSentTransactionsCache, } return dataPool.NewDataPool(dataPoolArgs) } diff --git a/dataRetriever/factory/dataPoolFactory_test.go b/dataRetriever/factory/dataPoolFactory_test.go index bea0d92c415..c3b8ef56eb6 100644 --- a/dataRetriever/factory/dataPoolFactory_test.go +++ b/dataRetriever/factory/dataPoolFactory_test.go @@ -136,6 +136,27 @@ func TestNewDataPoolFromConfig_BadConfigShouldErr(t *testing.T) { require.Nil(t, holder) require.True(t, errors.Is(err, storage.ErrInvalidConfig)) require.True(t, strings.Contains(err.Error(), "the cache for the validator info results")) + + args = getGoodArgs() + args.Config.ExecutedMiniBlocksCache.Type = "invalid cache type" + holder, err = NewDataPoolFromConfig(args) + require.Nil(t, holder) + require.True(t, errors.Is(err, storage.ErrNotSupportedCacheType)) + require.True(t, strings.Contains(err.Error(), "the cache for the executed mini blocks")) + + args = getGoodArgs() + args.Config.PostProcessTransactionsCache.Type = "invalid cache type" + holder, err = NewDataPoolFromConfig(args) + require.Nil(t, holder) + require.True(t, errors.Is(err, storage.ErrNotSupportedCacheType)) + require.True(t, strings.Contains(err.Error(), "the cache for the post process transactions")) + + args = getGoodArgs() + args.Config.DirectSentTransactions.CacheExpiryInSec = 0 + holder, err = NewDataPoolFromConfig(args) + require.Nil(t, holder) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "the cache for the direct sent transactions")) } func getGoodArgs() ArgsDataPool { diff --git a/dataRetriever/factory/epochProviders/currentEpochProvidersFactory.go b/dataRetriever/factory/epochProviders/currentEpochProvidersFactory.go index 7e3bb6c5840..95d1d932095 100644 --- a/dataRetriever/factory/epochProviders/currentEpochProvidersFactory.go +++ b/dataRetriever/factory/epochProviders/currentEpochProvidersFactory.go @@ -1,27 +1,28 @@ package epochProviders import ( - "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/dataRetriever/resolvers/epochproviders" "github.com/multiversx/mx-chain-go/dataRetriever/resolvers/epochproviders/disabled" + "github.com/multiversx/mx-chain-go/process" ) // CreateCurrentEpochProvider will create an instance of dataRetriever.CurrentNetworkEpochProviderHandler func CreateCurrentEpochProvider( - generalConfigs config.Config, - roundTimeInMilliseconds uint64, + chainParametersHandler process.ChainParametersHandler, startTime int64, isFullArchive bool, + enableEpochsHandler common.EnableEpochsHandler, ) (dataRetriever.CurrentNetworkEpochProviderHandler, error) { if !isFullArchive { return disabled.NewEpochProvider(), nil } arg := epochproviders.ArgArithmeticEpochProvider{ - RoundsPerEpoch: uint32(generalConfigs.EpochStartConfig.RoundsPerEpoch), - RoundTimeInMilliseconds: roundTimeInMilliseconds, - StartTime: startTime, + ChainParametersHandler: chainParametersHandler, + StartTime: startTime, + EnableEpochsHandler: enableEpochsHandler, } return epochproviders.NewArithmeticEpochProvider(arg) diff --git a/dataRetriever/factory/epochProviders/currentEpochProvidersFactory_test.go b/dataRetriever/factory/epochProviders/currentEpochProvidersFactory_test.go index 7335f591826..8b3b5e9f2a1 100644 --- a/dataRetriever/factory/epochProviders/currentEpochProvidersFactory_test.go +++ b/dataRetriever/factory/epochProviders/currentEpochProvidersFactory_test.go @@ -7,6 +7,8 @@ import ( "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever/resolvers/epochproviders" "github.com/multiversx/mx-chain-go/dataRetriever/resolvers/epochproviders/disabled" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -15,10 +17,10 @@ func TestCreateCurrentEpochProvider_NilCurrentEpochProvider(t *testing.T) { t.Parallel() cnep, err := CreateCurrentEpochProvider( - config.Config{}, - 0, + &chainParameters.ChainParametersHandlerStub{}, 0, false, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, ) assert.Nil(t, err) @@ -28,23 +30,27 @@ func TestCreateCurrentEpochProvider_NilCurrentEpochProvider(t *testing.T) { func TestCreateCurrentEpochProvider_ArithmeticEpochProvider(t *testing.T) { t.Parallel() - cnep, err := CreateCurrentEpochProvider( - config.Config{ - EpochStartConfig: config.EpochStartConfig{ + chainParameterHandler := &chainParameters.ChainParametersHandlerStub{ + CurrentChainParametersCalled: func() config.ChainParametersByEpochConfig { + return config.ChainParametersByEpochConfig{ RoundsPerEpoch: 1, - }, + RoundDuration: 1, + } }, - 1, + } + cnep, err := CreateCurrentEpochProvider( + chainParameterHandler, 1, true, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, ) require.Nil(t, err) aep, _ := epochproviders.NewArithmeticEpochProvider( epochproviders.ArgArithmeticEpochProvider{ - RoundsPerEpoch: 1, - RoundTimeInMilliseconds: 1, - StartTime: 1, + StartTime: 1, + ChainParametersHandler: chainParameterHandler, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, }, ) require.False(t, check.IfNil(aep)) diff --git a/dataRetriever/factory/resolverscontainer/args.go b/dataRetriever/factory/resolverscontainer/args.go index d0001014a4d..845868bcbf2 100644 --- a/dataRetriever/factory/resolverscontainer/args.go +++ b/dataRetriever/factory/resolverscontainer/args.go @@ -11,22 +11,21 @@ import ( // FactoryArgs will hold the arguments for ResolversContainerFactory for both shard and meta type FactoryArgs struct { - NumConcurrentResolvingJobs int32 - NumConcurrentResolvingTrieNodesJobs int32 - ShardCoordinator sharding.Coordinator - MainMessenger p2p.Messenger - FullArchiveMessenger p2p.Messenger - Store dataRetriever.StorageService - Marshalizer marshal.Marshalizer - DataPools dataRetriever.PoolsHolder - Uint64ByteSliceConverter typeConverters.Uint64ByteSliceConverter - DataPacker dataRetriever.DataPacker - TriesContainer common.TriesHolder - InputAntifloodHandler dataRetriever.P2PAntifloodHandler - OutputAntifloodHandler dataRetriever.P2PAntifloodHandler - MainPreferredPeersHolder p2p.PreferredPeersHolderHandler - FullArchivePreferredPeersHolder p2p.PreferredPeersHolderHandler - SizeCheckDelta uint32 - IsFullHistoryNode bool - PayloadValidator dataRetriever.PeerAuthenticationPayloadValidator + ShardCoordinator sharding.Coordinator + MainMessenger p2p.Messenger + FullArchiveMessenger p2p.Messenger + Store dataRetriever.StorageService + Marshalizer marshal.Marshalizer + DataPools dataRetriever.PoolsHolder + Uint64ByteSliceConverter typeConverters.Uint64ByteSliceConverter + DataPacker dataRetriever.DataPacker + TriesContainer common.TriesHolder + InputAntifloodHandler dataRetriever.P2PAntifloodHandler + OutputAntifloodHandler dataRetriever.P2PAntifloodHandler + MainPreferredPeersHolder p2p.PreferredPeersHolderHandler + FullArchivePreferredPeersHolder p2p.PreferredPeersHolderHandler + SizeCheckDelta uint32 + IsFullHistoryNode bool + PayloadValidator dataRetriever.PeerAuthenticationPayloadValidator + AntifloodConfigsHandler common.AntifloodConfigsHandler } diff --git a/dataRetriever/factory/resolverscontainer/metaResolversContainerFactory.go b/dataRetriever/factory/resolverscontainer/metaResolversContainerFactory.go index a84deb54aa4..d6b1dbe472d 100644 --- a/dataRetriever/factory/resolverscontainer/metaResolversContainerFactory.go +++ b/dataRetriever/factory/resolverscontainer/metaResolversContainerFactory.go @@ -8,6 +8,7 @@ import ( "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/dataRetriever/factory/containers" "github.com/multiversx/mx-chain-go/dataRetriever/resolvers" + "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-go/process/factory" @@ -26,13 +27,18 @@ func NewMetaResolversContainerFactory( if args.SizeCheckDelta > 0 { args.Marshalizer = marshal.NewSizeCheckUnmarshalizer(args.Marshalizer, args.SizeCheckDelta) } + if check.IfNil(args.AntifloodConfigsHandler) { + return nil, process.ErrNilAntifloodConfigsHandler + } + + currentConfig := args.AntifloodConfigsHandler.GetCurrentConfig() - mainThrottler, err := throttler.NewNumGoRoutinesThrottler(args.NumConcurrentResolvingJobs) + mainThrottler, err := throttler.NewNumGoRoutinesThrottler(currentConfig.NumConcurrentResolverJobs) if err != nil { return nil, err } - trieNodesThrottler, err := throttler.NewNumGoRoutinesThrottler(args.NumConcurrentResolvingTrieNodesJobs) + trieNodesThrottler, err := throttler.NewNumGoRoutinesThrottler(currentConfig.NumConcurrentResolvingTrieNodesJobs) if err != nil { return nil, err } diff --git a/dataRetriever/factory/resolverscontainer/metaResolversContainerFactory_test.go b/dataRetriever/factory/resolverscontainer/metaResolversContainerFactory_test.go index 533d682914b..003d20a37b6 100644 --- a/dataRetriever/factory/resolverscontainer/metaResolversContainerFactory_test.go +++ b/dataRetriever/factory/resolverscontainer/metaResolversContainerFactory_test.go @@ -5,10 +5,10 @@ import ( "strings" "testing" - "github.com/multiversx/mx-chain-core-go/core" "github.com/stretchr/testify/assert" "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/dataRetriever/factory/resolverscontainer" "github.com/multiversx/mx-chain-go/dataRetriever/mock" @@ -94,24 +94,6 @@ func createTriesHolderForMeta() common.TriesHolder { // ------- NewResolversContainerFactory -func TestNewMetaResolversContainerFactory_NewNumGoRoutinesThrottlerFailsShouldErr(t *testing.T) { - t.Parallel() - - args := getArgumentsMeta() - args.NumConcurrentResolvingJobs = 0 - - rcf, err := resolverscontainer.NewMetaResolversContainerFactory(args) - assert.Nil(t, rcf) - assert.Equal(t, core.ErrNotPositiveValue, err) - - args.NumConcurrentResolvingJobs = 10 - args.NumConcurrentResolvingTrieNodesJobs = 0 - - rcf, err = resolverscontainer.NewMetaResolversContainerFactory(args) - assert.Nil(t, rcf) - assert.Equal(t, core.ErrNotPositiveValue, err) -} - func TestNewMetaResolversContainerFactory_NilShardCoordinatorShouldErr(t *testing.T) { t.Parallel() @@ -371,22 +353,28 @@ func TestMetaResolversContainerFactory_IsInterfaceNil(t *testing.T) { func getArgumentsMeta() resolverscontainer.FactoryArgs { return resolverscontainer.FactoryArgs{ - ShardCoordinator: mock.NewOneShardCoordinatorMock(), - MainMessenger: createStubMessengerForMeta("", ""), - FullArchiveMessenger: createStubMessengerForMeta("", ""), - Store: createStoreForMeta(), - Marshalizer: &mock.MarshalizerMock{}, - DataPools: createDataPoolsForMeta(), - Uint64ByteSliceConverter: &mock.Uint64ByteSliceConverterMock{}, - DataPacker: &mock.DataPackerStub{}, - TriesContainer: createTriesHolderForMeta(), - SizeCheckDelta: 0, - InputAntifloodHandler: &mock.P2PAntifloodHandlerStub{}, - OutputAntifloodHandler: &mock.P2PAntifloodHandlerStub{}, - NumConcurrentResolvingJobs: 10, - NumConcurrentResolvingTrieNodesJobs: 3, - MainPreferredPeersHolder: &p2pmocks.PeersHolderStub{}, - FullArchivePreferredPeersHolder: &p2pmocks.PeersHolderStub{}, - PayloadValidator: &testscommon.PeerAuthenticationPayloadValidatorStub{}, + ShardCoordinator: mock.NewOneShardCoordinatorMock(), + MainMessenger: createStubMessengerForMeta("", ""), + FullArchiveMessenger: createStubMessengerForMeta("", ""), + Store: createStoreForMeta(), + Marshalizer: &mock.MarshalizerMock{}, + DataPools: createDataPoolsForMeta(), + Uint64ByteSliceConverter: &mock.Uint64ByteSliceConverterMock{}, + DataPacker: &mock.DataPackerStub{}, + TriesContainer: createTriesHolderForMeta(), + SizeCheckDelta: 0, + InputAntifloodHandler: &mock.P2PAntifloodHandlerStub{}, + OutputAntifloodHandler: &mock.P2PAntifloodHandlerStub{}, + MainPreferredPeersHolder: &p2pmocks.PeersHolderStub{}, + FullArchivePreferredPeersHolder: &p2pmocks.PeersHolderStub{}, + PayloadValidator: &testscommon.PeerAuthenticationPayloadValidatorStub{}, + AntifloodConfigsHandler: &testscommon.AntifloodConfigsHandlerStub{ + GetCurrentConfigCalled: func() config.AntifloodConfigByRound { + return config.AntifloodConfigByRound{ + NumConcurrentResolverJobs: 10, + NumConcurrentResolvingTrieNodesJobs: 3, + } + }, + }, } } diff --git a/dataRetriever/factory/resolverscontainer/shardResolversContainerFactory.go b/dataRetriever/factory/resolverscontainer/shardResolversContainerFactory.go index 3c1f374e4a8..6fc86dd2426 100644 --- a/dataRetriever/factory/resolverscontainer/shardResolversContainerFactory.go +++ b/dataRetriever/factory/resolverscontainer/shardResolversContainerFactory.go @@ -2,12 +2,14 @@ package resolverscontainer import ( "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/core/throttler" "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/dataRetriever/factory/containers" "github.com/multiversx/mx-chain-go/dataRetriever/resolvers" + "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/factory" ) @@ -24,13 +26,18 @@ func NewShardResolversContainerFactory( if args.SizeCheckDelta > 0 { args.Marshalizer = marshal.NewSizeCheckUnmarshalizer(args.Marshalizer, args.SizeCheckDelta) } + if check.IfNil(args.AntifloodConfigsHandler) { + return nil, process.ErrNilAntifloodConfigsHandler + } + + currentConfig := args.AntifloodConfigsHandler.GetCurrentConfig() - mainThrottler, err := throttler.NewNumGoRoutinesThrottler(args.NumConcurrentResolvingJobs) + mainThrottler, err := throttler.NewNumGoRoutinesThrottler(currentConfig.NumConcurrentResolverJobs) if err != nil { return nil, err } - trieNodesThrottler, err := throttler.NewNumGoRoutinesThrottler(args.NumConcurrentResolvingTrieNodesJobs) + trieNodesThrottler, err := throttler.NewNumGoRoutinesThrottler(currentConfig.NumConcurrentResolvingTrieNodesJobs) if err != nil { return nil, err } diff --git a/dataRetriever/factory/resolverscontainer/shardResolversContainerFactory_test.go b/dataRetriever/factory/resolverscontainer/shardResolversContainerFactory_test.go index 1d9e8c5a678..e2a26be3733 100644 --- a/dataRetriever/factory/resolverscontainer/shardResolversContainerFactory_test.go +++ b/dataRetriever/factory/resolverscontainer/shardResolversContainerFactory_test.go @@ -5,10 +5,10 @@ import ( "strings" "testing" - "github.com/multiversx/mx-chain-core-go/core" "github.com/stretchr/testify/assert" "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/dataRetriever/factory/resolverscontainer" "github.com/multiversx/mx-chain-go/dataRetriever/mock" @@ -100,24 +100,6 @@ func createTriesHolderForShard() common.TriesHolder { // ------- NewResolversContainerFactory -func TestNewShardResolversContainerFactory_NewNumGoRoutinesThrottlerFailsShouldErr(t *testing.T) { - t.Parallel() - - args := getArgumentsShard() - args.NumConcurrentResolvingJobs = 0 - - rcf, err := resolverscontainer.NewShardResolversContainerFactory(args) - assert.Nil(t, rcf) - assert.Equal(t, core.ErrNotPositiveValue, err) - - args.NumConcurrentResolvingJobs = 10 - args.NumConcurrentResolvingTrieNodesJobs = 0 - - rcf, err = resolverscontainer.NewShardResolversContainerFactory(args) - assert.Nil(t, rcf) - assert.Equal(t, core.ErrNotPositiveValue, err) -} - func TestNewShardResolversContainerFactory_NilShardCoordinatorShouldErr(t *testing.T) { t.Parallel() @@ -479,22 +461,28 @@ func TestShardResolversContainerFactory_IsInterfaceNil(t *testing.T) { func getArgumentsShard() resolverscontainer.FactoryArgs { return resolverscontainer.FactoryArgs{ - ShardCoordinator: mock.NewOneShardCoordinatorMock(), - MainMessenger: createMessengerStubForShard("", ""), - FullArchiveMessenger: createMessengerStubForShard("", ""), - Store: createStoreForShard(), - Marshalizer: &mock.MarshalizerMock{}, - DataPools: createDataPoolsForShard(), - Uint64ByteSliceConverter: &mock.Uint64ByteSliceConverterMock{}, - DataPacker: &mock.DataPackerStub{}, - TriesContainer: createTriesHolderForShard(), - SizeCheckDelta: 0, - InputAntifloodHandler: &mock.P2PAntifloodHandlerStub{}, - OutputAntifloodHandler: &mock.P2PAntifloodHandlerStub{}, - NumConcurrentResolvingJobs: 10, - NumConcurrentResolvingTrieNodesJobs: 3, - MainPreferredPeersHolder: &p2pmocks.PeersHolderStub{}, - FullArchivePreferredPeersHolder: &p2pmocks.PeersHolderStub{}, - PayloadValidator: &testscommon.PeerAuthenticationPayloadValidatorStub{}, + ShardCoordinator: mock.NewOneShardCoordinatorMock(), + MainMessenger: createMessengerStubForShard("", ""), + FullArchiveMessenger: createMessengerStubForShard("", ""), + Store: createStoreForShard(), + Marshalizer: &mock.MarshalizerMock{}, + DataPools: createDataPoolsForShard(), + Uint64ByteSliceConverter: &mock.Uint64ByteSliceConverterMock{}, + DataPacker: &mock.DataPackerStub{}, + TriesContainer: createTriesHolderForShard(), + SizeCheckDelta: 0, + InputAntifloodHandler: &mock.P2PAntifloodHandlerStub{}, + OutputAntifloodHandler: &mock.P2PAntifloodHandlerStub{}, + MainPreferredPeersHolder: &p2pmocks.PeersHolderStub{}, + FullArchivePreferredPeersHolder: &p2pmocks.PeersHolderStub{}, + PayloadValidator: &testscommon.PeerAuthenticationPayloadValidatorStub{}, + AntifloodConfigsHandler: &testscommon.AntifloodConfigsHandlerStub{ + GetCurrentConfigCalled: func() config.AntifloodConfigByRound { + return config.AntifloodConfigByRound{ + NumConcurrentResolverJobs: 10, + NumConcurrentResolvingTrieNodesJobs: 3, + } + }, + }, } } diff --git a/dataRetriever/interface.go b/dataRetriever/interface.go index f9d68fa9f92..9d0ef822ad5 100644 --- a/dataRetriever/interface.go +++ b/dataRetriever/interface.go @@ -6,7 +6,9 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/counting" "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/storage" @@ -182,6 +184,23 @@ type ShardedDataCacherNotifier interface { GetCounts() counting.CountsWithSize Keys() [][]byte IsInterfaceNil() bool + CleanupSelfShardTxCache(accountsProvider common.AccountNonceProvider, randomness uint64, maxNum int, cleanupLoopMaximumDuration time.Duration) + GetNumTrackedBlocks() uint64 + GetNumTrackedAccounts() uint64 + OnExecutedBlock(blockHeader data.HeaderHandler, rootHash []byte) error + OnProposedBlock( + blockHash []byte, + blockBody *block.Body, + blockHeader data.HeaderHandler, + accountsProvider common.AccountNonceAndBalanceProvider, + latestExecutedHash []byte, + ) error + OnBackfilledBlock( + blockHash []byte, + blockBody *block.Body, + blockHeader data.HeaderHandler, + ) error + ResetTracker() } // ShardIdHashMap represents a map for shardId and hash @@ -242,6 +261,9 @@ type PoolsHolder interface { Heartbeats() storage.Cacher ValidatorsInfo() ShardedDataCacherNotifier Proofs() ProofsPool + ExecutedMiniBlocks() storage.Cacher + PostProcessTransactions() storage.Cacher + DirectSentTransactions() storage.Cacher Close() error IsInterfaceNil() bool } diff --git a/dataRetriever/provider/miniBlocks.go b/dataRetriever/provider/miniBlocks.go index be846ab520b..7d637d218e8 100644 --- a/dataRetriever/provider/miniBlocks.go +++ b/dataRetriever/provider/miniBlocks.go @@ -142,7 +142,7 @@ func (mbp *miniBlockProvider) GetMiniBlocksFromStorer(hashes [][]byte) ([]*block return miniBlocksAndHashes, missingMiniBlocksHashes } -// getMiniBlocksFromStorer returns a list of mini blocks from storage and a list of missing hashes +// getMiniBlockFromStorer returns a list of mini blocks from storage and a list of missing hashes func (mbp *miniBlockProvider) getMiniBlockFromStorer(hash []byte) *block.MiniBlock { buff, err := mbp.miniBlockStorage.Get(hash) if err != nil { diff --git a/dataRetriever/requestHandlers/requestHandler.go b/dataRetriever/requestHandlers/requestHandler.go index 8b88fa98208..48eae37aecf 100644 --- a/dataRetriever/requestHandlers/requestHandler.go +++ b/dataRetriever/requestHandlers/requestHandler.go @@ -11,11 +11,12 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/core/partitioning" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/epochStart" "github.com/multiversx/mx-chain-go/process/factory" - logger "github.com/multiversx/mx-chain-logger-go" ) var _ epochStart.RequestHandler = (*resolverRequestHandler)(nil) @@ -112,12 +113,18 @@ func (rrh *resolverRequestHandler) getEpoch() uint32 { return rrh.epoch } -// RequestTransaction method asks for transactions from the connected peers -func (rrh *resolverRequestHandler) RequestTransaction(destShardID uint32, txHashes [][]byte) { - rrh.requestByHashes(destShardID, txHashes, factory.TransactionTopic, uniqueTxSuffix) +// RequestTransactions method asks for transactions from the connected peers +func (rrh *resolverRequestHandler) RequestTransactions(destShardID uint32, txHashes [][]byte) { + epoch := rrh.getEpoch() + rrh.requestByHashes(destShardID, txHashes, factory.TransactionTopic, uniqueTxSuffix, epoch) +} + +// RequestTransactionsForEpoch method asks for transactions from the connected peers for a specific epoch +func (rrh *resolverRequestHandler) RequestTransactionsForEpoch(destShardID uint32, txHashes [][]byte, epoch uint32) { + rrh.requestByHashes(destShardID, txHashes, factory.TransactionTopic, uniqueTxSuffix, epoch) } -func (rrh *resolverRequestHandler) requestByHashes(destShardID uint32, hashes [][]byte, topic string, abbreviatedTopic string) { +func (rrh *resolverRequestHandler) requestByHashes(destShardID uint32, hashes [][]byte, topic string, abbreviatedTopic string, epoch uint32) { suffix := fmt.Sprintf("%s_%d", abbreviatedTopic, destShardID) unrequestedHashes := rrh.getUnrequestedHashes(hashes, suffix) if len(unrequestedHashes) == 0 { @@ -125,6 +132,7 @@ func (rrh *resolverRequestHandler) requestByHashes(destShardID uint32, hashes [] } log.Debug("requesting transactions from network", "topic", topic, + "epoch", epoch, "shard", destShardID, "num txs", len(unrequestedHashes), ) @@ -133,6 +141,7 @@ func (rrh *resolverRequestHandler) requestByHashes(destShardID uint32, hashes [] log.Error("requestByHashes.CrossShardRequester", "error", err.Error(), "topic", topic, + "epoch", epoch, "shard", destShardID, ) return @@ -153,7 +162,7 @@ func (rrh *resolverRequestHandler) requestByHashes(destShardID uint32, hashes [] rrh.whiteList.Add(unrequestedHashes) - go rrh.requestHashesWithDataSplit(unrequestedHashes, txRequester) + go rrh.requestHashesWithDataSplit(unrequestedHashes, txRequester, epoch) rrh.addRequestedItems(unrequestedHashes, suffix) } @@ -161,6 +170,7 @@ func (rrh *resolverRequestHandler) requestByHashes(destShardID uint32, hashes [] func (rrh *resolverRequestHandler) requestHashesWithDataSplit( unrequestedHashes [][]byte, requester HashSliceRequester, + epoch uint32, ) { dataSplit := &partitioning.DataSplit{} sliceBatches, err := dataSplit.SplitDataInChunks(unrequestedHashes, rrh.maxTxsToRequest) @@ -172,7 +182,6 @@ func (rrh *resolverRequestHandler) requestHashesWithDataSplit( ) } - epoch := rrh.getEpoch() for _, batch := range sliceBatches { err = requester.RequestDataFromHashArray(batch, epoch) if err != nil { @@ -202,16 +211,34 @@ func (rrh *resolverRequestHandler) requestReferenceWithChunkIndex( // RequestUnsignedTransactions method asks for unsigned transactions from the connected peers func (rrh *resolverRequestHandler) RequestUnsignedTransactions(destShardID uint32, scrHashes [][]byte) { - rrh.requestByHashes(destShardID, scrHashes, factory.UnsignedTransactionTopic, uniqueScrSuffix) + epoch := rrh.getEpoch() + rrh.requestByHashes(destShardID, scrHashes, factory.UnsignedTransactionTopic, uniqueScrSuffix, epoch) +} + +// RequestUnsignedTransactionsForEpoch method asks for unsigned transactions from the connected peers for a specific epoch +func (rrh *resolverRequestHandler) RequestUnsignedTransactionsForEpoch(destShardID uint32, scrHashes [][]byte, epoch uint32) { + rrh.requestByHashes(destShardID, scrHashes, factory.UnsignedTransactionTopic, uniqueScrSuffix, epoch) } // RequestRewardTransactions requests for reward transactions from the connected peers func (rrh *resolverRequestHandler) RequestRewardTransactions(destShardID uint32, rewardTxHashes [][]byte) { - rrh.requestByHashes(destShardID, rewardTxHashes, factory.RewardsTransactionTopic, uniqueRwdSuffix) + epoch := rrh.getEpoch() + rrh.requestByHashes(destShardID, rewardTxHashes, factory.RewardsTransactionTopic, uniqueRwdSuffix, epoch) +} + +// RequestRewardTransactionsForEpoch requests for reward transactions from the connected peers for a specific epoch +func (rrh *resolverRequestHandler) RequestRewardTransactionsForEpoch(destShardID uint32, rewardTxHashes [][]byte, epoch uint32) { + rrh.requestByHashes(destShardID, rewardTxHashes, factory.RewardsTransactionTopic, uniqueRwdSuffix, epoch) } -// RequestMiniBlock method asks for miniblock from the connected peers -func (rrh *resolverRequestHandler) RequestMiniBlock(destShardID uint32, miniblockHash []byte) { +// RequestMiniBlock method asks for miniBlock from the connected peers +func (rrh *resolverRequestHandler) RequestMiniBlock(destShardID uint32, miniBlockHash []byte) { + epoch := rrh.getEpoch() + rrh.RequestMiniBlockForEpoch(destShardID, miniBlockHash, epoch) +} + +// RequestMiniBlockForEpoch method asks for miniblock from the connected peers for a specific epoch +func (rrh *resolverRequestHandler) RequestMiniBlockForEpoch(destShardID uint32, miniblockHash []byte, epoch uint32) { suffix := fmt.Sprintf("%s_%d", uniqueMiniblockSuffix, destShardID) if !rrh.testIfRequestIsNeeded(miniblockHash, suffix) { return @@ -219,15 +246,17 @@ func (rrh *resolverRequestHandler) RequestMiniBlock(destShardID uint32, minibloc log.Debug("requesting miniblock from network", "topic", factory.MiniBlocksTopic, + "epoch", epoch, "shard", destShardID, "hash", miniblockHash, ) requester, err := rrh.requestersFinder.CrossShardRequester(factory.MiniBlocksTopic, destShardID) if err != nil { - log.Error("RequestMiniBlock.CrossShardRequester", + log.Error("RequestMiniBlockForEpoch.CrossShardRequester", "error", err.Error(), "topic", factory.MiniBlocksTopic, + "epoch", epoch, "shard", destShardID, ) return @@ -235,10 +264,9 @@ func (rrh *resolverRequestHandler) RequestMiniBlock(destShardID uint32, minibloc rrh.whiteList.Add([][]byte{miniblockHash}) - epoch := rrh.getEpoch() err = requester.RequestDataFromHash(miniblockHash, epoch) if err != nil { - log.Debug("RequestMiniBlock.RequestDataFromHash", + log.Debug("RequestMiniBlockForEpoch.RequestDataFromHash", "error", err.Error(), "epoch", epoch, "hash", miniblockHash, @@ -249,24 +277,32 @@ func (rrh *resolverRequestHandler) RequestMiniBlock(destShardID uint32, minibloc rrh.addRequestedItems([][]byte{miniblockHash}, suffix) } -// RequestMiniBlocks method asks for miniblocks from the connected peers -func (rrh *resolverRequestHandler) RequestMiniBlocks(destShardID uint32, miniblocksHashes [][]byte) { +// RequestMiniBlocks method asks for miniBlocks from the connected peers +func (rrh *resolverRequestHandler) RequestMiniBlocks(destShardID uint32, miniBlocksHashes [][]byte) { + epoch := rrh.getEpoch() + rrh.RequestMiniBlocksForEpoch(destShardID, miniBlocksHashes, epoch) +} + +// RequestMiniBlocksForEpoch method asks for miniBlocks from the connected peers for a specific epoch +func (rrh *resolverRequestHandler) RequestMiniBlocksForEpoch(destShardID uint32, miniBlocksHashes [][]byte, epoch uint32) { suffix := fmt.Sprintf("%s_%d", uniqueMiniblockSuffix, destShardID) - unrequestedHashes := rrh.getUnrequestedHashes(miniblocksHashes, suffix) + unrequestedHashes := rrh.getUnrequestedHashes(miniBlocksHashes, suffix) if len(unrequestedHashes) == 0 { return } log.Debug("requesting miniblocks from network", "topic", factory.MiniBlocksTopic, + "epoch", epoch, "shard", destShardID, "num mbs", len(unrequestedHashes), ) requester, err := rrh.requestersFinder.CrossShardRequester(factory.MiniBlocksTopic, destShardID) if err != nil { - log.Error("RequestMiniBlocks.CrossShardRequester", + log.Error("RequestMiniBlocksForEpoch.CrossShardRequester", "error", err.Error(), "topic", factory.MiniBlocksTopic, + "epoch", epoch, "shard", destShardID, ) return @@ -280,10 +316,9 @@ func (rrh *resolverRequestHandler) RequestMiniBlocks(destShardID uint32, miniblo rrh.whiteList.Add(unrequestedHashes) - epoch := rrh.getEpoch() err = miniBlocksRequester.RequestDataFromHashArray(unrequestedHashes, epoch) if err != nil { - log.Debug("RequestMiniBlocks.RequestDataFromHashArray", + log.Debug("RequestMiniBlocksForEpoch.RequestDataFromHashArray", "error", err.Error(), "epoch", epoch, "num mbs", len(unrequestedHashes), @@ -296,22 +331,27 @@ func (rrh *resolverRequestHandler) RequestMiniBlocks(destShardID uint32, miniblo // RequestShardHeader method asks for shard header from the connected peers func (rrh *resolverRequestHandler) RequestShardHeader(shardID uint32, hash []byte) { + epoch := rrh.getEpoch() + rrh.RequestShardHeaderForEpoch(shardID, hash, epoch) +} + +// RequestShardHeaderForEpoch method asks for shard header from the connected peers for a specific epoch +func (rrh *resolverRequestHandler) RequestShardHeaderForEpoch(shardID uint32, hash []byte, epoch uint32) { suffix := fmt.Sprintf("%s_%d", uniqueHeadersSuffix, shardID) if !rrh.testIfRequestIsNeeded(hash, suffix) { return } - epoch := rrh.getEpoch() log.Debug("requesting shard header from network", "shard", shardID, "hash", hash, "epoch", epoch, ) - headerRequester, err := rrh.getShardHeaderRequester(shardID) if err != nil { - log.Error("RequestShardHeader.getShardHeaderRequester", + log.Error("RequestShardHeaderForEpoch.getShardHeaderRequester", "error", err.Error(), + "epoch", epoch, "shard", shardID, ) return @@ -321,7 +361,7 @@ func (rrh *resolverRequestHandler) RequestShardHeader(shardID uint32, hash []byt err = headerRequester.RequestDataFromHash(hash, epoch) if err != nil { - log.Debug("RequestShardHeader.RequestDataFromHash", + log.Debug("RequestShardHeaderForEpoch.RequestDataFromHash", "error", err.Error(), "epoch", epoch, "hash", hash, @@ -334,18 +374,25 @@ func (rrh *resolverRequestHandler) RequestShardHeader(shardID uint32, hash []byt // RequestMetaHeader method asks for meta header from the connected peers func (rrh *resolverRequestHandler) RequestMetaHeader(hash []byte) { + epoch := rrh.getEpoch() + rrh.RequestMetaHeaderForEpoch(hash, epoch) +} + +// RequestMetaHeaderForEpoch method asks for meta header from the connected peers for a specific epoch +func (rrh *resolverRequestHandler) RequestMetaHeaderForEpoch(hash []byte, epoch uint32) { if !rrh.testIfRequestIsNeeded(hash, uniqueMetaHeadersSuffix) { return } - log.Debug("requesting meta header from network", + "epoch", epoch, "hash", hash, ) requester, err := rrh.getMetaHeaderRequester() if err != nil { - log.Error("RequestMetaHeader.getMetaHeaderRequester", + log.Error("RequestMetaHeaderForEpoch.getMetaHeaderRequester", "error", err.Error(), + "epoch", epoch, "hash", hash, ) return @@ -358,11 +405,9 @@ func (rrh *resolverRequestHandler) RequestMetaHeader(hash []byte) { } rrh.whiteList.Add([][]byte{hash}) - - epoch := rrh.getEpoch() err = headerRequester.RequestDataFromHash(hash, epoch) if err != nil { - log.Debug("RequestMetaHeader.RequestDataFromHash", + log.Debug("RequestMetaHeaderForEpoch.RequestDataFromHash", "error", err.Error(), "epoch", epoch, "hash", hash, @@ -375,6 +420,12 @@ func (rrh *resolverRequestHandler) RequestMetaHeader(hash []byte) { // RequestShardHeaderByNonce method asks for shard header from the connected peers by nonce func (rrh *resolverRequestHandler) RequestShardHeaderByNonce(shardID uint32, nonce uint64) { + epoch := rrh.getEpoch() + rrh.RequestShardHeaderByNonceForEpoch(shardID, nonce, epoch) +} + +// RequestShardHeaderByNonceForEpoch method asks for shard header from the connected peers by nonce and epoch +func (rrh *resolverRequestHandler) RequestShardHeaderByNonceForEpoch(shardID uint32, nonce uint64, epoch uint32) { suffix := fmt.Sprintf("%s_%d", uniqueHeadersSuffix, shardID) key := []byte(fmt.Sprintf("%d-%d", shardID, nonce)) if !rrh.testIfRequestIsNeeded(key, suffix) { @@ -384,12 +435,14 @@ func (rrh *resolverRequestHandler) RequestShardHeaderByNonce(shardID uint32, non log.Debug("requesting shard header by nonce from network", "shard", shardID, "nonce", nonce, + "epoch", epoch, ) requester, err := rrh.getShardHeaderRequester(shardID) if err != nil { - log.Error("RequestShardHeaderByNonce.getShardHeaderRequester", + log.Error("RequestShardHeaderByNonceForEpoch.getShardHeaderRequester", "error", err.Error(), + "epoch", epoch, "shard", shardID, ) return @@ -403,10 +456,9 @@ func (rrh *resolverRequestHandler) RequestShardHeaderByNonce(shardID uint32, non rrh.whiteList.Add([][]byte{key}) - epoch := rrh.getEpoch() err = headerRequester.RequestDataFromNonce(nonce, epoch) if err != nil { - log.Debug("RequestShardHeaderByNonce.RequestDataFromNonce", + log.Debug("RequestShardHeaderByNonceForEpoch.RequestDataFromNonce", "error", err.Error(), "epoch", epoch, "nonce", nonce, @@ -419,6 +471,12 @@ func (rrh *resolverRequestHandler) RequestShardHeaderByNonce(shardID uint32, non // RequestTrieNodes method asks for trie nodes from the connected peers func (rrh *resolverRequestHandler) RequestTrieNodes(destShardID uint32, hashes [][]byte, topic string) { + epoch := rrh.getEpoch() + rrh.RequestTrieNodesForEpoch(destShardID, hashes, topic, epoch) +} + +// RequestTrieNodesForEpoch method asks for trie nodes from the connected peers for a specific epoch +func (rrh *resolverRequestHandler) RequestTrieNodesForEpoch(destShardID uint32, hashes [][]byte, topic string, epoch uint32) { unrequestedHashes := rrh.getUnrequestedHashes(hashes, uniqueTrieNodesSuffix) if len(unrequestedHashes) == 0 { return @@ -447,6 +505,7 @@ func (rrh *resolverRequestHandler) RequestTrieNodes(destShardID uint32, hashes [ log.Trace("requesting trie nodes from network", "topic", topic, + "epoch", epoch, "shard", destShardID, "num nodes", len(rrh.trieHashesAccumulator), "firstHash", unrequestedHashes[0], @@ -458,6 +517,7 @@ func (rrh *resolverRequestHandler) RequestTrieNodes(destShardID uint32, hashes [ log.Error("requestersFinder.MetaCrossShardRequester", "error", err.Error(), "topic", topic, + "epoch", epoch, "shard", destShardID, ) return @@ -471,7 +531,7 @@ func (rrh *resolverRequestHandler) RequestTrieNodes(destShardID uint32, hashes [ rrh.logTrieHashesFromAccumulator() - go rrh.requestHashesWithDataSplit(itemsToRequest, trieRequester) + go rrh.requestHashesWithDataSplit(itemsToRequest, trieRequester, epoch) rrh.addRequestedItems(itemsToRequest, uniqueTrieNodesSuffix) rrh.lastTrieRequestTime = time.Now() @@ -535,6 +595,12 @@ func (rrh *resolverRequestHandler) logTrieHashesFromAccumulator() { // RequestMetaHeaderByNonce method asks for meta header from the connected peers by nonce func (rrh *resolverRequestHandler) RequestMetaHeaderByNonce(nonce uint64) { + epoch := rrh.getEpoch() + rrh.RequestMetaHeaderByNonceForEpoch(nonce, epoch) +} + +// RequestMetaHeaderByNonceForEpoch method asks for meta header from the connected peers by nonce and epoch +func (rrh *resolverRequestHandler) RequestMetaHeaderByNonceForEpoch(nonce uint64, epoch uint32) { key := []byte(fmt.Sprintf("%d-%d", core.MetachainShardId, nonce)) if !rrh.testIfRequestIsNeeded(key, uniqueMetaHeadersSuffix) { return @@ -542,22 +608,21 @@ func (rrh *resolverRequestHandler) RequestMetaHeaderByNonce(nonce uint64) { log.Debug("requesting meta header by nonce from network", "nonce", nonce, + "epoch", epoch, ) headerRequester, err := rrh.getMetaHeaderRequester() if err != nil { - log.Error("RequestMetaHeaderByNonce.getMetaHeaderRequester", + log.Error("RequestMetaHeaderByNonceForEpoch.getMetaHeaderRequester", "error", err.Error(), ) return } rrh.whiteList.Add([][]byte{key}) - - epoch := rrh.getEpoch() err = headerRequester.RequestDataFromNonce(nonce, epoch) if err != nil { - log.Debug("RequestMetaHeaderByNonce.RequestDataFromNonce", + log.Debug("RequestMetaHeaderByNonceForEpoch.RequestDataFromNonce", "error", err.Error(), "epoch", epoch, "nonce", nonce, @@ -570,12 +635,16 @@ func (rrh *resolverRequestHandler) RequestMetaHeaderByNonce(nonce uint64) { // RequestValidatorInfo asks for the validator info associated with a specific hash from connected peers func (rrh *resolverRequestHandler) RequestValidatorInfo(hash []byte) { + epoch := rrh.getEpoch() + rrh.RequestValidatorInfoForEpoch(hash, epoch) +} + +// RequestValidatorInfoForEpoch asks for the validator info associated with a specific hash from connected peers for a specific epoch +func (rrh *resolverRequestHandler) RequestValidatorInfoForEpoch(hash []byte, epoch uint32) { if !rrh.testIfRequestIsNeeded(hash, uniqueValidatorInfoSuffix) { return } - epoch := rrh.getEpoch() - log.Debug("requesting validator info messages from network", "topic", common.ValidatorInfoTopic, "hash", hash, @@ -584,7 +653,7 @@ func (rrh *resolverRequestHandler) RequestValidatorInfo(hash []byte) { requester, err := rrh.requestersFinder.MetaChainRequester(common.ValidatorInfoTopic) if err != nil { - log.Error("RequestValidatorInfo.MetaChainRequester", + log.Error("RequestValidatorInfoForEpoch.MetaChainRequester", "error", err.Error(), "topic", common.ValidatorInfoTopic, "hash", hash, @@ -597,7 +666,7 @@ func (rrh *resolverRequestHandler) RequestValidatorInfo(hash []byte) { err = requester.RequestDataFromHash(hash, epoch) if err != nil { - log.Debug("RequestValidatorInfo.RequestDataFromHash", + log.Debug("RequestValidatorInfoForEpoch.RequestDataFromHash", "error", err.Error(), "topic", common.ValidatorInfoTopic, "hash", hash, @@ -611,13 +680,17 @@ func (rrh *resolverRequestHandler) RequestValidatorInfo(hash []byte) { // RequestValidatorsInfo asks for the validators` info associated with the specified hashes from connected peers func (rrh *resolverRequestHandler) RequestValidatorsInfo(hashes [][]byte) { + epoch := rrh.getEpoch() + rrh.RequestValidatorsInfoForEpoch(hashes, epoch) +} + +// RequestValidatorsInfoForEpoch asks for the validators` info associated with the specified hashes from connected peers for a specific epoch +func (rrh *resolverRequestHandler) RequestValidatorsInfoForEpoch(hashes [][]byte, epoch uint32) { unrequestedHashes := rrh.getUnrequestedHashes(hashes, uniqueValidatorInfoSuffix) if len(unrequestedHashes) == 0 { return } - epoch := rrh.getEpoch() - log.Debug("requesting validator info messages from network", "topic", common.ValidatorInfoTopic, "num hashes", len(unrequestedHashes), @@ -626,7 +699,7 @@ func (rrh *resolverRequestHandler) RequestValidatorsInfo(hashes [][]byte) { requester, err := rrh.requestersFinder.MetaChainRequester(common.ValidatorInfoTopic) if err != nil { - log.Error("RequestValidatorInfo.MetaChainRequester", + log.Error("RequestValidatorsInfoForEpoch.MetaChainRequester", "error", err.Error(), "topic", common.ValidatorInfoTopic, "num hashes", len(unrequestedHashes), @@ -645,7 +718,7 @@ func (rrh *resolverRequestHandler) RequestValidatorsInfo(hashes [][]byte) { err = validatorInfoRequester.RequestDataFromHashArray(unrequestedHashes, epoch) if err != nil { - log.Debug("RequestValidatorInfo.RequestDataFromHash", + log.Debug("RequestValidatorsInfoForEpoch.RequestDataFromHash", "error", err.Error(), "topic", common.ValidatorInfoTopic, "num hashes", len(unrequestedHashes), @@ -835,7 +908,11 @@ func (rrh *resolverRequestHandler) GetNumPeersToQuery(key string) (int, int, err // RequestPeerAuthenticationsByHashes asks for peer authentication messages from specific peers hashes func (rrh *resolverRequestHandler) RequestPeerAuthenticationsByHashes(destShardID uint32, hashes [][]byte) { epoch := rrh.getEpoch() + rrh.RequestPeerAuthenticationsByHashesForEpoch(destShardID, hashes, epoch) +} +// RequestPeerAuthenticationsByHashesForEpoch asks for peer authentication messages from specific peers hashes for a specific epoch +func (rrh *resolverRequestHandler) RequestPeerAuthenticationsByHashesForEpoch(destShardID uint32, hashes [][]byte, epoch uint32) { log.Debug("requesting peer authentication messages from network", "topic", common.PeerAuthenticationTopic, "shard", destShardID, @@ -845,7 +922,7 @@ func (rrh *resolverRequestHandler) RequestPeerAuthenticationsByHashes(destShardI requester, err := rrh.requestersFinder.MetaChainRequester(common.PeerAuthenticationTopic) if err != nil { - log.Error("RequestPeerAuthenticationsByHashes.MetaChainRequester", + log.Error("RequestPeerAuthenticationsByHashesForEpoch.MetaChainRequester", "error", err.Error(), "topic", common.PeerAuthenticationTopic, "shard", destShardID, @@ -862,7 +939,7 @@ func (rrh *resolverRequestHandler) RequestPeerAuthenticationsByHashes(destShardI err = peerAuthRequester.RequestDataFromHashArray(hashes, epoch) if err != nil { - log.Debug("RequestPeerAuthenticationsByHashes.RequestDataFromHashArray", + log.Debug("RequestPeerAuthenticationsByHashesForEpoch.RequestDataFromHashArray", "error", err.Error(), "topic", common.PeerAuthenticationTopic, "shard", destShardID, @@ -873,11 +950,16 @@ func (rrh *resolverRequestHandler) RequestPeerAuthenticationsByHashes(destShardI // RequestEquivalentProofByHash asks for equivalent proof for the provided header hash func (rrh *resolverRequestHandler) RequestEquivalentProofByHash(headerShard uint32, headerHash []byte) { + epoch := rrh.getEpoch() + rrh.RequestEquivalentProofByHashForEpoch(headerShard, headerHash, epoch) +} + +// RequestEquivalentProofByHashForEpoch asks for equivalent proof for the provided header hash and epoch +func (rrh *resolverRequestHandler) RequestEquivalentProofByHashForEpoch(headerShard uint32, headerHash []byte, epoch uint32) { if !rrh.testIfRequestIsNeeded(headerHash, uniqueEquivalentProofSuffix) { return } - epoch := rrh.getEpoch() encodedHash := hex.EncodeToString(headerHash) log.Debug("requesting equivalent proof from network", "headerHash", encodedHash, @@ -887,7 +969,7 @@ func (rrh *resolverRequestHandler) RequestEquivalentProofByHash(headerShard uint requester, err := rrh.getEquivalentProofsRequester(headerShard) if err != nil { - log.Error("RequestEquivalentProofByHash.getEquivalentProofsRequester", + log.Error("RequestEquivalentProofByHashForEpoch.getEquivalentProofsRequester", "error", err.Error(), "headerHash", encodedHash, "epoch", epoch, @@ -900,7 +982,7 @@ func (rrh *resolverRequestHandler) RequestEquivalentProofByHash(headerShard uint requestKey := fmt.Sprintf("%s-%d", encodedHash, headerShard) err = requester.RequestDataFromHash([]byte(requestKey), epoch) if err != nil { - log.Debug("RequestEquivalentProofByHash.RequestDataFromHash", + log.Debug("RequestEquivalentProofByHashForEpoch.RequestDataFromHash", "error", err.Error(), "headerHash", encodedHash, "headerShard", headerShard, @@ -914,12 +996,17 @@ func (rrh *resolverRequestHandler) RequestEquivalentProofByHash(headerShard uint // RequestEquivalentProofByNonce asks for equivalent proof for the provided header nonce func (rrh *resolverRequestHandler) RequestEquivalentProofByNonce(headerShard uint32, headerNonce uint64) { + epoch := rrh.getEpoch() + rrh.RequestEquivalentProofByNonceForEpoch(headerShard, headerNonce, epoch) +} + +// RequestEquivalentProofByNonceForEpoch asks for equivalent proof for the provided header nonce and epoch +func (rrh *resolverRequestHandler) RequestEquivalentProofByNonceForEpoch(headerShard uint32, headerNonce uint64, epoch uint32) { key := common.GetEquivalentProofNonceShardKey(headerNonce, headerShard) if !rrh.testIfRequestIsNeeded([]byte(key), uniqueEquivalentProofSuffix) { return } - epoch := rrh.getEpoch() log.Debug("requesting equivalent proof by nonce from network", "headerNonce", headerNonce, "headerShard", headerShard, @@ -928,7 +1015,7 @@ func (rrh *resolverRequestHandler) RequestEquivalentProofByNonce(headerShard uin requester, err := rrh.getEquivalentProofsRequester(headerShard) if err != nil { - log.Error("RequestEquivalentProofByNonce.getEquivalentProofsRequester", + log.Error("RequestEquivalentProofByNonceForEpoch.getEquivalentProofsRequester", "error", err.Error(), "headerNonce", headerNonce, ) @@ -945,7 +1032,7 @@ func (rrh *resolverRequestHandler) RequestEquivalentProofByNonce(headerShard uin err = proofsRequester.RequestDataFromNonce([]byte(key), epoch) if err != nil { - log.Debug("RequestEquivalentProofByNonce.RequestDataFromNonce", + log.Debug("RequestEquivalentProofByNonceForEpoch.RequestDataFromNonce", "error", err.Error(), "headerNonce", headerNonce, "headerShard", headerShard, diff --git a/dataRetriever/requestHandlers/requestHandler_test.go b/dataRetriever/requestHandlers/requestHandler_test.go index 7717c10f7e9..8ea9ea3b65d 100644 --- a/dataRetriever/requestHandlers/requestHandler_test.go +++ b/dataRetriever/requestHandlers/requestHandler_test.go @@ -9,14 +9,15 @@ import ( "time" "github.com/multiversx/mx-chain-core-go/core" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/dataRetriever/mock" "github.com/multiversx/mx-chain-go/storage/cache" dataRetrieverMocks "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) var timeoutSendRequests = time.Second * 2 @@ -161,7 +162,7 @@ func TestResolverRequestHandler_RequestTransaction(t *testing.T) { time.Second, ) - rrh.RequestTransaction(0, make([][]byte, 0)) + rrh.RequestTransactions(0, make([][]byte, 0)) }) t.Run("error when getting cross shard requester should not panic", func(t *testing.T) { t.Parallel() @@ -186,7 +187,7 @@ func TestResolverRequestHandler_RequestTransaction(t *testing.T) { time.Second, ) - rrh.RequestTransaction(0, [][]byte{[]byte("txHash")}) + rrh.RequestTransactions(0, [][]byte{[]byte("txHash")}) }) t.Run("uncastable requester should not panic", func(t *testing.T) { t.Parallel() @@ -213,7 +214,7 @@ func TestResolverRequestHandler_RequestTransaction(t *testing.T) { time.Second, ) - rrh.RequestTransaction(0, [][]byte{[]byte("txHash")}) + rrh.RequestTransactions(0, [][]byte{[]byte("txHash")}) }) t.Run("should request", func(t *testing.T) { t.Parallel() @@ -239,7 +240,7 @@ func TestResolverRequestHandler_RequestTransaction(t *testing.T) { time.Second, ) - rrh.RequestTransaction(0, [][]byte{[]byte("txHash")}) + rrh.RequestTransactions(0, [][]byte{[]byte("txHash")}) select { case <-chTxRequested: @@ -275,19 +276,19 @@ func TestResolverRequestHandler_RequestTransaction(t *testing.T) { time.Second, ) - rrh.RequestTransaction(0, [][]byte{[]byte("txHash")}) - rrh.RequestTransaction(1, [][]byte{[]byte("txHash")}) - rrh.RequestTransaction(0, [][]byte{[]byte("txHash")}) - rrh.RequestTransaction(1, [][]byte{[]byte("txHash")}) + rrh.RequestTransactions(0, [][]byte{[]byte("txHash")}) + rrh.RequestTransactions(1, [][]byte{[]byte("txHash")}) + rrh.RequestTransactions(0, [][]byte{[]byte("txHash")}) + rrh.RequestTransactions(1, [][]byte{[]byte("txHash")}) time.Sleep(time.Second) // let the go routines finish assert.Equal(t, uint32(2), atomic.LoadUint32(&numRequests)) time.Sleep(time.Second) // sweep will take effect - rrh.RequestTransaction(0, [][]byte{[]byte("txHash")}) - rrh.RequestTransaction(1, [][]byte{[]byte("txHash")}) - rrh.RequestTransaction(0, [][]byte{[]byte("txHash")}) - rrh.RequestTransaction(1, [][]byte{[]byte("txHash")}) + rrh.RequestTransactions(0, [][]byte{[]byte("txHash")}) + rrh.RequestTransactions(1, [][]byte{[]byte("txHash")}) + rrh.RequestTransactions(0, [][]byte{[]byte("txHash")}) + rrh.RequestTransactions(1, [][]byte{[]byte("txHash")}) time.Sleep(time.Second) // let the go routines finish assert.Equal(t, uint32(4), atomic.LoadUint32(&numRequests)) @@ -323,7 +324,46 @@ func TestResolverRequestHandler_RequestTransaction(t *testing.T) { time.Second, ) - rrh.RequestTransaction(0, [][]byte{[]byte("txHash")}) + rrh.RequestTransactions(0, [][]byte{[]byte("txHash")}) + + select { + case <-chTxRequested: + case <-time.After(timeoutSendRequests): + assert.Fail(t, "timeout while waiting to call RequestDataFromHashArray") + } + + time.Sleep(time.Second) + }) +} + +func TestResolverRequestHandler_RequestTransactionsForEpoch(t *testing.T) { + t.Run("should request for given epoch", func(t *testing.T) { + t.Parallel() + + requestEpoch := uint32(1) + chTxRequested := make(chan struct{}) + txRequester := &dataRetrieverMocks.HashSliceRequesterStub{ + RequestDataFromHashArrayCalled: func(hashes [][]byte, epoch uint32) error { + require.Equal(t, requestEpoch, epoch) + chTxRequested <- struct{}{} + return nil + }, + } + + rrh, _ := NewResolverRequestHandler( + &dataRetrieverMocks.RequestersFinderStub{ + CrossShardRequesterCalled: func(baseTopic string, crossShard uint32) (requester dataRetriever.Requester, e error) { + return txRequester, nil + }, + }, + &mock.RequestedItemsHandlerStub{}, + &mock.WhiteListHandlerStub{}, + 1, + 0, + time.Second, + ) + + rrh.RequestTransactionsForEpoch(0, [][]byte{[]byte("txHash")}, requestEpoch) select { case <-chTxRequested: @@ -689,6 +729,40 @@ func TestResolverRequestHandler_RequestMetaHeader(t *testing.T) { }) } +func TestResolverRequestHandler_RequestMetaHeaderForEpoch(t *testing.T) { + t.Parallel() + t.Run("should work", func(t *testing.T) { + t.Parallel() + + requestEpoch := uint32(1) + wasCalled := false + mbRequester := &dataRetrieverMocks.HeaderRequesterStub{ + RequestDataFromHashCalled: func(hash []byte, epoch uint32) error { + require.Equal(t, requestEpoch, epoch) + wasCalled = true + return nil + }, + } + + rrh, _ := NewResolverRequestHandler( + &dataRetrieverMocks.RequestersFinderStub{ + MetaChainRequesterCalled: func(baseTopic string) (requester dataRetriever.Requester, e error) { + return mbRequester, nil + }, + }, + &mock.RequestedItemsHandlerStub{}, + &mock.WhiteListHandlerStub{}, + 1, + 0, + time.Second, + ) + + rrh.RequestMetaHeaderForEpoch([]byte("hdrHash"), requestEpoch) + + assert.True(t, wasCalled) + }) +} + func TestResolverRequestHandler_RequestShardHeaderByNonce(t *testing.T) { t.Parallel() @@ -944,6 +1018,41 @@ func TestResolverRequestHandler_RequestMetaHeaderByNonce(t *testing.T) { }) } +func TestResolverRequestHandler_RequestMetaHeaderByNonceForEpoch(t *testing.T) { + t.Parallel() + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + requestEpoch := uint32(1) + wasCalled := false + hdrRequester := &dataRetrieverMocks.HeaderRequesterStub{ + RequestDataFromNonceCalled: func(nonce uint64, epoch uint32) error { + wasCalled = true + require.Equal(t, requestEpoch, epoch) + return nil + }, + } + + rrh, _ := NewResolverRequestHandler( + &dataRetrieverMocks.RequestersFinderStub{ + MetaChainRequesterCalled: func(baseTopic string) (requester dataRetriever.Requester, e error) { + return hdrRequester, nil + }, + }, + &mock.RequestedItemsHandlerStub{}, + &mock.WhiteListHandlerStub{}, + 100, + 0, + time.Second, + ) + + rrh.RequestMetaHeaderByNonceForEpoch(0, requestEpoch) + + assert.True(t, wasCalled) + }) +} + func TestResolverRequestHandler_RequestScrErrorWhenGettingCrossShardRequesterShouldNotPanic(t *testing.T) { t.Parallel() @@ -1029,8 +1138,41 @@ func TestResolverRequestHandler_RequestScrShouldRequestScr(t *testing.T) { case <-time.After(timeoutSendRequests): assert.Fail(t, "timeout while waiting to call RequestDataFromHashArray") } +} - time.Sleep(time.Second) +func TestResolverRequestHandler_RequestScrForEpochShouldRequestScr(t *testing.T) { + t.Parallel() + + requestEpoch := uint32(1) + chTxRequested := make(chan struct{}) + txRequester := &dataRetrieverMocks.HashSliceRequesterStub{ + RequestDataFromHashArrayCalled: func(hashes [][]byte, epoch uint32) error { + require.Equal(t, requestEpoch, epoch) + chTxRequested <- struct{}{} + return nil + }, + } + + rrh, _ := NewResolverRequestHandler( + &dataRetrieverMocks.RequestersFinderStub{ + CrossShardRequesterCalled: func(baseTopic string, crossShard uint32) (requester dataRetriever.Requester, e error) { + return txRequester, nil + }, + }, + &mock.RequestedItemsHandlerStub{}, + &mock.WhiteListHandlerStub{}, + 1, + 0, + time.Second, + ) + + rrh.RequestUnsignedTransactionsForEpoch(0, [][]byte{[]byte("txHash")}, requestEpoch) + + select { + case <-chTxRequested: + case <-time.After(timeoutSendRequests): + assert.Fail(t, "timeout while waiting to call RequestDataFromHashArray") + } } func TestResolverRequestHandler_RequestScrErrorsOnRequestShouldNotPanic(t *testing.T) { @@ -1071,8 +1213,6 @@ func TestResolverRequestHandler_RequestScrErrorsOnRequestShouldNotPanic(t *testi case <-time.After(timeoutSendRequests): assert.Fail(t, "timeout while waiting to call RequestDataFromHashArray") } - - time.Sleep(time.Second) } func TestResolverRequestHandler_RequestRewardShouldRequestReward(t *testing.T) { @@ -1106,8 +1246,41 @@ func TestResolverRequestHandler_RequestRewardShouldRequestReward(t *testing.T) { case <-time.After(timeoutSendRequests): assert.Fail(t, "timeout while waiting to call RequestDataFromHashArray") } +} - time.Sleep(time.Second) +func TestResolverRequestHandler_RequestRewardForEpochShouldRequestReward(t *testing.T) { + t.Parallel() + + requestEpoch := uint32(1) + chTxRequested := make(chan struct{}) + txRequester := &dataRetrieverMocks.HashSliceRequesterStub{ + RequestDataFromHashArrayCalled: func(hashes [][]byte, epoch uint32) error { + require.Equal(t, requestEpoch, epoch) + chTxRequested <- struct{}{} + return nil + }, + } + + rrh, _ := NewResolverRequestHandler( + &dataRetrieverMocks.RequestersFinderStub{ + CrossShardRequesterCalled: func(baseTopic string, crossShard uint32) (requester dataRetriever.Requester, e error) { + return txRequester, nil + }, + }, + &mock.RequestedItemsHandlerStub{}, + &mock.WhiteListHandlerStub{}, + 1, + 0, + time.Second, + ) + + rrh.RequestRewardTransactionsForEpoch(0, [][]byte{[]byte("txHash")}, requestEpoch) + + select { + case <-chTxRequested: + case <-time.After(timeoutSendRequests): + assert.Fail(t, "timeout while waiting to call RequestDataFromHashArray") + } } func TestRequestTrieNodes(t *testing.T) { @@ -2240,3 +2413,218 @@ func TestResolverRequestHandler_RequestEquivalentProofByHash(t *testing.T) { assert.True(t, wasCalled) }) } + +func TestResolverRequestHandler_RequestEquivalentProofByNonce(t *testing.T) { + t.Parallel() + + t.Run("nonce already requested should work", func(t *testing.T) { + t.Parallel() + + nonce := uint64(10) + rrh, _ := NewResolverRequestHandler( + &dataRetrieverMocks.RequestersFinderStub{ + MetaChainRequesterCalled: func(baseTopic string) (requester dataRetriever.Requester, e error) { + require.Fail(t, "should not have been called") + return nil, nil + }, + }, + &mock.RequestedItemsHandlerStub{ + HasCalled: func(key string) bool { + return true + }, + }, + &mock.WhiteListHandlerStub{ + AddCalled: func(keys [][]byte) { + require.Fail(t, "should not have been called") + }, + }, + 100, + 0, + time.Second, + ) + + rrh.RequestEquivalentProofByNonce(core.MetachainShardId, nonce) + }) + t.Run("invalid cross-shard request should early exit", func(t *testing.T) { + t.Parallel() + + nonce := uint64(10) + rrh, _ := NewResolverRequestHandler( + &dataRetrieverMocks.RequestersFinderStub{}, + &mock.RequestedItemsHandlerStub{}, + &mock.WhiteListHandlerStub{ + AddCalled: func(keys [][]byte) { + require.Fail(t, "should not have been called") + }, + }, + 100, + 0, + time.Second, + ) + + rrh.RequestEquivalentProofByNonce(1, nonce) + }) + t.Run("missing metachain requester should early exit", func(t *testing.T) { + t.Parallel() + + nonce := uint64(10) + rrh, _ := NewResolverRequestHandler( + &dataRetrieverMocks.RequestersFinderStub{ + MetaChainRequesterCalled: func(baseTopic string) (dataRetriever.Requester, error) { + return nil, errExpected + }, + }, + &mock.RequestedItemsHandlerStub{}, + &mock.WhiteListHandlerStub{ + AddCalled: func(keys [][]byte) { + require.Fail(t, "should not have been called") + }, + }, + 100, + core.MetachainShardId, + time.Second, + ) + + rrh.RequestEquivalentProofByNonce(core.MetachainShardId, nonce) + }) + t.Run("missing cross-shard requester should early exit", func(t *testing.T) { + t.Parallel() + + nonce := uint64(10) + rrh, _ := NewResolverRequestHandler( + &dataRetrieverMocks.RequestersFinderStub{ + CrossShardRequesterCalled: func(baseTopic string, crossShard uint32) (dataRetriever.Requester, error) { + return nil, errExpected + }, + }, + &mock.RequestedItemsHandlerStub{}, + &mock.WhiteListHandlerStub{ + AddCalled: func(keys [][]byte) { + require.Fail(t, "should not have been called") + }, + }, + 100, + 0, + time.Second, + ) + + rrh.RequestEquivalentProofByNonce(1, nonce) + }) + t.Run("MetaChainRequester returns error", func(t *testing.T) { + t.Parallel() + + nonce := uint64(10) + res := &dataRetrieverMocks.EquivalentProofRequesterStub{ + RequestDataFromNonceCalled: func(key []byte, epoch uint32) error { + require.Fail(t, "should not have been called") + + return nil + }, + } + + rrh, _ := NewResolverRequestHandler( + &dataRetrieverMocks.RequestersFinderStub{ + MetaChainRequesterCalled: func(baseTopic string) (requester dataRetriever.Requester, e error) { + return res, errExpected + }, + }, + &mock.RequestedItemsHandlerStub{}, + &mock.WhiteListHandlerStub{}, + 100, + core.MetachainShardId, + time.Second, + ) + + rrh.RequestEquivalentProofByNonce(core.MetachainShardId, nonce) + }) + t.Run("CrossChainRequester returns error", func(t *testing.T) { + t.Parallel() + + nonce := uint64(10) + res := &dataRetrieverMocks.EquivalentProofRequesterStub{ + RequestDataFromNonceCalled: func(hash []byte, epoch uint32) error { + require.Fail(t, "should not have been called") + return nil + }, + } + + rrh, _ := NewResolverRequestHandler( + &dataRetrieverMocks.RequestersFinderStub{ + CrossShardRequesterCalled: func(baseTopic string, crossShard uint32) (dataRetriever.Requester, error) { + return res, errExpected + }, + }, + &mock.RequestedItemsHandlerStub{}, + &mock.WhiteListHandlerStub{}, + 100, + 0, + time.Second, + ) + + rrh.RequestEquivalentProofByNonce(0, nonce) + }) + t.Run("RequestDataFromNonce returns error", func(t *testing.T) { + t.Parallel() + + shardID := core.MetachainShardId + requestNonce := uint64(10) + expectedRequestKey := common.GetEquivalentProofNonceShardKey(requestNonce, shardID) + res := &dataRetrieverMocks.EquivalentProofRequesterStub{ + RequestDataFromNonceCalled: func(nonceShardKey []byte, epoch uint32) error { + require.Equal(t, []byte(expectedRequestKey), nonceShardKey) + return errExpected + }, + } + + rrh, _ := NewResolverRequestHandler( + &dataRetrieverMocks.RequestersFinderStub{ + MetaChainRequesterCalled: func(baseTopic string) (requester dataRetriever.Requester, e error) { + return res, nil + }, + }, + &mock.RequestedItemsHandlerStub{ + AddCalled: func(key string) error { + require.Fail(t, "should not have been called") + return nil + }, + }, + &mock.WhiteListHandlerStub{}, + 100, + core.MetachainShardId, + time.Second, + ) + + rrh.RequestEquivalentProofByNonce(shardID, requestNonce) + }) + t.Run("should work shard 0 requesting from 0", func(t *testing.T) { + t.Parallel() + + shardID := uint32(0) + requestNonce := uint64(10) + expectedRequestKey := common.GetEquivalentProofNonceShardKey(requestNonce, shardID) + wasCalled := false + res := &dataRetrieverMocks.EquivalentProofRequesterStub{ + RequestDataFromNonceCalled: func(nonceShardKey []byte, epoch uint32) error { + require.Equal(t, []byte(expectedRequestKey), nonceShardKey) + wasCalled = true + return nil + }, + } + + rrh, _ := NewResolverRequestHandler( + &dataRetrieverMocks.RequestersFinderStub{ + CrossShardRequesterCalled: func(baseTopic string, crossShard uint32) (dataRetriever.Requester, error) { + return res, nil + }, + }, + &mock.RequestedItemsHandlerStub{}, + &mock.WhiteListHandlerStub{}, + 100, + 0, + time.Second, + ) + + rrh.RequestEquivalentProofByNonce(shardID, requestNonce) + require.True(t, wasCalled) + }) +} diff --git a/dataRetriever/resolvers/epochproviders/arithmeticEpochProvider.go b/dataRetriever/resolvers/epochproviders/arithmeticEpochProvider.go index 675ebd6f276..cc77f6e237a 100644 --- a/dataRetriever/resolvers/epochproviders/arithmeticEpochProvider.go +++ b/dataRetriever/resolvers/epochproviders/arithmeticEpochProvider.go @@ -5,6 +5,10 @@ import ( "sync" "time" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/errors" + "github.com/multiversx/mx-chain-go/process" logger "github.com/multiversx/mx-chain-logger-go" ) @@ -19,9 +23,9 @@ var log = logger.GetOrCreate("resolvers/epochproviders") // ArgArithmeticEpochProvider is the argument structure for the arithmetic epoch provider type ArgArithmeticEpochProvider struct { - RoundsPerEpoch uint32 - RoundTimeInMilliseconds uint64 - StartTime int64 + ChainParametersHandler process.ChainParametersHandler + StartTime int64 + EnableEpochsHandler common.EnableEpochsHandler } type arithmeticEpochProvider struct { @@ -29,32 +33,35 @@ type arithmeticEpochProvider struct { currentComputedEpoch uint32 headerEpoch uint32 headerTimestampForNewEpoch uint64 - roundsPerEpoch uint32 - roundTimeInMilliseconds uint64 - startTime int64 getUnixHandler func() int64 + enableEpochsHandler common.EnableEpochsHandler + chainParamsHandler process.ChainParametersHandler } // NewArithmeticEpochProvider returns a new arithmetic epoch provider able to mathematically compute the current network epoch // based on the last block saved, considering the block's timestamp and epoch in respect with the current time func NewArithmeticEpochProvider(arg ArgArithmeticEpochProvider) (*arithmeticEpochProvider, error) { - if arg.RoundsPerEpoch == 0 { - return nil, fmt.Errorf("%w in NewArithmeticEpochProvider", ErrInvalidRoundsPerEpoch) - } - if arg.RoundTimeInMilliseconds == 0 { - return nil, fmt.Errorf("%w in NewArithmeticEpochProvider", ErrInvalidRoundTimeInMilliseconds) - } if arg.StartTime < 0 { return nil, fmt.Errorf("%w in NewArithmeticEpochProvider", ErrInvalidStartTime) } + if check.IfNil(arg.EnableEpochsHandler) { + return nil, errors.ErrNilEnableEpochsHandler + } + if check.IfNil(arg.ChainParametersHandler) { + return nil, process.ErrNilChainParametersHandler + } + aep := &arithmeticEpochProvider{ headerEpoch: 0, headerTimestampForNewEpoch: uint64(arg.StartTime), - roundsPerEpoch: arg.RoundsPerEpoch, - roundTimeInMilliseconds: arg.RoundTimeInMilliseconds, - startTime: arg.StartTime, + enableEpochsHandler: arg.EnableEpochsHandler, + chainParamsHandler: arg.ChainParametersHandler, } aep.getUnixHandler = func() int64 { + if aep.enableEpochsHandler.IsFlagEnabledInEpoch(common.SupernovaFlag, aep.headerEpoch) { + return time.Now().UnixMilli() + } + return time.Now().Unix() } aep.computeCurrentEpoch() //based on the genesis provided data @@ -98,15 +105,28 @@ func (aep *arithmeticEpochProvider) computeCurrentEpoch() { return } - diffTimeStampInSeconds := currentTimeStamp - aep.headerTimestampForNewEpoch - diffTimeStampInMilliseconds := diffTimeStampInSeconds * millisecondsInOneSecond - diffRounds := diffTimeStampInMilliseconds / aep.roundTimeInMilliseconds - diffEpochs := diffRounds / uint64(aep.roundsPerEpoch+1) + currentChainParameters := aep.chainParamsHandler.CurrentChainParameters() + + diffTimeStampInMilliseconds := aep.getDiffTimeStampInMilliseconds(currentTimeStamp) + diffRounds := diffTimeStampInMilliseconds / currentChainParameters.RoundDuration + diffEpochs := diffRounds / uint64(currentChainParameters.RoundsPerEpoch+1) aep.currentComputedEpoch = aep.headerEpoch + uint32(diffEpochs) log.Debug("arithmeticEpochProvider.computeCurrentEpoch", - "computed network epoch", aep.currentComputedEpoch) + "computed network epoch", aep.currentComputedEpoch, + ) +} + +func (aep *arithmeticEpochProvider) getDiffTimeStampInMilliseconds(currentTimeStamp uint64) uint64 { + if aep.enableEpochsHandler.IsFlagEnabledInEpoch(common.SupernovaFlag, aep.headerEpoch) { + return currentTimeStamp - aep.headerTimestampForNewEpoch + } + + diffTimeStampInSeconds := currentTimeStamp - aep.headerTimestampForNewEpoch + diffTimeStampInMilliseconds := diffTimeStampInSeconds * millisecondsInOneSecond + + return diffTimeStampInMilliseconds } // IsInterfaceNil returns true if there is no value under the interface diff --git a/dataRetriever/resolvers/epochproviders/arithmeticEpochProvider_test.go b/dataRetriever/resolvers/epochproviders/arithmeticEpochProvider_test.go index ab177e8ea6e..66f5881a0d3 100644 --- a/dataRetriever/resolvers/epochproviders/arithmeticEpochProvider_test.go +++ b/dataRetriever/resolvers/epochproviders/arithmeticEpochProvider_test.go @@ -5,7 +5,14 @@ import ( "testing" "time" + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" + commonErrors "github.com/multiversx/mx-chain-go/errors" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/stretchr/testify/assert" ) @@ -15,33 +22,44 @@ func getUnixHandler(unix int64) func() int64 { } } -func TestNewArithmeticEpochProvider_InvalidRoundsPerEpoch(t *testing.T) { +func getMockChainParametersHandler() *chainParameters.ChainParametersHandlerStub { + return &chainParameters.ChainParametersHandlerStub{ + CurrentChainParametersCalled: func() config.ChainParametersByEpochConfig { + return config.ChainParametersByEpochConfig{ + RoundsPerEpoch: 2400, + RoundDuration: 6000, + } + }, + } +} + +func TestNewArithmeticEpochProvider_NilChainParameterHandler(t *testing.T) { t.Parallel() arg := ArgArithmeticEpochProvider{ - RoundsPerEpoch: 0, - RoundTimeInMilliseconds: 1, - StartTime: 1, + ChainParametersHandler: nil, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + StartTime: 1, } aep, err := NewArithmeticEpochProvider(arg) - assert.True(t, errors.Is(err, ErrInvalidRoundsPerEpoch)) + assert.True(t, errors.Is(err, process.ErrNilChainParametersHandler)) assert.True(t, check.IfNil(aep)) } -func TestNewArithmeticEpochProvider_InvalidRoundTimeInMilliseconds(t *testing.T) { +func TestNewArithmeticEpochProvider_NilEnableEpochsHandler(t *testing.T) { t.Parallel() arg := ArgArithmeticEpochProvider{ - RoundsPerEpoch: 1, - RoundTimeInMilliseconds: 0, - StartTime: 1, + ChainParametersHandler: getMockChainParametersHandler(), + EnableEpochsHandler: nil, + StartTime: 1, } aep, err := NewArithmeticEpochProvider(arg) - assert.True(t, errors.Is(err, ErrInvalidRoundTimeInMilliseconds)) + assert.True(t, errors.Is(err, commonErrors.ErrNilEnableEpochsHandler)) assert.True(t, check.IfNil(aep)) } @@ -49,9 +67,9 @@ func TestNewArithmeticEpochProvider_InvalidStartTime(t *testing.T) { t.Parallel() arg := ArgArithmeticEpochProvider{ - RoundsPerEpoch: 1, - RoundTimeInMilliseconds: 1, - StartTime: -1, + ChainParametersHandler: getMockChainParametersHandler(), + StartTime: -1, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, } aep, err := NewArithmeticEpochProvider(arg) @@ -63,65 +81,150 @@ func TestNewArithmeticEpochProvider_InvalidStartTime(t *testing.T) { func TestNewArithmeticEpochProvider_ShouldWork(t *testing.T) { t.Parallel() - arg := ArgArithmeticEpochProvider{ - RoundsPerEpoch: 2400, - RoundTimeInMilliseconds: 6000, - StartTime: time.Now().Unix(), - } - - aep, err := NewArithmeticEpochProvider(arg) - - assert.Nil(t, err) - assert.False(t, check.IfNil(aep)) - assert.Equal(t, uint32(0), aep.CurrentComputedEpoch()) + t.Run("before supernova", func(t *testing.T) { + t.Parallel() + + arg := ArgArithmeticEpochProvider{ + ChainParametersHandler: getMockChainParametersHandler(), + StartTime: time.Now().Unix(), + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + } + + aep, err := NewArithmeticEpochProvider(arg) + + assert.Nil(t, err) + assert.False(t, check.IfNil(aep)) + assert.Equal(t, uint32(0), aep.CurrentComputedEpoch()) + }) + + t.Run("after supernova", func(t *testing.T) { + t.Parallel() + + arg := ArgArithmeticEpochProvider{ + ChainParametersHandler: getMockChainParametersHandler(), + StartTime: time.Now().UnixMilli(), + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag + }, + }, + } + + aep, err := NewArithmeticEpochProvider(arg) + + assert.Nil(t, err) + assert.False(t, check.IfNil(aep)) + assert.Equal(t, uint32(0), aep.CurrentComputedEpoch()) + }) } func TestArithmeticEpochProvider_ComputeEpochAtGenesis(t *testing.T) { t.Parallel() - arg := ArgArithmeticEpochProvider{ - RoundsPerEpoch: 2400, - RoundTimeInMilliseconds: 6000, - StartTime: 1000, - } - aep := NewTestArithmeticEpochProvider(arg, getUnixHandler(0)) - assert.Equal(t, uint32(0), aep.CurrentComputedEpoch()) + t.Run("before supernova", func(t *testing.T) { + t.Parallel() - aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(1000)) - assert.Equal(t, uint32(0), aep.CurrentComputedEpoch()) + arg := ArgArithmeticEpochProvider{ + ChainParametersHandler: getMockChainParametersHandler(), + StartTime: 1000, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + } + aep := NewTestArithmeticEpochProvider(arg, getUnixHandler(0)) + assert.Equal(t, uint32(0), aep.CurrentComputedEpoch()) - aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(15400)) - assert.Equal(t, uint32(0), aep.CurrentComputedEpoch()) + aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(1000)) + assert.Equal(t, uint32(0), aep.CurrentComputedEpoch()) - aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(15401)) - assert.Equal(t, uint32(0), aep.CurrentComputedEpoch()) + aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(15400)) + assert.Equal(t, uint32(0), aep.CurrentComputedEpoch()) - aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(15405)) - assert.Equal(t, uint32(0), aep.CurrentComputedEpoch()) + aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(15401)) + assert.Equal(t, uint32(0), aep.CurrentComputedEpoch()) - aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(15406)) - assert.Equal(t, uint32(1), aep.CurrentComputedEpoch()) + aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(15405)) + assert.Equal(t, uint32(0), aep.CurrentComputedEpoch()) - aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(15412)) - assert.Equal(t, uint32(1), aep.CurrentComputedEpoch()) + aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(15406)) + assert.Equal(t, uint32(1), aep.CurrentComputedEpoch()) - aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(29800)) - assert.Equal(t, uint32(1), aep.CurrentComputedEpoch()) + aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(15412)) + assert.Equal(t, uint32(1), aep.CurrentComputedEpoch()) - aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(29806)) - assert.Equal(t, uint32(1), aep.CurrentComputedEpoch()) + aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(29800)) + assert.Equal(t, uint32(1), aep.CurrentComputedEpoch()) - aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(29812)) - assert.Equal(t, uint32(2), aep.CurrentComputedEpoch()) + aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(29806)) + assert.Equal(t, uint32(1), aep.CurrentComputedEpoch()) + + aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(29812)) + assert.Equal(t, uint32(2), aep.CurrentComputedEpoch()) + }) + + t.Run("after supernova", func(t *testing.T) { + t.Parallel() + + arg := ArgArithmeticEpochProvider{ + ChainParametersHandler: &chainParameters.ChainParametersHandlerStub{ + CurrentChainParametersCalled: func() config.ChainParametersByEpochConfig { + return config.ChainParametersByEpochConfig{ + RoundsPerEpoch: 2400, + RoundDuration: 6000, + } + }, + }, + StartTime: 1000 * 1000, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag + }, + }, + } + aep := NewTestArithmeticEpochProvider(arg, getUnixHandler(0)) + assert.Equal(t, uint32(0), aep.CurrentComputedEpoch()) + + aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(1000*1000)) + assert.Equal(t, uint32(0), aep.CurrentComputedEpoch()) + + aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(15400*1000)) + assert.Equal(t, uint32(0), aep.CurrentComputedEpoch()) + + aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(15401*1000)) + assert.Equal(t, uint32(0), aep.CurrentComputedEpoch()) + + aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(15405*1000)) + assert.Equal(t, uint32(0), aep.CurrentComputedEpoch()) + + aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(15406*1000)) + assert.Equal(t, uint32(1), aep.CurrentComputedEpoch()) + + aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(15412*1000)) + assert.Equal(t, uint32(1), aep.CurrentComputedEpoch()) + + aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(29800*1000)) + assert.Equal(t, uint32(1), aep.CurrentComputedEpoch()) + + aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(29806*1000)) + assert.Equal(t, uint32(1), aep.CurrentComputedEpoch()) + + aep = NewTestArithmeticEpochProvider(arg, getUnixHandler(29812*1000)) + assert.Equal(t, uint32(2), aep.CurrentComputedEpoch()) + }) } func TestArithmeticEpochProvider_EpochConfirmedInvalidTimestamp(t *testing.T) { t.Parallel() arg := ArgArithmeticEpochProvider{ - RoundsPerEpoch: 2400, - RoundTimeInMilliseconds: 6000, - StartTime: 1000, + ChainParametersHandler: &chainParameters.ChainParametersHandlerStub{ + CurrentChainParametersCalled: func() config.ChainParametersByEpochConfig { + return config.ChainParametersByEpochConfig{ + RoundsPerEpoch: 2400, + RoundDuration: 6000, + } + }, + }, + StartTime: 1000, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, } aep := NewTestArithmeticEpochProvider(arg, getUnixHandler(15500)) assert.Equal(t, uint32(1), aep.CurrentComputedEpoch()) @@ -134,28 +237,207 @@ func TestArithmeticEpochProvider_EpochConfirmedInvalidTimestamp(t *testing.T) { func TestArithmeticEpochProvider_EpochConfirmed(t *testing.T) { t.Parallel() + t.Run("before supernova", func(t *testing.T) { + t.Parallel() + + arg := ArgArithmeticEpochProvider{ + ChainParametersHandler: getMockChainParametersHandler(), + StartTime: 1000, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + } + aep := NewTestArithmeticEpochProvider(arg, getUnixHandler(15500)) + assert.Equal(t, uint32(1), aep.CurrentComputedEpoch()) + + aep.SetUnixHandler(getUnixHandler(17500)) + + aep.EpochConfirmed(1, 3000) + + assert.Equal(t, uint32(2), aep.CurrentComputedEpoch()) + }) + + t.Run("after supernova", func(t *testing.T) { + t.Parallel() + + arg := ArgArithmeticEpochProvider{ + ChainParametersHandler: &chainParameters.ChainParametersHandlerStub{ + CurrentChainParametersCalled: func() config.ChainParametersByEpochConfig { + return config.ChainParametersByEpochConfig{ + RoundsPerEpoch: 2400, + RoundDuration: 6000, + } + }, + }, + StartTime: 1000 * int64(millisecondsInOneSecond), + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag + }, + }, + } + aep := NewTestArithmeticEpochProvider(arg, getUnixHandler(15500*int64(millisecondsInOneSecond))) + assert.Equal(t, uint32(1), aep.CurrentComputedEpoch()) + + aep.SetUnixHandler(getUnixHandler(17500 * int64(millisecondsInOneSecond))) + + aep.EpochConfirmed(1, 3000*millisecondsInOneSecond) + + assert.Equal(t, uint32(2), aep.CurrentComputedEpoch()) + }) + + t.Run("in supernova epoch", func(t *testing.T) { + t.Parallel() + + supernovaActivationEpoch := uint32(2) + + arg := ArgArithmeticEpochProvider{ + ChainParametersHandler: &chainParameters.ChainParametersHandlerStub{ + CurrentChainParametersCalled: func() config.ChainParametersByEpochConfig { + return config.ChainParametersByEpochConfig{ + RoundsPerEpoch: 10, + RoundDuration: 6000, + } + }, + }, + StartTime: 0, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag && epoch >= supernovaActivationEpoch + }, + }, + } + aep := NewTestArithmeticEpochProvider(arg, getUnixHandler(18)) + assert.Equal(t, uint32(0), aep.CurrentComputedEpoch()) + + // current provided timestamp lower than new epoch timestamp, will return provided epoch + aep.SetUnixHandler(getUnixHandler(6000)) + aep.EpochConfirmed(1, 6600*millisecondsInOneSecond) + assert.Equal(t, uint32(1), aep.CurrentComputedEpoch()) + + aep.SetUnixHandler(getUnixHandler(40)) + aep.EpochConfirmed(0, 32) + assert.Equal(t, uint32(0), aep.CurrentComputedEpoch()) + + aep.SetUnixHandler(getUnixHandler(60)) + aep.EpochConfirmed(1, 60) + assert.Equal(t, uint32(1), aep.CurrentComputedEpoch()) + + aep.SetUnixHandler(getUnixHandler(60 + 1)) + aep.EpochConfirmed(1, 60) + assert.Equal(t, uint32(1), aep.CurrentComputedEpoch()) + + aep.SetUnixHandler(getUnixHandler(60 + 66)) + aep.EpochConfirmed(1, 60) + assert.Equal(t, uint32(2), aep.CurrentComputedEpoch()) + + aep.SetUnixHandler(getUnixHandler(60 + 66*2)) + aep.EpochConfirmed(1, 60) + assert.Equal(t, uint32(3), aep.CurrentComputedEpoch()) + + aep.SetUnixHandler(getUnixHandler(60 + 66*3)) + aep.EpochConfirmed(1, 60) + assert.Equal(t, uint32(4), aep.CurrentComputedEpoch()) + + aep.SetChainParametersHandler(&chainParameters.ChainParametersHandlerStub{ + CurrentChainParametersCalled: func() config.ChainParametersByEpochConfig { + return config.ChainParametersByEpochConfig{ + RoundsPerEpoch: 100, + RoundDuration: 600, + } + }, + }) + + aep.SetUnixHandler(getUnixHandler(120*int64(millisecondsInOneSecond) + 60*int64(millisecondsInOneSecond))) + aep.EpochConfirmed(supernovaActivationEpoch, 120*uint64(millisecondsInOneSecond)) + assert.Equal(t, uint32(2), aep.CurrentComputedEpoch()) + + aep.SetUnixHandler(getUnixHandler(120*int64(millisecondsInOneSecond) + 61*int64(millisecondsInOneSecond))) + aep.EpochConfirmed(supernovaActivationEpoch, 120*uint64(millisecondsInOneSecond)) + assert.Equal(t, uint32(3), aep.CurrentComputedEpoch()) + + aep.SetUnixHandler(getUnixHandler(120*int64(millisecondsInOneSecond) + 122*int64(millisecondsInOneSecond))) + aep.EpochConfirmed(supernovaActivationEpoch, 120*uint64(millisecondsInOneSecond)) + assert.Equal(t, uint32(4), aep.CurrentComputedEpoch()) + }) + +} + +func TestArithmeticEpochProvider_ComputeCurrentEpoch_WithRealConfigs(t *testing.T) { + t.Parallel() + + supernovaActivationEpoch := uint32(2) + + roundsPerEpoch := int64(14400) + roundDurationMs := int64(6000) + roundDurationS := roundDurationMs / 1000 + + firstEpochActivationTime := roundsPerEpoch * int64(roundDurationS) + supernovaActivationTime := roundsPerEpoch * 2 * int64(roundDurationS) + arg := ArgArithmeticEpochProvider{ - RoundsPerEpoch: 2400, - RoundTimeInMilliseconds: 6000, - StartTime: 1000, + ChainParametersHandler: &chainParameters.ChainParametersHandlerStub{ + CurrentChainParametersCalled: func() config.ChainParametersByEpochConfig { + return config.ChainParametersByEpochConfig{ + RoundsPerEpoch: roundsPerEpoch, + RoundDuration: uint64(roundDurationMs), + } + }, + }, + StartTime: 0, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag && epoch >= supernovaActivationEpoch + }, + }, } - aep := NewTestArithmeticEpochProvider(arg, getUnixHandler(15500)) + aep := NewTestArithmeticEpochProvider(arg, getUnixHandler(1000)) + assert.Equal(t, uint32(0), aep.CurrentComputedEpoch()) + + aep.SetUnixHandler(getUnixHandler(firstEpochActivationTime)) + aep.EpochConfirmed(1, uint64(firstEpochActivationTime)) assert.Equal(t, uint32(1), aep.CurrentComputedEpoch()) - aep.SetUnixHandler(getUnixHandler(17500)) + aep.SetUnixHandler(getUnixHandler(firstEpochActivationTime*2 + 6)) + aep.EpochConfirmed(1, uint64(firstEpochActivationTime)) + assert.Equal(t, uint32(2), aep.CurrentComputedEpoch()) + + roundsPerEpochSupernova := uint64(roundsPerEpoch * 10) + roundDurationSupernova := uint64(600) - aep.EpochConfirmed(1, 3000) + aep.SetChainParametersHandler(&chainParameters.ChainParametersHandlerStub{ + CurrentChainParametersCalled: func() config.ChainParametersByEpochConfig { + return config.ChainParametersByEpochConfig{ + RoundsPerEpoch: int64(roundsPerEpochSupernova), + RoundDuration: roundDurationSupernova, + } + }, + }) + aep.SetUnixHandler(getUnixHandler(firstEpochActivationTime*2 + 6)) + aep.EpochConfirmed(supernovaActivationEpoch, uint64(supernovaActivationTime)) assert.Equal(t, uint32(2), aep.CurrentComputedEpoch()) + + supernovaActivationTimeMs := uint64(supernovaActivationTime) * millisecondsInOneSecond + + aep.SetUnixHandler(getUnixHandler(int64(supernovaActivationTimeMs + roundDurationSupernova*20))) + aep.EpochConfirmed(supernovaActivationEpoch, uint64(supernovaActivationTime)*millisecondsInOneSecond) + assert.Equal(t, uint32(2), aep.CurrentComputedEpoch()) + + aep.SetUnixHandler(getUnixHandler(int64(supernovaActivationTimeMs + roundDurationSupernova*roundsPerEpochSupernova + 600 - 1))) + aep.EpochConfirmed(supernovaActivationEpoch, uint64(supernovaActivationTime)*millisecondsInOneSecond) + assert.Equal(t, uint32(2), aep.CurrentComputedEpoch()) + + aep.SetUnixHandler(getUnixHandler(int64(supernovaActivationTimeMs + roundDurationSupernova*roundsPerEpochSupernova + 600))) + aep.EpochConfirmed(supernovaActivationEpoch, uint64(supernovaActivationTime)*millisecondsInOneSecond) + assert.Equal(t, uint32(3), aep.CurrentComputedEpoch()) } func TestArithmeticEpochProvider_EpochIsActiveInNetwork(t *testing.T) { t.Parallel() arg := ArgArithmeticEpochProvider{ - RoundsPerEpoch: 1, - RoundTimeInMilliseconds: 1, - StartTime: 1, + ChainParametersHandler: getMockChainParametersHandler(), + StartTime: 1, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, } aep := NewTestArithmeticEpochProvider(arg, getUnixHandler(1)) diff --git a/dataRetriever/resolvers/epochproviders/export_test.go b/dataRetriever/resolvers/epochproviders/export_test.go index 9773f3705f9..f8e5ea5a529 100644 --- a/dataRetriever/resolvers/epochproviders/export_test.go +++ b/dataRetriever/resolvers/epochproviders/export_test.go @@ -1,14 +1,15 @@ package epochproviders +import "github.com/multiversx/mx-chain-go/process" + // NewTestArithmeticEpochProvider - func NewTestArithmeticEpochProvider(arg ArgArithmeticEpochProvider, unixHandler func() int64) *arithmeticEpochProvider { aep := &arithmeticEpochProvider{ headerEpoch: 0, headerTimestampForNewEpoch: uint64(arg.StartTime), - roundsPerEpoch: arg.RoundsPerEpoch, - roundTimeInMilliseconds: arg.RoundTimeInMilliseconds, - startTime: arg.StartTime, + chainParamsHandler: arg.ChainParametersHandler, getUnixHandler: unixHandler, + enableEpochsHandler: arg.EnableEpochsHandler, } aep.computeCurrentEpoch() @@ -38,3 +39,8 @@ func (aep *arithmeticEpochProvider) SetCurrentComputedEpoch(epoch uint32) { aep.currentComputedEpoch = epoch } + +// SetChainParametersHandler - +func (aep *arithmeticEpochProvider) SetChainParametersHandler(chainParamsHandler process.ChainParametersHandler) { + aep.chainParamsHandler = chainParamsHandler +} diff --git a/dataRetriever/shardedData/shardedData.go b/dataRetriever/shardedData/shardedData.go index 0724473d07b..b0345a3badd 100644 --- a/dataRetriever/shardedData/shardedData.go +++ b/dataRetriever/shardedData/shardedData.go @@ -3,15 +3,20 @@ package shardedData import ( "fmt" "sync" + "time" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/counting" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/marshal" + logger "github.com/multiversx/mx-chain-logger-go" + + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/storage" "github.com/multiversx/mx-chain-go/storage/cache" "github.com/multiversx/mx-chain-go/storage/storageunit" - logger "github.com/multiversx/mx-chain-logger-go" ) var log = logger.GetOrCreate("dataretriever/shardeddata") @@ -310,6 +315,55 @@ func (sd *shardedData) Diagnose(deep bool) { } } +// CleanupSelfShardTxCache does nothing (only to satisfy the interface dataRetriever.ShardedDataCacherNotifier) +func (sd *shardedData) CleanupSelfShardTxCache(_ common.AccountNonceProvider, _ uint64, _ int, _ time.Duration) { + log.Warn("shardedData.CleanupSelfShardTxCache() should not have been called") +} + +// GetNumTrackedBlocks returns 0 (only to satisfy the interface dataRetriever.ShardedDataCacherNotifier) +func (sd *shardedData) GetNumTrackedBlocks() uint64 { + log.Warn("shardedData.GetNumTrackedBlocks() should not have been called") + return 0 +} + +// GetNumTrackedAccounts returns 0 (only to satisfy the interface dataRetriever.ShardedDataCacherNotifier) +func (sd *shardedData) GetNumTrackedAccounts() uint64 { + log.Warn("shardedData.GetNumTrackedAccounts() should not have been called") + return 0 +} + +// OnExecutedBlock does nothing (only to satisfy the interface dataRetriever.ShardedDataCacherNotifier) +func (sd *shardedData) OnExecutedBlock(_ data.HeaderHandler, _ []byte) error { + log.Warn("shardedData.OnExecutedBlock() should not have been called") + return nil +} + +// OnProposedBlock does nothing (only to satisfy the interface dataRetriever.ShardedDataCacherNotifier) +func (sd *shardedData) OnProposedBlock( + _ []byte, + _ *block.Body, + _ data.HeaderHandler, + _ common.AccountNonceAndBalanceProvider, + _ []byte, +) error { + log.Warn("shardedData.OnProposedBlockCalled() should not have been called") + return nil +} + +// OnBackfilledBlock does nothing (only to satisfy the interface dataRetriever.ShardedDataCacherNotifier) +func (sd *shardedData) OnBackfilledBlock( + _ []byte, + _ *block.Body, + _ data.HeaderHandler, +) error { + log.Warn("shardedData.OnBackfilledBlock() should not have been called") + return nil +} + +// ResetTracker does nothing (only to satisfy the interface dataRetriever.ShardedDataCacherNotifier) +func (sd *shardedData) ResetTracker() { +} + // IsInterfaceNil returns true if there is no value under the interface func (sd *shardedData) IsInterfaceNil() bool { return sd == nil diff --git a/dataRetriever/shardedData/shardedData_test.go b/dataRetriever/shardedData/shardedData_test.go index d9ab827df10..1b8332d8aa2 100644 --- a/dataRetriever/shardedData/shardedData_test.go +++ b/dataRetriever/shardedData/shardedData_test.go @@ -12,6 +12,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-go/storage/storageunit" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var timeoutWaitForWaitGroups = time.Second * 2 @@ -112,7 +113,7 @@ func TestShardedData_AddDataInParallel(t *testing.T) { wg.Wait() - //checking + // checking for i := 0; i < vals; i++ { key := []byte(strconv.Itoa(i)) assert.True(t, sd.shardStore("1").cache.Has(key), fmt.Sprintf("for val %d", i)) @@ -271,10 +272,10 @@ func TestShardedData_RegisterAddedDataHandlerNotAddedShouldNotCall(t *testing.T) sd, _ := NewShardedData("", defaultTestConfig) - //first add, no call + // first add, no call sd.AddData([]byte("aaaa"), "bbbb", 4, "0") sd.RegisterOnAdded(f) - //second add, should not call as the data was found + // second add, should not call as the data was found sd.AddData([]byte("aaaa"), "bbbb", 4, "0") select { @@ -357,3 +358,22 @@ func TestShardedData_Diagnose(t *testing.T) { sd.AddData([]byte("bbb"), "b1", 2, "0") sd.Diagnose(true) } + +func TestShardedData_NotImplemented(t *testing.T) { + t.Parallel() + + sd, err := NewShardedData("", defaultTestConfig) + require.Nil(t, err) + + require.NotPanics(t, func() { + sd.CleanupSelfShardTxCache(nil, 0, 0, 0) + }) + + err = sd.OnExecutedBlock(nil, nil) + require.Nil(t, err) + + err = sd.OnProposedBlock(nil, nil, nil, nil, nil) + require.Nil(t, err) + + sd.ResetTracker() +} diff --git a/dataRetriever/storageRequesters/equivalentProofsRequester.go b/dataRetriever/storageRequesters/equivalentProofsRequester.go index 4454aa890cf..248ab34b014 100644 --- a/dataRetriever/storageRequesters/equivalentProofsRequester.go +++ b/dataRetriever/storageRequesters/equivalentProofsRequester.go @@ -1,15 +1,17 @@ package storagerequesters import ( + "time" + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data/endProcess" "github.com/multiversx/mx-chain-core-go/data/typeConverters" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/storage" - "time" ) // ArgEquivalentProofsRequester is the argument structure used to create a new equivalent proofs requester instance @@ -83,10 +85,12 @@ func checkArgs(args ArgEquivalentProofsRequester) error { // RequestDataFromHash requests equivalent proofs data from storage for the specified hash-shard key func (requester *equivalentProofsRequester) RequestDataFromHash(hashShardKey []byte, epoch uint32) error { - if !requester.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, epoch) { + if !requester.isAndromedaFlagEnabledForEpochOrNext(epoch) { return nil } + requester.manualEpochStartNotifier.NewEpoch(epoch + 1) + headerHash, _, err := common.GetHashAndShardFromKey(hashShardKey) if err != nil { return err @@ -107,10 +111,12 @@ func (requester *equivalentProofsRequester) RequestDataFromHash(hashShardKey []b // RequestDataFromNonce requests equivalent proofs data from storage for the specified nonce-shard key func (requester *equivalentProofsRequester) RequestDataFromNonce(nonceShardKey []byte, epoch uint32) error { - if !requester.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, epoch) { + if !requester.isAndromedaFlagEnabledForEpochOrNext(epoch) { return nil } + requester.manualEpochStartNotifier.NewEpoch(epoch + 1) + headerNonce, shardID, err := common.GetNonceAndShardFromKey(nonceShardKey) if err != nil { return err @@ -130,6 +136,12 @@ func (requester *equivalentProofsRequester) RequestDataFromNonce(nonceShardKey [ return requester.RequestDataFromHash([]byte(hashShardKey), epoch) } +// isAndromedaFlagEnabledForEpochOrNext also checks epoch+1 to handle epoch boundary transitions +func (requester *equivalentProofsRequester) isAndromedaFlagEnabledForEpochOrNext(epoch uint32) bool { + return requester.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, epoch) || + requester.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, epoch+1) +} + func (requester *equivalentProofsRequester) getStorerForShard(shardID uint32) (storage.Storer, error) { if shardID == core.MetachainShardId { return requester.storage.GetStorer(dataRetriever.MetaHdrNonceHashDataUnit) diff --git a/dataRetriever/storageRequesters/equivalentProofsRequester_test.go b/dataRetriever/storageRequesters/equivalentProofsRequester_test.go index d8ed8b1f08c..a40f847ffe1 100644 --- a/dataRetriever/storageRequesters/equivalentProofsRequester_test.go +++ b/dataRetriever/storageRequesters/equivalentProofsRequester_test.go @@ -5,6 +5,8 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data/endProcess" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/dataRetriever/mock" @@ -13,7 +15,6 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/genericMocks" "github.com/multiversx/mx-chain-go/testscommon/p2pmocks" "github.com/multiversx/mx-chain-go/testscommon/storage" - "github.com/stretchr/testify/require" ) func createMockArgEquivalentProofsRequester() ArgEquivalentProofsRequester { @@ -126,6 +127,63 @@ func TestEquivalentProofsRequester_IsInterfaceNil(t *testing.T) { func TestEquivalentProofsRequester_RequestDataFromHash(t *testing.T) { t.Parallel() + t.Run("flag disabled for both epoch and epoch+1 should skip", func(t *testing.T) { + t.Parallel() + + args := createMockArgEquivalentProofsRequester() + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return false + }, + } + req, err := NewEquivalentProofsRequester(args) + require.NoError(t, err) + + err = req.RequestDataFromHash([]byte(common.GetEquivalentProofHashShardKey([]byte("hash"), 1)), 0) + require.NoError(t, err) + }) + t.Run("flag disabled for epoch but enabled for epoch+1 should proceed", func(t *testing.T) { + t.Parallel() + + providedBuff := []byte("provided buff") + newEpochCalled := false + args := createMockArgEquivalentProofsRequester() + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return epoch == 1 + }, + } + args.ManualEpochStartNotifier = &mock.ManualEpochStartNotifierStub{ + NewEpochCalled: func(epoch uint32) { + if epoch == 1 { + newEpochCalled = true + } + }, + } + args.Storage = &storage.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (chainStorage.Storer, error) { + return &storage.StorerStub{ + SearchFirstCalled: func(key []byte) ([]byte, error) { + return providedBuff, nil + }, + }, nil + }, + } + wasSendToConnectedPeerCalled := false + args.Messenger = &p2pmocks.MessengerStub{ + SendToConnectedPeerCalled: func(topic string, buff []byte, peerID core.PeerID) error { + wasSendToConnectedPeerCalled = true + return nil + }, + } + req, err := NewEquivalentProofsRequester(args) + require.NoError(t, err) + + err = req.RequestDataFromHash([]byte(common.GetEquivalentProofHashShardKey([]byte("hash"), 1)), 0) + require.NoError(t, err) + require.True(t, newEpochCalled) + require.True(t, wasSendToConnectedPeerCalled) + }) t.Run("invalid key should error", func(t *testing.T) { t.Parallel() @@ -204,6 +262,63 @@ func TestEquivalentProofsRequester_RequestDataFromHash(t *testing.T) { func TestEquivalentProofsRequester_RequestDataFromNonce(t *testing.T) { t.Parallel() + t.Run("flag disabled for both epoch and epoch+1 should skip", func(t *testing.T) { + t.Parallel() + + args := createMockArgEquivalentProofsRequester() + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return false + }, + } + req, err := NewEquivalentProofsRequester(args) + require.NoError(t, err) + + err = req.RequestDataFromNonce([]byte(common.GetEquivalentProofNonceShardKey(123, 1)), 0) + require.NoError(t, err) + }) + t.Run("flag disabled for epoch but enabled for epoch+1 should proceed", func(t *testing.T) { + t.Parallel() + + providedBuff := []byte("provided buff") + newEpochCalled := false + args := createMockArgEquivalentProofsRequester() + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return epoch == 1 + }, + } + args.ManualEpochStartNotifier = &mock.ManualEpochStartNotifierStub{ + NewEpochCalled: func(epoch uint32) { + if epoch == 1 { + newEpochCalled = true + } + }, + } + args.Storage = &storage.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (chainStorage.Storer, error) { + return &storage.StorerStub{ + SearchFirstCalled: func(key []byte) ([]byte, error) { + return providedBuff, nil + }, + }, nil + }, + } + wasSendToConnectedPeerCalled := false + args.Messenger = &p2pmocks.MessengerStub{ + SendToConnectedPeerCalled: func(topic string, buff []byte, peerID core.PeerID) error { + wasSendToConnectedPeerCalled = true + return nil + }, + } + req, err := NewEquivalentProofsRequester(args) + require.NoError(t, err) + + err = req.RequestDataFromNonce([]byte(common.GetEquivalentProofNonceShardKey(123, 1)), 0) + require.NoError(t, err) + require.True(t, newEpochCalled) + require.True(t, wasSendToConnectedPeerCalled) + }) t.Run("invalid key should error", func(t *testing.T) { t.Parallel() diff --git a/dataRetriever/storageRequesters/headerRequester.go b/dataRetriever/storageRequesters/headerRequester.go index 0545ff5bccf..f225b6281ff 100644 --- a/dataRetriever/storageRequesters/headerRequester.go +++ b/dataRetriever/storageRequesters/headerRequester.go @@ -96,6 +96,10 @@ func (hdrReq *headerRequester) RequestDataFromHash(hash []byte, _ uint32) error // RequestDataFromNonce requests a header by its nonce func (hdrReq *headerRequester) RequestDataFromNonce(nonce uint64, epoch uint32) error { + if nonce == 0 { + return nil + } + nonceKey := hdrReq.nonceConverter.ToByteSlice(nonce) hash, err := hdrReq.hdrNoncesStorage.SearchFirst(nonceKey) if err != nil { diff --git a/dataRetriever/storageRequesters/headerRequester_test.go b/dataRetriever/storageRequesters/headerRequester_test.go index 73e54a96e4c..e5f4deaa27f 100644 --- a/dataRetriever/storageRequesters/headerRequester_test.go +++ b/dataRetriever/storageRequesters/headerRequester_test.go @@ -242,6 +242,31 @@ func TestHeaderRequester_RequestDataFromHashShouldWork(t *testing.T) { assert.True(t, sendCalled) } +func TestHeaderRequester_RequestDataFromNonce_ZeroNonceShouldDoNothing(t *testing.T) { + t.Parallel() + + searchCaled := false + arg := createMockHeaderRequesterArg() + arg.HdrStorage = &storageStubs.StorerStub{ + SearchFirstCalled: func(key []byte) ([]byte, error) { + return make([]byte, 0), nil + }, + } + arg.HeadersNoncesStorage = &storageStubs.StorerStub{ + SearchFirstCalled: func(key []byte) ([]byte, error) { + searchCaled = true + return nil, nil + }, + } + + hdReq, _ := NewHeaderRequester(arg) + + err := hdReq.RequestDataFromNonce(0, 0) + assert.Nil(t, err) + assert.False(t, searchCaled) + +} + func TestHeaderRequester_RequestDataFromNonceNotFoundShouldErr(t *testing.T) { t.Parallel() diff --git a/dataRetriever/topicSender/topicRequestSender.go b/dataRetriever/topicSender/topicRequestSender.go index a2fed8e2568..72d97c563e5 100644 --- a/dataRetriever/topicSender/topicRequestSender.go +++ b/dataRetriever/topicSender/topicRequestSender.go @@ -223,6 +223,10 @@ func (trs *topicRequestSender) sendOnTopic( for idx := 0; idx < len(shuffledIndexes); idx++ { peer := getPeerID(shuffledIndexes[idx], topRatedPeersList, preferredPeer, peerType, topicToSendRequest, histogramMap) + // no matter the outcome of sendToConnectedPeer, decrease the peer's rating + // this way we avoid(decreasing) peers with invalid connections or blacklisted by antiflooder + trs.peersRatingHandler.DecreaseRating(peer) + err := trs.sendToConnectedPeer(topicToSendRequest, buff, peer, messenger) if err != nil { log.Trace("sendToConnectedPeer failed", @@ -232,7 +236,6 @@ func (trs *topicRequestSender) sendOnTopic( "error", err.Error()) continue } - trs.peersRatingHandler.DecreaseRating(peer) logData = append(logData, peerType) logData = append(logData, peer.Pretty()) diff --git a/dataRetriever/txpool/argShardedTxPool.go b/dataRetriever/txpool/argShardedTxPool.go index dca1efa56bd..d82a22caa82 100644 --- a/dataRetriever/txpool/argShardedTxPool.go +++ b/dataRetriever/txpool/argShardedTxPool.go @@ -5,17 +5,19 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/storage/storageunit" ) // ArgShardedTxPool is the argument for ShardedTxPool's constructor type ArgShardedTxPool struct { - Config storageunit.CacheConfig - TxGasHandler txGasHandler - Marshalizer marshal.Marshalizer - NumberOfShards uint32 - SelfShardID uint32 + Config storageunit.CacheConfig + TxGasHandler txGasHandler + Marshalizer marshal.Marshalizer + NumberOfShards uint32 + SelfShardID uint32 + TxCacheBoundsConfig config.TxCacheBoundsConfig } // TODO: Upon further analysis and brainstorming, add some sensible minimum accepted values for the appropriate fields. @@ -47,5 +49,15 @@ func (args *ArgShardedTxPool) verify() error { return fmt.Errorf("%w: NumberOfShards is not valid", dataRetriever.ErrCacheConfigInvalidSharding) } + // TODO brainstorm if these checks could be moved in the underlying component + if args.TxCacheBoundsConfig.MaxNumBytesPerSenderUpperBound == 0 { + return fmt.Errorf("%w: MaxNumBytesPerSenderUpperBound is not valid", dataRetriever.ErrBadMaxNumBytesPerSenderUpperBound) + } + + // TODO compare with the maximum allowed offset between proposing a block and actually executing it + if args.TxCacheBoundsConfig.MaxTrackedBlocks == 0 { + return fmt.Errorf("%w: MaxTrackedBlocks is not valid", dataRetriever.ErrBadMaxTrackedBlocks) + } + return nil } diff --git a/dataRetriever/txpool/interface.go b/dataRetriever/txpool/interface.go index ee55a246a48..3b214a3dc08 100644 --- a/dataRetriever/txpool/interface.go +++ b/dataRetriever/txpool/interface.go @@ -2,10 +2,13 @@ package txpool import ( "math/big" + "time" "github.com/multiversx/mx-chain-core-go/data" + + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/storage" - "github.com/multiversx/mx-chain-go/storage/txcache" + "github.com/multiversx/mx-chain-go/txcache" ) type txCache interface { @@ -18,7 +21,13 @@ type txCache interface { ForEachTransaction(function txcache.ForEachTransaction) NumBytes() int Diagnose(deep bool) + GetTrackerDiagnosis() txcache.TrackerDiagnosis GetTransactionsPoolForSender(sender string) []*txcache.WrappedTransaction + OnProposedBlock(blockHash []byte, blockBody data.BodyHandler, blockHeader data.HeaderHandler, accountsProvider common.AccountNonceAndBalanceProvider, latestExecutedHash []byte) error + OnBackfilledBlock(blockHash []byte, blockBody data.BodyHandler, blockHeader data.HeaderHandler) error + OnExecutedBlock(blockHeader data.HeaderHandler, rootHash []byte) error + ResetTracker() + Cleanup(accountsProvider common.AccountNonceProvider, randomness uint64, maxNum int, cleanupLoopMaximumDurationMs time.Duration) uint64 } type txGasHandler interface { diff --git a/dataRetriever/txpool/memorytests/memory_test.go b/dataRetriever/txpool/memorytests/memory_test.go index ac2b58d16db..727cdbdca72 100644 --- a/dataRetriever/txpool/memorytests/memory_test.go +++ b/dataRetriever/txpool/memorytests/memory_test.go @@ -13,6 +13,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/dataRetriever/txpool" "github.com/multiversx/mx-chain-go/storage/storageunit" @@ -23,6 +24,7 @@ import ( // useMemPprof dictates whether to save heap profiles when running the test. // Enable this manually, locally. const useMemPprof = false +const maxNumBytesPerSenderUpperBoundTest = 33_554_432 // 32 MB // We run all scenarios within a single test so that we minimize memory interferences (of tests running in parallel) func TestShardedTxPool_MemoryFootprint(t *testing.T) { @@ -102,7 +104,7 @@ type memoryAssertion struct { } func newPool() dataRetriever.ShardedDataCacherNotifier { - config := storageunit.CacheConfig{ + cacheConfig := storageunit.CacheConfig{ Capacity: 600000, SizePerSender: 60000, SizeInBytes: 400 * core.MegabyteSize, @@ -111,11 +113,15 @@ func newPool() dataRetriever.ShardedDataCacherNotifier { } args := txpool.ArgShardedTxPool{ - Config: config, + Config: cacheConfig, TxGasHandler: txcachemocks.NewTxGasHandlerMock(), Marshalizer: &marshal.GogoProtoMarshalizer{}, NumberOfShards: 2, SelfShardID: 0, + TxCacheBoundsConfig: config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: maxNumBytesPerSenderUpperBoundTest, + MaxTrackedBlocks: 100, + }, } pool, err := txpool.NewShardedTxPool(args) if err != nil { diff --git a/dataRetriever/txpool/shardedTxPool.go b/dataRetriever/txpool/shardedTxPool.go index 0f40817893d..f81f4f39fa8 100644 --- a/dataRetriever/txpool/shardedTxPool.go +++ b/dataRetriever/txpool/shardedTxPool.go @@ -3,15 +3,19 @@ package txpool import ( "strconv" "sync" + "time" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/counting" "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + logger "github.com/multiversx/mx-chain-logger-go" + + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/storage" - "github.com/multiversx/mx-chain-go/storage/txcache" - logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/txcache" ) var _ dataRetriever.ShardedDataCacherNotifier = (*shardedTxPool)(nil) @@ -64,6 +68,7 @@ func NewShardedTxPool(args ArgShardedTxPool) (*shardedTxPool, error) { NumBytesPerSenderThreshold: args.Config.SizeInBytesPerSender, CountPerSenderThreshold: args.Config.SizePerSender, NumItemsToPreemptivelyEvict: storage.TxPoolSourceMeNumItemsToPreemptivelyEvict, + TxCacheBoundsConfig: args.TxCacheBoundsConfig, } // We do not reserve cross tx cache capacity for [metachain] -> [me] (no transactions), [me] -> me (already reserved above). @@ -142,7 +147,7 @@ func (txPool *shardedTxPool) createTxCache(cacheID string) txCache { if isForSenderMe { config := txPool.configPrototypeSourceMe config.Name = cacheID - cache, err := txcache.NewTxCache(config, txPool.host) + cache, err := txcache.NewTxCache(config, txPool.host, txPool.selfShardID) if err != nil { log.Error("shardedTxPool.createTxCache()", "err", err) return txcache.NewDisabledCache() @@ -387,3 +392,64 @@ func (txPool *shardedTxPool) routeToCacheUnions(cacheID string) string { return cacheID } + +func (txPool *shardedTxPool) getSelfShardTxCache() txCache { + return txPool.getTxCache(strconv.Itoa(int(txPool.selfShardID))) +} + +// CleanupSelfShardTxCache performs an automatic cleanup of the transaction cache for the node's own shard. +// It removes non-executable transactions based on provided time and number constraints. +func (txPool *shardedTxPool) CleanupSelfShardTxCache(accountsProvider common.AccountNonceProvider, randomness uint64, maxNum int, maxTime time.Duration) { + cache := txPool.getSelfShardTxCache() + + log.Debug("shardedTxPool.CleanupSelfShardTxCache(): starting cleanup", + "selfShardID", txPool.selfShardID, + "len", cache.Len(), + "numBytes", cache.NumBytes(), + ) + + // Perform the cleanup operation on the mempool + cache.Cleanup(accountsProvider, randomness, maxNum, maxTime) + + log.Debug("shardedTxPool.CleanupSelfShardTxCache(): self shard cache cleanup completed", + "selfShardID", txPool.selfShardID, + "len", cache.Len(), + "numBytes", cache.NumBytes(), + ) +} + +// GetNumTrackedBlocks returns the number of blocks being tracked by the underlying TxCache +func (txPool *shardedTxPool) GetNumTrackedBlocks() uint64 { + cache := txPool.getSelfShardTxCache() + return cache.GetTrackerDiagnosis().GetNumTrackedBlocks() +} + +// GetNumTrackedAccounts returns the number of accounts being tracked by the underlying TxCache +func (txPool *shardedTxPool) GetNumTrackedAccounts() uint64 { + cache := txPool.getSelfShardTxCache() + return cache.GetTrackerDiagnosis().GetNumTrackedAccounts() +} + +// OnProposedBlock notifies the underlying TxCache +func (txPool *shardedTxPool) OnProposedBlock(blockHash []byte, blockBody *block.Body, blockHeader data.HeaderHandler, accountsProvider common.AccountNonceAndBalanceProvider, latestExecutedHash []byte) error { + cache := txPool.getSelfShardTxCache() + return cache.OnProposedBlock(blockHash, blockBody, blockHeader, accountsProvider, latestExecutedHash) +} + +// OnBackfilledBlock notifies the underlying TxCache +func (txPool *shardedTxPool) OnBackfilledBlock(blockHash []byte, blockBody *block.Body, blockHeader data.HeaderHandler) error { + cache := txPool.getSelfShardTxCache() + return cache.OnBackfilledBlock(blockHash, blockBody, blockHeader) +} + +// OnExecutedBlock notifies the underlying TxCache +func (txPool *shardedTxPool) OnExecutedBlock(blockHeader data.HeaderHandler, rootHash []byte) error { + cache := txPool.getSelfShardTxCache() + return cache.OnExecutedBlock(blockHeader, rootHash) +} + +// ResetTracker resets the underlying TxCache +func (txPool *shardedTxPool) ResetTracker() { + cache := txPool.getSelfShardTxCache() + cache.ResetTracker() +} diff --git a/dataRetriever/txpool/shardedTxPool_test.go b/dataRetriever/txpool/shardedTxPool_test.go index 1b3ab585dc3..c2e26288ec6 100644 --- a/dataRetriever/txpool/shardedTxPool_test.go +++ b/dataRetriever/txpool/shardedTxPool_test.go @@ -2,6 +2,7 @@ package txpool import ( "fmt" + "math" "sync" "sync/atomic" "testing" @@ -9,14 +10,20 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/storage/storageunit" "github.com/multiversx/mx-chain-go/testscommon/txcachemocks" + "github.com/multiversx/mx-chain-go/txcache" "github.com/stretchr/testify/require" ) +const maxNumBytesPerSenderUpperBoundTest = 33_554_432 // 32 MB +const maxTrackedBlocks = 100 + func Test_NewShardedTxPool(t *testing.T) { pool, err := newTxPoolToTest() @@ -37,6 +44,10 @@ func Test_NewShardedTxPool_WhenBadConfig(t *testing.T) { TxGasHandler: txcachemocks.NewTxGasHandlerMock(), Marshalizer: &marshal.GogoProtoMarshalizer{}, NumberOfShards: 1, + TxCacheBoundsConfig: config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: maxNumBytesPerSenderUpperBoundTest, + MaxTrackedBlocks: maxTrackedBlocks, + }, } args := goodArgs @@ -94,15 +105,33 @@ func Test_NewShardedTxPool_WhenBadConfig(t *testing.T) { require.Nil(t, pool) require.NotNil(t, err) require.Errorf(t, err, dataRetriever.ErrCacheConfigInvalidSharding.Error()) + + args = goodArgs + args.TxCacheBoundsConfig.MaxNumBytesPerSenderUpperBound = 0 + pool, err = NewShardedTxPool(args) + require.Nil(t, pool) + require.NotNil(t, err) + require.Errorf(t, err, dataRetriever.ErrBadMaxNumBytesPerSenderUpperBound.Error()) + + args = goodArgs + args.TxCacheBoundsConfig.MaxTrackedBlocks = 0 + pool, err = NewShardedTxPool(args) + require.Nil(t, pool) + require.NotNil(t, err) + require.Errorf(t, err, dataRetriever.ErrBadMaxTrackedBlocks.Error()) } func Test_NewShardedTxPool_ComputesCacheConfig(t *testing.T) { - config := storageunit.CacheConfig{SizeInBytes: 419430400, SizeInBytesPerSender: 614400, Capacity: 600000, SizePerSender: 1000, Shards: 1} + cacheConfig := storageunit.CacheConfig{SizeInBytes: 419430400, SizeInBytesPerSender: 614400, Capacity: 600000, SizePerSender: 1000, Shards: 1} args := ArgShardedTxPool{ - Config: config, + Config: cacheConfig, TxGasHandler: txcachemocks.NewTxGasHandlerMock(), Marshalizer: &marshal.GogoProtoMarshalizer{}, NumberOfShards: 2, + TxCacheBoundsConfig: config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: maxNumBytesPerSenderUpperBoundTest, + MaxTrackedBlocks: maxTrackedBlocks, + }, } pool, err := NewShardedTxPool(args) @@ -256,6 +285,89 @@ func Test_RemoveSetOfDataFromPool(t *testing.T) { require.Zero(t, cache.Len()) } +func TestCleanupSelfShardTxCache_NilMempool(t *testing.T) { + t.Parallel() + t.Run("with nil self shard pool", func(t *testing.T) { + poolAsInterface, _ := newTxPoolToTest() + txPool := poolAsInterface.(*shardedTxPool) + delete(txPool.backingMap, "0") + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMock() + cleanupLoopMaximumDuration := time.Millisecond * 100 + + require.NotPanics(t, func() { + txPool.CleanupSelfShardTxCache(accountsProvider, 7, math.MaxInt, cleanupLoopMaximumDuration) + }) + }) +} + +func Test_Parallel_CleanupSelfShardTxCache(t *testing.T) { + t.Parallel() + t.Run("with lower nonces", func(t *testing.T) { + t.Parallel() + poolAsInterface, _ := newTxPoolToTest() + pool := poolAsInterface.(*shardedTxPool) + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMock() + accountsProvider.SetNonce([]byte("alice"), 2) + accountsProvider.SetNonce([]byte("bob"), 42) + accountsProvider.SetNonce([]byte("carol"), 7) + + // One lower nonce + pool.AddData([]byte("hash-alice-1"), createTx("alice", 1), 0, "0") + pool.AddData([]byte("hash-alice-2"), createTx("alice", 2), 0, "0") + pool.AddData([]byte("hash-alice-3"), createTx("alice", 3), 0, "0") + + // A few with lower nonce + pool.AddData([]byte("hash-bob-40"), createTx("bob", 40), 0, "0") + pool.AddData([]byte("hash-bob-41"), createTx("bob", 41), 0, "0") + pool.AddData([]byte("hash-bob-42"), createTx("bob", 42), 0, "0") + + // Good + pool.AddData([]byte("hash-carol-7"), createTx("carol", 7), 0, "0") + pool.AddData([]byte("hash-carol-8"), createTx("carol", 8), 0, "0") + + require.Equal(t, int64(8), pool.GetCounts().GetTotal()) + + cleanupLoopMaximumDuration := time.Millisecond * 100 + pool.CleanupSelfShardTxCache(accountsProvider, 7, math.MaxInt, cleanupLoopMaximumDuration) + + require.Equal(t, int64(5), pool.GetCounts().GetTotal()) + }) +} + +func Test_CleanupSelfShardTxCache(t *testing.T) { + t.Run("with lower nonces", func(t *testing.T) { + poolAsInterface, _ := newTxPoolToTest() + pool := poolAsInterface.(*shardedTxPool) + cache := pool.getTxCache("0").(*txcache.TxCache) + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMock() + accountsProvider.SetNonce([]byte("alice"), 2) + accountsProvider.SetNonce([]byte("bob"), 42) + accountsProvider.SetNonce([]byte("carol"), 7) + + // One lower nonce + pool.AddData([]byte("hash-alice-1"), createTx("alice", 1), 0, "0") + pool.AddData([]byte("hash-alice-2"), createTx("alice", 2), 0, "0") + pool.AddData([]byte("hash-alice-3"), createTx("alice", 3), 0, "0") + + // A few with lower nonce + pool.AddData([]byte("hash-bob-40"), createTx("bob", 40), 0, "0") + pool.AddData([]byte("hash-bob-41"), createTx("bob", 41), 0, "0") + pool.AddData([]byte("hash-bob-42"), createTx("bob", 42), 0, "0") + + // Good + pool.AddData([]byte("hash-carol-7"), createTx("carol", 7), 0, "0") + pool.AddData([]byte("hash-carol-8"), createTx("carol", 8), 0, "0") + + expectedNumEvicted := 1 + 2 // 1 alice + 2 bob + expectedNumRemained := 8 - expectedNumEvicted + selectionLoopMaximumDuration := time.Millisecond * 100 + + pool.CleanupSelfShardTxCache(accountsProvider, 7, math.MaxInt, selectionLoopMaximumDuration) + require.Equal(t, expectedNumRemained, cache.Len()) + }) +} + func Test_RemoveDataFromAllShards(t *testing.T) { poolAsInterface, _ := newTxPoolToTest() pool := poolAsInterface.(*shardedTxPool) @@ -374,7 +486,7 @@ func Test_IsInterfaceNil(t *testing.T) { } func Test_routeToCacheUnions(t *testing.T) { - config := storageunit.CacheConfig{ + cacheConfig := storageunit.CacheConfig{ Capacity: 100, SizePerSender: 10, SizeInBytes: 409600, @@ -382,12 +494,17 @@ func Test_routeToCacheUnions(t *testing.T) { Shards: 1, } args := ArgShardedTxPool{ - Config: config, + Config: cacheConfig, TxGasHandler: txcachemocks.NewTxGasHandlerMock(), Marshalizer: &marshal.GogoProtoMarshalizer{}, NumberOfShards: 4, SelfShardID: 42, + TxCacheBoundsConfig: config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: maxNumBytesPerSenderUpperBoundTest, + MaxTrackedBlocks: maxTrackedBlocks, + }, } + pool, _ := NewShardedTxPool(args) require.Equal(t, "42", pool.routeToCacheUnions("42")) @@ -399,11 +516,199 @@ func Test_routeToCacheUnions(t *testing.T) { require.Equal(t, "foobar", pool.routeToCacheUnions("foobar")) } +func TestShardedTxPool_getSelfShardTxCache(t *testing.T) { + t.Parallel() + + cacheConfig := storageunit.CacheConfig{ + Capacity: 100, + SizePerSender: 10, + SizeInBytes: 409600, + SizeInBytesPerSender: 40960, + Shards: 1, + } + args := ArgShardedTxPool{ + Config: cacheConfig, + TxGasHandler: txcachemocks.NewTxGasHandlerMock(), + Marshalizer: &marshal.GogoProtoMarshalizer{}, + NumberOfShards: 3, + SelfShardID: 2, + TxCacheBoundsConfig: config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: maxNumBytesPerSenderUpperBoundTest, + MaxTrackedBlocks: maxTrackedBlocks, + }, + } + + pool, _ := NewShardedTxPool(args) + require.Equal(t, pool.getTxCache("2"), pool.getSelfShardTxCache()) +} + +func TestShardedTxPool_GetNumTrackedBlocks(t *testing.T) { + t.Parallel() + + poolAsInterface, _ := newTxPoolToTest() + pool := poolAsInterface.(*shardedTxPool) + + txCache := pool.getSelfShardTxCache() + + numOfBlocks := 10 + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMock() + + for i := 1; i < numOfBlocks+1; i++ { + err := txCache.OnProposedBlock( + []byte(fmt.Sprintf("hash%d", i)), + &block.Body{}, + &block.Header{ + Nonce: uint64(i), + PrevHash: []byte(fmt.Sprintf("hash%d", i-1)), + RootHash: []byte("rootHash0"), + }, + accountsProvider, + []byte("hash0"), + ) + require.Nil(t, err) + } + + require.Equal(t, uint64(numOfBlocks), pool.GetNumTrackedBlocks()) +} + +func TestShardedTxPool_GetNumTrackedAccounts(t *testing.T) { + t.Parallel() + + poolAsInterface, _ := newTxPoolToTest() + pool := poolAsInterface.(*shardedTxPool) + + txCache := pool.getSelfShardTxCache() + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMock() + accountsProvider.GetRootHashCalled = func() ([]byte, error) { + return []byte("rootHash0"), nil + } + accountsProvider.SetNonce([]byte("alice"), 1) + accountsProvider.SetNonce([]byte("bob"), 40) + accountsProvider.SetNonce([]byte("carol"), 7) + + pool.AddData([]byte("hash1"), createTx("alice", 1), 0, "0") + pool.AddData([]byte("hash2"), createTx("alice", 2), 0, "0") + pool.AddData([]byte("hash3"), createTx("alice", 3), 0, "0") + + pool.AddData([]byte("hash4"), createTx("bob", 40), 0, "0") + pool.AddData([]byte("hash5"), createTx("bob", 41), 0, "0") + pool.AddData([]byte("hash6"), createTx("bob", 42), 0, "0") + + pool.AddData([]byte("hash7"), createTx("carol", 7), 0, "0") + pool.AddData([]byte("hash8"), createTx("carol", 8), 0, "0") + miniblocks := []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("hash1"), + []byte("hash2"), + []byte("hash3"), + }, + SenderShardID: 0, + ReceiverShardID: 0, + Type: block.TxBlock, + }, + { + TxHashes: [][]byte{ + []byte("hash4"), + []byte("hash5"), + []byte("hash6"), + }, + SenderShardID: 0, + ReceiverShardID: 0, + Type: block.TxBlock, + }, + { + TxHashes: [][]byte{ + []byte("hash7"), + []byte("hash8"), + }, + SenderShardID: 0, + ReceiverShardID: 0, + Type: block.TxBlock, + }, + } + + execHdr := &block.Header{Nonce: 1, RootHash: []byte("rootHash0")} + _ = txCache.OnExecutedBlock(execHdr, []byte("rootHash0")) + + err := txCache.OnProposedBlock( + []byte("hash0"), + &block.Body{ + MiniBlocks: miniblocks, + }, + &block.Header{ + Nonce: uint64(1), + PrevHash: []byte("hash0"), + RootHash: []byte("rootHash0"), + }, + accountsProvider, + []byte("hash0"), + ) + require.Nil(t, err) + require.Equal(t, uint64(3), pool.GetNumTrackedAccounts()) +} + +func TestShardedTxPool_OnProposedBlock_And_OnExecutedBlock(t *testing.T) { + t.Parallel() + + cacheConfig := storageunit.CacheConfig{ + Capacity: 100, + SizePerSender: 10, + SizeInBytes: 409600, + SizeInBytesPerSender: 40960, + Shards: 1, + } + args := ArgShardedTxPool{ + Config: cacheConfig, + TxGasHandler: txcachemocks.NewTxGasHandlerMock(), + Marshalizer: &marshal.GogoProtoMarshalizer{}, + NumberOfShards: 3, + SelfShardID: 0, + TxCacheBoundsConfig: config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: maxNumBytesPerSenderUpperBoundTest, + MaxTrackedBlocks: maxTrackedBlocks, + }, + } + + pool, err := NewShardedTxPool(args) + require.Nil(t, err) + + t.Run("OnProposedBlock calls TxCache.OnProposedBlock", func(t *testing.T) { + t.Parallel() + + err := pool.OnProposedBlock(nil, nil, nil, nil, nil) + require.ErrorContains(t, err, "nil block hash") + + err = pool.OnProposedBlock( + []byte("abba"), + &block.Body{}, + &block.HeaderV2{}, + txcachemocks.NewAccountNonceAndBalanceProviderMock(), + nil, + ) + require.Nil(t, err) + }) + + t.Run("OnExecutedBlock calls TxCache.OnExecutedBlock", func(t *testing.T) { + t.Parallel() + + err := pool.OnExecutedBlock(nil, []byte{}) + require.ErrorContains(t, err, "nil block header") + + err = pool.OnExecutedBlock(&block.HeaderV2{}, []byte{}) + require.Nil(t, err) + + pool.ResetTracker() // coverage + }) +} + func createTx(sender string, nonce uint64) data.TransactionHandler { return &transaction.Transaction{ SndAddr: []byte(sender), Nonce: nonce, GasLimit: 50000, + GasPrice: 20000, } } @@ -415,7 +720,7 @@ type thisIsNotATransaction struct { } func newTxPoolToTest() (dataRetriever.ShardedDataCacherNotifier, error) { - config := storageunit.CacheConfig{ + cacheConfig := storageunit.CacheConfig{ Capacity: 100, SizePerSender: 10, SizeInBytes: 409600, @@ -423,11 +728,15 @@ func newTxPoolToTest() (dataRetriever.ShardedDataCacherNotifier, error) { Shards: 1, } args := ArgShardedTxPool{ - Config: config, + Config: cacheConfig, TxGasHandler: txcachemocks.NewTxGasHandlerMock(), Marshalizer: &marshal.GogoProtoMarshalizer{}, NumberOfShards: 4, SelfShardID: 0, + TxCacheBoundsConfig: config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: maxNumBytesPerSenderUpperBoundTest, + MaxTrackedBlocks: maxTrackedBlocks, + }, } return NewShardedTxPool(args) } diff --git a/dataRetriever/unitType.go b/dataRetriever/unitType.go index a32313013f6..b67dbaf6596 100644 --- a/dataRetriever/unitType.go +++ b/dataRetriever/unitType.go @@ -53,6 +53,8 @@ const ( ProofsUnit UnitType = 23 // StateAccessesUnit is the state accesses storage unit identifier StateAccessesUnit UnitType = 24 + // ExecutionResultsUnit is the execution results unit identifier + ExecutionResultsUnit UnitType = 25 // ShardHdrNonceHashDataUnit is the header nonce-hash pair data unit identifier //TODO: Add only unit types lower than 100 @@ -118,6 +120,8 @@ func (ut UnitType) String() string { return "ProofsUnit" case StateAccessesUnit: return "StateAccessesUnit" + case ExecutionResultsUnit: + return "ExecutionResultsUnit" } if ut < ShardHdrNonceHashDataUnit { diff --git a/dblookupext/disabled/nilHistoryRepository.go b/dblookupext/disabled/nilHistoryRepository.go index 621ab56fd35..eefa2ef5b3e 100644 --- a/dblookupext/disabled/nilHistoryRepository.go +++ b/dblookupext/disabled/nilHistoryRepository.go @@ -20,7 +20,7 @@ func NewNilHistoryRepository() (*nilHistoryRepository, error) { } // RecordBlock returns a not implemented error -func (nhr *nilHistoryRepository) RecordBlock(_ []byte, _ data.HeaderHandler, _ data.BodyHandler, _, _ map[string]data.TransactionHandler, _ []*block.MiniBlock, _ []*data.LogData) error { +func (nhr *nilHistoryRepository) RecordBlock(_ []byte, _ data.HeaderHandler, _ data.BodyHandler, _, _ map[string]data.TransactionHandler, _ []*block.MiniBlock, _ []data.LogDataHandler) error { return nil } diff --git a/dblookupext/esdtSupply/esdtSuppliesProcessor.go b/dblookupext/esdtSupply/esdtSuppliesProcessor.go index 52d8c7d4acd..a10333b0a53 100644 --- a/dblookupext/esdtSupply/esdtSuppliesProcessor.go +++ b/dblookupext/esdtSupply/esdtSuppliesProcessor.go @@ -8,26 +8,32 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/marshal" - "github.com/multiversx/mx-chain-go/storage" logger "github.com/multiversx/mx-chain-logger-go" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/storage" ) var log = logger.GetOrCreate("dblookupext/esdtSupply") type suppliesProcessor struct { - logsProc *logsProcessor - logsGet *logsGetter - mutex sync.Mutex + logsProc *logsProcessor + logsGet *logsGetter + mbsStorer storage.Storer + marshaller marshal.Marshalizer + mutex sync.Mutex } // NewSuppliesProcessor will create a new instance of the supplies processor func NewSuppliesProcessor( - marshalizer marshal.Marshalizer, + marshaller marshal.Marshalizer, suppliesStorer storage.Storer, logsStorer storage.Storer, + mbsStorer storage.Storer, ) (*suppliesProcessor, error) { - if check.IfNil(marshalizer) { + if check.IfNil(marshaller) { return nil, core.ErrNilMarshalizer } if check.IfNil(suppliesStorer) { @@ -36,25 +42,30 @@ func NewSuppliesProcessor( if check.IfNil(logsStorer) { return nil, core.ErrNilStore } + if check.IfNil(mbsStorer) { + return nil, core.ErrNilStore + } - logsGet := newLogsGetter(marshalizer, logsStorer) - logsProc := newLogsProcessor(marshalizer, suppliesStorer) + logsGet := newLogsGetter(marshaller, logsStorer) + logsProc := newLogsProcessor(marshaller, suppliesStorer) return &suppliesProcessor{ - logsProc: logsProc, - logsGet: logsGet, + logsProc: logsProc, + logsGet: logsGet, + mbsStorer: mbsStorer, + marshaller: marshaller, }, nil } // ProcessLogs will process the provided logs -func (sp *suppliesProcessor) ProcessLogs(blockNonce uint64, logs []*data.LogData) error { +func (sp *suppliesProcessor) ProcessLogs(blockNonce uint64, logs []data.LogDataHandler) error { sp.mutex.Lock() defer sp.mutex.Unlock() - logsMap := make(map[string]*data.LogData) + logsMap := make(map[string]data.LogDataHandler) for _, logData := range logs { if logData != nil { - logsMap[logData.TxHash] = logData + logsMap[logData.GetTxHash()] = logData } } @@ -70,12 +81,59 @@ func (sp *suppliesProcessor) RevertChanges(header data.HeaderHandler, body data. sp.mutex.Lock() defer sp.mutex.Unlock() + if header.IsHeaderV3() { + return sp.revertChangesBasedOnExecutionResults(header) + } + + return sp.revertChanges(header.GetNonce(), body) +} + +func (sp *suppliesProcessor) revertChangesBasedOnExecutionResults(header data.HeaderHandler) error { + for _, executionResult := range header.GetExecutionResultsHandlers() { + mbHeaders, err := common.GetMiniBlocksHeaderHandlersFromExecResult(executionResult) + if err != nil { + return err + } + + body := &block.Body{MiniBlocks: make([]*block.MiniBlock, 0, len(mbHeaders))} + for _, mbHeader := range mbHeaders { + mb, errG := sp.getMbByHash(mbHeader.GetHash(), executionResult.GetHeaderEpoch()) + if errG != nil { + return errG + } + body.MiniBlocks = append(body.MiniBlocks, mb) + } + + err = sp.revertChanges(executionResult.GetHeaderNonce(), body) + if err != nil { + return err + } + } + return nil +} + +func (sp *suppliesProcessor) getMbByHash(hash []byte, epoch uint32) (*block.MiniBlock, error) { + mbBytes, err := sp.mbsStorer.GetFromEpoch(hash, epoch) + if err != nil { + return nil, err + } + + mb := &block.MiniBlock{} + err = sp.marshaller.Unmarshal(mb, mbBytes) + if err != nil { + return nil, err + } + + return mb, nil +} + +func (sp *suppliesProcessor) revertChanges(nonce uint64, body data.BodyHandler) error { logsFromDB, err := sp.logsGet.getLogsBasedOnBody(body) if err != nil { return err } - return sp.logsProc.processLogs(header.GetNonce(), logsFromDB, true) + return sp.logsProc.processLogs(nonce, logsFromDB, true) } // GetESDTSupply will return the supply from the storage for the given token diff --git a/dblookupext/esdtSupply/esdtSuppliesProcessor_test.go b/dblookupext/esdtSupply/esdtSuppliesProcessor_test.go index 56084ced28b..30bf896ef99 100644 --- a/dblookupext/esdtSupply/esdtSuppliesProcessor_test.go +++ b/dblookupext/esdtSupply/esdtSuppliesProcessor_test.go @@ -11,6 +11,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/storage" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/genericMocks" @@ -31,16 +32,19 @@ const ( func TestNewSuppliesProcessor(t *testing.T) { t.Parallel() - _, err := NewSuppliesProcessor(nil, &storageStubs.StorerStub{}, &storageStubs.StorerStub{}) + _, err := NewSuppliesProcessor(nil, &storageStubs.StorerStub{}, &storageStubs.StorerStub{}, &storageStubs.StorerStub{}) require.Equal(t, core.ErrNilMarshalizer, err) - _, err = NewSuppliesProcessor(&marshallerMock.MarshalizerMock{}, nil, &storageStubs.StorerStub{}) + _, err = NewSuppliesProcessor(&marshallerMock.MarshalizerMock{}, nil, &storageStubs.StorerStub{}, &storageStubs.StorerStub{}) require.Equal(t, core.ErrNilStore, err) - _, err = NewSuppliesProcessor(&marshallerMock.MarshalizerMock{}, &storageStubs.StorerStub{}, nil) + _, err = NewSuppliesProcessor(&marshallerMock.MarshalizerMock{}, &storageStubs.StorerStub{}, nil, &storageStubs.StorerStub{}) require.Equal(t, core.ErrNilStore, err) - proc, err := NewSuppliesProcessor(&marshallerMock.MarshalizerMock{}, &storageStubs.StorerStub{}, &storageStubs.StorerStub{}) + _, err = NewSuppliesProcessor(&marshallerMock.MarshalizerMock{}, &storageStubs.StorerStub{}, &storageStubs.StorerStub{}, nil) + require.Equal(t, core.ErrNilStore, err) + + proc, err := NewSuppliesProcessor(&marshallerMock.MarshalizerMock{}, &storageStubs.StorerStub{}, &storageStubs.StorerStub{}, &storageStubs.StorerStub{}) require.Nil(t, err) require.NotNil(t, proc) require.False(t, proc.IsInterfaceNil()) @@ -50,10 +54,10 @@ func TestProcessLogsSaveSupply(t *testing.T) { t.Parallel() token := []byte("nft-0001") - logs := []*data.LogData{ - { + logs := []data.LogDataHandler{ + &transaction.LogData{ TxHash: "txLog", - LogHandler: &transaction.Log{ + Log: &transaction.Log{ Events: []*transaction.Event{ { Identifier: []byte("something"), @@ -97,7 +101,7 @@ func TestProcessLogsSaveSupply(t *testing.T) { }, }, }, - { + &transaction.LogData{ TxHash: "log", }, } @@ -134,7 +138,7 @@ func TestProcessLogsSaveSupply(t *testing.T) { }, } - suppliesProc, err := NewSuppliesProcessor(marshalizer, suppliesStorer, &storageStubs.StorerStub{}) + suppliesProc, err := NewSuppliesProcessor(marshalizer, suppliesStorer, &storageStubs.StorerStub{}, &storageStubs.StorerStub{}) require.Nil(t, err) err = suppliesProc.ProcessLogs(6, logs) @@ -147,10 +151,10 @@ func TestProcessLogsSaveSupplyShouldUpdateSupplyMintedAndBurned(t *testing.T) { t.Parallel() token := []byte("nft-0001") - logsCreate := []*data.LogData{ - { + logsCreate := []data.LogDataHandler{ + &transaction.LogData{ TxHash: "txLog", - LogHandler: &transaction.Log{ + Log: &transaction.Log{ Events: []*transaction.Event{ { Identifier: []byte("something"), @@ -170,14 +174,14 @@ func TestProcessLogsSaveSupplyShouldUpdateSupplyMintedAndBurned(t *testing.T) { }, }, }, - { + &transaction.LogData{ TxHash: "log", }, } - logsAddQuantity := []*data.LogData{ - { + logsAddQuantity := []data.LogDataHandler{ + &transaction.LogData{ TxHash: "txLog", - LogHandler: &transaction.Log{ + Log: &transaction.Log{ Events: []*transaction.Event{ { Identifier: []byte("something"), @@ -197,15 +201,15 @@ func TestProcessLogsSaveSupplyShouldUpdateSupplyMintedAndBurned(t *testing.T) { }, }, }, - { + &transaction.LogData{ TxHash: "log", }, } - logsBurn := []*data.LogData{ - { + logsBurn := []data.LogDataHandler{ + &transaction.LogData{ TxHash: "txLog", - LogHandler: &transaction.Log{ + Log: &transaction.Log{ Events: []*transaction.Event{ { Identifier: []byte("something"), @@ -225,7 +229,7 @@ func TestProcessLogsSaveSupplyShouldUpdateSupplyMintedAndBurned(t *testing.T) { }, }, }, - { + &transaction.LogData{ TxHash: "log", }, } @@ -296,7 +300,7 @@ func TestProcessLogsSaveSupplyShouldUpdateSupplyMintedAndBurned(t *testing.T) { }, } - suppliesProc, err := NewSuppliesProcessor(marshalizer, suppliesStorer, &storageStubs.StorerStub{}) + suppliesProc, err := NewSuppliesProcessor(marshalizer, suppliesStorer, &storageStubs.StorerStub{}, &storageStubs.StorerStub{}) require.Nil(t, err) err = suppliesProc.ProcessLogs(6, logsCreate) @@ -315,10 +319,10 @@ func TestProcessLogs_RevertChangesShouldWorkForRevertingMinting(t *testing.T) { t.Parallel() token := []byte("BRT-1q2w3e") - logsMintNoRevert := []*data.LogData{ - { + logsMintNoRevert := []data.LogDataHandler{ + &transaction.LogData{ TxHash: "txLog0", - LogHandler: &transaction.Log{ + Log: &transaction.Log{ Events: []*transaction.Event{ { Identifier: []byte(core.BuiltInFunctionESDTLocalMint), @@ -329,9 +333,9 @@ func TestProcessLogs_RevertChangesShouldWorkForRevertingMinting(t *testing.T) { }, }, }, - { + &transaction.LogData{ TxHash: "txLog1", - LogHandler: &transaction.Log{ + Log: &transaction.Log{ Events: []*transaction.Event{ { Identifier: []byte(core.BuiltInFunctionESDTLocalMint), @@ -355,10 +359,10 @@ func TestProcessLogs_RevertChangesShouldWorkForRevertingMinting(t *testing.T) { }, } - logsMintRevert := []*data.LogData{ - { - TxHash: "txLog3", - LogHandler: mintLogToBeReverted, + logsMintRevert := []data.LogDataHandler{ + &transaction.LogData{ + TxHash: "txLog3", + Log: mintLogToBeReverted, }, } @@ -372,7 +376,7 @@ func TestProcessLogs_RevertChangesShouldWorkForRevertingMinting(t *testing.T) { suppliesStorer := genericMocks.NewStorerMockWithErrKeyNotFound(0) - suppliesProc, err := NewSuppliesProcessor(marshalizer, suppliesStorer, logsStorer) + suppliesProc, err := NewSuppliesProcessor(marshalizer, suppliesStorer, logsStorer, &storageStubs.StorerStub{}) require.Nil(t, err) err = suppliesProc.ProcessLogs(6, logsMintNoRevert) @@ -402,14 +406,96 @@ func TestProcessLogs_RevertChangesShouldWorkForRevertingMinting(t *testing.T) { testFungibleTokenMint*2, 0) } +func TestProcessLogs_RevertChangesHeaderV3(t *testing.T) { + t.Parallel() + + t.Run("cannot get mb headers should err", func(t *testing.T) { + t.Parallel() + + marshalizer := marshallerMock.MarshalizerMock{} + logsStorer := genericMocks.NewStorerMockWithErrKeyNotFound(0) + suppliesStorer := genericMocks.NewStorerMockWithErrKeyNotFound(0) + mbStorer := genericMocks.NewStorerMockWithErrKeyNotFound(0) + suppliesProc, err := NewSuppliesProcessor(marshalizer, suppliesStorer, logsStorer, mbStorer) + require.Nil(t, err) + + revertedHeader := block.HeaderV3{ + Nonce: 7, + ExecutionResults: []*block.ExecutionResult{ + nil, + }, + } + err = suppliesProc.RevertChanges(&revertedHeader, &block.Body{}) + require.Equal(t, process.ErrNilBaseExecutionResult, err) + }) + + t.Run("cannot get mb from storage should error", func(t *testing.T) { + t.Parallel() + + marshalizer := marshallerMock.MarshalizerMock{} + logsStorer := genericMocks.NewStorerMockWithErrKeyNotFound(0) + suppliesStorer := genericMocks.NewStorerMockWithErrKeyNotFound(0) + mbStorer := genericMocks.NewStorerMockWithErrKeyNotFound(0) + suppliesProc, err := NewSuppliesProcessor(marshalizer, suppliesStorer, logsStorer, mbStorer) + require.Nil(t, err) + + revertedHeader := block.HeaderV3{ + Nonce: 7, + ExecutionResults: []*block.ExecutionResult{ + { + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("mbHash"), + }, + }, + }, + }, + } + err = suppliesProc.RevertChanges(&revertedHeader, &block.Body{}) + require.Equal(t, storage.ErrKeyNotFound, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + marshalizer := marshallerMock.MarshalizerMock{} + logsStorer := genericMocks.NewStorerMockWithErrKeyNotFound(0) + suppliesStorer := genericMocks.NewStorerMockWithErrKeyNotFound(0) + + mbHash := []byte("mbHash") + mb := &block.MiniBlock{TxHashes: [][]byte{[]byte("txHash3"), []byte("txHash4")}} + mbBytes, _ := marshalizer.Marshal(mb) + mbStorer := genericMocks.NewStorerMockWithErrKeyNotFound(0) + _ = mbStorer.Put(mbHash, mbBytes) + + suppliesProc, err := NewSuppliesProcessor(marshalizer, suppliesStorer, logsStorer, mbStorer) + require.Nil(t, err) + + revertedHeader := block.HeaderV3{ + Nonce: 7, + ExecutionResults: []*block.ExecutionResult{ + { + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: mbHash, + }, + }, + }, + }, + } + err = suppliesProc.RevertChanges(&revertedHeader, &block.Body{}) + require.Equal(t, nil, err) + }) +} + func TestProcessLogs_RevertChangesShouldWorkForRevertingBurning(t *testing.T) { t.Parallel() token := []byte("BRT-1q2w3e") - logsMintNoRevert := []*data.LogData{ - { + logsMintNoRevert := []data.LogDataHandler{ + &transaction.LogData{ TxHash: "txLog0", - LogHandler: &transaction.Log{ + Log: &transaction.Log{ Events: []*transaction.Event{ { Identifier: []byte(core.BuiltInFunctionESDTLocalMint), @@ -420,9 +506,9 @@ func TestProcessLogs_RevertChangesShouldWorkForRevertingBurning(t *testing.T) { }, }, }, - { + &transaction.LogData{ TxHash: "txLog1", - LogHandler: &transaction.Log{ + Log: &transaction.Log{ Events: []*transaction.Event{ { Identifier: []byte(core.BuiltInFunctionESDTLocalMint), @@ -446,10 +532,10 @@ func TestProcessLogs_RevertChangesShouldWorkForRevertingBurning(t *testing.T) { }, } - logsMintRevert := []*data.LogData{ - { - TxHash: "txLog3", - LogHandler: mintLogToBeReverted, + logsMintRevert := []data.LogDataHandler{ + &transaction.LogData{ + TxHash: "txLog3", + Log: mintLogToBeReverted, }, } @@ -463,7 +549,7 @@ func TestProcessLogs_RevertChangesShouldWorkForRevertingBurning(t *testing.T) { suppliesStorer := genericMocks.NewStorerMockWithErrKeyNotFound(0) - suppliesProc, err := NewSuppliesProcessor(marshalizer, suppliesStorer, logsStorer) + suppliesProc, err := NewSuppliesProcessor(marshalizer, suppliesStorer, logsStorer, &storageStubs.StorerStub{}) require.Nil(t, err) err = suppliesProc.ProcessLogs(6, logsMintNoRevert) @@ -535,7 +621,7 @@ func TestSupplyESDT_GetSupply(t *testing.T) { } return nil, errors.New("local err") }, - }, &storageStubs.StorerStub{}) + }, &storageStubs.StorerStub{}, &storageStubs.StorerStub{}) res, err := proc.GetESDTSupply("my-token") require.Nil(t, err) diff --git a/dblookupext/esdtSupply/logsGetter.go b/dblookupext/esdtSupply/logsGetter.go index d29eeb742ba..f15cfb549f3 100644 --- a/dblookupext/esdtSupply/logsGetter.go +++ b/dblookupext/esdtSupply/logsGetter.go @@ -25,13 +25,13 @@ func newLogsGetter( } } -func (lg *logsGetter) getLogsBasedOnBody(blockBody data.BodyHandler) (map[string]*data.LogData, error) { +func (lg *logsGetter) getLogsBasedOnBody(blockBody data.BodyHandler) (map[string]data.LogDataHandler, error) { body, ok := blockBody.(*block.Body) if !ok { return nil, errCannotCastToBlockBody } - logsDB := make(map[string]*data.LogData) + logsDB := make(map[string]data.LogDataHandler) for _, mb := range body.MiniBlocks { shouldIgnore := mb.Type != block.TxBlock && mb.Type != block.SmartContractResultBlock if shouldIgnore { @@ -44,15 +44,15 @@ func (lg *logsGetter) getLogsBasedOnBody(blockBody data.BodyHandler) (map[string } for _, logData := range dbLogsMb { - logsDB[logData.TxHash] = logData + logsDB[logData.GetTxHash()] = logData } } return logsDB, nil } -func (lg *logsGetter) getLogsBasedOnMB(mb *block.MiniBlock) ([]*data.LogData, error) { - dbLogs := make([]*data.LogData, 0) +func (lg *logsGetter) getLogsBasedOnMB(mb *block.MiniBlock) ([]data.LogDataHandler, error) { + dbLogs := make([]data.LogDataHandler, 0) for _, txHash := range mb.TxHashes { txLog, ok, err := lg.getTxLog(txHash) if err != nil { @@ -63,16 +63,16 @@ func (lg *logsGetter) getLogsBasedOnMB(mb *block.MiniBlock) ([]*data.LogData, er continue } - dbLogs = append(dbLogs, &data.LogData{ - LogHandler: txLog, - TxHash: string(txHash), + dbLogs = append(dbLogs, &transaction.LogData{ + Log: txLog, + TxHash: string(txHash), }) } return dbLogs, nil } -func (lg *logsGetter) getTxLog(txHash []byte) (data.LogHandler, bool, error) { +func (lg *logsGetter) getTxLog(txHash []byte) (*transaction.Log, bool, error) { logBytes, err := lg.logsStorer.Get(txHash) if err != nil { return nil, false, nil diff --git a/dblookupext/esdtSupply/logsProcessor.go b/dblookupext/esdtSupply/logsProcessor.go index e583538af6c..cd5f5eb8685 100644 --- a/dblookupext/esdtSupply/logsProcessor.go +++ b/dblookupext/esdtSupply/logsProcessor.go @@ -43,7 +43,7 @@ func newLogsProcessor( } } -func (lp *logsProcessor) processLogs(blockNonce uint64, logs map[string]*data.LogData, isRevert bool) error { +func (lp *logsProcessor) processLogs(blockNonce uint64, logs map[string]data.LogDataHandler, isRevert bool) error { shouldProcess, err := lp.nonceProc.shouldProcessLog(blockNonce, isRevert) if err != nil { return err @@ -54,11 +54,11 @@ func (lp *logsProcessor) processLogs(blockNonce uint64, logs map[string]*data.Lo supplies := make(map[string]*SupplyESDT) for _, logHandler := range logs { - if logHandler == nil || check.IfNil(logHandler.LogHandler) { + if logHandler == nil || check.IfNil(logHandler.GetLogHandler()) { continue } - errProc := lp.processLog(logHandler.LogHandler, supplies, isRevert) + errProc := lp.processLog(logHandler.GetLogHandler(), supplies, isRevert) if errProc != nil { return errProc } diff --git a/dblookupext/esdtSupply/logsProcessor_test.go b/dblookupext/esdtSupply/logsProcessor_test.go index 6512e1d28ba..bb0d9517c8a 100644 --- a/dblookupext/esdtSupply/logsProcessor_test.go +++ b/dblookupext/esdtSupply/logsProcessor_test.go @@ -19,9 +19,9 @@ func TestProcessLogsSaveSupplyNothingInStorage(t *testing.T) { t.Parallel() token := []byte("nft-0001") - logs := map[string]*data.LogData{ - "txLog": { - LogHandler: &transaction.Log{ + logs := map[string]data.LogDataHandler{ + "txLog": &transaction.LogData{ + Log: &transaction.Log{ Events: []*transaction.Event{ { Identifier: []byte("something"), @@ -106,9 +106,9 @@ func TestTestProcessLogsSaveSupplyExistsInStorage(t *testing.T) { token := []byte("esdt-miiu") - logs := map[string]*data.LogData{ - "txLog": { - LogHandler: &transaction.Log{ + logs := map[string]data.LogDataHandler{ + "txLog": &transaction.LogData{ + Log: &transaction.Log{ Events: []*transaction.Event{ { Identifier: []byte(core.BuiltInFunctionESDTLocalBurn), diff --git a/dblookupext/executionResults.go b/dblookupext/executionResults.go new file mode 100644 index 00000000000..440aa66f412 --- /dev/null +++ b/dblookupext/executionResults.go @@ -0,0 +1,40 @@ +package dblookupext + +import ( + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/storage" +) + +type executionResultsProcessor struct { + marshaller marshal.Marshalizer + storer storage.Storer +} + +func newExecutionResultsProcessor(storer storage.Storer, marshaller marshal.Marshalizer) *executionResultsProcessor { + return &executionResultsProcessor{ + marshaller: marshaller, + storer: storer, + } +} + +func (erp *executionResultsProcessor) saveExecutionResultsFromHeader(header data.HeaderHandler) error { + for _, executionResult := range header.GetExecutionResultsHandlers() { + if check.IfNil(executionResult) { + continue + } + + executionResultBytes, err := erp.marshaller.Marshal(executionResult) + if err != nil { + return err + } + + err = erp.storer.PutInEpoch(executionResult.GetHeaderHash(), executionResultBytes, executionResult.GetHeaderEpoch()) + if err != nil { + return err + } + } + + return nil +} diff --git a/dblookupext/executionResults_test.go b/dblookupext/executionResults_test.go new file mode 100644 index 00000000000..78db80a11d8 --- /dev/null +++ b/dblookupext/executionResults_test.go @@ -0,0 +1,126 @@ +package dblookupext + +import ( + "errors" + "testing" + + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" + "github.com/multiversx/mx-chain-go/testscommon/storage" + "github.com/stretchr/testify/require" +) + +func TestSaveExecutionResultsFromHeader(t *testing.T) { + t.Parallel() + + t.Run("header v1 should do nothing", func(t *testing.T) { + t.Parallel() + + called := false + storer := &storage.StorerStub{ + PutInEpochCalled: func(key, data []byte, epoch uint32) error { + called = true + return nil + }, + } + marshaller := marshallerMock.MarshalizerMock{} + proc := newExecutionResultsProcessor(storer, marshaller) + + header := &block.Header{} + err := proc.saveExecutionResultsFromHeader(header) + require.NoError(t, err) + require.False(t, called) + }) + + t.Run("header v2 should do nothing", func(t *testing.T) { + t.Parallel() + + called := false + storer := &storage.StorerStub{ + PutInEpochCalled: func(key, data []byte, epoch uint32) error { + called = true + return nil + }, + } + marshaller := marshallerMock.MarshalizerMock{} + proc := newExecutionResultsProcessor(storer, marshaller) + + header := &block.HeaderV2{} + err := proc.saveExecutionResultsFromHeader(header) + require.NoError(t, err) + require.False(t, called) + }) + + t.Run("header v3 no execution results", func(t *testing.T) { + t.Parallel() + + called := false + storer := &storage.StorerStub{ + PutInEpochCalled: func(key, data []byte, epoch uint32) error { + called = true + return nil + }, + } + marshaller := marshallerMock.MarshalizerMock{} + proc := newExecutionResultsProcessor(storer, marshaller) + + header := &block.HeaderV3{} + err := proc.saveExecutionResultsFromHeader(header) + require.NoError(t, err) + require.False(t, called) + }) + + t.Run("header v3 with execution result should error", func(t *testing.T) { + t.Parallel() + + localError := errors.New("local error") + storer := &storage.StorerStub{ + PutInEpochCalled: func(key, data []byte, epoch uint32) error { + return localError + }, + } + marshaller := marshallerMock.MarshalizerMock{} + proc := newExecutionResultsProcessor(storer, marshaller) + + header := &block.HeaderV3{ + ExecutionResults: []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 1, + }, + }, + }, + } + err := proc.saveExecutionResultsFromHeader(header) + require.Equal(t, localError, err) + }) + + t.Run("header v3 with execution result should call put in epoch", func(t *testing.T) { + t.Parallel() + + called := false + storer := &storage.StorerStub{ + PutInEpochCalled: func(key, data []byte, epoch uint32) error { + called = true + return nil + }, + } + marshaller := marshallerMock.MarshalizerMock{} + proc := newExecutionResultsProcessor(storer, marshaller) + + header := &block.HeaderV3{ + ExecutionResults: []*block.ExecutionResult{ + nil, + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 1, + }, + }, + }, + } + err := proc.saveExecutionResultsFromHeader(header) + require.NoError(t, err) + require.True(t, called) + }) + +} diff --git a/dblookupext/factory/historyRepositoryFactory.go b/dblookupext/factory/historyRepositoryFactory.go index e2bf316620e..db95838556e 100644 --- a/dblookupext/factory/historyRepositoryFactory.go +++ b/dblookupext/factory/historyRepositoryFactory.go @@ -23,6 +23,7 @@ type ArgsHistoryRepositoryFactory struct { Marshalizer marshal.Marshalizer Hasher hashing.Hasher Uint64ByteSliceConverter typeConverters.Uint64ByteSliceConverter + DataPool dataRetriever.PoolsHolder } type historyRepositoryFactory struct { @@ -32,6 +33,7 @@ type historyRepositoryFactory struct { marshalizer marshal.Marshalizer hasher hashing.Hasher uInt64ByteSliceConverter typeConverters.Uint64ByteSliceConverter + dataPool dataRetriever.PoolsHolder } // NewHistoryRepositoryFactory creates an instance of historyRepositoryFactory @@ -48,6 +50,9 @@ func NewHistoryRepositoryFactory(args *ArgsHistoryRepositoryFactory) (dblookupex if check.IfNil(args.Uint64ByteSliceConverter) { return nil, process.ErrNilUint64Converter } + if check.IfNil(args.DataPool) { + return nil, process.ErrNilDataPoolHolder + } return &historyRepositoryFactory{ selfShardID: args.SelfShardID, @@ -56,6 +61,7 @@ func NewHistoryRepositoryFactory(args *ArgsHistoryRepositoryFactory) (dblookupex marshalizer: args.Marshalizer, hasher: args.Hasher, uInt64ByteSliceConverter: args.Uint64ByteSliceConverter, + dataPool: args.DataPool, }, nil } @@ -75,10 +81,16 @@ func (hpf *historyRepositoryFactory) Create() (dblookupext.HistoryRepository, er return nil, err } + mbsStorer, err := hpf.store.GetStorer(dataRetriever.MetaBlockUnit) + if err != nil { + return nil, err + } + esdtSuppliesHandler, err := esdtSupply.NewSuppliesProcessor( hpf.marshalizer, esdtSuppliesStorer, txLogsStorer, + mbsStorer, ) if err != nil { return nil, err @@ -109,6 +121,11 @@ func (hpf *historyRepositoryFactory) Create() (dblookupext.HistoryRepository, er return nil, err } + executionResultsStorer, err := hpf.store.GetStorer(dataRetriever.ExecutionResultsUnit) + if err != nil { + return nil, err + } + historyRepArgs := dblookupext.HistoryRepositoryArguments{ SelfShardID: hpf.selfShardID, Hasher: hpf.hasher, @@ -120,6 +137,8 @@ func (hpf *historyRepositoryFactory) Create() (dblookupext.HistoryRepository, er MiniblockHashByTxHashStorer: miniblockHashByTxHashStorer, EventsHashesByTxHashStorer: resultsHashesByTxHashStorer, ESDTSuppliesHandler: esdtSuppliesHandler, + ExecutionResultsStorer: executionResultsStorer, + DataPool: hpf.dataPool, } return dblookupext.NewHistoryRepository(historyRepArgs) } diff --git a/dblookupext/factory/historyRepositoryFactory_test.go b/dblookupext/factory/historyRepositoryFactory_test.go index 04419bbc093..e3483dbb1e5 100644 --- a/dblookupext/factory/historyRepositoryFactory_test.go +++ b/dblookupext/factory/historyRepositoryFactory_test.go @@ -14,6 +14,7 @@ import ( "github.com/multiversx/mx-chain-go/process" processMock "github.com/multiversx/mx-chain-go/process/mock" "github.com/multiversx/mx-chain-go/storage" + dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" "github.com/stretchr/testify/require" @@ -46,6 +47,12 @@ func TestNewHistoryRepositoryFactory(t *testing.T) { require.Equal(t, process.ErrNilUint64Converter, err) require.Nil(t, hrf) + argsNilDataPool := getArgs() + argsNilDataPool.DataPool = nil + hrf, err = factory.NewHistoryRepositoryFactory(argsNilDataPool) + require.Equal(t, process.ErrNilDataPoolHolder, err) + require.Nil(t, hrf) + hrf, err = factory.NewHistoryRepositoryFactory(args) require.NoError(t, err) require.False(t, check.IfNil(hrf)) @@ -120,5 +127,6 @@ func getArgs() *factory.ArgsHistoryRepositoryFactory { Marshalizer: &mock.MarshalizerMock{}, Hasher: &hashingMocks.HasherMock{}, Uint64ByteSliceConverter: &processMock.Uint64ByteSliceConverterMock{}, + DataPool: &dataRetrieverMock.PoolsHolderMock{}, } } diff --git a/dblookupext/historyRepository.go b/dblookupext/historyRepository.go index e3e6d98316d..462862ee9af 100644 --- a/dblookupext/historyRepository.go +++ b/dblookupext/historyRepository.go @@ -14,7 +14,9 @@ import ( "github.com/multiversx/mx-chain-core-go/data/typeConverters" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/common/logging" + "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/dblookupext/esdtSupply" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/storage" @@ -35,9 +37,11 @@ type HistoryRepositoryArguments struct { Uint64ByteSliceConverter typeConverters.Uint64ByteSliceConverter EpochByHashStorer storage.Storer EventsHashesByTxHashStorer storage.Storer + ExecutionResultsStorer storage.Storer Marshalizer marshal.Marshalizer Hasher hashing.Hasher ESDTSuppliesHandler SuppliesHandler + DataPool dataRetriever.PoolsHolder } type historyRepository struct { @@ -48,9 +52,11 @@ type historyRepository struct { uint64ByteSliceConverter typeConverters.Uint64ByteSliceConverter epochByHashIndex *epochByHashIndex eventsHashesByTxHashIndex *eventsHashesByTxHash + executionResultsProcessor *executionResultsProcessor marshalizer marshal.Marshalizer hasher hashing.Hasher esdtSuppliesHandler SuppliesHandler + dataPool dataRetriever.PoolsHolder // These maps temporarily hold notifications of "notarized at source or destination", to deal with unwanted concurrency effects // The unwanted concurrency effects could be accentuated by the fast db-replay-validate mechanism. @@ -97,11 +103,18 @@ func NewHistoryRepository(arguments HistoryRepositoryArguments) (*historyReposit if check.IfNil(arguments.Uint64ByteSliceConverter) { return nil, process.ErrNilUint64Converter } + if check.IfNil(arguments.ExecutionResultsStorer) { + return nil, process.ErrNilStore + } + if check.IfNil(arguments.DataPool) { + return nil, process.ErrNilDataPoolHolder + } hashToEpochIndex := newHashToEpochIndex(arguments.EpochByHashStorer, arguments.Marshalizer) deduplicationCacheForInsertMiniblockMetadata, _ := cache.NewLRUCache(sizeOfDeduplicationCache) eventsHashesToTxHashIndex := newEventsHashesByTxHash(arguments.EventsHashesByTxHashStorer, arguments.Marshalizer) + executionResultsProc := newExecutionResultsProcessor(arguments.ExecutionResultsStorer, arguments.Marshalizer) return &historyRepository{ selfShardID: arguments.SelfShardID, @@ -118,6 +131,8 @@ func NewHistoryRepository(arguments HistoryRepositoryArguments) (*historyReposit eventsHashesByTxHashIndex: eventsHashesToTxHashIndex, esdtSuppliesHandler: arguments.ESDTSuppliesHandler, uint64ByteSliceConverter: arguments.Uint64ByteSliceConverter, + executionResultsProcessor: executionResultsProc, + dataPool: arguments.DataPool, }, nil } @@ -129,58 +144,143 @@ func (hr *historyRepository) RecordBlock(blockHeaderHash []byte, scrResultsFromPool map[string]data.TransactionHandler, receiptsFromPool map[string]data.TransactionHandler, createdIntraShardMiniBlocks []*block.MiniBlock, - logs []*data.LogData) error { + logs []data.LogDataHandler, +) error { hr.recordBlockMutex.Lock() defer hr.recordBlockMutex.Unlock() - log.Debug("RecordBlock()", "nonce", blockHeader.GetNonce(), "blockHeaderHash", blockHeaderHash, "header type", fmt.Sprintf("%T", blockHeader)) - body, ok := blockBody.(*block.Body) if !ok { return errCannotCastToBlockBody } - epoch := blockHeader.GetEpoch() + err := hr.recordBlock(blockHeaderHash, blockHeader.GetEpoch(), blockHeader.GetNonce(), blockHeader.GetRound(), body.MiniBlocks) + if err != nil { + return err + } - err := hr.epochByHashIndex.saveEpochByHash(blockHeaderHash, epoch) + err = hr.recordExtraData(blockHeaderHash, blockHeader, scrResultsFromPool, receiptsFromPool, createdIntraShardMiniBlocks, logs) if err != nil { - return newErrCannotSaveEpochByHash("block header", blockHeaderHash, err) + return err } - for _, miniblock := range body.MiniBlocks { - if miniblock.Type == block.PeerBlock { - continue + err = hr.putHashByRound(blockHeaderHash, blockHeader) + if err != nil { + return err + } + + err = hr.executionResultsProcessor.saveExecutionResultsFromHeader(blockHeader) + if err != nil { + return err + } + + return nil +} + +func (hr *historyRepository) recordExtraData( + blockHeaderHash []byte, + blockHeader data.HeaderHandler, + scrResultsFromPool map[string]data.TransactionHandler, + receiptsFromPool map[string]data.TransactionHandler, + createdIntraShardMiniBlocks []*block.MiniBlock, + logs []data.LogDataHandler, +) error { + if blockHeader.IsHeaderV3() { + return hr.recordDataBasedOnExecutionResults(blockHeader) + } + + return hr.recordExecutionData(blockHeaderHash, blockHeader.GetEpoch(), blockHeader.GetNonce(), blockHeader.GetRound(), scrResultsFromPool, receiptsFromPool, createdIntraShardMiniBlocks, logs) +} + +func (hr *historyRepository) recordDataBasedOnExecutionResults(blockHeader data.HeaderHandler) error { + for _, executionResult := range blockHeader.GetExecutionResultsHandlers() { + headerHash := executionResult.GetHeaderHash() + + pool, err := common.GetCachedIntermediateTxs(hr.dataPool.PostProcessTransactions(), headerHash) + if err != nil { + return err + } + logs, err := common.GetCachedLogs(hr.dataPool.PostProcessTransactions(), headerHash) + if err != nil { + return err + } + body, err := common.GetCachedBody(hr.dataPool.ExecutedMiniBlocks(), hr.marshalizer, executionResult) + if err != nil { + return err + } + intraMbs, err := common.GetCachedMbs(hr.dataPool.ExecutedMiniBlocks(), hr.marshalizer, headerHash) + if err != nil { + return err } - err = hr.recordMiniblock(blockHeaderHash, blockHeader, miniblock, epoch) + headerNonce := executionResult.GetHeaderNonce() + headerEpoch := executionResult.GetHeaderEpoch() + headerRound := executionResult.GetHeaderRound() + err = hr.recordBlock(headerHash, headerEpoch, headerNonce, headerRound, body.MiniBlocks) if err != nil { - logging.LogErrAsErrorExceptAsDebugIfClosingError(log, err, "cannot record miniblock", - "type", miniblock.Type, "error", err) - continue + return err + } + + err = hr.recordExecutionData(headerHash, headerEpoch, headerNonce, headerRound, pool[block.SmartContractResultBlock], pool[block.ReceiptBlock], intraMbs, logs) + if err != nil { + return err } } + return nil +} + +func (hr *historyRepository) recordExecutionData(blockHeaderHash []byte, + epoch uint32, + nonce uint64, + round uint64, + scrResultsFromPool map[string]data.TransactionHandler, + receiptsFromPool map[string]data.TransactionHandler, + createdIntraShardMiniBlocks []*block.MiniBlock, + logs []data.LogDataHandler, +) error { + for _, miniBlock := range createdIntraShardMiniBlocks { - err = hr.recordMiniblock(blockHeaderHash, blockHeader, miniBlock, epoch) + err := hr.recordMiniblock(blockHeaderHash, nonce, round, miniBlock, epoch) if err != nil { logging.LogErrAsErrorExceptAsDebugIfClosingError(log, err, "cannot record in shard miniblock", "type", miniBlock.Type, "error", err) } } - err = hr.eventsHashesByTxHashIndex.saveResultsHashes(epoch, scrResultsFromPool, receiptsFromPool) + err := hr.eventsHashesByTxHashIndex.saveResultsHashes(epoch, scrResultsFromPool, receiptsFromPool) if err != nil { return err } - err = hr.esdtSuppliesHandler.ProcessLogs(blockHeader.GetNonce(), logs) + return hr.esdtSuppliesHandler.ProcessLogs(nonce, logs) +} + +func (hr *historyRepository) recordBlock( + blockHeaderHash []byte, + epoch uint32, + nonce uint64, + round uint64, + miniBlocks []*block.MiniBlock, +) error { + log.Debug("RecordBlock()", "nonce", nonce, "blockHeaderHash", blockHeaderHash) + + err := hr.epochByHashIndex.saveEpochByHash(blockHeaderHash, epoch) if err != nil { - return err + return newErrCannotSaveEpochByHash("block header", blockHeaderHash, err) } - err = hr.putHashByRound(blockHeaderHash, blockHeader) - if err != nil { - return err + for _, miniblock := range miniBlocks { + if miniblock.Type == block.PeerBlock { + continue + } + + err = hr.recordMiniblock(blockHeaderHash, nonce, round, miniblock, epoch) + if err != nil { + logging.LogErrAsErrorExceptAsDebugIfClosingError(log, err, "cannot record miniblock", + "type", miniblock.Type, "error", err) + continue + } } return nil @@ -191,7 +291,7 @@ func (hr *historyRepository) putHashByRound(blockHeaderHash []byte, header data. return hr.blockHashByRound.Put(roundToByteSlice, blockHeaderHash) } -func (hr *historyRepository) recordMiniblock(blockHeaderHash []byte, blockHeader data.HeaderHandler, miniblock *block.MiniBlock, epoch uint32) error { +func (hr *historyRepository) recordMiniblock(blockHeaderHash []byte, nonce uint64, round uint64, miniblock *block.MiniBlock, epoch uint32) error { miniblockHash, err := hr.computeMiniblockHash(miniblock) if err != nil { return err @@ -211,8 +311,8 @@ func (hr *historyRepository) recordMiniblock(blockHeaderHash []byte, blockHeader Epoch: epoch, HeaderHash: blockHeaderHash, MiniblockHash: miniblockHash, - Round: blockHeader.GetRound(), - HeaderNonce: blockHeader.GetNonce(), + Round: round, + HeaderNonce: nonce, SourceShardID: miniblock.GetSenderShardID(), DestinationShardID: miniblock.GetReceiverShardID(), } @@ -316,15 +416,21 @@ func (hr *historyRepository) OnNotarizedBlocks(shardID uint32, headers []data.He log.Trace("onNotarizedBlocks():", "shardID", shardID, "nonce", headerHandler.GetNonce(), "headerHash", headerHash, "type", fmt.Sprintf("%T", headerHandler)) - metaBlock, isMetaBlock := headerHandler.(*block.MetaBlock) + metaBlock, isMetaBlock := headerHandler.(data.MetaHeaderHandler) if isMetaBlock { - for _, miniBlock := range metaBlock.MiniBlockHeaders { + mbs, err := common.GetMiniBlockHeadersFromExecResult(metaBlock) + if err != nil { + log.Error("OnNotarizedBlocks.GetMiniBlockHeadersFromExecResult:", "error", err) + continue + } + + for _, miniBlock := range mbs { hr.onNotarizedMiniblock(headerHandler.GetNonce(), headerHash, headerHandler.GetShardID(), miniBlock) } - for _, shardData := range metaBlock.ShardInfo { - shardDataCopy := shardData - hr.onNotarizedInMetaBlock(headerHandler.GetNonce(), headerHash, &shardDataCopy) + for _, shardData := range metaBlock.GetShardInfoHandlers() { + shardDataCopy := shardData.ShallowClone() + hr.onNotarizedInMetaBlock(headerHandler.GetNonce(), headerHash, shardDataCopy) } } else { log.Error("onNotarizedBlocks(): unexpected type of header", "type", fmt.Sprintf("%T", headerHandler)) @@ -334,27 +440,27 @@ func (hr *historyRepository) OnNotarizedBlocks(shardID uint32, headers []data.He hr.consumePendingNotificationsWithLock() } -func (hr *historyRepository) onNotarizedInMetaBlock(metaBlockNonce uint64, metaBlockHash []byte, shardData *block.ShardData) { +func (hr *historyRepository) onNotarizedInMetaBlock(metaBlockNonce uint64, metaBlockHash []byte, shardData data.ShardDataHandler) { if metaBlockNonce < 1 { return } - for _, miniblockHeader := range shardData.GetShardMiniBlockHeaders() { + for _, miniblockHeader := range shardData.GetShardMiniBlockHeaderHandlers() { hr.onNotarizedMiniblock(metaBlockNonce, metaBlockHash, shardData.GetShardID(), miniblockHeader) } } -func (hr *historyRepository) onNotarizedMiniblock(metaBlockNonce uint64, metaBlockHash []byte, shardOfContainingBlock uint32, miniblockHeader block.MiniBlockHeader) { - miniblockHash := miniblockHeader.Hash - isIntra := miniblockHeader.SenderShardID == miniblockHeader.ReceiverShardID - isToMeta := miniblockHeader.ReceiverShardID == core.MetachainShardId - isNotarizedAtSource := miniblockHeader.SenderShardID == shardOfContainingBlock - isNotarizedAtDestination := miniblockHeader.ReceiverShardID == shardOfContainingBlock +func (hr *historyRepository) onNotarizedMiniblock(metaBlockNonce uint64, metaBlockHash []byte, shardOfContainingBlock uint32, miniblockHeader data.MiniBlockHeaderHandler) { + miniblockHash := miniblockHeader.GetHash() + isIntra := miniblockHeader.GetSenderShardID() == miniblockHeader.GetReceiverShardID() + isToMeta := miniblockHeader.GetReceiverShardID() == core.MetachainShardId + isNotarizedAtSource := miniblockHeader.GetSenderShardID() == shardOfContainingBlock + isNotarizedAtDestination := miniblockHeader.GetReceiverShardID() == shardOfContainingBlock isNotarizedAtBoth := isIntra || isToMeta - notFromMe := miniblockHeader.SenderShardID != hr.selfShardID - notToMe := miniblockHeader.ReceiverShardID != hr.selfShardID - isPeerMiniblock := miniblockHeader.Type == block.PeerBlock + notFromMe := miniblockHeader.GetSenderShardID() != hr.selfShardID + notToMe := miniblockHeader.GetReceiverShardID() != hr.selfShardID + isPeerMiniblock := miniblockHeader.GetTypeInt32() == int32(block.PeerBlock) iDontCare := (notFromMe && notToMe) || isPeerMiniblock if iDontCare { return @@ -365,7 +471,7 @@ func (hr *historyRepository) onNotarizedMiniblock(metaBlockNonce uint64, metaBlo "metaBlockHash", metaBlockHash, "shardOfContainingBlock", shardOfContainingBlock, "miniblock", miniblockHash, - "direction", fmt.Sprintf("[%d -> %d]", miniblockHeader.SenderShardID, miniblockHeader.ReceiverShardID), + "direction", fmt.Sprintf("[%d -> %d]", miniblockHeader.GetSenderShardID(), miniblockHeader.GetReceiverShardID()), ) if isNotarizedAtBoth { diff --git a/dblookupext/historyRepository_test.go b/dblookupext/historyRepository_test.go index 71ab71a3171..b94c3b07bbd 100644 --- a/dblookupext/historyRepository_test.go +++ b/dblookupext/historyRepository_test.go @@ -2,30 +2,39 @@ package dblookupext import ( "errors" + "math/big" "sync" "testing" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/data/receipt" + "github.com/multiversx/mx-chain-core-go/data/smartContractResult" + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/common/mock" "github.com/multiversx/mx-chain-go/dblookupext/esdtSupply" epochStartMocks "github.com/multiversx/mx-chain-go/epochStart/mock" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/storage" + dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/genericMocks" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" + "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +var expectedError = errors.New("expected error") + func createMockHistoryRepoArgs(epoch uint32) HistoryRepositoryArguments { sp, _ := esdtSupply.NewSuppliesProcessor(&mock.MarshalizerMock{}, &storageStubs.StorerStub{ GetCalled: func(key []byte) ([]byte, error) { return nil, storage.ErrKeyNotFound }, - }, &storageStubs.StorerStub{}) + }, &storageStubs.StorerStub{}, &storageStubs.StorerStub{}) args := HistoryRepositoryArguments{ SelfShardID: 0, @@ -34,10 +43,12 @@ func createMockHistoryRepoArgs(epoch uint32) HistoryRepositoryArguments { EpochByHashStorer: genericMocks.NewStorerMockWithEpoch(epoch), EventsHashesByTxHashStorer: genericMocks.NewStorerMockWithEpoch(epoch), BlockHashByRound: genericMocks.NewStorerMockWithEpoch(epoch), + ExecutionResultsStorer: genericMocks.NewStorerMockWithEpoch(epoch), Marshalizer: &mock.MarshalizerMock{}, Hasher: &hashingMocks.HasherMock{}, ESDTSuppliesHandler: sp, Uint64ByteSliceConverter: &epochStartMocks.Uint64ByteSliceConverterMock{}, + DataPool: &dataRetrieverMock.PoolsHolderMock{}, } return args @@ -88,12 +99,30 @@ func TestNewHistoryRepository(t *testing.T) { require.Nil(t, repo) require.Equal(t, process.ErrNilUint64Converter, err) + args = createMockHistoryRepoArgs(0) + args.DataPool = nil + repo, err = NewHistoryRepository(args) + require.Nil(t, repo) + require.Equal(t, process.ErrNilDataPoolHolder, err) + args = createMockHistoryRepoArgs(0) repo, err = NewHistoryRepository(args) require.Nil(t, err) require.NotNil(t, repo) } +func TestHistoryRepository_RecordBlockErrCannotCastToBlockBody(t *testing.T) { + t.Parallel() + + args := createMockHistoryRepoArgs(0) + + repo, err := NewHistoryRepository(args) + require.Nil(t, err) + + err = repo.RecordBlock([]byte("headerHash"), &block.Header{}, nil, nil, nil, nil, nil) + require.Equal(t, errCannotCastToBlockBody, err) +} + func TestHistoryRepository_RecordBlockInvalidBlockRoundByHashStorerExpectError(t *testing.T) { t.Parallel() @@ -741,3 +770,403 @@ func TestHistoryRepository_ConcurrentlyRecordAndNotarizeSameBlockMultipleTimes(t require.Equal(t, 4001, int(metadata.NotarizedAtDestinationInMetaNonce)) require.Equal(t, []byte("metablockFoo"), metadata.NotarizedAtDestinationInMetaHash) } + +func TestRecordHeaderV3(t *testing.T) { + t.Parallel() + + t.Run("record block v3 should work no execution results", func(t *testing.T) { + t.Parallel() + + args := createMockHistoryRepoArgs(42) + repo, err := NewHistoryRepository(args) + require.Nil(t, err) + + header := &block.HeaderV3{} + body := &block.Body{} + + headerHash := []byte("headerHash") + err = repo.RecordBlock(headerHash, header, body, nil, nil, nil, nil) + require.Nil(t, err) + }) + + t.Run("record block v3 should work", func(t *testing.T) { + t.Parallel() + + args := createMockHistoryRepoArgs(42) + args.DataPool = dataRetrieverMock.NewPoolsHolderMock() + repo, err := NewHistoryRepository(args) + require.Nil(t, err) + + executionResultHeaderHash := []byte("executionResultHeaderHash") + mb := &block.MiniBlock{SenderShardID: 0} + mbHash1, _ := repo.computeMiniblockHash(mb) + header := &block.HeaderV3{ + Nonce: 100, + Round: 101, + Epoch: 42, + ExecutionResults: []*block.ExecutionResult{ + { + + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: executionResultHeaderHash, + HeaderNonce: 99, + HeaderRound: 100, + HeaderEpoch: 42, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: mbHash1, + }, + }, + }, + }, + } + + mbBytes, _ := repo.marshalizer.Marshal(mb) + args.DataPool.ExecutedMiniBlocks().Put(mbHash1, mbBytes, 1) + + cachedIntermediateTxsMap := map[block.Type]map[string]data.TransactionHandler{} + cachedIntermediateTxsMap[block.SmartContractResultBlock] = map[string]data.TransactionHandler{ + "h1": &smartContractResult.SmartContractResult{}, + } + cachedIntermediateTxsMap[block.ReceiptBlock] = map[string]data.TransactionHandler{ + "r1": &receipt.Receipt{}, + } + args.DataPool.PostProcessTransactions().Put(executionResultHeaderHash, cachedIntermediateTxsMap, 1) + + expectedLogs := []data.LogDataHandler{ + &transaction.LogData{ + Log: &transaction.Log{}, + TxHash: "t1", + }, + } + logsKey := common.PrepareLogEventsKey(executionResultHeaderHash) + args.DataPool.PostProcessTransactions().Put(logsKey, expectedLogs, 1) + + expectedMbs := []*block.MiniBlock{ + {SenderShardID: 0}, + } + + args.DataPool.ExecutedMiniBlocks().Put(executionResultHeaderHash, expectedMbs, 0) + + body := &block.Body{} + + headerHash := []byte("headerHash") + err = repo.RecordBlock(headerHash, header, body, nil, nil, nil, nil) + require.Nil(t, err) + + epoch, err := repo.GetEpochByHash(executionResultHeaderHash) + require.Nil(t, err) + require.Equal(t, 42, int(epoch)) + + epoch, err = repo.GetEpochByHash(mbHash1) + require.Nil(t, err) + require.Equal(t, 42, int(epoch)) + }) + + t.Run("record block v3 should error because the headerHash is not found in cache", func(t *testing.T) { + t.Parallel() + + args := createMockHistoryRepoArgs(42) + args.DataPool = dataRetrieverMock.NewPoolsHolderMock() + repo, err := NewHistoryRepository(args) + require.Nil(t, err) + + executionResultHeaderHash := []byte("executionResultHeaderHash") + header := &block.HeaderV3{ + ExecutionResults: []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: executionResultHeaderHash, + }, + }, + }, + } + + body := &block.Body{} + headerHash := []byte("headerHash") + err = repo.RecordBlock(headerHash, header, body, nil, nil, nil, nil) + require.NotNil(t, err) + require.ErrorContains(t, err, common.ErrMissingCachedTransactions.Error()) + }) + + t.Run("record block v3 should error because logs were not found in dataPool", func(t *testing.T) { + t.Parallel() + + args := createMockHistoryRepoArgs(42) + args.DataPool = dataRetrieverMock.NewPoolsHolderMock() + repo, err := NewHistoryRepository(args) + require.Nil(t, err) + + executionResultHeaderHash := []byte("executionResultHeaderHash") + header := &block.HeaderV3{ + ExecutionResults: []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: executionResultHeaderHash, + }, + }, + }, + } + + cachedIntermediateTxsMap := map[block.Type]map[string]data.TransactionHandler{} + // add the header hash + args.DataPool.PostProcessTransactions().Put(executionResultHeaderHash, cachedIntermediateTxsMap, 1) + + body := &block.Body{} + headerHash := []byte("headerHash") + err = repo.RecordBlock(headerHash, header, body, nil, nil, nil, nil) + require.NotNil(t, err) + require.ErrorContains(t, err, common.ErrMissingCachedLogs.Error()) + }) + + t.Run("record block v3 should error because mini blocks were not cached", func(t *testing.T) { + t.Parallel() + + args := createMockHistoryRepoArgs(42) + args.DataPool = dataRetrieverMock.NewPoolsHolderMock() + repo, err := NewHistoryRepository(args) + require.Nil(t, err) + + executionResultHeaderHash := []byte("executionResultHeaderHash") + header := &block.HeaderV3{ + ExecutionResults: []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: executionResultHeaderHash, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("mbHash"), + }, + }, + }, + }, + } + + cachedIntermediateTxsMap := map[block.Type]map[string]data.TransactionHandler{} + // add the header hash + args.DataPool.PostProcessTransactions().Put(executionResultHeaderHash, cachedIntermediateTxsMap, 1) + + expectedLogs := []data.LogDataHandler{ + &transaction.LogData{ + Log: &transaction.Log{}, + TxHash: "t1", + }, + } + logsKey := common.PrepareLogEventsKey(executionResultHeaderHash) + // add the logs + args.DataPool.PostProcessTransactions().Put(logsKey, expectedLogs, 1) + + body := &block.Body{} + headerHash := []byte("headerHash") + err = repo.RecordBlock(headerHash, header, body, nil, nil, nil, nil) + require.NotNil(t, err) + require.ErrorContains(t, err, process.ErrMissingMiniBlock.Error()) + }) + + t.Run("record block v3 should error because intra mini blocks were not found", func(t *testing.T) { + t.Parallel() + + args := createMockHistoryRepoArgs(42) + args.DataPool = dataRetrieverMock.NewPoolsHolderMock() + repo, err := NewHistoryRepository(args) + require.Nil(t, err) + + executionResultHeaderHash := []byte("executionResultHeaderHash") + mb := &block.MiniBlock{SenderShardID: 0} + mbHash1, _ := repo.computeMiniblockHash(mb) + header := &block.HeaderV3{ + ExecutionResults: []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: executionResultHeaderHash, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: mbHash1, + }, + }, + }, + }, + } + + mbBytes, _ := repo.marshalizer.Marshal(mb) + // add the mini blocks + args.DataPool.ExecutedMiniBlocks().Put(mbHash1, mbBytes, 1) + + cachedIntermediateTxsMap := map[block.Type]map[string]data.TransactionHandler{} + // add the header hash + args.DataPool.PostProcessTransactions().Put(executionResultHeaderHash, cachedIntermediateTxsMap, 1) + + expectedLogs := []data.LogDataHandler{ + &transaction.LogData{ + Log: &transaction.Log{}, + TxHash: "t1", + }, + } + logsKey := common.PrepareLogEventsKey(executionResultHeaderHash) + // add the logs + args.DataPool.PostProcessTransactions().Put(logsKey, expectedLogs, 1) + + body := &block.Body{} + headerHash := []byte("headerHash") + err = repo.RecordBlock(headerHash, header, body, nil, nil, nil, nil) + require.NotNil(t, err) + require.ErrorContains(t, err, common.ErrMissingMiniBlock.Error()) + }) + + t.Run("record block v3 should error because of Marshal on recordBlock", func(t *testing.T) { + t.Parallel() + + args := createMockHistoryRepoArgs(42) + + expectedError := errors.New("expected error") + args.Marshalizer = &marshallerMock.MarshalizerStub{MarshalCalled: func(obj interface{}) ([]byte, error) { + return nil, expectedError + }} + + args.DataPool = dataRetrieverMock.NewPoolsHolderMock() + repo, err := NewHistoryRepository(args) + require.Nil(t, err) + + header := &block.HeaderV3{} + body := &block.Body{} + + headerHash := []byte("headerHash") + + err = repo.RecordBlock(headerHash, header, body, nil, nil, nil, nil) + require.ErrorContains(t, err, expectedError.Error()) + }) +} + +func TestHistoryRepository_GetResultsHashesByTxHashShouldError(t *testing.T) { + t.Parallel() + + t.Run("should error ErrNotFoundInStorage", func(t *testing.T) { + t.Parallel() + + args := createMockHistoryRepoArgs(42) + + repo, err := NewHistoryRepository(args) + require.NoError(t, err) + + results, err := repo.GetResultsHashesByTxHash([]byte("txHash1"), 42) + require.Nil(t, results) + require.ErrorContains(t, err, ErrNotFoundInStorage.Error()) + }) +} + +func TestHistoryRepository_RevertBlockShouldError(t *testing.T) { + t.Parallel() + + t.Run("should return nil because of nil body", func(t *testing.T) { + t.Parallel() + + args := createMockHistoryRepoArgs(42) + repo, err := NewHistoryRepository(args) + require.NoError(t, err) + + err = repo.RevertBlock(&block.Header{}, nil) + require.NoError(t, err) + }) + + t.Run("should return nil because of nil header", func(t *testing.T) { + t.Parallel() + + args := createMockHistoryRepoArgs(42) + repo, err := NewHistoryRepository(args) + require.NoError(t, err) + + err = repo.RevertBlock(nil, &block.Body{}) + require.NoError(t, err) + }) + + t.Run("should error because of unmarshal", func(t *testing.T) { + t.Parallel() + + sp, _ := esdtSupply.NewSuppliesProcessor(&mock.MarshalizerMock{ + Fail: true, + }, &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + return nil, storage.ErrKeyNotFound + }, + }, &storageStubs.StorerStub{}, &storageStubs.StorerStub{}) + + args := createMockHistoryRepoArgs(42) + args.ESDTSuppliesHandler = sp + + repo, err := NewHistoryRepository(args) + require.NoError(t, err) + + err = repo.RevertBlock(&block.Header{}, &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{[]byte("txHash1")}, + }, + }, + }) + require.Equal(t, mock.ErrMockMarshalizer, err) + }) +} + +func TestHistoryRepository_GetESDTSupply(t *testing.T) { + t.Parallel() + + t.Run("should return error", func(t *testing.T) { + t.Parallel() + + args := createMockHistoryRepoArgs(42) + sp, _ := esdtSupply.NewSuppliesProcessor(&mock.MarshalizerMock{}, &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + return nil, expectedError + }, + }, &storageStubs.StorerStub{}, &storageStubs.StorerStub{}) + + args.ESDTSuppliesHandler = sp + repo, err := NewHistoryRepository(args) + require.Nil(t, err) + + res, err := repo.GetESDTSupply("token1") + require.Nil(t, res) + require.Equal(t, expectedError, err) + }) + + t.Run("should return SupplyESDTZero because of ErrKeyNotFound", func(t *testing.T) { + t.Parallel() + + args := createMockHistoryRepoArgs(42) + repo, err := NewHistoryRepository(args) + require.Nil(t, err) + + res, err := repo.GetESDTSupply("token1") + require.Nil(t, err) + + require.Equal(t, big.NewInt(0), res.Supply) + require.Equal(t, big.NewInt(0), res.Burned) + require.Equal(t, big.NewInt(0), res.Minted) + }) +} + +func TestHistoryRepository_IsEnabled(t *testing.T) { + t.Parallel() + + args := createMockHistoryRepoArgs(42) + repo, err := NewHistoryRepository(args) + require.Nil(t, err) + + require.True(t, repo.IsEnabled()) +} + +func TestHistoryRepository_IsInterfaceNil(t *testing.T) { + t.Parallel() + + args := createMockHistoryRepoArgs(42) + repo, err := NewHistoryRepository(args) + require.Nil(t, err) + + require.False(t, repo.IsInterfaceNil()) + + var nilRepo *historyRepository + require.True(t, nilRepo.IsInterfaceNil()) +} diff --git a/dblookupext/interface.go b/dblookupext/interface.go index b026b9d3f3b..36be5587894 100644 --- a/dblookupext/interface.go +++ b/dblookupext/interface.go @@ -20,7 +20,7 @@ type HistoryRepository interface { scrResultsFromPool map[string]data.TransactionHandler, receiptsFromPool map[string]data.TransactionHandler, createdIntraShardMiniBlocks []*block.MiniBlock, - logs []*data.LogData) error + logs []data.LogDataHandler) error OnNotarizedBlocks(shardID uint32, headers []data.HeaderHandler, headersHashes [][]byte) GetMiniblockMetadataByTxHash(hash []byte) (*MiniblockMetadata, error) GetEpochByHash(hash []byte) (uint32, error) @@ -42,7 +42,7 @@ type BlockTracker interface { // SuppliesHandler defines the interface of a supplies processor type SuppliesHandler interface { - ProcessLogs(blockNonce uint64, logs []*data.LogData) error + ProcessLogs(blockNonce uint64, logs []data.LogDataHandler) error RevertChanges(header data.HeaderHandler, body data.BodyHandler) error GetESDTSupply(token string) (*esdtSupply.SupplyESDT, error) IsInterfaceNil() bool diff --git a/debug/factory/interceptorDebuggerFactory.go b/debug/factory/interceptorDebuggerFactory.go index e379499fe6a..12b35356c70 100644 --- a/debug/factory/interceptorDebuggerFactory.go +++ b/debug/factory/interceptorDebuggerFactory.go @@ -6,10 +6,10 @@ import ( ) // NewInterceptorDebuggerFactory will instantiate an InterceptorDebugHandler based on the provided config -func NewInterceptorDebuggerFactory(config config.InterceptorResolverDebugConfig) (InterceptorDebugHandler, error) { +func NewInterceptorDebuggerFactory(config config.InterceptorResolverDebugConfig, ntpTime handler.NTPTime) (InterceptorDebugHandler, error) { if !config.Enabled { return handler.NewDisabledInterceptorDebugHandler(), nil } - return handler.NewInterceptorDebugHandler(config) + return handler.NewInterceptorDebugHandler(config, ntpTime) } diff --git a/debug/factory/interceptorDebuggerFactory_test.go b/debug/factory/interceptorDebuggerFactory_test.go index dbce974cf4d..dfb6cd661ed 100644 --- a/debug/factory/interceptorDebuggerFactory_test.go +++ b/debug/factory/interceptorDebuggerFactory_test.go @@ -5,6 +5,7 @@ import ( "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/debug/handler" + "github.com/multiversx/mx-chain-go/testscommon" "github.com/stretchr/testify/assert" ) @@ -15,6 +16,7 @@ func TestNewInterceptorResolverDebuggerFactory_DisabledShouldWork(t *testing.T) config.InterceptorResolverDebugConfig{ Enabled: false, }, + &testscommon.SyncTimerStub{}, ) assert.Nil(t, err) @@ -30,12 +32,13 @@ func TestNewInterceptorResolverDebuggerFactory_InterceptorResolver(t *testing.T) Enabled: true, CacheSize: 1000, }, + &testscommon.SyncTimerStub{}, ) assert.Nil(t, err) expected, _ := handler.NewInterceptorDebugHandler(config.InterceptorResolverDebugConfig{ Enabled: false, CacheSize: 1, - }) + }, &testscommon.SyncTimerStub{}) assert.IsType(t, expected, idf) } diff --git a/debug/factory/interface.go b/debug/factory/interface.go index 275acb77b6d..f1bd8424f1c 100644 --- a/debug/factory/interface.go +++ b/debug/factory/interface.go @@ -1,12 +1,21 @@ package factory +import ( + "github.com/multiversx/mx-chain-communication-go/p2p" + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-go/epochStart" + "github.com/multiversx/mx-chain-go/process" +) + // InterceptorDebugHandler hold information about requested and received information type InterceptorDebugHandler interface { + EpochStartEventHandler() epochStart.ActionHandler LogRequestedData(topic string, hashes [][]byte, numReqIntra int, numReqCross int) LogReceivedHashes(topic string, hashes [][]byte) LogProcessedHashes(topic string, hashes [][]byte, err error) LogFailedToResolveData(topic string, hash []byte, err error) LogSucceededToResolveData(topic string, hash []byte) + LogReceivedData(data process.InterceptedData, msg p2p.MessageP2P, fromConnectedPeer core.PeerID) Query(topic string) []string Close() error IsInterfaceNil() bool diff --git a/debug/handler/broadcastDebug.go b/debug/handler/broadcastDebug.go new file mode 100644 index 00000000000..40a7cc29a56 --- /dev/null +++ b/debug/handler/broadcastDebug.go @@ -0,0 +1,135 @@ +package handler + +import ( + "encoding/hex" + "fmt" + "strings" + "sync" + "time" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/p2p" + "github.com/multiversx/mx-chain-go/process" +) + +const separator = "_" + +type receivedEvent struct { + from string + firstTimeReceivedMilli int64 + numReceived int + isCross bool +} + +type broadcastDebugHandler struct { + ntpTime NTPTime + mutex sync.RWMutex + messageTypes map[string]struct{} + receivedBroadcast map[string]map[string]*receivedEvent +} + +// NewBroadcastDebug will create a new instance of *interceptorTxDebug +func NewBroadcastDebug(config config.BroadcastStatisticsConfig, ntpTime NTPTime) (*broadcastDebugHandler, error) { + if check.IfNil(ntpTime) { + return nil, process.ErrNilSyncTimer + } + + messageTypes := make(map[string]struct{}) + for _, messageType := range config.Messages { + messageTypes[messageType] = struct{}{} + } + + return &broadcastDebugHandler{ + receivedBroadcast: make(map[string]map[string]*receivedEvent), + messageTypes: messageTypes, + ntpTime: ntpTime, + }, nil +} + +// Process will process the intercept data and add statistics about p2p message +func (bd *broadcastDebugHandler) Process(data process.InterceptedData, msg p2p.MessageP2P, fromConnectedPeer core.PeerID) { + if msg.BroadcastMethod() != p2p.Broadcast { + return + } + + bd.mutex.Lock() + defer bd.mutex.Unlock() + messageType := data.Type() + if _, ok := bd.messageTypes[messageType]; !ok { + return + } + + _, found := bd.receivedBroadcast[messageType] + if !found { + bd.receivedBroadcast[messageType] = make(map[string]*receivedEvent) + } + + hexHash := hex.EncodeToString(data.Hash()) + originatorPretty := msg.Peer().Pretty() + mapID := computeMapID(hexHash, originatorPretty, msg.Topic()) + + receivedE, found := bd.receivedBroadcast[messageType][mapID] + if !found { + bd.receivedBroadcast[messageType][mapID] = &receivedEvent{ + from: fromConnectedPeer.Pretty(), + numReceived: 1, + firstTimeReceivedMilli: bd.getCurrentTimeStampMilli(), + isCross: isCross(msg.Topic()), + } + return + } + + receivedE.numReceived++ +} + +func (bd *broadcastDebugHandler) getCurrentTimeStampMilli() int64 { + return bd.ntpTime.CurrentTime().UnixMilli() +} + +// PrintReceivedTxsBroadcastAndCleanRecords will print information about received transactions from current epoch and clean records +func (bd *broadcastDebugHandler) PrintReceivedTxsBroadcastAndCleanRecords() { + log.Info("Received broadcast information") + + bd.mutex.Lock() + defer bd.mutex.Unlock() + + for messageType := range bd.receivedBroadcast { + mapHashEvent := bd.receivedBroadcast[messageType] + for id, et := range mapHashEvent { + hash, originator := getHashAndOriginatorFromID(id) + + log.Debug("broadcast record", + "hash", hash, + "type", messageType, + "originator", originator, + "from", et.from, + "first received", time.Unix(0, et.firstTimeReceivedMilli*int64(time.Millisecond)).Format("2006-01-02 15:04:05.000"), + "times received", et.numReceived, + "is-cross", et.isCross) + } + } + + bd.receivedBroadcast = make(map[string]map[string]*receivedEvent) +} + +func isCross(topic string) bool { + split := strings.Split(topic, separator) + + return len(split) > 2 +} + +func computeMapID(key1, key2, key3 string) string { + return fmt.Sprintf("%s%s%s%s%s", key1, separator, key2, separator, key3) +} + +func getHashAndOriginatorFromID(id string) (string, string) { + split := strings.Split(id, separator) + return split[0], split[1] +} + +// IsInterfaceNil returns true if there is no value under the interface +func (bd *broadcastDebugHandler) IsInterfaceNil() bool { + return bd == nil +} diff --git a/debug/handler/broadcastDebug_test.go b/debug/handler/broadcastDebug_test.go new file mode 100644 index 00000000000..1fa1daca0be --- /dev/null +++ b/debug/handler/broadcastDebug_test.go @@ -0,0 +1,211 @@ +package handler + +import ( + "testing" + + "github.com/multiversx/mx-chain-communication-go/p2p" + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/ntp" + "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/p2pmocks" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBroadcastDebug_ProcessMultipleMessageTypes(t *testing.T) { + t.Parallel() + + _ = logger.SetLogLevel("*:DEBUG") + + cfg := config.BroadcastStatisticsConfig{ + Messages: []string{ + "intercepted tx", + "intercepted miniblock", + "intercepted header", + "intercepted heartbeat", + }, + } + + syncer := ntp.NewSyncTime(testscommon.NewNTPGoogleConfig(), nil) + syncer.StartSyncingTime() + defer func() { + _ = syncer.Close() + }() + + id, err := NewBroadcastDebug(cfg, syncer) + require.NoError(t, err) + require.NotNil(t, id) + require.False(t, id.IsInterfaceNil()) + + testCases := []struct { + name string + messageType string + hash []byte + originator string + fromPeer string + topic string + }{ + { + name: "transaction message", + messageType: "intercepted tx", + hash: []byte("tx_hash_1"), + originator: "originator_peer", + fromPeer: "connected_peer_1", + topic: "topic_1", + }, + { + name: "miniblock message", + messageType: "intercepted miniblock", + hash: []byte("miniblock_hash_1"), + originator: "originator_peer", + fromPeer: "connected_peer_2", + topic: "topic_1", + }, + { + name: "header message", + messageType: "intercepted header", + hash: []byte("header_hash_1"), + originator: "originator_peer", + fromPeer: "connected_peer_3", + topic: "topic_1", + }, + { + name: "heartbeat message", + messageType: "intercepted heartbeat", + hash: []byte("heartbeat_hash_1"), + originator: "originator_peer", + fromPeer: "connected_peer_4", + topic: "topic_1", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + mockMessage := &p2pmocks.P2PMessageMock{ + BroadcastMethodField: p2p.Broadcast, + FromField: []byte(tc.originator), + TopicField: tc.topic, + PeerField: core.PeerID(tc.originator), + } + mockData := &testscommon.InterceptedDataStub{ + TypeCalled: func() string { + return tc.messageType + }, + HashCalled: func() []byte { + return tc.hash + }, + } + + fromPeerID := core.PeerID(tc.fromPeer) + + id.Process(mockData, mockMessage, fromPeerID) + + hexHash := "74785f686173685f31" // hex encoded "tx_hash_1" + switch tc.messageType { + case "intercepted miniblock": + hexHash = "6d696e69626c6f636b5f686173685f31" // hex encoded "miniblock_hash_1" + case "intercepted header": + hexHash = "6865616465725f686173685f31" // hex encoded "header_hash_1" + case "intercepted heartbeat": + hexHash = "6865617274626561745f686173685f31" // hex encoded "heartbeat_hash_1" + } + + assert.Contains(t, id.receivedBroadcast, tc.messageType) + + originatorPretty := core.PeerID(tc.originator).Pretty() + messageID := computeMapID(hexHash, originatorPretty, tc.topic) + assert.Contains(t, id.receivedBroadcast[tc.messageType], messageID) + + ev := id.receivedBroadcast[tc.messageType][messageID] + assert.Equal(t, fromPeerID.Pretty(), ev.from) + assert.Equal(t, 1, ev.numReceived) + assert.Greater(t, ev.firstTimeReceivedMilli, int64(0)) + }) + } + + t.Run("duplicate message processing", func(t *testing.T) { + mockMessage := &p2pmocks.P2PMessageMock{ + BroadcastMethodField: p2p.Broadcast, + FromField: []byte("originator_peer"), + TopicField: "topic_1", + PeerField: core.PeerID("originator_peer"), + } + + mockData := &testscommon.InterceptedDataStub{ + TypeCalled: func() string { + return "intercepted tx" + }, + HashCalled: func() []byte { + return []byte("tx_hash_1") + }, + } + + fromPeerID := core.PeerID("connected_peer_1") + + id.Process(mockData, mockMessage, fromPeerID) + + hexHash := "74785f686173685f31" + originatorPretty := core.PeerID(mockMessage.From()).Pretty() + messageID := computeMapID(hexHash, originatorPretty, "topic_1") + ev := id.receivedBroadcast["intercepted tx"][messageID] + assert.Equal(t, 2, ev.numReceived) + }) + + t.Run("message type not in configuration", func(t *testing.T) { + mockMessage := &p2pmocks.P2PMessageMock{ + BroadcastMethodField: p2p.Broadcast, + FromField: []byte("originator_peer"), + } + + mockData := &testscommon.InterceptedDataStub{ + TypeCalled: func() string { + return "unknown message type" + }, + HashCalled: func() []byte { + return []byte("unknown_hash") + }, + } + + fromPeerID := core.PeerID("connected_peer_6") + initialCount := len(id.receivedBroadcast) + + id.Process(mockData, mockMessage, fromPeerID) + + assert.Equal(t, initialCount, len(id.receivedBroadcast)) + }) + + t.Run("non-broadcast message", func(t *testing.T) { + nonBroadcastMessage := &p2pmocks.P2PMessageMock{ + BroadcastMethodField: p2p.Direct, + FromField: []byte("originator_peer"), + } + + mockData := &testscommon.InterceptedDataStub{ + TypeCalled: func() string { + return "intercepted tx" + }, + HashCalled: func() []byte { + return []byte("direct_tx_hash") + }, + } + + fromPeerID := core.PeerID("connected_peer_7") + initialCount := len(id.receivedBroadcast["intercepted tx"]) + + id.Process(mockData, nonBroadcastMessage, fromPeerID) + + assert.Equal(t, initialCount, len(id.receivedBroadcast["intercepted tx"])) + }) + + id.PrintReceivedTxsBroadcastAndCleanRecords() +} + +func TestIsCross(t *testing.T) { + t.Parallel() + + require.True(t, isCross("transactions_0_2")) + require.True(t, isCross("transactions_0_3")) + require.False(t, isCross("transactions_2")) +} diff --git a/debug/handler/disabledBroadcastDebugHandler.go b/debug/handler/disabledBroadcastDebugHandler.go new file mode 100644 index 00000000000..4e99a562dc6 --- /dev/null +++ b/debug/handler/disabledBroadcastDebugHandler.go @@ -0,0 +1,22 @@ +package handler + +import ( + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-go/p2p" + "github.com/multiversx/mx-chain-go/process" +) + +type disabledBroadcastDebug struct{} + +// NewDisabledBroadcastDebug will create a new instance of *disabledBroadcastDebug +func NewDisabledBroadcastDebug() *disabledBroadcastDebug { + return new(disabledBroadcastDebug) +} + +// Process - +func (db *disabledBroadcastDebug) Process(_ process.InterceptedData, _ p2p.MessageP2P, _ core.PeerID) { + +} + +// PrintReceivedTxsBroadcastAndCleanRecords - +func (db *disabledBroadcastDebug) PrintReceivedTxsBroadcastAndCleanRecords() {} diff --git a/debug/handler/disabledInterceptorDebugHandler.go b/debug/handler/disabledInterceptorDebugHandler.go index 6ce20cc46cb..9b453a7b8c7 100644 --- a/debug/handler/disabledInterceptorDebugHandler.go +++ b/debug/handler/disabledInterceptorDebugHandler.go @@ -1,5 +1,15 @@ package handler +import ( + "github.com/multiversx/mx-chain-communication-go/p2p" + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/epochStart" + "github.com/multiversx/mx-chain-go/epochStart/notifier" + "github.com/multiversx/mx-chain-go/process" +) + type disabledInterceptorDebugHandler struct { } @@ -38,6 +48,21 @@ func (didh *disabledInterceptorDebugHandler) Close() error { return nil } +// LogReceivedData - +func (didh *disabledInterceptorDebugHandler) LogReceivedData(_ process.InterceptedData, _ p2p.MessageP2P, _ core.PeerID) { +} + +// EpochStartEventHandler - +func (didh *disabledInterceptorDebugHandler) EpochStartEventHandler() epochStart.ActionHandler { + subscribeHandler := notifier.NewHandlerForEpochStart( + func(hdr data.HeaderHandler) {}, + func(_ data.HeaderHandler) {}, + common.EpochTxBroadcastDebug, + ) + + return subscribeHandler +} + // IsInterfaceNil returns true if there is no value under the interface func (didh *disabledInterceptorDebugHandler) IsInterfaceNil() bool { return didh == nil diff --git a/debug/handler/interceptorDebugHandler.go b/debug/handler/interceptorDebugHandler.go index a00f7b878b9..f4cf57ba0a3 100644 --- a/debug/handler/interceptorDebugHandler.go +++ b/debug/handler/interceptorDebugHandler.go @@ -9,8 +9,15 @@ import ( "sync" "time" + p2p2 "github.com/multiversx/mx-chain-communication-go/p2p" + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/debug" + "github.com/multiversx/mx-chain-go/epochStart" + "github.com/multiversx/mx-chain-go/epochStart/notifier" + "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/storage" "github.com/multiversx/mx-chain-go/storage/cache" logger "github.com/multiversx/mx-chain-logger-go" @@ -93,6 +100,7 @@ func displayTime(timestamp int64) string { } type interceptorDebugHandler struct { + broadcastDebug BroadcastDebugHandler cache storage.Cacher intervalAutoPrint time.Duration requestsThreshold int @@ -104,7 +112,7 @@ type interceptorDebugHandler struct { } // NewInterceptorDebugHandler creates a new interceptorDebugHandler able to hold requested-intercepted information -func NewInterceptorDebugHandler(config config.InterceptorResolverDebugConfig) (*interceptorDebugHandler, error) { +func NewInterceptorDebugHandler(config config.InterceptorResolverDebugConfig, ntpTime NTPTime) (*interceptorDebugHandler, error) { lruCache, err := cache.NewLRUCache(config.CacheSize) if err != nil { return nil, fmt.Errorf("%w when creating NewInterceptorDebugHandler", err) @@ -120,6 +128,14 @@ func NewInterceptorDebugHandler(config config.InterceptorResolverDebugConfig) (* return nil, err } + idh.broadcastDebug = NewDisabledBroadcastDebug() + if config.BroadcastStatistics.Enabled { + idh.broadcastDebug, err = NewBroadcastDebug(config.BroadcastStatistics, ntpTime) + if err != nil { + return nil, err + } + } + idh.printEventFunc = idh.printEvent if config.EnablePrint { ctx, cancelFunc := context.WithCancel(context.Background()) @@ -419,6 +435,24 @@ func (idh *interceptorDebugHandler) LogSucceededToResolveData(topic string, hash idh.cache.Remove(identifier) } +// LogReceivedData will log the received data +func (idh *interceptorDebugHandler) LogReceivedData(data process.InterceptedData, msg p2p2.MessageP2P, fromConnectedPeer core.PeerID) { + idh.broadcastDebug.Process(data, msg, fromConnectedPeer) +} + +// EpochStartEventHandler returns the epoch start event handler +func (idh *interceptorDebugHandler) EpochStartEventHandler() epochStart.ActionHandler { + subscribeHandler := notifier.NewHandlerForEpochStart( + func(hdr data.HeaderHandler) { + idh.broadcastDebug.PrintReceivedTxsBroadcastAndCleanRecords() + }, + func(_ data.HeaderHandler) {}, + common.EpochTxBroadcastDebug, + ) + + return subscribeHandler +} + // Close closes all underlying components func (idh *interceptorDebugHandler) Close() error { idh.cancelFunc() diff --git a/debug/handler/interceptorDebugHandler_test.go b/debug/handler/interceptorDebugHandler_test.go index 7eeb3c7ec41..96362a967db 100644 --- a/debug/handler/interceptorDebugHandler_test.go +++ b/debug/handler/interceptorDebugHandler_test.go @@ -11,6 +11,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/debug" + "github.com/multiversx/mx-chain-go/testscommon" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -40,7 +41,7 @@ func TestNewInterceptorResolver_InvalidSizeShouldErr(t *testing.T) { cfg := createWorkableConfig() cfg.CacheSize = -1 - idh, err := NewInterceptorDebugHandler(cfg) + idh, err := NewInterceptorDebugHandler(cfg, &testscommon.SyncTimerStub{}) assert.True(t, check.IfNil(idh)) assert.NotNil(t, err) @@ -52,7 +53,7 @@ func TestNewInterceptorResolver_InvalidIntervalShouldErr(t *testing.T) { cfg := createWorkableConfig() cfg.EnablePrint = true cfg.IntervalAutoPrintInSeconds = 0 - idh, err := NewInterceptorDebugHandler(cfg) + idh, err := NewInterceptorDebugHandler(cfg, &testscommon.SyncTimerStub{}) assert.True(t, check.IfNil(idh)) assert.True(t, errors.Is(err, debug.ErrInvalidValue)) @@ -66,7 +67,7 @@ func TestNewInterceptorResolver_NumResolveFailureThresholdShouldErr(t *testing.T cfg.IntervalAutoPrintInSeconds = 1 cfg.NumResolveFailureThreshold = 0 cfg.NumRequestsThreshold = 1 - idh, err := NewInterceptorDebugHandler(cfg) + idh, err := NewInterceptorDebugHandler(cfg, &testscommon.SyncTimerStub{}) assert.True(t, check.IfNil(idh)) assert.True(t, errors.Is(err, debug.ErrInvalidValue)) @@ -80,7 +81,7 @@ func TestNewInterceptorResolver_NumRequestsThresholdShouldErr(t *testing.T) { cfg.IntervalAutoPrintInSeconds = 1 cfg.NumResolveFailureThreshold = 1 cfg.NumRequestsThreshold = 0 - idh, err := NewInterceptorDebugHandler(cfg) + idh, err := NewInterceptorDebugHandler(cfg, &testscommon.SyncTimerStub{}) assert.True(t, check.IfNil(idh)) assert.True(t, errors.Is(err, debug.ErrInvalidValue)) @@ -95,7 +96,7 @@ func TestNewInterceptorResolver_DebugLineExpirationShouldErr(t *testing.T) { cfg.NumResolveFailureThreshold = 1 cfg.NumRequestsThreshold = 1 cfg.DebugLineExpiration = 0 - idh, err := NewInterceptorDebugHandler(cfg) + idh, err := NewInterceptorDebugHandler(cfg, &testscommon.SyncTimerStub{}) assert.True(t, check.IfNil(idh)) assert.True(t, errors.Is(err, debug.ErrInvalidValue)) @@ -104,7 +105,7 @@ func TestNewInterceptorResolver_DebugLineExpirationShouldErr(t *testing.T) { func TestNewInterceptorResolver_ShouldWork(t *testing.T) { t.Parallel() - idh, err := NewInterceptorDebugHandler(createWorkableConfig()) + idh, err := NewInterceptorDebugHandler(createWorkableConfig(), &testscommon.SyncTimerStub{}) assert.False(t, check.IfNil(idh)) assert.Nil(t, err) @@ -119,7 +120,7 @@ func TestNewInterceptorResolver_EnablePrintShouldWork(t *testing.T) { cfg.NumResolveFailureThreshold = 1 cfg.NumRequestsThreshold = 1 cfg.DebugLineExpiration = 100 - idh, err := NewInterceptorDebugHandler(cfg) + idh, err := NewInterceptorDebugHandler(cfg, &testscommon.SyncTimerStub{}) assert.False(t, check.IfNil(idh)) assert.Nil(t, err) @@ -128,7 +129,7 @@ func TestNewInterceptorResolver_EnablePrintShouldWork(t *testing.T) { func TestInterceptorResolver_LogRequestedDataWithFiveIdentifiersShouldWork(t *testing.T) { t.Parallel() - idh, _ := NewInterceptorDebugHandler(createWorkableConfig()) + idh, _ := NewInterceptorDebugHandler(createWorkableConfig(), &testscommon.SyncTimerStub{}) idh.SetTimeHandler(mockTimestampHandler) numIdentifiers := 5 foundMap := make(map[string]struct{}) @@ -155,7 +156,7 @@ func TestInterceptorResolver_LogRequestedDataWithFiveIdentifiersShouldWork(t *te func TestInterceptorResolver_LogRequestedDataSameIdentifierShouldAddRequested(t *testing.T) { t.Parallel() - idh, _ := NewInterceptorDebugHandler(createWorkableConfig()) + idh, _ := NewInterceptorDebugHandler(createWorkableConfig(), &testscommon.SyncTimerStub{}) idh.SetTimeHandler(mockTimestampHandler) idh.LogRequestedData(topic, [][]byte{hash}, numIntra, numCross) events := idh.Events() @@ -192,7 +193,7 @@ func TestInterceptorResolver_LogRequestedDataSameIdentifierShouldAddRequested(t func TestInterceptorResolver_LogProcessedHashesNotFoundShouldNotAdd(t *testing.T) { t.Parallel() - idh, _ := NewInterceptorDebugHandler(createWorkableConfig()) + idh, _ := NewInterceptorDebugHandler(createWorkableConfig(), &testscommon.SyncTimerStub{}) idh.LogProcessedHashes(topic, [][]byte{hash}, nil) @@ -202,7 +203,7 @@ func TestInterceptorResolver_LogProcessedHashesNotFoundShouldNotAdd(t *testing.T func TestInterceptorResolver_LogProcessedHashesExistingNoErrorShouldRemove(t *testing.T) { t.Parallel() - idh, _ := NewInterceptorDebugHandler(createWorkableConfig()) + idh, _ := NewInterceptorDebugHandler(createWorkableConfig(), &testscommon.SyncTimerStub{}) idh.LogRequestedData(topic, [][]byte{hash}, numIntra, numCross) require.Equal(t, 1, len(idh.Events())) @@ -214,7 +215,7 @@ func TestInterceptorResolver_LogProcessedHashesExistingNoErrorShouldRemove(t *te func TestInterceptorResolver_LogProcessedHashesExistingWithErrorShouldIncrementProcessed(t *testing.T) { t.Parallel() - idh, _ := NewInterceptorDebugHandler(createWorkableConfig()) + idh, _ := NewInterceptorDebugHandler(createWorkableConfig(), &testscommon.SyncTimerStub{}) idh.SetTimeHandler(mockTimestampHandler) idh.LogRequestedData(topic, [][]byte{hash}, numIntra, numCross) require.Equal(t, 1, len(idh.Events())) @@ -246,7 +247,7 @@ func TestInterceptorResolver_LogProcessedHashesExistingWithErrorShouldIncrementP func TestInterceptorResolver_LogReceivedHashesNotFoundShouldNotAdd(t *testing.T) { t.Parallel() - idh, _ := NewInterceptorDebugHandler(createWorkableConfig()) + idh, _ := NewInterceptorDebugHandler(createWorkableConfig(), &testscommon.SyncTimerStub{}) idh.LogReceivedHashes(topic, [][]byte{hash}) @@ -256,7 +257,7 @@ func TestInterceptorResolver_LogReceivedHashesNotFoundShouldNotAdd(t *testing.T) func TestInterceptorResolver_LogReceivedHashesExistingShouldIncrementReceived(t *testing.T) { t.Parallel() - idh, _ := NewInterceptorDebugHandler(createWorkableConfig()) + idh, _ := NewInterceptorDebugHandler(createWorkableConfig(), &testscommon.SyncTimerStub{}) idh.SetTimeHandler(mockTimestampHandler) idh.LogRequestedData(topic, [][]byte{hash}, numIntra, numCross) require.Equal(t, 1, len(idh.Events())) @@ -286,7 +287,7 @@ func TestInterceptorResolver_LogReceivedHashesExistingShouldIncrementReceived(t func TestInterceptorResolver_LogFailedToResolveDataShouldWork(t *testing.T) { t.Parallel() - idh, _ := NewInterceptorDebugHandler(createWorkableConfig()) + idh, _ := NewInterceptorDebugHandler(createWorkableConfig(), &testscommon.SyncTimerStub{}) idh.SetTimeHandler(mockTimestampHandler) idh.LogFailedToResolveData(topic, hash, nil) @@ -323,7 +324,7 @@ func TestInterceptorResolver_LogFailedToResolveDataShouldWork(t *testing.T) { func TestInterceptorResolver_LogFailedToResolveDataAndRequestedDataShouldWork(t *testing.T) { t.Parallel() - idh, _ := NewInterceptorDebugHandler(createWorkableConfig()) + idh, _ := NewInterceptorDebugHandler(createWorkableConfig(), &testscommon.SyncTimerStub{}) idh.LogFailedToResolveData(topic, hash, nil) assert.Equal(t, 1, len(idh.Events())) @@ -339,7 +340,7 @@ func TestInterceptorResolver_LogFailedToResolveDataAndRequestedDataShouldWork(t func TestInterceptorResolver_LogSucceededToResolveDataShouldWork(t *testing.T) { t.Parallel() - idh, _ := NewInterceptorDebugHandler(createWorkableConfig()) + idh, _ := NewInterceptorDebugHandler(createWorkableConfig(), &testscommon.SyncTimerStub{}) idh.SetTimeHandler(mockTimestampHandler) idh.LogFailedToResolveData(topic, hash, nil) @@ -368,7 +369,7 @@ func TestInterceptorResolver_Query(t *testing.T) { topic1 := "topic1" topic2 := "aaaa" - idh, _ := NewInterceptorDebugHandler(createWorkableConfig()) + idh, _ := NewInterceptorDebugHandler(createWorkableConfig(), &testscommon.SyncTimerStub{}) idh.LogRequestedData(topic1, [][]byte{hash}, numIntra, numCross) idh.LogRequestedData(topic2, [][]byte{hash}, numIntra, numCross) @@ -381,7 +382,7 @@ func TestInterceptorResolver_Query(t *testing.T) { func TestInterceptorResolver_GetStringEventsShouldWork(t *testing.T) { t.Parallel() - idh, _ := NewInterceptorDebugHandler(createWorkableConfig()) + idh, _ := NewInterceptorDebugHandler(createWorkableConfig(), &testscommon.SyncTimerStub{}) assert.Equal(t, 0, len(idh.getStringEvents(100))) idh.LogFailedToResolveData(topic, hash, nil) @@ -404,7 +405,7 @@ func TestInterceptorResolver_NumPrintsShouldWork(t *testing.T) { cfg.NumResolveFailureThreshold = 1 cfg.NumRequestsThreshold = 1 cfg.DebugLineExpiration = 2 - idh, _ := NewInterceptorDebugHandler(cfg) + idh, _ := NewInterceptorDebugHandler(cfg, &testscommon.SyncTimerStub{}) idh.printEventFunc = func(data string) { atomic.AddUint32(&numPrintCalls, 1) } diff --git a/debug/handler/interface.go b/debug/handler/interface.go new file mode 100644 index 00000000000..2eab11b0ea4 --- /dev/null +++ b/debug/handler/interface.go @@ -0,0 +1,20 @@ +package handler + +import ( + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-go/p2p" + "github.com/multiversx/mx-chain-go/process" + "time" +) + +// BroadcastDebugHandler defines what an interceptor debug handler should be able to do +type BroadcastDebugHandler interface { + Process(data process.InterceptedData, msg p2p.MessageP2P, fromConnectedPeer core.PeerID) + PrintReceivedTxsBroadcastAndCleanRecords() +} + +// NTPTime defines methods for retrieving the current time +type NTPTime interface { + CurrentTime() time.Time + IsInterfaceNil() bool +} diff --git a/epochStart/bootstrap/disabled/disabledAccountsAdapter.go b/epochStart/bootstrap/disabled/disabledAccountsAdapter.go index abd9c7369b0..39547a07531 100644 --- a/epochStart/bootstrap/disabled/disabledAccountsAdapter.go +++ b/epochStart/bootstrap/disabled/disabledAccountsAdapter.go @@ -3,9 +3,10 @@ package disabled import ( "context" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/state" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" ) type accountsAdapter struct { @@ -90,10 +91,19 @@ func (a *accountsAdapter) RecreateTrie(_ common.RootHashHolder) error { return nil } +// RecreateTrieIfNeeded - +func (a *accountsAdapter) RecreateTrieIfNeeded(_ common.RootHashHolder) error { + return nil +} + // CancelPrune - func (a *accountsAdapter) CancelPrune(_ []byte, _ state.TriePruningIdentifier) { } +// ResetPruning - +func (a *accountsAdapter) ResetPruning() { +} + // SnapshotState - func (a *accountsAdapter) SnapshotState(_ []byte, _ uint32) { } @@ -137,6 +147,11 @@ func (a *accountsAdapter) Close() error { return nil } +// GetEvictionWaitingListSize returns 0 for the disabled accounts adapter +func (a *accountsAdapter) GetEvictionWaitingListSize() int { + return 0 +} + // IsInterfaceNil - func (a *accountsAdapter) IsInterfaceNil() bool { return a == nil diff --git a/epochStart/bootstrap/disabled/disabledEpochStartTrigger.go b/epochStart/bootstrap/disabled/disabledEpochStartTrigger.go index 38323d907ef..63465de8491 100644 --- a/epochStart/bootstrap/disabled/disabledEpochStartTrigger.go +++ b/epochStart/bootstrap/disabled/disabledEpochStartTrigger.go @@ -17,6 +17,23 @@ func NewEpochStartTrigger() *epochStartTrigger { func (e *epochStartTrigger) Update(_ uint64, _ uint64) { } +// ShouldProposeEpochChange - +func (e *epochStartTrigger) ShouldProposeEpochChange(_ uint64, _ uint64) bool { + return false +} + +// SetEpochChangeProposed - +func (e *epochStartTrigger) SetEpochChangeProposed(_ bool) { +} + +// GetEpochChangeProposed - +func (e *epochStartTrigger) GetEpochChangeProposed() bool { + return false +} + +// SetEpochChange - +func (e *epochStartTrigger) SetEpochChange(_ uint64) {} + // ReceivedHeader - func (e *epochStartTrigger) ReceivedHeader(_ data.HeaderHandler) { } diff --git a/epochStart/bootstrap/disabled/disabledMultiSigner.go b/epochStart/bootstrap/disabled/disabledMultiSigner.go index dd7c5e4d7b6..a437998fac1 100644 --- a/epochStart/bootstrap/disabled/disabledMultiSigner.go +++ b/epochStart/bootstrap/disabled/disabledMultiSigner.go @@ -1,5 +1,7 @@ package disabled +import crypto "github.com/multiversx/mx-chain-crypto-go" + type multiSigner struct { } @@ -13,21 +15,41 @@ func (m *multiSigner) CreateSignatureShare(_ []byte, _ []byte) ([]byte, error) { return nil, nil } +// CreateSignatureShareV2 returns nil byte slice and nil error +func (m *multiSigner) CreateSignatureShareV2(_ crypto.PrivateKey, _ []byte) ([]byte, error) { + return nil, nil +} + // VerifySignatureShare returns nil func (m *multiSigner) VerifySignatureShare(_ []byte, _ []byte, _ []byte) error { return nil } +// VerifySignatureShareV2 returns nil +func (m *multiSigner) VerifySignatureShareV2(_ crypto.PublicKey, _ []byte, _ []byte) error { + return nil +} + // AggregateSigs returns nil byte slice and nil error func (m *multiSigner) AggregateSigs(_ [][]byte, _ [][]byte) ([]byte, error) { return nil, nil } +// AggregateSigsV2 returns nil byte slice and nil error +func (m *multiSigner) AggregateSigsV2(_ []crypto.PublicKey, _ [][]byte) ([]byte, error) { + return nil, nil +} + // VerifyAggregatedSig returns nil func (m *multiSigner) VerifyAggregatedSig(_ [][]byte, _ []byte, _ []byte) error { return nil } +// VerifyAggregatedSigV2 returns nil +func (m *multiSigner) VerifyAggregatedSigV2(_ []crypto.PublicKey, _ []byte, _ []byte) error { + return nil +} + // IsInterfaceNil returns true if there is no value under the interface func (m *multiSigner) IsInterfaceNil() bool { return m == nil diff --git a/epochStart/bootstrap/disabled/disabledMultiSignerContainer.go b/epochStart/bootstrap/disabled/disabledMultiSignerContainer.go index 3b813c82da4..fa96b5c4813 100644 --- a/epochStart/bootstrap/disabled/disabledMultiSignerContainer.go +++ b/epochStart/bootstrap/disabled/disabledMultiSignerContainer.go @@ -3,7 +3,7 @@ package disabled import crypto "github.com/multiversx/mx-chain-crypto-go" type disabledMultiSignerContainer struct { - multiSigner crypto.MultiSigner + multiSigner crypto.MultiSignerV2 } // NewMultiSignerContainer creates a disabled multi signer container @@ -14,7 +14,7 @@ func NewMultiSignerContainer() *disabledMultiSignerContainer { } // GetMultiSigner returns a disabled multi signer as this is a disabled component -func (dmsc *disabledMultiSignerContainer) GetMultiSigner(_ uint32) (crypto.MultiSigner, error) { +func (dmsc *disabledMultiSignerContainer) GetMultiSigner(_ uint32) (crypto.MultiSignerV2, error) { return dmsc.multiSigner, nil } diff --git a/epochStart/bootstrap/disabled/disabledValidityAttester.go b/epochStart/bootstrap/disabled/disabledValidityAttester.go index fcc8e7b03dc..2f916eaefb1 100644 --- a/epochStart/bootstrap/disabled/disabledValidityAttester.go +++ b/epochStart/bootstrap/disabled/disabledValidityAttester.go @@ -2,6 +2,7 @@ package disabled import ( "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/process" ) @@ -25,11 +26,21 @@ func (v *validityAttester) CheckBlockAgainstRoundHandler(_ data.HeaderHandler) e return nil } -// CheckBlockAgainstWhitelist - -func (v *validityAttester) CheckBlockAgainstWhitelist(_ process.InterceptedData) bool { +// CheckAgainstWhitelist - +func (v *validityAttester) CheckAgainstWhitelist(_ process.InterceptedData) bool { return false } +// CheckProofAgainstFinal - +func (v *validityAttester) CheckProofAgainstFinal(_ data.HeaderProofHandler) error { + return nil +} + +// CheckProofAgainstRoundHandler - +func (v *validityAttester) CheckProofAgainstRoundHandler(_ data.HeaderProofHandler) error { + return nil +} + // IsInterfaceNil - func (v *validityAttester) IsInterfaceNil() bool { return v == nil diff --git a/epochStart/bootstrap/epochStartMetaBlockProcessor.go b/epochStart/bootstrap/epochStartMetaBlockProcessor.go index 419a243f3fd..81f8b769afa 100644 --- a/epochStart/bootstrap/epochStartMetaBlockProcessor.go +++ b/epochStart/bootstrap/epochStartMetaBlockProcessor.go @@ -8,10 +8,10 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" - "github.com/multiversx/mx-chain-core-go/data" - "github.com/multiversx/mx-chain-core-go/data/block" + coreData "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/epochStart" @@ -36,12 +36,12 @@ type epochStartMetaBlockProcessor struct { proofsPool ProofsPool mutReceivedMetaBlocks sync.RWMutex - mapReceivedMetaBlocks map[string]data.MetaHeaderHandler + mapReceivedMetaBlocks map[string]coreData.MetaHeaderHandler mapMetaBlocksFromPeers map[string][]core.PeerID chanMetaBlockProofReached chan bool chanMetaBlockReached chan bool - metaBlock data.MetaHeaderHandler + metaBlock coreData.MetaHeaderHandler metaBlockHash string peerCountTarget int minNumConnectedPeers int @@ -97,7 +97,7 @@ func NewEpochStartMetaBlockProcessor( minNumOfPeersToConsiderBlockValid: minNumOfPeersToConsiderBlockValidConfig, enableEpochsHandler: enableEpochsHandler, mutReceivedMetaBlocks: sync.RWMutex{}, - mapReceivedMetaBlocks: make(map[string]data.MetaHeaderHandler), + mapReceivedMetaBlocks: make(map[string]coreData.MetaHeaderHandler), mapMetaBlocksFromPeers: make(map[string][]core.PeerID), chanMetaBlockProofReached: make(chan bool, 1), chanMetaBlockReached: make(chan bool, 1), @@ -137,24 +137,24 @@ func (e *epochStartMetaBlockProcessor) waitForEnoughNumConnectedPeers(messenger // Save will handle the consensus mechanism for the fetched metablocks // All errors are just logged because if this function returns an error, the processing is finished. This way, we ignore // wrong received data and wait for relevant intercepted data -func (e *epochStartMetaBlockProcessor) Save(data process.InterceptedData, fromConnectedPeer core.PeerID, _ string) error { +func (e *epochStartMetaBlockProcessor) Save(data process.InterceptedData, fromConnectedPeer core.PeerID, _ string, _ p2p.BroadcastMethod) (dataSaved bool, err error) { if check.IfNil(data) { log.Debug("epoch bootstrapper: nil intercepted data") - return nil + return false, nil } log.Debug("received header", "type", data.Type(), "hash", data.Hash()) interceptedHdr, ok := data.(process.HdrValidatorHandler) if !ok { log.Warn("saving epoch start meta block error", "error", epochStart.ErrWrongTypeAssertion) - return nil + return false, nil } - metaBlock, ok := interceptedHdr.HeaderHandler().(*block.MetaBlock) + metaBlock, ok := interceptedHdr.HeaderHandler().(coreData.MetaHeaderHandler) if !ok { log.Warn("saving epoch start meta block error", "error", epochStart.ErrWrongTypeAssertion, "header", interceptedHdr.HeaderHandler()) - return nil + return false, nil } mbHash := interceptedHdr.Hash() @@ -166,10 +166,10 @@ func (e *epochStartMetaBlockProcessor) Save(data process.InterceptedData, fromCo e.addToPeerList(string(mbHash), fromConnectedPeer) e.mutReceivedMetaBlocks.Unlock() - return nil + return true, nil } - return nil + return true, nil } // this func should be called under mutex protection @@ -185,7 +185,7 @@ func (e *epochStartMetaBlockProcessor) addToPeerList(hash string, peer core.Peer // GetEpochStartMetaBlock will return the metablock after it is confirmed or an error if the number of tries was exceeded // This is a blocking method which will end after the consensus for the meta block is obtained or the context is done -func (e *epochStartMetaBlockProcessor) GetEpochStartMetaBlock(ctx context.Context) (data.MetaHeaderHandler, error) { +func (e *epochStartMetaBlockProcessor) GetEpochStartMetaBlock(ctx context.Context) (coreData.MetaHeaderHandler, error) { originalIntra, originalCross, err := e.requestHandler.GetNumPeersToQuery(factory.MetachainBlocksTopic) if err != nil { return nil, err @@ -216,7 +216,7 @@ func (e *epochStartMetaBlockProcessor) GetEpochStartMetaBlock(ctx context.Contex return metaBlock, nil } -func (e *epochStartMetaBlockProcessor) waitForMetaBlock(ctx context.Context) (data.MetaHeaderHandler, string, error) { +func (e *epochStartMetaBlockProcessor) waitForMetaBlock(ctx context.Context) (coreData.MetaHeaderHandler, string, error) { err := e.requestMetaBlock() if err != nil { return nil, "", err @@ -275,7 +275,7 @@ func (e *epochStartMetaBlockProcessor) waitForMetaBlockProof( } } -func (e *epochStartMetaBlockProcessor) getMostReceivedMetaBlock() (data.MetaHeaderHandler, string, error) { +func (e *epochStartMetaBlockProcessor) getMostReceivedMetaBlock() (coreData.MetaHeaderHandler, string, error) { e.mutReceivedMetaBlocks.RLock() defer e.mutReceivedMetaBlocks.RUnlock() @@ -320,7 +320,7 @@ func (e *epochStartMetaBlockProcessor) requestProofForMetaBlock(metablockHash [] return nil } -func (e *epochStartMetaBlockProcessor) receivedProof(proof data.HeaderProofHandler) { +func (e *epochStartMetaBlockProcessor) receivedProof(proof coreData.HeaderProofHandler) { startOfEpochMetaBlock, hash, err := e.getMostReceivedMetaBlock() if err != nil { return diff --git a/epochStart/bootstrap/epochStartMetaBlockProcessor_test.go b/epochStart/bootstrap/epochStartMetaBlockProcessor_test.go index 2f550842284..2d4e89bdfe3 100644 --- a/epochStart/bootstrap/epochStartMetaBlockProcessor_test.go +++ b/epochStart/bootstrap/epochStartMetaBlockProcessor_test.go @@ -214,7 +214,7 @@ func TestEpochStartMetaBlockProcessor_SaveNilInterceptedDataShouldNotReturnError &dataRetriever.ProofsPoolMock{}, ) - err := esmbp.Save(nil, "peer0", "") + _, err := esmbp.Save(nil, "peer0", "", "") assert.NoError(t, err) } @@ -243,7 +243,7 @@ func TestEpochStartMetaBlockProcessor_SaveOkInterceptedDataShouldWork(t *testing EpochStart: block.EpochStart{LastFinalizedHeaders: []block.EpochStartShardData{{Round: 1}}}, } intData := mock.NewInterceptedMetaBlockMock(mb, []byte("hash")) - err := esmbp.Save(intData, "peer0", "") + _, err := esmbp.Save(intData, "peer0", "", "") assert.NoError(t, err) assert.Equal(t, 1, len(esmbp.GetMapMetaBlock())) @@ -305,11 +305,11 @@ func TestEpochStartMetaBlockProcessor_GetEpochStartMetaBlockShouldReturnMostRece intData2 := mock.NewInterceptedMetaBlockMock(confirmationMetaBlock, []byte("hash2")) for i := 0; i < esmbp.minNumOfPeersToConsiderBlockValid; i++ { - _ = esmbp.Save(intData, core.PeerID(fmt.Sprintf("peer_%d", i)), "") + _, _ = esmbp.Save(intData, core.PeerID(fmt.Sprintf("peer_%d", i)), "", "") } for i := 0; i < esmbp.minNumOfPeersToConsiderBlockValid; i++ { - _ = esmbp.Save(intData2, core.PeerID(fmt.Sprintf("peer_%d", i)), "") + _, _ = esmbp.Save(intData2, core.PeerID(fmt.Sprintf("peer_%d", i)), "", "") } // we need a slightly more time than 1 second in order to also properly test the select branches @@ -351,11 +351,11 @@ func TestEpochStartMetaBlockProcessor_GetEpochStartMetaBlockShouldWorkFromFirstT intData2 := mock.NewInterceptedMetaBlockMock(confirmationMetaBlock, []byte("hash2")) for i := 0; i < 6; i++ { - _ = esmbp.Save(intData, core.PeerID(fmt.Sprintf("peer_%d", i)), "") + _, _ = esmbp.Save(intData, core.PeerID(fmt.Sprintf("peer_%d", i)), "", "") } for i := 0; i < 6; i++ { - _ = esmbp.Save(intData2, core.PeerID(fmt.Sprintf("peer_%d", i)), "") + _, _ = esmbp.Save(intData2, core.PeerID(fmt.Sprintf("peer_%d", i)), "", "") } ctx, cancel := context.WithTimeout(context.Background(), time.Minute) @@ -395,8 +395,8 @@ func TestEpochStartMetaBlockProcessor_GetEpochStartMetaBlock_BeforeAndromeda(t * index := 0 for { time.Sleep(tts) - _ = esmbp.Save(intData, core.PeerID(fmt.Sprintf("peer_%d", index)), "") - _ = esmbp.Save(intData, core.PeerID(fmt.Sprintf("peer_%d", index+1)), "") + _, _ = esmbp.Save(intData, core.PeerID(fmt.Sprintf("peer_%d", index)), "", "") + _, _ = esmbp.Save(intData, core.PeerID(fmt.Sprintf("peer_%d", index+1)), "", "") index += 2 } }() @@ -451,8 +451,8 @@ func TestEpochStartMetaBlockProcessor_GetEpochStartMetaBlock_AfterAndromeda(t *t index := 0 for { time.Sleep(tts) - _ = esmbp.Save(intData, core.PeerID(fmt.Sprintf("peer_%d", index)), "") - _ = esmbp.Save(intData, core.PeerID(fmt.Sprintf("peer_%d", index+1)), "") + _, _ = esmbp.Save(intData, core.PeerID(fmt.Sprintf("peer_%d", index)), "", "") + _, _ = esmbp.Save(intData, core.PeerID(fmt.Sprintf("peer_%d", index+1)), "", "") index += 2 } }() @@ -461,8 +461,8 @@ func TestEpochStartMetaBlockProcessor_GetEpochStartMetaBlock_AfterAndromeda(t *t index := 0 for { time.Sleep(tts) - _ = esmbp.Save(intData2, core.PeerID(fmt.Sprintf("peer_%d", index)), "") - _ = esmbp.Save(intData2, core.PeerID(fmt.Sprintf("peer_%d", index+1)), "") + _, _ = esmbp.Save(intData2, core.PeerID(fmt.Sprintf("peer_%d", index)), "", "") + _, _ = esmbp.Save(intData2, core.PeerID(fmt.Sprintf("peer_%d", index+1)), "", "") index += 2 } }() diff --git a/epochStart/bootstrap/factory/epochStartInterceptorsContainerFactory.go b/epochStart/bootstrap/factory/epochStartInterceptorsContainerFactory.go index 8700b1daa24..9397c5775e7 100644 --- a/epochStart/bootstrap/factory/epochStartInterceptorsContainerFactory.go +++ b/epochStart/bootstrap/factory/epochStartInterceptorsContainerFactory.go @@ -111,6 +111,7 @@ func NewEpochStartInterceptorsContainer(args ArgsEpochStartInterceptorContainer) HardforkTrigger: hardforkTrigger, NodeOperationMode: args.NodeOperationMode, InterceptedDataVerifierFactory: args.InterceptedDataVerifierFactory, + Config: args.Config, } interceptorsContainerFactory, err := interceptorscontainer.NewMetaInterceptorsContainerFactory(containerFactoryArgs) diff --git a/epochStart/bootstrap/fromLocalStorage.go b/epochStart/bootstrap/fromLocalStorage.go index 0572d3b376e..c6ae3f5bccf 100644 --- a/epochStart/bootstrap/fromLocalStorage.go +++ b/epochStart/bootstrap/fromLocalStorage.go @@ -13,6 +13,7 @@ import ( "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/epochStart" "github.com/multiversx/mx-chain-go/epochStart/bootstrap/disabled" + "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/block/bootstrapStorage" "github.com/multiversx/mx-chain-go/sharding" "github.com/multiversx/mx-chain-go/sharding/nodesCoordinator" @@ -155,7 +156,7 @@ func (e *epochStartBootstrap) prepareEpochFromStorage() (Parameters, error) { } prevEpochStartMetaHash := e.epochStartMeta.GetEpochStartHandler().GetEconomicsHandler().GetPrevEpochStartHash() - prevEpochStartMeta, ok := e.syncedHeaders[string(prevEpochStartMetaHash)].(*block.MetaBlock) + prevEpochStartMeta, ok := e.syncedHeaders[string(prevEpochStartMetaHash)].(data.MetaHeaderHandler) if !ok { return Parameters{}, epochStart.ErrWrongTypeAssertion } @@ -288,8 +289,7 @@ func (e *epochStartBootstrap) getEpochStartMetaFromStorage(storer storage.Storer return nil, err } - metaBlock := &block.MetaBlock{} - err = e.coreComponentsHolder.InternalMarshalizer().Unmarshal(metaBlock, epochStartMetaBlock) + metaBlock, err := process.UnmarshalMetaHeader(e.coreComponentsHolder.InternalMarshalizer(), epochStartMetaBlock) if err != nil { return nil, err } diff --git a/epochStart/bootstrap/metaStorageHandler.go b/epochStart/bootstrap/metaStorageHandler.go index 82a63d856a3..9b216dee234 100644 --- a/epochStart/bootstrap/metaStorageHandler.go +++ b/epochStart/bootstrap/metaStorageHandler.go @@ -5,8 +5,9 @@ import ( "strconv" "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" - "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/epochStart" @@ -103,6 +104,11 @@ func (msh *metaStorageHandler) SaveDataToStorage(components *ComponentsNeededFor return err } + err = msh.saveEpochStartMetaHdrs(components) + if err != nil { + return err + } + msh.saveMiniblocksFromComponents(components) miniBlocks, err := msh.groupMiniBlocksByShard(components.PendingMiniBlocks) @@ -120,15 +126,24 @@ func (msh *metaStorageHandler) SaveDataToStorage(components *ComponentsNeededFor return err } - lastCrossNotarizedHeader, err := msh.saveLastCrossNotarizedHeaders(components.EpochStartMetaBlock, components.Headers) + lastCrossNotarizedHeaders, err := msh.saveLastCrossNotarizedHeaders(components.EpochStartMetaBlock, components.Headers) + if err != nil { + return err + } + + lastSelfNotarizedHeaders, err := msh.getLastSelfNotarizedHeaders( + components.EpochStartMetaBlock, + lastHeader, + components.Headers, + ) if err != nil { return err } bootStrapData := bootstrapStorage.BootstrapData{ LastHeader: lastHeader, - LastCrossNotarizedHeaders: lastCrossNotarizedHeader, - LastSelfNotarizedHeaders: []bootstrapStorage.BootstrapHeaderInfo{lastHeader}, + LastCrossNotarizedHeaders: lastCrossNotarizedHeaders, + LastSelfNotarizedHeaders: lastSelfNotarizedHeaders, ProcessedMiniBlocks: []bootstrapStorage.MiniBlocksInMeta{}, PendingMiniBlocks: miniBlocks, NodesCoordinatorConfigKey: nodesCoordinatorConfigKey, @@ -162,6 +177,130 @@ func (msh *metaStorageHandler) SaveDataToStorage(components *ComponentsNeededFor return nil } +func (msh *metaStorageHandler) getLastSelfNotarizedHeaders( + epochStartMeta data.MetaHeaderHandler, + epochStartMetaBootstrapInfo bootstrapStorage.BootstrapHeaderInfo, + syncedHeaders map[string]data.HeaderHandler, +) ([]bootstrapStorage.BootstrapHeaderInfo, error) { + var lastSelfNotarizedHeaders []bootstrapStorage.BootstrapHeaderInfo + if !epochStartMeta.IsHeaderV3() { + return []bootstrapStorage.BootstrapHeaderInfo{ + epochStartMetaBootstrapInfo, + }, nil + } + + for _, epochStartData := range epochStartMeta.GetEpochStartHandler().GetLastFinalizedHeaderHandlers() { + bootstrapHdrInfo, err := msh.getLastNotarizedBootstrapInfoForEpochStartData(epochStartData, syncedHeaders) + if err != nil { + return nil, err + } + + lastSelfNotarizedHeaders = append(lastSelfNotarizedHeaders, bootstrapHdrInfo) + } + + bootstrapHdrInfoMeta, err := msh.getLastMetaBootstrapInfo(epochStartMeta, syncedHeaders) + if err != nil { + return nil, err + } + + lastSelfNotarizedHeaders = append(lastSelfNotarizedHeaders, bootstrapHdrInfoMeta) + + return lastSelfNotarizedHeaders, nil +} + +func (msh *metaStorageHandler) getLastMetaBootstrapInfo( + epochStartMeta data.MetaHeaderHandler, + syncedHeaders map[string]data.HeaderHandler, +) (bootstrapStorage.BootstrapHeaderInfo, error) { + lastExecRes, err := common.GetLastBaseExecutionResultHandler(epochStartMeta) + if err != nil { + return bootstrapStorage.BootstrapHeaderInfo{}, err + } + + lastExecMetaHeader, ok := syncedHeaders[string(lastExecRes.GetHeaderHash())] + if !ok { + return bootstrapStorage.BootstrapHeaderInfo{}, epochStart.ErrMissingHeader + } + + bootstrapHdrInfoMeta := bootstrapStorage.BootstrapHeaderInfo{ + ShardId: core.MetachainShardId, + Epoch: lastExecMetaHeader.GetEpoch(), + Nonce: lastExecMetaHeader.GetNonce(), + Hash: lastExecRes.GetHeaderHash(), + } + + return bootstrapHdrInfoMeta, nil +} + +func fetchPrevHeader( + syncedHeaders map[string]data.HeaderHandler, + header data.HeaderHandler, +) (data.ShardHeaderHandler, error) { + prevHash := header.GetPrevHash() + syncedHeader, ok := syncedHeaders[string(prevHash)] + if !ok { + return nil, epochStart.ErrMissingHeader + } + + shardHeader, ok := syncedHeader.(data.ShardHeaderHandler) + if !ok { + return nil, epochStart.ErrWrongTypeAssertion + } + + return shardHeader, nil +} + +func (msh *metaStorageHandler) getLastNotarizedBootstrapInfoForEpochStartData( + epochStartData data.EpochStartShardDataHandler, + syncedHeaders map[string]data.HeaderHandler, +) (bootstrapStorage.BootstrapHeaderInfo, error) { + shardHeaderHash := epochStartData.GetHeaderHash() + shardHeader, ok := syncedHeaders[string(shardHeaderHash)] + if !ok { + return bootstrapStorage.BootstrapHeaderInfo{}, epochStart.ErrMissingHeader + } + + lastReferencedMetaHash, err := getLastReferencedMetaHash(syncedHeaders, fetchPrevHeader, shardHeader) + if err != nil { + return bootstrapStorage.BootstrapHeaderInfo{}, err + } + + lastReferencesMetaBlock, ok := syncedHeaders[string(lastReferencedMetaHash)] + if !ok { + return bootstrapStorage.BootstrapHeaderInfo{}, epochStart.ErrMissingHeader + } + + bootstrapHdrInfo := bootstrapStorage.BootstrapHeaderInfo{ + ShardId: epochStartData.GetShardID(), + Epoch: lastReferencesMetaBlock.GetEpoch(), + Nonce: lastReferencesMetaBlock.GetNonce(), + Hash: lastReferencedMetaHash, + } + + return bootstrapHdrInfo, nil +} + +func (msh *metaStorageHandler) saveEpochStartMetaHdrs(components *ComponentsNeededForBootstrap) error { + for _, hdr := range components.Headers { + isForCurrentShard := hdr.GetShardID() == msh.shardCoordinator.SelfId() + if !isForCurrentShard { + _, err := msh.saveShardHdrToStorage(hdr) + if err != nil { + return err + } + + continue + } + + _, err := msh.saveMetaHdrToStorage(hdr) + if err != nil { + return err + } + } + + return nil +} + func (msh *metaStorageHandler) saveLastCrossNotarizedHeaders( meta data.MetaHeaderHandler, mapHeaders map[string]data.HeaderHandler, @@ -205,9 +344,9 @@ func (msh *metaStorageHandler) saveLastHeader(metaBlock data.HeaderHandler) (boo } func (msh *metaStorageHandler) saveTriggerRegistry(components *ComponentsNeededForBootstrap) ([]byte, error) { - metaBlock, ok := components.EpochStartMetaBlock.(*block.MetaBlock) - if !ok { - return nil, epochStart.ErrWrongTypeAssertion + metaBlock := components.EpochStartMetaBlock + if check.IfNil(metaBlock) { + return nil, epochStart.ErrNilMetaBlock } hash, err := core.CalculateHash(msh.marshalizer, msh.hasher, metaBlock) @@ -215,20 +354,19 @@ func (msh *metaStorageHandler) saveTriggerRegistry(components *ComponentsNeededF return nil, err } - triggerReg := block.MetaTriggerRegistry{ - Epoch: metaBlock.GetEpoch(), - CurrentRound: metaBlock.GetRound(), - EpochFinalityAttestingRound: metaBlock.GetRound(), - CurrEpochStartRound: metaBlock.GetRound(), - PrevEpochStartRound: components.PreviousEpochStart.GetRound(), - EpochStartMetaHash: hash, - EpochStartMeta: metaBlock, - } + triggerReg := epochStart.CreateMetaRegistryHandler(metaBlock) + _ = triggerReg.SetEpochStartMetaHeaderHandler(metaBlock) + _ = triggerReg.SetEpoch(metaBlock.GetEpoch()) + _ = triggerReg.SetEpochStartMetaHash(hash) + _ = triggerReg.SetCurrEpochStartRound(metaBlock.GetRound()) + _ = triggerReg.SetPrevEpochStartRound(components.PreviousEpochStart.GetRound()) + _ = triggerReg.SetEpochFinalityAttestingRound(metaBlock.GetRound()) + _ = triggerReg.SetEpochChangeProposed(false) bootstrapKey := []byte(fmt.Sprint(metaBlock.GetRound())) trigInternalKey := append([]byte(common.TriggerRegistryKeyPrefix), bootstrapKey...) - triggerRegBytes, err := msh.marshalizer.Marshal(&triggerReg) + triggerRegBytes, err := msh.marshalizer.Marshal(triggerReg) if err != nil { return nil, err } diff --git a/epochStart/bootstrap/metaStorageHandler_test.go b/epochStart/bootstrap/metaStorageHandler_test.go index ba47388db5f..821a344fc0e 100644 --- a/epochStart/bootstrap/metaStorageHandler_test.go +++ b/epochStart/bootstrap/metaStorageHandler_test.go @@ -10,6 +10,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/common/statistics/disabled" "github.com/multiversx/mx-chain-go/config" @@ -34,7 +35,7 @@ func createStorageHandlerArgs() StorageHandlerArgs { PreferencesConfig: config.PreferencesConfig{}, ShardCoordinator: &mock.ShardCoordinatorStub{}, PathManagerHandler: &testscommon.PathManagerStub{}, - Marshaller: &mock.MarshalizerMock{}, + Marshaller: &marshal.GogoProtoMarshalizer{}, Hasher: &hashingMocks.HasherMock{}, CurrentEpoch: 0, Uint64Converter: &mock.Uint64ByteSliceConverterMock{}, diff --git a/epochStart/bootstrap/process.go b/epochStart/bootstrap/process.go index 005dc3866e8..6fa278b77e7 100644 --- a/epochStart/bootstrap/process.go +++ b/epochStart/bootstrap/process.go @@ -3,6 +3,7 @@ package bootstrap import ( "bytes" "context" + "errors" "fmt" "sync" "time" @@ -58,6 +59,12 @@ import ( updateSync "github.com/multiversx/mx-chain-go/update/sync" ) +// ErrGetEpochStartRootHash signals that root hash was not found in execution results for epoch start header +var ErrGetEpochStartRootHash = errors.New("failed to get epoch start root hash from execution results") + +// ErrGetEpochStartValidatorStatsRootHash signals that validator stats root hash was not found in execution results for epoch start header +var ErrGetEpochStartValidatorStatsRootHash = errors.New("failed to get epoch start validator stats root hash from execution results") + var log = logger.GetOrCreate("epochStart/bootstrap") // DefaultTimeToWaitForRequestedData represents the default timespan until requested data needs to be received from the connected peers @@ -65,7 +72,6 @@ const DefaultTimeToWaitForRequestedData = 5 * time.Minute const timeBetweenRequests = 100 * time.Millisecond const maxToRequest = 100 const gracePeriodInPercentage = float64(0.25) -const roundGracePeriod = 25 // thresholdForConsideringMetaBlockCorrect represents the percentage (between 0 and 100) of connected peers to send // the same meta block in order to consider it correct @@ -125,6 +131,7 @@ type epochStartBootstrap struct { nodeOperationMode common.NodeOperation stateStatsHandler common.StateStatisticsHandler enableEpochsHandler common.EnableEpochsHandler + epochStartConfigsHandler common.CommonConfigsHandler // created components requestHandler process.RequestHandler @@ -253,6 +260,7 @@ func NewEpochStartBootstrap(args ArgsEpochStartBootstrap) (*epochStartBootstrap, startEpoch: args.GeneralConfig.EpochStartConfig.GenesisEpoch, nodesCoordinatorRegistryFactory: args.NodesCoordinatorRegistryFactory, enableEpochsHandler: args.EnableEpochsHandler, + epochStartConfigsHandler: args.CoreComponentsHolder.CommonConfigsHandler(), interceptedDataVerifierFactory: args.InterceptedDataVerifierFactory, } @@ -290,14 +298,14 @@ func NewEpochStartBootstrap(args ArgsEpochStartBootstrap) (*epochStartBootstrap, } func (e *epochStartBootstrap) isStartInEpochZero() bool { - startTime := time.Unix(e.genesisNodesConfig.GetStartTime(), 0) + startTime := common.GetGenesisStartTimeFromUnixTimestamp(e.genesisNodesConfig.GetStartTime(), e.enableEpochsHandler) isCurrentTimeBeforeGenesis := time.Since(startTime) < 0 if isCurrentTimeBeforeGenesis { return true } currentRound := e.roundHandler.Index() - e.startRound - epochEndPlusGracePeriod := float64(e.generalConfig.EpochStartConfig.RoundsPerEpoch) * (gracePeriodInPercentage + 1.0) + epochEndPlusGracePeriod := float64(e.getRoundsPerEpoch(e.baseData.lastEpoch)) * (gracePeriodInPercentage + 1.0) log.Debug("IsStartInEpochZero", "currentRound", currentRound, "epochEndRound", epochEndPlusGracePeriod) return float64(currentRound) < epochEndPlusGracePeriod } @@ -393,7 +401,6 @@ func (e *epochStartBootstrap) Bootstrap() (Parameters, error) { return Parameters{}, err } log.Debug("start in epoch bootstrap: got epoch start meta header", "epoch", e.epochStartMeta.GetEpoch(), "nonce", e.epochStartMeta.GetNonce()) - e.setEpochStartMetrics() err = e.createSyncers() if err != nil { @@ -417,6 +424,8 @@ func (e *epochStartBootstrap) Bootstrap() (Parameters, error) { return Parameters{}, err } + e.setEpochStartMetrics() + return params, nil } @@ -512,6 +521,8 @@ func (e *epochStartBootstrap) computeIfCurrentEpochIsSaved() bool { return false } + roundGracePeriod := e.getRoundGracePeriod() + computedRound := e.roundHandler.Index() log.Debug("computed round", "round", computedRound, "lastRound", e.baseData.lastRound) if computedRound-e.baseData.lastRound < roundGracePeriod { @@ -520,10 +531,25 @@ func (e *epochStartBootstrap) computeIfCurrentEpochIsSaved() bool { roundsSinceEpochStart := computedRound - int64(e.baseData.epochStartRound) log.Debug("epoch start round", "round", e.baseData.epochStartRound, "roundsSinceEpochStart", roundsSinceEpochStart) - epochEndPlusGracePeriod := float64(e.generalConfig.EpochStartConfig.RoundsPerEpoch) * (gracePeriodInPercentage + 1.0) + + epochEndPlusGracePeriod := float64(e.getRoundsPerEpoch(e.baseData.lastEpoch)) * (gracePeriodInPercentage + 1.0) return float64(roundsSinceEpochStart) < epochEndPlusGracePeriod } +func (e *epochStartBootstrap) getRoundGracePeriod() int64 { + return int64(e.epochStartConfigsHandler.GetGracePeriodRoundsByEpoch(e.baseData.lastEpoch)) +} + +func (e *epochStartBootstrap) getRoundsPerEpoch(epoch uint32) int64 { + chainParamtersForEpoch, err := e.coreComponentsHolder.ChainParametersHandler().ChainParametersForEpoch(epoch) + if err != nil { + log.Warn("could not get rounds per epoch for epoch, returned current chain paramters", "epoch", epoch, "error", err) + chainParamtersForEpoch = e.coreComponentsHolder.ChainParametersHandler().CurrentChainParameters() + } + + return chainParamtersForEpoch.RoundsPerEpoch +} + func (e *epochStartBootstrap) prepareComponentsToSyncFromNetwork() error { e.closeTrieComponents() e.storageService = disabled.NewChainStorer() @@ -665,28 +691,215 @@ func (e *epochStartBootstrap) createSyncers() error { return nil } -func (e *epochStartBootstrap) syncHeadersFrom(meta data.MetaHeaderHandler) (map[string]data.HeaderHandler, error) { - hashesToRequest := make([][]byte, 0, len(meta.GetEpochStartHandler().GetLastFinalizedHeaderHandlers())+1) - shardIds := make([]uint32, 0, len(meta.GetEpochStartHandler().GetLastFinalizedHeaderHandlers())+1) - epochStartMetaHash, err := core.CalculateHash(e.coreComponentsHolder.InternalMarshalizer(), e.coreComponentsHolder.Hasher(), meta) +func (e *epochStartBootstrap) syncHeadersV3From(meta data.MetaHeaderHandler) (map[string]data.HeaderHandler, error) { + syncedHeaders := make(map[string]data.HeaderHandler) + + hashesToRequest := make([][]byte, 0) + shardIds := make([]uint32, 0) + for _, epochStartData := range meta.GetEpochStartHandler().GetLastFinalizedHeaderHandlers() { + err := e.syncEpochStartDataInfo(meta, epochStartData, syncedHeaders) + if err != nil { + return nil, err + } + + hashesToRequest = append(hashesToRequest, epochStartData.GetLastFinishedMetaBlock()) + shardIds = append(shardIds, core.MetachainShardId) + } + + syncedMetaHeaders, err := e.syncEpochStartMetaHeaders(meta, hashesToRequest, shardIds) if err != nil { return nil, err } - for _, epochStartData := range meta.GetEpochStartHandler().GetLastFinalizedHeaderHandlers() { - hashesToRequest = append(hashesToRequest, epochStartData.GetHeaderHash()) - shardIds = append(shardIds, epochStartData.GetShardID()) + + for hash, header := range syncedMetaHeaders { + syncedHeaders[hash] = header } - if meta.GetEpoch() > e.startEpoch+1 { // no need to request genesis block - hashesToRequest = append(hashesToRequest, meta.GetEpochStartHandler().GetEconomicsHandler().GetPrevEpochStartHash()) - shardIds = append(shardIds, core.MetachainShardId) + return syncedHeaders, nil +} + +func (e *epochStartBootstrap) syncIntermediateBlocksIfNeeded( + syncedHeaders map[string]data.HeaderHandler, + header data.HeaderHandler, + startNonce uint64, +) error { + shardID := header.GetShardID() + + hashToSync := header.GetPrevHash() + currNonce := header.GetNonce() + + if startNonce >= currNonce { + return nil + } + + for currNonce > startNonce { + // check if not already synced (when handled for the other shards) + header, ok := syncedHeaders[string(hashToSync)] + if ok { + hashToSync = header.GetPrevHash() + currNonce = header.GetNonce() + continue + } + + header, err := e.syncOneHeader(hashToSync, shardID) + if err != nil { + return err + } + syncedHeaders[string(hashToSync)] = header + + hashToSync = header.GetPrevHash() + currNonce = header.GetNonce() + } + + return nil +} + +func (e *epochStartBootstrap) syncEpochStartDataInfo( + epochStartMeta data.HeaderHandler, + epochStartData data.EpochStartShardDataHandler, + syncedHeaders map[string]data.HeaderHandler, +) error { + syncedHeader, err := e.syncOneHeader(epochStartData.GetHeaderHash(), epochStartData.GetShardID()) + if err != nil { + return err + } + syncedHeaders[string(epochStartData.GetHeaderHash())] = syncedHeader + + if !syncedHeader.IsHeaderV3() { + return nil + } + + err = e.syncBlocksUpToEpochChangeProposed(syncedHeaders, syncedHeader) + if err != nil { + return err + } + + // sync last notarized meta header references by epoch start header for shard + // this will sync based on the meta block hashes references on header and based on the provided LastFinishedMetaBlock + err = e.syncLastNotarizedMetaForEpochStartData(syncedHeaders, syncedHeader) + if err != nil { + return err + } + + lastFinishedMetaBlockForShard, err := e.syncOneHeader(epochStartData.GetLastFinishedMetaBlock(), core.MetachainShardId) + if err != nil { + return err + } + syncedHeaders[string(epochStartData.GetLastFinishedMetaBlock())] = lastFinishedMetaBlockForShard + + // sync meta blocks from epoch start meta blocks up to last finished metablock referenced on shard + return e.syncIntermediateBlocksIfNeeded(syncedHeaders, epochStartMeta, lastFinishedMetaBlockForShard.GetNonce()) +} + +func (e *epochStartBootstrap) syncLastNotarizedMetaForEpochStartData( + syncedHeaders map[string]data.HeaderHandler, + header data.HeaderHandler, +) error { + lastReferencedMetaHash, err := getLastReferencedMetaHash(syncedHeaders, e.syncPrevShardHeaderHandler, header) + if err != nil { + return err + } + + syncedHeader, err := e.syncOneHeader(lastReferencedMetaHash, core.MetachainShardId) + if err != nil { + return err + } + syncedHeaders[string(lastReferencedMetaHash)] = syncedHeader + + return nil +} + +func getLastReferencedMetaHash( + syncedHeaders map[string]data.HeaderHandler, + fetchPrevHeader func(syncedHeaders map[string]data.HeaderHandler, header data.HeaderHandler) (data.ShardHeaderHandler, error), + header data.HeaderHandler, +) ([]byte, error) { + // get last execution result header for epoch start data shard header + lastExecutionResult, err := common.GetLastBaseExecutionResultHandler(header) + if err != nil { + return nil, err + } + + lastExecutedHeader, ok := syncedHeaders[string(lastExecutionResult.GetHeaderHash())] + if !ok { + return nil, epochStart.ErrMissingHeader + } + + lastExecutedShardHeader, ok := lastExecutedHeader.(data.ShardHeaderHandler) + if !ok { + return nil, epochStart.ErrWrongTypeAssertion + } + + var lastReferencedMetaHash []byte + + currentHdr := lastExecutedShardHeader + for currentHdr.GetNonce() > 0 { + numIncludedMetaBlocks := len(currentHdr.GetMetaBlockHashes()) + + // if there are notarized meta headers, return last included meta header + if numIncludedMetaBlocks > 0 { + lastReferencedMetaHash = currentHdr.GetMetaBlockHashes()[numIncludedMetaBlocks-1] + break + } + + // if there are no included meta blocks, go to prev header + header, err := fetchPrevHeader(syncedHeaders, currentHdr) + if err != nil { + return nil, err + } + + currentHdr = header + } + + return lastReferencedMetaHash, nil +} + +func (e *epochStartBootstrap) syncPrevShardHeaderHandler( + syncedHeaders map[string]data.HeaderHandler, + header data.HeaderHandler, +) (data.ShardHeaderHandler, error) { + prevHash := header.GetPrevHash() + syncedHeader, err := e.syncOneHeader(prevHash, header.GetShardID()) + if err != nil { + return nil, err + } + syncedHeaders[string(prevHash)] = syncedHeader + + shardHeader, ok := syncedHeader.(data.ShardHeaderHandler) + if !ok { + return nil, epochStart.ErrWrongTypeAssertion + } + + return shardHeader, nil +} + +func (e *epochStartBootstrap) syncEpochStartMetaHeaders( + meta data.MetaHeaderHandler, + hashesToRequest [][]byte, + shardIds []uint32, +) (map[string]data.HeaderHandler, error) { + epochStartMetaHash, err := core.CalculateHash(e.coreComponentsHolder.InternalMarshalizer(), e.coreComponentsHolder.Hasher(), meta) + if err != nil { + return nil, err } // add the epoch start meta hash to the list to sync its proof - // TODO: this can be removed when the proof will be loaded from storage hashesToRequest = append(hashesToRequest, epochStartMetaHash) shardIds = append(shardIds, core.MetachainShardId) + // sync meta header with intermediate blocks up to epoch change proposed (for supernova) + syncedHeaders := make(map[string]data.HeaderHandler) + err = e.syncBlocksUpToEpochChangeProposed(syncedHeaders, meta) + if err != nil { + return nil, err + } + + // sync also the other meta related headers, as before supernova + if meta.GetEpoch() > e.startEpoch+1 { // no need to request genesis block + hashesToRequest = append(hashesToRequest, meta.GetEpochStartHandler().GetEconomicsHandler().GetPrevEpochStartHash()) + shardIds = append(shardIds, core.MetachainShardId) + } + ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeToWaitForRequestedData) err = e.headersSyncer.SyncMissingHeadersByHash(shardIds, hashesToRequest, ctx) cancel() @@ -694,18 +907,89 @@ func (e *epochStartBootstrap) syncHeadersFrom(meta data.MetaHeaderHandler) (map[ return nil, err } - syncedHeaders, err := e.headersSyncer.GetHeaders() + syncedHeaders, err = e.headersSyncer.GetHeaders() if err != nil { return nil, err } if meta.GetEpoch() == e.startEpoch+1 { - syncedHeaders[string(meta.GetEpochStartHandler().GetEconomicsHandler().GetPrevEpochStartHash())] = &block.MetaBlock{} + // for genesis block there is no epoch start header to sync, so we set an empty meta block + var metaBlock data.MetaHeaderHandler = &block.MetaBlock{} + if meta.IsHeaderV3() { + metaBlock = &block.MetaBlockV3{} + } + syncedHeaders[string(meta.GetEpochStartHandler().GetEconomicsHandler().GetPrevEpochStartHash())] = metaBlock + } + + return syncedHeaders, nil +} + +func (e *epochStartBootstrap) syncHeadersFrom(meta data.MetaHeaderHandler) (map[string]data.HeaderHandler, error) { + if meta.IsHeaderV3() { + return e.syncHeadersV3From(meta) + } + + hashesToRequest := make([][]byte, 0, len(meta.GetEpochStartHandler().GetLastFinalizedHeaderHandlers())+1) + shardIds := make([]uint32, 0, len(meta.GetEpochStartHandler().GetLastFinalizedHeaderHandlers())+1) + for _, epochStartData := range meta.GetEpochStartHandler().GetLastFinalizedHeaderHandlers() { + hashesToRequest = append(hashesToRequest, epochStartData.GetHeaderHash()) + shardIds = append(shardIds, epochStartData.GetShardID()) + } + + syncedHeaders, err := e.syncEpochStartMetaHeaders(meta, hashesToRequest, shardIds) + if err != nil { + return nil, err } return syncedHeaders, nil } +func (e *epochStartBootstrap) syncBlocksUpToEpochChangeProposed( + syncedHeaders map[string]data.HeaderHandler, + header data.HeaderHandler, +) error { + if !header.IsHeaderV3() { + return nil + } + + lastExecutionResult, err := common.GetLastBaseExecutionResultHandler(header) + if err != nil { + return err + } + nonceToSyncFrom := lastExecutionResult.GetHeaderNonce() + execResults := header.GetExecutionResultsHandlers() + // if more execution results were included, sync from the lowest nonce + if len(execResults) > 1 { + nonceToSyncFrom = execResults[0].GetHeaderNonce() + } + + return e.syncIntermediateBlocksIfNeeded(syncedHeaders, header, nonceToSyncFrom) +} + +func (e *epochStartBootstrap) syncOneHeader( + headerHash []byte, + shardID uint32, +) (data.HeaderHandler, error) { + e.headersSyncer.ClearFields() + ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeToWaitForRequestedData) + err := e.headersSyncer.SyncMissingHeadersByHash([]uint32{shardID}, [][]byte{headerHash}, ctx) + cancel() + if err != nil { + return nil, err + } + + syncedHeadersTmp, err := e.headersSyncer.GetHeaders() + if err != nil { + return nil, err + } + syncedHeader, ok := syncedHeadersTmp[string(headerHash)] + if !ok { + return nil, epochStart.ErrMissingHeader + } + + return syncedHeader, nil +} + // requestAndProcessing will handle requesting and receiving the needed information the node will bootstrap from func (e *epochStartBootstrap) requestAndProcessing() (Parameters, error) { var err error @@ -719,7 +1003,7 @@ func (e *epochStartBootstrap) requestAndProcessing() (Parameters, error) { log.Debug("start in epoch bootstrap: got shard headers and previous epoch start meta block") prevEpochStartMetaHash := e.epochStartMeta.GetEpochStartHandler().GetEconomicsHandler().GetPrevEpochStartHash() - prevEpochStartMeta, ok := e.syncedHeaders[string(prevEpochStartMetaHash)].(*block.MetaBlock) + prevEpochStartMeta, ok := e.syncedHeaders[string(prevEpochStartMetaHash)].(data.MetaHeaderHandler) if !ok { return Parameters{}, epochStart.ErrWrongTypeAssertion } @@ -870,13 +1154,23 @@ func (e *epochStartBootstrap) requestAndProcessForMeta(peerMiniBlocks []*block.M e.trieStorageManagers = trieStorageManagers log.Debug("start in epoch bootstrap: started syncValidatorAccountsState") - err = e.syncValidatorAccountsState(e.epochStartMeta.GetValidatorStatsRootHash()) + validatorStatsRootHashToSync, err := e.getValidatorStatsRootHashToSync(e.epochStartMeta) + if err != nil { + return err + } + + err = e.syncValidatorAccountsState(validatorStatsRootHashToSync) if err != nil { return err } log.Debug("start in epoch bootstrap: syncUserAccountsState") - err = e.syncUserAccountsState(e.epochStartMeta.GetRootHash()) + rootHashToSync, err := e.getRootHashToSync(e.epochStartMeta, e.epochStartMeta.GetRootHash()) + if err != nil { + return err + } + + err = e.syncUserAccountsState(rootHashToSync) if err != nil { return err } @@ -1188,11 +1482,79 @@ func (e *epochStartBootstrap) updateDataForScheduled( return nil, err } - res.rootHashToSync = e.dataSyncerWithScheduled.GetRootHashToSync(shardNotarizedHeader) + rootHashToSync := e.dataSyncerWithScheduled.GetRootHashToSync(shardNotarizedHeader) + + if shardNotarizedHeader.IsHeaderV3() { + rootHashToSync, err = e.getRootHashToSync(shardNotarizedHeader, rootHashToSync) + if err != nil { + return nil, err + } + } + + res.rootHashToSync = rootHashToSync return res, nil } +func (e *epochStartBootstrap) getValidatorStatsRootHashToSync( + meta data.MetaHeaderHandler, +) ([]byte, error) { + if !meta.IsHeaderV3() { + return meta.GetValidatorStatsRootHash(), nil + } + + return getValidatorStatsRootHashFromLastExecutionResult(meta) +} + +func getValidatorStatsRootHashFromLastExecutionResult( + header data.MetaHeaderHandler, +) ([]byte, error) { + lastExecutionResult, err := common.GetLastBaseExecutionResultHandler(header) + if err != nil { + return nil, err + } + + metaLastExecutionResult, ok := lastExecutionResult.(data.BaseMetaExecutionResultHandler) + if !ok { + return nil, fmt.Errorf("%w for getting validator stats root hash", common.ErrWrongTypeAssertion) + } + + validatorStatsRootHash := metaLastExecutionResult.GetValidatorStatsRootHash() + + if len(validatorStatsRootHash) == 0 { + return nil, ErrGetEpochStartValidatorStatsRootHash + } + + return validatorStatsRootHash, nil +} + +func (e *epochStartBootstrap) getRootHashToSync( + header data.HeaderHandler, + rootHashToSync []byte, +) ([]byte, error) { + if !header.IsHeaderV3() { + return rootHashToSync, nil + } + + return getRootHashFromLastExecutionResult(header) +} + +func getRootHashFromLastExecutionResult( + header data.HeaderHandler, +) ([]byte, error) { + lastExecutionResult, err := common.GetLastBaseExecutionResultHandler(header) + if err != nil { + return nil, err + } + rootHash := lastExecutionResult.GetRootHash() + + if len(rootHash) == 0 { + return nil, ErrGetEpochStartRootHash + } + + return rootHash, nil +} + func (e *epochStartBootstrap) syncUserAccountsState(rootHash []byte) error { thr, err := throttler.NewNumGoRoutinesThrottler(int32(e.numConcurrentTrieSyncers)) if err != nil { @@ -1324,23 +1686,22 @@ func (e *epochStartBootstrap) createResolversContainer() error { // this one should only be used before determining the correct shard where the node should reside log.Debug("epochStartBootstrap.createRequestHandler", "shard", e.shardCoordinator.SelfId()) resolversContainerArgs := resolverscontainer.FactoryArgs{ - ShardCoordinator: e.shardCoordinator, - MainMessenger: e.mainMessenger, - FullArchiveMessenger: e.fullArchiveMessenger, - Store: storageService, - Marshalizer: e.coreComponentsHolder.InternalMarshalizer(), - DataPools: e.dataPool, - Uint64ByteSliceConverter: uint64ByteSlice.NewBigEndianConverter(), - NumConcurrentResolvingJobs: 10, - NumConcurrentResolvingTrieNodesJobs: 3, - DataPacker: dataPacker, - TriesContainer: e.trieContainer, - SizeCheckDelta: 0, - InputAntifloodHandler: disabled.NewAntiFloodHandler(), - OutputAntifloodHandler: disabled.NewAntiFloodHandler(), - MainPreferredPeersHolder: disabled.NewPreferredPeersHolder(), - FullArchivePreferredPeersHolder: disabled.NewPreferredPeersHolder(), - PayloadValidator: payloadValidator, + ShardCoordinator: e.shardCoordinator, + MainMessenger: e.mainMessenger, + FullArchiveMessenger: e.fullArchiveMessenger, + Store: storageService, + Marshalizer: e.coreComponentsHolder.InternalMarshalizer(), + DataPools: e.dataPool, + Uint64ByteSliceConverter: uint64ByteSlice.NewBigEndianConverter(), + DataPacker: dataPacker, + TriesContainer: e.trieContainer, + SizeCheckDelta: 0, + InputAntifloodHandler: disabled.NewAntiFloodHandler(), + OutputAntifloodHandler: disabled.NewAntiFloodHandler(), + MainPreferredPeersHolder: disabled.NewPreferredPeersHolder(), + FullArchivePreferredPeersHolder: disabled.NewPreferredPeersHolder(), + PayloadValidator: payloadValidator, + AntifloodConfigsHandler: e.coreComponentsHolder.AntifloodConfigsHandler(), } resolverFactory, err := resolverscontainer.NewMetaResolversContainerFactory(resolversContainerArgs) if err != nil { @@ -1404,13 +1765,61 @@ func (e *epochStartBootstrap) createRequestHandler() error { } func (e *epochStartBootstrap) setEpochStartMetrics() { - if !check.IfNil(e.epochStartMeta) { - metablockEconomics := e.epochStartMeta.GetEpochStartHandler().GetEconomicsHandler() - e.statusHandler.SetStringValue(common.MetricTotalSupply, metablockEconomics.GetTotalSupply().String()) - e.statusHandler.SetStringValue(common.MetricInflation, metablockEconomics.GetTotalNewlyMinted().String()) - e.statusHandler.SetStringValue(common.MetricTotalFees, e.epochStartMeta.GetAccumulatedFees().String()) + if check.IfNil(e.epochStartMeta) { + return + } + + metablockEconomics := e.epochStartMeta.GetEpochStartHandler().GetEconomicsHandler() + e.statusHandler.SetStringValue(common.MetricTotalSupply, metablockEconomics.GetTotalSupply().String()) + e.statusHandler.SetStringValue(common.MetricInflation, metablockEconomics.GetTotalNewlyMinted().String()) + e.statusHandler.SetUInt64Value(common.MetricEpochForEconomicsData, uint64(e.epochStartMeta.GetEpoch())) + + if !e.epochStartMeta.IsHeaderV3() { + e.statusHandler.SetStringValue(common.MetricTotalFees, e.epochStartMeta.GetAccumulatedFeesInEpoch().String()) e.statusHandler.SetStringValue(common.MetricDevRewardsInEpoch, e.epochStartMeta.GetDevFeesInEpoch().String()) - e.statusHandler.SetUInt64Value(common.MetricEpochForEconomicsData, uint64(e.epochStartMeta.GetEpoch())) + return + } + + e.setEpochStartMetricsV3() +} + +func (e *epochStartBootstrap) setEpochStartMetricsV3() { + var prevHashOfEpochChangeProposed []byte + for _, syncedHeader := range e.syncedHeaders { + metaHeader, ok := syncedHeader.(data.MetaHeaderHandler) + if !ok { + continue + } + + if !metaHeader.IsEpochChangeProposed() { + continue + } + + prevHashOfEpochChangeProposed = metaHeader.GetPrevHash() + break + } + + if len(prevHashOfEpochChangeProposed) == 0 { + // should never happen + return + } + + for _, syncedHeader := range e.syncedHeaders { + execResults := syncedHeader.GetExecutionResultsHandlers() + for _, execResult := range execResults { + if !bytes.Equal(prevHashOfEpochChangeProposed, execResult.GetHeaderHash()) { + continue + } + + metaExecResult, okMetaExecResultCast := execResult.(data.BaseMetaExecutionResultHandler) + if !okMetaExecResultCast { + continue + } + + e.statusHandler.SetStringValue(common.MetricTotalFees, metaExecResult.GetAccumulatedFeesInEpoch().String()) + e.statusHandler.SetStringValue(common.MetricDevRewardsInEpoch, metaExecResult.GetDevFeesInEpoch().String()) + return + } } } diff --git a/epochStart/bootstrap/process_test.go b/epochStart/bootstrap/process_test.go index 51a36dd92a4..49843c2bc8b 100644 --- a/epochStart/bootstrap/process_test.go +++ b/epochStart/bootstrap/process_test.go @@ -62,6 +62,8 @@ import ( "github.com/stretchr/testify/require" ) +var errExpected = errors.New("expected error") + func createPkBytes(numShards uint32) map[uint32][]byte { pksbytes := make(map[uint32][]byte, numShards+1) for i := uint32(0); i < numShards; i++ { @@ -112,6 +114,9 @@ func createComponentsForEpochStart() (*mock.CoreComponentsMock, *mock.CryptoComp }, EpochChangeGracePeriodHandlerField: gracePeriod, ChainParametersHandlerField: chainParams, + ProcessConfigsHandlerField: &testscommon.ProcessConfigsHandlerStub{}, + CommonConfigsHandlerField: testscommon.GetDefaultCommonConfigsHandler(), + AntifloodConfigsHandlerField: &testscommon.AntifloodConfigsHandlerStub{}, }, &mock.CryptoComponentsMock{ PubKey: &cryptoMocks.PublicKeyStub{}, @@ -161,6 +166,7 @@ func createMockEpochStartBootstrapArgs( HeartbeatV2: generalCfg.HeartbeatV2, Hardfork: generalCfg.Hardfork, ProofsStorage: generalCfg.ProofsStorage, + ExecutionResultsStorage: generalCfg.ExecutionResultsStorage, EvictionWaitingList: config.EvictionWaitingListConfig{ HashesSize: 100, RootHashesSize: 100, @@ -226,6 +232,15 @@ func createMockEpochStartBootstrapArgs( Shards: 10, }, Requesters: generalCfg.Requesters, + InterceptedDataVerifier: config.InterceptedDataVerifierConfig{ + CacheSpanInSec: 1, + CacheExpiryInSec: 1, + }, + Antiflood: testscommon.GetDefaultAntifloodConfig(), + DirectSentTransactions: config.DirectSentTransactionsConfig{ + CacheSpanInSec: 1, + CacheExpiryInSec: 1, + }, }, EconomicsData: &economicsmocks.EconomicsHandlerMock{ MinGasPriceCalled: func() uint64 { @@ -737,22 +752,37 @@ func TestEpochStartBootstrap_Boostrap(t *testing.T) { t.Run("bootstrap from local storage with StartInEpoch not enabled, should work", func(t *testing.T) { t.Parallel() - testBoostrapByStartInEpochFlag(t, false) + testBoostrapByStartInEpochFlag(t, false, false) }) t.Run("bootstrap from saved epoch, should work", func(t *testing.T) { t.Parallel() - testBoostrapByStartInEpochFlag(t, true) + testBoostrapByStartInEpochFlag(t, true, false) + }) + + t.Run("bootstrap from saved epoch, with supernova, should work", func(t *testing.T) { + t.Parallel() + + testBoostrapByStartInEpochFlag(t, true, true) }) } -func testBoostrapByStartInEpochFlag(t *testing.T, startInEpochEnabled bool) { +func testBoostrapByStartInEpochFlag( + t *testing.T, + startInEpochEnabled bool, + withSupernovaActivated bool, +) { coreComp, cryptoComp := createComponentsForEpochStart() args := createMockEpochStartBootstrapArgs(coreComp, cryptoComp) args.GeneralConfig = testscommon.GetGeneralConfig() args.GeneralConfig.GeneralSettings.StartInEpochEnabled = startInEpochEnabled + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag && withSupernovaActivated + }, + } epoch := uint32(1) shardId := uint32(0) @@ -855,7 +885,6 @@ func TestEpochStartBootstrap_BootstrapStartInEpochNotEnabled(t *testing.T) { } func TestEpochStartBootstrap_BootstrapShouldStartBootstrapProcess(t *testing.T) { - roundsPerEpoch := int64(100) roundDuration := uint64(60000) coreComp, cryptoComp := createComponentsForEpochStart() args := createMockEpochStartBootstrapArgs(coreComp, cryptoComp) @@ -865,7 +894,6 @@ func TestEpochStartBootstrap_BootstrapShouldStartBootstrapProcess(t *testing.T) }, } args.GeneralConfig = testscommon.GetGeneralConfig() - args.GeneralConfig.EpochStartConfig.RoundsPerEpoch = roundsPerEpoch epochStartProvider, err := NewEpochStartBootstrap(args) require.Nil(t, err) @@ -1006,6 +1034,9 @@ func TestCreateSyncers(t *testing.T) { ProofsCalled: func() dataRetriever.ProofsPool { return &dataRetrieverMock.ProofsPoolMock{} }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, } epochStartProvider.whiteListHandler = &testscommon.WhiteListHandlerStub{} epochStartProvider.whiteListerVerifiedTxs = &testscommon.WhiteListHandlerStub{} @@ -2179,6 +2210,9 @@ func TestEpochStartBootstrap_WithDisabledShardIDAsObserver(t *testing.T) { CurrEpochValidatorInfoCalled: func() dataRetriever.ValidatorInfoCacher { return &validatorInfoCacherStub.ValidatorInfoCacherStub{} }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, } epochStartProvider.requestHandler = &testscommon.RequestHandlerStub{} epochStartProvider.epochStartMeta = &block.MetaBlock{Epoch: 0} @@ -2521,6 +2555,9 @@ func TestSyncSetGuardianTransaction(t *testing.T) { ProofsCalled: func() dataRetriever.ProofsPool { return &dataRetrieverMock.ProofsPoolMock{} }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, } epochStartProvider.whiteListHandler = &testscommon.WhiteListHandlerStub{ IsWhiteListedCalled: func(interceptedData process.InterceptedData) bool { @@ -2577,3 +2614,578 @@ func TestSyncSetGuardianTransaction(t *testing.T) { _, found := transactions.SearchFirstData(txHash) assert.True(t, found) } + +func TestEpochStartBoostrap_SyncHeadersV3FromMeta(t *testing.T) { + t.Parallel() + + t.Run("should error if requested header not in returned headers", func(t *testing.T) { + t.Parallel() + + hdrHash1 := []byte("hdrHash1") + hdrHash2 := []byte("hdrHash2") + hdrHash3 := []byte("hdrHash3") + + header1 := &block.Header{ + Nonce: 11, + PrevHash: hdrHash2, + } + + coreComp, cryptoComp := createComponentsForEpochStart() + args := createMockEpochStartBootstrapArgs(coreComp, cryptoComp) + + epochStartProvider, _ := NewEpochStartBootstrap(args) + + epochStartProvider.headersSyncer = &epochStartMocks.HeadersByHashSyncerStub{ + SyncMissingHeadersByHashCalled: func(shardIDs []uint32, headersHashes [][]byte, ctx context.Context) error { + return nil + }, + GetHeadersCalled: func() (m map[string]data.HeaderHandler, err error) { + return map[string]data.HeaderHandler{ + string(hdrHash3): header1, + }, nil + }, + } + + metaBlock := &block.MetaBlockV3{ + Epoch: 2, + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + {HeaderHash: hdrHash1, ShardID: 0}, + }, + Economics: block.Economics{ + PrevEpochStartHash: hdrHash2, + }, + }, + } + + headers, err := epochStartProvider.syncHeadersFrom(metaBlock) + require.Equal(t, epochStart.ErrMissingHeader, err) + require.Nil(t, headers) + }) + + t.Run("should error if failed to get synced header", func(t *testing.T) { + t.Parallel() + + hdrHash1 := []byte("hdrHash1") + hdrHash2 := []byte("hdrHash2") + + coreComp, cryptoComp := createComponentsForEpochStart() + args := createMockEpochStartBootstrapArgs(coreComp, cryptoComp) + + epochStartProvider, _ := NewEpochStartBootstrap(args) + + epochStartProvider.headersSyncer = &epochStartMocks.HeadersByHashSyncerStub{ + SyncMissingHeadersByHashCalled: func(shardIDs []uint32, headersHashes [][]byte, ctx context.Context) error { + return nil + }, + GetHeadersCalled: func() (m map[string]data.HeaderHandler, err error) { + return nil, errExpected + }, + } + + metaBlock := &block.MetaBlockV3{ + Epoch: 2, + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + {HeaderHash: hdrHash1, ShardID: 0}, + }, + Economics: block.Economics{ + PrevEpochStartHash: hdrHash2, + }, + }, + } + + headers, err := epochStartProvider.syncHeadersFrom(metaBlock) + require.Equal(t, errExpected, err) + require.Nil(t, headers) + }) + + t.Run("should error if failed to sync one header", func(t *testing.T) { + t.Parallel() + + hdrHash1 := []byte("hdrHash1") + hdrHash2 := []byte("hdrHash2") + + header1 := &block.Header{ + Nonce: 11, + PrevHash: hdrHash2, + } + + coreComp, cryptoComp := createComponentsForEpochStart() + args := createMockEpochStartBootstrapArgs(coreComp, cryptoComp) + + epochStartProvider, _ := NewEpochStartBootstrap(args) + + expErr := errors.New("expected error") + epochStartProvider.headersSyncer = &epochStartMocks.HeadersByHashSyncerStub{ + SyncMissingHeadersByHashCalled: func(shardIDs []uint32, headersHashes [][]byte, ctx context.Context) error { + return expErr + }, + GetHeadersCalled: func() (m map[string]data.HeaderHandler, err error) { + return map[string]data.HeaderHandler{ + string(hdrHash1): header1, + }, nil + }, + } + + metaBlock := &block.MetaBlockV3{ + Epoch: 2, + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + {HeaderHash: hdrHash1, ShardID: 0}, + }, + Economics: block.Economics{ + PrevEpochStartHash: hdrHash2, + }, + }, + } + + headers, err := epochStartProvider.syncHeadersFrom(metaBlock) + require.Equal(t, expErr, err) + require.Nil(t, headers) + }) + + t.Run("should error if failed to sync one prev header", func(t *testing.T) { + t.Parallel() + + hdrHash1 := []byte("hdrHash1") + hdrHash2 := []byte("hdrHash2") + + header1 := &block.Header{ + Nonce: 11, + PrevHash: hdrHash2, + } + + coreComp, cryptoComp := createComponentsForEpochStart() + args := createMockEpochStartBootstrapArgs(coreComp, cryptoComp) + + epochStartProvider, _ := NewEpochStartBootstrap(args) + + numSyncCalls := 0 + expErr := errors.New("expected error") + epochStartProvider.headersSyncer = &epochStartMocks.HeadersByHashSyncerStub{ + SyncMissingHeadersByHashCalled: func(shardIDs []uint32, headersHashes [][]byte, ctx context.Context) error { + if numSyncCalls > 0 { + return expErr + } + + numSyncCalls++ + + return nil + }, + GetHeadersCalled: func() (m map[string]data.HeaderHandler, err error) { + return map[string]data.HeaderHandler{ + string(hdrHash1): header1, + }, nil + }, + } + + metaBlock := &block.MetaBlockV3{ + Epoch: 2, + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + {HeaderHash: hdrHash1, ShardID: 0}, + }, + Economics: block.Economics{ + PrevEpochStartHash: hdrHash2, + }, + }, + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{}, + }, + }, + } + + headers, err := epochStartProvider.syncHeadersFrom(metaBlock) + require.Equal(t, expErr, err) + require.Nil(t, headers) + }) + + t.Run("should work with meta v3 and shard v2", func(t *testing.T) { + t.Parallel() + + hdrHash1 := []byte("hdrHash1") + hdrHash2 := []byte("hdrHash2") + lastExecMetaHash := []byte("lastExecMetaHash") + + header1 := &block.Header{ + Nonce: 11, + PrevHash: hdrHash2, + } + + lastExecMeta := &block.MetaBlockV3{ + Nonce: 20, + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{}, + }, + }, + } + + coreComp, cryptoComp := createComponentsForEpochStart() + args := createMockEpochStartBootstrapArgs(coreComp, cryptoComp) + + epochStartProvider, _ := NewEpochStartBootstrap(args) + epochStartProvider.headersSyncer = &epochStartMocks.HeadersByHashSyncerStub{ + SyncMissingHeadersByHashCalled: func(shardIDs []uint32, headersHashes [][]byte, ctx context.Context) error { + return nil + }, + GetHeadersCalled: func() (m map[string]data.HeaderHandler, err error) { + return map[string]data.HeaderHandler{ + string(hdrHash1): header1, + string(lastExecMetaHash): lastExecMeta, + }, nil + }, + } + + metaBlock := &block.MetaBlockV3{ + Epoch: 2, + Nonce: 21, + PrevHash: lastExecMetaHash, + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + { + HeaderHash: hdrHash1, + ShardID: 0, + LastFinishedMetaBlock: lastExecMetaHash, + }, + }, + Economics: block.Economics{ + PrevEpochStartHash: hdrHash2, + }, + }, + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 20, + HeaderHash: lastExecMetaHash, + }, + }, + }, + } + + headers, err := epochStartProvider.syncHeadersFrom(metaBlock) + require.Nil(t, err) + require.Equal(t, 2, len(headers)) + }) + + t.Run("should work with meta v3 and shard v3", func(t *testing.T) { + t.Parallel() + + hdrHash1 := []byte("hdrHash1") + hdrHash2 := []byte("hdrHash2") + hdrHash3 := []byte("hdrHash3") + hdrHash4 := []byte("hdrHash4") + lastExecMetaHash := []byte("lastExecMetaHash") + + header1 := &block.HeaderV3{ + Nonce: 12, + PrevHash: hdrHash2, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 10, + HeaderHash: hdrHash3, + }, + }, + } + header2 := &block.HeaderV3{ + Nonce: 11, + PrevHash: hdrHash3, + LastExecutionResult: &block.ExecutionResultInfo{}, + } + header3 := &block.HeaderV3{ + Nonce: 10, + LastExecutionResult: &block.ExecutionResultInfo{}, + MetaBlockHashes: [][]byte{hdrHash4, lastExecMetaHash}, + } + + lastExecMeta := &block.MetaBlockV3{ + Nonce: 20, + PrevHash: hdrHash4, + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 19, + }, + }, + }, + } + + metaHeader4 := &block.MetaBlockV3{ + Nonce: 19, + } + + coreComp, cryptoComp := createComponentsForEpochStart() + args := createMockEpochStartBootstrapArgs(coreComp, cryptoComp) + + epochStartProvider, _ := NewEpochStartBootstrap(args) + epochStartProvider.headersSyncer = &epochStartMocks.HeadersByHashSyncerStub{ + SyncMissingHeadersByHashCalled: func(shardIDs []uint32, headersHashes [][]byte, ctx context.Context) error { + return nil + }, + GetHeadersCalled: func() (m map[string]data.HeaderHandler, err error) { + return map[string]data.HeaderHandler{ + string(hdrHash1): header1, + string(hdrHash2): header2, + string(hdrHash3): header3, + string(hdrHash4): metaHeader4, + string(lastExecMetaHash): lastExecMeta, + }, nil + }, + } + + metaBlock := &block.MetaBlockV3{ + Epoch: 2, + Nonce: 21, + PrevHash: lastExecMetaHash, + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + { + HeaderHash: hdrHash1, + ShardID: 0, + LastFinishedMetaBlock: lastExecMetaHash, + }, + }, + Economics: block.Economics{ + PrevEpochStartHash: hdrHash2, + }, + }, + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: lastExecMetaHash, + HeaderNonce: 20, + }, + }, + }, + } + + headers, err := epochStartProvider.syncHeadersFrom(metaBlock) + require.Nil(t, err) + require.Equal(t, 5, len(headers)) + }) +} + +func TestGetStartOfEpochRootHashFromExecutionResults(t *testing.T) { + t.Parallel() + + t.Run("should fail if invalid execution result", func(t *testing.T) { + t.Parallel() + + metaBlock := &block.MetaBlockV3{ + ExecutionResults: []*block.MetaExecutionResult{ + nil, + }, + } + + retRootHash, err := getRootHashFromLastExecutionResult(metaBlock) + require.Error(t, err) + require.Nil(t, retRootHash) + }) + + t.Run("shoud fail if not able to find epoch start mini blocks", func(t *testing.T) { + t.Parallel() + + mbHeader1 := &block.MiniBlockHeader{ + Hash: []byte("mbHeaderHash1"), + Type: block.TxBlock, + SenderShardID: 1, + ReceiverShardID: 1, + } + mbHeader2 := &block.MiniBlockHeader{ + Hash: []byte("mbHeaderHash2"), + Type: block.TxBlock, + SenderShardID: 2, + ReceiverShardID: 2, + } + mbHeader3 := &block.MiniBlockHeader{ + Hash: []byte("mbHeaderHash3"), + Type: block.InvalidBlock, + SenderShardID: 3, + ReceiverShardID: 3, + } + + miniBlockHeaderHandlers := []block.MiniBlockHeader{ + *mbHeader1, + *mbHeader2, + } + + metaBlock := &block.MetaBlockV3{ + MiniBlockHeaders: miniBlockHeaderHandlers, + LastExecutionResult: &block.MetaExecutionResultInfo{ // this should not be considered + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash1"), + }, + }, + }, + ExecutionResults: []*block.MetaExecutionResult{ + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash2"), + }, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + *mbHeader1, + *mbHeader2, + }, + }, + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash2"), + }, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + *mbHeader3, + }, + }, + }, + } + + retRootHash, err := getRootHashFromLastExecutionResult(metaBlock) + require.Equal(t, ErrGetEpochStartRootHash, err) + require.Nil(t, retRootHash) + }) + + t.Run("shoud work", func(t *testing.T) { + t.Parallel() + + expRootHash := []byte("expRootHash") + + mbHeader1 := &block.MiniBlockHeader{ + Hash: []byte("mbHeaderHash1"), + Type: block.TxBlock, + SenderShardID: 1, + ReceiverShardID: 1, + } + mbHeader2 := &block.MiniBlockHeader{ + Hash: []byte("mbHeaderHash2"), + Type: block.TxBlock, + SenderShardID: 2, + ReceiverShardID: 2, + } + mbHeader3 := &block.MiniBlockHeader{ + Hash: []byte("mbHeaderHash3"), + Type: block.PeerBlock, + SenderShardID: 3, + ReceiverShardID: 3, + } + + miniBlockHeaderHandlers := []block.MiniBlockHeader{ + *mbHeader1, + *mbHeader2, + } + + metaBlock := &block.MetaBlockV3{ + MiniBlockHeaders: miniBlockHeaderHandlers, + LastExecutionResult: &block.MetaExecutionResultInfo{ // this should not be considered + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash1"), + HeaderNonce: 3, + HeaderRound: 3, + RootHash: expRootHash, + }, + }, + }, + ExecutionResults: []*block.MetaExecutionResult{ + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash2"), + HeaderNonce: 2, + HeaderRound: 2, + RootHash: []byte("rootHash2"), + }, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + *mbHeader1, + *mbHeader2, + }, + }, + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash2"), + HeaderNonce: 3, + HeaderRound: 3, + RootHash: expRootHash, + }, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + *mbHeader3, + }, + }, + }, + } + + retRootHash, err := getRootHashFromLastExecutionResult(metaBlock) + require.Nil(t, err) + require.Equal(t, expRootHash, retRootHash) + }) +} + +func Test_GetValidatorStatsRootHashFromLastExecutionResult(t *testing.T) { + t.Parallel() + + t.Run("should fail if root hash is invalid", func(t *testing.T) { + t.Parallel() + + metaBlock := &block.MetaBlockV3{ + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash1"), + HeaderNonce: 3, + HeaderRound: 3, + RootHash: []byte("otherRootHash"), + }, + ValidatorStatsRootHash: nil, + }, + }, + } + + retRootHash, err := getValidatorStatsRootHashFromLastExecutionResult(metaBlock) + require.Nil(t, retRootHash) + require.Equal(t, ErrGetEpochStartValidatorStatsRootHash, err) + }) + + t.Run("shoud work", func(t *testing.T) { + t.Parallel() + + expRootHash := []byte("expRootHash") + + metaBlock := &block.MetaBlockV3{ + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash1"), + HeaderNonce: 3, + HeaderRound: 3, + RootHash: []byte("otherRootHash"), + }, + ValidatorStatsRootHash: expRootHash, + }, + }, + ExecutionResults: []*block.MetaExecutionResult{ + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash2"), + HeaderNonce: 2, + HeaderRound: 2, + RootHash: []byte("otherRootHash"), + }, + }, + }, + }, + } + + retRootHash, err := getValidatorStatsRootHashFromLastExecutionResult(metaBlock) + require.Nil(t, err) + require.Equal(t, expRootHash, retRootHash) + }) +} diff --git a/epochStart/bootstrap/shardStorageHandler.go b/epochStart/bootstrap/shardStorageHandler.go index 469089ee973..72a73b5a8fc 100644 --- a/epochStart/bootstrap/shardStorageHandler.go +++ b/epochStart/bootstrap/shardStorageHandler.go @@ -10,13 +10,14 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/epochStart" "github.com/multiversx/mx-chain-go/epochStart/bootstrap/disabled" "github.com/multiversx/mx-chain-go/process/block/bootstrapStorage" "github.com/multiversx/mx-chain-go/storage/factory" - logger "github.com/multiversx/mx-chain-logger-go" ) type shardStorageHandler struct { @@ -182,9 +183,8 @@ func (ssh *shardStorageHandler) saveEpochStartMetaHdrs(components *ComponentsNee func (ssh *shardStorageHandler) saveEpochStartShardHdrs(components *ComponentsNeededForBootstrap) error { for _, hdr := range components.Headers { - if !hdr.IsStartOfEpochBlock() { - continue - } + // not only start of epoch header should be saved at this point, we should save + // also intermediate headers up to last executed header isForCurrentShard := hdr.GetShardID() == ssh.shardCoordinator.SelfId() if !isForCurrentShard { @@ -252,7 +252,7 @@ func getProcessedMiniBlocksForFinishedMeta( func getNeededMetaBlock( referencedMetaBlockHash []byte, headers map[string]data.HeaderHandler, -) (*block.MetaBlock, error) { +) (data.MetaHeaderHandler, error) { header, ok := headers[string(referencedMetaBlockHash)] if !ok { return nil, fmt.Errorf("%w in getProcessedMiniBlocksForFinishedMeta: hash: %s", @@ -260,7 +260,7 @@ func getNeededMetaBlock( hex.EncodeToString(referencedMetaBlockHash)) } - neededMeta, ok := header.(*block.MetaBlock) + neededMeta, ok := header.(data.MetaHeaderHandler) if !ok { return nil, epochStart.ErrWrongTypeAssertion } @@ -272,7 +272,7 @@ func getNeededMetaBlock( } func getProcessedMiniBlocks( - metaBlock *block.MetaBlock, + metaBlock data.MetaHeaderHandler, shardID uint32, processedMiniBlocks []bootstrapStorage.MiniBlocksInMeta, referencedMetaBlockHash []byte, @@ -469,7 +469,7 @@ func getProcessedMiniBlockHashesForMetaBlockHash( epochStart.ErrMissingHeader, hex.EncodeToString(metaBlockHash)) } - neededMeta, ok := metaHeaderHandler.(*block.MetaBlock) + neededMeta, ok := metaHeaderHandler.(data.MetaHeaderHandler) if !ok { return nil, epochStart.ErrWrongTypeAssertion } @@ -609,7 +609,7 @@ func getEpochShardDataAndNeededMetaBlock( shardID uint32, meta data.MetaHeaderHandler, headers map[string]data.HeaderHandler, -) (data.EpochStartShardDataHandler, *block.MetaBlock, error) { +) (data.EpochStartShardDataHandler, data.MetaHeaderHandler, error) { epochShardData, err := getEpochStartShardData(meta, shardID) if err != nil { @@ -623,7 +623,7 @@ func getEpochShardDataAndNeededMetaBlock( hex.EncodeToString(epochShardData.GetFirstPendingMetaBlock())) } - neededMeta, ok := header.(*block.MetaBlock) + neededMeta, ok := header.(data.MetaHeaderHandler) if !ok { return nil, nil, epochStart.ErrWrongTypeAssertion } @@ -634,7 +634,7 @@ func getEpochShardDataAndNeededMetaBlock( return epochShardData, neededMeta, nil } -func getMiniBlocksInfo(epochShardData data.EpochStartShardDataHandler, neededMeta *block.MetaBlock, shardID uint32) *miniBlocksInfo { +func getMiniBlocksInfo(epochShardData data.EpochStartShardDataHandler, neededMeta data.MetaHeaderHandler, shardID uint32) *miniBlocksInfo { mbsInfo := &miniBlocksInfo{ miniBlockHashes: make([][]byte, 0), fullyProcessed: make([]bool, 0), @@ -672,7 +672,7 @@ func setMiniBlocksInfoWithPendingMiniBlocks(epochShardData data.EpochStartShardD } } -func setMiniBlocksInfoWithProcessedMiniBlocks(neededMeta *block.MetaBlock, shardID uint32, mbsInfo *miniBlocksInfo) { +func setMiniBlocksInfoWithProcessedMiniBlocks(neededMeta data.MetaHeaderHandler, shardID uint32, mbsInfo *miniBlocksInfo) { miniBlockHeaders := getProcessedMiniBlockHeaders(neededMeta, shardID, mbsInfo.pendingMiniBlocksMap) for mbHash, mbHeader := range miniBlockHeaders { log.Debug("shardStorageHandler.setMiniBlocksInfoWithProcessedMiniBlocks", @@ -714,7 +714,7 @@ func createProcessedAndPendingMiniBlocks( return processedMiniBlocks, pendingMiniBlocks } -func getProcessedMiniBlockHeaders(metaBlock *block.MetaBlock, destShardID uint32, pendingMBsMap map[string]struct{}) map[string]block.MiniBlockHeader { +func getProcessedMiniBlockHeaders(metaBlock data.MetaHeaderHandler, destShardID uint32, pendingMBsMap map[string]struct{}) map[string]block.MiniBlockHeader { processedMiniBlockHeaders := make(map[string]block.MiniBlockHeader) miniBlockHeadersDestMe := getMiniBlockHeadersForDest(metaBlock, destShardID) for hash, mbh := range miniBlockHeadersDestMe { @@ -740,7 +740,11 @@ func (ssh *shardStorageHandler) saveLastCrossNotarizedHeaders( } lastCrossMetaHdrHash := shardData.GetLastFinishedMetaBlock() - if len(shardData.GetPendingMiniBlockHeaderHandlers()) == 0 { + shouldUpdateLastCrossMeta, err := shouldUpdateLastCrossMetaToPending(shardData, headers) + if err != nil { + return nil, err + } + if shouldUpdateLastCrossMeta { log.Debug("saveLastCrossNotarizedHeaders changing lastCrossMetaHdrHash", "initial hash", lastCrossMetaHdrHash, "final hash", shardData.GetFirstPendingMetaBlock()) lastCrossMetaHdrHash = shardData.GetFirstPendingMetaBlock() } @@ -761,7 +765,7 @@ func (ssh *shardStorageHandler) saveLastCrossNotarizedHeaders( hex.EncodeToString(lastCrossMetaHdrHash)) } - neededMeta, ok := neededHdr.(*block.MetaBlock) + neededMeta, ok := neededHdr.(data.MetaHeaderHandler) if !ok { return nil, epochStart.ErrWrongTypeAssertion } @@ -781,6 +785,60 @@ func (ssh *shardStorageHandler) saveLastCrossNotarizedHeaders( return crossNotarizedHdrs, nil } +func shouldUpdateLastCrossMetaToPending(shardData data.EpochStartShardDataHandler, headers map[string]data.HeaderHandler) (bool, error) { + pendingMbs := shardData.GetPendingMiniBlockHeaderHandlers() + if len(pendingMbs) == 0 { + return true, nil + } + + shardHeader, ok := headers[string(shardData.GetHeaderHash())] + if !ok { + return false, fmt.Errorf("%w in shouldUpdateLastCrossMetaToPending: hash: %s", + epochStart.ErrMissingHeader, + hex.EncodeToString(shardData.GetHeaderHash())) + } + + if shardHeader.IsHeaderV3() { + return allPendingMbsAreProposed(pendingMbs, shardHeader, headers), nil + } + + return false, nil +} + +func allPendingMbsAreProposed( + pendingMbs []data.MiniBlockHeaderHandler, + header data.HeaderHandler, + headers map[string]data.HeaderHandler, +) bool { + var proposedMbs []data.MiniBlockHeaderHandler + currentHeader := header + for { + proposedMbs = currentHeader.GetMiniBlockHeaderHandlers() + if len(proposedMbs) > 0 { + break + } + + currentHeader = headers[string(currentHeader.GetPrevHash())] + if currentHeader == nil { + log.Warn("headerNotFound") + break + } + } + + proposedMbMap := make(map[string]struct{}) + for _, mb := range proposedMbs { + proposedMbMap[string(mb.GetHash())] = struct{}{} + } + + for _, pendingMb := range pendingMbs { + if _, exists := proposedMbMap[string(pendingMb.GetHash())]; !exists { + return false + } + } + + return true +} + func updateLastCrossMetaHdrHashIfNeeded( headers map[string]data.HeaderHandler, shardData data.EpochStartShardDataHandler, @@ -849,22 +907,21 @@ func (ssh *shardStorageHandler) saveTriggerRegistry(components *ComponentsNeeded return nil, err } - triggerReg := block.ShardTriggerRegistry{ - Epoch: shardHeader.GetEpoch(), - MetaEpoch: metaBlock.GetEpoch(), - CurrentRoundIndex: int64(shardHeader.GetRound()), - EpochStartRound: shardHeader.GetRound(), - EpochMetaBlockHash: metaBlockHash, - IsEpochStart: true, - NewEpochHeaderReceived: true, - EpochFinalityAttestingRound: 0, - EpochStartShardHeader: &block.Header{}, - } + triggerReg := epochStart.CreateShardRegistryHandler(shardHeader) + _ = triggerReg.SetEpoch(shardHeader.GetEpoch()) + _ = triggerReg.SetMetaEpoch(metaBlock.GetEpoch()) + _ = triggerReg.SetCurrentRoundIndex(int64(shardHeader.GetRound())) + _ = triggerReg.SetEpochStartRound(shardHeader.GetRound()) + _ = triggerReg.SetEpochMetaBlockHash(metaBlockHash) + _ = triggerReg.SetIsEpochStart(true) + _ = triggerReg.SetNewEpochHeaderReceived(true) + _ = triggerReg.SetEpochFinalityAttestingRound(0) + _ = triggerReg.SetEpochStartHeaderHandler(shardHeader) bootstrapKey := []byte(fmt.Sprint(shardHeader.GetRound())) trigInternalKey := append([]byte(common.TriggerRegistryKeyPrefix), bootstrapKey...) - triggerRegBytes, err := ssh.marshalizer.Marshal(&triggerReg) + triggerRegBytes, err := ssh.marshalizer.Marshal(triggerReg) if err != nil { return nil, err } @@ -882,34 +939,78 @@ func (ssh *shardStorageHandler) saveTriggerRegistry(components *ComponentsNeeded return bootstrapKey, nil } -func getMiniBlockHeadersForDest(metaBlock *block.MetaBlock, destId uint32) map[string]block.MiniBlockHeader { +func getMiniBlockHeadersForDest(metaBlock data.MetaHeaderHandler, destId uint32) map[string]block.MiniBlockHeader { hashDst := make(map[string]block.MiniBlockHeader) - for i := 0; i < len(metaBlock.ShardInfo); i++ { - if metaBlock.ShardInfo[i].ShardID == destId { + for i := 0; i < len(metaBlock.GetShardInfoHandlers()); i++ { + if metaBlock.GetShardInfoHandlers()[i].GetShardID() == destId { continue } - for _, val := range metaBlock.ShardInfo[i].ShardMiniBlockHeaders { - isCrossShardDestMe := val.ReceiverShardID == destId && val.SenderShardID != destId + for _, val := range metaBlock.GetShardInfoHandlers()[i].GetShardMiniBlockHeaderHandlers() { + isCrossShardDestMe := val.GetReceiverShardID() == destId && val.GetSenderShardID() != destId if !isCrossShardDestMe { continue } - hashDst[string(val.Hash)] = val + miniBlockHeader, ok := val.(*block.MiniBlockHeader) + if !ok { + log.Warn("wrong type assertion for mini block header handler", "err", epochStart.ErrWrongTypeAssertion) + continue + } + hashDst[string(val.GetHash())] = *miniBlockHeader } } - for _, val := range metaBlock.MiniBlockHeaders { - isCrossShardDestMe := (val.ReceiverShardID == destId || val.ReceiverShardID == core.AllShardId) && val.SenderShardID != destId + miniBlockHandlers := getMetaHeaderMiniBlockHandlersFromExecutionResults(metaBlock) + + for _, val := range miniBlockHandlers { + isCrossShardDestMe := (val.GetReceiverShardID() == destId || val.GetReceiverShardID() == core.AllShardId) && val.GetSenderShardID() != destId if !isCrossShardDestMe { continue } - hashDst[string(val.Hash)] = val + + miniBlockHeader, ok := val.(*block.MiniBlockHeader) + if !ok { + log.Warn("wrong type assertion for mini block header handler", "err", epochStart.ErrWrongTypeAssertion) + continue + } + hashDst[string(val.GetHash())] = *miniBlockHeader } return hashDst } +func getMetaHeaderMiniBlockHandlersFromExecutionResults( + metaBlock data.MetaHeaderHandler, +) []data.MiniBlockHeaderHandler { + if check.IfNil(metaBlock) { + return nil + } + + miniBlockHeaderHandlers := metaBlock.GetMiniBlockHeaderHandlers() + if !metaBlock.IsHeaderV3() { + return miniBlockHeaderHandlers + } + + baseExecutionResults := metaBlock.GetExecutionResultsHandlers() + if len(baseExecutionResults) == 0 { + return nil + } + + execResultsMiniBlockHeaderHandlers := make([]data.MiniBlockHeaderHandler, 0) + for _, baseExecutionResult := range baseExecutionResults { + miniBlockHeaderHandlers, err := common.GetMiniBlocksHeaderHandlersFromExecResult(baseExecutionResult) + if err != nil { + log.Warn("failed to get mini blocks header handlers from execution result", "err", err) + return nil + } + + execResultsMiniBlockHeaderHandlers = append(execResultsMiniBlockHeaderHandlers, miniBlockHeaderHandlers...) + } + + return execResultsMiniBlockHeaderHandlers +} + // IsInterfaceNil returns true if there is no value under the interface func (ssh *shardStorageHandler) IsInterfaceNil() bool { return ssh == nil diff --git a/epochStart/bootstrap/shardStorageHandler_test.go b/epochStart/bootstrap/shardStorageHandler_test.go index 018bc4b99b8..6edc884fb40 100644 --- a/epochStart/bootstrap/shardStorageHandler_test.go +++ b/epochStart/bootstrap/shardStorageHandler_test.go @@ -13,6 +13,9 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/epochStart" "github.com/multiversx/mx-chain-go/process/block/bootstrapStorage" @@ -21,8 +24,6 @@ import ( epochStartMocks "github.com/multiversx/mx-chain-go/testscommon/bootstrapMocks/epochStart" "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestNewShardStorageHandler_ShouldWork(t *testing.T) { @@ -701,10 +702,11 @@ func TestShardStorageHandler_saveLastCrossNotarizedHeadersWithoutScheduledWrongT lastFinishedMetaBlock := "last finished meta block" headers := map[string]data.HeaderHandler{ - shard0HeaderHash: &block.Header{Nonce: 100}, lastFinishedMetaBlock: &block.Header{Nonce: 99}, // wrong header type + shard0HeaderHash: &block.Header{Nonce: 100}, + lastFinishedMetaBlock: &block.Header{Nonce: 99}, // wrong header type } shardInfo := []block.ShardData{{HeaderHash: []byte(shard0HeaderHash), ShardMiniBlockHeaders: nil, ShardID: 0}} - epochStartShardData := createDefaultEpochStartShardData([]byte(lastFinishedMetaBlock), []byte("")) + epochStartShardData := createDefaultEpochStartShardData([]byte(lastFinishedMetaBlock), []byte(shard0HeaderHash)) meta := &block.MetaBlock{ Nonce: 100, @@ -739,7 +741,7 @@ func TestShardStorageHandler_saveLastCrossNotarizedHeadersWithoutScheduledErrorW shard0HeaderHash: &block.Header{Nonce: 100}, lastFinishedMetaBlock: &block.MetaBlock{Nonce: 99}, } shardInfo := []block.ShardData{{HeaderHash: []byte(shard0HeaderHash), ShardMiniBlockHeaders: nil, ShardID: 0}} - epochStartShardData := createDefaultEpochStartShardData([]byte(lastFinishedMetaBlock), []byte("")) + epochStartShardData := createDefaultEpochStartShardData([]byte(lastFinishedMetaBlock), []byte(shard0HeaderHash)) meta := &block.MetaBlock{ Nonce: 100, @@ -770,7 +772,7 @@ func TestShardStorageHandler_saveLastCrossNotarizedHeadersWithoutScheduled(t *te shard0HeaderHash: &block.Header{Nonce: 100}, lastFinishedMetaBlock: &block.MetaBlock{Nonce: 99}, } shardInfo := []block.ShardData{{HeaderHash: []byte(shard0HeaderHash), ShardMiniBlockHeaders: nil, ShardID: 0}} - epochStartShardData := createDefaultEpochStartShardData([]byte(lastFinishedMetaBlock), []byte("")) + epochStartShardData := createDefaultEpochStartShardData([]byte(lastFinishedMetaBlock), []byte(shard0HeaderHash)) meta := &block.MetaBlock{ Nonce: 100, @@ -1708,3 +1710,363 @@ func Test_setMiniBlocksInfoWithPendingMiniBlocks(t *testing.T) { assert.Equal(t, indexOfLastTxProcessed, mbsInfo.indexOfLastTxProcessed[0]) }) } + +func TestGetMetaHeadersMiniBlockHandlerFromExecutionResults(t *testing.T) { + t.Parallel() + + t.Run("nil header should return nil", func(t *testing.T) { + t.Parallel() + + retMiniBlockHandlers := getMetaHeaderMiniBlockHandlersFromExecutionResults(nil) + require.Nil(t, retMiniBlockHandlers) + }) + + t.Run("if not header v3, should return miniblock headers from header directly", func(t *testing.T) { + t.Parallel() + + mbHeader1 := &block.MiniBlockHeader{ + Hash: []byte("mbHeaderHash1"), + SenderShardID: 1, + ReceiverShardID: 1, + } + mbHeader2 := &block.MiniBlockHeader{ + Hash: []byte("mbHeaderHash2"), + SenderShardID: 2, + ReceiverShardID: 2, + } + miniBlockHeaderHandlers := []block.MiniBlockHeader{ + *mbHeader1, + *mbHeader2, + } + + metaBlock := &block.MetaBlock{ + MiniBlockHeaders: miniBlockHeaderHandlers, + } + + expMiniBlockHeaderHandlers := []data.MiniBlockHeaderHandler{ + mbHeader1, + mbHeader2, + } + + retMiniBlockHandlers := getMetaHeaderMiniBlockHandlersFromExecutionResults(metaBlock) + + require.Equal(t, expMiniBlockHeaderHandlers, retMiniBlockHandlers) + }) + + t.Run("if header v3, should return nil if no execution results on header", func(t *testing.T) { + t.Parallel() + + mbHeader1 := &block.MiniBlockHeader{ + Hash: []byte("mbHeaderHash1"), + SenderShardID: 1, + ReceiverShardID: 1, + } + mbHeader2 := &block.MiniBlockHeader{ + Hash: []byte("mbHeaderHash2"), + SenderShardID: 2, + ReceiverShardID: 2, + } + miniBlockHeaderHandlers := []block.MiniBlockHeader{ + *mbHeader1, + *mbHeader2, + } + + metaBlock := &block.MetaBlockV3{ + MiniBlockHeaders: miniBlockHeaderHandlers, + ExecutionResults: make([]*block.MetaExecutionResult, 0), + } + + retMiniBlockHandlers := getMetaHeaderMiniBlockHandlersFromExecutionResults(metaBlock) + + require.Nil(t, retMiniBlockHandlers) + }) + + t.Run("if header v3, should return nil if not able to get mini blocks handlers from execution result", func(t *testing.T) { + t.Parallel() + + mbHeader1 := &block.MiniBlockHeader{ + Hash: []byte("mbHeaderHash1"), + SenderShardID: 1, + ReceiverShardID: 1, + } + mbHeader2 := &block.MiniBlockHeader{ + Hash: []byte("mbHeaderHash2"), + SenderShardID: 2, + ReceiverShardID: 2, + } + + miniBlockHeaderHandlers := []block.MiniBlockHeader{ + *mbHeader1, + *mbHeader2, + } + + metaBlock := &block.MetaBlockV3{ + MiniBlockHeaders: miniBlockHeaderHandlers, + ExecutionResults: []*block.MetaExecutionResult{ + nil, + }, + } + + retMiniBlockHandlers := getMetaHeaderMiniBlockHandlersFromExecutionResults(metaBlock) + + require.Nil(t, retMiniBlockHandlers) + }) + + t.Run("if header v3, should get mini block handlers from execution results", func(t *testing.T) { + t.Parallel() + + mbHeader1 := &block.MiniBlockHeader{ + Hash: []byte("mbHeaderHash1"), + SenderShardID: 1, + ReceiverShardID: 1, + } + mbHeader2 := &block.MiniBlockHeader{ + Hash: []byte("mbHeaderHash2"), + SenderShardID: 2, + ReceiverShardID: 2, + } + mbHeader3 := &block.MiniBlockHeader{ + Hash: []byte("mbHeaderHash3"), + SenderShardID: 3, + ReceiverShardID: 3, + } + + miniBlockHeaderHandlers := []block.MiniBlockHeader{ + *mbHeader1, + *mbHeader2, + } + + metaBlock := &block.MetaBlockV3{ + MiniBlockHeaders: miniBlockHeaderHandlers, + LastExecutionResult: &block.MetaExecutionResultInfo{ // this should not be considered + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash1"), + HeaderNonce: 1, + HeaderRound: 1, + }, + }, + }, + ExecutionResults: []*block.MetaExecutionResult{ + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash2"), + HeaderNonce: 2, + HeaderRound: 2, + }, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + *mbHeader1, + *mbHeader2, + }, + }, + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash2"), + HeaderNonce: 3, + HeaderRound: 3, + }, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + *mbHeader3, + }, + }, + }, + } + + retMiniBlockHandlers := getMetaHeaderMiniBlockHandlersFromExecutionResults(metaBlock) + + expMiniBlockHeaderHandlers := []data.MiniBlockHeaderHandler{ + mbHeader1, + mbHeader2, + mbHeader3, + } + + require.Equal(t, expMiniBlockHeaderHandlers, retMiniBlockHandlers) + }) +} + +func createPendingMbsFromHashes(hashes ...[]byte) []data.MiniBlockHeaderHandler { + pendingMbs := make([]data.MiniBlockHeaderHandler, len(hashes)) + for i, hash := range hashes { + pendingMbs[i] = &block.MiniBlockHeader{Hash: hash} + } + return pendingMbs +} + +func createMbHeadersFromHashes(hashes ...[]byte) []block.MiniBlockHeader { + mbHeaders := make([]block.MiniBlockHeader, len(hashes)) + for i, hash := range hashes { + mbHeaders[i] = block.MiniBlockHeader{Hash: hash} + } + return mbHeaders +} + +func Test_allPendingMbsAreProposed(t *testing.T) { + t.Parallel() + + t.Run("empty pending mbs - should return true", func(t *testing.T) { + t.Parallel() + + result := allPendingMbsAreProposed([]data.MiniBlockHeaderHandler{}, &block.Header{}, nil) + assert.True(t, result) + }) + + t.Run("all pending mbs are proposed in current header - should return true", func(t *testing.T) { + t.Parallel() + + mbHash1, mbHash2 := []byte("mbHash1"), []byte("mbHash2") + pendingMbs := createPendingMbsFromHashes(mbHash1, mbHash2) + header := &block.Header{MiniBlockHeaders: createMbHeadersFromHashes(mbHash1, mbHash2, []byte("extra"))} + + result := allPendingMbsAreProposed(pendingMbs, header, nil) + assert.True(t, result) + }) + + t.Run("some pending mbs are not proposed - should return false", func(t *testing.T) { + t.Parallel() + + mbHash1, mbHash2, mbHash3 := []byte("mbHash1"), []byte("mbHash2"), []byte("mbHash3") + pendingMbs := createPendingMbsFromHashes(mbHash1, mbHash2, mbHash3) + header := &block.Header{MiniBlockHeaders: createMbHeadersFromHashes(mbHash1, mbHash2)} // mbHash3 missing + + result := allPendingMbsAreProposed(pendingMbs, header, nil) + assert.False(t, result) + }) + + t.Run("current header has no mbs but prev header has all - should return true", func(t *testing.T) { + t.Parallel() + + mbHash1, mbHash2 := []byte("mbHash1"), []byte("mbHash2") + prevHeaderHash := []byte("prevHeaderHash") + + pendingMbs := createPendingMbsFromHashes(mbHash1, mbHash2) + currentHeader := &block.Header{PrevHash: prevHeaderHash} + headers := map[string]data.HeaderHandler{ + string(prevHeaderHash): &block.Header{MiniBlockHeaders: createMbHeadersFromHashes(mbHash1, mbHash2)}, + } + + result := allPendingMbsAreProposed(pendingMbs, currentHeader, headers) + assert.True(t, result) + }) + + t.Run("traverse multiple headers to find mbs - should return true", func(t *testing.T) { + t.Parallel() + + mbHash1 := []byte("mbHash1") + prevHash1, prevHash2 := []byte("prevHash1"), []byte("prevHash2") + + pendingMbs := createPendingMbsFromHashes(mbHash1) + currentHeader := &block.Header{PrevHash: prevHash1} + headers := map[string]data.HeaderHandler{ + string(prevHash1): &block.Header{PrevHash: prevHash2}, + string(prevHash2): &block.Header{MiniBlockHeaders: createMbHeadersFromHashes(mbHash1)}, + } + + result := allPendingMbsAreProposed(pendingMbs, currentHeader, headers) + assert.True(t, result) + }) + + t.Run("prev header not found in map - should return false", func(t *testing.T) { + t.Parallel() + + pendingMbs := createPendingMbsFromHashes([]byte("mbHash1")) + currentHeader := &block.Header{PrevHash: []byte("missingHash")} + + result := allPendingMbsAreProposed(pendingMbs, currentHeader, map[string]data.HeaderHandler{}) + assert.False(t, result) + }) + + t.Run("header v3 traversal - should return true", func(t *testing.T) { + t.Parallel() + + mbHash1 := []byte("mbHash1") + prevHeaderHash := []byte("prevHeaderHash") + + pendingMbs := createPendingMbsFromHashes(mbHash1) + currentHeader := &block.HeaderV3{PrevHash: prevHeaderHash} + headers := map[string]data.HeaderHandler{ + string(prevHeaderHash): &block.HeaderV3{ + MiniBlockHeaders: createMbHeadersFromHashes(mbHash1), + }, + } + + result := allPendingMbsAreProposed(pendingMbs, currentHeader, headers) + assert.True(t, result) + }) +} + +func Test_shouldUpdateLastCrossMetaToPending(t *testing.T) { + t.Parallel() + + createShardDataStub := func(pendingMbs []data.MiniBlockHeaderHandler, headerHash []byte) *epochStartMocks.EpochStartShardDataStub { + return &epochStartMocks.EpochStartShardDataStub{ + GetPendingMiniBlockHeaderHandlersCalled: func() []data.MiniBlockHeaderHandler { return pendingMbs }, + GetHeaderHashCalled: func() []byte { return headerHash }, + } + } + + t.Run("no pending mbs - should return true", func(t *testing.T) { + t.Parallel() + + shardData := createShardDataStub([]data.MiniBlockHeaderHandler{}, nil) + result, err := shouldUpdateLastCrossMetaToPending(shardData, nil) + assert.Nil(t, err) + assert.True(t, result) + }) + + t.Run("header not found - should return error", func(t *testing.T) { + t.Parallel() + + shardData := createShardDataStub(createPendingMbsFromHashes([]byte("mb1")), []byte("missing")) + result, err := shouldUpdateLastCrossMetaToPending(shardData, map[string]data.HeaderHandler{}) + assert.True(t, errors.Is(err, epochStart.ErrMissingHeader)) + assert.False(t, result) + }) + + t.Run("non-v3 header - should return false", func(t *testing.T) { + t.Parallel() + + headerHash := []byte("headerHash") + shardData := createShardDataStub(createPendingMbsFromHashes([]byte("mb1")), headerHash) + headers := map[string]data.HeaderHandler{string(headerHash): &block.Header{}} + + result, err := shouldUpdateLastCrossMetaToPending(shardData, headers) + assert.Nil(t, err) + assert.False(t, result) + }) + + t.Run("v3 header with all mbs proposed - should return true", func(t *testing.T) { + t.Parallel() + + mbHash1, headerHash := []byte("mbHash1"), []byte("headerHash") + shardData := createShardDataStub(createPendingMbsFromHashes(mbHash1), headerHash) + headers := map[string]data.HeaderHandler{ + string(headerHash): &block.HeaderV3{ + MiniBlockHeaders: createMbHeadersFromHashes(mbHash1), + }, + } + + result, err := shouldUpdateLastCrossMetaToPending(shardData, headers) + assert.Nil(t, err) + assert.True(t, result) + }) + + t.Run("v3 header with missing mbs - should return false", func(t *testing.T) { + t.Parallel() + + mbHash1, mbHash2, headerHash := []byte("mbHash1"), []byte("mbHash2"), []byte("headerHash") + shardData := createShardDataStub(createPendingMbsFromHashes(mbHash1, mbHash2), headerHash) + headers := map[string]data.HeaderHandler{ + string(headerHash): &block.HeaderV3{ + MiniBlockHeaders: createMbHeadersFromHashes(mbHash1), // mbHash2 missing + }, + } + + result, err := shouldUpdateLastCrossMetaToPending(shardData, headers) + assert.Nil(t, err) + assert.False(t, result) + }) +} diff --git a/epochStart/bootstrap/startInEpochScheduled.go b/epochStart/bootstrap/startInEpochScheduled.go index 265cf4dcc72..4454bb96206 100644 --- a/epochStart/bootstrap/startInEpochScheduled.go +++ b/epochStart/bootstrap/startInEpochScheduled.go @@ -90,6 +90,9 @@ func (ses *startInEpochWithScheduledDataSyncer) IsInterfaceNil() bool { func (ses *startInEpochWithScheduledDataSyncer) getRequiredHeaderByHash( notarizedShardHeader data.ShardHeaderHandler, ) (data.ShardHeaderHandler, map[string]data.HeaderHandler, error) { + // TODO: analyze the requested headers in this func, after andromeda committed blocks are final + // it might not be needed to request based on prev header + shardIDs, hashesToRequest := getShardIDAndHashesForIncludedMetaBlocks(notarizedShardHeader) shardIDs = append(shardIDs, notarizedShardHeader.GetShardID()) @@ -145,6 +148,10 @@ func (ses *startInEpochWithScheduledDataSyncer) getRequiredHeaderByHash( } } + if notarizedShardHeader.IsHeaderV3() { + headerToBeProcessed = notarizedShardHeader + } + return headerToBeProcessed, headers, nil } diff --git a/epochStart/bootstrap/storageEpochStartMetaBlockProcessor.go b/epochStart/bootstrap/storageEpochStartMetaBlockProcessor.go index f290964d6aa..2e9829d94ce 100644 --- a/epochStart/bootstrap/storageEpochStartMetaBlockProcessor.go +++ b/epochStart/bootstrap/storageEpochStartMetaBlockProcessor.go @@ -8,11 +8,11 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" - "github.com/multiversx/mx-chain-core-go/data" - "github.com/multiversx/mx-chain-core-go/data/block" + coreData "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-go/epochStart" + "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process" ) @@ -25,7 +25,7 @@ type storageEpochStartMetaBlockProcessor struct { hasher hashing.Hasher chanReceived chan struct{} mutMetablock sync.Mutex - metaBlock data.MetaHeaderHandler + metaBlock coreData.MetaHeaderHandler } // NewStorageEpochStartMetaBlockProcessor will return an interceptor processor for epoch start meta block when importing @@ -68,29 +68,29 @@ func (ses *storageEpochStartMetaBlockProcessor) Validate(_ process.InterceptedDa // Save will handle the consensus mechanism for the fetched metablocks // All errors are just logged because if this function returns an error, the processing is finished. This way, we ignore // wrong received data and wait for relevant intercepted data -func (ses *storageEpochStartMetaBlockProcessor) Save(data process.InterceptedData, _ core.PeerID, _ string) error { +func (ses *storageEpochStartMetaBlockProcessor) Save(data process.InterceptedData, _ core.PeerID, _ string, _ p2p.BroadcastMethod) (dataSaved bool, err error) { if check.IfNil(data) { log.Debug("epoch bootstrapper: nil intercepted data") - return nil + return false, nil } log.Debug("received header", "type", data.Type(), "hash", data.Hash()) interceptedHdr, ok := data.(process.HdrValidatorHandler) if !ok { log.Warn("saving epoch start meta block error", "error", epochStart.ErrWrongTypeAssertion) - return nil + return false, nil } - metaBlock, ok := interceptedHdr.HeaderHandler().(*block.MetaBlock) + metaBlock, ok := interceptedHdr.HeaderHandler().(coreData.MetaHeaderHandler) if !ok { log.Warn("saving epoch start meta block error", "error", epochStart.ErrWrongTypeAssertion, "header", interceptedHdr.HeaderHandler()) - return nil + return false, nil } if !metaBlock.IsStartOfEpochBlock() { log.Warn("received metablock is not of type epoch start", "error", epochStart.ErrNotEpochStartBlock) - return nil + return false, nil } log.Debug("received epoch start meta", "epoch", metaBlock.GetEpoch(), "from peer", "self") @@ -103,12 +103,12 @@ func (ses *storageEpochStartMetaBlockProcessor) Save(data process.InterceptedDat default: } - return nil + return true, nil } // GetEpochStartMetaBlock will return the metablock after it is confirmed or an error if the number of tries was exceeded // This is a blocking method which will end after the consensus for the meta block is obtained or the context is done -func (ses *storageEpochStartMetaBlockProcessor) GetEpochStartMetaBlock(ctx context.Context) (data.MetaHeaderHandler, error) { +func (ses *storageEpochStartMetaBlockProcessor) GetEpochStartMetaBlock(ctx context.Context) (coreData.MetaHeaderHandler, error) { ses.requestMetaBlock() chanRequests := time.After(durationBetweenReRequests) @@ -125,7 +125,7 @@ func (ses *storageEpochStartMetaBlockProcessor) GetEpochStartMetaBlock(ctx conte } } -func (ses *storageEpochStartMetaBlockProcessor) getMetablock() (data.MetaHeaderHandler, error) { +func (ses *storageEpochStartMetaBlockProcessor) getMetablock() (coreData.MetaHeaderHandler, error) { ses.mutMetablock.Lock() defer ses.mutMetablock.Unlock() diff --git a/epochStart/bootstrap/storageEpochStartMetaBlockProcessor_test.go b/epochStart/bootstrap/storageEpochStartMetaBlockProcessor_test.go index d1a7e41e801..b8d77b56abf 100644 --- a/epochStart/bootstrap/storageEpochStartMetaBlockProcessor_test.go +++ b/epochStart/bootstrap/storageEpochStartMetaBlockProcessor_test.go @@ -111,7 +111,7 @@ func TestStorageEpochStartMetaBlockProcessor_SaveNilData(t *testing.T) { &hashingMocks.HasherMock{}, ) - err := sesmbp.Save(nil, "", "") + _, err := sesmbp.Save(nil, "", "", "") assert.Nil(t, err) mb, _ := sesmbp.getMetablock() assert.True(t, check.IfNil(mb)) @@ -127,7 +127,7 @@ func TestStorageEpochStartMetaBlockProcessor_SaveNotAHeader(t *testing.T) { &hashingMocks.HasherMock{}, ) - err := sesmbp.Save(&testscommon.InterceptedDataStub{}, "", "") + _, err := sesmbp.Save(&testscommon.InterceptedDataStub{}, "", "", "") assert.Nil(t, err) mb, _ := sesmbp.getMetablock() assert.True(t, check.IfNil(mb)) @@ -149,7 +149,7 @@ func TestStorageEpochStartMetaBlockProcessor_SaveNotAnEpochStartBlock(t *testing } interceptedMetaBlock := mock.NewInterceptedMetaBlockMock(metaBlock, []byte("hash")) - err := sesmbp.Save(interceptedMetaBlock, "", "") + _, err := sesmbp.Save(interceptedMetaBlock, "", "", "") assert.Nil(t, err) mb, _ := sesmbp.getMetablock() assert.True(t, check.IfNil(mb)) @@ -174,10 +174,10 @@ func TestStorageEpochStartMetaBlockProcessor_SaveShouldWork(t *testing.T) { } interceptedMetaBlock := mock.NewInterceptedMetaBlockMock(metaBlock, []byte("hash")) - err := sesmbp.Save(interceptedMetaBlock, "", "") + _, err := sesmbp.Save(interceptedMetaBlock, "", "", "") assert.Nil(t, err) mb, _ := sesmbp.getMetablock() - assert.True(t, mb == metaBlock) //pointer testing + assert.True(t, mb == metaBlock) // pointer testing } func TestStorageEpochStartMetaBlockProcessor_GetEpochStartMetaBlockShouldRequestRepeatedly(t *testing.T) { @@ -201,7 +201,7 @@ func TestStorageEpochStartMetaBlockProcessor_GetEpochStartMetaBlockShouldRequest RequestStartOfEpochMetaBlockCalled: func(epoch uint32) { numRequests++ if numRequests > numUnsuccessfulRequests { - _ = sesmbp.Save(interceptedMetaBlock, "", "") + _, _ = sesmbp.Save(interceptedMetaBlock, "", "", "") } }, }, diff --git a/epochStart/bootstrap/storageProcess.go b/epochStart/bootstrap/storageProcess.go index 1004583e07b..d5c228593de 100644 --- a/epochStart/bootstrap/storageProcess.go +++ b/epochStart/bootstrap/storageProcess.go @@ -11,6 +11,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/endProcess" + "github.com/multiversx/mx-chain-go/process/interceptors/processor" "github.com/multiversx/mx-chain-go/common" @@ -310,7 +311,7 @@ func (sesb *storageEpochStartBootstrap) requestAndProcessFromStorage() (Paramete log.Debug("start in epoch bootstrap: got shard header and previous epoch start meta block") prevEpochStartMetaHash := sesb.epochStartMeta.GetEpochStartHandler().GetEconomicsHandler().GetPrevEpochStartHash() - prevEpochStartMeta, ok := sesb.syncedHeaders[string(prevEpochStartMetaHash)].(*block.MetaBlock) + prevEpochStartMeta, ok := sesb.syncedHeaders[string(prevEpochStartMetaHash)].(data.MetaHeaderHandler) if !ok { return Parameters{}, epochStart.ErrWrongTypeAssertion } @@ -396,7 +397,11 @@ func (sesb *storageEpochStartBootstrap) syncHeadersFromStorage(meta data.MetaHea } if meta.GetEpoch() == sesb.startEpoch+1 { - syncedHeaders[string(meta.GetEpochStartHandler().GetEconomicsHandler().GetPrevEpochStartHash())] = &block.MetaBlock{} + var metaBlock data.MetaHeaderHandler = &block.MetaBlock{} + if meta.IsHeaderV3() { + metaBlock = &block.MetaBlockV3{} + } + syncedHeaders[string(meta.GetEpochStartHandler().GetEconomicsHandler().GetPrevEpochStartHash())] = metaBlock } return syncedHeaders, nil @@ -431,9 +436,9 @@ func (sesb *storageEpochStartBootstrap) processNodesConfig(pubKey []byte) error } clonedHeader := sesb.epochStartMeta.ShallowClone() - clonedEpochStartMeta, ok := clonedHeader.(*block.MetaBlock) + clonedEpochStartMeta, ok := clonedHeader.(data.MetaHeaderHandler) if !ok { - return fmt.Errorf("%w while trying to assert clonedHeader to *block.MetaBlock", epochStart.ErrWrongTypeAssertion) + return fmt.Errorf("%w while trying to assert clonedHeader to data.MetaHeaderHandler", epochStart.ErrWrongTypeAssertion) } err = sesb.applyCurrentShardIDOnMiniblocksCopy(clonedEpochStartMeta) if err != nil { @@ -441,9 +446,9 @@ func (sesb *storageEpochStartBootstrap) processNodesConfig(pubKey []byte) error } clonedHeader = sesb.prevEpochStartMeta.ShallowClone() - clonedPrevEpochStartMeta, ok := clonedHeader.(*block.MetaBlock) + clonedPrevEpochStartMeta, ok := clonedHeader.(data.MetaHeaderHandler) if !ok { - return fmt.Errorf("%w while trying to assert prevClonedHeader to *block.MetaBlock", epochStart.ErrWrongTypeAssertion) + return fmt.Errorf("%w while trying to assert prevClonedHeader to data.MetaHeaderHandler", epochStart.ErrWrongTypeAssertion) } err = sesb.applyCurrentShardIDOnMiniblocksCopy(clonedPrevEpochStartMeta) diff --git a/epochStart/bootstrap/storageProcess_test.go b/epochStart/bootstrap/storageProcess_test.go index 34a7f97cbcc..7276886985d 100644 --- a/epochStart/bootstrap/storageProcess_test.go +++ b/epochStart/bootstrap/storageProcess_test.go @@ -86,7 +86,6 @@ func TestStorageEpochStartBootstrap_BootstrapStartInEpochNotEnabled(t *testing.T } func TestStorageEpochStartBootstrap_BootstrapFromGenesis(t *testing.T) { - roundsPerEpoch := int64(100) roundDuration := uint64(60000) coreComp, cryptoComp := createComponentsForEpochStart() args := createMockStorageEpochStartBootstrapArgs(coreComp, cryptoComp) @@ -101,7 +100,6 @@ func TestStorageEpochStartBootstrap_BootstrapFromGenesis(t *testing.T) { }, } args.GeneralConfig = testscommon.GetGeneralConfig() - args.GeneralConfig.EpochStartConfig.RoundsPerEpoch = roundsPerEpoch sesb, _ := NewStorageEpochStartBootstrap(args) params, err := sesb.Bootstrap() @@ -128,7 +126,6 @@ func TestStorageEpochStartBootstrap_BootstrapMetablockNotFound(t *testing.T) { RoundIndex: 2*roundsPerEpoch + 1, } args.GeneralConfig = testscommon.GetGeneralConfig() - args.GeneralConfig.EpochStartConfig.RoundsPerEpoch = roundsPerEpoch args.InterceptedDataVerifierFactory = &processMock.InterceptedDataVerifierFactoryMock{} sesb, _ := NewStorageEpochStartBootstrap(args) diff --git a/epochStart/bootstrap/syncEpochStartMeta.go b/epochStart/bootstrap/syncEpochStartMeta.go index 07b3e9f1bd1..3b6ccf8a580 100644 --- a/epochStart/bootstrap/syncEpochStartMeta.go +++ b/epochStart/bootstrap/syncEpochStartMeta.go @@ -123,6 +123,7 @@ func NewEpochStartMetaSyncer(args ArgsNewEpochStartMetaSyncer) (*epochStartMetaS CurrentPeerId: args.Messenger.ID(), PreferredPeersHolder: disabled.NewPreferredPeersHolder(), InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: args.CryptoComponentsHolder.ManagedPeersHolder(), }, ) if err != nil { @@ -150,6 +151,7 @@ func NewEpochStartMetaSyncer(args ArgsNewEpochStartMetaSyncer) (*epochStartMetaS CurrentPeerId: args.Messenger.ID(), PreferredPeersHolder: disabled.NewPreferredPeersHolder(), InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: args.CryptoComponentsHolder.ManagedPeersHolder(), }, ) if err != nil { diff --git a/epochStart/bootstrap/syncEpochStartMeta_test.go b/epochStart/bootstrap/syncEpochStartMeta_test.go index 8edc24825fd..d4f023a5d4c 100644 --- a/epochStart/bootstrap/syncEpochStartMeta_test.go +++ b/epochStart/bootstrap/syncEpochStartMeta_test.go @@ -157,11 +157,12 @@ func getEpochStartSyncerArgs() ArgsNewEpochStartMetaSyncer { EpochChangeGracePeriodHandlerField: gracePeriod, }, CryptoComponentsHolder: &mock.CryptoComponentsMock{ - PubKey: &cryptoMocks.PublicKeyStub{}, - BlockSig: &cryptoMocks.SignerStub{}, - TxSig: &cryptoMocks.SignerStub{}, - BlKeyGen: &cryptoMocks.KeyGenStub{}, - TxKeyGen: &cryptoMocks.KeyGenStub{}, + PubKey: &cryptoMocks.PublicKeyStub{}, + BlockSig: &cryptoMocks.SignerStub{}, + TxSig: &cryptoMocks.SignerStub{}, + BlKeyGen: &cryptoMocks.KeyGenStub{}, + TxKeyGen: &cryptoMocks.KeyGenStub{}, + ManagedPeers: &testscommon.ManagedPeersHolderStub{}, }, RequestHandler: &testscommon.RequestHandlerStub{}, Messenger: &p2pmocks.MessengerStub{}, diff --git a/epochStart/bootstrap/syncValidatorStatus.go b/epochStart/bootstrap/syncValidatorStatus.go index 4a0883f51af..92281736e98 100644 --- a/epochStart/bootstrap/syncValidatorStatus.go +++ b/epochStart/bootstrap/syncValidatorStatus.go @@ -9,6 +9,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data/endProcess" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/epochStart" @@ -199,9 +200,13 @@ func (s *syncValidatorStatus) processValidatorChangesFor(metaBlock data.HeaderHa return miniBlocks, nil } -func findPeerMiniBlockHeaders(metaBlock data.HeaderHandler) []data.MiniBlockHeaderHandler { +func findPeerMiniBlockHeaders(metaBlock data.HeaderHandler) ([]data.MiniBlockHeaderHandler, error) { shardMBHeaderHandlers := make([]data.MiniBlockHeaderHandler, 0) - mbHeaderHandlers := metaBlock.GetMiniBlockHeaderHandlers() + mbHeaderHandlers, err := common.GetMiniBlockHeadersFromExecResult(metaBlock) + if err != nil { + return nil, err + } + for i, mbHeader := range mbHeaderHandlers { if mbHeader.GetTypeInt32() != int32(block.PeerBlock) { continue @@ -209,17 +214,20 @@ func findPeerMiniBlockHeaders(metaBlock data.HeaderHandler) []data.MiniBlockHead shardMBHeaderHandlers = append(shardMBHeaderHandlers, mbHeaderHandlers[i]) } - return shardMBHeaderHandlers + return shardMBHeaderHandlers, nil } func (s *syncValidatorStatus) getPeerBlockBodyForMeta( metaBlock data.HeaderHandler, ) (data.BodyHandler, []*block.MiniBlock, error) { - shardMBHeaders := findPeerMiniBlockHeaders(metaBlock) + shardMBHeaders, err := findPeerMiniBlockHeaders(metaBlock) + if err != nil { + return nil, nil, err + } s.miniBlocksSyncer.ClearFields() ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - err := s.miniBlocksSyncer.SyncPendingMiniBlocks(shardMBHeaders, ctx) + err = s.miniBlocksSyncer.SyncPendingMiniBlocks(shardMBHeaders, ctx) cancel() if err != nil { return nil, nil, err diff --git a/epochStart/bootstrap/syncValidatorStatus_test.go b/epochStart/bootstrap/syncValidatorStatus_test.go index ee8b7c02dae..0a785d7c1b4 100644 --- a/epochStart/bootstrap/syncValidatorStatus_test.go +++ b/epochStart/bootstrap/syncValidatorStatus_test.go @@ -192,7 +192,8 @@ func TestSyncValidatorStatus_findPeerMiniBlockHeaders(t *testing.T) { &mbHeader2, } - mbHeaderHandlers := findPeerMiniBlockHeaders(metaBlock) + mbHeaderHandlers, err := findPeerMiniBlockHeaders(metaBlock) + require.Nil(t, err) require.Equal(t, expectedMbHeaders, mbHeaderHandlers) } diff --git a/epochStart/errors.go b/epochStart/errors.go index 6c567b838f4..c7ba04082c7 100644 --- a/epochStart/errors.go +++ b/epochStart/errors.go @@ -356,5 +356,8 @@ var ErrReceivedAuctionValidatorsBeforeStakingV4 = errors.New("auction node has b // ErrNilEquivalentProofsProcessor signals that a nil equivalent proofs processor was provided var ErrNilEquivalentProofsProcessor = errors.New("nil equivalent proofs processor") -// ErrNilHeadersDataPool signals that a nil headers pool has been provided -var ErrNilHeadersDataPool = errors.New("nil headers data pool") +// ErrInvalidHeader signals that an invalid header was provided +var ErrInvalidHeader = errors.New("invalid header") + +// ErrNilPrevBlockExecutionResults signals that nil previous block execution results were provided +var ErrNilPrevBlockExecutionResults = errors.New("nil previous block execution results") diff --git a/epochStart/interface.go b/epochStart/interface.go index ee5c0ff01ed..cc3585dfd31 100644 --- a/epochStart/interface.go +++ b/epochStart/interface.go @@ -21,6 +21,10 @@ type TriggerHandler interface { Epoch() uint32 MetaEpoch() uint32 Update(round uint64, nonce uint64) + SetEpochChange(round uint64) + ShouldProposeEpochChange(round uint64, nonce uint64) bool + SetEpochChangeProposed(value bool) + GetEpochChangeProposed() bool EpochStartRound() uint64 EpochStartMetaHdrHash() []byte LastCommitedEpochStartHdr() (data.HeaderHandler, error) @@ -64,6 +68,7 @@ type RequestHandler interface { GetNumPeersToQuery(key string) (int, int, error) RequestValidatorInfo(hash []byte) RequestValidatorsInfo(hashes [][]byte) + RequestEquivalentProofByHash(headerShard uint32, headerHash []byte) IsInterfaceNil() bool } @@ -207,6 +212,12 @@ type RewardsCreator interface { VerifyRewardsMiniBlocks( metaBlock data.MetaHeaderHandler, validatorsInfo state.ShardValidatorsInfoMapHandler, computedEconomics *block.Economics, ) error + CreateRewardsMiniBlocksV3( + metaBlock data.MetaHeaderHandler, + validatorsInfo state.ShardValidatorsInfoMapHandler, + computedEconomics *block.Economics, + prevBlockExecutionResults data.BaseMetaExecutionResultHandler, + ) (block.MiniBlockSlice, error) GetAcceleratorRewards() *big.Int GetLocalTxCache() TransactionCacher CreateMarshalledData(body *block.Body) map[string][][]byte diff --git a/epochStart/metachain/baseRewards.go b/epochStart/metachain/baseRewards.go index a6cfffca45a..453cce98a44 100644 --- a/epochStart/metachain/baseRewards.go +++ b/epochStart/metachain/baseRewards.go @@ -334,17 +334,18 @@ func (brc *baseRewardsCreator) isSystemDelegationSC(address []byte) bool { } func (brc *baseRewardsCreator) createProtocolSustainabilityRewardTransaction( - metaBlock data.HeaderHandler, + epoch uint32, + round uint64, protocolSustainability *big.Int, ) (*rewardTx.RewardTx, uint32, error) { - protocolSustainabilityAddressForEpoch := brc.rewardsHandler.ProtocolSustainabilityAddressInEpoch(metaBlock.GetEpoch()) + protocolSustainabilityAddressForEpoch := brc.rewardsHandler.ProtocolSustainabilityAddressInEpoch(epoch) protocolSustainabilityShardID := brc.shardCoordinator.ComputeId([]byte(protocolSustainabilityAddressForEpoch)) protocolSustainabilityRwdTx := &rewardTx.RewardTx{ - Round: metaBlock.GetRound(), + Round: round, Value: big.NewInt(0).Set(protocolSustainability), RcvAddr: []byte(protocolSustainabilityAddressForEpoch), - Epoch: metaBlock.GetEpoch(), + Epoch: epoch, } brc.accumulatedRewards.Add(brc.accumulatedRewards, protocolSustainabilityRwdTx.Value) @@ -353,13 +354,14 @@ func (brc *baseRewardsCreator) createProtocolSustainabilityRewardTransaction( func (brc *baseRewardsCreator) createRewardFromRwdInfo( rwdInfo *rewardInfoData, - metaBlock data.HeaderHandler, + epoch uint32, + round uint64, ) (*rewardTx.RewardTx, []byte, error) { rwdTx := &rewardTx.RewardTx{ - Round: metaBlock.GetRound(), + Round: round, Value: big.NewInt(0).Add(rwdInfo.accumulatedFees, rwdInfo.rewardsFromProtocol), RcvAddr: []byte(rwdInfo.address), - Epoch: metaBlock.GetEpoch(), + Epoch: epoch, } rwdTxHash, err := core.CalculateHash(brc.marshalizer, brc.hasher, rwdTx) @@ -397,7 +399,7 @@ func (brc *baseRewardsCreator) addAcceleratorRewardToMiniBlocks( miniBlocks block.MiniBlockSlice, shardID uint32, ) error { - protocolSustainabilityRwdHash, errHash := core.CalculateHash(brc.marshalizer, brc.hasher, acceleratorRewardTx) + acceleratorRwdHash, errHash := core.CalculateHash(brc.marshalizer, brc.hasher, acceleratorRewardTx) if errHash != nil { return errHash } @@ -409,8 +411,8 @@ func (brc *baseRewardsCreator) addAcceleratorRewardToMiniBlocks( return nil } - brc.currTxs.AddTx(protocolSustainabilityRwdHash, acceleratorRewardTx) - miniBlocks[shardID].TxHashes = append(miniBlocks[shardID].TxHashes, protocolSustainabilityRwdHash) + brc.currTxs.AddTx(acceleratorRwdHash, acceleratorRewardTx) + miniBlocks[shardID].TxHashes = append(miniBlocks[shardID].TxHashes, acceleratorRwdHash) return nil } diff --git a/epochStart/metachain/baseRewards_test.go b/epochStart/metachain/baseRewards_test.go index 07da0b48aff..b0e1118b84e 100644 --- a/epochStart/metachain/baseRewards_test.go +++ b/epochStart/metachain/baseRewards_test.go @@ -883,7 +883,7 @@ func TestBaseRewardsCreator_createProtocolSustainabilityRewardTransaction(t *tes DevFeesInEpoch: big.NewInt(0), } - rwTx, _, err := rwd.createProtocolSustainabilityRewardTransaction(metaBlk, metaBlk.EpochStart.Economics.RewardsForProtocolSustainability) + rwTx, _, err := rwd.createProtocolSustainabilityRewardTransaction(metaBlk.GetEpoch(), metaBlk.GetRound(), metaBlk.EpochStart.Economics.GetRewardsForProtocolSustainability()) require.Nil(t, err) require.NotNil(t, rwTx) require.Equal(t, metaBlk.EpochStart.Economics.RewardsForProtocolSustainability, rwTx.Value) @@ -908,7 +908,7 @@ func TestBaseRewardsCreator_createRewardFromRwdInfo(t *testing.T) { rewardsFromProtocol: big.NewInt(1000), } - rwTx, rwTxHash, err := rwd.createRewardFromRwdInfo(rwInfo, metaBlk) + rwTx, rwTxHash, err := rwd.createRewardFromRwdInfo(rwInfo, metaBlk.GetEpoch(), metaBlk.GetRound()) require.Nil(t, err) require.NotNil(t, rwTx) require.NotNil(t, rwTxHash) diff --git a/epochStart/metachain/common.go b/epochStart/metachain/common.go index 9eb614772ab..169aaac6a56 100644 --- a/epochStart/metachain/common.go +++ b/epochStart/metachain/common.go @@ -1,16 +1,15 @@ package metachain -import "github.com/multiversx/mx-chain-go/state" +import ( + "github.com/multiversx/mx-chain-core-go/data" +) -// GetAllNodeKeys returns all from the provided map -func GetAllNodeKeys(validatorsInfo state.ShardValidatorsInfoMapHandler) map[uint32][][]byte { - nodeKeys := make(map[uint32][][]byte) - for shardID, validatorsInfoSlice := range validatorsInfo.GetShardValidatorsInfoMap() { - nodeKeys[shardID] = make([][]byte, 0) - for _, validatorInfo := range validatorsInfoSlice { - nodeKeys[shardID] = append(nodeKeys[shardID], validatorInfo.GetPublicKey()) - } +// GetEpochToUseEpochStartData returns the epoch to use for epoch start data computation +func GetEpochToUseEpochStartData(header data.HeaderHandler) uint32 { + epochToUse := header.GetEpoch() + if header.IsHeaderV3() { + // for meta headers v3 on the epoch change proposed block, the epoch is not yet updated + epochToUse = header.GetEpoch() + 1 } - - return nodeKeys + return epochToUse } diff --git a/epochStart/metachain/common_test.go b/epochStart/metachain/common_test.go new file mode 100644 index 00000000000..1320bc158c5 --- /dev/null +++ b/epochStart/metachain/common_test.go @@ -0,0 +1,41 @@ +package metachain + +import ( + "testing" + + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/stretchr/testify/require" +) + +func Test_GetEpochToUseEpochStartData(t *testing.T) { + t.Parallel() + + t.Run("should work correctly for header v3", func(t *testing.T) { + t.Parallel() + + epoch := GetEpochToUseEpochStartData(&block.HeaderV3{ + Epoch: 1, + }) + require.Equal(t, uint32(2), epoch) + }) + + t.Run("should work correctly for header v2", func(t *testing.T) { + t.Parallel() + + epoch := GetEpochToUseEpochStartData(&block.HeaderV2{ + Header: &block.Header{ + Epoch: 1, + }, + }) + require.Equal(t, uint32(1), epoch) + }) + + t.Run("should work correctly for header v1", func(t *testing.T) { + t.Parallel() + + epoch := GetEpochToUseEpochStartData(&block.Header{ + Epoch: 1, + }) + require.Equal(t, uint32(1), epoch) + }) +} diff --git a/epochStart/metachain/disabled/disabledEpochRewards.go b/epochStart/metachain/disabled/disabledEpochRewards.go new file mode 100644 index 00000000000..c61b36172a0 --- /dev/null +++ b/epochStart/metachain/disabled/disabledEpochRewards.go @@ -0,0 +1,24 @@ +package disabled + +import ( + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" +) + +// EpochRewardsDisabled does nothing +type EpochRewardsDisabled struct{} + +// NewDisabledEpochRewards will create a new instance of *EpochRewardsDisabled +func NewDisabledEpochRewards() *EpochRewardsDisabled { + return &EpochRewardsDisabled{} +} + +// GetRewardsTxs will return nothing +func (der *EpochRewardsDisabled) GetRewardsTxs(_ *block.Body) map[string]data.TransactionHandler { + return make(map[string]data.TransactionHandler) +} + +// IsInterfaceNil returns true if the underlying object is nil +func (der *EpochRewardsDisabled) IsInterfaceNil() bool { + return der == nil +} diff --git a/epochStart/metachain/disabled/disabledEpochRewards_test.go b/epochStart/metachain/disabled/disabledEpochRewards_test.go new file mode 100644 index 00000000000..17e574047f1 --- /dev/null +++ b/epochStart/metachain/disabled/disabledEpochRewards_test.go @@ -0,0 +1,18 @@ +package disabled + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewDisabledEpochRewards(t *testing.T) { + t.Parallel() + + rewardsGetter := NewDisabledEpochRewards() + require.NotNil(t, rewardsGetter) + require.False(t, rewardsGetter.IsInterfaceNil()) + + res := rewardsGetter.GetRewardsTxs(nil) + require.NotNil(t, res) +} diff --git a/epochStart/metachain/economics.go b/epochStart/metachain/economics.go index ea6d0a0a856..e65cefd924b 100644 --- a/epochStart/metachain/economics.go +++ b/epochStart/metachain/economics.go @@ -6,15 +6,20 @@ import ( "math/big" "strconv" "strings" + "time" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/display" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" + + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/epochStart" + "github.com/multiversx/mx-chain-go/errors" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/sharding" ) @@ -23,6 +28,15 @@ var _ process.EndOfEpochEconomics = (*economics)(nil) const numberOfDaysInYear = 365.0 const numberOfSecondsInDay = 86400 +const numberOfMillisecondsInDay = numberOfSecondsInDay * 1000 +const numberOfMillisecondsInYear = numberOfDaysInYear * numberOfMillisecondsInDay + +type argsComputeEconomics struct { + computationData economicsComputationData + prevEpochStart data.MetaHeaderHandler + lastNoncesPerShardPrevEpoch map[uint32]uint64 + lastNoncesPerShardCurrEpoch map[uint32]uint64 +} type economics struct { marshalizer marshal.Marshalizer @@ -33,9 +47,12 @@ type economics struct { roundTime process.RoundTimeDurationHandler genesisEpoch uint32 genesisNonce uint64 + genesisTimestamp uint64 genesisTotalSupply *big.Int economicsDataNotified epochStart.EpochEconomicsDataProvider stakingV2EnableEpoch uint32 + enableEpochsHandler common.EnableEpochsHandler + chainParamsHandler common.ChainParametersHandler } // ArgsNewEpochEconomics is the argument for the economics constructor @@ -48,9 +65,12 @@ type ArgsNewEpochEconomics struct { RoundTime process.RoundTimeDurationHandler GenesisEpoch uint32 GenesisNonce uint64 + GenesisTimestamp uint64 GenesisTotalSupply *big.Int EconomicsDataNotified epochStart.EpochEconomicsDataProvider StakingV2EnableEpoch uint32 + EnableEpochsHandler common.EnableEpochsHandler + ChainParamsHandler common.ChainParametersHandler } // NewEndOfEpochEconomicsDataCreator creates a new end of epoch economics data creator object @@ -79,6 +99,12 @@ func NewEndOfEpochEconomicsDataCreator(args ArgsNewEpochEconomics) (*economics, if args.GenesisTotalSupply == nil { return nil, epochStart.ErrNilGenesisTotalSupply } + if check.IfNil(args.EnableEpochsHandler) { + return nil, errors.ErrNilEnableEpochsHandler + } + if check.IfNil(args.ChainParamsHandler) { + return nil, errors.ErrNilChainParametersHandler + } e := &economics{ marshalizer: args.Marshalizer, @@ -89,9 +115,12 @@ func NewEndOfEpochEconomicsDataCreator(args ArgsNewEpochEconomics) (*economics, roundTime: args.RoundTime, genesisEpoch: args.GenesisEpoch, genesisNonce: args.GenesisNonce, + genesisTimestamp: args.GenesisTimestamp, genesisTotalSupply: big.NewInt(0).Set(args.GenesisTotalSupply), economicsDataNotified: args.EconomicsDataNotified, stakingV2EnableEpoch: args.StakingV2EnableEpoch, + enableEpochsHandler: args.EnableEpochsHandler, + chainParamsHandler: args.ChainParamsHandler, } log.Debug("economics: enable epoch for staking v2", "epoch", e.stakingV2EnableEpoch) @@ -100,64 +129,84 @@ func NewEndOfEpochEconomicsDataCreator(args ArgsNewEpochEconomics) (*economics, // ComputeEndOfEpochEconomics calculates the rewards per block value for the current epoch func (e *economics) ComputeEndOfEpochEconomics( - metaBlock *block.MetaBlock, + metaBlock data.MetaHeaderHandler, ) (*block.Economics, error) { + args, err := e.createLegacyEconomicsArgs(metaBlock) + if err != nil { + return nil, err + } + + return e.baseComputeEconomics(args) +} + +func (e *economics) createLegacyEconomicsArgs(metaBlock data.MetaHeaderHandler) (*argsComputeEconomics, error) { if check.IfNil(metaBlock) { return nil, epochStart.ErrNilHeaderHandler } - if metaBlock.AccumulatedFeesInEpoch == nil { + if metaBlock.GetAccumulatedFeesInEpoch() == nil { return nil, epochStart.ErrNilTotalAccumulatedFeesInEpoch } - if metaBlock.DevFeesInEpoch == nil { + if metaBlock.GetDevFeesInEpoch() == nil { return nil, epochStart.ErrNilTotalDevFeesInEpoch } - if !metaBlock.IsStartOfEpochBlock() || metaBlock.Epoch < e.genesisEpoch+1 { + if !metaBlock.IsStartOfEpochBlock() || metaBlock.GetEpoch() < e.genesisEpoch+1 { return nil, epochStart.ErrNotEpochStartBlock } e.economicsDataNotified.Clean() - noncesPerShardPrevEpoch, prevEpochStart, err := e.startNoncePerShardFromEpochStart(metaBlock.Epoch - 1) + noncesPerShardPrevEpoch, prevEpochStart, err := e.startNoncePerShardFromEpochStart(metaBlock.GetEpoch() - 1) if err != nil { return nil, err } - prevEpochEconomics := prevEpochStart.EpochStart.Economics - noncesPerShardCurrEpoch, err := e.startNoncePerShardFromLastCrossNotarized(metaBlock.GetNonce(), metaBlock.EpochStart) - if err != nil { - return nil, err - } + noncesPerShardCurrEpoch := e.startNoncePerShardFromLastCrossNotarized(metaBlock.GetNonce(), metaBlock.GetEpochStartHandler()) + + return &argsComputeEconomics{ + computationData: economicsComputationData{ + newEpoch: metaBlock.GetEpoch(), + round: metaBlock.GetRound(), + timeStamp: metaBlock.GetTimeStamp(), + accumulatedFeesInEpoch: metaBlock.GetAccumulatedFeesInEpoch(), + devFeesInEpoch: metaBlock.GetDevFeesInEpoch(), + }, + prevEpochStart: prevEpochStart, + lastNoncesPerShardPrevEpoch: noncesPerShardPrevEpoch, + lastNoncesPerShardCurrEpoch: noncesPerShardCurrEpoch, + }, nil +} - roundsPassedInEpoch := metaBlock.GetRound() - prevEpochStart.GetRound() +func (e *economics) baseComputeEconomics(args *argsComputeEconomics) (*block.Economics, error) { + roundsPassedInEpoch := args.computationData.round - args.prevEpochStart.GetRound() maxBlocksInEpoch := core.MaxUint64(1, roundsPassedInEpoch*uint64(e.shardCoordinator.NumberOfShards()+1)) - totalNumBlocksInEpoch := e.computeNumOfTotalCreatedBlocks(noncesPerShardPrevEpoch, noncesPerShardCurrEpoch) + totalNumBlocksInEpoch := e.computeNumOfTotalCreatedBlocks(args.lastNoncesPerShardPrevEpoch, args.lastNoncesPerShardCurrEpoch) supplyToUseForRewardsPerBlock := e.genesisTotalSupply - if e.rewardsHandler.IsTailInflationEnabled(metaBlock.GetEpoch()) { - supplyToUseForRewardsPerBlock = prevEpochEconomics.TotalSupply + if e.rewardsHandler.IsTailInflationEnabled(args.computationData.newEpoch) { + supplyToUseForRewardsPerBlock = args.prevEpochStart.GetEpochStartHandler().GetEconomicsHandler().GetTotalSupply() } - inflationRate := e.computeInflationRate(metaBlock.GetRound(), metaBlock.GetEpoch()) + inflationRate := e.computeInflationRate(&args.computationData) rwdPerBlock := e.computeRewardsPerBlock( supplyToUseForRewardsPerBlock, maxBlocksInEpoch, inflationRate, - metaBlock.Epoch, + args.computationData.newEpoch, ) totalRewardsToBeDistributed := big.NewInt(0).Mul(rwdPerBlock, big.NewInt(0).SetUint64(totalNumBlocksInEpoch)) - newTokens := big.NewInt(0).Sub(totalRewardsToBeDistributed, metaBlock.AccumulatedFeesInEpoch) + newTokens := big.NewInt(0).Sub(totalRewardsToBeDistributed, args.computationData.accumulatedFeesInEpoch) if newTokens.Cmp(big.NewInt(0)) < 0 { newTokens = big.NewInt(0) - totalRewardsToBeDistributed = big.NewInt(0).Set(metaBlock.AccumulatedFeesInEpoch) + totalRewardsToBeDistributed = big.NewInt(0).Set(args.computationData.accumulatedFeesInEpoch) rwdPerBlock.Div(totalRewardsToBeDistributed, big.NewInt(0).SetUint64(totalNumBlocksInEpoch)) } - remainingToBeDistributed := big.NewInt(0).Sub(totalRewardsToBeDistributed, metaBlock.DevFeesInEpoch) - e.adjustRewardsPerBlockWithDeveloperFees(rwdPerBlock, metaBlock.DevFeesInEpoch, totalNumBlocksInEpoch) - rewardsForLeaders := e.adjustRewardsPerBlockWithLeaderPercentage(rwdPerBlock, metaBlock.AccumulatedFeesInEpoch, metaBlock.DevFeesInEpoch, totalNumBlocksInEpoch, metaBlock.Epoch) + remainingToBeDistributed := big.NewInt(0).Sub(totalRewardsToBeDistributed, args.computationData.devFeesInEpoch) + e.adjustRewardsPerBlockWithDeveloperFees(rwdPerBlock, args.computationData.devFeesInEpoch, totalNumBlocksInEpoch) + rewardsForLeaders := e.adjustRewardsPerBlockWithLeaderPercentage(rwdPerBlock, args.computationData.accumulatedFeesInEpoch, args.computationData.devFeesInEpoch, totalNumBlocksInEpoch, args.computationData.newEpoch) remainingToBeDistributed = big.NewInt(0).Sub(remainingToBeDistributed, rewardsForLeaders) - rewardsForAccelerator, err := e.computeRewardsForAccelerator(totalRewardsToBeDistributed, metaBlock.Epoch) + rewardsForAccelerator, err := e.computeRewardsForAccelerator(totalRewardsToBeDistributed, args.computationData.GetEpoch()) if err != nil { return nil, err } @@ -175,24 +224,25 @@ func (e *economics) ComputeEndOfEpochEconomics( e.economicsDataNotified.SetRewardsToBeDistributed(totalRewardsToBeDistributed) e.economicsDataNotified.SetRewardsToBeDistributedForBlocks(remainingToBeDistributed) - prevEpochStartHash, err := core.CalculateHash(e.marshalizer, e.hasher, prevEpochStart) + prevEpochStartHash, err := core.CalculateHash(e.marshalizer, e.hasher, args.prevEpochStart) if err != nil { return nil, err } + prevEpochEconomics := args.prevEpochStart.GetEpochStartHandler().GetEconomicsHandler() computedEconomics := block.Economics{ - TotalSupply: big.NewInt(0).Add(prevEpochEconomics.TotalSupply, newTokens), + TotalSupply: big.NewInt(0).Add(prevEpochEconomics.GetTotalSupply(), newTokens), TotalToDistribute: big.NewInt(0).Set(totalRewardsToBeDistributed), TotalNewlyMinted: big.NewInt(0).Set(newTokens), RewardsPerBlock: rwdPerBlock, RewardsForProtocolSustainability: rewardsForAccelerator, - NodePrice: big.NewInt(0).Set(prevEpochEconomics.NodePrice), - PrevEpochStartRound: prevEpochStart.GetRound(), + NodePrice: big.NewInt(0).Set(prevEpochEconomics.GetNodePrice()), + PrevEpochStartRound: args.prevEpochStart.GetRound(), PrevEpochStartHash: prevEpochStartHash, } e.printEconomicsData( - metaBlock, + args.computationData, prevEpochEconomics, inflationRate, newTokens, @@ -203,8 +253,8 @@ func (e *economics) ComputeEndOfEpochEconomics( rewardsForAccelerator, ) - maxPossibleNotarizedBlocks := e.maxPossibleNotarizedBlocks(metaBlock.Round, prevEpochStart) - err = e.checkEconomicsInvariants(computedEconomics, inflationRate, maxBlocksInEpoch, totalNumBlocksInEpoch, metaBlock, metaBlock.Epoch, maxPossibleNotarizedBlocks) + maxPossibleNotarizedBlocks := e.maxPossibleNotarizedBlocks(args.computationData.round, args.prevEpochStart) + err = e.checkEconomicsInvariants(computedEconomics, inflationRate, maxBlocksInEpoch, totalNumBlocksInEpoch, &args.computationData, args.computationData.newEpoch, maxPossibleNotarizedBlocks) if err != nil { log.Warn("ComputeEndOfEpochEconomics", "error", err.Error()) @@ -214,9 +264,77 @@ func (e *economics) ComputeEndOfEpochEconomics( return &computedEconomics, nil } +// ComputeEndOfEpochEconomicsV3 will compute end of epoch economics using data from meta header v3 +func (e *economics) ComputeEndOfEpochEconomicsV3( + metaBlock data.MetaHeaderHandler, + prevBlockExecutionResults data.BaseMetaExecutionResultHandler, + epochStartHandler data.EpochStartHandler, +) (*block.Economics, error) { + args, err := e.createEconomicsV3Args(metaBlock, prevBlockExecutionResults, epochStartHandler) + if err != nil { + return nil, err + } + + return e.baseComputeEconomics(args) +} + +func (e *economics) createEconomicsV3Args( + metaBlock data.MetaHeaderHandler, + prevBlockExecutionResults data.BaseMetaExecutionResultHandler, + epochStartHandler data.EpochStartHandler, +) (*argsComputeEconomics, error) { + if check.IfNil(metaBlock) { + return nil, process.ErrNilMetaBlockHeader + } + if check.IfNil(prevBlockExecutionResults) { + return nil, process.ErrNilExecutionResultHandler + } + if epochStartHandler == nil { + return nil, process.ErrNilEpochStartData + } + if prevBlockExecutionResults.GetAccumulatedFeesInEpoch() == nil { + return nil, epochStart.ErrNilTotalAccumulatedFeesInEpoch + } + if prevBlockExecutionResults.GetDevFeesInEpoch() == nil { + return nil, epochStart.ErrNilTotalDevFeesInEpoch + } + + if !metaBlock.IsHeaderV3() { + return nil, fmt.Errorf("%w in createEconomicsV3Args", data.ErrInvalidHeaderType) + } + if !metaBlock.IsEpochChangeProposed() { + return nil, epochStart.ErrNotEpochStartBlock + } + if !bytes.Equal(metaBlock.GetPrevHash(), prevBlockExecutionResults.GetHeaderHash()) { + return nil, fmt.Errorf("%w in createEconomicsV3Args, metaBlock.GetPrevHash():%x, execResults.GetHeaderHash(): %x", + errHashMismatch, metaBlock.GetPrevHash(), prevBlockExecutionResults.GetHeaderHash()) + } + + lastNoncesPerShardPrevEpoch, prevEpochStart, err := e.startNoncePerShardFromEpochStart(metaBlock.GetEpoch()) + if err != nil { + return nil, err + } + + lastNoncesPerShardCurrEpoch := e.startNoncePerShardFromLastCrossNotarized(metaBlock.GetNonce(), epochStartHandler) + + return &argsComputeEconomics{ + computationData: economicsComputationData{ + newEpoch: metaBlock.GetEpoch() + 1, // meta block with proposed epoch change is for current epoch + round: metaBlock.GetRound(), + timeStamp: metaBlock.GetTimeStamp(), + // use accumulated fees up until proposed epoch change block + accumulatedFeesInEpoch: prevBlockExecutionResults.GetAccumulatedFeesInEpoch(), + devFeesInEpoch: prevBlockExecutionResults.GetDevFeesInEpoch(), + }, + prevEpochStart: prevEpochStart, + lastNoncesPerShardPrevEpoch: lastNoncesPerShardPrevEpoch, + lastNoncesPerShardCurrEpoch: lastNoncesPerShardCurrEpoch, + }, nil +} + func (e *economics) printEconomicsData( - metaBlock *block.MetaBlock, - prevEpochEconomics block.Economics, + computationData economicsComputationData, + prevEpochEconomics data.EconomicsHandler, inflationRate float64, newTokens *big.Int, computedEconomics block.Economics, @@ -228,38 +346,38 @@ func (e *economics) printEconomicsData( header := []string{"identifier", "", "value"} var rewardsForLeaders *big.Int - if metaBlock.Epoch > e.stakingV2EnableEpoch { - rewardsForLeaders = core.GetIntTrimmedPercentageOfValue(metaBlock.AccumulatedFeesInEpoch, e.rewardsHandler.LeaderPercentageInEpoch(metaBlock.GetEpoch())) + if computationData.newEpoch > e.stakingV2EnableEpoch { + rewardsForLeaders = core.GetIntTrimmedPercentageOfValue(computationData.accumulatedFeesInEpoch, e.rewardsHandler.LeaderPercentageInEpoch(computationData.newEpoch)) } else { - rewardsForLeaders = core.GetApproximatePercentageOfValue(metaBlock.AccumulatedFeesInEpoch, e.rewardsHandler.LeaderPercentageInEpoch(metaBlock.GetEpoch())) + rewardsForLeaders = core.GetApproximatePercentageOfValue(computationData.accumulatedFeesInEpoch, e.rewardsHandler.LeaderPercentageInEpoch(computationData.newEpoch)) } - maxSupplyLength := len(prevEpochEconomics.TotalSupply.String()) + maxSupplyLength := len(prevEpochEconomics.GetTotalSupply().String()) lines := []*display.LineData{ e.newDisplayLine("epoch", "", - e.alignRight(fmt.Sprintf("%d", metaBlock.Epoch), maxSupplyLength)), + e.alignRight(fmt.Sprintf("%d", computationData.newEpoch), maxSupplyLength)), e.newDisplayLine("inflation rate", "", e.alignRight(fmt.Sprintf("%.6f", inflationRate), maxSupplyLength)), e.newDisplayLine("previous total supply", "(1)", - e.alignRight(prevEpochEconomics.TotalSupply.String(), maxSupplyLength)), + e.alignRight(prevEpochEconomics.GetTotalSupply().String(), maxSupplyLength)), e.newDisplayLine("new tokens", "(2)", e.alignRight(newTokens.String(), maxSupplyLength)), e.newDisplayLine("current total supply", "(1+2)", e.alignRight(computedEconomics.TotalSupply.String(), maxSupplyLength)), e.newDisplayLine("accumulated fees in epoch", "(3)", - e.alignRight(metaBlock.AccumulatedFeesInEpoch.String(), maxSupplyLength)), + e.alignRight(computationData.accumulatedFeesInEpoch.String(), maxSupplyLength)), e.newDisplayLine("total rewards to be distributed", "(4)", e.alignRight(totalRewardsToBeDistributed.String(), maxSupplyLength)), e.newDisplayLine("total num blocks in epoch", "(5)", e.alignRight(fmt.Sprintf("%d", totalNumBlocksInEpoch), maxSupplyLength)), e.newDisplayLine("dev fees in epoch", "(6)", - e.alignRight(metaBlock.DevFeesInEpoch.String(), maxSupplyLength)), + e.alignRight(computationData.devFeesInEpoch.String(), maxSupplyLength)), e.newDisplayLine("leader fees in epoch", "(7)", e.alignRight(rewardsForLeaders.String(), maxSupplyLength)), e.newDisplayLine("reward per block", "(8)", e.alignRight(rwdPerBlock.String(), maxSupplyLength)), e.newDisplayLine("percent for protocol sustainability", "(9)", - e.alignRight(fmt.Sprintf("%.6f", e.rewardsHandler.ProtocolSustainabilityPercentageInEpoch(metaBlock.GetEpoch())), maxSupplyLength)), + e.alignRight(fmt.Sprintf("%.6f", e.rewardsHandler.ProtocolSustainabilityPercentageInEpoch(computationData.newEpoch)), maxSupplyLength)), e.newDisplayLine("reward for protocol sustainability", "(4 * 9)", e.alignRight(rewardsForAccelerator.String(), maxSupplyLength)), } @@ -333,6 +451,9 @@ func (e *economics) adjustRewardsPerBlockWithAcceleratorRewards( acceleratorRewards *big.Int, blocksInEpoch uint64, ) { + if blocksInEpoch == 0 { + return + } acceleratorRewardsPerBlock := big.NewInt(0).Div(acceleratorRewards, big.NewInt(0).SetUint64(blocksInEpoch)) rwdPerBlock.Sub(rwdPerBlock, acceleratorRewardsPerBlock) } @@ -370,16 +491,72 @@ func (e *economics) adjustRewardsPerBlockWithLeaderPercentage( } // compute inflation rate from genesisTotalSupply and economics settings for that year -func (e *economics) computeInflationRate(currentRound uint64, epoch uint32) float64 { - //TODO calculate according to new year start and give new years in when accelerator epoch is enabled +func (e *economics) computeInflationBeforeSupernova(currentRound uint64, epoch uint32) float64 { + roundDurationInSec := uint64(e.roundTime.TimeDuration().Seconds()) + if roundDurationInSec <= 0 { + // this means that round duration is sub-seconds + // set it to default number of seconds + log.Error("computeInflationBeforeSupernova: sub second round time before supernova activation") + roundDurationInSec = e.getDefaultRoundDuration() + } - roundsPerDay := numberOfSecondsInDay / uint64(e.roundTime.TimeDuration().Seconds()) + roundsPerDay := numberOfSecondsInDay / roundDurationInSec roundsPerYear := numberOfDaysInYear * roundsPerDay yearsIndex := uint32(currentRound/roundsPerYear) + 1 return e.rewardsHandler.MaxInflationRate(yearsIndex, epoch) } +func (e *economics) getDefaultRoundDuration() uint64 { + defaultRoundDuration := uint64(6) // seconds + chainParameters, err := e.chainParamsHandler.ChainParametersForEpoch(0) + if err != nil { + // this should not happen, chain parameter configs is checked at init + return defaultRoundDuration + } + + return chainParameters.RoundDuration +} + +func (e *economics) computeInflationRate( + computationData economicsComputationDataHandler, +) float64 { + prevEpoch := e.getPreviousEpoch(computationData.GetEpoch()) + supernovaInEpochActivated := e.enableEpochsHandler.IsFlagEnabledInEpoch(common.SupernovaFlag, prevEpoch) + + if !supernovaInEpochActivated { + return e.computeInflationBeforeSupernova(computationData.GetRound(), computationData.GetEpoch()) + } + + return e.computeInflationRateAfterSupernova(computationData.GetTimeStamp(), computationData.GetEpoch()) +} + +// currentTimestamp is defined as unix milliseconds after supernova is activated +func (e *economics) computeInflationRateAfterSupernova(currentTimestampMs uint64, epoch uint32) float64 { + // genesisTimestamp has to be converted as unix milliseconds + genesisTimestamp := common.ConvertTimeStampSecToMs(e.genesisTimestamp) + + // if supernova is activated from genesis, genesis timestamp has to be as milliseconds + if e.enableEpochsHandler.IsFlagEnabledInEpoch(common.SupernovaFlag, 0) { + genesisTimestamp = e.genesisTimestamp + } + + if currentTimestampMs < genesisTimestamp { + return e.rewardsHandler.MaxInflationRate(1, epoch) // years index are defined starting from 1 + } + + yearsIndex := (currentTimestampMs-genesisTimestamp)/numberOfMillisecondsInYear + 1 + return e.rewardsHandler.MaxInflationRate(uint32(yearsIndex), epoch) +} + +func (e *economics) getPreviousEpoch(epoch uint32) uint32 { + if epoch == 0 { + return epoch + } + + return epoch - 1 +} + // compute rewards per block from according to inflation rate and total supply from previous block and maxBlocksPerEpoch func (e *economics) computeRewardsPerBlock( prevTotalSupply *big.Int, @@ -388,7 +565,7 @@ func (e *economics) computeRewardsPerBlock( epoch uint32, ) *big.Int { - inflationRateForEpoch := e.computeInflationForEpoch(inflationRate, maxBlocksInEpoch) + inflationRateForEpoch := e.computeInflationForEpoch(inflationRate, maxBlocksInEpoch, epoch) rewardsPerBlock := big.NewInt(0).Div(prevTotalSupply, big.NewInt(0).SetUint64(maxBlocksInEpoch)) if epoch > e.stakingV2EnableEpoch { @@ -398,13 +575,40 @@ func (e *economics) computeRewardsPerBlock( return core.GetApproximatePercentageOfValue(rewardsPerBlock, inflationRateForEpoch) } -func (e *economics) computeInflationForEpoch(inflationRate float64, maxBlocksInEpoch uint64) float64 { +func (e *economics) computeInflationForEpoch( + inflationRate float64, + maxBlocksInEpoch uint64, + epoch uint32, +) float64 { + prevEpoch := e.getPreviousEpoch(epoch) + chainParameters, err := e.chainParamsHandler.ChainParametersForEpoch(prevEpoch) + if err != nil { + log.Warn("could not get rounds per epoch for epoch, returned current chain paramters", "prevEpoch", prevEpoch, "error", err) + chainParameters = e.chainParamsHandler.CurrentChainParameters() + } + roundDuration := time.Duration(chainParameters.RoundDuration) * time.Millisecond + inflationRatePerDay := inflationRate / numberOfDaysInYear - roundsPerDay := numberOfSecondsInDay / uint64(e.roundTime.TimeDuration().Seconds()) + roundsPerDay := common.ComputeRoundsPerDay(roundDuration, e.enableEpochsHandler, epoch) maxBlocksInADay := core.MaxUint64(1, roundsPerDay*uint64(e.shardCoordinator.NumberOfShards()+1)) + if maxBlocksInADay == 0 { + log.Warn("computeInflationForEpoch: max block in a day is zero, return inflation rate directly") + return inflationRate + } + inflationRateForEpoch := inflationRatePerDay * (float64(maxBlocksInEpoch) / float64(maxBlocksInADay)) + log.Trace("computeInflationForEpoch", + "epoch", epoch, + "inflationRateForEpoch", inflationRateForEpoch, + "inflationRatePerDay", inflationRatePerDay, + "inflationRate", inflationRate, + "roundsPerDay", roundsPerDay, + "maxBlocksInEpoch", maxBlocksInEpoch, + "maxBlocksInADay", maxBlocksInADay, + ) + return inflationRateForEpoch } @@ -434,7 +638,7 @@ func (e *economics) computeNumOfTotalCreatedBlocks( return core.MaxUint64(1, totalNumBlocks) } -func (e *economics) startNoncePerShardFromEpochStart(epoch uint32) (map[uint32]uint64, *block.MetaBlock, error) { +func (e *economics) startNoncePerShardFromEpochStart(epoch uint32) (map[uint32]uint64, data.MetaHeaderHandler, error) { mapShardIdNonce := make(map[uint32]uint64, e.shardCoordinator.NumberOfShards()+1) for i := uint32(0); i < e.shardCoordinator.NumberOfShards(); i++ { mapShardIdNonce[i] = e.genesisNonce @@ -451,37 +655,54 @@ func (e *economics) startNoncePerShardFromEpochStart(epoch uint32) (map[uint32]u return mapShardIdNonce, previousEpochStartMeta, nil } - mapShardIdNonce[core.MetachainShardId] = previousEpochStartMeta.GetNonce() - for _, shardData := range previousEpochStartMeta.EpochStart.LastFinalizedHeaders { - mapShardIdNonce[shardData.ShardID] = shardData.Nonce + prevEpochMetaNonce, err := getPrevEpochMetaStartNonceForEconomics(previousEpochStartMeta) + if err != nil { + return nil, nil, err + } + mapShardIdNonce[core.MetachainShardId] = prevEpochMetaNonce + for _, shardData := range previousEpochStartMeta.GetEpochStartHandler().GetLastFinalizedHeaderHandlers() { + mapShardIdNonce[shardData.GetShardID()] = shardData.GetNonce() } return mapShardIdNonce, previousEpochStartMeta, nil } -func (e *economics) maxPossibleNotarizedBlocks(currentRound uint64, prev *block.MetaBlock) uint64 { +func getPrevEpochMetaStartNonceForEconomics(previousEpochStartMeta data.MetaHeaderHandler) (uint64, error) { + if !previousEpochStartMeta.IsHeaderV3() { + return previousEpochStartMeta.GetNonce(), nil + } + // todo: extract the epoch change proposal execution result here + lastNotarizedResult, err := common.GetLastBaseExecutionResultHandler(previousEpochStartMeta) + if err != nil { + return 0, err + } + + return lastNotarizedResult.GetHeaderNonce(), nil +} + +func (e *economics) maxPossibleNotarizedBlocks(currentRound uint64, prev data.MetaHeaderHandler) uint64 { maxBlocks := uint64(0) - for _, shardData := range prev.EpochStart.LastFinalizedHeaders { - maxBlocks += currentRound - shardData.Round + for _, shardData := range prev.GetEpochStartHandler().GetLastFinalizedHeaderHandlers() { + maxBlocks += currentRound - shardData.GetRound() } // For metaChain blocks - maxBlocks += currentRound - prev.Round + maxBlocks += currentRound - prev.GetRound() return maxBlocks } -func (e *economics) startNoncePerShardFromLastCrossNotarized(metaNonce uint64, epochStart block.EpochStart) (map[uint32]uint64, error) { +func (e *economics) startNoncePerShardFromLastCrossNotarized(metaNonce uint64, epochStart data.EpochStartHandler) map[uint32]uint64 { mapShardIdNonce := make(map[uint32]uint64, e.shardCoordinator.NumberOfShards()+1) for i := uint32(0); i < e.shardCoordinator.NumberOfShards(); i++ { mapShardIdNonce[i] = e.genesisNonce } mapShardIdNonce[core.MetachainShardId] = metaNonce - for _, shardData := range epochStart.LastFinalizedHeaders { - mapShardIdNonce[shardData.ShardID] = shardData.Nonce + for _, shardData := range epochStart.GetLastFinalizedHeaderHandlers() { + mapShardIdNonce[shardData.GetShardID()] = shardData.GetNonce() } - return mapShardIdNonce, nil + return mapShardIdNonce } func (e *economics) checkEconomicsInvariants( @@ -489,7 +710,7 @@ func (e *economics) checkEconomicsInvariants( inflationRate float64, maxBlocksInEpoch uint64, totalNumBlocksInEpoch uint64, - metaBlock *block.MetaBlock, + computationData economicsComputationDataHandler, epoch uint32, maxPossibleNotarizedBlocks uint64, ) error { @@ -506,10 +727,10 @@ func (e *economics) checkEconomicsInvariants( } - if !core.IsInRangeInclusive(metaBlock.AccumulatedFeesInEpoch, zero, e.genesisTotalSupply) { + if !core.IsInRangeInclusive(computationData.GetAccumulatedFeesInEpoch(), zero, e.genesisTotalSupply) { return fmt.Errorf("%w, computed accumulated fees %s, max allowed %s", epochStart.ErrInvalidAccumulatedFees, - metaBlock.AccumulatedFeesInEpoch, + computationData.GetAccumulatedFeesInEpoch(), e.genesisTotalSupply, ) } @@ -519,10 +740,10 @@ func (e *economics) checkEconomicsInvariants( actualMaxBlocks = maxPossibleNotarizedBlocks } - inflationPerEpoch := e.computeInflationForEpoch(inflationRate, actualMaxBlocks) + inflationPerEpoch := e.computeInflationForEpoch(inflationRate, actualMaxBlocks, epoch) maxRewardsInEpoch := core.GetIntTrimmedPercentageOfValue(computedEconomics.TotalSupply, inflationPerEpoch) - if maxRewardsInEpoch.Cmp(metaBlock.AccumulatedFeesInEpoch) < 0 { - maxRewardsInEpoch.Set(metaBlock.AccumulatedFeesInEpoch) + if maxRewardsInEpoch.Cmp(computationData.GetAccumulatedFeesInEpoch()) < 0 { + maxRewardsInEpoch.Set(computationData.GetAccumulatedFeesInEpoch()) } if !core.IsInRangeInclusive(computedEconomics.RewardsForProtocolSustainability, zero, maxRewardsInEpoch) { @@ -562,9 +783,9 @@ func (e *economics) checkEconomicsInvariants( // VerifyRewardsPerBlock checks whether rewards per block value was correctly computed func (e *economics) VerifyRewardsPerBlock( - metaBlock *block.MetaBlock, + metaBlock data.MetaHeaderHandler, correctedProtocolSustainability *big.Int, - computedEconomics *block.Economics, + computedEconomics data.EconomicsHandler, ) error { if computedEconomics == nil { return epochStart.ErrNilEconomicsData @@ -573,20 +794,23 @@ func (e *economics) VerifyRewardsPerBlock( return nil } - computedEconomics.RewardsForProtocolSustainability.Set(correctedProtocolSustainability) + err := computedEconomics.SetRewardsForProtocolSustainability(correctedProtocolSustainability) + if err != nil { + return err + } computedEconomicsHash, err := core.CalculateHash(e.marshalizer, e.hasher, computedEconomics) if err != nil { return err } - receivedEconomics := metaBlock.EpochStart.Economics - receivedEconomicsHash, err := core.CalculateHash(e.marshalizer, e.hasher, &receivedEconomics) + receivedEconomics := metaBlock.GetEpochStartHandler().GetEconomicsHandler() + receivedEconomicsHash, err := core.CalculateHash(e.marshalizer, e.hasher, receivedEconomics) if err != nil { return err } if !bytes.Equal(receivedEconomicsHash, computedEconomicsHash) { - logEconomicsDifferences(computedEconomics, &receivedEconomics) + logEconomicsDifferences(computedEconomics, receivedEconomics) return epochStart.ErrEndOfEpochEconomicsDataDoesNotMatch } @@ -598,19 +822,19 @@ func (e *economics) IsInterfaceNil() bool { return e == nil } -func logEconomicsDifferences(computed *block.Economics, received *block.Economics) { +func logEconomicsDifferences(computed data.EconomicsHandler, received data.EconomicsHandler) { log.Warn("VerifyRewardsPerBlock error", - "\ncomputed total to distribute", computed.TotalToDistribute, - "computed total newly minted", computed.TotalNewlyMinted, - "computed total supply", computed.TotalSupply, - "computed rewards per block per node", computed.RewardsPerBlock, - "computed rewards for protocol sustainability", computed.RewardsForProtocolSustainability, - "computed node price", computed.NodePrice, - "\nreceived total to distribute", received.TotalToDistribute, - "received total newly minted", received.TotalNewlyMinted, - "received total supply", received.TotalSupply, - "received rewards per block per node", received.RewardsPerBlock, - "received rewards for protocol sustainability", received.RewardsForProtocolSustainability, - "received node price", received.NodePrice, + "\ncomputed total to distribute", computed.GetTotalToDistribute(), + "computed total newly minted", computed.GetTotalNewlyMinted(), + "computed total supply", computed.GetTotalSupply(), + "computed rewards per block per node", computed.GetRewardsPerBlock(), + "computed rewards for protocol sustainability", computed.GetRewardsForProtocolSustainability(), + "computed node price", computed.GetNodePrice(), + "\nreceived total to distribute", received.GetTotalToDistribute(), + "received total newly minted", received.GetTotalNewlyMinted(), + "received total supply", received.GetTotalSupply(), + "received rewards per block per node", received.GetRewardsPerBlock(), + "received rewards for protocol sustainability", received.GetRewardsForProtocolSustainability(), + "received node price", received.GetNodePrice(), ) } diff --git a/epochStart/metachain/economicsComputationData.go b/epochStart/metachain/economicsComputationData.go new file mode 100644 index 00000000000..dd95578d6ed --- /dev/null +++ b/epochStart/metachain/economicsComputationData.go @@ -0,0 +1,44 @@ +package metachain + +import "math/big" + +type economicsComputationDataHandler interface { + GetEpoch() uint32 + GetRound() uint64 + GetTimeStamp() uint64 + GetDevFeesInEpoch() *big.Int + GetAccumulatedFeesInEpoch() *big.Int +} + +type economicsComputationData struct { + newEpoch uint32 + round uint64 + timeStamp uint64 + accumulatedFeesInEpoch *big.Int + devFeesInEpoch *big.Int +} + +// GetEpoch returns epoch field +func (m *economicsComputationData) GetEpoch() uint32 { + return m.newEpoch +} + +// GetRound returns round field +func (m *economicsComputationData) GetRound() uint64 { + return m.round +} + +// GetTimeStamp returns time stamp field +func (m *economicsComputationData) GetTimeStamp() uint64 { + return m.timeStamp +} + +// GetDevFeesInEpoch returns dev fees in epoch field +func (m *economicsComputationData) GetDevFeesInEpoch() *big.Int { + return m.devFeesInEpoch +} + +// GetAccumulatedFeesInEpoch returns accumulated fees in epoch field +func (m *economicsComputationData) GetAccumulatedFeesInEpoch() *big.Int { + return m.accumulatedFeesInEpoch +} diff --git a/epochStart/metachain/economics_test.go b/epochStart/metachain/economics_test.go index 965d25e4fb7..3e8242c0f0a 100644 --- a/epochStart/metachain/economics_test.go +++ b/epochStart/metachain/economics_test.go @@ -10,16 +10,26 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/epochStart" "github.com/multiversx/mx-chain-go/epochStart/mock" + commonErrors "github.com/multiversx/mx-chain-go/errors" "github.com/multiversx/mx-chain-go/process" + processEconomics "github.com/multiversx/mx-chain-go/process/economics" "github.com/multiversx/mx-chain-go/storage" + "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" + "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func createMockEpochEconomicsArguments() ArgsNewEpochEconomics { @@ -34,6 +44,8 @@ func createMockEpochEconomicsArguments() ArgsNewEpochEconomics { RoundTime: &mock.RoundTimeDurationHandler{}, GenesisTotalSupply: big.NewInt(2000000), EconomicsDataNotified: NewEpochEconomicsStatistics(), + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + ChainParamsHandler: &chainParameters.ChainParametersHandlerStub{}, } return argsNewEpochEconomics } @@ -49,6 +61,17 @@ func TestEpochEconomics_NewEndOfEpochEconomicsDataCreatorNilMarshalizer(t *testi require.Equal(t, epochStart.ErrNilMarshalizer, err) } +func TestEpochEconomics_NewEndOfEpochEconomicsDataCreatorNilHasher(t *testing.T) { + t.Parallel() + + arguments := createMockEpochEconomicsArguments() + arguments.Hasher = nil + + esd, err := NewEndOfEpochEconomicsDataCreator(arguments) + require.Nil(t, esd) + require.Equal(t, epochStart.ErrNilHasher, err) +} + func TestEpochEconomics_NewEndOfEpochEconomicsDataCreatorNilStore(t *testing.T) { t.Parallel() @@ -158,6 +181,50 @@ func TestNewEndOfEpochEconomicsDataCreator_NilRoundTimeDurationHandler(t *testin assert.Equal(t, epochStart.ErrNilRoundHandler, err) } +func TestNewEndOfEpochEconomicsDataCreator_NilEconomicsDataProvider(t *testing.T) { + t.Parallel() + + args := getArguments() + args.EconomicsDataNotified = nil + eoeedc, err := NewEndOfEpochEconomicsDataCreator(args) + + assert.True(t, check.IfNil(eoeedc)) + assert.Equal(t, epochStart.ErrNilEconomicsDataProvider, err) +} + +func TestNewEndOfEpochEconomicsDataCreator_NilGenesisTotalSupply(t *testing.T) { + t.Parallel() + + args := getArguments() + args.GenesisTotalSupply = nil + eoeedc, err := NewEndOfEpochEconomicsDataCreator(args) + + assert.True(t, check.IfNil(eoeedc)) + assert.Equal(t, epochStart.ErrNilGenesisTotalSupply, err) +} + +func TestNewEndOfEpochEconomicsDataCreator_NilEnableEpochsHandler(t *testing.T) { + t.Parallel() + + args := getArguments() + args.EnableEpochsHandler = nil + eoeedc, err := NewEndOfEpochEconomicsDataCreator(args) + + assert.True(t, check.IfNil(eoeedc)) + assert.Equal(t, commonErrors.ErrNilEnableEpochsHandler, err) +} + +func TestNewEndOfEpochEconomicsDataCreator_NilChainParametersHandler(t *testing.T) { + t.Parallel() + + args := getArguments() + args.ChainParamsHandler = nil + eoeedc, err := NewEndOfEpochEconomicsDataCreator(args) + + assert.True(t, check.IfNil(eoeedc)) + assert.Equal(t, commonErrors.ErrNilChainParametersHandler, err) +} + func TestNewEndOfEpochEconomicsDataCreator_ShouldWork(t *testing.T) { t.Parallel() @@ -263,16 +330,44 @@ func TestEconomics_AdjustRewardsPerBlockWithLeaderPercentageAndDevFees(t *testin } func TestEconomics_AdjustRewardsPerBlockWithDeveloperFees(t *testing.T) { - args := getArguments() - ec, _ := NewEndOfEpochEconomicsDataCreator(args) + t.Parallel() - rwdPerBlock := big.NewInt(0).SetUint64(1000) - blocksInEpoch := uint64(100) - developerFees := big.NewInt(0).SetUint64(500) + t.Run("before supernova", func(t *testing.T) { + t.Parallel() - expectedRwdPerBlock := big.NewInt(995) - ec.adjustRewardsPerBlockWithDeveloperFees(rwdPerBlock, developerFees, blocksInEpoch) - assert.Equal(t, expectedRwdPerBlock, rwdPerBlock) + args := getArguments() + ec, _ := NewEndOfEpochEconomicsDataCreator(args) + + rwdPerBlock := big.NewInt(0).SetUint64(1000) + blocksInEpoch := uint64(100) + developerFees := big.NewInt(0).SetUint64(500) + + expectedRwdPerBlock := big.NewInt(995) + ec.adjustRewardsPerBlockWithDeveloperFees(rwdPerBlock, developerFees, blocksInEpoch) + assert.Equal(t, expectedRwdPerBlock, rwdPerBlock) + }) + + t.Run("after supernova", func(t *testing.T) { + t.Parallel() + + args := getArguments() + + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag + }, + } + + ec, _ := NewEndOfEpochEconomicsDataCreator(args) + + rwdPerBlock := big.NewInt(0).SetUint64(1000) + blocksInEpoch := uint64(100) + developerFees := big.NewInt(0).SetUint64(500) + + expectedRwdPerBlock := big.NewInt(995) + ec.adjustRewardsPerBlockWithDeveloperFees(rwdPerBlock, developerFees, blocksInEpoch) + assert.Equal(t, expectedRwdPerBlock, rwdPerBlock) + }) } func TestEconomics_ComputeEndOfEpochEconomics_NotEpochStartShouldErr(t *testing.T) { @@ -292,232 +387,901 @@ func TestEconomics_ComputeEndOfEpochEconomics_NotEpochStartShouldErr(t *testing. } func TestEconomics_ComputeInflationRate(t *testing.T) { - args := getArguments() - errNotGoodYear := errors.New("not good year") - var errFound error - year1inflation := 1.0 - year2inflation := 0.5 - lateYearInflation := 2.0 + t.Parallel() - args.RewardsHandler = &mock.RewardsHandlerStub{ - MaxInflationRateCalled: func(year uint32, _ uint32) float64 { - switch year { - case 0: - errFound = errNotGoodYear - return 0.0 - case 1: - return year1inflation - case 2: - return year2inflation - default: - return lateYearInflation - } - }, - } - ec, _ := NewEndOfEpochEconomicsDataCreator(args) + t.Run("before supernova", func(t *testing.T) { + t.Parallel() + + args := getArguments() + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return flag != common.SupernovaFlag + }, + } + + roundDuration := 4000 + args.RoundTime = &mock.RoundTimeDurationHandler{ + TimeDurationCalled: func() time.Duration { + return time.Duration(roundDuration) * time.Millisecond + }, + } + + errNotGoodYear := errors.New("not good year") + var errFound error + year1inflation := 1.0 + year2inflation := 0.5 + lateYearInflation := 2.0 + + args.RewardsHandler = &mock.RewardsHandlerStub{ + MaxInflationRateCalled: func(year uint32, _ uint32) float64 { + switch year { + case 0: + errFound = errNotGoodYear + return 0.0 + case 1: + return year1inflation + case 2: + return year2inflation + default: + return lateYearInflation + } + }, + } + + genesisTimestamp := args.GenesisTimestamp + + ec, _ := NewEndOfEpochEconomicsDataCreator(args) - rate := ec.computeInflationRate(1, 1) - assert.Nil(t, errFound) - assert.Equal(t, rate, year1inflation) + epoch := uint32(1) - rate = ec.computeInflationRate(50000, 1) - assert.Nil(t, errFound) - assert.Equal(t, rate, year1inflation) + header := &block.MetaBlock{ + Round: 1, + Epoch: epoch, + TimeStamp: genesisTimestamp + uint64(roundDuration), + } + rate := ec.computeInflationRate(header) + assert.Nil(t, errFound) + assert.Equal(t, rate, year1inflation) + + header = &block.MetaBlock{ + Round: 50000, + Epoch: epoch, + TimeStamp: genesisTimestamp + uint64(roundDuration)*50000, + } + rate = ec.computeInflationRate(header) + assert.Nil(t, errFound) + assert.Equal(t, rate, year1inflation) + + header = &block.MetaBlock{ + Round: 7884000, + Epoch: epoch, + TimeStamp: genesisTimestamp + numberOfMillisecondsInYear, + } + rate = ec.computeInflationRate(header) + assert.Nil(t, errFound) + assert.Equal(t, rate, year2inflation) + + header = &block.MetaBlock{ + Round: 8884000, + Epoch: epoch, + TimeStamp: genesisTimestamp + numberOfMillisecondsInYear, + } + rate = ec.computeInflationRate(header) + assert.Nil(t, errFound) + assert.Equal(t, rate, year2inflation) + + header = &block.MetaBlock{ + Round: 38884000, + Epoch: epoch, + TimeStamp: genesisTimestamp + 2*numberOfMillisecondsInYear, + } + rate = ec.computeInflationRate(header) + assert.Nil(t, errFound) + assert.Equal(t, rate, lateYearInflation) + }) + + t.Run("after supernova", func(t *testing.T) { + t.Parallel() + + args := getArguments() + + activationEpoch := uint32(2) + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag && epoch >= activationEpoch + }, + GetActivationEpochCalled: func(flag core.EnableEpochFlag) uint32 { + if flag == common.SupernovaFlag { + return activationEpoch + } + + return 0 + }, + } - rate = ec.computeInflationRate(7884000, 1) - assert.Nil(t, errFound) - assert.Equal(t, rate, year2inflation) + roundDurationBeforeSupernova := 4000 + supernovaActivationRound := uint64(7884000) // rounds of 4s in a day + + roundDurationAfterSupernova := 400 + + errNotGoodYear := errors.New("not good year") + var errFound error + year1inflation := 1.0 + year2inflation := 0.5 + lateYearInflation := 2.0 + + args.RewardsHandler = &mock.RewardsHandlerStub{ + MaxInflationRateCalled: func(year uint32, _ uint32) float64 { + switch year { + case 0: + errFound = errNotGoodYear + return 0.0 + case 1: + return year1inflation + case 2: + return year2inflation + default: + return lateYearInflation + } + }, + } + ec, _ := NewEndOfEpochEconomicsDataCreator(args) - rate = ec.computeInflationRate(8884000, 1) - assert.Nil(t, errFound) - assert.Equal(t, rate, year2inflation) + genesisTimestamp := args.GenesisTimestamp - rate = ec.computeInflationRate(38884000, 1) - assert.Nil(t, errFound) - assert.Equal(t, rate, lateYearInflation) + epoch := uint32(1) + + header := &block.MetaBlock{ + Round: 1, + Epoch: epoch + 1, + TimeStamp: genesisTimestamp + uint64(roundDurationBeforeSupernova), + } + rate := ec.computeInflationRate(header) + assert.Nil(t, errFound) + assert.Equal(t, year1inflation, rate) + + header = &block.MetaBlock{ + Round: 50000, + Epoch: epoch + 1, + TimeStamp: genesisTimestamp + uint64(roundDurationBeforeSupernova*50000), + } + rate = ec.computeInflationRate(header) + assert.Nil(t, errFound) + assert.Equal(t, year1inflation, rate) + + header = &block.MetaBlock{ + Round: supernovaActivationRound - 1, + Epoch: epoch + 1, + TimeStamp: genesisTimestamp + uint64(roundDurationBeforeSupernova)*(supernovaActivationRound-uint64(1)), + } + rate = ec.computeInflationRate(header) + assert.Nil(t, errFound) + assert.Equal(t, year1inflation, rate) + + header = &block.MetaBlock{ + Round: supernovaActivationRound, + Epoch: epoch + 1, + TimeStamp: genesisTimestamp + uint64(roundDurationBeforeSupernova)*supernovaActivationRound, + } + rate = ec.computeInflationRate(header) + assert.Nil(t, errFound) + assert.Equal(t, year2inflation, rate) + + ec.SetRoundTimeHandler( + &mock.RoundTimeDurationHandler{ + TimeDurationCalled: func() time.Duration { + return time.Duration(roundDurationAfterSupernova) * time.Millisecond + }, + }, + ) + + header = &block.MetaBlock{ + Round: supernovaActivationRound, + Epoch: activationEpoch + 1, + TimeStamp: genesisTimestamp + uint64(roundDurationBeforeSupernova)*supernovaActivationRound, + } + rate = ec.computeInflationRate(header) + assert.Nil(t, errFound) + assert.Equal(t, year2inflation, rate) + + header = &block.MetaBlock{ + Round: supernovaActivationRound * (10 + 1), + Epoch: activationEpoch + 10 + 1, + TimeStamp: genesisTimestamp + 2*numberOfMillisecondsInYear, + } + rate = ec.computeInflationRate(header) + assert.Nil(t, errFound) + assert.Equal(t, lateYearInflation, rate) + }) + + t.Run("supernova activated from genesis", func(t *testing.T) { + t.Parallel() + + args := getArguments() + + activationEpoch := uint32(0) + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag && epoch >= activationEpoch + }, + GetActivationEpochCalled: func(flag core.EnableEpochFlag) uint32 { + if flag == common.SupernovaFlag { + return activationEpoch + } + + return 0 + }, + } + + roundDurationSupernova := 400 + + errNotGoodYear := errors.New("not good year") + var errFound error + year1inflation := 1.0 + year2inflation := 0.5 + lateYearInflation := 2.0 + + args.RewardsHandler = &mock.RewardsHandlerStub{ + MaxInflationRateCalled: func(year uint32, _ uint32) float64 { + switch year { + case 0: + errFound = errNotGoodYear + return 0.0 + case 1: + return year1inflation + case 2: + return year2inflation + default: + return lateYearInflation + } + }, + } + ec, _ := NewEndOfEpochEconomicsDataCreator(args) + + genesisTimestamp := args.GenesisTimestamp + + epoch := uint32(1) + + header := &block.MetaBlock{ + Round: 1, + Epoch: epoch, + TimeStamp: genesisTimestamp + uint64(roundDurationSupernova), + } + rate := ec.computeInflationRate(header) + assert.Nil(t, errFound) + assert.Equal(t, rate, year1inflation) + + header = &block.MetaBlock{ + Round: 50000, + Epoch: epoch, + TimeStamp: genesisTimestamp + uint64(roundDurationSupernova)*50000, + } + rate = ec.computeInflationRate(header) + assert.Nil(t, errFound) + assert.Equal(t, rate, year1inflation) + + header = &block.MetaBlock{ + Round: 7884000, + Epoch: epoch, + TimeStamp: genesisTimestamp + numberOfMillisecondsInYear, + } + rate = ec.computeInflationRate(header) + assert.Nil(t, errFound) + assert.Equal(t, rate, year2inflation) + + header = &block.MetaBlock{ + Round: 8884000, + Epoch: epoch, + TimeStamp: genesisTimestamp + numberOfMillisecondsInYear, + } + rate = ec.computeInflationRate(header) + assert.Nil(t, errFound) + assert.Equal(t, rate, year2inflation) + + header = &block.MetaBlock{ + Round: 38884000, + Epoch: epoch, + TimeStamp: genesisTimestamp + 2*numberOfMillisecondsInYear, + } + rate = ec.computeInflationRate(header) + assert.Nil(t, errFound) + assert.Equal(t, rate, lateYearInflation) + }) } -func TestEconomics_ComputeEndOfEpochEconomics(t *testing.T) { +func TestEconomics_ComputeInflationRate_WithRealConfigData(t *testing.T) { t.Parallel() - mbPrevStartEpoch := block.MetaBlock{ - Round: 10, - Nonce: 5, - EpochStart: block.EpochStart{ - Economics: block.Economics{ - TotalSupply: big.NewInt(100000), - TotalToDistribute: big.NewInt(10), - TotalNewlyMinted: big.NewInt(109), - RewardsPerBlock: big.NewInt(10), - NodePrice: big.NewInt(10), + cfg, err := testscommon.CreateTestConfigs(t.TempDir(), "../../cmd/node/config") + require.Nil(t, err) + + argsNewEconomicsData := processEconomics.ArgsNewEconomicsData{ + Economics: cfg.EconomicsConfig, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.GasPriceModifierFlag }, }, + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + PubkeyConverter: &testscommon.PubkeyConverterStub{}, + ShardCoordinator: &testscommon.ShardsCoordinatorMock{}, + ChainParamsHandler: &chainParameters.ChainParametersHolderMock{}, } + argsNewEconomicsData.Economics.GlobalSettings.TailInflation.EnableEpoch = 999999 + economicsData, _ := processEconomics.NewEconomicsData(argsNewEconomicsData) - leaderPercentage := 0.1 args := getArguments() - args.RewardsHandler = &mock.RewardsHandlerStub{ - LeaderPercentageInEpochCalled: func(epoch uint32) float64 { - return leaderPercentage + + epochsPerYear := uint32(365) + roundsPerDay := uint64(14400) + roundsPerYear := uint64(epochsPerYear) * roundsPerDay + + supernovaActivationEpoch := epochsPerYear*5 + 10 + supernovaActivationRound := roundsPerYear*5 + 10*roundsPerDay + 50 + + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag && epoch >= supernovaActivationEpoch + }, + GetActivationEpochCalled: func(flag core.EnableEpochFlag) uint32 { + if flag == common.SupernovaFlag { + return supernovaActivationEpoch + } + + return 0 }, } - args.Store = &storageStubs.ChainStorerStub{ - GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { - return &storageStubs.StorerStub{GetCalled: func(key []byte) ([]byte, error) { - hdr := mbPrevStartEpoch - hdrBytes, _ := json.Marshal(hdr) - return hdrBytes, nil - }}, nil + roundDurationBeforeSupernova := uint64(6000) + roundDurationAfterSupernova := uint64(600) + + args.RoundTime = &mock.RoundTimeDurationHandler{ + TimeDurationCalled: func() time.Duration { + return time.Duration(roundDurationBeforeSupernova) * time.Millisecond }, } + + args.RewardsHandler = economicsData ec, _ := NewEndOfEpochEconomicsDataCreator(args) - mb := block.MetaBlock{ - Round: 15000, - EpochStart: block.EpochStart{ - LastFinalizedHeaders: []block.EpochStartShardData{ - {ShardID: 0, Round: 2, Nonce: 3}, - {ShardID: 1, Round: 2, Nonce: 3}, + genesisTimestamp := args.GenesisTimestamp + + header := &block.MetaBlock{ + Round: 1, + Epoch: 0, + TimeStamp: genesisTimestamp + roundDurationBeforeSupernova, + } + rate := ec.computeInflationRate(header) + assert.Equal(t, cfg.EconomicsConfig.GlobalSettings.YearSettings[0].MaximumInflation, rate) + assert.Equal(t, economicsData.MaxInflationRate(1, 1), rate) + + header = &block.MetaBlock{ + Round: roundsPerDay + 1, + Epoch: 1, + TimeStamp: genesisTimestamp + roundDurationBeforeSupernova*(roundsPerDay+1), + } + rate = ec.computeInflationRate(header) + assert.Equal(t, cfg.EconomicsConfig.GlobalSettings.YearSettings[0].MaximumInflation, rate) + assert.Equal(t, economicsData.MaxInflationRate(1, 1), rate) + + header = &block.MetaBlock{ + Round: roundsPerYear + 100, + Epoch: epochsPerYear + 1, + TimeStamp: genesisTimestamp + roundDurationBeforeSupernova*(roundsPerDay+100), + } + rate = ec.computeInflationRate(header) + assert.Equal(t, cfg.EconomicsConfig.GlobalSettings.YearSettings[1].MaximumInflation, rate) + assert.Equal(t, economicsData.MaxInflationRate(2, 1), rate) + + ec.SetRoundTimeHandler( + &mock.RoundTimeDurationHandler{ + TimeDurationCalled: func() time.Duration { + return time.Duration(roundDurationAfterSupernova) * time.Millisecond }, - Economics: block.Economics{}, }, - Epoch: 2, - AccumulatedFeesInEpoch: big.NewInt(10000), - DevFeesInEpoch: big.NewInt(0), - } + ) - res, err := ec.ComputeEndOfEpochEconomics(&mb) - assert.Nil(t, err) - assert.NotNil(t, res) + // after supernova activation, round is not used anymore + anyRound := uint64(1234) - var expectedLeaderFees *big.Int - if mb.Epoch > args.StakingV2EnableEpoch { - expectedLeaderFees = core.GetIntTrimmedPercentageOfValue(mb.AccumulatedFeesInEpoch, leaderPercentage) - } else { - expectedLeaderFees = core.GetApproximatePercentageOfValue(mb.AccumulatedFeesInEpoch, leaderPercentage) + header = &block.MetaBlock{ + Round: anyRound, + Epoch: supernovaActivationEpoch + 1, + TimeStamp: genesisTimestamp + roundDurationBeforeSupernova*(supernovaActivationRound), } - - assert.Equal(t, expectedLeaderFees, ec.economicsDataNotified.LeaderFees(), expectedLeaderFees) + rate = ec.computeInflationRate(header) + assert.Equal(t, economicsData.MaxInflationRate(6, 1), rate) + + // first time slot in year 7 + header = &block.MetaBlock{ + Round: anyRound, + Epoch: supernovaActivationEpoch + 1, + TimeStamp: genesisTimestamp + numberOfMillisecondsInYear*6, + } + rate = ec.computeInflationRate(header) + assert.Equal(t, economicsData.MaxInflationRate(7, 1), rate) + + // last time slot in year 7 + header = &block.MetaBlock{ + Round: anyRound, + Epoch: supernovaActivationEpoch + 1, + TimeStamp: genesisTimestamp + numberOfMillisecondsInYear*6 + roundDurationAfterSupernova, + } + rate = ec.computeInflationRate(header) + assert.Equal(t, economicsData.MaxInflationRate(7, 1), rate) + + // first time slot in year 8 + header = &block.MetaBlock{ + Round: anyRound, + Epoch: supernovaActivationEpoch + 1, + TimeStamp: genesisTimestamp + numberOfMillisecondsInYear*7, + } + rate = ec.computeInflationRate(header) + assert.Equal(t, economicsData.MaxInflationRate(8, 1), rate) + + // last time slot in year 8 + header = &block.MetaBlock{ + Round: anyRound, + Epoch: supernovaActivationEpoch + 1, + TimeStamp: genesisTimestamp + numberOfMillisecondsInYear*7 + roundDurationAfterSupernova, + } + rate = ec.computeInflationRate(header) + assert.Equal(t, economicsData.MaxInflationRate(8, 1), rate) + + // first time slot in year 9 + header = &block.MetaBlock{ + Round: anyRound, + Epoch: supernovaActivationEpoch + 1, + TimeStamp: genesisTimestamp + numberOfMillisecondsInYear*8, + } + rate = ec.computeInflationRate(header) + assert.Equal(t, economicsData.MaxInflationRate(9, 1), rate) + + // last time slot in year 9 + header = &block.MetaBlock{ + Round: anyRound, + Epoch: supernovaActivationEpoch + 1, + TimeStamp: genesisTimestamp + numberOfMillisecondsInYear*8 + roundDurationAfterSupernova, + } + rate = ec.computeInflationRate(header) + assert.Equal(t, economicsData.MaxInflationRate(9, 1), rate) + + // first time slot in year 10 + header = &block.MetaBlock{ + Round: anyRound, + Epoch: supernovaActivationEpoch + 1, + TimeStamp: genesisTimestamp + numberOfMillisecondsInYear*9, + } + rate = ec.computeInflationRate(header) + assert.Equal(t, economicsData.MaxInflationRate(10, 1), rate) + + // last time slot in year 10 + header = &block.MetaBlock{ + Round: anyRound, + Epoch: supernovaActivationEpoch + 1, + TimeStamp: genesisTimestamp + numberOfMillisecondsInYear*9 + roundDurationAfterSupernova, + } + rate = ec.computeInflationRate(header) + assert.Equal(t, economicsData.MaxInflationRate(10, 1), rate) + + // first time slot in year 11 + header = &block.MetaBlock{ + Round: anyRound, + Epoch: supernovaActivationEpoch + 1, + TimeStamp: genesisTimestamp + numberOfMillisecondsInYear*10, + } + rate = ec.computeInflationRate(header) + assert.Equal(t, economicsData.MaxInflationRate(11, 1), rate) + + // first time slot in year 11 + header = &block.MetaBlock{ + Round: anyRound, + Epoch: supernovaActivationEpoch + 1, + TimeStamp: genesisTimestamp + numberOfMillisecondsInYear*11, + } + rate = ec.computeInflationRate(header) + assert.Equal(t, economicsData.MaxInflationRate(11, 1), rate) } -func TestEconomics_VerifyRewardsPerBlock_DifferentHitRates(t *testing.T) { +func TestEconomics_ComputeEndOfEpochEconomics(t *testing.T) { t.Parallel() - commAddress := "protocolSustainabilityAddress" - totalSupply := big.NewInt(20000000000) // 20B - accFeesInEpoch := big.NewInt(0) - devFeesInEpoch := big.NewInt(0) - roundDur := 4 - args := getArguments() - accRewardsEnableEpoch := uint32(9999999) + t.Run("before supernova", func(t *testing.T) { + t.Parallel() - args.RewardsHandler = &mock.RewardsHandlerStub{ - MaxInflationRateCalled: func(_ uint32, _ uint32) float64 { - return 0.1 - }, - ProtocolSustainabilityAddressInEpochCalled: func(epoch uint32) string { - return commAddress - }, - ProtocolSustainabilityPercentageInEpochCalled: func(epoch uint32) float64 { - return 0.1 - }, - IsTailInflationEnabledCalled: func(epoch uint32) bool { - return epoch >= accRewardsEnableEpoch - }, - } - args.RoundTime = &mock.RoundTimeDurationHandler{ - TimeDurationCalled: func() time.Duration { - return time.Duration(roundDur) * time.Second - }, - } - newTotalSupply := big.NewInt(0).Add(totalSupply, totalSupply) - hdrPrevEpochStart := block.MetaBlock{ - Round: 0, - Nonce: 0, - Epoch: 0, - EpochStart: block.EpochStart{ - Economics: block.Economics{ - TotalSupply: newTotalSupply, - TotalToDistribute: big.NewInt(10), - TotalNewlyMinted: big.NewInt(10), - RewardsPerBlock: big.NewInt(10), - NodePrice: big.NewInt(10), - RewardsForProtocolSustainability: big.NewInt(10), + mbPrevStartEpoch := block.MetaBlock{ + Round: 10, + Nonce: 5, + EpochStart: block.EpochStart{ + Economics: block.Economics{ + TotalSupply: big.NewInt(100000), + TotalToDistribute: big.NewInt(10), + TotalNewlyMinted: big.NewInt(109), + RewardsPerBlock: big.NewInt(10), + NodePrice: big.NewInt(10), + }, }, - LastFinalizedHeaders: []block.EpochStartShardData{ - {ShardID: 0, Nonce: 0}, - {ShardID: 1, Nonce: 0}, + } + + leaderPercentage := 0.1 + args := getArguments() + args.RewardsHandler = &mock.RewardsHandlerStub{ + LeaderPercentageInEpochCalled: func(epoch uint32) float64 { + return leaderPercentage }, - }, - } - args.Store = &storageStubs.ChainStorerStub{ - GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { - // this will be the previous epoch meta block. It has initial 0 values, so it can be considered at genesis - return &storageStubs.StorerStub{GetCalled: func(key []byte) ([]byte, error) { - hdrBytes, _ := json.Marshal(hdrPrevEpochStart) - return hdrBytes, nil - }}, nil - }, - } - args.GenesisTotalSupply = totalSupply + } + args.Store = &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{GetCalled: func(key []byte) ([]byte, error) { + hdr := mbPrevStartEpoch + hdrBytes, _ := json.Marshal(hdr) + return hdrBytes, nil + }}, nil + }, + } + ec, _ := NewEndOfEpochEconomicsDataCreator(args) - ec, _ := NewEndOfEpochEconomicsDataCreator(args) + mb := block.MetaBlock{ + Round: 15000, + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + {ShardID: 0, Round: 2, Nonce: 3}, + {ShardID: 1, Round: 2, Nonce: 3}, + }, + Economics: block.Economics{}, + }, + Epoch: 2, + AccumulatedFeesInEpoch: big.NewInt(10000), + DevFeesInEpoch: big.NewInt(0), + } - expRwdPerBlock := 84 // based on 0.1 inflation + res, err := ec.ComputeEndOfEpochEconomics(&mb) + assert.Nil(t, err) + assert.NotNil(t, res) - numBlocksInEpochSlice := []int{ - numberOfSecondsInDay / roundDur, // 100 % hit rate - (numberOfSecondsInDay / roundDur) / 2, // 50 % hit rate - (numberOfSecondsInDay / roundDur) / 4, // 25 % hit rate - (numberOfSecondsInDay / roundDur) / 8, // 12.5 % hit rate - 1, // only the metablock was committed in that epoch - 37, // random - 63, // random - } + var expectedLeaderFees *big.Int + if mb.Epoch > args.StakingV2EnableEpoch { + expectedLeaderFees = core.GetIntTrimmedPercentageOfValue(mb.AccumulatedFeesInEpoch, leaderPercentage) + } else { + expectedLeaderFees = core.GetApproximatePercentageOfValue(mb.AccumulatedFeesInEpoch, leaderPercentage) + } - hdrPrevEpochStartHash, _ := core.CalculateHash(&mock.MarshalizerMock{}, &hashingMocks.HasherMock{}, hdrPrevEpochStart) - for _, numBlocksInEpoch := range numBlocksInEpochSlice { - expectedTotalToDistribute := big.NewInt(int64(expRwdPerBlock * numBlocksInEpoch * 3)) // 2 shards + meta - expectedTotalNewlyMinted := big.NewInt(0).Sub(expectedTotalToDistribute, accFeesInEpoch) - expectedTotalSupply := big.NewInt(0).Add(newTotalSupply, expectedTotalNewlyMinted) - expectedProtocolSustainabilityRewards := big.NewInt(0).Div(expectedTotalToDistribute, big.NewInt(10)) - commRewardPerBlock := big.NewInt(0).Div(expectedProtocolSustainabilityRewards, big.NewInt(int64(numBlocksInEpoch*3))) - adjustedRwdPerBlock := big.NewInt(0).Sub(big.NewInt(int64(expRwdPerBlock)), commRewardPerBlock) + assert.Equal(t, expectedLeaderFees, ec.economicsDataNotified.LeaderFees(), expectedLeaderFees) + }) + + t.Run("after supernova", func(t *testing.T) { + t.Parallel() + + mbPrevStartEpoch := block.MetaBlock{ + Round: 10, + Nonce: 5, + EpochStart: block.EpochStart{ + Economics: block.Economics{ + TotalSupply: big.NewInt(100000), + TotalToDistribute: big.NewInt(10), + TotalNewlyMinted: big.NewInt(109), + RewardsPerBlock: big.NewInt(10), + NodePrice: big.NewInt(10), + }, + }, + } + + leaderPercentage := 0.1 + args := getArguments() + + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag + }, + } + + args.ChainParamsHandler = &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(epoch uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{ + RoundDuration: uint64(600), + }, nil + }, + } + + args.RewardsHandler = &mock.RewardsHandlerStub{ + LeaderPercentageInEpochCalled: func(epoch uint32) float64 { + return leaderPercentage + }, + } + args.Store = &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{GetCalled: func(key []byte) ([]byte, error) { + hdr := mbPrevStartEpoch + hdrBytes, _ := json.Marshal(hdr) + return hdrBytes, nil + }}, nil + }, + } + ec, _ := NewEndOfEpochEconomicsDataCreator(args) mb := block.MetaBlock{ - Round: uint64(numBlocksInEpoch), - Nonce: uint64(numBlocksInEpoch), + Round: 15000, EpochStart: block.EpochStart{ LastFinalizedHeaders: []block.EpochStartShardData{ - {ShardID: 0, Round: uint64(numBlocksInEpoch), Nonce: uint64(numBlocksInEpoch)}, - {ShardID: 1, Round: uint64(numBlocksInEpoch), Nonce: uint64(numBlocksInEpoch)}, + {ShardID: 0, Round: 2, Nonce: 3}, + {ShardID: 1, Round: 2, Nonce: 3}, }, + Economics: block.Economics{}, + }, + Epoch: 2, + AccumulatedFeesInEpoch: big.NewInt(10000), + DevFeesInEpoch: big.NewInt(0), + } + + res, err := ec.ComputeEndOfEpochEconomics(&mb) + assert.Nil(t, err) + assert.NotNil(t, res) + + var expectedLeaderFees *big.Int + if mb.Epoch > args.StakingV2EnableEpoch { + expectedLeaderFees = core.GetIntTrimmedPercentageOfValue(mb.AccumulatedFeesInEpoch, leaderPercentage) + } else { + expectedLeaderFees = core.GetApproximatePercentageOfValue(mb.AccumulatedFeesInEpoch, leaderPercentage) + } + + assert.Equal(t, expectedLeaderFees, ec.economicsDataNotified.LeaderFees(), expectedLeaderFees) + }) +} + +func TestEconomics_VerifyRewardsPerBlock_DifferentHitRates(t *testing.T) { + t.Parallel() + + t.Run("before supernova", func(t *testing.T) { + t.Parallel() + + commAddress := "protocolSustainabilityAddress" + totalSupply := big.NewInt(20000000000) // 20B + accFeesInEpoch := big.NewInt(0) + devFeesInEpoch := big.NewInt(0) + roundDur := 4 + accRewardsEnableEpoch := uint32(9999999) + args := getArguments() + + args.RewardsHandler = &mock.RewardsHandlerStub{ + MaxInflationRateCalled: func(_ uint32, _ uint32) float64 { + return 0.1 + }, + ProtocolSustainabilityAddressInEpochCalled: func(epoch uint32) string { + return commAddress + }, + ProtocolSustainabilityPercentageInEpochCalled: func(epoch uint32) float64 { + return 0.1 + }, + IsTailInflationEnabledCalled: func(epoch uint32) bool { + return epoch >= accRewardsEnableEpoch + }, + } + args.RoundTime = &mock.RoundTimeDurationHandler{ + TimeDurationCalled: func() time.Duration { + return time.Duration(roundDur) * time.Second + }, + } + newTotalSupply := big.NewInt(0).Add(totalSupply, totalSupply) + hdrPrevEpochStart := block.MetaBlock{ + Round: 0, + Nonce: 0, + Epoch: 0, + EpochStart: block.EpochStart{ Economics: block.Economics{ - TotalSupply: expectedTotalSupply, - TotalToDistribute: expectedTotalToDistribute, - TotalNewlyMinted: expectedTotalNewlyMinted, - RewardsPerBlock: adjustedRwdPerBlock, + TotalSupply: newTotalSupply, + TotalToDistribute: big.NewInt(10), + TotalNewlyMinted: big.NewInt(10), + RewardsPerBlock: big.NewInt(10), NodePrice: big.NewInt(10), - PrevEpochStartHash: hdrPrevEpochStartHash, - RewardsForProtocolSustainability: expectedProtocolSustainabilityRewards, + RewardsForProtocolSustainability: big.NewInt(10), + }, + LastFinalizedHeaders: []block.EpochStartShardData{ + {ShardID: 0, Nonce: 0}, + {ShardID: 1, Nonce: 0}, }, }, - Epoch: 1, - AccumulatedFeesInEpoch: accFeesInEpoch, - DevFeesInEpoch: devFeesInEpoch, + } + args.Store = &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + // this will be the previous epoch meta block. It has initial 0 values, so it can be considered at genesis + return &storageStubs.StorerStub{GetCalled: func(key []byte) ([]byte, error) { + hdrBytes, _ := json.Marshal(hdrPrevEpochStart) + return hdrBytes, nil + }}, nil + }, + } + args.GenesisTotalSupply = totalSupply + ec, _ := NewEndOfEpochEconomicsDataCreator(args) + + expRwdPerBlock := 84 // based on 0.1 inflation + + numBlocksInEpochSlice := []int{ + numberOfSecondsInDay / roundDur, // 100 % hit rate + (numberOfSecondsInDay / roundDur) / 2, // 50 % hit rate + (numberOfSecondsInDay / roundDur) / 4, // 25 % hit rate + (numberOfSecondsInDay / roundDur) / 8, // 12.5 % hit rate + 1, // only the metablock was committed in that epoch + 37, // random + 63, // random } - computedEconomics, err := ec.ComputeEndOfEpochEconomics(&mb) - assert.Nil(t, err) - assert.NotNil(t, computedEconomics) + hdrPrevEpochStartHash, _ := core.CalculateHash(&mock.MarshalizerMock{}, &hashingMocks.HasherMock{}, hdrPrevEpochStart) + for _, numBlocksInEpoch := range numBlocksInEpochSlice { + expectedTotalToDistribute := big.NewInt(int64(expRwdPerBlock * numBlocksInEpoch * 3)) // 2 shards + meta + expectedTotalNewlyMinted := big.NewInt(0).Sub(expectedTotalToDistribute, accFeesInEpoch) + expectedTotalSupply := big.NewInt(0).Add(newTotalSupply, expectedTotalNewlyMinted) + expectedProtocolSustainabilityRewards := big.NewInt(0).Div(expectedTotalToDistribute, big.NewInt(10)) + commRewardPerBlock := big.NewInt(0).Div(expectedProtocolSustainabilityRewards, big.NewInt(int64(numBlocksInEpoch*3))) + adjustedRwdPerBlock := big.NewInt(0).Sub(big.NewInt(int64(expRwdPerBlock)), commRewardPerBlock) - err = ec.VerifyRewardsPerBlock(&mb, expectedProtocolSustainabilityRewards, computedEconomics) - assert.Nil(t, err) + mb := block.MetaBlock{ + Round: uint64(numBlocksInEpoch), + Nonce: uint64(numBlocksInEpoch), + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + {ShardID: 0, Round: uint64(numBlocksInEpoch), Nonce: uint64(numBlocksInEpoch)}, + {ShardID: 1, Round: uint64(numBlocksInEpoch), Nonce: uint64(numBlocksInEpoch)}, + }, + Economics: block.Economics{ + TotalSupply: expectedTotalSupply, + TotalToDistribute: expectedTotalToDistribute, + TotalNewlyMinted: expectedTotalNewlyMinted, + RewardsPerBlock: adjustedRwdPerBlock, + NodePrice: big.NewInt(10), + PrevEpochStartHash: hdrPrevEpochStartHash, + RewardsForProtocolSustainability: expectedProtocolSustainabilityRewards, + }, + }, + Epoch: 1, + AccumulatedFeesInEpoch: accFeesInEpoch, + DevFeesInEpoch: devFeesInEpoch, + } - ecos, err := ec.ComputeEndOfEpochEconomics(&mb) - assert.Nil(t, err) - assert.True(t, expectedProtocolSustainabilityRewards.Cmp(ecos.RewardsForProtocolSustainability) == 0) - } + computedEconomics, err := ec.ComputeEndOfEpochEconomics(&mb) + assert.Nil(t, err) + assert.NotNil(t, computedEconomics) + + err = ec.VerifyRewardsPerBlock(&mb, expectedProtocolSustainabilityRewards, computedEconomics) + assert.Nil(t, err) + + ecos, err := ec.ComputeEndOfEpochEconomics(&mb) + assert.Nil(t, err) + assert.True(t, expectedProtocolSustainabilityRewards.Cmp(ecos.RewardsForProtocolSustainability) == 0) + } + }) + + t.Run("after supernova", func(t *testing.T) { + t.Parallel() + + commAddress := "protocolSustainabilityAddress" + totalSupply := big.NewInt(20000000000) // 20B + accFeesInEpoch := big.NewInt(0) + devFeesInEpoch := big.NewInt(0) + accRewardsEnableEpoch := uint32(9999999) + roundDur := 4000 + args := getArguments() + + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag + }, + } + + args.ChainParamsHandler = &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(epoch uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{ + RoundDuration: uint64(roundDur), + }, nil + }, + } + + args.RewardsHandler = &mock.RewardsHandlerStub{ + MaxInflationRateCalled: func(_ uint32, _ uint32) float64 { + return 0.1 + }, + ProtocolSustainabilityAddressInEpochCalled: func(epoch uint32) string { + return commAddress + }, + ProtocolSustainabilityPercentageInEpochCalled: func(epoch uint32) float64 { + return 0.1 + }, + IsTailInflationEnabledCalled: func(epoch uint32) bool { + return epoch >= accRewardsEnableEpoch + }, + } + args.RoundTime = &mock.RoundTimeDurationHandler{ + TimeDurationCalled: func() time.Duration { + return time.Duration(roundDur) * time.Second + }, + } + newTotalSupply := big.NewInt(0).Add(totalSupply, totalSupply) + hdrPrevEpochStart := block.MetaBlock{ + Round: 0, + Nonce: 0, + Epoch: 0, + EpochStart: block.EpochStart{ + Economics: block.Economics{ + TotalSupply: newTotalSupply, + TotalToDistribute: big.NewInt(10), + TotalNewlyMinted: big.NewInt(10), + RewardsPerBlock: big.NewInt(10), + NodePrice: big.NewInt(10), + RewardsForProtocolSustainability: big.NewInt(10), + }, + LastFinalizedHeaders: []block.EpochStartShardData{ + {ShardID: 0, Nonce: 0}, + {ShardID: 1, Nonce: 0}, + }, + }, + } + args.Store = &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + // this will be the previous epoch meta block. It has initial 0 values, so it can be considered at genesis + return &storageStubs.StorerStub{GetCalled: func(key []byte) ([]byte, error) { + hdrBytes, _ := json.Marshal(hdrPrevEpochStart) + return hdrBytes, nil + }}, nil + }, + } + args.GenesisTotalSupply = totalSupply + ec, _ := NewEndOfEpochEconomicsDataCreator(args) + + expRwdPerBlock := 84 // based on 0.1 inflation + + numBlocksInEpochSlice := []int{ + numberOfSecondsInDay / roundDur, // 100 % hit rate + (numberOfSecondsInDay / roundDur) / 2, // 50 % hit rate + (numberOfSecondsInDay / roundDur) / 4, // 25 % hit rate + (numberOfSecondsInDay / roundDur) / 8, // 12.5 % hit rate + 1, // only the metablock was committed in that epoch + 37, // random + 63, // random + } + + hdrPrevEpochStartHash, _ := core.CalculateHash(&mock.MarshalizerMock{}, &hashingMocks.HasherMock{}, hdrPrevEpochStart) + for _, numBlocksInEpoch := range numBlocksInEpochSlice { + expectedTotalToDistribute := big.NewInt(int64(expRwdPerBlock * numBlocksInEpoch * 3)) // 2 shards + meta + expectedTotalNewlyMinted := big.NewInt(0).Sub(expectedTotalToDistribute, accFeesInEpoch) + expectedTotalSupply := big.NewInt(0).Add(newTotalSupply, expectedTotalNewlyMinted) + expectedProtocolSustainabilityRewards := big.NewInt(0).Div(expectedTotalToDistribute, big.NewInt(10)) + commRewardPerBlock := big.NewInt(0).Div(expectedProtocolSustainabilityRewards, big.NewInt(int64(numBlocksInEpoch*3))) + adjustedRwdPerBlock := big.NewInt(0).Sub(big.NewInt(int64(expRwdPerBlock)), commRewardPerBlock) + + mb := block.MetaBlock{ + Round: uint64(numBlocksInEpoch), + Nonce: uint64(numBlocksInEpoch), + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + {ShardID: 0, Round: uint64(numBlocksInEpoch), Nonce: uint64(numBlocksInEpoch)}, + {ShardID: 1, Round: uint64(numBlocksInEpoch), Nonce: uint64(numBlocksInEpoch)}, + }, + Economics: block.Economics{ + TotalSupply: expectedTotalSupply, + TotalToDistribute: expectedTotalToDistribute, + TotalNewlyMinted: expectedTotalNewlyMinted, + RewardsPerBlock: adjustedRwdPerBlock, + NodePrice: big.NewInt(10), + PrevEpochStartHash: hdrPrevEpochStartHash, + RewardsForProtocolSustainability: expectedProtocolSustainabilityRewards, + }, + }, + Epoch: 1, + AccumulatedFeesInEpoch: accFeesInEpoch, + DevFeesInEpoch: devFeesInEpoch, + } + + computedEconomics, err := ec.ComputeEndOfEpochEconomics(&mb) + assert.Nil(t, err) + assert.NotNil(t, computedEconomics) + + err = ec.VerifyRewardsPerBlock(&mb, expectedProtocolSustainabilityRewards, computedEconomics) + assert.Nil(t, err) + + ecos, err := ec.ComputeEndOfEpochEconomics(&mb) + assert.Nil(t, err) + assert.True(t, expectedProtocolSustainabilityRewards.Cmp(ecos.RewardsForProtocolSustainability) == 0) + } + }) } func TestEconomics_VerifyRewardsPerBlock_DifferentFees(t *testing.T) { @@ -548,6 +1312,15 @@ func TestEconomics_VerifyRewardsPerBlock_DifferentFees(t *testing.T) { return time.Duration(roundDur) * time.Second }, } + + args.ChainParamsHandler = &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(epoch uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{ + RoundDuration: 6000, + }, nil + }, + } + newTotalSupply := big.NewInt(0).Add(totalSupply, big.NewInt(0)) hdrPrevEpochStart := block.MetaBlock{ Round: 0, @@ -765,6 +1538,14 @@ func TestEconomics_VerifyRewardsPerBlock_MoreFeesThanInflation(t *testing.T) { return time.Duration(roundDur) * time.Second }, } + args.ChainParamsHandler = &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(epoch uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{ + RoundDuration: 6000, + }, nil + }, + } + newTotalSupply := big.NewInt(0).Add(totalSupply, big.NewInt(0)) hdrPrevEpochStart := block.MetaBlock{ Round: 0, @@ -1129,6 +1910,25 @@ type testInput struct { devFeesInEpoch *big.Int } +func computeRewardsPerBlockAfterSupernova( + totalSupply *big.Int, nbShards, roundDuration, epochDuration uint64, inflationRate float64, stakingV2 bool, +) *big.Int { + roundsPerEpoch := epochDuration / roundDuration + inflationPerDay := inflationRate / numberOfDaysInYear + roundsPerDay := numberOfMillisecondsInDay / roundDuration + maxBlocksInADay := roundsPerDay * (nbShards + 1) + maxBlocksPerEpoch := roundsPerEpoch * (nbShards + 1) + + inflationPerEpoch := inflationPerDay * (float64(maxBlocksPerEpoch / maxBlocksInADay)) + + rewardsPerBlock := big.NewInt(0).Div(totalSupply, big.NewInt(0).SetUint64(maxBlocksPerEpoch)) + if stakingV2 { + return core.GetIntTrimmedPercentageOfValue(rewardsPerBlock, inflationPerEpoch) + } else { + return core.GetApproximatePercentageOfValue(rewardsPerBlock, inflationPerEpoch) + } +} + func computeRewardsPerBlock( totalSupply *big.Int, nbShards, roundDuration, epochDuration uint64, inflationRate float64, stakingV2 bool, ) *big.Int { @@ -1148,63 +1948,146 @@ func computeRewardsPerBlock( } } -func TestComputeEndOfEpochEconomicsV2(t *testing.T) { +func TestComputeEndOfEpochEconomicsV2AndV3(t *testing.T) { t.Parallel() - totalSupply, _ := big.NewInt(0).SetString("20000000000000000000000000", 10) // 20 Million EGLD - nodePrice, _ := big.NewInt(0).SetString("1000000000000000000000", 10) // 1000 EGLD - roundDuration := 4 + t.Run("before supernova", func(t *testing.T) { + t.Parallel() + + totalSupply, _ := big.NewInt(0).SetString("20000000000000000000000000", 10) // 20 Million EGLD + nodePrice, _ := big.NewInt(0).SetString("1000000000000000000000", 10) // 1000 EGLD + roundDuration := 4 + + stakingV2EnableEpoch := uint32(0) + args := createArgsForComputeEndOfEpochEconomics(roundDuration, totalSupply, nodePrice, stakingV2EnableEpoch) + ec, _ := NewEndOfEpochEconomicsDataCreator(args) + + epochDuration := numberOfSecondsInDay + roundsPerEpoch := uint64(epochDuration / roundDuration) + + testInputs := []testInput{ + {blockPerEpochOneShard: roundsPerEpoch, accumulatedFeesInEpoch: intToEgld(1000), devFeesInEpoch: intToEgld(300)}, + {blockPerEpochOneShard: roundsPerEpoch / 2, accumulatedFeesInEpoch: intToEgld(1000), devFeesInEpoch: intToEgld(300)}, + {blockPerEpochOneShard: roundsPerEpoch / 4, accumulatedFeesInEpoch: intToEgld(1000), devFeesInEpoch: intToEgld(300)}, + {blockPerEpochOneShard: roundsPerEpoch / 8, accumulatedFeesInEpoch: intToEgld(1000), devFeesInEpoch: intToEgld(300)}, + {blockPerEpochOneShard: roundsPerEpoch / 16, accumulatedFeesInEpoch: intToEgld(1000), devFeesInEpoch: intToEgld(300)}, + {blockPerEpochOneShard: roundsPerEpoch / 32, accumulatedFeesInEpoch: intToEgld(1000), devFeesInEpoch: intToEgld(300)}, + {blockPerEpochOneShard: roundsPerEpoch / 64, accumulatedFeesInEpoch: intToEgld(10000000), devFeesInEpoch: intToEgld(100000)}, + {blockPerEpochOneShard: roundsPerEpoch, accumulatedFeesInEpoch: intToEgld(10000000), devFeesInEpoch: intToEgld(300000)}, + } - stakingV2EnableEpoch := uint32(0) - args := createArgsForComputeEndOfEpochEconomics(roundDuration, totalSupply, nodePrice, stakingV2EnableEpoch) - ec, _ := NewEndOfEpochEconomicsDataCreator(args) + var rewardsPerBlock *big.Int + metaEpoch := uint32(1) + isStakingV2 := metaEpoch > stakingV2EnableEpoch + rewardsPerBlock = computeRewardsPerBlock( + totalSupply, + uint64(args.ShardCoordinator.NumberOfShards()), + uint64(roundDuration), + uint64(epochDuration), + 0.1, + isStakingV2, + ) + + for _, input := range testInputs { + meta := &block.MetaBlock{ + AccumulatedFeesInEpoch: input.accumulatedFeesInEpoch, + DevFeesInEpoch: input.devFeesInEpoch, + Epoch: metaEpoch, + Round: roundsPerEpoch, + Nonce: input.blockPerEpochOneShard, + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + {ShardID: 0, Round: roundsPerEpoch, Nonce: input.blockPerEpochOneShard}, + {ShardID: 1, Round: roundsPerEpoch, Nonce: input.blockPerEpochOneShard}, + }, + }, + } - epochDuration := numberOfSecondsInDay - roundsPerEpoch := uint64(epochDuration / roundDuration) - - testInputs := []testInput{ - {blockPerEpochOneShard: roundsPerEpoch, accumulatedFeesInEpoch: intToEgld(1000), devFeesInEpoch: intToEgld(300)}, - {blockPerEpochOneShard: roundsPerEpoch / 2, accumulatedFeesInEpoch: intToEgld(1000), devFeesInEpoch: intToEgld(300)}, - {blockPerEpochOneShard: roundsPerEpoch / 4, accumulatedFeesInEpoch: intToEgld(1000), devFeesInEpoch: intToEgld(300)}, - {blockPerEpochOneShard: roundsPerEpoch / 8, accumulatedFeesInEpoch: intToEgld(1000), devFeesInEpoch: intToEgld(300)}, - {blockPerEpochOneShard: roundsPerEpoch / 16, accumulatedFeesInEpoch: intToEgld(1000), devFeesInEpoch: intToEgld(300)}, - {blockPerEpochOneShard: roundsPerEpoch / 32, accumulatedFeesInEpoch: intToEgld(1000), devFeesInEpoch: intToEgld(300)}, - {blockPerEpochOneShard: roundsPerEpoch / 64, accumulatedFeesInEpoch: intToEgld(10000000), devFeesInEpoch: intToEgld(100000)}, - {blockPerEpochOneShard: roundsPerEpoch, accumulatedFeesInEpoch: intToEgld(10000000), devFeesInEpoch: intToEgld(300000)}, - } - - var rewardsPerBlock *big.Int - metaEpoch := uint32(1) - isStakingV2 := metaEpoch > stakingV2EnableEpoch - rewardsPerBlock = computeRewardsPerBlock( - totalSupply, - uint64(args.ShardCoordinator.NumberOfShards()), - uint64(roundDuration), - uint64(epochDuration), - 0.1, - isStakingV2, - ) + economicsBlock, err := ec.ComputeEndOfEpochEconomics(meta) + assert.Nil(t, err) + verifyEconomicsBlock(t, economicsBlock, input, rewardsPerBlock, nodePrice, totalSupply, roundsPerEpoch, args.RewardsHandler, isStakingV2) + } + }) - for _, input := range testInputs { - meta := &block.MetaBlock{ - AccumulatedFeesInEpoch: input.accumulatedFeesInEpoch, - DevFeesInEpoch: input.devFeesInEpoch, - Epoch: metaEpoch, - Round: roundsPerEpoch, - Nonce: input.blockPerEpochOneShard, - EpochStart: block.EpochStart{ + t.Run("after supernova", func(t *testing.T) { + t.Parallel() + + totalSupply, _ := big.NewInt(0).SetString("20000000000000000000000000", 10) // 20 Million EGLD + nodePrice, _ := big.NewInt(0).SetString("1000000000000000000000", 10) // 1000 EGLD + roundDuration := 4000 + + stakingV2EnableEpoch := uint32(0) + args := createArgsForComputeEndOfEpochEconomics(roundDuration, totalSupply, nodePrice, stakingV2EnableEpoch) + + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag + }, + } + + args.ChainParamsHandler = &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(epoch uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{ + RoundDuration: uint64(roundDuration), + }, nil + }, + } + + ec, _ := NewEndOfEpochEconomicsDataCreator(args) + + epochDuration := numberOfMillisecondsInDay + roundsPerEpoch := uint64(epochDuration / roundDuration) + + testInputs := []testInput{ + {blockPerEpochOneShard: roundsPerEpoch, accumulatedFeesInEpoch: intToEgld(1000), devFeesInEpoch: intToEgld(300)}, + {blockPerEpochOneShard: roundsPerEpoch / 2, accumulatedFeesInEpoch: intToEgld(1000), devFeesInEpoch: intToEgld(300)}, + {blockPerEpochOneShard: roundsPerEpoch / 4, accumulatedFeesInEpoch: intToEgld(1000), devFeesInEpoch: intToEgld(300)}, + {blockPerEpochOneShard: roundsPerEpoch / 8, accumulatedFeesInEpoch: intToEgld(1000), devFeesInEpoch: intToEgld(300)}, + {blockPerEpochOneShard: roundsPerEpoch / 16, accumulatedFeesInEpoch: intToEgld(1000), devFeesInEpoch: intToEgld(300)}, + {blockPerEpochOneShard: roundsPerEpoch / 32, accumulatedFeesInEpoch: intToEgld(1000), devFeesInEpoch: intToEgld(300)}, + {blockPerEpochOneShard: roundsPerEpoch / 64, accumulatedFeesInEpoch: intToEgld(10000000), devFeesInEpoch: intToEgld(100000)}, + {blockPerEpochOneShard: roundsPerEpoch, accumulatedFeesInEpoch: intToEgld(10000000), devFeesInEpoch: intToEgld(300000)}, + } + + var rewardsPerBlock *big.Int + metaEpoch := uint32(1) + 1 + isStakingV2 := metaEpoch > stakingV2EnableEpoch + rewardsPerBlock = computeRewardsPerBlockAfterSupernova( + totalSupply, + uint64(args.ShardCoordinator.NumberOfShards()), + uint64(roundDuration), + uint64(epochDuration), + 0.1, + isStakingV2, + ) + + for _, input := range testInputs { + meta := &block.MetaBlockV3{ + Epoch: metaEpoch, + Round: roundsPerEpoch, + Nonce: input.blockPerEpochOneShard, + LastExecutionResult: &block.MetaExecutionResultInfo{}, + EpochChangeProposed: true, + } + execRes := &block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{ + AccumulatedFeesInEpoch: input.accumulatedFeesInEpoch, + DevFeesInEpoch: input.devFeesInEpoch, + }, + } + + blockEpochStart := &block.EpochStart{ LastFinalizedHeaders: []block.EpochStartShardData{ {ShardID: 0, Round: roundsPerEpoch, Nonce: input.blockPerEpochOneShard}, {ShardID: 1, Round: roundsPerEpoch, Nonce: input.blockPerEpochOneShard}, }, - }, - } - - economicsBlock, err := ec.ComputeEndOfEpochEconomics(meta) - assert.Nil(t, err) + } + economicsBlock, err := ec.ComputeEndOfEpochEconomicsV3(meta, execRes, blockEpochStart) + assert.Nil(t, err) - verifyEconomicsBlock(t, economicsBlock, input, rewardsPerBlock, nodePrice, totalSupply, roundsPerEpoch, args.RewardsHandler, isStakingV2) - } + verifyEconomicsBlock(t, economicsBlock, input, rewardsPerBlock, nodePrice, totalSupply, roundsPerEpoch, args.RewardsHandler, isStakingV2) + } + }) } func TestEconomics_checkEconomicsInvariantsV1ReturnsOK(t *testing.T) { @@ -1280,7 +2163,7 @@ func TestEconomics_checkEconomicsInvariantsAccumulatedFeesOutOfRange(t *testing. ec, _ := NewEndOfEpochEconomicsDataCreator(args) maxBlocksInEpoch := uint64(300) inflationRate := 0.1 - inflationPerEpoch := ec.computeInflationForEpoch(inflationRate, maxBlocksInEpoch) + inflationPerEpoch := ec.computeInflationForEpoch(inflationRate, maxBlocksInEpoch, 2) computedEconomics, metaBlock := defaultComputedEconomicsAndMetaBlock(totalSupply, inflationPerEpoch) metaBlock.AccumulatedFeesInEpoch = big.NewInt(-1) @@ -1324,7 +2207,7 @@ func TestEconomics_checkEconomicsInvariantsRewardsForProtocolSustainabilityOutOf ec, _ := NewEndOfEpochEconomicsDataCreator(args) maxBlocksInEpoch := uint64(300) inflationRate := 0.1 - inflationPerEpoch := ec.computeInflationForEpoch(inflationRate, maxBlocksInEpoch) + inflationPerEpoch := ec.computeInflationForEpoch(inflationRate, maxBlocksInEpoch, 2) computedEconomics, metaBlock := defaultComputedEconomicsAndMetaBlock(totalSupply, inflationPerEpoch) computedEconomics.RewardsForProtocolSustainability = big.NewInt(-1) @@ -1368,7 +2251,7 @@ func TestEconomics_checkEconomicsInvariantsMintedOutOfRange(t *testing.T) { ec, _ := NewEndOfEpochEconomicsDataCreator(args) maxBlocksInEpoch := uint64(300) inflationRate := 0.1 - inflationPerEpoch := ec.computeInflationForEpoch(inflationRate, maxBlocksInEpoch) + inflationPerEpoch := ec.computeInflationForEpoch(inflationRate, maxBlocksInEpoch, 2) computedEconomics, metaBlock := defaultComputedEconomicsAndMetaBlock(totalSupply, inflationPerEpoch) computedEconomics.TotalNewlyMinted = big.NewInt(-1) @@ -1412,7 +2295,7 @@ func TestEconomics_checkEconomicsInvariantsTotalToDistributeOutOfRange(t *testin ec, _ := NewEndOfEpochEconomicsDataCreator(args) maxBlocksInEpoch := uint64(300) inflationRate := 0.1 - inflationPerEpoch := ec.computeInflationForEpoch(inflationRate, maxBlocksInEpoch) + inflationPerEpoch := ec.computeInflationForEpoch(inflationRate, maxBlocksInEpoch, 2) computedEconomics, metaBlock := defaultComputedEconomicsAndMetaBlock(totalSupply, inflationPerEpoch) computedEconomics.TotalToDistribute = big.NewInt(-1) @@ -1456,7 +2339,7 @@ func TestEconomics_checkEconomicsInvariantsSumRewardsOutOfRange(t *testing.T) { ec, _ := NewEndOfEpochEconomicsDataCreator(args) maxBlocksInEpoch := uint64(300) inflationRate := 0.1 - inflationPerEpoch := ec.computeInflationForEpoch(inflationRate, maxBlocksInEpoch) + inflationPerEpoch := ec.computeInflationForEpoch(inflationRate, maxBlocksInEpoch, 2) computedEconomics, metaBlock := defaultComputedEconomicsAndMetaBlock(totalSupply, inflationPerEpoch) computedEconomics.RewardsPerBlock = big.NewInt(-1) @@ -1500,7 +2383,7 @@ func TestEconomics_checkEconomicsInvariantsV2ReturnsOK(t *testing.T) { ec, _ := NewEndOfEpochEconomicsDataCreator(args) maxBlocksInEpoch := uint64(300) inflationRate := 0.1 - inflationPerEpoch := ec.computeInflationForEpoch(inflationRate, maxBlocksInEpoch) + inflationPerEpoch := ec.computeInflationForEpoch(inflationRate, maxBlocksInEpoch, 2) computedEconomics, metaBlock := defaultComputedEconomicsAndMetaBlock(totalSupply, inflationPerEpoch) err := ec.checkEconomicsInvariants( @@ -1527,7 +2410,7 @@ func TestEconomics_checkEconomicsInvariantsV2ExtraBlocksNotarized(t *testing.T) maxBlocksInEpoch := uint64(300) inflationRate := 0.1 extraBlocksNotarized := uint64(100) - actualInflationPerEpochWithCarry := ec.computeInflationForEpoch(inflationRate, maxBlocksInEpoch+extraBlocksNotarized) + actualInflationPerEpochWithCarry := ec.computeInflationForEpoch(inflationRate, maxBlocksInEpoch+extraBlocksNotarized, 2) computedEconomics, metaBlock := defaultComputedEconomicsAndMetaBlock(totalSupply, actualInflationPerEpochWithCarry) err := ec.checkEconomicsInvariants( @@ -1541,6 +2424,171 @@ func TestEconomics_checkEconomicsInvariantsV2ExtraBlocksNotarized(t *testing.T) require.Nil(t, err) } +func TestEconomics_ComputeEndOfEpochEconomicsV3NilInputShouldError(t *testing.T) { + t.Parallel() + + args := getArguments() + ec, _ := NewEndOfEpochEconomicsDataCreator(args) + res, err := ec.ComputeEndOfEpochEconomicsV3(nil, createMetaExecRes(), &block.EpochStart{}) + require.Nil(t, res) + require.Equal(t, process.ErrNilMetaBlockHeader, err) +} + +func TestEconomics_createEconomicsV3Args(t *testing.T) { + t.Parallel() + + args := getArguments() + ec, _ := NewEndOfEpochEconomicsDataCreator(args) + + metaExecRes := createMetaExecRes() + metaBlock := &block.MetaBlockV3{ + Epoch: 2, + Nonce: 4, + PrevHash: metaExecRes.GetHeaderHash(), + EpochChangeProposed: true, + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("some hash"), + HeaderEpoch: 2, + HeaderNonce: 3, + RootHash: []byte("some root hash"), + }, + ValidatorStatsRootHash: []byte("validator stats root hash"), + AccumulatedFeesInEpoch: big.NewInt(1000), + DevFeesInEpoch: big.NewInt(300), + }, + }, + } + + t.Run("nil meta block", func(t *testing.T) { + res, err := ec.createEconomicsV3Args(nil, metaExecRes, &block.EpochStart{}) + require.Nil(t, res) + require.Equal(t, process.ErrNilMetaBlockHeader, err) + }) + t.Run("nil exec results", func(t *testing.T) { + res, err := ec.createEconomicsV3Args(metaBlock, nil, &block.EpochStart{}) + require.Nil(t, res) + require.Equal(t, process.ErrNilExecutionResultHandler, err) + }) + t.Run("nil nonces per shard map", func(t *testing.T) { + res, err := ec.createEconomicsV3Args(metaBlock, metaExecRes, nil) + require.Nil(t, res) + require.Equal(t, process.ErrNilEpochStartData, err) + }) + t.Run("nil acc fees in epoch", func(t *testing.T) { + metaExecResInvalid := createMetaExecRes() + metaExecResInvalid.ExecutionResult.AccumulatedFeesInEpoch = nil + res, err := ec.createEconomicsV3Args(metaBlock, metaExecResInvalid, &block.EpochStart{}) + require.Nil(t, res) + require.Equal(t, epochStart.ErrNilTotalAccumulatedFeesInEpoch, err) + }) + t.Run("nil dev fees in epoch", func(t *testing.T) { + metaExecResInvalid := createMetaExecRes() + metaExecResInvalid.ExecutionResult.DevFeesInEpoch = nil + res, err := ec.createEconomicsV3Args(metaBlock, metaExecResInvalid, &block.EpochStart{}) + require.Nil(t, res) + require.Equal(t, epochStart.ErrNilTotalDevFeesInEpoch, err) + }) + t.Run("not meta v3 header", func(t *testing.T) { + res, err := ec.createEconomicsV3Args(&block.MetaBlock{}, metaExecRes, &block.EpochStart{}) + require.Nil(t, res) + require.ErrorIs(t, err, data.ErrInvalidHeaderType) + }) + t.Run("not epoch change proposed", func(t *testing.T) { + metaBlockCopy := *metaBlock + metaBlockCopy.EpochChangeProposed = false + res, err := ec.createEconomicsV3Args(&metaBlockCopy, metaExecRes, &block.EpochStart{}) + require.Nil(t, res) + require.ErrorIs(t, err, epochStart.ErrNotEpochStartBlock) + }) + t.Run("exec result header hash does not match prev meta block header hash", func(t *testing.T) { + metaBlockCopy := *metaBlock + metaBlockCopy.PrevHash = []byte("another hash") + res, err := ec.createEconomicsV3Args(&metaBlockCopy, metaExecRes, &block.EpochStart{}) + require.Nil(t, res) + require.ErrorIs(t, err, errHashMismatch) + }) + t.Run("cannot get prev epoch start", func(t *testing.T) { + arguments := getArguments() + + localErr := errors.New("get storer failed") + arguments.Store = &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return nil, localErr + }, + } + economicsCreator, _ := NewEndOfEpochEconomicsDataCreator(arguments) + res, err := economicsCreator.createEconomicsV3Args(metaBlock, metaExecRes, &block.EpochStart{}) + require.Nil(t, res) + require.Equal(t, localErr, err) + }) + t.Run("should work", func(t *testing.T) { + arguments := getArguments() + + arguments.ShardCoordinator = mock.NewMultiShardsCoordinatorMock(3) + + metaBlockCopy := *metaBlock + metaBlockCopy.EpochStart.LastFinalizedHeaders = []block.EpochStartShardData{ + { + Nonce: 3, + ShardID: 0, + HeaderHash: []byte("hash3"), + }, + } + + storer := &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + return arguments.Marshalizer.Marshal(metaBlockCopy) + }, + } + arguments.Store = &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return storer, nil + }, + } + epochStartDta := &block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + { + Nonce: 1, + ShardID: 1, + HeaderHash: []byte("hash1"), + }, + { + Nonce: 2, + ShardID: 2, + HeaderHash: []byte("hash2"), + }, + }, + } + + economicsCreator, _ := NewEndOfEpochEconomicsDataCreator(arguments) + res, err := economicsCreator.createEconomicsV3Args(&metaBlockCopy, metaExecRes, epochStartDta) + require.Nil(t, err) + require.Equal(t, &argsComputeEconomics{ + computationData: economicsComputationData{ + newEpoch: metaBlockCopy.Epoch + 1, + round: metaBlockCopy.Round, + accumulatedFeesInEpoch: metaExecRes.GetAccumulatedFeesInEpoch(), + devFeesInEpoch: metaExecRes.GetDevFeesInEpoch(), + }, + prevEpochStart: &metaBlockCopy, + lastNoncesPerShardPrevEpoch: map[uint32]uint64{ + 0: 3, + 1: 0, + 2: 0, + core.MetachainShardId: 3, // taken from last execution result + }, + lastNoncesPerShardCurrEpoch: map[uint32]uint64{ + 0: 0, + 1: 1, + 2: 2, + core.MetachainShardId: 4, + }, + }, res) + }) +} + func defaultComputedEconomicsAndMetaBlock(totalSupply *big.Int, inflationPerEpoch float64) (*block.Economics, *block.MetaBlock) { numRoundsEpoch := uint64(100) numBlocksShard := uint64(80) @@ -1877,12 +2925,12 @@ func TestEconomics_ComputeEndOfEpochEconomicsWithPrevEpochTotalSupply(t *testing require.NotNil(t, res) // re-calculate expected values for verification - inflationRate := ec.computeInflationRate(mb.GetRound(), mb.GetEpoch()) + inflationRate := ec.computeInflationRate(&mb) roundsPassedInEpoch := mb.GetRound() - mbPrevStartEpoch.GetRound() maxBlocksInEpoch := core.MaxUint64(1, roundsPassedInEpoch*uint64(args.ShardCoordinator.NumberOfShards()+1)) noncesPerShardPrevEpoch, _, _ := ec.startNoncePerShardFromEpochStart(mb.Epoch - 1) - noncesPerShardCurrEpoch, _ := ec.startNoncePerShardFromLastCrossNotarized(mb.GetNonce(), mb.EpochStart) + noncesPerShardCurrEpoch := ec.startNoncePerShardFromLastCrossNotarized(mb.GetNonce(), &mb.EpochStart) totalNumBlocksInEpoch := ec.computeNumOfTotalCreatedBlocks(noncesPerShardPrevEpoch, noncesPerShardCurrEpoch) rwdPerBlock := ec.computeRewardsPerBlock( @@ -1949,12 +2997,12 @@ func TestEconomics_TotalSupplyCalculation(t *testing.T) { require.NotNil(t, res) // re-calculate expected values for verification - inflationRate := ec.computeInflationRate(mb.GetRound(), mb.GetEpoch()) + inflationRate := ec.computeInflationRate(&mb) roundsPassedInEpoch := mb.GetRound() - mbPrevStartEpoch.GetRound() maxBlocksInEpoch := core.MaxUint64(1, roundsPassedInEpoch*uint64(args.ShardCoordinator.NumberOfShards()+1)) noncesPerShardPrevEpoch, _, _ := ec.startNoncePerShardFromEpochStart(mb.Epoch - 1) - noncesPerShardCurrEpoch, _ := ec.startNoncePerShardFromLastCrossNotarized(mb.GetNonce(), mb.EpochStart) + noncesPerShardCurrEpoch := ec.startNoncePerShardFromLastCrossNotarized(mb.GetNonce(), &mb.EpochStart) totalNumBlocksInEpoch := ec.computeNumOfTotalCreatedBlocks(noncesPerShardPrevEpoch, noncesPerShardCurrEpoch) rwdPerBlock := ec.computeRewardsPerBlock( @@ -2004,6 +3052,27 @@ func getArguments() ArgsNewEpochEconomics { RewardsHandler: &mock.RewardsHandlerStub{}, RoundTime: &mock.RoundTimeDurationHandler{}, GenesisTotalSupply: genesisSupply, + GenesisTimestamp: 0, EconomicsDataNotified: NewEpochEconomicsStatistics(), + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + ChainParamsHandler: &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(epoch uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{ + RoundDuration: 4000, + }, nil + }, + }, + } +} + +func createMetaExecRes() *block.MetaExecutionResult { + return &block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hdrHash"), + }, + DevFeesInEpoch: big.NewInt(10), + AccumulatedFeesInEpoch: big.NewInt(12), + }, } } diff --git a/epochStart/metachain/epochStartData.go b/epochStart/metachain/epochStartData.go index 1a67b3a3692..dfff4361e7a 100644 --- a/epochStart/metachain/epochStartData.go +++ b/epochStart/metachain/epochStartData.go @@ -11,6 +11,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/epochStart" @@ -47,6 +48,11 @@ type ArgsNewEpochStartData struct { EnableEpochsHandler common.EnableEpochsHandler } +type rootHashData struct { + rootHash []byte + scheduledRootHash []byte +} + // NewEpochStartData creates a new epoch start creator func NewEpochStartData(args ArgsNewEpochStartData) (*epochStartData, error) { if check.IfNil(args.Marshalizer) { @@ -97,7 +103,7 @@ func NewEpochStartData(args ArgsNewEpochStartData) (*epochStartData, error) { } // VerifyEpochStartDataForMetablock verifies if epoch start data given by leader is the same as the one should be created -func (e *epochStartData) VerifyEpochStartDataForMetablock(metaBlock *block.MetaBlock) error { +func (e *epochStartData) VerifyEpochStartDataForMetablock(metaBlock data.MetaHeaderHandler) error { if !metaBlock.IsStartOfEpochBlock() { return nil } @@ -107,9 +113,29 @@ func (e *epochStartData) VerifyEpochStartDataForMetablock(metaBlock *block.MetaB return err } - epochStartDataWithoutEconomics := metaBlock.EpochStart - epochStartDataWithoutEconomics.Economics = block.Economics{} - receivedEpochStartHash, err := core.CalculateHash(e.marshalizer, e.hasher, &epochStartDataWithoutEconomics) + return e.verifyEpochStartDataForMetablock(metaBlock, startData) +} + +func (e *epochStartData) verifyEpochStartDataForMetablock( + metaBlock data.MetaHeaderHandler, + startData *block.EpochStart, +) error { + if !metaBlock.IsStartOfEpochBlock() { + return nil + } + + blockEpochStartData := metaBlock.GetEpochStartHandler() + epochStartDataWithoutEconomics := &block.EpochStart{} + err := epochStartDataWithoutEconomics.SetLastFinalizedHeaders(blockEpochStartData.GetLastFinalizedHeaderHandlers()) + if err != nil { + return err + } + esd := &block.Economics{} + err = epochStartDataWithoutEconomics.SetEconomics(esd) + if err != nil { + return err + } + receivedEpochStartHash, err := core.CalculateHash(e.marshalizer, e.hasher, epochStartDataWithoutEconomics) if err != nil { return err } @@ -120,7 +146,7 @@ func (e *epochStartData) VerifyEpochStartDataForMetablock(metaBlock *block.MetaB } if !bytes.Equal(receivedEpochStartHash, createdEpochStartHash) { - displayEpochStartData("received", receivedEpochStartHash, &metaBlock.EpochStart) + displayEpochStartData("received", receivedEpochStartHash, metaBlock.GetEpochStartHandler()) displayEpochStartData("created", createdEpochStartHash, startData) return process.ErrEpochStartDataDoesNotMatch @@ -129,18 +155,18 @@ func (e *epochStartData) VerifyEpochStartDataForMetablock(metaBlock *block.MetaB return nil } -func displayEpochStartData(mode string, hash []byte, startData *block.EpochStart) { +func displayEpochStartData(mode string, hash []byte, startData data.EpochStartHandler) { log.Warn(mode+" epoch start data", "hash", hash, "startData", spew.Sdump(startData)) - for _, shardData := range startData.LastFinalizedHeaders { + for _, shardData := range startData.GetLastFinalizedHeaderHandlers() { log.Warn("epoch start shard data", - "shardID", shardData.ShardID, - "num pending miniblocks", len(shardData.PendingMiniBlockHeaders), - "first pending meta", shardData.FirstPendingMetaBlock, - "last finished meta", shardData.LastFinishedMetaBlock, - "rootHash", shardData.RootHash, - "headerHash", shardData.HeaderHash) + "shardID", shardData.GetShardID(), + "num pending miniblocks", len(shardData.GetPendingMiniBlockHeaderHandlers()), + "first pending meta", shardData.GetFirstPendingMetaBlock(), + "last finished meta", shardData.GetLastFinishedMetaBlock(), + "rootHash", shardData.GetRootHash(), + "headerHash", shardData.GetHeaderHash()) } } @@ -150,6 +176,28 @@ func (e *epochStartData) CreateEpochStartData() (*block.EpochStart, error) { return &block.EpochStart{}, nil } + return e.createEpochStartData() +} + +// CreateEpochStartShardDataMetablockV3 creates epoch start data for metablock v3 if it is needed +func (e *epochStartData) CreateEpochStartShardDataMetablockV3(metablock data.MetaHeaderHandler) ([]block.EpochStartShardData, error) { + log.Debug("CreateEpochStartShardDataMetablockV3", + "metablock epoch", metablock.GetEpoch(), + "for epoch", metablock.GetEpoch()+1, + "isEpochChangeProposed", metablock.IsEpochChangeProposed(), + "trigger epoch", e.epochStartTrigger.Epoch()) + + if !metablock.IsEpochChangeProposed() { + return nil, nil + } + esd, err := e.createEpochStartData() + if err != nil { + return nil, err + } + return esd.LastFinalizedHeaders, nil +} + +func (e *epochStartData) createEpochStartData() (*block.EpochStart, error) { startData, allShardHdrList, err := e.createShardStartDataAndLastProcessedHeaders() if err != nil { return nil, err @@ -198,15 +246,18 @@ func (e *epochStartData) createShardStartDataAndLastProcessedHeaders() (*block.E Round: lastCrossNotarizedHeaderForShard.GetRound(), Nonce: lastCrossNotarizedHeaderForShard.GetNonce(), HeaderHash: hdrHash, - RootHash: lastCrossNotarizedHeaderForShard.GetRootHash(), FirstPendingMetaBlock: firstPendingMetaHash, LastFinishedMetaBlock: lastFinalizedMetaHash, } - additionalData := lastCrossNotarizedHeaderForShard.GetAdditionalData() - if additionalData != nil { - finalHeader.ScheduledRootHash = additionalData.GetScheduledRootHash() + + rhd, err := getRootHashForLastCrossNotarizedHeader(lastCrossNotarizedHeaderForShard) + if err != nil { + return nil, nil, err } + finalHeader.RootHash = rhd.rootHash + finalHeader.ScheduledRootHash = rhd.scheduledRootHash + startData.LastFinalizedHeaders = append(startData.LastFinalizedHeaders, finalHeader) allShardHdrList[shardID] = currShardHdrList } @@ -214,6 +265,29 @@ func (e *epochStartData) createShardStartDataAndLastProcessedHeaders() (*block.E return startData, allShardHdrList, nil } +func getRootHashForLastCrossNotarizedHeader(lastCrossNotarizedHeaderForShard data.HeaderHandler) (*rootHashData, error) { + rhd := &rootHashData{} + rhd.rootHash = lastCrossNotarizedHeaderForShard.GetRootHash() + + additionalData := lastCrossNotarizedHeaderForShard.GetAdditionalData() + if additionalData != nil { + rhd.scheduledRootHash = additionalData.GetScheduledRootHash() + } + + if !lastCrossNotarizedHeaderForShard.IsHeaderV3() { + return rhd, nil + } + + lastExecutionResultHandler := lastCrossNotarizedHeaderForShard.GetLastExecutionResultHandler() + lastExecutionOnHeader, ok := lastExecutionResultHandler.(data.LastShardExecutionResultHandler) + if !ok || check.IfNil(lastExecutionOnHeader.GetExecutionResultHandler()) { + return nil, process.ErrWrongTypeAssertion + } + + rhd.rootHash = lastExecutionOnHeader.GetExecutionResultHandler().GetRootHash() + return rhd, nil +} + func (e *epochStartData) lastFinalizedFirstPendingListHeadersForShard(shardHdr data.ShardHeaderHandler) ([]byte, []byte, []data.HeaderHandler, error) { var firstPendingMetaHash []byte var lastFinalizedMetaHash []byte @@ -302,32 +376,49 @@ func (e *epochStartData) getShardDataFromEpochStartData( return nil, nil, process.ErrNotEpochStartBlock } - for _, shardData := range previousEpochStartMeta.EpochStart.LastFinalizedHeaders { - if shardData.ShardID != shId { + for _, shardData := range previousEpochStartMeta.GetEpochStartHandler().GetLastFinalizedHeaderHandlers() { + if shardData.GetShardID() != shId { continue } - if len(lastMetaHash) == 0 || bytes.Equal(lastMetaHash, shardData.FirstPendingMetaBlock) { - return shardData.FirstPendingMetaBlock, shardData.LastFinishedMetaBlock, nil + if len(lastMetaHash) == 0 || bytes.Equal(lastMetaHash, shardData.GetFirstPendingMetaBlock()) { + return shardData.GetFirstPendingMetaBlock(), shardData.GetLastFinishedMetaBlock(), nil } - return lastMetaHash, shardData.FirstPendingMetaBlock, nil + return lastMetaHash, shardData.GetFirstPendingMetaBlock(), nil } return nil, nil, process.ErrGettingShardDataFromEpochStartData } +func (e *epochStartData) getPrevEpoch() uint32 { + prevEpoch := e.genesisEpoch + + epochStartTriggerEpoch := e.epochStartTrigger.Epoch() + + if epochStartTriggerEpoch <= e.genesisEpoch { + return prevEpoch + } + + prevEpoch = e.epochStartTrigger.Epoch() + + isAfterSupernova := epochStartTriggerEpoch > e.enableEpochsHandler.GetActivationEpoch(common.SupernovaFlag) + if !isAfterSupernova { + prevEpoch-- + } + + return prevEpoch +} + func (e *epochStartData) computePendingMiniBlockList( startData *block.EpochStart, allShardHdrList [][]data.HeaderHandler, ) ([]block.MiniBlockHeader, error) { - - prevEpoch := e.genesisEpoch - if e.epochStartTrigger.Epoch() > e.genesisEpoch { - prevEpoch = e.epochStartTrigger.Epoch() - 1 - } + prevEpoch := e.getPrevEpoch() epochStartIdentifier := core.EpochStartIdentifier(prevEpoch) + + // TODO: analyse error handling here previousEpochStartMeta, _ := process.GetMetaHeaderFromStorage([]byte(epochStartIdentifier), e.marshalizer, e.store) allPending := make([]block.MiniBlockHeader, 0) @@ -361,27 +452,38 @@ func (e *epochStartData) computePendingMiniBlockList( return allPending, nil } -func getEpochStartDataForShard(epochStartMetaHdr *block.MetaBlock, shardID uint32) ([]byte, map[string]block.MiniBlockHeader) { +func getEpochStartDataForShard(epochStartMetaHdr data.MetaHeaderHandler, shardID uint32) ([]byte, map[string]block.MiniBlockHeader) { if check.IfNil(epochStartMetaHdr) { return nil, nil } - for _, header := range epochStartMetaHdr.EpochStart.LastFinalizedHeaders { - if header.ShardID != shardID { + for _, header := range epochStartMetaHdr.GetEpochStartHandler().GetLastFinalizedHeaderHandlers() { + if header.GetShardID() != shardID { continue } - mapPendingMiniBlocks := make(map[string]block.MiniBlockHeader, len(header.PendingMiniBlockHeaders)) - for _, mbHdr := range header.PendingMiniBlockHeaders { - mapPendingMiniBlocks[string(mbHdr.Hash)] = mbHdr + mapPendingMiniBlocks := make(map[string]block.MiniBlockHeader, len(header.GetPendingMiniBlockHeaderHandlers())) + for _, mbHdrHandler := range header.GetPendingMiniBlockHeaderHandlers() { + addMBHeaderToMapIfPossible(mbHdrHandler, mapPendingMiniBlocks) } - return header.FirstPendingMetaBlock, mapPendingMiniBlocks + return header.GetFirstPendingMetaBlock(), mapPendingMiniBlocks } return nil, nil } +func addMBHeaderToMapIfPossible(mbHdrHandler data.MiniBlockHeaderHandler, destMap map[string]block.MiniBlockHeader) { + mbHdr, castOk := mbHdrHandler.(*block.MiniBlockHeader) + if !castOk { + // this should never happen + log.Error("addMBHeaderToMapIfPossible: invalid type assertion for mini block header") + return + } + + destMap[string(mbHdr.GetHash())] = *mbHdr +} + func (e *epochStartData) computeStillPending( shardID uint32, shardHdrs []data.HeaderHandler, @@ -436,8 +538,13 @@ func (e *epochStartData) computeStillPendingInShardHeader( continue } - if shardMiniBlockHeader.IsFinal() { - log.Debug("epochStartData.computeStillPendingInShardHeader: IsFinal", + notPending := shardMiniBlockHeader.IsFinal() + if shardHdr.IsHeaderV3() { + notPending = true + } + + if notPending { + log.Debug("epochStartData.computeStillPendingInShardHeader: not pending", "mb hash", shardMiniBlockHash, "shard", shardID, ) @@ -501,31 +608,38 @@ func (e *epochStartData) setIndexOfFirstAndLastTxProcessed(mbHeader *block.MiniB } } -func getAllMiniBlocksWithDst(m *block.MetaBlock, destId uint32) map[string]block.MiniBlockHeader { +func getAllMiniBlocksWithDst(m data.MetaHeaderHandler, destId uint32) map[string]block.MiniBlockHeader { hashDst := make(map[string]block.MiniBlockHeader) - for i := 0; i < len(m.ShardInfo); i++ { - if m.ShardInfo[i].ShardID == destId { + + for i := 0; i < len(m.GetShardInfoHandlers()); i++ { + if m.GetShardInfoHandlers()[i].GetShardID() == destId { continue } - for _, val := range m.ShardInfo[i].ShardMiniBlockHeaders { - if val.ReceiverShardID == destId && val.SenderShardID != destId { - hashDst[string(val.Hash)] = val + for _, val := range m.GetShardInfoHandlers()[i].GetShardMiniBlockHeaderHandlers() { + if val.GetReceiverShardID() == destId && val.GetSenderShardID() != destId { + addMBHeaderToMapIfPossible(val, hashDst) } } } - for _, val := range m.MiniBlockHeaders { - if val.ReceiverShardID == destId && val.SenderShardID != destId { - hashDst[string(val.Hash)] = val + miniBlockHeaders, _ := common.GetMiniBlockHeadersFromExecResult(m) + for _, val := range miniBlockHeaders { + if val.GetReceiverShardID() == destId && val.GetSenderShardID() != destId { + addMBHeaderToMapIfPossible(val, hashDst) } } return hashDst } -func (e *epochStartData) getMetaBlockByHash(metaHash []byte) (*block.MetaBlock, error) { - return process.GetMetaHeader(metaHash, e.dataPool.Headers(), e.marshalizer, e.store) +func (e *epochStartData) getMetaBlockByHash(metaHash []byte) (data.MetaHeaderHandler, error) { + metaHeaderHandler, err := process.GetMetaHeader(metaHash, e.dataPool.Headers(), e.marshalizer, e.store) + if err != nil { + return nil, err + } + + return metaHeaderHandler, nil } // IsInterfaceNil returns true if underlying object is nil diff --git a/epochStart/metachain/epochStartData_test.go b/epochStart/metachain/epochStartData_test.go index 35ef918d68f..50105625dba 100644 --- a/epochStart/metachain/epochStartData_test.go +++ b/epochStart/metachain/epochStartData_test.go @@ -10,6 +10,9 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" @@ -19,10 +22,10 @@ import ( dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) +var errExpected = errors.New("expected error") + func createGenesisBlocks(shardCoordinator sharding.Coordinator) map[uint32]data.HeaderHandler { genesisBlocks := make(map[uint32]data.HeaderHandler) for shardId := uint32(0); shardId < shardCoordinator.NumberOfShards(); shardId++ { @@ -200,6 +203,63 @@ func TestVerifyEpochStartDataForMetablock_NotEpochStartBlock(t *testing.T) { require.NoError(t, err) } +func TestEpochStartData_VerifyEpochStartDataForMetablock(t *testing.T) { + t.Parallel() + + t.Run("if not epoch start block, nil should be returned", func(t *testing.T) { + t.Parallel() + + arguments := createMockEpochStartCreatorArguments() + esd, _ := NewEpochStartData(arguments) + + err := esd.verifyEpochStartDataForMetablock(&block.MetaBlockV3{ + EpochStart: block.EpochStart{}, + }, nil) + require.NoError(t, err) + }) + + t.Run("if calculating the hash fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + arguments := createMockEpochStartCreatorArguments() + esd, _ := NewEpochStartData(arguments) + esd.marshalizer = &mock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + return nil, errExpected + }, + } + + err := esd.verifyEpochStartDataForMetablock(&block.MetaBlockV3{ + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + {}, {}, {}, + }, + }, + }, nil) + require.Equal(t, errExpected, err) + }) +} + +func TestEpochStartData_getRootHashForLastCrossNotarizedHeader(t *testing.T) { + t.Parallel() + + t.Run("should set the scheduled root hash in case of headerV2", func(t *testing.T) { + t.Parallel() + + rhd, err := getRootHashForLastCrossNotarizedHeader( + &block.HeaderV2{ + Header: &block.Header{ + RootHash: []byte("rootHash"), + }, + ScheduledRootHash: []byte("scheduledRootHash"), + }, + ) + require.NoError(t, err) + require.Equal(t, rhd.rootHash, []byte("rootHash")) + require.Equal(t, rhd.scheduledRootHash, []byte("scheduledRootHash")) + }) +} + func TestVerifyEpochStartDataForMetablock_DataDoesNotMatch(t *testing.T) { t.Parallel() @@ -761,3 +821,89 @@ func Test_setIndexOfFirstAndLastTxProcessedShouldSetReserved(t *testing.T) { require.NotNil(t, mbHeader.GetReserved()) } + +func Test_CreateEpochStartShardDataMetablockV3(t *testing.T) { + t.Parallel() + + t.Run("should return nil when start data is not needed", func(t *testing.T) { + t.Parallel() + + arguments := createMockEpochStartCreatorArguments() + epoch, err := NewEpochStartData(arguments) + require.Nil(t, err) + + res, err := epoch.CreateEpochStartShardDataMetablockV3(&block.MetaBlockV3{}) + require.Nil(t, res) + require.Nil(t, err) + }) + + t.Run("should error", func(t *testing.T) { + t.Parallel() + + arguments := createMockEpochStartCreatorArguments() + arguments.BlockTracker = &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return nil, nil, errExpected + }, + } + epoch, err := NewEpochStartData(arguments) + require.Nil(t, err) + + _, err = epoch.CreateEpochStartShardDataMetablockV3( + &block.MetaBlockV3{ + EpochChangeProposed: true, + }) + require.Equal(t, errExpected, err) + }) + + t.Run("should work in transition from epoch 0 to 1", func(t *testing.T) { + t.Parallel() + + lastExecutionResultRootHash := []byte("lastExecutionResultRootHash") + shardHeaderHash := []byte("shardHeaderHash") + lastHeaderShard0 := &block.HeaderV3{ + ShardID: 0, + PrevHash: []byte("hash"), + Nonce: 99, + Round: 100, + LastExecutionResult: &block.ExecutionResultInfo{ + NotarizedInRound: 100, + ExecutionResult: &block.BaseExecutionResult{ + RootHash: lastExecutionResultRootHash, + }, + }, + } + arguments := createMockEpochStartCreatorArguments() + + // there is only one shard configured in the mock coordinator + arguments.BlockTracker = &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(_ uint32) (data.HeaderHandler, []byte, error) { + return lastHeaderShard0, shardHeaderHash, nil + }, + } + epoch, err := NewEpochStartData(arguments) + require.Nil(t, err) + + startData, err := epoch.CreateEpochStartShardDataMetablockV3(&block.MetaBlockV3{ + Nonce: 101, + Round: 101, + EpochChangeProposed: true, + }) + + expectedShardData := []block.EpochStartShardData{ + { + ShardID: 0, + Epoch: 0, + Round: 100, + Nonce: 99, + HeaderHash: shardHeaderHash, + RootHash: lastExecutionResultRootHash, + ScheduledRootHash: nil, + LastFinishedMetaBlock: []byte(core.EpochStartIdentifier(0)), // for genesis the prevEpochStart block used + }, + } + + require.Nil(t, err) + require.Equal(t, expectedShardData, startData) + }) +} diff --git a/epochStart/metachain/errors.go b/epochStart/metachain/errors.go index b55c3c254e0..6bbba9788d1 100644 --- a/epochStart/metachain/errors.go +++ b/epochStart/metachain/errors.go @@ -13,3 +13,7 @@ var errNilTableDisplayHandler = errors.New("nil table display handler provided") var errNegativeAcceleratorReward = errors.New("negative accelerator reward") var errAcceleratorRewardsMoreThanTotalRewards = errors.New("accelerator rewards more than total rewards") + +var errHashMismatch = errors.New("hash mismatch") + +var errMethodNotSupported = errors.New("method not supported") diff --git a/epochStart/metachain/export_test.go b/epochStart/metachain/export_test.go index 263967489a7..e8414439f81 100644 --- a/epochStart/metachain/export_test.go +++ b/epochStart/metachain/export_test.go @@ -1,5 +1,9 @@ package metachain +import ( + "github.com/multiversx/mx-chain-go/process" +) + // SetInCache - func (sdp *stakingDataProvider) SetInCache(key []byte, ownerData *ownerStats) { sdp.mutStakingData.Lock() @@ -14,3 +18,8 @@ func (sdp *stakingDataProvider) GetFromCache(key []byte) *ownerStats { return sdp.cache[string(key)] } + +// SetRoundTimeHandler - +func (e *economics) SetRoundTimeHandler(roundHandler process.RoundTimeDurationHandler) { + e.roundTime = roundHandler +} diff --git a/epochStart/metachain/rewards.go b/epochStart/metachain/rewards.go index cb24d8a8800..1828cbb234f 100644 --- a/epochStart/metachain/rewards.go +++ b/epochStart/metachain/rewards.go @@ -8,6 +8,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/rewardTx" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/epochStart" "github.com/multiversx/mx-chain-go/process" @@ -87,7 +88,7 @@ func (rc *rewardsCreator) CreateRewardsMiniBlocks( miniBlocks := rc.initializeRewardsMiniBlocks() - protSustRwdTx, protSustShardId, err := rc.createProtocolSustainabilityRewardTransaction(metaBlock, computedEconomics.RewardsForProtocolSustainability) + protSustRwdTx, protSustShardId, err := rc.createProtocolSustainabilityRewardTransaction(metaBlock.GetEpoch(), metaBlock.GetRound(), computedEconomics.GetRewardsForProtocolSustainability()) if err != nil { return nil, err } @@ -139,7 +140,7 @@ func (rc *rewardsCreator) addValidatorRewardsToMiniBlocks( ) error { rwdAddrValidatorInfo := rc.computeValidatorInfoPerRewardAddress(validatorsInfo, protocolSustainabilityRwdTx, metaBlock.GetEpoch()) for _, rwdInfo := range rwdAddrValidatorInfo { - rwdTx, rwdTxHash, err := rc.createRewardFromRwdInfo(rwdInfo, metaBlock) + rwdTx, rwdTxHash, err := rc.createRewardFromRwdInfo(rwdInfo, metaBlock.GetEpoch(), metaBlock.GetRound()) if err != nil { return err } @@ -235,6 +236,16 @@ func (rc *rewardsCreator) VerifyRewardsMiniBlocks( return rc.verifyCreatedRewardMiniBlocksWithMetaBlock(metaBlock, createdMiniBlocks) } +// CreateRewardsMiniBlocksV3 is not supported +func (rcp *rewardsCreator) CreateRewardsMiniBlocksV3( + _ data.MetaHeaderHandler, + _ state.ShardValidatorsInfoMapHandler, + _ *block.Economics, + _ data.BaseMetaExecutionResultHandler, +) (block.MiniBlockSlice, error) { + return nil, errMethodNotSupported +} + // IsInterfaceNil return true if underlying object is nil func (rc *rewardsCreator) IsInterfaceNil() bool { return rc == nil diff --git a/epochStart/metachain/rewardsCreatorProxy.go b/epochStart/metachain/rewardsCreatorProxy.go index a3a0b396e6e..ad4da9170bc 100644 --- a/epochStart/metachain/rewardsCreatorProxy.go +++ b/epochStart/metachain/rewardsCreatorProxy.go @@ -6,6 +6,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/epochStart" "github.com/multiversx/mx-chain-go/process" @@ -73,6 +74,20 @@ func (rcp *rewardsCreatorProxy) CreateRewardsMiniBlocks( return rcp.rc.CreateRewardsMiniBlocks(metaBlock, validatorsInfo, computedEconomics) } +// CreateRewardsMiniBlocksV3 proxies the CreateRewardsMiniBlocksV3 method of the configured rewardsCreator instance +func (rcp *rewardsCreatorProxy) CreateRewardsMiniBlocksV3( + metaBlock data.MetaHeaderHandler, + validatorsInfo state.ShardValidatorsInfoMapHandler, + computedEconomics *block.Economics, + prevBlockExecutionResults data.BaseMetaExecutionResultHandler, +) (block.MiniBlockSlice, error) { + err := rcp.changeRewardCreatorIfNeeded(metaBlock.GetEpoch()) + if err != nil { + return nil, err + } + return rcp.rc.CreateRewardsMiniBlocksV3(metaBlock, validatorsInfo, computedEconomics, prevBlockExecutionResults) +} + // VerifyRewardsMiniBlocks proxies the same method of the configured rewardsCreator instance func (rcp *rewardsCreatorProxy) VerifyRewardsMiniBlocks( metaBlock data.MetaHeaderHandler, diff --git a/epochStart/metachain/rewardsV2.go b/epochStart/metachain/rewardsV2.go index 22c1917bbd6..d4da2d7b0c9 100644 --- a/epochStart/metachain/rewardsV2.go +++ b/epochStart/metachain/rewardsV2.go @@ -9,6 +9,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/rewardTx" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/common/validatorInfo" "github.com/multiversx/mx-chain-go/epochStart" @@ -43,6 +44,15 @@ type rewardsCreatorV2 struct { rewardsHandler process.RewardsHandler } +type argsRewardsMiniBlocksCreation struct { + newEpoch uint32 + round uint64 + devFeesInEpoch *big.Int + accumulatedFeesInEpoch *big.Int + validatorsInfo state.ShardValidatorsInfoMapHandler + computedEconomics *block.Economics +} + // NewRewardsCreatorV2 creates a new rewards creator object func NewRewardsCreatorV2(args RewardsCreatorArgsV2) (*rewardsCreatorV2, error) { brc, err := NewBaseRewardsCreator(args.BaseRewardsCreatorArgs) @@ -86,7 +96,51 @@ func (rc *rewardsCreatorV2) CreateRewardsMiniBlocks( if check.IfNil(metaBlock) { return nil, epochStart.ErrNilHeaderHandler } - if computedEconomics == nil { + + args := argsRewardsMiniBlocksCreation{ + newEpoch: metaBlock.GetEpoch(), + round: metaBlock.GetRound(), + devFeesInEpoch: metaBlock.GetDevFeesInEpoch(), + accumulatedFeesInEpoch: metaBlock.GetAccumulatedFeesInEpoch(), + validatorsInfo: validatorsInfo, + computedEconomics: computedEconomics, + } + return rc.createRewardsMiniBlocks(args) +} + +// CreateRewardsMiniBlocksV3 creates the rewards miniblocks according to economics data and validator info. +func (rc *rewardsCreatorV2) CreateRewardsMiniBlocksV3( + metaBlock data.MetaHeaderHandler, + validatorsInfo state.ShardValidatorsInfoMapHandler, + computedEconomics *block.Economics, + prevBlockExecutionResults data.BaseMetaExecutionResultHandler, +) (block.MiniBlockSlice, error) { + if check.IfNil(metaBlock) { + return nil, epochStart.ErrNilHeaderHandler + } + if check.IfNil(prevBlockExecutionResults) { + return nil, epochStart.ErrNilPrevBlockExecutionResults + } + if !metaBlock.IsHeaderV3() { + return nil, epochStart.ErrInvalidHeader + } + + epoch := metaBlock.GetEpoch() + 1 + args := argsRewardsMiniBlocksCreation{ + newEpoch: epoch, + round: metaBlock.GetRound(), + devFeesInEpoch: prevBlockExecutionResults.GetDevFeesInEpoch(), + accumulatedFeesInEpoch: prevBlockExecutionResults.GetAccumulatedFeesInEpoch(), + validatorsInfo: validatorsInfo, + computedEconomics: computedEconomics, + } + return rc.createRewardsMiniBlocks(args) +} + +func (rc *rewardsCreatorV2) createRewardsMiniBlocks( + args argsRewardsMiniBlocksCreation, +) (block.MiniBlockSlice, error) { + if args.computedEconomics == nil { return nil, epochStart.ErrNilEconomicsData } @@ -94,10 +148,10 @@ func (rc *rewardsCreatorV2) CreateRewardsMiniBlocks( defer rc.mutRewardsData.Unlock() log.Debug("rewardsCreatorV2.CreateRewardsMiniBlocks", - "totalToDistribute", computedEconomics.TotalToDistribute, - "rewardsForProtocolSustainability", computedEconomics.RewardsForProtocolSustainability, - "rewardsPerBlock", computedEconomics.RewardsPerBlock, - "devFeesInEpoch", metaBlock.GetDevFeesInEpoch(), + "totalToDistribute", args.computedEconomics.TotalToDistribute, + "rewardsForProtocolSustainability", args.computedEconomics.RewardsForProtocolSustainability, + "rewardsPerBlock", args.computedEconomics.RewardsPerBlock, + "devFeesInEpoch", args.devFeesInEpoch, "rewardsForBlocks no fees", rc.economicsDataProvider.RewardsToBeDistributedForBlocks(), "numberOfBlocks", rc.economicsDataProvider.NumberOfBlocks(), "numberOfBlocksPerShard", rc.economicsDataProvider.NumberOfBlocksPerShard(), @@ -105,17 +159,17 @@ func (rc *rewardsCreatorV2) CreateRewardsMiniBlocks( miniBlocks := rc.initializeRewardsMiniBlocks() rc.clean() - rc.flagDelegationSystemSCEnabled.SetValue(metaBlock.GetEpoch() >= rc.enableEpochsHandler.GetActivationEpoch(common.StakingV2Flag)) + rc.flagDelegationSystemSCEnabled.SetValue(args.newEpoch >= rc.enableEpochsHandler.GetActivationEpoch(common.StakingV2Flag)) - protRwdTx, protRwdShardId, err := rc.createProtocolSustainabilityRewardTransaction(metaBlock, rc.economicsDataProvider.RewardsForProtocolSustainability()) + protRwdTx, protRwdShardId, err := rc.createProtocolSustainabilityRewardTransaction(args.newEpoch, args.round, rc.economicsDataProvider.RewardsForProtocolSustainability()) if err != nil { return nil, err } - nodesRewardInfo, dustFromRewardsPerNode := rc.computeRewardsPerNode(validatorsInfo, metaBlock.GetEpoch()) + nodesRewardInfo, dustFromRewardsPerNode := rc.computeRewardsPerNode(args.validatorsInfo, args.newEpoch) log.Debug("arithmetic difference from dust rewards per node", "value", dustFromRewardsPerNode) - dust, err := rc.addValidatorRewardsToMiniBlocks(metaBlock, miniBlocks, nodesRewardInfo) + dust, err := rc.addValidatorRewardsToMiniBlocks(args.newEpoch, args.round, miniBlocks, nodesRewardInfo) if err != nil { return nil, err } @@ -129,11 +183,11 @@ func (rc *rewardsCreatorV2) CreateRewardsMiniBlocks( return nil, err } - ecoGrowthRwdTx, ecoGrowthShardId, err := rc.createEcosystemGrowthRewardTransaction(metaBlock) + ecoGrowthRwdTx, ecoGrowthShardId, err := rc.createEcosystemGrowthRewardTransaction(args.newEpoch, args.round) if err != nil { return nil, err } - growthDivRwdTx, growthDivShardId, err := rc.createGrowthDividendRewardTransaction(metaBlock) + growthDivRwdTx, growthDivShardId, err := rc.createGrowthDividendRewardTransaction(args.newEpoch, args.round) if err != nil { return nil, err } @@ -150,14 +204,15 @@ func (rc *rewardsCreatorV2) CreateRewardsMiniBlocks( } func (rc *rewardsCreatorV2) createEcosystemGrowthRewardTransaction( - metaBlock data.MetaHeaderHandler, + epoch uint32, + round uint64, ) (*rewardTx.RewardTx, uint32, error) { - epoch := metaBlock.GetEpoch() + rwdAddr := rc.rewardsHandler.EcosystemGrowthAddressInEpoch(epoch) shardId := rc.shardCoordinator.ComputeId([]byte(rwdAddr)) rwdTx := &rewardTx.RewardTx{ - Round: metaBlock.GetRound(), + Round: round, Epoch: epoch, RcvAddr: []byte(rwdAddr), Value: big.NewInt(0).Set(rc.economicsDataProvider.RewardsForEcosystemGrowth()), @@ -168,14 +223,14 @@ func (rc *rewardsCreatorV2) createEcosystemGrowthRewardTransaction( } func (rc *rewardsCreatorV2) createGrowthDividendRewardTransaction( - metaBlock data.MetaHeaderHandler, + epoch uint32, + round uint64, ) (*rewardTx.RewardTx, uint32, error) { - epoch := metaBlock.GetEpoch() rwdAddr := rc.rewardsHandler.GrowthDividendAddressInEpoch(epoch) shardId := rc.shardCoordinator.ComputeId([]byte(rwdAddr)) rwdTx := &rewardTx.RewardTx{ - Round: metaBlock.GetRound(), + Round: round, Epoch: epoch, RcvAddr: []byte(rwdAddr), Value: big.NewInt(0).Set(rc.economicsDataProvider.RewardsForGrowthDividend()), @@ -225,14 +280,15 @@ func (rc *rewardsCreatorV2) VerifyRewardsMiniBlocks( } func (rc *rewardsCreatorV2) addValidatorRewardsToMiniBlocks( - metaBlock data.HeaderHandler, + epoch uint32, + round uint64, miniBlocks block.MiniBlockSlice, nodesRewardInfo map[uint32][]*nodeRewardsData, ) (*big.Int, error) { rwdAddrValidatorInfo, accumulatedDust := rc.computeValidatorInfoPerRewardAddress(nodesRewardInfo) for _, rwdInfo := range rwdAddrValidatorInfo { - rwdTx, rwdTxHash, err := rc.createRewardFromRwdInfo(rwdInfo, metaBlock) + rwdTx, rwdTxHash, err := rc.createRewardFromRwdInfo(rwdInfo, epoch, round) if err != nil { return nil, err } diff --git a/epochStart/metachain/rewardsV2_test.go b/epochStart/metachain/rewardsV2_test.go index 23e7e5f3385..f1b9bdddd7d 100644 --- a/epochStart/metachain/rewardsV2_test.go +++ b/epochStart/metachain/rewardsV2_test.go @@ -13,6 +13,8 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/rewardTx" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/epochStart" "github.com/multiversx/mx-chain-go/epochStart/mock" @@ -20,7 +22,6 @@ import ( "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" "github.com/multiversx/mx-chain-go/testscommon/stakingcommon" - "github.com/stretchr/testify/require" ) const ( @@ -1217,7 +1218,7 @@ func setupNodeRewardInfo( nrValidatorsToRemoveTopup := big.NewInt(0).Div(validatorTopupStake, topupStakePerNode) - //remove the newly added topup from some other nodes + // remove the newly added topup from some other nodes for i := int64(1); i < nrValidatorsToRemoveTopup.Int64(); i++ { nodesRewardInfo[0][i].topUpStake = big.NewInt(0) } @@ -1509,7 +1510,7 @@ func TestNewRewardsCreatorV2_addValidatorRewardsToMiniBlocks(t *testing.T) { sumFees.Add(sumFees, vInfo.GetAccumulatedFees()) } - accumulatedDust, err := rwd.addValidatorRewardsToMiniBlocks(metaBlock, miniBlocks, nodesRewardInfo) + accumulatedDust, err := rwd.addValidatorRewardsToMiniBlocks(metaBlock.GetEpoch(), metaBlock.GetRound(), miniBlocks, nodesRewardInfo) require.Nil(t, err) require.Equal(t, big.NewInt(0), accumulatedDust) @@ -1560,7 +1561,7 @@ func TestNewRewardsCreatorV2_addValidatorRewardsToMiniBlocksAddressInMetaChainDe } } - accumulatedDust, err := rwd.addValidatorRewardsToMiniBlocks(metaBlock, miniBlocks, nodesRewardInfo) + accumulatedDust, err := rwd.addValidatorRewardsToMiniBlocks(metaBlock.GetEpoch(), metaBlock.GetRound(), miniBlocks, nodesRewardInfo) require.Nil(t, err) require.True(t, big.NewInt(0).Cmp(accumulatedDust) < 0) @@ -1582,7 +1583,7 @@ func TestNewRewardsCreatorV2_CreateRewardsMiniBlocks(t *testing.T) { t.Parallel() args := getRewardsCreatorV2Arguments() - nbEligiblePerShard := uint32(1) + nbEligiblePerShard := uint32(400) dummyRwd, _ := NewRewardsCreatorV2(args) vInfo := createDefaultValidatorInfo(nbEligiblePerShard, args.ShardCoordinator, args.NodesConfigProvider, 100, defaultBlocksPerShard) nodesRewardInfo := dummyRwd.initNodesRewardsInfo(vInfo) @@ -1855,6 +1856,75 @@ func TestRewardsCreatorV2_CreateRewardsMiniBlocksWithTopUp(t *testing.T) { require.Equal(t, expectedRewards, sumRewards) } +func TestRewardsCreatorV2_CreateRewardsMiniBlocksHeaderV3(t *testing.T) { + t.Parallel() + + t.Run("should return ErrNilHeaderHandler because of nil header argument", func(t *testing.T) { + t.Parallel() + + args := getRewardsCreatorV2Arguments() + rwd, err := NewRewardsCreatorV2(args) + require.Nil(t, err) + + vInfo := state.NewShardValidatorsInfoMap() + prevBlockExecResults := block.BaseMetaExecutionResult{} + + _, err = rwd.CreateRewardsMiniBlocksV3(nil, vInfo, &block.Economics{}, &prevBlockExecResults) + require.Equal(t, common.ErrNilHeaderHandler, err) + }) + + t.Run("should return ErrNilPrevBlockExecutionResults because of nil previous block execution results", func(t *testing.T) { + t.Parallel() + + args := getRewardsCreatorV2Arguments() + rwd, err := NewRewardsCreatorV2(args) + require.Nil(t, err) + + vInfo := state.NewShardValidatorsInfoMap() + metaBlock := &block.MetaBlockV3{} + + _, err = rwd.CreateRewardsMiniBlocksV3(metaBlock, vInfo, &block.Economics{}, nil) + require.Equal(t, epochStart.ErrNilPrevBlockExecutionResults, err) + }) + + t.Run("should return ErrInvalidHeader because headers is not V3", func(t *testing.T) { + t.Parallel() + + args := getRewardsCreatorV2Arguments() + rwd, err := NewRewardsCreatorV2(args) + require.Nil(t, err) + + vInfo := state.NewShardValidatorsInfoMap() + metaBlock := &block.MetaBlock{} + + prevBlockExecResults := block.BaseMetaExecutionResult{} + + _, err = rwd.CreateRewardsMiniBlocksV3(metaBlock, vInfo, &block.Economics{}, &prevBlockExecResults) + require.Equal(t, epochStart.ErrInvalidHeader, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + args := getRewardsCreatorV2Arguments() + rwd, err := NewRewardsCreatorV2(args) + require.Nil(t, err) + + nbEligiblePerShard := uint32(400) + vInfo := createDefaultValidatorInfo(nbEligiblePerShard, args.ShardCoordinator, args.NodesConfigProvider, 100, defaultBlocksPerShard) + metaBlock := &block.MetaBlockV3{ + EpochStart: getDefaultEpochStart(), + } + + prevBlockExecResults := block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{}, + } + + _, err = rwd.CreateRewardsMiniBlocksV3(metaBlock, vInfo, &metaBlock.EpochStart.Economics, &prevBlockExecResults) + require.Nil(t, err) + }) +} + func getRewardsCreatorV2Arguments() RewardsCreatorArgsV2 { rewardsTopUpGradientPoint, _ := big.NewInt(0).SetString("3000000000000000000000000", 10) topUpRewardFactor := 0.25 diff --git a/epochStart/metachain/rewards_test.go b/epochStart/metachain/rewards_test.go index 2aed0b3eb63..be601783c1d 100644 --- a/epochStart/metachain/rewards_test.go +++ b/epochStart/metachain/rewards_test.go @@ -9,6 +9,9 @@ import ( "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/rewardTx" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/epochStart" "github.com/multiversx/mx-chain-go/epochStart/mock" "github.com/multiversx/mx-chain-go/sharding" @@ -17,8 +20,6 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" "github.com/multiversx/mx-chain-go/vm" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestNewRewardsCreator_NilShardCoordinator(t *testing.T) { @@ -569,7 +570,7 @@ func TestRewardsCreator_addValidatorRewardsToMiniBlocks(t *testing.T) { miniBlocks[0].Type = block.RewardsBlock miniBlocks[0].TxHashes = make([][]byte, 0) - cloneMb := &(*miniBlocks[0]) //nolint + cloneMb := &(*miniBlocks[0]) // nolint cloneMb.TxHashes = make([][]byte, 0) expectedRwdTx := &rewardTx.RewardTx{ Round: 0, @@ -656,7 +657,7 @@ func TestRewardsCreator_CreateProtocolSustainabilityRewardTransaction(t *testing Epoch: 0, } - rwdTx, _, err := rwdc.createProtocolSustainabilityRewardTransaction(mb, mb.EpochStart.Economics.RewardsForProtocolSustainability) + rwdTx, _, err := rwdc.createProtocolSustainabilityRewardTransaction(mb.GetEpoch(), mb.GetRound(), mb.EpochStart.Economics.GetRewardsForProtocolSustainability()) assert.Equal(t, expectedRewardTx, rwdTx) assert.Nil(t, err) } @@ -678,7 +679,7 @@ func TestRewardsCreator_AddProtocolSustainabilityRewardToMiniBlocks(t *testing.T miniBlocks[0].Type = block.RewardsBlock miniBlocks[0].TxHashes = make([][]byte, 0) - cloneMb := &(*miniBlocks[0]) //nolint + cloneMb := &(*miniBlocks[0]) // nolint cloneMb.TxHashes = make([][]byte, 0) expectedRewardTx := &rewardTx.RewardTx{ Round: 0, diff --git a/epochStart/metachain/systemSCs.go b/epochStart/metachain/systemSCs.go index 96cba60251b..c37447097fc 100644 --- a/epochStart/metachain/systemSCs.go +++ b/epochStart/metachain/systemSCs.go @@ -8,6 +8,8 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/marshal" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/epochStart" "github.com/multiversx/mx-chain-go/process" @@ -15,7 +17,6 @@ import ( "github.com/multiversx/mx-chain-go/sharding/nodesCoordinator" "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/vm" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" ) // ArgsNewEpochStartSystemSCProcessing defines the arguments structure for the end of epoch system sc processor @@ -106,7 +107,9 @@ func (s *systemSCProcessor) ProcessSystemSmartContract( return err } - err = s.processLegacy(validatorsInfoMap, header.GetNonce(), header.GetEpoch()) + epochToUse := GetEpochToUseEpochStartData(header) + + err = s.processLegacy(validatorsInfoMap, header.GetNonce(), epochToUse) if err != nil { return err } @@ -129,6 +132,8 @@ func (s *systemSCProcessor) processWithNewFlags( validatorsInfoMap state.ShardValidatorsInfoMapHandler, header data.HeaderHandler, ) error { + epochToUse := GetEpochToUseEpochStartData(header) + if s.enableEpochsHandler.IsFlagEnabled(common.GovernanceFlagInSpecificEpochOnly) { err := s.updateToGovernanceV2() if err != nil { @@ -154,7 +159,7 @@ func (s *systemSCProcessor) processWithNewFlags( return err } - err = s.unStakeNodesWithNotEnoughFundsWithStakingV4(validatorsInfoMap, header.GetEpoch()) + err = s.unStakeNodesWithNotEnoughFundsWithStakingV4(validatorsInfoMap, epochToUse) if err != nil { return err } diff --git a/epochStart/metachain/systemSCs_test.go b/epochStart/metachain/systemSCs_test.go index dfe6dfe7191..aac10d69d01 100644 --- a/epochStart/metachain/systemSCs_test.go +++ b/epochStart/metachain/systemSCs_test.go @@ -19,6 +19,7 @@ import ( "github.com/multiversx/mx-chain-core-go/hashing/sha256" "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/common/configs/dto" "github.com/multiversx/mx-chain-go/common/enablers" "github.com/multiversx/mx-chain-go/common/forking" "github.com/multiversx/mx-chain-go/config" @@ -798,19 +799,26 @@ func createFullArgumentsForSystemSCProcessing(enableEpochsConfig config.EnableEp peerAccountsDB := createAccountsDB(hasher, marshalizer, peerAccCreator, trieFactoryManager, enableEpochsHandler) argsValidatorsProcessor := peer.ArgValidatorStatisticsProcessor{ - Marshalizer: marshalizer, - NodesCoordinator: &shardingMocks.NodesCoordinatorStub{}, - ShardCoordinator: &mock.ShardCoordinatorStub{}, - DataPool: &dataRetrieverMock.PoolsHolderStub{}, - StorageService: &storageMock.ChainStorerStub{}, - PubkeyConv: &testscommon.PubkeyConverterMock{}, - PeerAdapter: peerAccountsDB, - Rater: &mock.RaterStub{}, - RewardsHandler: &mock.RewardsHandlerStub{}, - NodesSetup: &genesisMocks.NodesSetupStub{}, - MaxComputableRounds: 1, - MaxConsecutiveRoundsOfRatingDecrease: 2000, - EnableEpochsHandler: enableEpochsHandler, + Marshalizer: marshalizer, + NodesCoordinator: &shardingMocks.NodesCoordinatorStub{}, + ShardCoordinator: &mock.ShardCoordinatorStub{}, + DataPool: &dataRetrieverMock.PoolsHolderStub{}, + StorageService: &storageMock.ChainStorerStub{}, + PubkeyConv: &testscommon.PubkeyConverterMock{}, + PeerAdapter: peerAccountsDB, + Rater: &mock.RaterStub{}, + RewardsHandler: &mock.RewardsHandlerStub{}, + NodesSetup: &genesisMocks.NodesSetupStub{}, + MaxComputableRounds: 1, + EnableEpochsHandler: enableEpochsHandler, + ProcessConfigsHandler: &testscommon.ProcessConfigsHandlerStub{ + GetValueCalled: func(variable dto.ConfigVariable) uint64 { + if variable == dto.MaxConsecutiveRoundsOfRatingDecrease { + return 2000 + } + return 10 + }, + }, } vCreator, _ := peer.NewValidatorStatisticsProcessor(argsValidatorsProcessor) @@ -884,6 +892,7 @@ func createFullArgumentsForSystemSCProcessing(enableEpochsConfig config.EnableEp MinStepValue: "10", MinStakeValue: "1", UnBondPeriod: 1, + UnBondPeriodSupernova: 2, NumRoundsWithoutBleed: 1, MaximumPercentageToBleed: 1, BleedPercentagePerRound: 1, @@ -914,6 +923,7 @@ func createFullArgumentsForSystemSCProcessing(enableEpochsConfig config.EnableEp ChanceComputer: &mock.ChanceComputerStub{}, ShardCoordinator: &mock.ShardCoordinatorStub{}, EnableEpochsHandler: enableEpochsHandler, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, NodesCoordinator: &shardingMocks.NodesCoordinatorStub{}, ArgBlockChainHook: argsHook, } diff --git a/epochStart/metachain/testTrigger.go b/epochStart/metachain/testTrigger.go index 22a9a30e382..a5594fd968d 100644 --- a/epochStart/metachain/testTrigger.go +++ b/epochStart/metachain/testTrigger.go @@ -1,6 +1,10 @@ package metachain -import "github.com/multiversx/mx-chain-go/epochStart" +import ( + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/epochStart" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" +) // TestTrigger extends start of epoch trigger and is used in integration tests as it exposes some functions // that are not supposed to be used in production code @@ -21,23 +25,41 @@ func (t *TestTrigger) SetTrigger(triggerHandler epochStart.TriggerHandler) { // SetRoundsPerEpoch sets the number of round between epochs func (t *TestTrigger) SetRoundsPerEpoch(roundsPerEpoch uint64) { - t.roundsPerEpoch = roundsPerEpoch - if t.minRoundsBetweenEpochs > t.roundsPerEpoch { - t.minRoundsBetweenEpochs = t.roundsPerEpoch - 1 + minRoundsBetweenEpochs := t.getMinRoundsBetweenEpochs(0) + if minRoundsBetweenEpochs > roundsPerEpoch { + minRoundsBetweenEpochs = roundsPerEpoch - 1 + } + + t.chainParametersHandler = &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(uint33 uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{ + RoundsPerEpoch: int64(roundsPerEpoch), + MinRoundsBetweenEpochs: int64(minRoundsBetweenEpochs), + }, nil + }, } } // SetMinRoundsBetweenEpochs sets the minimum number of round between epochs func (t *TestTrigger) SetMinRoundsBetweenEpochs(minRoundsPerEpoch uint64) { - t.minRoundsBetweenEpochs = minRoundsPerEpoch - if t.minRoundsBetweenEpochs > t.roundsPerEpoch { - t.minRoundsBetweenEpochs = t.roundsPerEpoch - 1 + roundsPerEpoch := t.getRoundsPerEpoch(0) + if minRoundsPerEpoch > roundsPerEpoch { + minRoundsPerEpoch = roundsPerEpoch - 1 + } + + t.chainParametersHandler = &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(uint33 uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{ + RoundsPerEpoch: int64(roundsPerEpoch), + MinRoundsBetweenEpochs: int64(minRoundsPerEpoch), + }, nil + }, } } // GetRoundsPerEpoch gets the number of rounds per epoch func (t *TestTrigger) GetRoundsPerEpoch() uint64 { - return t.roundsPerEpoch + return t.getRoundsPerEpoch(0) } // SetEpoch sets the current epoch for the testTrigger diff --git a/epochStart/metachain/trigger.go b/epochStart/metachain/trigger.go index 9d4855bb11e..0f81203adf1 100644 --- a/epochStart/metachain/trigger.go +++ b/epochStart/metachain/trigger.go @@ -15,7 +15,8 @@ import ( "github.com/multiversx/mx-chain-core-go/display" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" - "github.com/multiversx/mx-chain-logger-go" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" @@ -37,29 +38,27 @@ const disabledRoundForForceEpochStart = math.MaxUint64 // ArgsNewMetaEpochStartTrigger defines struct needed to create a new start of epoch trigger type ArgsNewMetaEpochStartTrigger struct { - GenesisTime time.Time - Settings *config.EpochStartConfig - Epoch uint32 - EpochStartRound uint64 - EpochStartNotifier epochStart.Notifier - Marshalizer marshal.Marshalizer - Hasher hashing.Hasher - Storage dataRetriever.StorageService - AppStatusHandler core.AppStatusHandler - DataPool dataRetriever.PoolsHolder + GenesisTime time.Time + Settings *config.EpochStartConfig + Epoch uint32 + EpochStartRound uint64 + EpochStartNotifier epochStart.Notifier + Marshalizer marshal.Marshalizer + Hasher hashing.Hasher + Storage dataRetriever.StorageService + AppStatusHandler core.AppStatusHandler + DataPool dataRetriever.PoolsHolder + ChainParametersHandler process.ChainParametersHandler } type trigger struct { isEpochStart bool epoch uint32 epochStartMeta data.HeaderHandler - currentRound uint64 epochFinalityAttestingRound uint64 currEpochStartRound uint64 prevEpochStartRound uint64 nextEpochStartRound uint64 - roundsPerEpoch uint64 - minRoundsBetweenEpochs uint64 epochStartMetaHash []byte triggerStateKey []byte epochStartTime time.Time @@ -71,6 +70,8 @@ type trigger struct { hasher hashing.Hasher appStatusHandler core.AppStatusHandler validatorInfoPool epochStart.ValidatorInfoCacher + chainParametersHandler process.ChainParametersHandler + epochChangeProposed bool } // NewEpochStartTrigger creates a trigger for start of epoch @@ -81,15 +82,6 @@ func NewEpochStartTrigger(args *ArgsNewMetaEpochStartTrigger) (*trigger, error) if args.Settings == nil { return nil, epochStart.ErrNilEpochStartSettings } - if args.Settings.RoundsPerEpoch < 1 { - return nil, fmt.Errorf("%w, RoundsPerEpoch < 1", epochStart.ErrInvalidSettingsForEpochStartTrigger) - } - if args.Settings.MinRoundsBetweenEpochs < 1 { - return nil, fmt.Errorf("%w, MinRoundsBetweenEpochs < 1", epochStart.ErrInvalidSettingsForEpochStartTrigger) - } - if args.Settings.MinRoundsBetweenEpochs > args.Settings.RoundsPerEpoch { - return nil, fmt.Errorf("%w, MinRoundsBetweenEpochs > RoundsPerEpoch", epochStart.ErrInvalidSettingsForEpochStartTrigger) - } if check.IfNil(args.EpochStartNotifier) { return nil, epochStart.ErrNilEpochStartNotifier } @@ -111,6 +103,9 @@ func NewEpochStartTrigger(args *ArgsNewMetaEpochStartTrigger) (*trigger, error) if check.IfNil(args.DataPool.CurrentEpochValidatorInfo()) { return nil, epochStart.ErrNilCurrentEpochValidatorsInfoPool } + if check.IfNil(args.ChainParametersHandler) { + return nil, process.ErrNilChainParametersHandler + } triggerStorage, err := args.Storage.GetStorer(dataRetriever.BootstrapUnit) if err != nil { @@ -125,12 +120,10 @@ func NewEpochStartTrigger(args *ArgsNewMetaEpochStartTrigger) (*trigger, error) trigggerStateKey := common.TriggerRegistryInitialKeyPrefix + fmt.Sprintf("%d", args.Epoch) trig := &trigger{ triggerStateKey: []byte(trigggerStateKey), - roundsPerEpoch: uint64(args.Settings.RoundsPerEpoch), epochStartTime: args.GenesisTime, currEpochStartRound: args.EpochStartRound, prevEpochStartRound: args.EpochStartRound, epoch: args.Epoch, - minRoundsBetweenEpochs: uint64(args.Settings.MinRoundsBetweenEpochs), mutTrigger: sync.RWMutex{}, epochFinalityAttestingRound: args.EpochStartRound, epochStartNotifier: args.EpochStartNotifier, @@ -142,6 +135,7 @@ func NewEpochStartTrigger(args *ArgsNewMetaEpochStartTrigger) (*trigger, error) appStatusHandler: args.AppStatusHandler, nextEpochStartRound: disabledRoundForForceEpochStart, validatorInfoPool: args.DataPool.CurrentEpochValidatorInfo(), + chainParametersHandler: args.ChainParametersHandler, } err = trig.saveState(trig.triggerStateKey) @@ -182,14 +176,16 @@ func (t *trigger) ForceEpochStart(round uint64) { defer t.mutTrigger.Unlock() t.nextEpochStartRound = round - if t.nextEpochStartRound > t.currEpochStartRound+t.roundsPerEpoch { + if t.nextEpochStartRound > t.currEpochStartRound+t.getRoundsPerEpoch(t.epoch)-t.getOffsetPerEpoch(t.epoch) { t.nextEpochStartRound = disabledRoundForForceEpochStart log.Debug("can not force epoch start because the resulting round is in the next epoch") return } - if t.nextEpochStartRound-t.currEpochStartRound < t.minRoundsBetweenEpochs { - t.nextEpochStartRound = t.currEpochStartRound + t.minRoundsBetweenEpochs + + minRoundsBetweenEpochs := t.getMinRoundsBetweenEpochs(t.epoch) + if t.nextEpochStartRound-t.currEpochStartRound < minRoundsBetweenEpochs { + t.nextEpochStartRound = t.currEpochStartRound + minRoundsBetweenEpochs log.Debug("can not force epoch start on provided round", "provided round", round, "computed round", t.nextEpochStartRound) } @@ -197,41 +193,112 @@ func (t *trigger) ForceEpochStart(round uint64) { log.Debug("set new epoch start round", "round", t.nextEpochStartRound) } -// Update processes changes in the trigger -func (t *trigger) Update(round uint64, nonce uint64) { +func (t *trigger) getRoundsPerEpoch(epoch uint32) uint64 { + chainParametersForEpoch, err := t.chainParametersHandler.ChainParametersForEpoch(epoch) + if err != nil { + log.Warn("could not get rounds per epoch for epoch, returned current chain parameters", "epoch", epoch, "error", err) + chainParametersForEpoch = t.chainParametersHandler.CurrentChainParameters() + } + + return uint64(chainParametersForEpoch.RoundsPerEpoch) +} + +func (t *trigger) getOffsetPerEpoch(epoch uint32) uint64 { + chainParametersForEpoch, err := t.chainParametersHandler.ChainParametersForEpoch(epoch) + if err != nil { + log.Warn("could not get rounds per epoch for epoch, returned current chain parameters", "epoch", epoch, "error", err) + chainParametersForEpoch = t.chainParametersHandler.CurrentChainParameters() + } + + return uint64(chainParametersForEpoch.Offset) +} + +func (t *trigger) getMinRoundsBetweenEpochs(epoch uint32) uint64 { + chainParametersForEpoch, err := t.chainParametersHandler.ChainParametersForEpoch(epoch) + if err != nil { + log.Warn("could not get min rounds between epoch, returned current chain parameters", "epoch", epoch, "error", err) + chainParametersForEpoch = t.chainParametersHandler.CurrentChainParameters() + } + + return uint64(chainParametersForEpoch.MinRoundsBetweenEpochs) +} + +// ShouldProposeEpochChange will return true if an epoch change event should be trigger +func (t *trigger) ShouldProposeEpochChange(currentRound uint64, currentNonce uint64) bool { t.mutTrigger.Lock() defer t.mutTrigger.Unlock() - t.currentRound = round + shouldTriggerEpochStart := t.shouldTriggerEpochStart(currentRound, currentNonce) + if shouldTriggerEpochStart && !t.epochChangeProposed { + return true + } + + return false +} + +// SetEpochChangeProposed sets the epoch change proposed flag to true +func (t *trigger) SetEpochChangeProposed(value bool) { + t.mutTrigger.Lock() + defer t.mutTrigger.Unlock() + t.epochChangeProposed = value +} +// GetEpochChangeProposed returns the epoch change proposed flag +func (t *trigger) GetEpochChangeProposed() bool { + t.mutTrigger.RLock() + defer t.mutTrigger.RUnlock() + return t.epochChangeProposed +} + +func (t *trigger) shouldTriggerEpochStart(currentRound uint64, currentNonce uint64) bool { + isZeroEpochEdgeCase := currentNonce < minimumNonceToStartEpoch + isNormalEpochStart := currentRound > t.currEpochStartRound+t.getRoundsPerEpoch(t.epoch)-t.getOffsetPerEpoch(t.epoch) + isWithEarlyEndOfEpoch := currentRound >= t.nextEpochStartRound + shouldTriggerEpochStart := (isNormalEpochStart || isWithEarlyEndOfEpoch) && !isZeroEpochEdgeCase + + return shouldTriggerEpochStart +} + +// Update processes changes in the trigger +func (t *trigger) Update(round uint64, nonce uint64) { + t.mutTrigger.Lock() + defer t.mutTrigger.Unlock() if t.isEpochStart { return } - isZeroEpochEdgeCase := nonce < minimumNonceToStartEpoch - isNormalEpochStart := t.currentRound > t.currEpochStartRound+t.roundsPerEpoch - isWithEarlyEndOfEpoch := t.currentRound >= t.nextEpochStartRound - shouldTriggerEpochStart := (isNormalEpochStart || isWithEarlyEndOfEpoch) && !isZeroEpochEdgeCase - if shouldTriggerEpochStart { - t.epoch += 1 - t.isEpochStart = true - t.prevEpochStartRound = t.currEpochStartRound - t.currEpochStartRound = t.currentRound - - msg := fmt.Sprintf("EPOCH %d BEGINS IN ROUND (%d)", t.epoch, t.currentRound) - log.Debug(display.Headline(msg, "", "#")) - log.Debug("trigger.Update", "isEpochStart", t.isEpochStart) - logger.SetCorrelationEpoch(t.epoch) - t.nextEpochStartRound = disabledRoundForForceEpochStart + if t.shouldTriggerEpochStart(round, nonce) { + t.setEpochChange(round, t.epoch+1, true) } } +// SetEpochChange will increment the epoch field and all fields related with epoch change +func (t *trigger) SetEpochChange(round uint64) { + t.mutTrigger.Lock() + defer t.mutTrigger.Unlock() + + t.setEpochChange(round, t.epoch+1, true) +} + +func (t *trigger) setEpochChange(round uint64, epoch uint32, isEpochStart bool) { + t.epoch = epoch + t.isEpochStart = isEpochStart + t.prevEpochStartRound = t.currEpochStartRound + t.currEpochStartRound = round + + msg := fmt.Sprintf("EPOCH %d BEGINS IN ROUND (%d)", t.epoch, t.currEpochStartRound) + log.Debug(display.Headline(msg, "", "#")) + log.Debug("trigger.Update", "isEpochStart", t.isEpochStart) + logger.SetCorrelationEpoch(t.epoch) + t.nextEpochStartRound = disabledRoundForForceEpochStart +} + // SetProcessed sets start of epoch to false and cleans underlying structure func (t *trigger) SetProcessed(header data.HeaderHandler, body data.BodyHandler) { t.mutTrigger.Lock() defer t.mutTrigger.Unlock() - metaBlock, ok := header.(*block.MetaBlock) + metaBlock, ok := header.(data.MetaHeaderHandler) if !ok { return } @@ -239,31 +306,35 @@ func (t *trigger) SetProcessed(header data.HeaderHandler, body data.BodyHandler) return } + if header.IsHeaderV3() { + t.setEpochChange(header.GetRound(), header.GetEpoch(), false) + } else { + t.currEpochStartRound = metaBlock.GetRound() + t.epoch = metaBlock.GetEpoch() + t.isEpochStart = false + } + metaBuff, errNotCritical := t.marshaller.Marshal(metaBlock) if errNotCritical != nil { log.Debug("SetProcessed marshal", "error", errNotCritical.Error()) } - t.appStatusHandler.SetUInt64Value(common.MetricRoundAtEpochStart, metaBlock.Round) - t.appStatusHandler.SetUInt64Value(common.MetricNonceAtEpochStart, metaBlock.Nonce) + t.appStatusHandler.SetUInt64Value(common.MetricRoundAtEpochStart, metaBlock.GetRound()) + t.appStatusHandler.SetUInt64Value(common.MetricNonceAtEpochStart, metaBlock.GetNonce()) metaHash := t.hasher.Compute(string(metaBuff)) - t.currEpochStartRound = metaBlock.Round - t.epoch = metaBlock.Epoch - t.isEpochStart = false - t.currentRound = metaBlock.Round t.epochStartMeta = metaBlock t.epochStartMetaHash = metaHash t.epochStartNotifier.NotifyAllPrepare(metaBlock, body) t.epochStartNotifier.NotifyAll(metaBlock) - t.saveCurrentState(metaBlock.Round) + t.saveCurrentState(metaBlock.GetRound()) log.Debug("trigger.SetProcessed", "isEpochStart", t.isEpochStart) - epochStartIdentifier := core.EpochStartIdentifier(metaBlock.Epoch) + epochStartIdentifier := core.EpochStartIdentifier(metaBlock.GetEpoch()) errNotCritical = t.triggerStorage.Put([]byte(epochStartIdentifier), metaBuff) if errNotCritical != nil { log.Warn("SetProcessed put into triggerStorage", "error", errNotCritical.Error()) @@ -319,10 +390,6 @@ func (t *trigger) RevertStateToBlock(header data.HeaderHandler) error { return err } - t.mutTrigger.Lock() - t.currentRound = header.GetRound() - t.mutTrigger.Unlock() - return nil } @@ -331,7 +398,7 @@ func (t *trigger) revert(header data.HeaderHandler) error { return nil } - metaHdr, ok := header.(*block.MetaBlock) + metaHdr, ok := header.(data.MetaHeaderHandler) if !ok { log.Warn("wrong type assertion in Revert metachain trigger") return epochStart.ErrWrongTypeAssertion @@ -340,21 +407,20 @@ func (t *trigger) revert(header data.HeaderHandler) error { t.mutTrigger.Lock() defer t.mutTrigger.Unlock() - prevEpochStartIdentifier := core.EpochStartIdentifier(metaHdr.Epoch - 1) + prevEpochStartIdentifier := core.EpochStartIdentifier(metaHdr.GetEpoch() - 1) epochStartMetaBuff, err := t.metaHeaderStorage.SearchFirst([]byte(prevEpochStartIdentifier)) if err != nil { log.Warn("Revert get previous meta from storage", "error", err) return err } - epochStartMeta := &block.MetaBlock{} - err = t.marshaller.Unmarshal(epochStartMeta, epochStartMetaBuff) + epochStartMeta, err := process.UnmarshalMetaHeader(t.marshaller, epochStartMetaBuff) if err != nil { log.Warn("Revert unmarshal previous meta", "error", err) return err } - epochStartIdentifier := core.EpochStartIdentifier(metaHdr.Epoch) + epochStartIdentifier := core.EpochStartIdentifier(metaHdr.GetEpoch()) errNotCritical := t.triggerStorage.Remove([]byte(epochStartIdentifier)) if errNotCritical != nil { log.Debug("Revert remove from triggerStorage", "error", errNotCritical.Error()) @@ -365,8 +431,8 @@ func (t *trigger) revert(header data.HeaderHandler) error { log.Debug("Revert remove from triggerStorage", "error", errNotCritical.Error()) } - t.currEpochStartRound = metaHdr.EpochStart.Economics.PrevEpochStartRound - t.epoch = metaHdr.Epoch - 1 + t.currEpochStartRound = metaHdr.GetEpochStartHandler().GetEconomicsHandler().GetPrevEpochStartRound() + t.epoch = metaHdr.GetEpoch() - 1 t.isEpochStart = false t.epochStartMeta = epochStartMeta @@ -429,8 +495,7 @@ func (t *trigger) GetEpochStartHdrFromStorage(epoch uint32) (data.HeaderHandler, return nil, err } - metaHdr := &block.MetaBlock{} - err = t.marshaller.Unmarshal(metaHdr, epochStartMetaBuff) + metaHdr, err := process.UnmarshalMetaHeader(t.marshaller, epochStartMetaBuff) if err != nil { return nil, err } @@ -452,7 +517,6 @@ func (t *trigger) SetEpochStartMetaHdrHash(metaHdrHash []byte) { func (t *trigger) SetCurrentEpochStartRound(round uint64) { t.mutTrigger.Lock() t.currEpochStartRound = round - t.currentRound = round t.saveCurrentState(round) t.mutTrigger.Unlock() } diff --git a/epochStart/metachain/triggerRegistry.go b/epochStart/metachain/triggerRegistry.go index 82451e9aae9..95f9ab1c143 100644 --- a/epochStart/metachain/triggerRegistry.go +++ b/epochStart/metachain/triggerRegistry.go @@ -1,10 +1,8 @@ package metachain import ( - "encoding/json" + "github.com/multiversx/mx-chain-core-go/data" - "github.com/multiversx/mx-chain-core-go/data/block" - "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/epochStart" ) @@ -19,58 +17,41 @@ func (t *trigger) LoadState(key []byte) error { return err } - state, err := UnmarshalTrigger(t.marshaller, d) + state, err := epochStart.UnmarshalMetaTrigger(t.marshaller, d) if err != nil { return err } t.mutTrigger.Lock() t.triggerStateKey = key - t.currentRound = state.CurrentRound - t.epochFinalityAttestingRound = state.EpochFinalityAttestingRound - t.currEpochStartRound = state.CurrEpochStartRound - t.prevEpochStartRound = state.PrevEpochStartRound - t.epoch = state.Epoch - t.epochStartMetaHash = state.EpochStartMetaHash - t.epochStartMeta = state.EpochStartMeta + t.epochFinalityAttestingRound = state.GetEpochFinalityAttestingRound() + t.currEpochStartRound = state.GetCurrEpochStartRound() + t.prevEpochStartRound = state.GetPrevEpochStartRound() + t.epoch = state.GetEpoch() + t.epochStartMetaHash = state.GetEpochStartMetaHash() + t.epochStartMeta = state.GetEpochStartMetaHeaderHandler() + t.epochChangeProposed = state.GetEpochChangeProposed() t.mutTrigger.Unlock() return nil } -// UnmarshalTrigger unmarshalls the trigger with json, for backwards compatibility -func UnmarshalTrigger(marshaller marshal.Marshalizer, data []byte) (*block.MetaTriggerRegistry, error) { - state := &block.MetaTriggerRegistry{ - EpochStartMeta: &block.MetaBlock{}, - } - - err := marshaller.Unmarshal(state, data) - if err == nil { - return state, nil - } - - // for backwards compatibility - err = json.Unmarshal(data, state) - if err != nil { - return nil, err - } - return state, nil -} - // saveState saves the trigger state. Needs to be called under mutex func (t *trigger) saveState(key []byte) error { - metaHeader, ok := t.epochStartMeta.(*block.MetaBlock) + metaHeader, ok := t.epochStartMeta.(data.MetaHeaderHandler) if !ok { return epochStart.ErrWrongTypeAssertion } - registry := &block.MetaTriggerRegistry{} - registry.CurrentRound = t.currentRound - registry.EpochFinalityAttestingRound = t.epochFinalityAttestingRound - registry.CurrEpochStartRound = t.currEpochStartRound - registry.PrevEpochStartRound = t.prevEpochStartRound - registry.Epoch = t.epoch - registry.EpochStartMetaHash = t.epochStartMetaHash - registry.EpochStartMeta = metaHeader + + registry := epochStart.CreateMetaRegistryHandler(t.epochStartMeta) + _ = registry.SetEpochFinalityAttestingRound(t.epochFinalityAttestingRound) + _ = registry.SetCurrEpochStartRound(t.currEpochStartRound) + _ = registry.SetPrevEpochStartRound(t.prevEpochStartRound) + _ = registry.SetEpoch(t.epoch) + _ = registry.SetEpochStartMetaHash(t.epochStartMetaHash) + _ = registry.SetEpochStartMetaHeaderHandler(metaHeader) + _ = registry.SetEpochChangeProposed(t.epochChangeProposed) + triggerData, err := t.marshaller.Marshal(registry) if err != nil { return err diff --git a/epochStart/metachain/triggerRegistry_test.go b/epochStart/metachain/triggerRegistry_test.go index f65414d94ac..7df84864131 100644 --- a/epochStart/metachain/triggerRegistry_test.go +++ b/epochStart/metachain/triggerRegistry_test.go @@ -6,12 +6,13 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/storage" "github.com/multiversx/mx-chain-go/testscommon/genericMocks" storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" - "github.com/stretchr/testify/require" ) func cloneTrigger(t *trigger) *trigger { @@ -19,12 +20,10 @@ func cloneTrigger(t *trigger) *trigger { rt.isEpochStart = t.isEpochStart rt.epoch = t.epoch - rt.currentRound = t.currentRound rt.epochFinalityAttestingRound = t.epochFinalityAttestingRound rt.currEpochStartRound = t.currEpochStartRound rt.prevEpochStartRound = t.prevEpochStartRound - rt.roundsPerEpoch = t.roundsPerEpoch - rt.minRoundsBetweenEpochs = t.minRoundsBetweenEpochs + rt.epochStartMeta = t.epochStartMeta rt.epochStartMetaHash = t.epochStartMetaHash rt.triggerStateKey = t.triggerStateKey rt.epochStartTime = t.epochStartTime @@ -36,6 +35,9 @@ func cloneTrigger(t *trigger) *trigger { rt.appStatusHandler = t.appStatusHandler rt.nextEpochStartRound = t.nextEpochStartRound rt.validatorInfoPool = t.validatorInfoPool + rt.chainParametersHandler = t.chainParametersHandler + rt.epochChangeProposed = t.epochChangeProposed + rt.triggerStorage = t.triggerStorage return rt } @@ -60,7 +62,6 @@ func TestTrigger_LoadStateAfterSave(t *testing.T) { key := []byte("key") epochStartTrigger1.triggerStateKey = key epochStartTrigger1.epoch = 6 - epochStartTrigger1.currentRound = 1000 epochStartTrigger1.epochFinalityAttestingRound = 998 epochStartTrigger1.currEpochStartRound = 800 epochStartTrigger1.prevEpochStartRound = 650 @@ -93,7 +94,6 @@ func TestTrigger_LoadStateBackwardsCompatibility(t *testing.T) { key := []byte("key") epochStartTrigger1.triggerStateKey = key epochStartTrigger1.epoch = 6 - epochStartTrigger1.currentRound = 1000 epochStartTrigger1.epochFinalityAttestingRound = 998 epochStartTrigger1.currEpochStartRound = 800 epochStartTrigger1.prevEpochStartRound = 650 @@ -111,6 +111,43 @@ func TestTrigger_LoadStateBackwardsCompatibility(t *testing.T) { require.Equal(t, epochStartTrigger1, epochStartTrigger2) } +func TestTrigger_LoadHeaderV3StateAfterSave(t *testing.T) { + t.Parallel() + + epoch := uint32(5) + arguments := createMockEpochStartTriggerArguments() + arguments.Epoch = epoch + bootStorer := genericMocks.NewStorerMock() + + arguments.Storage = &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return bootStorer, nil + }, + } + + epochStartTrigger1, _ := NewEpochStartTrigger(arguments) + // create a copy + epochStartTrigger2 := cloneTrigger(epochStartTrigger1) + + key := []byte("key") + epochStartTrigger1.triggerStateKey = key + epochStartTrigger1.epoch = 6 + epochStartTrigger1.epochFinalityAttestingRound = 998 + epochStartTrigger1.currEpochStartRound = 800 + epochStartTrigger1.prevEpochStartRound = 650 + + epochStartTrigger1.epochStartMeta = &block.MetaBlockV3{ + EpochChangeProposed: true, + } + err := epochStartTrigger1.saveState(key) + require.Nil(t, err) + require.NotEqual(t, epochStartTrigger1, epochStartTrigger2) + + err = epochStartTrigger2.LoadState(key) + require.Nil(t, err) + require.Equal(t, epochStartTrigger1, epochStartTrigger2) +} + type legacyTriggerRegistry struct { Epoch uint32 CurrentRound uint64 @@ -122,10 +159,9 @@ type legacyTriggerRegistry struct { } func createLegacyTriggerRegistryFromTrigger(t *trigger) *legacyTriggerRegistry { - metaBlock, _ := t.epochStartMeta.(*block.MetaBlock) + metaBlock, _ := t.epochStartMeta.(data.MetaHeaderHandler) return &legacyTriggerRegistry{ Epoch: t.epoch, - CurrentRound: t.currentRound, EpochFinalityAttestingRound: t.epochFinalityAttestingRound, CurrEpochStartRound: t.currEpochStartRound, PrevEpochStartRound: t.prevEpochStartRound, diff --git a/epochStart/metachain/trigger_test.go b/epochStart/metachain/trigger_test.go index c30a9cf4bd6..1ef55403afe 100644 --- a/epochStart/metachain/trigger_test.go +++ b/epochStart/metachain/trigger_test.go @@ -11,30 +11,31 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/epochStart" "github.com/multiversx/mx-chain-go/epochStart/mock" + "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/storage" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" statusHandlerMock "github.com/multiversx/mx-chain-go/testscommon/statusHandler" storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" vic "github.com/multiversx/mx-chain-go/testscommon/validatorInfoCacher" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func createMockEpochStartTriggerArguments() *ArgsNewMetaEpochStartTrigger { return &ArgsNewMetaEpochStartTrigger{ - GenesisTime: time.Time{}, - Settings: &config.EpochStartConfig{ - MinRoundsBetweenEpochs: 1, - RoundsPerEpoch: 2, - }, + GenesisTime: time.Time{}, + Settings: &config.EpochStartConfig{}, Epoch: 0, EpochStartNotifier: &mock.EpochStartNotifierStub{}, - Marshalizer: &mock.MarshalizerMock{}, + Marshalizer: &marshal.GogoProtoMarshalizer{}, Hasher: &hashingMocks.HasherMock{}, AppStatusHandler: &statusHandlerMock.AppStatusHandlerStub{}, Storage: &storageStubs.ChainStorerStub{ @@ -60,6 +61,14 @@ func createMockEpochStartTriggerArguments() *ArgsNewMetaEpochStartTrigger { return &vic.ValidatorInfoCacherStub{} }, }, + ChainParametersHandler: &chainParameters.ChainParametersHandlerStub{ + CurrentChainParametersCalled: func() config.ChainParametersByEpochConfig { + return config.ChainParametersByEpochConfig{ + RoundsPerEpoch: 2, + MinRoundsBetweenEpochs: 1, + } + }, + }, } } @@ -83,15 +92,15 @@ func TestNewEpochStartTrigger_NilSettingsShouldErr(t *testing.T) { assert.Equal(t, epochStart.ErrNilEpochStartSettings, err) } -func TestNewEpochStartTrigger_InvalidSettingsShouldErr(t *testing.T) { +func TestNewEpochStartTrigger_NilChainParametersHandler(t *testing.T) { t.Parallel() arguments := createMockEpochStartTriggerArguments() - arguments.Settings.RoundsPerEpoch = 0 + arguments.ChainParametersHandler = nil epochStartTrigger, err := NewEpochStartTrigger(arguments) assert.Nil(t, epochStartTrigger) - assert.True(t, errors.Is(err, epochStart.ErrInvalidSettingsForEpochStartTrigger)) + assert.True(t, errors.Is(err, process.ErrNilChainParametersHandler)) } func TestNewEpochStartTrigger_NilEpochStartNotifierShouldErr(t *testing.T) { @@ -105,30 +114,6 @@ func TestNewEpochStartTrigger_NilEpochStartNotifierShouldErr(t *testing.T) { assert.True(t, errors.Is(err, epochStart.ErrNilEpochStartNotifier)) } -func TestNewEpochStartTrigger_InvalidSettingsShouldErr2(t *testing.T) { - t.Parallel() - - arguments := createMockEpochStartTriggerArguments() - arguments.Settings.RoundsPerEpoch = 1 - arguments.Settings.MinRoundsBetweenEpochs = 0 - - epochStartTrigger, err := NewEpochStartTrigger(arguments) - assert.Nil(t, epochStartTrigger) - assert.True(t, errors.Is(err, epochStart.ErrInvalidSettingsForEpochStartTrigger)) -} - -func TestNewEpochStartTrigger_InvalidSettingsShouldErr3(t *testing.T) { - t.Parallel() - - arguments := createMockEpochStartTriggerArguments() - arguments.Settings.RoundsPerEpoch = 4 - arguments.Settings.MinRoundsBetweenEpochs = 6 - - epochStartTrigger, err := NewEpochStartTrigger(arguments) - assert.Nil(t, epochStartTrigger) - assert.True(t, errors.Is(err, epochStart.ErrInvalidSettingsForEpochStartTrigger)) -} - func TestNewEpochStartTrigger_MissingBootstrapUnit(t *testing.T) { t.Parallel() @@ -175,6 +160,61 @@ func TestNewEpochStartTrigger_ShouldOk(t *testing.T) { assert.Nil(t, err) } +func TestNewEpochStartTrigger_ShouldProposeEpochChange(t *testing.T) { + t.Parallel() + + arguments := createMockEpochStartTriggerArguments() + arguments.ChainParametersHandler = &chainParameters.ChainParametersHandlerStub{ + CurrentChainParametersCalled: func() config.ChainParametersByEpochConfig { + return config.ChainParametersByEpochConfig{ + RoundsPerEpoch: 200, + MinRoundsBetweenEpochs: 1, + Offset: 2, + } + }, + ChainParametersForEpochCalled: func(epoch uint32) (config.ChainParametersByEpochConfig, error) { + if epoch == 1 { + return config.ChainParametersByEpochConfig{ + RoundsPerEpoch: 200, + Offset: 0, + }, nil + } + if epoch == 2 { + return config.ChainParametersByEpochConfig{ + RoundsPerEpoch: 200, + Offset: 2, + }, nil + } + return config.ChainParametersByEpochConfig{}, errExpected + }, + } + + arguments.EpochStartRound = 100 + arguments.Epoch = 1 + + epochStartTrigger, err := NewEpochStartTrigger(arguments) + require.NotNil(t, epochStartTrigger) + require.Nil(t, err) + + nonce := uint64(100) + + round := uint64(300) + shouldProposeEpochChange := epochStartTrigger.ShouldProposeEpochChange(round, nonce) + require.False(t, shouldProposeEpochChange) + + round = uint64(301) + shouldProposeEpochChange = epochStartTrigger.ShouldProposeEpochChange(round, nonce) + require.True(t, shouldProposeEpochChange) + + epochStartTrigger.epoch = 2 + epochStartTrigger.currEpochStartRound = 301 + + round = 500 + shouldProposeEpochChange = epochStartTrigger.ShouldProposeEpochChange(round, nonce) + require.True(t, shouldProposeEpochChange) + require.False(t, epochStartTrigger.IsEpochStart()) +} + func TestTrigger_Update(t *testing.T) { t.Parallel() @@ -217,10 +257,7 @@ func TestTrigger_ForceEpochStartCloseToNormalEpochStartShouldNotForce(t *testing t.Parallel() arguments := createMockEpochStartTriggerArguments() - arguments.Settings.MinRoundsBetweenEpochs = 20 - arguments.Settings.RoundsPerEpoch = 200 epochStartTrigger, _ := NewEpochStartTrigger(arguments) - epochStartTrigger.currentRound = 20 epochStartTrigger.ForceEpochStart(201) assert.Equal(t, uint64(math.MaxUint64), epochStartTrigger.nextEpochStartRound) @@ -230,13 +267,18 @@ func TestTrigger_ForceEpochStartUnderMinimumBetweenEpochs(t *testing.T) { t.Parallel() arguments := createMockEpochStartTriggerArguments() - arguments.Settings.MinRoundsBetweenEpochs = 20 - arguments.Settings.RoundsPerEpoch = 200 + arguments.ChainParametersHandler = &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{ + MinRoundsBetweenEpochs: 20, + RoundsPerEpoch: 200, + }, nil + }, + } epochStartTrigger, _ := NewEpochStartTrigger(arguments) - epochStartTrigger.currentRound = 1 epochStartTrigger.ForceEpochStart(10) - assert.Equal(t, uint64(arguments.Settings.MinRoundsBetweenEpochs), epochStartTrigger.nextEpochStartRound) + assert.Equal(t, uint64(20), epochStartTrigger.nextEpochStartRound) } func TestTrigger_ForceEpochStartShouldOk(t *testing.T) { @@ -244,14 +286,19 @@ func TestTrigger_ForceEpochStartShouldOk(t *testing.T) { epoch := uint32(0) arguments := createMockEpochStartTriggerArguments() - arguments.Settings.MinRoundsBetweenEpochs = 20 - arguments.Settings.RoundsPerEpoch = 200 + arguments.ChainParametersHandler = &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(epoch uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{ + MinRoundsBetweenEpochs: 20, + RoundsPerEpoch: 200, + }, nil + }, + } + arguments.Epoch = epoch epochStartTrigger, err := NewEpochStartTrigger(arguments) require.Nil(t, err) - epochStartTrigger.currentRound = 50 - expectedRound := uint64(60) epochStartTrigger.ForceEpochStart(60) @@ -263,6 +310,34 @@ func TestTrigger_ForceEpochStartShouldOk(t *testing.T) { assert.True(t, isEpochStart) } +func TestTrigger_ForceEpochStartShouldWorkForSupernovaEpoch(t *testing.T) { + t.Parallel() + + epoch := uint32(2) + roundsPerEpoch := 200 + arguments := createMockEpochStartTriggerArguments() + arguments.ChainParametersHandler = &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(epoch uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{ + MinRoundsBetweenEpochs: 20, + RoundsPerEpoch: int64(roundsPerEpoch), + Offset: 2, + }, nil + }, + } + + arguments.Epoch = epoch + arguments.EpochStartRound = 100 + epochStartTrigger, err := NewEpochStartTrigger(arguments) + require.Nil(t, err) + + epochStartTrigger.ForceEpochStart(298) + assert.Equal(t, uint64(298), epochStartTrigger.nextEpochStartRound) + + epochStartTrigger.ForceEpochStart(299) + assert.Equal(t, uint64(disabledRoundForForceEpochStart), epochStartTrigger.nextEpochStartRound) +} + func TestTrigger_LastCommitedMetaEpochStartBlock(t *testing.T) { t.Parallel() @@ -462,3 +537,29 @@ func TestTrigger_RevertBehindEpochStartBlock(t *testing.T) { ret = epochStartTrigger.IsEpochStart() assert.False(t, ret) } + +func TestTrigger_SetProcessedHeaderV3(t *testing.T) { + t.Parallel() + + args := createMockEpochStartTriggerArguments() + + wasNotifyAllCalled := false + wasNotifyAllPrepareCalled := false + args.EpochStartNotifier = &mock.EpochStartNotifierStub{ + NotifyAllCalled: func(hdr data.HeaderHandler) { + wasNotifyAllCalled = true + }, + NotifyAllPrepareCalled: func(hdr data.HeaderHandler, body data.BodyHandler) { + wasNotifyAllPrepareCalled = true + }, + } + + epochStartTrigger, _ := NewEpochStartTrigger(args) + + header := &block.MetaBlockV3{Nonce: 4, EpochStart: block.EpochStart{LastFinalizedHeaders: make([]block.EpochStartShardData, 1)}} + epochStartTrigger.SetProcessed(header, &block.Body{}) + + require.True(t, wasNotifyAllCalled) + require.True(t, wasNotifyAllPrepareCalled) + require.False(t, epochStartTrigger.isEpochStart) +} diff --git a/epochStart/mock/coreComponentsMock.go b/epochStart/mock/coreComponentsMock.go index e02642b3538..e1888d7313f 100644 --- a/epochStart/mock/coreComponentsMock.go +++ b/epochStart/mock/coreComponentsMock.go @@ -3,6 +3,9 @@ package mock import ( "sync" + "github.com/multiversx/mx-chain-go/ntp" + "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data/endProcess" "github.com/multiversx/mx-chain-core-go/data/typeConverters" @@ -40,9 +43,17 @@ type CoreComponentsMock struct { ChainParametersSubscriberField process.ChainParametersSubscriber FieldsSizeCheckerField common.FieldsSizeChecker EpochChangeGracePeriodHandlerField common.EpochChangeGracePeriodHandler + ProcessConfigsHandlerField common.ProcessConfigsHandler + CommonConfigsHandlerField common.CommonConfigsHandler + AntifloodConfigsHandlerField common.AntifloodConfigsHandler mutCore sync.RWMutex } +// SyncTimer - +func (ccm *CoreComponentsMock) SyncTimer() ntp.SyncTimer { + return &testscommon.SyncTimerStub{} +} + // ChanStopNodeProcess - func (ccm *CoreComponentsMock) ChanStopNodeProcess() chan endProcess.ArgEndProcess { ccm.mutCore.RLock() @@ -185,6 +196,21 @@ func (ccm *CoreComponentsMock) EpochChangeGracePeriodHandler() common.EpochChang return ccm.EpochChangeGracePeriodHandlerField } +// ProcessConfigsHandler - +func (ccm *CoreComponentsMock) ProcessConfigsHandler() common.ProcessConfigsHandler { + return ccm.ProcessConfigsHandlerField +} + +// CommonConfigsHandler - +func (ccm *CoreComponentsMock) CommonConfigsHandler() common.CommonConfigsHandler { + return ccm.CommonConfigsHandlerField +} + +// AntifloodConfigsHandler - +func (ccm *CoreComponentsMock) AntifloodConfigsHandler() common.AntifloodConfigsHandler { + return ccm.AntifloodConfigsHandlerField +} + // IsInterfaceNil - func (ccm *CoreComponentsMock) IsInterfaceNil() bool { return ccm == nil diff --git a/epochStart/mock/cryptoComponentsMock.go b/epochStart/mock/cryptoComponentsMock.go index 4dfbe6d91a5..f2113ad5aab 100644 --- a/epochStart/mock/cryptoComponentsMock.go +++ b/epochStart/mock/cryptoComponentsMock.go @@ -4,7 +4,7 @@ import ( "errors" "sync" - "github.com/multiversx/mx-chain-crypto-go" + crypto "github.com/multiversx/mx-chain-crypto-go" "github.com/multiversx/mx-chain-go/common" cryptoCommon "github.com/multiversx/mx-chain-go/common/crypto" ) @@ -44,7 +44,7 @@ func (ccm *CryptoComponentsMock) TxSingleSigner() crypto.SingleSigner { } // GetMultiSigner - -func (ccm *CryptoComponentsMock) GetMultiSigner(epoch uint32) (crypto.MultiSigner, error) { +func (ccm *CryptoComponentsMock) GetMultiSigner(epoch uint32) (crypto.MultiSignerV2, error) { ccm.mutCrypto.RLock() defer ccm.mutCrypto.RUnlock() @@ -103,6 +103,7 @@ func (ccm *CryptoComponentsMock) Clone() interface{} { BlKeyGen: ccm.BlKeyGen, TxKeyGen: ccm.TxKeyGen, mutCrypto: sync.RWMutex{}, + ManagedPeers: ccm.ManagedPeers, } } diff --git a/epochStart/mock/epochStartMetaBlockProcessorStub.go b/epochStart/mock/epochStartMetaBlockProcessorStub.go index f5bad5d25d5..3f03a504ba1 100644 --- a/epochStart/mock/epochStartMetaBlockProcessorStub.go +++ b/epochStart/mock/epochStartMetaBlockProcessorStub.go @@ -5,13 +5,19 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process" ) // EpochStartMetaBlockProcessorStub - type EpochStartMetaBlockProcessorStub struct { - ValidateCalled func(data process.InterceptedData, fromConnectedPeer core.PeerID) error - SaveCalled func(data process.InterceptedData, fromConnectedPeer core.PeerID, topic string) error + ValidateCalled func(data process.InterceptedData, fromConnectedPeer core.PeerID) error + SaveCalled func( + data process.InterceptedData, + fromConnectedPeer core.PeerID, + topic string, + broadcastMethod p2p.BroadcastMethod, + ) (bool, error) RegisterHandlerCalled func(handler func(topic string, hash []byte, data interface{})) GetEpochStartMetaBlockCalled func(ctx context.Context) (data.MetaHeaderHandler, error) } @@ -26,12 +32,12 @@ func (esmbps *EpochStartMetaBlockProcessorStub) Validate(data process.Intercepte } // Save - -func (esmbps *EpochStartMetaBlockProcessorStub) Save(data process.InterceptedData, fromConnectedPeer core.PeerID, topic string) error { +func (esmbps *EpochStartMetaBlockProcessorStub) Save(data process.InterceptedData, fromConnectedPeer core.PeerID, topic string, broadcastMethod p2p.BroadcastMethod) (bool, error) { if esmbps.SaveCalled != nil { - return esmbps.SaveCalled(data, fromConnectedPeer, topic) + return esmbps.SaveCalled(data, fromConnectedPeer, topic, broadcastMethod) } - return nil + return true, nil } // RegisterHandler - diff --git a/epochStart/mock/headerIntegrityVerifierStub.go b/epochStart/mock/headerIntegrityVerifierStub.go index 3d793b89924..5fa049cd3bf 100644 --- a/epochStart/mock/headerIntegrityVerifierStub.go +++ b/epochStart/mock/headerIntegrityVerifierStub.go @@ -5,7 +5,7 @@ import "github.com/multiversx/mx-chain-core-go/data" // HeaderIntegrityVerifierStub - type HeaderIntegrityVerifierStub struct { VerifyCalled func(header data.HeaderHandler) error - GetVersionCalled func(epoch uint32) string + GetVersionCalled func(epoch uint32, round uint64) string } // Verify - @@ -18,9 +18,9 @@ func (h *HeaderIntegrityVerifierStub) Verify(header data.HeaderHandler) error { } // GetVersion - -func (h *HeaderIntegrityVerifierStub) GetVersion(epoch uint32) string { +func (h *HeaderIntegrityVerifierStub) GetVersion(epoch uint32, round uint64) string { if h.GetVersionCalled != nil { - return h.GetVersionCalled(epoch) + return h.GetVersionCalled(epoch, round) } return "version" diff --git a/epochStart/mock/interceptedMetaBlockMock.go b/epochStart/mock/interceptedMetaBlockMock.go index 0fb2708e107..b59ad96888f 100644 --- a/epochStart/mock/interceptedMetaBlockMock.go +++ b/epochStart/mock/interceptedMetaBlockMock.go @@ -27,6 +27,11 @@ func (i *interceptedMetaBlockMock) CheckValidity() error { return nil } +// ShouldAllowDuplicates - +func (i *interceptedMetaBlockMock) ShouldAllowDuplicates() bool { + return true +} + // IsForCurrentShard - func (i *interceptedMetaBlockMock) IsForCurrentShard() bool { return true diff --git a/epochStart/mock/metaBlockInterceptorProcessorStub.go b/epochStart/mock/metaBlockInterceptorProcessorStub.go index fbe2198e5c1..ae09cb74281 100644 --- a/epochStart/mock/metaBlockInterceptorProcessorStub.go +++ b/epochStart/mock/metaBlockInterceptorProcessorStub.go @@ -6,6 +6,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process" ) @@ -20,8 +21,8 @@ func (m *MetaBlockInterceptorProcessorStub) Validate(_ process.InterceptedData, } // Save - -func (m *MetaBlockInterceptorProcessorStub) Save(_ process.InterceptedData, _ core.PeerID, _ string) error { - return nil +func (m *MetaBlockInterceptorProcessorStub) Save(_ process.InterceptedData, _ core.PeerID, _ string, _ p2p.BroadcastMethod) (bool, error) { + return true, nil } // RegisterHandler - diff --git a/epochStart/mock/raterStub.go b/epochStart/mock/raterStub.go index 870e31a1612..5ecc21a1482 100644 --- a/epochStart/mock/raterStub.go +++ b/epochStart/mock/raterStub.go @@ -5,11 +5,11 @@ type RaterStub struct { GetRatingCalled func(string) uint32 GetStartRatingCalled func() uint32 GetSignedBlocksThresholdCalled func() float32 - ComputeIncreaseProposerCalled func(shardId uint32, rating uint32) uint32 - ComputeDecreaseProposerCalled func(shardId uint32, rating uint32, consecutiveMissedBlocks uint32) uint32 - RevertIncreaseProposerCalled func(shardId uint32, rating uint32, nrReverts uint32) uint32 - ComputeIncreaseValidatorCalled func(shardId uint32, rating uint32) uint32 - ComputeDecreaseValidatorCalled func(shardId uint32, rating uint32) uint32 + ComputeIncreaseProposerCalled func(shardId uint32, rating uint32, epoch uint32) uint32 + ComputeDecreaseProposerCalled func(shardId uint32, rating uint32, consecutiveMissedBlocks uint32, epoch uint32) uint32 + RevertIncreaseValidatorCalled func(shardId uint32, rating uint32, nrReverts uint32, epoch uint32) uint32 + ComputeIncreaseValidatorCalled func(shardId uint32, rating uint32, epoch uint32) uint32 + ComputeDecreaseValidatorCalled func(shardId uint32, rating uint32, epoch uint32) uint32 GetChanceCalled func(rating uint32) uint32 } @@ -32,28 +32,28 @@ func (rm *RaterStub) GetSignedBlocksThreshold() float32 { } // ComputeIncreaseProposer - -func (rm *RaterStub) ComputeIncreaseProposer(shardId uint32, currentRating uint32) uint32 { - return rm.ComputeIncreaseProposerCalled(shardId, currentRating) +func (rm *RaterStub) ComputeIncreaseProposer(shardId uint32, currentRating uint32, epoch uint32) uint32 { + return rm.ComputeIncreaseProposerCalled(shardId, currentRating, epoch) } // ComputeDecreaseProposer - -func (rm *RaterStub) ComputeDecreaseProposer(shardId uint32, currentRating uint32, consecutiveMisses uint32) uint32 { - return rm.ComputeDecreaseProposerCalled(shardId, currentRating, consecutiveMisses) +func (rm *RaterStub) ComputeDecreaseProposer(shardId uint32, currentRating uint32, consecutiveMisses uint32, epoch uint32) uint32 { + return rm.ComputeDecreaseProposerCalled(shardId, currentRating, consecutiveMisses, epoch) } // RevertIncreaseValidator - -func (rm *RaterStub) RevertIncreaseValidator(shardId uint32, currentRating uint32, nrReverts uint32) uint32 { - return rm.RevertIncreaseProposerCalled(shardId, currentRating, nrReverts) +func (rm *RaterStub) RevertIncreaseValidator(shardId uint32, currentRating uint32, nrReverts uint32, epoch uint32) uint32 { + return rm.RevertIncreaseValidatorCalled(shardId, currentRating, nrReverts, epoch) } // ComputeIncreaseValidator - -func (rm *RaterStub) ComputeIncreaseValidator(shardId uint32, currentRating uint32) uint32 { - return rm.ComputeIncreaseValidatorCalled(shardId, currentRating) +func (rm *RaterStub) ComputeIncreaseValidator(shardId uint32, currentRating uint32, epoch uint32) uint32 { + return rm.ComputeIncreaseValidatorCalled(shardId, currentRating, epoch) } // ComputeDecreaseValidator - -func (rm *RaterStub) ComputeDecreaseValidator(shardId uint32, currentRating uint32) uint32 { - return rm.ComputeDecreaseValidatorCalled(shardId, currentRating) +func (rm *RaterStub) ComputeDecreaseValidator(shardId uint32, currentRating uint32, epoch uint32) uint32 { + return rm.ComputeDecreaseValidatorCalled(shardId, currentRating, epoch) } // GetChance - diff --git a/epochStart/mock/syncTimerStub.go b/epochStart/mock/syncTimerStub.go deleted file mode 100644 index 8e9bb19c31e..00000000000 --- a/epochStart/mock/syncTimerStub.go +++ /dev/null @@ -1,43 +0,0 @@ -package mock - -import ( - "time" -) - -// SyncTimerStub is a mock implementation of SyncTimer interface -type SyncTimerStub struct { - StartSyncingTimeCalled func() - ClockOffsetCalled func() time.Duration - FormattedCurrentTimeCalled func() string - CurrentTimeCalled func() time.Time -} - -// StartSyncingTime is a mock implementation for StartSyncingTime -func (sts *SyncTimerStub) StartSyncingTime() { - sts.StartSyncingTimeCalled() -} - -// ClockOffset is a mock implementation for ClockOffset -func (sts *SyncTimerStub) ClockOffset() time.Duration { - return sts.ClockOffsetCalled() -} - -// FormattedCurrentTime is a mock implementation for FormattedCurrentTime -func (sts *SyncTimerStub) FormattedCurrentTime() string { - return sts.FormattedCurrentTimeCalled() -} - -// CurrentTime is a mock implementation for CurrentTime -func (sts *SyncTimerStub) CurrentTime() time.Time { - return sts.CurrentTimeCalled() -} - -// Close - -func (sts *SyncTimerStub) Close() error { - return nil -} - -// IsInterfaceNil returns true if there is no value under the interface -func (sts *SyncTimerStub) IsInterfaceNil() bool { - return sts == nil -} diff --git a/epochStart/notifier/epochStartSubscriptionHandler.go b/epochStart/notifier/epochStartSubscriptionHandler.go index 3d2041189ce..da4e78942f3 100644 --- a/epochStart/notifier/epochStartSubscriptionHandler.go +++ b/epochStart/notifier/epochStartSubscriptionHandler.go @@ -2,12 +2,12 @@ package notifier import ( "runtime/debug" - "sort" "sync" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/epochStart" ) @@ -57,9 +57,19 @@ func (essh *epochStartSubscriptionHandler) RegisterHandler(handler epochStart.Ac } essh.epochStartHandlers = append(essh.epochStartHandlers, handler) - sort.Slice(essh.epochStartHandlers, func(i, j int) bool { - return essh.epochStartHandlers[i].NotifyOrder() < essh.epochStartHandlers[j].NotifyOrder() - }) +} + +func canBeTriggeredAsync(notifyOrder uint32) bool { + switch notifyOrder { + case common.EpochTxBroadcastDebug: + return true + case common.OldDatabaseCleanOrder: + return true + case common.NetStatisticsOrder: + return true + } + + return false } // UnregisterHandler will unsubscribe a function from the slice @@ -80,7 +90,11 @@ func (essh *epochStartSubscriptionHandler) UnregisterHandler(handlerToUnregister func (essh *epochStartSubscriptionHandler) NotifyAll(hdr data.HeaderHandler) { essh.mutEpochStartHandler.RLock() for i := 0; i < len(essh.epochStartHandlers); i++ { - essh.epochStartHandlers[i].EpochStartAction(hdr) + if canBeTriggeredAsync(essh.epochStartHandlers[i].NotifyOrder()) { + go essh.epochStartHandlers[i].EpochStartAction(hdr) + } else { + essh.epochStartHandlers[i].EpochStartAction(hdr) + } } essh.mutEpochStartHandler.RUnlock() } diff --git a/epochStart/notifier/epochStartSubscriptionHandler_test.go b/epochStart/notifier/epochStartSubscriptionHandler_test.go index cfd5d2787a9..cb0e17b6c2d 100644 --- a/epochStart/notifier/epochStartSubscriptionHandler_test.go +++ b/epochStart/notifier/epochStartSubscriptionHandler_test.go @@ -3,11 +3,14 @@ package notifier_test import ( "sync" "testing" + "time" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" - "github.com/multiversx/mx-chain-go/epochStart/notifier" "github.com/stretchr/testify/assert" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/epochStart/notifier" ) func TestNewEpochStartSubscriptionHandler(t *testing.T) { @@ -107,34 +110,90 @@ func TestEpochStartSubscriptionHandler_UnregisterHandlerOkHandlerShouldRemove(t func TestEpochStartSubscriptionHandler_NotifyAll(t *testing.T) { t.Parallel() - calledHandlers := make(map[int]struct{}) - calledHandlersIndices := make([]int, 0) - essh := notifier.NewEpochStartSubscriptionHandler() - - handler1 := notifier.NewHandlerForEpochStart(func(hdr data.HeaderHandler) { - calledHandlers[1] = struct{}{} - calledHandlersIndices = append(calledHandlersIndices, 1) - }, nil, 1) - handler2 := notifier.NewHandlerForEpochStart(func(hdr data.HeaderHandler) { - calledHandlers[2] = struct{}{} - calledHandlersIndices = append(calledHandlersIndices, 2) - }, nil, 2) - handler3 := notifier.NewHandlerForEpochStart(func(hdr data.HeaderHandler) { - calledHandlers[3] = struct{}{} - calledHandlersIndices = append(calledHandlersIndices, 3) - }, nil, 3) - - essh.RegisterHandler(handler2) - essh.RegisterHandler(handler1) - essh.RegisterHandler(handler3) - - // make sure that the handler were not called yet - assert.Empty(t, calledHandlers) - - // now we call the NotifyAll method and all handlers should be called - essh.NotifyAll(&block.Header{}) - assert.Len(t, calledHandlers, 3) - assert.Equal(t, []int{1, 2, 3}, calledHandlersIndices) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + calledHandlersLock := &sync.RWMutex{} + calledHandlers := make(map[int]struct{}) + essh := notifier.NewEpochStartSubscriptionHandler() + + handler1 := notifier.NewHandlerForEpochStart(func(hdr data.HeaderHandler) { + calledHandlersLock.Lock() + calledHandlers[1] = struct{}{} + calledHandlersLock.Unlock() + }, nil, 1) + handler2 := notifier.NewHandlerForEpochStart(func(hdr data.HeaderHandler) { + calledHandlersLock.Lock() + calledHandlers[2] = struct{}{} + calledHandlersLock.Unlock() + }, nil, 2) + handler3 := notifier.NewHandlerForEpochStart(func(hdr data.HeaderHandler) { + calledHandlersLock.Lock() + calledHandlers[3] = struct{}{} + calledHandlersLock.Unlock() + }, nil, 3) + + essh.RegisterHandler(handler2) + essh.RegisterHandler(handler1) + essh.RegisterHandler(handler3) + + // make sure that the handler were not called yet + assert.Empty(t, calledHandlers) + + // now we call the NotifyAll method and all handlers should be called + essh.NotifyAll(&block.Header{}) + + time.Sleep(10 * time.Millisecond) + calledHandlersLock.RLock() + assert.Len(t, calledHandlers, 3) + calledHandlersLock.RUnlock() + }) + + t.Run("should work with async action handler", func(t *testing.T) { + t.Parallel() + + calledHandlersLock := &sync.RWMutex{} + calledHandlers := make(map[int]struct{}) + essh := notifier.NewEpochStartSubscriptionHandler() + + handler1 := notifier.NewHandlerForEpochStart(func(hdr data.HeaderHandler) { + calledHandlersLock.Lock() + calledHandlers[1] = struct{}{} + calledHandlersLock.Unlock() + }, nil, 1) + handler2 := notifier.NewHandlerForEpochStart(func(hdr data.HeaderHandler) { + calledHandlersLock.Lock() + calledHandlers[2] = struct{}{} + calledHandlersLock.Unlock() + }, nil, common.EpochTxBroadcastDebug) + handler3 := notifier.NewHandlerForEpochStart(func(hdr data.HeaderHandler) { + calledHandlersLock.Lock() + calledHandlers[3] = struct{}{} + calledHandlersLock.Unlock() + }, nil, common.OldDatabaseCleanOrder) + handler4 := notifier.NewHandlerForEpochStart(func(hdr data.HeaderHandler) { + calledHandlersLock.Lock() + calledHandlers[4] = struct{}{} + calledHandlersLock.Unlock() + }, nil, common.NetStatisticsOrder) + + essh.RegisterHandler(handler2) + essh.RegisterHandler(handler1) + essh.RegisterHandler(handler3) + essh.RegisterHandler(handler4) + + // make sure that the handler were not called yet + assert.Empty(t, calledHandlers) + + // now we call the NotifyAll method and all handlers should be called + essh.NotifyAll(&block.Header{}) + + time.Sleep(10 * time.Millisecond) + + calledHandlersLock.RLock() + defer calledHandlersLock.RUnlock() + assert.Len(t, calledHandlers, 4) + }) } func TestEpochStartSubscriptionHandler_ConcurrentOperations(t *testing.T) { diff --git a/epochStart/shardchain/peerMiniBlocksSyncer.go b/epochStart/shardchain/peerMiniBlocksSyncer.go index f5da3bbcca7..d5eaec41280 100644 --- a/epochStart/shardchain/peerMiniBlocksSyncer.go +++ b/epochStart/shardchain/peerMiniBlocksSyncer.go @@ -8,6 +8,8 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/epochStart" "github.com/multiversx/mx-chain-go/process" @@ -17,8 +19,8 @@ import ( var _ process.ValidatorInfoSyncer = (*peerMiniBlockSyncer)(nil) -// waitTime defines the time in seconds to wait after a request has been done -const waitTime = 5 * time.Second +// waitTime defines the time to wait after a request has been sent +const waitTime = 300 * time.Millisecond // ArgPeerMiniBlockSyncer holds all dependencies required to create a peerMiniBlockSyncer type ArgPeerMiniBlockSyncer struct { @@ -61,8 +63,8 @@ func NewPeerMiniBlockSyncer(arguments ArgPeerMiniBlockSyncer) (*peerMiniBlockSyn requestHandler: arguments.RequestHandler, } - //TODO: change the registerHandler for the miniblockPool to call - //directly with hash and value - like func (sp *shardProcessor) receivedMetaBlock + // TODO: change the registerHandler for the miniblockPool to call + // directly with hash and value - like func (sp *shardProcessor) receivedMetaBlock p.miniBlocksPool.RegisterHandler(p.receivedMiniBlock, core.UniqueIdentifier()) p.validatorsInfoPool.RegisterOnAdded(p.receivedValidatorInfo) @@ -91,14 +93,20 @@ func (p *peerMiniBlockSyncer) SyncMiniBlocks(headerHandler data.HeaderHandler) ( p.initMiniBlocks() - p.computeMissingPeerBlocks(headerHandler) + err := p.computeMissingPeerBlocks(headerHandler) + if err != nil { + return nil, nil, err + } allMissingPeerMiniBlocksHashes, err := p.retrieveMissingMiniBlocks() if err != nil { return allMissingPeerMiniBlocksHashes, nil, err } - peerBlockBody := p.getAllPeerMiniBlocks(headerHandler) + peerBlockBody, err := p.getAllPeerMiniBlocks(headerHandler) + if err != nil { + return allMissingPeerMiniBlocksHashes, nil, err + } return nil, peerBlockBody, nil } @@ -188,14 +196,20 @@ func (p *peerMiniBlockSyncer) receivedValidatorInfo(key []byte, val interface{}) } } -func (p *peerMiniBlockSyncer) getAllPeerMiniBlocks(metaBlock data.HeaderHandler) data.BodyHandler { +func (p *peerMiniBlockSyncer) getAllPeerMiniBlocks(metaBlock data.HeaderHandler) (data.BodyHandler, error) { p.mutMiniBlocksForBlock.Lock() defer p.mutMiniBlocksForBlock.Unlock() peerBlockBody := &block.Body{ MiniBlocks: make([]*block.MiniBlock, 0), } - for _, peerMiniBlock := range metaBlock.GetMiniBlockHeaderHandlers() { + + mbHeaders, err := common.GetMiniBlockHeadersFromExecResult(metaBlock) + if err != nil { + return nil, err + } + + for _, peerMiniBlock := range mbHeaders { if peerMiniBlock.GetTypeInt32() != int32(block.PeerBlock) { continue } @@ -204,7 +218,7 @@ func (p *peerMiniBlockSyncer) getAllPeerMiniBlocks(metaBlock data.HeaderHandler) peerBlockBody.MiniBlocks = append(peerBlockBody.MiniBlocks, mb) } - return peerBlockBody + return peerBlockBody, nil } func (p *peerMiniBlockSyncer) getAllValidatorsInfo(body *block.Body) map[string]*state.ShardValidatorInfo { @@ -226,12 +240,18 @@ func (p *peerMiniBlockSyncer) getAllValidatorsInfo(body *block.Body) map[string] return validatorsInfo } -func (p *peerMiniBlockSyncer) computeMissingPeerBlocks(metaBlock data.HeaderHandler) { +func (p *peerMiniBlockSyncer) computeMissingPeerBlocks(metaBlock data.HeaderHandler) error { p.mutMiniBlocksForBlock.Lock() defer p.mutMiniBlocksForBlock.Unlock() numMissingPeerMiniBlocks := uint32(0) - for _, mb := range metaBlock.GetMiniBlockHeaderHandlers() { + + mbHeaders, err := common.GetMiniBlockHeadersFromExecResult(metaBlock) + if err != nil { + return err + } + + for _, mb := range mbHeaders { if mb.GetTypeInt32() != int32(block.PeerBlock) { continue } @@ -254,6 +274,7 @@ func (p *peerMiniBlockSyncer) computeMissingPeerBlocks(metaBlock data.HeaderHand } p.numMissingPeerMiniBlocks = numMissingPeerMiniBlocks + return nil } func (p *peerMiniBlockSyncer) computeMissingValidatorsInfo(body *block.Body) { diff --git a/epochStart/shardchain/peerMiniBlocksSyncer_test.go b/epochStart/shardchain/peerMiniBlocksSyncer_test.go index 3e131fa7074..900b0e56e97 100644 --- a/epochStart/shardchain/peerMiniBlocksSyncer_test.go +++ b/epochStart/shardchain/peerMiniBlocksSyncer_test.go @@ -2,12 +2,14 @@ package shardchain import ( "bytes" + "sync" "testing" "time" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/atomic" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-go/epochStart" @@ -21,6 +23,8 @@ import ( "github.com/stretchr/testify/require" ) +type setMBHeaders func(mbHeaders []block.MiniBlockHeader) + func createDefaultArguments() ArgPeerMiniBlockSyncer { defaultArgs := ArgPeerMiniBlockSyncer{ MiniBlocksPool: cache.NewCacherStub(), @@ -203,7 +207,21 @@ func TestValidatorInfoProcessor_ProcesStartOfEpochWithPeerMiniblocksInPoolShould require.Nil(t, processError) } -func TestValidatorInfoProcessor_ProcesStartOfEpochWithMissinPeerMiniblocksShouldProcess(t *testing.T) { +func TestValidatorInfoProcessor_ProcessStartOfEpochWithMissingPeerMiniBlocksShouldProcess(t *testing.T) { + metaHdrV1 := &block.MetaBlock{} + setMBHeadersInMetaHdrV1 := func(mbHeaders []block.MiniBlockHeader) { + metaHdrV1.MiniBlockHeaders = mbHeaders + } + testProcessStartOfEpochWithMissingPeerMiniBlocksShouldProcess(t, metaHdrV1, setMBHeadersInMetaHdrV1) + + metaHdrV3 := &block.MetaBlockV3{} + setMBHeadersInMetaHdrV3 := func(mbHeaders []block.MiniBlockHeader) { + metaHdrV3.ExecutionResults = []*block.MetaExecutionResult{{MiniBlockHeaders: mbHeaders}} + } + testProcessStartOfEpochWithMissingPeerMiniBlocksShouldProcess(t, metaHdrV3, setMBHeadersInMetaHdrV3) +} + +func testProcessStartOfEpochWithMissingPeerMiniBlocksShouldProcess(t *testing.T, epochStartHeader data.MetaHeaderHandler, setMBHeadersFunc setMBHeaders) { args := createDefaultArguments() hash := []byte("hash") @@ -243,9 +261,11 @@ func TestValidatorInfoProcessor_ProcesStartOfEpochWithMissinPeerMiniblocksShould miniBlockHeader := block.MiniBlockHeader{ Hash: peerMiniBlockHash, Type: block.PeerBlock, SenderShardID: core.MetachainShardId, ReceiverShardID: core.AllShardId, TxCount: 1} - epochStartHeader := &block.MetaBlock{Nonce: 100, Epoch: 1, PrevHash: previousHash} - epochStartHeader.EpochStart.LastFinalizedHeaders = []block.EpochStartShardData{{ShardID: 0, RootHash: hash, HeaderHash: hash}} - epochStartHeader.MiniBlockHeaders = []block.MiniBlockHeader{miniBlockHeader} + _ = epochStartHeader.SetNonce(100) + _ = epochStartHeader.SetEpoch(1) + _ = epochStartHeader.SetPrevHash(previousHash) + _ = epochStartHeader.SetEpochStartHandler(&block.EpochStart{LastFinalizedHeaders: []block.EpochStartShardData{{ShardID: 0, RootHash: hash, HeaderHash: hash}}}) + setMBHeadersFunc([]block.MiniBlockHeader{miniBlockHeader}) var receivedMiniblock func(key []byte, value interface{}) args.MiniBlocksPool = &cache.CacherStub{ @@ -270,11 +290,14 @@ func TestValidatorInfoProcessor_ProcesStartOfEpochWithMissinPeerMiniblocksShould }, } + wg := sync.WaitGroup{} + wg.Add(1) args.RequestHandler = &testscommon.RequestHandlerStub{ RequestMiniBlocksHandlerCalled: func(destShardID uint32, miniblockHashes [][]byte) { if destShardID == core.MetachainShardId && bytes.Equal(miniblockHashes[0], peerMiniBlockHash) { args.MiniBlocksPool.Put(peerMiniBlockHash, peerMiniblock, peerMiniblock.Size()) + wg.Done() } }, } @@ -282,6 +305,7 @@ func TestValidatorInfoProcessor_ProcesStartOfEpochWithMissinPeerMiniblocksShould syncer, _ := NewPeerMiniBlockSyncer(args) _, _, processError := syncer.SyncMiniBlocks(epochStartHeader) + wg.Wait() require.Nil(t, processError) } diff --git a/epochStart/shardchain/trigger.go b/epochStart/shardchain/trigger.go index e6100f3ab3b..9a42f515fb9 100644 --- a/epochStart/shardchain/trigger.go +++ b/epochStart/shardchain/trigger.go @@ -19,12 +19,13 @@ import ( "github.com/multiversx/mx-chain-core-go/display" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/epochStart" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/storage" - logger "github.com/multiversx/mx-chain-logger-go" ) var log = logger.GetOrCreate("epochStart/shardchain") @@ -36,7 +37,9 @@ var _ process.EpochBootstrapper = (*trigger)(nil) var _ closing.Closer = (*trigger)(nil) // sleepTime defines the time in milliseconds between each iteration made in requestMissingMiniBlocks method -const sleepTime = 1 * time.Second +const sleepTime = 200 * time.Millisecond + +const numRoundsWithoutReceivedMetaBlocks = 5 // ArgsShardEpochStartTrigger struct { defines the arguments needed for new start of epoch trigger type ArgsShardEpochStartTrigger struct { @@ -46,15 +49,15 @@ type ArgsShardEpochStartTrigger struct { HeaderValidator epochStart.HeaderValidator Uint64Converter typeConverters.Uint64ByteSliceConverter - DataPool dataRetriever.PoolsHolder - Storage dataRetriever.StorageService - RequestHandler epochStart.RequestHandler - EpochStartNotifier epochStart.Notifier - PeerMiniBlocksSyncer process.ValidatorInfoSyncer - RoundHandler process.RoundHandler - AppStatusHandler core.AppStatusHandler - EnableEpochsHandler common.EnableEpochsHandler - ExtraDelayForRequestBlockInfo time.Duration + DataPool dataRetriever.PoolsHolder + Storage dataRetriever.StorageService + RequestHandler epochStart.RequestHandler + EpochStartNotifier epochStart.Notifier + PeerMiniBlocksSyncer process.ValidatorInfoSyncer + RoundHandler process.RoundHandler + AppStatusHandler core.AppStatusHandler + EnableEpochsHandler common.EnableEpochsHandler + CommonConfigsHandler common.CommonConfigsHandler Epoch uint32 Validity uint64 @@ -72,11 +75,12 @@ type trigger struct { epochStartShardHeader data.HeaderHandler epochStartMeta data.HeaderHandler - mutTrigger sync.RWMutex - mapHashHdr map[string]data.HeaderHandler - mapNonceHashes map[uint64][]string - mapEpochStartHdrs map[string]data.HeaderHandler - mapFinalizedEpochs map[uint32]string + mutTrigger sync.RWMutex + mapHashHdr map[string]data.HeaderHandler + mapNonceHashes map[uint64][]string + mapEpochStartHdrs map[string]data.HeaderHandler + mapFinalizedEpochs map[uint32]string + mapPreparedEpochStartHdrs map[string]struct{} headersPool dataRetriever.HeadersPool proofsPool dataRetriever.ProofsPool @@ -106,8 +110,9 @@ type trigger struct { peerMiniBlocksSyncer process.ValidatorInfoSyncer - appStatusHandler core.AppStatusHandler - enableEpochsHandler common.EnableEpochsHandler + appStatusHandler core.AppStatusHandler + enableEpochsHandler common.EnableEpochsHandler + epochStartConfigsHandler common.CommonConfigsHandler mapMissingMiniBlocks map[string]uint32 mapMissingValidatorsInfo map[string]uint32 @@ -115,7 +120,7 @@ type trigger struct { mutMissingValidatorsInfo sync.RWMutex cancelFunc func() - extraDelayForRequestBlockInfo time.Duration + chanMetaBlockReceived chan struct{} } type metaInfo struct { @@ -228,11 +233,6 @@ func NewEpochStartTrigger(args *ArgsShardEpochStartTrigger) (*trigger, error) { return nil, err } - if args.ExtraDelayForRequestBlockInfo != common.ExtraDelayForRequestBlockInfo { - log.Warn("different delay for request block info: the epoch change trigger might not behave normally", - "value from config", args.ExtraDelayForRequestBlockInfo.String(), "expected", common.ExtraDelayForRequestBlockInfo.String()) - } - triggerStateKey := common.TriggerRegistryInitialKeyPrefix + fmt.Sprintf("%d", args.Epoch) t := &trigger{ triggerStateKey: []byte(triggerStateKey), @@ -272,7 +272,8 @@ func NewEpochStartTrigger(args *ArgsShardEpochStartTrigger) (*trigger, error) { appStatusHandler: args.AppStatusHandler, roundHandler: args.RoundHandler, enableEpochsHandler: args.EnableEpochsHandler, - extraDelayForRequestBlockInfo: args.ExtraDelayForRequestBlockInfo, + epochStartConfigsHandler: args.CommonConfigsHandler, + chanMetaBlockReceived: make(chan struct{}, 1), } t.headersPool.RegisterHandler(t.receivedMetaBlock) @@ -285,15 +286,22 @@ func NewEpochStartTrigger(args *ArgsShardEpochStartTrigger) (*trigger, error) { t.mapMissingMiniBlocks = make(map[string]uint32) t.mapMissingValidatorsInfo = make(map[string]uint32) + t.mapPreparedEpochStartHdrs = make(map[string]struct{}) var ctx context.Context ctx, t.cancelFunc = context.WithCancel(context.Background()) go t.requestMissingMiniBlocks(ctx) go t.requestMissingValidatorsInfo(ctx) + go t.watchdogRequestEpochStartMetaBlock(ctx) return t, nil } +func (t *trigger) getExtraDelayForRequestsBlockInfo(epoch uint32) time.Duration { + extraDelayForRequestBlockInfoInMilliseconds := t.epochStartConfigsHandler.GetExtraDelayForRequestBlockInfoInMs(epoch) + return time.Duration(extraDelayForRequestBlockInfoInMilliseconds) * time.Millisecond +} + func (t *trigger) clearMissingMiniBlocksMap(epoch uint32) { t.mutMissingMiniBlocks.Lock() defer t.mutMissingMiniBlocks.Unlock() @@ -333,6 +341,13 @@ func (t *trigger) requestMissingMiniBlocks(ctx context.Context) { t.mutMissingMiniBlocks.RLock() if len(t.mapMissingMiniBlocks) == 0 { t.mutMissingMiniBlocks.RUnlock() + + t.mutTrigger.Lock() + if !t.isEpochStart { + t.updateTriggerFromMeta() + } + t.mutTrigger.Unlock() + continue } @@ -489,7 +504,7 @@ func (t *trigger) RequestEpochStartIfNeeded(interceptedHeader data.HeaderHandler if interceptedHeader.GetEpoch() <= t.Epoch() { return } - _, ok := interceptedHeader.(*block.MetaBlock) + _, ok := interceptedHeader.(data.MetaHeaderHandler) if !ok { return } @@ -511,18 +526,18 @@ func (t *trigger) RequestEpochStartIfNeeded(interceptedHeader data.HeaderHandler } func (t *trigger) changeEpochFinalityAttestingRoundIfNeeded( - metaHdr *block.MetaBlock, + metaHdr data.MetaHeaderHandler, receivedHash []byte, ) { - hash := t.mapFinalizedEpochs[metaHdr.Epoch] + hash := t.mapFinalizedEpochs[metaHdr.GetEpoch()] epochStartMetaHdr := t.mapEpochStartHdrs[hash] if check.IfNil(epochStartMetaHdr) { return } - isHeaderOnTopOfFinalityAttestingRound := metaHdr.Nonce == epochStartMetaHdr.GetNonce()+t.finality+1 + isHeaderOnTopOfFinalityAttestingRound := metaHdr.GetNonce() == epochStartMetaHdr.GetNonce()+t.finality+1 if isHeaderOnTopOfFinalityAttestingRound { - metaHdrWithFinalityAttestingRound, err := t.getHeaderWithNonceAndHash(epochStartMetaHdr.GetNonce()+t.finality, metaHdr.PrevHash) + metaHdrWithFinalityAttestingRound, err := t.getHeaderWithNonceAndHash(epochStartMetaHdr.GetNonce()+t.finality, metaHdr.GetPrevHash()) if err != nil { log.Debug("searched metaHeader was not found") _ = t.requestedFinalityAttestingBlock.SetReturningPrevious() @@ -533,7 +548,7 @@ func (t *trigger) changeEpochFinalityAttestingRoundIfNeeded( return } - isFinalityAttestingBlock := metaHdr.Nonce == epochStartMetaHdr.GetNonce()+t.finality + isFinalityAttestingBlock := metaHdr.GetNonce() == epochStartMetaHdr.GetNonce()+t.finality if !isFinalityAttestingBlock { return } @@ -544,7 +559,7 @@ func (t *trigger) changeEpochFinalityAttestingRoundIfNeeded( } if t.requestedFinalityAttestingBlock.IsSet() { - _, err = t.getHeaderWithNonceAndPrevHash(metaHdr.Nonce+1, receivedHash) + _, err = t.getHeaderWithNonceAndPrevHash(metaHdr.GetNonce()+1, receivedHash) if err != nil { return } @@ -588,10 +603,23 @@ func (t *trigger) receivedMetaBlock(headerHandler data.HeaderHandler, metaBlockH return } + select { + case t.chanMetaBlockReceived <- struct{}{}: + default: + } + log.Debug("received meta header in trigger", "header hash", metaBlockHash) if t.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, headerHandler.GetEpoch()) { proof, err := t.proofsPool.GetProof(headerHandler.GetShardID(), metaBlockHash) if err != nil { + metaHdr, ok := headerHandler.(data.MetaHeaderHandler) + if ok && metaHdr.IsStartOfEpochBlock() && metaHdr.GetEpoch() > t.Epoch() { + log.Debug("proof not found for epoch start meta header, requesting it", + "header hash", metaBlockHash, + "epoch", headerHandler.GetEpoch(), + ) + go t.requestHandler.RequestEquivalentProofByHash(core.MetachainShardId, metaBlockHash) + } return } @@ -608,10 +636,11 @@ func (t *trigger) receivedMetaBlock(headerHandler data.HeaderHandler, metaBlockH } func (t *trigger) checkMetaHeaderForEpochTriggerEquivalentProofs(headerHandler data.HeaderHandler, metaBlockHash []byte) { - metaHdr, ok := headerHandler.(*block.MetaBlock) + metaHdr, ok := headerHandler.(data.MetaHeaderHandler) if !ok { return } + log.Debug("trigger.checkMetaHeaderForEpochTriggerEquivalentProofs", "metaHdr epoch", metaHdr.GetEpoch(), "metaBlockHash", metaBlockHash) if !t.shouldUpdateTrigger(metaHdr, metaBlockHash) { return @@ -623,13 +652,13 @@ func (t *trigger) checkMetaHeaderForEpochTriggerEquivalentProofs(headerHandler d } func (t *trigger) checkMetaHeaderForEpochTriggerLegacy(headerHandler data.HeaderHandler, metaBlockHash []byte) { - metaHdr, ok := headerHandler.(*block.MetaBlock) + metaHdr, ok := headerHandler.(data.MetaHeaderHandler) if !ok { return } if !t.isPreviousEpochStartMetaBlock(metaHdr, metaBlockHash) { - _, ok = t.mapFinalizedEpochs[metaHdr.Epoch] + _, ok = t.mapFinalizedEpochs[metaHdr.GetEpoch()] if t.metaEpoch == headerHandler.GetEpoch() && ok { t.changeEpochFinalityAttestingRoundIfNeeded(metaHdr, metaBlockHash) return @@ -644,12 +673,12 @@ func (t *trigger) checkMetaHeaderForEpochTriggerLegacy(headerHandler data.Header t.updateTriggerFromMeta() } -func (t *trigger) shouldUpdateTrigger(metaHdr *block.MetaBlock, metaBlockHash []byte) bool { +func (t *trigger) shouldUpdateTrigger(metaHdr data.MetaHeaderHandler, metaBlockHash []byte) bool { if !t.newEpochHdrReceived && !metaHdr.IsStartOfEpochBlock() { return false } - isMetaStartOfEpochForCurrentOrOlderEpoch := metaHdr.Epoch <= t.epoch && metaHdr.IsStartOfEpochBlock() + isMetaStartOfEpochForCurrentOrOlderEpoch := metaHdr.GetEpoch() <= t.epoch && metaHdr.IsStartOfEpochBlock() if isMetaStartOfEpochForCurrentOrOlderEpoch { return false } @@ -657,7 +686,7 @@ func (t *trigger) shouldUpdateTrigger(metaHdr *block.MetaBlock, metaBlockHash [] _, foundHdrInMap := t.mapHashHdr[string(metaBlockHash)] _, foundHdrInEpochStartMap := t.mapEpochStartHdrs[string(metaBlockHash)] - finalizedMetaBlockHash, ok := t.mapFinalizedEpochs[metaHdr.Epoch] + finalizedMetaBlockHash, ok := t.mapFinalizedEpochs[metaHdr.GetEpoch()] foundHdrInFinalizedMap := ok && bytes.Equal(metaBlockHash, []byte(finalizedMetaBlockHash)) if foundHdrInMap && foundHdrInEpochStartMap && foundHdrInFinalizedMap { @@ -667,27 +696,31 @@ func (t *trigger) shouldUpdateTrigger(metaHdr *block.MetaBlock, metaBlockHash [] return true } -func (t *trigger) updateTriggerHeaderData(metaHdr *block.MetaBlock, metaBlockHash []byte) { +func (t *trigger) updateTriggerHeaderData(metaHdr data.MetaHeaderHandler, metaBlockHash []byte) { if metaHdr.IsStartOfEpochBlock() { t.newEpochHdrReceived = true t.mapEpochStartHdrs[string(metaBlockHash)] = metaHdr // waiting for late broadcast of mini blocks and transactions to be done and received - wait := t.extraDelayForRequestBlockInfo + wait := t.getExtraDelayForRequestsBlockInfo(metaHdr.GetEpoch()) roundDifferences := t.roundHandler.Index() - int64(metaHdr.GetRound()) if roundDifferences > 1 { wait = 0 } + log.Debug("updateTriggerHeaderData: waiting for late broadcast of mini blocks and transactions", + "wait time", wait, + ) + time.Sleep(wait) } t.mapHashHdr[string(metaBlockHash)] = metaHdr - t.mapNonceHashes[metaHdr.Nonce] = append(t.mapNonceHashes[metaHdr.Nonce], string(metaBlockHash)) + t.mapNonceHashes[metaHdr.GetNonce()] = append(t.mapNonceHashes[metaHdr.GetNonce()], string(metaBlockHash)) } // call only if mutex is locked before -func (t *trigger) isPreviousEpochStartMetaBlock(metaBlock *block.MetaBlock, metaBlockHash []byte) bool { - metaHdrHashesWithNonce := t.mapNonceHashes[metaBlock.Nonce+1] +func (t *trigger) isPreviousEpochStartMetaBlock(metaBlock data.MetaHeaderHandler, metaBlockHash []byte) bool { + metaHdrHashesWithNonce := t.mapNonceHashes[metaBlock.GetNonce()+1] for _, hash := range metaHdrHashesWithNonce { epochStartMetaBlock, ok := t.mapEpochStartHdrs[hash] if !ok { @@ -872,7 +905,10 @@ func (t *trigger) checkIfTriggerCanBeActivated(hash string, metaHdr data.HeaderH } } - t.epochStartNotifier.NotifyAllPrepare(metaHdr, blockBody) + if _, alreadyPrepared := t.mapPreparedEpochStartHdrs[hash]; !alreadyPrepared { + t.epochStartNotifier.NotifyAllPrepare(metaHdr, blockBody) + t.mapPreparedEpochStartHdrs[hash] = struct{}{} + } isMetaHdrFinal, finalityAttestingRound := t.isMetaBlockFinal(hash, metaHdr) return isMetaHdrFinal, finalityAttestingRound @@ -918,10 +954,10 @@ func (t *trigger) getHeaderWithNonceAndHashFromMaps(nonce uint64, neededHash []b // call only if mutex is locked before func (t *trigger) getHeaderWithHashFromPool(neededHash []byte) data.HeaderHandler { peekedData, _ := t.headersPool.GetHeaderByHash(neededHash) - neededHdr, ok := peekedData.(*block.MetaBlock) + neededHdr, ok := peekedData.(data.MetaHeaderHandler) if ok { t.mapHashHdr[string(neededHash)] = neededHdr - t.mapNonceHashes[neededHdr.Nonce] = append(t.mapNonceHashes[neededHdr.Nonce], string(neededHash)) + t.mapNonceHashes[neededHdr.GetNonce()] = append(t.mapNonceHashes[neededHdr.GetNonce()], string(neededHash)) return neededHdr } @@ -932,12 +968,11 @@ func (t *trigger) getHeaderWithHashFromPool(neededHash []byte) data.HeaderHandle func (t *trigger) getHeaderWithHashFromStorage(neededHash []byte) data.HeaderHandler { storageData, err := t.metaHdrStorage.Get(neededHash) if err == nil { - var neededHdr block.MetaBlock - err = t.marshaller.Unmarshal(&neededHdr, storageData) + neededHdr, err := process.UnmarshalMetaHeader(t.marshaller, storageData) if err == nil { - t.mapHashHdr[string(neededHash)] = &neededHdr - t.mapNonceHashes[neededHdr.Nonce] = append(t.mapNonceHashes[neededHdr.Nonce], string(neededHash)) - return &neededHdr + t.mapHashHdr[string(neededHash)] = neededHdr + t.mapNonceHashes[neededHdr.GetNonce()] = append(t.mapNonceHashes[neededHdr.GetNonce()], string(neededHash)) + return neededHdr } } @@ -1002,13 +1037,13 @@ func (t *trigger) getHeaderWithNonceAndPrevHashFromCache(nonce uint64, prevHash } lowestRound := uint64(math.MaxUint64) - chosenMeta := &block.MetaBlock{} + var chosenMeta data.MetaHeaderHandler for i, header := range headers { if !bytes.Equal(header.GetPrevHash(), prevHash) { continue } - hdrWithNonce, ok := header.(*block.MetaBlock) + hdrWithNonce, ok := header.(data.MetaHeaderHandler) if !ok { continue } @@ -1019,7 +1054,7 @@ func (t *trigger) getHeaderWithNonceAndPrevHashFromCache(nonce uint64, prevHash } t.mapHashHdr[string(hashes[i])] = hdrWithNonce - t.mapNonceHashes[hdrWithNonce.Nonce] = append(t.mapNonceHashes[hdrWithNonce.Nonce], string(hashes[i])) + t.mapNonceHashes[hdrWithNonce.GetNonce()] = append(t.mapNonceHashes[hdrWithNonce.GetNonce()], string(hashes[i])) } if lowestRound == uint64(math.MaxUint64) { @@ -1087,6 +1122,7 @@ func (t *trigger) SetProcessed(header data.HeaderHandler, _ data.BodyHandler) { t.mapNonceHashes = make(map[uint64][]string) t.mapEpochStartHdrs = make(map[string]data.HeaderHandler) t.mapFinalizedEpochs = make(map[uint32]string) + t.mapPreparedEpochStartHdrs = make(map[string]struct{}) t.saveCurrentState(header.GetRound()) @@ -1228,6 +1264,25 @@ func (t *trigger) GetSavedStateKey() []byte { func (t *trigger) Update(_ uint64, _ uint64) { } +// SetEpochChange will do nothing +func (t *trigger) SetEpochChange(_ uint64) { +} + +// ShouldProposeEpochChange will always return false +func (t *trigger) ShouldProposeEpochChange(_ uint64, _ uint64) bool { + return false +} + +// SetEpochChangeProposed will do nothing +func (t *trigger) SetEpochChangeProposed(_ bool) { + // no implementation needed +} + +// GetEpochChangeProposed will always return false +func (t *trigger) GetEpochChangeProposed() bool { + return false +} + // SetFinalityAttestingRound sets the round which finalized the start of epoch block func (t *trigger) SetFinalityAttestingRound(_ uint64) { } @@ -1245,6 +1300,66 @@ func (t *trigger) saveCurrentState(round uint64) { } } +func (t *trigger) computeWatchdogTimeout() time.Duration { + timeout := t.roundHandler.TimeDuration() * numRoundsWithoutReceivedMetaBlocks + if timeout <= 0 { + return 0 + } + return timeout +} + +func (t *trigger) watchdogRequestEpochStartMetaBlock(ctx context.Context) { + watchdogTimeout := t.computeWatchdogTimeout() + if watchdogTimeout == 0 { + return + } + + timer := time.NewTimer(watchdogTimeout) + defer timer.Stop() + + for { + select { + case <-ctx.Done(): + log.Debug("watchdogRequestEpochStartMetaBlock: trigger's go routine is stopping...") + return + case <-t.chanMetaBlockReceived: + timer.Reset(t.resetWatchdogTimeout(watchdogTimeout)) + case <-timer.C: + t.handleWatchdogTimeout() + timer.Reset(t.resetWatchdogTimeout(watchdogTimeout)) + } + } +} + +func (t *trigger) resetWatchdogTimeout(fallback time.Duration) time.Duration { + timeout := t.computeWatchdogTimeout() + if timeout == 0 { + return fallback + } + return timeout +} + +func (t *trigger) handleWatchdogTimeout() { + t.mutTrigger.RLock() + epoch := t.epoch + isEpochStart := t.isEpochStart + t.mutTrigger.RUnlock() + + if isEpochStart { + return + } + + if !t.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, epoch) { + return + } + + log.Debug("watchdog: no metablock received for too long, requesting epoch start metablock", + "current epoch", epoch, + "requesting epoch", epoch+1, + ) + go t.requestHandler.RequestStartOfEpochMetaBlock(epoch + 1) +} + // Close will close the endless running go routine func (t *trigger) Close() error { if t.cancelFunc != nil { diff --git a/epochStart/shardchain/triggerRegistry.go b/epochStart/shardchain/triggerRegistry.go index d3f5e8d18c6..a4d46be9b4b 100644 --- a/epochStart/shardchain/triggerRegistry.go +++ b/epochStart/shardchain/triggerRegistry.go @@ -1,14 +1,6 @@ package shardchain import ( - "encoding/json" - "fmt" - - "github.com/multiversx/mx-chain-core-go/core/check" - "github.com/multiversx/mx-chain-core-go/data" - "github.com/multiversx/mx-chain-core-go/data/block" - "github.com/multiversx/mx-chain-core-go/marshal" - "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/epochStart" ) @@ -23,7 +15,7 @@ func (t *trigger) LoadState(key []byte) error { return err } - state, err := UnmarshalTrigger(t.marshaller, triggerData) + state, err := epochStart.UnmarshalShardTrigger(t.marshaller, triggerData) if err != nil { return err } @@ -44,84 +36,9 @@ func (t *trigger) LoadState(key []byte) error { return nil } -// UnmarshalTrigger unmarshalls the trigger -func UnmarshalTrigger(marshaller marshal.Marshalizer, data []byte) (data.TriggerRegistryHandler, error) { - trig, err := unmarshalTriggerV2(marshaller, data) - if err == nil { - return trig, nil - } - - trig, err = unmarshalTriggerV1(marshaller, data) - if err == nil { - return trig, nil - } - - // for backwards compatibility - return unmarshalTriggerJson(data) -} - -// unmarshalTriggerJson unmarshalls the trigger with json, for backwards compatibility -func unmarshalTriggerJson(data []byte) (data.TriggerRegistryHandler, error) { - trig := &block.ShardTriggerRegistry{EpochStartShardHeader: &block.Header{}} - err := json.Unmarshal(data, trig) - if err != nil { - return nil, err - } - - return trig, nil -} - -// unmarshalTriggerV2 tries to unmarshal the data into a v2 trigger -func unmarshalTriggerV2(marshaller marshal.Marshalizer, data []byte) (data.TriggerRegistryHandler, error) { - triggerV2 := &block.ShardTriggerRegistryV2{ - EpochStartShardHeader: &block.HeaderV2{}, - } - - err := marshaller.Unmarshal(triggerV2, data) - if err != nil { - return nil, err - } - - if check.IfNil(triggerV2.EpochStartShardHeader) || check.IfNil(triggerV2.EpochStartShardHeader.Header) { - return nil, fmt.Errorf("%w while checking inner epoch start shard header", epochStart.ErrNilHeaderHandler) - } - return triggerV2, nil -} - -// unmarshalTriggerV1 tries to unmarshal the data into a v1 trigger -func unmarshalTriggerV1(marshaller marshal.Marshalizer, data []byte) (data.TriggerRegistryHandler, error) { - triggerV1 := &block.ShardTriggerRegistry{ - EpochStartShardHeader: &block.Header{}, - } - - err := marshaller.Unmarshal(triggerV1, data) - if err != nil { - return nil, err - } - - if check.IfNil(triggerV1.EpochStartShardHeader) { - return nil, fmt.Errorf("%w while checking inner epoch start shard header", epochStart.ErrNilHeaderHandler) - } - return triggerV1, nil -} - // saveState saves the trigger state. Needs to be called under mutex func (t *trigger) saveState(key []byte) error { - registryV2 := &block.ShardTriggerRegistryV2{ - EpochStartShardHeader: &block.HeaderV2{}, - } - registryV1 := &block.ShardTriggerRegistry{ - EpochStartShardHeader: &block.Header{}, - } - var registry data.TriggerRegistryHandler - - _, ok := t.epochStartShardHeader.(*block.HeaderV2) - if ok { - registry = registryV2 - } else { - registry = registryV1 - } - + registry := epochStart.CreateShardRegistryHandler(t.epochStartShardHeader) _ = registry.SetMetaEpoch(t.metaEpoch) _ = registry.SetEpoch(t.epoch) _ = registry.SetCurrentRoundIndex(t.currentRoundIndex) diff --git a/epochStart/shardchain/triggerRegistry_test.go b/epochStart/shardchain/triggerRegistry_test.go index 970f48f6a73..316fc62ac60 100644 --- a/epochStart/shardchain/triggerRegistry_test.go +++ b/epochStart/shardchain/triggerRegistry_test.go @@ -20,7 +20,7 @@ func cloneTrigger(t *trigger) *trigger { rt := &trigger{} rt.epoch = t.epoch - rt.metaEpoch = t.epoch + rt.metaEpoch = t.metaEpoch rt.currentRoundIndex = t.currentRoundIndex rt.epochStartRound = t.epochStartRound rt.epochMetaBlockHash = t.epochMetaBlockHash @@ -57,6 +57,9 @@ func cloneTrigger(t *trigger) *trigger { rt.mapFinalizedEpochs = t.mapFinalizedEpochs rt.roundHandler = t.roundHandler rt.enableEpochsHandler = t.enableEpochsHandler + rt.epochStartConfigsHandler = t.epochStartConfigsHandler + rt.chanMetaBlockReceived = t.chanMetaBlockReceived + rt.mapPreparedEpochStartHdrs = t.mapPreparedEpochStartHdrs return rt } @@ -71,7 +74,9 @@ func createDummyEpochStartTriggers(arguments *ArgsShardEpochStartTrigger, key [] epochStartTrigger1.currentRoundIndex = 800 epochStartTrigger1.epochStartRound = 650 epochStartTrigger1.epochMetaBlockHash = []byte("meta block hash") + epochStartTrigger1.mutTrigger.Lock() epochStartTrigger1.isEpochStart = false + epochStartTrigger1.mutTrigger.Unlock() epochStartTrigger1.epochFinalityAttestingRound = 680 epochStartTrigger1.cancelFunc = nil epochStartTrigger1.epochStartShardHeader = &block.Header{} @@ -134,30 +139,135 @@ func TestTrigger_LoadStateBackwardsCompatibility(t *testing.T) { t.Parallel() epoch := uint32(5) + key := []byte("key") + arguments := createMockShardEpochStartTriggerArguments() arguments.Epoch = epoch - bootStorer := genericMocks.NewStorerMock() - arguments.Storage = &storageStubs.ChainStorerStub{ - GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { - return bootStorer, nil - }, - } + t.Run("backwards compatibility", func(t *testing.T) { + bootStorer := genericMocks.NewStorerMock() + arguments.Storage = &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return bootStorer, nil + }, + } - key := []byte("key") - epochStartTrigger1, epochStartTrigger2 := createDummyEpochStartTriggers(arguments, key) + epochStartTrigger1, epochStartTrigger2 := createDummyEpochStartTriggers(arguments, key) - trig := createLegacyTriggerRegistryFromTrigger(epochStartTrigger1) - d, err := json.Marshal(trig) - require.Nil(t, err) - trigInternalKey := append([]byte(common.TriggerRegistryKeyPrefix), key...) + trig := createLegacyTriggerRegistryFromTrigger(epochStartTrigger1) + d, _ := json.Marshal(trig) + trigInternalKey := append([]byte(common.TriggerRegistryKeyPrefix), key...) - err = bootStorer.Put(trigInternalKey, d) - require.Nil(t, err) + err := bootStorer.Put(trigInternalKey, d) + require.Nil(t, err) - err = epochStartTrigger2.LoadState(key) - require.Nil(t, err) - require.Equal(t, epochStartTrigger1, epochStartTrigger2) + err = epochStartTrigger2.LoadState(key) + require.Nil(t, err) + require.Equal(t, epochStartTrigger1, epochStartTrigger2) + }) + + t.Run("header v1", func(t *testing.T) { + triggerRegistry := &block.ShardTriggerRegistry{ + Epoch: epoch, + MetaEpoch: epoch, + EpochStartShardHeader: &block.Header{}, + } + triggerRegistryBytes, _ := arguments.Marshalizer.Marshal(triggerRegistry) + + bootStorer := &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + return triggerRegistryBytes, nil + }, + } + + arguments.Storage = &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return bootStorer, nil + }, + } + + epochStartTrigger1, err := NewEpochStartTrigger(arguments) + require.Nil(t, err) + epochStartTrigger2 := cloneTrigger(epochStartTrigger1) + + epochStartTrigger1.epoch = epoch + epochStartTrigger1.triggerStateKey = key + epochStartTrigger1.cancelFunc = nil + + err = epochStartTrigger2.LoadState(key) + require.Nil(t, err) + require.Equal(t, epochStartTrigger1, epochStartTrigger2) + }) + + t.Run("header v2", func(t *testing.T) { + triggerRegistry := &block.ShardTriggerRegistryV2{ + Epoch: epoch, + MetaEpoch: epoch, + EpochStartShardHeader: &block.HeaderV2{ + Header: &block.Header{}, + }, + } + triggerRegistryBytes, _ := arguments.Marshalizer.Marshal(triggerRegistry) + + bootStorer := &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + return triggerRegistryBytes, nil + }, + } + + arguments.Storage = &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return bootStorer, nil + }, + } + + epochStartTrigger1, err := NewEpochStartTrigger(arguments) + require.Nil(t, err) + epochStartTrigger2 := cloneTrigger(epochStartTrigger1) + + epochStartTrigger1.epoch = epoch + epochStartTrigger1.triggerStateKey = key + epochStartTrigger1.epochStartShardHeader = &block.HeaderV2{ + Header: &block.Header{}, + } + epochStartTrigger1.cancelFunc = nil + + err = epochStartTrigger2.LoadState(key) + require.Nil(t, err) + require.Equal(t, epochStartTrigger1, epochStartTrigger2) + }) + + t.Run("header v3", func(t *testing.T) { + triggerRegistry := &block.ShardTriggerRegistryV3{ + Epoch: epoch, + MetaEpoch: epoch, + EpochStartShardHeader: &block.HeaderV3{}, + } + triggerRegistryBytes, _ := arguments.Marshalizer.Marshal(triggerRegistry) + + arguments.Storage = &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + return triggerRegistryBytes, nil + }, + }, nil + }, + } + + epochStartTrigger1, err := NewEpochStartTrigger(arguments) + require.Nil(t, err) + epochStartTrigger2 := cloneTrigger(epochStartTrigger1) + + epochStartTrigger1.epoch = epoch + epochStartTrigger1.triggerStateKey = key + epochStartTrigger1.epochStartShardHeader = &block.HeaderV3{} + epochStartTrigger1.cancelFunc = nil + + err = epochStartTrigger2.LoadState(key) + require.Nil(t, err) + require.Equal(t, epochStartTrigger1, epochStartTrigger2) + }) } type legacyTriggerRegistry struct { diff --git a/epochStart/shardchain/trigger_test.go b/epochStart/shardchain/trigger_test.go index 4a5d2e40998..0b47eca15d5 100644 --- a/epochStart/shardchain/trigger_test.go +++ b/epochStart/shardchain/trigger_test.go @@ -5,7 +5,10 @@ import ( "errors" "fmt" "strings" + "sync" + "sync/atomic" "testing" + "time" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" @@ -15,6 +18,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/epochStart" "github.com/multiversx/mx-chain-go/epochStart/mock" @@ -72,6 +76,7 @@ func createMockShardEpochStartTriggerArguments() *ArgsShardEpochStartTrigger { RoundHandler: &mock.RoundHandlerStub{}, AppStatusHandler: &statusHandlerMock.AppStatusHandlerStub{}, EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + CommonConfigsHandler: testscommon.GetDefaultCommonConfigsHandler(), } } @@ -759,11 +764,11 @@ func TestTrigger_ReceivedHeaderChangeEpochWithoutPrevHeader(t *testing.T) { epochStartTrigger.receivedMetaBlock(epochStartHeader, epochStartHash) - require.False(t, epochStartTrigger.isEpochStart) + require.False(t, epochStartTrigger.IsEpochStart()) epochStartTrigger.receivedMetaBlock(epochStartHeader, epochStartHash) - require.True(t, epochStartTrigger.isEpochStart) + require.True(t, epochStartTrigger.IsEpochStart()) } func TestTrigger_ClearMissingValidatorsInfoMapShouldWork(t *testing.T) { @@ -1061,3 +1066,335 @@ func TestTrigger_ReceivedProof(t *testing.T) { require.True(t, wasCalled) }) } + +func TestTrigger_WatchdogRequestEpochStartMetaBlock(t *testing.T) { + t.Parallel() + + t.Run("fires after timeout", func(t *testing.T) { + t.Parallel() + + var requestedEpoch atomic.Uint32 + var called atomic.Int32 + args := createMockShardEpochStartTriggerArguments() + args.RoundHandler = &mock.RoundHandlerStub{ + TimeDurationCalled: func() time.Duration { + return 10 * time.Millisecond + }, + } + args.Epoch = 5 + args.RequestHandler = &testscommon.RequestHandlerStub{ + RequestStartOfEpochMetaBlockCalled: func(epoch uint32) { + requestedEpoch.Store(epoch) + called.Add(1) + }, + } + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.AndromedaFlag + }, + } + + et, err := NewEpochStartTrigger(args) + require.Nil(t, err) + defer func() { + _ = et.Close() + }() + + time.Sleep(200 * time.Millisecond) + + require.Greater(t, called.Load(), int32(0)) + require.Equal(t, uint32(6), requestedEpoch.Load()) + }) + + t.Run("resets timer on any metablock reception", func(t *testing.T) { + t.Parallel() + + var called atomic.Int32 + args := createMockShardEpochStartTriggerArguments() + args.RoundHandler = &mock.RoundHandlerStub{ + TimeDurationCalled: func() time.Duration { + return 30 * time.Millisecond + }, + } + args.RequestHandler = &testscommon.RequestHandlerStub{ + RequestStartOfEpochMetaBlockCalled: func(epoch uint32) { + called.Add(1) + }, + } + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.AndromedaFlag + }, + } + + et, err := NewEpochStartTrigger(args) + require.Nil(t, err) + defer func() { + _ = et.Close() + }() + + for i := 0; i < 10; i++ { + select { + case et.chanMetaBlockReceived <- struct{}{}: + default: + } + time.Sleep(20 * time.Millisecond) + } + + require.Equal(t, int32(0), called.Load()) + }) + + t.Run("skips when epoch start already detected", func(t *testing.T) { + t.Parallel() + + var called atomic.Int32 + args := createMockShardEpochStartTriggerArguments() + args.RoundHandler = &mock.RoundHandlerStub{ + TimeDurationCalled: func() time.Duration { + return 10 * time.Millisecond + }, + IndexCalled: func() int64 { + return 100 + }, + } + args.RequestHandler = &testscommon.RequestHandlerStub{ + RequestStartOfEpochMetaBlockCalled: func(epoch uint32) { + called.Add(1) + }, + } + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.AndromedaFlag + }, + } + + et, err := NewEpochStartTrigger(args) + require.Nil(t, err) + defer func() { + _ = et.Close() + }() + + et.mutTrigger.Lock() + et.isEpochStart = true + et.mutTrigger.Unlock() + + time.Sleep(200 * time.Millisecond) + + require.Equal(t, int32(0), called.Load()) + }) + + t.Run("skips when Andromeda disabled", func(t *testing.T) { + t.Parallel() + + var called atomic.Int32 + args := createMockShardEpochStartTriggerArguments() + args.RoundHandler = &mock.RoundHandlerStub{ + TimeDurationCalled: func() time.Duration { + return 10 * time.Millisecond + }, + IndexCalled: func() int64 { + return 100 + }, + } + args.RequestHandler = &testscommon.RequestHandlerStub{ + RequestStartOfEpochMetaBlockCalled: func(epoch uint32) { + called.Add(1) + }, + } + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return false + }, + } + + et, err := NewEpochStartTrigger(args) + require.Nil(t, err) + defer func() { + _ = et.Close() + }() + + time.Sleep(200 * time.Millisecond) + + require.Equal(t, int32(0), called.Load()) + }) + + t.Run("stops on context cancellation", func(t *testing.T) { + t.Parallel() + + var called atomic.Int32 + args := createMockShardEpochStartTriggerArguments() + args.RoundHandler = &mock.RoundHandlerStub{ + TimeDurationCalled: func() time.Duration { + return 10 * time.Millisecond + }, + IndexCalled: func() int64 { + return 100 + }, + } + args.RequestHandler = &testscommon.RequestHandlerStub{ + RequestStartOfEpochMetaBlockCalled: func(epoch uint32) { + called.Add(1) + }, + } + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.AndromedaFlag + }, + } + + et, err := NewEpochStartTrigger(args) + require.Nil(t, err) + + err = et.Close() + require.Nil(t, err) + + calledBefore := called.Load() + time.Sleep(200 * time.Millisecond) + + require.Equal(t, calledBefore, called.Load()) + }) + + t.Run("does not start when TimeDuration is zero", func(t *testing.T) { + t.Parallel() + + var called atomic.Int32 + args := createMockShardEpochStartTriggerArguments() + args.RoundHandler = &mock.RoundHandlerStub{ + TimeDurationCalled: func() time.Duration { + return 0 + }, + } + args.RequestHandler = &testscommon.RequestHandlerStub{ + RequestStartOfEpochMetaBlockCalled: func(epoch uint32) { + called.Add(1) + }, + } + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.AndromedaFlag + }, + } + + et, err := NewEpochStartTrigger(args) + require.Nil(t, err) + defer func() { + _ = et.Close() + }() + + time.Sleep(100 * time.Millisecond) + + require.Equal(t, int32(0), called.Load()) + }) + + t.Run("receivedMetaBlock signals watchdog", func(t *testing.T) { + t.Parallel() + + args := createMockShardEpochStartTriggerArguments() + // zero TimeDuration prevents the watchdog goroutine from starting + args.RoundHandler = &mock.RoundHandlerStub{ + TimeDurationCalled: func() time.Duration { + return 0 + }, + } + et, err := NewEpochStartTrigger(args) + require.Nil(t, err) + defer func() { + _ = et.Close() + }() + + et.receivedMetaBlock(&block.MetaBlock{ + Nonce: 10, + Round: 42, + }, []byte("hash")) + + select { + case <-et.chanMetaBlockReceived: + // expected + default: + require.Fail(t, "channel should have been signaled") + } + }) + + t.Run("receivedMetaBlock requests proof when missing in Andromeda", func(t *testing.T) { + t.Parallel() + + var proofRequested atomic.Int32 + var requestedHashMut sync.Mutex + var requestedHash []byte + args := createMockShardEpochStartTriggerArguments() + args.Epoch = 5 + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.AndromedaFlag + }, + } + args.RequestHandler = &testscommon.RequestHandlerStub{ + RequestEquivalentProofByHashCalled: func(headerShard uint32, headerHash []byte) { + requestedHashMut.Lock() + requestedHash = headerHash + requestedHashMut.Unlock() + proofRequested.Add(1) + }, + } + args.DataPool = &dataRetrieverMock.PoolsHolderStub{ + HeadersCalled: func() dataRetriever.HeadersPool { + return &mock.HeadersCacherStub{} + }, + MiniBlocksCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, + CurrEpochValidatorInfoCalled: func() dataRetriever.ValidatorInfoCacher { + return &vic.ValidatorInfoCacherStub{} + }, + ProofsCalled: func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{ + GetProofCalled: func(_ uint32, _ []byte) (data.HeaderProofHandler, error) { + return nil, errors.New("proof not found") + }, + } + }, + } + + et, err := NewEpochStartTrigger(args) + require.Nil(t, err) + defer func() { + _ = et.Close() + }() + + metaBlockHash := []byte("metablock-hash") + et.receivedMetaBlock(&block.MetaBlock{ + Nonce: 10, + Round: 42, + Epoch: 6, + EpochStart: block.EpochStart{LastFinalizedHeaders: []block.EpochStartShardData{{}}}, + }, metaBlockHash) + + time.Sleep(50 * time.Millisecond) + + require.Equal(t, int32(1), proofRequested.Load()) + requestedHashMut.Lock() + require.Equal(t, metaBlockHash, requestedHash) + requestedHashMut.Unlock() + + proofRequested.Store(0) + et.receivedMetaBlock(&block.MetaBlock{ + Nonce: 11, + Round: 43, + Epoch: 6, + }, []byte("regular-metablock-hash")) + + time.Sleep(50 * time.Millisecond) + require.Equal(t, int32(0), proofRequested.Load()) + + proofRequested.Store(0) + et.receivedMetaBlock(&block.MetaBlock{ + Nonce: 5, + Round: 30, + Epoch: 5, + EpochStart: block.EpochStart{LastFinalizedHeaders: []block.EpochStartShardData{{}}}, + }, []byte("old-epoch-start-hash")) + + time.Sleep(50 * time.Millisecond) + require.Equal(t, int32(0), proofRequested.Load()) + }) +} diff --git a/epochStart/triggerHelpers.go b/epochStart/triggerHelpers.go new file mode 100644 index 00000000000..50e10bfbab0 --- /dev/null +++ b/epochStart/triggerHelpers.go @@ -0,0 +1,178 @@ +package epochStart + +import ( + "encoding/json" + "fmt" + + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/marshal" +) + +// UnmarshalMetaTrigger unmarshalls the meta trigger with json, for backwards compatibility +func UnmarshalMetaTrigger(marshaller marshal.Marshalizer, data []byte) (data.MetaTriggerRegistryHandler, error) { + trig, err := unmarshallMetaTriggerV3(marshaller, data) + if err == nil { + return trig, nil + } + + trig, err = unmarshallMetaTriggerV1(marshaller, data) + if err == nil { + return trig, nil + } + + // for backwards compatibility + return unmarshallMetaTriggerJson(data) +} + +func unmarshallMetaTriggerV3(marshaller marshal.Marshalizer, data []byte) (data.MetaTriggerRegistryHandler, error) { + state := &block.MetaTriggerRegistryV3{ + EpochStartMeta: &block.MetaBlockV3{}, + } + + err := marshaller.Unmarshal(state, data) + if err != nil { + return nil, err + } + + return state, nil +} + +func unmarshallMetaTriggerV1(marshaller marshal.Marshalizer, data []byte) (data.MetaTriggerRegistryHandler, error) { + state := &block.MetaTriggerRegistry{ + EpochStartMeta: &block.MetaBlock{}, + } + + err := marshaller.Unmarshal(state, data) + if err != nil { + return nil, err + } + + return state, nil +} + +func unmarshallMetaTriggerJson(data []byte) (data.MetaTriggerRegistryHandler, error) { + state := &block.MetaTriggerRegistry{ + EpochStartMeta: &block.MetaBlock{}, + } + + err := json.Unmarshal(data, state) + if err != nil { + return nil, err + } + + return state, nil +} + +// CreateMetaRegistryHandler creates the appropriate meta trigger registry handler based on the header type +func CreateMetaRegistryHandler(epochStartMeta data.HeaderHandler) data.MetaTriggerRegistryHandler { + if epochStartMeta.IsHeaderV3() { + return &block.MetaTriggerRegistryV3{} + } + + return &block.MetaTriggerRegistry{} +} + +// UnmarshalShardTrigger unmarshalls the shard trigger +func UnmarshalShardTrigger(marshaller marshal.Marshalizer, data []byte) (data.TriggerRegistryHandler, error) { + trig, err := unmarshalShardTriggerV3(marshaller, data) + if err == nil { + return trig, nil + } + + trig, err = unmarshalShardTriggerV2(marshaller, data) + if err == nil { + return trig, nil + } + + trig, err = unmarshalShardTriggerV1(marshaller, data) + if err == nil { + return trig, nil + } + + // for backwards compatibility + return unmarshalShardTriggerJson(data) +} + +// unmarshalShardTriggerJson unmarshalls the trigger with json, for backwards compatibility +func unmarshalShardTriggerJson(data []byte) (data.TriggerRegistryHandler, error) { + trig := &block.ShardTriggerRegistry{EpochStartShardHeader: &block.Header{}} + err := json.Unmarshal(data, trig) + if err != nil { + return nil, err + } + + return trig, nil +} + +// unmarshalShardTriggerV3 tries to unmarshal the data into a v3 trigger +func unmarshalShardTriggerV3(marshaller marshal.Marshalizer, data []byte) (data.TriggerRegistryHandler, error) { + triggerV3 := &block.ShardTriggerRegistryV3{ + EpochStartShardHeader: &block.HeaderV3{}, + } + + err := marshaller.Unmarshal(triggerV3, data) + if err != nil { + return nil, err + } + + if check.IfNil(triggerV3.EpochStartShardHeader) { + return nil, fmt.Errorf("%w while checking inner epoch start shard header", ErrNilHeaderHandler) + } + return triggerV3, nil +} + +// unmarshalShardTriggerV2 tries to unmarshal the data into a v2 trigger +func unmarshalShardTriggerV2(marshaller marshal.Marshalizer, data []byte) (data.TriggerRegistryHandler, error) { + triggerV2 := &block.ShardTriggerRegistryV2{ + EpochStartShardHeader: &block.HeaderV2{}, + } + + err := marshaller.Unmarshal(triggerV2, data) + if err != nil { + return nil, err + } + + if check.IfNil(triggerV2.EpochStartShardHeader) || check.IfNil(triggerV2.EpochStartShardHeader.Header) { + return nil, fmt.Errorf("%w while checking inner epoch start shard header", ErrNilHeaderHandler) + } + return triggerV2, nil +} + +// unmarshalShardTriggerV1 tries to unmarshal the data into a v1 trigger +func unmarshalShardTriggerV1(marshaller marshal.Marshalizer, data []byte) (data.TriggerRegistryHandler, error) { + triggerV1 := &block.ShardTriggerRegistry{ + EpochStartShardHeader: &block.Header{}, + } + + err := marshaller.Unmarshal(triggerV1, data) + if err != nil { + return nil, err + } + + if check.IfNil(triggerV1.EpochStartShardHeader) { + return nil, fmt.Errorf("%w while checking inner epoch start shard header", ErrNilHeaderHandler) + } + return triggerV1, nil +} + +// CreateShardRegistryHandler creates the appropriate shard trigger registry handler based on the header type +func CreateShardRegistryHandler(epochStartShardHeader data.HeaderHandler) data.TriggerRegistryHandler { + if epochStartShardHeader.IsHeaderV3() { + return &block.ShardTriggerRegistryV3{ + EpochStartShardHeader: &block.HeaderV3{}, + } + } + + _, ok := epochStartShardHeader.(*block.HeaderV2) + if ok { + return &block.ShardTriggerRegistryV2{ + EpochStartShardHeader: &block.HeaderV2{}, + } + } + + return &block.ShardTriggerRegistry{ + EpochStartShardHeader: &block.Header{}, + } +} diff --git a/errors/errors.go b/errors/errors.go index 7d2c26bff7a..7f9b5e85438 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -67,6 +67,9 @@ var ErrNilBlockChainHandler = errors.New("blockchain handler is nil") // ErrNilBlockProcessor is raised when a valid block processor is expected but nil used var ErrNilBlockProcessor = errors.New("block processor is nil") +// ErrNilExecutionManager is raised when a valid execution manager is expected but nil used +var ErrNilExecutionManager = errors.New("execution manager is nil") + // ErrNilBlockSigner signals the nil block signer was provided var ErrNilBlockSigner = errors.New("nil block signer") @@ -475,6 +478,9 @@ var ErrNilESDTDataStorage = errors.New("nil esdt data storage") // ErrNilEnableEpochsHandler signals that a nil enable epochs handler was provided var ErrNilEnableEpochsHandler = errors.New("nil enable epochs handler") +// ErrNilEnableRoundsHandler signals that a nil enable rounds handler was provided +var ErrNilEnableRoundsHandler = errors.New("nil enable rounds handler") + // ErrNilChainParametersHandler signals that a nil chain parameters handler was provided var ErrNilChainParametersHandler = errors.New("nil chain parameters handler") @@ -607,3 +613,6 @@ var ErrNilFieldsSizeChecker = errors.New("nil fields size checker") // ErrNilTrieLeavesRetriever defines the error for setting a nil TrieLeavesRetriever var ErrNilTrieLeavesRetriever = errors.New("nil trie leaves retriever") + +// ErrNilAOTSelector signals that a nil AOT selector has been provided +var ErrNilAOTSelector = errors.New("nil AOT selector") diff --git a/facade/errors.go b/facade/errors.go index 2085f474b94..5dc544479f8 100644 --- a/facade/errors.go +++ b/facade/errors.go @@ -23,6 +23,9 @@ var ErrNilPeerState = errors.New("nil peer state") // ErrNilAccountState signals that a nil account state has been provided var ErrNilAccountState = errors.New("nil account state") +// ErrNilAccountStateAPI signals that a nil account state api has been provided +var ErrNilAccountStateAPI = errors.New("nil account state api") + // ErrNilTransactionSimulatorProcessor signals that a nil transaction simulator processor has been provided var ErrNilTransactionSimulatorProcessor = errors.New("nil transaction simulator processor") diff --git a/facade/initial/initialNodeFacade.go b/facade/initial/initialNodeFacade.go index e881505b083..13bf2e0833e 100644 --- a/facade/initial/initialNodeFacade.go +++ b/facade/initial/initialNodeFacade.go @@ -417,6 +417,16 @@ func (inf *initialNodeFacade) GetTransactionsPoolNonceGapsForSender(_ string) (* return nil, errNodeStarting } +// GetSelectedTransactions returns a nil structure and error +func (inf *initialNodeFacade) GetSelectedTransactions(_ string) (*common.TransactionsSelectionSimulationResult, error) { + return nil, errNodeStarting +} + +// GetVirtualNonce returns a nil structure and error +func (inf *initialNodeFacade) GetVirtualNonce(_ string) (*common.VirtualNonceOfAccountResponse, error) { + return nil, errNodeStarting +} + // GetTransactionsPoolForSender returns a nil structure and error func (inf *initialNodeFacade) GetTransactionsPoolForSender(_, _ string) (*common.TransactionsPoolForSenderApiResponse, error) { return nil, errNodeStarting diff --git a/facade/initial/initialStatusMetricsProvider.go b/facade/initial/initialStatusMetricsProvider.go index 68c93eeaa07..9e8136adc56 100644 --- a/facade/initial/initialStatusMetricsProvider.go +++ b/facade/initial/initialStatusMetricsProvider.go @@ -26,9 +26,9 @@ func (provider *initialStatusMetricsProvider) BootstrapMetrics() (map[string]int return provider.realStatusMetricsProvider.BootstrapMetrics() } -// StatusMetricsMapWithoutP2P returns an empty map and the error which specifies that the node is starting +// StatusMetricsMapWithoutP2P returns the status metrics (excluding P2P-related ones) func (provider *initialStatusMetricsProvider) StatusMetricsMapWithoutP2P() (map[string]interface{}, error) { - return getEmptyReturnValues() + return provider.realStatusMetricsProvider.StatusMetricsMapWithoutP2P() } // StatusP2pMetricsMap returns an empty map and the error which specifies that the node is starting @@ -56,6 +56,16 @@ func (provider *initialStatusMetricsProvider) EnableEpochsMetrics() (map[string] return getEmptyReturnValues() } +// EnableEpochsMetricsV2 returns an empty map since the node is starting +func (provider *initialStatusMetricsProvider) EnableEpochsMetricsV2() map[string]uint32 { + return make(map[string]uint32) +} + +// EnableRoundsMetrics returns an empty map since the node is starting +func (provider *initialStatusMetricsProvider) EnableRoundsMetrics() map[string]uint64 { + return make(map[string]uint64) +} + // NetworkMetrics returns an empty map and the error which specifies that the node is starting func (provider *initialStatusMetricsProvider) NetworkMetrics() (map[string]interface{}, error) { return getEmptyReturnValues() diff --git a/facade/initial/initialStatusMetricsProvider_test.go b/facade/initial/initialStatusMetricsProvider_test.go index 92a88955e21..f9f6dade59a 100644 --- a/facade/initial/initialStatusMetricsProvider_test.go +++ b/facade/initial/initialStatusMetricsProvider_test.go @@ -34,16 +34,24 @@ func TestNewInitialStatusMetricsProvider(t *testing.T) { "key-2": uint64(15), "key-3": uint64(20), } + + providedMetrics2 := map[string]interface{}{ + "key-11": uint64(20), + "key-21": uint64(25), + "key-31": uint64(30), + } statusMetricsProvider := &testscommon.StatusMetricsStub{ BootstrapMetricsCalled: func() (map[string]interface{}, error) { return providedMetrics, nil }, + StatusMetricsMapWithoutP2PCalled: func() (map[string]interface{}, error) { + return providedMetrics2, nil + }, } provider, err := NewInitialStatusMetricsProvider(statusMetricsProvider) assert.Nil(t, err) assert.False(t, check.IfNil(provider)) - testDisabledGetter(t, provider.StatusMetricsMapWithoutP2P) testDisabledGetter(t, provider.StatusP2pMetricsMap) testDisabledGetter(t, provider.EconomicsMetrics) testDisabledGetter(t, provider.ConfigMetrics) @@ -51,6 +59,9 @@ func TestNewInitialStatusMetricsProvider(t *testing.T) { testDisabledGetter(t, provider.NetworkMetrics) testDisabledGetter(t, provider.RatingsMetrics) + enableRoundsMetrics := provider.EnableRoundsMetrics() + assert.Equal(t, map[string]uint64{}, enableRoundsMetrics) + metrics, err := provider.StatusMetricsWithoutP2PPrometheusString() assert.Equal(t, errNodeStarting, err) assert.Equal(t, "", metrics) @@ -58,6 +69,10 @@ func TestNewInitialStatusMetricsProvider(t *testing.T) { bootstrapMetrics, err := provider.BootstrapMetrics() assert.Nil(t, err) assert.Equal(t, providedMetrics, bootstrapMetrics) + + statusMetrics, err := provider.StatusMetricsMapWithoutP2P() + assert.Nil(t, err) + assert.Equal(t, providedMetrics2, statusMetrics) }) } diff --git a/facade/interface.go b/facade/interface.go index 9d56bf5f2ef..1c6a023010a 100644 --- a/facade/interface.go +++ b/facade/interface.go @@ -138,6 +138,8 @@ type ApiResolver interface { GetTransactionsPoolForSender(sender, fields string) (*common.TransactionsPoolForSenderApiResponse, error) GetLastPoolNonceForSender(sender string) (uint64, error) GetTransactionsPoolNonceGapsForSender(sender string, senderAccountNonce uint64) (*common.TransactionsPoolNonceGapsForSenderApiResponse, error) + GetSelectedTransactions(selectionOptions common.TxSelectionOptionsAPI, blockchain coreData.ChainHandler, accountsAdapter state.AccountsAdapter) (*common.TransactionsSelectionSimulationResult, error) + GetVirtualNonce(address string) (*common.VirtualNonceOfAccountResponse, error) GetBlockByHash(hash string, options api.BlockQueryOptions) (*api.Block, error) GetBlockByNonce(nonce uint64, options api.BlockQueryOptions) (*api.Block, error) GetBlockByRound(round uint64, options api.BlockQueryOptions) (*api.Block, error) diff --git a/facade/mock/apiResolverStub.go b/facade/mock/apiResolverStub.go index 8c27fd6547e..7385c52b915 100644 --- a/facade/mock/apiResolverStub.go +++ b/facade/mock/apiResolverStub.go @@ -2,6 +2,8 @@ package mock import ( "context" + + "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/smartContractResult" "github.com/multiversx/mx-chain-core-go/data/alteredAccount" @@ -44,6 +46,8 @@ type ApiResolverStub struct { GetTransactionsPoolForSenderCalled func(sender, fields string) (*common.TransactionsPoolForSenderApiResponse, error) GetLastPoolNonceForSenderCalled func(sender string) (uint64, error) GetTransactionsPoolNonceGapsForSenderCalled func(sender string, senderAccountNonce uint64) (*common.TransactionsPoolNonceGapsForSenderApiResponse, error) + GetSelectedTransactionsCalled func(selectionOptions common.TxSelectionOptionsAPI, blockchain data.ChainHandler, accountsAdapter state.AccountsAdapter) (*common.TransactionsSelectionSimulationResult, error) + GetVirtualNonceCalled func(address string) (*common.VirtualNonceOfAccountResponse, error) GetGasConfigsCalled func() map[string]map[string]uint64 GetManagedKeysCountCalled func() int GetManagedKeysCalled func() []string @@ -73,6 +77,15 @@ func (ars *ApiResolverStub) GetSCRsByTxHash(txHash string, scrHash string) ([]*t return nil, nil } +// GetVirtualNonce - +func (ars *ApiResolverStub) GetVirtualNonce(address string) (*common.VirtualNonceOfAccountResponse, error) { + if ars.GetVirtualNonceCalled != nil { + return ars.GetVirtualNonceCalled(address) + } + + return nil, nil +} + // GetTransaction - func (ars *ApiResolverStub) GetTransaction(hash string, withEvents bool) (*transaction.ApiTransactionResult, error) { if ars.GetTransactionHandler != nil { @@ -248,6 +261,15 @@ func (ars *ApiResolverStub) GetTransactionsPoolNonceGapsForSender(sender string, return nil, nil } +// GetSelectedTransactions - +func (ars *ApiResolverStub) GetSelectedTransactions(selectionOptions common.TxSelectionOptionsAPI, blockchain data.ChainHandler, accountsAdapter state.AccountsAdapter) (*common.TransactionsSelectionSimulationResult, error) { + if ars.GetSelectedTransactionsCalled != nil { + return ars.GetSelectedTransactionsCalled(selectionOptions, blockchain, accountsAdapter) + } + + return nil, nil +} + // GetInternalMetaBlockByHash - func (ars *ApiResolverStub) GetInternalMetaBlockByHash(format common.ApiOutputFormat, hash string) (interface{}, error) { if ars.GetInternalMetaBlockByHashCalled != nil { diff --git a/facade/mock/nodeStub.go b/facade/mock/nodeStub.go index e7b2817a32e..0323ad224c9 100644 --- a/facade/mock/nodeStub.go +++ b/facade/mock/nodeStub.go @@ -57,6 +57,7 @@ type NodeStub struct { GetTokenSupplyCalled func(token string) (*api.ESDTSupply, error) IsDataTrieMigratedCalled func(address string, options api.AccountQueryOptions) (bool, error) AuctionListApiCalled func() ([]*common.AuctionListValidatorAPIResponse, error) + DecodeAddressPubkeyCalled func(address string) ([]byte, error) } // GetProof - @@ -146,6 +147,9 @@ func (ns *NodeStub) EncodeAddressPubkey(pk []byte) (string, error) { // DecodeAddressPubkey - func (ns *NodeStub) DecodeAddressPubkey(pk string) ([]byte, error) { + if ns.DecodeAddressPubkeyCalled != nil { + return ns.DecodeAddressPubkeyCalled(pk) + } return hex.DecodeString(pk) } diff --git a/facade/mock/syncTimerMock.go b/facade/mock/syncTimerMock.go index b440600b7d8..8cef70ac6d6 100644 --- a/facade/mock/syncTimerMock.go +++ b/facade/mock/syncTimerMock.go @@ -6,12 +6,20 @@ import ( // SyncTimerMock is a mock implementation of SyncTimer interface type SyncTimerMock struct { + ForceSyncCalled func() StartSyncingTimeCalled func() ClockOffsetCalled func() time.Duration FormattedCurrentTimeCalled func() string CurrentTimeCalled func() time.Time } +// ForceSync - +func (stm *SyncTimerMock) ForceSync() { + if stm.ForceSyncCalled != nil { + stm.ForceSyncCalled() + } +} + // StartSyncingTime is a mock implementation for StartSyncingTime func (stm *SyncTimerMock) StartSyncingTime() { stm.StartSyncingTimeCalled() diff --git a/facade/nodeFacade.go b/facade/nodeFacade.go index 96c766e9059..7ea462d46a1 100644 --- a/facade/nodeFacade.go +++ b/facade/nodeFacade.go @@ -20,6 +20,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data/validator" "github.com/multiversx/mx-chain-core-go/data/vm" "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/common/holders" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/debug" "github.com/multiversx/mx-chain-go/epochStart/bootstrap/disabled" @@ -40,6 +41,8 @@ const DefaultRestInterface = "localhost:8080" // to start the node without a REST endpoint available const DefaultRestPortOff = "off" +const simulateSelectionMaxDuration = time.Millisecond * 150 + var log = logger.GetOrCreate("facade") // ArgNodeFacade represents the argument for the nodeFacade @@ -50,8 +53,7 @@ type ArgNodeFacade struct { WsAntifloodConfig config.WebServerAntifloodConfig FacadeConfig config.FacadeConfig ApiRoutesConfig config.ApiRoutesConfig - AccountsState state.AccountsAdapter - PeerState state.AccountsAdapter + AccountsStateAPI state.AccountsAdapter Blockchain chainData.ChainHandler } @@ -65,8 +67,7 @@ type nodeFacade struct { endpointsThrottlers map[string]core.Throttler wsAntifloodConfig config.WebServerAntifloodConfig restAPIServerDebugMode bool - accountsState state.AccountsAdapter - peerState state.AccountsAdapter + accountsStateAPI state.AccountsAdapter blockchain chainData.ChainHandler } @@ -85,12 +86,9 @@ func NewNodeFacade(arg ArgNodeFacade) (*nodeFacade, error) { if err != nil { return nil, err } - if check.IfNil(arg.AccountsState) { + if check.IfNil(arg.AccountsStateAPI) { return nil, ErrNilAccountState } - if check.IfNil(arg.PeerState) { - return nil, ErrNilPeerState - } if check.IfNil(arg.Blockchain) { return nil, ErrNilBlockchain } @@ -105,8 +103,7 @@ func NewNodeFacade(arg ArgNodeFacade) (*nodeFacade, error) { config: arg.FacadeConfig, apiRoutesConfig: arg.ApiRoutesConfig, endpointsThrottlers: throttlersMap, - accountsState: arg.AccountsState, - peerState: arg.PeerState, + accountsStateAPI: arg.AccountsStateAPI, blockchain: arg.Blockchain, } @@ -348,6 +345,38 @@ func (nf *nodeFacade) GetTransactionsPoolNonceGapsForSender(sender string) (*com return nf.apiResolver.GetTransactionsPoolNonceGapsForSender(sender, accountResponse.Nonce) } +// GetSelectedTransactions will simulate a SelectTransactions, and it will return the corresponding hash of each selected transaction +func (nf *nodeFacade) GetSelectedTransactions(fields string) (*common.TransactionsSelectionSimulationResult, error) { + // simulation only, should be safe to pass true here + startTime := time.Now() + haveTimeForSimulation := func() bool { + return time.Since(startTime) <= simulateSelectionMaxDuration + } + + selectionOptions, err := holders.NewTxSelectionOptions( + nf.config.TxCacheSelectionConfig.SelectionGasRequested, + nf.config.TxCacheSelectionConfig.SelectionMaxNumTxs, + nf.config.TxCacheSelectionConfig.SelectionLoopDurationCheckInterval, + haveTimeForSimulation, + ) + if err != nil { + return nil, err + } + + selectionOptionsAPI := holders.NewTxSelectionOptionsAPI( + selectionOptions, + fields, + ) + + // TODO brainstorm if this could be handled by the node + return nf.apiResolver.GetSelectedTransactions(selectionOptionsAPI, nf.blockchain, nf.accountsStateAPI) +} + +// GetVirtualNonce will return the virtual nonce of an account +func (nf *nodeFacade) GetVirtualNonce(address string) (*common.VirtualNonceOfAccountResponse, error) { + return nf.apiResolver.GetVirtualNonce(address) +} + // ComputeTransactionGasLimit will estimate how many gas a transaction will consume func (nf *nodeFacade) ComputeTransactionGasLimit(tx *transaction.Transaction) (*transaction.CostResponse, error) { return nf.apiResolver.ComputeTransactionGasLimit(tx) @@ -607,7 +636,7 @@ func (nf *nodeFacade) GetProofDataTrie(rootHash string, address string, key stri // GetProofCurrentRootHash returns the Merkle proof for the given address and current root hash func (nf *nodeFacade) GetProofCurrentRootHash(address string) (*common.GetProofResponse, error) { - rootHash := nf.blockchain.GetCurrentBlockRootHash() + rootHash := nf.getCurrentRootHash() if len(rootHash) == 0 { return nil, ErrEmptyRootHash } @@ -617,6 +646,16 @@ func (nf *nodeFacade) GetProofCurrentRootHash(address string) (*common.GetProofR return nf.node.GetProof(hexRootHash, address) } +func (nf *nodeFacade) getCurrentRootHash() []byte { + currentHeader := nf.blockchain.GetCurrentBlockHeader() + if currentHeader != nil && currentHeader.IsHeaderV3() { + _, _, lastExecutedRootHash := nf.blockchain.GetLastExecutedBlockInfo() + return lastExecutedRootHash + } + + return nf.blockchain.GetCurrentBlockRootHash() +} + // VerifyProof verifies the given Merkle proof func (nf *nodeFacade) VerifyProof(rootHash string, address string, proof [][]byte) (bool, error) { return nf.node.VerifyProof(rootHash, address, proof) diff --git a/facade/nodeFacade_test.go b/facade/nodeFacade_test.go index 21823b60b6e..4d4f71cb5e2 100644 --- a/facade/nodeFacade_test.go +++ b/facade/nodeFacade_test.go @@ -12,7 +12,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" atomicCore "github.com/multiversx/mx-chain-core-go/core/atomic" - nodeData "github.com/multiversx/mx-chain-core-go/data" + coreData "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/alteredAccount" "github.com/multiversx/mx-chain-core-go/data/api" "github.com/multiversx/mx-chain-core-go/data/block" @@ -61,10 +61,9 @@ func createMockArguments() ArgNodeFacade { }, }, }}, - AccountsState: &stateMock.AccountsStub{}, - PeerState: &stateMock.AccountsStub{}, + AccountsStateAPI: &stateMock.AccountsStub{}, Blockchain: &testscommon.ChainHandlerStub{ - GetCurrentBlockHeaderCalled: func() nodeData.HeaderHandler { + GetCurrentBlockHeaderCalled: func() coreData.HeaderHandler { return &block.Header{} }, GetCurrentBlockRootHashCalled: func() []byte { @@ -151,27 +150,17 @@ func TestNewNodeFacade(t *testing.T) { require.Nil(t, nf) require.True(t, errors.Is(err, ErrInvalidValue)) }) - t.Run("nil AccountsState should error", func(t *testing.T) { + t.Run("nil AccountsStateAPI should error", func(t *testing.T) { t.Parallel() arg := createMockArguments() arg.WsAntifloodConfig.WebServerAntifloodEnabled = true // coverage - arg.AccountsState = nil + arg.AccountsStateAPI = nil nf, err := NewNodeFacade(arg) require.Nil(t, nf) require.Equal(t, ErrNilAccountState, err) }) - t.Run("nil PeerState should error", func(t *testing.T) { - t.Parallel() - - arg := createMockArguments() - arg.PeerState = nil - nf, err := NewNodeFacade(arg) - - require.Nil(t, nf) - require.Equal(t, ErrNilPeerState, err) - }) t.Run("nil Blockchain should error", func(t *testing.T) { t.Parallel() @@ -1094,6 +1083,9 @@ func TestNodeFacade_GetProofCurrentRootHashIsEmptyShouldErr(t *testing.T) { arg := createMockArguments() arg.Blockchain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() coreData.HeaderHandler { + return &block.Header{} + }, GetCurrentBlockRootHashCalled: func() []byte { return nil }, @@ -1129,22 +1121,58 @@ func TestNodeFacade_GetProof(t *testing.T) { func TestNodeFacade_GetProofCurrentRootHash(t *testing.T) { t.Parallel() - expectedResponse := &common.GetProofResponse{ - Proof: [][]byte{[]byte("valid"), []byte("proof")}, - Value: []byte("value"), - RootHash: "rootHash", - } - arg := createMockArguments() - arg.Node = &mock.NodeStub{ - GetProofCalled: func(_ string, _ string) (*common.GetProofResponse, error) { - return expectedResponse, nil - }, - } - nf, _ := NewNodeFacade(arg) + t.Run("should work", func(t *testing.T) { + t.Parallel() - response, err := nf.GetProofCurrentRootHash("addr") - require.NoError(t, err) - require.Equal(t, expectedResponse, response) + expectedResponse := &common.GetProofResponse{ + Proof: [][]byte{[]byte("valid"), []byte("proof")}, + Value: []byte("value"), + RootHash: "rootHash", + } + arg := createMockArguments() + arg.Node = &mock.NodeStub{ + GetProofCalled: func(_ string, _ string) (*common.GetProofResponse, error) { + return expectedResponse, nil + }, + } + nf, _ := NewNodeFacade(arg) + + response, err := nf.GetProofCurrentRootHash("addr") + require.NoError(t, err) + require.Equal(t, expectedResponse, response) + }) + + t.Run("should work with header v3", func(t *testing.T) { + t.Parallel() + + rootHash1 := []byte("rootHash1") + expectedResponse := &common.GetProofResponse{ + Proof: [][]byte{[]byte("valid"), []byte("proof")}, + Value: []byte("value"), + RootHash: string(rootHash1), + } + + arg := createMockArguments() + arg.Blockchain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() coreData.HeaderHandler { + return &block.HeaderV3{} + }, + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return 0, []byte(""), rootHash1 + }, + } + arg.Node = &mock.NodeStub{ + GetProofCalled: func(rootHash string, _ string) (*common.GetProofResponse, error) { + require.Equal(t, hex.EncodeToString(rootHash1), rootHash) + return expectedResponse, nil + }, + } + nf, _ := NewNodeFacade(arg) + + response, err := nf.GetProofCurrentRootHash("addr") + require.NoError(t, err) + require.Equal(t, expectedResponse, response) + }) } func TestNodeFacade_GetProofDataTrie(t *testing.T) { @@ -2080,6 +2108,133 @@ func TestNodeFacade_GetTransactionsPoolNonceGapsForSender(t *testing.T) { }) } +func TestNodeFacade_GetSelectedTransactions(t *testing.T) { + t.Parallel() + + t.Run("should propagate error from ApiResolver", func(t *testing.T) { + t.Parallel() + + arg := createMockArguments() + arg.AccountsStateAPI = &stateMock.AccountsStub{ + RecreateTrieCalled: func(options common.RootHashHolder) error { + return nil + }, + } + + arg.ApiResolver = &mock.ApiResolverStub{ + GetSelectedTransactionsCalled: func(selectionOptions common.TxSelectionOptionsAPI, blockchain coreData.ChainHandler, accountsAdapter state.AccountsAdapter) (*common.TransactionsSelectionSimulationResult, error) { + return nil, expectedErr + }, + } + + nf, _ := NewNodeFacade(arg) + res, err := nf.GetSelectedTransactions("hash") + require.Nil(t, res) + require.Equal(t, expectedErr, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + arg := createMockArguments() + arg.AccountsStateAPI = &stateMock.AccountsStub{ + RecreateTrieCalled: func(options common.RootHashHolder) error { + return nil + }, + } + + expectedTxs := []common.Transaction{ + { + TxFields: map[string]interface{}{ + "hash": "txHash1", + }, + }, + { + TxFields: map[string]interface{}{ + "hash": "txHash2", + }, + }, + } + expectedRes := &common.TransactionsSelectionSimulationResult{ + Transactions: expectedTxs, + } + + arg.ApiResolver = &mock.ApiResolverStub{ + GetSelectedTransactionsCalled: func(selectionOptions common.TxSelectionOptionsAPI, blockchain coreData.ChainHandler, accountsAdapter state.AccountsAdapter) (*common.TransactionsSelectionSimulationResult, error) { + require.True(t, selectionOptions.HaveTimeForSelection()) + time.Sleep(simulateSelectionMaxDuration + 10*time.Millisecond) + require.False(t, selectionOptions.HaveTimeForSelection()) + + return expectedRes, nil + }, + } + + nf, _ := NewNodeFacade(arg) + res, err := nf.GetSelectedTransactions("hash") + require.NoError(t, err) + require.Equal(t, expectedRes, res) + }) +} + +func TestNodeFacade_GetVirtualNonce(t *testing.T) { + t.Parallel() + + t.Run("should propagate error from ApiResolver", func(t *testing.T) { + t.Parallel() + + arg := createMockArguments() + arg.AccountsStateAPI = &stateMock.AccountsStub{ + RecreateTrieCalled: func(options common.RootHashHolder) error { + return nil + }, + } + arg.Node = &mock.NodeStub{ + DecodeAddressPubkeyCalled: func(address string) ([]byte, error) { + return []byte("address"), nil + }, + } + + arg.ApiResolver = &mock.ApiResolverStub{ + GetVirtualNonceCalled: func(address string) (*common.VirtualNonceOfAccountResponse, error) { + return nil, expectedErr + }, + } + + nf, _ := NewNodeFacade(arg) + res, err := nf.GetVirtualNonce("address") + require.Nil(t, res) + require.Equal(t, expectedErr, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + arg := createMockArguments() + arg.AccountsStateAPI = &stateMock.AccountsStub{ + RecreateTrieCalled: func(options common.RootHashHolder) error { + return nil + }, + } + arg.Node = &mock.NodeStub{ + DecodeAddressPubkeyCalled: func(address string) ([]byte, error) { + return []byte("address"), nil + }, + } + + expectedRes := &common.VirtualNonceOfAccountResponse{VirtualNonce: 10} + arg.ApiResolver = &mock.ApiResolverStub{ + GetVirtualNonceCalled: func(address string) (*common.VirtualNonceOfAccountResponse, error) { + return expectedRes, nil + }, + } + + nf, _ := NewNodeFacade(arg) + res, err := nf.GetVirtualNonce("address") + require.NoError(t, err) + require.Equal(t, expectedRes, res) + }) +} + func TestNodeFacade_InternalValidatorsInfo(t *testing.T) { t.Parallel() diff --git a/factory/api/apiResolverFactory.go b/factory/api/apiResolverFactory.go index feb1b1a4d24..170d05ac839 100644 --- a/factory/api/apiResolverFactory.go +++ b/factory/api/apiResolverFactory.go @@ -9,6 +9,11 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/marshal" + logger "github.com/multiversx/mx-chain-logger-go" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/multiversx/mx-chain-vm-common-go/parsers" + datafield "github.com/multiversx/mx-chain-vm-common-go/parsers/dataField" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/common/disabled" "github.com/multiversx/mx-chain-go/common/operationmodes" @@ -46,10 +51,6 @@ import ( "github.com/multiversx/mx-chain-go/storage/storageunit" trieFactory "github.com/multiversx/mx-chain-go/trie/factory" "github.com/multiversx/mx-chain-go/vm" - logger "github.com/multiversx/mx-chain-logger-go" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" - "github.com/multiversx/mx-chain-vm-common-go/parsers" - datafield "github.com/multiversx/mx-chain-vm-common-go/parsers/dataField" ) var log = logger.GetOrCreate("factory") @@ -222,8 +223,9 @@ func CreateApiResolver(args *ApiResolverArgs) (facade.ApiResolver, error) { } argsDataFieldParser := &datafield.ArgsOperationDataFieldParser{ - AddressLength: args.CoreComponents.AddressPubKeyConverter().Len(), - Marshalizer: args.CoreComponents.InternalMarshalizer(), + AddressLength: args.CoreComponents.AddressPubKeyConverter().Len(), + Marshalizer: args.CoreComponents.InternalMarshalizer(), + RelayedTransactionsV1V2DisableEpoch: args.CoreComponents.EnableEpochsHandler().GetActivationEpoch(common.RelayedTransactionsV1V2DisableFlag), } dataFieldParser, err := datafield.NewOperationDataFieldParser(argsDataFieldParser) if err != nil { @@ -246,6 +248,8 @@ func CreateApiResolver(args *ApiResolverArgs) (facade.ApiResolver, error) { DataFieldParser: dataFieldParser, TxMarshaller: args.CoreComponents.TxMarshalizer(), EnableEpochsHandler: args.CoreComponents.EnableEpochsHandler(), + EnableRoundsHandler: args.CoreComponents.EnableRoundsHandler(), + TxVersionChecker: args.CoreComponents.TxVersionChecker(), } apiTransactionProcessor, err := transactionAPI.NewAPITransactionProcessor(argsAPITransactionProc) if err != nil { @@ -500,6 +504,7 @@ func createMetaVmContainerFactory(args scQueryElementArgs, argsHook hooks.ArgBlo ChanceComputer: args.coreComponents.Rater(), ShardCoordinator: args.processComponents.ShardCoordinator(), EnableEpochsHandler: args.coreComponents.EnableEpochsHandler(), + EnableRoundsHandler: args.coreComponents.EnableRoundsHandler(), NodesCoordinator: args.processComponents.NodesCoordinator(), } vmFactory, err := metachain.NewVMContainerFactory(argsNewVmFactory) @@ -737,6 +742,7 @@ func createAPIBlockProcessorArgs(args *ApiResolverArgs, apiTransactionHandler ex EnableEpochsHandler: args.CoreComponents.EnableEpochsHandler(), ProofsPool: args.DataComponents.Datapool().Proofs(), BlockChain: args.DataComponents.Blockchain(), + EnableRoundsHandler: args.CoreComponents.EnableRoundsHandler(), } return blockApiArgs, nil diff --git a/factory/block/headerVersionHandler.go b/factory/block/headerVersionHandler.go index 2c4de5ea4a4..3abaf4ada32 100644 --- a/factory/block/headerVersionHandler.go +++ b/factory/block/headerVersionHandler.go @@ -2,40 +2,29 @@ package block import ( "bytes" - "encoding/binary" "fmt" "sort" - "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/process" - "github.com/multiversx/mx-chain-go/storage" ) const wildcard = "*" -const keySize = 4 type headerVersionHandler struct { versions []config.VersionByEpochs defaultVersion string - versionCache storage.Cacher } // NewHeaderVersionHandler returns a new instance of a structure capable of handling the header versions func NewHeaderVersionHandler( versionsByEpochs []config.VersionByEpochs, defaultVersion string, - versionCache storage.Cacher, ) (*headerVersionHandler, error) { - if check.IfNil(versionCache) { - return nil, fmt.Errorf("%w, in NewHeaderVersionHandler", ErrNilCacher) - } - hvh := &headerVersionHandler{ defaultVersion: defaultVersion, - versionCache: versionCache, } var err error @@ -83,8 +72,8 @@ func (hvh *headerVersionHandler) prepareVersions(versionsByEpochs []config.Versi } // GetVersion returns the version by providing the epoch -func (hvh *headerVersionHandler) GetVersion(epoch uint32) string { - ver := hvh.getMatchingVersionAndUpdateCache(epoch) +func (hvh *headerVersionHandler) GetVersion(epoch uint32, round uint64) string { + ver := hvh.getMatchingVersionAndUpdateCache(epoch, round) if ver == wildcard { return hvh.defaultVersion } @@ -92,23 +81,18 @@ func (hvh *headerVersionHandler) GetVersion(epoch uint32) string { return ver } -func (hvh *headerVersionHandler) getMatchingVersionAndUpdateCache(epoch uint32) string { - version, ok := hvh.getFromCache(epoch) - if ok { - return version - } - - version = hvh.getVersionFromConfig(epoch) - hvh.setInCache(epoch, version) +func (hvh *headerVersionHandler) getMatchingVersionAndUpdateCache(epoch uint32, round uint64) string { + version := hvh.getVersionFromConfig(epoch, round) return version } -func (hvh *headerVersionHandler) getVersionFromConfig(epoch uint32) string { +func (hvh *headerVersionHandler) getVersionFromConfig(epoch uint32, round uint64) string { for idx := 0; idx < len(hvh.versions)-1; idx++ { crtVer := hvh.versions[idx] nextVer := hvh.versions[idx+1] - if crtVer.StartEpoch <= epoch && epoch < nextVer.StartEpoch { + + if (crtVer.StartEpoch <= epoch && epoch < nextVer.StartEpoch) || round < nextVer.StartRound { return crtVer.Version } } @@ -116,27 +100,6 @@ func (hvh *headerVersionHandler) getVersionFromConfig(epoch uint32) string { return hvh.versions[len(hvh.versions)-1].Version } -func (hvh *headerVersionHandler) getFromCache(epoch uint32) (string, bool) { - key := make([]byte, keySize) - binary.BigEndian.PutUint32(key, epoch) - - obj, ok := hvh.versionCache.Get(key) - if !ok { - return "", false - } - - str, ok := obj.(string) - - return str, ok -} - -func (hvh *headerVersionHandler) setInCache(epoch uint32, version string) { - key := make([]byte, keySize) - binary.BigEndian.PutUint32(key, epoch) - - _ = hvh.versionCache.Put(key, version, len(key)+len(version)) -} - // Verify will check the header's fields such as the chain ID or the software version func (hvh *headerVersionHandler) Verify(hdr data.HeaderHandler) error { if len(hdr.GetReserved()) > 0 { @@ -161,7 +124,7 @@ func (hvh *headerVersionHandler) checkSoftwareVersion(hdr data.HeaderHandler) er return err } - version := hvh.getMatchingVersionAndUpdateCache(hdr.GetEpoch()) + version := hvh.getMatchingVersionAndUpdateCache(hdr.GetEpoch(), hdr.GetRound()) if version == wildcard { return nil } diff --git a/factory/block/headerVersionHandler_test.go b/factory/block/headerVersionHandler_test.go index 4a17cb291a2..c6b185efb89 100644 --- a/factory/block/headerVersionHandler_test.go +++ b/factory/block/headerVersionHandler_test.go @@ -1,11 +1,8 @@ package block import ( - "encoding/binary" "errors" - "fmt" "strings" - "sync/atomic" "testing" "github.com/multiversx/mx-chain-core-go/core/check" @@ -14,8 +11,6 @@ import ( "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/process" - "github.com/multiversx/mx-chain-go/testscommon/cache" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -52,7 +47,6 @@ func TestNewHeaderIntegrityVerifierr_InvalidVersionElementOnEpochValuesEqualShou }, }, defaultVersion, - &cache.CacherStub{}, ) require.True(t, check.IfNil(hdrIntVer)) require.True(t, errors.Is(err, ErrInvalidVersionOnEpochValues)) @@ -69,7 +63,6 @@ func TestNewHeaderIntegrityVerifier_InvalidVersionElementOnStringTooLongShouldEr }, }, defaultVersion, - &cache.CacherStub{}, ) require.True(t, check.IfNil(hdrIntVer)) require.True(t, errors.Is(err, ErrInvalidVersionStringTooLong)) @@ -81,31 +74,17 @@ func TestNewHeaderIntegrityVerifierr_InvalidDefaultVersionShouldErr(t *testing.T hdrIntVer, err := NewHeaderVersionHandler( versionsCorrectlyConstructed, "", - &cache.CacherStub{}, ) require.True(t, check.IfNil(hdrIntVer)) require.True(t, errors.Is(err, ErrInvalidSoftwareVersion)) } -func TestNewHeaderIntegrityVerifier_NilCacherShouldErr(t *testing.T) { - t.Parallel() - - hdrIntVer, err := NewHeaderVersionHandler( - versionsCorrectlyConstructed, - "", - nil, - ) - require.True(t, check.IfNil(hdrIntVer)) - require.True(t, errors.Is(err, ErrNilCacher)) -} - func TestNewHeaderIntegrityVerifier_EmptyListShouldErr(t *testing.T) { t.Parallel() hdrIntVer, err := NewHeaderVersionHandler( make([]config.VersionByEpochs, 0), defaultVersion, - &cache.CacherStub{}, ) require.True(t, check.IfNil(hdrIntVer)) require.True(t, errors.Is(err, ErrEmptyVersionsByEpochsList)) @@ -122,7 +101,6 @@ func TestNewHeaderIntegrityVerifier_ZerothElementIsNotOnEpochZeroShouldErr(t *te }, }, defaultVersion, - &cache.CacherStub{}, ) require.True(t, check.IfNil(hdrIntVer)) require.True(t, errors.Is(err, ErrInvalidVersionOnEpochValues)) @@ -134,7 +112,6 @@ func TestNewHeaderIntegrityVerifier_ShouldWork(t *testing.T) { hdrIntVer, err := NewHeaderVersionHandler( versionsCorrectlyConstructed, defaultVersion, - &cache.CacherStub{}, ) require.False(t, check.IfNil(hdrIntVer)) require.NoError(t, err) @@ -149,7 +126,6 @@ func TestHeaderIntegrityVerifier_PopulatedReservedShouldErr(t *testing.T) { hdrIntVer, _ := NewHeaderVersionHandler( make([]config.VersionByEpochs, 0), defaultVersion, - &cache.CacherStub{}, ) err := hdrIntVer.Verify(hdr) require.Equal(t, process.ErrReservedFieldInvalid, err) @@ -161,7 +137,6 @@ func TestHeaderIntegrityVerifier_VerifySoftwareVersionEmptyVersionInHeaderShould hdrIntVer, _ := NewHeaderVersionHandler( make([]config.VersionByEpochs, 0), defaultVersion, - &cache.CacherStub{}, ) err := hdrIntVer.Verify(&block.MetaBlock{}) require.True(t, errors.Is(err, ErrInvalidSoftwareVersion)) @@ -182,7 +157,6 @@ func TestHeaderIntegrityVerifierr_VerifySoftwareVersionWrongVersionShouldErr(t * }, }, defaultVersion, - &cache.CacherStub{}, ) err := hdrIntVer.Verify( &block.MetaBlock{ @@ -209,7 +183,6 @@ func TestHeaderIntegrityVerifier_VerifySoftwareVersionWildcardShouldWork(t *test }, }, defaultVersion, - &cache.CacherStub{}, ) err := hdrIntVer.Verify( &block.MetaBlock{ @@ -229,7 +202,6 @@ func TestHeaderIntegrityVerifier_VerifyShouldWork(t *testing.T) { hdrIntVer, _ := NewHeaderVersionHandler( versionsCorrectlyConstructed, "software", - &cache.CacherStub{}, ) mb := &block.MetaBlock{ SoftwareVersion: []byte("software"), @@ -245,7 +217,6 @@ func TestHeaderIntegrityVerifier_VerifyNotWildcardShouldWork(t *testing.T) { hdrIntVer, _ := NewHeaderVersionHandler( versionsCorrectlyConstructed, "software", - &cache.CacherStub{}, ) mb := &block.MetaBlock{ SoftwareVersion: []byte("v1"), @@ -258,73 +229,47 @@ func TestHeaderIntegrityVerifier_VerifyNotWildcardShouldWork(t *testing.T) { func TestHeaderIntegrityVerifier_GetVersionShouldWork(t *testing.T) { t.Parallel() - numPutCalls := uint32(0) hdrIntVer, _ := NewHeaderVersionHandler( versionsCorrectlyConstructed, defaultVersion, - &cache.CacherStub{ - PutCalled: func(key []byte, value interface{}, sizeInBytes int) bool { - atomic.AddUint32(&numPutCalls, 1) - epoch := binary.BigEndian.Uint32(key) - switch epoch { - case 0: - assert.Equal(t, "*", value.(string)) - case 1: - assert.Equal(t, "v1", value.(string)) - case 2: - assert.Equal(t, "v1", value.(string)) - case 3: - assert.Equal(t, "v1", value.(string)) - case 4: - assert.Equal(t, "v1", value.(string)) - case 5: - assert.Equal(t, "v2", value.(string)) - case 6: - assert.Equal(t, "v2", value.(string)) - case 1000: - assert.Equal(t, "v2", value.(string)) - case 1200: - assert.Equal(t, "v2", value.(string)) - default: - assert.Fail(t, fmt.Sprintf("unexpected case for epoch %d", epoch)) - } - - return false - }, - }, ) - assert.Equal(t, defaultVersion, hdrIntVer.GetVersion(0)) - assert.Equal(t, "v1", hdrIntVer.GetVersion(1)) - assert.Equal(t, "v1", hdrIntVer.GetVersion(2)) - assert.Equal(t, "v1", hdrIntVer.GetVersion(3)) - assert.Equal(t, "v1", hdrIntVer.GetVersion(4)) - assert.Equal(t, "v2", hdrIntVer.GetVersion(5)) - assert.Equal(t, "v2", hdrIntVer.GetVersion(6)) - assert.Equal(t, "v2", hdrIntVer.GetVersion(1000)) - assert.Equal(t, "v2", hdrIntVer.GetVersion(1200)) - assert.Equal(t, uint32(9), atomic.LoadUint32(&numPutCalls)) + assert.Equal(t, defaultVersion, hdrIntVer.GetVersion(0, 1)) + assert.Equal(t, "v1", hdrIntVer.GetVersion(1, 1)) + assert.Equal(t, "v1", hdrIntVer.GetVersion(2, 1)) + assert.Equal(t, "v1", hdrIntVer.GetVersion(3, 1)) + assert.Equal(t, "v1", hdrIntVer.GetVersion(4, 1)) + assert.Equal(t, "v2", hdrIntVer.GetVersion(5, 1)) + assert.Equal(t, "v2", hdrIntVer.GetVersion(6, 1)) + assert.Equal(t, "v2", hdrIntVer.GetVersion(1000, 1)) + assert.Equal(t, "v2", hdrIntVer.GetVersion(1200, 1)) } -func TestHeaderIntegrityVerifier_ExistsInInternalCacheShouldReturn(t *testing.T) { +func TestHeaderIntegrityVerifier_NewHeaderActivatedByRound(t *testing.T) { t.Parallel() - cachedVersion := "cached version" + versionsByEpoch := []config.VersionByEpochs{ + { + StartEpoch: 0, + Version: "*", + }, + { + StartEpoch: 1, + Version: "v1", + }, + { + StartEpoch: 5, + StartRound: 150, + Version: "v2", + }, + } + hdrIntVer, _ := NewHeaderVersionHandler( - versionsCorrectlyConstructed, + versionsByEpoch, defaultVersion, - &cache.CacherStub{ - GetCalled: func(key []byte) (value interface{}, ok bool) { - return cachedVersion, true - }, - }, ) - assert.Equal(t, cachedVersion, hdrIntVer.GetVersion(0)) - assert.Equal(t, cachedVersion, hdrIntVer.GetVersion(1)) - assert.Equal(t, cachedVersion, hdrIntVer.GetVersion(2)) - assert.Equal(t, cachedVersion, hdrIntVer.GetVersion(500)) - assert.Equal(t, cachedVersion, hdrIntVer.GetVersion(999)) - assert.Equal(t, cachedVersion, hdrIntVer.GetVersion(1000)) - assert.Equal(t, cachedVersion, hdrIntVer.GetVersion(1200)) + require.Equal(t, defaultVersion, hdrIntVer.GetVersion(0, 1)) + require.Equal(t, "v1", hdrIntVer.GetVersion(5, 10)) + require.Equal(t, "v2", hdrIntVer.GetVersion(5, 150)) } diff --git a/factory/block/metaHeaderFactory.go b/factory/block/metaHeaderFactory.go index 0e86a799d09..7123c20d6dd 100644 --- a/factory/block/metaHeaderFactory.go +++ b/factory/block/metaHeaderFactory.go @@ -8,7 +8,7 @@ import ( // HeaderVersionGetter can get the header version based on epoch type HeaderVersionGetter interface { - GetVersion(epoch uint32) string + GetVersion(epoch uint32, round uint64) string IsInterfaceNil() bool } @@ -27,12 +27,21 @@ func NewMetaHeaderFactory(headerVersionHandler HeaderVersionGetter) (*metaHeader } // Create creates a metaBlock instance with the correct version and format, according to the epoch -func (mhv *metaHeaderVersionHandler) Create(epoch uint32) data.HeaderHandler { - version := mhv.headerVersionHandler.GetVersion(epoch) +func (mhv *metaHeaderVersionHandler) Create(epoch uint32, round uint64) data.HeaderHandler { + version := mhv.headerVersionHandler.GetVersion(epoch, round) - return &block.MetaBlock{ - Epoch: epoch, - SoftwareVersion: []byte(version), + switch version { + case "3": + return &block.MetaBlockV3{ + Epoch: epoch, + Round: round, + SoftwareVersion: []byte(version), + } + default: + return &block.MetaBlock{ + Epoch: epoch, + SoftwareVersion: []byte(version), + } } } diff --git a/factory/block/metaHeaderFactory_test.go b/factory/block/metaHeaderFactory_test.go index e4ccba0fb86..bb5abe00f00 100644 --- a/factory/block/metaHeaderFactory_test.go +++ b/factory/block/metaHeaderFactory_test.go @@ -31,10 +31,12 @@ func TestNewMetaHeaderFactory_CreateOK(t *testing.T) { t.Parallel() hvh := &testscommon.HeaderVersionHandlerStub{ - GetVersionCalled: func(epoch uint32) string { + GetVersionCalled: func(epoch uint32, _ uint64) string { switch epoch { case 1: return "2" + case 2: + return "3" } return "*" }, @@ -43,14 +45,21 @@ func TestNewMetaHeaderFactory_CreateOK(t *testing.T) { mhf, _ := NewMetaHeaderFactory(hvh) epoch := uint32(0) - header := mhf.Create(epoch) + header := mhf.Create(epoch, 0) require.NotNil(t, header) require.IsType(t, &block.MetaBlock{}, header) require.Equal(t, epoch, header.GetEpoch()) epoch = uint32(1) - header = mhf.Create(epoch) + header = mhf.Create(epoch, 0) require.NotNil(t, header) require.IsType(t, &block.MetaBlock{}, header) require.Equal(t, epoch, header.GetEpoch()) + + epoch = uint32(2) + header = mhf.Create(epoch, 0) + require.NotNil(t, header) + require.IsType(t, &block.MetaBlockV3{}, header) + require.Equal(t, epoch, header.GetEpoch()) + require.Equal(t, []byte("3"), header.GetSoftwareVersion()) } diff --git a/factory/block/shardHeaderFactory.go b/factory/block/shardHeaderFactory.go index 21e770606c8..ab540740e36 100644 --- a/factory/block/shardHeaderFactory.go +++ b/factory/block/shardHeaderFactory.go @@ -22,8 +22,8 @@ func NewShardHeaderFactory(headerVersionHandler HeaderVersionGetter) (*shardHead } // Create creates a header instance with the correct version and format, according to the epoch -func (shf *shardHeaderFactory) Create(epoch uint32) data.HeaderHandler { - version := shf.headerVersionHandler.GetVersion(epoch) +func (shf *shardHeaderFactory) Create(epoch uint32, round uint64) data.HeaderHandler { + version := shf.headerVersionHandler.GetVersion(epoch, round) switch version { case "2": @@ -34,6 +34,12 @@ func (shf *shardHeaderFactory) Create(epoch uint32) data.HeaderHandler { }, ScheduledRootHash: nil, } + case "3": + return &block.HeaderV3{ + Epoch: epoch, + Round: round, + SoftwareVersion: []byte(version), + } default: return &block.Header{ Epoch: epoch, diff --git a/factory/block/shardHeaderFactory_test.go b/factory/block/shardHeaderFactory_test.go index f6230001af5..736fbea3550 100644 --- a/factory/block/shardHeaderFactory_test.go +++ b/factory/block/shardHeaderFactory_test.go @@ -32,12 +32,15 @@ func TestNewShardHeaderFactory_CreateOK(t *testing.T) { v1Version := "*" v2Version := "2" + v3Version := "3" hvh := &testscommon.HeaderVersionHandlerStub{ - GetVersionCalled: func(epoch uint32) string { + GetVersionCalled: func(epoch uint32, _ uint64) string { switch epoch { case 1: return v2Version + case 2: + return v3Version } return v1Version }, @@ -46,14 +49,14 @@ func TestNewShardHeaderFactory_CreateOK(t *testing.T) { shf, _ := NewShardHeaderFactory(hvh) epoch := uint32(0) - header := shf.Create(epoch) + header := shf.Create(epoch, 0) require.NotNil(t, header) require.IsType(t, &block.Header{}, header) require.Equal(t, epoch, header.GetEpoch()) require.Equal(t, []byte(v1Version), header.GetSoftwareVersion()) epoch = uint32(1) - header = shf.Create(epoch) + header = shf.Create(epoch, 0) require.NotNil(t, header) require.IsType(t, &block.HeaderV2{}, header) @@ -62,4 +65,12 @@ func TestNewShardHeaderFactory_CreateOK(t *testing.T) { require.Equal(t, epoch, header.GetEpoch()) require.Equal(t, []byte(v2Version), header.GetSoftwareVersion()) + + epoch = uint32(2) + header = shf.Create(epoch, 0) + require.NotNil(t, header) + + require.IsType(t, &block.HeaderV3{}, header) + require.Equal(t, epoch, header.GetEpoch()) + require.Equal(t, []byte(v3Version), header.GetSoftwareVersion()) } diff --git a/factory/bootstrap/bootstrapComponents.go b/factory/bootstrap/bootstrapComponents.go index 2154289a4c7..9a6a091f834 100644 --- a/factory/bootstrap/bootstrapComponents.go +++ b/factory/bootstrap/bootstrapComponents.go @@ -3,7 +3,6 @@ package bootstrap import ( "fmt" "path/filepath" - "time" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" @@ -27,7 +26,6 @@ import ( "github.com/multiversx/mx-chain-go/storage/directoryhandler" storageFactory "github.com/multiversx/mx-chain-go/storage/factory" "github.com/multiversx/mx-chain-go/storage/latestData" - "github.com/multiversx/mx-chain-go/storage/storageunit" ) var log = logger.GetOrCreate("factory") @@ -118,15 +116,9 @@ func (bcf *bootstrapComponentsFactory) Create() (*bootstrapComponents, error) { return nil, err } - versionsCache, err := storageunit.NewCache(storageFactory.GetCacherFromConfig(bcf.config.Versions.Cache)) - if err != nil { - return nil, err - } - headerVersionHandler, err := block.NewHeaderVersionHandler( bcf.config.Versions.VersionsByEpochs, bcf.config.Versions.DefaultVersion, - versionsCache, ) if err != nil { return nil, err @@ -204,8 +196,7 @@ func (bcf *bootstrapComponentsFactory) Create() (*bootstrapComponents, error) { // create a new instance of interceptedDataVerifier which will be used for bootstrap only interceptedDataVerifierFactory := interceptorFactory.NewInterceptedDataVerifierFactory(interceptorFactory.InterceptedDataVerifierFactoryArgs{ - CacheSpan: time.Duration(bcf.config.InterceptedDataVerifier.CacheSpanInSec) * time.Second, - CacheExpiry: time.Duration(bcf.config.InterceptedDataVerifier.CacheExpiryInSec) * time.Second, + InterceptedDataVerifierConfig: bcf.config.InterceptedDataVerifier, }) epochStartBootstrapArgs := bootstrap.ArgsEpochStartBootstrap{ diff --git a/factory/bootstrap/bootstrapComponents_test.go b/factory/bootstrap/bootstrapComponents_test.go index 180315b1f36..ce4b0f5caf5 100644 --- a/factory/bootstrap/bootstrapComponents_test.go +++ b/factory/bootstrap/bootstrapComponents_test.go @@ -9,7 +9,6 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-go/common" - "github.com/multiversx/mx-chain-go/config" errorsMx "github.com/multiversx/mx-chain-go/errors" "github.com/multiversx/mx-chain-go/factory/bootstrap" "github.com/multiversx/mx-chain-go/testscommon" @@ -139,21 +138,6 @@ func TestBootstrapComponentsFactory_Create(t *testing.T) { require.Nil(t, bc) require.True(t, strings.Contains(err.Error(), "DestinationShardAsObserver")) }) - t.Run("NewCache fails should error", func(t *testing.T) { - t.Parallel() - - args := componentsMock.GetBootStrapFactoryArgs() - args.Config.Versions.Cache = config.CacheConfig{ - Type: "LRU", - SizeInBytes: 1, - } - bcf, _ := bootstrap.NewBootstrapComponentsFactory(args) - require.NotNil(t, bcf) - - bc, err := bcf.Create() - require.Nil(t, bc) - require.True(t, strings.Contains(err.Error(), "LRU")) - }) t.Run("NewHeaderVersionHandler fails should error", func(t *testing.T) { t.Parallel() diff --git a/factory/bootstrap/shardingFactory.go b/factory/bootstrap/shardingFactory.go index 3c23df8fdaa..f60f7b6cdcf 100644 --- a/factory/bootstrap/shardingFactory.go +++ b/factory/bootstrap/shardingFactory.go @@ -240,6 +240,7 @@ func CreateNodesShuffleOut( nodesConfig sharding.GenesisNodesSetupHandler, epochConfig config.EpochStartConfig, chanStopNodeProcess chan endProcess.ArgEndProcess, + chainParametersHandler process.ChainParametersHandler, ) (factory.ShuffleOutCloser, error) { if check.IfNil(nodesConfig) { @@ -255,7 +256,9 @@ func CreateNodesShuffleOut( return nil, fmt.Errorf("invalid min threshold for shuffled out handler") } - epochDuration := int64(nodesConfig.GetRoundDuration()) * epochConfig.RoundsPerEpoch + roundsPerEpoch := chainParametersHandler.CurrentChainParameters().RoundsPerEpoch + roundDuration := chainParametersHandler.CurrentChainParameters().RoundDuration + epochDuration := int64(roundDuration) * roundsPerEpoch minDurationBeforeStopProcess := int64(minThresholdEpochDuration * float64(epochDuration)) maxDurationBeforeStopProcess := int64(maxThresholdEpochDuration * float64(epochDuration)) diff --git a/factory/bootstrap/shardingFactory_test.go b/factory/bootstrap/shardingFactory_test.go index 4e6f118ab29..6adf32c55a7 100644 --- a/factory/bootstrap/shardingFactory_test.go +++ b/factory/bootstrap/shardingFactory_test.go @@ -628,19 +628,21 @@ func TestCreateNodesShuffleOut(t *testing.T) { t.Run("nil nodes config should error", func(t *testing.T) { t.Parallel() - shuffler, err := CreateNodesShuffleOut(nil, config.EpochStartConfig{}, make(chan endProcess.ArgEndProcess, 1)) + chainParameterHandler := &chainParameters.ChainParametersHandlerStub{} + shuffler, err := CreateNodesShuffleOut(nil, config.EpochStartConfig{}, make(chan endProcess.ArgEndProcess, 1), chainParameterHandler) require.Equal(t, errErd.ErrNilGenesisNodesSetupHandler, err) require.True(t, check.IfNil(shuffler)) }) t.Run("invalid MaxShuffledOutRestartThreshold should error", func(t *testing.T) { t.Parallel() - + chainParameterHandler := &chainParameters.ChainParametersHandlerStub{} shuffler, err := CreateNodesShuffleOut( &genesisMocks.NodesSetupStub{}, config.EpochStartConfig{ MaxShuffledOutRestartThreshold: 5.0, }, make(chan endProcess.ArgEndProcess, 1), + chainParameterHandler, ) require.True(t, strings.Contains(err.Error(), "invalid max threshold for shuffled out handler")) require.True(t, check.IfNil(shuffler)) @@ -648,12 +650,14 @@ func TestCreateNodesShuffleOut(t *testing.T) { t.Run("invalid MaxShuffledOutRestartThreshold should error", func(t *testing.T) { t.Parallel() + chainParameterHandler := &chainParameters.ChainParametersHandlerStub{} shuffler, err := CreateNodesShuffleOut( &genesisMocks.NodesSetupStub{}, config.EpochStartConfig{ MinShuffledOutRestartThreshold: 5.0, }, make(chan endProcess.ArgEndProcess, 1), + chainParameterHandler, ) require.True(t, strings.Contains(err.Error(), "invalid min threshold for shuffled out handler")) require.True(t, check.IfNil(shuffler)) @@ -661,10 +665,12 @@ func TestCreateNodesShuffleOut(t *testing.T) { t.Run("NewShuffleOutCloser fails should error", func(t *testing.T) { t.Parallel() + chainParameterHandler := &chainParameters.ChainParametersHandlerStub{} shuffler, err := CreateNodesShuffleOut( &genesisMocks.NodesSetupStub{}, config.EpochStartConfig{}, nil, // force NewShuffleOutCloser to fail + chainParameterHandler, ) require.NotNil(t, err) require.True(t, check.IfNil(shuffler)) @@ -672,6 +678,14 @@ func TestCreateNodesShuffleOut(t *testing.T) { t.Run("should work", func(t *testing.T) { t.Parallel() + chainParameterHandler := &chainParameters.ChainParametersHandlerStub{ + CurrentChainParametersCalled: func() config.ChainParametersByEpochConfig { + return config.ChainParametersByEpochConfig{ + RoundsPerEpoch: 20, + RoundDuration: 6000, + } + }, + } shuffler, err := CreateNodesShuffleOut( &genesisMocks.NodesSetupStub{ GetRoundDurationCalled: func() uint64 { @@ -679,11 +693,11 @@ func TestCreateNodesShuffleOut(t *testing.T) { }, }, config.EpochStartConfig{ - RoundsPerEpoch: 200, MinShuffledOutRestartThreshold: 0.05, MaxShuffledOutRestartThreshold: 0.25, }, make(chan endProcess.ArgEndProcess, 1), + chainParameterHandler, ) require.Nil(t, err) require.False(t, check.IfNil(shuffler)) diff --git a/factory/consensus/consensusComponents.go b/factory/consensus/consensusComponents.go index 39efa2c4240..8862fe21de6 100644 --- a/factory/consensus/consensusComponents.go +++ b/factory/consensus/consensusComponents.go @@ -161,6 +161,9 @@ func (ccf *consensusComponentsFactory) Create() (*consensusComponents, error) { ccf.processComponents.ShardCoordinator(), ccf.cryptoComponents.PeerSignatureHandler(), ccf.dataComponents.Datapool().Headers(), + ccf.dataComponents.Datapool().Headers(), + ccf.dataComponents.Datapool().Proofs(), + ccf.coreComponents.EnableEpochsHandler(), ccf.processComponents.InterceptorsContainer(), ccf.coreComponents.AlarmScheduler(), ccf.cryptoComponents.KeysHandler(), @@ -222,6 +225,7 @@ func (ccf *consensusComponentsFactory) Create() (*consensusComponents, error) { NodeRedundancyHandler: ccf.processComponents.NodeRedundancyHandler(), PeerBlacklistHandler: cc.peerBlacklistHandler, EnableEpochsHandler: ccf.coreComponents.EnableEpochsHandler(), + EnableRoundsHandler: ccf.coreComponents.EnableRoundsHandler(), InvalidSignersCache: invalidSignersCache, } @@ -245,6 +249,7 @@ func (ccf *consensusComponentsFactory) Create() (*consensusComponents, error) { consensusArgs := &spos.ConsensusCoreArgs{ BlockChain: ccf.dataComponents.Blockchain(), BlockProcessor: ccf.processComponents.BlockProcessor(), + ExecutionManager: ccf.processComponents.ExecutionManager(), Bootstrapper: cc.bootstrapper, BroadcastMessenger: cc.broadcastMessenger, ChronologyHandler: cc.chronology, @@ -266,9 +271,11 @@ func (ccf *consensusComponentsFactory) Create() (*consensusComponents, error) { PeerBlacklistHandler: cc.peerBlacklistHandler, SigningHandler: ccf.cryptoComponents.ConsensusSigningHandler(), EnableEpochsHandler: ccf.coreComponents.EnableEpochsHandler(), + EnableRoundsHandler: ccf.coreComponents.EnableRoundsHandler(), EquivalentProofsPool: ccf.dataComponents.Datapool().Proofs(), EpochNotifier: ccf.coreComponents.EpochNotifier(), InvalidSignersCache: invalidSignersCache, + AOTSelector: ccf.processComponents.AOTSelector(), } consensusDataContainer, err := spos.NewConsensusCore( @@ -355,11 +362,14 @@ func (ccf *consensusComponentsFactory) createChronology() (consensus.ChronologyH } chronologyArg := chronology.ArgChronology{ - GenesisTime: ccf.coreComponents.GenesisTime(), - RoundHandler: ccf.processComponents.RoundHandler(), - SyncTimer: ccf.coreComponents.SyncTimer(), - Watchdog: wd, - AppStatusHandler: ccf.statusCoreComponents.AppStatusHandler(), + GenesisTime: ccf.coreComponents.GenesisTime(), + RoundHandler: ccf.processComponents.RoundHandler(), + SyncTimer: ccf.coreComponents.SyncTimer(), + Watchdog: wd, + AppStatusHandler: ccf.statusCoreComponents.AppStatusHandler(), + EnableEpochsHandler: ccf.coreComponents.EnableEpochsHandler(), + EnableRoundsHandler: ccf.coreComponents.EnableRoundsHandler(), + ConfigsHandler: ccf.coreComponents.CommonConfigsHandler(), } return chronology.NewChronology(chronologyArg) } @@ -415,7 +425,9 @@ func (ccf *consensusComponentsFactory) createConsensusState(epoch uint32, consen consensusState := spos.NewConsensusState( roundConsensus, roundThreshold, - roundStatus) + roundStatus, + ccf.processComponents.NodeRedundancyHandler(), + ) return consensusState, nil } @@ -459,6 +471,7 @@ func (ccf *consensusComponentsFactory) createShardBootstrapper() (process.Bootst AppStatusHandler: ccf.statusCoreComponents.AppStatusHandler(), EnableEpochsHandler: ccf.coreComponents.EnableEpochsHandler(), ProofsPool: ccf.dataComponents.Datapool().Proofs(), + ExecutionManager: ccf.processComponents.ExecutionManager(), } argsShardStorageBootstrapper := storageBootstrap.ArgsShardStorageBootstrapper{ @@ -490,7 +503,6 @@ func (ccf *consensusComponentsFactory) createShardBootstrapper() (process.Bootst ChainHandler: ccf.dataComponents.Blockchain(), RoundHandler: ccf.processComponents.RoundHandler(), BlockProcessor: ccf.processComponents.BlockProcessor(), - WaitTime: ccf.processComponents.RoundHandler().TimeDuration(), Hasher: ccf.coreComponents.Hasher(), Marshalizer: ccf.coreComponents.InternalMarshalizer(), ForkDetector: ccf.processComponents.ForkDetector(), @@ -512,8 +524,12 @@ func (ccf *consensusComponentsFactory) createShardBootstrapper() (process.Bootst HistoryRepo: ccf.processComponents.HistoryRepository(), ScheduledTxsExecutionHandler: ccf.processComponents.ScheduledTxsExecutionHandler(), ProcessWaitTime: time.Duration(ccf.config.GeneralSettings.SyncProcessTimeInMillis) * time.Millisecond, + ProcessWaitTimeSupernova: time.Duration(ccf.config.GeneralSettings.SyncProcessTimeSupernovaInMillis) * time.Millisecond, RepopulateTokensSupplies: ccf.flagsConfig.RepopulateTokensSupplies, EnableEpochsHandler: ccf.coreComponents.EnableEpochsHandler(), + ExecutionManager: ccf.processComponents.ExecutionManager(), + EnableRoundsHandler: ccf.coreComponents.EnableRoundsHandler(), + ProcessConfigsHandler: ccf.coreComponents.ProcessConfigsHandler(), } argsShardBootstrapper := sync.ArgShardBootstrapper{ @@ -595,6 +611,7 @@ func (ccf *consensusComponentsFactory) createMetaChainBootstrapper() (process.Bo AppStatusHandler: ccf.statusCoreComponents.AppStatusHandler(), EnableEpochsHandler: ccf.coreComponents.EnableEpochsHandler(), ProofsPool: ccf.dataComponents.Datapool().Proofs(), + ExecutionManager: ccf.processComponents.ExecutionManager(), } argsMetaStorageBootstrapper := storageBootstrap.ArgsMetaStorageBootstrapper{ @@ -623,7 +640,7 @@ func (ccf *consensusComponentsFactory) createMetaChainBootstrapper() (process.Bo ChainHandler: ccf.dataComponents.Blockchain(), RoundHandler: ccf.processComponents.RoundHandler(), BlockProcessor: ccf.processComponents.BlockProcessor(), - WaitTime: ccf.processComponents.RoundHandler().TimeDuration(), + ExecutionManager: ccf.processComponents.ExecutionManager(), Hasher: ccf.coreComponents.Hasher(), Marshalizer: ccf.coreComponents.InternalMarshalizer(), ForkDetector: ccf.processComponents.ForkDetector(), @@ -645,8 +662,11 @@ func (ccf *consensusComponentsFactory) createMetaChainBootstrapper() (process.Bo HistoryRepo: ccf.processComponents.HistoryRepository(), ScheduledTxsExecutionHandler: ccf.processComponents.ScheduledTxsExecutionHandler(), ProcessWaitTime: time.Duration(ccf.config.GeneralSettings.SyncProcessTimeInMillis) * time.Millisecond, + ProcessWaitTimeSupernova: time.Duration(ccf.config.GeneralSettings.SyncProcessTimeSupernovaInMillis) * time.Millisecond, RepopulateTokensSupplies: ccf.flagsConfig.RepopulateTokensSupplies, EnableEpochsHandler: ccf.coreComponents.EnableEpochsHandler(), + EnableRoundsHandler: ccf.coreComponents.EnableRoundsHandler(), + ProcessConfigsHandler: ccf.coreComponents.ProcessConfigsHandler(), } argsMetaBootstrapper := sync.ArgMetaBootstrapper{ @@ -762,6 +782,9 @@ func checkArgs(args ConsensusComponentsFactoryArgs) error { if check.IfNil(args.ProcessComponents.HardforkTrigger()) { return errors.ErrNilHardforkTrigger } + if check.IfNil(args.ProcessComponents.NodeRedundancyHandler()) { + return errors.ErrNilNodeRedundancyHandler + } if check.IfNil(args.StateComponents) { return errors.ErrNilStateComponentsHolder } diff --git a/factory/consensus/consensusComponents_test.go b/factory/consensus/consensusComponents_test.go index 161c49e777f..d89121da7f1 100644 --- a/factory/consensus/consensusComponents_test.go +++ b/factory/consensus/consensusComponents_test.go @@ -11,6 +11,8 @@ import ( crypto "github.com/multiversx/mx-chain-crypto-go" "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/consensus" retriever "github.com/multiversx/mx-chain-go/dataRetriever" @@ -26,7 +28,6 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/cache" consensusMocks "github.com/multiversx/mx-chain-go/testscommon/consensus" "github.com/multiversx/mx-chain-go/testscommon/cryptoMocks" - "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" dataRetrieverMocks "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/dblookupext" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" @@ -69,9 +70,12 @@ func createMockConsensusComponentsFactoryArgs() consensusComp.ConsensusComponent return 2 }, }, - EpochChangeNotifier: &epochNotifier.EpochNotifierStub{}, - StartTime: time.Time{}, - EnableEpochsHandlerField: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EpochChangeNotifier: &epochNotifier.EpochNotifierStub{}, + StartTime: time.Time{}, + EnableEpochsHandlerField: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandlerField: &testscommon.EnableRoundsHandlerStub{}, + ProcessConfigsHandlerField: testscommon.GetDefaultProcessConfigsHandler(), + CommonConfigsHandlerField: testscommon.GetDefaultCommonConfigsHandler(), }, NetworkComponents: &testsMocks.NetworkComponentsStub{ Messenger: &p2pmocks.MessengerStub{}, @@ -92,7 +96,7 @@ func createMockConsensusComponentsFactoryArgs() consensusComp.ConsensusComponent SigHandler: &consensusMocks.SigningHandlerStub{}, }, DataComponents: &testsMocks.DataComponentsStub{ - DataPool: &dataRetriever.PoolsHolderStub{ + DataPool: &dataRetrieverMocks.PoolsHolderStub{ MiniBlocksCalled: func() storage.Cacher { return &cache.CacherStub{} }, @@ -115,7 +119,7 @@ func createMockConsensusComponentsFactoryArgs() consensusComp.ConsensusComponent }, }, MbProvider: &testsMocks.MiniBlocksProviderStub{}, - Store: &genericMocks.ChainStorerMock{}, + Store: genericMocks.NewChainStorerMock(0), }, ProcessComponents: &testsMocks.ProcessComponentsStub{ EpochTrigger: &testsMocks.EpochStartTriggerStub{}, @@ -148,6 +152,7 @@ func createMockConsensusComponentsFactoryArgs() consensusComp.ConsensusComponent FallbackHdrValidator: &testscommon.FallBackHeaderValidatorStub{}, SentSignaturesTrackerInternal: &testscommon.SentSignatureTrackerStub{}, BlockchainHookField: &testscommon.BlockChainHookStub{}, + ExecManager: &processMocks.ExecutionManagerMock{}, }, StateComponents: &factoryMocks.StateComponentsMock{ StorageManagers: map[string]common.StorageManager{ @@ -155,8 +160,10 @@ func createMockConsensusComponentsFactoryArgs() consensusComp.ConsensusComponent retriever.PeerAccountsUnit.String(): &storageManager.StorageManagerStub{}, }, Accounts: &stateMocks.AccountsStub{}, + AccountsProposal: &stateMocks.AccountsStub{}, PeersAcc: &stateMocks.AccountsStub{}, MissingNodesNotifier: &testscommon.MissingTrieNodesNotifierStub{}, + ChangesCollector: &stateMocks.StateAccessesCollectorStub{}, }, StatusComponents: &testsMocks.StatusComponentsStub{ Outport: &outportMocks.OutportStub{}, @@ -230,7 +237,7 @@ func TestNewConsensusComponentsFactory(t *testing.T) { args := createMockConsensusComponentsFactoryArgs() args.DataComponents = &testsMocks.DataComponentsStub{ - DataPool: &dataRetriever.PoolsHolderStub{}, + DataPool: &dataRetrieverMocks.PoolsHolderStub{}, BlockChain: nil, } ccf, err := consensusComp.NewConsensusComponentsFactory(args) @@ -344,6 +351,22 @@ func TestNewConsensusComponentsFactory(t *testing.T) { require.Nil(t, ccf) require.Equal(t, errorsMx.ErrNilRoundHandler, err) }) + t.Run("nil NodeRedundancyHandler should error", func(t *testing.T) { + t.Parallel() + + args := createMockConsensusComponentsFactoryArgs() + args.ProcessComponents = &testsMocks.ProcessComponentsStub{ + NodesCoord: &shardingMocks.NodesCoordinatorMock{}, + ShardCoord: &testscommon.ShardsCoordinatorMock{}, + RoundHandlerField: &testscommon.RoundHandlerMock{}, + HardforkTriggerField: &testscommon.HardforkTriggerStub{}, + NodeRedundancyHandlerInternal: nil, + } + ccf, err := consensusComp.NewConsensusComponentsFactory(args) + + require.Nil(t, ccf) + require.Equal(t, errorsMx.ErrNilNodeRedundancyHandler, err) + }) t.Run("nil HardforkTrigger should error", func(t *testing.T) { t.Parallel() diff --git a/factory/core/coreComponents.go b/factory/core/coreComponents.go index 4376798f825..9708aa668cf 100644 --- a/factory/core/coreComponents.go +++ b/factory/core/coreComponents.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "sync" + "sync/atomic" "time" "github.com/multiversx/mx-chain-core-go/core" @@ -23,6 +24,7 @@ import ( "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/common/chainparametersnotifier" + commonConfigs "github.com/multiversx/mx-chain-go/common/configs" "github.com/multiversx/mx-chain-go/common/enablers" commonFactory "github.com/multiversx/mx-chain-go/common/factory" "github.com/multiversx/mx-chain-go/common/fieldsChecker" @@ -98,12 +100,13 @@ type coreComponents struct { nodesShuffler nodesCoordinator.NodesShuffler txVersionChecker process.TxVersionCheckerHandler genesisTime time.Time + supernovaGenesisTime time.Time chainID string minTransactionVersion uint32 epochNotifier process.EpochNotifier roundNotifier process.RoundNotifier chainParametersSubscriber process.ChainParametersSubscriber - enableRoundsHandler process.EnableRoundsHandler + enableRoundsHandler common.EnableRoundsHandler epochStartNotifierWithConfirm factory.EpochStartNotifierWithConfirm chanStopNodeProcess chan endProcess.ArgEndProcess nodeTypeProvider core.NodeTypeProviderHandler @@ -115,6 +118,10 @@ type coreComponents struct { chainParametersHandler process.ChainParametersHandler fieldsSizeChecker common.FieldsSizeChecker epochChangeGracePeriodHandler common.EpochChangeGracePeriodHandler + processConfigsHandler common.ProcessConfigsHandler + epochStartConfigsHandler common.CommonConfigsHandler + antifloodConfigsHandler common.AntifloodConfigsHandler + closingNodeStarted *atomic.Bool } // NewCoreComponentsFactory initializes the factory which is responsible to creating core components @@ -177,6 +184,15 @@ func (ccf *coreComponentsFactory) Create() (*coreComponents, error) { return nil, fmt.Errorf("%w for epochChangeGracePeriod", err) } + commonConfigsHandler, err := commonConfigs.NewCommonConfigsHandler( + ccf.config.GeneralSettings.EpochStartConfigsByEpoch, + ccf.config.GeneralSettings.EpochStartConfigsByRound, + ccf.config.GeneralSettings.ConsensusConfigsByEpoch, + ) + if err != nil { + return nil, fmt.Errorf("%w for commonConfigsHandler", err) + } + pathHandler, err := storageFactory.CreatePathManager( storageFactory.ArgCreatePathManager{ WorkingDir: ccf.workingDir, @@ -187,12 +203,12 @@ func (ccf *coreComponentsFactory) Create() (*coreComponents, error) { return nil, err } - syncer := ntp.NewSyncTime(ccf.config.NTPConfig, nil) - syncer.StartSyncingTime() - log.Debug("NTP average clock offset", "value", syncer.ClockOffset()) - epochNotifier := forking.NewGenericEpochNotifier() epochStartHandlerWithConfirm := notifier.NewEpochStartSubscriptionHandler() + enableEpochsHandler, err := enablers.NewEnableEpochsHandler(ccf.epochConfig.EnableEpochs, epochNotifier) + if err != nil { + return nil, err + } chainParametersNotifier := chainparametersnotifier.NewChainParametersNotifier() argsChainParametersHandler := sharding.ArgsChainParametersHolder{ @@ -205,6 +221,29 @@ func (ccf *coreComponentsFactory) Create() (*coreComponents, error) { return nil, err } + roundNotifier := forking.NewGenericRoundNotifier() + enableRoundsHandler, err := enablers.NewEnableRoundsHandler(ccf.roundConfig, roundNotifier) + if err != nil { + return nil, err + } + + processConfigs, err := commonConfigs.NewProcessConfigsHandler( + ccf.config.GeneralSettings.ProcessConfigsByEpoch, + ccf.config.GeneralSettings.ProcessConfigsByRound, + roundNotifier, + ) + if err != nil { + return nil, fmt.Errorf("%w for processConfigsByEpoch", err) + } + + antifloodConfigsHandler, err := commonConfigs.NewAntifloodConfigsHandler( + ccf.config.Antiflood, + roundNotifier, + ) + if err != nil { + return nil, fmt.Errorf("%w for antifloodConfigsHandler", err) + } + genesisNodesConfig, err := sharding.NewNodesSetup( ccf.nodesSetupConfig, chainParametersHandler, @@ -216,7 +255,19 @@ func (ccf *coreComponentsFactory) Create() (*coreComponents, error) { return nil, err } + genesisRoundDuration := time.Millisecond * time.Duration(genesisNodesConfig.GetRoundDuration()) + supernovaRoundDuration, err := getSupernovaRoundDuration(enableEpochsHandler, chainParametersHandler) + if err != nil { + return nil, err + } + + syncer := ntp.NewSyncTime(ccf.config.NTPConfig, nil) + syncer.StartSyncingTime() + log.Debug("NTP average clock offset", "value", syncer.ClockOffset()) + startRound := int64(0) + supernovaStartRound := int64(enableRoundsHandler.GetActivationRound(common.SupernovaRoundFlag)) + if ccf.config.Hardfork.AfterHardFork { log.Debug("changed genesis time after hardfork", "old genesis time", genesisNodesConfig.StartTime, @@ -227,24 +278,52 @@ func (ccf *coreComponentsFactory) Create() (*coreComponents, error) { if genesisNodesConfig.StartTime == 0 { time.Sleep(1000 * time.Millisecond) - ntpTime := syncer.CurrentTime() - genesisNodesConfig.StartTime = (ntpTime.Unix()/60 + 1) * 60 + + startTime := common.RoundToNearestMinute(syncer.CurrentTime()) + + genesisNodesConfig.StartTime = common.GetGenesisUnixTimestampFromStartTime(startTime, enableEpochsHandler) } - startTime := time.Unix(genesisNodesConfig.StartTime, 0) + startTime := common.GetGenesisStartTimeFromUnixTimestamp(genesisNodesConfig.GetStartTime(), enableEpochsHandler) + + genesisTime := common.GetGenesisStartTimeFromUnixTimestamp(genesisNodesConfig.GetStartTime(), enableEpochsHandler) + supernovaGenesisTime := genesisTime.Add(time.Duration(supernovaStartRound * genesisRoundDuration.Nanoseconds())) + + if supernovaStartRound < startRound { + return nil, fmt.Errorf("supernovaStartRound %d lower then startRound %d", + supernovaStartRound, + startRound, + ) + } + + if supernovaGenesisTime.Compare(genesisTime) < 0 { + return nil, fmt.Errorf("supernovaGenesisTime %d lower then genesisTime %d", + supernovaGenesisTime.UnixMilli(), + genesisTime.UnixMilli(), + ) + } log.Info("start time", "formatted", startTime.Format("Mon Jan 2 15:04:05 MST 2006"), - "seconds", startTime.Unix()) - - genesisTime := time.Unix(genesisNodesConfig.StartTime, 0) - roundHandler, err := round.NewRound( - genesisTime, - syncer.CurrentTime(), - time.Millisecond*time.Duration(genesisNodesConfig.RoundDuration), - syncer, - startRound, + "unix timestamp", common.GetGenesisUnixTimestampFromStartTime(startTime, enableEpochsHandler), + "supernova unix timestamp", common.GetGenesisUnixTimestampFromStartTime(supernovaGenesisTime, enableEpochsHandler), + "round duration", genesisRoundDuration, + "supernova round duration", supernovaRoundDuration, ) + + roundArgs := round.ArgsRound{ + GenesisTimeStamp: genesisTime, + SupernovaGenesisTimeStamp: supernovaGenesisTime, + CurrentTimeStamp: syncer.CurrentTime(), + RoundTimeDuration: genesisRoundDuration, + SupernovaTimeDuration: supernovaRoundDuration, + SyncTimer: syncer, + StartRound: startRound, + SupernovaStartRound: supernovaStartRound, + EnableRoundsHandler: enableRoundsHandler, + ImportDBMode: ccf.importDbConfig.IsImportDBMode, + } + roundHandler, err := round.NewRound(roundArgs) if err != nil { return nil, err } @@ -256,17 +335,6 @@ func (ccf *coreComponentsFactory) Create() (*coreComponents, error) { return nil, err } - roundNotifier := forking.NewGenericRoundNotifier() - enableRoundsHandler, err := enablers.NewEnableRoundsHandler(ccf.roundConfig, roundNotifier) - if err != nil { - return nil, err - } - - enableEpochsHandler, err := enablers.NewEnableEpochsHandler(ccf.epochConfig.EnableEpochs, epochNotifier) - if err != nil { - return nil, err - } - wasmVMChangeLocker := &sync.RWMutex{} txVersionChecker := versioning.NewTxVersionChecker(ccf.config.GeneralSettings.MinTransactionVersion) @@ -283,7 +351,7 @@ func (ccf *coreComponentsFactory) Create() (*coreComponents, error) { log.Trace("creating economics data components") argsNewEconomicsData := economics.ArgsNewEconomicsData{ Economics: &ccf.economicsConfig, - GeneralConfig: &ccf.config, + ChainParamsHandler: chainParametersHandler, EpochNotifier: epochNotifier, EnableEpochsHandler: enableEpochsHandler, TxVersionChecker: txVersionChecker, @@ -302,17 +370,16 @@ func (ccf *coreComponentsFactory) Create() (*coreComponents, error) { log.Trace("creating ratings data") ratingDataArgs := rating.RatingsDataArg{ - Config: ccf.ratingsConfig, - ChainParametersHolder: chainParametersHandler, - RoundDurationMilliseconds: genesisNodesConfig.RoundDuration, - EpochNotifier: epochNotifier, + Config: ccf.ratingsConfig, + ChainParametersHolder: chainParametersHandler, + EpochNotifier: epochNotifier, } ratingsData, err := rating.NewRatingsData(ratingDataArgs) if err != nil { return nil, err } - rater, err := rating.NewBlockSigningRater(ratingsData) + rater, err := rating.NewBlockSigningRater(ratingsData, enableEpochsHandler) if err != nil { return nil, err } @@ -370,6 +437,7 @@ func (ccf *coreComponentsFactory) Create() (*coreComponents, error) { nodesShuffler: nodesShuffler, txVersionChecker: txVersionChecker, genesisTime: genesisTime, + supernovaGenesisTime: supernovaGenesisTime, chainID: ccf.config.GeneralSettings.ChainID, minTransactionVersion: ccf.config.GeneralSettings.MinTransactionVersion, epochNotifier: epochNotifier, @@ -387,9 +455,26 @@ func (ccf *coreComponentsFactory) Create() (*coreComponents, error) { chainParametersHandler: chainParametersHandler, fieldsSizeChecker: fieldsSizeChecker, epochChangeGracePeriodHandler: epochChangeGracePeriodHandler, + processConfigsHandler: processConfigs, + epochStartConfigsHandler: commonConfigsHandler, + antifloodConfigsHandler: antifloodConfigsHandler, + closingNodeStarted: &atomic.Bool{}, }, nil } +func getSupernovaRoundDuration( + enableEpochsHandler common.EnableEpochsHandler, + chainParametersHandler common.ChainParametersHandler, +) (time.Duration, error) { + activationEpoch := enableEpochsHandler.GetActivationEpoch(common.SupernovaFlag) + chainParams, err := chainParametersHandler.ChainParametersForEpoch(activationEpoch) + if err != nil { + return 0, err + } + + return time.Duration(chainParams.RoundDuration) * time.Millisecond, nil +} + // Close closes all underlying components func (cc *coreComponents) Close() error { if !check.IfNil(cc.alarmScheduler) { diff --git a/factory/core/coreComponentsHandler.go b/factory/core/coreComponentsHandler.go index 13b1735ad36..0e796e0f5d9 100644 --- a/factory/core/coreComponentsHandler.go +++ b/factory/core/coreComponentsHandler.go @@ -3,6 +3,7 @@ package core import ( "fmt" "sync" + "sync/atomic" "time" "github.com/multiversx/mx-chain-core-go/core" @@ -372,6 +373,18 @@ func (mcc *managedCoreComponents) GenesisTime() time.Time { return mcc.coreComponents.genesisTime } +// SupernovaGenesisTime returns the time for supernova round activation +func (mcc *managedCoreComponents) SupernovaGenesisTime() time.Time { + mcc.mutCoreComponents.RLock() + defer mcc.mutCoreComponents.RUnlock() + + if mcc.coreComponents == nil { + return time.Time{} + } + + return mcc.coreComponents.supernovaGenesisTime +} + // Watchdog returns the minimum watchdog func (mcc *managedCoreComponents) Watchdog() core.WatchdogTimer { mcc.mutCoreComponents.RLock() @@ -505,7 +518,7 @@ func (mcc *managedCoreComponents) ChainParametersSubscriber() process.ChainParam } // EnableRoundsHandler returns the rounds activation handler -func (mcc *managedCoreComponents) EnableRoundsHandler() process.EnableRoundsHandler { +func (mcc *managedCoreComponents) EnableRoundsHandler() common.EnableRoundsHandler { mcc.mutCoreComponents.RLock() defer mcc.mutCoreComponents.RUnlock() @@ -636,6 +649,54 @@ func (mcc *managedCoreComponents) EpochChangeGracePeriodHandler() common.EpochCh return mcc.coreComponents.epochChangeGracePeriodHandler } +// ProcessConfigsHandler returns the process configs handler component +func (mcc *managedCoreComponents) ProcessConfigsHandler() common.ProcessConfigsHandler { + mcc.mutCoreComponents.RLock() + defer mcc.mutCoreComponents.RUnlock() + + if mcc.coreComponents == nil { + return nil + } + + return mcc.coreComponents.processConfigsHandler +} + +// CommonConfigsHandler returns the epoch start configs handler component +func (mcc *managedCoreComponents) CommonConfigsHandler() common.CommonConfigsHandler { + mcc.mutCoreComponents.RLock() + defer mcc.mutCoreComponents.RUnlock() + + if mcc.coreComponents == nil { + return nil + } + + return mcc.coreComponents.epochStartConfigsHandler +} + +// AntifloodConfigsHandler returns the antiflood configs handler component +func (mcc *managedCoreComponents) AntifloodConfigsHandler() common.AntifloodConfigsHandler { + mcc.mutCoreComponents.RLock() + defer mcc.mutCoreComponents.RUnlock() + + if mcc.coreComponents == nil { + return nil + } + + return mcc.coreComponents.antifloodConfigsHandler +} + +// ClosingNodeStarted returns the flag that indicates if the node is in the process of closing +func (mcc *managedCoreComponents) ClosingNodeStarted() *atomic.Bool { + mcc.mutCoreComponents.RLock() + defer mcc.mutCoreComponents.RUnlock() + + if mcc.coreComponents == nil { + return nil + } + + return mcc.coreComponents.closingNodeStarted +} + // IsInterfaceNil returns true if there is no value under the interface func (mcc *managedCoreComponents) IsInterfaceNil() bool { return mcc == nil diff --git a/factory/crypto/cryptoComponents.go b/factory/crypto/cryptoComponents.go index 532a2e71356..9f5c23e40b4 100644 --- a/factory/crypto/cryptoComponents.go +++ b/factory/crypto/cryptoComponents.go @@ -27,6 +27,8 @@ import ( "github.com/multiversx/mx-chain-go/genesis/process/disabled" "github.com/multiversx/mx-chain-go/keysManagement" p2pFactory "github.com/multiversx/mx-chain-go/p2p/factory" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/storage/cache" storageFactory "github.com/multiversx/mx-chain-go/storage/factory" "github.com/multiversx/mx-chain-go/storage/storageunit" "github.com/multiversx/mx-chain-go/vm" @@ -36,6 +38,8 @@ import ( const ( disabledSigChecking = "disabled" + // TODO: add to config + pubKeysCacheSize = 5000 ) // CryptoComponentsFactoryArgs holds the arguments needed for creating crypto components @@ -68,6 +72,8 @@ type cryptoComponentsFactory struct { isInImportMode bool importModeNoSigCheck bool p2pKeyPemFileName string + + coreComponentsHolder factory.CoreComponentsHolder } // cryptoParams holds the node public/private key data @@ -113,6 +119,10 @@ func NewCryptoComponentsFactory(args CryptoComponentsFactoryArgs) (*cryptoCompon if check.IfNil(args.CoreComponentsHolder.ValidatorPubKeyConverter()) { return nil, errors.ErrNilPubKeyConverter } + if check.IfNil(args.CoreComponentsHolder.ProcessConfigsHandler()) { + return nil, process.ErrNilProcessConfigsHandler + } + if len(args.ValidatorKeyPemFileName) == 0 { return nil, errors.ErrNilPath } @@ -134,6 +144,7 @@ func NewCryptoComponentsFactory(args CryptoComponentsFactoryArgs) (*cryptoCompon enableEpochs: args.EnableEpochs, p2pKeyPemFileName: args.P2pKeyPemFileName, allValidatorKeysPemFileName: args.AllValidatorKeysPemFileName, + coreComponentsHolder: args.CoreComponentsHolder, } return ccf, nil @@ -201,12 +212,10 @@ func (ccf *cryptoComponentsFactory) Create() (*cryptoComponents, error) { return nil, err } - redundancyLevel := int(ccf.prefsConfig.Preferences.RedundancyLevel) - maxRoundsOfInactivity := redundancyLevel * ccf.config.Redundancy.MaxRoundsOfInactivityAccepted argsManagedPeersHolder := keysManagement.ArgsManagedPeersHolder{ KeyGenerator: blockSignKeyGen, P2PKeyGenerator: p2pKeyGenerator, - MaxRoundsOfInactivity: maxRoundsOfInactivity, + ProcessConfigsHandler: ccf.coreComponentsHolder.ProcessConfigsHandler(), PrefsConfig: ccf.prefsConfig, P2PKeyConverter: p2pFactory.NewP2PKeyConverter(), } @@ -239,12 +248,17 @@ func (ccf *cryptoComponentsFactory) Create() (*cryptoComponents, error) { return nil, err } + pubKeysCache, err := cache.NewLRUCache(pubKeysCacheSize) + if err != nil { + return nil, err + } signingHandlerArgs := ArgsSigningHandler{ PubKeys: []string{cp.publicKeyString}, MultiSignerContainer: multiSigner, KeyGenerator: blockSignKeyGen, SingleSigner: interceptSingleSigner, KeysHandler: keysHandler, + PubKeysCache: pubKeysCache, } consensusSigningHandler, err := NewSigningHandler(signingHandlerArgs) if err != nil { diff --git a/factory/crypto/cryptoComponentsHandler.go b/factory/crypto/cryptoComponentsHandler.go index cca697174fa..7eb1b8e7105 100644 --- a/factory/crypto/cryptoComponentsHandler.go +++ b/factory/crypto/cryptoComponentsHandler.go @@ -260,7 +260,7 @@ func (mcc *managedCryptoComponents) SetMultiSignerContainer(ms cryptoCommon.Mult } // GetMultiSigner returns the multiSigner configured in the multiSigner container for the given epoch -func (mcc *managedCryptoComponents) GetMultiSigner(epoch uint32) (crypto.MultiSigner, error) { +func (mcc *managedCryptoComponents) GetMultiSigner(epoch uint32) (crypto.MultiSignerV2, error) { mcc.mutCryptoComponents.RLock() defer mcc.mutCryptoComponents.RUnlock() diff --git a/factory/crypto/errors.go b/factory/crypto/errors.go index 6d349d4eb2b..513e7f09e88 100644 --- a/factory/crypto/errors.go +++ b/factory/crypto/errors.go @@ -20,6 +20,9 @@ var ErrNoPublicKeySet = errors.New("no public key was set") // ErrNilKeyGenerator is raised when a valid key generator is expected but nil used var ErrNilKeyGenerator = errors.New("key generator is nil") +// ErrNilCacher is raised when a valid cacher is expected but nil used +var ErrNilCacher = errors.New("cacher is nil") + // ErrNilPublicKeys is raised when public keys are expected but received nil var ErrNilPublicKeys = errors.New("public keys are nil") diff --git a/factory/crypto/multiSignerContainer.go b/factory/crypto/multiSignerContainer.go index 2455039e619..220ef43e684 100644 --- a/factory/crypto/multiSignerContainer.go +++ b/factory/crypto/multiSignerContainer.go @@ -24,7 +24,7 @@ const ( type epochMultiSigner struct { epoch uint32 - multiSigner crypto.MultiSigner + multiSigner crypto.MultiSignerV2 } type container struct { @@ -71,7 +71,7 @@ func NewMultiSignerContainer(args MultiSigArgs, multiSignerConfig []config.Multi } // GetMultiSigner returns the multiSigner configured for the given epoch -func (c *container) GetMultiSigner(epoch uint32) (crypto.MultiSigner, error) { +func (c *container) GetMultiSigner(epoch uint32) (crypto.MultiSignerV2, error) { c.mutSigners.RLock() defer c.mutSigners.RUnlock() @@ -88,7 +88,7 @@ func (c *container) IsInterfaceNil() bool { return c == nil } -func createMultiSigner(multiSigType string, args MultiSigArgs) (crypto.MultiSigner, error) { +func createMultiSigner(multiSigType string, args MultiSigArgs) (crypto.MultiSignerV2, error) { if args.ImportModeNoSigCheck { log.Warn("using disabled multi signer because the node is running in import-db 'turbo mode'") return &disabledMultiSig.DisabledMultiSig{}, nil diff --git a/factory/crypto/signingHandler.go b/factory/crypto/signingHandler.go index 8b921fc1447..f2a30671f43 100644 --- a/factory/crypto/signingHandler.go +++ b/factory/crypto/signingHandler.go @@ -7,8 +7,13 @@ import ( crypto "github.com/multiversx/mx-chain-crypto-go" cryptoCommon "github.com/multiversx/mx-chain-go/common/crypto" "github.com/multiversx/mx-chain-go/consensus" + "github.com/multiversx/mx-chain-go/errors" + "github.com/multiversx/mx-chain-go/storage" ) +// estimated size of a public key object +const pubKeySize = 96 + // ArgsSigningHandler defines the arguments needed to create a new signing handler component type ArgsSigningHandler struct { PubKeys []string @@ -16,6 +21,7 @@ type ArgsSigningHandler struct { SingleSigner crypto.SingleSigner KeyGenerator crypto.KeyGenerator KeysHandler consensus.KeysHandler + PubKeysCache storage.Cacher } type signatureHolderData struct { @@ -31,6 +37,7 @@ type signingHandler struct { singleSigner crypto.SingleSigner keyGen crypto.KeyGenerator keysHandler consensus.KeysHandler + pubKeysCache storage.Cacher } // NewSigningHandler will create a new signing handler component @@ -60,6 +67,7 @@ func NewSigningHandler(args ArgsSigningHandler) (*signingHandler, error) { singleSigner: args.SingleSigner, keyGen: args.KeyGenerator, keysHandler: args.KeysHandler, + pubKeysCache: args.PubKeysCache, }, nil } @@ -76,6 +84,9 @@ func checkArgs(args ArgsSigningHandler) error { if check.IfNil(args.KeyGenerator) { return ErrNilKeyGenerator } + if check.IfNil(args.PubKeysCache) { + return ErrNilCacher + } if len(args.PubKeys) == 0 { return ErrNoPublicKeySet } @@ -91,6 +102,7 @@ func (sh *signingHandler) Create(pubKeys []string) (*signingHandler, error) { MultiSignerContainer: sh.multiSignerContainer, SingleSigner: sh.singleSigner, KeyGenerator: sh.keyGen, + PubKeysCache: sh.pubKeysCache, } return NewSigningHandler(args) } @@ -184,14 +196,18 @@ func (sh *signingHandler) VerifySignatureShare(index uint16, sig []byte, message return ErrIndexOutOfBounds } - pubKey := sh.data.pubKeys[index] + pubKeyBytes := sh.data.pubKeys[index] + pubKey, err := sh.getPubKeyFromBytes(pubKeyBytes) + if err != nil { + return err + } multiSigner, err := sh.multiSignerContainer.GetMultiSigner(epoch) if err != nil { return err } - return multiSigner.VerifySignatureShare(pubKey, message, sig) + return multiSigner.VerifySignatureShareV2(pubKey, message, sig) } // StoreSignatureShare stores the partial signature of the signer with specified position @@ -261,7 +277,7 @@ func (sh *signingHandler) AggregateSigs(bitmap []byte, epoch uint32) ([]byte, er } signatures := make([][]byte, 0, len(sh.data.sigShares)) - pubKeysSigners := make([][]byte, 0, len(sh.data.sigShares)) + pubKeysSigners := make([]crypto.PublicKey, 0, len(sh.data.sigShares)) for i := range sh.data.sigShares { if !sh.isIndexInBitmap(uint16(i), bitmap) { @@ -269,10 +285,57 @@ func (sh *signingHandler) AggregateSigs(bitmap []byte, epoch uint32) ([]byte, er } signatures = append(signatures, sh.data.sigShares[i]) - pubKeysSigners = append(pubKeysSigners, sh.data.pubKeys[i]) + + pubKey, err := sh.getPubKeyFromBytes(sh.data.pubKeys[i]) + if err != nil { + return nil, err + } + pubKeysSigners = append(pubKeysSigners, pubKey) + } + + return multiSigner.AggregateSigsV2(pubKeysSigners, signatures) +} + +func (sh *signingHandler) getPubKeyFromBytes( + pubKeyBytes []byte, +) (crypto.PublicKey, error) { + pubKeyCached, ok := sh.pubKeysCache.Get(pubKeyBytes) + if !ok { + pubKey, err := sh.keyGen.PublicKeyFromByteArray(pubKeyBytes) + if err != nil { + return nil, err + } + + sh.pubKeysCache.Put(pubKeyBytes, pubKey, pubKeySize) + + return pubKey, nil + } + + pubKey, ok := pubKeyCached.(crypto.PublicKey) + if !ok { + return nil, errors.ErrWrongTypeAssertion } - return multiSigner.AggregateSigs(pubKeysSigners, signatures) + return pubKey, nil + +} + +// GetPubKeysFromBytes will return public keys corresponding to the provided public keys bytes +func (sh *signingHandler) GetPubKeysFromBytes( + pubKeysBytes [][]byte, +) ([]crypto.PublicKey, error) { + pubKeys := make([]crypto.PublicKey, 0, len(pubKeysBytes)) + + for _, pubKeyBytes := range pubKeysBytes { + pk, err := sh.getPubKeyFromBytes(pubKeyBytes) + if err != nil { + return nil, err + } + + pubKeys = append(pubKeys, pk) + } + + return pubKeys, nil } // SetAggregatedSig sets the aggregated signature @@ -306,16 +369,20 @@ func (sh *signingHandler) Verify(message []byte, bitmap []byte, epoch uint32) er return err } - pubKeys := make([][]byte, 0, len(sh.data.pubKeys)) - for i, pk := range sh.data.pubKeys { + pubKeys := make([]crypto.PublicKey, 0, len(sh.data.pubKeys)) + for i, pkBytes := range sh.data.pubKeys { if !sh.isIndexInBitmap(uint16(i), bitmap) { continue } + pk, err := sh.getPubKeyFromBytes(pkBytes) + if err != nil { + return err + } pubKeys = append(pubKeys, pk) } - return multiSigner.VerifyAggregatedSig(pubKeys, message, sh.data.aggSig) + return multiSigner.VerifyAggregatedSigV2(pubKeys, message, sh.data.aggSig) } func convertStringsToPubKeysBytes(pubKeys []string) ([][]byte, error) { diff --git a/factory/crypto/signingHandler_test.go b/factory/crypto/signingHandler_test.go index 41033b904ab..281a842e3bf 100644 --- a/factory/crypto/signingHandler_test.go +++ b/factory/crypto/signingHandler_test.go @@ -8,6 +8,7 @@ import ( crypto "github.com/multiversx/mx-chain-crypto-go" cryptoFactory "github.com/multiversx/mx-chain-go/factory/crypto" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/cache" "github.com/multiversx/mx-chain-go/testscommon/cryptoMocks" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -20,6 +21,7 @@ func createMockArgsSigningHandler() cryptoFactory.ArgsSigningHandler { MultiSignerContainer: &cryptoMocks.MultiSignerContainerMock{}, KeyGenerator: &cryptoMocks.KeyGenStub{}, SingleSigner: &cryptoMocks.SingleSignerStub{}, + PubKeysCache: &cache.CacherStub{}, } } @@ -191,7 +193,7 @@ func TestSigningHandler_CreateSignatureShareForPublicKey(t *testing.T) { expectedErr := errors.New("expected error") args.MultiSignerContainer = &cryptoMocks.MultiSignerContainerStub{ - GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSigner, error) { + GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSignerV2, error) { return nil, expectedErr }, } @@ -261,7 +263,7 @@ func TestSigningHandler_VerifySignatureShare(t *testing.T) { expectedErr := errors.New("expected error") multiSigner := &cryptoMocks.MultiSignerStub{ - VerifySignatureShareCalled: func(publicKey, message, sig []byte) error { + VerifySignatureShareV2Called: func(publicKey crypto.PublicKey, message, sig []byte) error { return expectedErr }, } @@ -280,7 +282,7 @@ func TestSigningHandler_VerifySignatureShare(t *testing.T) { expectedErr := errors.New("expected error") args.MultiSignerContainer = &cryptoMocks.MultiSignerContainerStub{ - GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSigner, error) { + GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSignerV2, error) { return nil, expectedErr }, } @@ -448,7 +450,7 @@ func TestSigningHandler_AggregateSigs(t *testing.T) { expectedErr := errors.New("expected error") multiSigner := &cryptoMocks.MultiSignerStub{ - AggregateSigsCalled: func(pubKeysSigners, signatures [][]byte) ([]byte, error) { + AggregateSigsV2Called: func(pubKeysSigners []crypto.PublicKey, signatures [][]byte) ([]byte, error) { return nil, expectedErr }, } @@ -474,7 +476,7 @@ func TestSigningHandler_AggregateSigs(t *testing.T) { expectedErr := errors.New("expected error") args.MultiSignerContainer = &cryptoMocks.MultiSignerContainerStub{ - GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSigner, error) { + GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSignerV2, error) { return nil, expectedErr }, } @@ -496,7 +498,7 @@ func TestSigningHandler_AggregateSigs(t *testing.T) { expectedAggSig := []byte("agg sig") multiSigner := &cryptoMocks.MultiSignerStub{ - AggregateSigsCalled: func(pubKeysSigners, signatures [][]byte) ([]byte, error) { + AggregateSigsV2Called: func(pubKeysSigners []crypto.PublicKey, signatures [][]byte) ([]byte, error) { require.Equal(t, len(args.PubKeys)-1, len(pubKeysSigners)) require.Equal(t, len(args.PubKeys)-1, len(signatures)) return expectedAggSig, nil @@ -559,7 +561,7 @@ func TestSigningHandler_Verify(t *testing.T) { expectedErr := errors.New("expected error") multiSigner := &cryptoMocks.MultiSignerStub{ - VerifyAggregatedSigCalled: func(pubKeysSigners [][]byte, message, aggSig []byte) error { + VerifyAggregatedSigV2Called: func(pubKeysSigners []crypto.PublicKey, message, aggSig []byte) error { return expectedErr }, } @@ -580,7 +582,7 @@ func TestSigningHandler_Verify(t *testing.T) { expectedErr := errors.New("expected error") args.MultiSignerContainer = &cryptoMocks.MultiSignerContainerStub{ - GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSigner, error) { + GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSignerV2, error) { return nil, expectedErr }, } @@ -602,7 +604,7 @@ func TestSigningHandler_Verify(t *testing.T) { expAggSig := []byte("aggSig") multiSigner := &cryptoMocks.MultiSignerStub{ - VerifyAggregatedSigCalled: func(pubKeysSigners [][]byte, message, aggSig []byte) error { + VerifyAggregatedSigV2Called: func(pubKeysSigners []crypto.PublicKey, message, aggSig []byte) error { require.Equal(t, len(args.PubKeys)-1, len(pubKeysSigners)) require.Equal(t, expAggSig, aggSig) return nil diff --git a/factory/data/dataComponentsHandler_test.go b/factory/data/dataComponentsHandler_test.go index 7c1e2fef913..7e2f9070e30 100644 --- a/factory/data/dataComponentsHandler_test.go +++ b/factory/data/dataComponentsHandler_test.go @@ -133,8 +133,10 @@ func TestManagedDataComponents_Clone(t *testing.T) { coreComponents := componentsMock.GetCoreComponents() shardCoordinator := mock.NewMultiShardsCoordinatorMock(2) args := componentsMock.GetDataArgs(coreComponents, shardCoordinator) - dataComponentsFactory, _ := dataComp.NewDataComponentsFactory(args) - managedDataComponents, _ := dataComp.NewManagedDataComponents(dataComponentsFactory) + dataComponentsFactory, err := dataComp.NewDataComponentsFactory(args) + require.NoError(t, err) + managedDataComponents, err := dataComp.NewManagedDataComponents(dataComponentsFactory) + require.NoError(t, err) clonedBeforeCreate := managedDataComponents.Clone() require.Equal(t, managedDataComponents, clonedBeforeCreate) diff --git a/factory/disabled/txCoordinator.go b/factory/disabled/txCoordinator.go index 2db31acfdc7..97fd37623d8 100644 --- a/factory/disabled/txCoordinator.go +++ b/factory/disabled/txCoordinator.go @@ -5,6 +5,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/block/processedMb" @@ -19,6 +20,11 @@ func (txCoordinator *TxCoordinator) CreatePostProcessMiniBlocks() block.MiniBloc return make(block.MiniBlockSlice, 0) } +// GetUnExecutableTransactions does nothing as it is disabled +func (txCoordinator *TxCoordinator) GetUnExecutableTransactions() map[string]struct{} { + return make(map[string]struct{}) +} + // CreateReceiptsHash does nothing as it is disabled func (txCoordinator *TxCoordinator) CreateReceiptsHash() ([]byte, error) { return nil, nil @@ -62,7 +68,7 @@ func (txCoordinator *TxCoordinator) RemoveBlockDataFromPool(_ *block.Body) error } // RemoveTxsFromPool does nothing as it is disabled -func (txCoordinator *TxCoordinator) RemoveTxsFromPool(_ *block.Body) error { +func (txCoordinator *TxCoordinator) RemoveTxsFromPool(_ *block.Body, _ common.RootHashHolder) error { return nil } @@ -71,10 +77,23 @@ func (txCoordinator *TxCoordinator) ProcessBlockTransaction(_ data.HeaderHandler return nil } +// GetCreatedMiniBlocksFromMe does nothing as it is disabled +func (txCoordinator *TxCoordinator) GetCreatedMiniBlocksFromMe() block.MiniBlockSlice { + return make(block.MiniBlockSlice, 0) +} + // CreateBlockStarted does nothing as it is disabled func (txCoordinator *TxCoordinator) CreateBlockStarted() { } +// CreateMbsCrossShardDstMe does nothing as it is disabled +func (txCoordinator *TxCoordinator) CreateMbsCrossShardDstMe( + _ data.HeaderHandler, + _ map[string]*processedMb.ProcessedMiniBlockInfo, +) ([]block.MiniblockAndHash, []block.MiniblockAndHash, uint32, bool, bool, error) { + return make([]block.MiniblockAndHash, 0), make([]block.MiniblockAndHash, 0), 0, false, false, nil +} + // CreateMbsAndProcessCrossShardTransactionsDstMe does nothing as it is disabled func (txCoordinator *TxCoordinator) CreateMbsAndProcessCrossShardTransactionsDstMe( _ data.HeaderHandler, @@ -86,6 +105,11 @@ func (txCoordinator *TxCoordinator) CreateMbsAndProcessCrossShardTransactionsDst return make(block.MiniBlockSlice, 0), 0, false, nil } +// SelectOutgoingTransactions does nothing as it is disabled +func (txCoordinator *TxCoordinator) SelectOutgoingTransactions(_ uint64, _ func() bool) ([][]byte, []data.MiniBlockHeaderHandler) { + return make([][]byte, 0), make([]data.MiniBlockHeaderHandler, 0) +} + // CreateMbsAndProcessTransactionsFromMe does nothing as it is disabled func (txCoordinator *TxCoordinator) CreateMbsAndProcessTransactionsFromMe(_ func() bool, _ []byte) block.MiniBlockSlice { return make(block.MiniBlockSlice, 0) @@ -96,6 +120,11 @@ func (txCoordinator *TxCoordinator) CreateMarshalizedData(_ *block.Body) map[str return make(map[string][][]byte) } +// CreateMarshalledDataForHeader does nothing as it is disabled +func (txCoordinator *TxCoordinator) CreateMarshalledDataForHeader(_ data.HeaderHandler, _ *block.Body, _ map[string]block.MiniBlockSlice) map[string][][]byte { + return make(map[string][][]byte) +} + // GetAllCurrentUsedTxs does nothing as it is disabled func (txCoordinator *TxCoordinator) GetAllCurrentUsedTxs(_ block.Type) map[string]data.TransactionHandler { return make(map[string]data.TransactionHandler) @@ -135,8 +164,13 @@ func (txCoordinator *TxCoordinator) AddTransactions(_ []data.TransactionHandler, } // GetAllCurrentLogs returns empty logs map -func (txCoordinator *TxCoordinator) GetAllCurrentLogs() []*data.LogData { - return make([]*data.LogData, 0) +func (txCoordinator *TxCoordinator) GetAllCurrentLogs() []data.LogDataHandler { + return make([]data.LogDataHandler, 0) +} + +// ProposedDirectSentTransactionsToBroadcast returns an empty map +func (txCoordinator *TxCoordinator) ProposedDirectSentTransactionsToBroadcast(_ data.BodyHandler) map[string][][]byte { + return make(map[string][][]byte) } // IsInterfaceNil returns true if there is no value under the interface diff --git a/factory/disabled/txProcessor.go b/factory/disabled/txProcessor.go index 16df5db2d89..cee2a591ec6 100644 --- a/factory/disabled/txProcessor.go +++ b/factory/disabled/txProcessor.go @@ -2,8 +2,9 @@ package disabled import ( "github.com/multiversx/mx-chain-core-go/data/transaction" - "github.com/multiversx/mx-chain-go/state" vmcommon "github.com/multiversx/mx-chain-vm-common-go" + + "github.com/multiversx/mx-chain-go/state" ) // TxProcessor implements the TransactionProcessor interface but does nothing as it is disabled diff --git a/factory/interface.go b/factory/interface.go index 7c33af8c51c..b594b3842e4 100644 --- a/factory/interface.go +++ b/factory/interface.go @@ -3,6 +3,7 @@ package factory import ( "context" "math/big" + "sync/atomic" "time" "github.com/multiversx/mx-chain-core-go/core" @@ -17,11 +18,13 @@ import ( vmcommon "github.com/multiversx/mx-chain-vm-common-go" "github.com/multiversx/mx-chain-core-go/data/smartContractResult" + "github.com/multiversx/mx-chain-go/cmd/node/factory" "github.com/multiversx/mx-chain-go/common" cryptoCommon "github.com/multiversx/mx-chain-go/common/crypto" "github.com/multiversx/mx-chain-go/common/statistics" "github.com/multiversx/mx-chain-go/consensus" + "github.com/multiversx/mx-chain-go/consensus/spos" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/dblookupext" "github.com/multiversx/mx-chain-go/epochStart" @@ -123,11 +126,12 @@ type CoreComponentsHolder interface { NodesShuffler() nodesCoordinator.NodesShuffler EpochNotifier() process.EpochNotifier ChainParametersSubscriber() process.ChainParametersSubscriber - EnableRoundsHandler() process.EnableRoundsHandler + EnableRoundsHandler() common.EnableRoundsHandler RoundNotifier() process.RoundNotifier EpochStartNotifierWithConfirm() EpochStartNotifierWithConfirm ChanStopNodeProcess() chan endProcess.ArgEndProcess GenesisTime() time.Time + SupernovaGenesisTime() time.Time ChainID() string MinTransactionVersion() uint32 TxVersionChecker() process.TxVersionCheckerHandler @@ -140,6 +144,10 @@ type CoreComponentsHolder interface { ChainParametersHandler() process.ChainParametersHandler FieldsSizeChecker() common.FieldsSizeChecker EpochChangeGracePeriodHandler() common.EpochChangeGracePeriodHandler + ProcessConfigsHandler() common.ProcessConfigsHandler + CommonConfigsHandler() common.CommonConfigsHandler + AntifloodConfigsHandler() common.AntifloodConfigsHandler + ClosingNodeStarted() *atomic.Bool IsInterfaceNil() bool } @@ -185,7 +193,7 @@ type CryptoComponentsHolder interface { BlockSigner() crypto.SingleSigner SetMultiSignerContainer(container cryptoCommon.MultiSignerContainer) error MultiSignerContainer() cryptoCommon.MultiSignerContainer - GetMultiSigner(epoch uint32) (crypto.MultiSigner, error) + GetMultiSigner(epoch uint32) (crypto.MultiSignerV2, error) PeerSignatureHandler() crypto.PeerSignatureHandler BlockSignKeyGen() crypto.KeyGenerator TxSignKeyGen() crypto.KeyGenerator @@ -287,6 +295,7 @@ type ProcessComponentsHolder interface { EpochStartNotifier() EpochStartNotifier ForkDetector() process.ForkDetector BlockProcessor() process.BlockProcessor + ExecutionManager() process.ExecutionManager BlackListHandler() process.TimeCacher BootStorer() process.BootStorer HeaderSigVerifier() process.InterceptedHeaderSigVerifier @@ -319,6 +328,7 @@ type ProcessComponentsHolder interface { SentSignaturesTracker() process.SentSignaturesTracker EpochSystemSCProcessor() process.EpochStartSystemSCProcessor BlockchainHook() process.BlockChainHookWithAccountsAdapter + AOTSelector() process.AOTTransactionSelector IsInterfaceNil() bool } @@ -339,6 +349,7 @@ type StateComponentsHolder interface { PeerAccounts() state.AccountsAdapter AccountsAdapter() state.AccountsAdapter AccountsAdapterAPI() state.AccountsAdapter + AccountsAdapterProposal() state.AccountsAdapter AccountsRepository() state.AccountsRepository TriesContainer() common.TriesHolder TrieStorageManagers() map[string]common.StorageManager @@ -421,6 +432,7 @@ type ConsensusWorker interface { ReceivedProof(proofHandler consensus.ProofHandler) // IsInterfaceNil returns true if there is no value under the interface IsInterfaceNil() bool + ConsensusMetrics() spos.ConsensusMetricsHandler } // HardforkTrigger defines the hard-fork trigger functionality @@ -524,7 +536,8 @@ type LogsFacade interface { // ReceiptsRepository defines the interface of a receiptsRepository type ReceiptsRepository interface { SaveReceipts(holder common.ReceiptsHolder, header data.HeaderHandler, headerHash []byte) error - LoadReceipts(header data.HeaderHandler, headerHash []byte) (common.ReceiptsHolder, error) + SaveReceiptsForExecResult(holder common.ReceiptsHolder, execResult data.BaseExecutionResultHandler) error + LoadReceipts(receiptsHash []byte, header data.HeaderHandler, headerHash []byte) (common.ReceiptsHolder, error) IsInterfaceNil() bool } diff --git a/factory/mock/blockTrackerStub.go b/factory/mock/blockTrackerStub.go index 1fc03429b4c..7c519a10863 100644 --- a/factory/mock/blockTrackerStub.go +++ b/factory/mock/blockTrackerStub.go @@ -2,6 +2,7 @@ package mock import ( "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/process" ) @@ -11,8 +12,10 @@ type BlockTrackerStub struct { AddCrossNotarizedHeaderCalled func(shardID uint32, crossNotarizedHeader data.HeaderHandler, crossNotarizedHeaderHash []byte) AddSelfNotarizedHeaderCalled func(shardID uint32, selfNotarizedHeader data.HeaderHandler, selfNotarizedHeaderHash []byte) CheckBlockAgainstRoundHandlerCalled func(headerHandler data.HeaderHandler) error + CheckProofAgainstRoundHandlerCalled func(proof data.HeaderProofHandler) error CheckBlockAgainstFinalCalled func(headerHandler data.HeaderHandler) error - CheckBlockAgainstWhitelistCalled func(interceptedData process.InterceptedData) bool + CheckProofAgainstFinalCalled func(proof data.HeaderProofHandler) error + CheckAgainstWhitelistCalled func(interceptedData process.InterceptedData) bool CleanupHeadersBehindNonceCalled func(shardID uint32, selfNotarizedNonce uint64, crossNotarizedNonce uint64) ComputeLongestChainCalled func(shardID uint32, header data.HeaderHandler) ([]data.HeaderHandler, [][]byte) ComputeLongestMetaChainFromLastNotarizedCalled func() ([]data.HeaderHandler, [][]byte, error) @@ -27,6 +30,7 @@ type BlockTrackerStub struct { GetTrackedHeadersForAllShardsCalled func() map[uint32][]data.HeaderHandler GetTrackedHeadersWithNonceCalled func(shardID uint32, nonce uint64) ([]data.HeaderHandler, [][]byte) IsShardStuckCalled func(shardId uint32) bool + IsOwnShardStuckCalled func() bool ShouldSkipMiniBlocksCreationFromSelfCalled func() bool RegisterCrossNotarizedHeadersHandlerCalled func(handler func(shardID uint32, headers []data.HeaderHandler, headersHashes [][]byte)) RegisterSelfNotarizedFromCrossHeadersHandlerCalled func(handler func(shardID uint32, headers []data.HeaderHandler, headersHashes [][]byte)) @@ -35,6 +39,7 @@ type BlockTrackerStub struct { RemoveLastNotarizedHeadersCalled func() RestoreToGenesisCalled func() ShouldAddHeaderCalled func(headerHandler data.HeaderHandler) bool + ComputeOwnShardStuckCalled func(lastExecutionResultsInfo data.BaseExecutionResultHandler, currentNonce uint64) } // AddTrackedHeader - @@ -76,10 +81,28 @@ func (bts *BlockTrackerStub) CheckBlockAgainstFinal(headerHandler data.HeaderHan return nil } -// CheckBlockAgainstWhitelist - -func (bts *BlockTrackerStub) CheckBlockAgainstWhitelist(interceptedData process.InterceptedData) bool { - if bts.CheckBlockAgainstWhitelistCalled != nil { - return bts.CheckBlockAgainstWhitelistCalled(interceptedData) +// CheckProofAgainstFinal - +func (bts *BlockTrackerStub) CheckProofAgainstFinal(proof data.HeaderProofHandler) error { + if bts.CheckProofAgainstFinalCalled != nil { + return bts.CheckProofAgainstFinalCalled(proof) + } + + return nil +} + +// CheckProofAgainstRoundHandler - +func (bts *BlockTrackerStub) CheckProofAgainstRoundHandler(proof data.HeaderProofHandler) error { + if bts.CheckProofAgainstRoundHandlerCalled != nil { + return bts.CheckProofAgainstRoundHandlerCalled(proof) + } + + return nil +} + +// CheckAgainstWhitelist - +func (bts *BlockTrackerStub) CheckAgainstWhitelist(interceptedData process.InterceptedData) bool { + if bts.CheckAgainstWhitelistCalled != nil { + return bts.CheckAgainstWhitelistCalled(interceptedData) } return false @@ -210,6 +233,15 @@ func (bts *BlockTrackerStub) IsShardStuck(shardId uint32) bool { return false } +// IsOwnShardStuck - +func (bts *BlockTrackerStub) IsOwnShardStuck() bool { + if bts.IsOwnShardStuckCalled != nil { + return bts.IsOwnShardStuckCalled() + } + + return false +} + // ShouldSkipMiniBlocksCreationFromSelf - func (bts *BlockTrackerStub) ShouldSkipMiniBlocksCreationFromSelf() bool { if bts.ShouldSkipMiniBlocksCreationFromSelfCalled != nil { @@ -270,6 +302,13 @@ func (bts *BlockTrackerStub) ShouldAddHeader(headerHandler data.HeaderHandler) b return true } +// ComputeOwnShardStuck - +func (bts *BlockTrackerStub) ComputeOwnShardStuck(lastExecutionResultsInfo data.BaseExecutionResultHandler, currentNonce uint64) { + if bts.ComputeOwnShardStuckCalled != nil { + bts.ComputeOwnShardStuckCalled(lastExecutionResultsInfo, currentNonce) + } +} + // IsInterfaceNil - func (bts *BlockTrackerStub) IsInterfaceNil() bool { return bts == nil diff --git a/factory/mock/coreComponentsMock.go b/factory/mock/coreComponentsMock.go index 6d38ca5208d..6a52f269c80 100644 --- a/factory/mock/coreComponentsMock.go +++ b/factory/mock/coreComponentsMock.go @@ -2,6 +2,7 @@ package mock import ( "sync" + "sync/atomic" "time" "github.com/multiversx/mx-chain-core-go/core" @@ -47,7 +48,7 @@ type CoreComponentsMock struct { Shuffler nodesCoordinator.NodesShuffler EpochChangeNotifier process.EpochNotifier RoundChangeNotifier process.RoundNotifier - EnableRoundsHandlerField process.EnableRoundsHandler + EnableRoundsHandlerField common.EnableRoundsHandler EpochNotifierWithConfirm factory.EpochStartNotifierWithConfirm TxVersionCheckHandler process.TxVersionCheckerHandler ChanStopProcess chan endProcess.ArgEndProcess @@ -61,6 +62,11 @@ type CoreComponentsMock struct { ChainParametersSubscriberField process.ChainParametersSubscriber FieldsSizeCheckerField common.FieldsSizeChecker EpochChangeGracePeriodHandlerField common.EpochChangeGracePeriodHandler + ProcessConfigsHandlerField common.ProcessConfigsHandler + ProcessConfigsHandlerCalled func() common.ProcessConfigsHandler + CommonConfigsHandlerField common.CommonConfigsHandler + AntifloodConfigsHandlerField common.AntifloodConfigsHandler + ClosingNodeStartedField *atomic.Bool } // InternalMarshalizer - @@ -140,6 +146,11 @@ func (ccm *CoreComponentsMock) GenesisTime() time.Time { return ccm.GenesisBlockTime } +// SupernovaGenesisTime - +func (ccm *CoreComponentsMock) SupernovaGenesisTime() time.Time { + return ccm.GenesisBlockTime +} + // ChainID - func (ccm *CoreComponentsMock) ChainID() string { if ccm.ChainIdCalled != nil { @@ -212,7 +223,7 @@ func (ccm *CoreComponentsMock) RoundNotifier() process.RoundNotifier { } // EnableRoundsHandler - -func (ccm *CoreComponentsMock) EnableRoundsHandler() process.EnableRoundsHandler { +func (ccm *CoreComponentsMock) EnableRoundsHandler() common.EnableRoundsHandler { return ccm.EnableRoundsHandlerField } @@ -271,6 +282,33 @@ func (ccm *CoreComponentsMock) EpochChangeGracePeriodHandler() common.EpochChang return ccm.EpochChangeGracePeriodHandlerField } +// ProcessConfigsHandler - +func (ccm *CoreComponentsMock) ProcessConfigsHandler() common.ProcessConfigsHandler { + if ccm.ProcessConfigsHandlerCalled != nil { + return ccm.ProcessConfigsHandlerCalled() + } + return ccm.ProcessConfigsHandlerField +} + +// CommonConfigsHandler - +func (ccm *CoreComponentsMock) CommonConfigsHandler() common.CommonConfigsHandler { + return ccm.CommonConfigsHandlerField +} + +// AntifloodConfigsHandler - +func (ccm *CoreComponentsMock) AntifloodConfigsHandler() common.AntifloodConfigsHandler { + return ccm.AntifloodConfigsHandlerField +} + +// ClosingNodeStarted - +func (ccm *CoreComponentsMock) ClosingNodeStarted() *atomic.Bool { + if ccm.ClosingNodeStartedField == nil { + ccm.ClosingNodeStartedField = &atomic.Bool{} + } + + return ccm.ClosingNodeStartedField +} + // IsInterfaceNil - func (ccm *CoreComponentsMock) IsInterfaceNil() bool { return ccm == nil diff --git a/factory/mock/cryptoComponentsMock.go b/factory/mock/cryptoComponentsMock.go index 5d33c918369..9a83f5c0501 100644 --- a/factory/mock/cryptoComponentsMock.go +++ b/factory/mock/cryptoComponentsMock.go @@ -97,7 +97,7 @@ func (ccm *CryptoComponentsMock) SetMultiSignerContainer(ms cryptoCommon.MultiSi } // GetMultiSigner - -func (ccm *CryptoComponentsMock) GetMultiSigner(epoch uint32) (crypto.MultiSigner, error) { +func (ccm *CryptoComponentsMock) GetMultiSigner(epoch uint32) (crypto.MultiSignerV2, error) { ccm.mutMultiSig.RLock() defer ccm.mutMultiSig.RUnlock() diff --git a/factory/mock/epochStartTriggerStub.go b/factory/mock/epochStartTriggerStub.go index dbd5d7cd09e..15e9cd7f002 100644 --- a/factory/mock/epochStartTriggerStub.go +++ b/factory/mock/epochStartTriggerStub.go @@ -4,6 +4,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/epochStart" ) @@ -19,6 +20,42 @@ type EpochStartTriggerStub struct { UpdateCalled func(round uint64, nonce uint64) ProcessedCalled func(header data.HeaderHandler) EpochStartRoundCalled func() uint64 + ShouldProposeEpochChangeCalled func(round uint64, nonce uint64) bool + SetEpochChangeCalled func(round uint64) + SetEpochChangeProposedCalled func(value bool) + GetEpochChangeProposedCalled func() bool +} + +// SetEpochChange - +func (e *EpochStartTriggerStub) SetEpochChange(round uint64) { + if e.SetEpochChangeCalled != nil { + e.SetEpochChangeCalled(round) + } +} + +// ShouldProposeEpochChange - +func (e *EpochStartTriggerStub) ShouldProposeEpochChange(round uint64, nonce uint64) bool { + if e.ShouldProposeEpochChangeCalled != nil { + return e.ShouldProposeEpochChangeCalled(round, nonce) + } + + return false +} + +// SetEpochChangeProposed - +func (e *EpochStartTriggerStub) SetEpochChangeProposed(value bool) { + if e.SetEpochChangeProposedCalled != nil { + e.SetEpochChangeProposedCalled(value) + } +} + +// GetEpochChangeProposed - +func (e *EpochStartTriggerStub) GetEpochChangeProposed() bool { + if e.GetEpochChangeProposedCalled != nil { + return e.GetEpochChangeProposedCalled() + } + + return false } // RevertStateToBlock - diff --git a/factory/mock/headerIntegrityVerifierStub.go b/factory/mock/headerIntegrityVerifierStub.go index 3d793b89924..5fa049cd3bf 100644 --- a/factory/mock/headerIntegrityVerifierStub.go +++ b/factory/mock/headerIntegrityVerifierStub.go @@ -5,7 +5,7 @@ import "github.com/multiversx/mx-chain-core-go/data" // HeaderIntegrityVerifierStub - type HeaderIntegrityVerifierStub struct { VerifyCalled func(header data.HeaderHandler) error - GetVersionCalled func(epoch uint32) string + GetVersionCalled func(epoch uint32, round uint64) string } // Verify - @@ -18,9 +18,9 @@ func (h *HeaderIntegrityVerifierStub) Verify(header data.HeaderHandler) error { } // GetVersion - -func (h *HeaderIntegrityVerifierStub) GetVersion(epoch uint32) string { +func (h *HeaderIntegrityVerifierStub) GetVersion(epoch uint32, round uint64) string { if h.GetVersionCalled != nil { - return h.GetVersionCalled(epoch) + return h.GetVersionCalled(epoch, round) } return "version" diff --git a/factory/mock/processComponentsStub.go b/factory/mock/processComponentsStub.go index 0c4531189bf..04bce4408be 100644 --- a/factory/mock/processComponentsStub.go +++ b/factory/mock/processComponentsStub.go @@ -1,6 +1,8 @@ package mock import ( + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/multiversx/mx-chain-go/consensus" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/dblookupext" @@ -11,7 +13,6 @@ import ( "github.com/multiversx/mx-chain-go/sharding" "github.com/multiversx/mx-chain-go/sharding/nodesCoordinator" "github.com/multiversx/mx-chain-go/update" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" ) // ProcessComponentsMock - @@ -27,6 +28,7 @@ type ProcessComponentsMock struct { EpochNotifier factory.EpochStartNotifier ForkDetect process.ForkDetector BlockProcess process.BlockProcessor + ExecManager process.ExecutionManager BlackListHdl process.TimeCacher BootSore process.BootStorer HeaderSigVerif process.InterceptedHeaderSigVerifier @@ -59,6 +61,7 @@ type ProcessComponentsMock struct { SentSignaturesTrackerInternal process.SentSignaturesTracker EpochSystemSCProcessorInternal process.EpochStartSystemSCProcessor BlockchainHookField process.BlockChainHookWithAccountsAdapter + AOTSelectorField process.AOTTransactionSelector } // Create - @@ -131,6 +134,11 @@ func (pcm *ProcessComponentsMock) BlockProcessor() process.BlockProcessor { return pcm.BlockProcess } +// ExecutionManager - +func (pcm *ProcessComponentsMock) ExecutionManager() process.ExecutionManager { + return pcm.ExecManager +} + // BlackListHandler - func (pcm *ProcessComponentsMock) BlackListHandler() process.TimeCacher { return pcm.BlackListHdl @@ -296,6 +304,11 @@ func (pcm *ProcessComponentsMock) BlockchainHook() process.BlockChainHookWithAcc return pcm.BlockchainHookField } +// AOTSelector - +func (pcm *ProcessComponentsMock) AOTSelector() process.AOTTransactionSelector { + return pcm.AOTSelectorField +} + // IsInterfaceNil - func (pcm *ProcessComponentsMock) IsInterfaceNil() bool { return pcm == nil diff --git a/factory/mock/rounderMock.go b/factory/mock/rounderMock.go index 4a56cb166aa..0dba7fef0f1 100644 --- a/factory/mock/rounderMock.go +++ b/factory/mock/rounderMock.go @@ -72,6 +72,11 @@ func (rndm *RoundHandlerMock) RemainingTime(_ time.Time, _ time.Duration) time.D return rndm.RoundTimeDuration } +// GetTimeStampForRound returns 0 +func (rndm *RoundHandlerMock) GetTimeStampForRound(round uint64) uint64 { + return 0 +} + // IsInterfaceNil returns true if there is no value under the interface func (rndm *RoundHandlerMock) IsInterfaceNil() bool { return rndm == nil diff --git a/factory/mock/stateComponentsHolderStub.go b/factory/mock/stateComponentsHolderStub.go index 5c1648ced57..73c7f078665 100644 --- a/factory/mock/stateComponentsHolderStub.go +++ b/factory/mock/stateComponentsHolderStub.go @@ -10,6 +10,7 @@ type StateComponentsHolderStub struct { PeerAccountsCalled func() state.AccountsAdapter AccountsAdapterCalled func() state.AccountsAdapter AccountsAdapterAPICalled func() state.AccountsAdapter + AccountsAdapterProposalCalled func() state.AccountsAdapter AccountsRepositoryCalled func() state.AccountsRepository TriesContainerCalled func() common.TriesHolder TrieStorageManagersCalled func() map[string]common.StorageManager @@ -45,6 +46,15 @@ func (s *StateComponentsHolderStub) AccountsAdapterAPI() state.AccountsAdapter { return nil } +// AccountsAdapterProposal - +func (s *StateComponentsHolderStub) AccountsAdapterProposal() state.AccountsAdapter { + if s.AccountsAdapterProposalCalled != nil { + return s.AccountsAdapterProposalCalled() + } + + return nil +} + // AccountsRepository - func (s *StateComponentsHolderStub) AccountsRepository() state.AccountsRepository { if s.AccountsRepositoryCalled != nil { diff --git a/factory/mock/txLogsProcessorMock.go b/factory/mock/txLogsProcessorMock.go index 3158e836493..4d7b7cca215 100644 --- a/factory/mock/txLogsProcessorMock.go +++ b/factory/mock/txLogsProcessorMock.go @@ -2,6 +2,7 @@ package mock import ( "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-go/process" ) @@ -12,8 +13,8 @@ type TxLogProcessorMock struct { } // GetLogFromCache - -func (t *TxLogProcessorMock) GetLogFromCache(_ []byte) (*data.LogData, bool) { - return &data.LogData{}, false +func (t *TxLogProcessorMock) GetLogFromCache(_ []byte) (data.LogDataHandler, bool) { + return &transaction.LogData{}, false } // EnableLogToBeSavedInCache - diff --git a/factory/network/networkComponents.go b/factory/network/networkComponents.go index afe5757ecd7..20fa38f1693 100644 --- a/factory/network/networkComponents.go +++ b/factory/network/networkComponents.go @@ -7,7 +7,6 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" - "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/consensus" @@ -35,13 +34,12 @@ type NetworkComponentsFactoryArgs struct { MainConfig config.Config RatingsConfig config.RatingsConfig StatusHandler core.AppStatusHandler - Marshalizer marshal.Marshalizer - Syncer p2p.SyncTimer PreferredPeersSlices []string BootstrapWaitTime time.Duration NodeOperationMode common.NodeOperation ConnectionWatcherType string CryptoComponents factory.CryptoComponentsHolder + CoreComponents process.CoreComponentsHolder } type networkComponentsFactory struct { @@ -50,13 +48,12 @@ type networkComponentsFactory struct { mainConfig config.Config ratingsConfig config.RatingsConfig statusHandler core.AppStatusHandler - marshalizer marshal.Marshalizer - syncer p2p.SyncTimer preferredPeersSlices []string bootstrapWaitTime time.Duration nodeOperationMode common.NodeOperation connectionWatcherType string cryptoComponents factory.CryptoComponentsHolder + coreComponents process.CoreComponentsHolder } type networkComponentsHolder struct { @@ -76,7 +73,6 @@ type networkComponents struct { topicFloodPreventer process.TopicFloodPreventer floodPreventers []process.FloodPreventer peerBlackListHandler process.PeerBlackListCacher - antifloodConfig config.AntifloodConfig peerHonestyHandler consensus.PeerHonestyHandler closeFunc context.CancelFunc } @@ -90,12 +86,18 @@ func NewNetworkComponentsFactory( if check.IfNil(args.StatusHandler) { return nil, errors.ErrNilStatusHandler } - if check.IfNil(args.Marshalizer) { + if check.IfNil(args.CoreComponents) { + return nil, process.ErrNilCoreComponentsHolder + } + if check.IfNil(args.CoreComponents.InternalMarshalizer()) { return nil, fmt.Errorf("%w in NewNetworkComponentsFactory", errors.ErrNilMarshalizer) } - if check.IfNil(args.Syncer) { + if check.IfNil(args.CoreComponents.SyncTimer()) { return nil, errors.ErrNilSyncTimer } + if check.IfNil(args.CoreComponents.ProcessConfigsHandler()) { + return nil, fmt.Errorf("%w in NewNetworkComponentsFactory", process.ErrNilProcessConfigsHandler) + } if check.IfNil(args.CryptoComponents) { return nil, errors.ErrNilCryptoComponentsHolder } @@ -107,15 +109,14 @@ func NewNetworkComponentsFactory( mainP2PConfig: args.MainP2pConfig, fullArchiveP2PConfig: args.FullArchiveP2pConfig, ratingsConfig: args.RatingsConfig, - marshalizer: args.Marshalizer, mainConfig: args.MainConfig, statusHandler: args.StatusHandler, - syncer: args.Syncer, bootstrapWaitTime: args.BootstrapWaitTime, preferredPeersSlices: args.PreferredPeersSlices, nodeOperationMode: args.NodeOperationMode, connectionWatcherType: args.ConnectionWatcherType, cryptoComponents: args.CryptoComponents, + coreComponents: args.CoreComponents, }, nil } @@ -171,7 +172,6 @@ func (ncf *networkComponentsFactory) Create() (*networkComponents, error) { topicFloodPreventer: antiFloodComponents.TopicPreventer, floodPreventers: antiFloodComponents.FloodPreventers, peerBlackListHandler: antiFloodComponents.BlacklistHandler, - antifloodConfig: ncf.mainConfig.Antiflood, peerHonestyHandler: peerHonestyHandler, closeFunc: cancelFunc, }, nil @@ -182,7 +182,7 @@ func (ncf *networkComponentsFactory) createAntifloodComponents( currentPid core.PeerID, ) (*antifloodFactory.AntiFloodComponents, factory.P2PAntifloodHandler, factory.P2PAntifloodHandler, consensus.PeerHonestyHandler, error) { var antiFloodComponents *antifloodFactory.AntiFloodComponents - antiFloodComponents, err := antifloodFactory.NewP2PAntiFloodComponents(ctx, ncf.mainConfig, ncf.statusHandler, currentPid) + antiFloodComponents, err := antifloodFactory.NewP2PAntiFloodComponents(ctx, ncf.mainConfig, ncf.statusHandler, currentPid, ncf.coreComponents.AntifloodConfigsHandler()) if err != nil { return nil, nil, nil, nil, err } @@ -194,7 +194,7 @@ func (ncf *networkComponentsFactory) createAntifloodComponents( } var outAntifloodHandler process.P2PAntifloodHandler - outAntifloodHandler, err = antifloodFactory.NewP2POutputAntiFlood(ctx, ncf.mainConfig) + outAntifloodHandler, err = antifloodFactory.NewP2POutputAntiFlood(ctx, ncf.coreComponents.AntifloodConfigsHandler()) if err != nil { return nil, nil, nil, nil, err } @@ -245,9 +245,9 @@ func (ncf *networkComponentsFactory) createNetworkHolder( } argsMessenger := p2pFactory.ArgsNetworkMessenger{ - Marshaller: ncf.marshalizer, + Marshaller: ncf.coreComponents.InternalMarshalizer(), P2pConfig: p2pConfig, - SyncTimer: ncf.syncer, + SyncTimer: ncf.coreComponents.SyncTimer(), PreferredPeersHolder: peersHolder, PeersRatingHandler: peersRatingHandler, ConnectionWatcherType: ncf.connectionWatcherType, diff --git a/factory/network/networkComponentsHandler_test.go b/factory/network/networkComponentsHandler_test.go index 811af70cc30..d4b952714db 100644 --- a/factory/network/networkComponentsHandler_test.go +++ b/factory/network/networkComponentsHandler_test.go @@ -91,9 +91,10 @@ func TestManagedNetworkComponents_CheckSubcomponents(t *testing.T) { require.Error(t, managedNetworkComponents.CheckSubcomponents()) - _ = managedNetworkComponents.Create() + err = managedNetworkComponents.Create() + require.Nil(t, err) - require.NoError(t, managedNetworkComponents.CheckSubcomponents()) + require.Nil(t, managedNetworkComponents.CheckSubcomponents()) } func TestManagedNetworkComponents_Close(t *testing.T) { @@ -103,9 +104,11 @@ func TestManagedNetworkComponents_Close(t *testing.T) { } networkArgs := componentsMock.GetNetworkFactoryArgs() - networkComponentsFactory, _ := networkComp.NewNetworkComponentsFactory(networkArgs) - managedNetworkComponents, _ := networkComp.NewManagedNetworkComponents(networkComponentsFactory) - err := managedNetworkComponents.Close() + networkComponentsFactory, err := networkComp.NewNetworkComponentsFactory(networkArgs) + require.Nil(t, err) + managedNetworkComponents, err := networkComp.NewManagedNetworkComponents(networkComponentsFactory) + require.Nil(t, err) + err = managedNetworkComponents.Close() require.NoError(t, err) err = managedNetworkComponents.Create() diff --git a/factory/network/networkComponents_test.go b/factory/network/networkComponents_test.go index 221651b22fb..decff82fc47 100644 --- a/factory/network/networkComponents_test.go +++ b/factory/network/networkComponents_test.go @@ -6,6 +6,7 @@ import ( errorsMx "github.com/multiversx/mx-chain-go/errors" networkComp "github.com/multiversx/mx-chain-go/factory/network" + "github.com/multiversx/mx-chain-go/process" componentsMock "github.com/multiversx/mx-chain-go/testscommon/components" "github.com/stretchr/testify/require" ) @@ -22,11 +23,24 @@ func TestNewNetworkComponentsFactory(t *testing.T) { require.Nil(t, ncf) require.Equal(t, errorsMx.ErrNilStatusHandler, err) }) + t.Run("nil core components should error", func(t *testing.T) { + t.Parallel() + + args := componentsMock.GetNetworkFactoryArgs() + args.CoreComponents = nil + + ncf, err := networkComp.NewNetworkComponentsFactory(args) + require.Nil(t, ncf) + require.True(t, errors.Is(err, process.ErrNilCoreComponentsHolder)) + }) t.Run("nil Marshalizer should error", func(t *testing.T) { t.Parallel() args := componentsMock.GetNetworkFactoryArgs() - args.Marshalizer = nil + coreComps := componentsMock.GetDefaultCoreComponents() + _ = coreComps.SetInternalMarshalizer(nil) + args.CoreComponents = coreComps + ncf, err := networkComp.NewNetworkComponentsFactory(args) require.Nil(t, ncf) require.True(t, errors.Is(err, errorsMx.ErrNilMarshalizer)) @@ -35,11 +49,26 @@ func TestNewNetworkComponentsFactory(t *testing.T) { t.Parallel() args := componentsMock.GetNetworkFactoryArgs() - args.Syncer = nil + coreComps := componentsMock.GetDefaultCoreComponents() + coreComps.NtpSyncTimer = nil + args.CoreComponents = coreComps + ncf, err := networkComp.NewNetworkComponentsFactory(args) require.Nil(t, ncf) require.Equal(t, errorsMx.ErrNilSyncTimer, err) }) + t.Run("nil process configs handler, should error", func(t *testing.T) { + t.Parallel() + + args := componentsMock.GetNetworkFactoryArgs() + coreComps := componentsMock.GetDefaultCoreComponents() + coreComps.ProcessConfigsHandlerField = nil + args.CoreComponents = coreComps + + ncf, err := networkComp.NewNetworkComponentsFactory(args) + require.Nil(t, ncf) + require.ErrorIs(t, err, process.ErrNilProcessConfigsHandler) + }) t.Run("nil CryptoComponents should error", func(t *testing.T) { t.Parallel() @@ -108,19 +137,7 @@ func TestNetworkComponentsFactory_Create(t *testing.T) { require.Error(t, err) require.Nil(t, nc) }) - t.Run("NewP2PAntiFloodComponents fails should error", func(t *testing.T) { - t.Parallel() - - args := componentsMock.GetNetworkFactoryArgs() - args.MainConfig.Antiflood.Enabled = true - args.MainConfig.Antiflood.SlowReacting.BlackList.NumFloodingRounds = 0 // NewP2PAntiFloodComponents fails - ncf, _ := networkComp.NewNetworkComponentsFactory(args) - - nc, err := ncf.Create() - require.Error(t, err) - require.Nil(t, nc) - }) t.Run("NewAntifloodDebugger fails should error", func(t *testing.T) { t.Parallel() diff --git a/factory/processing/blockProcessorCreator.go b/factory/processing/blockProcessorCreator.go index 904af17b231..39ace02da78 100644 --- a/factory/processing/blockProcessorCreator.go +++ b/factory/processing/blockProcessorCreator.go @@ -3,6 +3,7 @@ package processing import ( "errors" "fmt" + "time" "github.com/multiversx/mx-chain-core-go/core" dataBlock "github.com/multiversx/mx-chain-core-go/data/block" @@ -10,6 +11,11 @@ import ( vmcommon "github.com/multiversx/mx-chain-vm-common-go" "github.com/multiversx/mx-chain-vm-common-go/parsers" + "github.com/multiversx/mx-chain-go/epochStart/metachain/disabled" + + "github.com/multiversx/mx-chain-go/process/estimator" + "github.com/multiversx/mx-chain-go/process/missingData" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" @@ -24,8 +30,10 @@ import ( processOutport "github.com/multiversx/mx-chain-go/outport/process" factoryOutportProvider "github.com/multiversx/mx-chain-go/outport/process/factory" "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/aotSelection" "github.com/multiversx/mx-chain-go/process/block" "github.com/multiversx/mx-chain-go/process/block/cutoff" + "github.com/multiversx/mx-chain-go/process/block/headerForBlock" "github.com/multiversx/mx-chain-go/process/block/postprocess" "github.com/multiversx/mx-chain-go/process/block/preprocess" "github.com/multiversx/mx-chain-go/process/coordinator" @@ -44,7 +52,7 @@ import ( "github.com/multiversx/mx-chain-go/process/transaction" "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/state/syncer" - "github.com/multiversx/mx-chain-go/storage/txcache" + "github.com/multiversx/mx-chain-go/txcache" "github.com/multiversx/mx-chain-go/vm" ) @@ -52,6 +60,7 @@ type blockProcessorAndVmFactories struct { blockProcessor process.BlockProcessor vmFactoryForProcessing process.VirtualMachinesContainerFactory epochSystemSCProcessor process.EpochStartSystemSCProcessor + aotSelector process.AOTTransactionSelector } func (pcf *processComponentsFactory) newBlockProcessor( @@ -70,6 +79,7 @@ func (pcf *processComponentsFactory) newBlockProcessor( blockCutoffProcessingHandler cutoff.BlockProcessingCutoffHandler, missingTrieNodesNotifier common.MissingTrieNodesNotifier, sentSignaturesTracker process.SentSignaturesTracker, + executionManager process.ExecutionManager, ) (*blockProcessorAndVmFactories, error) { shardCoordinator := pcf.bootstrapComponents.ShardCoordinator() if shardCoordinator.SelfId() < shardCoordinator.NumberOfShards() { @@ -88,6 +98,7 @@ func (pcf *processComponentsFactory) newBlockProcessor( blockCutoffProcessingHandler, missingTrieNodesNotifier, sentSignaturesTracker, + executionManager, ) } if shardCoordinator.SelfId() == core.MetachainShardId { @@ -106,6 +117,7 @@ func (pcf *processComponentsFactory) newBlockProcessor( receiptsRepository, blockCutoffProcessingHandler, sentSignaturesTracker, + executionManager, ) } @@ -114,6 +126,71 @@ func (pcf *processComponentsFactory) newBlockProcessor( var log = logger.GetOrCreate("factory") +// createAOTSelector creates the AOT (Ahead-of-Time) transaction selector for shard nodes +// This enables pre-selection of transactions before the next consensus round +func (pcf *processComponentsFactory) createAOTSelector( + transactionProcessor process.TransactionProcessor, +) (process.AOTTransactionSelector, error) { + shardCoordinator := pcf.bootstrapComponents.ShardCoordinator() + + // AOT selection only applies to shard nodes, not metachain + if shardCoordinator.SelfId() == core.MetachainShardId { + log.Debug("AOT selection is not applicable for metachain") + return aotSelection.NewDisabledAOTSelector(), nil + } + + // Check if AOT selection is explicitly disabled in config + if !pcf.config.AOTSelection.Enabled { + log.Debug("AOT selection is disabled by configuration") + return aotSelection.NewDisabledAOTSelector(), nil + } + + // Get the TxCache from the datapool + txShardPool := pcf.data.Datapool().Transactions().ShardDataStore( + process.ShardCacherIdentifier(shardCoordinator.SelfId(), shardCoordinator.SelfId()), + ) + if txShardPool == nil { + return nil, fmt.Errorf("createAOTSelector: could not get transaction pool for shard %d", shardCoordinator.SelfId()) + } + + txCache, isTxCache := txShardPool.(preprocess.TxCache) + if !isTxCache { + return nil, fmt.Errorf("createAOTSelector: transaction pool is not a TxCache") + } + + aotSelectorArgs := aotSelection.AOTSelectorArgs{ + NodesCoordinator: pcf.nodesCoordinator, + ShardCoordinator: shardCoordinator, + KeysHandler: pcf.crypto.KeysHandler(), + NodeRedundancy: pcf.nodeRedundancyHandler, + TxCache: txCache, + AccountsAdapter: pcf.state.AccountsAdapterProposal(), + TransactionProcessor: transactionProcessor, + TxVersionChecker: pcf.coreData.TxVersionChecker(), + BlockChain: pcf.data.Blockchain(), + EconomicsDataHandler: pcf.coreData.EconomicsData(), + SelectionTimeout: time.Duration(pcf.config.AOTSelection.SelectionTimeoutMs) * time.Millisecond, + CacheSize: pcf.config.AOTSelection.CacheSize, + MaxTxsPerBlock: pcf.config.TxCacheSelection.SelectionMaxNumTxs, + LoopDurationCheckInterval: pcf.config.TxCacheSelection.SelectionLoopDurationCheckInterval, + } + + aotSelector, err := aotSelection.NewAOTSelector(aotSelectorArgs) + if err != nil { + return nil, fmt.Errorf("createAOTSelector: could not create AOT selector: %w", err) + } + + // Set the AOT selector as the preempter for the TxCache + // This allows the txcache to preempt AOT selection when needed + txCache.SetAOTSelectionPreempter(aotSelector) + + log.Info("AOT transaction selection enabled", + "cacheSize", pcf.config.AOTSelection.CacheSize, + "selectionTimeoutMs", pcf.config.TxCacheSelection.SelectionMaxNumTxs) + + return aotSelector, nil +} + func (pcf *processComponentsFactory) newShardBlockProcessor( requestHandler process.RequestHandler, forkDetector process.ForkDetector, @@ -129,6 +206,7 @@ func (pcf *processComponentsFactory) newShardBlockProcessor( blockProcessingCutoffHandler cutoff.BlockProcessingCutoffHandler, missingTrieNodesNotifier common.MissingTrieNodesNotifier, sentSignaturesTracker process.SentSignaturesTracker, + executionManager process.ExecutionManager, ) (*blockProcessorAndVmFactories, error) { argsParser := smartContract.NewArgumentParser() @@ -318,6 +396,7 @@ func (pcf *processComponentsFactory) newShardBlockProcessor( return nil, err } + // TODO: evaluate disabling this entirely (for old flows) - the check is not triggered if async enabled but there are still some checks in the old flow blockSizeComputationHandler, err := preprocess.NewBlockSizeComputation( pcf.coreData.InternalMarshalizer(), blockSizeThrottler, @@ -327,35 +406,64 @@ func (pcf *processComponentsFactory) newShardBlockProcessor( return nil, err } + blockSizeComputationProposalHandler, err := preprocess.NewBlockSizeComputation( + pcf.coreData.InternalMarshalizer(), + blockSizeThrottler, + pcf.config.BlockSizeThrottleConfig.MaxSizeInBytes, + ) + if err != nil { + return nil, err + } + balanceComputationHandler, err := preprocess.NewBalanceComputation() if err != nil { return nil, err } - preProcFactory, err := shard.NewPreProcessorsContainerFactory( - pcf.bootstrapComponents.ShardCoordinator(), - pcf.data.StorageService(), - pcf.coreData.InternalMarshalizer(), - pcf.coreData.Hasher(), - pcf.data.Datapool(), - pcf.coreData.AddressPubKeyConverter(), - pcf.state.AccountsAdapter(), - requestHandler, - transactionProcessor, - scProcessorProxy, - scProcessorProxy, - rewardsTxProcessor, - pcf.coreData.EconomicsData(), - gasHandler, - blockTracker, - blockSizeComputationHandler, - balanceComputationHandler, - pcf.coreData.EnableEpochsHandler(), - txTypeHandler, - scheduledTxsExecutionHandler, - processedMiniBlocksTracker, - pcf.txExecutionOrderHandler, - ) + argsGasConsumption := block.ArgsGasConsumption{ + EconomicsFee: pcf.coreData.EconomicsData(), + ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), + GasHandler: gasHandler, + BlockCapacityOverestimationFactor: pcf.economicsConfig.FeeSettings.BlockCapacityOverestimationFactor, + PercentDecreaseLimitsStep: pcf.economicsConfig.FeeSettings.PercentDecreaseLimitsStep, + BlockSizeComputation: blockSizeComputationProposalHandler, + } + gasConsumption, err := block.NewGasConsumption(argsGasConsumption) + if err != nil { + return nil, err + } + + argsPreProcFactory := shard.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), + Store: pcf.data.StorageService(), + Marshalizer: pcf.coreData.InternalMarshalizer(), + Hasher: pcf.coreData.Hasher(), + DataPool: pcf.data.Datapool(), + PubkeyConverter: pcf.coreData.AddressPubKeyConverter(), + Accounts: pcf.state.AccountsAdapter(), + AccountsProposal: pcf.state.AccountsAdapterProposal(), + RequestHandler: requestHandler, + TxProcessor: transactionProcessor, + ScProcessor: scProcessorProxy, + ScResultProcessor: scProcessorProxy, + RewardsTxProcessor: rewardsTxProcessor, + EconomicsFee: pcf.coreData.EconomicsData(), + GasHandler: gasHandler, + BlockTracker: blockTracker, + BlockSizeComputation: blockSizeComputationHandler, + BalanceComputation: balanceComputationHandler, + EnableEpochsHandler: pcf.coreData.EnableEpochsHandler(), + EpochNotifier: pcf.coreData.EpochNotifier(), + EnableRoundsHandler: pcf.coreData.EnableRoundsHandler(), + RoundNotifier: pcf.coreData.RoundNotifier(), + TxTypeHandler: txTypeHandler, + ScheduledTxsExecutionHandler: scheduledTxsExecutionHandler, + ProcessedMiniBlocksTracker: processedMiniBlocksTracker, + TxExecutionOrderHandler: pcf.txExecutionOrderHandler, + TxCacheSelectionConfig: pcf.config.TxCacheSelection, + TxVersionChecker: pcf.coreData.TxVersionChecker(), + } + preProcFactory, err := shard.NewPreProcessorsContainerFactory(argsPreProcFactory) if err != nil { return nil, err } @@ -365,6 +473,11 @@ func (pcf *processComponentsFactory) newShardBlockProcessor( return nil, err } + proposalPreProcContainer, err := preProcFactory.Create() + if err != nil { + return nil, err + } + argsDetector := coordinator.ArgsPrintDoubleTransactionsDetector{ Marshaller: pcf.coreData.InternalMarshalizer(), Hasher: pcf.coreData.Hasher(), @@ -375,14 +488,45 @@ func (pcf *processComponentsFactory) newShardBlockProcessor( return nil, err } + blockDataRequesterArgs := coordinator.BlockDataRequestArgs{ + RequestHandler: requestHandler, + MiniBlockPool: pcf.data.Datapool().MiniBlocks(), + PreProcessors: preProcContainer, + ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), + EnableEpochsHandler: pcf.coreData.EnableEpochsHandler(), + } + + blockDataRequester, err := coordinator.NewBlockDataRequester(blockDataRequesterArgs) + if err != nil { + return nil, err + } + + proposalBlockDataRequesterArgs := coordinator.BlockDataRequestArgs{ + RequestHandler: requestHandler, + MiniBlockPool: pcf.data.Datapool().MiniBlocks(), + PreProcessors: proposalPreProcContainer, + ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), + EnableEpochsHandler: pcf.coreData.EnableEpochsHandler(), + } + // second instance for proposal missing data fetching to avoid interferences + proposalBlockDataRequester, err := coordinator.NewBlockDataRequester(proposalBlockDataRequesterArgs) + if err != nil { + return nil, err + } + + aotSelector, err := pcf.createAOTSelector(transactionProcessor) + if err != nil { + return nil, err + } + argsTransactionCoordinator := coordinator.ArgTransactionCoordinator{ Hasher: pcf.coreData.Hasher(), Marshalizer: pcf.coreData.InternalMarshalizer(), ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), Accounts: pcf.state.AccountsAdapter(), - MiniBlockPool: pcf.data.Datapool().MiniBlocks(), - RequestHandler: requestHandler, + DataPool: pcf.data.Datapool(), PreProcessors: preProcContainer, + PreProcessorsProposal: proposalPreProcContainer, InterProcessors: interimProcContainer, GasHandler: gasHandler, FeeHandler: txFeeHandler, @@ -392,17 +536,23 @@ func (pcf *processComponentsFactory) newShardBlockProcessor( TxTypeHandler: txTypeHandler, TransactionsLogProcessor: pcf.txLogsProcessor, EnableEpochsHandler: pcf.coreData.EnableEpochsHandler(), + EnableRoundsHandler: pcf.coreData.EnableRoundsHandler(), ScheduledTxsExecutionHandler: scheduledTxsExecutionHandler, DoubleTransactionsDetector: doubleTransactionsDetector, ProcessedMiniBlocksTracker: processedMiniBlocksTracker, TxExecutionOrderHandler: pcf.txExecutionOrderHandler, + BlockDataRequester: blockDataRequester, + BlockDataRequesterProposal: proposalBlockDataRequester, + GasComputation: gasConsumption, + AOTSelector: aotSelector, } txCoordinator, err := coordinator.NewTransactionCoordinator(argsTransactionCoordinator) if err != nil { return nil, err } - outportDataProvider, err := pcf.createOutportDataProvider(txCoordinator, gasHandler) + disabledRewardsCreator := disabled.NewDisabledEpochRewards() + outportDataProvider, err := pcf.createOutportDataProvider(txCoordinator, gasHandler, disabledRewardsCreator) if err != nil { return nil, err } @@ -413,39 +563,112 @@ func (pcf *processComponentsFactory) newShardBlockProcessor( accountsDb[state.UserAccountsState] = pcf.state.AccountsAdapter() accountsDb[state.PeerAccountsState] = pcf.state.PeerAccounts() + argsHeadersForBlock := headerForBlock.ArgHeadersForBlock{ + DataPool: pcf.data.Datapool(), + RequestHandler: requestHandler, + EnableEpochsHandler: pcf.coreData.EnableEpochsHandler(), + ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), + BlockTracker: blockTracker, + TxCoordinator: txCoordinator, + RoundHandler: pcf.coreData.RoundHandler(), + ExtraDelayForRequestBlockInfoInMilliseconds: pcf.config.EpochStartConfig.ExtraDelayForRequestBlockInfoInMilliseconds, + GenesisNonce: pcf.genesisNonce, + } + hdrsForBlock, err := headerForBlock.NewHeadersForBlock(argsHeadersForBlock) + if err != nil { + return nil, err + } + + mbSelectionSession, err := block.NewMiniBlocksSelectionSession( + pcf.bootstrapComponents.ShardCoordinator().SelfId(), + pcf.coreData.InternalMarshalizer(), + pcf.coreData.Hasher(), + ) + if err != nil { + return nil, err + } + + err = process.SetBaseExecutionResult(executionManager, pcf.data.Blockchain()) + if err != nil { + return nil, err + } + + execResultsVerifier, err := block.NewExecutionResultsVerifier(pcf.data.Blockchain(), executionManager) + if err != nil { + return nil, err + } + + execResSizeComputationHandler, err := estimator.NewExecResultSizeComputationHandler( + pcf.coreData.InternalMarshalizer(), + pcf.config.BlockSizeThrottleConfig.MaxExecResSizeInBytes, + ) + if err != nil { + return nil, err + } + + inclusionEstimator, err := estimator.NewExecutionResultInclusionEstimator( + pcf.config.ExecutionResultInclusionEstimator, + pcf.coreData.RoundHandler(), + execResSizeComputationHandler, + ) + if err != nil { + return nil, err + } + + missingDataArgs := missingData.ResolverArgs{ + HeadersPool: pcf.data.Datapool().Headers(), + ProofsPool: pcf.data.Datapool().Proofs(), + RequestHandler: requestHandler, + BlockDataRequester: proposalBlockDataRequester, + } + missingDataResolver, err := missingData.NewMissingDataResolver(missingDataArgs) + if err != nil { + return nil, err + } + argumentsBaseProcessor := block.ArgBaseProcessor{ - CoreComponents: pcf.coreData, - DataComponents: pcf.data, - BootstrapComponents: pcf.bootstrapComponents, - StatusComponents: pcf.statusComponents, - StatusCoreComponents: pcf.statusCoreComponents, - Config: pcf.config, - PrefsConfig: pcf.prefConfigs, - Version: pcf.flagsConfig.Version, - AccountsDB: accountsDb, - ForkDetector: forkDetector, - NodesCoordinator: pcf.nodesCoordinator, - RequestHandler: requestHandler, - BlockChainHook: vmFactory.BlockChainHookImpl(), - TxCoordinator: txCoordinator, - EpochStartTrigger: epochStartTrigger, - HeaderValidator: headerValidator, - BootStorer: bootStorer, - BlockTracker: blockTracker, - FeeHandler: txFeeHandler, - BlockSizeThrottler: blockSizeThrottler, - HistoryRepository: pcf.historyRepo, - VMContainersFactory: vmFactory, - VmContainer: vmContainer, - GasHandler: gasHandler, - ScheduledTxsExecutionHandler: scheduledTxsExecutionHandler, - ProcessedMiniBlocksTracker: processedMiniBlocksTracker, - ReceiptsRepository: receiptsRepository, - OutportDataProvider: outportDataProvider, - BlockProcessingCutoffHandler: blockProcessingCutoffHandler, - ManagedPeersHolder: pcf.crypto.ManagedPeersHolder(), - SentSignaturesTracker: sentSignaturesTracker, - StateAccessesCollector: pcf.state.StateAccessesCollector(), + CoreComponents: pcf.coreData, + DataComponents: pcf.data, + BootstrapComponents: pcf.bootstrapComponents, + StatusComponents: pcf.statusComponents, + StatusCoreComponents: pcf.statusCoreComponents, + Config: pcf.config, + PrefsConfig: pcf.prefConfigs, + AccountsDB: accountsDb, + AccountsProposal: pcf.state.AccountsAdapterProposal(), + ForkDetector: forkDetector, + NodesCoordinator: pcf.nodesCoordinator, + FeeHandler: txFeeHandler, + RequestHandler: requestHandler, + BlockChainHook: vmFactory.BlockChainHookImpl(), + TxCoordinator: txCoordinator, + EpochStartTrigger: epochStartTrigger, + HeaderValidator: headerValidator, + BootStorer: bootStorer, + BlockTracker: blockTracker, + BlockSizeThrottler: blockSizeThrottler, + Version: pcf.flagsConfig.Version, + HistoryRepository: pcf.historyRepo, + VMContainersFactory: vmFactory, + VmContainer: vmContainer, + GasHandler: gasHandler, + OutportDataProvider: outportDataProvider, + ScheduledTxsExecutionHandler: scheduledTxsExecutionHandler, + ProcessedMiniBlocksTracker: processedMiniBlocksTracker, + ReceiptsRepository: receiptsRepository, + BlockProcessingCutoffHandler: blockProcessingCutoffHandler, + ManagedPeersHolder: pcf.crypto.ManagedPeersHolder(), + SentSignaturesTracker: sentSignaturesTracker, + StateAccessesCollector: pcf.state.StateAccessesCollector(), + HeadersForBlock: hdrsForBlock, + MiniBlocksSelectionSession: mbSelectionSession, + ExecutionResultsVerifier: execResultsVerifier, + MissingDataResolver: missingDataResolver, + ExecutionResultsInclusionEstimator: inclusionEstimator, + GasComputation: gasConsumption, + ExecutionManager: executionManager, + TxExecutionOrderHandler: pcf.txExecutionOrderHandler, + AOTSelector: aotSelector, } arguments := block.ArgShardProcessor{ ArgBaseProcessor: argumentsBaseProcessor, @@ -465,6 +688,7 @@ func (pcf *processComponentsFactory) newShardBlockProcessor( blockProcessor: blockProcessor, vmFactoryForProcessing: vmFactory, epochSystemSCProcessor: factoryDisabled.NewDisabledEpochStartSystemSC(), + aotSelector: aotSelector, } pcf.stakingDataProviderAPI = factoryDisabled.NewDisabledStakingDataProvider() @@ -488,6 +712,7 @@ func (pcf *processComponentsFactory) newMetaBlockProcessor( receiptsRepository mainFactory.ReceiptsRepository, blockProcessingCutoffhandler cutoff.BlockProcessingCutoffHandler, sentSignaturesTracker process.SentSignaturesTracker, + executionManager process.ExecutionManager, ) (*blockProcessorAndVmFactories, error) { builtInFuncFactory, err := pcf.createBuiltInFunctionContainer(pcf.state.AccountsAdapter(), make(map[string]struct{})) if err != nil { @@ -639,33 +864,63 @@ func (pcf *processComponentsFactory) newMetaBlockProcessor( return nil, err } + blockSizeComputationProposalHandler, err := preprocess.NewBlockSizeComputation( + pcf.coreData.InternalMarshalizer(), + blockSizeThrottler, + pcf.config.BlockSizeThrottleConfig.MaxSizeInBytes, + ) + if err != nil { + return nil, err + } + balanceComputationHandler, err := preprocess.NewBalanceComputation() if err != nil { return nil, err } - preProcFactory, err := metachain.NewPreProcessorsContainerFactory( - pcf.bootstrapComponents.ShardCoordinator(), - pcf.data.StorageService(), - pcf.coreData.InternalMarshalizer(), - pcf.coreData.Hasher(), - pcf.data.Datapool(), - pcf.state.AccountsAdapter(), - requestHandler, - transactionProcessor, - scProcessorProxy, - pcf.coreData.EconomicsData(), - gasHandler, - blockTracker, - pcf.coreData.AddressPubKeyConverter(), - blockSizeComputationHandler, - balanceComputationHandler, - pcf.coreData.EnableEpochsHandler(), - txTypeHandler, - scheduledTxsExecutionHandler, - processedMiniBlocksTracker, - pcf.txExecutionOrderHandler, - ) + argsGasConsumption := block.ArgsGasConsumption{ + EconomicsFee: pcf.coreData.EconomicsData(), + ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), + GasHandler: gasHandler, + BlockCapacityOverestimationFactor: pcf.economicsConfig.FeeSettings.BlockCapacityOverestimationFactor, + PercentDecreaseLimitsStep: pcf.economicsConfig.FeeSettings.PercentDecreaseLimitsStep, + BlockSizeComputation: blockSizeComputationProposalHandler, + } + gasConsumption, err := block.NewGasConsumption(argsGasConsumption) + if err != nil { + return nil, err + } + + argsPreprocContainerFactory := metachain.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), + Store: pcf.data.StorageService(), + Marshalizer: pcf.coreData.InternalMarshalizer(), + Hasher: pcf.coreData.Hasher(), + DataPool: pcf.data.Datapool(), + Accounts: pcf.state.AccountsAdapter(), + AccountsProposal: pcf.state.AccountsAdapterProposal(), + RequestHandler: requestHandler, + TxProcessor: transactionProcessor, + ScResultProcessor: scProcessorProxy, + EconomicsFee: pcf.coreData.EconomicsData(), + GasHandler: gasHandler, + BlockTracker: blockTracker, + PubkeyConverter: pcf.coreData.AddressPubKeyConverter(), + BlockSizeComputation: blockSizeComputationHandler, + BalanceComputation: balanceComputationHandler, + EnableEpochsHandler: pcf.coreData.EnableEpochsHandler(), + EpochNotifier: pcf.coreData.EpochNotifier(), + EnableRoundsHandler: pcf.coreData.EnableRoundsHandler(), + RoundNotifier: pcf.coreData.RoundNotifier(), + TxTypeHandler: txTypeHandler, + ScheduledTxsExecutionHandler: scheduledTxsExecutionHandler, + ProcessedMiniBlocksTracker: processedMiniBlocksTracker, + TxExecutionOrderHandler: pcf.txExecutionOrderHandler, + TxCacheSelectionConfig: pcf.config.TxCacheSelection, + TxVersionCheckerHandler: pcf.coreData.TxVersionChecker(), + } + + preProcFactory, err := metachain.NewPreProcessorsContainerFactory(argsPreprocContainerFactory) if err != nil { return nil, err } @@ -674,6 +929,10 @@ func (pcf *processComponentsFactory) newMetaBlockProcessor( if err != nil { return nil, err } + proposalPreProcContainer, err := preProcFactory.Create() + if err != nil { + return nil, err + } argsDetector := coordinator.ArgsPrintDoubleTransactionsDetector{ Marshaller: pcf.coreData.InternalMarshalizer(), @@ -685,14 +944,40 @@ func (pcf *processComponentsFactory) newMetaBlockProcessor( return nil, err } + blockDataRequesterArgs := coordinator.BlockDataRequestArgs{ + RequestHandler: requestHandler, + MiniBlockPool: pcf.data.Datapool().MiniBlocks(), + PreProcessors: preProcContainer, + ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), + EnableEpochsHandler: pcf.coreData.EnableEpochsHandler(), + } + + blockDataRequester, err := coordinator.NewBlockDataRequester(blockDataRequesterArgs) + if err != nil { + return nil, err + } + + proposalBlockDataRequesterArgs := coordinator.BlockDataRequestArgs{ + RequestHandler: requestHandler, + MiniBlockPool: pcf.data.Datapool().MiniBlocks(), + PreProcessors: proposalPreProcContainer, + ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), + EnableEpochsHandler: pcf.coreData.EnableEpochsHandler(), + } + // second instance for proposal missing data fetching to avoid interferences + proposalBlockDataRequester, err := coordinator.NewBlockDataRequester(proposalBlockDataRequesterArgs) + if err != nil { + return nil, err + } + argsTransactionCoordinator := coordinator.ArgTransactionCoordinator{ Hasher: pcf.coreData.Hasher(), Marshalizer: pcf.coreData.InternalMarshalizer(), ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), Accounts: pcf.state.AccountsAdapter(), - MiniBlockPool: pcf.data.Datapool().MiniBlocks(), - RequestHandler: requestHandler, + DataPool: pcf.data.Datapool(), PreProcessors: preProcContainer, + PreProcessorsProposal: proposalPreProcContainer, InterProcessors: interimProcContainer, GasHandler: gasHandler, FeeHandler: txFeeHandler, @@ -702,10 +987,15 @@ func (pcf *processComponentsFactory) newMetaBlockProcessor( TxTypeHandler: txTypeHandler, TransactionsLogProcessor: pcf.txLogsProcessor, EnableEpochsHandler: pcf.coreData.EnableEpochsHandler(), + EnableRoundsHandler: pcf.coreData.EnableRoundsHandler(), ScheduledTxsExecutionHandler: scheduledTxsExecutionHandler, DoubleTransactionsDetector: doubleTransactionsDetector, ProcessedMiniBlocksTracker: processedMiniBlocksTracker, TxExecutionOrderHandler: pcf.txExecutionOrderHandler, + BlockDataRequester: blockDataRequester, + BlockDataRequesterProposal: proposalBlockDataRequester, + GasComputation: gasConsumption, + AOTSelector: aotSelection.NewDisabledAOTSelector(), } txCoordinator, err := coordinator.NewTransactionCoordinator(argsTransactionCoordinator) if err != nil { @@ -758,9 +1048,12 @@ func (pcf *processComponentsFactory) newMetaBlockProcessor( RoundTime: pcf.coreData.RoundHandler(), GenesisNonce: genesisHdr.GetNonce(), GenesisEpoch: genesisHdr.GetEpoch(), + GenesisTimestamp: genesisHdr.GetTimeStamp(), GenesisTotalSupply: pcf.coreData.EconomicsData().GenesisTotalSupply(), EconomicsDataNotified: economicsDataProvider, StakingV2EnableEpoch: pcf.coreData.EnableEpochsHandler().GetActivationEpoch(common.StakingV2Flag), + EnableEpochsHandler: pcf.coreData.EnableEpochsHandler(), + ChainParamsHandler: pcf.coreData.ChainParametersHandler(), } epochEconomics, err := metachainEpochStart.NewEndOfEpochEconomicsDataCreator(argsEpochEconomics) if err != nil { @@ -842,7 +1135,7 @@ func (pcf *processComponentsFactory) newMetaBlockProcessor( return nil, err } - outportDataProvider, err := pcf.createOutportDataProvider(txCoordinator, gasHandler) + outportDataProvider, err := pcf.createOutportDataProvider(txCoordinator, gasHandler, epochRewards) if err != nil { return nil, err } @@ -851,39 +1144,113 @@ func (pcf *processComponentsFactory) newMetaBlockProcessor( accountsDb[state.UserAccountsState] = pcf.state.AccountsAdapter() accountsDb[state.PeerAccountsState] = pcf.state.PeerAccounts() + argsHeadersForBlock := headerForBlock.ArgHeadersForBlock{ + DataPool: pcf.data.Datapool(), + RequestHandler: requestHandler, + EnableEpochsHandler: pcf.coreData.EnableEpochsHandler(), + ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), + BlockTracker: blockTracker, + TxCoordinator: txCoordinator, + RoundHandler: pcf.coreData.RoundHandler(), + ExtraDelayForRequestBlockInfoInMilliseconds: pcf.config.EpochStartConfig.ExtraDelayForRequestBlockInfoInMilliseconds, + GenesisNonce: pcf.genesisNonce, + } + hdrsForBlock, err := headerForBlock.NewHeadersForBlock(argsHeadersForBlock) + if err != nil { + return nil, err + } + + mbSelectionSession, err := block.NewMiniBlocksSelectionSession( + pcf.bootstrapComponents.ShardCoordinator().SelfId(), + pcf.coreData.InternalMarshalizer(), + pcf.coreData.Hasher(), + ) + if err != nil { + return nil, err + } + + err = process.SetBaseExecutionResult(executionManager, pcf.data.Blockchain()) + if err != nil { + return nil, err + } + + execResultsVerifier, err := block.NewExecutionResultsVerifier(pcf.data.Blockchain(), executionManager) + if err != nil { + return nil, err + } + + execResSizeComputationHandler, err := estimator.NewExecResultSizeComputationHandler( + pcf.coreData.InternalMarshalizer(), + pcf.config.BlockSizeThrottleConfig.MaxExecResSizeInBytes, + ) + if err != nil { + return nil, err + } + + inclusionEstimator, err := estimator.NewExecutionResultInclusionEstimator( + pcf.config.ExecutionResultInclusionEstimator, + pcf.coreData.RoundHandler(), + execResSizeComputationHandler, + ) + if err != nil { + return nil, err + } + + missingDataArgs := missingData.ResolverArgs{ + HeadersPool: pcf.data.Datapool().Headers(), + ProofsPool: pcf.data.Datapool().Proofs(), + RequestHandler: requestHandler, + BlockDataRequester: proposalBlockDataRequester, + } + missingDataResolver, err := missingData.NewMissingDataResolver(missingDataArgs) + if err != nil { + return nil, err + } + + aotSelector := aotSelection.NewDisabledAOTSelector() // Metachain doesn't use AOT selection argumentsBaseProcessor := block.ArgBaseProcessor{ - CoreComponents: pcf.coreData, - DataComponents: pcf.data, - BootstrapComponents: pcf.bootstrapComponents, - StatusComponents: pcf.statusComponents, - StatusCoreComponents: pcf.statusCoreComponents, - Config: pcf.config, - PrefsConfig: pcf.prefConfigs, - Version: pcf.flagsConfig.Version, - AccountsDB: accountsDb, - ForkDetector: forkDetector, - NodesCoordinator: pcf.nodesCoordinator, - RequestHandler: requestHandler, - BlockChainHook: vmFactory.BlockChainHookImpl(), - TxCoordinator: txCoordinator, - EpochStartTrigger: epochStartTrigger, - HeaderValidator: headerValidator, - BootStorer: bootStorer, - BlockTracker: blockTracker, - FeeHandler: txFeeHandler, - BlockSizeThrottler: blockSizeThrottler, - HistoryRepository: pcf.historyRepo, - VMContainersFactory: vmFactory, - VmContainer: vmContainer, - GasHandler: gasHandler, - ScheduledTxsExecutionHandler: scheduledTxsExecutionHandler, - ProcessedMiniBlocksTracker: processedMiniBlocksTracker, - ReceiptsRepository: receiptsRepository, - OutportDataProvider: outportDataProvider, - BlockProcessingCutoffHandler: blockProcessingCutoffhandler, - ManagedPeersHolder: pcf.crypto.ManagedPeersHolder(), - SentSignaturesTracker: sentSignaturesTracker, - StateAccessesCollector: pcf.state.StateAccessesCollector(), + CoreComponents: pcf.coreData, + DataComponents: pcf.data, + BootstrapComponents: pcf.bootstrapComponents, + StatusComponents: pcf.statusComponents, + StatusCoreComponents: pcf.statusCoreComponents, + Config: pcf.config, + PrefsConfig: pcf.prefConfigs, + Version: pcf.flagsConfig.Version, + AccountsDB: accountsDb, + AccountsProposal: pcf.state.AccountsAdapterProposal(), + ForkDetector: forkDetector, + NodesCoordinator: pcf.nodesCoordinator, + RequestHandler: requestHandler, + BlockChainHook: vmFactory.BlockChainHookImpl(), + TxCoordinator: txCoordinator, + EpochStartTrigger: epochStartTrigger, + HeaderValidator: headerValidator, + BootStorer: bootStorer, + BlockTracker: blockTracker, + FeeHandler: txFeeHandler, + BlockSizeThrottler: blockSizeThrottler, + HistoryRepository: pcf.historyRepo, + VMContainersFactory: vmFactory, + VmContainer: vmContainer, + GasHandler: gasHandler, + ScheduledTxsExecutionHandler: scheduledTxsExecutionHandler, + ProcessedMiniBlocksTracker: processedMiniBlocksTracker, + ReceiptsRepository: receiptsRepository, + OutportDataProvider: outportDataProvider, + BlockProcessingCutoffHandler: blockProcessingCutoffhandler, + ManagedPeersHolder: pcf.crypto.ManagedPeersHolder(), + SentSignaturesTracker: sentSignaturesTracker, + StateAccessesCollector: pcf.state.StateAccessesCollector(), + HeadersForBlock: hdrsForBlock, + MiniBlocksSelectionSession: mbSelectionSession, + ExecutionResultsVerifier: execResultsVerifier, + MissingDataResolver: missingDataResolver, + ExecutionResultsInclusionEstimator: inclusionEstimator, + GasComputation: gasConsumption, + ExecutionManager: executionManager, + TxExecutionOrderHandler: pcf.txExecutionOrderHandler, + AOTSelector: aotSelector, } esdtOwnerAddress, err := pcf.coreData.AddressPubKeyConverter().Decode(pcf.systemSCConfig.ESDTSystemSCConfig.OwnerAddress) @@ -970,6 +1337,20 @@ func (pcf *processComponentsFactory) newMetaBlockProcessor( return nil, err } + shardInfoCreateDataArgs := block.ShardInfoCreateDataArgs{ + EnableEpochsHandler: pcf.coreData.EnableEpochsHandler(), + HeadersPool: pcf.data.Datapool().Headers(), + ProofsPool: pcf.data.Datapool().Proofs(), + PendingMiniBlocksHandler: pendingMiniBlocksHandler, + BlockTracker: blockTracker, + Storage: pcf.data.StorageService(), + Marshaller: pcf.coreData.InternalMarshalizer(), + } + shardInfoCreator, err := block.NewShardInfoCreateData(shardInfoCreateDataArgs) + if err != nil { + return nil, err + } + arguments := block.ArgMetaProcessor{ ArgBaseProcessor: argumentsBaseProcessor, SCToProtocol: smartContractToProtocol, @@ -980,6 +1361,7 @@ func (pcf *processComponentsFactory) newMetaBlockProcessor( EpochValidatorInfoCreator: validatorInfoCreator, ValidatorStatisticsProcessor: validatorStatisticsProcessor, EpochSystemSCProcessor: epochStartSystemSCProcessor, + ShardInfoCreator: shardInfoCreator, } metaProcessor, err := block.NewMetaProcessor(arguments) @@ -996,6 +1378,7 @@ func (pcf *processComponentsFactory) newMetaBlockProcessor( blockProcessor: metaProcessor, vmFactoryForProcessing: vmFactory, epochSystemSCProcessor: epochStartSystemSCProcessor, + aotSelector: aotSelector, } return blockProcessorComponents, nil @@ -1016,23 +1399,14 @@ func (pcf *processComponentsFactory) attachProcessDebugger( func (pcf *processComponentsFactory) createOutportDataProvider( txCoordinator process.TransactionCoordinator, gasConsumedProvider processOutport.GasConsumedProvider, + epochRewards processOutport.EpochRewardsGetter, ) (outport.DataProviderOutport, error) { - txsStorer, err := pcf.data.StorageService().GetStorer(dataRetriever.TransactionUnit) - if err != nil { - return nil, err - } - mbsStorer, err := pcf.data.StorageService().GetStorer(dataRetriever.MiniBlockUnit) - if err != nil { - return nil, err - } - return factoryOutportProvider.CreateOutportDataProvider(factoryOutportProvider.ArgOutportDataProviderFactory{ HasDrivers: pcf.statusComponents.OutportHandler().HasDrivers(), AddressConverter: pcf.coreData.AddressPubKeyConverter(), AccountsDB: pcf.state.AccountsAdapterAPI(), Marshaller: pcf.coreData.InternalMarshalizer(), EsdtDataStorageHandler: pcf.esdtNftStorage, - TransactionsStorer: txsStorer, ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), TxCoordinator: txCoordinator, NodesCoordinator: pcf.nodesCoordinator, @@ -1040,11 +1414,13 @@ func (pcf *processComponentsFactory) createOutportDataProvider( EconomicsData: pcf.coreData.EconomicsData(), IsImportDBMode: pcf.importDBConfig.IsImportDBMode, Hasher: pcf.coreData.Hasher(), - MbsStorer: mbsStorer, EnableEpochsHandler: pcf.coreData.EnableEpochsHandler(), ExecutionOrderGetter: pcf.txExecutionOrderHandler, - ProofsPool: pcf.data.Datapool().Proofs(), + DataPool: pcf.data.Datapool(), StateAccessesCollector: pcf.state.StateAccessesCollector(), + RoundHandler: pcf.coreData.RoundHandler(), + RewardsGetter: epochRewards, + StorageService: pcf.data.StorageService(), }) } @@ -1164,6 +1540,7 @@ func (pcf *processComponentsFactory) createVMFactoryMeta( ChanceComputer: pcf.coreData.Rater(), ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), EnableEpochsHandler: pcf.coreData.EnableEpochsHandler(), + EnableRoundsHandler: pcf.coreData.EnableRoundsHandler(), NodesCoordinator: pcf.nodesCoordinator, } return metachain.NewVMContainerFactory(argsNewVMContainer) diff --git a/factory/processing/blockProcessorCreator_test.go b/factory/processing/blockProcessorCreator_test.go index 54fa14ec9f4..008f5e5a4e7 100644 --- a/factory/processing/blockProcessorCreator_test.go +++ b/factory/processing/blockProcessorCreator_test.go @@ -58,6 +58,7 @@ func Test_newBlockProcessorCreatorForShard(t *testing.T) { &testscommon.BlockProcessingCutoffStub{}, &testscommon.MissingTrieNodesNotifierStub{}, &testscommon.SentSignatureTrackerStub{}, + &processMocks.ExecutionManagerMock{}, ) require.NoError(t, err) @@ -142,6 +143,9 @@ func Test_newBlockProcessorCreatorForMeta(t *testing.T) { AccountsAdapterAPICalled: func() state.AccountsAdapter { return adb }, + AccountsAdapterProposalCalled: func() state.AccountsAdapter { + return adb + }, TriesContainerCalled: func() common.TriesHolder { return &trieMock.TriesHolderStub{ GetCalled: func(bytes []byte) common.Trie { @@ -152,6 +156,9 @@ func Test_newBlockProcessorCreatorForMeta(t *testing.T) { TrieStorageManagersCalled: func() map[string]common.StorageManager { return trieStorageManagers }, + StateAccessesCollectorCalled: func() state.StateAccessesCollector { + return &stateMock.StateAccessesCollectorStub{} + }, } args := componentsMock.GetProcessArgs( shardC, @@ -187,6 +194,7 @@ func Test_newBlockProcessorCreatorForMeta(t *testing.T) { &testscommon.BlockProcessingCutoffStub{}, &testscommon.MissingTrieNodesNotifierStub{}, &testscommon.SentSignatureTrackerStub{}, + &processMocks.ExecutionManagerMock{}, ) require.NoError(t, err) diff --git a/factory/processing/export_test.go b/factory/processing/export_test.go index 1e0cdd6db8b..16d7eb5b149 100644 --- a/factory/processing/export_test.go +++ b/factory/processing/export_test.go @@ -25,6 +25,7 @@ func (pcf *processComponentsFactory) NewBlockProcessor( blockProcessingCutoff cutoff.BlockProcessingCutoffHandler, missingTrieNodesNotifier common.MissingTrieNodesNotifier, sentSignaturesTracker process.SentSignaturesTracker, + executionManager process.ExecutionManager, ) (process.BlockProcessor, process.EpochStartSystemSCProcessor, error) { blockProcessorComponents, err := pcf.newBlockProcessor( requestHandler, @@ -42,6 +43,7 @@ func (pcf *processComponentsFactory) NewBlockProcessor( blockProcessingCutoff, missingTrieNodesNotifier, sentSignaturesTracker, + executionManager, ) if err != nil { return nil, nil, err diff --git a/factory/processing/processComponents.go b/factory/processing/processComponents.go index 23c1b01a051..f5050230e1c 100644 --- a/factory/processing/processComponents.go +++ b/factory/processing/processComponents.go @@ -16,9 +16,15 @@ import ( dataBlock "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/outport" "github.com/multiversx/mx-chain-core-go/data/receipt" + "github.com/multiversx/mx-chain-core-go/data/transaction" vmcommon "github.com/multiversx/mx-chain-vm-common-go" vmcommonBuiltInFunctions "github.com/multiversx/mx-chain-vm-common-go/builtInFunctions" + "github.com/multiversx/mx-chain-go/process/asyncExecution" + headersCache "github.com/multiversx/mx-chain-go/process/asyncExecution/cache" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionManager" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionTrack" + nodeFactory "github.com/multiversx/mx-chain-go/cmd/node/factory" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/common/errChan" @@ -39,7 +45,6 @@ import ( "github.com/multiversx/mx-chain-go/epochStart/shardchain" errorsMx "github.com/multiversx/mx-chain-go/errors" "github.com/multiversx/mx-chain-go/factory" - mainFactory "github.com/multiversx/mx-chain-go/factory" "github.com/multiversx/mx-chain-go/factory/disabled" "github.com/multiversx/mx-chain-go/fallback" "github.com/multiversx/mx-chain-go/genesis" @@ -98,6 +103,7 @@ type processComponents struct { epochStartNotifier factory.EpochStartNotifier forkDetector process.ForkDetector blockProcessor process.BlockProcessor + executionManager process.ExecutionManager blackListHandler process.TimeCacher bootStorer process.BootStorer headerSigVerifier process.InterceptedHeaderSigVerifier @@ -131,11 +137,12 @@ type processComponents struct { processedMiniBlocksTracker process.ProcessedMiniBlocksTracker esdtDataStorageForApi vmcommon.ESDTNFTStorageHandler accountsParser genesis.AccountsParser - receiptsRepository mainFactory.ReceiptsRepository + receiptsRepository factory.ReceiptsRepository sentSignaturesTracker process.SentSignaturesTracker epochSystemSCProcessor process.EpochStartSystemSCProcessor interceptedDataVerifierFactory process.InterceptedDataVerifierFactory epochStartTriggerHanlder epochStart.TriggerHandler + aotSelector process.AOTTransactionSelector } // ProcessComponentsFactoryArgs holds the arguments needed to create a process components factory @@ -213,6 +220,7 @@ type processComponentsFactory struct { genesisRound uint64 interceptedDataVerifierFactory process.InterceptedDataVerifierFactory + nodeRedundancyHandler consensus.NodeRedundancyHandler } // NewProcessComponentsFactory will return a new instance of processComponentsFactory @@ -223,8 +231,7 @@ func NewProcessComponentsFactory(args ProcessComponentsFactoryArgs) (*processCom } interceptedDataVerifierFactory := interceptorFactory.NewInterceptedDataVerifierFactory(interceptorFactory.InterceptedDataVerifierFactoryArgs{ - CacheSpan: time.Duration(args.Config.InterceptedDataVerifier.CacheSpanInSec) * time.Second, - CacheExpiry: time.Duration(args.Config.InterceptedDataVerifier.CacheExpiryInSec) * time.Second, + InterceptedDataVerifierConfig: args.Config.InterceptedDataVerifier, }) return &processComponentsFactory{ @@ -266,11 +273,12 @@ func NewProcessComponentsFactory(args ProcessComponentsFactoryArgs) (*processCom // Create will create and return a struct containing process components func (pcf *processComponentsFactory) Create() (*processComponents, error) { + genesisUnixTime := common.GetGenesisUnixTimestampFromStartTime(pcf.coreData.GenesisTime(), pcf.coreData.EnableEpochsHandler()) currentEpochProvider, err := epochProviders.CreateCurrentEpochProvider( - pcf.config, - pcf.coreData.GenesisNodesSetup().GetRoundDuration(), - pcf.coreData.GenesisTime().Unix(), + pcf.coreData.ChainParametersHandler(), + genesisUnixTime, pcf.prefConfigs.Preferences.FullArchive, + pcf.coreData.EnableEpochsHandler(), ) if err != nil { return nil, err @@ -282,6 +290,8 @@ func (pcf *processComponentsFactory) Create() (*processComponents, error) { pcf.data.Datapool().Headers(), pcf.coreData.InternalMarshalizer(), pcf.data.StorageService(), + pcf.coreData.EnableRoundsHandler(), + pcf.coreData.CommonConfigsHandler(), ) if err != nil { return nil, err @@ -299,6 +309,7 @@ func (pcf *processComponentsFactory) Create() (*processComponents, error) { HeadersPool: pcf.data.Datapool().Headers(), ProofsPool: pcf.data.Datapool().Proofs(), StorageService: pcf.data.StorageService(), + PubKeysHandler: pcf.crypto.ConsensusSigningHandler(), } headerSigVerifier, err := headerCheck.NewHeaderSigVerifier(argsHeaderSig) if err != nil { @@ -488,31 +499,31 @@ func (pcf *processComponentsFactory) Create() (*processComponents, error) { argsMiniBlocksPoolsCleaner := poolsCleaner.ArgMiniBlocksPoolsCleaner{ ArgBasePoolsCleaner: poolsCleaner.ArgBasePoolsCleaner{ - RoundHandler: pcf.coreData.RoundHandler(), - ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), - MaxRoundsToKeepUnprocessedData: pcf.config.PoolsCleanersConfig.MaxRoundsToKeepUnprocessedMiniBlocks, + RoundHandler: pcf.coreData.RoundHandler(), + ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), + ProcessConfigsHandler: pcf.coreData.ProcessConfigsHandler(), }, MiniblocksPool: pcf.data.Datapool().MiniBlocks(), } mbsPoolsCleaner, err := poolsCleaner.NewMiniBlocksPoolsCleaner(argsMiniBlocksPoolsCleaner) if err != nil { - return nil, err + return nil, fmt.Errorf("%w in processComponentsFactory.Create for NewMiniBlocksPoolsCleaner", err) } mbsPoolsCleaner.StartCleaning() argsBasePoolsCleaner := poolsCleaner.ArgTxsPoolsCleaner{ ArgBasePoolsCleaner: poolsCleaner.ArgBasePoolsCleaner{ - RoundHandler: pcf.coreData.RoundHandler(), - ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), - MaxRoundsToKeepUnprocessedData: pcf.config.PoolsCleanersConfig.MaxRoundsToKeepUnprocessedTransactions, + RoundHandler: pcf.coreData.RoundHandler(), + ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), + ProcessConfigsHandler: pcf.coreData.ProcessConfigsHandler(), }, AddressPubkeyConverter: pcf.coreData.AddressPubKeyConverter(), DataPool: pcf.data.Datapool(), } txsPoolsCleaner, err := poolsCleaner.NewTxsPoolsCleaner(argsBasePoolsCleaner) if err != nil { - return nil, err + return nil, fmt.Errorf("%w in processComponentsFactory.Create for NewTxsPoolsCleaner", err) } txsPoolsCleaner.StartCleaning() @@ -576,7 +587,7 @@ func (pcf *processComponentsFactory) Create() (*processComponents, error) { return nil, err } if pcf.bootstrapComponents.ShardCoordinator().SelfId() == core.MetachainShardId { - pendingMiniBlocksHandler, err = pendingMb.NewPendingMiniBlocks() + pendingMiniBlocksHandler, err = pendingMb.NewPendingMiniBlocks(pcf.data.Datapool().Headers()) if err != nil { return nil, err } @@ -620,6 +631,46 @@ func (pcf *processComponentsFactory) Create() (*processComponents, error) { return nil, fmt.Errorf("%w when assembling components for the sent signatures tracker", err) } + blocksCache := headersCache.NewHeaderBodyCache(pcf.config.HeaderBodyCacheConfig) + executionResultsTracker := executionTrack.NewExecutionResultsTracker() + + argExecManager := executionManager.ArgsExecutionManager{ + BlocksCache: blocksCache, + ExecutionResultsTracker: executionResultsTracker, + BlockChain: pcf.data.Blockchain(), + Headers: pcf.data.Datapool().Headers(), + PostProcessTransactions: pcf.data.Datapool().PostProcessTransactions(), + ExecutedMiniBlocks: pcf.data.Datapool().ExecutedMiniBlocks(), + StorageService: pcf.data.StorageService(), + Marshaller: pcf.coreData.InternalMarshalizer(), + ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), + } + execManager, err := executionManager.NewExecutionManager(argExecManager) + if err != nil { + return nil, err + } + + observerBLSPrivateKey, observerBLSPublicKey := pcf.crypto.BlockSignKeyGen().GeneratePair() + observerBLSPublicKeyBuff, err := observerBLSPublicKey.ToByteArray() + if err != nil { + return nil, fmt.Errorf("error generating observerBLSPublicKeyBuff, %w", err) + } else { + log.Debug("generated BLS private key for redundancy handler. This key will be used on heartbeat messages "+ + "if the node is in backup mode and the main node is active", "hex public key", observerBLSPublicKeyBuff) + } + + nodeRedundancyArg := redundancy.ArgNodeRedundancy{ + RedundancyLevel: int(pcf.prefConfigs.Preferences.RedundancyLevel), + ProcessConfigsHandler: pcf.coreData.ProcessConfigsHandler(), + Messenger: pcf.network.NetworkMessenger(), + ObserverPrivateKey: observerBLSPrivateKey, + } + nodeRedundancyHandler, err := redundancy.NewNodeRedundancy(nodeRedundancyArg) + if err != nil { + return nil, err + } + pcf.nodeRedundancyHandler = nodeRedundancyHandler + blockProcessorComponents, err := pcf.newBlockProcessor( requestHandler, forkDetector, @@ -636,11 +687,32 @@ func (pcf *processComponentsFactory) Create() (*processComponents, error) { blockCutoffProcessingHandler, pcf.state.MissingTrieNodesNotifier(), sentSignaturesTracker, + execManager, ) if err != nil { return nil, err } + signalProcessCompletionChan := make(chan uint64, 1) + argsHeadersExecutor := asyncExecution.ArgsHeadersExecutor{ + BlocksCache: blocksCache, + ExecutionTracker: executionResultsTracker, + BlockProcessor: blockProcessorComponents.blockProcessor, + BlockChain: pcf.data.Blockchain(), + SignalProcessCompletionChan: signalProcessCompletionChan, + } + headersExecutor, err := asyncExecution.NewHeadersExecutor(argsHeadersExecutor) + if err != nil { + return nil, err + } + + err = execManager.SetHeadersExecutor(headersExecutor) + if err != nil { + return nil, err + } + + execManager.StartExecution() + startEpochNum := pcf.bootstrapComponents.EpochBootstrapParams().Epoch() if startEpochNum == 0 { err = pcf.indexGenesisBlocks(genesisBlocks, initialTxs, genesisAccounts) @@ -689,36 +761,16 @@ func (pcf *processComponentsFactory) Create() (*processComponents, error) { return nil, err } - observerBLSPrivateKey, observerBLSPublicKey := pcf.crypto.BlockSignKeyGen().GeneratePair() - observerBLSPublicKeyBuff, err := observerBLSPublicKey.ToByteArray() - if err != nil { - return nil, fmt.Errorf("error generating observerBLSPublicKeyBuff, %w", err) - } else { - log.Debug("generated BLS private key for redundancy handler. This key will be used on heartbeat messages "+ - "if the node is in backup mode and the main node is active", "hex public key", observerBLSPublicKeyBuff) - } - - maxRoundsOfInactivity := int(pcf.prefConfigs.Preferences.RedundancyLevel) * pcf.config.Redundancy.MaxRoundsOfInactivityAccepted - nodeRedundancyArg := redundancy.ArgNodeRedundancy{ - MaxRoundsOfInactivity: maxRoundsOfInactivity, - Messenger: pcf.network.NetworkMessenger(), - ObserverPrivateKey: observerBLSPrivateKey, - } - nodeRedundancyHandler, err := redundancy.NewNodeRedundancy(nodeRedundancyArg) - if err != nil { - return nil, err - } - dataPacker, err := partitioning.NewSimpleDataPacker(pcf.coreData.InternalMarshalizer()) if err != nil { return nil, err } args := txsSender.ArgsTxsSenderWithAccumulator{ - Marshaller: pcf.coreData.InternalMarshalizer(), - ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), - NetworkMessenger: pcf.network.NetworkMessenger(), - AccumulatorConfig: pcf.config.Antiflood.TxAccumulator, - DataPacker: dataPacker, + Marshaller: pcf.coreData.InternalMarshalizer(), + ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), + NetworkMessenger: pcf.network.NetworkMessenger(), + DataPacker: dataPacker, + AntifloodConfigHandler: pcf.coreData.AntifloodConfigsHandler(), } txsSenderWithAccumulator, err := txsSender.NewTxsSenderWithAccumulator(args) if err != nil { @@ -740,6 +792,7 @@ func (pcf *processComponentsFactory) Create() (*processComponents, error) { roundHandler: pcf.coreData.RoundHandler(), forkDetector: forkDetector, blockProcessor: blockProcessorComponents.blockProcessor, + executionManager: execManager, epochStartTrigger: epochStartTrigger, epochStartNotifier: pcf.coreData.EpochStartNotifierWithConfirm(), blackListHandler: blackListHandler, @@ -780,6 +833,7 @@ func (pcf *processComponentsFactory) Create() (*processComponents, error) { sentSignaturesTracker: sentSignaturesTracker, interceptedDataVerifierFactory: pcf.interceptedDataVerifierFactory, epochStartTriggerHanlder: epochStartTrigger, + aotSelector: blockProcessorComponents.aotSelector, }, nil } @@ -803,21 +857,21 @@ func (pcf *processComponentsFactory) newValidatorStatisticsProcessor() (process. } arguments := peer.ArgValidatorStatisticsProcessor{ - PeerAdapter: pcf.state.PeerAccounts(), - PubkeyConv: pcf.coreData.ValidatorPubKeyConverter(), - NodesCoordinator: pcf.nodesCoordinator, - ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), - DataPool: peerDataPool, - StorageService: storageService, - Marshalizer: pcf.coreData.InternalMarshalizer(), - Rater: pcf.coreData.Rater(), - MaxComputableRounds: pcf.config.GeneralSettings.MaxComputableRounds, - MaxConsecutiveRoundsOfRatingDecrease: pcf.config.GeneralSettings.MaxConsecutiveRoundsOfRatingDecrease, - RewardsHandler: pcf.coreData.EconomicsData(), - NodesSetup: pcf.coreData.GenesisNodesSetup(), - RatingEnableEpoch: ratingEnabledEpoch, - GenesisNonce: genesisHeader.GetNonce(), - EnableEpochsHandler: pcf.coreData.EnableEpochsHandler(), + PeerAdapter: pcf.state.PeerAccounts(), + PubkeyConv: pcf.coreData.ValidatorPubKeyConverter(), + NodesCoordinator: pcf.nodesCoordinator, + ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), + DataPool: peerDataPool, + StorageService: storageService, + Marshalizer: pcf.coreData.InternalMarshalizer(), + Rater: pcf.coreData.Rater(), + MaxComputableRounds: pcf.config.GeneralSettings.MaxComputableRounds, + RewardsHandler: pcf.coreData.EconomicsData(), + NodesSetup: pcf.coreData.GenesisNodesSetup(), + RatingEnableEpoch: ratingEnabledEpoch, + GenesisNonce: genesisHeader.GetNonce(), + EnableEpochsHandler: pcf.coreData.EnableEpochsHandler(), + ProcessConfigsHandler: pcf.coreData.ProcessConfigsHandler(), } return peer.NewValidatorStatisticsProcessor(arguments) @@ -848,22 +902,22 @@ func (pcf *processComponentsFactory) newEpochStartTrigger(requestHandler epochSt } argEpochStart := &shardchain.ArgsShardEpochStartTrigger{ - Marshalizer: pcf.coreData.InternalMarshalizer(), - Hasher: pcf.coreData.Hasher(), - HeaderValidator: headerValidator, - Uint64Converter: pcf.coreData.Uint64ByteSliceConverter(), - DataPool: pcf.data.Datapool(), - Storage: pcf.data.StorageService(), - RequestHandler: requestHandler, - Epoch: pcf.bootstrapComponents.EpochBootstrapParams().Epoch(), - EpochStartNotifier: pcf.coreData.EpochStartNotifierWithConfirm(), - Validity: process.MetaBlockValidity, - Finality: process.BlockFinality, - PeerMiniBlocksSyncer: peerMiniBlockSyncer, - RoundHandler: pcf.coreData.RoundHandler(), - AppStatusHandler: pcf.statusCoreComponents.AppStatusHandler(), - EnableEpochsHandler: pcf.coreData.EnableEpochsHandler(), - ExtraDelayForRequestBlockInfo: time.Duration(pcf.config.EpochStartConfig.ExtraDelayForRequestBlockInfoInMilliseconds) * time.Millisecond, + Marshalizer: pcf.coreData.InternalMarshalizer(), + Hasher: pcf.coreData.Hasher(), + HeaderValidator: headerValidator, + Uint64Converter: pcf.coreData.Uint64ByteSliceConverter(), + DataPool: pcf.data.Datapool(), + Storage: pcf.data.StorageService(), + RequestHandler: requestHandler, + Epoch: pcf.bootstrapComponents.EpochBootstrapParams().Epoch(), + EpochStartNotifier: pcf.coreData.EpochStartNotifierWithConfirm(), + Validity: process.MetaBlockValidity, + Finality: process.BlockFinality, + PeerMiniBlocksSyncer: peerMiniBlockSyncer, + RoundHandler: pcf.coreData.RoundHandler(), + AppStatusHandler: pcf.statusCoreComponents.AppStatusHandler(), + EnableEpochsHandler: pcf.coreData.EnableEpochsHandler(), + CommonConfigsHandler: pcf.coreData.CommonConfigsHandler(), } return shardchain.NewEpochStartTrigger(argEpochStart) } @@ -874,17 +928,19 @@ func (pcf *processComponentsFactory) newEpochStartTrigger(requestHandler epochSt return nil, errorsMx.ErrGenesisBlockNotInitialized } + genesisTime := common.GetGenesisStartTimeFromUnixTimestamp(pcf.coreData.GenesisNodesSetup().GetStartTime(), pcf.coreData.EnableEpochsHandler()) argEpochStart := &metachain.ArgsNewMetaEpochStartTrigger{ - GenesisTime: time.Unix(pcf.coreData.GenesisNodesSetup().GetStartTime(), 0), - Settings: &pcf.config.EpochStartConfig, - Epoch: pcf.bootstrapComponents.EpochBootstrapParams().Epoch(), - EpochStartRound: genesisHeader.GetRound(), - EpochStartNotifier: pcf.coreData.EpochStartNotifierWithConfirm(), - Storage: pcf.data.StorageService(), - Marshalizer: pcf.coreData.InternalMarshalizer(), - Hasher: pcf.coreData.Hasher(), - AppStatusHandler: pcf.statusCoreComponents.AppStatusHandler(), - DataPool: pcf.data.Datapool(), + GenesisTime: genesisTime, + Settings: &pcf.config.EpochStartConfig, + Epoch: pcf.bootstrapComponents.EpochBootstrapParams().Epoch(), + EpochStartRound: genesisHeader.GetRound(), + EpochStartNotifier: pcf.coreData.EpochStartNotifierWithConfirm(), + Storage: pcf.data.StorageService(), + Marshalizer: pcf.coreData.InternalMarshalizer(), + Hasher: pcf.coreData.Hasher(), + AppStatusHandler: pcf.statusCoreComponents.AppStatusHandler(), + DataPool: pcf.data.Datapool(), + ChainParametersHandler: pcf.coreData.ChainParametersHandler(), } return metachain.NewEpochStartTrigger(argEpochStart) @@ -907,6 +963,7 @@ func (pcf *processComponentsFactory) generateGenesisHeadersAndApplyInitialBalanc Data: pcf.data, Core: pcf.coreData, Accounts: pcf.state.AccountsAdapter(), + AccountsProposal: pcf.state.AccountsAdapterProposal(), ValidatorAccounts: pcf.state.PeerAccounts(), InitialNodesSetup: pcf.coreData.GenesisNodesSetup(), Economics: pcf.coreData.EconomicsData(), @@ -921,6 +978,7 @@ func (pcf *processComponentsFactory) generateGenesisHeadersAndApplyInitialBalanc SystemSCConfig: *pcf.systemSCConfig, RoundConfig: pcf.roundConfig, EpochConfig: pcf.epochConfig, + FeeSettings: pcf.economicsConfig.FeeSettings, HeaderVersionConfigs: pcf.config.Versions, BlockSignKeyGen: pcf.crypto.BlockSignKeyGen(), HistoryRepository: pcf.historyRepo, @@ -930,6 +988,7 @@ func (pcf *processComponentsFactory) generateGenesisHeadersAndApplyInitialBalanc GenesisEpoch: pcf.config.EpochStartConfig.GenesisEpoch, GenesisNonce: pcf.genesisNonce, GenesisRound: pcf.genesisRound, + TxCacheSelectionConfig: pcf.config.TxCacheSelection, } gbc, err := processGenesis.NewGenesisBlockCreator(arg) @@ -996,12 +1055,19 @@ func (pcf *processComponentsFactory) indexAndReturnGenesisAccounts() (map[string } shardID := pcf.bootstrapComponents.ShardCoordinator().SelfId() + blockTimestamp := uint64(pcf.coreData.GenesisNodesSetup().GetStartTime()) + + timestampMs := blockTimestamp + if !pcf.coreData.EnableEpochsHandler().IsFlagEnabledInEpoch(common.SupernovaFlag, 0) { + timestampMs = common.ConvertTimeStampSecToMs(blockTimestamp) + } + pcf.statusComponents.OutportHandler().SaveAccounts(&outport.Accounts{ ShardID: shardID, BlockTimestamp: blockTimestamp, AlteredAccounts: genesisAccounts, - BlockTimestampMs: common.ConvertTimeStampSecToMs(blockTimestamp), + BlockTimestampMs: timestampMs, }) return genesisAccounts, nil } @@ -1357,9 +1423,11 @@ func (pcf *processComponentsFactory) newBlockTracker( WhitelistHandler: pcf.whiteListHandler, FeeHandler: pcf.coreData.EconomicsData(), EnableEpochsHandler: pcf.coreData.EnableEpochsHandler(), + EnableRoundsHandler: pcf.coreData.EnableRoundsHandler(), ProofsPool: pcf.data.Datapool().Proofs(), IsImportDBMode: pcf.importDBConfig.IsImportDBMode, EpochChangeGracePeriodHandler: pcf.coreData.EpochChangeGracePeriodHandler(), + ProcessConfigsHandler: pcf.coreData.ProcessConfigsHandler(), } if shardCoordinator.SelfId() < shardCoordinator.NumberOfShards() { @@ -1414,24 +1482,23 @@ func (pcf *processComponentsFactory) newShardResolverContainerFactory( } resolversContainerFactoryArgs := resolverscontainer.FactoryArgs{ - ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), - MainMessenger: pcf.network.NetworkMessenger(), - FullArchiveMessenger: pcf.network.FullArchiveNetworkMessenger(), - Store: pcf.data.StorageService(), - Marshalizer: pcf.coreData.InternalMarshalizer(), - DataPools: pcf.data.Datapool(), - Uint64ByteSliceConverter: pcf.coreData.Uint64ByteSliceConverter(), - DataPacker: dataPacker, - TriesContainer: pcf.state.TriesContainer(), - SizeCheckDelta: pcf.config.Marshalizer.SizeCheckDelta, - InputAntifloodHandler: pcf.network.InputAntiFloodHandler(), - OutputAntifloodHandler: pcf.network.OutputAntiFloodHandler(), - NumConcurrentResolvingJobs: pcf.config.Antiflood.NumConcurrentResolverJobs, - NumConcurrentResolvingTrieNodesJobs: pcf.config.Antiflood.NumConcurrentResolvingTrieNodesJobs, - IsFullHistoryNode: pcf.prefConfigs.Preferences.FullArchive, - MainPreferredPeersHolder: pcf.network.PreferredPeersHolderHandler(), - FullArchivePreferredPeersHolder: pcf.network.FullArchivePreferredPeersHolderHandler(), - PayloadValidator: payloadValidator, + ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), + MainMessenger: pcf.network.NetworkMessenger(), + FullArchiveMessenger: pcf.network.FullArchiveNetworkMessenger(), + Store: pcf.data.StorageService(), + Marshalizer: pcf.coreData.InternalMarshalizer(), + DataPools: pcf.data.Datapool(), + Uint64ByteSliceConverter: pcf.coreData.Uint64ByteSliceConverter(), + DataPacker: dataPacker, + TriesContainer: pcf.state.TriesContainer(), + SizeCheckDelta: pcf.config.Marshalizer.SizeCheckDelta, + InputAntifloodHandler: pcf.network.InputAntiFloodHandler(), + OutputAntifloodHandler: pcf.network.OutputAntiFloodHandler(), + IsFullHistoryNode: pcf.prefConfigs.Preferences.FullArchive, + MainPreferredPeersHolder: pcf.network.PreferredPeersHolderHandler(), + FullArchivePreferredPeersHolder: pcf.network.FullArchivePreferredPeersHolderHandler(), + PayloadValidator: payloadValidator, + AntifloodConfigsHandler: pcf.coreData.AntifloodConfigsHandler(), } resolversContainerFactory, err := resolverscontainer.NewShardResolversContainerFactory(resolversContainerFactoryArgs) if err != nil { @@ -1451,24 +1518,23 @@ func (pcf *processComponentsFactory) newMetaResolverContainerFactory( } resolversContainerFactoryArgs := resolverscontainer.FactoryArgs{ - ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), - MainMessenger: pcf.network.NetworkMessenger(), - FullArchiveMessenger: pcf.network.FullArchiveNetworkMessenger(), - Store: pcf.data.StorageService(), - Marshalizer: pcf.coreData.InternalMarshalizer(), - DataPools: pcf.data.Datapool(), - Uint64ByteSliceConverter: pcf.coreData.Uint64ByteSliceConverter(), - DataPacker: dataPacker, - TriesContainer: pcf.state.TriesContainer(), - SizeCheckDelta: pcf.config.Marshalizer.SizeCheckDelta, - InputAntifloodHandler: pcf.network.InputAntiFloodHandler(), - OutputAntifloodHandler: pcf.network.OutputAntiFloodHandler(), - NumConcurrentResolvingJobs: pcf.config.Antiflood.NumConcurrentResolverJobs, - NumConcurrentResolvingTrieNodesJobs: pcf.config.Antiflood.NumConcurrentResolvingTrieNodesJobs, - IsFullHistoryNode: pcf.prefConfigs.Preferences.FullArchive, - MainPreferredPeersHolder: pcf.network.PreferredPeersHolderHandler(), - FullArchivePreferredPeersHolder: pcf.network.FullArchivePreferredPeersHolderHandler(), - PayloadValidator: payloadValidator, + ShardCoordinator: pcf.bootstrapComponents.ShardCoordinator(), + MainMessenger: pcf.network.NetworkMessenger(), + FullArchiveMessenger: pcf.network.FullArchiveNetworkMessenger(), + Store: pcf.data.StorageService(), + Marshalizer: pcf.coreData.InternalMarshalizer(), + DataPools: pcf.data.Datapool(), + Uint64ByteSliceConverter: pcf.coreData.Uint64ByteSliceConverter(), + DataPacker: dataPacker, + TriesContainer: pcf.state.TriesContainer(), + SizeCheckDelta: pcf.config.Marshalizer.SizeCheckDelta, + InputAntifloodHandler: pcf.network.InputAntiFloodHandler(), + OutputAntifloodHandler: pcf.network.OutputAntiFloodHandler(), + IsFullHistoryNode: pcf.prefConfigs.Preferences.FullArchive, + MainPreferredPeersHolder: pcf.network.PreferredPeersHolderHandler(), + FullArchivePreferredPeersHolder: pcf.network.FullArchivePreferredPeersHolderHandler(), + PayloadValidator: payloadValidator, + AntifloodConfigsHandler: pcf.coreData.AntifloodConfigsHandler(), } return resolverscontainer.NewMetaResolversContainerFactory(resolversContainerFactoryArgs) @@ -1723,6 +1789,7 @@ func (pcf *processComponentsFactory) newShardInterceptorContainerFactory( HardforkTrigger: hardforkTrigger, NodeOperationMode: nodeOperationMode, InterceptedDataVerifierFactory: pcf.interceptedDataVerifierFactory, + Config: pcf.config, } interceptorContainerFactory, err := interceptorscontainer.NewShardInterceptorsContainerFactory(shardInterceptorsContainerFactoryArgs) @@ -1777,6 +1844,7 @@ func (pcf *processComponentsFactory) newMetaInterceptorContainerFactory( HardforkTrigger: hardforkTrigger, NodeOperationMode: nodeOperationMode, InterceptedDataVerifierFactory: pcf.interceptedDataVerifierFactory, + Config: pcf.config, } interceptorContainerFactory, err := interceptorscontainer.NewMetaInterceptorsContainerFactory(metaInterceptorsContainerFactoryArgs) @@ -1798,8 +1866,14 @@ func (pcf *processComponentsFactory) newForkDetector( headerBlackList, blockTracker, pcf.coreData.GenesisNodesSetup().GetStartTime(), + pcf.coreData.SupernovaGenesisTime().UnixMilli(), pcf.coreData.EnableEpochsHandler(), - pcf.data.Datapool().Proofs()) + pcf.coreData.EnableRoundsHandler(), + pcf.data.Datapool().Proofs(), + pcf.coreData.ChainParametersHandler(), + pcf.coreData.ProcessConfigsHandler(), + shardCoordinator.SelfId(), + ) } if shardCoordinator.SelfId() == core.MetachainShardId { return sync.NewMetaForkDetector( @@ -1807,8 +1881,13 @@ func (pcf *processComponentsFactory) newForkDetector( headerBlackList, blockTracker, pcf.coreData.GenesisNodesSetup().GetStartTime(), + pcf.coreData.SupernovaGenesisTime().UnixMilli(), pcf.coreData.EnableEpochsHandler(), - pcf.data.Datapool().Proofs()) + pcf.coreData.EnableRoundsHandler(), + pcf.data.Datapool().Proofs(), + pcf.coreData.ChainParametersHandler(), + pcf.coreData.ProcessConfigsHandler(), + ) } return nil, errors.New("could not create fork detector") @@ -1883,12 +1962,12 @@ func (pcf *processComponentsFactory) createExportFactoryHandler( HeaderIntegrityVerifier: pcf.bootstrapComponents.HeaderIntegrityVerifier(), ValidityAttester: blockTracker, RoundHandler: pcf.coreData.RoundHandler(), - InterceptorDebugConfig: pcf.config.Debug.InterceptorResolver, MaxHardCapForMissingNodes: pcf.config.TrieSync.MaxHardCapForMissingNodes, NumConcurrentTrieSyncers: pcf.config.TrieSync.NumConcurrentTrieSyncers, TrieSyncerVersion: pcf.config.TrieSync.TrieSyncerVersion, NodeOperationMode: nodeOperationMode, InterceptedDataVerifierFactory: pcf.interceptedDataVerifierFactory, + Config: pcf.config, } return updateFactory.NewExportHandlerFactory(argsExporter) } @@ -1914,6 +1993,8 @@ func (pcf *processComponentsFactory) createHardforkTrigger(epochStartTrigger upd CloseAfterExportInMinutes: hardforkConfig.CloseAfterExportInMinutes, ImportStartHandler: pcf.importStartHandler, RoundHandler: pcf.coreData.RoundHandler(), + EnableEpochsHandler: pcf.coreData.EnableEpochsHandler(), + EnableRoundsHandler: pcf.coreData.EnableRoundsHandler(), } return trigger.NewTrigger(argTrigger) @@ -2058,6 +2139,9 @@ func (pc *processComponents) Close() error { if !check.IfNil(pc.blockProcessor) { log.LogIfError(pc.blockProcessor.Close()) } + if !check.IfNil(pc.executionManager) { + log.LogIfError(pc.executionManager.Close()) + } if !check.IfNil(pc.validatorsProvider) { log.LogIfError(pc.validatorsProvider.Close()) } @@ -2089,6 +2173,9 @@ func (pc *processComponents) Close() error { if !check.IfNil(pc.interceptedDataVerifierFactory) { log.LogIfError(pc.interceptedDataVerifierFactory.Close()) } + if !check.IfNil(pc.aotSelector) { + log.LogIfError(pc.aotSelector.Close()) + } return nil } @@ -2120,13 +2207,13 @@ func wrapReceipts(receipts map[string]*receipt.Receipt) map[string]data.Transact return ret } -func wrapLogs(logs []*outport.LogData) []*data.LogData { - ret := make([]*data.LogData, len(logs)) +func wrapLogs(logs []*transaction.LogData) []data.LogDataHandler { + ret := make([]data.LogDataHandler, len(logs)) for idx, logData := range logs { - ret[idx] = &data.LogData{ - LogHandler: logData.Log, - TxHash: logData.TxHash, + ret[idx] = &transaction.LogData{ + Log: logData.Log, + TxHash: logData.TxHash, } } diff --git a/factory/processing/processComponentsHandler.go b/factory/processing/processComponentsHandler.go index 21cf45107cd..abf70cd1eb9 100644 --- a/factory/processing/processComponentsHandler.go +++ b/factory/processing/processComponentsHandler.go @@ -5,6 +5,8 @@ import ( "sync" "github.com/multiversx/mx-chain-core-go/core/check" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/multiversx/mx-chain-go/consensus" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/dblookupext" @@ -16,7 +18,6 @@ import ( "github.com/multiversx/mx-chain-go/sharding" "github.com/multiversx/mx-chain-go/sharding/nodesCoordinator" "github.com/multiversx/mx-chain-go/update" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" ) var _ factory.ComponentHandler = (*managedProcessComponents)(nil) @@ -114,6 +115,9 @@ func (m *managedProcessComponents) CheckSubcomponents() error { if check.IfNil(m.processComponents.blockProcessor) { return errors.ErrNilBlockProcessor } + if check.IfNil(m.processComponents.executionManager) { + return errors.ErrNilExecutionManager + } if check.IfNil(m.processComponents.blackListHandler) { return errors.ErrNilBlackListHandler } @@ -180,6 +184,9 @@ func (m *managedProcessComponents) CheckSubcomponents() error { if check.IfNil(m.processComponents.epochSystemSCProcessor) { return errors.ErrNilEpochSystemSCProcessor } + if check.IfNil(m.processComponents.aotSelector) { + return errors.ErrNilAOTSelector + } return nil } @@ -316,6 +323,18 @@ func (m *managedProcessComponents) BlockProcessor() process.BlockProcessor { return m.processComponents.blockProcessor } +// ExecutionManager returns the execution manager +func (m *managedProcessComponents) ExecutionManager() process.ExecutionManager { + m.mutProcessComponents.RLock() + defer m.mutProcessComponents.RUnlock() + + if m.processComponents == nil { + return nil + } + + return m.processComponents.executionManager +} + // BlockchainHook returns the block chain hook func (m *managedProcessComponents) BlockchainHook() process.BlockChainHookWithAccountsAdapter { m.mutProcessComponents.RLock() @@ -704,6 +723,18 @@ func (m *managedProcessComponents) EpochSystemSCProcessor() process.EpochStartSy return m.processComponents.epochSystemSCProcessor } +// AOTSelector returns the AOT (Ahead-of-Time) transaction selector +func (m *managedProcessComponents) AOTSelector() process.AOTTransactionSelector { + m.mutProcessComponents.RLock() + defer m.mutProcessComponents.RUnlock() + + if m.processComponents == nil { + return nil + } + + return m.processComponents.aotSelector +} + // IsInterfaceNil returns true if the interface is nil func (m *managedProcessComponents) IsInterfaceNil() bool { return m == nil diff --git a/factory/processing/processComponentsHandler_test.go b/factory/processing/processComponentsHandler_test.go index 41fb4eb7d0e..a159e487621 100644 --- a/factory/processing/processComponentsHandler_test.go +++ b/factory/processing/processComponentsHandler_test.go @@ -4,9 +4,14 @@ import ( "testing" "github.com/multiversx/mx-chain-core-go/core/check" + coreData "github.com/multiversx/mx-chain-core-go/data" errorsMx "github.com/multiversx/mx-chain-go/errors" "github.com/multiversx/mx-chain-go/factory" processComp "github.com/multiversx/mx-chain-go/factory/processing" + testsMocks "github.com/multiversx/mx-chain-go/integrationTests/mock" + "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" + "github.com/multiversx/mx-chain-go/testscommon/genericMocks" "github.com/stretchr/testify/require" ) @@ -58,6 +63,7 @@ func TestManagedProcessComponents_Create(t *testing.T) { require.True(t, check.IfNil(managedProcessComponents.RequestersFinder())) require.True(t, check.IfNil(managedProcessComponents.RoundHandler())) require.True(t, check.IfNil(managedProcessComponents.ForkDetector())) + require.True(t, check.IfNil(managedProcessComponents.ExecutionManager())) require.True(t, check.IfNil(managedProcessComponents.BlockProcessor())) require.True(t, check.IfNil(managedProcessComponents.BlockchainHook())) require.True(t, check.IfNil(managedProcessComponents.EpochStartTrigger())) @@ -105,6 +111,7 @@ func TestManagedProcessComponents_Create(t *testing.T) { require.False(t, check.IfNil(managedProcessComponents.RoundHandler())) require.False(t, check.IfNil(managedProcessComponents.ForkDetector())) require.False(t, check.IfNil(managedProcessComponents.BlockProcessor())) + require.False(t, check.IfNil(managedProcessComponents.ExecutionManager())) require.False(t, check.IfNil(managedProcessComponents.EpochStartTrigger())) require.False(t, check.IfNil(managedProcessComponents.BlockchainHook())) require.False(t, check.IfNil(managedProcessComponents.EpochStartNotifier())) @@ -163,7 +170,29 @@ func TestManagedProcessComponents_CheckSubcomponents(t *testing.T) { func TestManagedProcessComponents_Close(t *testing.T) { t.Parallel() - processComponentsFactory, _ := processComp.NewProcessComponentsFactory(createMockProcessComponentsFactoryArgs()) + mockedArgs := createMockProcessComponentsFactoryArgs() + mockedArgs.Data = &testsMocks.DataComponentsStub{ + DataPool: dataRetriever.NewPoolsHolderMock(), + BlockChain: &testscommon.ChainHandlerStub{ + GetGenesisHeaderHashCalled: func() []byte { + return []byte("genesis hash") + }, + GetGenesisHeaderCalled: func() coreData.HeaderHandler { + return &testscommon.HeaderHandlerStub{ + GetRootHashCalled: func() []byte { + return []byte("root hash") + }, + GetPrevHashCalled: func() []byte { + return []byte("prev hash") + }, + } + }, + }, + MbProvider: &testsMocks.MiniBlocksProviderStub{}, + Store: genericMocks.NewChainStorerMock(0), + } + + processComponentsFactory, _ := processComp.NewProcessComponentsFactory(mockedArgs) managedProcessComponents, _ := processComp.NewManagedProcessComponents(processComponentsFactory) err := managedProcessComponents.Create() require.NoError(t, err) diff --git a/factory/processing/processComponents_test.go b/factory/processing/processComponents_test.go index 157088fd9f7..eebd5476e0e 100644 --- a/factory/processing/processComponents_test.go +++ b/factory/processing/processComponents_test.go @@ -37,8 +37,10 @@ import ( "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/bootstrapMocks" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" txExecOrderStub "github.com/multiversx/mx-chain-go/testscommon/common" "github.com/multiversx/mx-chain-go/testscommon/components" + consensusMocks "github.com/multiversx/mx-chain-go/testscommon/consensus" "github.com/multiversx/mx-chain-go/testscommon/cryptoMocks" "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/dblookupext" @@ -146,7 +148,8 @@ func createMockProcessComponentsFactoryArgs() processComp.ProcessComponentsFacto MinStakeValue: "1", UnJailValue: "1", MinStepValue: "1", - UnBondPeriod: 0, + UnBondPeriod: 1, + UnBondPeriodSupernova: 2, NumRoundsWithoutBleed: 0, MaximumPercentageToBleed: 0, BleedPercentagePerRound: 0, @@ -227,6 +230,10 @@ func createMockProcessComponentsFactoryArgs() processComp.ProcessComponentsFacto RatingsConfig: &testscommon.RatingsInfoMock{}, PathHdl: &testscommon.PathManagerStub{}, ProcessStatusHandlerInternal: &testscommon.ProcessStatusHandlerStub{}, + ChainParametersHandlerField: &chainParameters.ChainParametersHandlerStub{}, + ProcessConfigsHandlerField: testscommon.GetDefaultProcessConfigsHandler(), + CommonConfigsHandlerField: testscommon.GetDefaultCommonConfigsHandler(), + AntifloodConfigsHandlerField: &testscommon.AntifloodConfigsHandlerStub{}, }, Crypto: &testsMocks.CryptoComponentsStub{ BlKeyGen: &cryptoMocks.KeyGenStub{}, @@ -244,6 +251,7 @@ func createMockProcessComponentsFactoryArgs() processComp.ProcessComponentsFacto MsgSigVerifier: &testscommon.MessageSignVerifierMock{}, ManagedPeersHolderField: &testscommon.ManagedPeersHolderStub{}, KeysHandlerField: &testscommon.KeysHandlerStub{}, + SigHandler: &consensusMocks.SigningHandlerStub{}, }, Network: &testsMocks.NetworkComponentsStub{ Messenger: &p2pmocks.MessengerStub{}, @@ -269,6 +277,12 @@ func createMockProcessComponentsFactoryArgs() processComp.ProcessComponentsFacto StateStatsHandlerField: disabledStatistics.NewStateStatistics(), }, TxExecutionOrderHandler: &txExecOrderStub.TxExecutionOrderHandlerStub{}, + EconomicsConfig: config.EconomicsConfig{ + FeeSettings: config.FeeSettings{ + BlockCapacityOverestimationFactor: 200, + PercentDecreaseLimitsStep: 10, + }, + }, } args.State = components.GetStateComponents(args.CoreData, args.StatusCoreComponents) @@ -613,14 +627,6 @@ func TestProcessComponentsFactory_Create(t *testing.T) { t.Parallel() expectedErr := errors.New("expected error") - t.Run("CreateCurrentEpochProvider fails should error", func(t *testing.T) { - t.Parallel() - - args := createMockProcessComponentsFactoryArgs() - args.Config.EpochStartConfig.RoundsPerEpoch = 0 - args.PrefConfigs.Preferences.FullArchive = true - testCreateWithArgs(t, args, "rounds per epoch") - }) t.Run("createNetworkShardingCollector fails due to invalid PublicKeyPeerId config should error", func(t *testing.T) { t.Parallel() @@ -721,15 +727,29 @@ func TestProcessComponentsFactory_Create(t *testing.T) { t.Parallel() args := createMockProcessComponentsFactoryArgs() - args.Config.PoolsCleanersConfig.MaxRoundsToKeepUnprocessedMiniBlocks = 0 - testCreateWithArgs(t, args, "MaxRoundsToKeepUnprocessedData") + ct := 0 + args.CoreData.(*mock.CoreComponentsMock).ProcessConfigsHandlerCalled = func() common.ProcessConfigsHandler { + if ct == 2 { + return nil + } + ct++ + return args.CoreData.(*mock.CoreComponentsMock).ProcessConfigsHandlerField + } + testCreateWithArgs(t, args, "NewMiniBlocksPoolsCleaner") }) t.Run("NewTxsPoolsCleaner fails should error", func(t *testing.T) { t.Parallel() args := createMockProcessComponentsFactoryArgs() - args.Config.PoolsCleanersConfig.MaxRoundsToKeepUnprocessedTransactions = 0 - testCreateWithArgs(t, args, "MaxRoundsToKeepUnprocessedData") + ct := 0 + args.CoreData.(*mock.CoreComponentsMock).ProcessConfigsHandlerCalled = func() common.ProcessConfigsHandler { + if ct == 3 { + return nil + } + ct++ + return args.CoreData.(*mock.CoreComponentsMock).ProcessConfigsHandlerField + } + testCreateWithArgs(t, args, "NewTxsPoolsCleaner") }) t.Run("createHardforkTrigger fails due to Decode failure should error", func(t *testing.T) { t.Parallel() @@ -922,11 +942,13 @@ func TestProcessComponentsFactory_Create(t *testing.T) { CommitCalled: realStateComp.AccountsAdapter().Commit, RootHashCalled: realStateComp.AccountsAdapter().RootHash, }, + AccountsProposal: &testState.AccountsStub{}, PeersAcc: realStateComp.PeerAccounts(), Tries: realStateComp.TriesContainer(), AccountsAPI: realStateComp.AccountsAdapterAPI(), StorageManagers: realStateComp.TrieStorageManagers(), MissingNodesNotifier: realStateComp.MissingTrieNodesNotifier(), + ChangesCollector: realStateComp.StateAccessesCollector(), } pcf, _ := processComp.NewProcessComponentsFactory(args) @@ -966,8 +988,10 @@ func TestProcessComponentsFactory_Create(t *testing.T) { PeersAcc: realStateComp.PeerAccounts(), Tries: realStateComp.TriesContainer(), AccountsAPI: realStateComp.AccountsAdapterAPI(), + AccountsProposal: realStateComp.AccountsAdapterProposal(), StorageManagers: realStateComp.TrieStorageManagers(), MissingNodesNotifier: realStateComp.MissingTrieNodesNotifier(), + ChangesCollector: realStateComp.StateAccessesCollector(), } coreCompStub := factoryMocks.NewCoreComponentsHolderStubFromRealComponent(args.CoreData) coreCompStub.InternalMarshalizerCalled = func() marshal.Marshalizer { diff --git a/factory/processing/txSimulatorProcessComponents.go b/factory/processing/txSimulatorProcessComponents.go index 3b4878977e9..f89b0453c3f 100644 --- a/factory/processing/txSimulatorProcessComponents.go +++ b/factory/processing/txSimulatorProcessComponents.go @@ -3,6 +3,11 @@ package processing import ( "github.com/multiversx/mx-chain-core-go/core" dataBlock "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/common" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/multiversx/mx-chain-vm-common-go/parsers" + datafield "github.com/multiversx/mx-chain-vm-common-go/parsers/dataField" + "github.com/multiversx/mx-chain-go/common/disabled" bootstrapDisabled "github.com/multiversx/mx-chain-go/epochStart/bootstrap/disabled" "github.com/multiversx/mx-chain-go/factory" @@ -23,9 +28,6 @@ import ( "github.com/multiversx/mx-chain-go/storage" storageFactory "github.com/multiversx/mx-chain-go/storage/factory" "github.com/multiversx/mx-chain-go/storage/storageunit" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" - "github.com/multiversx/mx-chain-vm-common-go/parsers" - datafield "github.com/multiversx/mx-chain-vm-common-go/parsers/dataField" ) func (pcf *processComponentsFactory) createAPITransactionEvaluator(epochStartTrigger process.EpochStartTriggerHandler) (factory.TransactionEvaluator, process.VirtualMachinesContainerFactory, error) { @@ -54,8 +56,9 @@ func (pcf *processComponentsFactory) createAPITransactionEvaluator(epochStartTri } dataFieldParser, err := datafield.NewOperationDataFieldParser(&datafield.ArgsOperationDataFieldParser{ - AddressLength: pcf.coreData.AddressPubKeyConverter().Len(), - Marshalizer: pcf.coreData.InternalMarshalizer(), + AddressLength: pcf.coreData.AddressPubKeyConverter().Len(), + Marshalizer: pcf.coreData.InternalMarshalizer(), + RelayedTransactionsV1V2DisableEpoch: pcf.coreData.EnableEpochsHandler().GetActivationEpoch(common.RelayedTransactionsV1V2DisableFlag), }) if err != nil { return nil, nil, err diff --git a/factory/state/stateComponents.go b/factory/state/stateComponents.go index 0a747c81971..0a2e4f278df 100644 --- a/factory/state/stateComponents.go +++ b/factory/state/stateComponents.go @@ -55,6 +55,7 @@ type stateComponents struct { peerAccounts state.AccountsAdapter accountsAdapter state.AccountsAdapter accountsAdapterAPI state.AccountsAdapter + accountsAdapterProposal state.AccountsAdapter accountsRepository state.AccountsRepository triesContainer common.TriesHolder trieStorageManagers map[string]common.StorageManager @@ -86,6 +87,13 @@ func NewStateComponentsFactory(args StateComponentsFactoryArgs) (*stateComponent }, nil } +type accountsAdapterCreationResult struct { + accountsAdapter state.AccountsAdapter + accountsAdapterAPI state.AccountsAdapter + accountsAdapterProposal state.AccountsAdapter + accountsRepository state.AccountsRepository +} + // Create creates the state components func (scf *stateComponentsFactory) Create() (*stateComponents, error) { triesContainer, trieStorageManagers, err := trieFactory.CreateTriesComponentsForShardId( @@ -103,7 +111,7 @@ func (scf *stateComponentsFactory) Create() (*stateComponents, error) { return nil, err } - accountsAdapter, accountsAdapterAPI, accountsRepository, err := scf.createAccountsAdapters(triesContainer, stateAccessesCollector) + accCreationResult, err := scf.createAccountsAdapters(triesContainer, stateAccessesCollector) if err != nil { return nil, err } @@ -120,9 +128,10 @@ func (scf *stateComponentsFactory) Create() (*stateComponents, error) { return &stateComponents{ peerAccounts: peerAdapter, - accountsAdapter: accountsAdapter, - accountsAdapterAPI: accountsAdapterAPI, - accountsRepository: accountsRepository, + accountsAdapter: accCreationResult.accountsAdapter, + accountsAdapterAPI: accCreationResult.accountsAdapterAPI, + accountsAdapterProposal: accCreationResult.accountsAdapterProposal, + accountsRepository: accCreationResult.accountsRepository, triesContainer: triesContainer, trieStorageManagers: trieStorageManagers, missingTrieNodesNotifier: syncer.NewMissingTrieNodesNotifier(), @@ -210,7 +219,7 @@ func (scf *stateComponentsFactory) createSnapshotManager( return state.NewSnapshotsManager(argsSnapshotsManager) } -func (scf *stateComponentsFactory) createAccountsAdapters(triesContainer common.TriesHolder, StateAccessesCollector state.StateAccessesCollector) (state.AccountsAdapter, state.AccountsAdapter, state.AccountsRepository, error) { +func (scf *stateComponentsFactory) createAccountsAdapters(triesContainer common.TriesHolder, StateAccessesCollector state.StateAccessesCollector) (*accountsAdapterCreationResult, error) { argsAccCreator := factoryState.ArgsAccountCreator{ Hasher: scf.core.Hasher(), Marshaller: scf.core.InternalMarshalizer(), @@ -219,13 +228,13 @@ func (scf *stateComponentsFactory) createAccountsAdapters(triesContainer common. } accountFactory, err := factoryState.NewAccountCreator(argsAccCreator) if err != nil { - return nil, nil, nil, err + return nil, err } merkleTrie := triesContainer.Get([]byte(dataRetriever.UserAccountsUnit.String())) storagePruning, err := scf.newStoragePruningManager() if err != nil { - return nil, nil, nil, err + return nil, err } argStateMetrics := stateMetrics.ArgsStateMetrics{ @@ -235,12 +244,12 @@ func (scf *stateComponentsFactory) createAccountsAdapters(triesContainer common. } sm, err := stateMetrics.NewStateMetrics(argStateMetrics, scf.statusCore.AppStatusHandler()) if err != nil { - return nil, nil, nil, err + return nil, err } snapshotsManager, err := scf.createSnapshotManager(accountFactory, sm, iteratorChannelsProvider.NewUserStateIteratorChannelsProvider()) if err != nil { - return nil, nil, nil, err + return nil, err } argsProcessingAccountsDB := state.ArgsAccountsDB{ @@ -252,10 +261,11 @@ func (scf *stateComponentsFactory) createAccountsAdapters(triesContainer common. AddressConverter: scf.core.AddressPubKeyConverter(), SnapshotsManager: snapshotsManager, StateAccessesCollector: StateAccessesCollector, + PruningEnabled: scf.config.StateTriesConfig.AccountsStatePruningEnabled, } accountsAdapter, err := state.NewAccountsDB(argsProcessingAccountsDB) if err != nil { - return nil, nil, nil, fmt.Errorf("%w: %s", errors.ErrAccountsAdapterCreation, err.Error()) + return nil, fmt.Errorf("%w: %s", errors.ErrAccountsAdapterCreation, err.Error()) } argsAPIAccCreator := factoryState.ArgsAccountCreator{ @@ -266,7 +276,7 @@ func (scf *stateComponentsFactory) createAccountsAdapters(triesContainer common. } accountFactoryAPI, err := factoryState.NewAccountCreator(argsAPIAccCreator) if err != nil { - return nil, nil, nil, err + return nil, err } argsAPIAccountsDB := state.ArgsAccountsDB{ @@ -282,17 +292,17 @@ func (scf *stateComponentsFactory) createAccountsAdapters(triesContainer common. accountsAdapterApiOnFinal, err := factoryState.CreateAccountsAdapterAPIOnFinal(argsAPIAccountsDB, scf.chainHandler) if err != nil { - return nil, nil, nil, fmt.Errorf("accounts adapter API on final: %w: %s", errors.ErrAccountsAdapterCreation, err.Error()) + return nil, fmt.Errorf("accounts adapter API on final: %w: %s", errors.ErrAccountsAdapterCreation, err.Error()) } accountsAdapterApiOnCurrent, err := factoryState.CreateAccountsAdapterAPIOnCurrent(argsAPIAccountsDB, scf.chainHandler) if err != nil { - return nil, nil, nil, fmt.Errorf("accounts adapter API on current: %w: %s", errors.ErrAccountsAdapterCreation, err.Error()) + return nil, fmt.Errorf("accounts adapter API on current: %w: %s", errors.ErrAccountsAdapterCreation, err.Error()) } accountsAdapterApiOnHistorical, err := factoryState.CreateAccountsAdapterAPIOnHistorical(argsAPIAccountsDB) if err != nil { - return nil, nil, nil, fmt.Errorf("accounts adapter API on historical: %w: %s", errors.ErrAccountsAdapterCreation, err.Error()) + return nil, fmt.Errorf("accounts adapter API on historical: %w: %s", errors.ErrAccountsAdapterCreation, err.Error()) } argsAccountsRepository := state.ArgsAccountsRepository{ @@ -303,10 +313,23 @@ func (scf *stateComponentsFactory) createAccountsAdapters(triesContainer common. accountsRepository, err := state.NewAccountsRepository(argsAccountsRepository) if err != nil { - return nil, nil, nil, fmt.Errorf("accountsRepository: %w", err) + return nil, fmt.Errorf("accountsRepository: %w", err) + } + + // TODO: this account adapter may be required to be changed as the roothash should be the last execution result roothash + accountsAdapterProposal, err := state.NewAccountsDB(argsAPIAccountsDB) + if err != nil { + return nil, fmt.Errorf("accounts adapter for proposal: %w: %s", errors.ErrAccountsAdapterCreation, err) + } + + response := &accountsAdapterCreationResult{ + accountsAdapter: accountsAdapter, + accountsAdapterAPI: accountsRepository.GetCurrentStateAccountsWrapper(), + accountsAdapterProposal: accountsAdapterProposal, + accountsRepository: accountsRepository, } - return accountsAdapter, accountsRepository.GetCurrentStateAccountsWrapper(), accountsRepository, nil + return response, nil } func (scf *stateComponentsFactory) createPeerAdapter(triesContainer common.TriesHolder) (state.AccountsAdapter, error) { @@ -342,6 +365,7 @@ func (scf *stateComponentsFactory) createPeerAdapter(triesContainer common.Tries AddressConverter: scf.core.AddressPubKeyConverter(), SnapshotsManager: snapshotManager, StateAccessesCollector: disabled.NewDisabledStateAccessesCollector(), + PruningEnabled: scf.config.StateTriesConfig.PeerStatePruningEnabled, } peerAdapter, err := state.NewPeerAccountsDB(argsProcessingPeerAccountsDB) if err != nil { diff --git a/factory/state/stateComponentsHandler.go b/factory/state/stateComponentsHandler.go index a640c3f84cb..a33704893af 100644 --- a/factory/state/stateComponentsHandler.go +++ b/factory/state/stateComponentsHandler.go @@ -5,6 +5,7 @@ import ( "sync" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/errors" "github.com/multiversx/mx-chain-go/factory" @@ -136,6 +137,18 @@ func (msc *managedStateComponents) AccountsAdapterAPI() state.AccountsAdapter { return msc.stateComponents.accountsAdapterAPI } +// AccountsAdapterProposal returns the accounts adapter for the user accounts to be used in proposals +func (msc *managedStateComponents) AccountsAdapterProposal() state.AccountsAdapter { + msc.mutStateComponents.RLock() + defer msc.mutStateComponents.RUnlock() + + if msc.stateComponents == nil { + return nil + } + + return msc.stateComponents.accountsAdapterProposal +} + // AccountsRepository returns the accounts adapter for the user accounts to be used in REST API func (msc *managedStateComponents) AccountsRepository() state.AccountsRepository { msc.mutStateComponents.RLock() diff --git a/factory/state/stateComponentsHandler_test.go b/factory/state/stateComponentsHandler_test.go index e73600180ff..4ba3aa0aa44 100644 --- a/factory/state/stateComponentsHandler_test.go +++ b/factory/state/stateComponentsHandler_test.go @@ -75,6 +75,7 @@ func TestManagedStateComponents_Create(t *testing.T) { require.NotNil(t, managedStateComponents.TriesContainer()) require.NotNil(t, managedStateComponents.TrieStorageManagers()) require.NotNil(t, managedStateComponents.AccountsAdapterAPI()) + require.NotNil(t, managedStateComponents.AccountsAdapterProposal()) require.NotNil(t, managedStateComponents.AccountsRepository()) require.NotNil(t, managedStateComponents.MissingTrieNodesNotifier()) diff --git a/factory/status/statusComponents.go b/factory/status/statusComponents.go index 73ecb5e2777..2d2d3fab527 100644 --- a/factory/status/statusComponents.go +++ b/factory/status/statusComponents.go @@ -133,9 +133,9 @@ func (scf *statusComponentsFactory) Create() (*statusComponents, error) { softwareVersionChecker.StartCheckSoftwareVersion() - roundDurationSec := scf.coreComponents.GenesisNodesSetup().GetRoundDuration() / 1000 - if roundDurationSec < 1 { - return nil, errors.ErrInvalidRoundDuration + err = common.CheckRoundDuration(scf.coreComponents.GenesisNodesSetup().GetRoundDuration(), scf.coreComponents.EnableEpochsHandler()) + if err != nil { + return nil, err } outportHandler, err := scf.createOutportDriver() @@ -229,6 +229,8 @@ func (scf *statusComponentsFactory) createOutportDriver() (outport.OutportHandle EventNotifierFactoryArgs: eventNotifierArgs, HostDriversArgs: hostDriversArgs, IsImportDB: scf.isInImportMode, + EnableEpochsHandler: scf.coreComponents.EnableEpochsHandler(), + EnableRoundsHandler: scf.coreComponents.EnableRoundsHandler(), } return outportDriverFactory.CreateOutport(outportFactoryArgs) @@ -248,7 +250,6 @@ func (scf *statusComponentsFactory) makeElasticIndexerArgs() indexerFactory.Args ValidatorPubkeyConverter: scf.coreComponents.ValidatorPubKeyConverter(), EnabledIndexes: elasticSearchConfig.EnabledIndexes, Denomination: scf.economicsConfig.GlobalSettings.Denomination, - UseKibana: elasticSearchConfig.UseKibana, ImportDB: scf.isInImportMode, HeaderMarshaller: scf.coreComponents.InternalMarshalizer(), } diff --git a/factory/status/statusComponentsHandler.go b/factory/status/statusComponentsHandler.go index acc18cffc9f..9cac706935b 100644 --- a/factory/status/statusComponentsHandler.go +++ b/factory/status/statusComponentsHandler.go @@ -350,6 +350,9 @@ func registerPollProbableHighestNonce( ) error { probableHighestNonceHandlerFunc := func(appStatusHandler core.AppStatusHandler) { + if check.IfNil(forkDetector) { + return + } probableHigherNonce := forkDetector.ProbableHighestNonce() appStatusHandler.SetUInt64Value(common.MetricProbableHighestNonce, probableHigherNonce) } diff --git a/factory/status/statusComponentsHandler_test.go b/factory/status/statusComponentsHandler_test.go index c7252cbf6de..782fe968b76 100644 --- a/factory/status/statusComponentsHandler_test.go +++ b/factory/status/statusComponentsHandler_test.go @@ -145,7 +145,10 @@ func TestManagedStatusComponents_StartPolling(t *testing.T) { t.Run("should work", func(t *testing.T) { scf, _ := statusComp.NewStatusComponentsFactory(createMockStatusComponentsFactoryArgs()) managedStatusComponents, _ := statusComp.NewManagedStatusComponents(scf) - err := managedStatusComponents.Create() + err := managedStatusComponents.SetForkDetector(&mock.ForkDetectorMock{}) + require.Nil(t, err) + + err = managedStatusComponents.Create() require.NoError(t, err) err = managedStatusComponents.StartPolling() diff --git a/factory/status/statusComponents_test.go b/factory/status/statusComponents_test.go index 2b7c3e59379..3ee36253d20 100644 --- a/factory/status/statusComponents_test.go +++ b/factory/status/statusComponents_test.go @@ -13,6 +13,7 @@ import ( testsMocks "github.com/multiversx/mx-chain-go/integrationTests/mock" "github.com/multiversx/mx-chain-go/testscommon" componentsMock "github.com/multiversx/mx-chain-go/testscommon/components" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" "github.com/multiversx/mx-chain-go/testscommon/factory" "github.com/multiversx/mx-chain-go/testscommon/genesisMocks" @@ -51,7 +52,9 @@ func createMockStatusComponentsFactoryArgs() statusComp.StatusComponentsFactoryA return 1000 }, }, - EpochChangeNotifier: &epochNotifier.EpochNotifierStub{}, + EpochChangeNotifier: &epochNotifier.EpochNotifierStub{}, + EnableEpochsHandlerField: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandlerField: &testscommon.EnableRoundsHandlerStub{}, }, StatusCoreComponents: &factory.StatusCoreComponentsStub{ AppStatusHandlerField: &statusHandler.AppStatusHandlerStub{}, @@ -169,6 +172,7 @@ func TestStatusComponentsFactory_Create(t *testing.T) { return 0 }, }, + EnableEpochsHandlerField: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, } scf, _ := statusComp.NewStatusComponentsFactory(args) require.NotNil(t, scf) diff --git a/factory/statusCore/statusCoreComponents.go b/factory/statusCore/statusCoreComponents.go index b25cbca427e..695a0992eaf 100644 --- a/factory/statusCore/statusCoreComponents.go +++ b/factory/statusCore/statusCoreComponents.go @@ -3,6 +3,8 @@ package statusCore import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/common/statistics" "github.com/multiversx/mx-chain-go/common/statistics/disabled" @@ -15,7 +17,6 @@ import ( "github.com/multiversx/mx-chain-go/statusHandler" "github.com/multiversx/mx-chain-go/statusHandler/persister" trieStatistics "github.com/multiversx/mx-chain-go/trie/statistics" - logger "github.com/multiversx/mx-chain-logger-go" ) var log = logger.GetOrCreate("factory") @@ -97,6 +98,12 @@ func (sccf *statusCoreComponentsFactory) Create() (*statusCoreComponents, error) return nil, err } + chainParamsMetricsHandler, err := statusHandler.NewChainParamsMetricsHandler(appStatusHandler) + if err != nil { + return nil, err + } + sccf.coreComp.ChainParametersSubscriber().RegisterNotifyHandler(chainParamsMetricsHandler) + stateStatsHandler := sccf.createStateStatsHandler() ssc := &statusCoreComponents{ @@ -123,7 +130,10 @@ func (sccf *statusCoreComponentsFactory) createStateStatsHandler() common.StateS func (sccf *statusCoreComponentsFactory) createStatusHandler() (core.AppStatusHandler, external.StatusMetricsHandler, factory.PersistentStatusHandler, error) { var appStatusHandlers []core.AppStatusHandler var handler core.AppStatusHandler - statusMetrics := statusHandler.NewStatusMetrics() + statusMetrics, err := statusHandler.NewStatusMetrics(sccf.coreComp.EnableEpochsHandler(), sccf.coreComp.EnableRoundsHandler()) + if err != nil { + return nil, nil, nil, err + } appStatusHandlers = append(appStatusHandlers, statusMetrics) persistentHandler, err := persister.NewPersistentStatusHandler(sccf.coreComp.InternalMarshalizer(), sccf.coreComp.Uint64ByteSliceConverter()) diff --git a/fallback/headerValidator.go b/fallback/headerValidator.go index 4b9110582b0..8672e4356cd 100644 --- a/fallback/headerValidator.go +++ b/fallback/headerValidator.go @@ -5,19 +5,22 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/marshal" - "github.com/multiversx/mx-chain-logger-go" + logger "github.com/multiversx/mx-chain-logger-go" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" + "github.com/multiversx/mx-chain-go/errors" "github.com/multiversx/mx-chain-go/process" ) var log = logger.GetOrCreate("fallback") type fallbackHeaderValidator struct { - headersPool dataRetriever.HeadersPool - marshalizer marshal.Marshalizer - storageService dataRetriever.StorageService + headersPool dataRetriever.HeadersPool + marshalizer marshal.Marshalizer + storageService dataRetriever.StorageService + enableRoundsHandler common.EnableRoundsHandler + configsByRound common.CommonConfigsHandler } // NewFallbackHeaderValidator creates a new fallbackHeaderValidator object @@ -25,6 +28,8 @@ func NewFallbackHeaderValidator( headersPool dataRetriever.HeadersPool, marshalizer marshal.Marshalizer, storageService dataRetriever.StorageService, + enableRoundsHandler common.EnableRoundsHandler, + configsByRound common.CommonConfigsHandler, ) (*fallbackHeaderValidator, error) { if check.IfNil(headersPool) { @@ -36,11 +41,19 @@ func NewFallbackHeaderValidator( if check.IfNil(storageService) { return nil, process.ErrNilStorage } + if check.IfNil(enableRoundsHandler) { + return nil, errors.ErrNilEnableRoundsHandler + } + if check.IfNil(configsByRound) { + return nil, common.ErrNilCommonConfigsHandler + } hv := &fallbackHeaderValidator{ - headersPool: headersPool, - marshalizer: marshalizer, - storageService: storageService, + headersPool: headersPool, + marshalizer: marshalizer, + storageService: storageService, + enableRoundsHandler: enableRoundsHandler, + configsByRound: configsByRound, } return hv, nil @@ -61,10 +74,14 @@ func (fhv *fallbackHeaderValidator) ShouldApplyFallbackValidationForHeaderWith(s return false } - isRoundTooOld := int64(round)-int64(previousHeader.GetRound()) >= common.MaxRoundsWithoutCommittedStartInEpochBlock + isRoundTooOld := int64(round)-int64(previousHeader.GetRound()) >= fhv.getMaxRoundsWithoutCommittedStartInEpochBlock(previousHeader.GetRound()) return isRoundTooOld } +func (fhv *fallbackHeaderValidator) getMaxRoundsWithoutCommittedStartInEpochBlock(round uint64) int64 { + return int64(fhv.configsByRound.GetMaxRoundsWithoutCommittedStartInEpochBlockInRound(round)) +} + // ShouldApplyFallbackValidation returns if for the given header could be applied fallback validation or not func (fhv *fallbackHeaderValidator) ShouldApplyFallbackValidation(headerHandler data.HeaderHandler) bool { if check.IfNil(headerHandler) { diff --git a/fallback/headerValidator_test.go b/fallback/headerValidator_test.go index 7fada2d1285..5a5f35c838e 100644 --- a/fallback/headerValidator_test.go +++ b/fallback/headerValidator_test.go @@ -8,21 +8,26 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-go/common" + commonErrors "github.com/multiversx/mx-chain-go/errors" "github.com/multiversx/mx-chain-go/fallback" "github.com/multiversx/mx-chain-go/fallback/mock" "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/storage" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) +const maxRoundsWithoutCommittedStartInEpochBlock = 50 +const supernovaMaxRoundsWithoutCommittedStartInEpochBlock = 500 + func TestNewFallbackHeaderValidator_ShouldErrNilHeadersDataPool(t *testing.T) { t.Parallel() marshalizer := &mock.MarshalizerStub{} storageService := &storage.ChainStorerStub{} - fhv, err := fallback.NewFallbackHeaderValidator(nil, marshalizer, storageService) + fhv, err := fallback.NewFallbackHeaderValidator(nil, marshalizer, storageService, &testscommon.EnableRoundsHandlerStub{}, testscommon.GetDefaultCommonConfigsHandler()) assert.Nil(t, fhv) assert.Equal(t, process.ErrNilHeadersDataPool, err) } @@ -33,7 +38,7 @@ func TestNewFallbackHeaderValidator_ShouldErrNilMarshalizer(t *testing.T) { headersPool := &mock.HeadersCacherStub{} storageService := &storage.ChainStorerStub{} - fhv, err := fallback.NewFallbackHeaderValidator(headersPool, nil, storageService) + fhv, err := fallback.NewFallbackHeaderValidator(headersPool, nil, storageService, &testscommon.EnableRoundsHandlerStub{}, testscommon.GetDefaultCommonConfigsHandler()) assert.Nil(t, fhv) assert.Equal(t, process.ErrNilMarshalizer, err) } @@ -44,11 +49,35 @@ func TestNewFallbackHeaderValidator_ShouldErrNilStorage(t *testing.T) { headersPool := &mock.HeadersCacherStub{} marshalizer := &mock.MarshalizerStub{} - fhv, err := fallback.NewFallbackHeaderValidator(headersPool, marshalizer, nil) + fhv, err := fallback.NewFallbackHeaderValidator(headersPool, marshalizer, nil, &testscommon.EnableRoundsHandlerStub{}, testscommon.GetDefaultCommonConfigsHandler()) assert.Nil(t, fhv) assert.Equal(t, process.ErrNilStorage, err) } +func TestNewFallbackHeaderValidator_ShouldErrNilEnableEpochsHandler(t *testing.T) { + t.Parallel() + + headersPool := &mock.HeadersCacherStub{} + marshalizer := &mock.MarshalizerStub{} + storageService := &storage.ChainStorerStub{} + + fhv, err := fallback.NewFallbackHeaderValidator(headersPool, marshalizer, storageService, nil, testscommon.GetDefaultCommonConfigsHandler()) + assert.Nil(t, fhv) + assert.Equal(t, commonErrors.ErrNilEnableRoundsHandler, err) +} + +func TestNewFallbackHeaderValidator_ShouldErrNilCommonConfigsHandler(t *testing.T) { + t.Parallel() + + headersPool := &mock.HeadersCacherStub{} + marshalizer := &mock.MarshalizerStub{} + storageService := &storage.ChainStorerStub{} + + fhv, err := fallback.NewFallbackHeaderValidator(headersPool, marshalizer, storageService, &testscommon.EnableRoundsHandlerStub{}, nil) + assert.Nil(t, fhv) + assert.Equal(t, common.ErrNilCommonConfigsHandler, err) +} + func TestNewFallbackHeaderValidator_ShouldWork(t *testing.T) { t.Parallel() @@ -56,7 +85,7 @@ func TestNewFallbackHeaderValidator_ShouldWork(t *testing.T) { marshalizer := &mock.MarshalizerStub{} storageService := &storage.ChainStorerStub{} - fhv, err := fallback.NewFallbackHeaderValidator(headersPool, marshalizer, storageService) + fhv, err := fallback.NewFallbackHeaderValidator(headersPool, marshalizer, storageService, &testscommon.EnableRoundsHandlerStub{}, testscommon.GetDefaultCommonConfigsHandler()) assert.False(t, check.IfNil(fhv)) assert.Nil(t, err) } @@ -68,7 +97,7 @@ func TestShouldApplyFallbackConsensus_ShouldReturnFalseWhenHeaderIsNil(t *testin marshalizer := &mock.MarshalizerStub{} storageService := &storage.ChainStorerStub{} - fhv, _ := fallback.NewFallbackHeaderValidator(headersPool, marshalizer, storageService) + fhv, _ := fallback.NewFallbackHeaderValidator(headersPool, marshalizer, storageService, &testscommon.EnableRoundsHandlerStub{}, testscommon.GetDefaultCommonConfigsHandler()) assert.False(t, fhv.ShouldApplyFallbackValidation(nil)) } @@ -80,7 +109,7 @@ func TestShouldApplyFallbackConsensus_ShouldReturnFalseWhenIsNotMetachainBlock(t storageService := &storage.ChainStorerStub{} header := &block.Header{} - fhv, _ := fallback.NewFallbackHeaderValidator(headersPool, marshalizer, storageService) + fhv, _ := fallback.NewFallbackHeaderValidator(headersPool, marshalizer, storageService, &testscommon.EnableRoundsHandlerStub{}, testscommon.GetDefaultCommonConfigsHandler()) assert.False(t, fhv.ShouldApplyFallbackValidation(header)) } @@ -92,7 +121,7 @@ func TestShouldApplyFallbackConsensus_ShouldReturnFalseWhenIsNotStartOfEpochMeta storageService := &storage.ChainStorerStub{} metaBlock := &block.MetaBlock{} - fhv, _ := fallback.NewFallbackHeaderValidator(headersPool, marshalizer, storageService) + fhv, _ := fallback.NewFallbackHeaderValidator(headersPool, marshalizer, storageService, &testscommon.EnableRoundsHandlerStub{}, testscommon.GetDefaultCommonConfigsHandler()) assert.False(t, fhv.ShouldApplyFallbackValidation(metaBlock)) } @@ -111,7 +140,7 @@ func TestShouldApplyFallbackConsensus_ShouldReturnFalseWhenPreviousHeaderIsNotFo }, } - fhv, _ := fallback.NewFallbackHeaderValidator(headersPool, marshalizer, storageService) + fhv, _ := fallback.NewFallbackHeaderValidator(headersPool, marshalizer, storageService, &testscommon.EnableRoundsHandlerStub{}, testscommon.GetDefaultCommonConfigsHandler()) assert.False(t, fhv.ShouldApplyFallbackValidation(metaBlock)) } @@ -131,7 +160,7 @@ func TestShouldApplyFallbackConsensus_ShouldReturnFalseWhenRoundIsNotTooOld(t *t storageService := &storage.ChainStorerStub{} epochStartShardData := block.EpochStartShardData{} metaBlock := &block.MetaBlock{ - Round: common.MaxRoundsWithoutCommittedStartInEpochBlock - 1, + Round: maxRoundsWithoutCommittedStartInEpochBlock - 1, EpochStart: block.EpochStart{ LastFinalizedHeaders: []block.EpochStartShardData{ epochStartShardData, @@ -140,35 +169,73 @@ func TestShouldApplyFallbackConsensus_ShouldReturnFalseWhenRoundIsNotTooOld(t *t PrevHash: prevHash, } - fhv, _ := fallback.NewFallbackHeaderValidator(headersPool, marshalizer, storageService) + fhv, _ := fallback.NewFallbackHeaderValidator(headersPool, marshalizer, storageService, &testscommon.EnableRoundsHandlerStub{}, testscommon.GetDefaultCommonConfigsHandler()) assert.False(t, fhv.ShouldApplyFallbackValidation(metaBlock)) } func TestShouldApplyFallbackConsensus_ShouldReturnTrue(t *testing.T) { t.Parallel() - prevHash := []byte("prev_hash") - headersPool := &mock.HeadersCacherStub{ - GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { - if bytes.Equal(hash, prevHash) { - return &block.MetaBlock{}, nil - } - return nil, errors.New("error") - }, - } - marshalizer := &mock.MarshalizerStub{} - storageService := &storage.ChainStorerStub{} - epochStartShardData := block.EpochStartShardData{} - metaBlock := &block.MetaBlock{ - Round: common.MaxRoundsWithoutCommittedStartInEpochBlock, - EpochStart: block.EpochStart{ - LastFinalizedHeaders: []block.EpochStartShardData{ - epochStartShardData, + t.Run("should work", func(t *testing.T) { + t.Parallel() + + prevHash := []byte("prev_hash") + headersPool := &mock.HeadersCacherStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, prevHash) { + return &block.MetaBlock{}, nil + } + return nil, errors.New("error") }, - }, - PrevHash: prevHash, - } + } + marshalizer := &mock.MarshalizerStub{} + storageService := &storage.ChainStorerStub{} + epochStartShardData := block.EpochStartShardData{} + metaBlock := &block.MetaBlock{ + Round: maxRoundsWithoutCommittedStartInEpochBlock, + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + epochStartShardData, + }, + }, + PrevHash: prevHash, + } + + fhv, _ := fallback.NewFallbackHeaderValidator(headersPool, marshalizer, storageService, &testscommon.EnableRoundsHandlerStub{}, testscommon.GetDefaultCommonConfigsHandler()) + assert.True(t, fhv.ShouldApplyFallbackValidation(metaBlock)) + }) + + t.Run("with supernova activated, should work", func(t *testing.T) { + t.Parallel() + + prevHash := []byte("prev_hash") + headersPool := &mock.HeadersCacherStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, prevHash) { + return &block.MetaBlock{}, nil + } + return nil, errors.New("error") + }, + } + marshalizer := &mock.MarshalizerStub{} + storageService := &storage.ChainStorerStub{} + epochStartShardData := block.EpochStartShardData{} + metaBlock := &block.MetaBlock{ + Round: supernovaMaxRoundsWithoutCommittedStartInEpochBlock, + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + epochStartShardData, + }, + }, + PrevHash: prevHash, + } + enableRoundsHandlerMock := &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return flag == common.SupernovaRoundFlag + }, + } - fhv, _ := fallback.NewFallbackHeaderValidator(headersPool, marshalizer, storageService) - assert.True(t, fhv.ShouldApplyFallbackValidation(metaBlock)) + fhv, _ := fallback.NewFallbackHeaderValidator(headersPool, marshalizer, storageService, enableRoundsHandlerMock, testscommon.GetDefaultCommonConfigsHandler()) + assert.True(t, fhv.ShouldApplyFallbackValidation(metaBlock)) + }) } diff --git a/genesis/interface.go b/genesis/interface.go index e58708a236f..55c124924c3 100644 --- a/genesis/interface.go +++ b/genesis/interface.go @@ -118,6 +118,6 @@ type DeployProcessor interface { // VersionedHeaderFactory creates versioned headers type VersionedHeaderFactory interface { - Create(epoch uint32) data.HeaderHandler + Create(epoch uint32, round uint64) data.HeaderHandler IsInterfaceNil() bool } diff --git a/genesis/mock/txLogProcessorMock.go b/genesis/mock/txLogProcessorMock.go index 11cef23871a..8a8e2aacec0 100644 --- a/genesis/mock/txLogProcessorMock.go +++ b/genesis/mock/txLogProcessorMock.go @@ -31,6 +31,6 @@ func (tlpm *TxLogProcessorMock) IsInterfaceNil() bool { } // GetAllCurrentLogs - -func (tlpm *TxLogProcessorMock) GetAllCurrentLogs() []*data.LogData { +func (tlpm *TxLogProcessorMock) GetAllCurrentLogs() []data.LogDataHandler { return nil } diff --git a/genesis/process/argGenesisBlockCreator.go b/genesis/process/argGenesisBlockCreator.go index 685e356f31b..858a43c797d 100644 --- a/genesis/process/argGenesisBlockCreator.go +++ b/genesis/process/argGenesisBlockCreator.go @@ -9,6 +9,7 @@ import ( "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" crypto "github.com/multiversx/mx-chain-crypto-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" @@ -52,6 +53,7 @@ type ArgsGenesisBlockCreator struct { Data dataComponentsHandler Core coreComponentsHandler Accounts state.AccountsAdapter + AccountsProposal state.AccountsAdapter ValidatorAccounts state.AccountsAdapter InitialNodesSetup genesis.InitialNodesHandler Economics process.EconomicsDataHandler @@ -66,7 +68,9 @@ type ArgsGenesisBlockCreator struct { SystemSCConfig config.SystemSmartContractsConfig RoundConfig config.RoundConfig EpochConfig config.EpochConfig + FeeSettings config.FeeSettings HeaderVersionConfigs config.VersionsConfig + TxCacheSelectionConfig config.TxCacheSelectionConfig WorkingDir string BlockSignKeyGen crypto.KeyGenerator HistoryRepository dblookupext.HistoryRepository diff --git a/genesis/process/disabled/blockSizeComputationHandler.go b/genesis/process/disabled/blockSizeComputationHandler.go index a06a3cf9a7d..2404a56706d 100644 --- a/genesis/process/disabled/blockSizeComputationHandler.go +++ b/genesis/process/disabled/blockSizeComputationHandler.go @@ -16,6 +16,14 @@ func (b *BlockSizeComputationHandler) AddNumMiniBlocks(_ int) { func (b *BlockSizeComputationHandler) AddNumTxs(_ int) { } +// DecNumMiniBlocks does nothing +func (b *BlockSizeComputationHandler) DecNumMiniBlocks(numMiniBlocks int) { +} + +// DecNumTxs does nothing +func (b *BlockSizeComputationHandler) DecNumTxs(numTxs int) { +} + // IsMaxBlockSizeReached returns false as it is a disabled components func (b *BlockSizeComputationHandler) IsMaxBlockSizeReached(_ int, _ int) bool { return false diff --git a/genesis/process/disabled/blockTracker.go b/genesis/process/disabled/blockTracker.go index bfecf5f3d04..4a82c3183c7 100644 --- a/genesis/process/disabled/blockTracker.go +++ b/genesis/process/disabled/blockTracker.go @@ -9,6 +9,11 @@ func (b *BlockTracker) IsShardStuck(_ uint32) bool { return false } +// IsOwnShardStuck returns false as this is a disabled implementation +func (b *BlockTracker) IsOwnShardStuck() bool { + return false +} + // ShouldSkipMiniBlocksCreationFromSelf returns false as this is a disabled implementation func (b *BlockTracker) ShouldSkipMiniBlocksCreationFromSelf() bool { return false diff --git a/genesis/process/disabled/disabled_test.go b/genesis/process/disabled/disabled_test.go index 487a17c5af3..86edf18fab1 100644 --- a/genesis/process/disabled/disabled_test.go +++ b/genesis/process/disabled/disabled_test.go @@ -65,6 +65,7 @@ func TestBlockTracker(t *testing.T) { handler := &BlockTracker{} require.False(t, handler.IsShardStuck(0)) + require.False(t, handler.IsOwnShardStuck()) require.False(t, handler.ShouldSkipMiniBlocksCreationFromSelf()) require.False(t, handler.IsInterfaceNil()) } @@ -108,6 +109,10 @@ func TestFeeHandler(t *testing.T) { require.Equal(t, uint64(math.MaxUint64), handler.MaxGasLimitPerBlockForSafeCrossShard()) require.Equal(t, uint64(math.MaxUint64), handler.MaxGasLimitPerMiniBlockForSafeCrossShard()) require.Equal(t, uint64(math.MaxUint64), handler.MaxGasLimitPerTx()) + require.Equal(t, uint64(math.MaxUint64), handler.BlockCapacityOverestimationFactor()) + require.Equal(t, uint64(math.MaxUint64), handler.MaxGasLimitPerTxInEpoch(0)) + require.Equal(t, uint64(math.MaxUint64), handler.MaxGasLimitPerBlockForSafeCrossShardInEpoch(0)) + require.Equal(t, uint64(math.MaxUint64), handler.MaxGasLimitPerBlockInEpoch(0, 0)) require.Equal(t, uint64(0), handler.ComputeGasLimit(nil)) require.Equal(t, big.NewInt(0), handler.ComputeMoveBalanceFee(nil)) require.Equal(t, big.NewInt(0), handler.ComputeFeeForProcessing(nil, 0)) @@ -195,7 +200,7 @@ func TestRequestHandler(t *testing.T) { handler.RequestMetaHeader([]byte{}) handler.RequestMetaHeaderByNonce(0) handler.RequestShardHeaderByNonce(0, 0) - handler.RequestTransaction(0, [][]byte{}) + handler.RequestTransactions(0, [][]byte{}) handler.RequestUnsignedTransactions(0, [][]byte{}) handler.RequestRewardTransactions(0, [][]byte{}) handler.RequestMiniBlock(0, []byte{}) diff --git a/genesis/process/disabled/feeHandler.go b/genesis/process/disabled/feeHandler.go index 9f2a8f7ec29..652e03f15a9 100644 --- a/genesis/process/disabled/feeHandler.go +++ b/genesis/process/disabled/feeHandler.go @@ -57,11 +57,21 @@ func (fh *FeeHandler) MaxGasPriceSetGuardian() uint64 { return math.MaxUint64 } +// BlockCapacityOverestimationFactor returns max uint64 +func (fh *FeeHandler) BlockCapacityOverestimationFactor() uint64 { + return math.MaxUint64 +} + // MaxGasLimitPerBlock returns max uint64 func (fh *FeeHandler) MaxGasLimitPerBlock(uint32) uint64 { return math.MaxUint64 } +// MaxGasLimitPerBlockInEpoch returns max uint64 +func (fh *FeeHandler) MaxGasLimitPerBlockInEpoch(_ uint32, _ uint32) uint64 { + return math.MaxUint64 +} + // MaxGasLimitPerMiniBlock returns max uint64 func (fh *FeeHandler) MaxGasLimitPerMiniBlock(uint32) uint64 { return math.MaxUint64 @@ -72,6 +82,11 @@ func (fh *FeeHandler) MaxGasLimitPerBlockForSafeCrossShard() uint64 { return math.MaxUint64 } +// MaxGasLimitPerBlockForSafeCrossShardInEpoch returns max uint64 +func (fh *FeeHandler) MaxGasLimitPerBlockForSafeCrossShardInEpoch(_ uint32) uint64 { + return math.MaxUint64 +} + // MaxGasLimitPerMiniBlockForSafeCrossShard returns max uint64 func (fh *FeeHandler) MaxGasLimitPerMiniBlockForSafeCrossShard() uint64 { return math.MaxUint64 @@ -87,6 +102,11 @@ func (fh *FeeHandler) MaxGasLimitPerTx() uint64 { return math.MaxUint64 } +// MaxGasLimitPerTxInEpoch returns max uint64 +func (fh *FeeHandler) MaxGasLimitPerTxInEpoch(_ uint32) uint64 { + return math.MaxUint64 +} + // ComputeGasLimit returns 0 func (fh *FeeHandler) ComputeGasLimit(_ data.TransactionWithFeeHandler) uint64 { return 0 diff --git a/genesis/process/disabled/requestHandler.go b/genesis/process/disabled/requestHandler.go index d24590cc6d3..6f1aba31ebe 100644 --- a/genesis/process/disabled/requestHandler.go +++ b/genesis/process/disabled/requestHandler.go @@ -3,56 +3,73 @@ package disabled import "time" // RequestHandler implements the RequestHandler interface but does nothing as it is disabled -type RequestHandler struct { -} +type RequestHandler struct{} // SetEpoch does nothing -func (r *RequestHandler) SetEpoch(_ uint32) { -} +func (r *RequestHandler) SetEpoch(_ uint32) {} // RequestShardHeader does nothing -func (r *RequestHandler) RequestShardHeader(_ uint32, _ []byte) { -} +func (r *RequestHandler) RequestShardHeader(_ uint32, _ []byte) {} + +// RequestShardHeaderForEpoch does nothing +func (r *RequestHandler) RequestShardHeaderForEpoch(_ uint32, _ []byte, _ uint32) {} // RequestMetaHeader does nothing -func (r *RequestHandler) RequestMetaHeader(_ []byte) { -} +func (r *RequestHandler) RequestMetaHeader(_ []byte) {} + +// RequestMetaHeaderForEpoch does nothing +func (r *RequestHandler) RequestMetaHeaderForEpoch(hash []byte, epoch uint32) {} // RequestMetaHeaderByNonce does nothing -func (r *RequestHandler) RequestMetaHeaderByNonce(_ uint64) { -} +func (r *RequestHandler) RequestMetaHeaderByNonce(_ uint64) {} + +// RequestMetaHeaderByNonceForEpoch does nothing +func (r *RequestHandler) RequestMetaHeaderByNonceForEpoch(nonce uint64, epoch uint32) {} // RequestShardHeaderByNonce does nothing -func (r *RequestHandler) RequestShardHeaderByNonce(_ uint32, _ uint64) { -} +func (r *RequestHandler) RequestShardHeaderByNonce(_ uint32, _ uint64) {} -// RequestTransaction does nothing -func (r *RequestHandler) RequestTransaction(_ uint32, _ [][]byte) { -} +// RequestShardHeaderByNonceForEpoch does nothing +func (r *RequestHandler) RequestShardHeaderByNonceForEpoch(_ uint32, _ uint64, _ uint32) {} + +// RequestTransactions does nothing +func (r *RequestHandler) RequestTransactions(_ uint32, _ [][]byte) {} + +// RequestTransactionsForEpoch does nothing +func (r *RequestHandler) RequestTransactionsForEpoch(_ uint32, _ [][]byte, _ uint32) {} // RequestUnsignedTransactions does nothing -func (r *RequestHandler) RequestUnsignedTransactions(_ uint32, _ [][]byte) { -} +func (r *RequestHandler) RequestUnsignedTransactions(_ uint32, _ [][]byte) {} + +// RequestUnsignedTransactionsForEpoch does nothing +func (r *RequestHandler) RequestUnsignedTransactionsForEpoch(_ uint32, _ [][]byte, _ uint32) {} // RequestRewardTransactions does nothing -func (r *RequestHandler) RequestRewardTransactions(_ uint32, _ [][]byte) { -} +func (r *RequestHandler) RequestRewardTransactions(_ uint32, _ [][]byte) {} + +// RequestRewardTransactionsForEpoch does nothing +func (r *RequestHandler) RequestRewardTransactionsForEpoch(_ uint32, _ [][]byte, _ uint32) {} // RequestMiniBlock does nothing -func (r *RequestHandler) RequestMiniBlock(_ uint32, _ []byte) { -} +func (r *RequestHandler) RequestMiniBlock(_ uint32, _ []byte) {} + +// RequestMiniBlockForEpoch does nothing +func (r *RequestHandler) RequestMiniBlockForEpoch(_ uint32, _ []byte, _ uint32) {} // RequestMiniBlocks does nothing -func (r *RequestHandler) RequestMiniBlocks(_ uint32, _ [][]byte) { -} +func (r *RequestHandler) RequestMiniBlocks(_ uint32, _ [][]byte) {} + +// RequestMiniBlocksForEpoch does nothing +func (r *RequestHandler) RequestMiniBlocksForEpoch(_ uint32, _ [][]byte, _ uint32) {} // RequestTrieNodes does nothing -func (r *RequestHandler) RequestTrieNodes(_ uint32, _ [][]byte, _ string) { -} +func (r *RequestHandler) RequestTrieNodes(_ uint32, _ [][]byte, _ string) {} + +// RequestTrieNodesForEpoch does nothing +func (r *RequestHandler) RequestTrieNodesForEpoch(_ uint32, _ [][]byte, _ string, _ uint32) {} // RequestStartOfEpochMetaBlock does nothing -func (r *RequestHandler) RequestStartOfEpochMetaBlock(_ uint32) { -} +func (r *RequestHandler) RequestStartOfEpochMetaBlock(_ uint32) {} // RequestInterval returns one second func (r *RequestHandler) RequestInterval() time.Duration { @@ -70,8 +87,7 @@ func (r *RequestHandler) GetNumPeersToQuery(_ string) (int, int, error) { } // RequestTrieNode does nothing -func (r *RequestHandler) RequestTrieNode(_ []byte, _ string, _ uint32) { -} +func (r *RequestHandler) RequestTrieNode(_ []byte, _ string, _ uint32) {} // CreateTrieNodeIdentifier returns an empty slice func (r *RequestHandler) CreateTrieNodeIdentifier(_ []byte, _ uint32) []byte { @@ -79,24 +95,31 @@ func (r *RequestHandler) CreateTrieNodeIdentifier(_ []byte, _ uint32) []byte { } // RequestPeerAuthenticationsByHashes does nothing -func (r *RequestHandler) RequestPeerAuthenticationsByHashes(_ uint32, _ [][]byte) { -} +func (r *RequestHandler) RequestPeerAuthenticationsByHashes(_ uint32, _ [][]byte) {} // RequestValidatorInfo does nothing -func (r *RequestHandler) RequestValidatorInfo(_ []byte) { -} +func (r *RequestHandler) RequestValidatorInfo(_ []byte) {} + +// RequestValidatorInfoForEpoch does nothing +func (r *RequestHandler) RequestValidatorInfoForEpoch(_ []byte, _ uint32) {} // RequestValidatorsInfo does nothing -func (r *RequestHandler) RequestValidatorsInfo(_ [][]byte) { -} +func (r *RequestHandler) RequestValidatorsInfo(_ [][]byte) {} + +// RequestValidatorsInfoForEpoch does nothing +func (r *RequestHandler) RequestValidatorsInfoForEpoch(_ [][]byte, _ uint32) {} // RequestEquivalentProofByHash does nothing -func (r *RequestHandler) RequestEquivalentProofByHash(_ uint32, _ []byte) { -} +func (r *RequestHandler) RequestEquivalentProofByHash(_ uint32, _ []byte) {} + +// RequestEquivalentProofByHashForEpoch does nothing +func (r *RequestHandler) RequestEquivalentProofByHashForEpoch(_ uint32, _ []byte, _ uint32) {} // RequestEquivalentProofByNonce does nothing -func (r *RequestHandler) RequestEquivalentProofByNonce(_ uint32, _ uint64) { -} +func (r *RequestHandler) RequestEquivalentProofByNonce(_ uint32, _ uint64) {} + +// RequestEquivalentProofByNonceForEpoch does nothing +func (r *RequestHandler) RequestEquivalentProofByNonceForEpoch(_ uint32, _ uint64, _ uint32) {} // IsInterfaceNil returns true if there is no value under the interface func (r *RequestHandler) IsInterfaceNil() bool { diff --git a/genesis/process/disabled/roundHandler.go b/genesis/process/disabled/roundHandler.go index 4d5b2315cdf..c5b27faeec8 100644 --- a/genesis/process/disabled/roundHandler.go +++ b/genesis/process/disabled/roundHandler.go @@ -39,6 +39,11 @@ func (rh *RoundHandler) RemainingTime(startTime time.Time, maxTime time.Duration func (rh *RoundHandler) IncrementIndex() { } +// GetTimeStampForRound - +func (rh *RoundHandler) GetTimeStampForRound(round uint64) uint64 { + return 0 +} + // IsInterfaceNil returns true if there is no value under the interface func (rh *RoundHandler) IsInterfaceNil() bool { return rh == nil diff --git a/genesis/process/genesisBlockCreator.go b/genesis/process/genesisBlockCreator.go index 981d24e7a57..ac1ddd405c5 100644 --- a/genesis/process/genesisBlockCreator.go +++ b/genesis/process/genesisBlockCreator.go @@ -11,6 +11,8 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + vmcommonBuiltInFunctions "github.com/multiversx/mx-chain-vm-common-go/builtInFunctions" + "github.com/multiversx/mx-chain-go/common/enablers" "github.com/multiversx/mx-chain-go/common/forking" "github.com/multiversx/mx-chain-go/config" @@ -36,7 +38,6 @@ import ( hardfork "github.com/multiversx/mx-chain-go/update/genesis" hardForkProcess "github.com/multiversx/mx-chain-go/update/process" "github.com/multiversx/mx-chain-go/update/storing" - vmcommonBuiltInFunctions "github.com/multiversx/mx-chain-vm-common-go/builtInFunctions" ) const accountStartNonce = uint64(0) @@ -155,6 +156,9 @@ func checkArgumentsForBlockCreator(arg ArgsGenesisBlockCreator) error { if check.IfNil(arg.Accounts) { return process.ErrNilAccountsAdapter } + if check.IfNil(arg.AccountsProposal) { + return fmt.Errorf("%w for proposal", process.ErrNilAccountsAdapter) + } if check.IfNil(arg.Core) { return process.ErrNilCoreComponentsHolder } @@ -406,7 +410,9 @@ func (gbc *genesisBlockCreator) createHeaders( } // in case of hardfork initial smart contracts deployment is not called as they are all imported from previous state -func (gbc *genesisBlockCreator) computeDNSAddresses(enableEpochsConfig config.EnableEpochs) error { +func (gbc *genesisBlockCreator) computeDNSAddresses( + enableEpochsConfig config.EnableEpochs, +) error { var dnsSC genesis.InitialSmartContractHandler for _, sc := range gbc.arg.SmartContractParser.InitialSmartContracts() { if sc.GetType() == genesis.DNSType { @@ -519,6 +525,9 @@ func (gbc *genesisBlockCreator) getNewArgForShard(shardID uint32) (ArgsGenesisBl return ArgsGenesisBlockCreator{}, fmt.Errorf("'%w' while generating an in-memory accounts adapter for shard %d", err, shardID) } + // for genesis we can reuse the same account adapter for proposal as first proposal needs to happen after genesis block execution + // and the proposal won't use the genesis block creator + newArgument.AccountsProposal = newArgument.Accounts newArgument.ShardCoordinator, err = sharding.NewMultiShardCoordinator( newArgument.ShardCoordinator.NumberOfShards(), @@ -535,16 +544,9 @@ func (gbc *genesisBlockCreator) getNewArgForShard(shardID uint32) (ArgsGenesisBl } func (gbc *genesisBlockCreator) createVersionedHeaderFactory() (genesis.VersionedHeaderFactory, error) { - cacheConfig := factory.GetCacherFromConfig(gbc.arg.HeaderVersionConfigs.Cache) - cache, err := storageunit.NewCache(cacheConfig) - if err != nil { - return nil, err - } - headerVersionHandler, err := factoryBlock.NewHeaderVersionHandler( gbc.arg.HeaderVersionConfigs.VersionsByEpochs, gbc.arg.HeaderVersionConfigs.DefaultVersion, - cache, ) if err != nil { return nil, err diff --git a/genesis/process/genesisBlockCreator_test.go b/genesis/process/genesisBlockCreator_test.go index 27f849bf1e1..d5dada9caec 100644 --- a/genesis/process/genesisBlockCreator_test.go +++ b/genesis/process/genesisBlockCreator_test.go @@ -13,6 +13,11 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + wasmConfig "github.com/multiversx/mx-chain-vm-go/config" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" @@ -40,10 +45,6 @@ import ( "github.com/multiversx/mx-chain-go/update" updateMock "github.com/multiversx/mx-chain-go/update/mock" "github.com/multiversx/mx-chain-go/vm/systemSmartContracts/defaults" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" - wasmConfig "github.com/multiversx/mx-chain-vm-go/config" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) var nodePrice = big.NewInt(5000) @@ -148,6 +149,7 @@ func createMockArgument( MinStepValue: "10", MinStakeValue: "1", UnBondPeriod: 1, + UnBondPeriodSupernova: 2, NumRoundsWithoutBleed: 1, MaximumPercentageToBleed: 1, BleedPercentagePerRound: 1, @@ -184,15 +186,26 @@ func createMockArgument( StakeLimitsEnableEpoch: 10, }, }, + FeeSettings: config.FeeSettings{ + BlockCapacityOverestimationFactor: 200, + PercentDecreaseLimitsStep: 10, + }, RoundConfig: testscommon.GetDefaultRoundsConfig(), HeaderVersionConfigs: testscommon.GetDefaultHeaderVersionConfig(), HistoryRepository: &dblookupext.HistoryRepositoryStub{}, TxExecutionOrderHandler: &commonMocks.TxExecutionOrderHandlerStub{}, versionedHeaderFactory: &testscommon.VersionedHeaderFactoryStub{ - CreateCalled: func(epoch uint32) data.HeaderHandler { + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { return &block.Header{} }, }, + TxCacheSelectionConfig: config.TxCacheSelectionConfig{ + SelectionGasBandwidthIncreasePercent: 400, + SelectionGasBandwidthIncreaseScheduledPercent: 260, + SelectionGasRequested: 10_000_000_000, + SelectionMaxNumTxs: 30000, + SelectionLoopDurationCheckInterval: 10, + }, } arg.ShardCoordinator = &mock.ShardCoordinatorMock{ @@ -218,6 +231,7 @@ func createMockArgument( &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, ) require.Nil(t, err) + arg.AccountsProposal = arg.Accounts arg.ValidatorAccounts = &stateMock.AccountsStub{ RootHashCalled: func() ([]byte, error) { diff --git a/genesis/process/metaGenesisBlockCreator.go b/genesis/process/metaGenesisBlockCreator.go index f41ed60d107..d0fc949a722 100644 --- a/genesis/process/metaGenesisBlockCreator.go +++ b/genesis/process/metaGenesisBlockCreator.go @@ -15,6 +15,14 @@ import ( "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-core-go/marshal" + + "github.com/multiversx/mx-chain-go/process/aotSelection" + processBlock "github.com/multiversx/mx-chain-go/process/block" + + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + vmcommonBuiltInFunctions "github.com/multiversx/mx-chain-vm-common-go/builtInFunctions" + "github.com/multiversx/mx-chain-vm-common-go/parsers" + "github.com/multiversx/mx-chain-go/common" disabledCommon "github.com/multiversx/mx-chain-go/common/disabled" "github.com/multiversx/mx-chain-go/common/enablers" @@ -41,13 +49,10 @@ import ( syncDisabled "github.com/multiversx/mx-chain-go/process/sync/disabled" processTransaction "github.com/multiversx/mx-chain-go/process/transaction" "github.com/multiversx/mx-chain-go/state/syncer" - "github.com/multiversx/mx-chain-go/storage/txcache" + "github.com/multiversx/mx-chain-go/txcache" "github.com/multiversx/mx-chain-go/update" hardForkProcess "github.com/multiversx/mx-chain-go/update/process" "github.com/multiversx/mx-chain-go/vm" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" - vmcommonBuiltInFunctions "github.com/multiversx/mx-chain-vm-common-go/builtInFunctions" - "github.com/multiversx/mx-chain-vm-common-go/parsers" ) // CreateMetaGenesisBlock will create a metachain genesis block @@ -187,7 +192,7 @@ func createMetaGenesisBlockAfterHardFork( return nil, nil, nil, err } - metaHdr, ok := hdrHandler.(*block.MetaBlock) + metaHdr, ok := hdrHandler.(data.MetaHeaderHandler) if !ok { return nil, nil, nil, process.ErrWrongTypeAssertion } @@ -366,6 +371,7 @@ func createProcessorsForMetaGenesisBlock(arg ArgsGenesisBlockCreator, enableEpoc ChanceComputer: &disabled.Rater{}, ShardCoordinator: arg.ShardCoordinator, EnableEpochsHandler: enableEpochsHandler, + EnableRoundsHandler: enableRoundsHandler, NodesCoordinator: &disabled.NodesCoordinator{}, } virtualMachineFactory, err := metachain.NewVMContainerFactory(argsNewVMContainerFactory) @@ -494,28 +500,49 @@ func createProcessorsForMetaGenesisBlock(arg ArgsGenesisBlockCreator, enableEpoc disabledScheduledTxsExecutionHandler := &disabled.ScheduledTxsExecutionHandler{} disabledProcessedMiniBlocksTracker := &disabled.ProcessedMiniBlocksTracker{} - preProcFactory, err := metachain.NewPreProcessorsContainerFactory( - arg.ShardCoordinator, - arg.Data.StorageService(), - arg.Core.InternalMarshalizer(), - arg.Core.Hasher(), - arg.Data.Datapool(), - arg.Accounts, - disabledRequestHandler, - txProcessor, - scProcessorProxy, - arg.Economics, - gasHandler, - disabledBlockTracker, - arg.Core.AddressPubKeyConverter(), - disabledBlockSizeComputationHandler, - disabledBalanceComputationHandler, - enableEpochsHandler, - txTypeHandler, - disabledScheduledTxsExecutionHandler, - disabledProcessedMiniBlocksTracker, - arg.TxExecutionOrderHandler, - ) + argsGasConsumption := processBlock.ArgsGasConsumption{ + EconomicsFee: arg.Core.EconomicsData(), + ShardCoordinator: arg.ShardCoordinator, + GasHandler: gasHandler, + BlockCapacityOverestimationFactor: arg.FeeSettings.BlockCapacityOverestimationFactor, + PercentDecreaseLimitsStep: arg.FeeSettings.PercentDecreaseLimitsStep, + BlockSizeComputation: disabledBlockSizeComputationHandler, + } + gasConsumption, err := processBlock.NewGasConsumption(argsGasConsumption) + if err != nil { + return nil, err + } + + argsPreProcessorsContainerFactory := metachain.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: arg.ShardCoordinator, + Store: arg.Data.StorageService(), + Marshalizer: arg.Core.InternalMarshalizer(), + Hasher: arg.Core.Hasher(), + DataPool: arg.Data.Datapool(), + Accounts: arg.Accounts, + AccountsProposal: arg.AccountsProposal, + RequestHandler: disabledRequestHandler, + TxProcessor: txProcessor, + ScResultProcessor: scProcessorProxy, + EconomicsFee: arg.Economics, + GasHandler: gasHandler, + BlockTracker: disabledBlockTracker, + PubkeyConverter: arg.Core.AddressPubKeyConverter(), + BlockSizeComputation: disabledBlockSizeComputationHandler, + BalanceComputation: disabledBalanceComputationHandler, + EnableEpochsHandler: enableEpochsHandler, + EpochNotifier: epochNotifier, + EnableRoundsHandler: enableRoundsHandler, + RoundNotifier: roundNotifier, + TxTypeHandler: txTypeHandler, + ScheduledTxsExecutionHandler: disabledScheduledTxsExecutionHandler, + ProcessedMiniBlocksTracker: disabledProcessedMiniBlocksTracker, + TxExecutionOrderHandler: arg.TxExecutionOrderHandler, + TxCacheSelectionConfig: arg.TxCacheSelectionConfig, + TxVersionCheckerHandler: arg.Core.TxVersionChecker(), + } + + preProcFactory, err := metachain.NewPreProcessorsContainerFactory(argsPreProcessorsContainerFactory) if err != nil { return nil, err } @@ -535,14 +562,27 @@ func createProcessorsForMetaGenesisBlock(arg ArgsGenesisBlockCreator, enableEpoc return nil, err } + blockDataRequesterArgs := coordinator.BlockDataRequestArgs{ + RequestHandler: disabledRequestHandler, + MiniBlockPool: arg.Data.Datapool().MiniBlocks(), + PreProcessors: preProcContainer, + ShardCoordinator: arg.ShardCoordinator, + EnableEpochsHandler: enableEpochsHandler, + } + + blockDataRequester, err := coordinator.NewBlockDataRequester(blockDataRequesterArgs) + if err != nil { + return nil, err + } + argsTransactionCoordinator := coordinator.ArgTransactionCoordinator{ Hasher: arg.Core.Hasher(), Marshalizer: arg.Core.InternalMarshalizer(), ShardCoordinator: arg.ShardCoordinator, Accounts: arg.Accounts, - MiniBlockPool: arg.Data.Datapool().MiniBlocks(), - RequestHandler: disabledRequestHandler, + DataPool: arg.Data.Datapool(), PreProcessors: preProcContainer, + PreProcessorsProposal: preProcContainer, // for genesis no need for separate one InterProcessors: interimProcContainer, GasHandler: gasHandler, FeeHandler: genesisFeeHandler, @@ -552,10 +592,15 @@ func createProcessorsForMetaGenesisBlock(arg ArgsGenesisBlockCreator, enableEpoc TxTypeHandler: txTypeHandler, TransactionsLogProcessor: arg.TxLogsProcessor, EnableEpochsHandler: enableEpochsHandler, + EnableRoundsHandler: enableRoundsHandler, ScheduledTxsExecutionHandler: disabledScheduledTxsExecutionHandler, DoubleTransactionsDetector: doubleTransactionsDetector, ProcessedMiniBlocksTracker: disabledProcessedMiniBlocksTracker, TxExecutionOrderHandler: arg.TxExecutionOrderHandler, + BlockDataRequester: blockDataRequester, + BlockDataRequesterProposal: blockDataRequester, // for genesis block no need for separate one + GasComputation: gasConsumption, + AOTSelector: aotSelection.NewDisabledAOTSelector(), } txCoordinator, err := coordinator.NewTransactionCoordinator(argsTransactionCoordinator) if err != nil { diff --git a/genesis/process/shardGenesisBlockCreator.go b/genesis/process/shardGenesisBlockCreator.go index d3c106b1ecc..8ece334caca 100644 --- a/genesis/process/shardGenesisBlockCreator.go +++ b/genesis/process/shardGenesisBlockCreator.go @@ -9,8 +9,10 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" - "github.com/multiversx/mx-chain-core-go/data/block" dataBlock "github.com/multiversx/mx-chain-core-go/data/block" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-vm-common-go/parsers" + "github.com/multiversx/mx-chain-go/common" disabledCommon "github.com/multiversx/mx-chain-go/common/disabled" "github.com/multiversx/mx-chain-go/common/enablers" @@ -23,6 +25,8 @@ import ( "github.com/multiversx/mx-chain-go/genesis/process/disabled" "github.com/multiversx/mx-chain-go/genesis/process/intermediate" "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/aotSelection" + "github.com/multiversx/mx-chain-go/process/block" "github.com/multiversx/mx-chain-go/process/block/preprocess" "github.com/multiversx/mx-chain-go/process/coordinator" "github.com/multiversx/mx-chain-go/process/factory/shard" @@ -39,11 +43,9 @@ import ( "github.com/multiversx/mx-chain-go/process/transaction" "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/state/syncer" - "github.com/multiversx/mx-chain-go/storage/txcache" + "github.com/multiversx/mx-chain-go/txcache" "github.com/multiversx/mx-chain-go/update" hardForkProcess "github.com/multiversx/mx-chain-go/update/process" - logger "github.com/multiversx/mx-chain-logger-go" - "github.com/multiversx/mx-chain-vm-common-go/parsers" ) const unreachableEpoch = ^uint32(0) @@ -82,7 +84,7 @@ func createGenesisRoundConfig(providedEnableRounds config.RoundConfig) config.Ro // CreateShardGenesisBlock will create a shard genesis block func CreateShardGenesisBlock( arg ArgsGenesisBlockCreator, - body *block.Body, + body *dataBlock.Body, nodesListSplitter genesis.NodesListSplitter, hardForkBlockProcessor update.HardForkBlockProcessor, ) (data.HeaderHandler, [][]byte, *genesis.IndexingData, error) { @@ -140,7 +142,7 @@ func CreateShardGenesisBlock( err, arg.ShardCoordinator.SelfId()) } - scrsTxs := processors.txCoordinator.GetAllCurrentUsedTxs(block.SmartContractResultBlock) + scrsTxs := processors.txCoordinator.GetAllCurrentUsedTxs(dataBlock.SmartContractResultBlock) indexingData.ScrsTxs = scrsTxs rootHash, err := arg.Accounts.Commit() @@ -162,7 +164,7 @@ func CreateShardGenesisBlock( ) round, nonce, epoch := getGenesisBlocksRoundNonceEpoch(arg) - headerHandler := arg.versionedHeaderFactory.Create(epoch) + headerHandler := arg.versionedHeaderFactory.Create(epoch, round) err = setInitialDataInHeader(headerHandler, arg, epoch, nonce, round, rootHash) if err != nil { return nil, nil, nil, err @@ -199,7 +201,7 @@ func setInitialDataInHeader( setErrors = append(setErrors, shardHeaderHandler.SetNonce(nonce)) setErrors = append(setErrors, shardHeaderHandler.SetRound(round)) setErrors = append(setErrors, shardHeaderHandler.SetShardID(arg.ShardCoordinator.SelfId())) - setErrors = append(setErrors, shardHeaderHandler.SetBlockBodyTypeInt32(int32(block.StateBlock))) + setErrors = append(setErrors, shardHeaderHandler.SetBlockBodyTypeInt32(int32(dataBlock.StateBlock))) setErrors = append(setErrors, shardHeaderHandler.SetPubKeysBitmap([]byte{1})) setErrors = append(setErrors, shardHeaderHandler.SetSignature(rootHash)) setErrors = append(setErrors, shardHeaderHandler.SetRootHash(rootHash)) @@ -222,7 +224,7 @@ func setInitialDataInHeader( func createShardGenesisBlockAfterHardFork( arg ArgsGenesisBlockCreator, - body *block.Body, + body *dataBlock.Body, hardForkBlockProcessor update.HardForkBlockProcessor, ) (data.HeaderHandler, [][]byte, *genesis.IndexingData, error) { if check.IfNil(hardForkBlockProcessor) { @@ -582,30 +584,50 @@ func createProcessorsForShardGenesisBlock(arg ArgsGenesisBlockCreator, enableEpo disabledScheduledTxsExecutionHandler := &disabled.ScheduledTxsExecutionHandler{} disabledProcessedMiniBlocksTracker := &disabled.ProcessedMiniBlocksTracker{} - preProcFactory, err := shard.NewPreProcessorsContainerFactory( - arg.ShardCoordinator, - arg.Data.StorageService(), - arg.Core.InternalMarshalizer(), - arg.Core.Hasher(), - arg.Data.Datapool(), - arg.Core.AddressPubKeyConverter(), - arg.Accounts, - disabledRequestHandler, - transactionProcessor, - scProcessorProxy, - scProcessorProxy, - rewardsTxProcessor, - arg.Economics, - gasHandler, - disabledBlockTracker, - disabledBlockSizeComputationHandler, - disabledBalanceComputationHandler, - enableEpochsHandler, - txTypeHandler, - disabledScheduledTxsExecutionHandler, - disabledProcessedMiniBlocksTracker, - arg.TxExecutionOrderHandler, - ) + argsGasConsumption := block.ArgsGasConsumption{ + EconomicsFee: arg.Core.EconomicsData(), + ShardCoordinator: arg.ShardCoordinator, + GasHandler: gasHandler, + BlockCapacityOverestimationFactor: arg.FeeSettings.BlockCapacityOverestimationFactor, + PercentDecreaseLimitsStep: arg.FeeSettings.PercentDecreaseLimitsStep, + BlockSizeComputation: disabledBlockSizeComputationHandler, + } + gasConsumption, err := block.NewGasConsumption(argsGasConsumption) + if err != nil { + return nil, err + } + + argsPreProcessorContainerFactory := shard.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: arg.ShardCoordinator, + Store: arg.Data.StorageService(), + Marshalizer: arg.Core.InternalMarshalizer(), + Hasher: arg.Core.Hasher(), + DataPool: arg.Data.Datapool(), + PubkeyConverter: arg.Core.AddressPubKeyConverter(), + Accounts: arg.Accounts, + AccountsProposal: arg.AccountsProposal, + RequestHandler: disabledRequestHandler, + TxProcessor: transactionProcessor, + ScProcessor: scProcessorProxy, + ScResultProcessor: scProcessorProxy, + RewardsTxProcessor: rewardsTxProcessor, + EconomicsFee: arg.Economics, + GasHandler: gasHandler, + BlockTracker: disabledBlockTracker, + BlockSizeComputation: disabledBlockSizeComputationHandler, + BalanceComputation: disabledBalanceComputationHandler, + EnableEpochsHandler: enableEpochsHandler, + EpochNotifier: epochNotifier, + EnableRoundsHandler: enableRoundsHandler, + RoundNotifier: roundNotifier, + TxTypeHandler: txTypeHandler, + ScheduledTxsExecutionHandler: disabledScheduledTxsExecutionHandler, + ProcessedMiniBlocksTracker: disabledProcessedMiniBlocksTracker, + TxExecutionOrderHandler: arg.TxExecutionOrderHandler, + TxCacheSelectionConfig: arg.TxCacheSelectionConfig, + TxVersionChecker: arg.Core.TxVersionChecker(), + } + preProcFactory, err := shard.NewPreProcessorsContainerFactory(argsPreProcessorContainerFactory) if err != nil { return nil, err } @@ -625,14 +647,27 @@ func createProcessorsForShardGenesisBlock(arg ArgsGenesisBlockCreator, enableEpo return nil, err } + blockDataRequesterArgs := coordinator.BlockDataRequestArgs{ + RequestHandler: disabledRequestHandler, + MiniBlockPool: arg.Data.Datapool().MiniBlocks(), + PreProcessors: preProcContainer, + ShardCoordinator: arg.ShardCoordinator, + EnableEpochsHandler: enableEpochsHandler, + } + + blockDataRequester, err := coordinator.NewBlockDataRequester(blockDataRequesterArgs) + if err != nil { + return nil, err + } + argsTransactionCoordinator := coordinator.ArgTransactionCoordinator{ Hasher: arg.Core.Hasher(), Marshalizer: arg.Core.InternalMarshalizer(), ShardCoordinator: arg.ShardCoordinator, Accounts: arg.Accounts, - MiniBlockPool: arg.Data.Datapool().MiniBlocks(), - RequestHandler: disabledRequestHandler, + DataPool: arg.Data.Datapool(), PreProcessors: preProcContainer, + PreProcessorsProposal: preProcContainer, // no need for a different one in genesis InterProcessors: interimProcContainer, GasHandler: gasHandler, FeeHandler: genesisFeeHandler, @@ -642,10 +677,15 @@ func createProcessorsForShardGenesisBlock(arg ArgsGenesisBlockCreator, enableEpo TxTypeHandler: txTypeHandler, TransactionsLogProcessor: arg.TxLogsProcessor, EnableEpochsHandler: enableEpochsHandler, + EnableRoundsHandler: enableRoundsHandler, ScheduledTxsExecutionHandler: disabledScheduledTxsExecutionHandler, DoubleTransactionsDetector: doubleTransactionsDetector, ProcessedMiniBlocksTracker: disabledProcessedMiniBlocksTracker, TxExecutionOrderHandler: arg.TxExecutionOrderHandler, + BlockDataRequester: blockDataRequester, + BlockDataRequesterProposal: blockDataRequester, // no need for a different one in genesis + GasComputation: gasConsumption, + AOTSelector: aotSelection.NewDisabledAOTSelector(), } txCoordinator, err := coordinator.NewTransactionCoordinator(argsTransactionCoordinator) if err != nil { diff --git a/go.mod b/go.mod index 82f01ad56b5..a6ea1bb9970 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/multiversx/mx-chain-go -go 1.23 +go 1.23.0 require ( github.com/beevik/ntp v1.3.0 github.com/davecgh/go-spew v1.1.1 - github.com/gin-contrib/cors v1.4.0 + github.com/gin-contrib/cors v1.6.0 github.com/gin-contrib/pprof v1.4.0 github.com/gin-gonic/gin v1.10.0 github.com/gizak/termui/v3 v3.1.0 @@ -16,14 +16,14 @@ require ( github.com/libp2p/go-libp2p v0.38.2 github.com/libp2p/go-libp2p-pubsub v0.13.0 github.com/mitchellh/mapstructure v1.5.0 - github.com/multiversx/mx-chain-communication-go v1.3.0 - github.com/multiversx/mx-chain-core-go v1.4.1 - github.com/multiversx/mx-chain-crypto-go v1.3.0 - github.com/multiversx/mx-chain-es-indexer-go v1.9.2 + github.com/multiversx/mx-chain-communication-go v1.3.1 + github.com/multiversx/mx-chain-core-go v1.4.2-0.20260219091525-015123fd1603 + github.com/multiversx/mx-chain-crypto-go v1.3.1-0.20260130144701-dfa5fd3ea5d7 + github.com/multiversx/mx-chain-es-indexer-go v1.9.4-0.20260219114236-37624897dc3a github.com/multiversx/mx-chain-logger-go v1.1.0 github.com/multiversx/mx-chain-scenario-go v1.6.0 github.com/multiversx/mx-chain-storage-go v1.1.0 - github.com/multiversx/mx-chain-vm-common-go v1.6.0 + github.com/multiversx/mx-chain-vm-common-go v1.6.1-0.20251127112501-0b5f28e33b2e github.com/multiversx/mx-chain-vm-go v1.5.43 github.com/multiversx/mx-chain-vm-v1_2-go v1.2.69 github.com/multiversx/mx-chain-vm-v1_3-go v1.3.70 @@ -34,8 +34,9 @@ require ( github.com/shirou/gopsutil v3.21.11+incompatible github.com/stretchr/testify v1.10.0 github.com/urfave/cli v1.22.16 - golang.org/x/crypto v0.32.0 + golang.org/x/crypto v0.35.0 golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c + golang.org/x/sync v0.11.0 gopkg.in/go-playground/validator.v8 v8.18.2 ) @@ -196,9 +197,8 @@ require ( golang.org/x/arch v0.8.0 // indirect golang.org/x/mod v0.22.0 // indirect golang.org/x/net v0.34.0 // indirect - golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/text v0.22.0 // indirect golang.org/x/tools v0.29.0 // indirect gonum.org/v1/gonum v0.15.1 // indirect google.golang.org/protobuf v1.36.4 // indirect diff --git a/go.sum b/go.sum index f340fdf22d3..99b8496a5f8 100644 --- a/go.sum +++ b/go.sum @@ -121,8 +121,8 @@ github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmV github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc= github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g= -github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs= +github.com/gin-contrib/cors v1.6.0 h1:0Z7D/bVhE6ja07lI8CTjTonp6SB07o8bNuFyRbsBUQg= +github.com/gin-contrib/cors v1.6.0/go.mod h1:cI+h6iOAyxKRtUtC6iF/Si1KSFvGm/gK+kshxlCi8ro= github.com/gin-contrib/pprof v1.4.0 h1:XxiBSf5jWZ5i16lNOPbMTVdgHBdhfGRD5PZ1LWazzvg= github.com/gin-contrib/pprof v1.4.0/go.mod h1:RrehPJasUVBPK6yTUwOl8/NP6i0vbUgmxtis+Z5KE90= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -399,22 +399,22 @@ github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/n github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/multiversx/concurrent-map v0.1.4 h1:hdnbM8VE4b0KYJaGY5yJS2aNIW9TFFsUYwbO0993uPI= github.com/multiversx/concurrent-map v0.1.4/go.mod h1:8cWFRJDOrWHOTNSqgYCUvwT7c7eFQ4U2vKMOp4A/9+o= -github.com/multiversx/mx-chain-communication-go v1.3.0 h1:ziNM1dRuiR/7al2L/jGEA/a/hjurtJ/HEqgazHNt9P8= -github.com/multiversx/mx-chain-communication-go v1.3.0/go.mod h1:gDVWn6zUW6aCN1YOm/FbbT5MUmhgn/L1Rmpl8EoH3Yg= -github.com/multiversx/mx-chain-core-go v1.4.1 h1:ljs53jpdjtCohpaqm2n/dvTGrFlSgIpoZYH8RVt5cWo= -github.com/multiversx/mx-chain-core-go v1.4.1/go.mod h1:IO+vspNan+gT0WOHnJ95uvWygiziHZvfXpff6KnxV7g= -github.com/multiversx/mx-chain-crypto-go v1.3.0 h1:0eK2bkDOMi8VbSPrB1/vGJSYT81IBtfL4zw+C4sWe/k= -github.com/multiversx/mx-chain-crypto-go v1.3.0/go.mod h1:nPIkxxzyTP8IquWKds+22Q2OJ9W7LtusC7cAosz7ojM= -github.com/multiversx/mx-chain-es-indexer-go v1.9.2 h1:/K/cpTkwlFJ7zOD8VRhgc6ixi1t/3ua8CLl63LWHjvE= -github.com/multiversx/mx-chain-es-indexer-go v1.9.2/go.mod h1:t1rkD2vHXSI4EClig0h7+kRCSUCRrMF+emr4DHxFtfA= +github.com/multiversx/mx-chain-communication-go v1.3.1 h1:rJj4FOTqacD+yaAfz61FoEtwpAYmOQFyLEHdy1YZya4= +github.com/multiversx/mx-chain-communication-go v1.3.1/go.mod h1:gDVWn6zUW6aCN1YOm/FbbT5MUmhgn/L1Rmpl8EoH3Yg= +github.com/multiversx/mx-chain-core-go v1.4.2-0.20260219091525-015123fd1603 h1:PhvZUz2zHo3cjx/zySG02enKgvY4r0Vy39l0FKxc+D4= +github.com/multiversx/mx-chain-core-go v1.4.2-0.20260219091525-015123fd1603/go.mod h1:IO+vspNan+gT0WOHnJ95uvWygiziHZvfXpff6KnxV7g= +github.com/multiversx/mx-chain-crypto-go v1.3.1-0.20260130144701-dfa5fd3ea5d7 h1:3cJf1poYPhurIenMd3GYCEh0npaEchodVzcdmHxJrY4= +github.com/multiversx/mx-chain-crypto-go v1.3.1-0.20260130144701-dfa5fd3ea5d7/go.mod h1:nPIkxxzyTP8IquWKds+22Q2OJ9W7LtusC7cAosz7ojM= +github.com/multiversx/mx-chain-es-indexer-go v1.9.4-0.20260219114236-37624897dc3a h1:4ExtYUKB8Hd+IlvxscVpAHjO6hMSYlL2yFfsKBUAzgY= +github.com/multiversx/mx-chain-es-indexer-go v1.9.4-0.20260219114236-37624897dc3a/go.mod h1:aPHgLkELJWxCDXBFF9+tbYaXJH2GIRk723YOaTejYWc= github.com/multiversx/mx-chain-logger-go v1.1.0 h1:97x84A6L4RfCa6YOx1HpAFxZp1cf/WI0Qh112whgZNM= github.com/multiversx/mx-chain-logger-go v1.1.0/go.mod h1:K9XgiohLwOsNACETMNL0LItJMREuEvTH6NsoXWXWg7g= github.com/multiversx/mx-chain-scenario-go v1.6.0 h1:cwDFuS1pSc4YXnfiKKDTEb+QDY4fulPQaiRgIebnKxI= github.com/multiversx/mx-chain-scenario-go v1.6.0/go.mod h1:GrSYu1SnMvsIm9djUz1X13224HcvdY6Nb5KHNT3xZPA= github.com/multiversx/mx-chain-storage-go v1.1.0 h1:M1Y9DqMrJ62s7Zw31+cyuqsnPIvlG4jLBJl5WzeZLe8= github.com/multiversx/mx-chain-storage-go v1.1.0/go.mod h1:o6Jm7cjfPmcc6XpyihYWrd6sx3sgqwurrunw3ZrfyxI= -github.com/multiversx/mx-chain-vm-common-go v1.6.0 h1:M2zmf/ptEINciWxYCPLIkwOMTvvzWjELYYB+0MMQ5Gw= -github.com/multiversx/mx-chain-vm-common-go v1.6.0/go.mod h1:Lc7r4VDPYRDS0CVIaWAoLtf3YQn6PZEYHv4QtaOE2Z0= +github.com/multiversx/mx-chain-vm-common-go v1.6.1-0.20251127112501-0b5f28e33b2e h1:3gboKT4hPEb9ZkAYO2Z/y3sOaUBzxVMN4FhyLFcRSHI= +github.com/multiversx/mx-chain-vm-common-go v1.6.1-0.20251127112501-0b5f28e33b2e/go.mod h1:Lc7r4VDPYRDS0CVIaWAoLtf3YQn6PZEYHv4QtaOE2Z0= github.com/multiversx/mx-chain-vm-go v1.5.43 h1:bMC6aAv0T8BFDPQlc0kSnZtv413NOlF+le5zluMmNQY= github.com/multiversx/mx-chain-vm-go v1.5.43/go.mod h1:Qc2Sckw+EfQwnapkzghFfhuUAOGv29oSZgvj8LJ+xWQ= github.com/multiversx/mx-chain-vm-v1_2-go v1.2.69 h1:5gSR3IMw1mcp/v5oO+vZ5YOyWO8w7O2qKhCKNPwsWNE= @@ -697,8 +697,8 @@ golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc= golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= @@ -761,8 +761,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -824,8 +824,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= diff --git a/integrationTests/chainSimulator/mempool/benchmarks_test.go b/integrationTests/chainSimulator/mempool/benchmarks_test.go new file mode 100644 index 00000000000..9de1d7c4809 --- /dev/null +++ b/integrationTests/chainSimulator/mempool/benchmarks_test.go @@ -0,0 +1,539 @@ +package mempool + +import ( + "fmt" + "io" + "math" + "os" + "runtime/pprof" + "strings" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/txcache" + "github.com/stretchr/testify/require" +) + +// disabledCloser defines a mock for io.Closer +type disabledCloser struct { +} + +// Close - +func (d disabledCloser) Close() error { + return nil +} + +var ( + transferredValue = 1 + configSourceMe = txcache.ConfigSourceMe{ + Name: "test", + NumChunks: 16, + NumBytesThreshold: maxNumBytesUpperBound, + NumBytesPerSenderThreshold: maxNumBytesPerSenderUpperBoundTest, + CountThreshold: math.MaxUint32, + CountPerSenderThreshold: math.MaxUint32, + EvictionEnabled: false, + NumItemsToPreemptivelyEvict: 1, + TxCacheBoundsConfig: config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: maxNumBytesPerSenderUpperBoundTest, + MaxTrackedBlocks: maxTrackedBlocks, + }, + } +) + +func setUpPprofFilesIfNecessary() (func(t *testing.T) io.Closer, func(t *testing.T, closable io.Closer)) { + if !shouldDoProfiling() { + return disabledCreatePprofFiles, disabledStopProfiling + } + + return createPprofFiles, stopProfiling +} + +func shouldDoProfiling() bool { + envPprof := os.Getenv("PPROF") + shouldCreatePprofFiles := envPprof == "1" + + return shouldCreatePprofFiles +} + +func createPprofFiles(t *testing.T) io.Closer { + pprofDir := "./pprof" + err := os.MkdirAll(pprofDir, os.ModePerm) + require.Nil(t, err) + + testName := strings.ReplaceAll(t.Name(), "/", "_") + fileName := fmt.Sprintf("%s/%s.pprof", pprofDir, testName) + + f, err := os.Create(fileName) + require.NoError(t, err) + + err = pprof.StartCPUProfile(f) + require.NoError(t, err) + + return f +} + +// disabledCreatePprofFiles defines a disabled method for createPprofFiles +func disabledCreatePprofFiles(t *testing.T) io.Closer { + return &disabledCloser{} +} + +func stopProfiling(t *testing.T, f io.Closer) { + pprof.StopCPUProfile() + err := f.Close() + require.NoError(t, err) +} + +// disabledStopProfiling defines a disabled method for stopProfiling +func disabledStopProfiling(t *testing.T, f io.Closer) { +} + +// benchmark for the creation of breadcrumbs (which are created with each proposed block) +func TestBenchmark_OnProposedWithManyTxsAndSenders(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + sw := core.NewStopWatch() + startPprof, stopPprof := setUpPprofFilesIfNecessary() + + t.Run("30_000 txs with 10_000 addresses", func(t *testing.T) { + numTxs := 30_000 + numAddresses := 10_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testOnProposed(t, sw, numTxs, numAddresses) + }) + + t.Run("30_000 txs with 1000 addresses", func(t *testing.T) { + numTxs := 30_000 + numAddresses := 1000 + + f := startPprof(t) + defer stopPprof(t, f) + + testOnProposed(t, sw, numTxs, numAddresses) + }) + + t.Run("30_000 txs with 100 addresses", func(t *testing.T) { + numTxs := 30_000 + numAddresses := 100 + + f := startPprof(t) + defer stopPprof(t, f) + + testOnProposed(t, sw, numTxs, numAddresses) + }) + + t.Run("30_000 txs with 10 addresses", func(t *testing.T) { + numTxs := 30_000 + numAddresses := 10 + + f := startPprof(t) + defer stopPprof(t, f) + + testOnProposed(t, sw, numTxs, numAddresses) + }) + + t.Run("60_000 txs with 10_000 addresses", func(t *testing.T) { + numTxs := 60_000 + numAddresses := 10_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testOnProposed(t, sw, numTxs, numAddresses) + }) + + t.Run("60_000 txs with 1000 addresses", func(t *testing.T) { + numTxs := 60_000 + numAddresses := 1000 + + f := startPprof(t) + defer stopPprof(t, f) + + testOnProposed(t, sw, numTxs, numAddresses) + }) + + t.Run("60_000 txs with 100 addresses", func(t *testing.T) { + numTxs := 60_000 + numAddresses := 100 + + f := startPprof(t) + defer stopPprof(t, f) + + testOnProposed(t, sw, numTxs, numAddresses) + }) + + t.Run("60_000 txs with 10 addresses", func(t *testing.T) { + numTxs := 60_000 + numAddresses := 10 + + f := startPprof(t) + defer stopPprof(t, f) + + testOnProposed(t, sw, numTxs, numAddresses) + }) + + for name, measurement := range sw.GetMeasurementsMap() { + fmt.Printf("%fs (%s)\n", measurement, name) + } +} + +// benchmark for the selection of txs +func TestBenchmark_FirstSelectionWithManyTxsAndSenders(t *testing.T) { + sw := core.NewStopWatch() + if testing.Short() { + t.Skip("this is not a short test") + } + startPprof, stopPprof := setUpPprofFilesIfNecessary() + + t.Run("15_000 txs with 10_000 addresses", func(t *testing.T) { + numTxs := 30_000 + numTxsToBeSelected := numTxs / 2 + numAddresses := 10_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testFirstSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + t.Run("30_000 txs with 10_000 addresses", func(t *testing.T) { + numTxs := 60_000 + numTxsToBeSelected := numTxs / 2 + numAddresses := 10_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testFirstSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + t.Run("60_000 txs with 10_000 addresses", func(t *testing.T) { + numTxs := 120_000 + numTxsToBeSelected := numTxs / 2 + numAddresses := 10_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testFirstSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + t.Run("90_000 txs with 10_000 addresses", func(t *testing.T) { + numTxs := 180_000 + numTxsToBeSelected := numTxs / 2 + numAddresses := 10_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testFirstSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + t.Run("100_000 txs with 10_000 addresses", func(t *testing.T) { + numTxs := 200_000 + numTxsToBeSelected := numTxs / 2 + numAddresses := 10_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testFirstSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + t.Run("1_000_000 txs with 10_000 addresses", func(t *testing.T) { + numTxs := 2_000_000 + numTxsToBeSelected := numTxs / 2 + numAddresses := 10_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testFirstSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + for name, measurement := range sw.GetMeasurementsMap() { + fmt.Printf("%fs (%s)\n", measurement, name) + } +} + +// benchmark for the selection of txs +func TestBenchmark_SecondSelectionWithManyTxsAndSenders(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + sw := core.NewStopWatch() + startPprof, stopPprof := setUpPprofFilesIfNecessary() + + t.Run("15_000 txs with 10_000 addresses", func(t *testing.T) { + numTxs := 30_000 + numTxsToBeSelected := numTxs / 2 + numAddresses := 10_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testSecondSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + t.Run("30_000 txs with 10_000 addresses", func(t *testing.T) { + numTxs := 60_000 + numTxsToBeSelected := numTxs / 2 + numAddresses := 10_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testSecondSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + t.Run("60_000 txs with 10_000 addresses", func(t *testing.T) { + numTxs := 120_000 + numTxsToBeSelected := numTxs / 2 + numAddresses := 10_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testSecondSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + t.Run("90_000 txs with 10_000 addresses", func(t *testing.T) { + numTxs := 180_000 + numTxsToBeSelected := numTxs / 2 + numAddresses := 10_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testSecondSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + t.Run("100_000 txs with 10_000 addresses", func(t *testing.T) { + numTxs := 200_000 + numTxsToBeSelected := numTxs / 2 + numAddresses := 10_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testSecondSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + t.Run("1_000_000 txs with 10_000 addresses", func(t *testing.T) { + numTxs := 2_000_000 + numTxsToBeSelected := numTxs / 2 + numAddresses := 10_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testSecondSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + for name, measurement := range sw.GetMeasurementsMap() { + fmt.Printf("%fs (%s)\n", measurement, name) + } +} + +func TestBenchmark_FirstSelectionOf10kTransactionsAndVariableNumberOfAddresses(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + sw := core.NewStopWatch() + startPprof, stopPprof := setUpPprofFilesIfNecessary() + + numTxs := 20_000 + numTxsToBeSelected := numTxs / 2 + + t.Run("10_000 txs out of 20_000 in pool with 10 addresses", func(t *testing.T) { + numAddresses := 10 + + f := startPprof(t) + defer stopPprof(t, f) + + testFirstSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + t.Run("10_000 txs out of 20_000 in pool with 100 addresses", func(t *testing.T) { + numAddresses := 100 + + f := startPprof(t) + defer stopPprof(t, f) + + testFirstSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + t.Run("10_000 txs out of 20_000 in pool with 1000 addresses", func(t *testing.T) { + numAddresses := 1000 + + f := startPprof(t) + defer stopPprof(t, f) + + testFirstSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + t.Run("10_000 txs out of 20_000 in pool with 10_000 addresses", func(t *testing.T) { + numAddresses := 10_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testFirstSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + t.Run("10_000 txs out of 20_000 in pool with 100_000 addresses", func(t *testing.T) { + numAddresses := 100_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testFirstSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + for name, measurement := range sw.GetMeasurementsMap() { + fmt.Printf("%fs (%s)\n", measurement, name) + } +} + +func TestBenchmark_SecondSelection10kTransactionsAndVariableNumberOfAddresses(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + sw := core.NewStopWatch() + numTxs := 20_000 + numTxsToBeSelected := numTxs / 2 + startPprof, stopPprof := setUpPprofFilesIfNecessary() + + t.Run("10_000 txs with 10 addresses", func(t *testing.T) { + numAddresses := 10 + + f := startPprof(t) + defer stopPprof(t, f) + + testSecondSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + t.Run("10_000 txs with 100 addresses", func(t *testing.T) { + numAddresses := 100 + + f := startPprof(t) + defer stopPprof(t, f) + + testSecondSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + t.Run("10_000 txs with 1000 addresses", func(t *testing.T) { + numAddresses := 1000 + + f := startPprof(t) + defer stopPprof(t, f) + + testSecondSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + t.Run("10_000 txs with 10_000 addresses", func(t *testing.T) { + numAddresses := 10_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testSecondSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + t.Run("10_000 txs with 100_000 addresses", func(t *testing.T) { + numAddresses := 100_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testSecondSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + for name, measurement := range sw.GetMeasurementsMap() { + fmt.Printf("%fs (%s)\n", measurement, name) + } +} + +// worst case scenario: 100k addresses +func TestBenchmark_FirstSelection10KTransactionAndVariableNumOfTxsInPool(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + sw := core.NewStopWatch() + numTxsToBeSelected := 10_000 + startPprof, stopPprof := setUpPprofFilesIfNecessary() + + t.Run("10_000 txs out of 10_000 in pool with 100_000 addresses", func(t *testing.T) { + numTxs := 10_000 + numAddresses := 100_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testFirstSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + t.Run("10_000 txs out of 100_000 in pool with 100_000 addresses", func(t *testing.T) { + numTxs := 100_000 + numAddresses := 100_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testFirstSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + t.Run("10_000 txs out of 1_000_000 in pool with 100_000 addresses", func(t *testing.T) { + numTxs := 1_000_000 + numAddresses := 100_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testFirstSelection(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + for name, measurement := range sw.GetMeasurementsMap() { + fmt.Printf("%fs (%s)\n", measurement, name) + } +} + +func TestBenchmark_SecondSelection10KTransactionAndVariableNumOfTxsInPool(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + sw := core.NewStopWatch() + startPprof, stopPprof := setUpPprofFilesIfNecessary() + + numTxsToBeSelected := 10_000 + + t.Run("10_000 txs out of 100_000 in pool with 100_000 addresses", func(t *testing.T) { + numTxs := 100_000 + numAddresses := 100_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testSecondSelectionWithManyTxsInPool(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + t.Run("10_000 txs out of 1_000_000 in pool with 100_000 addresses", func(t *testing.T) { + numTxs := 1_000_000 + numAddresses := 100_000 + + f := startPprof(t) + defer stopPprof(t, f) + + testSecondSelectionWithManyTxsInPool(t, sw, numTxs, numTxsToBeSelected, numAddresses) + }) + + for name, measurement := range sw.GetMeasurementsMap() { + fmt.Printf("%fs (%s)\n", measurement, name) + } +} diff --git a/integrationTests/chainSimulator/mempool/mempool_test.go b/integrationTests/chainSimulator/mempool/mempool_test.go index af2518e841e..f6d22147c5f 100644 --- a/integrationTests/chainSimulator/mempool/mempool_test.go +++ b/integrationTests/chainSimulator/mempool/mempool_test.go @@ -1,13 +1,25 @@ package mempool import ( + "encoding/hex" + "fmt" + "math" "math/big" "testing" "time" + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/api" + "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common/holders" + stateMock "github.com/multiversx/mx-chain-go/testscommon/state" + "github.com/multiversx/mx-chain-go/testscommon/txcachemocks" + "github.com/multiversx/mx-chain-go/txcache" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/node/chainSimulator/configs" "github.com/multiversx/mx-chain-go/storage" @@ -69,6 +81,7 @@ func TestMempoolWithChainSimulator_Selection(t *testing.T) { require.Equal(t, 50_000*(30_000-27_756), int(gas)) } +// TODO should activate Supernova for this test func TestMempoolWithChainSimulator_Selection_WhenUsersHaveZeroBalance_WithRelayedV3(t *testing.T) { if testing.Short() { t.Skip("this is not a short test") @@ -76,7 +89,19 @@ func TestMempoolWithChainSimulator_Selection_WhenUsersHaveZeroBalance_WithRelaye shard := 0 - simulator := startChainSimulator(t, func(cfg *config.Configs) {}) + alterConfigsFunc := func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.FixRelayedBaseCostEnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.RelayedTransactionsV3EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.RelayedTransactionsV3FixESDTTransferEnableEpoch = 2 + cfg.RoundConfig.RoundActivations = map[string]config.ActivationRoundByName{ + "DisableAsyncCallV1": { + Round: "9999999", + }, + } + } + + simulator := startChainSimulator(t, alterConfigsFunc) defer simulator.Close() err := simulator.GenerateBlocksUntilEpochIsReached(2) @@ -137,16 +162,13 @@ func TestMempoolWithChainSimulator_Selection_WhenUsersHaveZeroBalance_WithRelaye require.Equal(t, 2, getNumTransactionsInPool(simulator, shard)) selectedTransactions, _ := selectTransactions(t, simulator, shard) - require.Equal(t, 2, len(selectedTransactions)) - require.Equal(t, alice.Bytes, selectedTransactions[0].Tx.GetSndAddr()) - require.Equal(t, bob.Bytes, selectedTransactions[1].Tx.GetSndAddr()) + require.Equal(t, 1, len(selectedTransactions)) + require.Equal(t, bob.Bytes, selectedTransactions[0].Tx.GetSndAddr()) err = simulator.GenerateBlocks(1) require.Nil(t, err) - require.Equal(t, 2, getNumTransactionsInCurrentBlock(simulator, shard)) - - require.Equal(t, "invalid", getTransaction(t, simulator, shard, selectedTransactions[0].TxHash).Status.String()) - require.Equal(t, "success", getTransaction(t, simulator, shard, selectedTransactions[1].TxHash).Status.String()) + require.Equal(t, 1, getNumTransactionsInCurrentBlock(simulator, shard)) + require.Equal(t, "success", getTransaction(t, simulator, shard, selectedTransactions[0].TxHash).Status.String()) } func TestMempoolWithChainSimulator_Selection_WhenInsufficientBalanceForFee_WithRelayedV3(t *testing.T) { @@ -251,6 +273,153 @@ func TestMempoolWithChainSimulator_Selection_WhenInsufficientBalanceForFee_WithR require.Equal(t, bob.Bytes, selectedTransactions[2].Tx.GetSndAddr()) } +func TestMempoolWithChainSimulator_Selection_WhenInitialGap(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + numSenders := 2 + shard := 0 + + simulator := startChainSimulator(t, func(cfg *config.Configs) {}) + defer simulator.Close() + + err := simulator.GenerateBlocksUntilEpochIsReached(2) + require.NoError(t, err) + + participants := createParticipants(t, simulator, numSenders) + noncesTracker := newNoncesTracker() + + alice := participants.sendersByShard[shard][0] + bob := participants.sendersByShard[shard][1] + relayer := participants.relayerByShard[shard] + receiver := participants.receiverByShard[shard] + + transactions := make([]*transaction.Transaction, 0) + + // Transfer from Alice (relayed) with wrong nonce + transactions = append(transactions, &transaction.Transaction{ + Nonce: 1, + Value: oneQuarterOfEGLD, + SndAddr: alice.Bytes, + RcvAddr: receiver.Bytes, + RelayerAddr: relayer.Bytes, + Data: []byte{}, + GasLimit: 100_000, + GasPrice: 1_000_000_003, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + RelayerSignature: []byte("signature"), + }) + + // Transfer from Bob (relayed) + transactions = append(transactions, &transaction.Transaction{ + Nonce: noncesTracker.getThenIncrementNonce(bob), + Value: oneQuarterOfEGLD, + SndAddr: bob.Bytes, + RcvAddr: receiver.Bytes, + RelayerAddr: relayer.Bytes, + Data: []byte{}, + GasLimit: 100_000, + GasPrice: 1_000_000_002, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + RelayerSignature: []byte("signature"), + }) + + sendTransactions(t, simulator, transactions) + time.Sleep(durationWaitAfterSendSome) + require.Equal(t, 2, getNumTransactionsInPool(simulator, shard)) + + selectedTransactions, _ := selectTransactions(t, simulator, shard) + require.Equal(t, 1, len(selectedTransactions)) + require.Equal(t, bob.Bytes, selectedTransactions[0].Tx.GetSndAddr()) +} + +func TestMempoolWithChainSimulator_Selection_WhenMiddleGap(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + numSenders := 2 + shard := 0 + + simulator := startChainSimulator(t, func(cfg *config.Configs) {}) + defer simulator.Close() + + err := simulator.GenerateBlocksUntilEpochIsReached(2) + require.NoError(t, err) + + participants := createParticipants(t, simulator, numSenders) + noncesTracker := newNoncesTracker() + + alice := participants.sendersByShard[shard][0] + bob := participants.sendersByShard[shard][1] + relayer := participants.relayerByShard[shard] + receiver := participants.receiverByShard[shard] + + transactions := make([]*transaction.Transaction, 0) + + // Transfer from Alice (relayed) + transactions = append(transactions, &transaction.Transaction{ + Nonce: noncesTracker.getThenIncrementNonce(alice), + Value: oneQuarterOfEGLD, + SndAddr: alice.Bytes, + RcvAddr: receiver.Bytes, + RelayerAddr: relayer.Bytes, + Data: []byte{}, + GasLimit: 100_000, + GasPrice: 1_000_000_003, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + RelayerSignature: []byte("signature"), + }) + + // Another transfer from Alice (relayed) which is not following the last nonce + transactions = append(transactions, &transaction.Transaction{ + Nonce: 100, + Value: oneQuarterOfEGLD, + SndAddr: alice.Bytes, + RcvAddr: receiver.Bytes, + RelayerAddr: relayer.Bytes, + Data: []byte{}, + GasLimit: 100_000, + GasPrice: 1_000_000_003, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + RelayerSignature: []byte("signature"), + }) + + // Transfer from Bob (relayed) + transactions = append(transactions, &transaction.Transaction{ + Nonce: noncesTracker.getThenIncrementNonce(bob), + Value: oneQuarterOfEGLD, + SndAddr: bob.Bytes, + RcvAddr: receiver.Bytes, + RelayerAddr: relayer.Bytes, + Data: []byte{}, + GasLimit: 100_000, + GasPrice: 1_000_000_002, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + RelayerSignature: []byte("signature"), + }) + + sendTransactions(t, simulator, transactions) + time.Sleep(durationWaitAfterSendSome) + require.Equal(t, 3, getNumTransactionsInPool(simulator, shard)) + + selectedTransactions, _ := selectTransactions(t, simulator, shard) + require.Equal(t, 2, len(selectedTransactions)) + require.Equal(t, alice.Bytes, selectedTransactions[0].Tx.GetSndAddr()) + require.Equal(t, bob.Bytes, selectedTransactions[1].Tx.GetSndAddr()) +} + func TestMempoolWithChainSimulator_Eviction(t *testing.T) { if testing.Short() { t.Skip("this is not a short test") @@ -313,7 +482,7 @@ func TestMempoolWithChainSimulator_Eviction(t *testing.T) { // Send one more transaction to trigger eviction sendTransaction(t, simulator, &transaction.Transaction{ - Nonce: 42, + Nonce: 43, Value: oneEGLD, SndAddr: participants.sendersByShard[shard][7].Bytes, RcvAddr: participants.receiverByShard[shard].Bytes, @@ -326,8 +495,2403 @@ func TestMempoolWithChainSimulator_Eviction(t *testing.T) { }) // Allow the eviction to complete (even if it's quite fast). - time.Sleep(3 * time.Second) + time.Sleep(5 * time.Second) expectedNumTransactionsInPool := 300_000 + 1 + 1 - int(storage.TxPoolSourceMeNumItemsToPreemptivelyEvict) require.Equal(t, expectedNumTransactionsInPool, getNumTransactionsInPool(simulator, shard)) } + +func Test_Selection_ShouldNotSelectSameTransactionsWithSameSender(t *testing.T) { + t.Parallel() + + host := txcachemocks.NewMempoolHostMock() + txpool, err := txcache.NewTxCache(configSourceMe, host, 0) + + require.Nil(t, err) + require.NotNil(t, txpool) + + // create the non-virtual selection session, assure we have enough balance + accounts := map[string]*stateMock.UserAccountStub{ + "alice": { + Balance: oneEGLD, + Nonce: 0, + }, + "receiver": { + Balance: big.NewInt(0), + Nonce: 0, + }, + } + + selectionSession := txcachemocks.NewSelectionSessionMockWithAccounts(accounts) + selectionSession.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMockWithAccounts(accounts) + accountsProvider.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + err = txpool.OnExecutedBlock(&block.Header{}, []byte(testRootHash)) + require.Nil(t, err) + + maxNumTxs := 2 + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + maxNumTxs, + 10, + haveTimeTrue, + ) + + numPoolTxs := maxNumTxs * 2 + txHashes := make([][]byte, 0, numPoolTxs) + + nonceTracker := newNoncesTracker() + for i := 0; i < numPoolTxs; i++ { + tx := &transaction.Transaction{ + Nonce: nonceTracker.getThenIncrementNonceByStringAddress("alice"), + Value: oneQuarterOfEGLD, + SndAddr: []byte("alice"), + RcvAddr: []byte("receiver"), + GasLimit: 50_000, + GasPrice: 1_000_000_000, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + } + + txHash := []byte(fmt.Sprintf("txHash%d", i)) + txpool.AddTx(&txcache.WrappedTransaction{ + Tx: tx, + TxHash: txHash, + Fee: big.NewInt(int64(tx.GasLimit * tx.GasPrice)), + TransferredValue: tx.Value, + FeePayer: tx.SndAddr, + }) + + txHashes = append(txHashes, txHash) + } + + blockBody := block.Body{MiniBlocks: []*block.MiniBlock{ + { + TxHashes: txHashes[:len(txHashes)/2], + }, + }} + + require.Equal(t, txpool.CountTx(), uint64(4)) + + // do the first selection, first two txs should be returned + selectedTransactions, _, err := txpool.SelectTransactions(selectionSession, options, 0) + require.Nil(t, err) + require.Equal(t, 2, len(selectedTransactions)) + require.Equal(t, "txHash0", string(selectedTransactions[0].TxHash)) + require.Equal(t, "txHash1", string(selectedTransactions[1].TxHash)) + + // propose the block + err = txpool.OnProposedBlock([]byte(testBlockHash1), &blockBody, &block.Header{ + Nonce: 1, + PrevHash: []byte(testBlockHash0), + RootHash: []byte(testRootHash), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // do the second selection. should not return same txs + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 2) + require.Nil(t, err) + require.Equal(t, 1, len(selectedTransactions)) + require.Equal(t, "txHash2", string(selectedTransactions[0].TxHash)) +} + +func Test_Selection_ShouldNotSelectSameTransactionsWithDifferentSenders(t *testing.T) { + t.Parallel() + + host := txcachemocks.NewMempoolHostMock() + txpool, err := txcache.NewTxCache(configSourceMe, host, 0) + + require.Nil(t, err) + require.NotNil(t, txpool) + + // assure we have enough balance for each account + accounts := map[string]*stateMock.UserAccountStub{ + "alice": { + Balance: oneEGLD, + Nonce: 0, + }, + "bob": { + Balance: oneEGLD, + Nonce: 0, + }, + "receiver": { + Balance: big.NewInt(0), + Nonce: 0, + }, + } + + selectionSession := txcachemocks.NewSelectionSessionMockWithAccounts(accounts) + selectionSession.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMockWithAccounts(accounts) + accountsProvider.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + err = txpool.OnExecutedBlock(&block.Header{}, []byte(testRootHash)) + require.Nil(t, err) + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + 2, + 10, + haveTimeTrue, + ) + + nonceTracker := newNoncesTracker() + transactions := make([]*transaction.Transaction, 0) + + transactions = append(transactions, &transaction.Transaction{ + Nonce: nonceTracker.getThenIncrementNonceByStringAddress("alice"), + Value: oneQuarterOfEGLD, + SndAddr: []byte("alice"), + RcvAddr: []byte("receiver"), + GasLimit: 50_000, + GasPrice: 1_000_000_000, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + }) + + transactions = append(transactions, &transaction.Transaction{ + Nonce: nonceTracker.getThenIncrementNonceByStringAddress("bob"), + Value: oneQuarterOfEGLD, + SndAddr: []byte("bob"), + RcvAddr: []byte("receiver"), + GasLimit: 50_000, + GasPrice: 1_000_000_000, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + }) + + transactions = append(transactions, &transaction.Transaction{ + Nonce: nonceTracker.getThenIncrementNonceByStringAddress("alice"), + Value: oneQuarterOfEGLD, + SndAddr: []byte("alice"), + RcvAddr: []byte("receiver"), + GasLimit: 50_000, + GasPrice: 1_000_000_000, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + }) + + transactions = append(transactions, &transaction.Transaction{ + Nonce: nonceTracker.getThenIncrementNonceByStringAddress("bob"), + Value: oneQuarterOfEGLD, + SndAddr: []byte("bob"), + RcvAddr: []byte("receiver"), + GasLimit: 50_000, + GasPrice: 1_000_000_000, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + }) + + txHashes := make([][]byte, 0) + for i, tx := range transactions { + txHash := []byte(fmt.Sprintf("txHash%d", i)) + txHashes = append(txHashes, txHash) + txpool.AddTx(&txcache.WrappedTransaction{ + Tx: tx, + TxHash: txHash, + Fee: big.NewInt(int64(tx.GasLimit * tx.GasPrice)), + TransferredValue: tx.Value, + FeePayer: tx.SndAddr, + }) + } + + blockBody := block.Body{MiniBlocks: []*block.MiniBlock{ + { + TxHashes: txHashes[:len(txHashes)/2], + }, + }} + + require.Equal(t, txpool.CountTx(), uint64(4)) + + selectedTransactions, _, err := txpool.SelectTransactions(selectionSession, options, 0) + require.Nil(t, err) + require.Equal(t, 2, len(selectedTransactions)) + require.Equal(t, "txHash0", string(selectedTransactions[0].TxHash)) + require.Equal(t, "txHash1", string(selectedTransactions[1].TxHash)) + + // propose the selected transactions + err = txpool.OnProposedBlock([]byte(testBlockHash1), &blockBody, + &block.Header{ + Nonce: 1, + PrevHash: []byte(testBlockHash0), + RootHash: []byte(testRootHash), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // do the second selection. should not return same txs + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 2) + require.Nil(t, err) + require.Equal(t, 2, len(selectedTransactions)) + require.Equal(t, "txHash2", string(selectedTransactions[0].TxHash)) + require.Equal(t, "txHash3", string(selectedTransactions[1].TxHash)) +} + +func Test_Selection_ShouldNotSelectSameTransactionsWithManyTransactions(t *testing.T) { + t.Parallel() + + host := txcachemocks.NewMempoolHostMock() + txpool, err := txcache.NewTxCache(configSourceMe, host, 0) + + require.Nil(t, err) + require.NotNil(t, txpool) + + numTxsPerSender := 30_000 + initialAmount := big.NewInt(int64(numTxsPerSender) * 50_000 * 1_000_000_000) + + senders := []string{"alice", "bob"} + accounts := map[string]*stateMock.UserAccountStub{ + "alice": { + Balance: initialAmount, + Nonce: 0, + }, + "bob": { + Balance: initialAmount, + Nonce: 0, + }, + "receiver": { + Balance: big.NewInt(0), + Nonce: 0, + }, + } + + selectionSession := txcachemocks.NewSelectionSessionMockWithAccounts(accounts) + selectionSession.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMockWithAccounts(accounts) + accountsProvider.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + err = txpool.OnExecutedBlock(&block.Header{}, []byte(testRootHash)) + require.Nil(t, err) + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + numTxsPerSender, + 10, + haveTimeTrue, + ) + + numTxs := numTxsPerSender * len(senders) + nonceTracker := newNoncesTracker() + + // create numTxs transactions and save them to txpool + addTransactionsToTxPool(txpool, nonceTracker, numTxsPerSender, senders) + require.Equal(t, txpool.CountTx(), uint64(numTxs)) + + // do the first selections + selectedTransactions, _, err := txpool.SelectTransactions(selectionSession, options, 0) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // propose those txs in order to track them (create the breadcrumbs used for the virtual records) + proposedBlock1 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash1), proposedBlock1, &block.Header{ + Nonce: 1, + PrevHash: []byte(testBlockHash0), + RootHash: []byte(testRootHash), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // do the second selection (the rest of the transactions should be selected) + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 2) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // propose the second block + proposedBlock2 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash2), proposedBlock2, + &block.Header{ + Nonce: 2, + PrevHash: []byte(testBlockHash1), + RootHash: []byte(testRootHash), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // do the last selection (no tx should be returned) + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 3) + require.Nil(t, err) + require.Equal(t, 0, len(selectedTransactions)) +} + +func Test_Selection_ProposeEmptyBlocks(t *testing.T) { + t.Parallel() + + host := txcachemocks.NewMempoolHostMock() + txpool, err := txcache.NewTxCache(configSourceMe, host, 0) + + require.Nil(t, err) + require.NotNil(t, txpool) + + numTxsPerSender := 30_000 + initialAmount := big.NewInt(int64(numTxsPerSender) * 50_000 * 1_000_000_000) + + senders := []string{"alice", "bob"} + accounts := map[string]*stateMock.UserAccountStub{ + "alice": { + Balance: initialAmount, + Nonce: 0, + }, + "bob": { + Balance: initialAmount, + Nonce: 0, + }, + "receiver": { + Balance: big.NewInt(0), + Nonce: 0, + }, + } + + selectionSession := txcachemocks.NewSelectionSessionMockWithAccounts(accounts) + selectionSession.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMockWithAccounts(accounts) + accountsProvider.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + err = txpool.OnExecutedBlock(&block.Header{}, []byte(testRootHash)) + require.Nil(t, err) + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + numTxsPerSender, + 10, + haveTimeTrue, + ) + + numTxs := numTxsPerSender * len(senders) + nonceTracker := newNoncesTracker() + + // create numTxs transactions and save them to txpool + addTransactionsToTxPool(txpool, nonceTracker, numTxsPerSender, senders) + require.Equal(t, txpool.CountTx(), uint64(numTxs)) + + // do the first selections + selectedTransactions, _, err := txpool.SelectTransactions(selectionSession, options, 0) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // propose those txs in order to track them (create the breadcrumbs used for the virtual records) + proposedBlock1 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash1), proposedBlock1, &block.Header{ + Nonce: 1, + PrevHash: []byte(testBlockHash0), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // propose some empty blocks + err = txpool.OnProposedBlock([]byte(testBlockHash2), &block.Body{}, + &block.Header{ + Nonce: 2, + PrevHash: []byte(testBlockHash1), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + err = txpool.OnProposedBlock([]byte("blockHash3"), &block.Body{}, + &block.Header{ + Nonce: 3, + PrevHash: []byte(testBlockHash2), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // do the second selection (the rest of the transactions should be selected) + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 4) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // propose the second block + proposedBlock2 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte("blockHash4"), proposedBlock2, + &block.Header{ + Nonce: 4, + PrevHash: []byte("blockHash3"), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // do the last selection (no tx should be returned) + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 5) + require.Nil(t, err) + require.Equal(t, 0, len(selectedTransactions)) +} + +func Test_Selection_ProposeBlocksWithSameNonceToTriggerForkScenarios(t *testing.T) { + t.Parallel() + + t.Run("should work with only one proposed block being replaced", func(t *testing.T) { + host := txcachemocks.NewMempoolHostMock() + txpool, err := txcache.NewTxCache(configSourceMe, host, 0) + + require.Nil(t, err) + require.NotNil(t, txpool) + + numTxsPerSender := 30_000 + initialAmount := big.NewInt(int64(numTxsPerSender) * 50_000 * 1_000_000_000) + + senders := []string{"alice", "bob"} + accounts := map[string]*stateMock.UserAccountStub{ + "alice": { + Balance: initialAmount, + Nonce: 0, + }, + "bob": { + Balance: initialAmount, + Nonce: 0, + }, + "receiver": { + Balance: big.NewInt(0), + Nonce: 0, + }, + } + + selectionSession := txcachemocks.NewSelectionSessionMockWithAccounts(accounts) + selectionSession.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMockWithAccounts(accounts) + accountsProvider.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + err = txpool.OnExecutedBlock(&block.Header{}, []byte(testRootHash)) + require.Nil(t, err) + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + numTxsPerSender, + 10, + haveTimeTrue, + ) + + numTxs := numTxsPerSender * len(senders) + nonceTracker := newNoncesTracker() + + // create numTxs transactions and save them to txpool + addTransactionsToTxPool(txpool, nonceTracker, numTxsPerSender, senders) + require.Equal(t, txpool.CountTx(), uint64(numTxs)) + + // do the first selection + selectedTransactions, _, err := txpool.SelectTransactions(selectionSession, options, 0) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // propose those txs in order to track them (create the breadcrumbs used for the virtual records) + proposedBlock1 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash1), proposedBlock1, &block.Header{ + Nonce: 1, + PrevHash: []byte(testBlockHash0), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // propose an empty block with same nonce as the previous one + err = txpool.OnProposedBlock([]byte(testBlockHash1), &block.Body{}, + &block.Header{ + Nonce: 1, + PrevHash: []byte(testBlockHash0), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // because the first one was replaced, the same transactions should be selected again + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 2) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // propose those txs in order to track them (create the breadcrumbs used for the virtual records) + proposedBlock2 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash2), proposedBlock2, &block.Header{ + Nonce: 2, + PrevHash: []byte(testBlockHash1), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // do the second selection (the rest of the transactions should be selected) + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 3) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // propose the second block + proposedBlock3 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte("blockHash3"), proposedBlock3, + &block.Header{ + Nonce: 3, + PrevHash: []byte(testBlockHash2), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // do the last selection (no tx should be returned) + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 4) + require.Nil(t, err) + require.Equal(t, 0, len(selectedTransactions)) + }) + + t.Run("should work with many proposed blocks being replaced", func(t *testing.T) { + host := txcachemocks.NewMempoolHostMock() + txpool, err := txcache.NewTxCache(configSourceMe, host, 0) + + require.Nil(t, err) + require.NotNil(t, txpool) + + numTxsPerSender := 30_000 + initialAmount := big.NewInt(int64(numTxsPerSender) * 50_000 * 1_000_000_000) + + senders := []string{"alice", "bob", "carol"} + accounts := map[string]*stateMock.UserAccountStub{ + "alice": { + Balance: initialAmount, + Nonce: 0, + }, + "bob": { + Balance: initialAmount, + Nonce: 0, + }, + "carol": { + Balance: initialAmount, + Nonce: 0, + }, + "receiver": { + Balance: big.NewInt(0), + Nonce: 0, + }, + } + + selectionSession := txcachemocks.NewSelectionSessionMockWithAccounts(accounts) + selectionSession.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMockWithAccounts(accounts) + accountsProvider.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + err = txpool.OnExecutedBlock(&block.Header{}, []byte(testRootHash)) + require.Nil(t, err) + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + numTxsPerSender, + 10, + haveTimeTrue, + ) + + numTxs := numTxsPerSender * len(senders) + nonceTracker := newNoncesTracker() + + // create numTxs transactions and save them to txpool + addTransactionsToTxPool(txpool, nonceTracker, numTxsPerSender, senders) + require.Equal(t, txpool.CountTx(), uint64(numTxs)) + + // do the first selection + selectedTransactions, _, err := txpool.SelectTransactions(selectionSession, options, 0) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // propose those txs in order to track them (create the breadcrumbs used for the virtual records) + proposedBlock1 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash1), proposedBlock1, &block.Header{ + Nonce: 1, + PrevHash: []byte(testBlockHash0), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // do the second selection + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 2) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // propose those txs in order to track them (create the breadcrumbs used for the virtual records) + proposedBlock2 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash2), proposedBlock2, &block.Header{ + Nonce: 2, + PrevHash: []byte(testBlockHash1), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // do the third selection (the rest of the transactions should be selected) + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 3) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // propose the third block + proposedBlock3 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte("blockHash3"), proposedBlock3, + &block.Header{ + Nonce: 3, + PrevHash: []byte(testBlockHash2), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // do the last selection (no tx should be returned) + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 4) + require.Nil(t, err) + require.Equal(t, 0, len(selectedTransactions)) + + // now, generate a fork by replacing the block with nonce 2 + err = txpool.OnProposedBlock([]byte("blockHashF1"), &block.Body{}, &block.Header{ + Nonce: 2, + PrevHash: []byte(testBlockHash1), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // because the block with nonce 2 was replaced, we expect to still have two non-empty selections + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 3) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // propose this block + proposedBlock4 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte("blockHashF2"), proposedBlock4, + &block.Header{ + Nonce: 3, + PrevHash: []byte("blockHashF1"), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // expect one more non-empty selection + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 4) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // propose this block + proposedBlock5 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte("blockHashF3"), proposedBlock5, + &block.Header{ + Nonce: 4, + PrevHash: []byte("blockHashF2"), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // no txs should be returned for the last selection + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 5) + require.Nil(t, err) + require.Equal(t, 0, len(selectedTransactions)) + }) +} + +func Test_Selection_ShouldNotSelectSameTransactionsWithManyTransactionsAndExecutedBlockNotification(t *testing.T) { + t.Parallel() + + host := txcachemocks.NewMempoolHostMock() + txpool, err := txcache.NewTxCache(configSourceMe, host, 0) + + require.Nil(t, err) + require.NotNil(t, txpool) + + // set the number of transactions that we want for each sender + numTxsPerSender := 60_000 + // assure that we have enough balance for fees + initialAmount := big.NewInt(int64(numTxsPerSender) * 50_000 * 1_000_000_000) + + senders := []string{"alice"} + accounts := map[string]*stateMock.UserAccountStub{ + "alice": { + Balance: initialAmount, + Nonce: 0, + }, + "receiver": { + Balance: big.NewInt(0), + Nonce: 0, + }, + } + + selectionSession := txcachemocks.NewSelectionSessionMockWithAccounts(accounts) + selectionSession.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMockWithAccounts(accounts) + accountsProvider.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + err = txpool.OnExecutedBlock(&block.Header{}, []byte(testRootHash)) + require.Nil(t, err) + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + 30_000, + 10, + haveTimeTrue, + ) + + nonceTracker := newNoncesTracker() + + // create txs and add them to txpool + numTxs := numTxsPerSender * len(senders) + addTransactionsToTxPool(txpool, nonceTracker, numTxsPerSender, senders) + require.Equal(t, txpool.CountTx(), uint64(numTxs)) + + // do the first selection + selectedTransactions, _, err := txpool.SelectTransactions(selectionSession, options, 0) + require.Nil(t, err) + require.Equal(t, 30_000, len(selectedTransactions)) + + // propose those txs in order to track them (create the breadcrumbs used for the virtual records) + proposedBlock1 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash1), proposedBlock1, + &block.Header{ + Nonce: 1, + PrevHash: []byte(testBlockHash0), + RootHash: []byte(testRootHash), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // do the second selection (the rest of the transactions should be selected) + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 2) + require.Nil(t, err) + require.Equal(t, 30_000, len(selectedTransactions)) + + // execute the first proposed block + err = txpool.OnExecutedBlock(&block.Header{ + Nonce: 1, + PrevHash: []byte(testBlockHash0), + }, []byte(fmt.Sprintf("rootHash%d", 1))) + require.Nil(t, err) + + // remove the executed txs from the pool + for _, tx := range proposedBlock1.MiniBlocks[0].TxHashes { + require.True(t, txpool.RemoveTxByHash(tx)) + } + + // update the state of the account on the blockchain + selectionSession.SetNonce([]byte("alice"), 30_000) + selectionSession.GetRootHashCalled = func() ([]byte, error) { + return []byte("rootHash1"), nil + } + accountsProvider.GetRootHashCalled = func() ([]byte, error) { + return []byte("rootHash1"), nil + } + + // propose the second block + proposedBlock2 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash2), proposedBlock2, + &block.Header{ + Nonce: 2, + PrevHash: []byte(testBlockHash1), + RootHash: []byte(fmt.Sprintf("rootHash%d", 1)), + }, + accountsProvider, + []byte(testBlockHash1), + ) + require.Nil(t, err) + + // the currentNonce should represent here the nonce of the block for which the selection is built + // no transactions should be returned + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 3) + require.Nil(t, err) + require.Equal(t, 0, len(selectedTransactions)) + + for _, tx := range proposedBlock2.MiniBlocks[0].TxHashes { + require.True(t, txpool.RemoveTxByHash(tx)) + } +} + +func Test_Selection_ProposeEmptyBlocksAndExecutedBlockNotification(t *testing.T) { + t.Parallel() + + host := txcachemocks.NewMempoolHostMock() + txpool, err := txcache.NewTxCache(configSourceMe, host, 0) + + require.Nil(t, err) + require.NotNil(t, txpool) + + // set the number of transactions that we want for each sender + numTxsPerSender := 60_000 + // assure that we have enough balance for fees + initialAmount := big.NewInt(int64(numTxsPerSender) * 50_000 * 1_000_000_000) + + // mock the non-virtual selection session + senders := []string{"alice"} + accounts := map[string]*stateMock.UserAccountStub{ + "alice": { + Balance: initialAmount, + Nonce: 0, + }, + } + + selectionSession := txcachemocks.NewSelectionSessionMockWithAccounts(accounts) + selectionSession.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMockWithAccounts(accounts) + accountsProvider.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + err = txpool.OnExecutedBlock(&block.Header{}, []byte(testRootHash)) + require.Nil(t, err) + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + 30_000, + 10, + haveTimeTrue, + ) + + nonceTracker := newNoncesTracker() + numTxs := numTxsPerSender * len(senders) + + // create txs and add them to txpool + addTransactionsToTxPool(txpool, nonceTracker, numTxsPerSender, senders) + require.Equal(t, txpool.CountTx(), uint64(numTxs)) + + // do the first selection + selectedTransactions, _, err := txpool.SelectTransactions(selectionSession, options, 0) + require.Nil(t, err) + require.Equal(t, 30_000, len(selectedTransactions)) + + // propose those txs in order to track them (create the breadcrumbs used for the virtual records) + proposedBlock1 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash1), proposedBlock1, + &block.Header{ + Nonce: 1, + PrevHash: []byte(testBlockHash0), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // propose empty blocks + err = txpool.OnProposedBlock([]byte(testBlockHash2), &block.Body{}, + &block.Header{ + Nonce: 2, + PrevHash: []byte(testBlockHash1), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + err = txpool.OnProposedBlock([]byte("blockHash3"), &block.Body{}, + &block.Header{ + Nonce: 3, + PrevHash: []byte(testBlockHash2), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // do the second selection (the rest of the transactions should be selected) + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 4) + require.Nil(t, err) + require.Equal(t, 30_000, len(selectedTransactions)) + + // execute the first proposed block + err = txpool.OnExecutedBlock(&block.Header{ + Nonce: 1, + PrevHash: []byte(testBlockHash0), + }, []byte(fmt.Sprintf("rootHash%d", 1))) + require.Nil(t, err) + + // remove the executed txs from the pool + for _, tx := range proposedBlock1.MiniBlocks[0].TxHashes { + require.True(t, txpool.RemoveTxByHash(tx)) + } + + // update the state of the account on the blockchain + selectionSession.SetNonce([]byte("alice"), 30_000) + selectionSession.GetRootHashCalled = func() ([]byte, error) { + return []byte("rootHash1"), nil + } + + // propose the second block + proposedBlock2 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte("blockHash4"), proposedBlock2, + &block.Header{ + Nonce: 4, + PrevHash: []byte("blockHash3"), + RootHash: []byte(fmt.Sprintf("rootHash%d", 1)), + }, + selectionSession, + []byte(testBlockHash1), + ) + require.Nil(t, err) + + // execute the empty proposed blocks + err = txpool.OnExecutedBlock(&block.Header{ + Nonce: 2, + PrevHash: []byte(testBlockHash1), + }, []byte(fmt.Sprintf("rootHash%d", 1))) + require.Nil(t, err) + + // execute the empty proposed blocks + err = txpool.OnExecutedBlock(&block.Header{ + Nonce: 3, + PrevHash: []byte(testBlockHash2), + }, []byte(fmt.Sprintf("rootHash%d", 1))) + require.Nil(t, err) + + selectionSession.GetRootHashCalled = func() ([]byte, error) { + return []byte("rootHash1"), nil + } + + // the currentNonce should represent here the nonce of the block for which the selection is built + // no transactions should be returned + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 5) + require.NoError(t, err) + require.Equal(t, 0, len(selectedTransactions)) + + for _, tx := range proposedBlock2.MiniBlocks[0].TxHashes { + require.True(t, txpool.RemoveTxByHash(tx)) + } +} + +func Test_Selection_WithRemovingProposedBlocks(t *testing.T) { + t.Parallel() + + host := txcachemocks.NewMempoolHostMock() + txpool, err := txcache.NewTxCache(configSourceMe, host, 0) + + require.Nil(t, err) + require.NotNil(t, txpool) + + numTxsPerSender := 30_000 + initialAmount := big.NewInt(int64(numTxsPerSender) * 50_000 * 1_000_000_000) + + senders := []string{"alice", "bob", "carol"} + accounts := map[string]*stateMock.UserAccountStub{ + "alice": { + Balance: initialAmount, + Nonce: 0, + }, + "bob": { + Balance: initialAmount, + Nonce: 0, + }, + "carol": { + Balance: initialAmount, + Nonce: 0, + }, + "receiver": { + Balance: big.NewInt(0), + Nonce: 0, + }, + } + + selectionSession := txcachemocks.NewSelectionSessionMockWithAccounts(accounts) + selectionSession.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMockWithAccounts(accounts) + accountsProvider.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + err = txpool.OnExecutedBlock(&block.Header{}, []byte(testRootHash)) + require.Nil(t, err) + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + numTxsPerSender, + 10, + haveTimeTrue, + ) + + numTxs := numTxsPerSender * len(senders) + nonceTracker := newNoncesTracker() + + // create numTxs transactions and save them to txpool + addTransactionsToTxPool(txpool, nonceTracker, numTxsPerSender, senders) + require.Equal(t, txpool.CountTx(), uint64(numTxs)) + + // do the first selection + selectedTransactions, _, err := txpool.SelectTransactions(selectionSession, options, 0) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // propose those txs in order to track them (create the breadcrumbs used for the virtual records) + proposedBlock1 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash1), proposedBlock1, &block.Header{ + Nonce: 1, + PrevHash: []byte(testBlockHash0), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // do the second selection + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 2) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // propose those txs in order to track them (create the breadcrumbs used for the virtual records) + proposedBlock2 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash2), proposedBlock2, &block.Header{ + Nonce: 2, + PrevHash: []byte(testBlockHash1), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // now, suppose we want to re-select again for the block with nonce 2 + // this means we do not want to use the second proposed block + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 2) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // propose those txs in order to track them + proposedBlock2 = createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte("blockHash3"), proposedBlock2, &block.Header{ + Nonce: 2, + PrevHash: []byte(testBlockHash1), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // now, we should have one more non-empty selection + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 3) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // propose those txs in order to track them + proposedBlock2 = createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte("blockHash4"), proposedBlock2, &block.Header{ + Nonce: 3, + PrevHash: []byte("blockHash3"), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // now, do the last selection and expect an empty one + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 4) + require.Nil(t, err) + require.Equal(t, 0, len(selectedTransactions)) +} + +func Test_SimulateSelection_ShouldNotRemoveProposedBlocks(t *testing.T) { + t.Parallel() + + host := txcachemocks.NewMempoolHostMock() + txpool, err := txcache.NewTxCache(configSourceMe, host, 0) + + require.Nil(t, err) + require.NotNil(t, txpool) + + numTxsPerSender := 30_000 + initialAmount := big.NewInt(int64(numTxsPerSender) * 50_000 * 1_000_000_000) + + senders := []string{"alice", "bob", "carol"} + accounts := map[string]*stateMock.UserAccountStub{ + "alice": { + Balance: initialAmount, + Nonce: 0, + }, + "bob": { + Balance: initialAmount, + Nonce: 0, + }, + "carol": { + Balance: initialAmount, + Nonce: 0, + }, + "receiver": { + Balance: big.NewInt(0), + Nonce: 0, + }, + } + + selectionSession := txcachemocks.NewSelectionSessionMockWithAccounts(accounts) + selectionSession.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMockWithAccounts(accounts) + accountsProvider.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + err = txpool.OnExecutedBlock(&block.Header{}, []byte(testRootHash)) + require.Nil(t, err) + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + numTxsPerSender, + 10, + haveTimeTrue, + ) + + numTxs := numTxsPerSender * len(senders) + nonceTracker := newNoncesTracker() + + // create numTxs transactions and save them to txpool + addTransactionsToTxPool(txpool, nonceTracker, numTxsPerSender, senders) + require.Equal(t, txpool.CountTx(), uint64(numTxs)) + + // do the first selection + selectedTransactions, _, err := txpool.SimulateSelectTransactions(selectionSession, options, 0) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // propose those txs in order to track them (create the breadcrumbs used for the virtual records) + proposedBlock1 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash1), proposedBlock1, &block.Header{ + Nonce: 1, + PrevHash: []byte(testBlockHash0), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // do the second selection + selectedTransactions, _, err = txpool.SimulateSelectTransactions(selectionSession, options, 0) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // propose those txs in order to track them (create the breadcrumbs used for the virtual records) + proposedBlock2 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash2), proposedBlock2, &block.Header{ + Nonce: 2, + PrevHash: []byte(testBlockHash1), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // because it is only a simulation, we should have only one more non-empty selection. + selectedTransactions, _, err = txpool.SimulateSelectTransactions(selectionSession, options, 0) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // propose those txs in order to track them + proposedBlock2 = createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte("blockHash3"), proposedBlock2, &block.Header{ + Nonce: 3, + PrevHash: []byte(testBlockHash2), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // now, do the last selection and expect an empty one + // used a lower nonce to highlight that the proposed blocks will not be removed + selectedTransactions, _, err = txpool.SimulateSelectTransactions(selectionSession, options, 0) + require.Nil(t, err) + require.Equal(t, 0, len(selectedTransactions)) +} + +func Test_Selection_MaxTrackedBlocksReached(t *testing.T) { + t.Parallel() + + host := txcachemocks.NewMempoolHostMock() + txpool, err := txcache.NewTxCache(txcache.ConfigSourceMe{ + Name: "test", + NumChunks: 16, + NumBytesThreshold: maxNumBytesUpperBound, + NumBytesPerSenderThreshold: maxNumBytesPerSenderUpperBoundTest, + CountThreshold: math.MaxUint32, + CountPerSenderThreshold: math.MaxUint32, + EvictionEnabled: false, + NumItemsToPreemptivelyEvict: 1, + TxCacheBoundsConfig: config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: maxNumBytesPerSenderUpperBoundTest, + MaxTrackedBlocks: 3, + }, + }, host, 0) + + require.Nil(t, err) + require.NotNil(t, txpool) + + numTxsPerSender := 30_000 + initialAmount := big.NewInt(int64(numTxsPerSender) * 50_000 * 1_000_000_000) + + senders := []string{"alice", "bob"} + accounts := map[string]*stateMock.UserAccountStub{ + "alice": { + Balance: initialAmount, + Nonce: 0, + }, + "bob": { + Balance: initialAmount, + Nonce: 0, + }, + "receiver": { + Balance: big.NewInt(0), + Nonce: 0, + }, + } + + selectionSession := txcachemocks.NewSelectionSessionMockWithAccounts(accounts) + selectionSession.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMockWithAccounts(accounts) + accountsProvider.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + err = txpool.OnExecutedBlock(&block.Header{}, []byte(testRootHash)) + require.Nil(t, err) + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + numTxsPerSender, + 10, + haveTimeTrue, + ) + + numTxs := numTxsPerSender * len(senders) + nonceTracker := newNoncesTracker() + + addTransactionsToTxPool(txpool, nonceTracker, numTxsPerSender, senders) + require.Equal(t, txpool.CountTx(), uint64(numTxs)) + + // do the first selections + selectedTransactions, _, err := txpool.SelectTransactions(selectionSession, options, 0) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // propose those txs in order to track them (create the breadcrumbs used for the virtual records) + proposedBlock1 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash1), proposedBlock1, &block.Header{ + Nonce: 1, + PrevHash: []byte(testBlockHash0), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // do the second selection (the rest of the transactions should be selected) + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 2) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // propose the second block + proposedBlock2 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash2), proposedBlock2, + &block.Header{ + Nonce: 2, + PrevHash: []byte(testBlockHash1), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // do the last selection (no tx should be returned) + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 3) + require.Nil(t, err) + require.Equal(t, 0, len(selectedTransactions)) + + // propose one more block (an empty one) just to trigger the MaxTrackedBlocks + err = txpool.OnProposedBlock([]byte("blockHash3"), &block.Body{}, + &block.Header{ + Nonce: 3, + PrevHash: []byte(testBlockHash2), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // proposing an empty block when MaxTrackedBlocks is reached should work + err = txpool.OnProposedBlock([]byte("blockHash4"), &block.Body{}, + &block.Header{ + Nonce: 4, + PrevHash: []byte("blockHash3"), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // proposing a block with transactions when MaxTrackedBlocks is reached should not work + err = txpool.OnProposedBlock([]byte("blockHash4"), &block.Body{ + MiniBlocks: []*block.MiniBlock{ + {}, + }, + }, + &block.Header{ + Nonce: 4, + PrevHash: []byte("blockHash3"), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.ErrorContains(t, err, "bad block received while max tracked blocks is reached") + + // proposing a block with transactions and with new execution results when MaxTrackedBlocks is reached should work + err = txpool.OnProposedBlock([]byte("blockHash5"), &block.Body{ + MiniBlocks: []*block.MiniBlock{ + {}, + }, + }, + &block.HeaderV3{ + Nonce: 5, + PrevHash: []byte("blockHash4"), + ExecutionResults: []*block.ExecutionResult{ + {}, + }, + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) +} + +func Test_SelectionWhenFeeExceedsBalanceWithMax3TxsSelected(t *testing.T) { + t.Parallel() + + host := txcachemocks.NewMempoolHostMock() + txpool, err := txcache.NewTxCache(configSourceMe, host, 0) + + require.Nil(t, err) + require.NotNil(t, txpool) + + accounts := map[string]*stateMock.UserAccountStub{ + "alice": { + Balance: oneQuarterOfEGLD, + Nonce: 0, + }, + "bob": { + Balance: oneQuarterOfEGLD, + Nonce: 0, + }, + "carol": { + Balance: oneQuarterOfEGLD, + Nonce: 0, + }, + "receiver": { + Balance: big.NewInt(0), + Nonce: 0, + }, + "relayer": { + Balance: oneEGLD, + Nonce: 0, + }, + } + + selectionSession := txcachemocks.NewSelectionSessionMockWithAccounts(accounts) + selectionSession.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMockWithAccounts(accounts) + accountsProvider.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + err = txpool.OnExecutedBlock(&block.Header{}, []byte(testRootHash)) + require.Nil(t, err) + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + 3, + 10, + haveTimeTrue, + ) + + // Consume most of relayer's balance. Keep an amount that is enough for the fee of two simple transfer transactions. + currentRelayerBalance := int64(1000000000000000000) + feeForTransfer := int64(50_000 * 1_000_000_004) + feeForRelayingTransactionsOfAliceAndBob := int64(100_000*1_000_000_003 + 100_000*1_000_000_002) + + transactions := make([]*transaction.Transaction, 0) + + transactions = append(transactions, &transaction.Transaction{ + Nonce: 0, + Value: big.NewInt(currentRelayerBalance - feeForTransfer - feeForRelayingTransactionsOfAliceAndBob), + SndAddr: []byte("relayer"), + RcvAddr: []byte("receiver"), + GasLimit: 50_000, + GasPrice: 1_000_000_004, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + }) + + // Transfer from Alice (relayed) + transactions = append(transactions, &transaction.Transaction{ + Nonce: 0, + Value: oneQuarterOfEGLD, + SndAddr: []byte("alice"), + RcvAddr: []byte("receiver"), + RelayerAddr: []byte("relayer"), + GasLimit: 100_000, + GasPrice: 1_000_000_003, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + RelayerSignature: []byte("signature"), + }) + + // Transfer from Bob (relayed) + transactions = append(transactions, &transaction.Transaction{ + Nonce: 0, + Value: oneQuarterOfEGLD, + SndAddr: []byte("bob"), + RcvAddr: []byte("receiver"), + RelayerAddr: []byte("relayer"), + GasLimit: 100_000, + GasPrice: 1_000_000_002, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + RelayerSignature: []byte("signature"), + }) + + // Transfer from Carol (relayed) - this one should not be selected due to insufficient balance (of the relayer) + transactions = append(transactions, &transaction.Transaction{ + Nonce: 0, + Value: oneQuarterOfEGLD, + SndAddr: []byte("carol"), + RcvAddr: []byte("receiver"), + RelayerAddr: []byte("relayer"), + GasLimit: 100_000, + GasPrice: 1_000_000_001, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + RelayerSignature: []byte("signature"), + }) + + for i, tx := range transactions { + txHash := []byte(fmt.Sprintf("txHash%d", i)) + txpool.AddTx(&txcache.WrappedTransaction{ + Tx: tx, + TxHash: txHash, + Fee: big.NewInt(int64(tx.GasLimit * tx.GasPrice)), + TransferredValue: tx.Value, + FeePayer: tx.RelayerAddr, + }) + } + + require.Equal(t, txpool.CountTx(), uint64(4)) + + // do the first selection: first 3 transactions should be returned + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err := txpool.SelectTransactions(selectionSession, options, 1) + require.Nil(t, err) + require.Equal(t, 3, len(selectedTransactions)) + require.Equal(t, "relayer", string(selectedTransactions[0].Tx.GetSndAddr())) + require.Equal(t, "alice", string(selectedTransactions[1].Tx.GetSndAddr())) + require.Equal(t, "bob", string(selectedTransactions[2].Tx.GetSndAddr())) + + // propose those txs in order to track them (create the breadcrumbs used for the virtual records) + proposedBlock1 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash1), proposedBlock1, + &block.Header{ + Nonce: 1, + PrevHash: []byte(testBlockHash0), + RootHash: []byte(testRootHash), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // do the second selection, last tx should not be returned (relayer has insufficient balance) + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 2) + require.Nil(t, err) + require.Equal(t, 0, len(selectedTransactions)) +} + +func Test_SelectionWhenFeeExceedsBalanceWithMax2TxsSelected(t *testing.T) { + t.Parallel() + + host := txcachemocks.NewMempoolHostMock() + txpool, err := txcache.NewTxCache(configSourceMe, host, 0) + + require.Nil(t, err) + require.NotNil(t, txpool) + + accounts := map[string]*stateMock.UserAccountStub{ + "alice": { + Balance: oneQuarterOfEGLD, + Nonce: 0, + }, + "bob": { + Balance: oneQuarterOfEGLD, + Nonce: 0, + }, + "carol": { + Balance: oneQuarterOfEGLD, + Nonce: 0, + }, + "receiver": { + Balance: big.NewInt(0), + Nonce: 0, + }, + "relayer": { + Balance: oneEGLD, + Nonce: 0, + }, + } + + selectionSession := txcachemocks.NewSelectionSessionMockWithAccounts(accounts) + selectionSession.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMockWithAccounts(accounts) + accountsProvider.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + err = txpool.OnExecutedBlock(&block.Header{}, []byte(testRootHash)) + require.Nil(t, err) + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + 2, + 10, + haveTimeTrue, + ) + + // Consume most of relayer's balance. Keep an amount that is enough for the fee of two simple transfer transactions. + currentRelayerBalance := int64(1000000000000000000) + feeForTransfer := int64(50_000 * 1_000_000_004) + feeForRelayingTransactionsOfAliceAndBob := int64(100_000*1_000_000_003 + 100_000*1_000_000_002) + + transactions := make([]*transaction.Transaction, 0) + + transactions = append(transactions, &transaction.Transaction{ + Nonce: 0, + Value: big.NewInt(currentRelayerBalance - feeForTransfer - feeForRelayingTransactionsOfAliceAndBob), + SndAddr: []byte("relayer"), + RcvAddr: []byte("receiver"), + GasLimit: 50_000, + GasPrice: 1_000_000_004, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + }) + + // Transfer from Alice (relayed) + transactions = append(transactions, &transaction.Transaction{ + Nonce: 0, + Value: oneQuarterOfEGLD, + SndAddr: []byte("alice"), + RcvAddr: []byte("receiver"), + RelayerAddr: []byte("relayer"), + GasLimit: 100_000, + GasPrice: 1_000_000_003, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + RelayerSignature: []byte("signature"), + }) + + // Transfer from Bob (relayed) + transactions = append(transactions, &transaction.Transaction{ + Nonce: 0, + Value: oneQuarterOfEGLD, + SndAddr: []byte("bob"), + RcvAddr: []byte("receiver"), + RelayerAddr: []byte("relayer"), + GasLimit: 100_000, + GasPrice: 1_000_000_002, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + RelayerSignature: []byte("signature"), + }) + + // Transfer from Carol (relayed) - this one should not be selected due to insufficient balance (of the relayer) + transactions = append(transactions, &transaction.Transaction{ + Nonce: 0, + Value: oneQuarterOfEGLD, + SndAddr: []byte("carol"), + RcvAddr: []byte("receiver"), + RelayerAddr: []byte("relayer"), + GasLimit: 100_000, + GasPrice: 1_000_000_001, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + RelayerSignature: []byte("signature"), + }) + + for i, tx := range transactions { + txHash := []byte(fmt.Sprintf("txHash%d", i)) + txpool.AddTx(&txcache.WrappedTransaction{ + Tx: tx, + TxHash: txHash, + Fee: big.NewInt(int64(tx.GasLimit * tx.GasPrice)), + TransferredValue: tx.Value, + FeePayer: tx.RelayerAddr, + }) + } + + require.Equal(t, txpool.CountTx(), uint64(4)) + + // do the first selection: first 3 transactions should be returned + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err := txpool.SelectTransactions(selectionSession, options, 1) + require.Nil(t, err) + require.Equal(t, 2, len(selectedTransactions)) + require.Equal(t, "relayer", string(selectedTransactions[0].Tx.GetSndAddr())) + require.Equal(t, "alice", string(selectedTransactions[1].Tx.GetSndAddr())) + + // propose those txs in order to track them (create the breadcrumbs used for the virtual records) + proposedBlock1 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash1), proposedBlock1, + &block.Header{ + Nonce: 1, + PrevHash: []byte(testBlockHash0), + RootHash: []byte(testRootHash), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // do the second selection, last tx should not be returned (relayer has insufficient balance) + // the currentNonce should represent here the nonce of the block for which the selection is built + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 2) + require.Nil(t, err) + require.Equal(t, 1, len(selectedTransactions)) + require.Equal(t, "bob", string(selectedTransactions[0].Tx.GetSndAddr())) +} + +func Test_SelectionWithRootHashMismatch(t *testing.T) { + t.Parallel() + + host := txcachemocks.NewMempoolHostMock() + txpool, err := txcache.NewTxCache(txcache.ConfigSourceMe{ + Name: "test", + NumChunks: 16, + NumBytesThreshold: maxNumBytesUpperBound, + NumBytesPerSenderThreshold: maxNumBytesPerSenderUpperBoundTest, + CountThreshold: math.MaxUint32, + CountPerSenderThreshold: math.MaxUint32, + EvictionEnabled: false, + NumItemsToPreemptivelyEvict: 1, + TxCacheBoundsConfig: config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: maxNumBytesPerSenderUpperBoundTest, + MaxTrackedBlocks: 3, + }, + }, host, 0) + + require.Nil(t, err) + require.NotNil(t, txpool) + + numTxsPerSender := 30_000 + initialAmount := big.NewInt(int64(numTxsPerSender) * 50_000 * 1_000_000_000) + + senders := []string{"alice", "bob"} + accounts := map[string]*stateMock.UserAccountStub{ + "alice": { + Balance: initialAmount, + Nonce: 0, + }, + "bob": { + Balance: initialAmount, + Nonce: 0, + }, + "receiver": { + Balance: big.NewInt(0), + Nonce: 0, + }, + } + + selectionSession := txcachemocks.NewSelectionSessionMockWithAccounts(accounts) + // keep the same root hash with the one used on the OnExecutedBlock to avoid root hash mismatch on selection + selectionSession.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + err = txpool.OnExecutedBlock(&block.Header{}, []byte(testRootHash)) + require.Nil(t, err) + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + numTxsPerSender, + 10, + haveTimeTrue, + ) + + numTxs := numTxsPerSender * len(senders) + nonceTracker := newNoncesTracker() + + addTransactionsToTxPool(txpool, nonceTracker, numTxsPerSender, senders) + require.Equal(t, txpool.CountTx(), uint64(numTxs)) + + // do the first selections + selectedTransactions, _, err := txpool.SelectTransactions(selectionSession, options, 0) + require.Nil(t, err) + require.Equal(t, numTxsPerSender, len(selectedTransactions)) + + // change the returned root hash on selection session to trigger root hash mismatch + selectionSession.GetRootHashCalled = func() ([]byte, error) { + return []byte("rootHashX"), nil + } + // propose those txs in order to track them (create the breadcrumbs used for the virtual records) + proposedBlock1 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash1), proposedBlock1, &block.Header{ + Nonce: 1, + PrevHash: []byte(testBlockHash0), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.ErrorContains(t, err, "root hash mismatch") +} + +func Test_SelectionWithAliceRelayerAndSenderOnSameTxs(t *testing.T) { + t.Parallel() + + host := txcachemocks.NewMempoolHostMock() + txpool, err := txcache.NewTxCache(configSourceMe, host, 0) + + require.Nil(t, err) + require.NotNil(t, txpool) + + // calculate the fee for transfer + feeForTransfer := int64(100_000 * 1_000_000_000) + accounts := map[string]*stateMock.UserAccountStub{ + "alice": { + // alice has enough balance for one transaction + Balance: big.NewInt(0).Add(oneEGLD, big.NewInt(feeForTransfer)), + Nonce: 0, + }, + } + + selectionSession := txcachemocks.NewSelectionSessionMockWithAccounts(accounts) + // keep the same root hash with the one used on the OnExecutedBlock to avoid root hash mismatch on selection + selectionSession.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMockWithAccounts(accounts) + accountsProvider.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + err = txpool.OnExecutedBlock(&block.Header{}, []byte(testRootHash)) + require.Nil(t, err) + + nonceTracker := newNoncesTracker() + + // create two transactions. + // both transactions have alice as sender and relayer + // alice has enough balance only for this transaction + tx := &transaction.Transaction{ + Nonce: nonceTracker.getThenIncrementNonceByStringAddress("alice"), + Value: oneEGLD, + SndAddr: []byte("alice"), + RcvAddr: []byte("receiver"), + Data: []byte{}, + GasLimit: 100_000, + GasPrice: 1_000_000_000, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + RelayerAddr: []byte("alice"), + } + + txpool.AddTx(&txcache.WrappedTransaction{ + Tx: tx, + TxHash: []byte("txHash1"), + }) + + // the second transaction has alice as sender and as relayer. + // this transaction should not be selected + tx = &transaction.Transaction{ + Nonce: nonceTracker.getThenIncrementNonceByStringAddress("alice"), + Value: big.NewInt(0), + SndAddr: []byte("alice"), + RcvAddr: []byte("receiver"), + Data: []byte{}, + GasLimit: 100_000, + GasPrice: 1_000_000_000, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + RelayerAddr: []byte("alice"), + } + + txpool.AddTx(&txcache.WrappedTransaction{ + Tx: tx, + TxHash: []byte("txHash2"), + }) + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + 1, + 10, + haveTimeTrue, + ) + + // do the first selection + selectedTransactions, _, err := txpool.SelectTransactions(selectionSession, options, 1) + require.Nil(t, err) + require.Len(t, selectedTransactions, 1) + require.Equal(t, selectedTransactions[0].TxHash, []byte("txHash1")) + + // propose the block + proposedBlock1 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash1), proposedBlock1, + &block.Header{ + Nonce: 1, + PrevHash: []byte(testBlockHash0), + RootHash: []byte(testRootHash), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // the second tx should not be selected, because alice has insufficient funds + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 2) + require.Nil(t, err) + require.Len(t, selectedTransactions, 0) +} + +func Test_SelectionWithAliceSenderAndThenRelayerOnDifferentTxs(t *testing.T) { + t.Parallel() + + host := txcachemocks.NewMempoolHostMock() + txpool, err := txcache.NewTxCache(configSourceMe, host, 0) + + require.Nil(t, err) + require.NotNil(t, txpool) + + // calculate the fee for transfer + feeForTransfer := int64(100_000 * 1_000_000_000) + accounts := map[string]*stateMock.UserAccountStub{ + "alice": { + // alice has enough balance for one transaction + Balance: big.NewInt(feeForTransfer), + Nonce: 0, + }, + "bob": { + Balance: big.NewInt(0), + Nonce: 0, + }, + } + + selectionSession := txcachemocks.NewSelectionSessionMockWithAccounts(accounts) + // keep the same root hash with the one used on the OnExecutedBlock to avoid root hash mismatch on selection + selectionSession.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMockWithAccounts(accounts) + accountsProvider.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + err = txpool.OnExecutedBlock(&block.Header{}, []byte(testRootHash)) + require.Nil(t, err) + + nonceTracker := newNoncesTracker() + + // the first transaction has alice as sender and its own relayer + tx := &transaction.Transaction{ + Nonce: nonceTracker.getThenIncrementNonceByStringAddress("alice"), + Value: big.NewInt(0), + SndAddr: []byte("alice"), + RcvAddr: []byte("receiver"), + Data: []byte{}, + GasLimit: 100_000, + GasPrice: 1_000_000_000, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + RelayerAddr: []byte("alice"), + } + + txpool.AddTx(&txcache.WrappedTransaction{ + Tx: tx, + TxHash: []byte("txHash1"), + }) + + // the second transaction has bob as sender and alice as relayer. + // this transaction should not be selected + tx = &transaction.Transaction{ + Nonce: nonceTracker.getThenIncrementNonceByStringAddress("bob"), + Value: big.NewInt(0), + SndAddr: []byte("bob"), + RcvAddr: []byte("receiver"), + Data: []byte{}, + GasLimit: 100_000, + GasPrice: 1_000_000_000, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + RelayerAddr: []byte("alice"), + } + + txpool.AddTx(&txcache.WrappedTransaction{ + Tx: tx, + TxHash: []byte("txHash2"), + }) + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + // select max 2 txs + 2, + 10, + haveTimeTrue, + ) + + // do the first selection + // only one should be selected + selectedTransactions, _, err := txpool.SelectTransactions(selectionSession, options, 1) + require.Nil(t, err) + require.Len(t, selectedTransactions, 1) + require.Equal(t, selectedTransactions[0].TxHash, []byte("txHash1")) + + // propose the block + proposedBlock1 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash1), proposedBlock1, + &block.Header{ + Nonce: 1, + PrevHash: []byte(testBlockHash0), + RootHash: []byte(testRootHash), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // the second tx should not be selected, because alice has insufficient funds + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 2) + require.Nil(t, err) + require.Len(t, selectedTransactions, 0) +} + +func TestMempoolWithChainSimulator_Selection_InstantChangeGuardian(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + numSenders := 2 + shard := 0 + + simulator := startChainSimulator(t, func(cfg *config.Configs) {}) + defer simulator.Close() + + err := simulator.GenerateBlocksUntilEpochIsReached(2) + require.NoError(t, err) + + participants := createParticipants(t, simulator, numSenders) + noncesTracker := newNoncesTracker() + + alice := participants.sendersByShard[shard][0] + bob := participants.sendersByShard[shard][1] + receiver := participants.receiverByShard[shard] + + transactions := make([]*transaction.Transaction, 0) + + // Guard Alice's account + setGuardianTxData := "SetGuardian@" + hex.EncodeToString(bob.Bytes) + "@" + hex.EncodeToString([]byte("uuid")) + setGuardianTx := &transaction.Transaction{ + Nonce: noncesTracker.getThenIncrementNonce(alice), + Value: big.NewInt(0), + SndAddr: alice.Bytes, + RcvAddr: alice.Bytes, + Data: []byte(setGuardianTxData), + GasLimit: 600_000, + GasPrice: 1_000_000_000, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + } + _, err = simulator.SendTxAndGenerateBlockTilTxIsExecuted(setGuardianTx, 10) + require.NoError(t, err) + + // fast-forward until the guardian becomes active + err = simulator.GenerateBlocks(int(simulator.GetNodeHandler(uint32(shard)).GetCoreComponents().ChainParametersHandler().CurrentChainParameters().RoundsPerEpoch * 20)) + require.NoError(t, err) + + guardAccountTx := &transaction.Transaction{ + Nonce: noncesTracker.getThenIncrementNonce(alice), + Value: big.NewInt(0), + SndAddr: alice.Bytes, + RcvAddr: alice.Bytes, + Data: []byte("GuardAccount"), + GasLimit: 400_000, + GasPrice: 1_000_000_000, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + } + _, err = simulator.SendTxAndGenerateBlockTilTxIsExecuted(guardAccountTx, 10) + require.NoError(t, err) + + guardianData, _, err := simulator.GetNodeHandler(uint32(shard)).GetFacadeHandler().GetGuardianData(alice.Bech32, api.AccountQueryOptions{}) + require.NoError(t, err) + + require.NotNil(t, guardianData) + require.True(t, guardianData.Guarded) + require.NotNil(t, guardianData.ActiveGuardian) + require.Equal(t, bob.Bech32, guardianData.ActiveGuardian.Address) + + // Transfer from Alice to receiver -> should be selected + transactions = append(transactions, &transaction.Transaction{ + Nonce: noncesTracker.getThenIncrementNonce(alice), + Value: oneQuarterOfEGLD, + SndAddr: alice.Bytes, + RcvAddr: receiver.Bytes, + Data: []byte{}, + GasLimit: 100_000, + GasPrice: 1_000_000_002, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + GuardianAddr: bob.Bytes, + GuardianSignature: []byte("signature"), + Options: 2, + }) + + // Change guardian -> should be selected + setGuardianTxData = "SetGuardian@" + hex.EncodeToString(receiver.Bytes) + "@" + hex.EncodeToString([]byte("uuid")) + transactions = append(transactions, &transaction.Transaction{ + Nonce: noncesTracker.getThenIncrementNonce(alice), + Value: big.NewInt(0), + SndAddr: alice.Bytes, + RcvAddr: alice.Bytes, + Data: []byte(setGuardianTxData), + GasLimit: 600_000, + GasPrice: 1_000_000_002, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + GuardianAddr: bob.Bytes, + GuardianSignature: []byte("signature"), + Options: 2, + }) + + // Transfer from Alice to receiver -> should NOT be selected + transactions = append(transactions, &transaction.Transaction{ + Nonce: noncesTracker.getThenIncrementNonce(alice), + Value: oneQuarterOfEGLD, + SndAddr: alice.Bytes, + RcvAddr: receiver.Bytes, + Data: []byte{}, + GasLimit: 100_000, + GasPrice: 1_000_000_002, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + GuardianAddr: bob.Bytes, // guarded by the old guardian + GuardianSignature: []byte("signature"), + Options: 2, + }) + + sendTransactions(t, simulator, transactions) + time.Sleep(durationWaitAfterSendSome) + require.Equal(t, 3, getNumTransactionsInPool(simulator, shard)) + + // Propose the header with initial 2 transactions + err = simulator.GenerateBlocks(1) + require.NoError(t, err) + + currentHeader := simulator.GetNodeHandler(uint32(shard)).GetDataComponents().Blockchain().GetCurrentBlockHeader() + require.Equal(t, uint32(2), currentHeader.GetTxCount()) + + // Propose one more header, will select the 3rd transaction + err = simulator.GenerateBlocks(1) + require.NoError(t, err) + + currentHeader = simulator.GetNodeHandler(uint32(shard)).GetDataComponents().Blockchain().GetCurrentBlockHeader() + require.Equal(t, uint32(1), currentHeader.GetTxCount()) + + require.Equal(t, 1, getNumTransactionsInPool(simulator, shard)) + + // Propose one more header, should not select anything (empty pool) + err = simulator.GenerateBlocks(1) + require.NoError(t, err) + + currentHeader = simulator.GetNodeHandler(uint32(shard)).GetDataComponents().Blockchain().GetCurrentBlockHeader() + require.Equal(t, uint32(0), currentHeader.GetTxCount()) + + require.Equal(t, 0, getNumTransactionsInPool(simulator, shard)) +} + +func TestMempoolWithChainSimulator_Selection_InstantChangeGuardian_ReplaceHeader(t *testing.T) { + t.Parallel() + + host := txcachemocks.NewMempoolHostMock() + txpool, err := txcache.NewTxCache(configSourceMe, host, 0) + + require.Nil(t, err) + require.NotNil(t, txpool) + + // calculate the fee for transfer + accounts := map[string]*stateMock.UserAccountStub{ + "alice": { + // alice has enough balance for transactions + Balance: core.SafeMul(oneEGLD.Uint64(), 100), + Nonce: 0, + }, + "bob": { + Balance: big.NewInt(0), + Nonce: 0, + }, + "receiver": { + Balance: big.NewInt(0), + Nonce: 0, + }, + } + + selectionSession := txcachemocks.NewSelectionSessionMockWithAccounts(accounts) + // all transactions are correctly guarded, except the last one + selectionSession.IsGuardedCalled = func(tx data.TransactionHandler) bool { + return true + } + selectionSession.IsIncorrectlyGuardedCalled = func(tx data.TransactionHandler) bool { + return tx.GetNonce() == 4 + } + // keep the same root hash with the one used on the OnExecutedBlock to avoid root hash mismatch on selection + selectionSession.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMockWithAccounts(accounts) + accountsProvider.GetRootHashCalled = func() ([]byte, error) { + return []byte(testRootHash), nil + } + + err = txpool.OnExecutedBlock(&block.Header{ + Nonce: 0, + }, []byte(testRootHash)) + require.Nil(t, err) + + nonceTracker := newNoncesTracker() + + // Transfer from Alice to receiver -> should be selected + tx1 := &transaction.Transaction{ + Nonce: nonceTracker.getThenIncrementNonceByStringAddress("alice"), + Value: oneQuarterOfEGLD, + SndAddr: []byte("alice"), + RcvAddr: []byte("receiver"), + Data: []byte{}, + GasLimit: 100_000, + GasPrice: 1_000_000_000, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + GuardianAddr: []byte("bob"), + GuardianSignature: []byte("signature"), + Options: 2, + } + txpool.AddTx(&txcache.WrappedTransaction{ + Tx: tx1, + TxHash: []byte("txHash1"), + }) + + // Change guardian -> should be selected + setGuardianTxData := "SetGuardian@" + hex.EncodeToString([]byte("receiver")) + "@" + hex.EncodeToString([]byte("uuid")) + tx2 := &transaction.Transaction{ + Nonce: nonceTracker.getThenIncrementNonceByStringAddress("alice"), + Value: big.NewInt(0), + SndAddr: []byte("alice"), + RcvAddr: []byte("alice"), + Data: []byte(setGuardianTxData), + GasLimit: 600_000, + GasPrice: 1_000_000_000, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + GuardianAddr: []byte("bob"), + GuardianSignature: []byte("signature"), + Options: 2, + } + txpool.AddTx(&txcache.WrappedTransaction{ + Tx: tx2, + TxHash: []byte("txHash2"), + }) + + // Transfer from Alice to receiver -> should NOT be selected + tx3 := &transaction.Transaction{ + Nonce: nonceTracker.getThenIncrementNonceByStringAddress("alice"), + Value: oneQuarterOfEGLD, + SndAddr: []byte("alice"), + RcvAddr: []byte("receiver"), + Data: []byte{}, + GasLimit: 100_000, + GasPrice: 1_000_000_000, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + GuardianAddr: []byte("bob"), // guarded by the old guardian + GuardianSignature: []byte("signature"), + Options: 2, + } + txpool.AddTx(&txcache.WrappedTransaction{ + Tx: tx3, + TxHash: []byte("txHash3"), + }) + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + // select max 3 txs + 3, + 10, + haveTimeTrue, + ) + + // do the first selection + selectedTransactions, _, err := txpool.SelectTransactions(selectionSession, options, 1) + require.Nil(t, err) + require.Len(t, selectedTransactions, 2) + require.Equal(t, selectedTransactions[0].TxHash, []byte("txHash1")) + require.Equal(t, selectedTransactions[1].TxHash, []byte("txHash2")) + + // propose the block + proposedBlock1 := createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte(testBlockHash1), proposedBlock1, + &block.Header{ + Nonce: 1, + PrevHash: []byte(testBlockHash0), + RootHash: []byte(testRootHash), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // do the second selection with the same block nonce + // should select the same txs + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 1) + require.Nil(t, err) + require.Len(t, selectedTransactions, 2) + require.Equal(t, selectedTransactions[0].TxHash, []byte("txHash1")) + require.Equal(t, selectedTransactions[1].TxHash, []byte("txHash2")) +} diff --git a/integrationTests/chainSimulator/mempool/testutils_test.go b/integrationTests/chainSimulator/mempool/testutils_test.go index a86494f9fa8..7d0a5ddb3b5 100644 --- a/integrationTests/chainSimulator/mempool/testutils_test.go +++ b/integrationTests/chainSimulator/mempool/testutils_test.go @@ -2,44 +2,69 @@ package mempool import ( "encoding/hex" + "fmt" "math/big" + "math/rand" "strconv" "testing" "time" "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/common/holders" "github.com/multiversx/mx-chain-go/config" testsChainSimulator "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator" "github.com/multiversx/mx-chain-go/node/chainSimulator" "github.com/multiversx/mx-chain-go/node/chainSimulator/components/api" + "github.com/multiversx/mx-chain-go/node/chainSimulator/configs" "github.com/multiversx/mx-chain-go/node/chainSimulator/dtos" - "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/block/preprocess" - "github.com/multiversx/mx-chain-go/storage/txcache" "github.com/multiversx/mx-chain-go/testscommon" - "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/testscommon/txcachemocks" + "github.com/multiversx/mx-chain-go/txcache" ) var ( oneEGLD = big.NewInt(1000000000000000000) oneQuarterOfEGLD = big.NewInt(250000000000000000) - durationWaitAfterSendMany = 3000 * time.Millisecond - durationWaitAfterSendSome = 300 * time.Millisecond + durationWaitAfterSendMany = 7500 * time.Millisecond + durationWaitAfterSendSome = 1000 * time.Millisecond + defaultLatestExecutedHash = []byte("blockHash0") + gasLimit = 50_000 + gasPrice = 1_000_000_000 + haveTimeTrue = func() bool { + return true + } ) +const maxNumBytesUpperBound = 1_073_741_824 // one GB +const maxNumBytesPerSenderUpperBoundTest = 33_554_432 // 32 MB +const maxTrackedBlocks = 100 +const testRootHash = "rootHash0" +const testBlockHash0 = "blockHash0" +const testBlockHash1 = "blockHash1" +const testBlockHash2 = "blockHash2" + func startChainSimulator(t *testing.T, alterConfigsFunction func(cfg *config.Configs)) testsChainSimulator.ChainSimulator { simulator, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: "../../../cmd/node/config/", - NumOfShards: 1, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: uint64(4000), + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: "../../../cmd/node/config/", + NumOfShards: 1, + RoundDurationInMillis: uint64(4000), + SupernovaRoundDurationInMillis: uint64(400), RoundsPerEpoch: core.OptionalUint64{ HasValue: true, Value: 10, }, + SupernovaRoundsPerEpoch: core.OptionalUint64{ + HasValue: true, + Value: 100, + }, ApiInterface: api.NewNoApiInterface(), MinNodesPerShard: 1, MetaChainMinNodes: 1, @@ -121,6 +146,16 @@ func (tracker *noncesTracker) getThenIncrementNonce(address dtos.WalletAddress) return nonce } +func (tracker *noncesTracker) getThenIncrementNonceByStringAddress(address string) uint64 { + nonce, ok := tracker.nonceByAddress[address] + if !ok { + tracker.nonceByAddress[address] = 0 + } + + tracker.nonceByAddress[address]++ + return nonce +} + func sendTransactions(t *testing.T, simulator testsChainSimulator.ChainSimulator, transactions []*transaction.Transaction) { transactionsBySenderShard := make(map[int][]*transaction.Transaction) shardCoordinator := simulator.GetNodeHandler(0).GetShardCoordinator() @@ -156,20 +191,23 @@ func selectTransactions(t *testing.T, simulator testsChainSimulator.ChainSimulat poolsHolder := node.GetDataComponents().Datapool().Transactions() selectionSession, err := preprocess.NewSelectionSession(preprocess.ArgsSelectionSession{ - AccountsAdapter: accountsAdapter, - TransactionsProcessor: &testscommon.TxProcessorStub{}, + AccountsAdapter: accountsAdapter, + TransactionsProcessor: &testscommon.TxProcessorStub{}, + TxVersionCheckerHandler: node.GetCoreComponents().TxVersionChecker(), }) require.NoError(t, err) - mempool := poolsHolder.ShardDataStore(shardAsString).(*txcache.TxCache) - - selectedTransactions, gas := mempool.SelectTransactions( - selectionSession, - process.TxCacheSelectionGasRequested, - process.TxCacheSelectionMaxNumTxs, - process.TxCacheSelectionLoopMaximumDuration, + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + 30_000, + 10, + haveTimeTrue, ) + mempool := poolsHolder.ShardDataStore(shardAsString).(*txcache.TxCache) + selectedTransactions, gas, err := mempool.SelectTransactions(selectionSession, options, 0) + require.NoError(t, err) + return selectedTransactions, gas } @@ -191,3 +229,380 @@ func getTransaction(t *testing.T, simulator testsChainSimulator.ChainSimulator, require.NoError(t, err) return transaction } + +func createProposedBlock(selectedTransactions []*txcache.WrappedTransaction) *block.Body { + // extract the tx hashes from the selected transactions + proposedTxs := make([][]byte, 0, len(selectedTransactions)) + for _, tx := range selectedTransactions { + proposedTxs = append(proposedTxs, tx.TxHash) + } + + return &block.Body{MiniBlocks: []*block.MiniBlock{ + { + TxHashes: proposedTxs, + }, + }} +} + +func createFakeAddresses(numAddresses int) []string { + base := "sender" + addresses := make([]string, numAddresses) + for i := 0; i < numAddresses; i++ { + addresses[i] = fmt.Sprintf("%s:%d", base, i) + } + + return addresses +} + +func createRandomTx(nonceTracker *noncesTracker, accounts []string) *transaction.Transaction { + sender := rand.Intn(len(accounts)) + receiver := rand.Intn(len(accounts)) + for sender == receiver { + receiver = rand.Intn(len(accounts)) + } + + return &transaction.Transaction{ + Nonce: nonceTracker.getThenIncrementNonceByStringAddress(accounts[sender]), + Value: big.NewInt(1), + SndAddr: []byte(accounts[sender]), + RcvAddr: []byte(accounts[receiver]), + Data: []byte{}, + GasLimit: 50_000, + GasPrice: 1_000_000_000, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + } +} + +func createRandomTxs(txpool *txcache.TxCache, numTxs int, nonceTracker *noncesTracker, accounts []string) { + for i := 0; i < numTxs; i++ { + tx := createRandomTx(nonceTracker, accounts) + txHash := []byte(fmt.Sprintf("txHash%d", i)) + wtx := &txcache.WrappedTransaction{ + Tx: tx, + TxHash: txHash, + SenderShardID: 0, + ReceiverShardID: 0, + Size: 0, + Fee: core.SafeMul(tx.GasLimit, tx.GasPrice), + PricePerUnit: 0, + TransferredValue: tx.Value, + FeePayer: tx.SndAddr, + } + txpool.AddTx(wtx) + } +} + +func addTransactionsToTxPool(txpool *txcache.TxCache, nonceTracker *noncesTracker, numTxsPerSender int, senders []string) { + // create numTxs transactions and save them to txpool + for i := 0; i < numTxsPerSender; i++ { + for j := 0; j < len(senders); j++ { + tx := &transaction.Transaction{ + Nonce: nonceTracker.getThenIncrementNonceByStringAddress(senders[j]), + Value: big.NewInt(0), + SndAddr: []byte(senders[j]), + RcvAddr: []byte("receiver"), + Data: []byte{}, + GasLimit: 50_000, + GasPrice: 1_000_000_000, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + } + txHash := []byte(fmt.Sprintf("txHash%d", i*len(senders)+j)) + txpool.AddTx(&txcache.WrappedTransaction{ + Tx: tx, + TxHash: txHash, + Fee: big.NewInt(int64(tx.GasLimit * tx.GasPrice)), + TransferredValue: tx.Value, + FeePayer: tx.SndAddr, + }) + } + } +} + +func testOnProposed(t *testing.T, sw *core.StopWatch, numTxs int, numAddresses int) { + // create some fake address for each account + accounts := createFakeAddresses(numAddresses) + + host := txcachemocks.NewMempoolHostMock() + txpool, err := txcache.NewTxCache(configSourceMe, host, 0) + + require.Nil(t, err) + require.NotNil(t, txpool) + + initialAmount := big.NewInt(0) + numTxsAsBigInt := big.NewInt(int64(numTxs)) + + // assuming the scenario when we always have the same sender, assure we have enough balance for fees and transfers + _ = initialAmount.Mul(numTxsAsBigInt, core.SafeMul(uint64(gasLimit), uint64(gasPrice))) + _ = initialAmount.Add(initialAmount, core.SafeMul(uint64(numTxs), uint64(transferredValue))) + + selectionSession := &txcachemocks.SelectionSessionMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 0, initialAmount, true, nil + }, + GetRootHashCalled: func() ([]byte, error) { + return []byte(testRootHash), nil + }, + } + + accountsAdapter := &txcachemocks.AccountNonceAndBalanceProviderMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 0, initialAmount, true, nil + }, + GetRootHashCalled: func() ([]byte, error) { + return []byte(testRootHash), nil + }, + } + + err = txpool.OnExecutedBlock(&block.Header{}, []byte(testRootHash)) + require.Nil(t, err) + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + numTxs, + 10, + haveTimeTrue, + ) + + nonceTracker := newNoncesTracker() + createRandomTxs(txpool, numTxs, nonceTracker, accounts) + + require.Equal(t, numTxs, int(txpool.CountTx())) + + selectedTransactions, _, err := txpool.SelectTransactions(selectionSession, options, 1) + require.NoError(t, err) + require.Equal(t, numTxs, len(selectedTransactions)) + + proposedBlock1 := createProposedBlock(selectedTransactions) + + sw.Start(t.Name()) + // measure the time spent + err = txpool.OnProposedBlock([]byte("blockHash1"), proposedBlock1, &block.Header{ + Nonce: 0, + PrevHash: []byte("blockHash0"), + RootHash: []byte(testRootHash), + }, + accountsAdapter, + defaultLatestExecutedHash, + ) + sw.Stop(t.Name()) + require.Nil(t, err) +} + +func testFirstSelection(t *testing.T, sw *core.StopWatch, numTxs int, numTxsToBeSelected, numAddresses int) { + // create some fake address for each account + accounts := createFakeAddresses(numAddresses) + + host := txcachemocks.NewMempoolHostMock() + txpool, err := txcache.NewTxCache(configSourceMe, host, 0) + + require.Nil(t, err) + require.NotNil(t, txpool) + + // assuming the scenario when we always have the same sender, assure we have enough balance for fees and transfers + initialAmount := big.NewInt(0) + numTxsAsBigInt := big.NewInt(int64(numTxs)) + + _ = initialAmount.Mul(numTxsAsBigInt, core.SafeMul(uint64(gasLimit), uint64(gasPrice))) + _ = initialAmount.Add(initialAmount, big.NewInt(int64(numTxs))) + + selectionSession := &txcachemocks.SelectionSessionMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 0, initialAmount, true, nil + }, + GetRootHashCalled: func() ([]byte, error) { + return []byte(testRootHash), nil + }, + } + + err = txpool.OnExecutedBlock(&block.Header{}, []byte(testRootHash)) + require.Nil(t, err) + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000*10, // in case of 1_000_000 txs + numTxsToBeSelected, + 10, + haveTimeTrue, + ) + + nonceTracker := newNoncesTracker() + createRandomTxs(txpool, numTxs, nonceTracker, accounts) + + require.Equal(t, numTxs, int(txpool.CountTx())) + + sw.Start(t.Name()) + selectedTransactions, _, err := txpool.SelectTransactions(selectionSession, options, 1) + sw.Stop(t.Name()) + + require.Nil(t, err) + require.Equal(t, numTxsToBeSelected, len(selectedTransactions)) +} + +func testSecondSelection(t *testing.T, sw *core.StopWatch, numTxs int, numTxsToBeSelected int, numAddresses int) { + // create some fake address for each account + accounts := createFakeAddresses(numAddresses) + + host := txcachemocks.NewMempoolHostMock() + txpool, err := txcache.NewTxCache(configSourceMe, host, 0) + + require.Nil(t, err) + require.NotNil(t, txpool) + + // assuming the scenario when we always have the same sender, assure we have enough balance for fees and transfers + initialAmount := big.NewInt(0) + numTxsAsBigInt := big.NewInt(int64(numTxs)) + + _ = initialAmount.Mul(numTxsAsBigInt, core.SafeMul(uint64(gasLimit), uint64(gasPrice))) + _ = initialAmount.Add(initialAmount, core.SafeMul(uint64(numTxs), uint64(transferredValue))) + + selectionSession := &txcachemocks.SelectionSessionMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 0, initialAmount, true, nil + }, + GetRootHashCalled: func() ([]byte, error) { + return []byte(testRootHash), nil + }, + } + + accountsAdapter := &txcachemocks.AccountNonceAndBalanceProviderMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 0, initialAmount, true, nil + }, + GetRootHashCalled: func() ([]byte, error) { + return []byte(testRootHash), nil + }, + } + + err = txpool.OnExecutedBlock(&block.Header{}, []byte(testRootHash)) + require.Nil(t, err) + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000*10, + numTxsToBeSelected, + 10, + haveTimeTrue, + ) + + nonceTracker := newNoncesTracker() + createRandomTxs(txpool, numTxs, nonceTracker, accounts) + + require.Equal(t, numTxs, int(txpool.CountTx())) + + selectedTransactions, _, err := txpool.SelectTransactions(selectionSession, options, 1) + + require.NoError(t, err) + require.Equal(t, numTxsToBeSelected, len(selectedTransactions)) + + proposedBlock := createProposedBlock(selectedTransactions) + // propose those txs in order to track them (create the breadcrumbs used for the virtual records) + err = txpool.OnProposedBlock([]byte("blockHash1"), proposedBlock, &block.Header{ + Nonce: 1, + PrevHash: []byte("blockHash0"), + RootHash: []byte(testRootHash), + }, + accountsAdapter, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // measure the time for the second selection (now we use the breadcrumbs to create the virtual records) + sw.Start(t.Name()) + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 2) + sw.Stop(t.Name()) + + require.NoError(t, err) + require.Equal(t, numTxsToBeSelected, len(selectedTransactions)) + + // propose the block and make sure the selection works well + proposedBlock = createProposedBlock(selectedTransactions) + err = txpool.OnProposedBlock([]byte("blockHash2"), proposedBlock, &block.Header{ + Nonce: 2, + PrevHash: []byte("blockHash1"), + RootHash: []byte(testRootHash), + }, + selectionSession, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 3) + require.NoError(t, err) + require.Equal(t, 0, len(selectedTransactions)) +} + +func testSecondSelectionWithManyTxsInPool(t *testing.T, sw *core.StopWatch, numTxs int, numTxsToBeSelected int, numAddresses int) { + accounts := createFakeAddresses(numAddresses) + + host := txcachemocks.NewMempoolHostMock() + txpool, err := txcache.NewTxCache(configSourceMe, host, 0) + + require.Nil(t, err) + require.NotNil(t, txpool) + + // assuming the scenario when we always have the same sender, assure we have enough balance for fees and transfers + initialAmount := big.NewInt(0) + numTxsAsBigInt := big.NewInt(int64(numTxs)) + + _ = initialAmount.Mul(numTxsAsBigInt, core.SafeMul(uint64(gasLimit), uint64(gasPrice))) + _ = initialAmount.Add(initialAmount, core.SafeMul(uint64(numTxs), uint64(transferredValue))) + + selectionSession := &txcachemocks.SelectionSessionMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 0, initialAmount, true, nil + }, + GetRootHashCalled: func() ([]byte, error) { + return []byte(testRootHash), nil + }, + } + + accountsAdapter := &txcachemocks.AccountNonceAndBalanceProviderMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 0, initialAmount, true, nil + }, + GetRootHashCalled: func() ([]byte, error) { + return []byte(testRootHash), nil + }, + } + + err = txpool.OnExecutedBlock(&block.Header{}, []byte(testRootHash)) + require.Nil(t, err) + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + numTxsToBeSelected, + 10, + haveTimeTrue, + ) + + nonceTracker := newNoncesTracker() + createRandomTxs(txpool, numTxs, nonceTracker, accounts) + + require.Equal(t, numTxs, int(txpool.CountTx())) + + selectedTransactions, _, err := txpool.SelectTransactions(selectionSession, options, 1) + require.NoError(t, err) + require.Equal(t, numTxsToBeSelected, len(selectedTransactions)) + + proposedBlock := createProposedBlock(selectedTransactions) + // propose those txs in order to track them (create the breadcrumbs used for the virtual records) + err = txpool.OnProposedBlock([]byte("blockHash1"), proposedBlock, &block.Header{ + Nonce: 1, + PrevHash: []byte("blockHash0"), + RootHash: []byte(testRootHash), + }, + accountsAdapter, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // measure the time for the second selection (now we use the breadcrumbs to create the virtual records) + sw.Start(t.Name()) + selectedTransactions, _, err = txpool.SelectTransactions(selectionSession, options, 2) + sw.Stop(t.Name()) + + require.NoError(t, err) + require.Equal(t, numTxsToBeSelected, len(selectedTransactions)) +} diff --git a/integrationTests/chainSimulator/relayedTx/relayedTx_test.go b/integrationTests/chainSimulator/relayedTx/relayedTx_test.go index 417b8daf383..42a59c4c4c3 100644 --- a/integrationTests/chainSimulator/relayedTx/relayedTx_test.go +++ b/integrationTests/chainSimulator/relayedTx/relayedTx_test.go @@ -8,15 +8,15 @@ import ( "strconv" "strings" "testing" - "time" "github.com/multiversx/mx-chain-core-go/core" apiData "github.com/multiversx/mx-chain-core-go/data/api" "github.com/multiversx/mx-chain-core-go/data/transaction" - "github.com/multiversx/mx-chain-go/integrationTests" logger "github.com/multiversx/mx-chain-logger-go" "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/integrationTests" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" testsChainSimulator "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator" @@ -41,7 +41,7 @@ const ( mockTxSignature = "ssig" mockRelayerTxSignature = "rsig" maxNumOfBlocksToGenerateWhenExecutingTx = 10 - roundsPerEpoch = 30 + roundsPerEpoch = 40 guardAccountCost = 250_000 extraGasLimitForGuarded = minGasLimit extraGasESDTTransfer = 250000 @@ -54,6 +54,8 @@ var ( ) func TestRelayedV3WithChainSimulator(t *testing.T) { + t.Parallel() + if testing.Short() { t.Skip("this is not a short test") } @@ -1014,6 +1016,8 @@ func testRelayedV3MetaInteraction() func(t *testing.T) { } func TestFixRelayedMoveBalanceWithChainSimulator(t *testing.T) { + t.Parallel() + if testing.Short() { t.Skip("this is not a short test") } @@ -1325,21 +1329,27 @@ func startChainSimulator( HasValue: true, Value: roundsPerEpoch, } + supernovaRoundsPerEpochOpt := core.OptionalUint64{ + HasValue: true, + Value: roundsPerEpoch * 10, + } cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpochOpt, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: alterConfigsFunction, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: roundDurationInMillis / 10, + RoundsPerEpoch: roundsPerEpochOpt, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpochOpt, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: alterConfigsFunction, }) require.NoError(t, err) require.NotNil(t, cs) @@ -1528,3 +1538,49 @@ func checkSCRSucceeded( require.Equal(t, core.CompletedTxEventIdentifier, event.Identifier) } } + +func TestSupernovaRelayedV3Txs(t *testing.T) { + providedActivationEpoch := uint32(1) + alterConfigsFunc := func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.FixRelayedBaseCostEnableEpoch = providedActivationEpoch + cfg.EpochConfig.EnableEpochs.RelayedTransactionsV3EnableEpoch = providedActivationEpoch + cfg.EpochConfig.EnableEpochs.RelayedTransactionsV3FixESDTTransferEnableEpoch = providedActivationEpoch + cfg.RoundConfig.RoundActivations = map[string]config.ActivationRoundByName{ + "DisableAsyncCallV1": { + Round: "9999999", + }, + "SupernovaEnableRound": { + Round: "0", + }, + } + } + + cs := startChainSimulator(t, alterConfigsFunc) + defer cs.Close() + + initialBalance := big.NewInt(0).Mul(oneEGLD, big.NewInt(10)) + relayer1, err := cs.GenerateAndMintWalletAddress(0, initialBalance) + require.NoError(t, err) + + sender, err := cs.GenerateAndMintWalletAddress(0, initialBalance) + require.NoError(t, err) + + relayer2, err := cs.GenerateAndMintWalletAddress(2, initialBalance) + require.NoError(t, err) + + // receiverB shard 2 + receiverB := "erd1wg0kx4ntn0p0sactdyvuw3zn389sq0uz3502386wtu588srnqxhqqy8uk3" + receiverBytes, _ := cs.GetNodeHandler(0).GetCoreComponents().AddressPubKeyConverter().Decode(receiverB) + + // generate one block so the minting has effect + err = cs.GenerateBlocksUntilEpochIsReached(2) + require.NoError(t, err) + relayedTx := generateRelayedV3Transaction(sender.Bytes, 0, receiverBytes, relayer1.Bytes, oneEGLD, "", uint64(100_000)) + + _, _ = cs.SendTxAndGenerateBlockTilTxIsExecuted(relayedTx, 4) + + relayedTx = generateRelayedV3Transaction(receiverBytes, 0, receiverBytes, relayer2.Bytes, big.NewInt(0), "", uint64(100_000)) + result, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(relayedTx, maxNumOfBlocksToGenerateWhenExecutingTx) + require.NoError(t, err) + require.Equal(t, transaction.TxStatusSuccess, result.Status) +} diff --git a/integrationTests/chainSimulator/rewards/rewards_test.go b/integrationTests/chainSimulator/rewards/rewards_test.go index f298e9d9889..f207b15a1e4 100644 --- a/integrationTests/chainSimulator/rewards/rewards_test.go +++ b/integrationTests/chainSimulator/rewards/rewards_test.go @@ -9,20 +9,21 @@ import ( "os" "path" "testing" - "time" "github.com/multiversx/mx-chain-core-go/core" apiCore "github.com/multiversx/mx-chain-core-go/data/api" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" csUtils "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator" "github.com/multiversx/mx-chain-go/node/chainSimulator" "github.com/multiversx/mx-chain-go/node/chainSimulator/components/api" "github.com/multiversx/mx-chain-go/node/chainSimulator/configs" "github.com/multiversx/mx-chain-go/node/chainSimulator/dtos" "github.com/multiversx/mx-chain-go/sharding/nodesCoordinator" - "github.com/stretchr/testify/require" ) const ( @@ -37,27 +38,43 @@ func TestRewardsAfterAndromedaWithTxs(t *testing.T) { t.Skip("this is not a short test") } - startTime := time.Now().Unix() roundDurationInMillis := uint64(6000) roundsPerEpoch := core.OptionalUint64{ HasValue: true, Value: 200, } + supernovaRoundsPerEpochOpt := core.OptionalUint64{ + HasValue: true, + Value: 200, + } numOfShards := uint32(3) tempDir := t.TempDir() cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: tempDir, - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: tempDir, + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: roundDurationInMillis / 10, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpochOpt, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 10 + cfg.RoundConfig.RoundActivations = map[string]config.ActivationRoundByName{ + "DisableAsyncCallV1": { + Round: "9999999", + }, + "SupernovaEnableRound": { + Round: "1000", + }, + } + }, }) require.Nil(t, err) require.NotNil(t, cs) @@ -135,6 +152,122 @@ func TestRewardsAfterAndromedaWithTxs(t *testing.T) { require.Equal(t, rewardsPerShard[targetShardID], big.NewInt(0).Add(rewardsPerShard[core.MetachainShardId], diff)) } +func TestRewardsAfterSupernovaWithTxs(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + roundDurationInMillis := uint64(6000) + roundsPerEpoch := core.OptionalUint64{ + HasValue: true, + Value: 20, + } + + numOfShards := uint32(3) + + tempDir := t.TempDir() + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: tempDir, + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: roundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.RoundConfig.RoundActivations = map[string]config.ActivationRoundByName{ + "DisableAsyncCallV1": { + Round: "9999999", + }, + } + }, + SupernovaRoundsPerEpoch: roundsPerEpoch, + }) + require.Nil(t, err) + require.NotNil(t, cs) + defer cs.Close() + + targetEpoch := 9 + err = cs.GenerateBlocksUntilEpochIsReached(int32(targetEpoch)) + require.Nil(t, err) + + err = cs.GenerateBlocks(1) + require.Nil(t, err) + + targetShardID := uint32(0) + numTxs := 10_000 + txs := generateMoveBalance(t, cs, numTxs, targetShardID, targetShardID) + + results, err := cs.SendTxsAndGenerateBlocksTilAreExecuted(txs, 10) + require.Nil(t, err) + + blockWithTxsHash := results[0].BlockHash + blocksWithTxs, err := cs.GetNodeHandler(targetShardID).GetFacadeHandler().GetBlockByHash(blockWithTxsHash, apiCore.BlockQueryOptions{ + WithTransactions: true, + }) + require.Nil(t, err) + + prevRandSeed, _ := hex.DecodeString(blocksWithTxs.PrevRandSeed) + leader, _, err := cs.GetNodeHandler(targetShardID).GetProcessComponents().NodesCoordinator().ComputeConsensusGroup(prevRandSeed, blocksWithTxs.Round, 0, blocksWithTxs.Epoch) + require.Nil(t, err) + + nodesSetupFile := path.Join(tempDir, "config", "nodesSetup.json") + validators, err := readValidatorsAndOwners(nodesSetupFile) + require.Nil(t, err) + + err = cs.GenerateBlocks(21) + require.Nil(t, err) + + startOfEpochBlock := getLastStartOfEpochBlock(t, cs, core.MetachainShardId) + require.NotNil(t, startOfEpochBlock) + + leaderEncoded, _ := cs.GetNodeHandler(0).GetCoreComponents().ValidatorPubKeyConverter().Encode(leader.PubKey()) + leaderOwnerBlockWithTxs := validators[leaderEncoded] + + var anotherOwner string + found := false + for _, address := range validators { + if address != leaderOwnerBlockWithTxs { + anotherOwner = address + found = true + } + } + require.True(t, found) + + metachainHandler := cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler() + metaBlock, err := metachainHandler.GetBlockByNonce(startOfEpochBlock.Nonce-1, apiCore.BlockQueryOptions{ + WithTransactions: true, + }) + require.Nil(t, err) + + rewardTxForLeader := getRewardTxForAddress(metaBlock, leaderOwnerBlockWithTxs) + require.NotNil(t, rewardTxForLeader) + anotherRewardTx := getRewardTxForAddress(metaBlock, anotherOwner) + require.NotNil(t, anotherRewardTx) + + rewardTxValueLeaderWithTxs, _ := big.NewInt(0).SetString(rewardTxForLeader.Value, 10) + rewardTxValueAnotherOwner, _ := big.NewInt(0).SetString(anotherRewardTx.Value, 10) + + coordinator := cs.GetNodeHandler(0).GetProcessComponents().NodesCoordinator() + + rewardsPerShard, err := computeRewardsForShards(metaBlock, coordinator, validators) + require.Nil(t, err) + + // diff should be equal with 0.1 * moveBalanceCost * num transactions + // diff = 0.1 * move balance gas limit * gas price * num transactions + diff := big.NewInt(0).Mul(big.NewInt(moveBalanceGasLimit*0.1), big.NewInt(gasPrice)) + diff.Mul(diff, big.NewInt(int64(numTxs))) + + // check reward tx value + require.Equal(t, rewardTxValueLeaderWithTxs, big.NewInt(0).Add(rewardTxValueAnotherOwner, diff)) + + // rewards for target shard should be rewards for another shard + diff + require.Equal(t, rewardsPerShard[targetShardID], big.NewInt(0).Add(rewardsPerShard[core.MetachainShardId], diff)) +} + func getRewardTxForAddress(block *apiCore.Block, address string) *transaction.ApiTransactionResult { for _, mb := range block.MiniBlocks { for _, tx := range mb.Transactions { @@ -210,27 +343,45 @@ func TestRewardsTxsAfterAndromeda(t *testing.T) { t.Skip("this is not a short test") } - startTime := time.Now().Unix() roundDurationInMillis := uint64(6000) roundsPerEpoch := core.OptionalUint64{ HasValue: true, Value: 200, } + supernovaRoundsPerEpochOpt := core.OptionalUint64{ + HasValue: true, + Value: 2000, + } numOfShards := uint32(3) + alterConfigsFunc := func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 999999 + cfg.RoundConfig.RoundActivations = map[string]config.ActivationRoundByName{ + "DisableAsyncCallV1": { + Round: "9999999", + }, + "SupernovaEnableRound": { + Round: "9999999", + }, + } + } + tempDir := t.TempDir() cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: tempDir, - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: tempDir, + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: roundDurationInMillis / 10, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpochOpt, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + AlterConfigsFunction: alterConfigsFunc, }) require.Nil(t, err) require.NotNil(t, cs) diff --git a/integrationTests/chainSimulator/simulate/simulate_test.go b/integrationTests/chainSimulator/simulate/simulate_test.go index 4627d08b932..d7a2ff4fc6c 100644 --- a/integrationTests/chainSimulator/simulate/simulate_test.go +++ b/integrationTests/chainSimulator/simulate/simulate_test.go @@ -9,13 +9,14 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/integrationTests/vm/wasm" "github.com/multiversx/mx-chain-go/node/chainSimulator" "github.com/multiversx/mx-chain-go/node/chainSimulator/components/api" "github.com/multiversx/mx-chain-go/node/chainSimulator/configs" "github.com/multiversx/mx-chain-go/node/chainSimulator/dtos" - "github.com/stretchr/testify/require" ) const ( @@ -36,20 +37,27 @@ func TestCostScDeploy(t *testing.T) { HasValue: true, Value: 20, } + supernovaRoundsPerEpochOpt := core.OptionalUint64{ + HasValue: true, + Value: 200, + } cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpochOpt, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + GenesisTimestamp: time.Now().Unix(), + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: roundDurationInMillis / 10, + RoundsPerEpoch: roundsPerEpochOpt, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpochOpt, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { }, @@ -86,28 +94,45 @@ func TestSimulateIntraShardTxWithGuardian(t *testing.T) { t.Skip("this is not a short test") } + alterConfigsFunc := func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 999999 + cfg.RoundConfig.RoundActivations = map[string]config.ActivationRoundByName{ + "DisableAsyncCallV1": { + Round: "9999999", + }, + "SupernovaEnableRound": { + Round: "9999999", + }, + } + } + roundDurationInMillis := uint64(6000) roundsPerEpochOpt := core.OptionalUint64{ HasValue: true, Value: 20, } + supernovaRoundsPerEpochOpt := core.OptionalUint64{ + HasValue: true, + Value: 200, + } cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpochOpt, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - - }, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + GenesisTimestamp: time.Now().Unix(), + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: roundDurationInMillis / 10, + RoundsPerEpoch: roundsPerEpochOpt, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpochOpt, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: alterConfigsFunc, }) require.NoError(t, err) require.NotNil(t, cs) @@ -176,20 +201,27 @@ func TestRelayedV3(t *testing.T) { HasValue: true, Value: 20, } + supernovaRoundsPerEpochOpt := core.OptionalUint64{ + HasValue: true, + Value: 200, + } cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpochOpt, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + GenesisTimestamp: time.Now().Unix(), + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: roundDurationInMillis / 10, + RoundsPerEpoch: roundsPerEpochOpt, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpochOpt, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.SCProcessorV2EnableEpoch = 2 }, @@ -237,6 +269,7 @@ func TestRelayedV3(t *testing.T) { err = cs.ForceChangeOfEpoch() require.NoError(t, err) + tx.GasLimit = 0 // reset GasLimit so it will be completed according to the new block limits for the updated epoch cost, err = cs.GetNodeHandler(0).GetFacadeHandler().ComputeTransactionGasLimit(tx) require.NoError(t, err) require.Equal(t, uint64(855001), cost.GasUnits) diff --git a/integrationTests/chainSimulator/staking/common.go b/integrationTests/chainSimulator/staking/common.go index e9e8bee3643..a244cb5f494 100644 --- a/integrationTests/chainSimulator/staking/common.go +++ b/integrationTests/chainSimulator/staking/common.go @@ -6,12 +6,13 @@ import ( "testing" "github.com/multiversx/mx-chain-core-go/core" + "github.com/stretchr/testify/require" + chainSimulatorIntegrationTests "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator" "github.com/multiversx/mx-chain-go/node/chainSimulator/dtos" chainSimulatorProcess "github.com/multiversx/mx-chain-go/node/chainSimulator/process" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/vm" - "github.com/stretchr/testify/require" ) const ( @@ -22,7 +23,7 @@ const ( // GasLimitForUnBond the const for the gas limit value for the unBond operation GasLimitForUnBond = 12_000_000 // MaxNumOfBlockToGenerateWhenExecutingTx the const for the maximum number of block to generate when execute a transaction - MaxNumOfBlockToGenerateWhenExecutingTx = 7 + MaxNumOfBlockToGenerateWhenExecutingTx = 10 // QueuedStatus the const for the queued status of a validators QueuedStatus = "queued" @@ -37,7 +38,7 @@ const ( ) var ( - //InitialDelegationValue the variable for the initial delegation value + // InitialDelegationValue the variable for the initial delegation value InitialDelegationValue = big.NewInt(0).Mul(chainSimulatorIntegrationTests.OneEGLD, big.NewInt(1250)) ) diff --git a/integrationTests/chainSimulator/staking/jail/jail_test.go b/integrationTests/chainSimulator/staking/jail/jail_test.go index 9f431d1f924..bd43dfaa9b7 100644 --- a/integrationTests/chainSimulator/staking/jail/jail_test.go +++ b/integrationTests/chainSimulator/staking/jail/jail_test.go @@ -5,7 +5,6 @@ import ( "fmt" "math/big" "testing" - "time" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" @@ -56,26 +55,31 @@ func TestChainSimulator_ValidatorJailUnJail(t *testing.T) { } func testChainSimulatorJailAndUnJail(t *testing.T, targetEpoch int32, nodeStatusAfterUnJail string) { - startTime := time.Now().Unix() roundDurationInMillis := uint64(6000) roundsPerEpoch := core.OptionalUint64{ HasValue: true, Value: 20, } + supernovaRoundsPerEpochOpt := core.OptionalUint64{ + HasValue: true, + Value: 200, + } numOfShards := uint32(3) cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 4, - MetaChainMinNodes: 4, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: roundDurationInMillis / 10, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpochOpt, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 4, + MetaChainMinNodes: 4, AlterConfigsFunction: func(cfg *config.Configs) { configs.SetStakingV4ActivationEpochs(cfg, stakingV4JailUnJailStep1EnableEpoch) cfg.EpochConfig.EnableEpochs.AndromedaEnableEpoch = 100 @@ -160,32 +164,39 @@ func TestChainSimulator_FromQueueToAuctionList(t *testing.T) { t.Skip("this is not a short test") } - startTime := time.Now().Unix() roundDurationInMillis := uint64(6000) roundsPerEpoch := core.OptionalUint64{ HasValue: true, - Value: 20, + Value: 40, + } + supernovaRoundsPerEpochOpt := core.OptionalUint64{ + HasValue: true, + Value: 400, } numOfShards := uint32(3) cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 4, - MetaChainMinNodes: 4, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: roundDurationInMillis / 10, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpochOpt, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 4, + MetaChainMinNodes: 4, AlterConfigsFunction: func(cfg *config.Configs) { configs.SetStakingV4ActivationEpochs(cfg, stakingV4JailUnJailStep1EnableEpoch) configs.SetQuickJailRatingConfig(cfg) newNumNodes := cfg.SystemSCConfig.StakingSystemSCConfig.MaxNumberOfNodesForStake + 1 configs.SetMaxNumberOfNodesInConfigs(cfg, uint32(newNumNodes), 0, numOfShards) + + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 100 }, }) require.Nil(t, err) @@ -257,32 +268,42 @@ func TestChainSimulator_FromQueueToAuctionList(t *testing.T) { } func TestJailNodes(t *testing.T) { - startTime := time.Now().Unix() roundDurationInMillis := uint64(6000) roundsPerEpoch := core.OptionalUint64{ HasValue: true, - Value: 20, + Value: 40, + } + supernovaRoundsPerEpochOpt := core.OptionalUint64{ + HasValue: true, + Value: 50, } numOfShards := uint32(3) cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 4, - MetaChainMinNodes: 4, - NumNodesWaitingListMeta: 1, - NumNodesWaitingListShard: 1, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: roundDurationInMillis / 10, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpochOpt, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 4, + MetaChainMinNodes: 4, + NumNodesWaitingListMeta: 1, + NumNodesWaitingListShard: 1, AlterConfigsFunction: func(cfg *config.Configs) { configs.SetQuickJailRatingConfig(cfg) newNumNodes := cfg.SystemSCConfig.StakingSystemSCConfig.MaxNumberOfNodesForStake + 1 configs.SetMaxNumberOfNodesInConfigs(cfg, uint32(newNumNodes), 0, numOfShards) + cfg.RoundConfig.RoundActivations = map[string]config.ActivationRoundByName{ + "DisableAsyncCallV1": { + Round: "9999999", + }, + } }, }) require.Nil(t, err) @@ -299,10 +320,8 @@ func TestJailNodes(t *testing.T) { walletAddress, err := cs.GenerateAndMintWalletAddress(core.AllShardId, mintValue) require.Nil(t, err) - for i := 0; i < 10; i++ { - err = cs.ForceChangeOfEpoch() - require.Nil(t, err) - } + err = cs.GenerateBlocksUntilEpochIsReached(10) + require.Nil(t, err) txDataField := fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) txStake := chainSimulatorIntegrationTests.GenerateTransaction(walletAddress.Bytes, 0, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.MinimumStakeValue, txDataField, staking.GasLimitForStakeOperation) @@ -310,7 +329,7 @@ func TestJailNodes(t *testing.T) { require.Nil(t, err) require.NotNil(t, stakeTx) - err = cs.GenerateBlocks(200) + err = cs.GenerateBlocks(250) require.Nil(t, err) decodedBLSKey0, _ := hex.DecodeString(blsKeys[0]) diff --git a/integrationTests/chainSimulator/staking/stake/common.go b/integrationTests/chainSimulator/staking/stake/common.go new file mode 100644 index 00000000000..291c93094dc --- /dev/null +++ b/integrationTests/chainSimulator/staking/stake/common.go @@ -0,0 +1,138 @@ +package stake + +import ( + "encoding/hex" + "math/big" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator" + "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator/staking" + processChainSimulator "github.com/multiversx/mx-chain-go/node/chainSimulator/process" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/vm" + "github.com/multiversx/mx-chain-logger-go" + "github.com/stretchr/testify/require" +) + +const ( + DefaultPathToInitialConfig = "../../../../../cmd/node/config/" +) + +var ( + RoundDurationInMillis = uint64(6000) + SupernovaRoundDurationInMillis = uint64(600) + RoundsPerEpoch = core.OptionalUint64{ + HasValue: true, + Value: 20, + } + SupernovaRoundsPerEpoch = core.OptionalUint64{ + HasValue: true, + Value: 20, + } + + Log = logger.GetOrCreate("integrationTests/chainSimulator") +) + +func TestBLSKeyStaked(t *testing.T, + metachainNode processChainSimulator.NodeHandler, + blsKey string, +) { + decodedBLSKey, _ := hex.DecodeString(blsKey) + err := metachainNode.GetProcessComponents().ValidatorsProvider().ForceUpdate() + require.Nil(t, err) + + validatorStatistics, err := metachainNode.GetFacadeHandler().ValidatorStatisticsApi() + require.Nil(t, err) + + activationEpoch := metachainNode.GetCoreComponents().EnableEpochsHandler().GetActivationEpoch(common.StakingV4Step1Flag) + if activationEpoch <= metachainNode.GetCoreComponents().EnableEpochsHandler().GetCurrentEpoch() { + require.Equal(t, staking.StakedStatus, staking.GetBLSKeyStatus(t, metachainNode, decodedBLSKey)) + return + } + + // in staking ph 2/3.5 we do not find the bls key on the validator statistics + _, found := validatorStatistics[blsKey] + require.False(t, found) + require.Equal(t, staking.QueuedStatus, staking.GetBLSKeyStatus(t, metachainNode, decodedBLSKey)) +} + +func CheckExpectedStakedValue(t *testing.T, metachainNode processChainSimulator.NodeHandler, blsKey []byte, expectedValue int64) { + totalStaked := GetTotalStaked(t, metachainNode, blsKey) + + expectedStaked := big.NewInt(expectedValue) + expectedStaked = expectedStaked.Mul(chainSimulator.OneEGLD, expectedStaked) + require.Equal(t, expectedStaked.String(), string(totalStaked)) +} + +func GetTotalStaked(t *testing.T, metachainNode processChainSimulator.NodeHandler, blsKey []byte) []byte { + scQuery := &process.SCQuery{ + ScAddress: vm.ValidatorSCAddress, + FuncName: "getTotalStaked", + CallerAddr: vm.ValidatorSCAddress, + CallValue: big.NewInt(0), + Arguments: [][]byte{blsKey}, + } + result, _, err := metachainNode.GetFacadeHandler().ExecuteSCQuery(scQuery) + require.Nil(t, err) + require.Equal(t, chainSimulator.OkReturnCode, result.ReturnCode) + + return result.ReturnData[0] +} + +func GetQualifiedAndUnqualifiedNodes(t *testing.T, metachainNode processChainSimulator.NodeHandler) ([]string, []string) { + err := metachainNode.GetProcessComponents().ValidatorsProvider().ForceUpdate() + require.Nil(t, err) + auctionList, err := metachainNode.GetProcessComponents().ValidatorsProvider().GetAuctionList() + require.Nil(t, err) + + qualified := make([]string, 0) + unQualified := make([]string, 0) + + for _, auctionOwnerData := range auctionList { + for _, auctionNode := range auctionOwnerData.Nodes { + if auctionNode.Qualified { + qualified = append(qualified, auctionNode.BlsKey) + } else { + unQualified = append(unQualified, auctionNode.BlsKey) + } + } + } + + return qualified, unQualified +} + +func GetUnStakedTokensList(t *testing.T, metachainNode processChainSimulator.NodeHandler, blsKey []byte) []byte { + scQuery := &process.SCQuery{ + ScAddress: vm.ValidatorSCAddress, + FuncName: "getUnStakedTokensList", + CallerAddr: vm.ValidatorSCAddress, + CallValue: big.NewInt(0), + Arguments: [][]byte{blsKey}, + } + result, _, err := metachainNode.GetFacadeHandler().ExecuteSCQuery(scQuery) + require.Nil(t, err) + require.Equal(t, chainSimulator.OkReturnCode, result.ReturnCode) + + return result.ReturnData[0] +} + +func CheckOneOfTheNodesIsUnstaked(t *testing.T, + metachainNode processChainSimulator.NodeHandler, + blsKeys []string, +) { + decodedBLSKey0, _ := hex.DecodeString(blsKeys[0]) + keyStatus0 := staking.GetBLSKeyStatus(t, metachainNode, decodedBLSKey0) + Log.Info("Key info", "key", blsKeys[0], "status", keyStatus0) + + isNotStaked0 := keyStatus0 == staking.UnStakedStatus + + decodedBLSKey1, _ := hex.DecodeString(blsKeys[1]) + keyStatus1 := staking.GetBLSKeyStatus(t, metachainNode, decodedBLSKey1) + Log.Info("Key info", "key", blsKeys[1], "status", keyStatus1) + + isNotStaked1 := keyStatus1 == staking.UnStakedStatus + + require.True(t, isNotStaked0 != isNotStaked1) +} diff --git a/integrationTests/chainSimulator/staking/stake/simpleStake_test.go b/integrationTests/chainSimulator/staking/stake/simpleStake/simpleStake_test.go similarity index 86% rename from integrationTests/chainSimulator/staking/stake/simpleStake_test.go rename to integrationTests/chainSimulator/staking/stake/simpleStake/simpleStake_test.go index bfc9f3c11b6..5ffb83a30c6 100644 --- a/integrationTests/chainSimulator/staking/stake/simpleStake_test.go +++ b/integrationTests/chainSimulator/staking/stake/simpleStake/simpleStake_test.go @@ -1,20 +1,19 @@ -package stake +package simpleStake import ( "encoding/hex" "fmt" "math/big" "testing" - "time" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" chainSimulatorIntegrationTests "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator" "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator/staking" + "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator/staking/stake" "github.com/multiversx/mx-chain-go/node/chainSimulator" "github.com/multiversx/mx-chain-go/node/chainSimulator/components/api" "github.com/multiversx/mx-chain-go/node/chainSimulator/configs" - "github.com/multiversx/mx-chain-go/node/chainSimulator/process" "github.com/multiversx/mx-chain-go/vm" "github.com/multiversx/mx-chain-core-go/core" @@ -56,28 +55,33 @@ func testChainSimulatorSimpleStake(t *testing.T, targetEpoch int32, nodesStatus if testing.Short() { t.Skip("this is not a short test") } - startTime := time.Now().Unix() roundDurationInMillis := uint64(6000) roundsPerEpoch := core.OptionalUint64{ HasValue: true, Value: 20, } + supernovaRoundsPerEpochOpt := core.OptionalUint64{ + HasValue: true, + Value: 200, + } numOfShards := uint32(3) cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: roundDurationInMillis / 10, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpochOpt, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { configs.SetStakingV4ActivationEpochs(cfg, 2) }, @@ -162,16 +166,21 @@ func TestChainSimulator_StakingV4Step2APICalls(t *testing.T) { stakingV4Step3Epoch := uint32(4) cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: uint64(6000), + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: uint64(6000), + SupernovaRoundDurationInMillis: uint64(600), RoundsPerEpoch: core.OptionalUint64{ HasValue: true, Value: 30, }, + SupernovaRoundsPerEpoch: core.OptionalUint64{ + HasValue: true, + Value: 300, + }, ApiInterface: api.NewNoApiInterface(), MinNodesPerShard: 4, MetaChainMinNodes: 4, @@ -188,6 +197,8 @@ func TestChainSimulator_StakingV4Step2APICalls(t *testing.T) { cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = stakingV4Step3Epoch cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].MaxNumNodes = 24 cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].NodesToShufflePerShard = 2 + + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 100 }, }) require.Nil(t, err) @@ -269,30 +280,8 @@ func TestChainSimulator_StakingV4Step2APICalls(t *testing.T) { err = cs.GenerateBlocks(2) require.Nil(t, err) - qualified, unQualified := getQualifiedAndUnqualifiedNodes(t, metachainNode) + qualified, unQualified := stake.GetQualifiedAndUnqualifiedNodes(t, metachainNode) require.Equal(t, 8, len(qualified)) require.Equal(t, 1, len(unQualified)) } } - -func getQualifiedAndUnqualifiedNodes(t *testing.T, metachainNode process.NodeHandler) ([]string, []string) { - err := metachainNode.GetProcessComponents().ValidatorsProvider().ForceUpdate() - require.Nil(t, err) - auctionList, err := metachainNode.GetProcessComponents().ValidatorsProvider().GetAuctionList() - require.Nil(t, err) - - qualified := make([]string, 0) - unQualified := make([]string, 0) - - for _, auctionOwnerData := range auctionList { - for _, auctionNode := range auctionOwnerData.Nodes { - if auctionNode.Qualified { - qualified = append(qualified, auctionNode.BlsKey) - } else { - unQualified = append(unQualified, auctionNode.BlsKey) - } - } - } - - return qualified, unQualified -} diff --git a/integrationTests/chainSimulator/staking/stake/stakeAndUnStakeSet1/stakeAndUnStake_test.go b/integrationTests/chainSimulator/staking/stake/stakeAndUnStakeSet1/stakeAndUnStake_test.go new file mode 100644 index 00000000000..619804a5642 --- /dev/null +++ b/integrationTests/chainSimulator/staking/stake/stakeAndUnStakeSet1/stakeAndUnStake_test.go @@ -0,0 +1,615 @@ +package stakeAndUnStakeSet1 + +import ( + "encoding/hex" + "fmt" + "math/big" + "testing" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" + chainSimulatorIntegrationTests "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator" + "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator/staking" + "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator/staking/stake" + "github.com/multiversx/mx-chain-go/node/chainSimulator" + "github.com/multiversx/mx-chain-go/node/chainSimulator/components/api" + "github.com/multiversx/mx-chain-go/node/chainSimulator/configs" + "github.com/multiversx/mx-chain-go/node/chainSimulator/dtos" + "github.com/multiversx/mx-chain-go/vm" + + "github.com/multiversx/mx-chain-core-go/core" + coreAPI "github.com/multiversx/mx-chain-core-go/data/api" + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-core-go/data/validator" + "github.com/stretchr/testify/require" +) + +// TODO scenarios +// Make a staking provider with max num of nodes +// DO a merge transaction + +// Test scenario +// 1. Add a new validator private key in the multi key handler +// 2. Do a stake transaction for the validator key +// 3. Do an unstake transaction (to make a place for the new validator) +// 4. Check if the new validator has generated rewards +func TestChainSimulator_AddValidatorKey(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + numOfShards := uint32(3) + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 0, + NumNodesWaitingListShard: 0, + AlterConfigsFunction: func(cfg *config.Configs) { + newNumNodes := cfg.SystemSCConfig.StakingSystemSCConfig.MaxNumberOfNodesForStake + 8 // 8 nodes until new nodes will be placed on queue + configs.SetMaxNumberOfNodesInConfigs(cfg, uint32(newNumNodes), 0, numOfShards) + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + err = cs.GenerateBlocks(30) + require.Nil(t, err) + + // Step 1 --- add a new validator key in the chain simulator + privateKey, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(1) + require.Nil(t, err) + + err = cs.AddValidatorKeys(privateKey) + require.Nil(t, err) + + newValidatorOwner := "erd1l6xt0rqlyzw56a3k8xwwshq2dcjwy3q9cppucvqsmdyw8r98dz3sae0kxl" + newValidatorOwnerBytes, _ := cs.GetNodeHandler(0).GetCoreComponents().AddressPubKeyConverter().Decode(newValidatorOwner) + rcv := "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + rcvAddrBytes, _ := cs.GetNodeHandler(0).GetCoreComponents().AddressPubKeyConverter().Decode(rcv) + + // Step 2 --- set an initial balance for the address that will initialize all the transactions + err = cs.SetStateMultiple([]*dtos.AddressState{ + { + Address: "erd1l6xt0rqlyzw56a3k8xwwshq2dcjwy3q9cppucvqsmdyw8r98dz3sae0kxl", + Balance: "10000000000000000000000", + }, + }) + require.Nil(t, err) + + err = cs.GenerateBlocks(1) + require.Nil(t, err) + + // Step 3 --- generate and send a stake transaction with the BLS key of the validator key that was added at step 1 + stakeValue, _ := big.NewInt(0).SetString("2500000000000000000000", 10) + tx := &transaction.Transaction{ + Nonce: 0, + Value: stakeValue, + SndAddr: newValidatorOwnerBytes, + RcvAddr: rcvAddrBytes, + Data: []byte(fmt.Sprintf("stake@01@%s@010101", blsKeys[0])), + GasLimit: 50_000_000, + GasPrice: 1000000000, + Signature: []byte("dummy"), + ChainID: []byte(configs.ChainID), + Version: 1, + } + stakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, stakeTx) + + shardIDValidatorOwner := cs.GetNodeHandler(0).GetShardCoordinator().ComputeId(newValidatorOwnerBytes) + accountValidatorOwner, _, err := cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(newValidatorOwner, coreAPI.AccountQueryOptions{}) + require.Nil(t, err) + balanceBeforeActiveValidator := accountValidatorOwner.Balance + + // Step 5 --- create an unStake transaction with the bls key of an initial validator and execute the transaction to make place for the validator that was added at step 3 + firstValidatorKey, err := cs.GetValidatorPrivateKeys()[0].GeneratePublic().ToByteArray() + require.Nil(t, err) + + initialAddressWithValidators := cs.GetInitialWalletKeys().StakeWallets[0].Address + shardID := cs.GetNodeHandler(0).GetShardCoordinator().ComputeId(initialAddressWithValidators.Bytes) + initialAccount, _, err := cs.GetNodeHandler(shardID).GetFacadeHandler().GetAccount(initialAddressWithValidators.Bech32, coreAPI.AccountQueryOptions{}) + require.Nil(t, err) + tx = &transaction.Transaction{ + Nonce: initialAccount.Nonce, + Value: big.NewInt(0), + SndAddr: initialAddressWithValidators.Bytes, + RcvAddr: rcvAddrBytes, + Data: []byte(fmt.Sprintf("unStake@%s", hex.EncodeToString(firstValidatorKey))), + GasLimit: 50_000_000, + GasPrice: 1000000000, + Signature: []byte("dummy"), + ChainID: []byte(configs.ChainID), + Version: 1, + } + _, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + + // Step 6 --- generate 8 epochs to get rewards + err = cs.GenerateBlocksUntilEpochIsReached(8) + require.Nil(t, err) + + metachainNode := cs.GetNodeHandler(core.MetachainShardId) + err = cs.ForceResetValidatorStatisticsCache() + require.Nil(t, err) + validatorStatistics, err := metachainNode.GetFacadeHandler().ValidatorStatisticsApi() + require.Nil(t, err) + checkValidatorsRating(t, validatorStatistics) + + accountValidatorOwner, _, err = cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(newValidatorOwner, coreAPI.AccountQueryOptions{}) + require.Nil(t, err) + balanceAfterActiveValidator := accountValidatorOwner.Balance + + stake.Log.Info("balance before validator", "value", balanceBeforeActiveValidator) + stake.Log.Info("balance after validator", "value", balanceAfterActiveValidator) + + balanceBeforeBig, _ := big.NewInt(0).SetString(balanceBeforeActiveValidator, 10) + balanceAfterBig, _ := big.NewInt(0).SetString(balanceAfterActiveValidator, 10) + diff := balanceAfterBig.Sub(balanceAfterBig, balanceBeforeBig) + stake.Log.Info("difference", "value", diff.String()) + + // Step 7 --- check the balance of the validator owner has been increased + require.True(t, diff.Cmp(big.NewInt(0)) > 0) +} + +func TestChainSimulator_AddANewValidatorAfterStakingV4(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + numOfShards := uint32(3) + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 100, + MetaChainMinNodes: 100, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.SystemSCConfig.StakingSystemSCConfig.NodeLimitPercentage = 1 + cfg.GeneralConfig.ValidatorStatistics.CacheRefreshIntervalInSec = 1 + eligibleNodes := cfg.SystemSCConfig.StakingSystemSCConfig.MaxNumberOfNodesForStake + // 8 nodes until new nodes will be placed on queue + waitingNodes := uint32(8) + configs.SetMaxNumberOfNodesInConfigs(cfg, uint32(eligibleNodes), waitingNodes, numOfShards) + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + err = cs.GenerateBlocks(150) + require.Nil(t, err) + + // Step 1 --- add a new validator key in the chain simulator + numOfNodes := 20 + validatorSecretKeysBytes, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(numOfNodes) + require.Nil(t, err) + err = cs.AddValidatorKeys(validatorSecretKeysBytes) + require.Nil(t, err) + + newValidatorOwner := "erd1l6xt0rqlyzw56a3k8xwwshq2dcjwy3q9cppucvqsmdyw8r98dz3sae0kxl" + newValidatorOwnerBytes, _ := cs.GetNodeHandler(0).GetCoreComponents().AddressPubKeyConverter().Decode(newValidatorOwner) + rcv := "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + rcvAddrBytes, _ := cs.GetNodeHandler(0).GetCoreComponents().AddressPubKeyConverter().Decode(rcv) + + // Step 2 --- set an initial balance for the address that will initialize all the transactions + err = cs.SetStateMultiple([]*dtos.AddressState{ + { + Address: "erd1l6xt0rqlyzw56a3k8xwwshq2dcjwy3q9cppucvqsmdyw8r98dz3sae0kxl", + Balance: "1000000000000000000000000", + }, + }) + require.Nil(t, err) + + err = cs.GenerateBlocks(1) + require.Nil(t, err) + + // Step 3 --- generate and send a stake transaction with the BLS keys of the validators key that were added at step 1 + validatorData := "" + for _, blsKey := range blsKeys { + validatorData += fmt.Sprintf("@%s@010101", blsKey) + } + + numOfNodesHex := hex.EncodeToString(big.NewInt(int64(numOfNodes)).Bytes()) + stakeValue, _ := big.NewInt(0).SetString("51000000000000000000000", 10) + tx := &transaction.Transaction{ + Nonce: 0, + Value: stakeValue, + SndAddr: newValidatorOwnerBytes, + RcvAddr: rcvAddrBytes, + Data: []byte(fmt.Sprintf("stake@%s%s", numOfNodesHex, validatorData)), + GasLimit: 500_000_000, + GasPrice: 1000000000, + Signature: []byte("dummy"), + ChainID: []byte(configs.ChainID), + Version: 1, + } + + txFromNetwork, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txFromNetwork) + + err = cs.GenerateBlocks(1) + require.Nil(t, err) + + metachainNode := cs.GetNodeHandler(core.MetachainShardId) + err = cs.ForceResetValidatorStatisticsCache() + require.Nil(t, err) + results, err := metachainNode.GetFacadeHandler().AuctionListApi() + require.Nil(t, err) + require.Equal(t, newValidatorOwner, results[0].Owner) + require.Equal(t, 20, len(results[0].Nodes)) + checkTotalQualified(t, results, 8) + + err = cs.GenerateBlocks(100) + require.Nil(t, err) + + results, err = cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().AuctionListApi() + require.Nil(t, err) + checkTotalQualified(t, results, 0) +} + +// Internal test scenario #4 #5 #6 +// do stake +// do unStake +// do unBondNodes +// do unBondTokens +func TestChainSimulatorStakeUnStakeUnBond(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + t.Run("staking ph 4 is not active", func(t *testing.T) { + testStakeUnStakeUnBond(t, 1) + }) + + t.Run("staking ph 4 step 1 active", func(t *testing.T) { + testStakeUnStakeUnBond(t, 4) + }) + + t.Run("staking ph 4 step 2 active", func(t *testing.T) { + testStakeUnStakeUnBond(t, 5) + }) + + t.Run("staking ph 4 step 3 active", func(t *testing.T) { + testStakeUnStakeUnBond(t, 6) + }) +} + +func testStakeUnStakeUnBond(t *testing.T, targetEpoch int32) { + numOfShards := uint32(3) + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriod = 1 + cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodSupernova = 1 // TODO: add custom tests for supernova + cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 1 + newNumNodes := cfg.SystemSCConfig.StakingSystemSCConfig.MaxNumberOfNodesForStake + 10 + configs.SetMaxNumberOfNodesInConfigs(cfg, uint32(newNumNodes), 0, numOfShards) + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + err = cs.GenerateBlocksUntilEpochIsReached(targetEpoch) + require.Nil(t, err) + + privateKeys, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(1) + require.Nil(t, err) + err = cs.AddValidatorKeys(privateKeys) + require.Nil(t, err) + + mintValue := big.NewInt(0).Mul(chainSimulatorIntegrationTests.OneEGLD, big.NewInt(2600)) + walletAddressShardID := uint32(0) + walletAddress, err := cs.GenerateAndMintWalletAddress(walletAddressShardID, mintValue) + require.Nil(t, err) + + err = cs.GenerateBlocks(1) + require.Nil(t, err) + + txDataField := fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) + txStake := chainSimulatorIntegrationTests.GenerateTransaction(walletAddress.Bytes, 0, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.MinimumStakeValue, txDataField, staking.GasLimitForStakeOperation) + stakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, stakeTx) + + metachainNode := cs.GetNodeHandler(core.MetachainShardId) + bls0, _ := hex.DecodeString(blsKeys[0]) + blsKeyStatus := staking.GetBLSKeyStatus(t, metachainNode, bls0) + require.Equal(t, "staked", blsKeyStatus) + + // do unStake + txUnStake := chainSimulatorIntegrationTests.GenerateTransaction(walletAddress.Bytes, 1, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, fmt.Sprintf("unStake@%s", blsKeys[0]), staking.GasLimitForStakeOperation) + unStakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, unStakeTx) + + blsKeyStatus = staking.GetBLSKeyStatus(t, metachainNode, bls0) + require.Equal(t, "unStaked", blsKeyStatus) + + err = cs.GenerateBlocksUntilEpochIsReached(targetEpoch + 1) + require.Nil(t, err) + + // do unBond + txUnBond := chainSimulatorIntegrationTests.GenerateTransaction(walletAddress.Bytes, 2, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, fmt.Sprintf("unBondNodes@%s", blsKeys[0]), staking.GasLimitForStakeOperation) + unBondTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnBond, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, unBondTx) + + // do claim + txClaim := chainSimulatorIntegrationTests.GenerateTransaction(walletAddress.Bytes, 3, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, "unBondTokens", staking.GasLimitForStakeOperation) + claimTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txClaim, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, claimTx) + + err = cs.GenerateBlocks(5) + require.Nil(t, err) + + // check tokens are in the wallet balance + walletAccount, _, err := cs.GetNodeHandler(walletAddressShardID).GetFacadeHandler().GetAccount(walletAddress.Bech32, coreAPI.AccountQueryOptions{}) + require.Nil(t, err) + walletBalanceBig, _ := big.NewInt(0).SetString(walletAccount.Balance, 10) + require.True(t, walletBalanceBig.Cmp(chainSimulatorIntegrationTests.MinimumStakeValue) > 0) +} + +func checkTotalQualified(t *testing.T, auctionList []*common.AuctionListValidatorAPIResponse, expected int) { + totalQualified := 0 + for _, res := range auctionList { + for _, node := range res.Nodes { + if node.Qualified { + totalQualified++ + } + } + } + require.Equal(t, expected, totalQualified) +} + +func checkValidatorsRating(t *testing.T, validatorStatistics map[string]*validator.ValidatorStatistics) { + countRatingIncreased := 0 + for _, validatorInfo := range validatorStatistics { + validatorSignedAtLeastOneBlock := validatorInfo.NumValidatorSuccess > 0 || validatorInfo.NumLeaderSuccess > 0 + if !validatorSignedAtLeastOneBlock { + continue + } + countRatingIncreased++ + require.Greater(t, validatorInfo.TempRating, validatorInfo.Rating) + } + require.Greater(t, countRatingIncreased, 0) +} + +// Test description +// Stake funds - happy flow +// +// Preconditions: have an account with egld and 2 staked nodes (2500 stake per node) - directly staked, and no unstake +// +// 1. Check the stake amount for the owner of the staked nodes with the vmquery "getTotalStaked", and the account current EGLD balance +// 2. Create from the owner of staked nodes a transaction to stake 1 EGLD and send it to the network +// 3. Check the outcome of the TX & verify new stake state with vmquery + +// Internal test scenario #24 +func TestChainSimulator_DirectStakingNodes_StakeFunds(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + t.Run("staking ph 4 is not active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 100 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 101 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 102 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 102 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedNodesStakingFunds(t, cs, 1) + }) + + t.Run("staking ph 4 step 1 is active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedNodesStakingFunds(t, cs, 2) + }) + + t.Run("staking ph 4 step 2 is active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedNodesStakingFunds(t, cs, 3) + }) + + t.Run("staking ph 4 step 3 is active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedNodesStakingFunds(t, cs, 4) + }) +} + +func testChainSimulatorDirectStakedNodesStakingFunds(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator, targetEpoch int32) { + err := cs.GenerateBlocksUntilEpochIsReached(targetEpoch) + require.Nil(t, err) + + stake.Log.Info("Preconditions. Have an account with 2 staked nodes") + privateKeys, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(2) + require.Nil(t, err) + + err = cs.AddValidatorKeys(privateKeys) + require.Nil(t, err) + metachainNode := cs.GetNodeHandler(core.MetachainShardId) + + mintValue := big.NewInt(5010) + mintValue = mintValue.Mul(chainSimulatorIntegrationTests.OneEGLD, mintValue) + + validatorOwner, err := cs.GenerateAndMintWalletAddress(core.AllShardId, mintValue) + require.Nil(t, err) + + err = cs.GenerateBlocks(1) + require.Nil(t, err) + + stakeValue := big.NewInt(0).Set(chainSimulatorIntegrationTests.MinimumStakeValue) + txDataField := fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) + txStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 0, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) + stakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, stakeTx) + + err = cs.GenerateBlocks(2) // allow the metachain to finalize the block that contains the staking of the node + require.Nil(t, err) + + stakeValue = big.NewInt(0).Set(chainSimulatorIntegrationTests.MinimumStakeValue) + txDataField = fmt.Sprintf("stake@01@%s@%s", blsKeys[1], staking.MockBLSSignature) + txStake = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 1, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) + stakeTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, stakeTx) + + err = cs.GenerateBlocks(2) // allow the metachain to finalize the block that contains the staking of the node + require.Nil(t, err) + + stake.Log.Info("Step 1. Check the stake amount for the owner of the staked nodes") + stake.CheckExpectedStakedValue(t, metachainNode, validatorOwner.Bytes, 5000) + + stake.Log.Info("Step 2. Create from the owner of the staked nodes a tx to stake 1 EGLD") + + stakeValue = big.NewInt(0).Mul(chainSimulatorIntegrationTests.OneEGLD, big.NewInt(1)) + txDataField = fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) + txStake = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 2, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) + stakeTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, stakeTx) + + err = cs.GenerateBlocks(2) // allow the metachain to finalize the block that contains the staking of the node + require.Nil(t, err) + + stake.Log.Info("Step 3. Check the stake amount for the owner of the staked nodes") + stake.CheckExpectedStakedValue(t, metachainNode, validatorOwner.Bytes, 5001) +} diff --git a/integrationTests/chainSimulator/staking/stake/stakeAndUnStakeSet2/stakeAndUnStake_test.go b/integrationTests/chainSimulator/staking/stake/stakeAndUnStakeSet2/stakeAndUnStake_test.go new file mode 100644 index 00000000000..7f6362ad654 --- /dev/null +++ b/integrationTests/chainSimulator/staking/stake/stakeAndUnStakeSet2/stakeAndUnStake_test.go @@ -0,0 +1,731 @@ +package stakeAndUnStakeSet2 + +import ( + "encoding/hex" + "fmt" + "math/big" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + coreAPI "github.com/multiversx/mx-chain-core-go/data/api" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/config" + chainSimulatorIntegrationTests "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator" + "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator/staking" + "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator/staking/stake" + "github.com/multiversx/mx-chain-go/node/chainSimulator" + "github.com/multiversx/mx-chain-go/node/chainSimulator/components/api" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/vm" +) + +// Test description: +// Unstake funds with deactivation of node if below 2500 -> the rest of funds are distributed as topup at epoch change +// +// Internal test scenario #26 +func TestChainSimulator_DirectStakingNodes_UnstakeFundsWithDeactivation(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + // Test Steps + // 1. Check the stake amount and number of nodes for the owner of the staked nodes with the vmquery "getTotalStaked", and the account current EGLD balance + // 2. Create from the owner of staked nodes a transaction to unstake 10 EGLD and send it to the network + // 3. Check the outcome of the TX & verify new stake state with vmquery "getTotalStaked" and "getUnStakedTokensList" + // 4. Wait for change of epoch and check the outcome + + t.Run("staking ph 4 is not active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 100 + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 100 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 101 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 102 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 102 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedUnstakeFundsWithDeactivation(t, cs, 1) + }) + + t.Run("staking ph 4 step 1 is active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + cfg.SystemSCConfig.StakingSystemSCConfig.NodeLimitPercentage = 1 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedUnstakeFundsWithDeactivation(t, cs, 2) + }) + + t.Run("staking ph 4 step 2 is active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + cfg.SystemSCConfig.StakingSystemSCConfig.NodeLimitPercentage = 1 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedUnstakeFundsWithDeactivation(t, cs, 3) + }) + + t.Run("staking ph 4 step 3 is active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + cfg.SystemSCConfig.StakingSystemSCConfig.NodeLimitPercentage = 1 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedUnstakeFundsWithDeactivation(t, cs, 4) + }) +} + +func testChainSimulatorDirectStakedUnstakeFundsWithDeactivation(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator, targetEpoch int32) { + err := cs.GenerateBlocksUntilEpochIsReached(targetEpoch) + require.Nil(t, err) + + privateKeys, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(2) + require.Nil(t, err) + + err = cs.AddValidatorKeys(privateKeys) + require.Nil(t, err) + metachainNode := cs.GetNodeHandler(core.MetachainShardId) + + mintValue := big.NewInt(5010) + mintValue = mintValue.Mul(chainSimulatorIntegrationTests.OneEGLD, mintValue) + + validatorOwner, err := cs.GenerateAndMintWalletAddress(core.AllShardId, mintValue) + require.Nil(t, err) + + err = cs.GenerateBlocks(1) + require.Nil(t, err) + + stakeValue := big.NewInt(0).Set(chainSimulatorIntegrationTests.MinimumStakeValue) + txDataField := fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) + txStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 0, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) + stakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, stakeTx) + + err = cs.GenerateBlocks(2) // allow the metachain to finalize the block that contains the staking of the node + require.Nil(t, err) + + stake.TestBLSKeyStaked(t, metachainNode, blsKeys[0]) + + stakeValue = big.NewInt(0).Set(chainSimulatorIntegrationTests.MinimumStakeValue) + txDataField = fmt.Sprintf("stake@01@%s@%s", blsKeys[1], staking.MockBLSSignature) + txStake = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 1, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) + stakeTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, stakeTx) + + err = cs.GenerateBlocks(2) // allow the metachain to finalize the block that contains the staking of the node + require.Nil(t, err) + + stake.TestBLSKeyStaked(t, metachainNode, blsKeys[1]) + + stake.Log.Info("Step 1. Check the stake amount for the owner of the staked nodes") + stake.CheckExpectedStakedValue(t, metachainNode, validatorOwner.Bytes, 5000) + + stake.Log.Info("Step 2. Create from the owner of staked nodes a transaction to unstake 10 EGLD and send it to the network") + + unStakeValue := big.NewInt(10) + unStakeValue = unStakeValue.Mul(chainSimulatorIntegrationTests.OneEGLD, unStakeValue) + txDataField = fmt.Sprintf("unStakeTokens@%s", hex.EncodeToString(unStakeValue.Bytes())) + txUnStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 2, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForStakeOperation) + unStakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, unStakeTx) + + err = cs.GenerateBlocks(2) + require.Nil(t, err) + + stake.Log.Info("Step 3. Check the outcome of the TX & verify new stake state with vmquery getTotalStaked and getUnStakedTokensList") + stake.CheckExpectedStakedValue(t, metachainNode, validatorOwner.Bytes, 4990) + + unStakedTokensAmount := stake.GetUnStakedTokensList(t, metachainNode, validatorOwner.Bytes) + + expectedUnStaked := big.NewInt(10) + expectedUnStaked = expectedUnStaked.Mul(chainSimulatorIntegrationTests.OneEGLD, expectedUnStaked) + require.Equal(t, expectedUnStaked.String(), big.NewInt(0).SetBytes(unStakedTokensAmount).String()) + + stake.Log.Info("Step 4. Wait for change of epoch and check the outcome") + err = cs.GenerateBlocksUntilEpochIsReached(targetEpoch + 1) + require.Nil(t, err) + + stake.CheckOneOfTheNodesIsUnstaked(t, metachainNode, blsKeys[:2]) +} + +// Test description: +// Unstake funds with deactivation of node, followed by stake with sufficient ammount does not unstake node at end of epoch +// +// Internal test scenario #27 +func TestChainSimulator_DirectStakingNodes_UnstakeFundsWithDeactivation_WithReactivation(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + // Test Steps + // 1. Check the stake amount and number of nodes for the owner of the staked nodes with the vmquery "getTotalStaked", and the account current EGLD balance + // 2. Create from the owner of staked nodes a transaction to unstake 10 EGLD and send it to the network + // 3. Check the outcome of the TX & verify new stake state with vmquery + // 4. Create from the owner of staked nodes a transaction to stake 10 EGLD and send it to the network + // 5. Check the outcome of the TX & verify new stake state with vmquery + // 6. Wait for change of epoch and check the outcome + + t.Run("staking ph 4 is not active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 100 + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 100 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 101 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 102 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 102 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedUnstakeFundsWithDeactivationAndReactivation(t, cs, 1) + }) + + t.Run("staking ph 4 step 1 is active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + cfg.SystemSCConfig.StakingSystemSCConfig.NodeLimitPercentage = 1 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedUnstakeFundsWithDeactivationAndReactivation(t, cs, 2) + }) + + t.Run("staking ph 4 step 2 is active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + cfg.SystemSCConfig.StakingSystemSCConfig.NodeLimitPercentage = 1 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedUnstakeFundsWithDeactivationAndReactivation(t, cs, 3) + }) + + t.Run("staking ph 4 step 3 is active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + cfg.SystemSCConfig.StakingSystemSCConfig.NodeLimitPercentage = 1 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedUnstakeFundsWithDeactivationAndReactivation(t, cs, 4) + }) +} + +func testChainSimulatorDirectStakedUnstakeFundsWithDeactivationAndReactivation(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator, targetEpoch int32) { + err := cs.GenerateBlocksUntilEpochIsReached(targetEpoch) + require.Nil(t, err) + + privateKeys, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(2) + require.Nil(t, err) + + err = cs.AddValidatorKeys(privateKeys) + require.Nil(t, err) + metachainNode := cs.GetNodeHandler(core.MetachainShardId) + + mintValue := big.NewInt(6000) + mintValue = mintValue.Mul(chainSimulatorIntegrationTests.OneEGLD, mintValue) + + validatorOwner, err := cs.GenerateAndMintWalletAddress(core.AllShardId, mintValue) + require.Nil(t, err) + + err = cs.GenerateBlocks(1) + require.Nil(t, err) + + stakeValue := big.NewInt(0).Set(chainSimulatorIntegrationTests.MinimumStakeValue) + txDataField := fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) + txStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 0, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) + stakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, stakeTx) + + err = cs.GenerateBlocks(2) // allow the metachain to finalize the block that contains the staking of the node + require.Nil(t, err) + + stake.TestBLSKeyStaked(t, metachainNode, blsKeys[0]) + + stakeValue = big.NewInt(0).Set(chainSimulatorIntegrationTests.MinimumStakeValue) + txDataField = fmt.Sprintf("stake@01@%s@%s", blsKeys[1], staking.MockBLSSignature) + txStake = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 1, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) + stakeTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, stakeTx) + + err = cs.GenerateBlocks(2) // allow the metachain to finalize the block that contains the staking of the node + require.Nil(t, err) + + stake.TestBLSKeyStaked(t, metachainNode, blsKeys[1]) + + stake.Log.Info("Step 1. Check the stake amount for the owner of the staked nodes") + stake.CheckExpectedStakedValue(t, metachainNode, validatorOwner.Bytes, 5000) + + stake.Log.Info("Step 2. Create from the owner of staked nodes a transaction to unstake 10 EGLD and send it to the network") + + unStakeValue := big.NewInt(10) + unStakeValue = unStakeValue.Mul(chainSimulatorIntegrationTests.OneEGLD, unStakeValue) + txDataField = fmt.Sprintf("unStakeTokens@%s", hex.EncodeToString(unStakeValue.Bytes())) + txUnStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 2, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForStakeOperation) + unStakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, unStakeTx) + + err = cs.GenerateBlocks(2) + require.Nil(t, err) + + stake.Log.Info("Step 3. Check the outcome of the TX & verify new stake state with vmquery getTotalStaked and getUnStakedTokensList") + stake.CheckExpectedStakedValue(t, metachainNode, validatorOwner.Bytes, 4990) + + unStakedTokensAmount := stake.GetUnStakedTokensList(t, metachainNode, validatorOwner.Bytes) + + expectedUnStaked := big.NewInt(10) + expectedUnStaked = expectedUnStaked.Mul(chainSimulatorIntegrationTests.OneEGLD, expectedUnStaked) + require.Equal(t, expectedUnStaked.String(), big.NewInt(0).SetBytes(unStakedTokensAmount).String()) + + stake.Log.Info("Step 4. Create from the owner of staked nodes a transaction to stake 10 EGLD and send it to the network") + + newStakeValue := big.NewInt(10) + newStakeValue = newStakeValue.Mul(chainSimulatorIntegrationTests.OneEGLD, newStakeValue) + txDataField = fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) + txStake = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 3, vm.ValidatorSCAddress, newStakeValue, txDataField, staking.GasLimitForStakeOperation) + stakeTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, stakeTx) + + err = cs.GenerateBlocks(2) + require.Nil(t, err) + + stake.Log.Info("5. Check the outcome of the TX & verify new stake state with vmquery") + stake.CheckExpectedStakedValue(t, metachainNode, validatorOwner.Bytes, 5000) + + stake.Log.Info("Step 6. Wait for change of epoch and check the outcome") + err = cs.GenerateBlocksUntilEpochIsReached(targetEpoch + 1) + require.Nil(t, err) + + stake.TestBLSKeyStaked(t, metachainNode, blsKeys[0]) + stake.TestBLSKeyStaked(t, metachainNode, blsKeys[1]) +} + +// Test description: +// Withdraw unstaked funds before unbonding period should return error +// +// Internal test scenario #28 +func TestChainSimulator_DirectStakingNodes_WithdrawUnstakedFundsBeforeUnbonding(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + // Test Steps + // 1. Create from the owner of staked nodes a transaction to withdraw the unstaked funds + // 2. Check the outcome of the TX & verify new stake state with vmquery ("getUnStakedTokensList") + + t.Run("staking ph 4 is not active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 100 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 101 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 102 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 102 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedWithdrawUnstakedFundsBeforeUnbonding(t, cs, 1) + }) + + t.Run("staking ph 4 step 1 is active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedWithdrawUnstakedFundsBeforeUnbonding(t, cs, 2) + }) + + t.Run("staking ph 4 step 2 is active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedWithdrawUnstakedFundsBeforeUnbonding(t, cs, 3) + }) + + t.Run("staking ph 4 step 3 is active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedWithdrawUnstakedFundsBeforeUnbonding(t, cs, 4) + }) +} + +func testChainSimulatorDirectStakedWithdrawUnstakedFundsBeforeUnbonding(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator, targetEpoch int32) { + err := cs.GenerateBlocksUntilEpochIsReached(targetEpoch) + require.Nil(t, err) + + privateKey, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(1) + require.Nil(t, err) + + err = cs.AddValidatorKeys(privateKey) + require.Nil(t, err) + metachainNode := cs.GetNodeHandler(core.MetachainShardId) + + mintValue := big.NewInt(10000) + mintValue = mintValue.Mul(chainSimulatorIntegrationTests.OneEGLD, mintValue) + + validatorOwner, err := cs.GenerateAndMintWalletAddress(core.AllShardId, mintValue) + require.Nil(t, err) + + err = cs.GenerateBlocks(1) + require.Nil(t, err) + + stakeValue := big.NewInt(0).Mul(chainSimulatorIntegrationTests.OneEGLD, big.NewInt(2600)) + txDataField := fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) + txStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 0, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) + stakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, stakeTx) + + err = cs.GenerateBlocks(2) // allow the metachain to finalize the block that contains the staking of the node + require.Nil(t, err) + + stake.TestBLSKeyStaked(t, metachainNode, blsKeys[0]) + + shardIDValidatorOwner := cs.GetNodeHandler(0).GetShardCoordinator().ComputeId(validatorOwner.Bytes) + accountValidatorOwner, _, err := cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(validatorOwner.Bech32, coreAPI.AccountQueryOptions{}) + require.Nil(t, err) + balanceBeforeUnbonding, _ := big.NewInt(0).SetString(accountValidatorOwner.Balance, 10) + + stake.Log.Info("Step 1. Create from the owner of staked nodes a transaction to withdraw the unstaked funds") + + unStakeValue := big.NewInt(10) + unStakeValue = unStakeValue.Mul(chainSimulatorIntegrationTests.OneEGLD, unStakeValue) + txDataField = fmt.Sprintf("unStakeTokens@%s", hex.EncodeToString(unStakeValue.Bytes())) + txUnStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 1, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForStakeOperation) + unStakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, unStakeTx) + + err = cs.GenerateBlocks(2) + require.Nil(t, err) + + // check bls key is still staked + stake.TestBLSKeyStaked(t, metachainNode, blsKeys[0]) + + txDataField = fmt.Sprintf("unBondTokens@%s", blsKeys[0]) + txUnBond := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 2, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForUnBond) + unBondTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnBond, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, unBondTx) + + err = cs.GenerateBlocks(2) + require.Nil(t, err) + + stake.Log.Info("Step 2. Check the outcome of the TX & verify new stake state with vmquery (`getUnStakedTokensList`)") + + scQuery := &process.SCQuery{ + ScAddress: vm.ValidatorSCAddress, + FuncName: "getUnStakedTokensList", + CallerAddr: vm.ValidatorSCAddress, + CallValue: big.NewInt(0), + Arguments: [][]byte{validatorOwner.Bytes}, + } + result, _, err := metachainNode.GetFacadeHandler().ExecuteSCQuery(scQuery) + require.Nil(t, err) + require.Equal(t, chainSimulatorIntegrationTests.OkReturnCode, result.ReturnCode) + + expectedUnStaked := big.NewInt(10) + expectedUnStaked = expectedUnStaked.Mul(chainSimulatorIntegrationTests.OneEGLD, expectedUnStaked) + require.Equal(t, expectedUnStaked.String(), big.NewInt(0).SetBytes(result.ReturnData[0]).String()) + + // the owner balance should decrease only with the txs fee + accountValidatorOwner, _, err = cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(validatorOwner.Bech32, coreAPI.AccountQueryOptions{}) + require.Nil(t, err) + balanceAfterUnbonding, _ := big.NewInt(0).SetString(accountValidatorOwner.Balance, 10) + + txsFee, _ := big.NewInt(0).SetString(unBondTx.Fee, 10) + balanceAfterUnbondingWithFee := big.NewInt(0).Add(balanceAfterUnbonding, txsFee) + + txsFee, _ = big.NewInt(0).SetString(unStakeTx.Fee, 10) + balanceAfterUnbondingWithFee.Add(balanceAfterUnbondingWithFee, txsFee) + + txsFee, _ = big.NewInt(0).SetString(stakeTx.Fee, 10) + balanceAfterUnbondingWithFee.Add(balanceAfterUnbondingWithFee, txsFee) + + require.Equal(t, 1, balanceAfterUnbondingWithFee.Cmp(balanceBeforeUnbonding)) +} diff --git a/integrationTests/chainSimulator/staking/stake/stakeAndUnStakeSet3/stakeAndUnStake_test.go b/integrationTests/chainSimulator/staking/stake/stakeAndUnStakeSet3/stakeAndUnStake_test.go new file mode 100644 index 00000000000..dd219749c5b --- /dev/null +++ b/integrationTests/chainSimulator/staking/stake/stakeAndUnStakeSet3/stakeAndUnStake_test.go @@ -0,0 +1,876 @@ +package stakeAndUnStakeSet3 + +import ( + "encoding/hex" + "fmt" + "math/big" + "strings" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + coreAPI "github.com/multiversx/mx-chain-core-go/data/api" + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/config" + chainSimulatorIntegrationTests "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator" + "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator/staking" + "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator/staking/stake" + "github.com/multiversx/mx-chain-go/node/chainSimulator" + "github.com/multiversx/mx-chain-go/node/chainSimulator/components/api" + "github.com/multiversx/mx-chain-go/node/chainSimulator/configs" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/vm" +) + +// Test description: +// Withdraw unstaked funds in first available withdraw epoch +// +// Internal test scenario #29 +func TestChainSimulator_DirectStakingNodes_WithdrawUnstakedInWithdrawEpoch(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + // Test Steps + // 1. Wait for the unbonding epoch to start + // 2. Create from the owner of staked nodes a transaction to withdraw the unstaked funds + // 3. Check the outcome of the TX & verify new stake state with vmquery ("getUnStakedTokensList") + + t.Run("staking ph 4 is not active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 100 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 101 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 102 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 102 + cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 1 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedWithdrawUnstakedFundsInFirstEpoch(t, cs, 1) + }) + + t.Run("staking ph 4 step 1 is active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 1 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedWithdrawUnstakedFundsInFirstEpoch(t, cs, 2) + }) + + t.Run("staking ph 4 step 2 is active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 1 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedWithdrawUnstakedFundsInFirstEpoch(t, cs, 3) + }) + + t.Run("staking ph 4 step 3 is active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 1 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedWithdrawUnstakedFundsInFirstEpoch(t, cs, 4) + }) +} + +func testChainSimulatorDirectStakedWithdrawUnstakedFundsInFirstEpoch(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator, targetEpoch int32) { + err := cs.GenerateBlocksUntilEpochIsReached(targetEpoch) + require.Nil(t, err) + + privateKey, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(1) + require.Nil(t, err) + + err = cs.AddValidatorKeys(privateKey) + require.Nil(t, err) + metachainNode := cs.GetNodeHandler(core.MetachainShardId) + + mintValue := big.NewInt(10000) + mintValue = mintValue.Mul(chainSimulatorIntegrationTests.OneEGLD, mintValue) + + validatorOwner, err := cs.GenerateAndMintWalletAddress(core.AllShardId, mintValue) + require.Nil(t, err) + + err = cs.GenerateBlocks(1) + require.Nil(t, err) + + stakeValue := big.NewInt(0).Mul(chainSimulatorIntegrationTests.OneEGLD, big.NewInt(2600)) + txDataField := fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) + txStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 0, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) + stakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, stakeTx) + + err = cs.GenerateBlocks(2) // allow the metachain to finalize the block that contains the staking of the node + require.Nil(t, err) + + stake.TestBLSKeyStaked(t, metachainNode, blsKeys[0]) + + shardIDValidatorOwner := cs.GetNodeHandler(0).GetShardCoordinator().ComputeId(validatorOwner.Bytes) + accountValidatorOwner, _, err := cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(validatorOwner.Bech32, coreAPI.AccountQueryOptions{}) + require.Nil(t, err) + balanceBeforeUnbonding, _ := big.NewInt(0).SetString(accountValidatorOwner.Balance, 10) + + unStakeValue := big.NewInt(10) + unStakeValue = unStakeValue.Mul(chainSimulatorIntegrationTests.OneEGLD, unStakeValue) + txDataField = fmt.Sprintf("unStakeTokens@%s", hex.EncodeToString(unStakeValue.Bytes())) + txUnStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 1, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForStakeOperation) + unStakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, unStakeTx) + + err = cs.GenerateBlocks(2) + require.Nil(t, err) + + // check bls key is still staked + stake.TestBLSKeyStaked(t, metachainNode, blsKeys[0]) + + scQuery := &process.SCQuery{ + ScAddress: vm.ValidatorSCAddress, + FuncName: "getUnStakedTokensList", + CallerAddr: vm.ValidatorSCAddress, + CallValue: big.NewInt(0), + Arguments: [][]byte{validatorOwner.Bytes}, + } + result, _, err := metachainNode.GetFacadeHandler().ExecuteSCQuery(scQuery) + require.Nil(t, err) + require.Equal(t, chainSimulatorIntegrationTests.OkReturnCode, result.ReturnCode) + + expectedUnStaked := big.NewInt(10) + expectedUnStaked = expectedUnStaked.Mul(chainSimulatorIntegrationTests.OneEGLD, expectedUnStaked) + require.Equal(t, expectedUnStaked.String(), big.NewInt(0).SetBytes(result.ReturnData[0]).String()) + + stake.Log.Info("Step 1. Wait for the unbonding epoch to start") + + err = cs.GenerateBlocksUntilEpochIsReached(targetEpoch + 1) + require.Nil(t, err) + + stake.Log.Info("Step 2. Create from the owner of staked nodes a transaction to withdraw the unstaked funds") + + txDataField = fmt.Sprintf("unBondTokens@%s", blsKeys[0]) + txUnBond := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 2, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForUnBond) + unBondTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnBond, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, unBondTx) + + err = cs.GenerateBlocks(2) + require.Nil(t, err) + + stake.Log.Info("Step 3. Check the outcome of the TX & verify new stake state with vmquery (`getUnStakedTokensList`)") + + scQuery = &process.SCQuery{ + ScAddress: vm.ValidatorSCAddress, + FuncName: "getTotalStaked", + CallerAddr: vm.ValidatorSCAddress, + CallValue: big.NewInt(0), + Arguments: [][]byte{validatorOwner.Bytes}, + } + result, _, err = metachainNode.GetFacadeHandler().ExecuteSCQuery(scQuery) + require.Nil(t, err) + require.Equal(t, chainSimulatorIntegrationTests.OkReturnCode, result.ReturnCode) + + expectedStaked := big.NewInt(2590) + expectedStaked = expectedStaked.Mul(chainSimulatorIntegrationTests.OneEGLD, expectedStaked) + require.Equal(t, expectedStaked.String(), string(result.ReturnData[0])) + + // the owner balance should increase with the (10 EGLD - tx fee) + accountValidatorOwner, _, err = cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(validatorOwner.Bech32, coreAPI.AccountQueryOptions{}) + require.Nil(t, err) + balanceAfterUnbonding, _ := big.NewInt(0).SetString(accountValidatorOwner.Balance, 10) + + // substract unbonding value + balanceAfterUnbonding.Sub(balanceAfterUnbonding, unStakeValue) + + txsFee, _ := big.NewInt(0).SetString(unBondTx.Fee, 10) + balanceAfterUnbondingWithFee := big.NewInt(0).Add(balanceAfterUnbonding, txsFee) + + txsFee, _ = big.NewInt(0).SetString(unStakeTx.Fee, 10) + balanceAfterUnbondingWithFee.Add(balanceAfterUnbondingWithFee, txsFee) + + txsFee, _ = big.NewInt(0).SetString(stakeTx.Fee, 10) + balanceAfterUnbondingWithFee.Add(balanceAfterUnbondingWithFee, txsFee) + + require.Equal(t, 1, balanceAfterUnbondingWithFee.Cmp(balanceBeforeUnbonding)) +} + +// Test description: +// Unstake funds in different batches in the same epoch allows correct withdrawal in the correct epoch +// +// Internal test scenario #31 +func TestChainSimulator_DirectStakingNodes_WithdrawUnstakedInEpoch(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + // Test Steps + // 1. Create 3 transactions for unstaking: first one unstaking 11 egld each, second one unstaking 12 egld and third one unstaking 13 egld. + // 2. Send the transactions consecutively in the same epoch + // 3. Wait for the epoch when unbonding period ends. + // 4. Create a transaction for withdraw and send it to the network + + t.Run("staking ph 4 is not active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 100 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 101 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 102 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 102 + + cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 3 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedWithdrawUnstakedFundsInEpoch(t, cs, 1) + }) + + t.Run("staking ph 4 step 1 is active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: core.OptionalUint64{ + HasValue: true, + Value: 30, + }, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + + cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 3 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedWithdrawUnstakedFundsInEpoch(t, cs, 2) + }) + + t.Run("staking ph 4 step 2 is active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: core.OptionalUint64{ + HasValue: true, + Value: 30, + }, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + + cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 3 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedWithdrawUnstakedFundsInEpoch(t, cs, 3) + }) + + t.Run("staking ph 4 step 3 is active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: core.OptionalUint64{ + HasValue: true, + Value: 30, + }, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + + cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 3 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedWithdrawUnstakedFundsInEpoch(t, cs, 4) + }) +} + +func testChainSimulatorDirectStakedWithdrawUnstakedFundsInEpoch(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator, targetEpoch int32) { + err := cs.GenerateBlocksUntilEpochIsReached(targetEpoch) + require.Nil(t, err) + + privateKey, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(1) + require.Nil(t, err) + + err = cs.AddValidatorKeys(privateKey) + require.Nil(t, err) + metachainNode := cs.GetNodeHandler(core.MetachainShardId) + + mintValue := big.NewInt(2700) + mintValue = mintValue.Mul(chainSimulatorIntegrationTests.OneEGLD, mintValue) + + validatorOwner, err := cs.GenerateAndMintWalletAddress(core.AllShardId, mintValue) + require.Nil(t, err) + + err = cs.GenerateBlocks(1) + require.Nil(t, err) + + stakeValue := big.NewInt(0).Mul(chainSimulatorIntegrationTests.OneEGLD, big.NewInt(2600)) + txDataField := fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) + txStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 0, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) + stakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, stakeTx) + + stakeTxFee, _ := big.NewInt(0).SetString(stakeTx.Fee, 10) + + err = cs.GenerateBlocks(2) + require.Nil(t, err) + + stake.TestBLSKeyStaked(t, metachainNode, blsKeys[0]) + + shardIDValidatorOwner := cs.GetNodeHandler(0).GetShardCoordinator().ComputeId(validatorOwner.Bytes) + accountValidatorOwner, _, err := cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(validatorOwner.Bech32, coreAPI.AccountQueryOptions{}) + require.Nil(t, err) + balanceBeforeUnbonding, _ := big.NewInt(0).SetString(accountValidatorOwner.Balance, 10) + + stake.Log.Info("Step 1. Create 3 transactions for unstaking: first one unstaking 11 egld each, second one unstaking 12 egld and third one unstaking 13 egld.") + stake.Log.Info("Step 2. Send the transactions in consecutively in same epoch.") + + unStakeValue1 := big.NewInt(11) + unStakeValue1 = unStakeValue1.Mul(chainSimulatorIntegrationTests.OneEGLD, unStakeValue1) + txDataField = fmt.Sprintf("unStakeTokens@%s", hex.EncodeToString(unStakeValue1.Bytes())) + txUnStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 1, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForStakeOperation) + unStakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, unStakeTx) + + unStakeTxFee, _ := big.NewInt(0).SetString(unStakeTx.Fee, 10) + + unStakeValue2 := big.NewInt(12) + unStakeValue2 = unStakeValue2.Mul(chainSimulatorIntegrationTests.OneEGLD, unStakeValue2) + txDataField = fmt.Sprintf("unStakeTokens@%s", hex.EncodeToString(unStakeValue2.Bytes())) + txUnStake = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 2, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForStakeOperation) + unStakeTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, unStakeTx) + + unStakeValue3 := big.NewInt(13) + unStakeValue3 = unStakeValue3.Mul(chainSimulatorIntegrationTests.OneEGLD, unStakeValue3) + txDataField = fmt.Sprintf("unStakeTokens@%s", hex.EncodeToString(unStakeValue3.Bytes())) + txUnStake = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 3, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForStakeOperation) + unStakeTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, unStakeTx) + + err = cs.GenerateBlocks(1) + require.Nil(t, err) + + // check bls key is still staked + stake.TestBLSKeyStaked(t, metachainNode, blsKeys[0]) + + scQuery := &process.SCQuery{ + ScAddress: vm.ValidatorSCAddress, + FuncName: "getUnStakedTokensList", + CallerAddr: vm.ValidatorSCAddress, + CallValue: big.NewInt(0), + Arguments: [][]byte{validatorOwner.Bytes}, + } + result, _, err := metachainNode.GetFacadeHandler().ExecuteSCQuery(scQuery) + require.Nil(t, err) + require.Equal(t, chainSimulatorIntegrationTests.OkReturnCode, result.ReturnCode) + + expectedUnStaked := big.NewInt(11 + 12 + 13) + expectedUnStaked = expectedUnStaked.Mul(chainSimulatorIntegrationTests.OneEGLD, expectedUnStaked) + require.Equal(t, expectedUnStaked.String(), big.NewInt(0).SetBytes(result.ReturnData[0]).String()) + + scQuery = &process.SCQuery{ + ScAddress: vm.ValidatorSCAddress, + FuncName: "getTotalStaked", + CallerAddr: vm.ValidatorSCAddress, + CallValue: big.NewInt(0), + Arguments: [][]byte{validatorOwner.Bytes}, + } + result, _, err = metachainNode.GetFacadeHandler().ExecuteSCQuery(scQuery) + require.Nil(t, err) + require.Equal(t, chainSimulatorIntegrationTests.OkReturnCode, result.ReturnCode) + + expectedStaked := big.NewInt(2600 - 11 - 12 - 13) + expectedStaked = expectedStaked.Mul(chainSimulatorIntegrationTests.OneEGLD, expectedStaked) + require.Equal(t, expectedStaked.String(), string(result.ReturnData[0])) + + stake.Log.Info("Step 3. Wait for the unbonding epoch to start") + + testEpoch := targetEpoch + 3 + err = cs.GenerateBlocksUntilEpochIsReached(testEpoch) + require.Nil(t, err) + + stake.Log.Info("Step 4.1. Create from the owner of staked nodes a transaction to withdraw the unstaked funds") + + txDataField = fmt.Sprintf("unBondTokens@%s", blsKeys[0]) + txUnBond := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 4, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForUnBond) + unBondTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnBond, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, unBondTx) + + unBondTxFee, _ := big.NewInt(0).SetString(unBondTx.Fee, 10) + + err = cs.GenerateBlocks(2) + require.Nil(t, err) + + // the owner balance should increase with the (11+12+13 EGLD - tx fee) + accountValidatorOwner, _, err = cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(validatorOwner.Bech32, coreAPI.AccountQueryOptions{}) + require.Nil(t, err) + balanceAfterUnbonding, _ := big.NewInt(0).SetString(accountValidatorOwner.Balance, 10) + + balanceAfterUnbonding.Sub(balanceAfterUnbonding, unStakeValue1) + balanceAfterUnbonding.Sub(balanceAfterUnbonding, unStakeValue2) + balanceAfterUnbonding.Sub(balanceAfterUnbonding, unStakeValue3) + + txsFee := big.NewInt(0) + + txsFee.Add(txsFee, stakeTxFee) + txsFee.Add(txsFee, unBondTxFee) + txsFee.Add(txsFee, unStakeTxFee) + txsFee.Add(txsFee, unStakeTxFee) + txsFee.Add(txsFee, unStakeTxFee) + + balanceAfterUnbonding.Add(balanceAfterUnbonding, txsFee) + + require.Equal(t, 1, balanceAfterUnbonding.Cmp(balanceBeforeUnbonding)) +} + +// Test that if we unStake one active node(waiting/eligible), the number of qualified nodes will remain the same +// Nodes configuration at genesis consisting of a total of 32 nodes, distributed on 3 shards + meta: +// - 4 eligible nodes/shard +// - 4 waiting nodes/shard +// - 2 nodes to shuffle per shard +// - max num nodes config for stakingV4 step3 = 24 (being downsized from previously 32 nodes) +// - with this config, we should always select 8 nodes from auction list +// We will add one extra node, so auction list size = 9, but will always select 8. Even if we unStake one active node, +// we should still only select 8 nodes. +func TestChainSimulator_UnStakeOneActiveNodeAndCheckAPIAuctionList(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + stakingV4Step1Epoch := uint32(2) + stakingV4Step2Epoch := uint32(3) + stakingV4Step3Epoch := uint32(4) + + numOfShards := uint32(3) + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 4, + MetaChainMinNodes: 4, + NumNodesWaitingListMeta: 4, + NumNodesWaitingListShard: 4, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = stakingV4Step1Epoch + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = stakingV4Step2Epoch + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = stakingV4Step3Epoch + cfg.EpochConfig.EnableEpochs.CleanupAuctionOnLowWaitingListEnableEpoch = stakingV4Step1Epoch + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[1].MaxNumNodes = 32 + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[1].NodesToShufflePerShard = 2 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = stakingV4Step3Epoch + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].MaxNumNodes = 24 + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].NodesToShufflePerShard = 2 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + err = cs.GenerateBlocksUntilEpochIsReached(int32(stakingV4Step3Epoch + 1)) + require.Nil(t, err) + + metachainNode := cs.GetNodeHandler(core.MetachainShardId) + + qualified, unQualified := stake.GetQualifiedAndUnqualifiedNodes(t, metachainNode) + require.Equal(t, 8, len(qualified)) + require.Equal(t, 0, len(unQualified)) + + stakeOneNode(t, cs) + + qualified, unQualified = stake.GetQualifiedAndUnqualifiedNodes(t, metachainNode) + require.Equal(t, 8, len(qualified)) + require.Equal(t, 1, len(unQualified)) + + unStakeOneActiveNode(t, cs) + + qualified, unQualified = stake.GetQualifiedAndUnqualifiedNodes(t, metachainNode) + require.Equal(t, 8, len(qualified)) + require.Equal(t, 1, len(unQualified)) +} + +// Nodes configuration at genesis consisting of a total of 40 nodes, distributed on 3 shards + meta: +// - 4 eligible nodes/shard +// - 4 waiting nodes/shard +// - 2 nodes to shuffle per shard +// - max num nodes config for stakingV4 step3 = 32 (being downsized from previously 40 nodes) +// - with this config, we should always select max 8 nodes from auction list if there are > 40 nodes in the network +// This test will run with only 32 nodes and check that there are no nodes in the auction list, +// because of the lowWaitingList condition being triggered when in staking v4 +func TestChainSimulator_EdgeCaseLowWaitingList(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + stakingV4Step1Epoch := uint32(2) + stakingV4Step2Epoch := uint32(3) + stakingV4Step3Epoch := uint32(4) + + numOfShards := uint32(3) + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 4, + MetaChainMinNodes: 4, + NumNodesWaitingListMeta: 2, + NumNodesWaitingListShard: 2, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = stakingV4Step1Epoch + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = stakingV4Step2Epoch + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = stakingV4Step3Epoch + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[1].MaxNumNodes = 40 + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[1].NodesToShufflePerShard = 2 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = stakingV4Step3Epoch + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].MaxNumNodes = 32 + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].NodesToShufflePerShard = 2 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + epochToCheck := int32(stakingV4Step3Epoch + 1) + err = cs.GenerateBlocksUntilEpochIsReached(epochToCheck) + require.Nil(t, err) + + metachainNode := cs.GetNodeHandler(core.MetachainShardId) + qualified, unQualified := stake.GetQualifiedAndUnqualifiedNodes(t, metachainNode) + require.Equal(t, 0, len(qualified)) + require.Equal(t, 0, len(unQualified)) + + // we always have 0 in auction list because of the lowWaitingList condition + epochToCheck += 1 + err = cs.GenerateBlocksUntilEpochIsReached(epochToCheck) + require.Nil(t, err) + + qualified, unQualified = stake.GetQualifiedAndUnqualifiedNodes(t, metachainNode) + require.Equal(t, 0, len(qualified)) + require.Equal(t, 0, len(unQualified)) + + // stake 16 mode nodes, these will go to auction list + stakeNodes(t, cs, 17) + + epochToCheck += 1 + err = cs.GenerateBlocksUntilEpochIsReached(epochToCheck) + require.Nil(t, err) + + qualified, unQualified = stake.GetQualifiedAndUnqualifiedNodes(t, metachainNode) + // all the previously registered will be selected, as we have 24 nodes in eligible+waiting, 8 will shuffle out, + // but this time there will be not be lowWaitingList, as there are enough in auction, so we will end up with + // 24-8 = 16 nodes remaining + 16 from auction, to fill up all 32 positions + require.Equal(t, 16, len(qualified)) + require.Equal(t, 1, len(unQualified)) + + shuffledOutNodesKeys, err := metachainNode.GetProcessComponents().NodesCoordinator().GetShuffledOutToAuctionValidatorsPublicKeys(uint32(epochToCheck)) + require.Nil(t, err) + + checkKeysNotInMap(t, shuffledOutNodesKeys, qualified) + checkKeysNotInMap(t, shuffledOutNodesKeys, unQualified) +} + +func checkKeysNotInMap(t *testing.T, m map[uint32][][]byte, keys []string) { + for _, key := range keys { + for _, v := range m { + for _, k := range v { + mapKey := hex.EncodeToString(k) + require.NotEqual(t, key, mapKey) + } + } + } +} + +func stakeNodes(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator, numNodesToStake int) { + txs := make([]*transaction.Transaction, numNodesToStake) + for i := 0; i < numNodesToStake; i++ { + txs[i] = createStakeTransaction(t, cs) + } + + stakeTxs, err := cs.SendTxsAndGenerateBlocksTilAreExecuted(txs, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, stakeTxs) + require.Len(t, stakeTxs, numNodesToStake) + + require.Nil(t, cs.GenerateBlocks(1)) +} + +func stakeOneNode(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator) { + txStake := createStakeTransaction(t, cs) + stakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, stakeTx) + + require.Nil(t, cs.GenerateBlocks(1)) +} + +func createStakeTransaction(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator) *transaction.Transaction { + privateKey, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(1) + require.Nil(t, err) + err = cs.AddValidatorKeys(privateKey) + require.Nil(t, err) + + mintValue := big.NewInt(0).Add(chainSimulatorIntegrationTests.MinimumStakeValue, chainSimulatorIntegrationTests.OneEGLD) + validatorOwner, err := cs.GenerateAndMintWalletAddress(core.AllShardId, mintValue) + require.Nil(t, err) + + err = cs.GenerateBlocks(1) + require.Nil(t, err) + + txDataField := fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) + return chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 0, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.MinimumStakeValue, txDataField, staking.GasLimitForStakeOperation) +} + +func unStakeOneActiveNode(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator) { + err := cs.ForceResetValidatorStatisticsCache() + require.Nil(t, err) + + validators, err := cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ValidatorStatisticsApi() + require.Nil(t, err) + + idx := 0 + keyToUnStake := make([]byte, 0) + numKeys := len(cs.GetValidatorPrivateKeys()) + for idx = 0; idx < numKeys; idx++ { + keyToUnStake, err = cs.GetValidatorPrivateKeys()[idx].GeneratePublic().ToByteArray() + require.Nil(t, err) + + apiValidator, found := validators[hex.EncodeToString(keyToUnStake)] + require.True(t, found) + + validatorStatus := apiValidator.ValidatorStatus + if validatorStatus == "waiting" || validatorStatus == "eligible" { + stake.Log.Info("found active key to unStake", "index", idx, "bls key", keyToUnStake, "list", validatorStatus) + break + } + + if idx == numKeys-1 { + require.Fail(t, "did not find key to unStake") + } + } + + rcv := "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" + rcvAddrBytes, _ := cs.GetNodeHandler(0).GetCoreComponents().AddressPubKeyConverter().Decode(rcv) + + validatorWallet := cs.GetInitialWalletKeys().StakeWallets[idx].Address + shardID := cs.GetNodeHandler(0).GetShardCoordinator().ComputeId(validatorWallet.Bytes) + initialAccount, _, err := cs.GetNodeHandler(shardID).GetFacadeHandler().GetAccount(validatorWallet.Bech32, coreAPI.AccountQueryOptions{}) + + require.Nil(t, err) + tx := &transaction.Transaction{ + Nonce: initialAccount.Nonce, + Value: big.NewInt(0), + SndAddr: validatorWallet.Bytes, + RcvAddr: rcvAddrBytes, + Data: []byte(fmt.Sprintf("unStake@%s", hex.EncodeToString(keyToUnStake))), + GasLimit: 50_000_000, + GasPrice: 1000000000, + Signature: []byte("dummy"), + ChainID: []byte(configs.ChainID), + Version: 1, + } + _, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + + err = cs.GenerateBlocks(1) + require.Nil(t, err) + + err = cs.ForceResetValidatorStatisticsCache() + require.Nil(t, err) + validators, err = cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ValidatorStatisticsApi() + require.Nil(t, err) + + apiValidator, found := validators[hex.EncodeToString(keyToUnStake)] + require.True(t, found) + require.True(t, strings.Contains(apiValidator.ValidatorStatus, "leaving")) +} diff --git a/integrationTests/chainSimulator/staking/stake/stakeAndUnStakeSet4/stakeAndUnStake_test.go b/integrationTests/chainSimulator/staking/stake/stakeAndUnStakeSet4/stakeAndUnStake_test.go new file mode 100644 index 00000000000..6e35ef44d3b --- /dev/null +++ b/integrationTests/chainSimulator/staking/stake/stakeAndUnStakeSet4/stakeAndUnStake_test.go @@ -0,0 +1,391 @@ +package stakeAndUnStakeSet4 + +import ( + "encoding/hex" + "fmt" + "math/big" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + coreAPI "github.com/multiversx/mx-chain-core-go/data/api" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/config" + chainSimulatorIntegrationTests "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator" + "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator/staking" + "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator/staking/stake" + "github.com/multiversx/mx-chain-go/node/chainSimulator" + "github.com/multiversx/mx-chain-go/node/chainSimulator/components/api" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/vm" +) + +// Test description: +// Unstaking funds in different batches allows correct withdrawal for each batch +// at the corresponding epoch. +// +// Internal test scenario #30 +func TestChainSimulator_DirectStakingNodes_WithdrawUnstakedInBatches(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + // Test Steps + // 1. Create 3 transactions for unstaking: first one unstaking 11 egld each, second one unstaking 12 egld and third one unstaking 13 egld. + // 2. Send the transactions in consecutive epochs, one TX in each epoch. + // 3. Wait for the epoch when first tx unbonding period ends. + // 4. Create a transaction for withdraw and send it to the network + // 5. Wait for an epoch + // 6. Create another transaction for withdraw and send it to the network + // 7. Wait for an epoch + // 8. Create another transasction for withdraw and send it to the network + + t.Run("staking ph 4 is not active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 100 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 101 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 102 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 102 + + cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 6 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedWithdrawUnstakedFundsInBatches(t, cs, 1) + }) + + t.Run("staking ph 4 step 1 is active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + + cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 6 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedWithdrawUnstakedFundsInBatches(t, cs, 2) + }) + + t.Run("staking ph 4 step 2 is active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + + cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 6 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedWithdrawUnstakedFundsInBatches(t, cs, 3) + }) + + t.Run("staking ph 4 step 3 is active", func(t *testing.T) { + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: stake.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: stake.RoundDurationInMillis, + SupernovaRoundDurationInMillis: stake.SupernovaRoundDurationInMillis, + RoundsPerEpoch: stake.RoundsPerEpoch, + SupernovaRoundsPerEpoch: stake.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 + cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 + cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + + cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + + cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 6 + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + testChainSimulatorDirectStakedWithdrawUnstakedFundsInBatches(t, cs, 4) + }) +} + +func testChainSimulatorDirectStakedWithdrawUnstakedFundsInBatches(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator, targetEpoch int32) { + err := cs.GenerateBlocksUntilEpochIsReached(targetEpoch) + require.Nil(t, err) + + privateKey, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(1) + require.Nil(t, err) + + err = cs.AddValidatorKeys(privateKey) + require.Nil(t, err) + metachainNode := cs.GetNodeHandler(core.MetachainShardId) + + mintValue := big.NewInt(2700) + mintValue = mintValue.Mul(chainSimulatorIntegrationTests.OneEGLD, mintValue) + + validatorOwner, err := cs.GenerateAndMintWalletAddress(core.AllShardId, mintValue) + require.Nil(t, err) + + err = cs.GenerateBlocks(1) + require.Nil(t, err) + + stakeValue := big.NewInt(0).Mul(chainSimulatorIntegrationTests.OneEGLD, big.NewInt(2600)) + txDataField := fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) + txStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 0, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) + stakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, stakeTx) + + stakeTxFee, _ := big.NewInt(0).SetString(stakeTx.Fee, 10) + + err = cs.GenerateBlocks(2) + require.Nil(t, err) + + stake.TestBLSKeyStaked(t, metachainNode, blsKeys[0]) + + shardIDValidatorOwner := cs.GetNodeHandler(0).GetShardCoordinator().ComputeId(validatorOwner.Bytes) + accountValidatorOwner, _, err := cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(validatorOwner.Bech32, coreAPI.AccountQueryOptions{}) + require.Nil(t, err) + balanceBeforeUnbonding, _ := big.NewInt(0).SetString(accountValidatorOwner.Balance, 10) + + stake.Log.Info("Step 1. Create 3 transactions for unstaking: first one unstaking 11 egld, second one unstaking 12 egld and third one unstaking 13 egld.") + stake.Log.Info("Step 2. Send the transactions in consecutive epochs, one TX in each epoch.") + + unStakeValue1 := big.NewInt(11) + unStakeValue1 = unStakeValue1.Mul(chainSimulatorIntegrationTests.OneEGLD, unStakeValue1) + txDataField = fmt.Sprintf("unStakeTokens@%s", hex.EncodeToString(unStakeValue1.Bytes())) + txUnStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 1, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForStakeOperation) + unStakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, unStakeTx) + + unStakeTxFee1, _ := big.NewInt(0).SetString(unStakeTx.Fee, 10) + + testEpoch := targetEpoch + 1 + err = cs.GenerateBlocksUntilEpochIsReached(testEpoch) + require.Nil(t, err) + + unStakeValue2 := big.NewInt(12) + unStakeValue2 = unStakeValue2.Mul(chainSimulatorIntegrationTests.OneEGLD, unStakeValue2) + txDataField = fmt.Sprintf("unStakeTokens@%s", hex.EncodeToString(unStakeValue2.Bytes())) + txUnStake = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 2, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForStakeOperation) + unStakeTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, unStakeTx) + + unStakeTxFee2, _ := big.NewInt(0).SetString(unStakeTx.Fee, 10) + + testEpoch++ + err = cs.GenerateBlocksUntilEpochIsReached(testEpoch) + require.Nil(t, err) + + unStakeValue3 := big.NewInt(13) + unStakeValue3 = unStakeValue3.Mul(chainSimulatorIntegrationTests.OneEGLD, unStakeValue3) + txDataField = fmt.Sprintf("unStakeTokens@%s", hex.EncodeToString(unStakeValue3.Bytes())) + txUnStake = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 3, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForStakeOperation) + unStakeTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, unStakeTx) + + unStakeTxFee3, _ := big.NewInt(0).SetString(unStakeTx.Fee, 10) + + testEpoch++ + err = cs.GenerateBlocksUntilEpochIsReached(testEpoch) + require.Nil(t, err) + + // check bls key is still staked + stake.TestBLSKeyStaked(t, metachainNode, blsKeys[0]) + + scQuery := &process.SCQuery{ + ScAddress: vm.ValidatorSCAddress, + FuncName: "getUnStakedTokensList", + CallerAddr: vm.ValidatorSCAddress, + CallValue: big.NewInt(0), + Arguments: [][]byte{validatorOwner.Bytes}, + } + result, _, err := metachainNode.GetFacadeHandler().ExecuteSCQuery(scQuery) + require.Nil(t, err) + require.Equal(t, chainSimulatorIntegrationTests.OkReturnCode, result.ReturnCode) + + expectedUnStaked := big.NewInt(11) + expectedUnStaked = expectedUnStaked.Mul(chainSimulatorIntegrationTests.OneEGLD, expectedUnStaked) + require.Equal(t, expectedUnStaked.String(), big.NewInt(0).SetBytes(result.ReturnData[0]).String()) + + scQuery = &process.SCQuery{ + ScAddress: vm.ValidatorSCAddress, + FuncName: "getTotalStaked", + CallerAddr: vm.ValidatorSCAddress, + CallValue: big.NewInt(0), + Arguments: [][]byte{validatorOwner.Bytes}, + } + result, _, err = metachainNode.GetFacadeHandler().ExecuteSCQuery(scQuery) + require.Nil(t, err) + require.Equal(t, chainSimulatorIntegrationTests.OkReturnCode, result.ReturnCode) + + expectedStaked := big.NewInt(2600 - 11 - 12 - 13) + expectedStaked = expectedStaked.Mul(chainSimulatorIntegrationTests.OneEGLD, expectedStaked) + require.Equal(t, expectedStaked.String(), string(result.ReturnData[0])) + + stake.Log.Info("Step 3. Wait for the unbonding epoch to start") + + testEpoch += 3 + err = cs.GenerateBlocksUntilEpochIsReached(testEpoch) + require.Nil(t, err) + + stake.Log.Info("Step 4.1. Create from the owner of staked nodes a transaction to withdraw the unstaked funds") + + txDataField = fmt.Sprintf("unBondTokens@%s", blsKeys[0]) + txUnBond := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 4, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForUnBond) + unBondTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnBond, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, unBondTx) + + unBondTxFee, _ := big.NewInt(0).SetString(unBondTx.Fee, 10) + + err = cs.GenerateBlocks(2) + require.Nil(t, err) + + // the owner balance should increase with the (11 EGLD - tx fee) + accountValidatorOwner, _, err = cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(validatorOwner.Bech32, coreAPI.AccountQueryOptions{}) + require.Nil(t, err) + balanceAfterUnbonding, _ := big.NewInt(0).SetString(accountValidatorOwner.Balance, 10) + + balanceAfterUnbonding.Sub(balanceAfterUnbonding, unStakeValue1) + + txsFee := big.NewInt(0) + + txsFee.Add(txsFee, stakeTxFee) + txsFee.Add(txsFee, unBondTxFee) + txsFee.Add(txsFee, unStakeTxFee1) + txsFee.Add(txsFee, unStakeTxFee2) + txsFee.Add(txsFee, unStakeTxFee3) + + balanceAfterUnbonding.Add(balanceAfterUnbonding, txsFee) + + require.Equal(t, 1, balanceAfterUnbonding.Cmp(balanceBeforeUnbonding)) + + stake.Log.Info("Step 4.2. Create from the owner of staked nodes a transaction to withdraw the unstaked funds") + + testEpoch++ + err = cs.GenerateBlocksUntilEpochIsReached(testEpoch) + require.Nil(t, err) + + txDataField = fmt.Sprintf("unBondTokens@%s", blsKeys[0]) + txUnBond = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 5, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForUnBond) + unBondTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnBond, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, unBondTx) + + err = cs.GenerateBlocks(2) + require.Nil(t, err) + + // the owner balance should increase with the (11+12 EGLD - tx fee) + accountValidatorOwner, _, err = cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(validatorOwner.Bech32, coreAPI.AccountQueryOptions{}) + require.Nil(t, err) + balanceAfterUnbonding, _ = big.NewInt(0).SetString(accountValidatorOwner.Balance, 10) + + balanceAfterUnbonding.Sub(balanceAfterUnbonding, unStakeValue1) + balanceAfterUnbonding.Sub(balanceAfterUnbonding, unStakeValue2) + + txsFee.Add(txsFee, unBondTxFee) + balanceAfterUnbonding.Add(balanceAfterUnbonding, txsFee) + + require.Equal(t, 1, balanceAfterUnbonding.Cmp(balanceBeforeUnbonding)) + + stake.Log.Info("Step 4.3. Create from the owner of staked nodes a transaction to withdraw the unstaked funds") + + testEpoch++ + err = cs.GenerateBlocksUntilEpochIsReached(testEpoch) + require.Nil(t, err) + + txDataField = fmt.Sprintf("unBondTokens@%s", blsKeys[0]) + txUnBond = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 6, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForUnBond) + unBondTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnBond, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, unBondTx) + + err = cs.GenerateBlocks(2) + require.Nil(t, err) + + // the owner balance should increase with the (11+12+13 EGLD - tx fee) + accountValidatorOwner, _, err = cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(validatorOwner.Bech32, coreAPI.AccountQueryOptions{}) + require.Nil(t, err) + balanceAfterUnbonding, _ = big.NewInt(0).SetString(accountValidatorOwner.Balance, 10) + + balanceAfterUnbonding.Sub(balanceAfterUnbonding, unStakeValue1) + balanceAfterUnbonding.Sub(balanceAfterUnbonding, unStakeValue2) + balanceAfterUnbonding.Sub(balanceAfterUnbonding, unStakeValue3) + + txsFee.Add(txsFee, unBondTxFee) + balanceAfterUnbonding.Add(balanceAfterUnbonding, txsFee) + + require.Equal(t, 1, balanceAfterUnbonding.Cmp(balanceBeforeUnbonding)) +} diff --git a/integrationTests/chainSimulator/staking/stake/stakeAndUnStake_test.go b/integrationTests/chainSimulator/staking/stake/stakeAndUnStake_test.go deleted file mode 100644 index 471ff3ec1a7..00000000000 --- a/integrationTests/chainSimulator/staking/stake/stakeAndUnStake_test.go +++ /dev/null @@ -1,2634 +0,0 @@ -package stake - -import ( - "encoding/hex" - "fmt" - "math/big" - "strings" - "testing" - "time" - - "github.com/multiversx/mx-chain-go/common" - "github.com/multiversx/mx-chain-go/config" - chainSimulatorIntegrationTests "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator" - "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator/staking" - "github.com/multiversx/mx-chain-go/node/chainSimulator" - "github.com/multiversx/mx-chain-go/node/chainSimulator/components/api" - "github.com/multiversx/mx-chain-go/node/chainSimulator/configs" - "github.com/multiversx/mx-chain-go/node/chainSimulator/dtos" - chainSimulatorProcess "github.com/multiversx/mx-chain-go/node/chainSimulator/process" - "github.com/multiversx/mx-chain-go/process" - "github.com/multiversx/mx-chain-go/vm" - - "github.com/multiversx/mx-chain-core-go/core" - coreAPI "github.com/multiversx/mx-chain-core-go/data/api" - "github.com/multiversx/mx-chain-core-go/data/transaction" - "github.com/multiversx/mx-chain-core-go/data/validator" - logger "github.com/multiversx/mx-chain-logger-go" - "github.com/stretchr/testify/require" -) - -const ( - defaultPathToInitialConfig = "../../../../cmd/node/config/" -) - -var log = logger.GetOrCreate("integrationTests/chainSimulator") - -// TODO scenarios -// Make a staking provider with max num of nodes -// DO a merge transaction - -// Test scenario -// 1. Add a new validator private key in the multi key handler -// 2. Do a stake transaction for the validator key -// 3. Do an unstake transaction (to make a place for the new validator) -// 4. Check if the new validator has generated rewards -func TestChainSimulator_AddValidatorKey(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 20, - } - - numOfShards := uint32(3) - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 0, - NumNodesWaitingListShard: 0, - AlterConfigsFunction: func(cfg *config.Configs) { - newNumNodes := cfg.SystemSCConfig.StakingSystemSCConfig.MaxNumberOfNodesForStake + 8 // 8 nodes until new nodes will be placed on queue - configs.SetMaxNumberOfNodesInConfigs(cfg, uint32(newNumNodes), 0, numOfShards) - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - err = cs.GenerateBlocks(30) - require.Nil(t, err) - - // Step 1 --- add a new validator key in the chain simulator - privateKey, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(1) - require.Nil(t, err) - - err = cs.AddValidatorKeys(privateKey) - require.Nil(t, err) - - newValidatorOwner := "erd1l6xt0rqlyzw56a3k8xwwshq2dcjwy3q9cppucvqsmdyw8r98dz3sae0kxl" - newValidatorOwnerBytes, _ := cs.GetNodeHandler(0).GetCoreComponents().AddressPubKeyConverter().Decode(newValidatorOwner) - rcv := "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" - rcvAddrBytes, _ := cs.GetNodeHandler(0).GetCoreComponents().AddressPubKeyConverter().Decode(rcv) - - // Step 2 --- set an initial balance for the address that will initialize all the transactions - err = cs.SetStateMultiple([]*dtos.AddressState{ - { - Address: "erd1l6xt0rqlyzw56a3k8xwwshq2dcjwy3q9cppucvqsmdyw8r98dz3sae0kxl", - Balance: "10000000000000000000000", - }, - }) - require.Nil(t, err) - - err = cs.GenerateBlocks(1) - require.Nil(t, err) - - // Step 3 --- generate and send a stake transaction with the BLS key of the validator key that was added at step 1 - stakeValue, _ := big.NewInt(0).SetString("2500000000000000000000", 10) - tx := &transaction.Transaction{ - Nonce: 0, - Value: stakeValue, - SndAddr: newValidatorOwnerBytes, - RcvAddr: rcvAddrBytes, - Data: []byte(fmt.Sprintf("stake@01@%s@010101", blsKeys[0])), - GasLimit: 50_000_000, - GasPrice: 1000000000, - Signature: []byte("dummy"), - ChainID: []byte(configs.ChainID), - Version: 1, - } - stakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, stakeTx) - - shardIDValidatorOwner := cs.GetNodeHandler(0).GetShardCoordinator().ComputeId(newValidatorOwnerBytes) - accountValidatorOwner, _, err := cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(newValidatorOwner, coreAPI.AccountQueryOptions{}) - require.Nil(t, err) - balanceBeforeActiveValidator := accountValidatorOwner.Balance - - // Step 5 --- create an unStake transaction with the bls key of an initial validator and execute the transaction to make place for the validator that was added at step 3 - firstValidatorKey, err := cs.GetValidatorPrivateKeys()[0].GeneratePublic().ToByteArray() - require.Nil(t, err) - - initialAddressWithValidators := cs.GetInitialWalletKeys().StakeWallets[0].Address - shardID := cs.GetNodeHandler(0).GetShardCoordinator().ComputeId(initialAddressWithValidators.Bytes) - initialAccount, _, err := cs.GetNodeHandler(shardID).GetFacadeHandler().GetAccount(initialAddressWithValidators.Bech32, coreAPI.AccountQueryOptions{}) - require.Nil(t, err) - tx = &transaction.Transaction{ - Nonce: initialAccount.Nonce, - Value: big.NewInt(0), - SndAddr: initialAddressWithValidators.Bytes, - RcvAddr: rcvAddrBytes, - Data: []byte(fmt.Sprintf("unStake@%s", hex.EncodeToString(firstValidatorKey))), - GasLimit: 50_000_000, - GasPrice: 1000000000, - Signature: []byte("dummy"), - ChainID: []byte(configs.ChainID), - Version: 1, - } - _, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - - // Step 6 --- generate 8 epochs to get rewards - err = cs.GenerateBlocksUntilEpochIsReached(8) - require.Nil(t, err) - - metachainNode := cs.GetNodeHandler(core.MetachainShardId) - err = cs.ForceResetValidatorStatisticsCache() - require.Nil(t, err) - validatorStatistics, err := metachainNode.GetFacadeHandler().ValidatorStatisticsApi() - require.Nil(t, err) - checkValidatorsRating(t, validatorStatistics) - - accountValidatorOwner, _, err = cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(newValidatorOwner, coreAPI.AccountQueryOptions{}) - require.Nil(t, err) - balanceAfterActiveValidator := accountValidatorOwner.Balance - - log.Info("balance before validator", "value", balanceBeforeActiveValidator) - log.Info("balance after validator", "value", balanceAfterActiveValidator) - - balanceBeforeBig, _ := big.NewInt(0).SetString(balanceBeforeActiveValidator, 10) - balanceAfterBig, _ := big.NewInt(0).SetString(balanceAfterActiveValidator, 10) - diff := balanceAfterBig.Sub(balanceAfterBig, balanceBeforeBig) - log.Info("difference", "value", diff.String()) - - // Step 7 --- check the balance of the validator owner has been increased - require.True(t, diff.Cmp(big.NewInt(0)) > 0) -} - -func TestChainSimulator_AddANewValidatorAfterStakingV4(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 20, - } - numOfShards := uint32(3) - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 100, - MetaChainMinNodes: 100, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.SystemSCConfig.StakingSystemSCConfig.NodeLimitPercentage = 1 - cfg.GeneralConfig.ValidatorStatistics.CacheRefreshIntervalInSec = 1 - eligibleNodes := cfg.SystemSCConfig.StakingSystemSCConfig.MaxNumberOfNodesForStake - // 8 nodes until new nodes will be placed on queue - waitingNodes := uint32(8) - configs.SetMaxNumberOfNodesInConfigs(cfg, uint32(eligibleNodes), waitingNodes, numOfShards) - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - err = cs.GenerateBlocks(150) - require.Nil(t, err) - - // Step 1 --- add a new validator key in the chain simulator - numOfNodes := 20 - validatorSecretKeysBytes, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(numOfNodes) - require.Nil(t, err) - err = cs.AddValidatorKeys(validatorSecretKeysBytes) - require.Nil(t, err) - - newValidatorOwner := "erd1l6xt0rqlyzw56a3k8xwwshq2dcjwy3q9cppucvqsmdyw8r98dz3sae0kxl" - newValidatorOwnerBytes, _ := cs.GetNodeHandler(0).GetCoreComponents().AddressPubKeyConverter().Decode(newValidatorOwner) - rcv := "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" - rcvAddrBytes, _ := cs.GetNodeHandler(0).GetCoreComponents().AddressPubKeyConverter().Decode(rcv) - - // Step 2 --- set an initial balance for the address that will initialize all the transactions - err = cs.SetStateMultiple([]*dtos.AddressState{ - { - Address: "erd1l6xt0rqlyzw56a3k8xwwshq2dcjwy3q9cppucvqsmdyw8r98dz3sae0kxl", - Balance: "1000000000000000000000000", - }, - }) - require.Nil(t, err) - - err = cs.GenerateBlocks(1) - require.Nil(t, err) - - // Step 3 --- generate and send a stake transaction with the BLS keys of the validators key that were added at step 1 - validatorData := "" - for _, blsKey := range blsKeys { - validatorData += fmt.Sprintf("@%s@010101", blsKey) - } - - numOfNodesHex := hex.EncodeToString(big.NewInt(int64(numOfNodes)).Bytes()) - stakeValue, _ := big.NewInt(0).SetString("51000000000000000000000", 10) - tx := &transaction.Transaction{ - Nonce: 0, - Value: stakeValue, - SndAddr: newValidatorOwnerBytes, - RcvAddr: rcvAddrBytes, - Data: []byte(fmt.Sprintf("stake@%s%s", numOfNodesHex, validatorData)), - GasLimit: 500_000_000, - GasPrice: 1000000000, - Signature: []byte("dummy"), - ChainID: []byte(configs.ChainID), - Version: 1, - } - - txFromNetwork, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txFromNetwork) - - err = cs.GenerateBlocks(1) - require.Nil(t, err) - - metachainNode := cs.GetNodeHandler(core.MetachainShardId) - err = cs.ForceResetValidatorStatisticsCache() - require.Nil(t, err) - results, err := metachainNode.GetFacadeHandler().AuctionListApi() - require.Nil(t, err) - require.Equal(t, newValidatorOwner, results[0].Owner) - require.Equal(t, 20, len(results[0].Nodes)) - checkTotalQualified(t, results, 8) - - err = cs.GenerateBlocks(100) - require.Nil(t, err) - - results, err = cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().AuctionListApi() - require.Nil(t, err) - checkTotalQualified(t, results, 0) -} - -// Internal test scenario #4 #5 #6 -// do stake -// do unStake -// do unBondNodes -// do unBondTokens -func TestChainSimulatorStakeUnStakeUnBond(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - t.Run("staking ph 4 is not active", func(t *testing.T) { - testStakeUnStakeUnBond(t, 1) - }) - - t.Run("staking ph 4 step 1 active", func(t *testing.T) { - testStakeUnStakeUnBond(t, 4) - }) - - t.Run("staking ph 4 step 2 active", func(t *testing.T) { - testStakeUnStakeUnBond(t, 5) - }) - - t.Run("staking ph 4 step 3 active", func(t *testing.T) { - testStakeUnStakeUnBond(t, 6) - }) -} - -func testStakeUnStakeUnBond(t *testing.T, targetEpoch int32) { - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 30, - } - numOfShards := uint32(3) - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriod = 1 - cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 1 - newNumNodes := cfg.SystemSCConfig.StakingSystemSCConfig.MaxNumberOfNodesForStake + 10 - configs.SetMaxNumberOfNodesInConfigs(cfg, uint32(newNumNodes), 0, numOfShards) - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - err = cs.GenerateBlocksUntilEpochIsReached(targetEpoch) - require.Nil(t, err) - - privateKeys, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(1) - require.Nil(t, err) - err = cs.AddValidatorKeys(privateKeys) - require.Nil(t, err) - - mintValue := big.NewInt(0).Mul(chainSimulatorIntegrationTests.OneEGLD, big.NewInt(2600)) - walletAddressShardID := uint32(0) - walletAddress, err := cs.GenerateAndMintWalletAddress(walletAddressShardID, mintValue) - require.Nil(t, err) - - err = cs.GenerateBlocks(1) - require.Nil(t, err) - - txDataField := fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) - txStake := chainSimulatorIntegrationTests.GenerateTransaction(walletAddress.Bytes, 0, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.MinimumStakeValue, txDataField, staking.GasLimitForStakeOperation) - stakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, stakeTx) - - metachainNode := cs.GetNodeHandler(core.MetachainShardId) - bls0, _ := hex.DecodeString(blsKeys[0]) - blsKeyStatus := staking.GetBLSKeyStatus(t, metachainNode, bls0) - require.Equal(t, "staked", blsKeyStatus) - - // do unStake - txUnStake := chainSimulatorIntegrationTests.GenerateTransaction(walletAddress.Bytes, 1, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, fmt.Sprintf("unStake@%s", blsKeys[0]), staking.GasLimitForStakeOperation) - unStakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, unStakeTx) - - blsKeyStatus = staking.GetBLSKeyStatus(t, metachainNode, bls0) - require.Equal(t, "unStaked", blsKeyStatus) - - err = cs.GenerateBlocksUntilEpochIsReached(targetEpoch + 1) - require.Nil(t, err) - - // do unBond - txUnBond := chainSimulatorIntegrationTests.GenerateTransaction(walletAddress.Bytes, 2, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, fmt.Sprintf("unBondNodes@%s", blsKeys[0]), staking.GasLimitForStakeOperation) - unBondTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnBond, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, unBondTx) - - // do claim - txClaim := chainSimulatorIntegrationTests.GenerateTransaction(walletAddress.Bytes, 3, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, "unBondTokens", staking.GasLimitForStakeOperation) - claimTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txClaim, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, claimTx) - - err = cs.GenerateBlocks(5) - require.Nil(t, err) - - // check tokens are in the wallet balance - walletAccount, _, err := cs.GetNodeHandler(walletAddressShardID).GetFacadeHandler().GetAccount(walletAddress.Bech32, coreAPI.AccountQueryOptions{}) - require.Nil(t, err) - walletBalanceBig, _ := big.NewInt(0).SetString(walletAccount.Balance, 10) - require.True(t, walletBalanceBig.Cmp(chainSimulatorIntegrationTests.MinimumStakeValue) > 0) -} - -func checkTotalQualified(t *testing.T, auctionList []*common.AuctionListValidatorAPIResponse, expected int) { - totalQualified := 0 - for _, res := range auctionList { - for _, node := range res.Nodes { - if node.Qualified { - totalQualified++ - } - } - } - require.Equal(t, expected, totalQualified) -} - -func checkValidatorsRating(t *testing.T, validatorStatistics map[string]*validator.ValidatorStatistics) { - countRatingIncreased := 0 - for _, validatorInfo := range validatorStatistics { - validatorSignedAtLeastOneBlock := validatorInfo.NumValidatorSuccess > 0 || validatorInfo.NumLeaderSuccess > 0 - if !validatorSignedAtLeastOneBlock { - continue - } - countRatingIncreased++ - require.Greater(t, validatorInfo.TempRating, validatorInfo.Rating) - } - require.Greater(t, countRatingIncreased, 0) -} - -// Test description -// Stake funds - happy flow -// -// Preconditions: have an account with egld and 2 staked nodes (2500 stake per node) - directly staked, and no unstake -// -// 1. Check the stake amount for the owner of the staked nodes with the vmquery "getTotalStaked", and the account current EGLD balance -// 2. Create from the owner of staked nodes a transaction to stake 1 EGLD and send it to the network -// 3. Check the outcome of the TX & verify new stake state with vmquery - -// Internal test scenario #24 -func TestChainSimulator_DirectStakingNodes_StakeFunds(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 30, - } - - t.Run("staking ph 4 is not active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 100 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 101 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 102 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 102 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedNodesStakingFunds(t, cs, 1) - }) - - t.Run("staking ph 4 step 1 is active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedNodesStakingFunds(t, cs, 2) - }) - - t.Run("staking ph 4 step 2 is active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedNodesStakingFunds(t, cs, 3) - }) - - t.Run("staking ph 4 step 3 is active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedNodesStakingFunds(t, cs, 4) - }) -} - -func testChainSimulatorDirectStakedNodesStakingFunds(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator, targetEpoch int32) { - err := cs.GenerateBlocksUntilEpochIsReached(targetEpoch) - require.Nil(t, err) - - log.Info("Preconditions. Have an account with 2 staked nodes") - privateKeys, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(2) - require.Nil(t, err) - - err = cs.AddValidatorKeys(privateKeys) - require.Nil(t, err) - metachainNode := cs.GetNodeHandler(core.MetachainShardId) - - mintValue := big.NewInt(5010) - mintValue = mintValue.Mul(chainSimulatorIntegrationTests.OneEGLD, mintValue) - - validatorOwner, err := cs.GenerateAndMintWalletAddress(core.AllShardId, mintValue) - require.Nil(t, err) - - err = cs.GenerateBlocks(1) - require.Nil(t, err) - - stakeValue := big.NewInt(0).Set(chainSimulatorIntegrationTests.MinimumStakeValue) - txDataField := fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) - txStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 0, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) - stakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, stakeTx) - - err = cs.GenerateBlocks(2) // allow the metachain to finalize the block that contains the staking of the node - require.Nil(t, err) - - stakeValue = big.NewInt(0).Set(chainSimulatorIntegrationTests.MinimumStakeValue) - txDataField = fmt.Sprintf("stake@01@%s@%s", blsKeys[1], staking.MockBLSSignature) - txStake = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 1, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) - stakeTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, stakeTx) - - err = cs.GenerateBlocks(2) // allow the metachain to finalize the block that contains the staking of the node - require.Nil(t, err) - - log.Info("Step 1. Check the stake amount for the owner of the staked nodes") - checkExpectedStakedValue(t, metachainNode, validatorOwner.Bytes, 5000) - - log.Info("Step 2. Create from the owner of the staked nodes a tx to stake 1 EGLD") - - stakeValue = big.NewInt(0).Mul(chainSimulatorIntegrationTests.OneEGLD, big.NewInt(1)) - txDataField = fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) - txStake = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 2, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) - stakeTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, stakeTx) - - err = cs.GenerateBlocks(2) // allow the metachain to finalize the block that contains the staking of the node - require.Nil(t, err) - - log.Info("Step 3. Check the stake amount for the owner of the staked nodes") - checkExpectedStakedValue(t, metachainNode, validatorOwner.Bytes, 5001) -} - -func checkExpectedStakedValue(t *testing.T, metachainNode chainSimulatorProcess.NodeHandler, blsKey []byte, expectedValue int64) { - totalStaked := getTotalStaked(t, metachainNode, blsKey) - - expectedStaked := big.NewInt(expectedValue) - expectedStaked = expectedStaked.Mul(chainSimulatorIntegrationTests.OneEGLD, expectedStaked) - require.Equal(t, expectedStaked.String(), string(totalStaked)) -} - -func getTotalStaked(t *testing.T, metachainNode chainSimulatorProcess.NodeHandler, blsKey []byte) []byte { - scQuery := &process.SCQuery{ - ScAddress: vm.ValidatorSCAddress, - FuncName: "getTotalStaked", - CallerAddr: vm.ValidatorSCAddress, - CallValue: big.NewInt(0), - Arguments: [][]byte{blsKey}, - } - result, _, err := metachainNode.GetFacadeHandler().ExecuteSCQuery(scQuery) - require.Nil(t, err) - require.Equal(t, chainSimulatorIntegrationTests.OkReturnCode, result.ReturnCode) - - return result.ReturnData[0] -} - -// Test description: -// Unstake funds with deactivation of node if below 2500 -> the rest of funds are distributed as topup at epoch change -// -// Internal test scenario #26 -func TestChainSimulator_DirectStakingNodes_UnstakeFundsWithDeactivation(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 30, - } - - // Test Steps - // 1. Check the stake amount and number of nodes for the owner of the staked nodes with the vmquery "getTotalStaked", and the account current EGLD balance - // 2. Create from the owner of staked nodes a transaction to unstake 10 EGLD and send it to the network - // 3. Check the outcome of the TX & verify new stake state with vmquery "getTotalStaked" and "getUnStakedTokensList" - // 4. Wait for change of epoch and check the outcome - - t.Run("staking ph 4 is not active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 100 - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 100 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 101 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 102 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 102 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedUnstakeFundsWithDeactivation(t, cs, 1) - }) - - t.Run("staking ph 4 step 1 is active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 - cfg.SystemSCConfig.StakingSystemSCConfig.NodeLimitPercentage = 1 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedUnstakeFundsWithDeactivation(t, cs, 2) - }) - - t.Run("staking ph 4 step 2 is active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 - cfg.SystemSCConfig.StakingSystemSCConfig.NodeLimitPercentage = 1 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedUnstakeFundsWithDeactivation(t, cs, 3) - }) - - t.Run("staking ph 4 step 3 is active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 - cfg.SystemSCConfig.StakingSystemSCConfig.NodeLimitPercentage = 1 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedUnstakeFundsWithDeactivation(t, cs, 4) - }) -} - -func testChainSimulatorDirectStakedUnstakeFundsWithDeactivation(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator, targetEpoch int32) { - err := cs.GenerateBlocksUntilEpochIsReached(targetEpoch) - require.Nil(t, err) - - privateKeys, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(2) - require.Nil(t, err) - - err = cs.AddValidatorKeys(privateKeys) - require.Nil(t, err) - metachainNode := cs.GetNodeHandler(core.MetachainShardId) - - mintValue := big.NewInt(5010) - mintValue = mintValue.Mul(chainSimulatorIntegrationTests.OneEGLD, mintValue) - - validatorOwner, err := cs.GenerateAndMintWalletAddress(core.AllShardId, mintValue) - require.Nil(t, err) - - err = cs.GenerateBlocks(1) - require.Nil(t, err) - - stakeValue := big.NewInt(0).Set(chainSimulatorIntegrationTests.MinimumStakeValue) - txDataField := fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) - txStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 0, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) - stakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, stakeTx) - - err = cs.GenerateBlocks(2) // allow the metachain to finalize the block that contains the staking of the node - require.Nil(t, err) - - testBLSKeyStaked(t, metachainNode, blsKeys[0]) - - stakeValue = big.NewInt(0).Set(chainSimulatorIntegrationTests.MinimumStakeValue) - txDataField = fmt.Sprintf("stake@01@%s@%s", blsKeys[1], staking.MockBLSSignature) - txStake = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 1, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) - stakeTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, stakeTx) - - err = cs.GenerateBlocks(2) // allow the metachain to finalize the block that contains the staking of the node - require.Nil(t, err) - - testBLSKeyStaked(t, metachainNode, blsKeys[1]) - - log.Info("Step 1. Check the stake amount for the owner of the staked nodes") - checkExpectedStakedValue(t, metachainNode, validatorOwner.Bytes, 5000) - - log.Info("Step 2. Create from the owner of staked nodes a transaction to unstake 10 EGLD and send it to the network") - - unStakeValue := big.NewInt(10) - unStakeValue = unStakeValue.Mul(chainSimulatorIntegrationTests.OneEGLD, unStakeValue) - txDataField = fmt.Sprintf("unStakeTokens@%s", hex.EncodeToString(unStakeValue.Bytes())) - txUnStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 2, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForStakeOperation) - unStakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, unStakeTx) - - err = cs.GenerateBlocks(2) - require.Nil(t, err) - - log.Info("Step 3. Check the outcome of the TX & verify new stake state with vmquery getTotalStaked and getUnStakedTokensList") - checkExpectedStakedValue(t, metachainNode, validatorOwner.Bytes, 4990) - - unStakedTokensAmount := getUnStakedTokensList(t, metachainNode, validatorOwner.Bytes) - - expectedUnStaked := big.NewInt(10) - expectedUnStaked = expectedUnStaked.Mul(chainSimulatorIntegrationTests.OneEGLD, expectedUnStaked) - require.Equal(t, expectedUnStaked.String(), big.NewInt(0).SetBytes(unStakedTokensAmount).String()) - - log.Info("Step 4. Wait for change of epoch and check the outcome") - err = cs.GenerateBlocksUntilEpochIsReached(targetEpoch + 1) - require.Nil(t, err) - - checkOneOfTheNodesIsUnstaked(t, metachainNode, blsKeys[:2]) -} - -func getUnStakedTokensList(t *testing.T, metachainNode chainSimulatorProcess.NodeHandler, blsKey []byte) []byte { - scQuery := &process.SCQuery{ - ScAddress: vm.ValidatorSCAddress, - FuncName: "getUnStakedTokensList", - CallerAddr: vm.ValidatorSCAddress, - CallValue: big.NewInt(0), - Arguments: [][]byte{blsKey}, - } - result, _, err := metachainNode.GetFacadeHandler().ExecuteSCQuery(scQuery) - require.Nil(t, err) - require.Equal(t, chainSimulatorIntegrationTests.OkReturnCode, result.ReturnCode) - - return result.ReturnData[0] -} - -func checkOneOfTheNodesIsUnstaked(t *testing.T, - metachainNode chainSimulatorProcess.NodeHandler, - blsKeys []string, -) { - decodedBLSKey0, _ := hex.DecodeString(blsKeys[0]) - keyStatus0 := staking.GetBLSKeyStatus(t, metachainNode, decodedBLSKey0) - log.Info("Key info", "key", blsKeys[0], "status", keyStatus0) - - isNotStaked0 := keyStatus0 == staking.UnStakedStatus - - decodedBLSKey1, _ := hex.DecodeString(blsKeys[1]) - keyStatus1 := staking.GetBLSKeyStatus(t, metachainNode, decodedBLSKey1) - log.Info("Key info", "key", blsKeys[1], "status", keyStatus1) - - isNotStaked1 := keyStatus1 == staking.UnStakedStatus - - require.True(t, isNotStaked0 != isNotStaked1) -} - -func testBLSKeyStaked(t *testing.T, - metachainNode chainSimulatorProcess.NodeHandler, - blsKey string, -) { - decodedBLSKey, _ := hex.DecodeString(blsKey) - err := metachainNode.GetProcessComponents().ValidatorsProvider().ForceUpdate() - require.Nil(t, err) - - validatorStatistics, err := metachainNode.GetFacadeHandler().ValidatorStatisticsApi() - require.Nil(t, err) - - activationEpoch := metachainNode.GetCoreComponents().EnableEpochsHandler().GetActivationEpoch(common.StakingV4Step1Flag) - if activationEpoch <= metachainNode.GetCoreComponents().EnableEpochsHandler().GetCurrentEpoch() { - require.Equal(t, staking.StakedStatus, staking.GetBLSKeyStatus(t, metachainNode, decodedBLSKey)) - return - } - - // in staking ph 2/3.5 we do not find the bls key on the validator statistics - _, found := validatorStatistics[blsKey] - require.False(t, found) - require.Equal(t, staking.QueuedStatus, staking.GetBLSKeyStatus(t, metachainNode, decodedBLSKey)) -} - -// Test description: -// Unstake funds with deactivation of node, followed by stake with sufficient ammount does not unstake node at end of epoch -// -// Internal test scenario #27 -func TestChainSimulator_DirectStakingNodes_UnstakeFundsWithDeactivation_WithReactivation(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 30, - } - - // Test Steps - // 1. Check the stake amount and number of nodes for the owner of the staked nodes with the vmquery "getTotalStaked", and the account current EGLD balance - // 2. Create from the owner of staked nodes a transaction to unstake 10 EGLD and send it to the network - // 3. Check the outcome of the TX & verify new stake state with vmquery - // 4. Create from the owner of staked nodes a transaction to stake 10 EGLD and send it to the network - // 5. Check the outcome of the TX & verify new stake state with vmquery - // 6. Wait for change of epoch and check the outcome - - t.Run("staking ph 4 is not active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 100 - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 100 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 101 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 102 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 102 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedUnstakeFundsWithDeactivationAndReactivation(t, cs, 1) - }) - - t.Run("staking ph 4 step 1 is active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 - cfg.SystemSCConfig.StakingSystemSCConfig.NodeLimitPercentage = 1 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedUnstakeFundsWithDeactivationAndReactivation(t, cs, 2) - }) - - t.Run("staking ph 4 step 2 is active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 - cfg.SystemSCConfig.StakingSystemSCConfig.NodeLimitPercentage = 1 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedUnstakeFundsWithDeactivationAndReactivation(t, cs, 3) - }) - - t.Run("staking ph 4 step 3 is active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 - cfg.SystemSCConfig.StakingSystemSCConfig.NodeLimitPercentage = 1 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedUnstakeFundsWithDeactivationAndReactivation(t, cs, 4) - }) -} - -func testChainSimulatorDirectStakedUnstakeFundsWithDeactivationAndReactivation(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator, targetEpoch int32) { - err := cs.GenerateBlocksUntilEpochIsReached(targetEpoch) - require.Nil(t, err) - - privateKeys, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(2) - require.Nil(t, err) - - err = cs.AddValidatorKeys(privateKeys) - require.Nil(t, err) - metachainNode := cs.GetNodeHandler(core.MetachainShardId) - - mintValue := big.NewInt(6000) - mintValue = mintValue.Mul(chainSimulatorIntegrationTests.OneEGLD, mintValue) - - validatorOwner, err := cs.GenerateAndMintWalletAddress(core.AllShardId, mintValue) - require.Nil(t, err) - - err = cs.GenerateBlocks(1) - require.Nil(t, err) - - stakeValue := big.NewInt(0).Set(chainSimulatorIntegrationTests.MinimumStakeValue) - txDataField := fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) - txStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 0, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) - stakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, stakeTx) - - err = cs.GenerateBlocks(2) // allow the metachain to finalize the block that contains the staking of the node - require.Nil(t, err) - - testBLSKeyStaked(t, metachainNode, blsKeys[0]) - - stakeValue = big.NewInt(0).Set(chainSimulatorIntegrationTests.MinimumStakeValue) - txDataField = fmt.Sprintf("stake@01@%s@%s", blsKeys[1], staking.MockBLSSignature) - txStake = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 1, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) - stakeTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, stakeTx) - - err = cs.GenerateBlocks(2) // allow the metachain to finalize the block that contains the staking of the node - require.Nil(t, err) - - testBLSKeyStaked(t, metachainNode, blsKeys[1]) - - log.Info("Step 1. Check the stake amount for the owner of the staked nodes") - checkExpectedStakedValue(t, metachainNode, validatorOwner.Bytes, 5000) - - log.Info("Step 2. Create from the owner of staked nodes a transaction to unstake 10 EGLD and send it to the network") - - unStakeValue := big.NewInt(10) - unStakeValue = unStakeValue.Mul(chainSimulatorIntegrationTests.OneEGLD, unStakeValue) - txDataField = fmt.Sprintf("unStakeTokens@%s", hex.EncodeToString(unStakeValue.Bytes())) - txUnStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 2, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForStakeOperation) - unStakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, unStakeTx) - - err = cs.GenerateBlocks(2) - require.Nil(t, err) - - log.Info("Step 3. Check the outcome of the TX & verify new stake state with vmquery getTotalStaked and getUnStakedTokensList") - checkExpectedStakedValue(t, metachainNode, validatorOwner.Bytes, 4990) - - unStakedTokensAmount := getUnStakedTokensList(t, metachainNode, validatorOwner.Bytes) - - expectedUnStaked := big.NewInt(10) - expectedUnStaked = expectedUnStaked.Mul(chainSimulatorIntegrationTests.OneEGLD, expectedUnStaked) - require.Equal(t, expectedUnStaked.String(), big.NewInt(0).SetBytes(unStakedTokensAmount).String()) - - log.Info("Step 4. Create from the owner of staked nodes a transaction to stake 10 EGLD and send it to the network") - - newStakeValue := big.NewInt(10) - newStakeValue = newStakeValue.Mul(chainSimulatorIntegrationTests.OneEGLD, newStakeValue) - txDataField = fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) - txStake = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 3, vm.ValidatorSCAddress, newStakeValue, txDataField, staking.GasLimitForStakeOperation) - stakeTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, stakeTx) - - err = cs.GenerateBlocks(2) - require.Nil(t, err) - - log.Info("5. Check the outcome of the TX & verify new stake state with vmquery") - checkExpectedStakedValue(t, metachainNode, validatorOwner.Bytes, 5000) - - log.Info("Step 6. Wait for change of epoch and check the outcome") - err = cs.GenerateBlocksUntilEpochIsReached(targetEpoch + 1) - require.Nil(t, err) - - testBLSKeyStaked(t, metachainNode, blsKeys[0]) - testBLSKeyStaked(t, metachainNode, blsKeys[1]) -} - -// Test description: -// Withdraw unstaked funds before unbonding period should return error -// -// Internal test scenario #28 -func TestChainSimulator_DirectStakingNodes_WithdrawUnstakedFundsBeforeUnbonding(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 30, - } - - // Test Steps - // 1. Create from the owner of staked nodes a transaction to withdraw the unstaked funds - // 2. Check the outcome of the TX & verify new stake state with vmquery ("getUnStakedTokensList") - - t.Run("staking ph 4 is not active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 100 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 101 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 102 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 102 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedWithdrawUnstakedFundsBeforeUnbonding(t, cs, 1) - }) - - t.Run("staking ph 4 step 1 is active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedWithdrawUnstakedFundsBeforeUnbonding(t, cs, 2) - }) - - t.Run("staking ph 4 step 2 is active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedWithdrawUnstakedFundsBeforeUnbonding(t, cs, 3) - }) - - t.Run("staking ph 4 step 3 is active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedWithdrawUnstakedFundsBeforeUnbonding(t, cs, 4) - }) -} - -func testChainSimulatorDirectStakedWithdrawUnstakedFundsBeforeUnbonding(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator, targetEpoch int32) { - err := cs.GenerateBlocksUntilEpochIsReached(targetEpoch) - require.Nil(t, err) - - privateKey, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(1) - require.Nil(t, err) - - err = cs.AddValidatorKeys(privateKey) - require.Nil(t, err) - metachainNode := cs.GetNodeHandler(core.MetachainShardId) - - mintValue := big.NewInt(10000) - mintValue = mintValue.Mul(chainSimulatorIntegrationTests.OneEGLD, mintValue) - - validatorOwner, err := cs.GenerateAndMintWalletAddress(core.AllShardId, mintValue) - require.Nil(t, err) - - err = cs.GenerateBlocks(1) - require.Nil(t, err) - - stakeValue := big.NewInt(0).Mul(chainSimulatorIntegrationTests.OneEGLD, big.NewInt(2600)) - txDataField := fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) - txStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 0, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) - stakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, stakeTx) - - err = cs.GenerateBlocks(2) // allow the metachain to finalize the block that contains the staking of the node - require.Nil(t, err) - - testBLSKeyStaked(t, metachainNode, blsKeys[0]) - - shardIDValidatorOwner := cs.GetNodeHandler(0).GetShardCoordinator().ComputeId(validatorOwner.Bytes) - accountValidatorOwner, _, err := cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(validatorOwner.Bech32, coreAPI.AccountQueryOptions{}) - require.Nil(t, err) - balanceBeforeUnbonding, _ := big.NewInt(0).SetString(accountValidatorOwner.Balance, 10) - - log.Info("Step 1. Create from the owner of staked nodes a transaction to withdraw the unstaked funds") - - unStakeValue := big.NewInt(10) - unStakeValue = unStakeValue.Mul(chainSimulatorIntegrationTests.OneEGLD, unStakeValue) - txDataField = fmt.Sprintf("unStakeTokens@%s", hex.EncodeToString(unStakeValue.Bytes())) - txUnStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 1, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForStakeOperation) - unStakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, unStakeTx) - - err = cs.GenerateBlocks(2) - require.Nil(t, err) - - // check bls key is still staked - testBLSKeyStaked(t, metachainNode, blsKeys[0]) - - txDataField = fmt.Sprintf("unBondTokens@%s", blsKeys[0]) - txUnBond := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 2, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForUnBond) - unBondTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnBond, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, unBondTx) - - err = cs.GenerateBlocks(2) - require.Nil(t, err) - - log.Info("Step 2. Check the outcome of the TX & verify new stake state with vmquery (`getUnStakedTokensList`)") - - scQuery := &process.SCQuery{ - ScAddress: vm.ValidatorSCAddress, - FuncName: "getUnStakedTokensList", - CallerAddr: vm.ValidatorSCAddress, - CallValue: big.NewInt(0), - Arguments: [][]byte{validatorOwner.Bytes}, - } - result, _, err := metachainNode.GetFacadeHandler().ExecuteSCQuery(scQuery) - require.Nil(t, err) - require.Equal(t, chainSimulatorIntegrationTests.OkReturnCode, result.ReturnCode) - - expectedUnStaked := big.NewInt(10) - expectedUnStaked = expectedUnStaked.Mul(chainSimulatorIntegrationTests.OneEGLD, expectedUnStaked) - require.Equal(t, expectedUnStaked.String(), big.NewInt(0).SetBytes(result.ReturnData[0]).String()) - - // the owner balance should decrease only with the txs fee - accountValidatorOwner, _, err = cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(validatorOwner.Bech32, coreAPI.AccountQueryOptions{}) - require.Nil(t, err) - balanceAfterUnbonding, _ := big.NewInt(0).SetString(accountValidatorOwner.Balance, 10) - - txsFee, _ := big.NewInt(0).SetString(unBondTx.Fee, 10) - balanceAfterUnbondingWithFee := big.NewInt(0).Add(balanceAfterUnbonding, txsFee) - - txsFee, _ = big.NewInt(0).SetString(unStakeTx.Fee, 10) - balanceAfterUnbondingWithFee.Add(balanceAfterUnbondingWithFee, txsFee) - - txsFee, _ = big.NewInt(0).SetString(stakeTx.Fee, 10) - balanceAfterUnbondingWithFee.Add(balanceAfterUnbondingWithFee, txsFee) - - require.Equal(t, 1, balanceAfterUnbondingWithFee.Cmp(balanceBeforeUnbonding)) -} - -// Test description: -// Withdraw unstaked funds in first available withdraw epoch -// -// Internal test scenario #29 -func TestChainSimulator_DirectStakingNodes_WithdrawUnstakedInWithdrawEpoch(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 30, - } - - // Test Steps - // 1. Wait for the unbonding epoch to start - // 2. Create from the owner of staked nodes a transaction to withdraw the unstaked funds - // 3. Check the outcome of the TX & verify new stake state with vmquery ("getUnStakedTokensList") - - t.Run("staking ph 4 is not active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 100 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 101 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 102 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 102 - cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 1 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedWithdrawUnstakedFundsInFirstEpoch(t, cs, 1) - }) - - t.Run("staking ph 4 step 1 is active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 - cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 1 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedWithdrawUnstakedFundsInFirstEpoch(t, cs, 2) - }) - - t.Run("staking ph 4 step 2 is active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 - cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 1 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedWithdrawUnstakedFundsInFirstEpoch(t, cs, 3) - }) - - t.Run("staking ph 4 step 3 is active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 - cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 1 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedWithdrawUnstakedFundsInFirstEpoch(t, cs, 4) - }) -} - -func testChainSimulatorDirectStakedWithdrawUnstakedFundsInFirstEpoch(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator, targetEpoch int32) { - err := cs.GenerateBlocksUntilEpochIsReached(targetEpoch) - require.Nil(t, err) - - privateKey, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(1) - require.Nil(t, err) - - err = cs.AddValidatorKeys(privateKey) - require.Nil(t, err) - metachainNode := cs.GetNodeHandler(core.MetachainShardId) - - mintValue := big.NewInt(10000) - mintValue = mintValue.Mul(chainSimulatorIntegrationTests.OneEGLD, mintValue) - - validatorOwner, err := cs.GenerateAndMintWalletAddress(core.AllShardId, mintValue) - require.Nil(t, err) - - err = cs.GenerateBlocks(1) - require.Nil(t, err) - - stakeValue := big.NewInt(0).Mul(chainSimulatorIntegrationTests.OneEGLD, big.NewInt(2600)) - txDataField := fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) - txStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 0, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) - stakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, stakeTx) - - err = cs.GenerateBlocks(2) // allow the metachain to finalize the block that contains the staking of the node - require.Nil(t, err) - - testBLSKeyStaked(t, metachainNode, blsKeys[0]) - - shardIDValidatorOwner := cs.GetNodeHandler(0).GetShardCoordinator().ComputeId(validatorOwner.Bytes) - accountValidatorOwner, _, err := cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(validatorOwner.Bech32, coreAPI.AccountQueryOptions{}) - require.Nil(t, err) - balanceBeforeUnbonding, _ := big.NewInt(0).SetString(accountValidatorOwner.Balance, 10) - - unStakeValue := big.NewInt(10) - unStakeValue = unStakeValue.Mul(chainSimulatorIntegrationTests.OneEGLD, unStakeValue) - txDataField = fmt.Sprintf("unStakeTokens@%s", hex.EncodeToString(unStakeValue.Bytes())) - txUnStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 1, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForStakeOperation) - unStakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, unStakeTx) - - err = cs.GenerateBlocks(2) - require.Nil(t, err) - - // check bls key is still staked - testBLSKeyStaked(t, metachainNode, blsKeys[0]) - - scQuery := &process.SCQuery{ - ScAddress: vm.ValidatorSCAddress, - FuncName: "getUnStakedTokensList", - CallerAddr: vm.ValidatorSCAddress, - CallValue: big.NewInt(0), - Arguments: [][]byte{validatorOwner.Bytes}, - } - result, _, err := metachainNode.GetFacadeHandler().ExecuteSCQuery(scQuery) - require.Nil(t, err) - require.Equal(t, chainSimulatorIntegrationTests.OkReturnCode, result.ReturnCode) - - expectedUnStaked := big.NewInt(10) - expectedUnStaked = expectedUnStaked.Mul(chainSimulatorIntegrationTests.OneEGLD, expectedUnStaked) - require.Equal(t, expectedUnStaked.String(), big.NewInt(0).SetBytes(result.ReturnData[0]).String()) - - log.Info("Step 1. Wait for the unbonding epoch to start") - - err = cs.GenerateBlocksUntilEpochIsReached(targetEpoch + 1) - require.Nil(t, err) - - log.Info("Step 2. Create from the owner of staked nodes a transaction to withdraw the unstaked funds") - - txDataField = fmt.Sprintf("unBondTokens@%s", blsKeys[0]) - txUnBond := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 2, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForUnBond) - unBondTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnBond, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, unBondTx) - - err = cs.GenerateBlocks(2) - require.Nil(t, err) - - log.Info("Step 3. Check the outcome of the TX & verify new stake state with vmquery (`getUnStakedTokensList`)") - - scQuery = &process.SCQuery{ - ScAddress: vm.ValidatorSCAddress, - FuncName: "getTotalStaked", - CallerAddr: vm.ValidatorSCAddress, - CallValue: big.NewInt(0), - Arguments: [][]byte{validatorOwner.Bytes}, - } - result, _, err = metachainNode.GetFacadeHandler().ExecuteSCQuery(scQuery) - require.Nil(t, err) - require.Equal(t, chainSimulatorIntegrationTests.OkReturnCode, result.ReturnCode) - - expectedStaked := big.NewInt(2590) - expectedStaked = expectedStaked.Mul(chainSimulatorIntegrationTests.OneEGLD, expectedStaked) - require.Equal(t, expectedStaked.String(), string(result.ReturnData[0])) - - // the owner balance should increase with the (10 EGLD - tx fee) - accountValidatorOwner, _, err = cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(validatorOwner.Bech32, coreAPI.AccountQueryOptions{}) - require.Nil(t, err) - balanceAfterUnbonding, _ := big.NewInt(0).SetString(accountValidatorOwner.Balance, 10) - - // substract unbonding value - balanceAfterUnbonding.Sub(balanceAfterUnbonding, unStakeValue) - - txsFee, _ := big.NewInt(0).SetString(unBondTx.Fee, 10) - balanceAfterUnbondingWithFee := big.NewInt(0).Add(balanceAfterUnbonding, txsFee) - - txsFee, _ = big.NewInt(0).SetString(unStakeTx.Fee, 10) - balanceAfterUnbondingWithFee.Add(balanceAfterUnbondingWithFee, txsFee) - - txsFee, _ = big.NewInt(0).SetString(stakeTx.Fee, 10) - balanceAfterUnbondingWithFee.Add(balanceAfterUnbondingWithFee, txsFee) - - require.Equal(t, 1, balanceAfterUnbondingWithFee.Cmp(balanceBeforeUnbonding)) -} - -// Test description: -// Unstaking funds in different batches allows correct withdrawal for each batch -// at the corresponding epoch. -// -// Internal test scenario #30 -func TestChainSimulator_DirectStakingNodes_WithdrawUnstakedInBatches(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 30, - } - - // Test Steps - // 1. Create 3 transactions for unstaking: first one unstaking 11 egld each, second one unstaking 12 egld and third one unstaking 13 egld. - // 2. Send the transactions in consecutive epochs, one TX in each epoch. - // 3. Wait for the epoch when first tx unbonding period ends. - // 4. Create a transaction for withdraw and send it to the network - // 5. Wait for an epoch - // 6. Create another transaction for withdraw and send it to the network - // 7. Wait for an epoch - // 8. Create another transasction for withdraw and send it to the network - - t.Run("staking ph 4 is not active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 100 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 101 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 102 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 102 - - cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 6 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedWithdrawUnstakedFundsInBatches(t, cs, 1) - }) - - t.Run("staking ph 4 step 1 is active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 - - cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 6 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedWithdrawUnstakedFundsInBatches(t, cs, 2) - }) - - t.Run("staking ph 4 step 2 is active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 - - cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 6 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedWithdrawUnstakedFundsInBatches(t, cs, 3) - }) - - t.Run("staking ph 4 step 3 is active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 - - cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 6 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedWithdrawUnstakedFundsInBatches(t, cs, 4) - }) -} - -func testChainSimulatorDirectStakedWithdrawUnstakedFundsInBatches(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator, targetEpoch int32) { - err := cs.GenerateBlocksUntilEpochIsReached(targetEpoch) - require.Nil(t, err) - - privateKey, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(1) - require.Nil(t, err) - - err = cs.AddValidatorKeys(privateKey) - require.Nil(t, err) - metachainNode := cs.GetNodeHandler(core.MetachainShardId) - - mintValue := big.NewInt(2700) - mintValue = mintValue.Mul(chainSimulatorIntegrationTests.OneEGLD, mintValue) - - validatorOwner, err := cs.GenerateAndMintWalletAddress(core.AllShardId, mintValue) - require.Nil(t, err) - - err = cs.GenerateBlocks(1) - require.Nil(t, err) - - stakeValue := big.NewInt(0).Mul(chainSimulatorIntegrationTests.OneEGLD, big.NewInt(2600)) - txDataField := fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) - txStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 0, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) - stakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, stakeTx) - - stakeTxFee, _ := big.NewInt(0).SetString(stakeTx.Fee, 10) - - err = cs.GenerateBlocks(2) - require.Nil(t, err) - - testBLSKeyStaked(t, metachainNode, blsKeys[0]) - - shardIDValidatorOwner := cs.GetNodeHandler(0).GetShardCoordinator().ComputeId(validatorOwner.Bytes) - accountValidatorOwner, _, err := cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(validatorOwner.Bech32, coreAPI.AccountQueryOptions{}) - require.Nil(t, err) - balanceBeforeUnbonding, _ := big.NewInt(0).SetString(accountValidatorOwner.Balance, 10) - - log.Info("Step 1. Create 3 transactions for unstaking: first one unstaking 11 egld, second one unstaking 12 egld and third one unstaking 13 egld.") - log.Info("Step 2. Send the transactions in consecutive epochs, one TX in each epoch.") - - unStakeValue1 := big.NewInt(11) - unStakeValue1 = unStakeValue1.Mul(chainSimulatorIntegrationTests.OneEGLD, unStakeValue1) - txDataField = fmt.Sprintf("unStakeTokens@%s", hex.EncodeToString(unStakeValue1.Bytes())) - txUnStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 1, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForStakeOperation) - unStakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, unStakeTx) - - unStakeTxFee1, _ := big.NewInt(0).SetString(unStakeTx.Fee, 10) - - testEpoch := targetEpoch + 1 - err = cs.GenerateBlocksUntilEpochIsReached(testEpoch) - require.Nil(t, err) - - unStakeValue2 := big.NewInt(12) - unStakeValue2 = unStakeValue2.Mul(chainSimulatorIntegrationTests.OneEGLD, unStakeValue2) - txDataField = fmt.Sprintf("unStakeTokens@%s", hex.EncodeToString(unStakeValue2.Bytes())) - txUnStake = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 2, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForStakeOperation) - unStakeTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, unStakeTx) - - unStakeTxFee2, _ := big.NewInt(0).SetString(unStakeTx.Fee, 10) - - testEpoch++ - err = cs.GenerateBlocksUntilEpochIsReached(testEpoch) - require.Nil(t, err) - - unStakeValue3 := big.NewInt(13) - unStakeValue3 = unStakeValue3.Mul(chainSimulatorIntegrationTests.OneEGLD, unStakeValue3) - txDataField = fmt.Sprintf("unStakeTokens@%s", hex.EncodeToString(unStakeValue3.Bytes())) - txUnStake = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 3, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForStakeOperation) - unStakeTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, unStakeTx) - - unStakeTxFee3, _ := big.NewInt(0).SetString(unStakeTx.Fee, 10) - - testEpoch++ - err = cs.GenerateBlocksUntilEpochIsReached(testEpoch) - require.Nil(t, err) - - // check bls key is still staked - testBLSKeyStaked(t, metachainNode, blsKeys[0]) - - scQuery := &process.SCQuery{ - ScAddress: vm.ValidatorSCAddress, - FuncName: "getUnStakedTokensList", - CallerAddr: vm.ValidatorSCAddress, - CallValue: big.NewInt(0), - Arguments: [][]byte{validatorOwner.Bytes}, - } - result, _, err := metachainNode.GetFacadeHandler().ExecuteSCQuery(scQuery) - require.Nil(t, err) - require.Equal(t, chainSimulatorIntegrationTests.OkReturnCode, result.ReturnCode) - - expectedUnStaked := big.NewInt(11) - expectedUnStaked = expectedUnStaked.Mul(chainSimulatorIntegrationTests.OneEGLD, expectedUnStaked) - require.Equal(t, expectedUnStaked.String(), big.NewInt(0).SetBytes(result.ReturnData[0]).String()) - - scQuery = &process.SCQuery{ - ScAddress: vm.ValidatorSCAddress, - FuncName: "getTotalStaked", - CallerAddr: vm.ValidatorSCAddress, - CallValue: big.NewInt(0), - Arguments: [][]byte{validatorOwner.Bytes}, - } - result, _, err = metachainNode.GetFacadeHandler().ExecuteSCQuery(scQuery) - require.Nil(t, err) - require.Equal(t, chainSimulatorIntegrationTests.OkReturnCode, result.ReturnCode) - - expectedStaked := big.NewInt(2600 - 11 - 12 - 13) - expectedStaked = expectedStaked.Mul(chainSimulatorIntegrationTests.OneEGLD, expectedStaked) - require.Equal(t, expectedStaked.String(), string(result.ReturnData[0])) - - log.Info("Step 3. Wait for the unbonding epoch to start") - - testEpoch += 3 - err = cs.GenerateBlocksUntilEpochIsReached(testEpoch) - require.Nil(t, err) - - log.Info("Step 4.1. Create from the owner of staked nodes a transaction to withdraw the unstaked funds") - - txDataField = fmt.Sprintf("unBondTokens@%s", blsKeys[0]) - txUnBond := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 4, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForUnBond) - unBondTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnBond, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, unBondTx) - - unBondTxFee, _ := big.NewInt(0).SetString(unBondTx.Fee, 10) - - err = cs.GenerateBlocks(2) - require.Nil(t, err) - - // the owner balance should increase with the (11 EGLD - tx fee) - accountValidatorOwner, _, err = cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(validatorOwner.Bech32, coreAPI.AccountQueryOptions{}) - require.Nil(t, err) - balanceAfterUnbonding, _ := big.NewInt(0).SetString(accountValidatorOwner.Balance, 10) - - balanceAfterUnbonding.Sub(balanceAfterUnbonding, unStakeValue1) - - txsFee := big.NewInt(0) - - txsFee.Add(txsFee, stakeTxFee) - txsFee.Add(txsFee, unBondTxFee) - txsFee.Add(txsFee, unStakeTxFee1) - txsFee.Add(txsFee, unStakeTxFee2) - txsFee.Add(txsFee, unStakeTxFee3) - - balanceAfterUnbonding.Add(balanceAfterUnbonding, txsFee) - - require.Equal(t, 1, balanceAfterUnbonding.Cmp(balanceBeforeUnbonding)) - - log.Info("Step 4.2. Create from the owner of staked nodes a transaction to withdraw the unstaked funds") - - testEpoch++ - err = cs.GenerateBlocksUntilEpochIsReached(testEpoch) - require.Nil(t, err) - - txDataField = fmt.Sprintf("unBondTokens@%s", blsKeys[0]) - txUnBond = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 5, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForUnBond) - unBondTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnBond, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, unBondTx) - - err = cs.GenerateBlocks(2) - require.Nil(t, err) - - // the owner balance should increase with the (11+12 EGLD - tx fee) - accountValidatorOwner, _, err = cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(validatorOwner.Bech32, coreAPI.AccountQueryOptions{}) - require.Nil(t, err) - balanceAfterUnbonding, _ = big.NewInt(0).SetString(accountValidatorOwner.Balance, 10) - - balanceAfterUnbonding.Sub(balanceAfterUnbonding, unStakeValue1) - balanceAfterUnbonding.Sub(balanceAfterUnbonding, unStakeValue2) - - txsFee.Add(txsFee, unBondTxFee) - balanceAfterUnbonding.Add(balanceAfterUnbonding, txsFee) - - require.Equal(t, 1, balanceAfterUnbonding.Cmp(balanceBeforeUnbonding)) - - log.Info("Step 4.3. Create from the owner of staked nodes a transaction to withdraw the unstaked funds") - - testEpoch++ - err = cs.GenerateBlocksUntilEpochIsReached(testEpoch) - require.Nil(t, err) - - txDataField = fmt.Sprintf("unBondTokens@%s", blsKeys[0]) - txUnBond = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 6, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForUnBond) - unBondTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnBond, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, unBondTx) - - err = cs.GenerateBlocks(2) - require.Nil(t, err) - - // the owner balance should increase with the (11+12+13 EGLD - tx fee) - accountValidatorOwner, _, err = cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(validatorOwner.Bech32, coreAPI.AccountQueryOptions{}) - require.Nil(t, err) - balanceAfterUnbonding, _ = big.NewInt(0).SetString(accountValidatorOwner.Balance, 10) - - balanceAfterUnbonding.Sub(balanceAfterUnbonding, unStakeValue1) - balanceAfterUnbonding.Sub(balanceAfterUnbonding, unStakeValue2) - balanceAfterUnbonding.Sub(balanceAfterUnbonding, unStakeValue3) - - txsFee.Add(txsFee, unBondTxFee) - balanceAfterUnbonding.Add(balanceAfterUnbonding, txsFee) - - require.Equal(t, 1, balanceAfterUnbonding.Cmp(balanceBeforeUnbonding)) -} - -// Test description: -// Unstake funds in different batches in the same epoch allows correct withdrawal in the correct epoch -// -// Internal test scenario #31 -func TestChainSimulator_DirectStakingNodes_WithdrawUnstakedInEpoch(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 30, - } - - // Test Steps - // 1. Create 3 transactions for unstaking: first one unstaking 11 egld each, second one unstaking 12 egld and third one unstaking 13 egld. - // 2. Send the transactions consecutively in the same epoch - // 3. Wait for the epoch when unbonding period ends. - // 4. Create a transaction for withdraw and send it to the network - - t.Run("staking ph 4 is not active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 100 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 101 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 102 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 102 - - cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 3 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedWithdrawUnstakedFundsInEpoch(t, cs, 1) - }) - - t.Run("staking ph 4 step 1 is active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 - - cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 3 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedWithdrawUnstakedFundsInEpoch(t, cs, 2) - }) - - t.Run("staking ph 4 step 2 is active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 - - cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 3 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedWithdrawUnstakedFundsInEpoch(t, cs, 3) - }) - - t.Run("staking ph 4 step 3 is active", func(t *testing.T) { - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 - - cfg.SystemSCConfig.StakingSystemSCConfig.UnBondPeriodInEpochs = 3 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - testChainSimulatorDirectStakedWithdrawUnstakedFundsInEpoch(t, cs, 4) - }) -} - -func testChainSimulatorDirectStakedWithdrawUnstakedFundsInEpoch(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator, targetEpoch int32) { - err := cs.GenerateBlocksUntilEpochIsReached(targetEpoch) - require.Nil(t, err) - - privateKey, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(1) - require.Nil(t, err) - - err = cs.AddValidatorKeys(privateKey) - require.Nil(t, err) - metachainNode := cs.GetNodeHandler(core.MetachainShardId) - - mintValue := big.NewInt(2700) - mintValue = mintValue.Mul(chainSimulatorIntegrationTests.OneEGLD, mintValue) - - validatorOwner, err := cs.GenerateAndMintWalletAddress(core.AllShardId, mintValue) - require.Nil(t, err) - - err = cs.GenerateBlocks(1) - require.Nil(t, err) - - stakeValue := big.NewInt(0).Mul(chainSimulatorIntegrationTests.OneEGLD, big.NewInt(2600)) - txDataField := fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) - txStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 0, vm.ValidatorSCAddress, stakeValue, txDataField, staking.GasLimitForStakeOperation) - stakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, stakeTx) - - stakeTxFee, _ := big.NewInt(0).SetString(stakeTx.Fee, 10) - - err = cs.GenerateBlocks(2) - require.Nil(t, err) - - testBLSKeyStaked(t, metachainNode, blsKeys[0]) - - shardIDValidatorOwner := cs.GetNodeHandler(0).GetShardCoordinator().ComputeId(validatorOwner.Bytes) - accountValidatorOwner, _, err := cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(validatorOwner.Bech32, coreAPI.AccountQueryOptions{}) - require.Nil(t, err) - balanceBeforeUnbonding, _ := big.NewInt(0).SetString(accountValidatorOwner.Balance, 10) - - log.Info("Step 1. Create 3 transactions for unstaking: first one unstaking 11 egld each, second one unstaking 12 egld and third one unstaking 13 egld.") - log.Info("Step 2. Send the transactions in consecutively in same epoch.") - - unStakeValue1 := big.NewInt(11) - unStakeValue1 = unStakeValue1.Mul(chainSimulatorIntegrationTests.OneEGLD, unStakeValue1) - txDataField = fmt.Sprintf("unStakeTokens@%s", hex.EncodeToString(unStakeValue1.Bytes())) - txUnStake := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 1, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForStakeOperation) - unStakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, unStakeTx) - - unStakeTxFee, _ := big.NewInt(0).SetString(unStakeTx.Fee, 10) - - unStakeValue2 := big.NewInt(12) - unStakeValue2 = unStakeValue2.Mul(chainSimulatorIntegrationTests.OneEGLD, unStakeValue2) - txDataField = fmt.Sprintf("unStakeTokens@%s", hex.EncodeToString(unStakeValue2.Bytes())) - txUnStake = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 2, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForStakeOperation) - unStakeTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, unStakeTx) - - unStakeValue3 := big.NewInt(13) - unStakeValue3 = unStakeValue3.Mul(chainSimulatorIntegrationTests.OneEGLD, unStakeValue3) - txDataField = fmt.Sprintf("unStakeTokens@%s", hex.EncodeToString(unStakeValue3.Bytes())) - txUnStake = chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 3, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForStakeOperation) - unStakeTx, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, unStakeTx) - - // check bls key is still staked - testBLSKeyStaked(t, metachainNode, blsKeys[0]) - - scQuery := &process.SCQuery{ - ScAddress: vm.ValidatorSCAddress, - FuncName: "getUnStakedTokensList", - CallerAddr: vm.ValidatorSCAddress, - CallValue: big.NewInt(0), - Arguments: [][]byte{validatorOwner.Bytes}, - } - result, _, err := metachainNode.GetFacadeHandler().ExecuteSCQuery(scQuery) - require.Nil(t, err) - require.Equal(t, chainSimulatorIntegrationTests.OkReturnCode, result.ReturnCode) - - expectedUnStaked := big.NewInt(11 + 12 + 13) - expectedUnStaked = expectedUnStaked.Mul(chainSimulatorIntegrationTests.OneEGLD, expectedUnStaked) - require.Equal(t, expectedUnStaked.String(), big.NewInt(0).SetBytes(result.ReturnData[0]).String()) - - scQuery = &process.SCQuery{ - ScAddress: vm.ValidatorSCAddress, - FuncName: "getTotalStaked", - CallerAddr: vm.ValidatorSCAddress, - CallValue: big.NewInt(0), - Arguments: [][]byte{validatorOwner.Bytes}, - } - result, _, err = metachainNode.GetFacadeHandler().ExecuteSCQuery(scQuery) - require.Nil(t, err) - require.Equal(t, chainSimulatorIntegrationTests.OkReturnCode, result.ReturnCode) - - expectedStaked := big.NewInt(2600 - 11 - 12 - 13) - expectedStaked = expectedStaked.Mul(chainSimulatorIntegrationTests.OneEGLD, expectedStaked) - require.Equal(t, expectedStaked.String(), string(result.ReturnData[0])) - - log.Info("Step 3. Wait for the unbonding epoch to start") - - testEpoch := targetEpoch + 3 - err = cs.GenerateBlocksUntilEpochIsReached(testEpoch) - require.Nil(t, err) - - log.Info("Step 4.1. Create from the owner of staked nodes a transaction to withdraw the unstaked funds") - - txDataField = fmt.Sprintf("unBondTokens@%s", blsKeys[0]) - txUnBond := chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 4, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.ZeroValue, txDataField, staking.GasLimitForUnBond) - unBondTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txUnBond, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, unBondTx) - - unBondTxFee, _ := big.NewInt(0).SetString(unBondTx.Fee, 10) - - err = cs.GenerateBlocks(2) - require.Nil(t, err) - - // the owner balance should increase with the (11+12+13 EGLD - tx fee) - accountValidatorOwner, _, err = cs.GetNodeHandler(shardIDValidatorOwner).GetFacadeHandler().GetAccount(validatorOwner.Bech32, coreAPI.AccountQueryOptions{}) - require.Nil(t, err) - balanceAfterUnbonding, _ := big.NewInt(0).SetString(accountValidatorOwner.Balance, 10) - - balanceAfterUnbonding.Sub(balanceAfterUnbonding, unStakeValue1) - balanceAfterUnbonding.Sub(balanceAfterUnbonding, unStakeValue2) - balanceAfterUnbonding.Sub(balanceAfterUnbonding, unStakeValue3) - - txsFee := big.NewInt(0) - - txsFee.Add(txsFee, stakeTxFee) - txsFee.Add(txsFee, unBondTxFee) - txsFee.Add(txsFee, unStakeTxFee) - txsFee.Add(txsFee, unStakeTxFee) - txsFee.Add(txsFee, unStakeTxFee) - - balanceAfterUnbonding.Add(balanceAfterUnbonding, txsFee) - - require.Equal(t, 1, balanceAfterUnbonding.Cmp(balanceBeforeUnbonding)) -} - -// Test that if we unStake one active node(waiting/eligible), the number of qualified nodes will remain the same -// Nodes configuration at genesis consisting of a total of 32 nodes, distributed on 3 shards + meta: -// - 4 eligible nodes/shard -// - 4 waiting nodes/shard -// - 2 nodes to shuffle per shard -// - max num nodes config for stakingV4 step3 = 24 (being downsized from previously 32 nodes) -// - with this config, we should always select 8 nodes from auction list -// We will add one extra node, so auction list size = 9, but will always select 8. Even if we unStake one active node, -// we should still only select 8 nodes. -func TestChainSimulator_UnStakeOneActiveNodeAndCheckAPIAuctionList(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 30, - } - - stakingV4Step1Epoch := uint32(2) - stakingV4Step2Epoch := uint32(3) - stakingV4Step3Epoch := uint32(4) - - numOfShards := uint32(3) - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 4, - MetaChainMinNodes: 4, - NumNodesWaitingListMeta: 4, - NumNodesWaitingListShard: 4, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = stakingV4Step1Epoch - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = stakingV4Step2Epoch - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = stakingV4Step3Epoch - cfg.EpochConfig.EnableEpochs.CleanupAuctionOnLowWaitingListEnableEpoch = stakingV4Step1Epoch - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[1].MaxNumNodes = 32 - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[1].NodesToShufflePerShard = 2 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = stakingV4Step3Epoch - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].MaxNumNodes = 24 - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].NodesToShufflePerShard = 2 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - err = cs.GenerateBlocksUntilEpochIsReached(int32(stakingV4Step3Epoch + 1)) - require.Nil(t, err) - - metachainNode := cs.GetNodeHandler(core.MetachainShardId) - - qualified, unQualified := getQualifiedAndUnqualifiedNodes(t, metachainNode) - require.Equal(t, 8, len(qualified)) - require.Equal(t, 0, len(unQualified)) - - stakeOneNode(t, cs) - - qualified, unQualified = getQualifiedAndUnqualifiedNodes(t, metachainNode) - require.Equal(t, 8, len(qualified)) - require.Equal(t, 1, len(unQualified)) - - unStakeOneActiveNode(t, cs) - - qualified, unQualified = getQualifiedAndUnqualifiedNodes(t, metachainNode) - require.Equal(t, 8, len(qualified)) - require.Equal(t, 1, len(unQualified)) -} - -// Nodes configuration at genesis consisting of a total of 40 nodes, distributed on 3 shards + meta: -// - 4 eligible nodes/shard -// - 4 waiting nodes/shard -// - 2 nodes to shuffle per shard -// - max num nodes config for stakingV4 step3 = 32 (being downsized from previously 40 nodes) -// - with this config, we should always select max 8 nodes from auction list if there are > 40 nodes in the network -// This test will run with only 32 nodes and check that there are no nodes in the auction list, -// because of the lowWaitingList condition being triggered when in staking v4 -func TestChainSimulator_EdgeCaseLowWaitingList(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 20, - } - - stakingV4Step1Epoch := uint32(2) - stakingV4Step2Epoch := uint32(3) - stakingV4Step3Epoch := uint32(4) - - numOfShards := uint32(3) - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 4, - MetaChainMinNodes: 4, - NumNodesWaitingListMeta: 2, - NumNodesWaitingListShard: 2, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = stakingV4Step1Epoch - cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = stakingV4Step2Epoch - cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = stakingV4Step3Epoch - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[1].MaxNumNodes = 40 - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[1].NodesToShufflePerShard = 2 - - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = stakingV4Step3Epoch - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].MaxNumNodes = 32 - cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].NodesToShufflePerShard = 2 - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - epochToCheck := int32(stakingV4Step3Epoch + 1) - err = cs.GenerateBlocksUntilEpochIsReached(epochToCheck) - require.Nil(t, err) - - metachainNode := cs.GetNodeHandler(core.MetachainShardId) - qualified, unQualified := getQualifiedAndUnqualifiedNodes(t, metachainNode) - require.Equal(t, 0, len(qualified)) - require.Equal(t, 0, len(unQualified)) - - // we always have 0 in auction list because of the lowWaitingList condition - epochToCheck += 1 - err = cs.GenerateBlocksUntilEpochIsReached(epochToCheck) - require.Nil(t, err) - - qualified, unQualified = getQualifiedAndUnqualifiedNodes(t, metachainNode) - require.Equal(t, 0, len(qualified)) - require.Equal(t, 0, len(unQualified)) - - // stake 16 mode nodes, these will go to auction list - stakeNodes(t, cs, 17) - - epochToCheck += 1 - err = cs.GenerateBlocksUntilEpochIsReached(epochToCheck) - require.Nil(t, err) - - qualified, unQualified = getQualifiedAndUnqualifiedNodes(t, metachainNode) - // all the previously registered will be selected, as we have 24 nodes in eligible+waiting, 8 will shuffle out, - // but this time there will be not be lowWaitingList, as there are enough in auction, so we will end up with - // 24-8 = 16 nodes remaining + 16 from auction, to fill up all 32 positions - require.Equal(t, 16, len(qualified)) - require.Equal(t, 1, len(unQualified)) - - shuffledOutNodesKeys, err := metachainNode.GetProcessComponents().NodesCoordinator().GetShuffledOutToAuctionValidatorsPublicKeys(uint32(epochToCheck)) - require.Nil(t, err) - - checkKeysNotInMap(t, shuffledOutNodesKeys, qualified) - checkKeysNotInMap(t, shuffledOutNodesKeys, unQualified) -} - -func checkKeysNotInMap(t *testing.T, m map[uint32][][]byte, keys []string) { - for _, key := range keys { - for _, v := range m { - for _, k := range v { - mapKey := hex.EncodeToString(k) - require.NotEqual(t, key, mapKey) - } - } - } -} - -func stakeNodes(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator, numNodesToStake int) { - txs := make([]*transaction.Transaction, numNodesToStake) - for i := 0; i < numNodesToStake; i++ { - txs[i] = createStakeTransaction(t, cs) - } - - stakeTxs, err := cs.SendTxsAndGenerateBlocksTilAreExecuted(txs, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, stakeTxs) - require.Len(t, stakeTxs, numNodesToStake) - - require.Nil(t, cs.GenerateBlocks(1)) -} - -func stakeOneNode(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator) { - txStake := createStakeTransaction(t, cs) - stakeTx, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(txStake, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, stakeTx) - - require.Nil(t, cs.GenerateBlocks(1)) -} - -func createStakeTransaction(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator) *transaction.Transaction { - privateKey, blsKeys, err := chainSimulator.GenerateBlsPrivateKeys(1) - require.Nil(t, err) - err = cs.AddValidatorKeys(privateKey) - require.Nil(t, err) - - mintValue := big.NewInt(0).Add(chainSimulatorIntegrationTests.MinimumStakeValue, chainSimulatorIntegrationTests.OneEGLD) - validatorOwner, err := cs.GenerateAndMintWalletAddress(core.AllShardId, mintValue) - require.Nil(t, err) - - err = cs.GenerateBlocks(1) - require.Nil(t, err) - - txDataField := fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) - return chainSimulatorIntegrationTests.GenerateTransaction(validatorOwner.Bytes, 0, vm.ValidatorSCAddress, chainSimulatorIntegrationTests.MinimumStakeValue, txDataField, staking.GasLimitForStakeOperation) -} - -func unStakeOneActiveNode(t *testing.T, cs chainSimulatorIntegrationTests.ChainSimulator) { - err := cs.ForceResetValidatorStatisticsCache() - require.Nil(t, err) - - validators, err := cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ValidatorStatisticsApi() - require.Nil(t, err) - - idx := 0 - keyToUnStake := make([]byte, 0) - numKeys := len(cs.GetValidatorPrivateKeys()) - for idx = 0; idx < numKeys; idx++ { - keyToUnStake, err = cs.GetValidatorPrivateKeys()[idx].GeneratePublic().ToByteArray() - require.Nil(t, err) - - apiValidator, found := validators[hex.EncodeToString(keyToUnStake)] - require.True(t, found) - - validatorStatus := apiValidator.ValidatorStatus - if validatorStatus == "waiting" || validatorStatus == "eligible" { - log.Info("found active key to unStake", "index", idx, "bls key", keyToUnStake, "list", validatorStatus) - break - } - - if idx == numKeys-1 { - require.Fail(t, "did not find key to unStake") - } - } - - rcv := "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l" - rcvAddrBytes, _ := cs.GetNodeHandler(0).GetCoreComponents().AddressPubKeyConverter().Decode(rcv) - - validatorWallet := cs.GetInitialWalletKeys().StakeWallets[idx].Address - shardID := cs.GetNodeHandler(0).GetShardCoordinator().ComputeId(validatorWallet.Bytes) - initialAccount, _, err := cs.GetNodeHandler(shardID).GetFacadeHandler().GetAccount(validatorWallet.Bech32, coreAPI.AccountQueryOptions{}) - - require.Nil(t, err) - tx := &transaction.Transaction{ - Nonce: initialAccount.Nonce, - Value: big.NewInt(0), - SndAddr: validatorWallet.Bytes, - RcvAddr: rcvAddrBytes, - Data: []byte(fmt.Sprintf("unStake@%s", hex.EncodeToString(keyToUnStake))), - GasLimit: 50_000_000, - GasPrice: 1000000000, - Signature: []byte("dummy"), - ChainID: []byte(configs.ChainID), - Version: 1, - } - _, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, staking.MaxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - - err = cs.GenerateBlocks(1) - require.Nil(t, err) - - err = cs.ForceResetValidatorStatisticsCache() - require.Nil(t, err) - validators, err = cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ValidatorStatisticsApi() - require.Nil(t, err) - - apiValidator, found := validators[hex.EncodeToString(keyToUnStake)] - require.True(t, found) - require.True(t, strings.Contains(apiValidator.ValidatorStatus, "leaving")) -} diff --git a/integrationTests/chainSimulator/staking/stakingProvider/delegation_test.go b/integrationTests/chainSimulator/staking/stakingProvider/delegation_test.go index a218f00f6da..c83c2188972 100644 --- a/integrationTests/chainSimulator/staking/stakingProvider/delegation_test.go +++ b/integrationTests/chainSimulator/staking/stakingProvider/delegation_test.go @@ -37,12 +37,25 @@ const gasLimitForConvertOperation = 510_000_000 const gasLimitForDelegationContractCreationOperation = 100_000_000 const gasLimitForAddNodesOperation = 500_000_000 const gasLimitForUndelegateOperation = 500_000_000 -const gasLimitForMergeOperation = 600_000_000 +const gasLimitForMergeOperation = 510_000_000 const gasLimitForDelegate = 12_000_000 const maxCap = "00" // no cap const hexServiceFee = "0ea1" // 37.45% +var ( + roundDurationInMillis = uint64(6000) + supernovaRoundDurationInMillis = uint64(600) + roundsPerEpoch = core.OptionalUint64{ + HasValue: true, + Value: 30, + } + supernovaRoundsPerEpoch = core.OptionalUint64{ + HasValue: true, + Value: 300, + } +) + // Test description: // Test that delegation contract created with MakeNewContractFromValidatorData works properly // Also check that delegate and undelegate works properly and the top-up remain the same if every delegator undelegates. @@ -54,12 +67,6 @@ func TestChainSimulator_MakeNewContractFromValidatorData(t *testing.T) { t.Skip("this is not a short test") } - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 30, - } - // Test scenario done in staking 3.5 phase (staking v4 is not active) // 1. Add a new validator private key in the multi key handler // 2. Set the initial state for the owner and the 2 delegators @@ -69,18 +76,20 @@ func TestChainSimulator_MakeNewContractFromValidatorData(t *testing.T) { // 6. Execute 2 unDelegate operations of 100 EGLD each, check the topup is back to 500 t.Run("staking ph 4 is not active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { maxNodesChangeEnableEpoch := cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch blsMultiSignerEnableEpoch := cfg.EpochConfig.EnableEpochs.BLSMultiSignerEnableEpoch @@ -96,6 +105,7 @@ func TestChainSimulator_MakeNewContractFromValidatorData(t *testing.T) { cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 102 cfg.EpochConfig.EnableEpochs.AndromedaEnableEpoch = 1 + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 100 }, }) require.Nil(t, err) @@ -115,18 +125,20 @@ func TestChainSimulator_MakeNewContractFromValidatorData(t *testing.T) { // 6. Execute 2 unDelegate operations of 100 EGLD each, check the topup is back to 500 t.Run("staking ph 4 is not active and all is done in epoch 0", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { maxNodesChangeEnableEpoch := cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch blsMultiSignerEnableEpoch := cfg.EpochConfig.EnableEpochs.BLSMultiSignerEnableEpoch @@ -143,6 +155,7 @@ func TestChainSimulator_MakeNewContractFromValidatorData(t *testing.T) { cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 102 cfg.EpochConfig.EnableEpochs.AndromedaEnableEpoch = 1 + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 100 }, }) require.Nil(t, err) @@ -168,24 +181,28 @@ func TestChainSimulator_MakeNewContractFromValidatorData(t *testing.T) { // 6. Execute 2 unDelegate operations of 100 EGLD each, check the topup is back to 500 t.Run("staking ph 4 step 1 is active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 100 }, }) require.Nil(t, err) @@ -205,24 +222,28 @@ func TestChainSimulator_MakeNewContractFromValidatorData(t *testing.T) { // 6. Execute 2 unDelegate operations of 100 EGLD each, check the topup is back to 500 t.Run("staking ph 4 step 2 is active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 100 }, }) require.Nil(t, err) @@ -242,24 +263,28 @@ func TestChainSimulator_MakeNewContractFromValidatorData(t *testing.T) { // 6. Execute 2 unDelegate operations of 100 EGLD each, check the topup is back to 500 t.Run("staking ph 4 step 3 is active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 100 }, }) require.Nil(t, err) @@ -296,7 +321,7 @@ func testChainSimulatorMakeNewContractFromValidatorData(t *testing.T, cs chainSi delegator2, err := cs.GenerateAndMintWalletAddress(core.AllShardId, mintValue) require.Nil(t, err) - err = cs.GenerateBlocks(1) + err = cs.GenerateBlocks(2) require.Nil(t, err) log.Info("working with the following addresses", @@ -486,26 +511,22 @@ func TestChainSimulator_MakeNewContractFromValidatorDataWith2StakingContracts(t t.Skip("this is not a short test") } - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 30, - } - t.Run("staking ph 4 is not active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 100 cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 101 @@ -523,24 +544,28 @@ func TestChainSimulator_MakeNewContractFromValidatorDataWith2StakingContracts(t }) t.Run("staking ph 4 step 1 is active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 100 }, }) require.Nil(t, err) @@ -552,24 +577,28 @@ func TestChainSimulator_MakeNewContractFromValidatorDataWith2StakingContracts(t }) t.Run("staking ph 4 step 2 is active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 100 }, }) require.Nil(t, err) @@ -581,24 +610,28 @@ func TestChainSimulator_MakeNewContractFromValidatorDataWith2StakingContracts(t }) t.Run("staking ph 4 step 3 is active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 + + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 100 }, }) require.Nil(t, err) @@ -714,26 +747,22 @@ func TestChainSimulatorMakeNewContractFromValidatorDataWith1StakingContractUnsta t.Skip("this is not a short test") } - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 80, - } - t.Run("staking ph 4 is not active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 100 cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 100 @@ -753,24 +782,27 @@ func TestChainSimulatorMakeNewContractFromValidatorDataWith1StakingContractUnsta }) t.Run("staking ph 4 step 1 is active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 cfg.EpochConfig.EnableEpochs.AlwaysMergeContextsInEEIEnableEpoch = 1 + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 100 cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 cfg.SystemSCConfig.StakingSystemSCConfig.NodeLimitPercentage = 1 @@ -785,24 +817,27 @@ func TestChainSimulatorMakeNewContractFromValidatorDataWith1StakingContractUnsta }) t.Run("staking ph 4 step 2 is active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 cfg.EpochConfig.EnableEpochs.AlwaysMergeContextsInEEIEnableEpoch = 1 + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 100 cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 cfg.SystemSCConfig.StakingSystemSCConfig.NodeLimitPercentage = 1 @@ -817,24 +852,27 @@ func TestChainSimulatorMakeNewContractFromValidatorDataWith1StakingContractUnsta }) t.Run("staking ph 4 step 3 is active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 cfg.EpochConfig.EnableEpochs.AlwaysMergeContextsInEEIEnableEpoch = 1 + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 100 cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 cfg.SystemSCConfig.StakingSystemSCConfig.NodeLimitPercentage = 1 @@ -1028,12 +1066,6 @@ func TestChainSimulator_CreateNewDelegationContract(t *testing.T) { t.Skip("this is not a short test") } - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 30, - } - // Test scenario done in staking 3.5 phase (staking v4 is not active) // 1. Add a new validator private key in the multi key handler // 2. Set the initial state for the owner and the 2 delegators @@ -1045,18 +1077,20 @@ func TestChainSimulator_CreateNewDelegationContract(t *testing.T) { // 6. Check the node is unstaked in the next epoch t.Run("staking ph 4 is not active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 100 cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 101 @@ -1084,22 +1118,25 @@ func TestChainSimulator_CreateNewDelegationContract(t *testing.T) { // 6. Check the node is unstaked in the next epoch t.Run("staking ph 4 step 1 is active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 100 cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 }, @@ -1123,22 +1160,25 @@ func TestChainSimulator_CreateNewDelegationContract(t *testing.T) { // 6. Check the node is unstaked in the next epoch t.Run("staking ph 4 step 2 is active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 100 cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 }, @@ -1162,22 +1202,25 @@ func TestChainSimulator_CreateNewDelegationContract(t *testing.T) { // 6. Check the node is unstaked in the next epoch t.Run("staking ph 4 step 3 is active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 100 cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 }, @@ -1397,12 +1440,6 @@ func TestChainSimulator_MaxDelegationCap(t *testing.T) { t.Skip("this is not a short test") } - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 30, - } - // Test scenario done in staking 3.5 phase (staking v4 is not active) // 1. Add a new validator private key in the multi key handler // 2. Set the initial state for the owner and the 3 delegators @@ -1416,18 +1453,20 @@ func TestChainSimulator_MaxDelegationCap(t *testing.T) { // 10. Delegate from user B 20 EGLD, check it fails t.Run("staking ph 4 is not active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 100 cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 101 @@ -1457,22 +1496,25 @@ func TestChainSimulator_MaxDelegationCap(t *testing.T) { // 10. Delegate from user B 20 EGLD, check it fails t.Run("staking ph 4 step 1 is active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 100 cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 }, @@ -1498,22 +1540,25 @@ func TestChainSimulator_MaxDelegationCap(t *testing.T) { // 10. Delegate from user B 20 EGLD, check it fails t.Run("staking ph 4 step 2 is active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 cfg.EpochConfig.EnableEpochs.StakingV4Step3EnableEpoch = 4 + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 100 cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = 4 }, @@ -1539,18 +1584,20 @@ func TestChainSimulator_MaxDelegationCap(t *testing.T) { // 10. Delegate from user B 20 EGLD, check it fails t.Run("staking ph 4 step 3 is active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step2EnableEpoch = 3 @@ -1814,12 +1861,6 @@ func TestChainSimulator_MergeDelegation(t *testing.T) { t.Skip("this is not a short test") } - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 30, - } - // Test steps: // 1. User A - Stake 1 node to have 100 egld more than minimum required stake value // 2. User A - Execute `makeNewContractFromValidatorData` to create delegation contract based on User A account @@ -1829,18 +1870,20 @@ func TestChainSimulator_MergeDelegation(t *testing.T) { t.Run("staking ph 4 is not active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 100 cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 100 @@ -1860,18 +1903,20 @@ func TestChainSimulator_MergeDelegation(t *testing.T) { t.Run("staking ph 4 step 1 is active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 @@ -1892,18 +1937,20 @@ func TestChainSimulator_MergeDelegation(t *testing.T) { t.Run("staking ph 4 step 2 is active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 @@ -1924,18 +1971,20 @@ func TestChainSimulator_MergeDelegation(t *testing.T) { t.Run("staking ph 4 step 3 is active", func(t *testing.T) { cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 @@ -2081,26 +2130,21 @@ func TestChainSimulator_CreateDelegationContractAndWithdraw(t *testing.T) { t.Skip("this is not a short test") } - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 30, - } - - //Staking V4 activated + // Staking V4 activated cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + GenesisTimestamp: time.Now().Unix(), + RoundDurationInMillis: roundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.StakeLimitsEnableEpoch = 2 cfg.EpochConfig.EnableEpochs.StakingV4Step1EnableEpoch = 2 diff --git a/integrationTests/chainSimulator/staking/stakingProvider/stakingProviderWithNodesinQueue_test.go b/integrationTests/chainSimulator/staking/stakingProvider/stakingProviderWithNodesinQueue_test.go index dd89ecf2c28..919569187b2 100644 --- a/integrationTests/chainSimulator/staking/stakingProvider/stakingProviderWithNodesinQueue_test.go +++ b/integrationTests/chainSimulator/staking/stakingProvider/stakingProviderWithNodesinQueue_test.go @@ -5,7 +5,6 @@ import ( "fmt" "math/big" "testing" - "time" "github.com/multiversx/mx-chain-go/config" chainSimulatorIntegrationTests "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator" @@ -45,25 +44,21 @@ func TestStakingProviderWithNodes(t *testing.T) { } func testStakingProviderWithNodesReStakeUnStaked(t *testing.T, stakingV4ActivationEpoch uint32) { - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 20, - } - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: supernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, AlterConfigsFunction: func(cfg *config.Configs) { configs.SetStakingV4ActivationEpochs(cfg, stakingV4ActivationEpoch) }, diff --git a/integrationTests/chainSimulator/testing.go b/integrationTests/chainSimulator/testing.go index 212021a8fbd..15944ba7b0b 100644 --- a/integrationTests/chainSimulator/testing.go +++ b/integrationTests/chainSimulator/testing.go @@ -246,3 +246,29 @@ func CheckGenerateTransactions(t *testing.T, chainSimulator ChainSimulator) { assert.Equal(t, expectedBalance.String(), account.Balance) }) } + +// GenerateMoveBalanceTxsInShardsWithMoreGasLimit - +func GenerateMoveBalanceTxsInShardsWithMoreGasLimit(t *testing.T, chainSimulator ChainSimulator) { + transferValue := big.NewInt(0).Mul(OneEGLD, big.NewInt(5)) + + wallet0, err := chainSimulator.GenerateAndMintWalletAddress(0, InitialAmount) + require.Nil(t, err) + + wallet1, err := chainSimulator.GenerateAndMintWalletAddress(0, InitialAmount) + require.Nil(t, err) + + err = chainSimulator.GenerateBlocks(1) + require.Nil(t, err) + + gasLimit := uint64(150_000) + tx0 := GenerateTransaction(wallet0.Bytes, 0, wallet1.Bytes, transferValue, "", gasLimit) + + _, err = chainSimulator.SendTxAndGenerateBlockTilTxIsExecuted(tx0, 3) + require.Nil(t, err) + + account, err := chainSimulator.GetAccount(wallet1) + require.Nil(t, err) + expectedBalance := big.NewInt(0).Add(InitialAmount, transferValue) + require.Equal(t, expectedBalance.String(), account.Balance) + +} diff --git a/integrationTests/chainSimulator/vm/common.go b/integrationTests/chainSimulator/vm/common.go new file mode 100644 index 00000000000..98c5c21af38 --- /dev/null +++ b/integrationTests/chainSimulator/vm/common.go @@ -0,0 +1,1106 @@ +package vm + +import ( + "bytes" + "encoding/hex" + "math/big" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data/esdt" + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-logger-go" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/integrationTests" + chainSimulatorIntegrationTests "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator" + "github.com/multiversx/mx-chain-go/integrationTests/vm/txsFee" + "github.com/multiversx/mx-chain-go/integrationTests/vm/txsFee/utils" + "github.com/multiversx/mx-chain-go/node/chainSimulator" + "github.com/multiversx/mx-chain-go/node/chainSimulator/components/api" + "github.com/multiversx/mx-chain-go/node/chainSimulator/configs" + "github.com/multiversx/mx-chain-go/node/chainSimulator/dtos" + "github.com/multiversx/mx-chain-go/state" + "github.com/multiversx/mx-chain-go/vm" +) + +const ( + DefaultPathToInitialConfig = "../../../../cmd/node/config/" + + MinGasPrice = 1000000000 + MaxNumOfBlockToGenerateWhenExecutingTx = 7 +) + +var ( + RoundDurationInMillis = uint64(6000) + SupernovaRoundDurationInMillis = uint64(600) + RoundsPerEpoch = core.OptionalUint64{ + HasValue: true, + Value: 20, + } + SupernovaRoundsPerEpoch = core.OptionalUint64{ + HasValue: true, + Value: 200, + } + + OneEGLD = big.NewInt(1000000000000000000) + + Log = logger.GetOrCreate("integrationTests/chainSimulator/vm") +) + +func TransferAndCheckTokensMetaData(t *testing.T, isCrossShard bool, isMultiTransfer bool) { + activationEpoch := uint32(4) + + baseIssuingCost := "1000" + + numOfShards := uint32(3) + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: DefaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: RoundDurationInMillis, + SupernovaRoundDurationInMillis: SupernovaRoundDurationInMillis, + RoundsPerEpoch: RoundsPerEpoch, + SupernovaRoundsPerEpoch: SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 0, + NumNodesWaitingListShard: 0, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.DynamicESDTEnableEpoch = activationEpoch + cfg.SystemSCConfig.ESDTSystemSCConfig.BaseIssuingCost = baseIssuingCost + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + addrs := CreateAddresses(t, cs, isCrossShard) + + err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch) - 1) + require.Nil(t, err) + + Log.Info("Initial setup: Create NFT, SFT and metaESDT tokens (before the activation of DynamicEsdtFlag)") + + // issue metaESDT + metaESDTTicker := []byte("METATICKER") + nonce := uint64(0) + tx := IssueMetaESDTTx(nonce, addrs[0].Bytes, metaESDTTicker, baseIssuingCost) + nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + metaESDTTokenID := txResult.Logs.Events[0].Topics[0] + + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleTransfer), + } + SetAddressEsdtRoles(t, cs, nonce, addrs[0], metaESDTTokenID, roles) + nonce++ + + rolesTransfer := [][]byte{[]byte(core.ESDTRoleTransfer)} + tx = SetSpecialRoleTx(nonce, addrs[0].Bytes, addrs[1].Bytes, metaESDTTokenID, rolesTransfer) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + Log.Info("Issued metaESDT token id", "tokenID", string(metaESDTTokenID)) + + // issue NFT + nftTicker := []byte("NFTTICKER") + tx = IssueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + nftTokenID := txResult.Logs.Events[0].Topics[0] + SetAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) + nonce++ + + tx = SetSpecialRoleTx(nonce, addrs[0].Bytes, addrs[1].Bytes, nftTokenID, rolesTransfer) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + Log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) + + // issue SFT + sftTicker := []byte("SFTTICKER") + tx = IssueSemiFungibleTx(nonce, addrs[0].Bytes, sftTicker, baseIssuingCost) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + sftTokenID := txResult.Logs.Events[0].Topics[0] + SetAddressEsdtRoles(t, cs, nonce, addrs[0], sftTokenID, roles) + nonce++ + + tx = SetSpecialRoleTx(nonce, addrs[0].Bytes, addrs[1].Bytes, sftTokenID, rolesTransfer) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + Log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) + + nftMetaData := txsFee.GetDefaultMetaData() + nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + sftMetaData := txsFee.GetDefaultMetaData() + sftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + esdtMetaData := txsFee.GetDefaultMetaData() + esdtMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tokenIDs := [][]byte{ + nftTokenID, + sftTokenID, + metaESDTTokenID, + } + + tokensMetadata := []*txsFee.MetaData{ + nftMetaData, + sftMetaData, + esdtMetaData, + } + + for i := range tokenIDs { + tx = EsdtNftCreateTx(nonce, addrs[0].Bytes, tokenIDs[i], tokensMetadata[i], 1) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + } + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + Log.Info("Step 1. check that the metadata for all tokens is saved on the system account") + + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) + + CheckMetaData(t, cs, core.SystemAccountAddress, nftTokenID, shardID, nftMetaData) + CheckMetaData(t, cs, core.SystemAccountAddress, sftTokenID, shardID, sftMetaData) + CheckMetaData(t, cs, core.SystemAccountAddress, metaESDTTokenID, shardID, esdtMetaData) + + Log.Info("Step 2. wait for DynamicEsdtFlag activation") + + err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch)) + require.Nil(t, err) + + Log.Info("Step 3. transfer the tokens to another account") + + if isMultiTransfer { + tx = MultiESDTNFTTransferTx(nonce, addrs[0].Bytes, addrs[1].Bytes, tokenIDs) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + } else { + for _, tokenID := range tokenIDs { + Log.Info("transfering token id", "tokenID", tokenID) + + tx = EsdtNFTTransferTx(nonce, addrs[0].Bytes, addrs[1].Bytes, tokenID) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + } + } + + Log.Info("Step 4. check that the metadata for all tokens is saved on the system account") + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + shardID = cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[1].Bytes) + + CheckMetaData(t, cs, core.SystemAccountAddress, nftTokenID, shardID, nftMetaData) + CheckMetaData(t, cs, core.SystemAccountAddress, sftTokenID, shardID, sftMetaData) + CheckMetaData(t, cs, core.SystemAccountAddress, metaESDTTokenID, shardID, esdtMetaData) + + Log.Info("Step 5. make an updateTokenID@tokenID function call on the ESDTSystem SC for all token types") + + for _, tokenID := range tokenIDs { + tx = UpdateTokenIDTx(nonce, addrs[0].Bytes, tokenID) + + Log.Info("updating token id", "tokenID", tokenID) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + } + + Log.Info("Step 6. check that the metadata for all tokens is saved on the system account") + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + CheckMetaData(t, cs, core.SystemAccountAddress, nftTokenID, shardID, nftMetaData) + CheckMetaData(t, cs, core.SystemAccountAddress, sftTokenID, shardID, sftMetaData) + CheckMetaData(t, cs, core.SystemAccountAddress, metaESDTTokenID, shardID, esdtMetaData) + + Log.Info("Step 7. transfer the tokens to another account") + + nonce = uint64(0) + if isMultiTransfer { + tx = MultiESDTNFTTransferTx(nonce, addrs[1].Bytes, addrs[2].Bytes, tokenIDs) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + } else { + for _, tokenID := range tokenIDs { + Log.Info("transfering token id", "tokenID", tokenID) + + tx = EsdtNFTTransferTx(nonce, addrs[1].Bytes, addrs[2].Bytes, tokenID) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + } + } + + Log.Info("Step 8. check that the metaData for the NFT was removed from the system account and moved to the user account") + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + shardID = cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[2].Bytes) + + CheckMetaData(t, cs, addrs[2].Bytes, nftTokenID, shardID, nftMetaData) + CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) + + Log.Info("Step 9. check that the metaData for the rest of the tokens is still present on the system account and not on the userAccount") + + CheckMetaData(t, cs, core.SystemAccountAddress, sftTokenID, shardID, sftMetaData) + CheckMetaDataNotInAcc(t, cs, addrs[2].Bytes, sftTokenID, shardID) + + CheckMetaData(t, cs, core.SystemAccountAddress, metaESDTTokenID, shardID, esdtMetaData) + CheckMetaDataNotInAcc(t, cs, addrs[2].Bytes, metaESDTTokenID, shardID) +} + +func CreateAddresses( + t *testing.T, + cs chainSimulatorIntegrationTests.ChainSimulator, + isCrossShard bool, +) []dtos.WalletAddress { + var shardIDs []uint32 + if !isCrossShard { + shardIDs = []uint32{1, 1, 1} + } else { + shardIDs = []uint32{0, 1, 2} + } + + mintValue := big.NewInt(10) + mintValue = mintValue.Mul(OneEGLD, mintValue) + + address, err := cs.GenerateAndMintWalletAddress(shardIDs[0], mintValue) + require.Nil(t, err) + + address2, err := cs.GenerateAndMintWalletAddress(shardIDs[1], mintValue) + require.Nil(t, err) + + address3, err := cs.GenerateAndMintWalletAddress(shardIDs[2], mintValue) + require.Nil(t, err) + + err = cs.GenerateBlocks(1) + require.Nil(t, err) + + return []dtos.WalletAddress{address, address2, address3} +} + +func CheckMetaData( + t *testing.T, + cs chainSimulatorIntegrationTests.ChainSimulator, + addressBytes []byte, + token []byte, + shardID uint32, + expectedMetaData *txsFee.MetaData, +) { + retrievedMetaData := GetMetaDataFromAcc(t, cs, addressBytes, token, shardID) + + require.Equal(t, expectedMetaData.Nonce, []byte(hex.EncodeToString(big.NewInt(int64(retrievedMetaData.Nonce)).Bytes()))) + require.Equal(t, expectedMetaData.Name, []byte(hex.EncodeToString(retrievedMetaData.Name))) + require.Equal(t, expectedMetaData.Royalties, []byte(hex.EncodeToString(big.NewInt(int64(retrievedMetaData.Royalties)).Bytes()))) + require.Equal(t, expectedMetaData.Hash, []byte(hex.EncodeToString(retrievedMetaData.Hash))) + for i, uri := range expectedMetaData.Uris { + require.Equal(t, uri, []byte(hex.EncodeToString(retrievedMetaData.URIs[i]))) + } + require.Equal(t, expectedMetaData.Attributes, []byte(hex.EncodeToString(retrievedMetaData.Attributes))) +} + +func CheckReservedField( + t *testing.T, + cs chainSimulatorIntegrationTests.ChainSimulator, + addressBytes []byte, + tokenID []byte, + shardID uint32, + expectedReservedField []byte, +) { + esdtData := GetESDTDataFromAcc(t, cs, addressBytes, tokenID, shardID) + require.Equal(t, expectedReservedField, esdtData.Reserved) +} + +func CheckMetaDataNotInAcc( + t *testing.T, + cs chainSimulatorIntegrationTests.ChainSimulator, + addressBytes []byte, + token []byte, + shardID uint32, +) { + esdtData := GetESDTDataFromAcc(t, cs, addressBytes, token, shardID) + + require.Nil(t, esdtData.TokenMetaData) +} + +func MultiESDTNFTTransferTx(nonce uint64, sndAdr, rcvAddr []byte, tokens [][]byte) *transaction.Transaction { + transferData := make([]*utils.TransferESDTData, 0) + + for _, tokenID := range tokens { + transferData = append(transferData, &utils.TransferESDTData{ + Token: tokenID, + Nonce: 1, + Value: big.NewInt(1), + }) + } + + tx := utils.CreateMultiTransferTX( + nonce, + sndAdr, + rcvAddr, + MinGasPrice, + 10_000_000, + transferData..., + ) + tx.Version = 1 + tx.Signature = []byte("dummySig") + tx.ChainID = []byte(configs.ChainID) + + return tx +} + +func EsdtNFTTransferTx(nonce uint64, sndAdr, rcvAddr, token []byte) *transaction.Transaction { + tx := utils.CreateESDTNFTTransferTx( + nonce, + sndAdr, + rcvAddr, + token, + 1, + big.NewInt(1), + MinGasPrice, + 10_000_000, + "", + ) + tx.Version = 1 + tx.Signature = []byte("dummySig") + tx.ChainID = []byte(configs.ChainID) + + return tx +} + +func IssueTx(nonce uint64, sndAdr []byte, ticker []byte, baseIssuingCost string) *transaction.Transaction { + callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) + + txDataField := bytes.Join( + [][]byte{ + []byte("issue"), + []byte(hex.EncodeToString([]byte("asdname1"))), + []byte(hex.EncodeToString(ticker)), + []byte(hex.EncodeToString(big.NewInt(10).Bytes())), + []byte(hex.EncodeToString(big.NewInt(10).Bytes())), + }, + []byte("@"), + ) + + return &transaction.Transaction{ + Nonce: nonce, + SndAddr: sndAdr, + RcvAddr: core.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: callValue, + ChainID: []byte(configs.ChainID), + Version: 1, + } +} + +func IssueMetaESDTTx(nonce uint64, sndAdr []byte, ticker []byte, baseIssuingCost string) *transaction.Transaction { + callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) + + txDataField := bytes.Join( + [][]byte{ + []byte("registerMetaESDT"), + []byte(hex.EncodeToString([]byte("asdname"))), + []byte(hex.EncodeToString(ticker)), + []byte(hex.EncodeToString(big.NewInt(10).Bytes())), + }, + []byte("@"), + ) + + return &transaction.Transaction{ + Nonce: nonce, + SndAddr: sndAdr, + RcvAddr: core.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: callValue, + ChainID: []byte(configs.ChainID), + Version: 1, + } +} + +func IssueNonFungibleTx(nonce uint64, sndAdr []byte, ticker []byte, baseIssuingCost string) *transaction.Transaction { + callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) + + txDataField := bytes.Join( + [][]byte{ + []byte("issueNonFungible"), + []byte(hex.EncodeToString([]byte("asdname"))), + []byte(hex.EncodeToString(ticker)), + }, + []byte("@"), + ) + + return &transaction.Transaction{ + Nonce: nonce, + SndAddr: sndAdr, + RcvAddr: core.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: callValue, + ChainID: []byte(configs.ChainID), + Version: 1, + } +} + +func IssueSemiFungibleTx(nonce uint64, sndAdr []byte, ticker []byte, baseIssuingCost string) *transaction.Transaction { + callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) + + txDataField := bytes.Join( + [][]byte{ + []byte("issueSemiFungible"), + []byte(hex.EncodeToString([]byte("asdname"))), + []byte(hex.EncodeToString(ticker)), + }, + []byte("@"), + ) + + return &transaction.Transaction{ + Nonce: nonce, + SndAddr: sndAdr, + RcvAddr: core.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: callValue, + ChainID: []byte(configs.ChainID), + Version: 1, + } +} + +func ChangeToDynamicTx(nonce uint64, sndAdr []byte, tokenID []byte) *transaction.Transaction { + txDataField := []byte("changeToDynamic@" + hex.EncodeToString(tokenID)) + + return &transaction.Transaction{ + Nonce: nonce, + SndAddr: sndAdr, + RcvAddr: vm.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: big.NewInt(0), + ChainID: []byte(configs.ChainID), + Version: 1, + } +} + +func UpdateTokenIDTx(nonce uint64, sndAdr []byte, tokenID []byte) *transaction.Transaction { + txDataField := []byte("updateTokenID@" + hex.EncodeToString(tokenID)) + + return &transaction.Transaction{ + Nonce: nonce, + SndAddr: sndAdr, + RcvAddr: vm.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: big.NewInt(0), + ChainID: []byte(configs.ChainID), + Version: 1, + } +} + +func EsdtNftCreateTx( + nonce uint64, + sndAdr []byte, + tokenID []byte, + metaData *txsFee.MetaData, + quantity int64, +) *transaction.Transaction { + txDataField := bytes.Join( + [][]byte{ + []byte(core.BuiltInFunctionESDTNFTCreate), + []byte(hex.EncodeToString(tokenID)), + []byte(hex.EncodeToString(big.NewInt(quantity).Bytes())), // quantity + metaData.Name, + metaData.Royalties, + metaData.Hash, + metaData.Attributes, + metaData.Uris[0], + metaData.Uris[1], + metaData.Uris[2], + }, + []byte("@"), + ) + + return &transaction.Transaction{ + Nonce: nonce, + SndAddr: sndAdr, + RcvAddr: sndAdr, + GasLimit: 10_000_000, + GasPrice: MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: big.NewInt(0), + ChainID: []byte(configs.ChainID), + Version: 1, + } +} + +func ModifyCreatorTx( + nonce uint64, + sndAdr []byte, + tokenID []byte, +) *transaction.Transaction { + txDataField := bytes.Join( + [][]byte{ + []byte(core.ESDTModifyCreator), + []byte(hex.EncodeToString(tokenID)), + []byte(hex.EncodeToString(big.NewInt(1).Bytes())), + }, + []byte("@"), + ) + + return &transaction.Transaction{ + Nonce: nonce, + SndAddr: sndAdr, + RcvAddr: sndAdr, + GasLimit: 10_000_000, + GasPrice: MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: big.NewInt(0), + ChainID: []byte(configs.ChainID), + Version: 1, + } +} + +func GetESDTDataFromAcc( + t *testing.T, + cs chainSimulatorIntegrationTests.ChainSimulator, + addressBytes []byte, + token []byte, + shardID uint32, +) *esdt.ESDigitalToken { + account, err := cs.GetNodeHandler(shardID).GetStateComponents().AccountsAdapter().LoadAccount(addressBytes) + require.Nil(t, err) + userAccount, ok := account.(state.UserAccountHandler) + require.True(t, ok) + + baseEsdtKeyPrefix := core.ProtectedKeyPrefix + core.ESDTKeyIdentifier + key := append([]byte(baseEsdtKeyPrefix), token...) + + key2 := append(key, big.NewInt(0).SetUint64(1).Bytes()...) + esdtDataBytes, _, err := userAccount.RetrieveValue(key2) + require.Nil(t, err) + + esdtData := &esdt.ESDigitalToken{} + err = cs.GetNodeHandler(shardID).GetCoreComponents().InternalMarshalizer().Unmarshal(esdtData, esdtDataBytes) + require.Nil(t, err) + + return esdtData +} + +func GetMetaDataFromAcc( + t *testing.T, + cs chainSimulatorIntegrationTests.ChainSimulator, + addressBytes []byte, + token []byte, + shardID uint32, +) *esdt.MetaData { + esdtData := GetESDTDataFromAcc(t, cs, addressBytes, token, shardID) + + require.NotNil(t, esdtData.TokenMetaData) + + return esdtData.TokenMetaData +} + +func SetSpecialRoleTx( + nonce uint64, + sndAddr []byte, + address []byte, + token []byte, + roles [][]byte, +) *transaction.Transaction { + txDataBytes := [][]byte{ + []byte("setSpecialRole"), + []byte(hex.EncodeToString(token)), + []byte(hex.EncodeToString(address)), + } + + for _, role := range roles { + txDataBytes = append(txDataBytes, []byte(hex.EncodeToString(role))) + } + + txDataField := bytes.Join( + txDataBytes, + []byte("@"), + ) + + return &transaction.Transaction{ + Nonce: nonce, + SndAddr: sndAddr, + RcvAddr: vm.ESDTSCAddress, + GasLimit: 60_000_000, + GasPrice: MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: big.NewInt(0), + ChainID: []byte(configs.ChainID), + Version: 1, + } +} + +func SetAddressEsdtRoles( + t *testing.T, + cs chainSimulatorIntegrationTests.ChainSimulator, + nonce uint64, + address dtos.WalletAddress, + token []byte, + roles [][]byte, +) { + tx := SetSpecialRoleTx(nonce, address.Bytes, address.Bytes, token, roles) + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) +} + +type IssueTxFunc func(uint64, []byte, []byte, string) *transaction.Transaction + +func TestChainSimulatorChangeMetaData(t *testing.T, issueFn IssueTxFunc) { + baseIssuingCost := "1000" + + cs, _ := GetTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) + defer cs.Close() + + addrs := CreateAddresses(t, cs, true) + + Log.Info("Initial setup: Create token and send in another shard") + + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleTransfer), + []byte(core.ESDTRoleNFTAddQuantity), + } + + ticker := []byte("TICKER") + nonce := uint64(0) + tx := issueFn(nonce, addrs[1].Bytes, ticker, baseIssuingCost) + nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + tokenID := txResult.Logs.Events[0].Topics[0] + SetAddressEsdtRoles(t, cs, nonce, addrs[1], tokenID, roles) + nonce++ + + Log.Info("Issued token id", "tokenID", string(tokenID)) + + metaData := txsFee.GetDefaultMetaData() + metaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tx = EsdtNftCreateTx(nonce, addrs[1].Bytes, tokenID, metaData, 2) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + tx = ChangeToDynamicTx(nonce, addrs[1].Bytes, tokenID) + nonce++ + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + Log.Info("Send to separate shards") + + tx = EsdtNFTTransferTx(nonce, addrs[1].Bytes, addrs[2].Bytes, tokenID) + nonce++ + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + tx = EsdtNFTTransferTx(nonce, addrs[1].Bytes, addrs[0].Bytes, tokenID) + nonce++ + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + roles = [][]byte{ + []byte(core.ESDTRoleTransfer), + []byte(core.ESDTRoleNFTUpdate), + } + tx = SetSpecialRoleTx(nonce, addrs[1].Bytes, addrs[0].Bytes, tokenID, roles) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + Log.Info("Step 1. change the sft meta data in one shard") + + sftMetaData2 := txsFee.GetDefaultMetaData() + sftMetaData2.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + sftMetaData2.Name = []byte(hex.EncodeToString([]byte("name2"))) + sftMetaData2.Hash = []byte(hex.EncodeToString([]byte("hash2"))) + sftMetaData2.Attributes = []byte(hex.EncodeToString([]byte("attributes2"))) + + tx = EsdtMetaDataUpdateTx(tokenID, sftMetaData2, 0, addrs[0].Bytes) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + Log.Info("Step 2. check that the newest metadata is saved") + + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) + CheckMetaData(t, cs, core.SystemAccountAddress, tokenID, shardID, sftMetaData2) + + shard2ID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[2].Bytes) + CheckMetaData(t, cs, core.SystemAccountAddress, tokenID, shard2ID, metaData) + + Log.Info("Step 3. create new wallet is shard 2") + + mintValue := big.NewInt(10) + mintValue = mintValue.Mul(OneEGLD, mintValue) + newShard2Addr, err := cs.GenerateAndMintWalletAddress(2, mintValue) + require.Nil(t, err) + err = cs.GenerateBlocks(1) + require.Nil(t, err) + + Log.Info("Step 4. send updated token to shard 2 ") + + tx = EsdtNFTTransferTx(1, addrs[0].Bytes, newShard2Addr.Bytes, tokenID) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + err = cs.GenerateBlocks(5) + require.Nil(t, err) + + Log.Info("Step 5. check meta data in shard 2 is updated to latest version ") + + CheckMetaData(t, cs, core.SystemAccountAddress, tokenID, shard2ID, sftMetaData2) + CheckMetaData(t, cs, core.SystemAccountAddress, tokenID, shardID, sftMetaData2) + +} + +func CheckTokenRoles(t *testing.T, returnData [][]byte, expectedRoles [][]byte) { + for _, expRole := range expectedRoles { + found := false + + for _, item := range returnData { + if bytes.Equal(expRole, item) { + found = true + } + } + + require.True(t, found) + } +} + +func GetTestChainSimulatorWithDynamicNFTEnabled(t *testing.T, baseIssuingCost string) (chainSimulatorIntegrationTests.ChainSimulator, int32) { + activationEpochForDynamicNFT := uint32(2) + + numOfShards := uint32(3) + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: DefaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: RoundDurationInMillis, + SupernovaRoundDurationInMillis: SupernovaRoundDurationInMillis, + RoundsPerEpoch: RoundsPerEpoch, + SupernovaRoundsPerEpoch: SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 0, + NumNodesWaitingListShard: 0, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.DynamicESDTEnableEpoch = activationEpochForDynamicNFT + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = integrationTests.UnreachableEpoch // TODO: handle supernova activation with transition + cfg.SystemSCConfig.ESDTSystemSCConfig.BaseIssuingCost = baseIssuingCost + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpochForDynamicNFT)) + require.Nil(t, err) + + return cs, int32(activationEpochForDynamicNFT) +} + +func GetTestChainSimulatorWithSaveToSystemAccountDisabled(t *testing.T, baseIssuingCost string) (chainSimulatorIntegrationTests.ChainSimulator, int32) { + activationEpochForSaveToSystemAccount := uint32(4) + activationEpochForDynamicNFT := uint32(6) + + numOfShards := uint32(3) + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: DefaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: RoundDurationInMillis, + SupernovaRoundDurationInMillis: SupernovaRoundDurationInMillis, + RoundsPerEpoch: RoundsPerEpoch, + SupernovaRoundsPerEpoch: SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 0, + NumNodesWaitingListShard: 0, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.OptimizeNFTStoreEnableEpoch = activationEpochForSaveToSystemAccount + cfg.EpochConfig.EnableEpochs.DynamicESDTEnableEpoch = activationEpochForDynamicNFT + cfg.SystemSCConfig.ESDTSystemSCConfig.BaseIssuingCost = baseIssuingCost + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpochForSaveToSystemAccount) - 2) + require.Nil(t, err) + + return cs, int32(activationEpochForDynamicNFT) +} + +func CreateTokenUpdateTokenIDAndTransfer( + t *testing.T, + cs chainSimulatorIntegrationTests.ChainSimulator, + originAddress []byte, + targetAddress []byte, + tokenID []byte, + metaData *txsFee.MetaData, + epochForDynamicNFT int32, + walletWithRoles dtos.WalletAddress, +) { + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleTransfer), + } + SetAddressEsdtRoles(t, cs, 1, walletWithRoles, tokenID, roles) + + tx := EsdtNftCreateTx(2, originAddress, tokenID, metaData, 1) + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + Log.Info("check that the metadata is saved on the user account") + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(originAddress) + CheckMetaData(t, cs, originAddress, tokenID, shardID, metaData) + CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, shardID) + + err = cs.GenerateBlocksUntilEpochIsReached(epochForDynamicNFT) + require.Nil(t, err) + + tx = UpdateTokenIDTx(3, originAddress, tokenID) + + Log.Info("updating token id", "tokenID", tokenID) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + Log.Info("transferring token id", "tokenID", tokenID) + + tx = EsdtNFTTransferTx(4, originAddress, targetAddress, tokenID) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) +} + +func UnsetSpecialRole( + nonce uint64, + sndAddr []byte, + address []byte, + token []byte, + role []byte, +) *transaction.Transaction { + txDataBytes := [][]byte{ + []byte("unSetSpecialRole"), + []byte(hex.EncodeToString(token)), + []byte(hex.EncodeToString(address)), + []byte(hex.EncodeToString(role)), + } + + txDataField := bytes.Join( + txDataBytes, + []byte("@"), + ) + + return &transaction.Transaction{ + Nonce: nonce, + SndAddr: sndAddr, + RcvAddr: vm.ESDTSCAddress, + GasLimit: 60_000_000, + GasPrice: MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: big.NewInt(0), + ChainID: []byte(configs.ChainID), + Version: 1, + } +} + +func EsdtMetaDataUpdateTx(tokenID []byte, metaData *txsFee.MetaData, nonce uint64, address []byte) *transaction.Transaction { + txData := [][]byte{ + []byte(core.ESDTMetaDataUpdate), + []byte(hex.EncodeToString(tokenID)), + metaData.Nonce, + metaData.Name, + metaData.Royalties, + metaData.Hash, + metaData.Attributes, + } + if len(metaData.Uris) > 0 { + txData = append(txData, metaData.Uris...) + } else { + txData = append(txData, nil) + } + + txDataField := bytes.Join( + txData, + []byte("@"), + ) + + tx := &transaction.Transaction{ + Nonce: nonce, + SndAddr: address, + RcvAddr: address, + GasLimit: 10_000_000, + GasPrice: MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: big.NewInt(0), + ChainID: []byte(configs.ChainID), + Version: 1, + } + + return tx +} + +func TransferSpecialRoleToAddr( + t *testing.T, + cs chainSimulatorIntegrationTests.ChainSimulator, + nonce uint64, + tokenID []byte, + sndAddr []byte, + dstAddr []byte, + role []byte, +) uint64 { + tx := UnsetSpecialRole(nonce, sndAddr, sndAddr, tokenID, role) + nonce++ + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + tx = SetSpecialRoleTx(nonce, sndAddr, dstAddr, tokenID, [][]byte{role}) + nonce++ + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + return nonce +} diff --git a/integrationTests/chainSimulator/vm/egldMultiTransfer_test.go b/integrationTests/chainSimulator/vm/egldMultiTransfer/egldMultiTransfer_test.go similarity index 78% rename from integrationTests/chainSimulator/vm/egldMultiTransfer_test.go rename to integrationTests/chainSimulator/vm/egldMultiTransfer/egldMultiTransfer_test.go index f664eff7539..42ec18b5bc4 100644 --- a/integrationTests/chainSimulator/vm/egldMultiTransfer_test.go +++ b/integrationTests/chainSimulator/vm/egldMultiTransfer/egldMultiTransfer_test.go @@ -1,4 +1,4 @@ -package vm +package egldMultiTransfer import ( "encoding/hex" @@ -6,11 +6,13 @@ import ( "math/big" "strings" "testing" - "time" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/config" + vm2 "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator/vm" "github.com/multiversx/mx-chain-go/integrationTests/vm/txsFee" "github.com/multiversx/mx-chain-go/integrationTests/vm/txsFee/utils" "github.com/multiversx/mx-chain-go/node/chainSimulator" @@ -18,7 +20,6 @@ import ( "github.com/multiversx/mx-chain-go/node/chainSimulator/configs" "github.com/multiversx/mx-chain-go/node/chainSimulator/dtos" "github.com/multiversx/mx-chain-go/vm" - "github.com/stretchr/testify/require" ) func TestChainSimulator_EGLD_MultiTransfer(t *testing.T) { @@ -26,31 +27,26 @@ func TestChainSimulator_EGLD_MultiTransfer(t *testing.T) { t.Skip("this is not a short test") } - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 20, - } - activationEpoch := uint32(4) baseIssuingCost := "1000" numOfShards := uint32(3) cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 0, - NumNodesWaitingListShard: 0, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: vm2.DefaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: vm2.RoundDurationInMillis, + SupernovaRoundDurationInMillis: vm2.SupernovaRoundDurationInMillis, + RoundsPerEpoch: vm2.RoundsPerEpoch, + SupernovaRoundsPerEpoch: vm2.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 0, + NumNodesWaitingListShard: 0, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.EGLDInMultiTransferEnableEpoch = activationEpoch cfg.SystemSCConfig.ESDTSystemSCConfig.BaseIssuingCost = baseIssuingCost @@ -61,7 +57,7 @@ func TestChainSimulator_EGLD_MultiTransfer(t *testing.T) { defer cs.Close() - addrs := createAddresses(t, cs, false) + addrs := vm2.CreateAddresses(t, cs, false) err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch)) require.Nil(t, err) @@ -69,10 +65,10 @@ func TestChainSimulator_EGLD_MultiTransfer(t *testing.T) { // issue metaESDT metaESDTTicker := []byte("METATICKER") nonce := uint64(0) - tx := issueMetaESDTTx(nonce, addrs[0].Bytes, metaESDTTicker, baseIssuingCost) + tx := vm2.IssueMetaESDTTx(nonce, addrs[0].Bytes, metaESDTTicker, baseIssuingCost) nonce++ - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) require.Equal(t, "success", txResult.Status.String()) @@ -83,42 +79,42 @@ func TestChainSimulator_EGLD_MultiTransfer(t *testing.T) { []byte(core.ESDTRoleNFTCreate), []byte(core.ESDTRoleTransfer), } - setAddressEsdtRoles(t, cs, nonce, addrs[0], metaESDTTokenID, roles) + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[0], metaESDTTokenID, roles) nonce++ - log.Info("Issued metaESDT token id", "tokenID", string(metaESDTTokenID)) + vm2.Log.Info("Issued metaESDT token id", "tokenID", string(metaESDTTokenID)) // issue NFT nftTicker := []byte("NFTTICKER") - tx = issueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) + tx = vm2.IssueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) require.Equal(t, "success", txResult.Status.String()) nftTokenID := txResult.Logs.Events[0].Topics[0] - setAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) nonce++ - log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) + vm2.Log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) // issue SFT sftTicker := []byte("SFTTICKER") - tx = issueSemiFungibleTx(nonce, addrs[0].Bytes, sftTicker, baseIssuingCost) + tx = vm2.IssueSemiFungibleTx(nonce, addrs[0].Bytes, sftTicker, baseIssuingCost) nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) require.Equal(t, "success", txResult.Status.String()) sftTokenID := txResult.Logs.Events[0].Topics[0] - setAddressEsdtRoles(t, cs, nonce, addrs[0], sftTokenID, roles) + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[0], sftTokenID, roles) nonce++ - log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) + vm2.Log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) nftMetaData := txsFee.GetDefaultMetaData() nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) @@ -142,9 +138,9 @@ func TestChainSimulator_EGLD_MultiTransfer(t *testing.T) { } for i := range tokenIDs { - tx = esdtNftCreateTx(nonce, addrs[0].Bytes, tokenIDs[i], tokensMetadata[i], 1) + tx = vm2.EsdtNftCreateTx(nonce, addrs[0].Bytes, tokenIDs[i], tokensMetadata[i], 1) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) @@ -170,10 +166,10 @@ func TestChainSimulator_EGLD_MultiTransfer(t *testing.T) { beforeBalanceStr1 := account1.Balance - egldValue := oneEGLD.Mul(oneEGLD, big.NewInt(3)) + egldValue := vm2.OneEGLD.Mul(vm2.OneEGLD, big.NewInt(3)) tx = multiESDTNFTTransferWithEGLDTx(nonce, addrs[0].Bytes, addrs[1].Bytes, tokenIDs, egldValue) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) @@ -211,31 +207,26 @@ func TestChainSimulator_EGLD_MultiTransfer_Insufficient_Funds(t *testing.T) { t.Skip("this is not a short test") } - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 20, - } - activationEpoch := uint32(4) baseIssuingCost := "1000" numOfShards := uint32(3) cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 0, - NumNodesWaitingListShard: 0, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: vm2.DefaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: vm2.RoundDurationInMillis, + SupernovaRoundDurationInMillis: vm2.SupernovaRoundDurationInMillis, + RoundsPerEpoch: vm2.RoundsPerEpoch, + SupernovaRoundsPerEpoch: vm2.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 0, + NumNodesWaitingListShard: 0, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.EGLDInMultiTransferEnableEpoch = activationEpoch cfg.SystemSCConfig.ESDTSystemSCConfig.BaseIssuingCost = baseIssuingCost @@ -246,7 +237,7 @@ func TestChainSimulator_EGLD_MultiTransfer_Insufficient_Funds(t *testing.T) { defer cs.Close() - addrs := createAddresses(t, cs, false) + addrs := vm2.CreateAddresses(t, cs, false) err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch)) require.Nil(t, err) @@ -254,10 +245,10 @@ func TestChainSimulator_EGLD_MultiTransfer_Insufficient_Funds(t *testing.T) { // issue NFT nftTicker := []byte("NFTTICKER") nonce := uint64(0) - tx := issueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) + tx := vm2.IssueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) nonce++ - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) require.Equal(t, "success", txResult.Status.String()) @@ -268,18 +259,18 @@ func TestChainSimulator_EGLD_MultiTransfer_Insufficient_Funds(t *testing.T) { []byte(core.ESDTRoleNFTCreate), []byte(core.ESDTRoleTransfer), } - setAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) nonce++ - log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) + vm2.Log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) nftMetaData := txsFee.GetDefaultMetaData() nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - tx = esdtNftCreateTx(nonce, addrs[0].Bytes, nftTokenID, nftMetaData, 1) + tx = vm2.EsdtNftCreateTx(nonce, addrs[0].Bytes, nftTokenID, nftMetaData, 1) nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) @@ -295,6 +286,8 @@ func TestChainSimulator_EGLD_MultiTransfer_Insufficient_Funds(t *testing.T) { account1, err := cs.GetAccount(addrs[1]) require.Nil(t, err) + _, err = cs.GetAccount(addrs[1]) + require.Nil(t, err) beforeBalanceStr1 := account1.Balance @@ -302,25 +295,15 @@ func TestChainSimulator_EGLD_MultiTransfer_Insufficient_Funds(t *testing.T) { egldValue = egldValue.Add(egldValue, big.NewInt(13)) tx = multiESDTNFTTransferWithEGLDTx(nonce, addrs[0].Bytes, addrs[1].Bytes, [][]byte{nftTokenID}, egldValue) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.NotEqual(t, "success", txResult.Status.String()) - - eventLog := string(txResult.Logs.Events[0].Topics[1]) - require.Equal(t, "insufficient funds for token EGLD-000000", eventLog) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.ErrorContains(t, err, "Transaction(s) is/are still in pending") + require.Nil(t, txResult) // check accounts balance account0, err = cs.GetAccount(addrs[0]) require.Nil(t, err) - beforeBalance0, _ := big.NewInt(0).SetString(beforeBalanceStr0, 10) - - txsFee, _ := big.NewInt(0).SetString(txResult.Fee, 10) - expectedBalanceWithFee0 := big.NewInt(0).Sub(beforeBalance0, txsFee) - - require.Equal(t, expectedBalanceWithFee0.String(), account0.Balance) + require.Equal(t, beforeBalanceStr0, account0.Balance) account1, err = cs.GetAccount(addrs[1]) require.Nil(t, err) @@ -333,31 +316,26 @@ func TestChainSimulator_EGLD_MultiTransfer_Invalid_Value(t *testing.T) { t.Skip("this is not a short test") } - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 20, - } - activationEpoch := uint32(4) baseIssuingCost := "1000" numOfShards := uint32(3) cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 0, - NumNodesWaitingListShard: 0, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: vm2.DefaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: vm2.RoundDurationInMillis, + SupernovaRoundDurationInMillis: vm2.SupernovaRoundDurationInMillis, + RoundsPerEpoch: vm2.RoundsPerEpoch, + SupernovaRoundsPerEpoch: vm2.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 0, + NumNodesWaitingListShard: 0, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.EGLDInMultiTransferEnableEpoch = activationEpoch cfg.SystemSCConfig.ESDTSystemSCConfig.BaseIssuingCost = baseIssuingCost @@ -368,7 +346,7 @@ func TestChainSimulator_EGLD_MultiTransfer_Invalid_Value(t *testing.T) { defer cs.Close() - addrs := createAddresses(t, cs, false) + addrs := vm2.CreateAddresses(t, cs, false) err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch)) require.Nil(t, err) @@ -376,10 +354,10 @@ func TestChainSimulator_EGLD_MultiTransfer_Invalid_Value(t *testing.T) { // issue NFT nftTicker := []byte("NFTTICKER") nonce := uint64(0) - tx := issueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) + tx := vm2.IssueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) nonce++ - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) require.Equal(t, "success", txResult.Status.String()) @@ -390,18 +368,18 @@ func TestChainSimulator_EGLD_MultiTransfer_Invalid_Value(t *testing.T) { []byte(core.ESDTRoleNFTCreate), []byte(core.ESDTRoleTransfer), } - setAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) nonce++ - log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) + vm2.Log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) nftMetaData := txsFee.GetDefaultMetaData() nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - tx = esdtNftCreateTx(nonce, addrs[0].Bytes, nftTokenID, nftMetaData, 1) + tx = vm2.EsdtNftCreateTx(nonce, addrs[0].Bytes, nftTokenID, nftMetaData, 1) nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) @@ -420,11 +398,11 @@ func TestChainSimulator_EGLD_MultiTransfer_Invalid_Value(t *testing.T) { beforeBalanceStr1 := account1.Balance - egldValue := oneEGLD.Mul(oneEGLD, big.NewInt(3)) + egldValue := vm2.OneEGLD.Mul(vm2.OneEGLD, big.NewInt(3)) tx = multiESDTNFTTransferWithEGLDTx(nonce, addrs[0].Bytes, addrs[1].Bytes, [][]byte{nftTokenID}, egldValue) tx.Value = egldValue // invalid value field - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) @@ -455,7 +433,6 @@ func TestChainSimulator_Multiple_EGLD_Transfers(t *testing.T) { t.Skip("this is not a short test") } - startTime := time.Now().Unix() roundDurationInMillis := uint64(6000) roundsPerEpoch := core.OptionalUint64{ HasValue: true, @@ -468,18 +445,20 @@ func TestChainSimulator_Multiple_EGLD_Transfers(t *testing.T) { numOfShards := uint32(3) cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 0, - NumNodesWaitingListShard: 0, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: vm2.DefaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: vm2.SupernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: vm2.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 0, + NumNodesWaitingListShard: 0, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.EGLDInMultiTransferEnableEpoch = activationEpoch cfg.SystemSCConfig.ESDTSystemSCConfig.BaseIssuingCost = baseIssuingCost @@ -490,7 +469,7 @@ func TestChainSimulator_Multiple_EGLD_Transfers(t *testing.T) { defer cs.Close() - addrs := createAddresses(t, cs, false) + addrs := vm2.CreateAddresses(t, cs, false) err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch)) require.Nil(t, err) @@ -498,10 +477,10 @@ func TestChainSimulator_Multiple_EGLD_Transfers(t *testing.T) { // issue NFT nftTicker := []byte("NFTTICKER") nonce := uint64(0) - tx := issueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) + tx := vm2.IssueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) nonce++ - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) require.Equal(t, "success", txResult.Status.String()) @@ -512,18 +491,18 @@ func TestChainSimulator_Multiple_EGLD_Transfers(t *testing.T) { []byte(core.ESDTRoleNFTCreate), []byte(core.ESDTRoleTransfer), } - setAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) nonce++ - log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) + vm2.Log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) nftMetaData := txsFee.GetDefaultMetaData() nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - tx = esdtNftCreateTx(nonce, addrs[0].Bytes, nftTokenID, nftMetaData, 1) + tx = vm2.EsdtNftCreateTx(nonce, addrs[0].Bytes, nftTokenID, nftMetaData, 1) nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) @@ -545,7 +524,7 @@ func TestChainSimulator_Multiple_EGLD_Transfers(t *testing.T) { // multi nft transfer with multiple EGLD-000000 tokens numTransfers := 3 encodedReceiver := hex.EncodeToString(addrs[1].Bytes) - egldValue := oneEGLD.Mul(oneEGLD, big.NewInt(3)) + egldValue := vm2.OneEGLD.Mul(vm2.OneEGLD, big.NewInt(3)) txDataField := []byte(strings.Join( []string{ @@ -569,7 +548,7 @@ func TestChainSimulator_Multiple_EGLD_Transfers(t *testing.T) { SndAddr: addrs[0].Bytes, RcvAddr: addrs[0].Bytes, GasLimit: 10_000_000, - GasPrice: minGasPrice, + GasPrice: vm2.MinGasPrice, Data: txDataField, Value: big.NewInt(0), Version: 1, @@ -577,7 +556,7 @@ func TestChainSimulator_Multiple_EGLD_Transfers(t *testing.T) { ChainID: []byte(configs.ChainID), } - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) @@ -650,7 +629,7 @@ func multiESDTNFTTransferWithEGLDTx(nonce uint64, sndAdr, rcvAddr []byte, tokens SndAddr: sndAdr, RcvAddr: sndAdr, GasLimit: 10_000_000, - GasPrice: minGasPrice, + GasPrice: vm2.MinGasPrice, Data: txDataField, Value: big.NewInt(0), Version: 1, @@ -666,7 +645,6 @@ func TestChainSimulator_IssueToken_EGLDTicker(t *testing.T) { t.Skip("this is not a short test") } - startTime := time.Now().Unix() roundDurationInMillis := uint64(6000) roundsPerEpoch := core.OptionalUint64{ HasValue: true, @@ -679,18 +657,20 @@ func TestChainSimulator_IssueToken_EGLDTicker(t *testing.T) { numOfShards := uint32(3) cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 0, - NumNodesWaitingListShard: 0, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: vm2.DefaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: roundDurationInMillis, + SupernovaRoundDurationInMillis: vm2.SupernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: vm2.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 0, + NumNodesWaitingListShard: 0, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.EGLDInMultiTransferEnableEpoch = activationEpoch cfg.SystemSCConfig.ESDTSystemSCConfig.BaseIssuingCost = baseIssuingCost @@ -701,20 +681,20 @@ func TestChainSimulator_IssueToken_EGLDTicker(t *testing.T) { defer cs.Close() - addrs := createAddresses(t, cs, false) + addrs := vm2.CreateAddresses(t, cs, false) err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch) - 1) require.Nil(t, err) - log.Info("Initial setup: Issue token (before the activation of EGLDInMultiTransferFlag)") + vm2.Log.Info("Initial setup: Issue token (before the activation of EGLDInMultiTransferFlag)") // issue NFT nftTicker := []byte("EGLD") nonce := uint64(0) - tx := issueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) + tx := vm2.IssueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) nonce++ - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) require.Equal(t, "success", txResult.Status.String()) @@ -725,18 +705,18 @@ func TestChainSimulator_IssueToken_EGLDTicker(t *testing.T) { []byte(core.ESDTRoleNFTCreate), []byte(core.ESDTRoleTransfer), } - setAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) nonce++ - log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) + vm2.Log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) nftMetaData := txsFee.GetDefaultMetaData() nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - tx = esdtNftCreateTx(nonce, addrs[0].Bytes, nftTokenID, nftMetaData, 1) + tx = vm2.EsdtNftCreateTx(nonce, addrs[0].Bytes, nftTokenID, nftMetaData, 1) nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) @@ -748,12 +728,12 @@ func TestChainSimulator_IssueToken_EGLDTicker(t *testing.T) { err = cs.GenerateBlocks(10) require.Nil(t, err) - log.Info("Issue token (after activation of EGLDInMultiTransferFlag)") + vm2.Log.Info("Issue token (after activation of EGLDInMultiTransferFlag)") // should fail issuing token with EGLD ticker - tx = issueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) + tx = vm2.IssueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) @@ -775,22 +755,23 @@ func TestScCallTransferValueESDT(t *testing.T) { } cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: time.Now().Unix(), - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpochOpt, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 3, - NumNodesWaitingListShard: 3, - - InitialEpoch: 1700, - InitialNonce: 1700, - InitialRound: 1700, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: vm2.DefaultPathToInitialConfig, + NumOfShards: 3, + RoundDurationInMillis: roundDurationInMillis, + RoundsPerEpoch: roundsPerEpochOpt, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 3, + NumNodesWaitingListShard: 3, + InitialEpoch: 1, + InitialNonce: 1, + InitialRound: 1, + AlterConfigsFunction: func(cfg *config.Configs) { + }, }) require.NoError(t, err) require.NotNil(t, cs) @@ -834,13 +815,13 @@ func TestScCallTransferValueESDT(t *testing.T) { RcvAddr: rcv, Data: []byte("upgradeContract@0061736d01000000016b116000017f60000060027f7f017f60027f7f0060017f0060017f017f60047f7f7f7f0060037f7f7f017f60027f7e0060037f7f7f0060047f7f7f7f017f60067e7f7f7f7f7f017f60057f7f7e7f7f017f6000017e60057f7f7f7f7f0060047f7f7f7e0060057e7f7f7f7f0002bd041803656e760a6d4275666665724e6577000003656e760d6d427566666572417070656e64000203656e760d6d616e6167656443616c6c6572000403656e76126d427566666572417070656e644279746573000703656e76126d616e616765645369676e616c4572726f72000403656e76126d427566666572476574417267756d656e74000203656e76106d4275666665724765744c656e677468000503656e7619626967496e74476574556e7369676e6564417267756d656e74000303656e760f6765744e756d417267756d656e7473000003656e760b7369676e616c4572726f72000303656e761b6d616e61676564457865637574654f6e44657374436f6e74657874000b03656e760f6d4275666665725365744279746573000703656e76196d42756666657246726f6d426967496e74556e7369676e6564000203656e760e626967496e74536574496e743634000803656e76106d616e61676564534341646472657373000403656e760e636865636b4e6f5061796d656e74000103656e761b6d616e616765645472616e7366657256616c756545786563757465000c03656e761c6d616e616765644765744d756c74694553445443616c6c56616c7565000403656e76096d4275666665724571000203656e7609626967496e74416464000903656e760a6765744761734c656674000d03656e760f636c65616e52657475726e44617461000103656e76136d42756666657253746f7261676553746f7265000203656e76136d42756666657247657442797465536c696365000a031e1d05000002040509000605030e05030a06060f081000030300010101010105030100030616037f01418080080b7f00419582080b7f0041a082080b075608066d656d6f7279020004696e697400300775706772616465003107666f7277617264003208726563656976656400330863616c6c4261636b00340a5f5f646174615f656e6403010b5f5f686561705f6261736503020a84151d0f01017f10002201200010011a20010b0c01017f101a2200100220000b1901017f419082084190820828020041016b220036020020000b1101017f101a220220002001100b1a20020b1400100820004604400f0b41d6800841191009000b2b01027f2000419482082d0000220171200041ff01714622024504404194820820002001723a00000b20020b180020012002101b21012000101f360204200020013602000b080041014100101b0b1e00101f1a200220032802001021102220002002360204200020013602000b0f01017f101a22012000100c1a20010b4601017f230041106b220224002002200141187420014180fe03714108747220014108764180fe03712001411876727236020c20002002410c6a410410031a200241106a24000b8e0101037f230041106b220524000240200310240d00200220031025200410062107410021030340200320074f0d012005410036020c200420032005410c6a410410261a2002200528020c220641187420064180fe03714108747220064108764180fe0371200641187672721025200341046a21030c000b000b2000200236020420002001360200200541106a24000b070020001006450b0d00101f1a20002001101810220b0f00200020012003200210174100470b1e00101f1a200220032802001018102220002002360204200020013602000b1b00101f1a200220031018102220002002360204200020013602000b2001017f101f22042003102a20022004102220002002360204200020013602000bff0102027f017e230041106b220324002003200142388620014280fe0383422886842001428080fc0783421886200142808080f80f834208868484200142088842808080f80f832001421888428080fc078384200142288822044280fe03832001423888848484370308200041002001428080808080808080015422002001423088a741ff01711b220220006a410020022004a741ff01711b22006a410020002001422088a741ff01711b22006a410020002001a722004118761b22026a41002002200041107641ff01711b22026a41002002200041087641ff01711b22006a200041002001501b6a2200200341086a6a410820006b100b1a200341106a24000b110020002001200220032004101a100a1a0b0a0041764200100d41760b7001037f230041106b22022400200020012802042204200128020849047f200241086a2203420037030020024200370300200128020020042002411010261a2001200441106a36020420002002290300370001200041096a200329030037000041010541000b3a0000200241106a24000bb90102017f017e2000200128000c220241187420024180fe03714108747220024108764180fe03712002411876727236020c20002001280000220241187420024180fe03714108747220024108764180fe03712002411876727236020820002001290004220342388620034280fe0383422886842003428080fc0783421886200342808080f80f834208868484200342088842808080f80f832003421888428080fc07838420034228884280fe038320034238888484843703000b0c01017f101a2200100e20000b0800100f4100101c0b3301037f100f4100101c1019210141e58108412a101b2100101f2102200010244504402001102c42a0c21e2000200210101a0b0b930b020a7f027e230041d0016b220024004101101c4100101a220110051a20011006412047044041bc80084117101b220041d58108411010031a200041d38008410310031a200041bb8108411010031a20001004000b200121034102101d450440415a10110b02404104101d0d00415841b18008410b100b1a2000415a100636029801200042daffffff0f370290010340200041b8016a20004190016a102d20002d00b8014101470d01415820002800b901220141187420014180fe03714108747220014108764180fe037120014118767272101241004c0d000b4199800841181009000b1019101821010240200341feffffff07470440200041f8006a41d181084104101e200041f0006a2000280278200028027c200110282000280274210520002802702106101f21022000415a100636028c01200042daffffff0f37028401200041c0016a210820004191016a2107034020004190016a20004184016a102d20002d0090014101460440200041b0016a200741086a290000370300200020072900003703a8012008200041a8016a102e20002903c001210a20002802cc01210920002802c80110182104101a22014200100d20012001200910132000200a423886200a4280fe038342288684200a428080fc0783421886200a42808080f80f834208868484200a42088842808080f80f83200a421888428080fc078384200a4228884280fe0383200a423888848484370294012000200441187420044180fe03714108747220044108764180fe037120044118767272360290012000200141187420014180fe03714108747220014108764180fe03712001411876727236029c01200220004190016a411010031a0c010b0b1014220a42a08d067d200a200a42a08d06561b210b0240024002400240200210064104760e020102000b200041186a41ef80084114101e20002802182104200028021c2101101f1a2001200310181022200210062103101f22072003410476ad102a20012007102220002002100636029801200041003602940120002002360290010340200041b8016a20004190016a102d20002d00b801410146044020002800c501210220002900bd01210a200120002800b901220341187420034180fe03714108747220034108764180fe0371200341187672721025200041086a20042001200a423886200a4280fe038342288684200a428080fc0783421886200a42808080f80f834208868484200a42088842808080f80f83200a421888428080fc078384200a4228884280fe0383200a423888848484102920002802082104200028020c2101101f1a2001200241187420024180fe03714108747220024108764180fe037120024118767272102110220c010b0b200041106a200420012006200510232000280214210120002802102102200b102f102c20022001102b0c020b200b2003102c20062005102b0c010b20004198016a420037030020004200370390012002410020004190016a411010260d02200041c0016a20004190016a102e200041b0016a2201200041c8016a290300370300200020002903c001220a3703a801200041b4016a2102200a500440200041386a41928108410c101e200041306a2000280238200028023c20011027200041286a2000280230200028023420021020200041206a2000280228200028022c2006200510232000280224210120002802202102200b2003102c20022001102b0c010b200041e8006a41838108410f101e200041e0006a2000280268200028026c20011027200041d8006a20002802602000280264200a1029200041d0006a2000280258200028025c20021020200041c8006a2000280250200028025420031028200041406b2000280248200028024c2006200510232000280244210120002802402102200b102f102c20022001102b0b1015200041d0016a24000f0b4180800841191009000b419e8108411d1009000b2101017f100f4101101c4100101a2200100741cb81084106101b2000102110161a0b0300010b0ba3020200418080080b8f02726563697069656e742061646472657373206e6f7420736574756e65787065637465642045474c44207472616e7366657245474c442d303030303030617267756d656e74206465636f6465206572726f722028293a2077726f6e67206e756d626572206f6620617267756d656e74734d756c7469455344544e46545472616e73666572455344544e46545472616e73666572455344545472616e736665724d616e6167656456656320696e646578206f7574206f662072616e6765626164206172726179206c656e677468616d6f756e747465737464756d6d795f73635f61646472657373455344545472616e7366657240353535333434343332443333333533303633333436354030463432343000419082080b0438ffffff@0504"), GasLimit: 100_000_000, - GasPrice: minGasPrice, + GasPrice: vm2.MinGasPrice, ChainID: []byte(configs.ChainID), Version: 1, Signature: []byte("dummy"), } - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) require.Equal(t, "success", txResult.Status.String()) diff --git a/integrationTests/chainSimulator/vm/esdtImprovementsSet1/esdtImprovements_test.go b/integrationTests/chainSimulator/vm/esdtImprovementsSet1/esdtImprovements_test.go new file mode 100644 index 00000000000..a75143ec134 --- /dev/null +++ b/integrationTests/chainSimulator/vm/esdtImprovementsSet1/esdtImprovements_test.go @@ -0,0 +1,2058 @@ +package esdtImprovements + +import ( + "bytes" + "encoding/hex" + "fmt" + "math/big" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data/esdt" + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/config" + testsChainSimulator "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator" + vm2 "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator/vm" + "github.com/multiversx/mx-chain-go/integrationTests/vm/txsFee" + "github.com/multiversx/mx-chain-go/node/chainSimulator" + "github.com/multiversx/mx-chain-go/node/chainSimulator/components/api" + "github.com/multiversx/mx-chain-go/node/chainSimulator/configs" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/vm" +) + +// Test scenario #1 +// +// Initial setup: Create fungible, NFT, SFT and metaESDT tokens +// (before the activation of DynamicEsdtFlag) +// +// 1.check that the metadata for all tokens is saved on the system account +// 2. wait for DynamicEsdtFlag activation +// 3. transfer the tokens to another account +// 4. check that the metadata for all tokens is saved on the system account +// 5. make an updateTokenID@tokenID function call on the ESDTSystem SC for all token types +// 6. check that the metadata for all tokens is saved on the system account +// 7. transfer the tokens to another account +// 8. check that the metaData for the NFT was removed from the system account and moved to the user account +// 9. check that the metaData for the rest of the tokens is still present on the system account and not on the userAccount +// 10. do the test for both intra and cross shard txs +func TestChainSimulator_CheckTokensMetadata_TransferTokens(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + t.Run("transfer and check all tokens - intra shard", func(t *testing.T) { + vm2.TransferAndCheckTokensMetaData(t, false, false) + }) + + t.Run("transfer and check all tokens - intra shard - multi transfer", func(t *testing.T) { + vm2.TransferAndCheckTokensMetaData(t, false, true) + }) + + t.Run("transfer and check all tokens - cross shard", func(t *testing.T) { + vm2.TransferAndCheckTokensMetaData(t, true, false) + }) + + t.Run("transfer and check all tokens - cross shard - multi transfer", func(t *testing.T) { + vm2.TransferAndCheckTokensMetaData(t, true, true) + }) +} + +// Test scenario #3 +// +// Initial setup: Create NFT, SFT and metaESDT tokens +// (after the activation of DynamicEsdtFlag) +// +// 1. check that the metaData for the NFT was saved in the user account and not on the system account +// 2. check that the metaData for the other token types is saved on the system account and not at the user account level +func TestChainSimulator_CreateTokensAfterActivation(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + + cs, _ := vm2.GetTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, false) + + vm2.Log.Info("Initial setup: Create NFT, SFT and metaESDT tokens (after the activation of DynamicEsdtFlag)") + + // issue metaESDT + metaESDTTicker := []byte("METATICKER") + nonce := uint64(0) + tx := vm2.IssueMetaESDTTx(nonce, addrs[0].Bytes, metaESDTTicker, baseIssuingCost) + nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + metaESDTTokenID := txResult.Logs.Events[0].Topics[0] + + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleTransfer), + } + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[0], metaESDTTokenID, roles) + nonce++ + + vm2.Log.Info("Issued metaESDT token id", "tokenID", string(metaESDTTokenID)) + + // issue NFT + nftTicker := []byte("NFTTICKER") + tx = vm2.IssueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + nftTokenID := txResult.Logs.Events[0].Topics[0] + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) + nonce++ + + vm2.Log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) + + // issue SFT + sftTicker := []byte("SFTTICKER") + tx = vm2.IssueSemiFungibleTx(nonce, addrs[0].Bytes, sftTicker, baseIssuingCost) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + sftTokenID := txResult.Logs.Events[0].Topics[0] + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[0], sftTokenID, roles) + nonce++ + + vm2.Log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) + + tokenIDs := [][]byte{ + nftTokenID, + sftTokenID, + metaESDTTokenID, + } + + nftMetaData := txsFee.GetDefaultMetaData() + nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + sftMetaData := txsFee.GetDefaultMetaData() + sftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + esdtMetaData := txsFee.GetDefaultMetaData() + esdtMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tokensMetadata := []*txsFee.MetaData{ + nftMetaData, + sftMetaData, + esdtMetaData, + } + + for i := range tokenIDs { + tx = vm2.EsdtNftCreateTx(nonce, addrs[0].Bytes, tokenIDs[i], tokensMetadata[i], 1) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + } + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.Log.Info("Step 1. check that the metaData for the NFT was saved in the user account and not on the system account") + + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) + + vm2.CheckMetaData(t, cs, addrs[0].Bytes, nftTokenID, shardID, nftMetaData) + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) + + vm2.Log.Info("Step 2. check that the metaData for the other token types is saved on the system account and not at the user account level") + + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, sftTokenID, shardID, sftMetaData) + vm2.CheckMetaDataNotInAcc(t, cs, addrs[0].Bytes, sftTokenID, shardID) + + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, metaESDTTokenID, shardID, esdtMetaData) + vm2.CheckMetaDataNotInAcc(t, cs, addrs[0].Bytes, metaESDTTokenID, shardID) +} + +// Test scenario #4 +// +// Initial setup: Create NFT, SFT, metaESDT tokens +// +// Call ESDTMetaDataRecreate to rewrite the meta data for the nft +// (The sender must have the ESDTMetaDataRecreate role) +func TestChainSimulator_ESDTMetaDataRecreate(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + + cs, _ := vm2.GetTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) + defer cs.Close() + + vm2.Log.Info("Initial setup: Create NFT, SFT and metaESDT tokens (after the activation of DynamicEsdtFlag)") + + addrs := vm2.CreateAddresses(t, cs, false) + + // issue metaESDT + metaESDTTicker := []byte("METATICKER") + nonce := uint64(0) + tx := vm2.IssueMetaESDTTx(nonce, addrs[0].Bytes, metaESDTTicker, baseIssuingCost) + nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + metaESDTTokenID := txResult.Logs.Events[0].Topics[0] + + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + } + tx = vm2.SetSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, metaESDTTokenID, roles) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("Issued metaESDT token id", "tokenID", string(metaESDTTokenID)) + + // issue NFT + nftTicker := []byte("NFTTICKER") + tx = vm2.IssueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + nftTokenID := txResult.Logs.Events[0].Topics[0] + tx = vm2.SetSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, nftTokenID, roles) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) + + // issue SFT + sftTicker := []byte("SFTTICKER") + tx = vm2.IssueSemiFungibleTx(nonce, addrs[0].Bytes, sftTicker, baseIssuingCost) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + sftTokenID := txResult.Logs.Events[0].Topics[0] + tx = vm2.SetSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, sftTokenID, roles) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) + + tokenIDs := [][]byte{ + nftTokenID, + sftTokenID, + metaESDTTokenID, + } + + nftMetaData := txsFee.GetDefaultMetaData() + nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + sftMetaData := txsFee.GetDefaultMetaData() + sftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + esdtMetaData := txsFee.GetDefaultMetaData() + esdtMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tokensMetadata := []*txsFee.MetaData{ + nftMetaData, + sftMetaData, + esdtMetaData, + } + + for i := range tokenIDs { + tx = vm2.EsdtNftCreateTx(nonce, addrs[0].Bytes, tokenIDs[i], tokensMetadata[i], 1) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + + tx = vm2.ChangeToDynamicTx(nonce, addrs[0].Bytes, tokenIDs[i]) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + + roles := [][]byte{ + []byte(core.ESDTRoleNFTRecreate), + } + tx = vm2.SetSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, tokenIDs[i], roles) + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + } + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.Log.Info("Call ESDTMetaDataRecreate to rewrite the meta data for the nft") + + for i := range tokenIDs { + newMetaData := txsFee.GetDefaultMetaData() + newMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + newMetaData.Name = []byte(hex.EncodeToString([]byte("name2"))) + newMetaData.Hash = []byte(hex.EncodeToString([]byte("hash2"))) + newMetaData.Attributes = []byte(hex.EncodeToString([]byte("attributes2"))) + + txDataField := bytes.Join( + [][]byte{ + []byte(core.ESDTMetaDataRecreate), + []byte(hex.EncodeToString(tokenIDs[i])), + newMetaData.Nonce, + newMetaData.Name, + []byte(hex.EncodeToString(big.NewInt(10).Bytes())), + newMetaData.Hash, + newMetaData.Attributes, + newMetaData.Uris[0], + newMetaData.Uris[1], + newMetaData.Uris[2], + }, + []byte("@"), + ) + + tx = &transaction.Transaction{ + Nonce: nonce, + SndAddr: addrs[0].Bytes, + RcvAddr: addrs[0].Bytes, + GasLimit: 10_000_000, + GasPrice: vm2.MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: big.NewInt(0), + ChainID: []byte(configs.ChainID), + Version: 1, + } + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) + + if bytes.Equal(tokenIDs[i], tokenIDs[0]) { // nft token + vm2.CheckMetaData(t, cs, addrs[0].Bytes, tokenIDs[i], shardID, newMetaData) + } else { + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, tokenIDs[i], shardID, newMetaData) + } + + nonce++ + } +} + +// Test scenario #5 +// +// Initial setup: Create NFT, SFT, metaESDT tokens +// +// Call ESDTMetaDataUpdate to update some of the meta data parameters +// (The sender must have the ESDTRoleNFTUpdate role) +func TestChainSimulator_ESDTMetaDataUpdate(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + + cs, _ := vm2.GetTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) + defer cs.Close() + + vm2.Log.Info("Initial setup: Create NFT, SFT and metaESDT tokens (after the activation of DynamicEsdtFlag)") + + addrs := vm2.CreateAddresses(t, cs, false) + + // issue metaESDT + metaESDTTicker := []byte("METATICKER") + nonce := uint64(0) + tx := vm2.IssueMetaESDTTx(nonce, addrs[0].Bytes, metaESDTTicker, baseIssuingCost) + nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + metaESDTTokenID := txResult.Logs.Events[0].Topics[0] + + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleTransfer), + } + tx = vm2.SetSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, metaESDTTokenID, roles) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("Issued metaESDT token id", "tokenID", string(metaESDTTokenID)) + + // issue NFT + nftTicker := []byte("NFTTICKER") + tx = vm2.IssueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + nftTokenID := txResult.Logs.Events[0].Topics[0] + tx = vm2.SetSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, nftTokenID, roles) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) + + // issue SFT + sftTicker := []byte("SFTTICKER") + tx = vm2.IssueSemiFungibleTx(nonce, addrs[0].Bytes, sftTicker, baseIssuingCost) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + sftTokenID := txResult.Logs.Events[0].Topics[0] + tx = vm2.SetSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, sftTokenID, roles) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) + + tokenIDs := [][]byte{ + nftTokenID, + sftTokenID, + metaESDTTokenID, + } + + nftMetaData := txsFee.GetDefaultMetaData() + nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + sftMetaData := txsFee.GetDefaultMetaData() + sftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + esdtMetaData := txsFee.GetDefaultMetaData() + esdtMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tokensMetadata := []*txsFee.MetaData{ + nftMetaData, + sftMetaData, + esdtMetaData, + } + + for i := range tokenIDs { + tx = vm2.EsdtNftCreateTx(nonce, addrs[0].Bytes, tokenIDs[i], tokensMetadata[i], 1) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + + tx = vm2.ChangeToDynamicTx(nonce, addrs[0].Bytes, tokenIDs[i]) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + + roles := [][]byte{ + []byte(core.ESDTRoleNFTUpdate), + } + tx = vm2.SetSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, tokenIDs[i], roles) + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + } + + vm2.Log.Info("Call ESDTMetaDataUpdate to rewrite the meta data for the nft") + + for i := range tokenIDs { + newMetaData := txsFee.GetDefaultMetaData() + newMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + newMetaData.Name = []byte(hex.EncodeToString([]byte("name2"))) + newMetaData.Hash = []byte(hex.EncodeToString([]byte("hash2"))) + newMetaData.Attributes = []byte(hex.EncodeToString([]byte("attributes2"))) + + tx = vm2.EsdtMetaDataUpdateTx(tokenIDs[i], newMetaData, nonce, addrs[0].Bytes) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) + + if bytes.Equal(tokenIDs[i], tokenIDs[0]) { // nft token + vm2.CheckMetaData(t, cs, addrs[0].Bytes, tokenIDs[i], shardID, newMetaData) + } else { + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, tokenIDs[i], shardID, newMetaData) + } + + nonce++ + } +} + +// Test scenario #6 +// +// Initial setup: Create NFT, SFT, metaESDT tokens +// +// Call ESDTModifyCreator and check that the creator was modified +// (The sender must have the ESDTRoleModifyCreator role) +func TestChainSimulator_ESDTModifyCreator(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + + cs, _ := vm2.GetTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) + defer cs.Close() + + vm2.Log.Info("Initial setup: Create NFT, SFT and metaESDT tokens (after the activation of DynamicEsdtFlag). Register NFT directly as dynamic") + + addrs := vm2.CreateAddresses(t, cs, false) + + // issue metaESDT + metaESDTTicker := []byte("METATICKER") + nonce := uint64(0) + tx := vm2.IssueMetaESDTTx(nonce, addrs[1].Bytes, metaESDTTicker, baseIssuingCost) + nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + metaESDTTokenID := txResult.Logs.Events[0].Topics[0] + + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleTransfer), + } + tx = vm2.SetSpecialRoleTx(nonce, addrs[1].Bytes, addrs[1].Bytes, metaESDTTokenID, roles) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("Issued metaESDT token id", "tokenID", string(metaESDTTokenID)) + + // register dynamic NFT + nftTicker := []byte("NFTTICKER") + nftTokenName := []byte("tokenName") + + txDataField := bytes.Join( + [][]byte{ + []byte("registerDynamic"), + []byte(hex.EncodeToString(nftTokenName)), + []byte(hex.EncodeToString(nftTicker)), + []byte(hex.EncodeToString([]byte("NFT"))), + }, + []byte("@"), + ) + + callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) + + tx = &transaction.Transaction{ + Nonce: nonce, + SndAddr: addrs[1].Bytes, + RcvAddr: vm.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: vm2.MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: callValue, + ChainID: []byte(configs.ChainID), + Version: 1, + } + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + nftTokenID := txResult.Logs.Events[0].Topics[0] + tx = vm2.SetSpecialRoleTx(nonce, addrs[1].Bytes, addrs[1].Bytes, nftTokenID, roles) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) + + // issue SFT + sftTicker := []byte("SFTTICKER") + tx = vm2.IssueSemiFungibleTx(nonce, addrs[1].Bytes, sftTicker, baseIssuingCost) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + sftTokenID := txResult.Logs.Events[0].Topics[0] + tx = vm2.SetSpecialRoleTx(nonce, addrs[1].Bytes, addrs[1].Bytes, sftTokenID, roles) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) + + tokenIDs := [][]byte{ + nftTokenID, + sftTokenID, + metaESDTTokenID, + } + + nftMetaData := txsFee.GetDefaultMetaData() + nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + sftMetaData := txsFee.GetDefaultMetaData() + sftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + esdtMetaData := txsFee.GetDefaultMetaData() + esdtMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tokensMetadata := []*txsFee.MetaData{ + nftMetaData, + sftMetaData, + esdtMetaData, + } + + for i := range tokenIDs { + tx = vm2.EsdtNftCreateTx(nonce, addrs[1].Bytes, tokenIDs[i], tokensMetadata[i], 1) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + } + + vm2.Log.Info("Change to DYNAMIC type") + + for i := range tokenIDs { + tx = vm2.ChangeToDynamicTx(nonce, addrs[1].Bytes, tokenIDs[i]) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + } + + vm2.Log.Info("Call ESDTModifyCreator and check that the creator was modified") + + mintValue := big.NewInt(10) + mintValue = mintValue.Mul(vm2.OneEGLD, mintValue) + + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[1].Bytes) + + for i := range tokenIDs { + vm2.Log.Info("Modify creator for token", "tokenID", tokenIDs[i]) + + newCreatorAddress, err := cs.GenerateAndMintWalletAddress(shardID, mintValue) + require.Nil(t, err) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + roles = [][]byte{ + []byte(core.ESDTRoleModifyCreator), + } + tx = vm2.SetSpecialRoleTx(nonce, addrs[1].Bytes, newCreatorAddress.Bytes, tokenIDs[i], roles) + nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + tx = vm2.ModifyCreatorTx(0, newCreatorAddress.Bytes, tokenIDs[i]) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + retrievedMetaData := &esdt.MetaData{} + if bytes.Equal(tokenIDs[i], nftTokenID) { + retrievedMetaData = vm2.GetMetaDataFromAcc(t, cs, newCreatorAddress.Bytes, tokenIDs[i], shardID) + } else { + retrievedMetaData = vm2.GetMetaDataFromAcc(t, cs, core.SystemAccountAddress, tokenIDs[i], shardID) + } + + require.Equal(t, newCreatorAddress.Bytes, retrievedMetaData.Creator) + } +} + +func TestChainSimulator_ESDTModifyCreator_CrossShard(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + + cs, _ := vm2.GetTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, false) + + // issue metaESDT + metaESDTTicker := []byte("METATICKER") + nonce := uint64(0) + tx := vm2.IssueMetaESDTTx(nonce, addrs[1].Bytes, metaESDTTicker, baseIssuingCost) + nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + metaESDTTokenID := txResult.Logs.Events[0].Topics[0] + + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleTransfer), + } + tx = vm2.SetSpecialRoleTx(nonce, addrs[1].Bytes, addrs[1].Bytes, metaESDTTokenID, roles) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("Issued metaESDT token id", "tokenID", string(metaESDTTokenID)) + + // register dynamic NFT + nftTicker := []byte("NFTTICKER") + nftTokenName := []byte("tokenName") + + txDataField := bytes.Join( + [][]byte{ + []byte("registerDynamic"), + []byte(hex.EncodeToString(nftTokenName)), + []byte(hex.EncodeToString(nftTicker)), + []byte(hex.EncodeToString([]byte("NFT"))), + }, + []byte("@"), + ) + + callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) + + tx = &transaction.Transaction{ + Nonce: nonce, + SndAddr: addrs[1].Bytes, + RcvAddr: vm.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: vm2.MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: callValue, + ChainID: []byte(configs.ChainID), + Version: 1, + } + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + nftTokenID := txResult.Logs.Events[0].Topics[0] + tx = vm2.SetSpecialRoleTx(nonce, addrs[1].Bytes, addrs[1].Bytes, nftTokenID, roles) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) + + // issue SFT + sftTicker := []byte("SFTTICKER") + tx = vm2.IssueSemiFungibleTx(nonce, addrs[1].Bytes, sftTicker, baseIssuingCost) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + sftTokenID := txResult.Logs.Events[0].Topics[0] + tx = vm2.SetSpecialRoleTx(nonce, addrs[1].Bytes, addrs[1].Bytes, sftTokenID, roles) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) + + tokenIDs := [][]byte{ + nftTokenID, + sftTokenID, + metaESDTTokenID, + } + + nftMetaData := txsFee.GetDefaultMetaData() + nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + sftMetaData := txsFee.GetDefaultMetaData() + sftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + esdtMetaData := txsFee.GetDefaultMetaData() + esdtMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tokensMetadata := []*txsFee.MetaData{ + nftMetaData, + sftMetaData, + esdtMetaData, + } + + for i := range tokenIDs { + tx = vm2.EsdtNftCreateTx(nonce, addrs[1].Bytes, tokenIDs[i], tokensMetadata[i], 1) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + } + + vm2.Log.Info("Change to DYNAMIC type") + + for i := range tokenIDs { + tx = vm2.ChangeToDynamicTx(nonce, addrs[1].Bytes, tokenIDs[i]) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + } + + vm2.Log.Info("Call ESDTModifyCreator and check that the creator was modified") + + mintValue := big.NewInt(10) + mintValue = mintValue.Mul(vm2.OneEGLD, mintValue) + + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[1].Bytes) + + crossShardID := uint32(2) + if shardID == uint32(2) { + crossShardID = uint32(1) + } + + for i := range tokenIDs { + vm2.Log.Info("Modify creator for token", "tokenID", string(tokenIDs[i])) + + newCreatorAddress, err := cs.GenerateAndMintWalletAddress(crossShardID, mintValue) + require.Nil(t, err) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + roles = [][]byte{ + []byte(core.ESDTRoleModifyCreator), + } + tx = vm2.SetSpecialRoleTx(nonce, addrs[1].Bytes, newCreatorAddress.Bytes, tokenIDs[i], roles) + nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("transfering token id", "tokenID", tokenIDs[i]) + + tx = vm2.EsdtNFTTransferTx(nonce, addrs[1].Bytes, newCreatorAddress.Bytes, tokenIDs[i]) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + tx = vm2.ModifyCreatorTx(0, newCreatorAddress.Bytes, tokenIDs[i]) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + retrievedMetaData := &esdt.MetaData{} + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(newCreatorAddress.Bytes) + if bytes.Equal(tokenIDs[i], nftTokenID) { + retrievedMetaData = vm2.GetMetaDataFromAcc(t, cs, newCreatorAddress.Bytes, tokenIDs[i], shardID) + } else { + retrievedMetaData = vm2.GetMetaDataFromAcc(t, cs, core.SystemAccountAddress, tokenIDs[i], shardID) + } + + require.Equal(t, newCreatorAddress.Bytes, retrievedMetaData.Creator) + + nonce++ + } +} + +// Test scenario #7 +// +// Initial setup: Create NFT, SFT, metaESDT tokens +// +// Call ESDTSetNewURIs and check that the new URIs were set for the token +// (The sender must have the ESDTRoleSetNewURI role) +func TestChainSimulator_ESDTSetNewURIs(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + + cs, _ := vm2.GetTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, false) + + // issue metaESDT + metaESDTTicker := []byte("METATICKER") + nonce := uint64(0) + tx := vm2.IssueMetaESDTTx(nonce, addrs[0].Bytes, metaESDTTicker, baseIssuingCost) + nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + metaESDTTokenID := txResult.Logs.Events[0].Topics[0] + + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleTransfer), + } + tx = vm2.SetSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, metaESDTTokenID, roles) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("Issued metaESDT token id", "tokenID", string(metaESDTTokenID)) + + // issue NFT + nftTicker := []byte("NFTTICKER") + tx = vm2.IssueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + nftTokenID := txResult.Logs.Events[0].Topics[0] + tx = vm2.SetSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, nftTokenID, roles) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) + + // issue SFT + sftTicker := []byte("SFTTICKER") + tx = vm2.IssueSemiFungibleTx(nonce, addrs[0].Bytes, sftTicker, baseIssuingCost) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + sftTokenID := txResult.Logs.Events[0].Topics[0] + tx = vm2.SetSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, sftTokenID, roles) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) + + tokenIDs := [][]byte{ + nftTokenID, + sftTokenID, + metaESDTTokenID, + } + + nftMetaData := txsFee.GetDefaultMetaData() + nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + sftMetaData := txsFee.GetDefaultMetaData() + sftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + esdtMetaData := txsFee.GetDefaultMetaData() + esdtMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tokensMetadata := []*txsFee.MetaData{ + nftMetaData, + sftMetaData, + esdtMetaData, + } + + for i := range tokenIDs { + tx = vm2.EsdtNftCreateTx(nonce, addrs[0].Bytes, tokenIDs[i], tokensMetadata[i], 1) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + + tx = vm2.ChangeToDynamicTx(nonce, addrs[0].Bytes, tokenIDs[i]) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + + roles := [][]byte{ + []byte(core.ESDTRoleSetNewURI), + } + tx = vm2.SetSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, tokenIDs[i], roles) + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + } + + vm2.Log.Info("Call ESDTSetNewURIs and check that the new URIs were set for the tokens") + + metaDataNonce := []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + uris := [][]byte{ + []byte(hex.EncodeToString([]byte("uri0"))), + []byte(hex.EncodeToString([]byte("uri1"))), + []byte(hex.EncodeToString([]byte("uri2"))), + } + + expUris := [][]byte{ + []byte("uri0"), + []byte("uri1"), + []byte("uri2"), + } + + for i := range tokenIDs { + vm2.Log.Info("Set new uris for token", "tokenID", string(tokenIDs[i])) + + txDataField := bytes.Join( + [][]byte{ + []byte(core.ESDTSetNewURIs), + []byte(hex.EncodeToString(tokenIDs[i])), + metaDataNonce, + uris[0], + uris[1], + uris[2], + }, + []byte("@"), + ) + + tx = &transaction.Transaction{ + Nonce: nonce, + SndAddr: addrs[0].Bytes, + RcvAddr: addrs[0].Bytes, + GasLimit: 10_000_000, + GasPrice: vm2.MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: big.NewInt(0), + ChainID: []byte(configs.ChainID), + Version: 1, + } + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) + var retrievedMetaData *esdt.MetaData + if bytes.Equal(tokenIDs[i], tokenIDs[0]) { // nft token + retrievedMetaData = vm2.GetMetaDataFromAcc(t, cs, addrs[0].Bytes, tokenIDs[i], shardID) + } else { + retrievedMetaData = vm2.GetMetaDataFromAcc(t, cs, core.SystemAccountAddress, tokenIDs[i], shardID) + } + + require.Equal(t, expUris, retrievedMetaData.URIs) + + nonce++ + } +} + +// Test scenario #8 +// +// Initial setup: Create NFT, SFT, metaESDT tokens +// +// Call ESDTModifyRoyalties and check that the royalties were changed +// (The sender must have the ESDTRoleModifyRoyalties role) +func TestChainSimulator_ESDTModifyRoyalties(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + + cs, _ := vm2.GetTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, false) + + // issue metaESDT + metaESDTTicker := []byte("METATICKER") + nonce := uint64(0) + tx := vm2.IssueMetaESDTTx(nonce, addrs[0].Bytes, metaESDTTicker, baseIssuingCost) + nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + metaESDTTokenID := txResult.Logs.Events[0].Topics[0] + + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleTransfer), + } + tx = vm2.SetSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, metaESDTTokenID, roles) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("Issued metaESDT token id", "tokenID", string(metaESDTTokenID)) + + // issue NFT + nftTicker := []byte("NFTTICKER") + tx = vm2.IssueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + nftTokenID := txResult.Logs.Events[0].Topics[0] + tx = vm2.SetSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, nftTokenID, roles) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) + + // issue SFT + sftTicker := []byte("SFTTICKER") + tx = vm2.IssueSemiFungibleTx(nonce, addrs[0].Bytes, sftTicker, baseIssuingCost) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + sftTokenID := txResult.Logs.Events[0].Topics[0] + tx = vm2.SetSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, sftTokenID, roles) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) + + tokenIDs := [][]byte{ + nftTokenID, + sftTokenID, + metaESDTTokenID, + } + + nftMetaData := txsFee.GetDefaultMetaData() + nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + sftMetaData := txsFee.GetDefaultMetaData() + sftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + esdtMetaData := txsFee.GetDefaultMetaData() + esdtMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tokensMetadata := []*txsFee.MetaData{ + nftMetaData, + sftMetaData, + esdtMetaData, + } + + for i := range tokenIDs { + tx = vm2.EsdtNftCreateTx(nonce, addrs[0].Bytes, tokenIDs[i], tokensMetadata[i], 1) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + + tx = vm2.ChangeToDynamicTx(nonce, addrs[0].Bytes, tokenIDs[i]) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + + roles := [][]byte{ + []byte(core.ESDTRoleModifyRoyalties), + } + tx = vm2.SetSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, tokenIDs[i], roles) + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + } + + vm2.Log.Info("Call ESDTModifyRoyalties and check that the royalties were changed") + + metaDataNonce := []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + royalties := []byte(hex.EncodeToString(big.NewInt(20).Bytes())) + + for i := range tokenIDs { + vm2.Log.Info("Set new royalties for token", "tokenID", string(tokenIDs[i])) + + txDataField := bytes.Join( + [][]byte{ + []byte(core.ESDTModifyRoyalties), + []byte(hex.EncodeToString(tokenIDs[i])), + metaDataNonce, + royalties, + }, + []byte("@"), + ) + + tx = &transaction.Transaction{ + Nonce: nonce, + SndAddr: addrs[0].Bytes, + RcvAddr: addrs[0].Bytes, + GasLimit: 10_000_000, + GasPrice: vm2.MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: big.NewInt(0), + ChainID: []byte(configs.ChainID), + Version: 1, + } + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + shardID := cs.GetNodeHandler(0).GetShardCoordinator().ComputeId(addrs[0].Bytes) + retrievedMetaData := vm2.GetMetaDataFromAcc(t, cs, addrs[0].Bytes, nftTokenID, shardID) + + require.Equal(t, uint32(big.NewInt(20).Uint64()), retrievedMetaData.Royalties) + + nonce++ + } +} + +// Test scenario #9 +// +// Initial setup: Create NFT +// +// 1. Change the nft to DYNAMIC type - the metadata should be on the system account +// 2. Send the NFT cross shard +// 3. The meta data should still be present on the system account +func TestChainSimulator_NFT_ChangeToDynamicType(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + activationEpoch := uint32(4) + + baseIssuingCost := "1000" + + numOfShards := uint32(3) + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: vm2.DefaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: vm2.RoundDurationInMillis, + SupernovaRoundDurationInMillis: vm2.SupernovaRoundDurationInMillis, + RoundsPerEpoch: vm2.RoundsPerEpoch, + SupernovaRoundsPerEpoch: vm2.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 0, + NumNodesWaitingListShard: 0, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.DynamicESDTEnableEpoch = activationEpoch + cfg.SystemSCConfig.ESDTSystemSCConfig.BaseIssuingCost = baseIssuingCost + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, true) + + err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch) - 2) + require.Nil(t, err) + + vm2.Log.Info("Initial setup: Create NFT") + + nftTicker := []byte("NFTTICKER") + nonce := uint64(0) + tx := vm2.IssueNonFungibleTx(nonce, addrs[1].Bytes, nftTicker, baseIssuingCost) + nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + } + + nftTokenID := txResult.Logs.Events[0].Topics[0] + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[1], nftTokenID, roles) + nonce++ + + vm2.Log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) + + nftMetaData := txsFee.GetDefaultMetaData() + nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tx = vm2.EsdtNftCreateTx(nonce, addrs[1].Bytes, nftTokenID, nftMetaData, 1) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch)) + require.Nil(t, err) + + vm2.Log.Info("Step 1. Change the nft to DYNAMIC type - the metadata should be on the system account") + + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[1].Bytes) + + tx = vm2.ChangeToDynamicTx(nonce, addrs[1].Bytes, nftTokenID) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + roles = [][]byte{ + []byte(core.ESDTRoleNFTUpdate), + } + + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[1], nftTokenID, roles) + nonce++ + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, nftTokenID, shardID, nftMetaData) + + vm2.Log.Info("Step 2. Send the NFT cross shard") + + tx = vm2.EsdtNFTTransferTx(nonce, addrs[1].Bytes, addrs[2].Bytes, nftTokenID) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("Step 3. The meta data should still be present on the system account") + + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, nftTokenID, shardID, nftMetaData) +} + +// Test scenario #10 +// +// Initial setup: Create SFT and send in another shard +// +// 1. change the sft meta data (differently from the previous one) in the other shard +// 2. check that the newest metadata is saved +func TestChainSimulator_ChangeMetaData(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + t.Run("sft change metadata", func(t *testing.T) { + vm2.TestChainSimulatorChangeMetaData(t, vm2.IssueSemiFungibleTx) + }) + + t.Run("metaESDT change metadata", func(t *testing.T) { + vm2.TestChainSimulatorChangeMetaData(t, vm2.IssueMetaESDTTx) + }) +} + +func TestChainSimulator_NFT_RegisterDynamic(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + + cs, _ := vm2.GetTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, true) + + vm2.Log.Info("Register dynamic nft token") + + nftTicker := []byte("NFTTICKER") + nftTokenName := []byte("tokenName") + + txDataField := bytes.Join( + [][]byte{ + []byte("registerDynamic"), + []byte(hex.EncodeToString(nftTokenName)), + []byte(hex.EncodeToString(nftTicker)), + []byte(hex.EncodeToString([]byte("NFT"))), + }, + []byte("@"), + ) + + callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) + + nonce := uint64(0) + tx := &transaction.Transaction{ + Nonce: nonce, + SndAddr: addrs[0].Bytes, + RcvAddr: vm.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: vm2.MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: callValue, + ChainID: []byte(configs.ChainID), + Version: 1, + } + nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + nftTokenID := txResult.Logs.Events[0].Topics[0] + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleTransfer), + } + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) + nonce++ + + nftMetaData := txsFee.GetDefaultMetaData() + nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tx = vm2.EsdtNftCreateTx(nonce, addrs[0].Bytes, nftTokenID, nftMetaData, 1) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) + + vm2.CheckMetaData(t, cs, addrs[0].Bytes, nftTokenID, shardID, nftMetaData) + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) + + vm2.Log.Info("Check that token type is Dynamic") + + scQuery := &process.SCQuery{ + ScAddress: vm.ESDTSCAddress, + FuncName: "getTokenProperties", + CallValue: big.NewInt(0), + Arguments: [][]byte{nftTokenID}, + } + result, _, err := cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ExecuteSCQuery(scQuery) + require.Nil(t, err) + require.Equal(t, "", result.ReturnMessage) + require.Equal(t, testsChainSimulator.OkReturnCode, result.ReturnCode) + + tokenType := result.ReturnData[1] + require.Equal(t, core.DynamicNFTESDT, string(tokenType)) +} + +func TestChainSimulator_MetaESDT_RegisterDynamic(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + + cs, _ := vm2.GetTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, true) + + vm2.Log.Info("Register dynamic metaESDT token") + + metaTicker := []byte("METATICKER") + metaTokenName := []byte("tokenName") + + decimals := big.NewInt(15) + + txDataField := bytes.Join( + [][]byte{ + []byte("registerDynamic"), + []byte(hex.EncodeToString(metaTokenName)), + []byte(hex.EncodeToString(metaTicker)), + []byte(hex.EncodeToString([]byte("META"))), + []byte(hex.EncodeToString(decimals.Bytes())), + }, + []byte("@"), + ) + + callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) + + nonce := uint64(0) + tx := &transaction.Transaction{ + Nonce: nonce, + SndAddr: addrs[0].Bytes, + RcvAddr: vm.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: vm2.MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: callValue, + ChainID: []byte(configs.ChainID), + Version: 1, + } + nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + nftTokenID := txResult.Logs.Events[0].Topics[0] + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleTransfer), + } + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) + nonce++ + + nftMetaData := txsFee.GetDefaultMetaData() + nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tx = vm2.EsdtNftCreateTx(nonce, addrs[0].Bytes, nftTokenID, nftMetaData, 1) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) + + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, nftTokenID, shardID, nftMetaData) + + vm2.Log.Info("Check that token type is Dynamic") + + scQuery := &process.SCQuery{ + ScAddress: vm.ESDTSCAddress, + FuncName: "getTokenProperties", + CallValue: big.NewInt(0), + Arguments: [][]byte{nftTokenID}, + } + result, _, err := cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ExecuteSCQuery(scQuery) + require.Nil(t, err) + require.Equal(t, "", result.ReturnMessage) + require.Equal(t, testsChainSimulator.OkReturnCode, result.ReturnCode) + + tokenType := result.ReturnData[1] + require.Equal(t, core.Dynamic+core.MetaESDT, string(tokenType)) +} + +func TestChainSimulator_FNG_RegisterDynamic(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + + cs, _ := vm2.GetTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, true) + + vm2.Log.Info("Register dynamic fungible token") + + metaTicker := []byte("FNGTICKER") + metaTokenName := []byte("tokenName") + + decimals := big.NewInt(15) + + txDataField := bytes.Join( + [][]byte{ + []byte("registerDynamic"), + []byte(hex.EncodeToString(metaTokenName)), + []byte(hex.EncodeToString(metaTicker)), + []byte(hex.EncodeToString([]byte("FNG"))), + []byte(hex.EncodeToString(decimals.Bytes())), + }, + []byte("@"), + ) + + callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) + + tx := &transaction.Transaction{ + Nonce: 0, + SndAddr: addrs[0].Bytes, + RcvAddr: vm.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: vm2.MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: callValue, + ChainID: []byte(configs.ChainID), + Version: 1, + } + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + signalErrorTopic := string(txResult.Logs.Events[0].Topics[1]) + + require.Equal(t, fmt.Sprintf("cannot create %s tokens as dynamic", core.FungibleESDT), signalErrorTopic) +} + +func TestChainSimulator_NFT_RegisterAndSetAllRolesDynamic(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + + cs, _ := vm2.GetTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, true) + + vm2.Log.Info("Register dynamic nft token") + + nftTicker := []byte("NFTTICKER") + nftTokenName := []byte("tokenName") + + txDataField := bytes.Join( + [][]byte{ + []byte("registerAndSetAllRolesDynamic"), + []byte(hex.EncodeToString(nftTokenName)), + []byte(hex.EncodeToString(nftTicker)), + []byte(hex.EncodeToString([]byte("NFT"))), + }, + []byte("@"), + ) + + callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) + + nonce := uint64(0) + tx := &transaction.Transaction{ + Nonce: nonce, + SndAddr: addrs[0].Bytes, + RcvAddr: vm.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: vm2.MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: callValue, + ChainID: []byte(configs.ChainID), + Version: 1, + } + nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + nftTokenID := txResult.Logs.Events[0].Topics[0] + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleTransfer), + } + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) + nonce++ + + nftMetaData := txsFee.GetDefaultMetaData() + nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tx = vm2.EsdtNftCreateTx(nonce, addrs[0].Bytes, nftTokenID, nftMetaData, 1) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) + + vm2.CheckMetaData(t, cs, addrs[0].Bytes, nftTokenID, shardID, nftMetaData) + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) + + vm2.Log.Info("Check that token type is Dynamic") + + scQuery := &process.SCQuery{ + ScAddress: vm.ESDTSCAddress, + FuncName: "getTokenProperties", + CallValue: big.NewInt(0), + Arguments: [][]byte{nftTokenID}, + } + result, _, err := cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ExecuteSCQuery(scQuery) + require.Nil(t, err) + require.Equal(t, "", result.ReturnMessage) + require.Equal(t, testsChainSimulator.OkReturnCode, result.ReturnCode) + + tokenType := result.ReturnData[1] + require.Equal(t, core.DynamicNFTESDT, string(tokenType)) + + vm2.Log.Info("Check token roles") + + scQuery = &process.SCQuery{ + ScAddress: vm.ESDTSCAddress, + FuncName: "getAllAddressesAndRoles", + CallValue: big.NewInt(0), + Arguments: [][]byte{nftTokenID}, + } + result, _, err = cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ExecuteSCQuery(scQuery) + require.Nil(t, err) + require.Equal(t, "", result.ReturnMessage) + require.Equal(t, testsChainSimulator.OkReturnCode, result.ReturnCode) + + expectedRoles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleNFTBurn), + []byte(core.ESDTRoleNFTUpdateAttributes), + []byte(core.ESDTRoleNFTAddURI), + []byte(core.ESDTRoleNFTRecreate), + []byte(core.ESDTRoleModifyCreator), + []byte(core.ESDTRoleModifyRoyalties), + []byte(core.ESDTRoleSetNewURI), + []byte(core.ESDTRoleNFTUpdate), + } + + vm2.CheckTokenRoles(t, result.ReturnData, expectedRoles) +} + +func TestChainSimulator_SFT_RegisterAndSetAllRolesDynamic(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + + cs, _ := vm2.GetTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, true) + + vm2.Log.Info("Register dynamic sft token") + + sftTicker := []byte("SFTTICKER") + sftTokenName := []byte("tokenName") + + txDataField := bytes.Join( + [][]byte{ + []byte("registerAndSetAllRolesDynamic"), + []byte(hex.EncodeToString(sftTokenName)), + []byte(hex.EncodeToString(sftTicker)), + []byte(hex.EncodeToString([]byte("SFT"))), + }, + []byte("@"), + ) + + callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) + + nonce := uint64(0) + tx := &transaction.Transaction{ + Nonce: nonce, + SndAddr: addrs[0].Bytes, + RcvAddr: vm.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: vm2.MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: callValue, + ChainID: []byte(configs.ChainID), + Version: 1, + } + nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + sftTokenID := txResult.Logs.Events[0].Topics[0] + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleTransfer), + } + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[0], sftTokenID, roles) + nonce++ + + nftMetaData := txsFee.GetDefaultMetaData() + nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tx = vm2.EsdtNftCreateTx(nonce, addrs[0].Bytes, sftTokenID, nftMetaData, 1) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) + + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, sftTokenID, shardID, nftMetaData) + + vm2.Log.Info("Check that token type is Dynamic") + + scQuery := &process.SCQuery{ + ScAddress: vm.ESDTSCAddress, + FuncName: "getTokenProperties", + CallValue: big.NewInt(0), + Arguments: [][]byte{sftTokenID}, + } + result, _, err := cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ExecuteSCQuery(scQuery) + require.Nil(t, err) + require.Equal(t, "", result.ReturnMessage) + require.Equal(t, testsChainSimulator.OkReturnCode, result.ReturnCode) + + tokenType := result.ReturnData[1] + require.Equal(t, core.DynamicSFTESDT, string(tokenType)) + + vm2.Log.Info("Check token roles") + + scQuery = &process.SCQuery{ + ScAddress: vm.ESDTSCAddress, + FuncName: "getAllAddressesAndRoles", + CallValue: big.NewInt(0), + Arguments: [][]byte{sftTokenID}, + } + result, _, err = cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ExecuteSCQuery(scQuery) + require.Nil(t, err) + require.Equal(t, "", result.ReturnMessage) + require.Equal(t, testsChainSimulator.OkReturnCode, result.ReturnCode) + + expectedRoles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleNFTBurn), + []byte(core.ESDTRoleNFTUpdateAttributes), + []byte(core.ESDTRoleNFTAddURI), + []byte(core.ESDTRoleNFTRecreate), + []byte(core.ESDTRoleModifyCreator), + []byte(core.ESDTRoleModifyRoyalties), + []byte(core.ESDTRoleSetNewURI), + []byte(core.ESDTRoleNFTUpdate), + } + + vm2.CheckTokenRoles(t, result.ReturnData, expectedRoles) +} + +func TestChainSimulator_FNG_RegisterAndSetAllRolesDynamic(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + + cs, _ := vm2.GetTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, true) + + vm2.Log.Info("Register dynamic fungible token") + + fngTicker := []byte("FNGTICKER") + fngTokenName := []byte("tokenName") + + txDataField := bytes.Join( + [][]byte{ + []byte("registerAndSetAllRolesDynamic"), + []byte(hex.EncodeToString(fngTokenName)), + []byte(hex.EncodeToString(fngTicker)), + []byte(hex.EncodeToString([]byte("FNG"))), + []byte(hex.EncodeToString(big.NewInt(10).Bytes())), + }, + []byte("@"), + ) + + callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) + + tx := &transaction.Transaction{ + Nonce: 0, + SndAddr: addrs[0].Bytes, + RcvAddr: vm.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: vm2.MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: callValue, + ChainID: []byte(configs.ChainID), + Version: 1, + } + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + signalErrorTopic := string(txResult.Logs.Events[0].Topics[1]) + + require.Equal(t, fmt.Sprintf("cannot create %s tokens as dynamic", core.FungibleESDT), signalErrorTopic) +} diff --git a/integrationTests/chainSimulator/vm/esdtImprovementsSet2/esdtImprovements_test.go b/integrationTests/chainSimulator/vm/esdtImprovementsSet2/esdtImprovements_test.go new file mode 100644 index 00000000000..593ddfaba95 --- /dev/null +++ b/integrationTests/chainSimulator/vm/esdtImprovementsSet2/esdtImprovements_test.go @@ -0,0 +1,1591 @@ +package esdtImprovementsSet2 + +import ( + "bytes" + "encoding/hex" + "math/big" + "strings" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data/esdt" + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/config" + testsChainSimulator "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator" + vm2 "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator/vm" + "github.com/multiversx/mx-chain-go/integrationTests/vm/txsFee" + "github.com/multiversx/mx-chain-go/node/chainSimulator" + "github.com/multiversx/mx-chain-go/node/chainSimulator/components/api" + "github.com/multiversx/mx-chain-go/node/chainSimulator/configs" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/vm" +) + +func TestChainSimulator_MetaESDT_RegisterAndSetAllRolesDynamic(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + + cs, _ := vm2.GetTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, true) + + vm2.Log.Info("Register dynamic meta esdt token") + + ticker := []byte("META" + "TICKER") + tokenName := []byte("tokenName") + + decimals := big.NewInt(10) + + txDataField := bytes.Join( + [][]byte{ + []byte("registerAndSetAllRolesDynamic"), + []byte(hex.EncodeToString(tokenName)), + []byte(hex.EncodeToString(ticker)), + []byte(hex.EncodeToString([]byte("META"))), + []byte(hex.EncodeToString(decimals.Bytes())), + }, + []byte("@"), + ) + + callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) + + nonce := uint64(0) + tx := &transaction.Transaction{ + Nonce: nonce, + SndAddr: addrs[0].Bytes, + RcvAddr: vm.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: vm2.MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: callValue, + ChainID: []byte(configs.ChainID), + Version: 1, + } + nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + metaTokenID := txResult.Logs.Events[0].Topics[0] + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleTransfer), + } + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[0], metaTokenID, roles) + nonce++ + + nftMetaData := txsFee.GetDefaultMetaData() + nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tx = vm2.EsdtNftCreateTx(nonce, addrs[0].Bytes, metaTokenID, nftMetaData, 1) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) + + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, metaTokenID, shardID, nftMetaData) + + vm2.Log.Info("Check that token type is Dynamic") + + scQuery := &process.SCQuery{ + ScAddress: vm.ESDTSCAddress, + FuncName: "getTokenProperties", + CallValue: big.NewInt(0), + Arguments: [][]byte{metaTokenID}, + } + result, _, err := cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ExecuteSCQuery(scQuery) + require.Nil(t, err) + require.Equal(t, "", result.ReturnMessage) + require.Equal(t, testsChainSimulator.OkReturnCode, result.ReturnCode) + + tokenType := result.ReturnData[1] + require.Equal(t, core.Dynamic+core.MetaESDT, string(tokenType)) + + vm2.Log.Info("Check token roles") + + scQuery = &process.SCQuery{ + ScAddress: vm.ESDTSCAddress, + FuncName: "getAllAddressesAndRoles", + CallValue: big.NewInt(0), + Arguments: [][]byte{metaTokenID}, + } + result, _, err = cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ExecuteSCQuery(scQuery) + require.Nil(t, err) + require.Equal(t, "", result.ReturnMessage) + require.Equal(t, testsChainSimulator.OkReturnCode, result.ReturnCode) + + expectedRoles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleNFTBurn), + []byte(core.ESDTRoleNFTAddQuantity), + []byte(core.ESDTRoleNFTUpdateAttributes), + []byte(core.ESDTRoleNFTAddURI), + } + + vm2.CheckTokenRoles(t, result.ReturnData, expectedRoles) +} + +func TestChainSimulator_NFTcreatedBeforeSaveToSystemAccountEnabled(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + cs, epochForDynamicNFT := vm2.GetTestChainSimulatorWithSaveToSystemAccountDisabled(t, baseIssuingCost) + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, false) + + vm2.Log.Info("Initial setup: Create NFT that will have it's metadata saved to the user account") + + nftTicker := []byte("NFTTICKER") + tx := vm2.IssueNonFungibleTx(0, addrs[0].Bytes, nftTicker, baseIssuingCost) + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + nftTokenID := txResult.Logs.Events[0].Topics[0] + + vm2.Log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) + + nftMetaData := txsFee.GetDefaultMetaData() + nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + vm2.CreateTokenUpdateTokenIDAndTransfer(t, cs, addrs[0].Bytes, addrs[1].Bytes, nftTokenID, nftMetaData, epochForDynamicNFT, addrs[0]) + + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) + vm2.CheckMetaData(t, cs, addrs[1].Bytes, nftTokenID, shardID, nftMetaData) + vm2.CheckMetaDataNotInAcc(t, cs, addrs[0].Bytes, nftTokenID, shardID) + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) +} + +func TestChainSimulator_SFTcreatedBeforeSaveToSystemAccountEnabled(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + cs, epochForDynamicNFT := vm2.GetTestChainSimulatorWithSaveToSystemAccountDisabled(t, baseIssuingCost) + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, false) + + vm2.Log.Info("Initial setup: Create SFT that will have it's metadata saved to the user account") + + sftTicker := []byte("SFTTICKER") + tx := vm2.IssueSemiFungibleTx(0, addrs[0].Bytes, sftTicker, baseIssuingCost) + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + sftTokenID := txResult.Logs.Events[0].Topics[0] + + vm2.Log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) + + metaData := txsFee.GetDefaultMetaData() + metaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + vm2.CreateTokenUpdateTokenIDAndTransfer(t, cs, addrs[0].Bytes, addrs[1].Bytes, sftTokenID, metaData, epochForDynamicNFT, addrs[0]) + + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) + + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, sftTokenID, shardID, metaData) + vm2.CheckMetaDataNotInAcc(t, cs, addrs[0].Bytes, sftTokenID, shardID) + vm2.CheckMetaDataNotInAcc(t, cs, addrs[1].Bytes, sftTokenID, shardID) +} + +func TestChainSimulator_MetaESDTCreatedBeforeSaveToSystemAccountEnabled(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + cs, epochForDynamicNFT := vm2.GetTestChainSimulatorWithSaveToSystemAccountDisabled(t, baseIssuingCost) + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, false) + + vm2.Log.Info("Initial setup: Create MetaESDT that will have it's metadata saved to the user account") + + metaTicker := []byte("METATICKER") + tx := vm2.IssueMetaESDTTx(0, addrs[0].Bytes, metaTicker, baseIssuingCost) + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + metaTokenID := txResult.Logs.Events[0].Topics[0] + + vm2.Log.Info("Issued MetaESDT token id", "tokenID", string(metaTokenID)) + + metaData := txsFee.GetDefaultMetaData() + metaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + vm2.CreateTokenUpdateTokenIDAndTransfer(t, cs, addrs[0].Bytes, addrs[1].Bytes, metaTokenID, metaData, epochForDynamicNFT, addrs[0]) + + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, metaTokenID, shardID, metaData) + vm2.CheckMetaDataNotInAcc(t, cs, addrs[0].Bytes, metaTokenID, shardID) + vm2.CheckMetaDataNotInAcc(t, cs, addrs[1].Bytes, metaTokenID, shardID) +} + +func TestChainSimulator_ChangeToDynamic_OldTokens(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + + cs, epochForDynamicNFT := vm2.GetTestChainSimulatorWithSaveToSystemAccountDisabled(t, baseIssuingCost) + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, false) + + // issue metaESDT + metaESDTTicker := []byte("METATICKER") + nonce := uint64(0) + tx := vm2.IssueMetaESDTTx(nonce, addrs[0].Bytes, metaESDTTicker, baseIssuingCost) + nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + metaESDTTokenID := txResult.Logs.Events[0].Topics[0] + + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleTransfer), + } + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[0], metaESDTTokenID, roles) + nonce++ + + vm2.Log.Info("Issued metaESDT token id", "tokenID", string(metaESDTTokenID)) + + // issue NFT + nftTicker := []byte("NFTTICKER") + tx = vm2.IssueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + nftTokenID := txResult.Logs.Events[0].Topics[0] + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) + nonce++ + + vm2.Log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) + + // issue SFT + sftTicker := []byte("SFTTICKER") + tx = vm2.IssueSemiFungibleTx(nonce, addrs[0].Bytes, sftTicker, baseIssuingCost) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + sftTokenID := txResult.Logs.Events[0].Topics[0] + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[0], sftTokenID, roles) + nonce++ + + vm2.Log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) + + tokenIDs := [][]byte{ + nftTokenID, + sftTokenID, + metaESDTTokenID, + } + + nftMetaData := txsFee.GetDefaultMetaData() + nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + sftMetaData := txsFee.GetDefaultMetaData() + sftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + esdtMetaData := txsFee.GetDefaultMetaData() + esdtMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tokensMetadata := []*txsFee.MetaData{ + nftMetaData, + sftMetaData, + esdtMetaData, + } + + for i := range tokenIDs { + tx = vm2.EsdtNftCreateTx(nonce, addrs[0].Bytes, tokenIDs[i], tokensMetadata[i], 1) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + } + + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) + + // meta data should be saved on account, since it is before `OptimizeNFTStoreEnableEpoch` + vm2.CheckMetaData(t, cs, addrs[0].Bytes, nftTokenID, shardID, nftMetaData) + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) + + vm2.CheckMetaData(t, cs, addrs[0].Bytes, sftTokenID, shardID, sftMetaData) + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, sftTokenID, shardID) + + vm2.CheckMetaData(t, cs, addrs[0].Bytes, metaESDTTokenID, shardID, esdtMetaData) + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, metaESDTTokenID, shardID) + + err = cs.GenerateBlocksUntilEpochIsReached(epochForDynamicNFT) + require.Nil(t, err) + + vm2.Log.Info("Change to DYNAMIC type") + + // it will not be able to change nft to dynamic type + for i := range tokenIDs { + tx = vm2.ChangeToDynamicTx(nonce, addrs[0].Bytes, tokenIDs[i]) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + } + + for _, tokenID := range tokenIDs { + tx = vm2.UpdateTokenIDTx(nonce, addrs[0].Bytes, tokenID) + + vm2.Log.Info("updating token id", "tokenID", tokenID) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + } + + for _, tokenID := range tokenIDs { + vm2.Log.Info("transfering token id", "tokenID", tokenID) + + tx = vm2.EsdtNFTTransferTx(nonce, addrs[0].Bytes, addrs[1].Bytes, tokenID) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + nonce++ + } + + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, sftTokenID, shardID, sftMetaData) + vm2.CheckMetaDataNotInAcc(t, cs, addrs[0].Bytes, sftTokenID, shardID) + vm2.CheckMetaDataNotInAcc(t, cs, addrs[1].Bytes, sftTokenID, shardID) + + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, metaESDTTokenID, shardID, esdtMetaData) + vm2.CheckMetaDataNotInAcc(t, cs, addrs[0].Bytes, metaESDTTokenID, shardID) + vm2.CheckMetaDataNotInAcc(t, cs, addrs[1].Bytes, metaESDTTokenID, shardID) + + vm2.CheckMetaData(t, cs, addrs[1].Bytes, nftTokenID, shardID, nftMetaData) + vm2.CheckMetaDataNotInAcc(t, cs, addrs[0].Bytes, nftTokenID, shardID) + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) +} + +func TestChainSimulator_CreateAndPause_NFT(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + activationEpoch := uint32(4) + + baseIssuingCost := "1000" + + numOfShards := uint32(3) + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: vm2.DefaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: vm2.RoundDurationInMillis, + SupernovaRoundDurationInMillis: vm2.SupernovaRoundDurationInMillis, + RoundsPerEpoch: vm2.RoundsPerEpoch, + SupernovaRoundsPerEpoch: vm2.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 0, + NumNodesWaitingListShard: 0, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.DynamicESDTEnableEpoch = activationEpoch + cfg.SystemSCConfig.ESDTSystemSCConfig.BaseIssuingCost = baseIssuingCost + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, false) + + err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch) - 1) + require.Nil(t, err) + + // issue NFT + nftTicker := []byte("NFTTICKER") + callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) + + txDataField := bytes.Join( + [][]byte{ + []byte("issueNonFungible"), + []byte(hex.EncodeToString([]byte("asdname"))), + []byte(hex.EncodeToString(nftTicker)), + []byte(hex.EncodeToString([]byte("canPause"))), + []byte(hex.EncodeToString([]byte("true"))), + }, + []byte("@"), + ) + + nonce := uint64(0) + tx := &transaction.Transaction{ + Nonce: nonce, + SndAddr: addrs[0].Bytes, + RcvAddr: core.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: vm2.MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: callValue, + ChainID: []byte(configs.ChainID), + Version: 1, + } + nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleTransfer), + } + nftTokenID := txResult.Logs.Events[0].Topics[0] + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) + nonce++ + + vm2.Log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) + + nftMetaData := txsFee.GetDefaultMetaData() + nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tx = vm2.EsdtNftCreateTx(nonce, addrs[0].Bytes, nftTokenID, nftMetaData, 1) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.Log.Info("check that the metadata for all tokens is saved on the system account") + + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) + + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, nftTokenID, shardID, nftMetaData) + + vm2.Log.Info("Pause all tokens") + + scQuery := &process.SCQuery{ + ScAddress: vm.ESDTSCAddress, + CallerAddr: addrs[0].Bytes, + FuncName: "pause", + CallValue: big.NewInt(0), + Arguments: [][]byte{nftTokenID}, + } + result, _, err := cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ExecuteSCQuery(scQuery) + require.Nil(t, err) + require.Equal(t, "", result.ReturnMessage) + require.Equal(t, testsChainSimulator.OkReturnCode, result.ReturnCode) + + vm2.Log.Info("wait for DynamicEsdtFlag activation") + + err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch)) + require.Nil(t, err) + + vm2.Log.Info("make an updateTokenID@tokenID function call on the ESDTSystem SC for all token types") + + tx = vm2.UpdateTokenIDTx(nonce, addrs[0].Bytes, nftTokenID) + nonce++ + + vm2.Log.Info("updating token id", "tokenID", nftTokenID) + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("check that the metadata for all tokens is saved on the system account") + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, nftTokenID, shardID, nftMetaData) + + vm2.Log.Info("transfer the tokens to another account") + + vm2.Log.Info("transfering token id", "tokenID", nftTokenID) + + tx = vm2.EsdtNFTTransferTx(nonce, addrs[0].Bytes, addrs[1].Bytes, nftTokenID) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("check that the metaData for the NFT is still on the system account") + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + shardID = cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[2].Bytes) + + vm2.CheckMetaData(t, cs, addrs[1].Bytes, nftTokenID, shardID, nftMetaData) + vm2.CheckMetaDataNotInAcc(t, cs, addrs[0].Bytes, nftTokenID, shardID) + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) +} + +func TestChainSimulator_CreateAndPauseTokens_DynamicNFT(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + activationEpoch := uint32(4) + + baseIssuingCost := "1000" + + numOfShards := uint32(3) + cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: vm2.DefaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: vm2.RoundDurationInMillis, + SupernovaRoundDurationInMillis: vm2.SupernovaRoundDurationInMillis, + RoundsPerEpoch: vm2.RoundsPerEpoch, + SupernovaRoundsPerEpoch: vm2.SupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 0, + NumNodesWaitingListShard: 0, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.DynamicESDTEnableEpoch = activationEpoch + cfg.SystemSCConfig.ESDTSystemSCConfig.BaseIssuingCost = baseIssuingCost + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, false) + + err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch) - 1) + require.Nil(t, err) + + vm2.Log.Info("Step 2. wait for DynamicEsdtFlag activation") + + err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch)) + require.Nil(t, err) + + // register dynamic NFT + nftTicker := []byte("NFTTICKER") + nftTokenName := []byte("tokenName") + + txDataField := bytes.Join( + [][]byte{ + []byte("registerDynamic"), + []byte(hex.EncodeToString(nftTokenName)), + []byte(hex.EncodeToString(nftTicker)), + []byte(hex.EncodeToString([]byte("NFT"))), + []byte(hex.EncodeToString([]byte("canPause"))), + []byte(hex.EncodeToString([]byte("true"))), + }, + []byte("@"), + ) + + callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) + + nonce := uint64(0) + tx := &transaction.Transaction{ + Nonce: nonce, + SndAddr: addrs[0].Bytes, + RcvAddr: vm.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: vm2.MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: callValue, + ChainID: []byte(configs.ChainID), + Version: 1, + } + nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleTransfer), + []byte(core.ESDTRoleNFTUpdate), + } + + nftTokenID := txResult.Logs.Events[0].Topics[0] + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) + nonce++ + + vm2.Log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) + + nftMetaData := txsFee.GetDefaultMetaData() + nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tx = vm2.EsdtNftCreateTx(nonce, addrs[0].Bytes, nftTokenID, nftMetaData, 1) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.Log.Info("Step 1. check that the metadata for the Dynamic NFT is saved on the user account") + + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) + + vm2.CheckMetaData(t, cs, addrs[0].Bytes, nftTokenID, shardID, nftMetaData) + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) + + vm2.Log.Info("Step 1b. Pause all tokens") + + scQuery := &process.SCQuery{ + ScAddress: vm.ESDTSCAddress, + CallerAddr: addrs[0].Bytes, + FuncName: "pause", + CallValue: big.NewInt(0), + Arguments: [][]byte{nftTokenID}, + } + result, _, err := cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ExecuteSCQuery(scQuery) + require.Nil(t, err) + require.Equal(t, "", result.ReturnMessage) + require.Equal(t, testsChainSimulator.OkReturnCode, result.ReturnCode) + + vm2.Log.Info("check that the metadata for the Dynamic NFT is saved on the user account") + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.CheckMetaData(t, cs, addrs[0].Bytes, nftTokenID, shardID, nftMetaData) + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) + + vm2.Log.Info("transfering token id", "tokenID", nftTokenID) + + tx = vm2.EsdtNFTTransferTx(nonce, addrs[0].Bytes, addrs[1].Bytes, nftTokenID) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("check that the metaData for the NFT is on the new user account") + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + shardID = cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[2].Bytes) + + vm2.CheckMetaData(t, cs, addrs[1].Bytes, nftTokenID, shardID, nftMetaData) + vm2.CheckMetaDataNotInAcc(t, cs, addrs[0].Bytes, nftTokenID, shardID) + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) +} + +func TestChainSimulator_CheckRolesWhichHasToBeSingular(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + + cs, _ := vm2.GetTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, true) + + // register dynamic NFT + nftTicker := []byte("NFTTICKER") + nftTokenName := []byte("tokenName") + + txDataField := bytes.Join( + [][]byte{ + []byte("registerDynamic"), + []byte(hex.EncodeToString(nftTokenName)), + []byte(hex.EncodeToString(nftTicker)), + []byte(hex.EncodeToString([]byte("NFT"))), + []byte(hex.EncodeToString([]byte("canPause"))), + []byte(hex.EncodeToString([]byte("true"))), + }, + []byte("@"), + ) + + callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) + + nonce := uint64(0) + tx := &transaction.Transaction{ + Nonce: nonce, + SndAddr: addrs[0].Bytes, + RcvAddr: vm.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: vm2.MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: callValue, + ChainID: []byte(configs.ChainID), + Version: 1, + } + nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + nftTokenID := txResult.Logs.Events[0].Topics[0] + + vm2.Log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) + + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleNFTUpdateAttributes), + []byte(core.ESDTRoleNFTAddURI), + []byte(core.ESDTRoleSetNewURI), + []byte(core.ESDTRoleModifyCreator), + []byte(core.ESDTRoleModifyRoyalties), + []byte(core.ESDTRoleNFTRecreate), + []byte(core.ESDTRoleNFTUpdate), + } + vm2.SetAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) + nonce++ + + for _, role := range roles { + tx = vm2.SetSpecialRoleTx(nonce, addrs[0].Bytes, addrs[1].Bytes, nftTokenID, [][]byte{role}) + nonce++ + + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + if txResult.Logs != nil && len(txResult.Logs.Events) > 0 { + returnMessage := string(txResult.Logs.Events[0].Topics[1]) + require.True(t, strings.Contains(returnMessage, "already exists")) + } else { + require.Fail(t, "should have been return error message") + } + } +} + +func TestChainSimulator_metaESDT_mergeMetaDataFromMultipleUpdates(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + cs, _ := vm2.GetTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) + defer cs.Close() + marshaller := cs.GetNodeHandler(0).GetCoreComponents().InternalMarshalizer() + addrs := vm2.CreateAddresses(t, cs, true) + + vm2.Log.Info("Register dynamic metaESDT token") + + metaTicker := []byte("METATICKER") + metaTokenName := []byte("tokenName") + + decimals := big.NewInt(15) + txDataField := bytes.Join( + [][]byte{ + []byte("registerDynamic"), + []byte(hex.EncodeToString(metaTokenName)), + []byte(hex.EncodeToString(metaTicker)), + []byte(hex.EncodeToString([]byte("META"))), + []byte(hex.EncodeToString(decimals.Bytes())), + }, + []byte("@"), + ) + + callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) + + shard0Nonce := uint64(0) + tx := &transaction.Transaction{ + Nonce: shard0Nonce, + SndAddr: addrs[0].Bytes, + RcvAddr: vm.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: vm2.MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: callValue, + ChainID: []byte(configs.ChainID), + Version: 1, + } + shard0Nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + tokenID := txResult.Logs.Events[0].Topics[0] + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleNFTAddQuantity), + []byte(core.ESDTRoleTransfer), + []byte(core.ESDTRoleNFTUpdate), + } + vm2.SetAddressEsdtRoles(t, cs, shard0Nonce, addrs[0], tokenID, roles) + shard0Nonce++ + + metaData := txsFee.GetDefaultMetaData() + metaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tx = vm2.EsdtNftCreateTx(shard0Nonce, addrs[0].Bytes, tokenID, metaData, 2) + shard0Nonce++ + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) + + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, tokenID, shardID, metaData) + vm2.CheckReservedField(t, cs, core.SystemAccountAddress, tokenID, shardID, []byte{1}) + + vm2.Log.Info("send metaEsdt cross shard") + + tx = vm2.EsdtNFTTransferTx(shard0Nonce, addrs[0].Bytes, addrs[1].Bytes, tokenID) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + shard0Nonce++ + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.Log.Info("update metaData on shard 0") + + newMetaData := &txsFee.MetaData{} + newMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + newMetaData.Name = []byte(hex.EncodeToString([]byte("name2"))) + newMetaData.Hash = []byte(hex.EncodeToString([]byte("hash2"))) + newMetaData.Attributes = []byte(hex.EncodeToString([]byte("attributes2"))) + + tx = vm2.EsdtMetaDataUpdateTx(tokenID, newMetaData, shard0Nonce, addrs[0].Bytes) + shard0Nonce++ + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + expectedMetaData := txsFee.GetDefaultMetaData() + expectedMetaData.Nonce = newMetaData.Nonce + expectedMetaData.Name = newMetaData.Name + expectedMetaData.Hash = newMetaData.Hash + expectedMetaData.Attributes = newMetaData.Attributes + + round := cs.GetNodeHandler(0).GetChainHandler().GetCurrentBlockHeader().GetRound() + reserved := &esdt.MetaDataVersion{ + Name: round, + Creator: round, + Hash: round, + Attributes: round, + } + firstVersion, _ := marshaller.Marshal(reserved) + + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, tokenID, 0, expectedMetaData) + vm2.CheckReservedField(t, cs, core.SystemAccountAddress, tokenID, 0, firstVersion) + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, tokenID, 1, metaData) + vm2.CheckReservedField(t, cs, core.SystemAccountAddress, tokenID, 1, []byte{1}) + + vm2.Log.Info("send the update role to shard 2") + + shard0Nonce = vm2.TransferSpecialRoleToAddr(t, cs, shard0Nonce, tokenID, addrs[0].Bytes, addrs[2].Bytes, []byte(core.ESDTRoleNFTUpdate)) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.Log.Info("update metaData on shard 2") + + newMetaData2 := &txsFee.MetaData{} + newMetaData2.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + newMetaData2.Uris = [][]byte{[]byte(hex.EncodeToString([]byte("uri5"))), []byte(hex.EncodeToString([]byte("uri6"))), []byte(hex.EncodeToString([]byte("uri7")))} + newMetaData2.Royalties = []byte(hex.EncodeToString(big.NewInt(15).Bytes())) + + tx = vm2.EsdtMetaDataUpdateTx(tokenID, newMetaData2, 0, addrs[2].Bytes) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, tokenID, 0, expectedMetaData) + vm2.CheckReservedField(t, cs, core.SystemAccountAddress, tokenID, 0, firstVersion) + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, tokenID, 1, metaData) + vm2.CheckReservedField(t, cs, core.SystemAccountAddress, tokenID, 1, []byte{1}) + + retrievedMetaData := vm2.GetMetaDataFromAcc(t, cs, core.SystemAccountAddress, tokenID, 2) + require.Equal(t, uint64(1), retrievedMetaData.Nonce) + require.Equal(t, 0, len(retrievedMetaData.Name)) + require.Equal(t, addrs[2].Bytes, retrievedMetaData.Creator) + require.Equal(t, newMetaData2.Royalties, []byte(hex.EncodeToString(big.NewInt(int64(retrievedMetaData.Royalties)).Bytes()))) + require.Equal(t, 0, len(retrievedMetaData.Hash)) + require.Equal(t, 3, len(retrievedMetaData.URIs)) + for i, uri := range newMetaData2.Uris { + require.Equal(t, uri, []byte(hex.EncodeToString(retrievedMetaData.URIs[i]))) + } + require.Equal(t, 0, len(retrievedMetaData.Attributes)) + + round2 := cs.GetNodeHandler(2).GetChainHandler().GetCurrentBlockHeader().GetRound() + reserved = &esdt.MetaDataVersion{ + URIs: round2, + Creator: round2, + Royalties: round2, + } + secondVersion, _ := cs.GetNodeHandler(shardID).GetCoreComponents().InternalMarshalizer().Marshal(reserved) + vm2.CheckReservedField(t, cs, core.SystemAccountAddress, tokenID, 2, secondVersion) + + vm2.Log.Info("transfer from shard 0 to shard 1 - should merge metaData") + + tx = vm2.EsdtNFTTransferTx(shard0Nonce, addrs[0].Bytes, addrs[1].Bytes, tokenID) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + shard0Nonce++ + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, tokenID, 0, expectedMetaData) + vm2.CheckReservedField(t, cs, core.SystemAccountAddress, tokenID, 0, firstVersion) + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, tokenID, 1, expectedMetaData) + vm2.CheckReservedField(t, cs, core.SystemAccountAddress, tokenID, 1, firstVersion) + + vm2.Log.Info("transfer from shard 1 to shard 2 - should merge metaData") + + tx = vm2.SetSpecialRoleTx(shard0Nonce, addrs[0].Bytes, addrs[1].Bytes, tokenID, [][]byte{[]byte(core.ESDTRoleTransfer)}) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + shard0Nonce++ + + tx = vm2.EsdtNFTTransferTx(0, addrs[1].Bytes, addrs[2].Bytes, tokenID) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, tokenID, 0, expectedMetaData) + vm2.CheckReservedField(t, cs, core.SystemAccountAddress, tokenID, 0, firstVersion) + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, tokenID, 1, expectedMetaData) + vm2.CheckReservedField(t, cs, core.SystemAccountAddress, tokenID, 1, firstVersion) + + latestMetaData := txsFee.GetDefaultMetaData() + latestMetaData.Nonce = expectedMetaData.Nonce + latestMetaData.Name = expectedMetaData.Name + latestMetaData.Royalties = newMetaData2.Royalties + latestMetaData.Hash = expectedMetaData.Hash + latestMetaData.Attributes = expectedMetaData.Attributes + latestMetaData.Uris = newMetaData2.Uris + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, tokenID, 2, latestMetaData) + + reserved = &esdt.MetaDataVersion{ + Name: round, + Creator: round2, + Royalties: round2, + Hash: round, + URIs: round2, + Attributes: round, + } + thirdVersion, _ := cs.GetNodeHandler(shardID).GetCoreComponents().InternalMarshalizer().Marshal(reserved) + vm2.CheckReservedField(t, cs, core.SystemAccountAddress, tokenID, 2, thirdVersion) + + vm2.Log.Info("transfer from shard 2 to shard 0 - should update metaData") + + tx = vm2.SetSpecialRoleTx(shard0Nonce, addrs[0].Bytes, addrs[2].Bytes, tokenID, [][]byte{[]byte(core.ESDTRoleTransfer)}) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + tx = vm2.EsdtNFTTransferTx(1, addrs[2].Bytes, addrs[0].Bytes, tokenID) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, tokenID, 0, latestMetaData) + vm2.CheckReservedField(t, cs, core.SystemAccountAddress, tokenID, 0, thirdVersion) + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, tokenID, 1, expectedMetaData) + vm2.CheckReservedField(t, cs, core.SystemAccountAddress, tokenID, 1, firstVersion) + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, tokenID, 2, latestMetaData) + vm2.CheckReservedField(t, cs, core.SystemAccountAddress, tokenID, 2, thirdVersion) + + vm2.Log.Info("transfer from shard 1 to shard 0 - liquidity should be updated") + + tx = vm2.EsdtNFTTransferTx(1, addrs[1].Bytes, addrs[0].Bytes, tokenID) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, tokenID, 0, latestMetaData) + vm2.CheckReservedField(t, cs, core.SystemAccountAddress, tokenID, 0, thirdVersion) + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, tokenID, 1, expectedMetaData) + vm2.CheckReservedField(t, cs, core.SystemAccountAddress, tokenID, 1, firstVersion) + vm2.CheckMetaData(t, cs, core.SystemAccountAddress, tokenID, 2, latestMetaData) + vm2.CheckReservedField(t, cs, core.SystemAccountAddress, tokenID, 2, thirdVersion) +} + +func TestChainSimulator_dynamicNFT_mergeMetaDataFromMultipleUpdates(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + cs, _ := vm2.GetTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, true) + + vm2.Log.Info("Register dynamic NFT token") + + ticker := []byte("NFTTICKER") + tokenName := []byte("tokenName") + + txDataField := bytes.Join( + [][]byte{ + []byte("registerDynamic"), + []byte(hex.EncodeToString(tokenName)), + []byte(hex.EncodeToString(ticker)), + []byte(hex.EncodeToString([]byte("NFT"))), + }, + []byte("@"), + ) + + callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) + + shard0Nonce := uint64(0) + tx := &transaction.Transaction{ + Nonce: shard0Nonce, + SndAddr: addrs[0].Bytes, + RcvAddr: vm.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: vm2.MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: callValue, + ChainID: []byte(configs.ChainID), + Version: 1, + } + shard0Nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + tokenID := txResult.Logs.Events[0].Topics[0] + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleTransfer), + []byte(core.ESDTRoleNFTUpdate), + } + vm2.SetAddressEsdtRoles(t, cs, shard0Nonce, addrs[0], tokenID, roles) + shard0Nonce++ + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + metaData := txsFee.GetDefaultMetaData() + metaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tx = vm2.EsdtNftCreateTx(shard0Nonce, addrs[0].Bytes, tokenID, metaData, 1) + shard0Nonce++ + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 0) + vm2.CheckMetaData(t, cs, addrs[0].Bytes, tokenID, 0, metaData) + + vm2.Log.Info("give update role to another account and update metaData") + + shard0Nonce = vm2.TransferSpecialRoleToAddr(t, cs, shard0Nonce, tokenID, addrs[0].Bytes, addrs[1].Bytes, []byte(core.ESDTRoleNFTUpdate)) + + newMetaData := &txsFee.MetaData{} + newMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + newMetaData.Name = []byte(hex.EncodeToString([]byte("name2"))) + newMetaData.Hash = []byte(hex.EncodeToString([]byte("hash2"))) + newMetaData.Royalties = []byte(hex.EncodeToString(big.NewInt(15).Bytes())) + + tx = vm2.EsdtMetaDataUpdateTx(tokenID, newMetaData, 0, addrs[1].Bytes) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 0) + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 1) + vm2.CheckMetaData(t, cs, addrs[0].Bytes, tokenID, 0, metaData) + newMetaData.Attributes = []byte{} + vm2.CheckMetaData(t, cs, addrs[1].Bytes, tokenID, 1, newMetaData) + + vm2.Log.Info("transfer nft - should merge metaData") + + tx = vm2.EsdtNFTTransferTx(shard0Nonce, addrs[0].Bytes, addrs[1].Bytes, tokenID) + shard0Nonce++ + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + mergedMetaData := txsFee.GetDefaultMetaData() + mergedMetaData.Nonce = metaData.Nonce + mergedMetaData.Name = newMetaData.Name + mergedMetaData.Hash = newMetaData.Hash + mergedMetaData.Royalties = newMetaData.Royalties + + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 0) + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 1) + vm2.CheckMetaDataNotInAcc(t, cs, addrs[0].Bytes, tokenID, 0) + vm2.CheckMetaData(t, cs, addrs[1].Bytes, tokenID, 1, mergedMetaData) + + vm2.Log.Info("transfer nft - should remove metaData from sender") + + tx = vm2.SetSpecialRoleTx(shard0Nonce, addrs[0].Bytes, addrs[1].Bytes, tokenID, [][]byte{[]byte(core.ESDTRoleTransfer)}) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + tx = vm2.EsdtNFTTransferTx(1, addrs[1].Bytes, addrs[2].Bytes, tokenID) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 0) + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 1) + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 2) + vm2.CheckMetaDataNotInAcc(t, cs, addrs[0].Bytes, tokenID, 0) + vm2.CheckMetaDataNotInAcc(t, cs, addrs[1].Bytes, tokenID, 1) + vm2.CheckMetaData(t, cs, addrs[2].Bytes, tokenID, 2, mergedMetaData) +} + +func TestChainSimulator_dynamicNFT_changeMetaDataForOneNFTShouldNotChangeOtherNonces(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + cs, _ := vm2.GetTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, true) + + vm2.Log.Info("Register dynamic NFT token") + + ticker := []byte("NFTTICKER") + tokenName := []byte("tokenName") + + txDataField := bytes.Join( + [][]byte{ + []byte("registerDynamic"), + []byte(hex.EncodeToString(tokenName)), + []byte(hex.EncodeToString(ticker)), + []byte(hex.EncodeToString([]byte("NFT"))), + }, + []byte("@"), + ) + + callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) + + shard0Nonce := uint64(0) + tx := &transaction.Transaction{ + Nonce: shard0Nonce, + SndAddr: addrs[0].Bytes, + RcvAddr: vm.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: vm2.MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: callValue, + ChainID: []byte(configs.ChainID), + Version: 1, + } + shard0Nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + tokenID := txResult.Logs.Events[0].Topics[0] + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleTransfer), + []byte(core.ESDTRoleNFTUpdate), + } + vm2.SetAddressEsdtRoles(t, cs, shard0Nonce, addrs[0], tokenID, roles) + shard0Nonce++ + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + metaData := txsFee.GetDefaultMetaData() + metaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tx = vm2.EsdtNftCreateTx(shard0Nonce, addrs[0].Bytes, tokenID, metaData, 1) + shard0Nonce++ + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + metaData.Nonce = []byte(hex.EncodeToString(big.NewInt(2).Bytes())) + tx = vm2.EsdtNftCreateTx(shard0Nonce, addrs[0].Bytes, tokenID, metaData, 1) + shard0Nonce++ + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.Log.Info("give update role to another account and update metaData for nonce 2") + + shard0Nonce = vm2.TransferSpecialRoleToAddr(t, cs, shard0Nonce, tokenID, addrs[0].Bytes, addrs[1].Bytes, []byte(core.ESDTRoleNFTUpdate)) + + newMetaData := &txsFee.MetaData{} + newMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(2).Bytes())) + newMetaData.Name = []byte(hex.EncodeToString([]byte("name2"))) + newMetaData.Hash = []byte(hex.EncodeToString([]byte("hash2"))) + newMetaData.Royalties = []byte(hex.EncodeToString(big.NewInt(15).Bytes())) + + tx = vm2.EsdtMetaDataUpdateTx(tokenID, newMetaData, 0, addrs[1].Bytes) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.Log.Info("transfer nft with nonce 1 - should not merge metaData") + + tx = vm2.EsdtNFTTransferTx(shard0Nonce, addrs[0].Bytes, addrs[1].Bytes, tokenID) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 0) + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 1) + metaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + vm2.CheckMetaData(t, cs, addrs[1].Bytes, tokenID, 1, metaData) +} + +func TestChainSimulator_dynamicNFT_updateBeforeCreateOnSameAccountShouldOverwrite(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + cs, _ := vm2.GetTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, true) + + vm2.Log.Info("Register dynamic NFT token") + + ticker := []byte("NFTTICKER") + tokenName := []byte("tokenName") + + txDataField := bytes.Join( + [][]byte{ + []byte("registerDynamic"), + []byte(hex.EncodeToString(tokenName)), + []byte(hex.EncodeToString(ticker)), + []byte(hex.EncodeToString([]byte("NFT"))), + }, + []byte("@"), + ) + + callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) + + shard0Nonce := uint64(0) + tx := &transaction.Transaction{ + Nonce: shard0Nonce, + SndAddr: addrs[0].Bytes, + RcvAddr: vm.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: vm2.MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: callValue, + ChainID: []byte(configs.ChainID), + Version: 1, + } + shard0Nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + tokenID := txResult.Logs.Events[0].Topics[0] + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleTransfer), + []byte(core.ESDTRoleNFTUpdate), + } + vm2.SetAddressEsdtRoles(t, cs, shard0Nonce, addrs[0], tokenID, roles) + shard0Nonce++ + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.Log.Info("update meta data for a token that is not yet created") + + newMetaData := &txsFee.MetaData{} + newMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + newMetaData.Name = []byte(hex.EncodeToString([]byte("name2"))) + newMetaData.Hash = []byte(hex.EncodeToString([]byte("hash2"))) + newMetaData.Royalties = []byte(hex.EncodeToString(big.NewInt(15).Bytes())) + + tx = vm2.EsdtMetaDataUpdateTx(tokenID, newMetaData, shard0Nonce, addrs[0].Bytes) + shard0Nonce++ + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 1) + newMetaData.Attributes = []byte{} + newMetaData.Uris = [][]byte{} + vm2.CheckMetaData(t, cs, addrs[0].Bytes, tokenID, 0, newMetaData) + + vm2.Log.Info("create nft with the same nonce - should overwrite the metadata") + + metaData := txsFee.GetDefaultMetaData() + metaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tx = vm2.EsdtNftCreateTx(shard0Nonce, addrs[0].Bytes, tokenID, metaData, 1) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 0) + vm2.CheckMetaData(t, cs, addrs[0].Bytes, tokenID, 0, metaData) +} + +func TestChainSimulator_dynamicNFT_updateBeforeCreateOnDifferentAccountsShouldMergeMetaDataWhenTransferred(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + baseIssuingCost := "1000" + cs, _ := vm2.GetTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) + defer cs.Close() + + addrs := vm2.CreateAddresses(t, cs, true) + + vm2.Log.Info("Register dynamic NFT token") + + ticker := []byte("NFTTICKER") + tokenName := []byte("tokenName") + + txDataField := bytes.Join( + [][]byte{ + []byte("registerDynamic"), + []byte(hex.EncodeToString(tokenName)), + []byte(hex.EncodeToString(ticker)), + []byte(hex.EncodeToString([]byte("NFT"))), + }, + []byte("@"), + ) + + callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) + + shard0Nonce := uint64(0) + tx := &transaction.Transaction{ + Nonce: shard0Nonce, + SndAddr: addrs[0].Bytes, + RcvAddr: vm.ESDTSCAddress, + GasLimit: 100_000_000, + GasPrice: vm2.MinGasPrice, + Signature: []byte("dummySig"), + Data: txDataField, + Value: callValue, + ChainID: []byte(configs.ChainID), + Version: 1, + } + shard0Nonce++ + + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + + require.Equal(t, "success", txResult.Status.String()) + + tokenID := txResult.Logs.Events[0].Topics[0] + roles := [][]byte{ + []byte(core.ESDTRoleNFTCreate), + []byte(core.ESDTRoleTransfer), + []byte(core.ESDTRoleNFTUpdate), + } + vm2.SetAddressEsdtRoles(t, cs, shard0Nonce, addrs[0], tokenID, roles) + shard0Nonce++ + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.Log.Info("transfer update role to another address") + + shard0Nonce = vm2.TransferSpecialRoleToAddr(t, cs, shard0Nonce, tokenID, addrs[0].Bytes, addrs[1].Bytes, []byte(core.ESDTRoleNFTUpdate)) + + vm2.Log.Info("update meta data for a token that is not yet created") + + newMetaData := &txsFee.MetaData{} + newMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + newMetaData.Name = []byte(hex.EncodeToString([]byte("name2"))) + newMetaData.Hash = []byte(hex.EncodeToString([]byte("hash2"))) + newMetaData.Royalties = []byte(hex.EncodeToString(big.NewInt(15).Bytes())) + + tx = vm2.EsdtMetaDataUpdateTx(tokenID, newMetaData, 0, addrs[1].Bytes) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 0) + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 1) + vm2.CheckMetaDataNotInAcc(t, cs, addrs[0].Bytes, tokenID, 0) + newMetaData.Attributes = []byte{} + newMetaData.Uris = [][]byte{} + vm2.CheckMetaData(t, cs, addrs[1].Bytes, tokenID, 1, newMetaData) + + vm2.Log.Info("create nft with the same nonce on different account") + + metaData := txsFee.GetDefaultMetaData() + metaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) + + tx = vm2.EsdtNftCreateTx(shard0Nonce, addrs[0].Bytes, tokenID, metaData, 1) + shard0Nonce++ + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 0) + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 1) + vm2.CheckMetaData(t, cs, addrs[0].Bytes, tokenID, 0, metaData) + vm2.CheckMetaData(t, cs, addrs[1].Bytes, tokenID, 1, newMetaData) + + vm2.Log.Info("transfer dynamic NFT to the updated account") + + tx = vm2.EsdtNFTTransferTx(shard0Nonce, addrs[0].Bytes, addrs[1].Bytes, tokenID) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm2.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.NotNil(t, txResult) + require.Equal(t, "success", txResult.Status.String()) + + err = cs.GenerateBlocks(10) + require.Nil(t, err) + + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 0) + vm2.CheckMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 1) + vm2.CheckMetaDataNotInAcc(t, cs, addrs[0].Bytes, tokenID, 0) + newMetaData.Attributes = metaData.Attributes + newMetaData.Uris = metaData.Uris + vm2.CheckMetaData(t, cs, addrs[1].Bytes, tokenID, 1, newMetaData) +} diff --git a/integrationTests/chainSimulator/vm/esdtImprovements_test.go b/integrationTests/chainSimulator/vm/esdtImprovements_test.go deleted file mode 100644 index 80748a22d7c..00000000000 --- a/integrationTests/chainSimulator/vm/esdtImprovements_test.go +++ /dev/null @@ -1,4729 +0,0 @@ -package vm - -import ( - "bytes" - "encoding/hex" - "fmt" - "math/big" - "strings" - "testing" - "time" - - "github.com/multiversx/mx-chain-core-go/core" - "github.com/multiversx/mx-chain-core-go/data/esdt" - "github.com/multiversx/mx-chain-core-go/data/transaction" - logger "github.com/multiversx/mx-chain-logger-go" - "github.com/stretchr/testify/require" - - "github.com/multiversx/mx-chain-go/config" - testsChainSimulator "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator" - "github.com/multiversx/mx-chain-go/integrationTests/vm/txsFee" - "github.com/multiversx/mx-chain-go/integrationTests/vm/txsFee/utils" - "github.com/multiversx/mx-chain-go/node/chainSimulator" - "github.com/multiversx/mx-chain-go/node/chainSimulator/components/api" - "github.com/multiversx/mx-chain-go/node/chainSimulator/configs" - "github.com/multiversx/mx-chain-go/node/chainSimulator/dtos" - "github.com/multiversx/mx-chain-go/process" - "github.com/multiversx/mx-chain-go/state" - "github.com/multiversx/mx-chain-go/vm" -) - -const ( - defaultPathToInitialConfig = "../../../cmd/node/config/" - - minGasPrice = 1000000000 - maxNumOfBlockToGenerateWhenExecutingTx = 7 -) - -var oneEGLD = big.NewInt(1000000000000000000) - -var log = logger.GetOrCreate("integrationTests/chainSimulator/vm") - -// Test scenario #1 -// -// Initial setup: Create fungible, NFT, SFT and metaESDT tokens -// (before the activation of DynamicEsdtFlag) -// -// 1.check that the metadata for all tokens is saved on the system account -// 2. wait for DynamicEsdtFlag activation -// 3. transfer the tokens to another account -// 4. check that the metadata for all tokens is saved on the system account -// 5. make an updateTokenID@tokenID function call on the ESDTSystem SC for all token types -// 6. check that the metadata for all tokens is saved on the system account -// 7. transfer the tokens to another account -// 8. check that the metaData for the NFT was removed from the system account and moved to the user account -// 9. check that the metaData for the rest of the tokens is still present on the system account and not on the userAccount -// 10. do the test for both intra and cross shard txs -func TestChainSimulator_CheckTokensMetadata_TransferTokens(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - t.Run("transfer and check all tokens - intra shard", func(t *testing.T) { - transferAndCheckTokensMetaData(t, false, false) - }) - - t.Run("transfer and check all tokens - intra shard - multi transfer", func(t *testing.T) { - transferAndCheckTokensMetaData(t, false, true) - }) - - t.Run("transfer and check all tokens - cross shard", func(t *testing.T) { - transferAndCheckTokensMetaData(t, true, false) - }) - - t.Run("transfer and check all tokens - cross shard - multi transfer", func(t *testing.T) { - transferAndCheckTokensMetaData(t, true, true) - }) -} - -func transferAndCheckTokensMetaData(t *testing.T, isCrossShard bool, isMultiTransfer bool) { - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 20, - } - - activationEpoch := uint32(4) - - baseIssuingCost := "1000" - - numOfShards := uint32(3) - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 0, - NumNodesWaitingListShard: 0, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.DynamicESDTEnableEpoch = activationEpoch - cfg.SystemSCConfig.ESDTSystemSCConfig.BaseIssuingCost = baseIssuingCost - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - addrs := createAddresses(t, cs, isCrossShard) - - err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch) - 1) - require.Nil(t, err) - - log.Info("Initial setup: Create NFT, SFT and metaESDT tokens (before the activation of DynamicEsdtFlag)") - - // issue metaESDT - metaESDTTicker := []byte("METATICKER") - nonce := uint64(0) - tx := issueMetaESDTTx(nonce, addrs[0].Bytes, metaESDTTicker, baseIssuingCost) - nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - metaESDTTokenID := txResult.Logs.Events[0].Topics[0] - - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleTransfer), - } - setAddressEsdtRoles(t, cs, nonce, addrs[0], metaESDTTokenID, roles) - nonce++ - - rolesTransfer := [][]byte{[]byte(core.ESDTRoleTransfer)} - tx = setSpecialRoleTx(nonce, addrs[0].Bytes, addrs[1].Bytes, metaESDTTokenID, rolesTransfer) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Issued metaESDT token id", "tokenID", string(metaESDTTokenID)) - - // issue NFT - nftTicker := []byte("NFTTICKER") - tx = issueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - nftTokenID := txResult.Logs.Events[0].Topics[0] - setAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) - nonce++ - - tx = setSpecialRoleTx(nonce, addrs[0].Bytes, addrs[1].Bytes, nftTokenID, rolesTransfer) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) - - // issue SFT - sftTicker := []byte("SFTTICKER") - tx = issueSemiFungibleTx(nonce, addrs[0].Bytes, sftTicker, baseIssuingCost) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - sftTokenID := txResult.Logs.Events[0].Topics[0] - setAddressEsdtRoles(t, cs, nonce, addrs[0], sftTokenID, roles) - nonce++ - - tx = setSpecialRoleTx(nonce, addrs[0].Bytes, addrs[1].Bytes, sftTokenID, rolesTransfer) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) - - nftMetaData := txsFee.GetDefaultMetaData() - nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - sftMetaData := txsFee.GetDefaultMetaData() - sftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - esdtMetaData := txsFee.GetDefaultMetaData() - esdtMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tokenIDs := [][]byte{ - nftTokenID, - sftTokenID, - metaESDTTokenID, - } - - tokensMetadata := []*txsFee.MetaData{ - nftMetaData, - sftMetaData, - esdtMetaData, - } - - for i := range tokenIDs { - tx = esdtNftCreateTx(nonce, addrs[0].Bytes, tokenIDs[i], tokensMetadata[i], 1) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - } - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - log.Info("Step 1. check that the metadata for all tokens is saved on the system account") - - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) - - checkMetaData(t, cs, core.SystemAccountAddress, nftTokenID, shardID, nftMetaData) - checkMetaData(t, cs, core.SystemAccountAddress, sftTokenID, shardID, sftMetaData) - checkMetaData(t, cs, core.SystemAccountAddress, metaESDTTokenID, shardID, esdtMetaData) - - log.Info("Step 2. wait for DynamicEsdtFlag activation") - - err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch)) - require.Nil(t, err) - - log.Info("Step 3. transfer the tokens to another account") - - if isMultiTransfer { - tx = multiESDTNFTTransferTx(nonce, addrs[0].Bytes, addrs[1].Bytes, tokenIDs) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - } else { - for _, tokenID := range tokenIDs { - log.Info("transfering token id", "tokenID", tokenID) - - tx = esdtNFTTransferTx(nonce, addrs[0].Bytes, addrs[1].Bytes, tokenID) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - } - } - - log.Info("Step 4. check that the metadata for all tokens is saved on the system account") - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - shardID = cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[1].Bytes) - - checkMetaData(t, cs, core.SystemAccountAddress, nftTokenID, shardID, nftMetaData) - checkMetaData(t, cs, core.SystemAccountAddress, sftTokenID, shardID, sftMetaData) - checkMetaData(t, cs, core.SystemAccountAddress, metaESDTTokenID, shardID, esdtMetaData) - - log.Info("Step 5. make an updateTokenID@tokenID function call on the ESDTSystem SC for all token types") - - for _, tokenID := range tokenIDs { - tx = updateTokenIDTx(nonce, addrs[0].Bytes, tokenID) - - log.Info("updating token id", "tokenID", tokenID) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - } - - log.Info("Step 6. check that the metadata for all tokens is saved on the system account") - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - checkMetaData(t, cs, core.SystemAccountAddress, nftTokenID, shardID, nftMetaData) - checkMetaData(t, cs, core.SystemAccountAddress, sftTokenID, shardID, sftMetaData) - checkMetaData(t, cs, core.SystemAccountAddress, metaESDTTokenID, shardID, esdtMetaData) - - log.Info("Step 7. transfer the tokens to another account") - - nonce = uint64(0) - if isMultiTransfer { - tx = multiESDTNFTTransferTx(nonce, addrs[1].Bytes, addrs[2].Bytes, tokenIDs) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - } else { - for _, tokenID := range tokenIDs { - log.Info("transfering token id", "tokenID", tokenID) - - tx = esdtNFTTransferTx(nonce, addrs[1].Bytes, addrs[2].Bytes, tokenID) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - } - } - - log.Info("Step 8. check that the metaData for the NFT was removed from the system account and moved to the user account") - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - shardID = cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[2].Bytes) - - checkMetaData(t, cs, addrs[2].Bytes, nftTokenID, shardID, nftMetaData) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) - - log.Info("Step 9. check that the metaData for the rest of the tokens is still present on the system account and not on the userAccount") - - checkMetaData(t, cs, core.SystemAccountAddress, sftTokenID, shardID, sftMetaData) - checkMetaDataNotInAcc(t, cs, addrs[2].Bytes, sftTokenID, shardID) - - checkMetaData(t, cs, core.SystemAccountAddress, metaESDTTokenID, shardID, esdtMetaData) - checkMetaDataNotInAcc(t, cs, addrs[2].Bytes, metaESDTTokenID, shardID) -} - -func createAddresses( - t *testing.T, - cs testsChainSimulator.ChainSimulator, - isCrossShard bool, -) []dtos.WalletAddress { - var shardIDs []uint32 - if !isCrossShard { - shardIDs = []uint32{1, 1, 1} - } else { - shardIDs = []uint32{0, 1, 2} - } - - mintValue := big.NewInt(10) - mintValue = mintValue.Mul(oneEGLD, mintValue) - - address, err := cs.GenerateAndMintWalletAddress(shardIDs[0], mintValue) - require.Nil(t, err) - - address2, err := cs.GenerateAndMintWalletAddress(shardIDs[1], mintValue) - require.Nil(t, err) - - address3, err := cs.GenerateAndMintWalletAddress(shardIDs[2], mintValue) - require.Nil(t, err) - - err = cs.GenerateBlocks(1) - require.Nil(t, err) - - return []dtos.WalletAddress{address, address2, address3} -} - -func checkMetaData( - t *testing.T, - cs testsChainSimulator.ChainSimulator, - addressBytes []byte, - token []byte, - shardID uint32, - expectedMetaData *txsFee.MetaData, -) { - retrievedMetaData := getMetaDataFromAcc(t, cs, addressBytes, token, shardID) - - require.Equal(t, expectedMetaData.Nonce, []byte(hex.EncodeToString(big.NewInt(int64(retrievedMetaData.Nonce)).Bytes()))) - require.Equal(t, expectedMetaData.Name, []byte(hex.EncodeToString(retrievedMetaData.Name))) - require.Equal(t, expectedMetaData.Royalties, []byte(hex.EncodeToString(big.NewInt(int64(retrievedMetaData.Royalties)).Bytes()))) - require.Equal(t, expectedMetaData.Hash, []byte(hex.EncodeToString(retrievedMetaData.Hash))) - for i, uri := range expectedMetaData.Uris { - require.Equal(t, uri, []byte(hex.EncodeToString(retrievedMetaData.URIs[i]))) - } - require.Equal(t, expectedMetaData.Attributes, []byte(hex.EncodeToString(retrievedMetaData.Attributes))) -} - -func checkReservedField( - t *testing.T, - cs testsChainSimulator.ChainSimulator, - addressBytes []byte, - tokenID []byte, - shardID uint32, - expectedReservedField []byte, -) { - esdtData := getESDTDataFromAcc(t, cs, addressBytes, tokenID, shardID) - require.Equal(t, expectedReservedField, esdtData.Reserved) -} - -func checkMetaDataNotInAcc( - t *testing.T, - cs testsChainSimulator.ChainSimulator, - addressBytes []byte, - token []byte, - shardID uint32, -) { - esdtData := getESDTDataFromAcc(t, cs, addressBytes, token, shardID) - - require.Nil(t, esdtData.TokenMetaData) -} - -func multiESDTNFTTransferTx(nonce uint64, sndAdr, rcvAddr []byte, tokens [][]byte) *transaction.Transaction { - transferData := make([]*utils.TransferESDTData, 0) - - for _, tokenID := range tokens { - transferData = append(transferData, &utils.TransferESDTData{ - Token: tokenID, - Nonce: 1, - Value: big.NewInt(1), - }) - } - - tx := utils.CreateMultiTransferTX( - nonce, - sndAdr, - rcvAddr, - minGasPrice, - 10_000_000, - transferData..., - ) - tx.Version = 1 - tx.Signature = []byte("dummySig") - tx.ChainID = []byte(configs.ChainID) - - return tx -} - -func esdtNFTTransferTx(nonce uint64, sndAdr, rcvAddr, token []byte) *transaction.Transaction { - tx := utils.CreateESDTNFTTransferTx( - nonce, - sndAdr, - rcvAddr, - token, - 1, - big.NewInt(1), - minGasPrice, - 10_000_000, - "", - ) - tx.Version = 1 - tx.Signature = []byte("dummySig") - tx.ChainID = []byte(configs.ChainID) - - return tx -} - -func issueTx(nonce uint64, sndAdr []byte, ticker []byte, baseIssuingCost string) *transaction.Transaction { - callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) - - txDataField := bytes.Join( - [][]byte{ - []byte("issue"), - []byte(hex.EncodeToString([]byte("asdname1"))), - []byte(hex.EncodeToString(ticker)), - []byte(hex.EncodeToString(big.NewInt(10).Bytes())), - []byte(hex.EncodeToString(big.NewInt(10).Bytes())), - }, - []byte("@"), - ) - - return &transaction.Transaction{ - Nonce: nonce, - SndAddr: sndAdr, - RcvAddr: core.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: callValue, - ChainID: []byte(configs.ChainID), - Version: 1, - } -} - -func issueMetaESDTTx(nonce uint64, sndAdr []byte, ticker []byte, baseIssuingCost string) *transaction.Transaction { - callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) - - txDataField := bytes.Join( - [][]byte{ - []byte("registerMetaESDT"), - []byte(hex.EncodeToString([]byte("asdname"))), - []byte(hex.EncodeToString(ticker)), - []byte(hex.EncodeToString(big.NewInt(10).Bytes())), - }, - []byte("@"), - ) - - return &transaction.Transaction{ - Nonce: nonce, - SndAddr: sndAdr, - RcvAddr: core.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: callValue, - ChainID: []byte(configs.ChainID), - Version: 1, - } -} - -func issueNonFungibleTx(nonce uint64, sndAdr []byte, ticker []byte, baseIssuingCost string) *transaction.Transaction { - callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) - - txDataField := bytes.Join( - [][]byte{ - []byte("issueNonFungible"), - []byte(hex.EncodeToString([]byte("asdname"))), - []byte(hex.EncodeToString(ticker)), - }, - []byte("@"), - ) - - return &transaction.Transaction{ - Nonce: nonce, - SndAddr: sndAdr, - RcvAddr: core.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: callValue, - ChainID: []byte(configs.ChainID), - Version: 1, - } -} - -func issueSemiFungibleTx(nonce uint64, sndAdr []byte, ticker []byte, baseIssuingCost string) *transaction.Transaction { - callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) - - txDataField := bytes.Join( - [][]byte{ - []byte("issueSemiFungible"), - []byte(hex.EncodeToString([]byte("asdname"))), - []byte(hex.EncodeToString(ticker)), - }, - []byte("@"), - ) - - return &transaction.Transaction{ - Nonce: nonce, - SndAddr: sndAdr, - RcvAddr: core.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: callValue, - ChainID: []byte(configs.ChainID), - Version: 1, - } -} - -func changeToDynamicTx(nonce uint64, sndAdr []byte, tokenID []byte) *transaction.Transaction { - txDataField := []byte("changeToDynamic@" + hex.EncodeToString(tokenID)) - - return &transaction.Transaction{ - Nonce: nonce, - SndAddr: sndAdr, - RcvAddr: vm.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: big.NewInt(0), - ChainID: []byte(configs.ChainID), - Version: 1, - } -} - -func updateTokenIDTx(nonce uint64, sndAdr []byte, tokenID []byte) *transaction.Transaction { - txDataField := []byte("updateTokenID@" + hex.EncodeToString(tokenID)) - - return &transaction.Transaction{ - Nonce: nonce, - SndAddr: sndAdr, - RcvAddr: vm.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: big.NewInt(0), - ChainID: []byte(configs.ChainID), - Version: 1, - } -} - -func esdtNftCreateTx( - nonce uint64, - sndAdr []byte, - tokenID []byte, - metaData *txsFee.MetaData, - quantity int64, -) *transaction.Transaction { - txDataField := bytes.Join( - [][]byte{ - []byte(core.BuiltInFunctionESDTNFTCreate), - []byte(hex.EncodeToString(tokenID)), - []byte(hex.EncodeToString(big.NewInt(quantity).Bytes())), // quantity - metaData.Name, - metaData.Royalties, - metaData.Hash, - metaData.Attributes, - metaData.Uris[0], - metaData.Uris[1], - metaData.Uris[2], - }, - []byte("@"), - ) - - return &transaction.Transaction{ - Nonce: nonce, - SndAddr: sndAdr, - RcvAddr: sndAdr, - GasLimit: 10_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: big.NewInt(0), - ChainID: []byte(configs.ChainID), - Version: 1, - } -} - -func modifyCreatorTx( - nonce uint64, - sndAdr []byte, - tokenID []byte, -) *transaction.Transaction { - txDataField := bytes.Join( - [][]byte{ - []byte(core.ESDTModifyCreator), - []byte(hex.EncodeToString(tokenID)), - []byte(hex.EncodeToString(big.NewInt(1).Bytes())), - }, - []byte("@"), - ) - - return &transaction.Transaction{ - Nonce: nonce, - SndAddr: sndAdr, - RcvAddr: sndAdr, - GasLimit: 10_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: big.NewInt(0), - ChainID: []byte(configs.ChainID), - Version: 1, - } -} - -func getESDTDataFromAcc( - t *testing.T, - cs testsChainSimulator.ChainSimulator, - addressBytes []byte, - token []byte, - shardID uint32, -) *esdt.ESDigitalToken { - account, err := cs.GetNodeHandler(shardID).GetStateComponents().AccountsAdapter().LoadAccount(addressBytes) - require.Nil(t, err) - userAccount, ok := account.(state.UserAccountHandler) - require.True(t, ok) - - baseEsdtKeyPrefix := core.ProtectedKeyPrefix + core.ESDTKeyIdentifier - key := append([]byte(baseEsdtKeyPrefix), token...) - - key2 := append(key, big.NewInt(0).SetUint64(1).Bytes()...) - esdtDataBytes, _, err := userAccount.RetrieveValue(key2) - require.Nil(t, err) - - esdtData := &esdt.ESDigitalToken{} - err = cs.GetNodeHandler(shardID).GetCoreComponents().InternalMarshalizer().Unmarshal(esdtData, esdtDataBytes) - require.Nil(t, err) - - return esdtData -} - -func getMetaDataFromAcc( - t *testing.T, - cs testsChainSimulator.ChainSimulator, - addressBytes []byte, - token []byte, - shardID uint32, -) *esdt.MetaData { - esdtData := getESDTDataFromAcc(t, cs, addressBytes, token, shardID) - - require.NotNil(t, esdtData.TokenMetaData) - - return esdtData.TokenMetaData -} - -func setSpecialRoleTx( - nonce uint64, - sndAddr []byte, - address []byte, - token []byte, - roles [][]byte, -) *transaction.Transaction { - txDataBytes := [][]byte{ - []byte("setSpecialRole"), - []byte(hex.EncodeToString(token)), - []byte(hex.EncodeToString(address)), - } - - for _, role := range roles { - txDataBytes = append(txDataBytes, []byte(hex.EncodeToString(role))) - } - - txDataField := bytes.Join( - txDataBytes, - []byte("@"), - ) - - return &transaction.Transaction{ - Nonce: nonce, - SndAddr: sndAddr, - RcvAddr: vm.ESDTSCAddress, - GasLimit: 60_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: big.NewInt(0), - ChainID: []byte(configs.ChainID), - Version: 1, - } -} - -func setAddressEsdtRoles( - t *testing.T, - cs testsChainSimulator.ChainSimulator, - nonce uint64, - address dtos.WalletAddress, - token []byte, - roles [][]byte, -) { - tx := setSpecialRoleTx(nonce, address.Bytes, address.Bytes, token, roles) - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) -} - -// Test scenario #3 -// -// Initial setup: Create NFT, SFT and metaESDT tokens -// (after the activation of DynamicEsdtFlag) -// -// 1. check that the metaData for the NFT was saved in the user account and not on the system account -// 2. check that the metaData for the other token types is saved on the system account and not at the user account level -func TestChainSimulator_CreateTokensAfterActivation(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - - cs, _ := getTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) - defer cs.Close() - - addrs := createAddresses(t, cs, false) - - log.Info("Initial setup: Create NFT, SFT and metaESDT tokens (after the activation of DynamicEsdtFlag)") - - // issue metaESDT - metaESDTTicker := []byte("METATICKER") - nonce := uint64(0) - tx := issueMetaESDTTx(nonce, addrs[0].Bytes, metaESDTTicker, baseIssuingCost) - nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - metaESDTTokenID := txResult.Logs.Events[0].Topics[0] - - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleTransfer), - } - setAddressEsdtRoles(t, cs, nonce, addrs[0], metaESDTTokenID, roles) - nonce++ - - log.Info("Issued metaESDT token id", "tokenID", string(metaESDTTokenID)) - - // issue NFT - nftTicker := []byte("NFTTICKER") - tx = issueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - nftTokenID := txResult.Logs.Events[0].Topics[0] - setAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) - nonce++ - - log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) - - // issue SFT - sftTicker := []byte("SFTTICKER") - tx = issueSemiFungibleTx(nonce, addrs[0].Bytes, sftTicker, baseIssuingCost) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - sftTokenID := txResult.Logs.Events[0].Topics[0] - setAddressEsdtRoles(t, cs, nonce, addrs[0], sftTokenID, roles) - nonce++ - - log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) - - tokenIDs := [][]byte{ - nftTokenID, - sftTokenID, - metaESDTTokenID, - } - - nftMetaData := txsFee.GetDefaultMetaData() - nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - sftMetaData := txsFee.GetDefaultMetaData() - sftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - esdtMetaData := txsFee.GetDefaultMetaData() - esdtMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tokensMetadata := []*txsFee.MetaData{ - nftMetaData, - sftMetaData, - esdtMetaData, - } - - for i := range tokenIDs { - tx = esdtNftCreateTx(nonce, addrs[0].Bytes, tokenIDs[i], tokensMetadata[i], 1) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - } - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - log.Info("Step 1. check that the metaData for the NFT was saved in the user account and not on the system account") - - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) - - checkMetaData(t, cs, addrs[0].Bytes, nftTokenID, shardID, nftMetaData) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) - - log.Info("Step 2. check that the metaData for the other token types is saved on the system account and not at the user account level") - - checkMetaData(t, cs, core.SystemAccountAddress, sftTokenID, shardID, sftMetaData) - checkMetaDataNotInAcc(t, cs, addrs[0].Bytes, sftTokenID, shardID) - - checkMetaData(t, cs, core.SystemAccountAddress, metaESDTTokenID, shardID, esdtMetaData) - checkMetaDataNotInAcc(t, cs, addrs[0].Bytes, metaESDTTokenID, shardID) -} - -// Test scenario #4 -// -// Initial setup: Create NFT, SFT, metaESDT tokens -// -// Call ESDTMetaDataRecreate to rewrite the meta data for the nft -// (The sender must have the ESDTMetaDataRecreate role) -func TestChainSimulator_ESDTMetaDataRecreate(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - - cs, _ := getTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) - defer cs.Close() - - log.Info("Initial setup: Create NFT, SFT and metaESDT tokens (after the activation of DynamicEsdtFlag)") - - addrs := createAddresses(t, cs, false) - - // issue metaESDT - metaESDTTicker := []byte("METATICKER") - nonce := uint64(0) - tx := issueMetaESDTTx(nonce, addrs[0].Bytes, metaESDTTicker, baseIssuingCost) - nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - metaESDTTokenID := txResult.Logs.Events[0].Topics[0] - - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - } - tx = setSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, metaESDTTokenID, roles) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Issued metaESDT token id", "tokenID", string(metaESDTTokenID)) - - // issue NFT - nftTicker := []byte("NFTTICKER") - tx = issueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - nftTokenID := txResult.Logs.Events[0].Topics[0] - tx = setSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, nftTokenID, roles) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) - - // issue SFT - sftTicker := []byte("SFTTICKER") - tx = issueSemiFungibleTx(nonce, addrs[0].Bytes, sftTicker, baseIssuingCost) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - sftTokenID := txResult.Logs.Events[0].Topics[0] - tx = setSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, sftTokenID, roles) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) - - tokenIDs := [][]byte{ - nftTokenID, - sftTokenID, - metaESDTTokenID, - } - - nftMetaData := txsFee.GetDefaultMetaData() - nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - sftMetaData := txsFee.GetDefaultMetaData() - sftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - esdtMetaData := txsFee.GetDefaultMetaData() - esdtMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tokensMetadata := []*txsFee.MetaData{ - nftMetaData, - sftMetaData, - esdtMetaData, - } - - for i := range tokenIDs { - tx = esdtNftCreateTx(nonce, addrs[0].Bytes, tokenIDs[i], tokensMetadata[i], 1) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - - tx = changeToDynamicTx(nonce, addrs[0].Bytes, tokenIDs[i]) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - - roles := [][]byte{ - []byte(core.ESDTRoleNFTRecreate), - } - tx = setSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, tokenIDs[i], roles) - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - } - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - log.Info("Call ESDTMetaDataRecreate to rewrite the meta data for the nft") - - for i := range tokenIDs { - newMetaData := txsFee.GetDefaultMetaData() - newMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - newMetaData.Name = []byte(hex.EncodeToString([]byte("name2"))) - newMetaData.Hash = []byte(hex.EncodeToString([]byte("hash2"))) - newMetaData.Attributes = []byte(hex.EncodeToString([]byte("attributes2"))) - - txDataField := bytes.Join( - [][]byte{ - []byte(core.ESDTMetaDataRecreate), - []byte(hex.EncodeToString(tokenIDs[i])), - newMetaData.Nonce, - newMetaData.Name, - []byte(hex.EncodeToString(big.NewInt(10).Bytes())), - newMetaData.Hash, - newMetaData.Attributes, - newMetaData.Uris[0], - newMetaData.Uris[1], - newMetaData.Uris[2], - }, - []byte("@"), - ) - - tx = &transaction.Transaction{ - Nonce: nonce, - SndAddr: addrs[0].Bytes, - RcvAddr: addrs[0].Bytes, - GasLimit: 10_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: big.NewInt(0), - ChainID: []byte(configs.ChainID), - Version: 1, - } - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) - - if bytes.Equal(tokenIDs[i], tokenIDs[0]) { // nft token - checkMetaData(t, cs, addrs[0].Bytes, tokenIDs[i], shardID, newMetaData) - } else { - checkMetaData(t, cs, core.SystemAccountAddress, tokenIDs[i], shardID, newMetaData) - } - - nonce++ - } -} - -// Test scenario #5 -// -// Initial setup: Create NFT, SFT, metaESDT tokens -// -// Call ESDTMetaDataUpdate to update some of the meta data parameters -// (The sender must have the ESDTRoleNFTUpdate role) -func TestChainSimulator_ESDTMetaDataUpdate(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - - cs, _ := getTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) - defer cs.Close() - - log.Info("Initial setup: Create NFT, SFT and metaESDT tokens (after the activation of DynamicEsdtFlag)") - - addrs := createAddresses(t, cs, false) - - // issue metaESDT - metaESDTTicker := []byte("METATICKER") - nonce := uint64(0) - tx := issueMetaESDTTx(nonce, addrs[0].Bytes, metaESDTTicker, baseIssuingCost) - nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - metaESDTTokenID := txResult.Logs.Events[0].Topics[0] - - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleTransfer), - } - tx = setSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, metaESDTTokenID, roles) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Issued metaESDT token id", "tokenID", string(metaESDTTokenID)) - - // issue NFT - nftTicker := []byte("NFTTICKER") - tx = issueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - nftTokenID := txResult.Logs.Events[0].Topics[0] - tx = setSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, nftTokenID, roles) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) - - // issue SFT - sftTicker := []byte("SFTTICKER") - tx = issueSemiFungibleTx(nonce, addrs[0].Bytes, sftTicker, baseIssuingCost) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - sftTokenID := txResult.Logs.Events[0].Topics[0] - tx = setSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, sftTokenID, roles) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) - - tokenIDs := [][]byte{ - nftTokenID, - sftTokenID, - metaESDTTokenID, - } - - nftMetaData := txsFee.GetDefaultMetaData() - nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - sftMetaData := txsFee.GetDefaultMetaData() - sftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - esdtMetaData := txsFee.GetDefaultMetaData() - esdtMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tokensMetadata := []*txsFee.MetaData{ - nftMetaData, - sftMetaData, - esdtMetaData, - } - - for i := range tokenIDs { - tx = esdtNftCreateTx(nonce, addrs[0].Bytes, tokenIDs[i], tokensMetadata[i], 1) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - - tx = changeToDynamicTx(nonce, addrs[0].Bytes, tokenIDs[i]) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - - roles := [][]byte{ - []byte(core.ESDTRoleNFTUpdate), - } - tx = setSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, tokenIDs[i], roles) - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - } - - log.Info("Call ESDTMetaDataUpdate to rewrite the meta data for the nft") - - for i := range tokenIDs { - newMetaData := txsFee.GetDefaultMetaData() - newMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - newMetaData.Name = []byte(hex.EncodeToString([]byte("name2"))) - newMetaData.Hash = []byte(hex.EncodeToString([]byte("hash2"))) - newMetaData.Attributes = []byte(hex.EncodeToString([]byte("attributes2"))) - - tx = esdtMetaDataUpdateTx(tokenIDs[i], newMetaData, nonce, addrs[0].Bytes) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) - - if bytes.Equal(tokenIDs[i], tokenIDs[0]) { // nft token - checkMetaData(t, cs, addrs[0].Bytes, tokenIDs[i], shardID, newMetaData) - } else { - checkMetaData(t, cs, core.SystemAccountAddress, tokenIDs[i], shardID, newMetaData) - } - - nonce++ - } -} - -// Test scenario #6 -// -// Initial setup: Create NFT, SFT, metaESDT tokens -// -// Call ESDTModifyCreator and check that the creator was modified -// (The sender must have the ESDTRoleModifyCreator role) -func TestChainSimulator_ESDTModifyCreator(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - - cs, _ := getTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) - defer cs.Close() - - log.Info("Initial setup: Create NFT, SFT and metaESDT tokens (after the activation of DynamicEsdtFlag). Register NFT directly as dynamic") - - addrs := createAddresses(t, cs, false) - - // issue metaESDT - metaESDTTicker := []byte("METATICKER") - nonce := uint64(0) - tx := issueMetaESDTTx(nonce, addrs[1].Bytes, metaESDTTicker, baseIssuingCost) - nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - metaESDTTokenID := txResult.Logs.Events[0].Topics[0] - - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleTransfer), - } - tx = setSpecialRoleTx(nonce, addrs[1].Bytes, addrs[1].Bytes, metaESDTTokenID, roles) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Issued metaESDT token id", "tokenID", string(metaESDTTokenID)) - - // register dynamic NFT - nftTicker := []byte("NFTTICKER") - nftTokenName := []byte("tokenName") - - txDataField := bytes.Join( - [][]byte{ - []byte("registerDynamic"), - []byte(hex.EncodeToString(nftTokenName)), - []byte(hex.EncodeToString(nftTicker)), - []byte(hex.EncodeToString([]byte("NFT"))), - }, - []byte("@"), - ) - - callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) - - tx = &transaction.Transaction{ - Nonce: nonce, - SndAddr: addrs[1].Bytes, - RcvAddr: vm.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: callValue, - ChainID: []byte(configs.ChainID), - Version: 1, - } - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - nftTokenID := txResult.Logs.Events[0].Topics[0] - tx = setSpecialRoleTx(nonce, addrs[1].Bytes, addrs[1].Bytes, nftTokenID, roles) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) - - // issue SFT - sftTicker := []byte("SFTTICKER") - tx = issueSemiFungibleTx(nonce, addrs[1].Bytes, sftTicker, baseIssuingCost) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - sftTokenID := txResult.Logs.Events[0].Topics[0] - tx = setSpecialRoleTx(nonce, addrs[1].Bytes, addrs[1].Bytes, sftTokenID, roles) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) - - tokenIDs := [][]byte{ - nftTokenID, - sftTokenID, - metaESDTTokenID, - } - - nftMetaData := txsFee.GetDefaultMetaData() - nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - sftMetaData := txsFee.GetDefaultMetaData() - sftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - esdtMetaData := txsFee.GetDefaultMetaData() - esdtMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tokensMetadata := []*txsFee.MetaData{ - nftMetaData, - sftMetaData, - esdtMetaData, - } - - for i := range tokenIDs { - tx = esdtNftCreateTx(nonce, addrs[1].Bytes, tokenIDs[i], tokensMetadata[i], 1) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - } - - log.Info("Change to DYNAMIC type") - - for i := range tokenIDs { - tx = changeToDynamicTx(nonce, addrs[1].Bytes, tokenIDs[i]) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - } - - log.Info("Call ESDTModifyCreator and check that the creator was modified") - - mintValue := big.NewInt(10) - mintValue = mintValue.Mul(oneEGLD, mintValue) - - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[1].Bytes) - - for i := range tokenIDs { - log.Info("Modify creator for token", "tokenID", tokenIDs[i]) - - newCreatorAddress, err := cs.GenerateAndMintWalletAddress(shardID, mintValue) - require.Nil(t, err) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - roles = [][]byte{ - []byte(core.ESDTRoleModifyCreator), - } - tx = setSpecialRoleTx(nonce, addrs[1].Bytes, newCreatorAddress.Bytes, tokenIDs[i], roles) - nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - tx = modifyCreatorTx(0, newCreatorAddress.Bytes, tokenIDs[i]) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - retrievedMetaData := &esdt.MetaData{} - if bytes.Equal(tokenIDs[i], nftTokenID) { - retrievedMetaData = getMetaDataFromAcc(t, cs, newCreatorAddress.Bytes, tokenIDs[i], shardID) - } else { - retrievedMetaData = getMetaDataFromAcc(t, cs, core.SystemAccountAddress, tokenIDs[i], shardID) - } - - require.Equal(t, newCreatorAddress.Bytes, retrievedMetaData.Creator) - } -} - -func TestChainSimulator_ESDTModifyCreator_CrossShard(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - - cs, _ := getTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) - defer cs.Close() - - addrs := createAddresses(t, cs, false) - - // issue metaESDT - metaESDTTicker := []byte("METATICKER") - nonce := uint64(0) - tx := issueMetaESDTTx(nonce, addrs[1].Bytes, metaESDTTicker, baseIssuingCost) - nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - metaESDTTokenID := txResult.Logs.Events[0].Topics[0] - - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleTransfer), - } - tx = setSpecialRoleTx(nonce, addrs[1].Bytes, addrs[1].Bytes, metaESDTTokenID, roles) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Issued metaESDT token id", "tokenID", string(metaESDTTokenID)) - - // register dynamic NFT - nftTicker := []byte("NFTTICKER") - nftTokenName := []byte("tokenName") - - txDataField := bytes.Join( - [][]byte{ - []byte("registerDynamic"), - []byte(hex.EncodeToString(nftTokenName)), - []byte(hex.EncodeToString(nftTicker)), - []byte(hex.EncodeToString([]byte("NFT"))), - }, - []byte("@"), - ) - - callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) - - tx = &transaction.Transaction{ - Nonce: nonce, - SndAddr: addrs[1].Bytes, - RcvAddr: vm.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: callValue, - ChainID: []byte(configs.ChainID), - Version: 1, - } - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - nftTokenID := txResult.Logs.Events[0].Topics[0] - tx = setSpecialRoleTx(nonce, addrs[1].Bytes, addrs[1].Bytes, nftTokenID, roles) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) - - // issue SFT - sftTicker := []byte("SFTTICKER") - tx = issueSemiFungibleTx(nonce, addrs[1].Bytes, sftTicker, baseIssuingCost) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - sftTokenID := txResult.Logs.Events[0].Topics[0] - tx = setSpecialRoleTx(nonce, addrs[1].Bytes, addrs[1].Bytes, sftTokenID, roles) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) - - tokenIDs := [][]byte{ - nftTokenID, - sftTokenID, - metaESDTTokenID, - } - - nftMetaData := txsFee.GetDefaultMetaData() - nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - sftMetaData := txsFee.GetDefaultMetaData() - sftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - esdtMetaData := txsFee.GetDefaultMetaData() - esdtMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tokensMetadata := []*txsFee.MetaData{ - nftMetaData, - sftMetaData, - esdtMetaData, - } - - for i := range tokenIDs { - tx = esdtNftCreateTx(nonce, addrs[1].Bytes, tokenIDs[i], tokensMetadata[i], 1) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - } - - log.Info("Change to DYNAMIC type") - - for i := range tokenIDs { - tx = changeToDynamicTx(nonce, addrs[1].Bytes, tokenIDs[i]) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - } - - log.Info("Call ESDTModifyCreator and check that the creator was modified") - - mintValue := big.NewInt(10) - mintValue = mintValue.Mul(oneEGLD, mintValue) - - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[1].Bytes) - - crossShardID := uint32(2) - if shardID == uint32(2) { - crossShardID = uint32(1) - } - - for i := range tokenIDs { - log.Info("Modify creator for token", "tokenID", string(tokenIDs[i])) - - newCreatorAddress, err := cs.GenerateAndMintWalletAddress(crossShardID, mintValue) - require.Nil(t, err) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - roles = [][]byte{ - []byte(core.ESDTRoleModifyCreator), - } - tx = setSpecialRoleTx(nonce, addrs[1].Bytes, newCreatorAddress.Bytes, tokenIDs[i], roles) - nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("transfering token id", "tokenID", tokenIDs[i]) - - tx = esdtNFTTransferTx(nonce, addrs[1].Bytes, newCreatorAddress.Bytes, tokenIDs[i]) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - tx = modifyCreatorTx(0, newCreatorAddress.Bytes, tokenIDs[i]) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - retrievedMetaData := &esdt.MetaData{} - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(newCreatorAddress.Bytes) - if bytes.Equal(tokenIDs[i], nftTokenID) { - retrievedMetaData = getMetaDataFromAcc(t, cs, newCreatorAddress.Bytes, tokenIDs[i], shardID) - } else { - retrievedMetaData = getMetaDataFromAcc(t, cs, core.SystemAccountAddress, tokenIDs[i], shardID) - } - - require.Equal(t, newCreatorAddress.Bytes, retrievedMetaData.Creator) - - nonce++ - } -} - -// Test scenario #7 -// -// Initial setup: Create NFT, SFT, metaESDT tokens -// -// Call ESDTSetNewURIs and check that the new URIs were set for the token -// (The sender must have the ESDTRoleSetNewURI role) -func TestChainSimulator_ESDTSetNewURIs(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - - cs, _ := getTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) - defer cs.Close() - - addrs := createAddresses(t, cs, false) - - // issue metaESDT - metaESDTTicker := []byte("METATICKER") - nonce := uint64(0) - tx := issueMetaESDTTx(nonce, addrs[0].Bytes, metaESDTTicker, baseIssuingCost) - nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - metaESDTTokenID := txResult.Logs.Events[0].Topics[0] - - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleTransfer), - } - tx = setSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, metaESDTTokenID, roles) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Issued metaESDT token id", "tokenID", string(metaESDTTokenID)) - - // issue NFT - nftTicker := []byte("NFTTICKER") - tx = issueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - nftTokenID := txResult.Logs.Events[0].Topics[0] - tx = setSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, nftTokenID, roles) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) - - // issue SFT - sftTicker := []byte("SFTTICKER") - tx = issueSemiFungibleTx(nonce, addrs[0].Bytes, sftTicker, baseIssuingCost) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - sftTokenID := txResult.Logs.Events[0].Topics[0] - tx = setSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, sftTokenID, roles) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) - - tokenIDs := [][]byte{ - nftTokenID, - sftTokenID, - metaESDTTokenID, - } - - nftMetaData := txsFee.GetDefaultMetaData() - nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - sftMetaData := txsFee.GetDefaultMetaData() - sftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - esdtMetaData := txsFee.GetDefaultMetaData() - esdtMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tokensMetadata := []*txsFee.MetaData{ - nftMetaData, - sftMetaData, - esdtMetaData, - } - - for i := range tokenIDs { - tx = esdtNftCreateTx(nonce, addrs[0].Bytes, tokenIDs[i], tokensMetadata[i], 1) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - - tx = changeToDynamicTx(nonce, addrs[0].Bytes, tokenIDs[i]) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - - roles := [][]byte{ - []byte(core.ESDTRoleSetNewURI), - } - tx = setSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, tokenIDs[i], roles) - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - } - - log.Info("Call ESDTSetNewURIs and check that the new URIs were set for the tokens") - - metaDataNonce := []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - uris := [][]byte{ - []byte(hex.EncodeToString([]byte("uri0"))), - []byte(hex.EncodeToString([]byte("uri1"))), - []byte(hex.EncodeToString([]byte("uri2"))), - } - - expUris := [][]byte{ - []byte("uri0"), - []byte("uri1"), - []byte("uri2"), - } - - for i := range tokenIDs { - log.Info("Set new uris for token", "tokenID", string(tokenIDs[i])) - - txDataField := bytes.Join( - [][]byte{ - []byte(core.ESDTSetNewURIs), - []byte(hex.EncodeToString(tokenIDs[i])), - metaDataNonce, - uris[0], - uris[1], - uris[2], - }, - []byte("@"), - ) - - tx = &transaction.Transaction{ - Nonce: nonce, - SndAddr: addrs[0].Bytes, - RcvAddr: addrs[0].Bytes, - GasLimit: 10_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: big.NewInt(0), - ChainID: []byte(configs.ChainID), - Version: 1, - } - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) - var retrievedMetaData *esdt.MetaData - if bytes.Equal(tokenIDs[i], tokenIDs[0]) { // nft token - retrievedMetaData = getMetaDataFromAcc(t, cs, addrs[0].Bytes, tokenIDs[i], shardID) - } else { - retrievedMetaData = getMetaDataFromAcc(t, cs, core.SystemAccountAddress, tokenIDs[i], shardID) - } - - require.Equal(t, expUris, retrievedMetaData.URIs) - - nonce++ - } -} - -// Test scenario #8 -// -// Initial setup: Create NFT, SFT, metaESDT tokens -// -// Call ESDTModifyRoyalties and check that the royalties were changed -// (The sender must have the ESDTRoleModifyRoyalties role) -func TestChainSimulator_ESDTModifyRoyalties(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - - cs, _ := getTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) - defer cs.Close() - - addrs := createAddresses(t, cs, false) - - // issue metaESDT - metaESDTTicker := []byte("METATICKER") - nonce := uint64(0) - tx := issueMetaESDTTx(nonce, addrs[0].Bytes, metaESDTTicker, baseIssuingCost) - nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - metaESDTTokenID := txResult.Logs.Events[0].Topics[0] - - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleTransfer), - } - tx = setSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, metaESDTTokenID, roles) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Issued metaESDT token id", "tokenID", string(metaESDTTokenID)) - - // issue NFT - nftTicker := []byte("NFTTICKER") - tx = issueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - nftTokenID := txResult.Logs.Events[0].Topics[0] - tx = setSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, nftTokenID, roles) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) - - // issue SFT - sftTicker := []byte("SFTTICKER") - tx = issueSemiFungibleTx(nonce, addrs[0].Bytes, sftTicker, baseIssuingCost) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - sftTokenID := txResult.Logs.Events[0].Topics[0] - tx = setSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, sftTokenID, roles) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) - - tokenIDs := [][]byte{ - nftTokenID, - sftTokenID, - metaESDTTokenID, - } - - nftMetaData := txsFee.GetDefaultMetaData() - nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - sftMetaData := txsFee.GetDefaultMetaData() - sftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - esdtMetaData := txsFee.GetDefaultMetaData() - esdtMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tokensMetadata := []*txsFee.MetaData{ - nftMetaData, - sftMetaData, - esdtMetaData, - } - - for i := range tokenIDs { - tx = esdtNftCreateTx(nonce, addrs[0].Bytes, tokenIDs[i], tokensMetadata[i], 1) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - - tx = changeToDynamicTx(nonce, addrs[0].Bytes, tokenIDs[i]) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - - roles := [][]byte{ - []byte(core.ESDTRoleModifyRoyalties), - } - tx = setSpecialRoleTx(nonce, addrs[0].Bytes, addrs[0].Bytes, tokenIDs[i], roles) - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - } - - log.Info("Call ESDTModifyRoyalties and check that the royalties were changed") - - metaDataNonce := []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - royalties := []byte(hex.EncodeToString(big.NewInt(20).Bytes())) - - for i := range tokenIDs { - log.Info("Set new royalties for token", "tokenID", string(tokenIDs[i])) - - txDataField := bytes.Join( - [][]byte{ - []byte(core.ESDTModifyRoyalties), - []byte(hex.EncodeToString(tokenIDs[i])), - metaDataNonce, - royalties, - }, - []byte("@"), - ) - - tx = &transaction.Transaction{ - Nonce: nonce, - SndAddr: addrs[0].Bytes, - RcvAddr: addrs[0].Bytes, - GasLimit: 10_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: big.NewInt(0), - ChainID: []byte(configs.ChainID), - Version: 1, - } - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - shardID := cs.GetNodeHandler(0).GetShardCoordinator().ComputeId(addrs[0].Bytes) - retrievedMetaData := getMetaDataFromAcc(t, cs, addrs[0].Bytes, nftTokenID, shardID) - - require.Equal(t, uint32(big.NewInt(20).Uint64()), retrievedMetaData.Royalties) - - nonce++ - } -} - -// Test scenario #9 -// -// Initial setup: Create NFT -// -// 1. Change the nft to DYNAMIC type - the metadata should be on the system account -// 2. Send the NFT cross shard -// 3. The meta data should still be present on the system account -func TestChainSimulator_NFT_ChangeToDynamicType(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 20, - } - - activationEpoch := uint32(4) - - baseIssuingCost := "1000" - - numOfShards := uint32(3) - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 0, - NumNodesWaitingListShard: 0, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.DynamicESDTEnableEpoch = activationEpoch - cfg.SystemSCConfig.ESDTSystemSCConfig.BaseIssuingCost = baseIssuingCost - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - addrs := createAddresses(t, cs, true) - - err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch) - 2) - require.Nil(t, err) - - log.Info("Initial setup: Create NFT") - - nftTicker := []byte("NFTTICKER") - nonce := uint64(0) - tx := issueNonFungibleTx(nonce, addrs[1].Bytes, nftTicker, baseIssuingCost) - nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - } - - nftTokenID := txResult.Logs.Events[0].Topics[0] - setAddressEsdtRoles(t, cs, nonce, addrs[1], nftTokenID, roles) - nonce++ - - log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) - - nftMetaData := txsFee.GetDefaultMetaData() - nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tx = esdtNftCreateTx(nonce, addrs[1].Bytes, nftTokenID, nftMetaData, 1) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch)) - require.Nil(t, err) - - log.Info("Step 1. Change the nft to DYNAMIC type - the metadata should be on the system account") - - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[1].Bytes) - - tx = changeToDynamicTx(nonce, addrs[1].Bytes, nftTokenID) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - roles = [][]byte{ - []byte(core.ESDTRoleNFTUpdate), - } - - setAddressEsdtRoles(t, cs, nonce, addrs[1], nftTokenID, roles) - nonce++ - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - checkMetaData(t, cs, core.SystemAccountAddress, nftTokenID, shardID, nftMetaData) - - log.Info("Step 2. Send the NFT cross shard") - - tx = esdtNFTTransferTx(nonce, addrs[1].Bytes, addrs[2].Bytes, nftTokenID) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Step 3. The meta data should still be present on the system account") - - checkMetaData(t, cs, core.SystemAccountAddress, nftTokenID, shardID, nftMetaData) -} - -// Test scenario #10 -// -// Initial setup: Create SFT and send in another shard -// -// 1. change the sft meta data (differently from the previous one) in the other shard -// 2. check that the newest metadata is saved -func TestChainSimulator_ChangeMetaData(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - t.Run("sft change metadata", func(t *testing.T) { - testChainSimulatorChangeMetaData(t, issueSemiFungibleTx) - }) - - t.Run("metaESDT change metadata", func(t *testing.T) { - testChainSimulatorChangeMetaData(t, issueMetaESDTTx) - }) -} - -type issueTxFunc func(uint64, []byte, []byte, string) *transaction.Transaction - -func testChainSimulatorChangeMetaData(t *testing.T, issueFn issueTxFunc) { - baseIssuingCost := "1000" - - cs, _ := getTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) - defer cs.Close() - - addrs := createAddresses(t, cs, true) - - log.Info("Initial setup: Create token and send in another shard") - - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleTransfer), - []byte(core.ESDTRoleNFTAddQuantity), - } - - ticker := []byte("TICKER") - nonce := uint64(0) - tx := issueFn(nonce, addrs[1].Bytes, ticker, baseIssuingCost) - nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - tokenID := txResult.Logs.Events[0].Topics[0] - setAddressEsdtRoles(t, cs, nonce, addrs[1], tokenID, roles) - nonce++ - - log.Info("Issued token id", "tokenID", string(tokenID)) - - metaData := txsFee.GetDefaultMetaData() - metaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tx = esdtNftCreateTx(nonce, addrs[1].Bytes, tokenID, metaData, 2) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - tx = changeToDynamicTx(nonce, addrs[1].Bytes, tokenID) - nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - log.Info("Send to separate shards") - - tx = esdtNFTTransferTx(nonce, addrs[1].Bytes, addrs[2].Bytes, tokenID) - nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - tx = esdtNFTTransferTx(nonce, addrs[1].Bytes, addrs[0].Bytes, tokenID) - nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - roles = [][]byte{ - []byte(core.ESDTRoleTransfer), - []byte(core.ESDTRoleNFTUpdate), - } - tx = setSpecialRoleTx(nonce, addrs[1].Bytes, addrs[0].Bytes, tokenID, roles) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - log.Info("Step 1. change the sft meta data in one shard") - - sftMetaData2 := txsFee.GetDefaultMetaData() - sftMetaData2.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - sftMetaData2.Name = []byte(hex.EncodeToString([]byte("name2"))) - sftMetaData2.Hash = []byte(hex.EncodeToString([]byte("hash2"))) - sftMetaData2.Attributes = []byte(hex.EncodeToString([]byte("attributes2"))) - - tx = esdtMetaDataUpdateTx(tokenID, sftMetaData2, 0, addrs[0].Bytes) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("Step 2. check that the newest metadata is saved") - - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) - checkMetaData(t, cs, core.SystemAccountAddress, tokenID, shardID, sftMetaData2) - - shard2ID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[2].Bytes) - checkMetaData(t, cs, core.SystemAccountAddress, tokenID, shard2ID, metaData) - - log.Info("Step 3. create new wallet is shard 2") - - mintValue := big.NewInt(10) - mintValue = mintValue.Mul(oneEGLD, mintValue) - newShard2Addr, err := cs.GenerateAndMintWalletAddress(2, mintValue) - require.Nil(t, err) - err = cs.GenerateBlocks(1) - require.Nil(t, err) - - log.Info("Step 4. send updated token to shard 2 ") - - tx = esdtNFTTransferTx(1, addrs[0].Bytes, newShard2Addr.Bytes, tokenID) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - err = cs.GenerateBlocks(5) - require.Nil(t, err) - - log.Info("Step 5. check meta data in shard 2 is updated to latest version ") - - checkMetaData(t, cs, core.SystemAccountAddress, tokenID, shard2ID, sftMetaData2) - checkMetaData(t, cs, core.SystemAccountAddress, tokenID, shardID, sftMetaData2) - -} - -func TestChainSimulator_NFT_RegisterDynamic(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - - cs, _ := getTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) - defer cs.Close() - - addrs := createAddresses(t, cs, true) - - log.Info("Register dynamic nft token") - - nftTicker := []byte("NFTTICKER") - nftTokenName := []byte("tokenName") - - txDataField := bytes.Join( - [][]byte{ - []byte("registerDynamic"), - []byte(hex.EncodeToString(nftTokenName)), - []byte(hex.EncodeToString(nftTicker)), - []byte(hex.EncodeToString([]byte("NFT"))), - }, - []byte("@"), - ) - - callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) - - nonce := uint64(0) - tx := &transaction.Transaction{ - Nonce: nonce, - SndAddr: addrs[0].Bytes, - RcvAddr: vm.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: callValue, - ChainID: []byte(configs.ChainID), - Version: 1, - } - nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - nftTokenID := txResult.Logs.Events[0].Topics[0] - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleTransfer), - } - setAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) - nonce++ - - nftMetaData := txsFee.GetDefaultMetaData() - nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tx = esdtNftCreateTx(nonce, addrs[0].Bytes, nftTokenID, nftMetaData, 1) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) - - checkMetaData(t, cs, addrs[0].Bytes, nftTokenID, shardID, nftMetaData) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) - - log.Info("Check that token type is Dynamic") - - scQuery := &process.SCQuery{ - ScAddress: vm.ESDTSCAddress, - FuncName: "getTokenProperties", - CallValue: big.NewInt(0), - Arguments: [][]byte{nftTokenID}, - } - result, _, err := cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ExecuteSCQuery(scQuery) - require.Nil(t, err) - require.Equal(t, "", result.ReturnMessage) - require.Equal(t, testsChainSimulator.OkReturnCode, result.ReturnCode) - - tokenType := result.ReturnData[1] - require.Equal(t, core.DynamicNFTESDT, string(tokenType)) -} - -func TestChainSimulator_MetaESDT_RegisterDynamic(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - - cs, _ := getTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) - defer cs.Close() - - addrs := createAddresses(t, cs, true) - - log.Info("Register dynamic metaESDT token") - - metaTicker := []byte("METATICKER") - metaTokenName := []byte("tokenName") - - decimals := big.NewInt(15) - - txDataField := bytes.Join( - [][]byte{ - []byte("registerDynamic"), - []byte(hex.EncodeToString(metaTokenName)), - []byte(hex.EncodeToString(metaTicker)), - []byte(hex.EncodeToString([]byte("META"))), - []byte(hex.EncodeToString(decimals.Bytes())), - }, - []byte("@"), - ) - - callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) - - nonce := uint64(0) - tx := &transaction.Transaction{ - Nonce: nonce, - SndAddr: addrs[0].Bytes, - RcvAddr: vm.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: callValue, - ChainID: []byte(configs.ChainID), - Version: 1, - } - nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - nftTokenID := txResult.Logs.Events[0].Topics[0] - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleTransfer), - } - setAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) - nonce++ - - nftMetaData := txsFee.GetDefaultMetaData() - nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tx = esdtNftCreateTx(nonce, addrs[0].Bytes, nftTokenID, nftMetaData, 1) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) - - checkMetaData(t, cs, core.SystemAccountAddress, nftTokenID, shardID, nftMetaData) - - log.Info("Check that token type is Dynamic") - - scQuery := &process.SCQuery{ - ScAddress: vm.ESDTSCAddress, - FuncName: "getTokenProperties", - CallValue: big.NewInt(0), - Arguments: [][]byte{nftTokenID}, - } - result, _, err := cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ExecuteSCQuery(scQuery) - require.Nil(t, err) - require.Equal(t, "", result.ReturnMessage) - require.Equal(t, testsChainSimulator.OkReturnCode, result.ReturnCode) - - tokenType := result.ReturnData[1] - require.Equal(t, core.Dynamic+core.MetaESDT, string(tokenType)) -} - -func TestChainSimulator_FNG_RegisterDynamic(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - - cs, _ := getTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) - defer cs.Close() - - addrs := createAddresses(t, cs, true) - - log.Info("Register dynamic fungible token") - - metaTicker := []byte("FNGTICKER") - metaTokenName := []byte("tokenName") - - decimals := big.NewInt(15) - - txDataField := bytes.Join( - [][]byte{ - []byte("registerDynamic"), - []byte(hex.EncodeToString(metaTokenName)), - []byte(hex.EncodeToString(metaTicker)), - []byte(hex.EncodeToString([]byte("FNG"))), - []byte(hex.EncodeToString(decimals.Bytes())), - }, - []byte("@"), - ) - - callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) - - tx := &transaction.Transaction{ - Nonce: 0, - SndAddr: addrs[0].Bytes, - RcvAddr: vm.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: callValue, - ChainID: []byte(configs.ChainID), - Version: 1, - } - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - signalErrorTopic := string(txResult.Logs.Events[0].Topics[1]) - - require.Equal(t, fmt.Sprintf("cannot create %s tokens as dynamic", core.FungibleESDT), signalErrorTopic) -} - -func TestChainSimulator_NFT_RegisterAndSetAllRolesDynamic(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - - cs, _ := getTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) - defer cs.Close() - - addrs := createAddresses(t, cs, true) - - log.Info("Register dynamic nft token") - - nftTicker := []byte("NFTTICKER") - nftTokenName := []byte("tokenName") - - txDataField := bytes.Join( - [][]byte{ - []byte("registerAndSetAllRolesDynamic"), - []byte(hex.EncodeToString(nftTokenName)), - []byte(hex.EncodeToString(nftTicker)), - []byte(hex.EncodeToString([]byte("NFT"))), - }, - []byte("@"), - ) - - callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) - - nonce := uint64(0) - tx := &transaction.Transaction{ - Nonce: nonce, - SndAddr: addrs[0].Bytes, - RcvAddr: vm.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: callValue, - ChainID: []byte(configs.ChainID), - Version: 1, - } - nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - nftTokenID := txResult.Logs.Events[0].Topics[0] - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleTransfer), - } - setAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) - nonce++ - - nftMetaData := txsFee.GetDefaultMetaData() - nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tx = esdtNftCreateTx(nonce, addrs[0].Bytes, nftTokenID, nftMetaData, 1) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) - - checkMetaData(t, cs, addrs[0].Bytes, nftTokenID, shardID, nftMetaData) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) - - log.Info("Check that token type is Dynamic") - - scQuery := &process.SCQuery{ - ScAddress: vm.ESDTSCAddress, - FuncName: "getTokenProperties", - CallValue: big.NewInt(0), - Arguments: [][]byte{nftTokenID}, - } - result, _, err := cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ExecuteSCQuery(scQuery) - require.Nil(t, err) - require.Equal(t, "", result.ReturnMessage) - require.Equal(t, testsChainSimulator.OkReturnCode, result.ReturnCode) - - tokenType := result.ReturnData[1] - require.Equal(t, core.DynamicNFTESDT, string(tokenType)) - - log.Info("Check token roles") - - scQuery = &process.SCQuery{ - ScAddress: vm.ESDTSCAddress, - FuncName: "getAllAddressesAndRoles", - CallValue: big.NewInt(0), - Arguments: [][]byte{nftTokenID}, - } - result, _, err = cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ExecuteSCQuery(scQuery) - require.Nil(t, err) - require.Equal(t, "", result.ReturnMessage) - require.Equal(t, testsChainSimulator.OkReturnCode, result.ReturnCode) - - expectedRoles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleNFTBurn), - []byte(core.ESDTRoleNFTUpdateAttributes), - []byte(core.ESDTRoleNFTAddURI), - []byte(core.ESDTRoleNFTRecreate), - []byte(core.ESDTRoleModifyCreator), - []byte(core.ESDTRoleModifyRoyalties), - []byte(core.ESDTRoleSetNewURI), - []byte(core.ESDTRoleNFTUpdate), - } - - checkTokenRoles(t, result.ReturnData, expectedRoles) -} - -func TestChainSimulator_SFT_RegisterAndSetAllRolesDynamic(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - - cs, _ := getTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) - defer cs.Close() - - addrs := createAddresses(t, cs, true) - - log.Info("Register dynamic sft token") - - sftTicker := []byte("SFTTICKER") - sftTokenName := []byte("tokenName") - - txDataField := bytes.Join( - [][]byte{ - []byte("registerAndSetAllRolesDynamic"), - []byte(hex.EncodeToString(sftTokenName)), - []byte(hex.EncodeToString(sftTicker)), - []byte(hex.EncodeToString([]byte("SFT"))), - }, - []byte("@"), - ) - - callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) - - nonce := uint64(0) - tx := &transaction.Transaction{ - Nonce: nonce, - SndAddr: addrs[0].Bytes, - RcvAddr: vm.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: callValue, - ChainID: []byte(configs.ChainID), - Version: 1, - } - nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - sftTokenID := txResult.Logs.Events[0].Topics[0] - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleTransfer), - } - setAddressEsdtRoles(t, cs, nonce, addrs[0], sftTokenID, roles) - nonce++ - - nftMetaData := txsFee.GetDefaultMetaData() - nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tx = esdtNftCreateTx(nonce, addrs[0].Bytes, sftTokenID, nftMetaData, 1) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) - - checkMetaData(t, cs, core.SystemAccountAddress, sftTokenID, shardID, nftMetaData) - - log.Info("Check that token type is Dynamic") - - scQuery := &process.SCQuery{ - ScAddress: vm.ESDTSCAddress, - FuncName: "getTokenProperties", - CallValue: big.NewInt(0), - Arguments: [][]byte{sftTokenID}, - } - result, _, err := cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ExecuteSCQuery(scQuery) - require.Nil(t, err) - require.Equal(t, "", result.ReturnMessage) - require.Equal(t, testsChainSimulator.OkReturnCode, result.ReturnCode) - - tokenType := result.ReturnData[1] - require.Equal(t, core.DynamicSFTESDT, string(tokenType)) - - log.Info("Check token roles") - - scQuery = &process.SCQuery{ - ScAddress: vm.ESDTSCAddress, - FuncName: "getAllAddressesAndRoles", - CallValue: big.NewInt(0), - Arguments: [][]byte{sftTokenID}, - } - result, _, err = cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ExecuteSCQuery(scQuery) - require.Nil(t, err) - require.Equal(t, "", result.ReturnMessage) - require.Equal(t, testsChainSimulator.OkReturnCode, result.ReturnCode) - - expectedRoles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleNFTBurn), - []byte(core.ESDTRoleNFTUpdateAttributes), - []byte(core.ESDTRoleNFTAddURI), - []byte(core.ESDTRoleNFTRecreate), - []byte(core.ESDTRoleModifyCreator), - []byte(core.ESDTRoleModifyRoyalties), - []byte(core.ESDTRoleSetNewURI), - []byte(core.ESDTRoleNFTUpdate), - } - - checkTokenRoles(t, result.ReturnData, expectedRoles) -} - -func TestChainSimulator_FNG_RegisterAndSetAllRolesDynamic(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - - cs, _ := getTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) - defer cs.Close() - - addrs := createAddresses(t, cs, true) - - log.Info("Register dynamic fungible token") - - fngTicker := []byte("FNGTICKER") - fngTokenName := []byte("tokenName") - - txDataField := bytes.Join( - [][]byte{ - []byte("registerAndSetAllRolesDynamic"), - []byte(hex.EncodeToString(fngTokenName)), - []byte(hex.EncodeToString(fngTicker)), - []byte(hex.EncodeToString([]byte("FNG"))), - []byte(hex.EncodeToString(big.NewInt(10).Bytes())), - }, - []byte("@"), - ) - - callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) - - tx := &transaction.Transaction{ - Nonce: 0, - SndAddr: addrs[0].Bytes, - RcvAddr: vm.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: callValue, - ChainID: []byte(configs.ChainID), - Version: 1, - } - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - signalErrorTopic := string(txResult.Logs.Events[0].Topics[1]) - - require.Equal(t, fmt.Sprintf("cannot create %s tokens as dynamic", core.FungibleESDT), signalErrorTopic) -} - -func TestChainSimulator_MetaESDT_RegisterAndSetAllRolesDynamic(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - - cs, _ := getTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) - defer cs.Close() - - addrs := createAddresses(t, cs, true) - - log.Info("Register dynamic meta esdt token") - - ticker := []byte("META" + "TICKER") - tokenName := []byte("tokenName") - - decimals := big.NewInt(10) - - txDataField := bytes.Join( - [][]byte{ - []byte("registerAndSetAllRolesDynamic"), - []byte(hex.EncodeToString(tokenName)), - []byte(hex.EncodeToString(ticker)), - []byte(hex.EncodeToString([]byte("META"))), - []byte(hex.EncodeToString(decimals.Bytes())), - }, - []byte("@"), - ) - - callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) - - nonce := uint64(0) - tx := &transaction.Transaction{ - Nonce: nonce, - SndAddr: addrs[0].Bytes, - RcvAddr: vm.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: callValue, - ChainID: []byte(configs.ChainID), - Version: 1, - } - nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - metaTokenID := txResult.Logs.Events[0].Topics[0] - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleTransfer), - } - setAddressEsdtRoles(t, cs, nonce, addrs[0], metaTokenID, roles) - nonce++ - - nftMetaData := txsFee.GetDefaultMetaData() - nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tx = esdtNftCreateTx(nonce, addrs[0].Bytes, metaTokenID, nftMetaData, 1) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) - - checkMetaData(t, cs, core.SystemAccountAddress, metaTokenID, shardID, nftMetaData) - - log.Info("Check that token type is Dynamic") - - scQuery := &process.SCQuery{ - ScAddress: vm.ESDTSCAddress, - FuncName: "getTokenProperties", - CallValue: big.NewInt(0), - Arguments: [][]byte{metaTokenID}, - } - result, _, err := cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ExecuteSCQuery(scQuery) - require.Nil(t, err) - require.Equal(t, "", result.ReturnMessage) - require.Equal(t, testsChainSimulator.OkReturnCode, result.ReturnCode) - - tokenType := result.ReturnData[1] - require.Equal(t, core.Dynamic+core.MetaESDT, string(tokenType)) - - log.Info("Check token roles") - - scQuery = &process.SCQuery{ - ScAddress: vm.ESDTSCAddress, - FuncName: "getAllAddressesAndRoles", - CallValue: big.NewInt(0), - Arguments: [][]byte{metaTokenID}, - } - result, _, err = cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ExecuteSCQuery(scQuery) - require.Nil(t, err) - require.Equal(t, "", result.ReturnMessage) - require.Equal(t, testsChainSimulator.OkReturnCode, result.ReturnCode) - - expectedRoles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleNFTBurn), - []byte(core.ESDTRoleNFTAddQuantity), - []byte(core.ESDTRoleNFTUpdateAttributes), - []byte(core.ESDTRoleNFTAddURI), - } - - checkTokenRoles(t, result.ReturnData, expectedRoles) -} - -func checkTokenRoles(t *testing.T, returnData [][]byte, expectedRoles [][]byte) { - for _, expRole := range expectedRoles { - found := false - - for _, item := range returnData { - if bytes.Equal(expRole, item) { - found = true - } - } - - require.True(t, found) - } -} - -func TestChainSimulator_NFTcreatedBeforeSaveToSystemAccountEnabled(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - cs, epochForDynamicNFT := getTestChainSimulatorWithSaveToSystemAccountDisabled(t, baseIssuingCost) - defer cs.Close() - - addrs := createAddresses(t, cs, false) - - log.Info("Initial setup: Create NFT that will have it's metadata saved to the user account") - - nftTicker := []byte("NFTTICKER") - tx := issueNonFungibleTx(0, addrs[0].Bytes, nftTicker, baseIssuingCost) - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - nftTokenID := txResult.Logs.Events[0].Topics[0] - - log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) - - nftMetaData := txsFee.GetDefaultMetaData() - nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - createTokenUpdateTokenIDAndTransfer(t, cs, addrs[0].Bytes, addrs[1].Bytes, nftTokenID, nftMetaData, epochForDynamicNFT, addrs[0]) - - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) - checkMetaData(t, cs, addrs[1].Bytes, nftTokenID, shardID, nftMetaData) - checkMetaDataNotInAcc(t, cs, addrs[0].Bytes, nftTokenID, shardID) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) -} - -func TestChainSimulator_SFTcreatedBeforeSaveToSystemAccountEnabled(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - cs, epochForDynamicNFT := getTestChainSimulatorWithSaveToSystemAccountDisabled(t, baseIssuingCost) - defer cs.Close() - - addrs := createAddresses(t, cs, false) - - log.Info("Initial setup: Create SFT that will have it's metadata saved to the user account") - - sftTicker := []byte("SFTTICKER") - tx := issueSemiFungibleTx(0, addrs[0].Bytes, sftTicker, baseIssuingCost) - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - sftTokenID := txResult.Logs.Events[0].Topics[0] - - log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) - - metaData := txsFee.GetDefaultMetaData() - metaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - createTokenUpdateTokenIDAndTransfer(t, cs, addrs[0].Bytes, addrs[1].Bytes, sftTokenID, metaData, epochForDynamicNFT, addrs[0]) - - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) - - checkMetaData(t, cs, core.SystemAccountAddress, sftTokenID, shardID, metaData) - checkMetaDataNotInAcc(t, cs, addrs[0].Bytes, sftTokenID, shardID) - checkMetaDataNotInAcc(t, cs, addrs[1].Bytes, sftTokenID, shardID) -} - -func TestChainSimulator_MetaESDTCreatedBeforeSaveToSystemAccountEnabled(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - cs, epochForDynamicNFT := getTestChainSimulatorWithSaveToSystemAccountDisabled(t, baseIssuingCost) - defer cs.Close() - - addrs := createAddresses(t, cs, false) - - log.Info("Initial setup: Create MetaESDT that will have it's metadata saved to the user account") - - metaTicker := []byte("METATICKER") - tx := issueMetaESDTTx(0, addrs[0].Bytes, metaTicker, baseIssuingCost) - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - metaTokenID := txResult.Logs.Events[0].Topics[0] - - log.Info("Issued MetaESDT token id", "tokenID", string(metaTokenID)) - - metaData := txsFee.GetDefaultMetaData() - metaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - createTokenUpdateTokenIDAndTransfer(t, cs, addrs[0].Bytes, addrs[1].Bytes, metaTokenID, metaData, epochForDynamicNFT, addrs[0]) - - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) - checkMetaData(t, cs, core.SystemAccountAddress, metaTokenID, shardID, metaData) - checkMetaDataNotInAcc(t, cs, addrs[0].Bytes, metaTokenID, shardID) - checkMetaDataNotInAcc(t, cs, addrs[1].Bytes, metaTokenID, shardID) -} - -func getTestChainSimulatorWithDynamicNFTEnabled(t *testing.T, baseIssuingCost string) (testsChainSimulator.ChainSimulator, int32) { - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 20, - } - - activationEpochForDynamicNFT := uint32(2) - - numOfShards := uint32(3) - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 0, - NumNodesWaitingListShard: 0, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.DynamicESDTEnableEpoch = activationEpochForDynamicNFT - cfg.SystemSCConfig.ESDTSystemSCConfig.BaseIssuingCost = baseIssuingCost - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpochForDynamicNFT)) - require.Nil(t, err) - - return cs, int32(activationEpochForDynamicNFT) -} - -func getTestChainSimulatorWithSaveToSystemAccountDisabled(t *testing.T, baseIssuingCost string) (testsChainSimulator.ChainSimulator, int32) { - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 20, - } - - activationEpochForSaveToSystemAccount := uint32(4) - activationEpochForDynamicNFT := uint32(6) - - numOfShards := uint32(3) - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 0, - NumNodesWaitingListShard: 0, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.OptimizeNFTStoreEnableEpoch = activationEpochForSaveToSystemAccount - cfg.EpochConfig.EnableEpochs.DynamicESDTEnableEpoch = activationEpochForDynamicNFT - cfg.SystemSCConfig.ESDTSystemSCConfig.BaseIssuingCost = baseIssuingCost - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpochForSaveToSystemAccount) - 2) - require.Nil(t, err) - - return cs, int32(activationEpochForDynamicNFT) -} - -func createTokenUpdateTokenIDAndTransfer( - t *testing.T, - cs testsChainSimulator.ChainSimulator, - originAddress []byte, - targetAddress []byte, - tokenID []byte, - metaData *txsFee.MetaData, - epochForDynamicNFT int32, - walletWithRoles dtos.WalletAddress, -) { - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleTransfer), - } - setAddressEsdtRoles(t, cs, 1, walletWithRoles, tokenID, roles) - - tx := esdtNftCreateTx(2, originAddress, tokenID, metaData, 1) - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - log.Info("check that the metadata is saved on the user account") - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(originAddress) - checkMetaData(t, cs, originAddress, tokenID, shardID, metaData) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, shardID) - - err = cs.GenerateBlocksUntilEpochIsReached(epochForDynamicNFT) - require.Nil(t, err) - - tx = updateTokenIDTx(3, originAddress, tokenID) - - log.Info("updating token id", "tokenID", tokenID) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - log.Info("transferring token id", "tokenID", tokenID) - - tx = esdtNFTTransferTx(4, originAddress, targetAddress, tokenID) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) -} - -func TestChainSimulator_ChangeToDynamic_OldTokens(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - - cs, epochForDynamicNFT := getTestChainSimulatorWithSaveToSystemAccountDisabled(t, baseIssuingCost) - defer cs.Close() - - addrs := createAddresses(t, cs, false) - - // issue metaESDT - metaESDTTicker := []byte("METATICKER") - nonce := uint64(0) - tx := issueMetaESDTTx(nonce, addrs[0].Bytes, metaESDTTicker, baseIssuingCost) - nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - metaESDTTokenID := txResult.Logs.Events[0].Topics[0] - - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleTransfer), - } - setAddressEsdtRoles(t, cs, nonce, addrs[0], metaESDTTokenID, roles) - nonce++ - - log.Info("Issued metaESDT token id", "tokenID", string(metaESDTTokenID)) - - // issue NFT - nftTicker := []byte("NFTTICKER") - tx = issueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - nftTokenID := txResult.Logs.Events[0].Topics[0] - setAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) - nonce++ - - log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) - - // issue SFT - sftTicker := []byte("SFTTICKER") - tx = issueSemiFungibleTx(nonce, addrs[0].Bytes, sftTicker, baseIssuingCost) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - sftTokenID := txResult.Logs.Events[0].Topics[0] - setAddressEsdtRoles(t, cs, nonce, addrs[0], sftTokenID, roles) - nonce++ - - log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) - - tokenIDs := [][]byte{ - nftTokenID, - sftTokenID, - metaESDTTokenID, - } - - nftMetaData := txsFee.GetDefaultMetaData() - nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - sftMetaData := txsFee.GetDefaultMetaData() - sftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - esdtMetaData := txsFee.GetDefaultMetaData() - esdtMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tokensMetadata := []*txsFee.MetaData{ - nftMetaData, - sftMetaData, - esdtMetaData, - } - - for i := range tokenIDs { - tx = esdtNftCreateTx(nonce, addrs[0].Bytes, tokenIDs[i], tokensMetadata[i], 1) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - } - - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) - - // meta data should be saved on account, since it is before `OptimizeNFTStoreEnableEpoch` - checkMetaData(t, cs, addrs[0].Bytes, nftTokenID, shardID, nftMetaData) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) - - checkMetaData(t, cs, addrs[0].Bytes, sftTokenID, shardID, sftMetaData) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, sftTokenID, shardID) - - checkMetaData(t, cs, addrs[0].Bytes, metaESDTTokenID, shardID, esdtMetaData) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, metaESDTTokenID, shardID) - - err = cs.GenerateBlocksUntilEpochIsReached(epochForDynamicNFT) - require.Nil(t, err) - - log.Info("Change to DYNAMIC type") - - // it will not be able to change nft to dynamic type - for i := range tokenIDs { - tx = changeToDynamicTx(nonce, addrs[0].Bytes, tokenIDs[i]) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - } - - for _, tokenID := range tokenIDs { - tx = updateTokenIDTx(nonce, addrs[0].Bytes, tokenID) - - log.Info("updating token id", "tokenID", tokenID) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - } - - for _, tokenID := range tokenIDs { - log.Info("transfering token id", "tokenID", tokenID) - - tx = esdtNFTTransferTx(nonce, addrs[0].Bytes, addrs[1].Bytes, tokenID) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - nonce++ - } - - checkMetaData(t, cs, core.SystemAccountAddress, sftTokenID, shardID, sftMetaData) - checkMetaDataNotInAcc(t, cs, addrs[0].Bytes, sftTokenID, shardID) - checkMetaDataNotInAcc(t, cs, addrs[1].Bytes, sftTokenID, shardID) - - checkMetaData(t, cs, core.SystemAccountAddress, metaESDTTokenID, shardID, esdtMetaData) - checkMetaDataNotInAcc(t, cs, addrs[0].Bytes, metaESDTTokenID, shardID) - checkMetaDataNotInAcc(t, cs, addrs[1].Bytes, metaESDTTokenID, shardID) - - checkMetaData(t, cs, addrs[1].Bytes, nftTokenID, shardID, nftMetaData) - checkMetaDataNotInAcc(t, cs, addrs[0].Bytes, nftTokenID, shardID) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) -} - -func TestChainSimulator_CreateAndPause_NFT(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 20, - } - - activationEpoch := uint32(4) - - baseIssuingCost := "1000" - - numOfShards := uint32(3) - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 0, - NumNodesWaitingListShard: 0, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.DynamicESDTEnableEpoch = activationEpoch - cfg.SystemSCConfig.ESDTSystemSCConfig.BaseIssuingCost = baseIssuingCost - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - addrs := createAddresses(t, cs, false) - - err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch) - 1) - require.Nil(t, err) - - // issue NFT - nftTicker := []byte("NFTTICKER") - callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) - - txDataField := bytes.Join( - [][]byte{ - []byte("issueNonFungible"), - []byte(hex.EncodeToString([]byte("asdname"))), - []byte(hex.EncodeToString(nftTicker)), - []byte(hex.EncodeToString([]byte("canPause"))), - []byte(hex.EncodeToString([]byte("true"))), - }, - []byte("@"), - ) - - nonce := uint64(0) - tx := &transaction.Transaction{ - Nonce: nonce, - SndAddr: addrs[0].Bytes, - RcvAddr: core.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: callValue, - ChainID: []byte(configs.ChainID), - Version: 1, - } - nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleTransfer), - } - nftTokenID := txResult.Logs.Events[0].Topics[0] - setAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) - nonce++ - - log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) - - nftMetaData := txsFee.GetDefaultMetaData() - nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tx = esdtNftCreateTx(nonce, addrs[0].Bytes, nftTokenID, nftMetaData, 1) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - log.Info("check that the metadata for all tokens is saved on the system account") - - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) - - checkMetaData(t, cs, core.SystemAccountAddress, nftTokenID, shardID, nftMetaData) - - log.Info("Pause all tokens") - - scQuery := &process.SCQuery{ - ScAddress: vm.ESDTSCAddress, - CallerAddr: addrs[0].Bytes, - FuncName: "pause", - CallValue: big.NewInt(0), - Arguments: [][]byte{nftTokenID}, - } - result, _, err := cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ExecuteSCQuery(scQuery) - require.Nil(t, err) - require.Equal(t, "", result.ReturnMessage) - require.Equal(t, testsChainSimulator.OkReturnCode, result.ReturnCode) - - log.Info("wait for DynamicEsdtFlag activation") - - err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch)) - require.Nil(t, err) - - log.Info("make an updateTokenID@tokenID function call on the ESDTSystem SC for all token types") - - tx = updateTokenIDTx(nonce, addrs[0].Bytes, nftTokenID) - nonce++ - - log.Info("updating token id", "tokenID", nftTokenID) - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("check that the metadata for all tokens is saved on the system account") - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - checkMetaData(t, cs, core.SystemAccountAddress, nftTokenID, shardID, nftMetaData) - - log.Info("transfer the tokens to another account") - - log.Info("transfering token id", "tokenID", nftTokenID) - - tx = esdtNFTTransferTx(nonce, addrs[0].Bytes, addrs[1].Bytes, nftTokenID) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("check that the metaData for the NFT is still on the system account") - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - shardID = cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[2].Bytes) - - checkMetaData(t, cs, addrs[1].Bytes, nftTokenID, shardID, nftMetaData) - checkMetaDataNotInAcc(t, cs, addrs[0].Bytes, nftTokenID, shardID) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) -} - -func TestChainSimulator_CreateAndPauseTokens_DynamicNFT(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 20, - } - - activationEpoch := uint32(4) - - baseIssuingCost := "1000" - - numOfShards := uint32(3) - cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 0, - NumNodesWaitingListShard: 0, - AlterConfigsFunction: func(cfg *config.Configs) { - cfg.EpochConfig.EnableEpochs.DynamicESDTEnableEpoch = activationEpoch - cfg.SystemSCConfig.ESDTSystemSCConfig.BaseIssuingCost = baseIssuingCost - }, - }) - require.Nil(t, err) - require.NotNil(t, cs) - - defer cs.Close() - - addrs := createAddresses(t, cs, false) - - err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch) - 1) - require.Nil(t, err) - - log.Info("Step 2. wait for DynamicEsdtFlag activation") - - err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch)) - require.Nil(t, err) - - // register dynamic NFT - nftTicker := []byte("NFTTICKER") - nftTokenName := []byte("tokenName") - - txDataField := bytes.Join( - [][]byte{ - []byte("registerDynamic"), - []byte(hex.EncodeToString(nftTokenName)), - []byte(hex.EncodeToString(nftTicker)), - []byte(hex.EncodeToString([]byte("NFT"))), - []byte(hex.EncodeToString([]byte("canPause"))), - []byte(hex.EncodeToString([]byte("true"))), - }, - []byte("@"), - ) - - callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) - - nonce := uint64(0) - tx := &transaction.Transaction{ - Nonce: nonce, - SndAddr: addrs[0].Bytes, - RcvAddr: vm.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: callValue, - ChainID: []byte(configs.ChainID), - Version: 1, - } - nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleTransfer), - []byte(core.ESDTRoleNFTUpdate), - } - - nftTokenID := txResult.Logs.Events[0].Topics[0] - setAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) - nonce++ - - log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) - - nftMetaData := txsFee.GetDefaultMetaData() - nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tx = esdtNftCreateTx(nonce, addrs[0].Bytes, nftTokenID, nftMetaData, 1) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - log.Info("Step 1. check that the metadata for the Dynamic NFT is saved on the user account") - - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) - - checkMetaData(t, cs, addrs[0].Bytes, nftTokenID, shardID, nftMetaData) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) - - log.Info("Step 1b. Pause all tokens") - - scQuery := &process.SCQuery{ - ScAddress: vm.ESDTSCAddress, - CallerAddr: addrs[0].Bytes, - FuncName: "pause", - CallValue: big.NewInt(0), - Arguments: [][]byte{nftTokenID}, - } - result, _, err := cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler().ExecuteSCQuery(scQuery) - require.Nil(t, err) - require.Equal(t, "", result.ReturnMessage) - require.Equal(t, testsChainSimulator.OkReturnCode, result.ReturnCode) - - log.Info("check that the metadata for the Dynamic NFT is saved on the user account") - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - checkMetaData(t, cs, addrs[0].Bytes, nftTokenID, shardID, nftMetaData) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) - - log.Info("transfering token id", "tokenID", nftTokenID) - - tx = esdtNFTTransferTx(nonce, addrs[0].Bytes, addrs[1].Bytes, nftTokenID) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("check that the metaData for the NFT is on the new user account") - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - shardID = cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[2].Bytes) - - checkMetaData(t, cs, addrs[1].Bytes, nftTokenID, shardID, nftMetaData) - checkMetaDataNotInAcc(t, cs, addrs[0].Bytes, nftTokenID, shardID) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, nftTokenID, shardID) -} - -func TestChainSimulator_CheckRolesWhichHasToBeSingular(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - - cs, _ := getTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) - defer cs.Close() - - addrs := createAddresses(t, cs, true) - - // register dynamic NFT - nftTicker := []byte("NFTTICKER") - nftTokenName := []byte("tokenName") - - txDataField := bytes.Join( - [][]byte{ - []byte("registerDynamic"), - []byte(hex.EncodeToString(nftTokenName)), - []byte(hex.EncodeToString(nftTicker)), - []byte(hex.EncodeToString([]byte("NFT"))), - []byte(hex.EncodeToString([]byte("canPause"))), - []byte(hex.EncodeToString([]byte("true"))), - }, - []byte("@"), - ) - - callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) - - nonce := uint64(0) - tx := &transaction.Transaction{ - Nonce: nonce, - SndAddr: addrs[0].Bytes, - RcvAddr: vm.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: callValue, - ChainID: []byte(configs.ChainID), - Version: 1, - } - nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - nftTokenID := txResult.Logs.Events[0].Topics[0] - - log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) - - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleNFTUpdateAttributes), - []byte(core.ESDTRoleNFTAddURI), - []byte(core.ESDTRoleSetNewURI), - []byte(core.ESDTRoleModifyCreator), - []byte(core.ESDTRoleModifyRoyalties), - []byte(core.ESDTRoleNFTRecreate), - []byte(core.ESDTRoleNFTUpdate), - } - setAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) - nonce++ - - for _, role := range roles { - tx = setSpecialRoleTx(nonce, addrs[0].Bytes, addrs[1].Bytes, nftTokenID, [][]byte{role}) - nonce++ - - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - if txResult.Logs != nil && len(txResult.Logs.Events) > 0 { - returnMessage := string(txResult.Logs.Events[0].Topics[1]) - require.True(t, strings.Contains(returnMessage, "already exists")) - } else { - require.Fail(t, "should have been return error message") - } - } -} - -func TestChainSimulator_metaESDT_mergeMetaDataFromMultipleUpdates(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - cs, _ := getTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) - defer cs.Close() - marshaller := cs.GetNodeHandler(0).GetCoreComponents().InternalMarshalizer() - addrs := createAddresses(t, cs, true) - - log.Info("Register dynamic metaESDT token") - - metaTicker := []byte("METATICKER") - metaTokenName := []byte("tokenName") - - decimals := big.NewInt(15) - txDataField := bytes.Join( - [][]byte{ - []byte("registerDynamic"), - []byte(hex.EncodeToString(metaTokenName)), - []byte(hex.EncodeToString(metaTicker)), - []byte(hex.EncodeToString([]byte("META"))), - []byte(hex.EncodeToString(decimals.Bytes())), - }, - []byte("@"), - ) - - callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) - - shard0Nonce := uint64(0) - tx := &transaction.Transaction{ - Nonce: shard0Nonce, - SndAddr: addrs[0].Bytes, - RcvAddr: vm.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: callValue, - ChainID: []byte(configs.ChainID), - Version: 1, - } - shard0Nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - tokenID := txResult.Logs.Events[0].Topics[0] - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleNFTAddQuantity), - []byte(core.ESDTRoleTransfer), - []byte(core.ESDTRoleNFTUpdate), - } - setAddressEsdtRoles(t, cs, shard0Nonce, addrs[0], tokenID, roles) - shard0Nonce++ - - metaData := txsFee.GetDefaultMetaData() - metaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tx = esdtNftCreateTx(shard0Nonce, addrs[0].Bytes, tokenID, metaData, 2) - shard0Nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - shardID := cs.GetNodeHandler(0).GetProcessComponents().ShardCoordinator().ComputeId(addrs[0].Bytes) - - checkMetaData(t, cs, core.SystemAccountAddress, tokenID, shardID, metaData) - checkReservedField(t, cs, core.SystemAccountAddress, tokenID, shardID, []byte{1}) - - log.Info("send metaEsdt cross shard") - - tx = esdtNFTTransferTx(shard0Nonce, addrs[0].Bytes, addrs[1].Bytes, tokenID) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - shard0Nonce++ - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - log.Info("update metaData on shard 0") - - newMetaData := &txsFee.MetaData{} - newMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - newMetaData.Name = []byte(hex.EncodeToString([]byte("name2"))) - newMetaData.Hash = []byte(hex.EncodeToString([]byte("hash2"))) - newMetaData.Attributes = []byte(hex.EncodeToString([]byte("attributes2"))) - - tx = esdtMetaDataUpdateTx(tokenID, newMetaData, shard0Nonce, addrs[0].Bytes) - shard0Nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - expectedMetaData := txsFee.GetDefaultMetaData() - expectedMetaData.Nonce = newMetaData.Nonce - expectedMetaData.Name = newMetaData.Name - expectedMetaData.Hash = newMetaData.Hash - expectedMetaData.Attributes = newMetaData.Attributes - - round := cs.GetNodeHandler(0).GetChainHandler().GetCurrentBlockHeader().GetRound() - reserved := &esdt.MetaDataVersion{ - Name: round, - Creator: round, - Hash: round, - Attributes: round, - } - firstVersion, _ := marshaller.Marshal(reserved) - - checkMetaData(t, cs, core.SystemAccountAddress, tokenID, 0, expectedMetaData) - checkReservedField(t, cs, core.SystemAccountAddress, tokenID, 0, firstVersion) - checkMetaData(t, cs, core.SystemAccountAddress, tokenID, 1, metaData) - checkReservedField(t, cs, core.SystemAccountAddress, tokenID, 1, []byte{1}) - - log.Info("send the update role to shard 2") - - shard0Nonce = transferSpecialRoleToAddr(t, cs, shard0Nonce, tokenID, addrs[0].Bytes, addrs[2].Bytes, []byte(core.ESDTRoleNFTUpdate)) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - log.Info("update metaData on shard 2") - - newMetaData2 := &txsFee.MetaData{} - newMetaData2.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - newMetaData2.Uris = [][]byte{[]byte(hex.EncodeToString([]byte("uri5"))), []byte(hex.EncodeToString([]byte("uri6"))), []byte(hex.EncodeToString([]byte("uri7")))} - newMetaData2.Royalties = []byte(hex.EncodeToString(big.NewInt(15).Bytes())) - - tx = esdtMetaDataUpdateTx(tokenID, newMetaData2, 0, addrs[2].Bytes) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - checkMetaData(t, cs, core.SystemAccountAddress, tokenID, 0, expectedMetaData) - checkReservedField(t, cs, core.SystemAccountAddress, tokenID, 0, firstVersion) - checkMetaData(t, cs, core.SystemAccountAddress, tokenID, 1, metaData) - checkReservedField(t, cs, core.SystemAccountAddress, tokenID, 1, []byte{1}) - - retrievedMetaData := getMetaDataFromAcc(t, cs, core.SystemAccountAddress, tokenID, 2) - require.Equal(t, uint64(1), retrievedMetaData.Nonce) - require.Equal(t, 0, len(retrievedMetaData.Name)) - require.Equal(t, addrs[2].Bytes, retrievedMetaData.Creator) - require.Equal(t, newMetaData2.Royalties, []byte(hex.EncodeToString(big.NewInt(int64(retrievedMetaData.Royalties)).Bytes()))) - require.Equal(t, 0, len(retrievedMetaData.Hash)) - require.Equal(t, 3, len(retrievedMetaData.URIs)) - for i, uri := range newMetaData2.Uris { - require.Equal(t, uri, []byte(hex.EncodeToString(retrievedMetaData.URIs[i]))) - } - require.Equal(t, 0, len(retrievedMetaData.Attributes)) - - round2 := cs.GetNodeHandler(2).GetChainHandler().GetCurrentBlockHeader().GetRound() - reserved = &esdt.MetaDataVersion{ - URIs: round2, - Creator: round2, - Royalties: round2, - } - secondVersion, _ := cs.GetNodeHandler(shardID).GetCoreComponents().InternalMarshalizer().Marshal(reserved) - checkReservedField(t, cs, core.SystemAccountAddress, tokenID, 2, secondVersion) - - log.Info("transfer from shard 0 to shard 1 - should merge metaData") - - tx = esdtNFTTransferTx(shard0Nonce, addrs[0].Bytes, addrs[1].Bytes, tokenID) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - shard0Nonce++ - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - checkMetaData(t, cs, core.SystemAccountAddress, tokenID, 0, expectedMetaData) - checkReservedField(t, cs, core.SystemAccountAddress, tokenID, 0, firstVersion) - checkMetaData(t, cs, core.SystemAccountAddress, tokenID, 1, expectedMetaData) - checkReservedField(t, cs, core.SystemAccountAddress, tokenID, 1, firstVersion) - - log.Info("transfer from shard 1 to shard 2 - should merge metaData") - - tx = setSpecialRoleTx(shard0Nonce, addrs[0].Bytes, addrs[1].Bytes, tokenID, [][]byte{[]byte(core.ESDTRoleTransfer)}) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - shard0Nonce++ - - tx = esdtNFTTransferTx(0, addrs[1].Bytes, addrs[2].Bytes, tokenID) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - checkMetaData(t, cs, core.SystemAccountAddress, tokenID, 0, expectedMetaData) - checkReservedField(t, cs, core.SystemAccountAddress, tokenID, 0, firstVersion) - checkMetaData(t, cs, core.SystemAccountAddress, tokenID, 1, expectedMetaData) - checkReservedField(t, cs, core.SystemAccountAddress, tokenID, 1, firstVersion) - - latestMetaData := txsFee.GetDefaultMetaData() - latestMetaData.Nonce = expectedMetaData.Nonce - latestMetaData.Name = expectedMetaData.Name - latestMetaData.Royalties = newMetaData2.Royalties - latestMetaData.Hash = expectedMetaData.Hash - latestMetaData.Attributes = expectedMetaData.Attributes - latestMetaData.Uris = newMetaData2.Uris - checkMetaData(t, cs, core.SystemAccountAddress, tokenID, 2, latestMetaData) - - reserved = &esdt.MetaDataVersion{ - Name: round, - Creator: round2, - Royalties: round2, - Hash: round, - URIs: round2, - Attributes: round, - } - thirdVersion, _ := cs.GetNodeHandler(shardID).GetCoreComponents().InternalMarshalizer().Marshal(reserved) - checkReservedField(t, cs, core.SystemAccountAddress, tokenID, 2, thirdVersion) - - log.Info("transfer from shard 2 to shard 0 - should update metaData") - - tx = setSpecialRoleTx(shard0Nonce, addrs[0].Bytes, addrs[2].Bytes, tokenID, [][]byte{[]byte(core.ESDTRoleTransfer)}) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - tx = esdtNFTTransferTx(1, addrs[2].Bytes, addrs[0].Bytes, tokenID) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - checkMetaData(t, cs, core.SystemAccountAddress, tokenID, 0, latestMetaData) - checkReservedField(t, cs, core.SystemAccountAddress, tokenID, 0, thirdVersion) - checkMetaData(t, cs, core.SystemAccountAddress, tokenID, 1, expectedMetaData) - checkReservedField(t, cs, core.SystemAccountAddress, tokenID, 1, firstVersion) - checkMetaData(t, cs, core.SystemAccountAddress, tokenID, 2, latestMetaData) - checkReservedField(t, cs, core.SystemAccountAddress, tokenID, 2, thirdVersion) - - log.Info("transfer from shard 1 to shard 0 - liquidity should be updated") - - tx = esdtNFTTransferTx(1, addrs[1].Bytes, addrs[0].Bytes, tokenID) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - checkMetaData(t, cs, core.SystemAccountAddress, tokenID, 0, latestMetaData) - checkReservedField(t, cs, core.SystemAccountAddress, tokenID, 0, thirdVersion) - checkMetaData(t, cs, core.SystemAccountAddress, tokenID, 1, expectedMetaData) - checkReservedField(t, cs, core.SystemAccountAddress, tokenID, 1, firstVersion) - checkMetaData(t, cs, core.SystemAccountAddress, tokenID, 2, latestMetaData) - checkReservedField(t, cs, core.SystemAccountAddress, tokenID, 2, thirdVersion) -} - -func unsetSpecialRole( - nonce uint64, - sndAddr []byte, - address []byte, - token []byte, - role []byte, -) *transaction.Transaction { - txDataBytes := [][]byte{ - []byte("unSetSpecialRole"), - []byte(hex.EncodeToString(token)), - []byte(hex.EncodeToString(address)), - []byte(hex.EncodeToString(role)), - } - - txDataField := bytes.Join( - txDataBytes, - []byte("@"), - ) - - return &transaction.Transaction{ - Nonce: nonce, - SndAddr: sndAddr, - RcvAddr: vm.ESDTSCAddress, - GasLimit: 60_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: big.NewInt(0), - ChainID: []byte(configs.ChainID), - Version: 1, - } -} - -func esdtMetaDataUpdateTx(tokenID []byte, metaData *txsFee.MetaData, nonce uint64, address []byte) *transaction.Transaction { - txData := [][]byte{ - []byte(core.ESDTMetaDataUpdate), - []byte(hex.EncodeToString(tokenID)), - metaData.Nonce, - metaData.Name, - metaData.Royalties, - metaData.Hash, - metaData.Attributes, - } - if len(metaData.Uris) > 0 { - txData = append(txData, metaData.Uris...) - } else { - txData = append(txData, nil) - } - - txDataField := bytes.Join( - txData, - []byte("@"), - ) - - tx := &transaction.Transaction{ - Nonce: nonce, - SndAddr: address, - RcvAddr: address, - GasLimit: 10_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: big.NewInt(0), - ChainID: []byte(configs.ChainID), - Version: 1, - } - - return tx -} - -func transferSpecialRoleToAddr( - t *testing.T, - cs testsChainSimulator.ChainSimulator, - nonce uint64, - tokenID []byte, - sndAddr []byte, - dstAddr []byte, - role []byte, -) uint64 { - tx := unsetSpecialRole(nonce, sndAddr, sndAddr, tokenID, role) - nonce++ - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - tx = setSpecialRoleTx(nonce, sndAddr, dstAddr, tokenID, [][]byte{role}) - nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - return nonce -} - -func TestChainSimulator_dynamicNFT_mergeMetaDataFromMultipleUpdates(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - cs, _ := getTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) - defer cs.Close() - - addrs := createAddresses(t, cs, true) - - log.Info("Register dynamic NFT token") - - ticker := []byte("NFTTICKER") - tokenName := []byte("tokenName") - - txDataField := bytes.Join( - [][]byte{ - []byte("registerDynamic"), - []byte(hex.EncodeToString(tokenName)), - []byte(hex.EncodeToString(ticker)), - []byte(hex.EncodeToString([]byte("NFT"))), - }, - []byte("@"), - ) - - callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) - - shard0Nonce := uint64(0) - tx := &transaction.Transaction{ - Nonce: shard0Nonce, - SndAddr: addrs[0].Bytes, - RcvAddr: vm.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: callValue, - ChainID: []byte(configs.ChainID), - Version: 1, - } - shard0Nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - tokenID := txResult.Logs.Events[0].Topics[0] - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleTransfer), - []byte(core.ESDTRoleNFTUpdate), - } - setAddressEsdtRoles(t, cs, shard0Nonce, addrs[0], tokenID, roles) - shard0Nonce++ - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - metaData := txsFee.GetDefaultMetaData() - metaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tx = esdtNftCreateTx(shard0Nonce, addrs[0].Bytes, tokenID, metaData, 1) - shard0Nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 0) - checkMetaData(t, cs, addrs[0].Bytes, tokenID, 0, metaData) - - log.Info("give update role to another account and update metaData") - - shard0Nonce = transferSpecialRoleToAddr(t, cs, shard0Nonce, tokenID, addrs[0].Bytes, addrs[1].Bytes, []byte(core.ESDTRoleNFTUpdate)) - - newMetaData := &txsFee.MetaData{} - newMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - newMetaData.Name = []byte(hex.EncodeToString([]byte("name2"))) - newMetaData.Hash = []byte(hex.EncodeToString([]byte("hash2"))) - newMetaData.Royalties = []byte(hex.EncodeToString(big.NewInt(15).Bytes())) - - tx = esdtMetaDataUpdateTx(tokenID, newMetaData, 0, addrs[1].Bytes) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 0) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 1) - checkMetaData(t, cs, addrs[0].Bytes, tokenID, 0, metaData) - newMetaData.Attributes = []byte{} - checkMetaData(t, cs, addrs[1].Bytes, tokenID, 1, newMetaData) - - log.Info("transfer nft - should merge metaData") - - tx = esdtNFTTransferTx(shard0Nonce, addrs[0].Bytes, addrs[1].Bytes, tokenID) - shard0Nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - mergedMetaData := txsFee.GetDefaultMetaData() - mergedMetaData.Nonce = metaData.Nonce - mergedMetaData.Name = newMetaData.Name - mergedMetaData.Hash = newMetaData.Hash - mergedMetaData.Royalties = newMetaData.Royalties - - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 0) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 1) - checkMetaDataNotInAcc(t, cs, addrs[0].Bytes, tokenID, 0) - checkMetaData(t, cs, addrs[1].Bytes, tokenID, 1, mergedMetaData) - - log.Info("transfer nft - should remove metaData from sender") - - tx = setSpecialRoleTx(shard0Nonce, addrs[0].Bytes, addrs[1].Bytes, tokenID, [][]byte{[]byte(core.ESDTRoleTransfer)}) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - tx = esdtNFTTransferTx(1, addrs[1].Bytes, addrs[2].Bytes, tokenID) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 0) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 1) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 2) - checkMetaDataNotInAcc(t, cs, addrs[0].Bytes, tokenID, 0) - checkMetaDataNotInAcc(t, cs, addrs[1].Bytes, tokenID, 1) - checkMetaData(t, cs, addrs[2].Bytes, tokenID, 2, mergedMetaData) -} - -func TestChainSimulator_dynamicNFT_changeMetaDataForOneNFTShouldNotChangeOtherNonces(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - cs, _ := getTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) - defer cs.Close() - - addrs := createAddresses(t, cs, true) - - log.Info("Register dynamic NFT token") - - ticker := []byte("NFTTICKER") - tokenName := []byte("tokenName") - - txDataField := bytes.Join( - [][]byte{ - []byte("registerDynamic"), - []byte(hex.EncodeToString(tokenName)), - []byte(hex.EncodeToString(ticker)), - []byte(hex.EncodeToString([]byte("NFT"))), - }, - []byte("@"), - ) - - callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) - - shard0Nonce := uint64(0) - tx := &transaction.Transaction{ - Nonce: shard0Nonce, - SndAddr: addrs[0].Bytes, - RcvAddr: vm.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: callValue, - ChainID: []byte(configs.ChainID), - Version: 1, - } - shard0Nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - tokenID := txResult.Logs.Events[0].Topics[0] - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleTransfer), - []byte(core.ESDTRoleNFTUpdate), - } - setAddressEsdtRoles(t, cs, shard0Nonce, addrs[0], tokenID, roles) - shard0Nonce++ - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - metaData := txsFee.GetDefaultMetaData() - metaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tx = esdtNftCreateTx(shard0Nonce, addrs[0].Bytes, tokenID, metaData, 1) - shard0Nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - metaData.Nonce = []byte(hex.EncodeToString(big.NewInt(2).Bytes())) - tx = esdtNftCreateTx(shard0Nonce, addrs[0].Bytes, tokenID, metaData, 1) - shard0Nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - log.Info("give update role to another account and update metaData for nonce 2") - - shard0Nonce = transferSpecialRoleToAddr(t, cs, shard0Nonce, tokenID, addrs[0].Bytes, addrs[1].Bytes, []byte(core.ESDTRoleNFTUpdate)) - - newMetaData := &txsFee.MetaData{} - newMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(2).Bytes())) - newMetaData.Name = []byte(hex.EncodeToString([]byte("name2"))) - newMetaData.Hash = []byte(hex.EncodeToString([]byte("hash2"))) - newMetaData.Royalties = []byte(hex.EncodeToString(big.NewInt(15).Bytes())) - - tx = esdtMetaDataUpdateTx(tokenID, newMetaData, 0, addrs[1].Bytes) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - log.Info("transfer nft with nonce 1 - should not merge metaData") - - tx = esdtNFTTransferTx(shard0Nonce, addrs[0].Bytes, addrs[1].Bytes, tokenID) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 0) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 1) - metaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - checkMetaData(t, cs, addrs[1].Bytes, tokenID, 1, metaData) -} - -func TestChainSimulator_dynamicNFT_updateBeforeCreateOnSameAccountShouldOverwrite(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - cs, _ := getTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) - defer cs.Close() - - addrs := createAddresses(t, cs, true) - - log.Info("Register dynamic NFT token") - - ticker := []byte("NFTTICKER") - tokenName := []byte("tokenName") - - txDataField := bytes.Join( - [][]byte{ - []byte("registerDynamic"), - []byte(hex.EncodeToString(tokenName)), - []byte(hex.EncodeToString(ticker)), - []byte(hex.EncodeToString([]byte("NFT"))), - }, - []byte("@"), - ) - - callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) - - shard0Nonce := uint64(0) - tx := &transaction.Transaction{ - Nonce: shard0Nonce, - SndAddr: addrs[0].Bytes, - RcvAddr: vm.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: callValue, - ChainID: []byte(configs.ChainID), - Version: 1, - } - shard0Nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - tokenID := txResult.Logs.Events[0].Topics[0] - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleTransfer), - []byte(core.ESDTRoleNFTUpdate), - } - setAddressEsdtRoles(t, cs, shard0Nonce, addrs[0], tokenID, roles) - shard0Nonce++ - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - log.Info("update meta data for a token that is not yet created") - - newMetaData := &txsFee.MetaData{} - newMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - newMetaData.Name = []byte(hex.EncodeToString([]byte("name2"))) - newMetaData.Hash = []byte(hex.EncodeToString([]byte("hash2"))) - newMetaData.Royalties = []byte(hex.EncodeToString(big.NewInt(15).Bytes())) - - tx = esdtMetaDataUpdateTx(tokenID, newMetaData, shard0Nonce, addrs[0].Bytes) - shard0Nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 1) - newMetaData.Attributes = []byte{} - newMetaData.Uris = [][]byte{} - checkMetaData(t, cs, addrs[0].Bytes, tokenID, 0, newMetaData) - - log.Info("create nft with the same nonce - should overwrite the metadata") - - metaData := txsFee.GetDefaultMetaData() - metaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tx = esdtNftCreateTx(shard0Nonce, addrs[0].Bytes, tokenID, metaData, 1) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 0) - checkMetaData(t, cs, addrs[0].Bytes, tokenID, 0, metaData) -} - -func TestChainSimulator_dynamicNFT_updateBeforeCreateOnDifferentAccountsShouldMergeMetaDataWhenTransferred(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - baseIssuingCost := "1000" - cs, _ := getTestChainSimulatorWithDynamicNFTEnabled(t, baseIssuingCost) - defer cs.Close() - - addrs := createAddresses(t, cs, true) - - log.Info("Register dynamic NFT token") - - ticker := []byte("NFTTICKER") - tokenName := []byte("tokenName") - - txDataField := bytes.Join( - [][]byte{ - []byte("registerDynamic"), - []byte(hex.EncodeToString(tokenName)), - []byte(hex.EncodeToString(ticker)), - []byte(hex.EncodeToString([]byte("NFT"))), - }, - []byte("@"), - ) - - callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10) - - shard0Nonce := uint64(0) - tx := &transaction.Transaction{ - Nonce: shard0Nonce, - SndAddr: addrs[0].Bytes, - RcvAddr: vm.ESDTSCAddress, - GasLimit: 100_000_000, - GasPrice: minGasPrice, - Signature: []byte("dummySig"), - Data: txDataField, - Value: callValue, - ChainID: []byte(configs.ChainID), - Version: 1, - } - shard0Nonce++ - - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - - require.Equal(t, "success", txResult.Status.String()) - - tokenID := txResult.Logs.Events[0].Topics[0] - roles := [][]byte{ - []byte(core.ESDTRoleNFTCreate), - []byte(core.ESDTRoleTransfer), - []byte(core.ESDTRoleNFTUpdate), - } - setAddressEsdtRoles(t, cs, shard0Nonce, addrs[0], tokenID, roles) - shard0Nonce++ - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - log.Info("transfer update role to another address") - - shard0Nonce = transferSpecialRoleToAddr(t, cs, shard0Nonce, tokenID, addrs[0].Bytes, addrs[1].Bytes, []byte(core.ESDTRoleNFTUpdate)) - - log.Info("update meta data for a token that is not yet created") - - newMetaData := &txsFee.MetaData{} - newMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - newMetaData.Name = []byte(hex.EncodeToString([]byte("name2"))) - newMetaData.Hash = []byte(hex.EncodeToString([]byte("hash2"))) - newMetaData.Royalties = []byte(hex.EncodeToString(big.NewInt(15).Bytes())) - - tx = esdtMetaDataUpdateTx(tokenID, newMetaData, 0, addrs[1].Bytes) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 0) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 1) - checkMetaDataNotInAcc(t, cs, addrs[0].Bytes, tokenID, 0) - newMetaData.Attributes = []byte{} - newMetaData.Uris = [][]byte{} - checkMetaData(t, cs, addrs[1].Bytes, tokenID, 1, newMetaData) - - log.Info("create nft with the same nonce on different account") - - metaData := txsFee.GetDefaultMetaData() - metaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - - tx = esdtNftCreateTx(shard0Nonce, addrs[0].Bytes, tokenID, metaData, 1) - shard0Nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 0) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 1) - checkMetaData(t, cs, addrs[0].Bytes, tokenID, 0, metaData) - checkMetaData(t, cs, addrs[1].Bytes, tokenID, 1, newMetaData) - - log.Info("transfer dynamic NFT to the updated account") - - tx = esdtNFTTransferTx(shard0Nonce, addrs[0].Bytes, addrs[1].Bytes, tokenID) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) - require.Nil(t, err) - require.NotNil(t, txResult) - require.Equal(t, "success", txResult.Status.String()) - - err = cs.GenerateBlocks(10) - require.Nil(t, err) - - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 0) - checkMetaDataNotInAcc(t, cs, core.SystemAccountAddress, tokenID, 1) - checkMetaDataNotInAcc(t, cs, addrs[0].Bytes, tokenID, 0) - newMetaData.Attributes = metaData.Attributes - newMetaData.Uris = metaData.Uris - checkMetaData(t, cs, addrs[1].Bytes, tokenID, 1, newMetaData) -} diff --git a/integrationTests/chainSimulator/vm/esdtTokens_test.go b/integrationTests/chainSimulator/vm/esdtTokens/esdtTokens_test.go similarity index 67% rename from integrationTests/chainSimulator/vm/esdtTokens_test.go rename to integrationTests/chainSimulator/vm/esdtTokens/esdtTokens_test.go index a5505de14ff..a74fa21fd46 100644 --- a/integrationTests/chainSimulator/vm/esdtTokens_test.go +++ b/integrationTests/chainSimulator/vm/esdtTokens/esdtTokens_test.go @@ -1,4 +1,4 @@ -package vm +package esdtTokens import ( "encoding/hex" @@ -7,15 +7,16 @@ import ( "math/big" "net/http" "testing" - "time" "github.com/multiversx/mx-chain-core-go/core" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/api/groups" "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator/vm" "github.com/multiversx/mx-chain-go/integrationTests/vm/txsFee" "github.com/multiversx/mx-chain-go/node/chainSimulator" "github.com/multiversx/mx-chain-go/node/chainSimulator/components/api" - "github.com/stretchr/testify/require" ) type esdtTokensCompleteResponseData struct { @@ -33,31 +34,26 @@ func TestChainSimulator_Api_TokenType(t *testing.T) { t.Skip("this is not a short test") } - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 20, - } - activationEpoch := uint32(2) baseIssuingCost := "1000" numOfShards := uint32(3) cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewFreePortAPIConfigurator("localhost"), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 0, - NumNodesWaitingListShard: 0, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: vm.DefaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: vm.RoundDurationInMillis, + SupernovaRoundDurationInMillis: vm.SupernovaRoundDurationInMillis, + RoundsPerEpoch: vm.RoundsPerEpoch, + SupernovaRoundsPerEpoch: vm.SupernovaRoundsPerEpoch, + ApiInterface: api.NewFreePortAPIConfigurator("localhost"), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 0, + NumNodesWaitingListShard: 0, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.DynamicESDTEnableEpoch = activationEpoch cfg.SystemSCConfig.ESDTSystemSCConfig.BaseIssuingCost = baseIssuingCost @@ -71,9 +67,9 @@ func TestChainSimulator_Api_TokenType(t *testing.T) { err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch)) require.Nil(t, err) - log.Info("Initial setup: Create tokens") + vm.Log.Info("Initial setup: Create tokens") - addrs := createAddresses(t, cs, false) + addrs := vm.CreateAddresses(t, cs, false) roles := [][]byte{ []byte(core.ESDTRoleNFTCreate), @@ -83,26 +79,26 @@ func TestChainSimulator_Api_TokenType(t *testing.T) { // issue fungible fungibleTicker := []byte("FUNTICKER") nonce := uint64(0) - tx := issueTx(nonce, addrs[0].Bytes, fungibleTicker, baseIssuingCost) + tx := vm.IssueTx(nonce, addrs[0].Bytes, fungibleTicker, baseIssuingCost) nonce++ - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) require.Equal(t, "success", txResult.Status.String()) fungibleTokenID := txResult.Logs.Events[0].Topics[0] - setAddressEsdtRoles(t, cs, nonce, addrs[0], fungibleTokenID, roles) + vm.SetAddressEsdtRoles(t, cs, nonce, addrs[0], fungibleTokenID, roles) nonce++ - log.Info("Issued fungible token id", "tokenID", string(fungibleTokenID)) + vm.Log.Info("Issued fungible token id", "tokenID", string(fungibleTokenID)) // issue NFT nftTicker := []byte("NFTTICKER") - tx = issueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) + tx = vm.IssueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) require.Equal(t, "success", txResult.Status.String()) @@ -113,26 +109,26 @@ func TestChainSimulator_Api_TokenType(t *testing.T) { require.Equal(t, len(txResult.SmartContractResults), len(scrs)) nftTokenID := txResult.Logs.Events[0].Topics[0] - setAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) + vm.SetAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) nonce++ - log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) + vm.Log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) // issue SFT sftTicker := []byte("SFTTICKER") - tx = issueSemiFungibleTx(nonce, addrs[0].Bytes, sftTicker, baseIssuingCost) + tx = vm.IssueSemiFungibleTx(nonce, addrs[0].Bytes, sftTicker, baseIssuingCost) nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) require.Equal(t, "success", txResult.Status.String()) sftTokenID := txResult.Logs.Events[0].Topics[0] - setAddressEsdtRoles(t, cs, nonce, addrs[0], sftTokenID, roles) + vm.SetAddressEsdtRoles(t, cs, nonce, addrs[0], sftTokenID, roles) nonce++ - log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) + vm.Log.Info("Issued SFT token id", "tokenID", string(sftTokenID)) nftMetaData := txsFee.GetDefaultMetaData() nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) @@ -154,9 +150,9 @@ func TestChainSimulator_Api_TokenType(t *testing.T) { } for i := range tokenIDs { - tx = esdtNftCreateTx(nonce, addrs[0].Bytes, tokenIDs[i], tokensMetadata[i], 1) + tx = vm.EsdtNftCreateTx(nonce, addrs[0].Bytes, tokenIDs[i], tokensMetadata[i], 1) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) @@ -206,31 +202,26 @@ func TestChainSimulator_Api_NFTToken(t *testing.T) { t.Skip("this is not a short test") } - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 20, - } - activationEpoch := uint32(2) baseIssuingCost := "1000" numOfShards := uint32(3) cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: numOfShards, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewFreePortAPIConfigurator("localhost"), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, - NumNodesWaitingListMeta: 0, - NumNodesWaitingListShard: 0, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: vm.DefaultPathToInitialConfig, + NumOfShards: numOfShards, + RoundDurationInMillis: vm.RoundDurationInMillis, + SupernovaRoundDurationInMillis: vm.SupernovaRoundDurationInMillis, + RoundsPerEpoch: vm.RoundsPerEpoch, + SupernovaRoundsPerEpoch: vm.SupernovaRoundsPerEpoch, + ApiInterface: api.NewFreePortAPIConfigurator("localhost"), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + NumNodesWaitingListMeta: 0, + NumNodesWaitingListShard: 0, AlterConfigsFunction: func(cfg *config.Configs) { cfg.EpochConfig.EnableEpochs.DynamicESDTEnableEpoch = activationEpoch cfg.SystemSCConfig.ESDTSystemSCConfig.BaseIssuingCost = baseIssuingCost @@ -244,9 +235,9 @@ func TestChainSimulator_Api_NFTToken(t *testing.T) { err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch) - 1) require.Nil(t, err) - log.Info("Initial setup: Create NFT token before activation") + vm.Log.Info("Initial setup: Create NFT token before activation") - addrs := createAddresses(t, cs, false) + addrs := vm.CreateAddresses(t, cs, false) roles := [][]byte{ []byte(core.ESDTRoleNFTCreate), @@ -256,27 +247,27 @@ func TestChainSimulator_Api_NFTToken(t *testing.T) { // issue NFT nftTicker := []byte("NFTTICKER") nonce := uint64(0) - tx := issueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) + tx := vm.IssueNonFungibleTx(nonce, addrs[0].Bytes, nftTicker, baseIssuingCost) nonce++ - txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) require.Equal(t, "success", txResult.Status.String()) nftTokenID := txResult.Logs.Events[0].Topics[0] - setAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) + vm.SetAddressEsdtRoles(t, cs, nonce, addrs[0], nftTokenID, roles) nonce++ - log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) + vm.Log.Info("Issued NFT token id", "tokenID", string(nftTokenID)) nftMetaData := txsFee.GetDefaultMetaData() nftMetaData.Nonce = []byte(hex.EncodeToString(big.NewInt(1).Bytes())) - tx = esdtNftCreateTx(nonce, addrs[0].Bytes, nftTokenID, nftMetaData, 1) + tx = vm.EsdtNftCreateTx(nonce, addrs[0].Bytes, nftTokenID, nftMetaData, 1) nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) @@ -305,7 +296,7 @@ func TestChainSimulator_Api_NFTToken(t *testing.T) { require.Equal(t, expTokenID, tokenData.TokenIdentifier) require.Equal(t, "", tokenData.Type) - log.Info("Wait for DynamicESDTFlag activation") + vm.Log.Info("Wait for DynamicESDTFlag activation") err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch)) require.Nil(t, err) @@ -322,12 +313,12 @@ func TestChainSimulator_Api_NFTToken(t *testing.T) { require.Equal(t, expTokenID, tokenData.TokenIdentifier) require.Equal(t, "", tokenData.Type) - log.Info("Update token id", "tokenID", nftTokenID) + vm.Log.Info("Update token id", "tokenID", nftTokenID) - tx = updateTokenIDTx(nonce, addrs[0].Bytes, nftTokenID) + tx = vm.UpdateTokenIDTx(nonce, addrs[0].Bytes, nftTokenID) nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) require.Equal(t, "success", txResult.Status.String()) @@ -344,11 +335,11 @@ func TestChainSimulator_Api_NFTToken(t *testing.T) { require.Equal(t, expTokenID, tokenData.TokenIdentifier) require.Equal(t, "", tokenData.Type) - log.Info("Transfer token id", "tokenID", nftTokenID) + vm.Log.Info("Transfer token id", "tokenID", nftTokenID) - tx = esdtNFTTransferTx(nonce, addrs[0].Bytes, addrs[1].Bytes, nftTokenID) + tx = vm.EsdtNFTTransferTx(nonce, addrs[0].Bytes, addrs[1].Bytes, nftTokenID) nonce++ - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) require.Equal(t, "success", txResult.Status.String()) @@ -366,11 +357,11 @@ func TestChainSimulator_Api_NFTToken(t *testing.T) { require.Equal(t, expTokenID, tokenData.TokenIdentifier) require.Equal(t, core.NonFungibleESDTv2, tokenData.Type) - log.Info("Change to DYNAMIC type") + vm.Log.Info("Change to DYNAMIC type") - tx = changeToDynamicTx(nonce, addrs[0].Bytes, nftTokenID) + tx = vm.ChangeToDynamicTx(nonce, addrs[0].Bytes, nftTokenID) - txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlockToGenerateWhenExecutingTx) + txResult, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, vm.MaxNumOfBlockToGenerateWhenExecutingTx) require.Nil(t, err) require.NotNil(t, txResult) diff --git a/integrationTests/common.go b/integrationTests/common.go index e4365471cd7..f624ec93458 100644 --- a/integrationTests/common.go +++ b/integrationTests/common.go @@ -1,10 +1,14 @@ package integrationTests import ( + "strconv" + + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/testscommon/stakingcommon" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" ) // ProcessSCOutputAccounts will save account changes in accounts db from vmOutput @@ -36,3 +40,43 @@ func ProcessSCOutputAccounts(vmOutput *vmcommon.VMOutput, accountsDB state.Accou return nil } + +// GetSupernovaRoundsConfigActivated - +func GetSupernovaRoundsConfigActivated() config.RoundConfig { + return config.RoundConfig{ + RoundActivations: map[string]config.ActivationRoundByName{ + "DisableAsyncCallV1": { + Round: "9999999", + }, + "SupernovaEnableRound": { + Round: "0", + }, + }, + } +} + +func GetSupernovaRoundsConfigDeactivated() config.RoundConfig { + return config.RoundConfig{ + RoundActivations: map[string]config.ActivationRoundByName{ + "DisableAsyncCallV1": { + Round: "9999999", + }, + "SupernovaEnableRound": { + Round: "9999999", + }, + }, + } +} + +func GetSupernovaRoundConfigActivatedAt(round int64) config.RoundConfig { + return config.RoundConfig{ + RoundActivations: map[string]config.ActivationRoundByName{ + "DisableAsyncCallV1": { + Round: "9999999", + }, + "SupernovaEnableRound": { + Round: strconv.Itoa(int(round)), + }, + }, + } +} diff --git a/integrationTests/consensus/consensusSigning_test.go b/integrationTests/consensus/consensusSigning_test.go index 4d6b57d3929..6d1d8ffaaca 100644 --- a/integrationTests/consensus/consensusSigning_test.go +++ b/integrationTests/consensus/consensusSigning_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/integrationTests" ) @@ -29,6 +30,7 @@ func initNodesWithTestSigner( enableEpochsConfig := integrationTests.CreateEnableEpochsConfig() enableEpochsConfig.AndromedaEnableEpoch = equivalentProofsActivationEpoch + enableEpochsConfig.SupernovaEnableEpoch = 0 nodes := integrationTests.CreateNodesWithTestFullNode( int(numMetaNodes), @@ -38,7 +40,8 @@ func initNodesWithTestSigner( consensusType, 1, enableEpochsConfig, - false, + integrationTests.GetSupernovaRoundsConfigActivated(), + 100, ) time.Sleep(p2pBootstrapDelay) @@ -72,6 +75,7 @@ func initNodesWithTestSigner( } func TestConsensusWithInvalidSigners(t *testing.T) { + t.Skip("TODO: activate this test once meta processing is done") if testing.Short() { t.Skip("this is not a short test") } diff --git a/integrationTests/consensus/consensus_test.go b/integrationTests/consensus/consensus_test.go index e1cb29a0611..85ad98c97bf 100644 --- a/integrationTests/consensus/consensus_test.go +++ b/integrationTests/consensus/consensus_test.go @@ -3,6 +3,8 @@ package consensus import ( "encoding/hex" "fmt" + "math/big" + "os" "sync" "testing" "time" @@ -11,6 +13,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/core/pubkeyConverter" "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/transaction" crypto "github.com/multiversx/mx-chain-crypto-go" logger "github.com/multiversx/mx-chain-logger-go" "github.com/stretchr/testify/assert" @@ -20,12 +23,15 @@ import ( consensusComp "github.com/multiversx/mx-chain-go/factory/consensus" "github.com/multiversx/mx-chain-go/integrationTests" "github.com/multiversx/mx-chain-go/process" + vmFactory "github.com/multiversx/mx-chain-go/process/factory" consensusMocks "github.com/multiversx/mx-chain-go/testscommon/consensus" ) const ( consensusTimeBetweenRounds = time.Second blsConsensusType = "bls" + roundsPerEpoch = 10 + roundTime = uint64(1000) // 1 second ) var ( @@ -34,6 +40,21 @@ var ( log = logger.GetOrCreate("integrationtests/consensus") ) +type generatedTxsParams struct { + numScTxs int + numMoveBalanceTxs int +} + +type fullConsensusTestParams struct { + enableEpochsConfig config.EnableEpochs + roundsConfig config.RoundConfig + numKeysOnEachNode int + roundsPerEpoch int64 + roundTime uint64 + targetEpoch uint32 + txs *generatedTxsParams +} + func TestConsensusBLSFullTestSingleKeys(t *testing.T) { if testing.Short() { t.Skip("this is not a short test") @@ -63,7 +84,26 @@ func TestConsensusBLSWithFullProcessing_BeforeEquivalentProofs(t *testing.T) { t.Skip("this is not a short test") } - testConsensusBLSWithFullProcessing(t, integrationTests.UnreachableEpoch, 1) + enableEpochsConfig := integrationTests.CreateEnableEpochsConfig() + enableEpochsConfig.AndromedaEnableEpoch = integrationTests.UnreachableEpoch + enableEpochsConfig.SupernovaEnableEpoch = integrationTests.UnreachableEpoch + numKeysOnEachNode := 1 + targetEpoch := uint32(2) + txs := &generatedTxsParams{ + numScTxs: 100, + numMoveBalanceTxs: 5000, + } + + testParams := fullConsensusTestParams{ + enableEpochsConfig: enableEpochsConfig, + roundsConfig: integrationTests.GetDefaultRoundsConfig(), + numKeysOnEachNode: numKeysOnEachNode, + roundsPerEpoch: roundsPerEpoch, + roundTime: roundTime, + targetEpoch: targetEpoch, + txs: txs, + } + testConsensusBLSWithFullProcessing(t, testParams) } func TestConsensusBLSWithFullProcessing_WithEquivalentProofs(t *testing.T) { @@ -71,7 +111,55 @@ func TestConsensusBLSWithFullProcessing_WithEquivalentProofs(t *testing.T) { t.Skip("this is not a short test") } - testConsensusBLSWithFullProcessing(t, uint32(0), 1) + enableEpochsConfig := integrationTests.CreateEnableEpochsConfig() + enableEpochsConfig.AndromedaEnableEpoch = uint32(0) + enableEpochsConfig.SupernovaEnableEpoch = integrationTests.UnreachableEpoch + numKeysOnEachNode := 1 + targetEpoch := uint32(2) + txs := &generatedTxsParams{ + numScTxs: 100, + numMoveBalanceTxs: 5000, + } + + testParams := fullConsensusTestParams{ + enableEpochsConfig: enableEpochsConfig, + roundsConfig: integrationTests.GetDefaultRoundsConfig(), + numKeysOnEachNode: numKeysOnEachNode, + roundsPerEpoch: roundsPerEpoch, + roundTime: roundTime, + targetEpoch: targetEpoch, + txs: txs, + } + + testConsensusBLSWithFullProcessing(t, testParams) +} + +func TestConsensusBLSWithFullProcessing_TransitionWithEquivalentProofs(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + enableEpochsConfig := integrationTests.CreateEnableEpochsConfig() + enableEpochsConfig.AndromedaEnableEpoch = uint32(1) + enableEpochsConfig.SupernovaEnableEpoch = integrationTests.UnreachableEpoch + numKeysOnEachNode := 1 + targetEpoch := uint32(2) + txs := &generatedTxsParams{ + numScTxs: 100, + numMoveBalanceTxs: 5000, + } + + testParams := fullConsensusTestParams{ + enableEpochsConfig: enableEpochsConfig, + roundsConfig: integrationTests.GetSupernovaRoundsConfigDeactivated(), + numKeysOnEachNode: numKeysOnEachNode, + roundsPerEpoch: roundsPerEpoch, + roundTime: roundTime, + targetEpoch: targetEpoch, + txs: txs, + } + + testConsensusBLSWithFullProcessing(t, testParams) } func TestConsensusBLSWithFullProcessing_WithEquivalentProofs_MultiKeys(t *testing.T) { @@ -79,14 +167,99 @@ func TestConsensusBLSWithFullProcessing_WithEquivalentProofs_MultiKeys(t *testin t.Skip("this is not a short test") } - testConsensusBLSWithFullProcessing(t, uint32(0), 3) + enableEpochsConfig := integrationTests.CreateEnableEpochsConfig() + enableEpochsConfig.AndromedaEnableEpoch = uint32(0) + enableEpochsConfig.SupernovaEnableEpoch = integrationTests.UnreachableEpoch + numKeysOnEachNode := 3 + targetEpoch := uint32(2) + txs := &generatedTxsParams{ + numScTxs: 100, + numMoveBalanceTxs: 5000, + } + + testParams := fullConsensusTestParams{ + enableEpochsConfig: enableEpochsConfig, + roundsConfig: integrationTests.GetSupernovaRoundsConfigDeactivated(), + numKeysOnEachNode: numKeysOnEachNode, + roundsPerEpoch: roundsPerEpoch, + roundTime: roundTime, + targetEpoch: targetEpoch, + txs: txs, + } + + testConsensusBLSWithFullProcessing(t, testParams) } -func testConsensusBLSWithFullProcessing(t *testing.T, equivalentProofsActivationEpoch uint32, numKeysOnEachNode int) { +func TestConsensusBLSWithFullProcessing_TransitionToSupernova(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + enableEpochsConfig := integrationTests.CreateEnableEpochsConfig() + enableEpochsConfig.AndromedaEnableEpoch = uint32(0) + enableEpochsConfig.SupernovaEnableEpoch = uint32(2) + numKeysOnEachNode := 3 + targetEpoch := uint32(4) + txs := &generatedTxsParams{ + numScTxs: 0, + numMoveBalanceTxs: 0, + } + + testParams := fullConsensusTestParams{ + enableEpochsConfig: enableEpochsConfig, + roundsConfig: integrationTests.GetSupernovaRoundConfigActivatedAt(int64(enableEpochsConfig.SupernovaEnableEpoch*uint32(roundsPerEpoch)) + int64(roundsPerEpoch/2)), + numKeysOnEachNode: numKeysOnEachNode, + roundsPerEpoch: roundsPerEpoch, + roundTime: roundTime, + targetEpoch: targetEpoch, + txs: txs, + } + testConsensusBLSWithFullProcessing(t, testParams) +} + +func TestConsensusBLSWithFullProcessing_TransitionToSupernova_HighLoad(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + enableEpochsConfig := integrationTests.CreateEnableEpochsConfig() + enableEpochsConfig.AndromedaEnableEpoch = uint32(0) + enableEpochsConfig.SupernovaEnableEpoch = uint32(2) + numKeysOnEachNode := 3 + targetEpoch := uint32(4) + txs := &generatedTxsParams{ + numScTxs: 500, + numMoveBalanceTxs: 10000, + } + + testParams := fullConsensusTestParams{ + enableEpochsConfig: enableEpochsConfig, + roundsConfig: integrationTests.GetSupernovaRoundConfigActivatedAt(int64(enableEpochsConfig.SupernovaEnableEpoch*uint32(roundsPerEpoch)) + int64(roundsPerEpoch/2)), + numKeysOnEachNode: numKeysOnEachNode, + roundsPerEpoch: roundsPerEpoch, + roundTime: roundTime, + targetEpoch: targetEpoch, + txs: txs, + } + + testConsensusBLSWithFullProcessing(t, testParams) +} + +func testConsensusBLSWithFullProcessing( + t *testing.T, + testParams fullConsensusTestParams, +) { + numKeysOnEachNode := testParams.numKeysOnEachNode + enableEpochsConfig := testParams.enableEpochsConfig + roundsConfig := testParams.roundsConfig + targetEpoch := testParams.targetEpoch + txs := testParams.txs + roundsPerEpoch := testParams.roundsPerEpoch + roundTime := testParams.roundTime + numMetaNodes := uint32(2) numNodes := uint32(2) consensusSize := uint32(2 * numKeysOnEachNode) - roundTime := uint64(1000) log.Info("runFullNodesTest", "numNodes", numNodes, @@ -94,10 +267,6 @@ func testConsensusBLSWithFullProcessing(t *testing.T, equivalentProofsActivation "consensusSize", consensusSize, ) - enableEpochsConfig := integrationTests.CreateEnableEpochsConfig() - - enableEpochsConfig.AndromedaEnableEpoch = equivalentProofsActivationEpoch - fmt.Println("Step 1. Setup nodes...") nodes := integrationTests.CreateNodesWithTestFullNode( @@ -108,8 +277,10 @@ func testConsensusBLSWithFullProcessing(t *testing.T, equivalentProofsActivation blsConsensusType, numKeysOnEachNode, enableEpochsConfig, - true, + roundsConfig, + roundsPerEpoch, ) + shard0Node := nodes[0][0] for shardID, nodesList := range nodes { for _, n := range nodesList { @@ -145,11 +316,41 @@ func testConsensusBLSWithFullProcessing(t *testing.T, equivalentProofsActivation fmt.Println("Wait for several rounds...") - time.Sleep(15 * time.Second) + nodesList := make([]*integrationTests.TestProcessorNode, 0) + shard0Nodes := nodes[0] + for _, n := range shard0Nodes { + nodesList = append(nodesList, n.TestProcessorNode) + } + integrationTests.MintAllNodes(nodesList, big.NewInt(100000000000)) + integrationTests.SetRootHashOfGenesisBlocks(nodesList) + + maxRounds := uint64(roundsPerEpoch)*uint64(enableEpochsConfig.SCDeployEnableEpoch) + uint64(roundsPerEpoch) + timeoutSeconds := (maxRounds * roundTime) / 1000 + waitForEpoch(shard0Node, enableEpochsConfig.SCDeployEnableEpoch, timeoutSeconds) + + scTxs(t, shard0Node, txs.numScTxs, nodesList) + + encodedReceiverAddr, err := integrationTests.TestAddressPubkeyConverter.Encode(integrationTests.CreateRandomBytes(32)) + assert.Nil(t, err) + moveBalanceTxs(t, shard0Node.TestProcessorNode, encodedReceiverAddr, txs.numMoveBalanceTxs) + + maxRounds = uint64(roundsPerEpoch)*uint64(targetEpoch) + uint64(roundsPerEpoch) + timeoutSeconds = (maxRounds * roundTime) / 1000 + waitForEpoch(shard0Node, targetEpoch, timeoutSeconds) fmt.Println("Checking shards...") + receiverBytes, err := integrationTests.TestAddressPubkeyConverter.Decode(encodedReceiverAddr) + require.Nil(t, err) + acc, err := shard0Node.AccntState.LoadAccount(receiverBytes) + require.Nil(t, err) + + if txs.numMoveBalanceTxs > 0 { + require.NotEqual(t, uint64(0), acc.(data.UserAccountHandler).GetBalance().Uint64()) + } + expectedNonce := uint64(10) + var nodeEpoch uint32 for _, nodesList := range nodes { for _, n := range nodesList { for i := 1; i < len(nodes); i++ { @@ -157,6 +358,8 @@ func testConsensusBLSWithFullProcessing(t *testing.T, equivalentProofsActivation assert.Fail(t, fmt.Sprintf("Node with idx %d does not have a current block", i)) } else { assert.GreaterOrEqual(t, n.Node.GetDataComponents().Blockchain().GetCurrentBlockHeader().GetNonce(), expectedNonce) + nodeEpoch = n.Node.GetDataComponents().Blockchain().GetCurrentBlockHeader().GetEpoch() + assert.Equal(t, targetEpoch, nodeEpoch) } } } @@ -185,7 +388,8 @@ func startFullConsensusNode( CheckNodesOnDisk: false, }, GeneralSettings: config.GeneralSettingsConfig{ - SyncProcessTimeInMillis: 6000, + SyncProcessTimeInMillis: 6000, + SyncProcessTimeSupernovaInMillis: 3000, }, }, BootstrapRoundIndex: 0, @@ -315,7 +519,8 @@ func startNodesWithCommitBlock(nodes []*integrationTests.TestConsensusNode, mute CheckNodesOnDisk: false, }, GeneralSettings: config.GeneralSettingsConfig{ - SyncProcessTimeInMillis: 6000, + SyncProcessTimeInMillis: 6000, + SyncProcessTimeSupernovaInMillis: 3000, }, }, BootstrapRoundIndex: 0, @@ -396,7 +601,6 @@ func runFullConsensusTest( numNodes := uint32(4) consensusSize := uint32(3 * numKeysOnEachNode) numInvalid := uint32(0) - roundTime := uint64(1000) numCommBlock := uint64(8) log.Info("runFullConsensusTest", @@ -406,9 +610,7 @@ func runFullConsensusTest( ) enableEpochsConfig := integrationTests.CreateEnableEpochsConfig() - - equivalentProofsActivationEpoch := integrationTests.UnreachableEpoch - enableEpochsConfig.AndromedaEnableEpoch = equivalentProofsActivationEpoch + enableEpochsConfig.SupernovaEnableEpoch = integrationTests.UnreachableEpoch nodes := initNodesAndTest( numMetaNodes, @@ -465,9 +667,9 @@ func runConsensusWithNotEnoughValidators(t *testing.T, consensusType string) { numNodes := uint32(4) consensusSize := uint32(4) numInvalid := uint32(2) - roundTime := uint64(1000) enableEpochsConfig := integrationTests.CreateEnableEpochsConfig() - enableEpochsConfig.AndromedaEnableEpoch = integrationTests.UnreachableEpoch + enableEpochsConfig.SupernovaEnableEpoch = integrationTests.UnreachableEpoch + nodes := initNodesAndTest(numMetaNodes, numNodes, consensusSize, numInvalid, roundTime, consensusType, 1, enableEpochsConfig) defer func() { @@ -528,3 +730,105 @@ func getPkEncoded(pubKey crypto.PublicKey) string { return encodeAddress(pk) } + +func waitForEpoch(node *integrationTests.TestFullNode, targetEpoch uint32, timeoutSeconds uint64) { + epochReached := false + timeStart := time.Now() + for !epochReached { + blockHeader := node.Node.GetDataComponents().Blockchain().GetCurrentBlockHeader() + if check.IfNil(blockHeader) { + time.Sleep(time.Second) + continue + } + epochReached = blockHeader.GetEpoch() == targetEpoch + secondsPassed := time.Since(timeStart).Seconds() + if secondsPassed > float64(timeoutSeconds) { + break + } + } + + time.Sleep(time.Second * 3) // wait for all nodes to change epoch + fmt.Println("Wait for all nodes to change epoch...") +} + +func scTxs(t *testing.T, senderNode *integrationTests.TestFullNode, numTxs int, nodesList []*integrationTests.TestProcessorNode) { + if numTxs <= 0 { + return + } + + numPlayers := 10 + players := make([]*integrationTests.TestWalletAccount, numPlayers) + for i := 0; i < numPlayers; i++ { + players[i] = integrationTests.CreateTestWalletAccount(senderNode.ShardCoordinator, 0) + } + initialVal := big.NewInt(100000000000) + integrationTests.MintAllPlayers(nodesList, players, initialVal) + + scCode, err := os.ReadFile("../vm/wasm/testdata/erc20-c-03/wrc20_wasm.wasm") + require.Nil(t, err) + + scAddress, _ := senderNode.TestProcessorNode.BlockchainHook.NewAddress(senderNode.OwnAccount.Address, senderNode.OwnAccount.Nonce, vmFactory.WasmVirtualMachine) + initialSupply := hex.EncodeToString(big.NewInt(100000000000).Bytes()) + integrationTests.DeployScTx(nodesList, 0, hex.EncodeToString(scCode), vmFactory.WasmVirtualMachine, initialSupply) + time.Sleep(time.Second) + + for i := 0; i < numTxs; i++ { + playersDoTransfer(senderNode.TestProcessorNode, players, scAddress, big.NewInt(100)) + } +} + +func moveBalanceTxs(t *testing.T, senderNode *integrationTests.TestProcessorNode, receiverAddr string, numTxs int) { + if numTxs <= 0 { + return + } + + err := senderNode.Node.GenerateAndSendBulkTransactions( + receiverAddr, + big.NewInt(1), + uint64(numTxs), + senderNode.OwnAccount.SkTxSign, + nil, + integrationTests.ChainID, + integrationTests.MinTransactionVersion, + ) + assert.Nil(t, err) +} + +func playersDoTransfer( + node *integrationTests.TestProcessorNode, + players []*integrationTests.TestWalletAccount, + scAddress []byte, + txValue *big.Int, +) { + for _, playerToTransfer := range players { + createAndSendTx(node, node.OwnAccount, big.NewInt(0), 200000, scAddress, + []byte("transferToken@"+hex.EncodeToString(playerToTransfer.Address)+"@"+hex.EncodeToString(txValue.Bytes()))) + } +} + +func createAndSendTx( + node *integrationTests.TestProcessorNode, + player *integrationTests.TestWalletAccount, + txValue *big.Int, + gasLimit uint64, + rcvAddress []byte, + txData []byte, +) { + tx := &transaction.Transaction{ + Nonce: player.Nonce, + Value: txValue, + SndAddr: player.Address, + RcvAddr: rcvAddress, + Data: txData, + GasPrice: node.EconomicsData.GetMinGasPrice(), + GasLimit: gasLimit, + Version: integrationTests.MinTransactionVersion, + ChainID: integrationTests.ChainID, + } + + txBuff, _ := tx.GetDataForSigning(integrationTests.TestAddressPubkeyConverter, integrationTests.TestTxSignMarshalizer, integrationTests.TestTxSignHasher) + tx.Signature, _ = player.SingleSigner.Sign(player.SkTxSign, txBuff) + + _, _ = node.SendTransaction(tx) + player.Nonce++ +} diff --git a/integrationTests/factory/componentsHelper.go b/integrationTests/factory/componentsHelper.go index 3006dd3182c..62b60d5d0ee 100644 --- a/integrationTests/factory/componentsHelper.go +++ b/integrationTests/factory/componentsHelper.go @@ -94,6 +94,8 @@ func computeChainParameters(numInitialNodes uint32, numShardsWithoutMeta uint32) MetachainConsensusGroupSize: nodesPerShards, MetachainMinNumNodes: nodesPerShards + diff, RoundDuration: 2000, + RoundsPerEpoch: 200, + MinRoundsBetweenEpochs: 20, }, } } diff --git a/integrationTests/factory/consensusComponents/consensusComponents_test.go b/integrationTests/factory/consensusComponents/consensusComponents_test.go index a7eec6bde69..d6c80672fb9 100644 --- a/integrationTests/factory/consensusComponents/consensusComponents_test.go +++ b/integrationTests/factory/consensusComponents/consensusComponents_test.go @@ -56,7 +56,12 @@ func TestConsensusComponents_Close_ShouldWork(t *testing.T) { require.Nil(t, err) managedStateComponents, err := nr.CreateManagedStateComponents(managedCoreComponents, managedDataComponents, managedStatusCoreComponents) require.Nil(t, err) - nodesShufflerOut, err := bootstrapComp.CreateNodesShuffleOut(managedCoreComponents.GenesisNodesSetup(), configs.GeneralConfig.EpochStartConfig, managedCoreComponents.ChanStopNodeProcess()) + nodesShufflerOut, err := bootstrapComp.CreateNodesShuffleOut( + managedCoreComponents.GenesisNodesSetup(), + configs.GeneralConfig.EpochStartConfig, + managedCoreComponents.ChanStopNodeProcess(), + managedCoreComponents.ChainParametersHandler(), + ) require.Nil(t, err) storer, err := managedDataComponents.StorageService().GetStorer(dataRetriever.BootstrapUnit) require.Nil(t, err) diff --git a/integrationTests/factory/heartbeatComponents/heartbeatComponents_test.go b/integrationTests/factory/heartbeatComponents/heartbeatComponents_test.go index d296be05b04..0908e43144b 100644 --- a/integrationTests/factory/heartbeatComponents/heartbeatComponents_test.go +++ b/integrationTests/factory/heartbeatComponents/heartbeatComponents_test.go @@ -56,7 +56,7 @@ func TestHeartbeatComponents_Close_ShouldWork(t *testing.T) { require.Nil(t, err) managedStateComponents, err := nr.CreateManagedStateComponents(managedCoreComponents, managedDataComponents, managedStatusCoreComponents) require.Nil(t, err) - nodesShufflerOut, err := bootstrapComp.CreateNodesShuffleOut(managedCoreComponents.GenesisNodesSetup(), configs.GeneralConfig.EpochStartConfig, managedCoreComponents.ChanStopNodeProcess()) + nodesShufflerOut, err := bootstrapComp.CreateNodesShuffleOut(managedCoreComponents.GenesisNodesSetup(), configs.GeneralConfig.EpochStartConfig, managedCoreComponents.ChanStopNodeProcess(), managedCoreComponents.ChainParametersHandler()) require.Nil(t, err) storer, err := managedDataComponents.StorageService().GetStorer(dataRetriever.BootstrapUnit) require.Nil(t, err) diff --git a/integrationTests/factory/processComponents/processComponents_test.go b/integrationTests/factory/processComponents/processComponents_test.go index 6f82bbf1188..1d72c71c650 100644 --- a/integrationTests/factory/processComponents/processComponents_test.go +++ b/integrationTests/factory/processComponents/processComponents_test.go @@ -57,7 +57,7 @@ func TestProcessComponents_Close_ShouldWork(t *testing.T) { require.Nil(t, err) managedStateComponents, err := nr.CreateManagedStateComponents(managedCoreComponents, managedDataComponents, managedStatusCoreComponents) require.Nil(t, err) - nodesShufflerOut, err := bootstrapComp.CreateNodesShuffleOut(managedCoreComponents.GenesisNodesSetup(), configs.GeneralConfig.EpochStartConfig, managedCoreComponents.ChanStopNodeProcess()) + nodesShufflerOut, err := bootstrapComp.CreateNodesShuffleOut(managedCoreComponents.GenesisNodesSetup(), configs.GeneralConfig.EpochStartConfig, managedCoreComponents.ChanStopNodeProcess(), managedCoreComponents.ChainParametersHandler()) require.Nil(t, err) storer, err := managedDataComponents.StorageService().GetStorer(dataRetriever.BootstrapUnit) require.Nil(t, err) diff --git a/integrationTests/factory/statusComponents/statusComponents_test.go b/integrationTests/factory/statusComponents/statusComponents_test.go index 488d20baea7..a1f0c3d886e 100644 --- a/integrationTests/factory/statusComponents/statusComponents_test.go +++ b/integrationTests/factory/statusComponents/statusComponents_test.go @@ -57,7 +57,7 @@ func TestStatusComponents_Create_Close_ShouldWork(t *testing.T) { require.Nil(t, err) managedStateComponents, err := nr.CreateManagedStateComponents(managedCoreComponents, managedDataComponents, managedStatusCoreComponents) require.Nil(t, err) - nodesShufflerOut, err := bootstrapComp.CreateNodesShuffleOut(managedCoreComponents.GenesisNodesSetup(), configs.GeneralConfig.EpochStartConfig, managedCoreComponents.ChanStopNodeProcess()) + nodesShufflerOut, err := bootstrapComp.CreateNodesShuffleOut(managedCoreComponents.GenesisNodesSetup(), configs.GeneralConfig.EpochStartConfig, managedCoreComponents.ChanStopNodeProcess(), managedCoreComponents.ChainParametersHandler()) require.Nil(t, err) storer, err := managedDataComponents.StorageService().GetStorer(dataRetriever.BootstrapUnit) require.Nil(t, err) diff --git a/integrationTests/interface.go b/integrationTests/interface.go index 23504565a25..08a63c6fa4d 100644 --- a/integrationTests/interface.go +++ b/integrationTests/interface.go @@ -5,7 +5,6 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data/alteredAccount" - "github.com/multiversx/mx-chain-core-go/data/api" dataApi "github.com/multiversx/mx-chain-core-go/data/api" "github.com/multiversx/mx-chain-core-go/data/esdt" "github.com/multiversx/mx-chain-core-go/data/smartContractResult" @@ -58,23 +57,23 @@ type NetworkShardingUpdater interface { // Facade is the node facade used to decouple the node implementation with the web server. Used in integration tests type Facade interface { - GetBalance(address string, options api.AccountQueryOptions) (*big.Int, api.BlockInfo, error) - GetUsername(address string, options api.AccountQueryOptions) (string, api.BlockInfo, error) - GetCodeHash(address string, options api.AccountQueryOptions) ([]byte, api.BlockInfo, error) - GetValueForKey(address string, key string, options api.AccountQueryOptions) (string, api.BlockInfo, error) - GetAccount(address string, options api.AccountQueryOptions) (dataApi.AccountResponse, api.BlockInfo, error) - GetAccounts(addresses []string, options api.AccountQueryOptions) (map[string]*api.AccountResponse, api.BlockInfo, error) - GetESDTData(address string, key string, nonce uint64, options api.AccountQueryOptions) (*esdt.ESDigitalToken, api.BlockInfo, error) - GetNFTTokenIDsRegisteredByAddress(address string, options api.AccountQueryOptions) ([]string, api.BlockInfo, error) - GetESDTsWithRole(address string, role string, options api.AccountQueryOptions) ([]string, api.BlockInfo, error) - GetAllESDTTokens(address string, options api.AccountQueryOptions) (map[string]*esdt.ESDigitalToken, api.BlockInfo, error) - GetESDTsRoles(address string, options api.AccountQueryOptions) (map[string][]string, api.BlockInfo, error) - GetKeyValuePairs(address string, options api.AccountQueryOptions) (map[string]string, api.BlockInfo, error) - IterateKeys(address string, numKeys uint, iteratorState [][]byte, options api.AccountQueryOptions) (map[string]string, [][]byte, api.BlockInfo, error) - GetGuardianData(address string, options api.AccountQueryOptions) (api.GuardianData, api.BlockInfo, error) - GetBlockByHash(hash string, options api.BlockQueryOptions) (*dataApi.Block, error) - GetBlockByNonce(nonce uint64, options api.BlockQueryOptions) (*dataApi.Block, error) - GetBlockByRound(round uint64, options api.BlockQueryOptions) (*dataApi.Block, error) + GetBalance(address string, options dataApi.AccountQueryOptions) (*big.Int, dataApi.BlockInfo, error) + GetUsername(address string, options dataApi.AccountQueryOptions) (string, dataApi.BlockInfo, error) + GetCodeHash(address string, options dataApi.AccountQueryOptions) ([]byte, dataApi.BlockInfo, error) + GetValueForKey(address string, key string, options dataApi.AccountQueryOptions) (string, dataApi.BlockInfo, error) + GetAccount(address string, options dataApi.AccountQueryOptions) (dataApi.AccountResponse, dataApi.BlockInfo, error) + GetAccounts(addresses []string, options dataApi.AccountQueryOptions) (map[string]*dataApi.AccountResponse, dataApi.BlockInfo, error) + GetESDTData(address string, key string, nonce uint64, options dataApi.AccountQueryOptions) (*esdt.ESDigitalToken, dataApi.BlockInfo, error) + GetNFTTokenIDsRegisteredByAddress(address string, options dataApi.AccountQueryOptions) ([]string, dataApi.BlockInfo, error) + GetESDTsWithRole(address string, role string, options dataApi.AccountQueryOptions) ([]string, dataApi.BlockInfo, error) + GetAllESDTTokens(address string, options dataApi.AccountQueryOptions) (map[string]*esdt.ESDigitalToken, dataApi.BlockInfo, error) + GetESDTsRoles(address string, options dataApi.AccountQueryOptions) (map[string][]string, dataApi.BlockInfo, error) + GetKeyValuePairs(address string, options dataApi.AccountQueryOptions) (map[string]string, dataApi.BlockInfo, error) + IterateKeys(address string, numKeys uint, iteratorState [][]byte, options dataApi.AccountQueryOptions) (map[string]string, [][]byte, dataApi.BlockInfo, error) + GetGuardianData(address string, options dataApi.AccountQueryOptions) (dataApi.GuardianData, dataApi.BlockInfo, error) + GetBlockByHash(hash string, options dataApi.BlockQueryOptions) (*dataApi.Block, error) + GetBlockByNonce(nonce uint64, options dataApi.BlockQueryOptions) (*dataApi.Block, error) + GetBlockByRound(round uint64, options dataApi.BlockQueryOptions) (*dataApi.Block, error) Trigger(epoch uint32, withEarlyEndOfEpoch bool) error IsSelfTrigger() bool GetTotalStakedValue() (*dataApi.StakeValues, error) @@ -100,7 +99,7 @@ type Facade interface { GetThrottlerForEndpoint(endpoint string) (core.Throttler, bool) ValidatorStatisticsApi() (map[string]*validator.ValidatorStatistics, error) AuctionListApi() ([]*common.AuctionListValidatorAPIResponse, error) - ExecuteSCQuery(*process.SCQuery) (*vm.VMOutputApi, api.BlockInfo, error) + ExecuteSCQuery(*process.SCQuery) (*vm.VMOutputApi, dataApi.BlockInfo, error) DecodeAddressPubkey(pk string) ([]byte, error) GetProof(rootHash string, address string) (*common.GetProofResponse, error) GetProofDataTrie(rootHash string, address string, key string) (*common.GetProofResponse, *common.GetProofResponse, error) @@ -113,8 +112,10 @@ type Facade interface { GetTransactionsPoolForSender(sender, fields string) (*common.TransactionsPoolForSenderApiResponse, error) GetLastPoolNonceForSender(sender string) (uint64, error) GetTransactionsPoolNonceGapsForSender(sender string) (*common.TransactionsPoolNonceGapsForSenderApiResponse, error) + GetSelectedTransactions(fields string) (*common.TransactionsSelectionSimulationResult, error) + GetVirtualNonce(address string) (*common.VirtualNonceOfAccountResponse, error) GetAlteredAccountsForBlock(options dataApi.GetAlteredAccountsForBlockOptions) ([]*alteredAccount.AlteredAccount, error) - IsDataTrieMigrated(address string, options api.AccountQueryOptions) (bool, error) + IsDataTrieMigrated(address string, options dataApi.AccountQueryOptions) (bool, error) GetManagedKeysCount() int GetManagedKeys() []string GetLoadedKeys() []string diff --git a/integrationTests/longTests/antiflooding/antiflooding_test.go b/integrationTests/longTests/antiflooding/antiflooding_test.go index ad2fca583d2..9ca6e1242be 100644 --- a/integrationTests/longTests/antiflooding/antiflooding_test.go +++ b/integrationTests/longTests/antiflooding/antiflooding_test.go @@ -8,12 +8,14 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/display" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/integrationTests" "github.com/multiversx/mx-chain-go/integrationTests/mock" "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process/throttle/antiflood/blackList" "github.com/multiversx/mx-chain-go/process/throttle/antiflood/factory" + "github.com/multiversx/mx-chain-go/testscommon" statusHandlerMock "github.com/multiversx/mx-chain-go/testscommon/statusHandler" logger "github.com/multiversx/mx-chain-logger-go" "github.com/stretchr/testify/assert" @@ -21,61 +23,71 @@ import ( var log = logger.GetOrCreate("integrationtests/longtests/antiflood") //nolint -// nolint -func createWorkableConfig() config.Config { - return config.Config{ - Antiflood: config.AntifloodConfig{ - Enabled: true, - Cache: config.CacheConfig{ - Type: "LRU", - Capacity: 5000, - Shards: 16, - }, - FastReacting: config.FloodPreventerConfig{ - IntervalInSeconds: 1, - ReservedPercent: 20, - PeerMaxInput: config.AntifloodLimitsConfig{ - BaseMessagesPerInterval: 75, - TotalSizePerInterval: 2097152, - }, - BlackList: config.BlackListConfig{ - ThresholdNumMessagesPerInterval: 480, - ThresholdSizePerInterval: 5242880, - NumFloodingRounds: 10, - PeerBanDurationInSeconds: 300, +func createAntifloodConfigHandler(enabled bool) common.AntifloodConfigsHandler { + antifloodConfigHandler := &testscommon.AntifloodConfigsHandlerStub{ + IsEnabledCalled: func() bool { + return enabled + }, + GetCurrentConfigCalled: func() config.AntifloodConfigByRound { + return config.AntifloodConfigByRound{ + Cache: config.CacheConfig{ + Type: "LRU", + Capacity: 5000, + Shards: 16, }, - }, - SlowReacting: config.FloodPreventerConfig{ - IntervalInSeconds: 30, - ReservedPercent: 20, - PeerMaxInput: config.AntifloodLimitsConfig{ - BaseMessagesPerInterval: 2500, - TotalSizePerInterval: 15728640, + FastReacting: config.FloodPreventerConfig{ + IntervalInSeconds: 1, + ReservedPercent: 20, + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: 75, + TotalSizePerInterval: 2097152, + }, + BlackList: config.BlackListConfig{ + ThresholdNumMessagesPerInterval: 480, + ThresholdSizePerInterval: 5242880, + PeerBanDurationInSeconds: 300, + }, }, - BlackList: config.BlackListConfig{ - ThresholdNumMessagesPerInterval: 6000, - ThresholdSizePerInterval: 37748736, - NumFloodingRounds: 2, - PeerBanDurationInSeconds: 3600, + SlowReacting: config.FloodPreventerConfig{ + IntervalInSeconds: 30, + ReservedPercent: 20, + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: 2500, + TotalSizePerInterval: 15728640, + }, + BlackList: config.BlackListConfig{ + ThresholdNumMessagesPerInterval: 6000, + ThresholdSizePerInterval: 37748736, + PeerBanDurationInSeconds: 3600, + }, }, - }, - OutOfSpecs: config.FloodPreventerConfig{ - IntervalInSeconds: 1, - ReservedPercent: 0, - PeerMaxInput: config.AntifloodLimitsConfig{ - BaseMessagesPerInterval: 1000, - TotalSizePerInterval: 8388608, + OutOfSpecs: config.FloodPreventerConfig{ + IntervalInSeconds: 1, + ReservedPercent: 0, + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: 1000, + TotalSizePerInterval: 8388608, + }, + BlackList: config.BlackListConfig{ + ThresholdNumMessagesPerInterval: 1500, + ThresholdSizePerInterval: 10485760, + PeerBanDurationInSeconds: 3600, + }, }, - BlackList: config.BlackListConfig{ - ThresholdNumMessagesPerInterval: 1500, - ThresholdSizePerInterval: 10485760, - NumFloodingRounds: 2, - PeerBanDurationInSeconds: 3600, + Topic: config.TopicAntifloodConfig{ + DefaultMaxMessagesPerSec: 10000, }, - }, - Topic: config.TopicAntifloodConfig{ - DefaultMaxMessagesPerSec: 10000, - }, + } + }, + } + + return antifloodConfigHandler +} + +func createWorkableConfig() config.Config { + return config.Config{ + Antiflood: config.AntifloodConfig{ + Enabled: false, }, } } @@ -123,13 +135,14 @@ func createProcessors(peers []p2p.Messenger, topic string, idxBadPeers []int, id var err error if intInSlice(i, idxBadPeers) { - antifloodComponents, err = factory.NewP2PAntiFloodComponents(ctx, createDisabledConfig(), &statusHandlerMock.AppStatusHandlerStub{}, peers[i].ID()) + antifloodComponents, err = factory.NewP2PAntiFloodComponents(ctx, createDisabledConfig(), &statusHandlerMock.AppStatusHandlerStub{}, peers[i].ID(), createAntifloodConfigHandler(false)) log.LogIfError(err) } if intInSlice(i, idxGoodPeers) { statusHandler := &statusHandlerMock.AppStatusHandlerStub{} - antifloodComponents, err = factory.NewP2PAntiFloodComponents(ctx, createWorkableConfig(), statusHandler, peers[i].ID()) + + antifloodComponents, err = factory.NewP2PAntiFloodComponents(ctx, createWorkableConfig(), statusHandler, peers[i].ID(), createAntifloodConfigHandler(true)) log.LogIfError(err) } diff --git a/integrationTests/mock/blockProcessorMock.go b/integrationTests/mock/blockProcessorMock.go index b3f42dd8e52..a985485e629 100644 --- a/integrationTests/mock/blockProcessorMock.go +++ b/integrationTests/mock/blockProcessorMock.go @@ -14,17 +14,36 @@ type BlockProcessorMock struct { NumCommitBlockCalled uint32 Marshalizer marshal.Marshalizer ProcessBlockCalled func(header data.HeaderHandler, body data.BodyHandler, haveTime func() time.Duration) error + ProcessBlockProposalCalled func(header data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) + CommitBlockProposalStateCalled func(headerHandler data.HeaderHandler) error + RevertBlockProposalStateCalled func() ProcessScheduledBlockCalled func(header data.HeaderHandler, body data.BodyHandler, haveTime func() time.Duration) error CommitBlockCalled func(header data.HeaderHandler, body data.BodyHandler) error RevertCurrentBlockCalled func() CreateBlockCalled func(initialHdrData data.HeaderHandler, haveTime func() bool) (data.HeaderHandler, data.BodyHandler, error) + CreateBlockProposalCalled func(initialHdr data.HeaderHandler, haveTime func() bool) (data.HeaderHandler, data.BodyHandler, error) + CreateNewHeaderProposalCalled func(round uint64, nonce uint64) (data.HeaderHandler, error) RestoreBlockIntoPoolsCalled func(header data.HeaderHandler, body data.BodyHandler) error RestoreBlockBodyIntoPoolsCalled func(body data.BodyHandler) error - MarshalizedDataToBroadcastCalled func(header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) + MarshalizedDataToBroadcastCalled func(headerHash []byte, header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) CreateNewHeaderCalled func(round uint64, nonce uint64) (data.HeaderHandler, error) PruneStateOnRollbackCalled func(currHeader data.HeaderHandler, currHeaderHash []byte, prevHeader data.HeaderHandler, prevHeaderHash []byte) RevertStateToBlockCalled func(header data.HeaderHandler, rootHash []byte) error DecodeBlockHeaderCalled func(dta []byte) data.HeaderHandler + RemoveHeaderFromPoolCalled func(headerHash []byte) + VerifyBlockProposalCalled func( + headerHandler data.HeaderHandler, + bodyHandler data.BodyHandler, + haveTime func() time.Duration, + ) error + OnProposedBlockCalled func( + proposedBody data.BodyHandler, + proposedHeader data.HeaderHandler, + proposedHash []byte, + ) error + OnExecutedBlockCalled func(header data.HeaderHandler, rootHash []byte) error + ProposedDirectSentTransactionsToBroadcastCalled func(proposedBody data.BodyHandler) map[string][][]byte + PruneTrieAsyncHeaderCalled func() } // ProcessBlock mocks processing a block @@ -36,6 +55,31 @@ func (bpm *BlockProcessorMock) ProcessBlock(header data.HeaderHandler, body data return nil } +// ProcessBlockProposal mocks processing a block +func (bpm *BlockProcessorMock) ProcessBlockProposal(header data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + if bpm.ProcessBlockProposalCalled != nil { + return bpm.ProcessBlockProposalCalled(header, headerHash, body) + } + + return nil, nil +} + +// CommitBlockProposalState - +func (bpm *BlockProcessorMock) CommitBlockProposalState(headerHandler data.HeaderHandler) error { + if bpm.CommitBlockProposalStateCalled != nil { + return bpm.CommitBlockProposalStateCalled(headerHandler) + } + + return nil +} + +// RevertBlockProposalState - +func (bpm *BlockProcessorMock) RevertBlockProposalState() { + if bpm.RevertBlockProposalStateCalled != nil { + bpm.RevertBlockProposalStateCalled() + } +} + // ProcessScheduledBlock mocks processing a scheduled block func (bpm *BlockProcessorMock) ProcessScheduledBlock(header data.HeaderHandler, body data.BodyHandler, haveTime func() time.Duration) error { if bpm.ProcessScheduledBlockCalled != nil { @@ -70,6 +114,15 @@ func (bpm *BlockProcessorMock) CreateNewHeader(round uint64, nonce uint64) (data return nil, nil } +// CreateNewHeaderProposal - +func (bpm *BlockProcessorMock) CreateNewHeaderProposal(round uint64, nonce uint64) (data.HeaderHandler, error) { + if bpm.CreateNewHeaderProposalCalled != nil { + return bpm.CreateNewHeaderProposalCalled(round, nonce) + } + + return nil, nil +} + // CreateBlock - func (bpm *BlockProcessorMock) CreateBlock(initialHdrData data.HeaderHandler, haveTime func() bool) (data.HeaderHandler, data.BodyHandler, error) { if bpm.CreateBlockCalled != nil { @@ -79,6 +132,15 @@ func (bpm *BlockProcessorMock) CreateBlock(initialHdrData data.HeaderHandler, ha return nil, nil, nil } +// CreateBlockProposal - +func (bpm *BlockProcessorMock) CreateBlockProposal(initialHdr data.HeaderHandler, haveTime func() bool) (data.HeaderHandler, data.BodyHandler, error) { + if bpm.CreateBlockProposalCalled != nil { + return bpm.CreateBlockProposalCalled(initialHdr, haveTime) + } + + return nil, nil, nil +} + // RestoreBlockIntoPools - func (bpm *BlockProcessorMock) RestoreBlockIntoPools(header data.HeaderHandler, body data.BodyHandler) error { if bpm.RestoreBlockIntoPoolsCalled != nil { @@ -116,8 +178,8 @@ func (bpm *BlockProcessorMock) SetNumProcessedObj(_ uint64) { } // MarshalizedDataToBroadcast - -func (bpm *BlockProcessorMock) MarshalizedDataToBroadcast(header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) { - return bpm.MarshalizedDataToBroadcastCalled(header, body) +func (bpm *BlockProcessorMock) MarshalizedDataToBroadcast(headerHash []byte, header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) { + return bpm.MarshalizedDataToBroadcastCalled(headerHash, header, body) } // DecodeBlockBody method decodes block body from a given byte array @@ -163,6 +225,69 @@ func (bpm *BlockProcessorMock) NonceOfFirstCommittedBlock() core.OptionalUint64 } } +// RemoveHeaderFromPool - +func (bpm *BlockProcessorMock) RemoveHeaderFromPool(headerHash []byte) { + if bpm.RemoveHeaderFromPoolCalled != nil { + bpm.RemoveHeaderFromPoolCalled(headerHash) + } +} + +// PruneTrieAsyncHeader - +func (bpm *BlockProcessorMock) PruneTrieAsyncHeader() { + if bpm.PruneTrieAsyncHeaderCalled != nil { + bpm.PruneTrieAsyncHeaderCalled() + } +} + +// VerifyBlockProposal - +func (bpm *BlockProcessorMock) VerifyBlockProposal( + headerHandler data.HeaderHandler, + bodyHandler data.BodyHandler, + haveTime func() time.Duration, +) error { + if bpm.VerifyBlockProposalCalled != nil { + return bpm.VerifyBlockProposalCalled(headerHandler, bodyHandler, haveTime) + } + return nil +} + +// OnProposedBlock - +func (bpm *BlockProcessorMock) OnProposedBlock( + proposedBody data.BodyHandler, + proposedHeader data.HeaderHandler, + proposedHash []byte, +) error { + if bpm.OnProposedBlockCalled != nil { + return bpm.OnProposedBlockCalled(proposedBody, proposedHeader, proposedHash) + } + return nil +} + +// OnBackfilledBlock - +func (bpm *BlockProcessorMock) OnBackfilledBlock( + _ data.BodyHandler, + _ data.HeaderHandler, + _ []byte, +) error { + return nil +} + +// OnExecutedBlock - +func (bpm *BlockProcessorMock) OnExecutedBlock(header data.HeaderHandler, rootHash []byte) error { + if bpm.OnExecutedBlockCalled != nil { + return bpm.OnExecutedBlockCalled(header, rootHash) + } + return nil +} + +// ProposedDirectSentTransactionsToBroadcast - +func (bpm *BlockProcessorMock) ProposedDirectSentTransactionsToBroadcast(proposedBody data.BodyHandler) map[string][][]byte { + if bpm.ProposedDirectSentTransactionsToBroadcastCalled != nil { + return bpm.ProposedDirectSentTransactionsToBroadcastCalled(proposedBody) + } + return nil +} + // Close - func (bpm *BlockProcessorMock) Close() error { return nil diff --git a/integrationTests/mock/blockTrackerStub.go b/integrationTests/mock/blockTrackerStub.go index eea751ecf48..36418df7ed6 100644 --- a/integrationTests/mock/blockTrackerStub.go +++ b/integrationTests/mock/blockTrackerStub.go @@ -3,6 +3,7 @@ package mock import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/process" ) @@ -12,8 +13,10 @@ type BlockTrackerStub struct { AddCrossNotarizedHeaderCalled func(shardID uint32, crossNotarizedHeader data.HeaderHandler, crossNotarizedHeaderHash []byte) AddSelfNotarizedHeaderCalled func(shardID uint32, selfNotarizedHeader data.HeaderHandler, selfNotarizedHeaderHash []byte) CheckBlockAgainstFinalCalled func(headerHandler data.HeaderHandler) error + CheckProofAgainstFinalCalled func(proof data.HeaderProofHandler) error CheckBlockAgainstRoundHandlerCalled func(headerHandler data.HeaderHandler) error - CheckBlockAgainstWhitelistCalled func(interceptedData process.InterceptedData) bool + CheckProofAgainstRoundHandlerCalled func(proof data.HeaderProofHandler) error + CheckAgainstWhitelistCalled func(interceptedData process.InterceptedData) bool CleanupHeadersBehindNonceCalled func(shardID uint32, selfNotarizedNonce uint64, crossNotarizedNonce uint64) ComputeLongestChainCalled func(shardID uint32, header data.HeaderHandler) ([]data.HeaderHandler, [][]byte) ComputeLongestMetaChainFromLastNotarizedCalled func() ([]data.HeaderHandler, [][]byte, error) @@ -28,6 +31,7 @@ type BlockTrackerStub struct { GetTrackedHeadersForAllShardsCalled func() map[uint32][]data.HeaderHandler GetTrackedHeadersWithNonceCalled func(shardID uint32, nonce uint64) ([]data.HeaderHandler, [][]byte) IsShardStuckCalled func(shardId uint32) bool + IsOwnShardStuckCalled func() bool ShouldSkipMiniBlocksCreationFromSelfCalled func() bool RegisterCrossNotarizedHeadersHandlerCalled func(handler func(shardID uint32, headers []data.HeaderHandler, headersHashes [][]byte)) RegisterSelfNotarizedFromCrossHeadersHandlerCalled func(handler func(shardID uint32, headers []data.HeaderHandler, headersHashes [][]byte)) @@ -36,6 +40,7 @@ type BlockTrackerStub struct { RemoveLastNotarizedHeadersCalled func() RestoreToGenesisCalled func() ShouldAddHeaderCalled func(headerHandler data.HeaderHandler) bool + ComputeOwnShardStuckCalled func(lastExecutionResultsInfo data.BaseExecutionResultHandler, currentNonce uint64) } // AddTrackedHeader - @@ -77,10 +82,28 @@ func (bts *BlockTrackerStub) CheckBlockAgainstFinal(headerHandler data.HeaderHan return nil } -// CheckBlockAgainstWhitelist - -func (bts *BlockTrackerStub) CheckBlockAgainstWhitelist(interceptedData process.InterceptedData) bool { - if bts.CheckBlockAgainstWhitelistCalled != nil { - return bts.CheckBlockAgainstWhitelistCalled(interceptedData) +// CheckProofAgainstFinal - +func (bts *BlockTrackerStub) CheckProofAgainstFinal(proof data.HeaderProofHandler) error { + if bts.CheckProofAgainstFinalCalled != nil { + return bts.CheckProofAgainstFinalCalled(proof) + } + + return nil +} + +// CheckProofAgainstRoundHandler - +func (bts *BlockTrackerStub) CheckProofAgainstRoundHandler(proof data.HeaderProofHandler) error { + if bts.CheckProofAgainstRoundHandlerCalled != nil { + return bts.CheckProofAgainstRoundHandlerCalled(proof) + } + + return nil +} + +// CheckAgainstWhitelist - +func (bts *BlockTrackerStub) CheckAgainstWhitelist(interceptedData process.InterceptedData) bool { + if bts.CheckAgainstWhitelistCalled != nil { + return bts.CheckAgainstWhitelistCalled(interceptedData) } return false @@ -211,6 +234,15 @@ func (bts *BlockTrackerStub) IsShardStuck(shardId uint32) bool { return false } +// IsOwnShardStuck - +func (bts *BlockTrackerStub) IsOwnShardStuck() bool { + if bts.IsOwnShardStuckCalled != nil { + return bts.IsOwnShardStuckCalled() + } + + return false +} + // ShouldSkipMiniBlocksCreationFromSelf - func (bts *BlockTrackerStub) ShouldSkipMiniBlocksCreationFromSelf() bool { if bts.ShouldSkipMiniBlocksCreationFromSelfCalled != nil { @@ -270,6 +302,13 @@ func (bts *BlockTrackerStub) ShouldAddHeader(headerHandler data.HeaderHandler) b return true } +// ComputeOwnShardStuck - +func (bts *BlockTrackerStub) ComputeOwnShardStuck(lastExecutionResultsInfo data.BaseExecutionResultHandler, currentNonce uint64) { + if bts.ComputeOwnShardStuckCalled != nil { + bts.ComputeOwnShardStuckCalled(lastExecutionResultsInfo, currentNonce) + } +} + // IsInterfaceNil - func (bts *BlockTrackerStub) IsInterfaceNil() bool { return bts == nil diff --git a/integrationTests/mock/coreComponentsStub.go b/integrationTests/mock/coreComponentsStub.go index f221a77610b..8d7674dc333 100644 --- a/integrationTests/mock/coreComponentsStub.go +++ b/integrationTests/mock/coreComponentsStub.go @@ -1,6 +1,7 @@ package mock import ( + "sync/atomic" "time" "github.com/multiversx/mx-chain-core-go/core" @@ -45,10 +46,11 @@ type CoreComponentsStub struct { NodesShufflerField nodesCoordinator.NodesShuffler EpochNotifierField process.EpochNotifier RoundNotifierField process.RoundNotifier - EnableRoundsHandlerField process.EnableRoundsHandler + EnableRoundsHandlerField common.EnableRoundsHandler EpochStartNotifierWithConfirmField factory.EpochStartNotifierWithConfirm ChanStopNodeProcessField chan endProcess.ArgEndProcess GenesisTimeField time.Time + SupernovaGenesisTimeField time.Time TxVersionCheckField process.TxVersionCheckerHandler NodeTypeProviderField core.NodeTypeProviderHandler WasmVMChangeLockerInternal common.Locker @@ -59,6 +61,10 @@ type CoreComponentsStub struct { ChainParametersSubscriberField process.ChainParametersSubscriber FieldsSizeCheckerField common.FieldsSizeChecker EpochChangeGracePeriodHandlerField common.EpochChangeGracePeriodHandler + ProcessConfigsHandlerField common.ProcessConfigsHandler + CommonConfigsHandlerField common.CommonConfigsHandler + AntifloodConfigsHandlerField common.AntifloodConfigsHandler + ClosingNodeStartedField *atomic.Bool } // Create - @@ -143,7 +149,7 @@ func (ccs *CoreComponentsStub) RoundNotifier() process.RoundNotifier { } // EnableRoundsHandler - -func (ccs *CoreComponentsStub) EnableRoundsHandler() process.EnableRoundsHandler { +func (ccs *CoreComponentsStub) EnableRoundsHandler() common.EnableRoundsHandler { return ccs.EnableRoundsHandlerField } @@ -157,6 +163,11 @@ func (ccs *CoreComponentsStub) GenesisTime() time.Time { return ccs.GenesisTimeField } +// SupernovaGenesisTime - +func (ccs *CoreComponentsStub) SupernovaGenesisTime() time.Time { + return ccs.SupernovaGenesisTimeField +} + // InternalMarshalizer - func (ccs *CoreComponentsStub) InternalMarshalizer() marshal.Marshalizer { return ccs.InternalMarshalizerField @@ -284,6 +295,30 @@ func (ccs *CoreComponentsStub) EpochChangeGracePeriodHandler() common.EpochChang return ccs.EpochChangeGracePeriodHandlerField } +// ProcessConfigsHandler - +func (ccs *CoreComponentsStub) ProcessConfigsHandler() common.ProcessConfigsHandler { + return ccs.ProcessConfigsHandlerField +} + +// CommonConfigsHandler - +func (ccs *CoreComponentsStub) CommonConfigsHandler() common.CommonConfigsHandler { + return ccs.CommonConfigsHandlerField +} + +// AntifloodConfigsHandler - +func (ccs *CoreComponentsStub) AntifloodConfigsHandler() common.AntifloodConfigsHandler { + return ccs.AntifloodConfigsHandlerField +} + +// ClosingNodeStarted - +func (ccs *CoreComponentsStub) ClosingNodeStarted() *atomic.Bool { + if ccs.ClosingNodeStartedField == nil { + ccs.ClosingNodeStartedField = &atomic.Bool{} + } + + return ccs.ClosingNodeStartedField +} + // IsInterfaceNil - func (ccs *CoreComponentsStub) IsInterfaceNil() bool { return ccs == nil diff --git a/integrationTests/mock/cryptoComponentsStub.go b/integrationTests/mock/cryptoComponentsStub.go index 53abe25fe9b..2ac9687a1d8 100644 --- a/integrationTests/mock/cryptoComponentsStub.go +++ b/integrationTests/mock/cryptoComponentsStub.go @@ -125,7 +125,7 @@ func (ccs *CryptoComponentsStub) SetMultiSignerContainer(ms cryptoCommon.MultiSi } // GetMultiSigner - -func (ccs *CryptoComponentsStub) GetMultiSigner(epoch uint32) (crypto.MultiSigner, error) { +func (ccs *CryptoComponentsStub) GetMultiSigner(epoch uint32) (crypto.MultiSignerV2, error) { ccs.mutMultiSig.RLock() defer ccs.mutMultiSig.RUnlock() diff --git a/integrationTests/mock/endOfEpochTriggerStub.go b/integrationTests/mock/endOfEpochTriggerStub.go index eb132fac8dd..fce663469ea 100644 --- a/integrationTests/mock/endOfEpochTriggerStub.go +++ b/integrationTests/mock/endOfEpochTriggerStub.go @@ -4,6 +4,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/epochStart" ) @@ -19,6 +20,42 @@ type EpochStartTriggerStub struct { UpdateCalled func(round uint64, nonce uint64) ProcessedCalled func(header data.HeaderHandler) EpochStartRoundCalled func() uint64 + ShouldProposeEpochChangeCalled func(round uint64, nonce uint64) bool + SetEpochChangeCalled func(round uint64) + SetEpochChangeProposedCalled func(value bool) + GetEpochChangeProposedCalled func() bool +} + +// SetEpochChange - +func (e *EpochStartTriggerStub) SetEpochChange(round uint64) { + if e.SetEpochChangeCalled != nil { + e.SetEpochChangeCalled(round) + } +} + +// ShouldProposeEpochChange - +func (e *EpochStartTriggerStub) ShouldProposeEpochChange(round uint64, nonce uint64) bool { + if e.ShouldProposeEpochChangeCalled != nil { + return e.ShouldProposeEpochChangeCalled(round, nonce) + } + + return false +} + +// SetEpochChangeProposed - +func (e *EpochStartTriggerStub) SetEpochChangeProposed(value bool) { + if e.SetEpochChangeProposedCalled != nil { + e.SetEpochChangeProposedCalled(value) + } +} + +// GetEpochChangeProposed - +func (e *EpochStartTriggerStub) GetEpochChangeProposed() bool { + if e.GetEpochChangeProposedCalled != nil { + return e.GetEpochChangeProposedCalled() + } + + return false } // RevertStateToBlock - diff --git a/integrationTests/mock/epochEconomicsStub.go b/integrationTests/mock/epochEconomicsStub.go index 99e8b0dd359..71f710a925f 100644 --- a/integrationTests/mock/epochEconomicsStub.go +++ b/integrationTests/mock/epochEconomicsStub.go @@ -3,30 +3,48 @@ package mock import ( "math/big" + "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" ) // EpochEconomicsStub - type EpochEconomicsStub struct { - ComputeEndOfEpochEconomicsCalled func(metaBlock *block.MetaBlock) (*block.Economics, error) - VerifyRewardsPerBlockCalled func( - metaBlock *block.MetaBlock, correctedProtocolSustainability *big.Int, computedEconomics *block.Economics, + ComputeEndOfEpochEconomicsCalled func(metaBlock data.MetaHeaderHandler) (*block.Economics, error) + ComputeEndOfEpochEconomicsV3Called func( + metaBlock data.MetaHeaderHandler, + prevBlockExecutionResults data.BaseMetaExecutionResultHandler, + epochStartHandler data.EpochStartHandler, + ) (*block.Economics, error) + VerifyRewardsPerBlockCalled func( + metaBlock data.MetaHeaderHandler, correctedProtocolSustainability *big.Int, computedEconomics data.EconomicsHandler, ) error } // ComputeEndOfEpochEconomics - -func (e *EpochEconomicsStub) ComputeEndOfEpochEconomics(metaBlock *block.MetaBlock) (*block.Economics, error) { +func (e *EpochEconomicsStub) ComputeEndOfEpochEconomics(metaBlock data.MetaHeaderHandler) (*block.Economics, error) { if e.ComputeEndOfEpochEconomicsCalled != nil { return e.ComputeEndOfEpochEconomicsCalled(metaBlock) } return &block.Economics{}, nil } +// ComputeEndOfEpochEconomicsV3 - +func (e *EpochEconomicsStub) ComputeEndOfEpochEconomicsV3( + metaBlock data.MetaHeaderHandler, + prevBlockExecutionResults data.BaseMetaExecutionResultHandler, + epochStartHandler data.EpochStartHandler, +) (*block.Economics, error) { + if e.ComputeEndOfEpochEconomicsV3Called != nil { + return e.ComputeEndOfEpochEconomicsV3Called(metaBlock, prevBlockExecutionResults, epochStartHandler) + } + return &block.Economics{}, nil +} + // VerifyRewardsPerBlock - func (e *EpochEconomicsStub) VerifyRewardsPerBlock( - metaBlock *block.MetaBlock, + metaBlock data.MetaHeaderHandler, correctedProtocolSustainability *big.Int, - computedEconomics *block.Economics, + computedEconomics data.EconomicsHandler, ) error { if e.VerifyRewardsPerBlockCalled != nil { return e.VerifyRewardsPerBlockCalled(metaBlock, correctedProtocolSustainability, computedEconomics) diff --git a/integrationTests/mock/epochStartDataCreatorStub.go b/integrationTests/mock/epochStartDataCreatorStub.go index 1cbfccaec5b..de82c9b707d 100644 --- a/integrationTests/mock/epochStartDataCreatorStub.go +++ b/integrationTests/mock/epochStartDataCreatorStub.go @@ -1,11 +1,15 @@ package mock -import "github.com/multiversx/mx-chain-core-go/data/block" +import ( + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" +) // EpochStartDataCreatorStub - type EpochStartDataCreatorStub struct { CreateEpochStartDataCalled func() (*block.EpochStart, error) - VerifyEpochStartDataForMetablockCalled func(metaBlock *block.MetaBlock) error + CreateEpochStartDataMetablockV3Called func(metablock data.MetaHeaderHandler) ([]block.EpochStartShardData, error) + VerifyEpochStartDataForMetablockCalled func(metaBlock data.MetaHeaderHandler) error } // CreateEpochStartData - @@ -16,8 +20,16 @@ func (e *EpochStartDataCreatorStub) CreateEpochStartData() (*block.EpochStart, e return &block.EpochStart{}, nil } +// CreateEpochStartShardDataMetablockV3 - +func (e *EpochStartDataCreatorStub) CreateEpochStartShardDataMetablockV3(metablock data.MetaHeaderHandler) ([]block.EpochStartShardData, error) { + if e.CreateEpochStartDataMetablockV3Called != nil { + return e.CreateEpochStartDataMetablockV3Called(metablock) + } + return nil, nil +} + // VerifyEpochStartDataForMetablock - -func (e *EpochStartDataCreatorStub) VerifyEpochStartDataForMetablock(metaBlock *block.MetaBlock) error { +func (e *EpochStartDataCreatorStub) VerifyEpochStartDataForMetablock(metaBlock data.MetaHeaderHandler) error { if e.VerifyEpochStartDataForMetablockCalled != nil { return e.VerifyEpochStartDataForMetablockCalled(metaBlock) } diff --git a/integrationTests/mock/headerIntegrityVerifierStub.go b/integrationTests/mock/headerIntegrityVerifierStub.go index 3d793b89924..5fa049cd3bf 100644 --- a/integrationTests/mock/headerIntegrityVerifierStub.go +++ b/integrationTests/mock/headerIntegrityVerifierStub.go @@ -5,7 +5,7 @@ import "github.com/multiversx/mx-chain-core-go/data" // HeaderIntegrityVerifierStub - type HeaderIntegrityVerifierStub struct { VerifyCalled func(header data.HeaderHandler) error - GetVersionCalled func(epoch uint32) string + GetVersionCalled func(epoch uint32, round uint64) string } // Verify - @@ -18,9 +18,9 @@ func (h *HeaderIntegrityVerifierStub) Verify(header data.HeaderHandler) error { } // GetVersion - -func (h *HeaderIntegrityVerifierStub) GetVersion(epoch uint32) string { +func (h *HeaderIntegrityVerifierStub) GetVersion(epoch uint32, round uint64) string { if h.GetVersionCalled != nil { - return h.GetVersionCalled(epoch) + return h.GetVersionCalled(epoch, round) } return "version" diff --git a/integrationTests/mock/processComponentsStub.go b/integrationTests/mock/processComponentsStub.go index abecf1fb5f5..fe21fee154a 100644 --- a/integrationTests/mock/processComponentsStub.go +++ b/integrationTests/mock/processComponentsStub.go @@ -1,6 +1,8 @@ package mock import ( + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/multiversx/mx-chain-go/consensus" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/dblookupext" @@ -11,7 +13,6 @@ import ( "github.com/multiversx/mx-chain-go/sharding" "github.com/multiversx/mx-chain-go/sharding/nodesCoordinator" "github.com/multiversx/mx-chain-go/update" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" ) // ProcessComponentsStub - @@ -30,6 +31,7 @@ type ProcessComponentsStub struct { EpochNotifier factory.EpochStartNotifier ForkDetect process.ForkDetector BlockProcess process.BlockProcessor + ExecManager process.ExecutionManager BlackListHdl process.TimeCacher BootSore process.BootStorer HeaderSigVerif process.InterceptedHeaderSigVerifier @@ -62,6 +64,7 @@ type ProcessComponentsStub struct { SentSignaturesTrackerInternal process.SentSignaturesTracker EpochSystemSCProcessorInternal process.EpochStartSystemSCProcessor BlockchainHookField process.BlockChainHookWithAccountsAdapter + AOTSelectorField process.AOTTransactionSelector } // Create - @@ -143,6 +146,11 @@ func (pcs *ProcessComponentsStub) BlockProcessor() process.BlockProcessor { return pcs.BlockProcess } +// ExecutionManager - +func (pcs *ProcessComponentsStub) ExecutionManager() process.ExecutionManager { + return pcs.ExecManager +} + // BlackListHandler - func (pcs *ProcessComponentsStub) BlackListHandler() process.TimeCacher { return pcs.BlackListHdl @@ -308,6 +316,11 @@ func (pcs *ProcessComponentsStub) BlockchainHook() process.BlockChainHookWithAcc return pcs.BlockchainHookField } +// AOTSelector - +func (pcs *ProcessComponentsStub) AOTSelector() process.AOTTransactionSelector { + return pcs.AOTSelectorField +} + // IsInterfaceNil - func (pcs *ProcessComponentsStub) IsInterfaceNil() bool { return pcs == nil diff --git a/integrationTests/mock/raterMock.go b/integrationTests/mock/raterMock.go index ffee38747cf..546d2061bd2 100644 --- a/integrationTests/mock/raterMock.go +++ b/integrationTests/mock/raterMock.go @@ -5,11 +5,11 @@ type RaterMock struct { GetRatingCalled func(string) uint32 GetStartRatingCalled func() uint32 GetSignedBlocksThresholdCalled func() float32 - ComputeIncreaseProposerCalled func(shardId uint32, rating uint32) uint32 - ComputeDecreaseProposerCalled func(shardId uint32, rating uint32, consecutiveMissedBlocks uint32) uint32 - RevertIncreaseProposerCalled func(shardId uint32, rating uint32, nrReverts uint32) uint32 - ComputeIncreaseValidatorCalled func(shardId uint32, rating uint32) uint32 - ComputeDecreaseValidatorCalled func(shardId uint32, rating uint32) uint32 + ComputeIncreaseProposerCalled func(shardId uint32, rating uint32, epoch uint32) uint32 + ComputeDecreaseProposerCalled func(shardId uint32, rating uint32, consecutiveMissedBlocks uint32, epoch uint32) uint32 + RevertIncreaseValidatorCalled func(shardId uint32, rating uint32, nrReverts uint32, epoch uint32) uint32 + ComputeIncreaseValidatorCalled func(shardId uint32, rating uint32, epoch uint32) uint32 + ComputeDecreaseValidatorCalled func(shardId uint32, rating uint32, epoch uint32) uint32 GetChanceCalled func(rating uint32) uint32 } @@ -32,28 +32,28 @@ func (rm *RaterMock) GetSignedBlocksThreshold() float32 { } // ComputeIncreaseProposer - -func (rm *RaterMock) ComputeIncreaseProposer(shardId uint32, currentRating uint32) uint32 { - return rm.ComputeIncreaseProposerCalled(shardId, currentRating) +func (rm *RaterMock) ComputeIncreaseProposer(shardId uint32, currentRating uint32, epoch uint32) uint32 { + return rm.ComputeIncreaseProposerCalled(shardId, currentRating, epoch) } // ComputeDecreaseProposer - -func (rm *RaterMock) ComputeDecreaseProposer(shardId uint32, currentRating uint32, consecutiveMisses uint32) uint32 { - return rm.ComputeDecreaseProposerCalled(shardId, currentRating, consecutiveMisses) +func (rm *RaterMock) ComputeDecreaseProposer(shardId uint32, currentRating uint32, consecutiveMisses uint32, epoch uint32) uint32 { + return rm.ComputeDecreaseProposerCalled(shardId, currentRating, consecutiveMisses, epoch) } // RevertIncreaseValidator - -func (rm *RaterMock) RevertIncreaseValidator(shardId uint32, currentRating uint32, nrReverts uint32) uint32 { - return rm.RevertIncreaseProposerCalled(shardId, currentRating, nrReverts) +func (rm *RaterMock) RevertIncreaseValidator(shardId uint32, currentRating uint32, nrReverts uint32, epoch uint32) uint32 { + return rm.RevertIncreaseValidatorCalled(shardId, currentRating, nrReverts, epoch) } // ComputeIncreaseValidator - -func (rm *RaterMock) ComputeIncreaseValidator(shardId uint32, currentRating uint32) uint32 { - return rm.ComputeIncreaseValidatorCalled(shardId, currentRating) +func (rm *RaterMock) ComputeIncreaseValidator(shardId uint32, currentRating uint32, epoch uint32) uint32 { + return rm.ComputeIncreaseValidatorCalled(shardId, currentRating, epoch) } // ComputeDecreaseValidator - -func (rm *RaterMock) ComputeDecreaseValidator(shardId uint32, currentRating uint32) uint32 { - return rm.ComputeDecreaseValidatorCalled(shardId, currentRating) +func (rm *RaterMock) ComputeDecreaseValidator(shardId uint32, currentRating uint32, epoch uint32) uint32 { + return rm.ComputeDecreaseValidatorCalled(shardId, currentRating, epoch) } // GetChance - diff --git a/integrationTests/mock/roundHandlerMock.go b/integrationTests/mock/roundHandlerMock.go index 4234684ada7..8051a909e01 100644 --- a/integrationTests/mock/roundHandlerMock.go +++ b/integrationTests/mock/roundHandlerMock.go @@ -53,6 +53,11 @@ func (mock *RoundHandlerMock) RemainingTime(_ time.Time, _ time.Duration) time.D return mock.RemainingTimeField } +// GetTimeStampForRound - +func (mock *RoundHandlerMock) GetTimeStampForRound(round uint64) uint64 { + return round * uint64(mock.TimeDuration().Milliseconds()) +} + // IsInterfaceNil - func (mock *RoundHandlerMock) IsInterfaceNil() bool { return mock == nil diff --git a/integrationTests/mock/syncTimerMock.go b/integrationTests/mock/syncTimerMock.go index 2fa41d42341..77c0a8d487b 100644 --- a/integrationTests/mock/syncTimerMock.go +++ b/integrationTests/mock/syncTimerMock.go @@ -15,6 +15,10 @@ func (stm *SyncTimerMock) StartSyncingTime() { panic("implement me") } +// ForceSync - +func (stm *SyncTimerMock) ForceSync() { +} + // ClockOffset method gets the current time offset func (stm *SyncTimerMock) ClockOffset() time.Duration { if stm.ClockOffsetCalled != nil { diff --git a/integrationTests/mock/transactionCoordinatorMock.go b/integrationTests/mock/transactionCoordinatorMock.go index 337302148ed..73052e72f26 100644 --- a/integrationTests/mock/transactionCoordinatorMock.go +++ b/integrationTests/mock/transactionCoordinatorMock.go @@ -5,6 +5,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/block/processedMb" @@ -21,11 +22,15 @@ type TransactionCoordinatorMock struct { RemoveBlockDataFromPoolCalled func(body *block.Body) error RemoveTxsFromPoolCalled func(body *block.Body) error ProcessBlockTransactionCalled func(header data.HeaderHandler, body *block.Body, haveTime func() time.Duration) error + GetCreatedMiniBlocksFromMeCalled func() block.MiniBlockSlice CreateBlockStartedCalled func() + CreateMbsCrossShardDstMeCalled func(header data.HeaderHandler, processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo) ([]block.MiniblockAndHash, []block.MiniblockAndHash, uint32, bool, bool, error) CreateMbsAndProcessCrossShardTransactionsDstMeCalled func(header data.HeaderHandler, processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo, haveTime func() bool, haveAdditionalTime func() bool, scheduledMode bool) (block.MiniBlockSlice, uint32, bool, error) CreateMbsAndProcessTransactionsFromMeCalled func(haveTime func() bool) block.MiniBlockSlice CreateMarshalizedDataCalled func(body *block.Body) map[string][][]byte + CreateMarshalledDataForHeaderCalled func(header data.HeaderHandler, body *block.Body, miniBlocksMap map[string]block.MiniBlockSlice) map[string][][]byte GetCreatedInShardMiniBlocksCalled func() []*block.MiniBlock + SelectOutgoingTransactionsCalled func(nonce uint64, haveTimeForSelection func() bool) ([][]byte, []data.MiniBlockHeaderHandler) GetAllCurrentUsedTxsCalled func(blockType block.Type) map[string]data.TransactionHandler VerifyCreatedBlockTransactionsCalled func(hdr data.HeaderHandler, body *block.Body) error CreatePostProcessMiniBlocksCalled func() block.MiniBlockSlice @@ -35,10 +40,12 @@ type TransactionCoordinatorMock struct { AddTxsFromMiniBlocksCalled func(miniBlocks block.MiniBlockSlice) AddTransactionsCalled func(txHandlers []data.TransactionHandler, blockType block.Type) ComputeTransactionTypeInEpochCalled func(tx data.TransactionHandler, epoch uint32) (process.TransactionType, process.TransactionType, bool) + GetUnExecutableTransactionsCalled func() map[string]struct{} + ProposedDirectSentTransactionsToBroadcastCalled func(proposedBody data.BodyHandler) map[string][][]byte } // GetAllCurrentLogs - -func (tcm *TransactionCoordinatorMock) GetAllCurrentLogs() []*data.LogData { +func (tcm *TransactionCoordinatorMock) GetAllCurrentLogs() []data.LogDataHandler { return nil } @@ -109,6 +116,15 @@ func (tcm *TransactionCoordinatorMock) SaveTxsToStorage(body *block.Body) { tcm.SaveTxsToStorageCalled(body) } +// GetUnExecutableTransactions - +func (tcm *TransactionCoordinatorMock) GetUnExecutableTransactions() map[string]struct{} { + if tcm.GetUnExecutableTransactionsCalled != nil { + return tcm.GetUnExecutableTransactionsCalled() + } + + return nil +} + // RestoreBlockDataFromStorage - func (tcm *TransactionCoordinatorMock) RestoreBlockDataFromStorage(body *block.Body) (int, error) { if tcm.RestoreBlockDataFromStorageCalled == nil { @@ -128,7 +144,7 @@ func (tcm *TransactionCoordinatorMock) RemoveBlockDataFromPool(body *block.Body) } // RemoveTxsFromPool - -func (tcm *TransactionCoordinatorMock) RemoveTxsFromPool(body *block.Body) error { +func (tcm *TransactionCoordinatorMock) RemoveTxsFromPool(body *block.Body, _ common.RootHashHolder) error { if tcm.RemoveTxsFromPoolCalled == nil { return nil } @@ -145,6 +161,15 @@ func (tcm *TransactionCoordinatorMock) ProcessBlockTransaction(header data.Heade return tcm.ProcessBlockTransactionCalled(header, body, haveTime) } +// GetCreatedMiniBlocksFromMe - +func (tcm *TransactionCoordinatorMock) GetCreatedMiniBlocksFromMe() block.MiniBlockSlice { + if tcm.GetCreatedMiniBlocksFromMeCalled == nil { + return nil + } + + return tcm.GetCreatedMiniBlocksFromMeCalled() +} + // CreateBlockStarted - func (tcm *TransactionCoordinatorMock) CreateBlockStarted() { if tcm.CreateBlockStartedCalled == nil { @@ -154,6 +179,18 @@ func (tcm *TransactionCoordinatorMock) CreateBlockStarted() { tcm.CreateBlockStartedCalled() } +// CreateMbsCrossShardDstMe - +func (tcm *TransactionCoordinatorMock) CreateMbsCrossShardDstMe( + header data.HeaderHandler, + processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo, +) ([]block.MiniblockAndHash, []block.MiniblockAndHash, uint32, bool, bool, error) { + if tcm.CreateMbsCrossShardDstMeCalled == nil { + return nil, nil, 0, false, false, nil + } + + return tcm.CreateMbsCrossShardDstMeCalled(header, processedMiniBlocksInfo) +} + // CreateMbsAndProcessCrossShardTransactionsDstMe - func (tcm *TransactionCoordinatorMock) CreateMbsAndProcessCrossShardTransactionsDstMe( header data.HeaderHandler, @@ -169,6 +206,15 @@ func (tcm *TransactionCoordinatorMock) CreateMbsAndProcessCrossShardTransactions return tcm.CreateMbsAndProcessCrossShardTransactionsDstMeCalled(header, processedMiniBlocksInfo, haveTime, haveAdditionalTime, scheduledMode) } +// SelectOutgoingTransactions - +func (tcm *TransactionCoordinatorMock) SelectOutgoingTransactions(nonce uint64, haveTimeForSelection func() bool) ([][]byte, []data.MiniBlockHeaderHandler) { + if tcm.SelectOutgoingTransactionsCalled == nil { + return make([][]byte, 0), make([]data.MiniBlockHeaderHandler, 0) + } + + return tcm.SelectOutgoingTransactionsCalled(nonce, haveTimeForSelection) +} + // CreateMbsAndProcessTransactionsFromMe - func (tcm *TransactionCoordinatorMock) CreateMbsAndProcessTransactionsFromMe(haveTime func() bool, _ []byte) block.MiniBlockSlice { if tcm.CreateMbsAndProcessTransactionsFromMeCalled == nil { @@ -187,6 +233,15 @@ func (tcm *TransactionCoordinatorMock) CreateMarshalizedData(body *block.Body) m return tcm.CreateMarshalizedDataCalled(body) } +// CreateMarshalledDataForHeader - +func (tcm *TransactionCoordinatorMock) CreateMarshalledDataForHeader(header data.HeaderHandler, body *block.Body, miniBlocksMap map[string]block.MiniBlockSlice) map[string][][]byte { + if tcm.CreateMarshalledDataForHeaderCalled == nil { + return make(map[string][][]byte) + } + + return tcm.CreateMarshalledDataForHeaderCalled(header, body, miniBlocksMap) +} + // GetCreatedInShardMiniBlocks - func (tcm *TransactionCoordinatorMock) GetCreatedInShardMiniBlocks() []*block.MiniBlock { if tcm.GetCreatedInShardMiniBlocksCalled != nil { @@ -258,6 +313,15 @@ func (tcm *TransactionCoordinatorMock) AddTransactions(txHandlers []data.Transac tcm.AddTransactionsCalled(txHandlers, blockType) } +// ProposedDirectSentTransactionsToBroadcast - +func (tcm *TransactionCoordinatorMock) ProposedDirectSentTransactionsToBroadcast(proposedBody data.BodyHandler) map[string][][]byte { + if tcm.ProposedDirectSentTransactionsToBroadcastCalled == nil { + return nil + } + + return tcm.ProposedDirectSentTransactionsToBroadcastCalled(proposedBody) +} + // IsInterfaceNil returns true if there is no value under the interface func (tcm *TransactionCoordinatorMock) IsInterfaceNil() bool { return tcm == nil diff --git a/integrationTests/mock/txLogsProcessorMock.go b/integrationTests/mock/txLogsProcessorMock.go index 3158e836493..4d7b7cca215 100644 --- a/integrationTests/mock/txLogsProcessorMock.go +++ b/integrationTests/mock/txLogsProcessorMock.go @@ -2,6 +2,7 @@ package mock import ( "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-go/process" ) @@ -12,8 +13,8 @@ type TxLogProcessorMock struct { } // GetLogFromCache - -func (t *TxLogProcessorMock) GetLogFromCache(_ []byte) (*data.LogData, bool) { - return &data.LogData{}, false +func (t *TxLogProcessorMock) GetLogFromCache(_ []byte) (data.LogDataHandler, bool) { + return &transaction.LogData{}, false } // EnableLogToBeSavedInCache - diff --git a/integrationTests/mock/txLogsProcessorStub.go b/integrationTests/mock/txLogsProcessorStub.go index 124f5712843..c49425fcb5b 100644 --- a/integrationTests/mock/txLogsProcessorStub.go +++ b/integrationTests/mock/txLogsProcessorStub.go @@ -39,6 +39,6 @@ func (txls *TxLogsProcessorStub) IsInterfaceNil() bool { } // GetAllCurrentLogs - -func (txls *TxLogsProcessorStub) GetAllCurrentLogs() []*data.LogData { +func (txls *TxLogsProcessorStub) GetAllCurrentLogs() []data.LogDataHandler { return nil } diff --git a/integrationTests/multiShard/block/executingMiniblocks/executingMiniblocks_test.go b/integrationTests/multiShard/block/executingMiniblocks/executingMiniblocks_test.go index fcf5ec9178c..31f18566ee1 100644 --- a/integrationTests/multiShard/block/executingMiniblocks/executingMiniblocks_test.go +++ b/integrationTests/multiShard/block/executingMiniblocks/executingMiniblocks_test.go @@ -8,19 +8,16 @@ import ( "testing" "time" - "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-crypto-go" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/integrationTests" "github.com/multiversx/mx-chain-go/process/factory" "github.com/multiversx/mx-chain-go/sharding" "github.com/multiversx/mx-chain-go/state" + "github.com/stretchr/testify/assert" ) func TestShouldProcessBlocksInMultiShardArchitecture(t *testing.T) { @@ -233,125 +230,6 @@ func TestSimpleTransactionsWithMoreGasWhichYieldInReceiptsInMultiShardedEnvironm } } -func TestSimpleTransactionsWithMoreValueThanBalanceYieldReceiptsInMultiShardedEnvironment(t *testing.T) { - if testing.Short() { - t.Skip("this is not a short test") - } - - numOfShards := 2 - nodesPerShard := 2 - numMetachainNodes := 2 - - nodes := integrationTests.CreateNodes( - numOfShards, - nodesPerShard, - numMetachainNodes, - ) - - minGasLimit := uint64(10000) - for _, node := range nodes { - node.EconomicsData.SetMinGasLimit(minGasLimit, 0) - } - - leaders := make([]*integrationTests.TestProcessorNode, numOfShards+1) - for i := 0; i < numOfShards; i++ { - leaders[i] = nodes[i*nodesPerShard] - } - leaders[numOfShards] = nodes[numOfShards*nodesPerShard] - - integrationTests.DisplayAndStartNodes(nodes) - - defer func() { - for _, n := range nodes { - n.Close() - } - }() - - nrTxsToSend := uint64(10) - initialVal := big.NewInt(0).SetUint64(nrTxsToSend * minGasLimit * integrationTests.MinTxGasPrice) - halfInitVal := big.NewInt(0).Div(initialVal, big.NewInt(2)) - integrationTests.MintAllNodes(nodes, initialVal) - receiverAddress := []byte("12345678901234567890123456789012") - - round := uint64(0) - nonce := uint64(0) - round = integrationTests.IncrementAndPrintRound(round) - nonce++ - - for _, node := range nodes { - for j := uint64(0); j < nrTxsToSend; j++ { - integrationTests.PlayerSendsTransaction( - nodes, - node.OwnAccount, - receiverAddress, - halfInitVal, - "", - minGasLimit, - ) - } - } - - time.Sleep(2 * time.Second) - - integrationTests.UpdateRound(nodes, round) - integrationTests.ProposeBlock(nodes, leaders, round, nonce) - integrationTests.SyncBlock(t, nodes, leaders, round) - round = integrationTests.IncrementAndPrintRound(round) - nonce++ - - for _, node := range nodes { - if node.ShardCoordinator.SelfId() == core.MetachainShardId { - continue - } - - header := node.BlockChain.GetCurrentBlockHeader() - shardHdr, ok := header.(*block.Header) - numInvalid := 0 - require.True(t, ok) - for _, mb := range shardHdr.MiniBlockHeaders { - if mb.Type == block.InvalidBlock { - numInvalid++ - } - } - assert.Equal(t, 1, numInvalid) - } - - time.Sleep(time.Second) - numRoundsToTest := 6 - for i := 0; i < numRoundsToTest; i++ { - integrationTests.UpdateRound(nodes, round) - integrationTests.ProposeBlock(nodes, leaders, round, nonce) - integrationTests.SyncBlock(t, nodes, leaders, round) - round = integrationTests.IncrementAndPrintRound(round) - nonce++ - - time.Sleep(integrationTests.StepDelay) - } - - time.Sleep(time.Second) - - expectedReceiverValue := big.NewInt(0).Mul(big.NewInt(int64(len(nodes))), halfInitVal) - for _, verifierNode := range nodes { - for _, node := range nodes { - accWrp, err := verifierNode.AccntState.GetExistingAccount(node.OwnAccount.Address) - if err != nil { - continue - } - - account, _ := accWrp.(state.UserAccountHandler) - assert.Equal(t, big.NewInt(0), account.GetBalance()) - } - - accWrp, err := verifierNode.AccntState.GetExistingAccount(receiverAddress) - if err != nil { - continue - } - - account, _ := accWrp.(state.UserAccountHandler) - assert.Equal(t, expectedReceiverValue, account.GetBalance()) - } -} - // TestShouldSubtractTheCorrectTxFee uses the mock VM as it's gas model is predictable // The test checks the tx fee subtraction from the sender account when deploying a SC // It also checks the fee obtained by the leader is correct diff --git a/integrationTests/multiShard/endOfEpoch/epochChangeWithNodesShufflingAndRater/epochChangeWithNodesShufflingAndRater_test.go b/integrationTests/multiShard/endOfEpoch/epochChangeWithNodesShufflingAndRater/epochChangeWithNodesShufflingAndRater_test.go index 59c0abc5156..e853f490e70 100644 --- a/integrationTests/multiShard/endOfEpoch/epochChangeWithNodesShufflingAndRater/epochChangeWithNodesShufflingAndRater_test.go +++ b/integrationTests/multiShard/endOfEpoch/epochChangeWithNodesShufflingAndRater/epochChangeWithNodesShufflingAndRater_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" logger "github.com/multiversx/mx-chain-logger-go" "github.com/multiversx/mx-chain-go/integrationTests" @@ -25,7 +26,7 @@ func TestEpochChangeWithNodesShufflingAndRater(t *testing.T) { consensusGroupSize := 1 maxGasLimitPerBlock := uint64(100000) - rater, _ := rating.NewBlockSigningRater(integrationTests.CreateRatingsData()) + rater, _ := rating.NewBlockSigningRater(integrationTests.CreateRatingsData(), &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) coordinatorFactory := &integrationTests.IndexHashedNodesCoordinatorWithRaterFactory{ PeerAccountListAndRatingHandler: rater, } diff --git a/integrationTests/multiShard/endOfEpoch/epochStartChangeWithContinuousTransactionsInMultiShardedEnvironment/epochStartChangeWithContinuousTransactionsInMultiShardedEnvironment_test.go b/integrationTests/multiShard/endOfEpoch/epochStartChangeWithContinuousTransactionsInMultiShardedEnvironment/epochStartChangeWithContinuousTransactionsInMultiShardedEnvironment_test.go index f8bde5fb75d..0c84bdc5347 100644 --- a/integrationTests/multiShard/endOfEpoch/epochStartChangeWithContinuousTransactionsInMultiShardedEnvironment/epochStartChangeWithContinuousTransactionsInMultiShardedEnvironment_test.go +++ b/integrationTests/multiShard/endOfEpoch/epochStartChangeWithContinuousTransactionsInMultiShardedEnvironment/epochStartChangeWithContinuousTransactionsInMultiShardedEnvironment_test.go @@ -27,6 +27,7 @@ func TestEpochStartChangeWithContinuousTransactionsInMultiShardedEnvironment(t * StakingV4Step2EnableEpoch: integrationTests.UnreachableEpoch, StakingV4Step3EnableEpoch: integrationTests.UnreachableEpoch, AndromedaEnableEpoch: integrationTests.UnreachableEpoch, + SupernovaEnableEpoch: integrationTests.UnreachableEpoch, } nodes := integrationTests.CreateNodesWithEnableEpochs( diff --git a/integrationTests/multiShard/endOfEpoch/epochStartChangeWithoutTransactionInMultiShardedEnvironment/epochStartChangeWithoutTransactionInMultiShardedEnvironment_test.go b/integrationTests/multiShard/endOfEpoch/epochStartChangeWithoutTransactionInMultiShardedEnvironment/epochStartChangeWithoutTransactionInMultiShardedEnvironment_test.go index 92e626602a9..5f0f0f4aaf8 100644 --- a/integrationTests/multiShard/endOfEpoch/epochStartChangeWithoutTransactionInMultiShardedEnvironment/epochStartChangeWithoutTransactionInMultiShardedEnvironment_test.go +++ b/integrationTests/multiShard/endOfEpoch/epochStartChangeWithoutTransactionInMultiShardedEnvironment/epochStartChangeWithoutTransactionInMultiShardedEnvironment_test.go @@ -26,6 +26,7 @@ func TestEpochStartChangeWithoutTransactionInMultiShardedEnvironment(t *testing. StakingV4Step2EnableEpoch: integrationTests.UnreachableEpoch, StakingV4Step3EnableEpoch: integrationTests.UnreachableEpoch, AndromedaEnableEpoch: integrationTests.UnreachableEpoch, + SupernovaEnableEpoch: integrationTests.UnreachableEpoch, } nodes := integrationTests.CreateNodesWithEnableEpochs( diff --git a/integrationTests/multiShard/endOfEpoch/startInEpoch/startInEpoch_test.go b/integrationTests/multiShard/endOfEpoch/startInEpoch/startInEpoch_test.go index 8ac99dad211..8525f093cd4 100644 --- a/integrationTests/multiShard/endOfEpoch/startInEpoch/startInEpoch_test.go +++ b/integrationTests/multiShard/endOfEpoch/startInEpoch/startInEpoch_test.go @@ -11,7 +11,6 @@ import ( "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/endProcess" "github.com/multiversx/mx-chain-core-go/data/typeConverters/uint64ByteSlice" - dataRetrieverMocks "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/stretchr/testify/assert" "github.com/multiversx/mx-chain-go/common" @@ -38,12 +37,15 @@ import ( "github.com/multiversx/mx-chain-go/storage/storageunit" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/chainParameters" + dataRetrieverMocks "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/genericMocks" "github.com/multiversx/mx-chain-go/testscommon/genesisMocks" "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" "github.com/multiversx/mx-chain-go/testscommon/nodeTypeProviderMock" "github.com/multiversx/mx-chain-go/testscommon/p2pmocks" + "github.com/multiversx/mx-chain-go/testscommon/pool" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" "github.com/multiversx/mx-chain-go/testscommon/scheduledDataSyncer" "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" statusHandlerMock "github.com/multiversx/mx-chain-go/testscommon/statusHandler" @@ -79,6 +81,7 @@ func testNodeStartsInEpoch(t *testing.T, shardID uint32, expectedHighestRound ui StakingV4Step2EnableEpoch: integrationTests.UnreachableEpoch, StakingV4Step3EnableEpoch: integrationTests.UnreachableEpoch, AndromedaEnableEpoch: integrationTests.UnreachableEpoch, + SupernovaEnableEpoch: integrationTests.UnreachableEpoch, } nodes := integrationTests.CreateNodesWithEnableEpochs( @@ -153,7 +156,8 @@ func testNodeStartsInEpoch(t *testing.T, shardID uint32, expectedHighestRound ui generalConfig := getGeneralConfig() roundDurationMillis := 4000 - epochDurationMillis := generalConfig.EpochStartConfig.RoundsPerEpoch * int64(roundDurationMillis) + numRoundsPerEpoch := nodes[0].Node.GetCoreComponents().ChainParametersHandler().CurrentChainParameters().RoundsPerEpoch + epochDurationMillis := numRoundsPerEpoch * int64(roundDurationMillis) prefsConfig := config.PreferencesConfig{ FullArchive: false, } @@ -250,8 +254,9 @@ func testNodeStartsInEpoch(t *testing.T, shardID uint32, expectedHighestRound ui 444, ) interceptorDataVerifierArgs := interceptorsFactory.InterceptedDataVerifierFactoryArgs{ - CacheSpan: time.Second * 5, - CacheExpiry: time.Second * 10, + InterceptedDataVerifierConfig: config.InterceptedDataVerifierConfig{ + EnableCaching: false, + }, } argsBootstrapHandler := bootstrap.ArgsEpochStartBootstrap{ NodesCoordinatorRegistryFactory: nodesCoordinatorRegistryFactory, @@ -366,6 +371,7 @@ func testNodeStartsInEpoch(t *testing.T, shardID uint32, expectedHighestRound ui AppStatusHandler: &statusHandlerMock.AppStatusHandlerMock{}, EnableEpochsHandler: enableEpochsHandler, ProofsPool: &dataRetrieverMocks.ProofsPoolMock{}, + ExecutionManager: &processMocks.ExecutionManagerMock{}, } bootstrapper, err := getBootstrapper(shardID, argsBaseBootstrapper) @@ -381,7 +387,7 @@ func testNodeStartsInEpoch(t *testing.T, shardID uint32, expectedHighestRound ui func getBootstrapper(shardID uint32, baseArgs storageBootstrap.ArgsBaseStorageBootstrapper) (process.BootstrapperFromStorage, error) { if shardID == core.MetachainShardId { - pendingMiniBlocksHandler, _ := pendingMb.NewPendingMiniBlocks() + pendingMiniBlocksHandler, _ := pendingMb.NewPendingMiniBlocks(&pool.HeadersPoolStub{}) bootstrapperArgs := storageBootstrap.ArgsMetaStorageBootstrapper{ ArgsBaseStorageBootstrapper: baseArgs, PendingMiniBlocksHandler: pendingMiniBlocksHandler, diff --git a/integrationTests/multiShard/hardFork/hardFork_test.go b/integrationTests/multiShard/hardFork/hardFork_test.go index 5b2754110ef..234dd81f063 100644 --- a/integrationTests/multiShard/hardFork/hardFork_test.go +++ b/integrationTests/multiShard/hardFork/hardFork_test.go @@ -419,6 +419,7 @@ func hardForkImport( Core: coreComponents, Data: dataComponents, Accounts: node.AccntState, + AccountsProposal: node.AccntState, InitialNodesSetup: node.NodesSetup, Economics: node.EconomicsData, ShardCoordinator: node.ShardCoordinator, @@ -466,6 +467,7 @@ func hardForkImport( MinStepValue: "10", MinStakeValue: "1", UnBondPeriod: 1, + UnBondPeriodSupernova: 2, NumRoundsWithoutBleed: 1, MaximumPercentageToBleed: 1, BleedPercentagePerRound: 1, @@ -506,10 +508,21 @@ func hardForkImport( DelegationSmartContractEnableEpoch: 0, }, }, + FeeSettings: config.FeeSettings{ + BlockCapacityOverestimationFactor: 200, + PercentDecreaseLimitsStep: 10, + }, RoundConfig: testscommon.GetDefaultRoundsConfig(), HeaderVersionConfigs: testscommon.GetDefaultHeaderVersionConfig(), HistoryRepository: &dblookupext.HistoryRepositoryStub{}, TxExecutionOrderHandler: &commonMocks.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: config.TxCacheSelectionConfig{ + SelectionGasBandwidthIncreasePercent: 400, + SelectionGasBandwidthIncreaseScheduledPercent: 260, + SelectionGasRequested: 10_000_000_000, + SelectionMaxNumTxs: 30000, + SelectionLoopDurationCheckInterval: 10, + }, } genesisProcessor, err := process.NewGenesisBlockCreator(argsGenesis) @@ -611,8 +624,9 @@ func createHardForkExporter( networkComponents.OutputAntiFlood = &mock.NilAntifloodHandler{} interceptorDataVerifierFactoryArgs := interceptorFactory.InterceptedDataVerifierFactoryArgs{ - CacheSpan: time.Second * 5, - CacheExpiry: time.Second * 10, + InterceptedDataVerifierConfig: config.InterceptedDataVerifierConfig{ + EnableCaching: false, + }, } argsExportHandler := factory.ArgsExporter{ CoreComponents: coreComponents, @@ -654,21 +668,29 @@ func createHardForkExporter( HeaderIntegrityVerifier: node.HeaderIntegrityVerifier, ValidityAttester: node.BlockTracker, RoundHandler: &mock.RoundHandlerMock{}, - InterceptorDebugConfig: config.InterceptorResolverDebugConfig{ - Enabled: true, - EnablePrint: true, - CacheSize: 10000, - IntervalAutoPrintInSeconds: 20, - NumRequestsThreshold: 3, - NumResolveFailureThreshold: 3, - DebugLineExpiration: 3, + MaxHardCapForMissingNodes: 500, + NumConcurrentTrieSyncers: 50, + TrieSyncerVersion: 2, + CheckNodesOnDisk: false, + NodeOperationMode: node.NodeOperationMode, + InterceptedDataVerifierFactory: interceptorFactory.NewInterceptedDataVerifierFactory(interceptorDataVerifierFactoryArgs), + Config: config.Config{ + Debug: config.DebugConfig{ + InterceptorResolver: config.InterceptorResolverDebugConfig{ + Enabled: true, + EnablePrint: true, + CacheSize: 10000, + IntervalAutoPrintInSeconds: 20, + NumRequestsThreshold: 3, + NumResolveFailureThreshold: 3, + DebugLineExpiration: 3, + }, + }, + InterceptedDataVerifier: config.InterceptedDataVerifierConfig{ + CacheExpiryInSec: 1, + CacheSpanInSec: 1, + }, }, - MaxHardCapForMissingNodes: 500, - NumConcurrentTrieSyncers: 50, - TrieSyncerVersion: 2, - CheckNodesOnDisk: false, - NodeOperationMode: node.NodeOperationMode, - InterceptedDataVerifierFactory: interceptorFactory.NewInterceptedDataVerifierFactory(interceptorDataVerifierFactoryArgs), } exportHandler, err := factory.NewExportHandlerFactory(argsExportHandler) diff --git a/integrationTests/multiShard/relayedTx/relayedTx_test.go b/integrationTests/multiShard/relayedTx/relayedTx_test.go index bba732f7a5b..6ef2c75e28a 100644 --- a/integrationTests/multiShard/relayedTx/relayedTx_test.go +++ b/integrationTests/multiShard/relayedTx/relayedTx_test.go @@ -144,7 +144,7 @@ func testRelayedTransactionInMultiShardEnvironmentWithSmartContractTX( receiverAddress2 := []byte("12345678901234567890123456789011") integrationTests.MintAllPlayers(nodes, players, big.NewInt(1)) - + integrationTests.SetRootHashOfGenesisBlocks(nodes) ownerNode := nodes[0] initialSupply := "00" + hex.EncodeToString(big.NewInt(100000000000).Bytes()) scCode := wasm.GetSCCode("../../vm/wasm/testdata/erc20-c-03/wrc20_wasm.wasm") diff --git a/integrationTests/multiShard/smartContract/dns/dns_test.go b/integrationTests/multiShard/smartContract/dns/dns_test.go index 40f7117e469..a62dae82def 100644 --- a/integrationTests/multiShard/smartContract/dns/dns_test.go +++ b/integrationTests/multiShard/smartContract/dns/dns_test.go @@ -132,6 +132,7 @@ func prepareNodesAndPlayers() ([]*integrationTests.TestProcessorNode, []*integra enableEpochsConfig := integrationTests.GetDefaultEnableEpochsConfig() enableEpochsConfig.StakingV2EnableEpoch = integrationTests.UnreachableEpoch enableEpochsConfig.ChangeUsernameEnableEpoch = integrationTests.UnreachableEpoch + enableEpochsConfig.RelayedTransactionsV1V2DisableEpoch = integrationTests.UnreachableEpoch nodes, _ := integrationTests.CreateNodesWithFullGenesisCustomEnableEpochs( numOfShards, nodesPerShard, diff --git a/integrationTests/multiShard/smartContract/polynetworkbridge/bridge_test.go b/integrationTests/multiShard/smartContract/polynetworkbridge/bridge_test.go index 7ed4de2112c..0caf8620988 100644 --- a/integrationTests/multiShard/smartContract/polynetworkbridge/bridge_test.go +++ b/integrationTests/multiShard/smartContract/polynetworkbridge/bridge_test.go @@ -33,6 +33,7 @@ func TestBridgeSetupAndBurn(t *testing.T) { SCProcessorV2EnableEpoch: integrationTests.UnreachableEpoch, FixAsyncCallBackArgsListEnableEpoch: integrationTests.UnreachableEpoch, AndromedaEnableEpoch: integrationTests.UnreachableEpoch, + SupernovaEnableEpoch: integrationTests.UnreachableEpoch, } arwenVersion := config.WasmVMVersionByEpoch{Version: "v1.4"} vmConfig := &config.VirtualMachineConfig{ diff --git a/integrationTests/multiShard/smartContract/scCallingSC_test.go b/integrationTests/multiShard/smartContract/scCallingSC_test.go index 9f46f6e8f03..796fdc6ee6b 100644 --- a/integrationTests/multiShard/smartContract/scCallingSC_test.go +++ b/integrationTests/multiShard/smartContract/scCallingSC_test.go @@ -76,6 +76,8 @@ func TestSCCallingIntraShard(t *testing.T) { mintPubKey(firstSCOwner, initialVal, nodes) mintPubKey(secondSCOwner, initialVal, nodes) + integrationTests.SetRootHashOfGenesisBlocks(nodes) + // deploy the smart contracts firstSCAddress := putDeploySCToDataPool( "./testdata/first/output/first.wasm", @@ -159,6 +161,8 @@ func TestScDeployAndChangeScOwner(t *testing.T) { initialVal := big.NewInt(1000000000) integrationTests.MintAllNodes(nodes, initialVal) + integrationTests.SetRootHashOfGenesisBlocks(nodes) + firstSCOwner := nodes[0].OwnAccount.Address // deploy the smart contracts @@ -412,6 +416,8 @@ func TestSCCallingInCrossShard(t *testing.T) { mintPubKey(firstSCOwner, initialVal, nodes) mintPubKey(secondSCOwner, initialVal, nodes) + integrationTests.SetRootHashOfGenesisBlocks(nodes) + // deploy the smart contracts firstSCAddress := putDeploySCToDataPool( "./testdata/first/output/first.wasm", @@ -639,6 +645,8 @@ func TestSCCallingInCrossShardDelegationMock(t *testing.T) { mintPubKey(delegateSCOwner, initialVal, nodes) + integrationTests.SetRootHashOfGenesisBlocks(nodes) + // deploy the smart contracts delegateSCAddress := putDeploySCToDataPool( "./testdata/delegate-mock/output/delegate.wasm", diff --git a/integrationTests/multiShard/softfork/scDeploy_test.go b/integrationTests/multiShard/softfork/scDeploy_test.go index 5b4252b7806..d0cdcff69a6 100644 --- a/integrationTests/multiShard/softfork/scDeploy_test.go +++ b/integrationTests/multiShard/softfork/scDeploy_test.go @@ -7,10 +7,9 @@ import ( "testing" "time" - crypto "github.com/multiversx/mx-chain-crypto-go" - "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + crypto "github.com/multiversx/mx-chain-crypto-go" logger "github.com/multiversx/mx-chain-logger-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -41,6 +40,7 @@ func TestScDeploy(t *testing.T) { enableEpochs.StakingV4Step1EnableEpoch = integrationTests.StakingV4Step1EnableEpoch enableEpochs.StakingV4Step2EnableEpoch = integrationTests.StakingV4Step2EnableEpoch enableEpochs.StakingV4Step3EnableEpoch = integrationTests.StakingV4Step3EnableEpoch + enableEpochs.SupernovaEnableEpoch = integrationTests.UnreachableEpoch shardNode := integrationTests.NewTestProcessorNode(integrationTests.ArgTestProcessorNode{ MaxShards: 1, diff --git a/integrationTests/multiShard/txScenarios/common.go b/integrationTests/multiShard/txScenarios/common.go index 78c11d8f2df..b00de91df94 100644 --- a/integrationTests/multiShard/txScenarios/common.go +++ b/integrationTests/multiShard/txScenarios/common.go @@ -42,6 +42,7 @@ func createGeneralSetupForTxTest(initialBalance *big.Int) ( ScheduledMiniBlocksEnableEpoch: integrationTests.UnreachableEpoch, MiniBlockPartialExecutionEnableEpoch: integrationTests.UnreachableEpoch, AndromedaEnableEpoch: integrationTests.UnreachableEpoch, + SupernovaEnableEpoch: integrationTests.UnreachableEpoch, } nodes := integrationTests.CreateNodesWithEnableEpochs( diff --git a/integrationTests/multisig/blsMultisig_bench_test.go b/integrationTests/multisig/blsMultisig_bench_test.go index af95f1a8bda..d5d03761ba3 100644 --- a/integrationTests/multisig/blsMultisig_bench_test.go +++ b/integrationTests/multisig/blsMultisig_bench_test.go @@ -19,7 +19,8 @@ const shardConsensusSize = uint16(63) type multiSignerSetup struct { privKeys [][]byte - pubKeys [][]byte + pubKeysBytes [][]byte + pubKeys []crypto.PublicKey partialSignatures [][][]byte messages []string aggSignatures [][]byte @@ -48,46 +49,46 @@ func benchmarkMultiSigners(b *testing.B, numSigners uint16, numMessages int) { b.Run("AggregateSigs KOSK", func(b *testing.B) { for i := 0; i < b.N; i++ { - _, _ = multiSignerKOSK.AggregateSigs(setupKOSK.pubKeys, setupKOSK.partialSignatures[i%numMessages]) + _, _ = multiSignerKOSK.AggregateSigsV2(setupKOSK.pubKeys, setupKOSK.partialSignatures[i%numMessages]) } }) b.Run("VerifyAggregatedSig KOSK", func(b *testing.B) { for i := 0; i < b.N; i++ { - err := multiSignerKOSK.VerifyAggregatedSig(setupKOSK.pubKeys, []byte(setupKOSK.messages[i%numMessages]), setupKOSK.aggSignatures[i%numMessages]) + err := multiSignerKOSK.VerifyAggregatedSigV2(setupKOSK.pubKeys, []byte(setupKOSK.messages[i%numMessages]), setupKOSK.aggSignatures[i%numMessages]) require.Nil(b, err) } }) b.Run("Verify signature shares KOSK", func(b *testing.B) { for i := 0; i < b.N; i++ { for j := 0; j < int(numSigners); j++ { - err := multiSignerKOSK.VerifySignatureShare(setupKOSK.pubKeys[j], []byte(setupKOSK.messages[i%numMessages]), setupKOSK.partialSignatures[i%numMessages][j]) + err := multiSignerKOSK.VerifySignatureShare(setupKOSK.pubKeysBytes[j], []byte(setupKOSK.messages[i%numMessages]), setupKOSK.partialSignatures[i%numMessages][j]) require.Nil(b, err) } } }) b.Run("AggregateSigs NoKOSK", func(b *testing.B) { for i := 0; i < b.N; i++ { - _, _ = multiSignerNoKOSK.AggregateSigs(setupNoKOSK.pubKeys, setupNoKOSK.partialSignatures[i%numMessages]) + _, _ = multiSignerNoKOSK.AggregateSigsV2(setupNoKOSK.pubKeys, setupNoKOSK.partialSignatures[i%numMessages]) } }) b.Run("VerifyAggregatedSig NoKOSK", func(b *testing.B) { for i := 0; i < b.N; i++ { - err := multiSignerNoKOSK.VerifyAggregatedSig(setupNoKOSK.pubKeys, []byte(setupNoKOSK.messages[i%numMessages]), setupNoKOSK.aggSignatures[i%numMessages]) + err := multiSignerNoKOSK.VerifyAggregatedSigV2(setupNoKOSK.pubKeys, []byte(setupNoKOSK.messages[i%numMessages]), setupNoKOSK.aggSignatures[i%numMessages]) require.Nil(b, err) } }) b.Run("Verify signature shares NoKOSK", func(b *testing.B) { for i := 0; i < b.N; i++ { for j := 0; j < int(numSigners); j++ { - err := multiSignerNoKOSK.VerifySignatureShare(setupNoKOSK.pubKeys[j], []byte(setupNoKOSK.messages[i%numMessages]), setupNoKOSK.partialSignatures[i%numMessages][j]) + err := multiSignerNoKOSK.VerifySignatureShare(setupNoKOSK.pubKeysBytes[j], []byte(setupNoKOSK.messages[i%numMessages]), setupNoKOSK.partialSignatures[i%numMessages][j]) require.Nil(b, err) } } }) } -func createMultiSigSetupKOSK(numSigners uint16, numMessages int) (*multiSignerSetup, crypto.MultiSigner) { - var multiSigner crypto.MultiSigner +func createMultiSigSetupKOSK(numSigners uint16, numMessages int) (*multiSignerSetup, crypto.MultiSignerV2) { + var multiSigner crypto.MultiSignerV2 setup := &multiSignerSetup{} suite := mcl.NewSuiteBLS12() setup.privKeys, setup.pubKeys, multiSigner = createKeysAndMultiSignerBlsKOSK(numSigners, suite) @@ -111,17 +112,17 @@ func createMessagesAndPartialSignatures(numMessages int, privKeys [][]byte, mult func aggregateSignatures( setup *multiSignerSetup, - multiSigner crypto.MultiSigner, + multiSigner crypto.MultiSignerV2, ) [][]byte { aggSignatures := make([][]byte, len(setup.messages)) for i := 0; i < len(setup.messages); i++ { - aggSignatures[i], _ = multiSigner.AggregateSigs(setup.pubKeys, setup.partialSignatures[i]) + aggSignatures[i], _ = multiSigner.AggregateSigsV2(setup.pubKeys, setup.partialSignatures[i]) } return aggSignatures } -func createMultiSignerNoKOSK() crypto.MultiSigner { +func createMultiSignerNoKOSK() crypto.MultiSignerV2 { suite := mcl.NewSuiteBLS12() kg := signing.NewKeyGenerator(suite) hashSize := hashing.BlsHashSize diff --git a/integrationTests/multisig/blsMultisig_test.go b/integrationTests/multisig/blsMultisig_test.go index f1c711df745..152701783e9 100644 --- a/integrationTests/multisig/blsMultisig_test.go +++ b/integrationTests/multisig/blsMultisig_test.go @@ -6,7 +6,7 @@ import ( "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/hashing/blake2b" - "github.com/multiversx/mx-chain-crypto-go" + crypto "github.com/multiversx/mx-chain-crypto-go" "github.com/multiversx/mx-chain-crypto-go/signing" "github.com/multiversx/mx-chain-crypto-go/signing/mcl" llsig "github.com/multiversx/mx-chain-crypto-go/signing/mcl/multisig" @@ -17,7 +17,7 @@ import ( func createKeysAndMultiSignerBlsKOSK( grSize uint16, suite crypto.Suite, -) ([][]byte, [][]byte, crypto.MultiSigner) { +) ([][]byte, []crypto.PublicKey, crypto.MultiSignerV2) { kg, privKeys, pubKeys := createMultiSignerSetup(grSize, suite) llSigner := &llsig.BlsMultiSignerKOSK{} @@ -30,7 +30,7 @@ func createKeysAndMultiSignerBlsNoKOSK( grSize uint16, hasher hashing.Hasher, suite crypto.Suite, -) ([][]byte, [][]byte, crypto.MultiSigner) { +) ([][]byte, []crypto.PublicKey, crypto.MultiSignerV2) { kg, privKeys, pubKeys := createMultiSignerSetup(grSize, suite) llSigner := &llsig.BlsMultiSigner{Hasher: hasher} @@ -39,15 +39,15 @@ func createKeysAndMultiSignerBlsNoKOSK( return privKeys, pubKeys, multiSigner } -func createMultiSignerSetup(grSize uint16, suite crypto.Suite) (crypto.KeyGenerator, [][]byte, [][]byte) { +func createMultiSignerSetup(grSize uint16, suite crypto.Suite) (crypto.KeyGenerator, [][]byte, []crypto.PublicKey) { kg := signing.NewKeyGenerator(suite) privKeys := make([][]byte, grSize) - pubKeys := make([][]byte, grSize) + pubKeys := make([]crypto.PublicKey, grSize) for i := uint16(0); i < grSize; i++ { sk, pk := kg.GeneratePair() privKeys[i], _ = sk.ToByteArray() - pubKeys[i], _ = pk.ToByteArray() + pubKeys[i] = pk } return kg, privKeys, pubKeys } @@ -81,11 +81,11 @@ func TestMultiSig_Bls(t *testing.T) { message = fmt.Sprintf("%s%d", message, currentIdx) signatures := createSignaturesShares(privKeys, multiSigner, []byte(message)) - aggSig, err := multiSigner.AggregateSigs(pubKeys, signatures) + aggSig, err := multiSigner.AggregateSigsV2(pubKeys, signatures) assert.Nil(t, err) assert.NotNil(t, aggSig) - err = multiSigner.VerifyAggregatedSig(pubKeys, []byte(message), aggSig) + err = multiSigner.VerifyAggregatedSigV2(pubKeys, []byte(message), aggSig) assert.Nil(t, err) } } diff --git a/integrationTests/node/getAccount/getAccount_test.go b/integrationTests/node/getAccount/getAccount_test.go index acb4e92fd75..3b4d6f364e4 100644 --- a/integrationTests/node/getAccount/getAccount_test.go +++ b/integrationTests/node/getAccount/getAccount_test.go @@ -48,8 +48,12 @@ func TestNode_GetAccountAccountDoesNotExistsShouldRetEmpty(t *testing.T) { coreComponents.AddressPubKeyConverterField = integrationTests.TestAddressPubkeyConverter dataComponents := integrationTests.GetDefaultDataComponents() - _ = dataComponents.BlockChain.SetCurrentBlockHeaderAndRootHash(&block.Header{Nonce: 42}, rootHash) - dataComponents.BlockChain.SetCurrentBlockHeaderHash([]byte("header hash")) + + header := &block.Header{Nonce: 42} + headerHash := []byte("header hash") + _ = dataComponents.BlockChain.SetCurrentBlockHeaderAndRootHash(header, rootHash) + dataComponents.BlockChain.SetCurrentBlockHeaderHash(headerHash) + dataComponents.BlockChain.SetLastExecutedBlockHeaderAndRootHash(header, headerHash, rootHash) stateComponents := integrationTests.GetDefaultStateComponents() stateComponents.AccountsRepo = createAccountsRepository(accDB, dataComponents.BlockChain) @@ -92,8 +96,12 @@ func TestNode_GetAccountAccountExistsShouldReturn(t *testing.T) { coreComponents.AddressPubKeyConverterField = testscommon.RealWorldBech32PubkeyConverter dataComponents := integrationTests.GetDefaultDataComponents() - _ = dataComponents.BlockChain.SetCurrentBlockHeaderAndRootHash(&block.Header{Nonce: 42}, rootHash) - dataComponents.BlockChain.SetCurrentBlockHeaderHash([]byte("header hash")) + + header := &block.Header{Nonce: 42} + headerHash := []byte("header hash") + _ = dataComponents.BlockChain.SetCurrentBlockHeaderAndRootHash(header, rootHash) + dataComponents.BlockChain.SetCurrentBlockHeaderHash(headerHash) + dataComponents.BlockChain.SetLastExecutedBlockHeaderAndRootHash(header, headerHash, rootHash) stateComponents := integrationTests.GetDefaultStateComponents() stateComponents.AccountsRepo = createAccountsRepository(accDB, dataComponents.BlockChain) diff --git a/integrationTests/p2p/antiflood/blacklist/blacklist_test.go b/integrationTests/p2p/antiflood/blacklist/blacklist_test.go index ab267de20b7..caa9b25935f 100644 --- a/integrationTests/p2p/antiflood/blacklist/blacklist_test.go +++ b/integrationTests/p2p/antiflood/blacklist/blacklist_test.go @@ -6,6 +6,8 @@ import ( "testing" "time" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/integrationTests" "github.com/multiversx/mx-chain-go/integrationTests/mock" "github.com/multiversx/mx-chain-go/integrationTests/p2p/antiflood" @@ -15,7 +17,7 @@ import ( "github.com/multiversx/mx-chain-go/process/throttle/antiflood/floodPreventers" "github.com/multiversx/mx-chain-go/storage/cache" "github.com/multiversx/mx-chain-go/testscommon" - "github.com/multiversx/mx-chain-logger-go" + logger "github.com/multiversx/mx-chain-logger-go" "github.com/stretchr/testify/assert" ) @@ -157,7 +159,7 @@ func createBlacklistHandlersAndProcessors( peers []p2p.Messenger, thresholdNumReceived uint32, thresholdSizeReceived uint64, - maxFloodingRounds uint32, + maxFloodingRounds uint64, ) ([]floodPreventers.QuotaStatusHandler, []process.PeerBlackListCacher) { var err error @@ -170,12 +172,20 @@ func createBlacklistHandlersAndProcessors( blacklistProcessors[i], err = blackList.NewP2PBlackListProcessor( blacklistCache, blacklistCachers[i], - thresholdNumReceived, - thresholdSizeReceived, - maxFloodingRounds, - time.Minute*5, "", peers[i].ID(), + &testscommon.AntifloodConfigsHandlerStub{ + GetFloodPreventerConfigByTypeCalled: func(configType common.FloodPreventerType) config.FloodPreventerConfig { + return config.FloodPreventerConfig{ + BlackList: config.BlackListConfig{ + ThresholdNumMessagesPerInterval: thresholdNumReceived, + ThresholdSizePerInterval: thresholdSizeReceived, + NumFloodingRounds: uint32(maxFloodingRounds), + PeerBanDurationInSeconds: 5 * 60, + }, + } + }, + }, ) log.LogIfError(err) } diff --git a/integrationTests/p2p/antiflood/common.go b/integrationTests/p2p/antiflood/common.go index df0ed2b190c..1577c530c02 100644 --- a/integrationTests/p2p/antiflood/common.go +++ b/integrationTests/p2p/antiflood/common.go @@ -5,9 +5,12 @@ import ( "sync/atomic" "time" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process/throttle/antiflood/floodPreventers" "github.com/multiversx/mx-chain-go/storage/storageunit" + "github.com/multiversx/mx-chain-go/testscommon" ) // DurationBootstrapingTime - @@ -50,14 +53,24 @@ func CreateTopicsAndMockInterceptors( statusHandlers = append(statusHandlers, blacklistHandlers[idx]) } arg := floodPreventers.ArgQuotaFloodPreventer{ - Name: "test", - Cacher: antifloodPool, - StatusHandlers: statusHandlers, - BaseMaxNumMessagesPerPeer: peerMaxNumMessages, - MaxTotalSizePerPeer: peerMaxSize, - PercentReserved: 0, - IncreaseThreshold: 0, - IncreaseFactor: 0, + Name: "test", + Cacher: antifloodPool, + StatusHandlers: statusHandlers, + AntifloodConfigs: &testscommon.AntifloodConfigsHandlerStub{ + GetFloodPreventerConfigByTypeCalled: func(configType common.FloodPreventerType) config.FloodPreventerConfig { + return config.FloodPreventerConfig{ + ReservedPercent: 0, + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: peerMaxNumMessages, + TotalSizePerInterval: peerMaxSize, + IncreaseFactor: config.IncreaseFactorConfig{ + Threshold: 0, + Factor: 0, + }, + }, + } + }, + }, } interceptors[idx].FloodPreventer, err = floodPreventers.NewQuotaFloodPreventer(arg) if err != nil { diff --git a/integrationTests/p2p/peersRating/peersRating_test.go b/integrationTests/p2p/peersRating/peersRating_test.go index 3cbd3383da2..fac3c1b958b 100644 --- a/integrationTests/p2p/peersRating/peersRating_test.go +++ b/integrationTests/p2p/peersRating/peersRating_test.go @@ -152,6 +152,7 @@ func TestPeersRatingAndResponsivenessOnFullArchive(t *testing.T) { // clean the above broadcast consequences requesterFullArchiveNode.DataPool.Headers().RemoveHeaderByHash(hdrHash) resolverFullArchiveNode.DataPool.Headers().RemoveHeaderByHash(hdrHash) + time.Sleep(time.Second * 10) // invalidate the intercepted data verifier cache // Broadcast on main network should also work and reach all nodes topic = factory.ShardBlocksTopic + regularNode.ShardCoordinator.CommunicationIdentifier(requesterFullArchiveNode.ShardCoordinator.SelfId()) @@ -166,6 +167,7 @@ func TestPeersRatingAndResponsivenessOnFullArchive(t *testing.T) { requesterFullArchiveNode.DataPool.Headers().RemoveHeaderByHash(hdrHash) resolverFullArchiveNode.DataPool.Headers().RemoveHeaderByHash(hdrHash) regularNode.DataPool.Headers().RemoveHeaderByHash(hdrHash) + time.Sleep(time.Second * 10) // invalidate the intercepted data verifier cache numOfRequests := 10 // Add header to the resolver node's cache diff --git a/integrationTests/realcomponents/processorRunner.go b/integrationTests/realcomponents/processorRunner.go index 20a33dcffc8..e66d6b310e2 100644 --- a/integrationTests/realcomponents/processorRunner.go +++ b/integrationTests/realcomponents/processorRunner.go @@ -174,8 +174,7 @@ func (pr *ProcessorRunner) createNetworkComponents(tb testing.TB) { MainConfig: *pr.Config.GeneralConfig, RatingsConfig: *pr.Config.RatingsConfig, StatusHandler: pr.StatusCoreComponents.AppStatusHandler(), - Marshalizer: pr.CoreComponents.InternalMarshalizer(), - Syncer: pr.CoreComponents.SyncTimer(), + CoreComponents: pr.CoreComponents, PreferredPeersSlices: make([]string, 0), BootstrapWaitTime: 1, NodeOperationMode: common.NormalOperation, @@ -283,6 +282,7 @@ func (pr *ProcessorRunner) createStatusComponents(tb testing.TB) { pr.CoreComponents.GenesisNodesSetup(), pr.Config.GeneralConfig.EpochStartConfig, pr.CoreComponents.ChanStopNodeProcess(), + pr.CoreComponents.ChainParametersHandler(), ) require.Nil(tb, err) @@ -393,6 +393,7 @@ func (pr *ProcessorRunner) createProcessComponents(tb testing.TB) { Marshalizer: pr.CoreComponents.InternalMarshalizer(), Store: pr.DataComponents.StorageService(), Uint64ByteSliceConverter: pr.CoreComponents.Uint64ByteSliceConverter(), + DataPool: pr.DataComponents.Datapool(), } historyRepositoryFactory, err := dbLookupFactory.NewHistoryRepositoryFactory(historyRepoFactoryArgs) require.Nil(tb, err) @@ -438,6 +439,7 @@ func (pr *ProcessorRunner) createProcessComponents(tb testing.TB) { StatusComponents: pr.StatusComponents, StatusCoreComponents: pr.StatusCoreComponents, TxExecutionOrderHandler: txExecutionOrderHandler, + EconomicsConfig: *pr.Config.EconomicsConfig, } processFactory, err := factoryProcessing.NewProcessComponentsFactory(argsProcess) diff --git a/integrationTests/realcomponents/txsimulator/componentConstruction_test.go b/integrationTests/realcomponents/txsimulator/componentConstruction_test.go index fe162c5a2d5..6366d50ba9f 100644 --- a/integrationTests/realcomponents/txsimulator/componentConstruction_test.go +++ b/integrationTests/realcomponents/txsimulator/componentConstruction_test.go @@ -108,7 +108,7 @@ func TestTransactionSimulationComponentConstructionOnShard(t *testing.T) { // get the contract address from logs logs, found := pr.ProcessComponents.TxLogsProcessor().GetLogFromCache(hash) require.True(t, found) - events := logs.GetLogEvents() + events := logs.GetLogHandler().GetLogEvents() require.Equal(t, 1, len(events)) require.Equal(t, "SCDeploy", string(events[0].GetIdentifier())) contractAddress := events[0].GetAddress() diff --git a/integrationTests/resolvers/transactions/transactionsRequest_test.go b/integrationTests/resolvers/transactions/transactionsRequest_test.go index fc116f02fda..39d3cb2f862 100644 --- a/integrationTests/resolvers/transactions/transactionsRequest_test.go +++ b/integrationTests/resolvers/transactions/transactionsRequest_test.go @@ -8,6 +8,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-crypto-go/signing" ed255192 "github.com/multiversx/mx-chain-crypto-go/signing/ed25519" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/integrationTests" "github.com/multiversx/mx-chain-go/integrationTests/resolvers" @@ -78,7 +79,7 @@ func TestTransactionsRequestsShouldWorkForHigherMaxTxNonceDeltaAllowed(t *testin _ = nRequester.AccntState.SaveAccount(account) _, _ = nRequester.AccntState.Commit() - nRequester.RequestHandler.RequestTransaction(shardIdResolver, txHashes) + nRequester.RequestHandler.RequestTransactions(shardIdResolver, txHashes) rm.WaitWithTimeout() } diff --git a/integrationTests/singleShard/block/executingMiniblocks/executingMiniblocks_test.go b/integrationTests/singleShard/block/executingMiniblocks/executingMiniblocks_test.go index b09e7892a17..e5dae09aaab 100644 --- a/integrationTests/singleShard/block/executingMiniblocks/executingMiniblocks_test.go +++ b/integrationTests/singleShard/block/executingMiniblocks/executingMiniblocks_test.go @@ -129,6 +129,8 @@ func TestShardShouldProposeBlockContainingInvalidTransactions(t *testing.T) { transferValue := uint64(1000000) mintAllNodes(nodes, transferValue) + integrationTests.SetRootHashOfGenesisBlocks(nodes) + txs, hashes := generateTransferTxs(transferValue, leader.OwnAccount.SkTxSign, nodes[1].OwnAccount.PkTxSign) addTxsInDataPool(leader, txs, hashes) @@ -194,16 +196,16 @@ func testStateOnNodes(t *testing.T, nodes []*integrationTests.TestProcessorNode, testSameBlockHeight(t, nodes, idxProposer, expectedHeaderNonce) testTxIsInMiniblock(t, proposer, hashes[txValidIdx], block.TxBlock) - testTxIsInMiniblock(t, proposer, hashes[txInvalidIdx], block.InvalidBlock) + testTxIsInNotInBody(t, proposer, hashes[txInvalidIdx]) testTxIsInNotInBody(t, proposer, hashes[txDeletedIdx]) // Removed from mempool. _, ok := proposer.DataPool.Transactions().SearchFirstData(hashes[txValidIdx]) assert.False(t, ok) - // Removed from mempool. + // Not removed from mempool. _, ok = proposer.DataPool.Transactions().SearchFirstData(hashes[txInvalidIdx]) - assert.False(t, ok) + assert.True(t, ok) // Not removed from mempool (see MX-16200). _, ok = proposer.DataPool.Transactions().SearchFirstData(hashes[txDeletedIdx]) diff --git a/integrationTests/singleShard/transaction/interceptedResolvedTx/interceptedResolvedTx_test.go b/integrationTests/singleShard/transaction/interceptedResolvedTx/interceptedResolvedTx_test.go index c0c6250cdc2..5f682dc52db 100644 --- a/integrationTests/singleShard/transaction/interceptedResolvedTx/interceptedResolvedTx_test.go +++ b/integrationTests/singleShard/transaction/interceptedResolvedTx/interceptedResolvedTx_test.go @@ -10,11 +10,12 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data/rewardTx" "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/integrationTests" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/factory" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestNode_RequestInterceptTransactionWithMessengerAndWhitelist(t *testing.T) { @@ -44,7 +45,7 @@ func TestNode_RequestInterceptTransactionWithMessengerAndWhitelist(t *testing.T) nResolver.Close() }() - //connect messengers together + // connect messengers together time.Sleep(time.Second) err := nRequester.ConnectOnMain(nResolver) require.Nil(t, err) @@ -53,11 +54,11 @@ func TestNode_RequestInterceptTransactionWithMessengerAndWhitelist(t *testing.T) buffPk1, _ := nRequester.OwnAccount.SkTxSign.GeneratePublic().ToByteArray() - //minting the sender is no longer required as the requests are whitelisted + // minting the sender is no longer required as the requests are whitelisted - //Step 1. Generate a signed transaction + // Step 1. Generate a signed transaction txData := "tx notarized data" - //TODO change here when gas limit will no longer be linear with the tx data length + // TODO change here when gas limit will no longer be linear with the tx data length txDataCost := uint64(len(txData)) tx := transaction.Transaction{ Nonce: 0, @@ -80,7 +81,7 @@ func TestNode_RequestInterceptTransactionWithMessengerAndWhitelist(t *testing.T) txHash := integrationTests.TestHasher.Compute(string(signedTxBuff)) - //step 2. wire up a received handler for requester + // step 2. wire up a received handler for requester nRequester.DataPool.Transactions().RegisterOnAdded(func(key []byte, value interface{}) { txStored, _ := nRequester.DataPool.Transactions().ShardDataStore( process.ShardCacherIdentifier(nRequester.ShardCoordinator.SelfId(), nRequester.ShardCoordinator.SelfId()), @@ -94,7 +95,7 @@ func TestNode_RequestInterceptTransactionWithMessengerAndWhitelist(t *testing.T) assert.Equal(t, txHash, key) }) - //Step 3. add the transaction in resolver pool + // Step 3. add the transaction in resolver pool nResolver.DataPool.Transactions().AddData( txHash, &tx, @@ -102,8 +103,8 @@ func TestNode_RequestInterceptTransactionWithMessengerAndWhitelist(t *testing.T) process.ShardCacherIdentifier(nRequester.ShardCoordinator.SelfId(), nRequester.ShardCoordinator.SelfId()), ) - //Step 4. request tx through request handler that will whitelist the hash - nRequester.RequestHandler.RequestTransaction(0, [][]byte{txHash}) + // Step 4. request tx through request handler that will whitelist the hash + nRequester.RequestHandler.RequestTransactions(0, [][]byte{txHash}) assert.Nil(t, err) select { @@ -140,14 +141,14 @@ func TestNode_RequestInterceptRewardTransactionWithMessenger(t *testing.T) { nResolver.Close() }() - //connect messengers together + // connect messengers together time.Sleep(time.Second) err := nRequester.ConnectOnMain(nResolver) require.Nil(t, err) time.Sleep(time.Second) - //Step 1. Generate a reward Transaction + // Step 1. Generate a reward Transaction _, pubKey, _ := integrationTests.GenerateSkAndPkInShard(nRequester.ShardCoordinator, nRequester.ShardCoordinator.SelfId()) pubKeyArray, _ := pubKey.ToByteArray() tx := rewardTx.RewardTx{ @@ -163,7 +164,7 @@ func TestNode_RequestInterceptRewardTransactionWithMessenger(t *testing.T) { txHash := integrationTests.TestHasher.Compute(string(marshaledTxBuff)) - //step 2. wire up a received handler for requester + // step 2. wire up a received handler for requester nRequester.DataPool.RewardTransactions().RegisterOnAdded(func(key []byte, value interface{}) { rewardTxStored, _ := nRequester.DataPool.RewardTransactions().ShardDataStore( process.ShardCacherIdentifier(core.MetachainShardId, nRequester.ShardCoordinator.SelfId()), @@ -177,7 +178,7 @@ func TestNode_RequestInterceptRewardTransactionWithMessenger(t *testing.T) { assert.Equal(t, txHash, key) }) - //Step 3. add the transaction in resolver pool + // Step 3. add the transaction in resolver pool nResolver.DataPool.RewardTransactions().AddData( txHash, &tx, @@ -185,7 +186,7 @@ func TestNode_RequestInterceptRewardTransactionWithMessenger(t *testing.T) { process.ShardCacherIdentifier(nRequester.ShardCoordinator.SelfId(), core.MetachainShardId), ) - //Step 4. request tx + // Step 4. request tx rewardTxRequester, _ := nRequester.RequestersFinder.CrossShardRequester(factory.RewardsTransactionTopic, core.MetachainShardId) err = rewardTxRequester.RequestDataFromHash(txHash, 0) assert.Nil(t, err) diff --git a/integrationTests/state/stateTrie/stateTrie_test.go b/integrationTests/state/stateTrie/stateTrie_test.go index 78767cef6e6..befb38a5d57 100644 --- a/integrationTests/state/stateTrie/stateTrie_test.go +++ b/integrationTests/state/stateTrie/stateTrie_test.go @@ -24,6 +24,9 @@ import ( dataTx "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-core-go/hashing/sha256" crypto "github.com/multiversx/mx-chain-crypto-go" + "github.com/multiversx/mx-chain-go/epochStart/notifier" + trieMock "github.com/multiversx/mx-chain-go/testscommon/trie" + "github.com/multiversx/mx-chain-storage-go/types" vmcommon "github.com/multiversx/mx-chain-vm-common-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -1329,7 +1332,6 @@ func TestRollbackBlockAndCheckThatPruningIsCancelledOnAccountsTrie(t *testing.T) fmt.Println("Minting sender addresses...") integrationTests.CreateMintingForSenders(nodes, 0, sendersPrivateKeys, valMinting) - shardNode := nodes[0] round = integrationTests.IncrementAndPrintRound(round) @@ -1389,6 +1391,8 @@ func TestRollbackBlockAndCheckThatPruningIsCancelledOnAccountsTrie(t *testing.T) atomic.AddUint64(nonces[0], 2) atomic.AddUint64(nonces[1], 3) + integrationTests.SetRootHashOfGenesisBlocks(nodes) + numOfRounds := 2 integrationTests.ProposeBlocks( nodes, @@ -2493,6 +2497,7 @@ func startNodesAndIssueToken( StakingV4Step2EnableEpoch: integrationTests.UnreachableEpoch, StakingV4Step3EnableEpoch: integrationTests.UnreachableEpoch, AndromedaEnableEpoch: integrationTests.UnreachableEpoch, + SupernovaEnableEpoch: integrationTests.UnreachableEpoch, AutoBalanceDataTriesEnableEpoch: 1, } nodes = integrationTests.CreateNodesWithEnableEpochs( @@ -2768,3 +2773,108 @@ func createAccountsDBTestSetup() *state.AccountsDB { return adb } + +// TestStateSnapshot_MultipleEpochsWithoutCompleteSnapshot tests the behavior of the state snapshot mechanism +// when multiple epochs are processed without creating a complete snapshot. It verifies that if a trie node is found +// in the db for epoch n-2, all following nodes will be retrieved starting from that db and continuing with +// older ones. +func TestStateSnapshot_MultipleEpochsWithoutCompleteSnapshot(t *testing.T) { + epochNotifier := notifier.NewEpochStartSubscriptionHandler() + mainStorer, persistersMap, err := stateMock.CreateTestingTriePruningStorer(&testscommon.ShardsCoordinatorMock{}, epochNotifier) + assert.Nil(t, err) + + mainStorerWithEpoch, ok := mainStorer.(types.StorerWithPutInEpoch) + assert.True(t, ok) + + numEpochs := 5 + getCounters := make([]int, numEpochs) + + args := testStorage.GetStorageManagerArgs() + args.MainStorer = mainStorer + trieStorage, _ := trie.NewTrieStorageManager(args) + maxTrieLevelInMemory := uint(5) + tr, _ := trie.NewTrie(trieStorage, args.Marshalizer, args.Hasher, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, maxTrieLevelInMemory) + defer func() { + _ = trieStorage.Close() + }() + + numTrieEntries := 100 + numTrieEntriesEpoch1 := 100000 + numLeaves := 0 + + for i := 1; i < numEpochs; i++ { + epochIdx := i + persister := testscommon.NewMemDbMock() + persister.GetCalledNoReturn = func(key []byte) { + getCounters[epochIdx]++ + } + + persisterPath := fmt.Sprintf("Epoch_%d/Shard_0/id", i) + persistersMap.AddPersister(persisterPath, persister) + mainStorerWithEpoch.SetEpochForPutOperation(uint32(i)) + epochNotifier.NotifyAll(&block.Header{Epoch: uint32(i)}) + + if i == 1 { + // add multiple entries for epoch 1; this way there will be multiple trie nodes that + // do not change in the following epochs + numLeaves += addDataToTrie(t, tr, 0, numTrieEntriesEpoch1) + } else { + startingIndexBasedOnEpoch := numTrieEntriesEpoch1 + (i-2)*numTrieEntries + numLeaves += addDataToTrie(t, tr, startingIndexBasedOnEpoch, numTrieEntries) + } + + err = tr.Commit() + assert.Nil(t, err) + } + + rootHash, err := tr.RootHash() + assert.Nil(t, err) + chLeaves := &common.TrieIteratorChannels{ + LeavesChan: make(chan core.KeyValueHolder, common.TrieLeavesChannelDefaultCapacity), + ErrChan: errChan.NewErrChanWrapper(), + } + missingNodesChannel := make(chan []byte, 100) + epochNotifier.NotifyAll(&block.Header{Epoch: uint32(5)}) + + wg := sync.WaitGroup{} + wg.Add(1) + trieStorage.TakeSnapshot( + "", + rootHash, + rootHash, + chLeaves, + missingNodesChannel, + &trieMock.MockStatistics{ + AddTrieStatsCalled: func(_ common.TrieStatisticsHandler, _ common.TrieType) { + wg.Done() + }, + }, + 5, + ) + + numRetrievedLeaves := 0 + for range chLeaves.LeavesChan { + numRetrievedLeaves++ + } + + assert.Equal(t, numLeaves, numRetrievedLeaves) + wg.Wait() + + hashes, err := tr.GetAllHashes() + assert.Nil(t, err) + numTrieNodes := len(hashes) + assert.Less(t, getCounters[numEpochs-1], numTrieNodes) +} + +func addDataToTrie(t *testing.T, tr common.Trie, startIndex int, numEntries int) int { + numLeaves := 0 + for j := startIndex; j < startIndex+numEntries; j++ { + key := []byte(fmt.Sprintf("key%d", j)) + value := []byte(fmt.Sprintf("value%d", j)) + err := tr.Update(key, value) + assert.Nil(t, err) + numLeaves++ + } + + return numLeaves +} diff --git a/integrationTests/sync/basicSync/basicSync_test.go b/integrationTests/sync/basicSync/basicSync_test.go index 2d75ec9e10f..8454d5f8de9 100644 --- a/integrationTests/sync/basicSync/basicSync_test.go +++ b/integrationTests/sync/basicSync/basicSync_test.go @@ -210,6 +210,7 @@ func TestSyncWorksInShard_EmptyBlocksNoForks_With_EquivalentProofs(t *testing.T) enableEpochs := integrationTests.CreateEnableEpochsConfig() enableEpochs.AndromedaEnableEpoch = uint32(0) + enableEpochs.SupernovaEnableEpoch = integrationTests.UnreachableEpoch nodes := make([]*integrationTests.TestProcessorNode, numNodesPerShard+1) connectableNodes := make([]integrationTests.Connectable, 0) @@ -296,6 +297,7 @@ func TestSyncMetaAndShard_With_EquivalentProofs(t *testing.T) { enableEpochs := integrationTests.CreateEnableEpochsConfig() enableEpochs.AndromedaEnableEpoch = uint32(0) + enableEpochs.SupernovaEnableEpoch = integrationTests.UnreachableEpoch nodes := make([]*integrationTests.TestProcessorNode, 2*numNodesPerShard) leaders := make([]*integrationTests.TestProcessorNode, 0) diff --git a/integrationTests/sync/edgeCases/edgeCases_test.go b/integrationTests/sync/edgeCases/edgeCases_test.go index 285fed4dd8c..630500ba95c 100644 --- a/integrationTests/sync/edgeCases/edgeCases_test.go +++ b/integrationTests/sync/edgeCases/edgeCases_test.go @@ -67,6 +67,8 @@ func TestSyncMetaNodeIsSyncingReceivedHigherRoundBlockFromShard(t *testing.T) { numRoundsBlocksAreProposedOnlyByMeta, ) + integrationTests.SetRootHashOfGenesisBlocks(nodes) + secondNumRoundsBlocksAreProposedCorrectly := 2 integrationTests.ProposeBlocks( nodes, diff --git a/integrationTests/testConsensusNode.go b/integrationTests/testConsensusNode.go index 282d14b6bbd..ae39e806ebe 100644 --- a/integrationTests/testConsensusNode.go +++ b/integrationTests/testConsensusNode.go @@ -17,6 +17,8 @@ import ( mclMultiSig "github.com/multiversx/mx-chain-crypto-go/signing/mcl/multisig" "github.com/multiversx/mx-chain-crypto-go/signing/multisig" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/common/enablers" "github.com/multiversx/mx-chain-go/common/forking" @@ -53,6 +55,7 @@ import ( "github.com/multiversx/mx-chain-go/storage/cache" "github.com/multiversx/mx-chain-go/storage/storageunit" "github.com/multiversx/mx-chain-go/testscommon" + cacheMocks "github.com/multiversx/mx-chain-go/testscommon/cache" "github.com/multiversx/mx-chain-go/testscommon/chainParameters" consensusMocks "github.com/multiversx/mx-chain-go/testscommon/consensus" "github.com/multiversx/mx-chain-go/testscommon/cryptoMocks" @@ -110,6 +113,8 @@ type TestConsensusNode struct { MainInterceptorsContainer process.InterceptorsContainer DataPool dataRetriever.PoolsHolder RequestHandler process.RequestHandler + ChainParametersHandler common.ChainParametersHandler + ProcessConfigsHandler common.ProcessConfigsHandler } // NewTestConsensusNode returns a new TestConsensusNode @@ -146,7 +151,7 @@ func CreateNodesWithTestConsensusNode( waitingMap := make(map[uint32][]nodesCoordinator.Validator) connectableNodes := make(map[uint32][]Connectable, 0) - startTime := time.Now().Unix() + startTime := time.Now().Add(10 * time.Second).Round(time.Second).Unix() testHasher := createHasher(consensusType) for shardID := range cp.NodesKeys { @@ -182,20 +187,32 @@ func CreateNodesWithTestConsensusNode( return nodes } -func createCustomMultiSignerMock(multiSigner crypto.MultiSigner) *cryptoMocks.MultisignerMock { +func createCustomMultiSignerMock(multiSigner crypto.MultiSignerV2) *cryptoMocks.MultisignerMock { multiSignerMock := &cryptoMocks.MultisignerMock{} multiSignerMock.CreateSignatureShareCalled = func(privateKeyBytes, message []byte) ([]byte, error) { return multiSigner.CreateSignatureShare(privateKeyBytes, message) } + multiSignerMock.CreateSignatureShareV2Called = func(privateKey crypto.PrivateKey, message []byte) ([]byte, error) { + return multiSigner.CreateSignatureShareV2(privateKey, message) + } multiSignerMock.VerifySignatureShareCalled = func(publicKey, message, sig []byte) error { return multiSigner.VerifySignatureShare(publicKey, message, sig) } - multiSignerMock.AggregateSigsCalled = func(pubKeysSigners, signatures [][]byte) ([]byte, error) { + multiSignerMock.VerifySignatureShareV2Called = func(publicKey crypto.PublicKey, message, sig []byte) error { + return multiSigner.VerifySignatureShareV2(publicKey, message, sig) + } + multiSignerMock.AggregateSigsCalled = func(pubKeysSigners [][]byte, signatures [][]byte) ([]byte, error) { return multiSigner.AggregateSigs(pubKeysSigners, signatures) } + multiSignerMock.AggregateSigsV2Called = func(pubKeysSigners []crypto.PublicKey, signatures [][]byte) ([]byte, error) { + return multiSigner.AggregateSigsV2(pubKeysSigners, signatures) + } multiSignerMock.VerifyAggregatedSigCalled = func(pubKeysSigners [][]byte, message, aggSig []byte) error { return multiSigner.VerifyAggregatedSig(pubKeysSigners, message, aggSig) } + multiSignerMock.VerifyAggregatedSigV2Called = func(pubKeysSigners []crypto.PublicKey, message, aggSig []byte) error { + return multiSigner.VerifyAggregatedSigV2(pubKeysSigners, message, aggSig) + } return multiSignerMock } @@ -208,28 +225,56 @@ func (tcn *TestConsensusNode) initNode(args ArgsTestConsensusNode) { consensusCache, _ := cache.NewLRUCache(10000) pkBytes, _ := tcn.NodeKeys.Pk.ToByteArray() - tcn.initNodesCoordinator(args.ConsensusSize, testHasher, epochStartRegistrationHandler, args.EligibleMap, args.WaitingMap, pkBytes, consensusCache) + roundTime := time.Millisecond * time.Duration(args.RoundTime) + + tcn.ChainParametersHandler = &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(_ uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{ + ShardConsensusGroupSize: uint32(args.ConsensusSize), + MetachainConsensusGroupSize: uint32(args.ConsensusSize), + RoundsPerEpoch: 1000, + MinRoundsBetweenEpochs: 1, + RoundDuration: uint64(roundTime.Milliseconds()), + }, nil + }, + } + + tcn.ProcessConfigsHandler = testscommon.GetDefaultProcessConfigsHandler() + + tcn.initNodesCoordinator(testHasher, epochStartRegistrationHandler, args.EligibleMap, args.WaitingMap, pkBytes, consensusCache) tcn.MainMessenger = CreateMessengerWithNoDiscovery() tcn.FullArchiveMessenger = &p2pmocks.MessengerStub{} tcn.initBlockChain(testHasher) tcn.initBlockProcessor(tcn.ShardCoordinator.SelfId()) - syncer := ntp.NewSyncTime(ntp.NewNTPGoogleConfig(), nil) + syncer := ntp.NewSyncTime(testscommon.NewNTPGoogleConfig(), nil) syncer.StartSyncingTime() genericEpochNotifier := forking.NewGenericEpochNotifier() epochsConfig := GetDefaultEnableEpochsConfig() enableEpochsHandler, _ := enablers.NewEnableEpochsHandler(*epochsConfig, genericEpochNotifier) + enableRoundsHandler := &testscommon.EnableRoundsHandlerStub{} - storage := CreateStore(tcn.ShardCoordinator.NumberOfShards()) + store := CreateStore(tcn.ShardCoordinator.NumberOfShards()) - roundHandler, _ := round.NewRound( - time.Unix(args.StartTime, 0), - syncer.CurrentTime(), - time.Millisecond*time.Duration(args.RoundTime), - syncer, - 0) + supernovaGenesisTimeStamp := time.UnixMilli(args.StartTime * 1000) + if args.EnableEpochsConfig.SupernovaEnableEpoch == UnreachableEpoch { + supernovaGenesisTimeStamp = time.UnixMilli(args.StartTime * int64(UnreachableEpoch)) + } + + roundArgs := round.ArgsRound{ + GenesisTimeStamp: time.Unix(args.StartTime, 0), + SupernovaGenesisTimeStamp: supernovaGenesisTimeStamp, + CurrentTimeStamp: syncer.CurrentTime(), + RoundTimeDuration: roundTime, + SupernovaTimeDuration: roundTime, + SyncTimer: syncer, + StartRound: 0, + SupernovaStartRound: 0, + EnableRoundsHandler: enableRoundsHandler, + } + roundHandler, _ := round.NewRound(roundArgs) dataPool := dataRetrieverMock.CreatePoolsHolder(1, 0) tcn.DataPool = dataPool @@ -237,18 +282,16 @@ func (tcn *TestConsensusNode) initNode(args ArgsTestConsensusNode) { var epochTrigger TestEpochStartTrigger if tcn.ShardCoordinator.SelfId() == core.MetachainShardId { argsNewMetaEpochStart := &metachain.ArgsNewMetaEpochStartTrigger{ - GenesisTime: time.Unix(args.StartTime, 0), - EpochStartNotifier: notifier.NewEpochStartSubscriptionHandler(), - Settings: &config.EpochStartConfig{ - MinRoundsBetweenEpochs: 1, - RoundsPerEpoch: 1000, - }, - Epoch: 0, - Storage: createTestStore(), - Marshalizer: TestMarshalizer, - Hasher: testHasher, - AppStatusHandler: &statusHandlerMock.AppStatusHandlerStub{}, - DataPool: dataPool, + GenesisTime: time.Unix(args.StartTime, 0), + EpochStartNotifier: notifier.NewEpochStartSubscriptionHandler(), + Settings: &config.EpochStartConfig{}, + Epoch: 0, + Storage: createTestStore(), + Marshalizer: TestMarshalizer, + Hasher: testHasher, + AppStatusHandler: &statusHandlerMock.AppStatusHandlerStub{}, + DataPool: dataPool, + ChainParametersHandler: tcn.ChainParametersHandler, } epochStartTrigger, err := metachain.NewEpochStartTrigger(argsNewMetaEpochStart) if err != nil { @@ -270,7 +313,7 @@ func (tcn *TestConsensusNode) initNode(args ArgsTestConsensusNode) { HeaderValidator: &mock.HeaderValidatorStub{}, Uint64Converter: TestUint64Converter, DataPool: tcn.DataPool, - Storage: storage, + Storage: store, RequestHandler: &testscommon.RequestHandlerStub{}, Epoch: 0, Validity: 1, @@ -280,6 +323,7 @@ func (tcn *TestConsensusNode) initNode(args ArgsTestConsensusNode) { RoundHandler: roundHandler, AppStatusHandler: &statusHandlerMock.AppStatusHandlerStub{}, EnableEpochsHandler: enableEpochsHandler, + CommonConfigsHandler: testscommon.GetDefaultCommonConfigsHandler(), } epochStartTrigger, err := shardchain.NewEpochStartTrigger(argsShardEpochStart) if err != nil { @@ -324,11 +368,15 @@ func (tcn *TestConsensusNode) initNode(args ArgsTestConsensusNode) { coreComponents.HardforkTriggerPubKeyField = []byte("provided hardfork pub key") argsKeysHolder := keysManagement.ArgsManagedPeersHolder{ - KeyGenerator: args.KeyGen, - P2PKeyGenerator: args.P2PKeyGen, - MaxRoundsOfInactivity: 0, // 0 for main node, non-0 for backup node - PrefsConfig: config.Preferences{}, + KeyGenerator: args.KeyGen, + P2PKeyGenerator: args.P2PKeyGen, + PrefsConfig: config.Preferences{ + Preferences: config.PreferencesConfig{ + RedundancyLevel: 0, // 0 for main node, non-0 for backup node + }, + }, P2PKeyConverter: p2pFactory.NewP2PKeyConverter(), + ProcessConfigsHandler: &testscommon.ProcessConfigsHandlerStub{}, } keysHolder, _ := keysManagement.NewManagedPeersHolder(argsKeysHolder) @@ -353,6 +401,7 @@ func (tcn *TestConsensusNode) initNode(args ArgsTestConsensusNode) { KeyGenerator: args.KeyGen, KeysHandler: keysHandler, SingleSigner: TestSingleBlsSigner, + PubKeysCache: cacheMocks.NewCacherMock(), } sigHandler, _ := cryptoFactory.NewSigningHandler(signingHandlerArgs) @@ -377,8 +426,13 @@ func (tcn *TestConsensusNode) initNode(args ArgsTestConsensusNode) { cache.NewTimeCache(time.Second), &mock.BlockTrackerStub{}, args.StartTime, + args.StartTime*1000, enableEpochsHandler, + enableRoundsHandler, dataPool.Proofs(), + tcn.ChainParametersHandler, + tcn.ProcessConfigsHandler, + tcn.ShardCoordinator.SelfId(), ) processComponents := GetDefaultProcessComponents() @@ -400,8 +454,9 @@ func (tcn *TestConsensusNode) initNode(args ArgsTestConsensusNode) { processComponents.ScheduledTxsExecutionHandlerInternal = &testscommon.ScheduledTxsExecutionStub{} processComponents.ProcessedMiniBlocksTrackerInternal = &testscommon.ProcessedMiniBlocksTrackerStub{} processComponents.SentSignaturesTrackerInternal = &testscommon.SentSignatureTrackerStub{} + processComponents.ExecManager = &processMocks.ExecutionManagerMock{} - tcn.initInterceptors(coreComponents, cryptoComponents, roundHandler, enableEpochsHandler, storage, epochTrigger) + tcn.initInterceptors(coreComponents, cryptoComponents, roundHandler, enableEpochsHandler, store, epochTrigger) processComponents.IntContainer = tcn.MainInterceptorsContainer dataComponents := GetDefaultDataComponents() @@ -411,6 +466,7 @@ func (tcn *TestConsensusNode) initNode(args ArgsTestConsensusNode) { stateComponents := GetDefaultStateComponents() stateComponents.Accounts = tcn.AccountsDB + stateComponents.AccountsProposal = tcn.AccountsDB stateComponents.AccountsAPI = tcn.AccountsDB statusCoreComponents := &testFactory.StatusCoreComponentsStub{ @@ -446,8 +502,9 @@ func (tcn *TestConsensusNode) initInterceptors( epochStartTrigger TestEpochStartTrigger, ) { interceptorDataVerifierArgs := interceptorsFactory.InterceptedDataVerifierFactoryArgs{ - CacheSpan: time.Second * 10, - CacheExpiry: time.Second * 10, + InterceptedDataVerifierConfig: config.InterceptedDataVerifierConfig{ + EnableCaching: false, + }, } accountsAdapter := epochStartDisabled.NewAccountsAdapter() @@ -495,6 +552,12 @@ func (tcn *TestConsensusNode) initInterceptors( HardforkTrigger: &testscommon.HardforkTriggerStub{}, NodeOperationMode: common.NormalOperation, InterceptedDataVerifierFactory: interceptorsFactory.NewInterceptedDataVerifierFactory(interceptorDataVerifierArgs), + Config: config.Config{ + InterceptedDataVerifier: config.InterceptedDataVerifierConfig{ + CacheSpanInSec: 1, + CacheExpiryInSec: 1, + }, + }, } if tcn.ShardCoordinator.SelfId() == core.MetachainShardId { interceptorContainerFactory, err := interceptorscontainer.NewMetaInterceptorsContainerFactory(interceptorContainerFactoryArgs) @@ -529,6 +592,7 @@ func (tcn *TestConsensusNode) initInterceptors( RoundHandler: roundHandler, AppStatusHandler: &statusHandlerMock.AppStatusHandlerStub{}, EnableEpochsHandler: enableEpochsHandler, + CommonConfigsHandler: testscommon.GetDefaultCommonConfigsHandler(), } _, _ = shardchain.NewEpochStartTrigger(argsShardEpochStart) @@ -545,7 +609,6 @@ func (tcn *TestConsensusNode) initInterceptors( } func (tcn *TestConsensusNode) initNodesCoordinator( - consensusSize int, hasher hashing.Hasher, epochStartRegistrationHandler notifier.EpochStartNotifier, eligibleMap map[uint32][]nodesCoordinator.Validator, @@ -554,14 +617,7 @@ func (tcn *TestConsensusNode) initNodesCoordinator( cache storage.Cacher, ) { argumentsNodesCoordinator := nodesCoordinator.ArgNodesCoordinator{ - ChainParametersHandler: &chainParameters.ChainParametersHandlerStub{ - ChainParametersForEpochCalled: func(_ uint32) (config.ChainParametersByEpochConfig, error) { - return config.ChainParametersByEpochConfig{ - ShardConsensusGroupSize: uint32(consensusSize), - MetachainConsensusGroupSize: uint32(consensusSize), - }, nil - }, - }, + ChainParametersHandler: tcn.ChainParametersHandler, Marshalizer: TestMarshalizer, Hasher: hasher, Shuffler: &shardingMocks.NodeShufflerMock{}, @@ -639,7 +695,7 @@ func (tcn *TestConsensusNode) initBlockProcessor(shardId uint32) { return header, &dataBlock.Body{}, nil }, - MarshalizedDataToBroadcastCalled: func(header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) { + MarshalizedDataToBroadcastCalled: func(hash []byte, header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) { mrsData := make(map[uint32][]byte) mrsTxs := make(map[string][][]byte) return mrsData, mrsTxs, nil @@ -720,6 +776,7 @@ func createHasher(consensusType string) hashing.Hasher { func createTestStore() dataRetriever.StorageService { store := dataRetriever.NewChainStorer() store.AddStorer(dataRetriever.TransactionUnit, CreateMemUnit()) + store.AddStorer(dataRetriever.UnsignedTransactionUnit, CreateMemUnit()) store.AddStorer(dataRetriever.MiniBlockUnit, CreateMemUnit()) store.AddStorer(dataRetriever.RewardTransactionUnit, CreateMemUnit()) store.AddStorer(dataRetriever.MetaBlockUnit, CreateMemUnit()) diff --git a/integrationTests/testFullNode.go b/integrationTests/testFullNode.go index 4c122860f52..fdce2e36300 100644 --- a/integrationTests/testFullNode.go +++ b/integrationTests/testFullNode.go @@ -14,9 +14,21 @@ import ( crypto "github.com/multiversx/mx-chain-crypto-go" mclMultiSig "github.com/multiversx/mx-chain-crypto-go/signing/mcl/multisig" "github.com/multiversx/mx-chain-crypto-go/signing/multisig" - "github.com/multiversx/mx-chain-go/state/disabled" wasmConfig "github.com/multiversx/mx-chain-vm-go/config" + "github.com/multiversx/mx-chain-go/process/aotSelection" + "github.com/multiversx/mx-chain-go/process/asyncExecution" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionManager" + "github.com/multiversx/mx-chain-go/state/disabled" + + headersCache "github.com/multiversx/mx-chain-go/process/asyncExecution/cache" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionTrack" + "github.com/multiversx/mx-chain-go/process/estimator" + "github.com/multiversx/mx-chain-go/process/missingData" + + "github.com/multiversx/mx-chain-go/process/block/headerForBlock" + "github.com/multiversx/mx-chain-go/process/coordinator" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/common/enablers" "github.com/multiversx/mx-chain-go/common/forking" @@ -41,6 +53,7 @@ import ( "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/block" "github.com/multiversx/mx-chain-go/process/block/bootstrapStorage" + processFactory "github.com/multiversx/mx-chain-go/process/factory" "github.com/multiversx/mx-chain-go/process/factory/interceptorscontainer" "github.com/multiversx/mx-chain-go/process/interceptors" disabledInterceptors "github.com/multiversx/mx-chain-go/process/interceptors/disabled" @@ -59,12 +72,12 @@ import ( "github.com/multiversx/mx-chain-go/storage/cache" "github.com/multiversx/mx-chain-go/storage/storageunit" "github.com/multiversx/mx-chain-go/testscommon" + cacheMocks "github.com/multiversx/mx-chain-go/testscommon/cache" "github.com/multiversx/mx-chain-go/testscommon/chainParameters" consensusMocks "github.com/multiversx/mx-chain-go/testscommon/consensus" "github.com/multiversx/mx-chain-go/testscommon/cryptoMocks" "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" - "github.com/multiversx/mx-chain-go/testscommon/factory" testFactory "github.com/multiversx/mx-chain-go/testscommon/factory" "github.com/multiversx/mx-chain-go/testscommon/genesisMocks" "github.com/multiversx/mx-chain-go/testscommon/nodeTypeProviderMock" @@ -72,6 +85,7 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/p2pmocks" "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" statusHandlerMock "github.com/multiversx/mx-chain-go/testscommon/statusHandler" + storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" vic "github.com/multiversx/mx-chain-go/testscommon/validatorInfoCacher" "github.com/multiversx/mx-chain-go/vm" "github.com/multiversx/mx-chain-go/vm/systemSmartContracts/defaults" @@ -86,20 +100,28 @@ func CreateNodesWithTestFullNode( consensusType string, numKeysOnEachNode int, enableEpochsConfig config.EnableEpochs, - withSync bool, + roundsConfig config.RoundConfig, + roundsPerEpoch int64, ) map[uint32][]*TestFullNode { - nodes := make(map[uint32][]*TestFullNode, nodesPerShard) cp := CreateCryptoParams(nodesPerShard, numMetaNodes, maxShards, numKeysOnEachNode) keysMap := PubKeysMapFromNodesKeysMap(cp.NodesKeys) validatorsMap := GenValidatorsFromPubKeys(keysMap, maxShards) eligibleMap, _ := nodesCoordinator.NodesInfoToValidators(validatorsMap) waitingMap := make(map[uint32][]nodesCoordinator.Validator) - connectableNodes := make(map[uint32][]Connectable, 0) + connectableNodes := make([]Connectable, 0) - startTime := time.Now().Unix() testHasher := createHasher(consensusType) + nodesSetup := &genesisMocks.NodesSetupStub{InitialNodesInfoCalled: func() (m map[uint32][]nodesCoordinator.GenesisNodeInfoHandler, m2 map[uint32][]nodesCoordinator.GenesisNodeInfoHandler) { + return validatorsMap, map[uint32][]nodesCoordinator.GenesisNodeInfoHandler{} + }} + + startTime := time.Now().UnixMilli() + if enableEpochsConfig.SupernovaEnableEpoch > 0 { + startTime = time.Now().Unix() + } + for shardID := range cp.NodesKeys { for _, keysPair := range cp.NodesKeys[shardID] { multiSigner, _ := multisig.NewBLSMultisig(&mclMultiSig.BlsMultiSigner{Hasher: testHasher}, cp.KeyGen) @@ -107,34 +129,34 @@ func CreateNodesWithTestFullNode( args := ArgsTestFullNode{ ArgTestProcessorNode: &ArgTestProcessorNode{ - MaxShards: 2, - NodeShardId: 0, - TxSignPrivKeyShardId: 0, - WithSync: withSync, + MaxShards: maxShards, + NodeShardId: shardID, + TxSignPrivKeyShardId: shardID, EpochsConfig: &enableEpochsConfig, + RoundsConfig: &roundsConfig, NodeKeys: keysPair, + NodesSetup: nodesSetup, }, - ShardID: shardID, - ConsensusSize: consensusSize, - RoundTime: roundTime, - ConsensusType: consensusType, - EligibleMap: eligibleMap, - WaitingMap: waitingMap, - KeyGen: cp.KeyGen, - P2PKeyGen: cp.P2PKeyGen, - MultiSigner: multiSignerMock, - StartTime: startTime, + ShardID: shardID, + ConsensusSize: consensusSize, + RoundTime: roundTime, + ConsensusType: consensusType, + EligibleMap: eligibleMap, + WaitingMap: waitingMap, + KeyGen: cp.KeyGen, + P2PKeyGen: cp.P2PKeyGen, + MultiSigner: multiSignerMock, + StartTime: startTime, + RoundsPerEpoch: roundsPerEpoch, } tfn := NewTestFullNode(args) nodes[shardID] = append(nodes[shardID], tfn) - connectableNodes[shardID] = append(connectableNodes[shardID], tfn) + connectableNodes = append(connectableNodes, tfn) } } - for shardID := range nodes { - ConnectNodes(connectableNodes[shardID]) - } + ConnectNodes(connectableNodes) return nodes } @@ -143,25 +165,27 @@ func CreateNodesWithTestFullNode( type ArgsTestFullNode struct { *ArgTestProcessorNode - ShardID uint32 - ConsensusSize int - RoundTime uint64 - ConsensusType string - EligibleMap map[uint32][]nodesCoordinator.Validator - WaitingMap map[uint32][]nodesCoordinator.Validator - KeyGen crypto.KeyGenerator - P2PKeyGen crypto.KeyGenerator - MultiSigner *cryptoMocks.MultisignerMock - StartTime int64 + ShardID uint32 + ConsensusSize int + RoundTime uint64 + ConsensusType string + EligibleMap map[uint32][]nodesCoordinator.Validator + WaitingMap map[uint32][]nodesCoordinator.Validator + KeyGen crypto.KeyGenerator + P2PKeyGen crypto.KeyGenerator + MultiSigner *cryptoMocks.MultisignerMock + StartTime int64 + RoundsPerEpoch int64 } // TestFullNode defines the structure for testing node with full processing and consensus components type TestFullNode struct { *TestProcessorNode - ShardCoordinator sharding.Coordinator - MultiSigner *cryptoMocks.MultisignerMock - GenesisTimeField time.Time + ShardCoordinator sharding.Coordinator + MultiSigner *cryptoMocks.MultisignerMock + GenesisTimeField time.Time + SupernovaGenesisTimeField time.Time } // NewTestFullNode will create a new instance of full testing node @@ -182,7 +206,6 @@ func NewTestFullNode(args ArgsTestFullNode) *TestFullNode { } func (tfn *TestFullNode) initNodesCoordinator( - consensusSize int, hasher hashing.Hasher, epochStartRegistrationHandler notifier.EpochStartNotifier, eligibleMap map[uint32][]nodesCoordinator.Validator, @@ -191,14 +214,7 @@ func (tfn *TestFullNode) initNodesCoordinator( cache storage.Cacher, ) { argumentsNodesCoordinator := nodesCoordinator.ArgNodesCoordinator{ - ChainParametersHandler: &chainParameters.ChainParametersHandlerStub{ - ChainParametersForEpochCalled: func(_ uint32) (config.ChainParametersByEpochConfig, error) { - return config.ChainParametersByEpochConfig{ - ShardConsensusGroupSize: uint32(consensusSize), - MetachainConsensusGroupSize: uint32(consensusSize), - }, nil - }, - }, + ChainParametersHandler: tfn.ChainParametersHandler, Marshalizer: TestMarshalizer, Hasher: hasher, Shuffler: &shardingMocks.NodeShufflerMock{}, @@ -224,6 +240,13 @@ func (tfn *TestFullNode) initNodesCoordinator( } func (tpn *TestFullNode) initTestNodeWithArgs(args ArgTestProcessorNode, fullArgs ArgsTestFullNode) { + if tpn.EpochNotifier == nil { + tpn.EpochNotifier = forking.NewGenericEpochNotifier() + } + if tpn.EnableEpochsHandler == nil { + tpn.EnableEpochsHandler, _ = enablers.NewEnableEpochsHandler(*args.EpochsConfig, tpn.EpochNotifier) + } + tpn.AppStatusHandler = args.AppStatusHandler if check.IfNil(args.AppStatusHandler) { tpn.AppStatusHandler = TestAppStatusHandler @@ -238,18 +261,34 @@ func (tpn *TestFullNode) initTestNodeWithArgs(args ArgTestProcessorNode, fullArg tpn.initChainHandler() tpn.initHeaderValidator() - tpn.initRoundHandler() - syncer := ntp.NewSyncTime(ntp.NewNTPGoogleConfig(), nil) + roundDuration := time.Millisecond * time.Duration(fullArgs.RoundTime) + + tpn.initRoundHandler(roundDuration) + + syncer := ntp.NewSyncTime(testscommon.NewNTPGoogleConfig(), nil) syncer.StartSyncingTime() - tpn.GenesisTimeField = time.Unix(fullArgs.StartTime, 0) - roundHandler, _ := round.NewRound( - tpn.GenesisTimeField, - syncer.CurrentTime(), - time.Millisecond*time.Duration(fullArgs.RoundTime), - syncer, - 0) + if tpn.EnableEpochsHandler.IsFlagEnabled(common.SupernovaFlag) { + tpn.GenesisTimeField = time.UnixMilli(fullArgs.StartTime) + } else { + tpn.GenesisTimeField = time.Unix(fullArgs.StartTime, 0) + } + supernovaRound := tpn.EnableRoundsHandler.GetActivationRound(common.SupernovaRoundFlag) + tpn.SupernovaGenesisTimeField = tpn.GenesisTimeField.Add(time.Duration(supernovaRound) * roundDuration) + + roundArgs := round.ArgsRound{ + GenesisTimeStamp: tpn.GenesisTimeField, + SupernovaGenesisTimeStamp: tpn.SupernovaGenesisTimeField, + CurrentTimeStamp: syncer.CurrentTime(), + RoundTimeDuration: roundDuration, + SupernovaTimeDuration: roundDuration, + SyncTimer: syncer, + StartRound: 0, + SupernovaStartRound: int64(supernovaRound), + EnableRoundsHandler: tpn.EnableRoundsHandler, + } + roundHandler, _ := round.NewRound(roundArgs) tpn.NetworkShardingCollector = mock.NewNetworkShardingCollectorMock() if check.IfNil(tpn.EpochNotifier) { @@ -262,6 +301,25 @@ func (tpn *TestFullNode) initTestNodeWithArgs(args ArgTestProcessorNode, fullArg tpn.initAccountDBs(args.TrieStore) } + chainParam := config.ChainParametersByEpochConfig{ + RoundDuration: uint64(roundDuration.Milliseconds()), + ShardConsensusGroupSize: uint32(fullArgs.ConsensusSize), + MetachainConsensusGroupSize: uint32(fullArgs.ConsensusSize), + RoundsPerEpoch: fullArgs.RoundsPerEpoch, + MinRoundsBetweenEpochs: 1, + } + tpn.ChainParametersHandler = &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(_ uint32) (config.ChainParametersByEpochConfig, error) { + return chainParam, nil + }, + CurrentChainParametersCalled: func() config.ChainParametersByEpochConfig { + return chainParam + }, + AllChainParametersCalled: func() []config.ChainParametersByEpochConfig { + return []config.ChainParametersByEpochConfig{chainParam} + }, + } + economicsConfig := args.EconomicsConfig if economicsConfig == nil { economicsConfig = createDefaultEconomicsConfig() @@ -272,6 +330,20 @@ func (tpn *TestFullNode) initTestNodeWithArgs(args ArgTestProcessorNode, fullArg tpn.initRequestedItemsHandler() tpn.initResolvers() tpn.initRequesters() + + // init the real nodesCoordinator before the init of the validatorStatistics to avoid saving + // the mock validator address in the peer trie + pkBytes, _ := tpn.NodeKeys.MainKey.Pk.ToByteArray() + consensusCache, _ := cache.NewLRUCache(10000) + tpn.initNodesCoordinator( + createHasher(fullArgs.ConsensusType), + tpn.EpochStartNotifier, + fullArgs.EligibleMap, + fullArgs.WaitingMap, + pkBytes, + consensusCache, + ) + tpn.initValidatorStatistics() tpn.initGenesisBlocks(args) tpn.initBlockTracker(roundHandler) @@ -315,21 +387,6 @@ func (tpn *TestFullNode) initTestNodeWithArgs(args ArgTestProcessorNode, fullArg tpn.createFullSCQueryService(gasMap, vmConfig) } - testHasher := createHasher(fullArgs.ConsensusType) - epochStartRegistrationHandler := notifier.NewEpochStartSubscriptionHandler() - pkBytes, _ := tpn.NodeKeys.MainKey.Pk.ToByteArray() - consensusCache, _ := cache.NewLRUCache(10000) - - tpn.initNodesCoordinator( - fullArgs.ConsensusSize, - testHasher, - epochStartRegistrationHandler, - fullArgs.EligibleMap, - fullArgs.WaitingMap, - pkBytes, - consensusCache, - ) - tpn.BroadcastMessenger, _ = sposFactory.GetBroadcastMessenger( TestMarshalizer, TestHasher, @@ -337,6 +394,9 @@ func (tpn *TestFullNode) initTestNodeWithArgs(args ArgTestProcessorNode, fullArg tpn.ShardCoordinator, tpn.OwnAccount.PeerSigHandler, tpn.DataPool.Headers(), + tpn.DataPool.Headers(), + tpn.DataPool.Proofs(), + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, tpn.MainInterceptorsContainer, &testscommon.AlarmSchedulerStub{}, testscommon.NewKeysHandlerSingleSignerMock( @@ -394,8 +454,11 @@ func (tpn *TestFullNode) initNode( if tpn.EnableEpochsHandler == nil { tpn.EnableEpochsHandler, _ = enablers.NewEnableEpochsHandler(CreateEnableEpochsConfig(), tpn.EpochNotifier) } + if tpn.EnableRoundsHandler == nil { + tpn.EnableRoundsHandler, _ = enablers.NewEnableRoundsHandler(*args.RoundsConfig, tpn.RoundNotifier) + } - epochTrigger := tpn.createEpochStartTrigger(args.StartTime) + epochTrigger := tpn.createEpochStartTrigger() tpn.EpochStartTrigger = epochTrigger strPk := "" @@ -410,7 +473,7 @@ func (tpn *TestFullNode) initNode( coreComponents := GetDefaultCoreComponents(tpn.EnableEpochsHandler, tpn.EpochNotifier) coreComponents.SyncTimerField = syncer coreComponents.RoundHandlerField = roundHandler - + coreComponents.EnableRoundsHandlerField = tpn.EnableRoundsHandler coreComponents.InternalMarshalizerField = TestMarshalizer coreComponents.VmMarshalizerField = TestVmMarshalizer coreComponents.TxMarshalizerField = TestTxSignMarshalizer @@ -422,6 +485,7 @@ func (tpn *TestFullNode) initNode( } coreComponents.GenesisTimeField = tpn.GenesisTimeField + coreComponents.SupernovaGenesisTimeField = tpn.SupernovaGenesisTimeField coreComponents.GenesisNodesSetupField = &genesisMocks.NodesSetupStub{ GetShardConsensusGroupSizeCalled: func() uint32 { return uint32(args.ConsensusSize) @@ -450,17 +514,21 @@ func (tpn *TestFullNode) initNode( dataComponents.DataPool = tpn.DataPool dataComponents.Store = tpn.Storage - bootstrapComponents := getDefaultBootstrapComponents(tpn.ShardCoordinator, tpn.EnableEpochsHandler) + bootstrapComponents := getDefaultBootstrapComponents(tpn.ShardCoordinator, tpn.EnableEpochsHandler, tpn.EnableRoundsHandler) tpn.BlockBlackListHandler = cache.NewTimeCache(TimeSpanForBadHeaders) - tpn.ForkDetector = tpn.createForkDetector(args.StartTime, roundHandler) + tpn.ForkDetector = tpn.createForkDetector(roundHandler) argsKeysHolder := keysManagement.ArgsManagedPeersHolder{ - KeyGenerator: args.KeyGen, - P2PKeyGenerator: args.P2PKeyGen, - MaxRoundsOfInactivity: 0, // 0 for main node, non-0 for backup node - PrefsConfig: config.Preferences{}, + KeyGenerator: args.KeyGen, + P2PKeyGenerator: args.P2PKeyGen, + PrefsConfig: config.Preferences{ + Preferences: config.PreferencesConfig{ + RedundancyLevel: 0, // 0 for main node, non-0 for backup node + }, + }, P2PKeyConverter: p2pFactory.NewP2PKeyConverter(), + ProcessConfigsHandler: &testscommon.ProcessConfigsHandlerStub{}, } keysHolder, _ := keysManagement.NewManagedPeersHolder(argsKeysHolder) @@ -487,6 +555,7 @@ func (tpn *TestFullNode) initNode( KeyGenerator: args.KeyGen, KeysHandler: keysHandler, SingleSigner: TestSingleBlsSigner, + PubKeysCache: cacheMocks.NewCacherMock(), } sigHandler, _ := cryptoFactory.NewSigningHandler(signingHandlerArgs) @@ -532,6 +601,7 @@ func (tpn *TestFullNode) initNode( processComponents.ScheduledTxsExecutionHandlerInternal = &testscommon.ScheduledTxsExecutionStub{} processComponents.ProcessedMiniBlocksTrackerInternal = &testscommon.ProcessedMiniBlocksTrackerStub{} processComponents.SentSignaturesTrackerInternal = &testscommon.SentSignatureTrackerStub{} + processComponents.ExecManager = tpn.ExecutionManager processComponents.RoundHandlerField = roundHandler processComponents.EpochNotifier = tpn.EpochStartNotifier @@ -539,6 +609,7 @@ func (tpn *TestFullNode) initNode( stateComponents := GetDefaultStateComponents() stateComponents.Accounts = tpn.AccntState stateComponents.AccountsAPI = tpn.AccntState + stateComponents.AccountsProposal = tpn.AccntState finalProvider, _ := blockInfoProviders.NewFinalBlockInfo(dataComponents.BlockChain) finalAccountsApi, _ := state.NewAccountsDBApi(tpn.AccntState, finalProvider) @@ -595,12 +666,13 @@ func (tpn *TestFullNode) initNode( NumResolveFailureThreshold: 1, DebugLineExpiration: 1000, }, + tpn.EpochStartNotifier, + coreComponents.SyncTimer(), ) log.LogIfError(err) } func (tfn *TestFullNode) createForkDetector( - startTime int64, roundHandler consensus.RoundHandler, ) process.ForkDetector { var err error @@ -612,16 +684,27 @@ func (tfn *TestFullNode) createForkDetector( tfn.BlockBlackListHandler, tfn.BlockTracker, tfn.GenesisTimeField.Unix(), + tfn.SupernovaGenesisTimeField.UnixMilli(), tfn.EnableEpochsHandler, - tfn.DataPool.Proofs()) + tfn.EnableRoundsHandler, + tfn.DataPool.Proofs(), + tfn.ChainParametersHandler, + tfn.ProcessConfigsHandler, + tfn.ShardCoordinator.SelfId(), + ) } else { forkDetector, err = processSync.NewMetaForkDetector( roundHandler, tfn.BlockBlackListHandler, tfn.BlockTracker, tfn.GenesisTimeField.Unix(), + tfn.SupernovaGenesisTimeField.UnixMilli(), tfn.EnableEpochsHandler, - tfn.DataPool.Proofs()) + tfn.EnableRoundsHandler, + tfn.DataPool.Proofs(), + tfn.ChainParametersHandler, + tfn.ProcessConfigsHandler, + ) } if err != nil { log.Error("error creating fork detector", "error", err) @@ -631,22 +714,22 @@ func (tfn *TestFullNode) createForkDetector( return forkDetector } -func (tfn *TestFullNode) createEpochStartTrigger(startTime int64) TestEpochStartTrigger { +func (tfn *TestFullNode) createEpochStartTrigger() TestEpochStartTrigger { var epochTrigger TestEpochStartTrigger if tfn.ShardCoordinator.SelfId() == core.MetachainShardId { + genesisTime := common.GetGenesisStartTimeFromUnixTimestamp(tfn.GenesisTimeField.Unix(), tfn.EnableEpochsHandler) + argsNewMetaEpochStart := &metachain.ArgsNewMetaEpochStartTrigger{ - GenesisTime: tfn.GenesisTimeField, - EpochStartNotifier: notifier.NewEpochStartSubscriptionHandler(), - Settings: &config.EpochStartConfig{ - MinRoundsBetweenEpochs: 1, - RoundsPerEpoch: 1000, - }, - Epoch: 0, - Storage: createTestStore(), - Marshalizer: TestMarshalizer, - Hasher: TestHasher, - AppStatusHandler: &statusHandlerMock.AppStatusHandlerStub{}, - DataPool: tfn.DataPool, + GenesisTime: genesisTime, + EpochStartNotifier: tfn.EpochStartNotifier, + Settings: &config.EpochStartConfig{}, + Epoch: 0, + Storage: tfn.Storage, + Marshalizer: TestMarshalizer, + Hasher: TestHasher, + AppStatusHandler: &statusHandlerMock.AppStatusHandlerStub{}, + DataPool: tfn.DataPool, + ChainParametersHandler: tfn.ChainParametersHandler, } epochStartTrigger, err := metachain.NewEpochStartTrigger(argsNewMetaEpochStart) if err != nil { @@ -673,11 +756,12 @@ func (tfn *TestFullNode) createEpochStartTrigger(startTime int64) TestEpochStart Epoch: 0, Validity: 1, Finality: 1, - EpochStartNotifier: notifier.NewEpochStartSubscriptionHandler(), + EpochStartNotifier: tfn.EpochStartNotifier, PeerMiniBlocksSyncer: peerMiniBlockSyncer, RoundHandler: tfn.RoundHandler, AppStatusHandler: &statusHandlerMock.AppStatusHandlerStub{}, EnableEpochsHandler: tfn.EnableEpochsHandler, + CommonConfigsHandler: testscommon.GetDefaultCommonConfigsHandler(), } epochStartTrigger, err := shardchain.NewEpochStartTrigger(argsShardEpochStart) if err != nil { @@ -690,7 +774,7 @@ func (tfn *TestFullNode) createEpochStartTrigger(startTime int64) TestEpochStart return epochTrigger } -func (tcn *TestFullNode) initInterceptors( +func (tfn *TestFullNode) initInterceptors( coreComponents process.CoreComponentsHolder, cryptoComponents process.CryptoComponentsHolder, roundHandler consensus.RoundHandler, @@ -699,8 +783,9 @@ func (tcn *TestFullNode) initInterceptors( epochStartTrigger TestEpochStartTrigger, ) { interceptorDataVerifierArgs := interceptorsFactory.InterceptedDataVerifierFactoryArgs{ - CacheSpan: time.Second * 10, - CacheExpiry: time.Second * 10, + InterceptedDataVerifierConfig: config.InterceptedDataVerifierConfig{ + EnableCaching: false, + }, } accountsAdapter := epochStartDisabled.NewAccountsAdapter() @@ -708,7 +793,7 @@ func (tcn *TestFullNode) initInterceptors( blockBlackListHandler := cache.NewTimeCache(TimeSpanForBadHeaders) genesisBlocks := make(map[uint32]data.HeaderHandler) - blockTracker := processMock.NewBlockTrackerMock(tcn.ShardCoordinator, genesisBlocks) + blockTracker := processMock.NewBlockTrackerMock(tfn.ShardCoordinator, genesisBlocks) whiteLstHandler, _ := disabledInterceptors.NewDisabledWhiteListDataVerifier() @@ -720,12 +805,12 @@ func (tcn *TestFullNode) initInterceptors( CoreComponents: coreComponents, CryptoComponents: cryptoComponents, Accounts: accountsAdapter, - ShardCoordinator: tcn.ShardCoordinator, - NodesCoordinator: tcn.NodesCoordinator, - MainMessenger: tcn.MainMessenger, - FullArchiveMessenger: tcn.FullArchiveMessenger, + ShardCoordinator: tfn.ShardCoordinator, + NodesCoordinator: tfn.NodesCoordinator, + MainMessenger: tfn.MainMessenger, + FullArchiveMessenger: tfn.FullArchiveMessenger, Store: storage, - DataPool: tcn.DataPool, + DataPool: tfn.DataPool, MaxTxNonceDeltaAllowed: common.MaxTxNonceDeltaAllowed, TxFeeHandler: &economicsmocks.EconomicsHandlerMock{}, BlockBlackList: blockBlackListHandler, @@ -748,21 +833,27 @@ func (tcn *TestFullNode) initInterceptors( HardforkTrigger: &testscommon.HardforkTriggerStub{}, NodeOperationMode: common.NormalOperation, InterceptedDataVerifierFactory: interceptorsFactory.NewInterceptedDataVerifierFactory(interceptorDataVerifierArgs), + Config: config.Config{ + InterceptedDataVerifier: config.InterceptedDataVerifierConfig{ + CacheSpanInSec: 1, + CacheExpiryInSec: 1, + }, + }, } - if tcn.ShardCoordinator.SelfId() == core.MetachainShardId { + if tfn.ShardCoordinator.SelfId() == core.MetachainShardId { interceptorContainerFactory, err := interceptorscontainer.NewMetaInterceptorsContainerFactory(interceptorContainerFactoryArgs) if err != nil { fmt.Println(err.Error()) } - tcn.MainInterceptorsContainer, _, err = interceptorContainerFactory.Create() + tfn.MainInterceptorsContainer, _, err = interceptorContainerFactory.Create() if err != nil { log.Debug("interceptor container factory Create", "error", err.Error()) } } else { argsPeerMiniBlocksSyncer := shardchain.ArgPeerMiniBlockSyncer{ - MiniBlocksPool: tcn.DataPool.MiniBlocks(), - ValidatorsInfoPool: tcn.DataPool.ValidatorsInfo(), + MiniBlocksPool: tfn.DataPool.MiniBlocks(), + ValidatorsInfoPool: tfn.DataPool.ValidatorsInfo(), RequestHandler: &testscommon.RequestHandlerStub{}, } peerMiniBlockSyncer, _ := shardchain.NewPeerMiniBlockSyncer(argsPeerMiniBlocksSyncer) @@ -771,17 +862,18 @@ func (tcn *TestFullNode) initInterceptors( Hasher: TestHasher, HeaderValidator: &mock.HeaderValidatorStub{}, Uint64Converter: TestUint64Converter, - DataPool: tcn.DataPool, + DataPool: tfn.DataPool, Storage: storage, RequestHandler: &testscommon.RequestHandlerStub{}, Epoch: 0, Validity: 1, Finality: 1, - EpochStartNotifier: notifier.NewEpochStartSubscriptionHandler(), + EpochStartNotifier: tfn.EpochStartNotifier, PeerMiniBlocksSyncer: peerMiniBlockSyncer, RoundHandler: roundHandler, AppStatusHandler: &statusHandlerMock.AppStatusHandlerStub{}, EnableEpochsHandler: enableEpochsHandler, + CommonConfigsHandler: testscommon.GetDefaultCommonConfigsHandler(), } _, _ = shardchain.NewEpochStartTrigger(argsShardEpochStart) @@ -790,7 +882,7 @@ func (tcn *TestFullNode) initInterceptors( fmt.Println(err.Error()) } - tcn.MainInterceptorsContainer, _, err = interceptorContainerFactory.Create() + tfn.MainInterceptorsContainer, _, err = interceptorContainerFactory.Create() if err != nil { fmt.Println(err.Error()) } @@ -800,7 +892,7 @@ func (tcn *TestFullNode) initInterceptors( func (tpn *TestFullNode) initBlockProcessor( coreComponents *mock.CoreComponentsStub, dataComponents *mock.DataComponentsStub, - args ArgsTestFullNode, + _ ArgsTestFullNode, roundHandler consensus.RoundHandler, ) { var err error @@ -816,7 +908,7 @@ func (tpn *TestFullNode) initBlockProcessor( tpn.EnableEpochsHandler, _ = enablers.NewEnableEpochsHandler(CreateEnableEpochsConfig(), tpn.EpochNotifier) } - bootstrapComponents := getDefaultBootstrapComponents(tpn.ShardCoordinator, tpn.EnableEpochsHandler) + bootstrapComponents := getDefaultBootstrapComponents(tpn.ShardCoordinator, tpn.EnableEpochsHandler, tpn.EnableRoundsHandler) bootstrapComponents.HdrIntegrityVerifier = tpn.HeaderIntegrityVerifier statusComponents := GetDefaultStatusComponents() @@ -825,37 +917,154 @@ func (tpn *TestFullNode) initBlockProcessor( AppStatusHandlerField: &statusHandlerMock.AppStatusHandlerStub{}, } + argsHeadersForBlock := headerForBlock.ArgHeadersForBlock{ + DataPool: tpn.DataPool, + RequestHandler: tpn.RequestHandler, + EnableEpochsHandler: tpn.EnableEpochsHandler, + ShardCoordinator: tpn.ShardCoordinator, + BlockTracker: tpn.BlockTracker, + TxCoordinator: tpn.TxCoordinator, + RoundHandler: tpn.RoundHandler, + ExtraDelayForRequestBlockInfoInMilliseconds: 0, + GenesisNonce: tpn.GenesisBlocks[tpn.ShardCoordinator.SelfId()].GetNonce(), + } + hdrsForBlock, err := headerForBlock.NewHeadersForBlock(argsHeadersForBlock) + if err != nil { + log.Error("initBlockProcessor NewHeadersForBlock", "error", err) + } + + blockDataRequesterArgs := coordinator.BlockDataRequestArgs{ + RequestHandler: tpn.RequestHandler, + MiniBlockPool: tpn.DataPool.MiniBlocks(), + PreProcessors: tpn.PreProcessorsProposal, + ShardCoordinator: tpn.ShardCoordinator, + EnableEpochsHandler: tpn.EnableEpochsHandler, + } + // second instance for proposal missing data fetching to avoid interferences + proposalBlockDataRequester, err := coordinator.NewBlockDataRequester(blockDataRequesterArgs) + if err != nil { + log.LogIfError(err) + } + + mbSelectionSession, err := block.NewMiniBlocksSelectionSession( + tpn.ShardCoordinator.SelfId(), + TestMarshalizer, + TestHasher, + ) + if err != nil { + log.LogIfError(err) + } + + executionResultsTracker := executionTrack.NewExecutionResultsTracker() + tpn.BlocksCache = headersCache.NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + + argsExecutionManager := executionManager.ArgsExecutionManager{ + BlocksCache: tpn.BlocksCache, + ExecutionResultsTracker: executionResultsTracker, + BlockChain: tpn.BlockChain, + Headers: tpn.DataPool.Headers(), + PostProcessTransactions: tpn.DataPool.PostProcessTransactions(), + ExecutedMiniBlocks: tpn.DataPool.ExecutedMiniBlocks(), + StorageService: &storageStubs.ChainStorerStub{}, + Marshaller: TestMarshaller, + ShardCoordinator: &testscommon.ShardsCoordinatorMock{}, + } + tpn.ExecutionManager, err = executionManager.NewExecutionManager(argsExecutionManager) + if err != nil { + log.LogIfError(err) + } + + err = process.SetBaseExecutionResult(tpn.ExecutionManager, tpn.BlockChain) + if err != nil { + log.LogIfError(err) + } + + execResultsVerifier, err := block.NewExecutionResultsVerifier(tpn.BlockChain, tpn.ExecutionManager) + if err != nil { + log.LogIfError(err) + } + + inclusionEstimator, err := estimator.NewExecutionResultInclusionEstimator( + config.ExecutionResultInclusionEstimatorConfig{ + SafetyMargin: 110, + MaxResultsPerBlock: 20, + }, + tpn.RoundHandler, + &testscommon.ExecResSizeComputationStub{}, + ) + if err != nil { + log.LogIfError(err) + } + + missingDataArgs := missingData.ResolverArgs{ + HeadersPool: tpn.DataPool.Headers(), + ProofsPool: tpn.DataPool.Proofs(), + RequestHandler: tpn.RequestHandler, + BlockDataRequester: proposalBlockDataRequester, + } + missingDataResolver, err := missingData.NewMissingDataResolver(missingDataArgs) + if err != nil { + log.LogIfError(err) + } + + argsGasConsumption := block.ArgsGasConsumption{ + EconomicsFee: tpn.EconomicsData, + ShardCoordinator: tpn.ShardCoordinator, + GasHandler: tpn.GasHandler, + BlockCapacityOverestimationFactor: 200, + PercentDecreaseLimitsStep: 10, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + } + gasConsumption, err := block.NewGasConsumption(argsGasConsumption) + if err != nil { + log.LogIfError(err) + } + argumentsBase := block.ArgBaseProcessor{ CoreComponents: coreComponents, DataComponents: dataComponents, BootstrapComponents: bootstrapComponents, StatusComponents: statusComponents, StatusCoreComponents: statusCoreComponents, - Config: config.Config{}, - AccountsDB: accountsDb, - ForkDetector: tpn.ForkDetector, - NodesCoordinator: tpn.NodesCoordinator, - FeeHandler: tpn.FeeAccumulator, - RequestHandler: tpn.RequestHandler, - BlockChainHook: tpn.BlockchainHook, - HeaderValidator: tpn.HeaderValidator, + Config: config.Config{ + GeneralSettings: config.GeneralSettingsConfig{ + MaxProposalNonceGap: 50, + }, + }, + AccountsDB: accountsDb, + AccountsProposal: tpn.AccntStateProposal, + ForkDetector: tpn.ForkDetector, + NodesCoordinator: tpn.NodesCoordinator, + FeeHandler: tpn.FeeAccumulator, + RequestHandler: tpn.RequestHandler, + BlockChainHook: tpn.BlockchainHook, + HeaderValidator: tpn.HeaderValidator, BootStorer: &mock.BoostrapStorerMock{ PutCalled: func(round int64, bootData bootstrapStorage.BootstrapData) error { return nil }, }, - BlockTracker: tpn.BlockTracker, - BlockSizeThrottler: TestBlockSizeThrottler, - HistoryRepository: tpn.HistoryRepository, - GasHandler: tpn.GasHandler, - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - ReceiptsRepository: &testscommon.ReceiptsRepositoryStub{}, - OutportDataProvider: &outport.OutportDataProviderStub{}, - BlockProcessingCutoffHandler: &testscommon.BlockProcessingCutoffStub{}, - ManagedPeersHolder: &testscommon.ManagedPeersHolderStub{}, - SentSignaturesTracker: &testscommon.SentSignatureTrackerStub{}, - StateAccessesCollector: disabled.NewDisabledStateAccessesCollector(), + BlockTracker: tpn.BlockTracker, + BlockSizeThrottler: TestBlockSizeThrottler, + HistoryRepository: tpn.HistoryRepository, + GasHandler: tpn.GasHandler, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + ReceiptsRepository: &testscommon.ReceiptsRepositoryStub{}, + OutportDataProvider: &outport.OutportDataProviderStub{}, + BlockProcessingCutoffHandler: &testscommon.BlockProcessingCutoffStub{}, + ManagedPeersHolder: &testscommon.ManagedPeersHolderStub{}, + SentSignaturesTracker: &testscommon.SentSignatureTrackerStub{}, + StateAccessesCollector: disabled.NewDisabledStateAccessesCollector(), + HeadersForBlock: hdrsForBlock, + MiniBlocksSelectionSession: mbSelectionSession, + ExecutionResultsVerifier: execResultsVerifier, + MissingDataResolver: missingDataResolver, + ExecutionResultsInclusionEstimator: inclusionEstimator, + GasComputation: gasConsumption, + ExecutionManager: tpn.ExecutionManager, + TxExecutionOrderHandler: tpn.TxExecutionOrderHandler, + AOTSelector: aotSelection.NewDisabledAOTSelector(), } if check.IfNil(tpn.EpochStartNotifier) { @@ -903,11 +1112,12 @@ func (tpn *TestFullNode) initBlockProcessor( GenesisTotalSupply: tpn.EconomicsData.GenesisTotalSupply(), EconomicsDataNotified: economicsDataProvider, StakingV2EnableEpoch: tpn.EnableEpochs.StakingV2EnableEpoch, + EnableEpochsHandler: tpn.EnableEpochsHandler, + ChainParamsHandler: tpn.ChainParametersHandler, } epochEconomics, _ := metachain.NewEndOfEpochEconomicsDataCreator(argsEpochEconomics) - systemVM, _ := mock.NewOneSCExecutorMockVM(tpn.BlockchainHook, TestHasher) - + systemVM, _ := tpn.VMContainer.Get(processFactory.SystemVirtualMachine) argsStakingDataProvider := metachain.StakingDataProviderArgs{ EnableEpochsHandler: coreComponents.EnableEpochsHandler(), SystemVM: systemVM, @@ -1004,6 +1214,18 @@ func (tpn *TestFullNode) initBlockProcessor( epochStartSystemSCProcessor, _ := metachain.NewSystemSCProcessor(argsEpochSystemSC) tpn.EpochStartSystemSCProcessor = epochStartSystemSCProcessor + shardInfoCreateDataArgs := block.ShardInfoCreateDataArgs{ + EnableEpochsHandler: tpn.EnableEpochsHandler, + HeadersPool: tpn.DataPool.Headers(), + ProofsPool: tpn.DataPool.Proofs(), + PendingMiniBlocksHandler: &mock.PendingMiniBlocksHandlerStub{}, + BlockTracker: argumentsBase.BlockTracker, + Storage: tpn.Storage, + Marshaller: TestMarshalizer, + } + shardInfoCreator, errShardInfoCreate := block.NewShardInfoCreateData(shardInfoCreateDataArgs) + log.LogIfError(errShardInfoCreate) + arguments := block.ArgMetaProcessor{ ArgBaseProcessor: argumentsBase, SCToProtocol: scToProtocolInstance, @@ -1014,6 +1236,7 @@ func (tpn *TestFullNode) initBlockProcessor( EpochValidatorInfoCreator: epochStartValidatorInfo, ValidatorStatisticsProcessor: tpn.ValidatorStatisticsProcessor, EpochSystemSCProcessor: epochStartSystemSCProcessor, + ShardInfoCreator: shardInfoCreator, } tpn.BlockProcessor, err = block.NewMetaProcessor(arguments) @@ -1036,12 +1259,25 @@ func (tpn *TestFullNode) initBlockProcessor( } } + argsHeadersExecutor := asyncExecution.ArgsHeadersExecutor{ + BlocksCache: tpn.BlocksCache, + ExecutionTracker: executionResultsTracker, + BlockProcessor: tpn.BlockProcessor, + BlockChain: tpn.BlockChain, + } + headerExecutor, err := asyncExecution.NewHeadersExecutor(argsHeadersExecutor) + log.LogIfError(err) + + err = tpn.ExecutionManager.SetHeadersExecutor(headerExecutor) + log.LogIfError(err) + + tpn.ExecutionManager.StartExecution() } func (tpn *TestFullNode) initBlockProcessorWithSync( coreComponents *mock.CoreComponentsStub, dataComponents *mock.DataComponentsStub, - roundHandler consensus.RoundHandler, + _ consensus.RoundHandler, ) { var err error @@ -1056,15 +1292,118 @@ func (tpn *TestFullNode) initBlockProcessorWithSync( tpn.EnableEpochsHandler, _ = enablers.NewEnableEpochsHandler(CreateEnableEpochsConfig(), tpn.EpochNotifier) } - bootstrapComponents := getDefaultBootstrapComponents(tpn.ShardCoordinator, tpn.EnableEpochsHandler) + bootstrapComponents := getDefaultBootstrapComponents(tpn.ShardCoordinator, tpn.EnableEpochsHandler, tpn.EnableRoundsHandler) bootstrapComponents.HdrIntegrityVerifier = tpn.HeaderIntegrityVerifier statusComponents := GetDefaultStatusComponents() - statusCoreComponents := &factory.StatusCoreComponentsStub{ + statusCoreComponents := &testFactory.StatusCoreComponentsStub{ AppStatusHandlerField: &statusHandlerMock.AppStatusHandlerStub{}, } + argsHeadersForBlock := headerForBlock.ArgHeadersForBlock{ + DataPool: tpn.DataPool, + RequestHandler: tpn.RequestHandler, + EnableEpochsHandler: tpn.EnableEpochsHandler, + ShardCoordinator: tpn.ShardCoordinator, + BlockTracker: tpn.BlockTracker, + TxCoordinator: tpn.TxCoordinator, + RoundHandler: tpn.RoundHandler, + ExtraDelayForRequestBlockInfoInMilliseconds: 0, + GenesisNonce: tpn.GenesisBlocks[tpn.ShardCoordinator.SelfId()].GetNonce(), + } + hdrsForBlock, err := headerForBlock.NewHeadersForBlock(argsHeadersForBlock) + if err != nil { + log.Error("initBlockProcessorWithSync NewHeadersForBlock", "error", err) + } + + blockDataRequesterArgs := coordinator.BlockDataRequestArgs{ + RequestHandler: tpn.RequestHandler, + MiniBlockPool: tpn.DataPool.MiniBlocks(), + PreProcessors: tpn.PreProcessorsProposal, + ShardCoordinator: tpn.ShardCoordinator, + EnableEpochsHandler: tpn.EnableEpochsHandler, + } + // second instance for proposal missing data fetching to avoid interferences + proposalBlockDataRequester, err := coordinator.NewBlockDataRequester(blockDataRequesterArgs) + if err != nil { + log.LogIfError(err) + } + + mbSelectionSession, err := block.NewMiniBlocksSelectionSession( + tpn.ShardCoordinator.SelfId(), + TestMarshalizer, + TestHasher, + ) + if err != nil { + log.LogIfError(err) + } + + executionResultsTracker := executionTrack.NewExecutionResultsTracker() + tpn.BlocksCache = headersCache.NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + + argsExecutionManager := executionManager.ArgsExecutionManager{ + BlocksCache: tpn.BlocksCache, + ExecutionResultsTracker: executionResultsTracker, + BlockChain: tpn.BlockChain, + Headers: tpn.DataPool.Headers(), + PostProcessTransactions: tpn.DataPool.PostProcessTransactions(), + ExecutedMiniBlocks: tpn.DataPool.ExecutedMiniBlocks(), + StorageService: &storageStubs.ChainStorerStub{}, + Marshaller: TestMarshaller, + ShardCoordinator: &testscommon.ShardsCoordinatorMock{}, + } + tpn.ExecutionManager, err = executionManager.NewExecutionManager(argsExecutionManager) + if err != nil { + log.LogIfError(err) + } + + err = process.SetBaseExecutionResult(tpn.ExecutionManager, tpn.BlockChain) + if err != nil { + log.LogIfError(err) + } + + execResultsVerifier, err := block.NewExecutionResultsVerifier(tpn.BlockChain, tpn.ExecutionManager) + if err != nil { + log.LogIfError(err) + } + + inclusionEstimator, err := estimator.NewExecutionResultInclusionEstimator( + config.ExecutionResultInclusionEstimatorConfig{ + SafetyMargin: 110, + MaxResultsPerBlock: 20, + }, + tpn.RoundHandler, + &testscommon.ExecResSizeComputationStub{}, + ) + if err != nil { + log.LogIfError(err) + } + + missingDataArgs := missingData.ResolverArgs{ + HeadersPool: tpn.DataPool.Headers(), + ProofsPool: tpn.DataPool.Proofs(), + RequestHandler: tpn.RequestHandler, + BlockDataRequester: proposalBlockDataRequester, + } + missingDataResolver, err := missingData.NewMissingDataResolver(missingDataArgs) + if err != nil { + log.LogIfError(err) + } + + argsGasConsumption := block.ArgsGasConsumption{ + EconomicsFee: tpn.EconomicsData, + ShardCoordinator: tpn.ShardCoordinator, + GasHandler: tpn.GasHandler, + BlockCapacityOverestimationFactor: 200, + PercentDecreaseLimitsStep: 10, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + } + gasConsumption, err := block.NewGasConsumption(argsGasConsumption) + if err != nil { + log.LogIfError(err) + } + argumentsBase := block.ArgBaseProcessor{ CoreComponents: coreComponents, DataComponents: dataComponents, @@ -1073,6 +1412,7 @@ func (tpn *TestFullNode) initBlockProcessorWithSync( StatusCoreComponents: statusCoreComponents, Config: config.Config{}, AccountsDB: accountsDb, + AccountsProposal: tpn.AccntStateProposal, ForkDetector: nil, NodesCoordinator: tpn.NodesCoordinator, FeeHandler: tpn.FeeAccumulator, @@ -1085,23 +1425,45 @@ func (tpn *TestFullNode) initBlockProcessorWithSync( return nil }, }, - BlockTracker: tpn.BlockTracker, - BlockSizeThrottler: TestBlockSizeThrottler, - HistoryRepository: tpn.HistoryRepository, - GasHandler: tpn.GasHandler, - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - ReceiptsRepository: &testscommon.ReceiptsRepositoryStub{}, - OutportDataProvider: &outport.OutportDataProviderStub{}, - BlockProcessingCutoffHandler: &testscommon.BlockProcessingCutoffStub{}, - ManagedPeersHolder: &testscommon.ManagedPeersHolderStub{}, - SentSignaturesTracker: &testscommon.SentSignatureTrackerStub{}, - StateAccessesCollector: disabled.NewDisabledStateAccessesCollector(), + BlockTracker: tpn.BlockTracker, + BlockSizeThrottler: TestBlockSizeThrottler, + HistoryRepository: tpn.HistoryRepository, + GasHandler: tpn.GasHandler, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + ReceiptsRepository: &testscommon.ReceiptsRepositoryStub{}, + OutportDataProvider: &outport.OutportDataProviderStub{}, + BlockProcessingCutoffHandler: &testscommon.BlockProcessingCutoffStub{}, + ManagedPeersHolder: &testscommon.ManagedPeersHolderStub{}, + SentSignaturesTracker: &testscommon.SentSignatureTrackerStub{}, + StateAccessesCollector: disabled.NewDisabledStateAccessesCollector(), + HeadersForBlock: hdrsForBlock, + MiniBlocksSelectionSession: mbSelectionSession, + ExecutionResultsVerifier: execResultsVerifier, + MissingDataResolver: missingDataResolver, + ExecutionResultsInclusionEstimator: inclusionEstimator, + GasComputation: gasConsumption, + ExecutionManager: tpn.ExecutionManager, + TxExecutionOrderHandler: tpn.TxExecutionOrderHandler, + AOTSelector: aotSelection.NewDisabledAOTSelector(), } if tpn.ShardCoordinator.SelfId() == core.MetachainShardId { argumentsBase.ForkDetector = tpn.ForkDetector argumentsBase.TxCoordinator = &mock.TransactionCoordinatorMock{} + + shardInfoCreateDataArgs := block.ShardInfoCreateDataArgs{ + EnableEpochsHandler: tpn.EnableEpochsHandler, + HeadersPool: tpn.DataPool.Headers(), + ProofsPool: tpn.DataPool.Proofs(), + PendingMiniBlocksHandler: &mock.PendingMiniBlocksHandlerStub{}, + BlockTracker: argumentsBase.BlockTracker, + Storage: tpn.Storage, + Marshaller: TestMarshalizer, + } + shardInfoCreator, errShardInfoCreate := block.NewShardInfoCreateData(shardInfoCreateDataArgs) + log.LogIfError(errShardInfoCreate) + arguments := block.ArgMetaProcessor{ ArgBaseProcessor: argumentsBase, SCToProtocol: &mock.SCToProtocolStub{}, @@ -1116,6 +1478,7 @@ func (tpn *TestFullNode) initBlockProcessorWithSync( }, }, EpochSystemSCProcessor: &testscommon.EpochStartSystemSCStub{}, + ShardInfoCreator: shardInfoCreator, } tpn.BlockProcessor, err = block.NewMetaProcessor(arguments) @@ -1152,8 +1515,10 @@ func (tpn *TestFullNode) initBlockTracker( WhitelistHandler: tpn.WhiteListHandler, FeeHandler: tpn.EconomicsData, EnableEpochsHandler: tpn.EnableEpochsHandler, + EnableRoundsHandler: tpn.EnableRoundsHandler, ProofsPool: tpn.DataPool.Proofs(), EpochChangeGracePeriodHandler: tpn.EpochChangeGracePeriodHandler, + ProcessConfigsHandler: tpn.ProcessConfigsHandler, } var err error diff --git a/integrationTests/testHeartbeatNode.go b/integrationTests/testHeartbeatNode.go index 23b13c40e3d..0bb0284728d 100644 --- a/integrationTests/testHeartbeatNode.go +++ b/integrationTests/testHeartbeatNode.go @@ -236,9 +236,8 @@ func NewTestHeartbeatNode( thn.MainPeerShardMapper.UpdatePeerIDInfo(localId, pkBytes, shardCoordinator.SelfId()) argsKeysManagement := keysManagement.ArgsManagedPeersHolder{ - KeyGenerator: TestBLSKeyGenerator, - P2PKeyGenerator: TestP2PKeyGenerator, - MaxRoundsOfInactivity: 0, + KeyGenerator: TestBLSKeyGenerator, + P2PKeyGenerator: TestP2PKeyGenerator, PrefsConfig: config.Preferences{ Preferences: config.PreferencesConfig{ NodeDisplayName: DefaultNodeName, @@ -246,7 +245,8 @@ func NewTestHeartbeatNode( RedundancyLevel: 0, }, }, - P2PKeyConverter: factory.NewP2PKeyConverter(), + P2PKeyConverter: factory.NewP2PKeyConverter(), + ProcessConfigsHandler: &testscommon.ProcessConfigsHandlerStub{}, } thn.ManagedPeersHolder, _ = keysManagement.NewManagedPeersHolder(argsKeysManagement) @@ -542,14 +542,20 @@ func (thn *TestHeartbeatNode) initResolversAndRequesters() { return &trieMock.TrieStub{} }, }, - SizeCheckDelta: 100, - InputAntifloodHandler: &mock.NilAntifloodHandler{}, - OutputAntifloodHandler: &mock.NilAntifloodHandler{}, - NumConcurrentResolvingJobs: 10, - NumConcurrentResolvingTrieNodesJobs: 3, - MainPreferredPeersHolder: &p2pmocks.PeersHolderStub{}, - FullArchivePreferredPeersHolder: &p2pmocks.PeersHolderStub{}, - PayloadValidator: payloadValidator, + SizeCheckDelta: 100, + InputAntifloodHandler: &mock.NilAntifloodHandler{}, + OutputAntifloodHandler: &mock.NilAntifloodHandler{}, + MainPreferredPeersHolder: &p2pmocks.PeersHolderStub{}, + FullArchivePreferredPeersHolder: &p2pmocks.PeersHolderStub{}, + PayloadValidator: payloadValidator, + AntifloodConfigsHandler: &testscommon.AntifloodConfigsHandlerStub{ + GetCurrentConfigCalled: func() config.AntifloodConfigByRound { + return config.AntifloodConfigByRound{ + NumConcurrentResolverJobs: 10, + NumConcurrentResolvingTrieNodesJobs: 3, + } + }, + }, } requestersContainerFactoryArgs := requesterscontainer.FactoryArgs{ @@ -726,6 +732,7 @@ func (thn *TestHeartbeatNode) initMultiDataInterceptor(topic string, dataFactory PreferredPeersHolder: &p2pmocks.PeersHolderStub{}, CurrentPeerId: thn.MainMessenger.ID(), InterceptedDataVerifier: &processMock.InterceptedDataVerifierMock{}, + ManagedPeersHolder: thn.ManagedPeersHolder, }, ) @@ -750,6 +757,7 @@ func (thn *TestHeartbeatNode) initSingleDataInterceptor(topic string, dataFactor PreferredPeersHolder: &p2pmocks.PeersHolderStub{}, CurrentPeerId: thn.MainMessenger.ID(), InterceptedDataVerifier: &processMock.InterceptedDataVerifierMock{}, + ManagedPeersHolder: thn.ManagedPeersHolder, }, ) diff --git a/integrationTests/testInitializer.go b/integrationTests/testInitializer.go index 45f7078f137..8e1d8969632 100644 --- a/integrationTests/testInitializer.go +++ b/integrationTests/testInitializer.go @@ -413,6 +413,8 @@ func CreateStore(numOfShards uint32) dataRetriever.StorageService { store.AddStorer(dataRetriever.ReceiptsUnit, CreateMemUnit()) store.AddStorer(dataRetriever.ScheduledSCRsUnit, CreateMemUnit()) store.AddStorer(dataRetriever.ProofsUnit, CreateMemUnit()) + store.AddStorer(dataRetriever.TrieEpochRootHashUnit, CreateMemUnit()) + store.AddStorer(dataRetriever.ExecutionResultsUnit, CreateMemUnit()) for i := uint32(0); i < numOfShards; i++ { hdrNonceHashDataUnit := dataRetriever.ShardHdrNonceHashDataUnit + dataRetriever.UnitType(i) @@ -486,6 +488,7 @@ func CreateAccountsDBWithEnableEpochsHandler( LastSnapshotMarker: lastSnapshotMarker.NewLastSnapshotMarker(), StateStatsHandler: statistics.NewStateStatistics(), }) + _ = snapshotsManager.SetSyncer(&mock.AccountsDBSyncerStub{}) args := state.ArgsAccountsDB{ Trie: tr, @@ -496,6 +499,7 @@ func CreateAccountsDBWithEnableEpochsHandler( AddressConverter: &testscommon.PubkeyConverterMock{}, SnapshotsManager: snapshotsManager, StateAccessesCollector: disabled.NewDisabledStateAccessesCollector(), + PruningEnabled: trieStorageManager.IsPruningEnabled(), } adb, _ := state.NewAccountsDB(args) @@ -541,21 +545,19 @@ func CreateMetaChain() data.ChainHandler { } // CreateSimpleGenesisBlocks creates empty genesis blocks for all known shards, including metachain -func CreateSimpleGenesisBlocks(shardCoordinator sharding.Coordinator) map[uint32]data.HeaderHandler { +func CreateSimpleGenesisBlocks(shardCoordinator sharding.Coordinator, rootHash []byte) map[uint32]data.HeaderHandler { genesisBlocks := make(map[uint32]data.HeaderHandler) for shardId := uint32(0); shardId < shardCoordinator.NumberOfShards(); shardId++ { - genesisBlocks[shardId] = CreateSimpleGenesisBlock(shardId) + genesisBlocks[shardId] = CreateSimpleGenesisBlock(shardId, rootHash) } - genesisBlocks[core.MetachainShardId] = CreateSimpleGenesisMetaBlock() + genesisBlocks[core.MetachainShardId] = CreateSimpleGenesisMetaBlock(rootHash) return genesisBlocks } // CreateSimpleGenesisBlock creates a new mock shard genesis block -func CreateSimpleGenesisBlock(shardId uint32) *dataBlock.Header { - rootHash := []byte("root hash") - +func CreateSimpleGenesisBlock(shardId uint32, rootHash []byte) *dataBlock.Header { return &dataBlock.Header{ Nonce: 0, Round: 0, @@ -572,9 +574,7 @@ func CreateSimpleGenesisBlock(shardId uint32) *dataBlock.Header { } // CreateSimpleGenesisMetaBlock creates a new mock meta genesis block -func CreateSimpleGenesisMetaBlock() *dataBlock.MetaBlock { - rootHash := []byte("root hash") - +func CreateSimpleGenesisMetaBlock(rootHash []byte) *dataBlock.MetaBlock { return &dataBlock.MetaBlock{ Nonce: 0, Epoch: 0, @@ -613,11 +613,16 @@ func CreateGenesisBlocks( dataPool dataRetriever.PoolsHolder, economics process.EconomicsDataHandler, enableEpochsConfig config.EnableEpochs, + chainParametersHandler common.ChainParametersHandler, ) map[uint32]data.HeaderHandler { genesisBlocks := make(map[uint32]data.HeaderHandler) for shardId := uint32(0); shardId < shardCoordinator.NumberOfShards(); shardId++ { - genesisBlocks[shardId] = CreateSimpleGenesisBlock(shardId) + rootHash, err := accounts.RootHash() + if err != nil { + log.Debug("Creating genesis block for shard ", "shardId", shardId, "err", err) + } + genesisBlocks[shardId] = CreateSimpleGenesisBlock(shardId, rootHash) } genesisBlocks[core.MetachainShardId] = CreateGenesisMetaBlock( @@ -635,6 +640,7 @@ func CreateGenesisBlocks( dataPool, economics, enableEpochsConfig, + chainParametersHandler, ) return genesisBlocks @@ -684,6 +690,7 @@ func CreateFullGenesisBlocks( GenesisTime: 0, StartEpochNum: 0, Accounts: accounts, + AccountsProposal: accounts, InitialNodesSetup: nodesSetup, Economics: economics, ShardCoordinator: shardCoordinator, @@ -722,6 +729,7 @@ func CreateFullGenesisBlocks( MinStepValue: "10", MinStakeValue: "1", UnBondPeriod: 1, + UnBondPeriodSupernova: 2, NumRoundsWithoutBleed: 1, MaximumPercentageToBleed: 1, BleedPercentagePerRound: 1, @@ -753,10 +761,21 @@ func CreateFullGenesisBlocks( EpochConfig: config.EpochConfig{ EnableEpochs: enableEpochsConfig, }, + FeeSettings: config.FeeSettings{ + BlockCapacityOverestimationFactor: 200, + PercentDecreaseLimitsStep: 10, + }, RoundConfig: testscommon.GetDefaultRoundsConfig(), HeaderVersionConfigs: testscommon.GetDefaultHeaderVersionConfig(), HistoryRepository: &dblookupext.HistoryRepositoryStub{}, TxExecutionOrderHandler: &commonMocks.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: config.TxCacheSelectionConfig{ + SelectionGasBandwidthIncreasePercent: 400, + SelectionGasBandwidthIncreaseScheduledPercent: 260, + SelectionGasRequested: 10_000_000_000, + SelectionMaxNumTxs: 30000, + SelectionLoopDurationCheckInterval: 10, + }, } genesisProcessor, _ := genesisProcess.NewGenesisBlockCreator(argsGenesis) @@ -781,6 +800,7 @@ func CreateGenesisMetaBlock( dataPool dataRetriever.PoolsHolder, economics process.EconomicsDataHandler, enableEpochsConfig config.EnableEpochs, + chainParametersHandler common.ChainParametersHandler, ) data.MetaHeaderHandler { gasSchedule := wasmConfig.MakeGasMapForTests() defaults.FillGasMapInternal(gasSchedule, 1) @@ -803,6 +823,7 @@ func CreateGenesisMetaBlock( Data: dataComponents, GenesisTime: 0, Accounts: accounts, + AccountsProposal: accounts, TrieStorageManagers: trieStorageManagers, InitialNodesSetup: nodesSetup, ShardCoordinator: shardCoordinator, @@ -842,6 +863,7 @@ func CreateGenesisMetaBlock( MinStepValue: "10", MinStakeValue: "1", UnBondPeriod: 1, + UnBondPeriodSupernova: 2, NumRoundsWithoutBleed: 1, MaximumPercentageToBleed: 1, BleedPercentagePerRound: 1, @@ -872,10 +894,21 @@ func CreateGenesisMetaBlock( EpochConfig: config.EpochConfig{ EnableEpochs: enableEpochsConfig, }, + FeeSettings: config.FeeSettings{ + BlockCapacityOverestimationFactor: 200, + PercentDecreaseLimitsStep: 10, + }, RoundConfig: testscommon.GetDefaultRoundsConfig(), HeaderVersionConfigs: testscommon.GetDefaultHeaderVersionConfig(), HistoryRepository: &dblookupext.HistoryRepositoryStub{}, TxExecutionOrderHandler: &commonMocks.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: config.TxCacheSelectionConfig{ + SelectionGasBandwidthIncreasePercent: 400, + SelectionGasBandwidthIncreaseScheduledPercent: 260, + SelectionGasRequested: 10_000_000_000, + SelectionMaxNumTxs: 30000, + SelectionLoopDurationCheckInterval: 10, + }, } if shardCoordinator.SelfId() != core.MetachainShardId { @@ -912,6 +945,42 @@ func CreateGenesisMetaBlock( return metaHdr } +// SetRootHashOfGenesisBlocks updates the root hash of the genesis block of each node and calls the OnGenesisExecutedBlock method +func SetRootHashOfGenesisBlocks(nodes []*TestProcessorNode) { + for _, tpn := range nodes { + rootHash, err := tpn.Node.GetStateComponents().AccountsAdapter().RootHash() + if err != nil { + log.Error("SetRootHashOfGenesisBlocks", "err", err) + } + + shardID := tpn.ShardCoordinator.SelfId() + genesisBlock := tpn.GenesisBlocks[shardID] + err = genesisBlock.SetRootHash(rootHash) + if err != nil { + log.Error("SetRootHashOfGenesisBlocks", "err", err) + } + + err = tpn.Node.GetDataComponents().Blockchain().SetGenesisHeader(genesisBlock) + if err != nil { + log.Error("SetRootHashOfGenesisBlocks", "err", err) + } + + err = OnGenesisExecutedBlock(tpn) + if err != nil { + log.Error("SetRootHashOfGenesisBlocks", "err", err) + } + } +} + +// OnGenesisExecutedBlock resets the tracker and calls the OnGenesisExecutedBlock with the genesis block +func OnGenesisExecutedBlock(node *TestProcessorNode) error { + shardID := node.ShardCoordinator.SelfId() + genesisBlock := node.GenesisBlocks[shardID] + node.DataPool.Transactions().ResetTracker() + + return node.DataPool.Transactions().OnExecutedBlock(genesisBlock, genesisBlock.GetRootHash()) +} + // CreateRandomAddress creates a random byte array with fixed size func CreateRandomAddress() []byte { return CreateRandomBytes(32) @@ -1405,11 +1474,39 @@ func CreateNodesWithEnableEpochsConfig( return createNodesWithEpochsConfig(numOfShards, nodesPerShard, numMetaChainNodes, enableEpochsConfig) } +// CreateNodesWithEnableConfigs - +func CreateNodesWithEnableConfigs( + numOfShards int, + nodesPerShard int, + numMetaChainNodes int, + enableEpochsConfig *config.EnableEpochs, + enableRoundsConfig *config.RoundConfig, +) []*TestProcessorNode { + return createNodesWithEnableConfigs(numOfShards, nodesPerShard, numMetaChainNodes, enableEpochsConfig, enableRoundsConfig) +} + func createNodesWithEpochsConfig( numOfShards int, nodesPerShard int, numMetaChainNodes int, enableEpochsConfig *config.EnableEpochs, +) []*TestProcessorNode { + defaultRoundsConfig := testscommon.GetDefaultRoundsConfig() + return createNodesWithEnableConfigs( + numOfShards, + nodesPerShard, + numMetaChainNodes, + enableEpochsConfig, + &defaultRoundsConfig, + ) +} + +func createNodesWithEnableConfigs( + numOfShards int, + nodesPerShard int, + numMetaChainNodes int, + enableEpochsConfig *config.EnableEpochs, + enableRoundsConfig *config.RoundConfig, ) []*TestProcessorNode { nodes := make([]*TestProcessorNode, numOfShards*nodesPerShard+numMetaChainNodes) connectableNodes := make([]Connectable, len(nodes)) @@ -1422,6 +1519,7 @@ func createNodesWithEpochsConfig( NodeShardId: shardId, TxSignPrivKeyShardId: shardId, EpochsConfig: enableEpochsConfig, + RoundsConfig: enableRoundsConfig, }) nodes[idx] = n connectableNodes[idx] = n @@ -1435,6 +1533,7 @@ func createNodesWithEpochsConfig( NodeShardId: core.MetachainShardId, TxSignPrivKeyShardId: 0, EpochsConfig: enableEpochsConfig, + RoundsConfig: enableRoundsConfig, }) idx = i + numOfShards*nodesPerShard nodes[idx] = metaNode @@ -1446,6 +1545,24 @@ func createNodesWithEpochsConfig( return nodes } +// CreateNodesWithEnableEpochsAndEnableRounds +func CreateNodesWithEnableEpochsAndEnableRounds( + numOfShards int, + nodesPerShard int, + numMetaChainNodes int, + epochConfig config.EnableEpochs, + roundConfig config.RoundConfig, +) []*TestProcessorNode { + return CreateNodesWithEnableEpochsAndVmConfigWithRoundsConfig( + numOfShards, + nodesPerShard, + numMetaChainNodes, + epochConfig, + roundConfig, + nil, + ) +} + // CreateNodesWithEnableEpochs creates multiple nodes with custom epoch config func CreateNodesWithEnableEpochs( numOfShards int, @@ -2276,6 +2393,7 @@ func generateValidTx( stateComponents := GetDefaultStateComponents() stateComponents.Accounts = accnts + stateComponents.AccountsProposal = accnts stateComponents.AccountsAPI = accnts mockNode, _ := node.NewNode( diff --git a/integrationTests/testNetwork.go b/integrationTests/testNetwork.go index c64651fab3f..7c7294fd3ca 100644 --- a/integrationTests/testNetwork.go +++ b/integrationTests/testNetwork.go @@ -423,13 +423,15 @@ func (net *TestNetwork) createNodes() { ScheduledMiniBlocksEnableEpoch: UnreachableEpoch, MiniBlockPartialExecutionEnableEpoch: UnreachableEpoch, AndromedaEnableEpoch: UnreachableEpoch, + SupernovaEnableEpoch: UnreachableEpoch, } - net.Nodes = CreateNodesWithEnableEpochs( + net.Nodes = CreateNodesWithEnableEpochsAndEnableRounds( net.NumShards, net.NodesPerShard, net.NodesInMetashard, enableEpochsConfig, + GetDefaultRoundsConfig(), ) } diff --git a/integrationTests/testProcessorNode.go b/integrationTests/testProcessorNode.go index 208c9183dab..b63908e027a 100644 --- a/integrationTests/testProcessorNode.go +++ b/integrationTests/testProcessorNode.go @@ -35,8 +35,23 @@ import ( "github.com/multiversx/mx-chain-vm-common-go/parsers" wasmConfig "github.com/multiversx/mx-chain-vm-go/config" + "github.com/multiversx/mx-chain-go/process/aotSelection" + "github.com/multiversx/mx-chain-go/process/asyncExecution" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionManager" + + headersCache "github.com/multiversx/mx-chain-go/process/asyncExecution/cache" + + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionTrack" + "github.com/multiversx/mx-chain-go/process/estimator" + "github.com/multiversx/mx-chain-go/process/missingData" + + "github.com/multiversx/mx-chain-go/txcache" + + "github.com/multiversx/mx-chain-go/process/block/headerForBlock" + nodeFactory "github.com/multiversx/mx-chain-go/cmd/node/factory" "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/common/configs" "github.com/multiversx/mx-chain-go/common/enablers" "github.com/multiversx/mx-chain-go/common/errChan" "github.com/multiversx/mx-chain-go/common/forking" @@ -77,7 +92,6 @@ import ( "github.com/multiversx/mx-chain-go/process/block/processedMb" "github.com/multiversx/mx-chain-go/process/coordinator" "github.com/multiversx/mx-chain-go/process/economics" - "github.com/multiversx/mx-chain-go/process/factory" procFactory "github.com/multiversx/mx-chain-go/process/factory" "github.com/multiversx/mx-chain-go/process/factory/interceptorscontainer" metaProcess "github.com/multiversx/mx-chain-go/process/factory/metachain" @@ -108,7 +122,6 @@ import ( "github.com/multiversx/mx-chain-go/storage" "github.com/multiversx/mx-chain-go/storage/cache" "github.com/multiversx/mx-chain-go/storage/storageunit" - "github.com/multiversx/mx-chain-go/storage/txcache" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/bootstrapMocks" cacheMocks "github.com/multiversx/mx-chain-go/testscommon/cache" @@ -118,6 +131,7 @@ import ( dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" dblookupextMock "github.com/multiversx/mx-chain-go/testscommon/dblookupext" "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" testFactory "github.com/multiversx/mx-chain-go/testscommon/factory" "github.com/multiversx/mx-chain-go/testscommon/genesisMocks" @@ -149,6 +163,31 @@ var TestHasher = sha256.NewSha256() // TestEpochChangeGracePeriod represents the grace period for epoch change handler var TestEpochChangeGracePeriod, _ = graceperiod.NewEpochChangeGracePeriod([]config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}) +// TestEpochChangeGracePeriod represents the grace period for epoch change handler +var TestProcessConfigsHandler, _ = configs.NewProcessConfigsHandler([]config.ProcessConfigByEpoch{{ + EnableEpoch: 0, + MaxMetaNoncesBehind: 15, + MaxMetaNoncesBehindForGlobalStuck: 30, + MaxShardNoncesBehind: 15, +}}, + []config.ProcessConfigByRound{ + { + EnableRound: 0, + MaxRoundsWithoutNewBlockReceived: 10, + MaxRoundsWithoutCommittedBlock: 10, + RoundModulusTriggerWhenSyncIsStuck: 20, + MaxRoundsToKeepUnprocessedTransactions: 50, + MaxRoundsToKeepUnprocessedMiniBlocks: 50, + NumFloodingRoundsSlowReacting: 20, + NumFloodingRoundsFastReacting: 30, + NumFloodingRoundsOutOfSpecs: 40, + MaxConsecutiveRoundsOfRatingDecrease: 600, + MaxBlockProcessingTimeMs: 1000, + }, + }, + forking.NewGenericRoundNotifier(), +) + // TestTxSignHasher represents a sha3 legacy keccak 256 hasher var TestTxSignHasher = keccak.NewKeccak() @@ -173,6 +212,9 @@ var TestMultiSig = cryptoMocks.NewMultiSigner() // TestKeyGenForAccounts represents a mock key generator for balances var TestKeyGenForAccounts = signing.NewKeyGenerator(ed25519.NewEd25519()) +// TestBlsKeyGen represents a mock key generator for pub keys +var TestBlsKeyGen = signing.NewKeyGenerator(mcl.NewSuiteBLS12()) + // TestUint64Converter represents an uint64 to byte slice converter var TestUint64Converter = uint64ByteSlice.NewBigEndianConverter() @@ -303,7 +345,7 @@ type ArgTestProcessorNode struct { NodeKeys *TestNodeKeys NodesSetup sharding.GenesisNodesSetupHandler NodesCoordinator nodesCoordinator.NodesCoordinator - MultiSigner crypto.MultiSigner + MultiSigner crypto.MultiSignerV2 RatingsData *rating.RatingsData HeaderSigVerifier process.InterceptedHeaderSigVerifier HeaderIntegrityVerifier process.HeaderIntegrityVerifier @@ -314,6 +356,7 @@ type ArgTestProcessorNode struct { WithPeersRatingHandler bool NodeOperationMode common.NodeOperation Proofs dataRetriever.ProofsPool + TxPoolConfig *config.CacheConfig } // TestProcessorNode represents a container type of class used in integration tests @@ -336,6 +379,7 @@ type TestProcessorNode struct { Storage dataRetriever.StorageService PeerState state.AccountsAdapter AccntState state.AccountsAdapter + AccntStateProposal state.AccountsAdapter TrieStorageManagers map[string]common.StorageManager TrieContainer common.TriesHolder BlockChain data.ChainHandler @@ -366,6 +410,8 @@ type TestProcessorNode struct { ArgsParser process.ArgumentsParser ScProcessor process.SmartContractProcessorFacade RewardsProcessor process.RewardTransactionProcessor + PreProcessorsFactory process.PreProcessorsContainerFactory + PreProcessorsProposal process.PreProcessorsContainer PreProcessorsContainer process.PreProcessorsContainer GasHandler process.GasHandler FeeAccumulator process.TransactionFeeHandler @@ -374,6 +420,8 @@ type TestProcessorNode struct { ForkDetector process.ForkDetector BlockProcessor process.BlockProcessor + BlocksCache process.BlocksCache + ExecutionManager process.ExecutionManager BroadcastMessenger consensus.BroadcastMessenger MiniblocksProvider process.MiniBlockProvider Bootstrapper TestBootstrapper @@ -389,7 +437,7 @@ type TestProcessorNode struct { EpochStartNotifier notifier.EpochStartNotifier EpochProvider dataRetriever.CurrentNetworkEpochProviderHandler - MultiSigner crypto.MultiSigner + MultiSigner crypto.MultiSignerV2 HeaderSigVerifier process.InterceptedHeaderSigVerifier HeaderIntegrityVerifier process.HeaderIntegrityVerifier GuardedAccountHandler process.GuardedAccountHandler @@ -421,9 +469,10 @@ type TestProcessorNode struct { EpochNotifier process.EpochNotifier RoundNotifier process.RoundNotifier EnableEpochs config.EnableEpochs - EnableRoundsHandler process.EnableRoundsHandler + EnableRoundsHandler common.EnableRoundsHandler EnableEpochsHandler common.EnableEpochsHandler EpochChangeGracePeriodHandler common.EpochChangeGracePeriodHandler + ProcessConfigsHandler common.ProcessConfigsHandler UseValidVmBlsSigVerifier bool TransactionLogProcessor process.TransactionLogProcessor @@ -432,6 +481,7 @@ type TestProcessorNode struct { HardforkTrigger node.HardforkTrigger AppStatusHandler core.AppStatusHandler StatusMetrics external.StatusMetricsHandler + ChainParametersHandler common.ChainParametersHandler } // CreatePkBytes creates 'numShards' public key-like byte slices @@ -532,6 +582,7 @@ func newBaseTestProcessorNode(args ArgTestProcessorNode) *TestProcessorNode { EnableRoundsHandler: enableRoundsHandler, EnableEpochsHandler: enableEpochsHandler, EpochChangeGracePeriodHandler: TestEpochChangeGracePeriod, + ProcessConfigsHandler: TestProcessConfigsHandler, EpochProvider: &mock.CurrentNetworkEpochProviderStub{}, WasmVMChangeLocker: &sync.RWMutex{}, TransactionLogProcessor: logsProcessor, @@ -646,6 +697,7 @@ func (tpn *TestProcessorNode) Close() { _ = tpn.MainMessenger.Close() _ = tpn.FullArchiveMessenger.Close() _ = tpn.VMContainer.Close() + _ = tpn.ExecutionManager.Close() } func (tpn *TestProcessorNode) initAccountDBsWithPruningStorer() { @@ -656,10 +708,11 @@ func (tpn *TestProcessorNode) initAccountDBsWithPruningStorer() { tpn.TrieContainer = state.NewDataTriesHolder() var stateTrie common.Trie tpn.AccntState, stateTrie = CreateAccountsDBWithEnableEpochsHandler(UserAccount, trieStorageManager, tpn.EnableEpochsHandler) + tpn.AccntStateProposal, _ = CreateAccountsDBWithEnableEpochsHandler(UserAccount, trieStorageManager, tpn.EnableEpochsHandler) tpn.TrieContainer.Put([]byte(dataRetriever.UserAccountsUnit.String()), stateTrie) - var peerTrie common.Trie - tpn.PeerState, peerTrie = CreateAccountsDBWithEnableEpochsHandler(ValidatorAccount, trieStorageManager, tpn.EnableEpochsHandler) + peerState, peerTrie := CreateAccountsDBWithEnableEpochsHandler(ValidatorAccount, trieStorageManager, tpn.EnableEpochsHandler) + tpn.PeerState = &state.PeerAccountsDB{AccountsDB: peerState} tpn.TrieContainer.Put([]byte(dataRetriever.PeerAccountsUnit.String()), peerTrie) tpn.TrieStorageManagers = make(map[string]common.StorageManager) @@ -672,10 +725,11 @@ func (tpn *TestProcessorNode) initAccountDBs(store storage.Storer) { tpn.TrieContainer = state.NewDataTriesHolder() var stateTrie common.Trie tpn.AccntState, stateTrie = CreateAccountsDBWithEnableEpochsHandler(UserAccount, trieStorageManager, tpn.EnableEpochsHandler) + tpn.AccntStateProposal, _ = CreateAccountsDBWithEnableEpochsHandler(UserAccount, trieStorageManager, tpn.EnableEpochsHandler) tpn.TrieContainer.Put([]byte(dataRetriever.UserAccountsUnit.String()), stateTrie) - var peerTrie common.Trie - tpn.PeerState, peerTrie = CreateAccountsDBWithEnableEpochsHandler(ValidatorAccount, trieStorageManager, tpn.EnableEpochsHandler) + peerState, peerTrie := CreateAccountsDBWithEnableEpochsHandler(ValidatorAccount, trieStorageManager, tpn.EnableEpochsHandler) + tpn.PeerState = &state.PeerAccountsDB{AccountsDB: peerState} tpn.TrieContainer.Put([]byte(dataRetriever.PeerAccountsUnit.String()), peerTrie) tpn.TrieStorageManagers = make(map[string]common.StorageManager) @@ -684,7 +738,7 @@ func (tpn *TestProcessorNode) initAccountDBs(store storage.Storer) { } func (tpn *TestProcessorNode) initValidatorStatistics() { - rater, _ := rating.NewBlockSigningRater(tpn.RatingsData) + rater, _ := rating.NewBlockSigningRater(tpn.RatingsData, tpn.EnableEpochsHandler) if check.IfNil(tpn.NodesSetup) { tpn.NodesSetup = &genesisMocks.NodesSetupStub{ @@ -695,20 +749,20 @@ func (tpn *TestProcessorNode) initValidatorStatistics() { } arguments := peer.ArgValidatorStatisticsProcessor{ - PeerAdapter: tpn.PeerState, - PubkeyConv: TestValidatorPubkeyConverter, - NodesCoordinator: tpn.NodesCoordinator, - ShardCoordinator: tpn.ShardCoordinator, - DataPool: tpn.DataPool, - StorageService: tpn.Storage, - Marshalizer: TestMarshalizer, - Rater: rater, - MaxComputableRounds: 1000, - MaxConsecutiveRoundsOfRatingDecrease: 2000, - RewardsHandler: tpn.EconomicsData, - NodesSetup: tpn.NodesSetup, - GenesisNonce: tpn.BlockChain.GetGenesisHeader().GetNonce(), - EnableEpochsHandler: tpn.EnableEpochsHandler, + PeerAdapter: tpn.PeerState, + PubkeyConv: TestValidatorPubkeyConverter, + NodesCoordinator: tpn.NodesCoordinator, + ShardCoordinator: tpn.ShardCoordinator, + DataPool: tpn.DataPool, + StorageService: tpn.Storage, + Marshalizer: TestMarshalizer, + Rater: rater, + MaxComputableRounds: 1000, + RewardsHandler: tpn.EconomicsData, + NodesSetup: tpn.NodesSetup, + GenesisNonce: tpn.BlockChain.GetGenesisHeader().GetNonce(), + EnableEpochsHandler: tpn.EnableEpochsHandler, + ProcessConfigsHandler: tpn.ProcessConfigsHandler, } tpn.ValidatorStatisticsProcessor, _ = peer.NewValidatorStatisticsProcessor(arguments) @@ -735,11 +789,27 @@ func (tpn *TestProcessorNode) initGenesisBlocks(args ArgTestProcessorNode) { tpn.SmartContractParser, tpn.EnableEpochs, ) + + err := OnGenesisExecutedBlock(tpn) + if err != nil { + log.Error("tpn.initGenesisBlocks: OnGenesisExecutedBlock(tpn)", "err", err) + } + return } if args.WithSync { - tpn.GenesisBlocks = CreateSimpleGenesisBlocks(tpn.ShardCoordinator) + rootHash, err := tpn.AccntState.RootHash() + if err != nil { + log.Error("tpn.initGenesisBlocks: AccntState.RootHash()", "err", err) + } + + tpn.GenesisBlocks = CreateSimpleGenesisBlocks(tpn.ShardCoordinator, rootHash) + err = OnGenesisExecutedBlock(tpn) + if err != nil { + log.Error("tpn.initGenesisBlocks: OnGenesisExecutedBlock(tpn)", "err", err) + } + return } @@ -758,7 +828,13 @@ func (tpn *TestProcessorNode) initGenesisBlocks(args ArgTestProcessorNode) { tpn.DataPool, tpn.EconomicsData, tpn.EnableEpochs, + tpn.ChainParametersHandler, ) + + err := OnGenesisExecutedBlock(tpn) + if err != nil { + log.Error("tpn.initGenesisBlocks: OnGenesisExecutedBlock(tpn)", "err", err) + } } func (tpn *TestProcessorNode) initTestNodeWithArgs(args ArgTestProcessorNode) { @@ -772,9 +848,29 @@ func (tpn *TestProcessorNode) initTestNodeWithArgs(args ArgTestProcessorNode) { args.StatusMetrics = &testscommon.StatusMetricsStub{} } + roundTime := time.Millisecond * time.Duration(5000) + tpn.initChainHandler() tpn.initHeaderValidator() - tpn.initRoundHandler() + tpn.initRoundHandler(roundTime) + + chainParam := config.ChainParametersByEpochConfig{ + RoundDuration: uint64(roundTime.Milliseconds()), + RoundsPerEpoch: 1000, + MinRoundsBetweenEpochs: 1, + } + tpn.ChainParametersHandler = &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(_ uint32) (config.ChainParametersByEpochConfig, error) { + return chainParam, nil + }, + CurrentChainParametersCalled: func() config.ChainParametersByEpochConfig { + return chainParam + }, + AllChainParametersCalled: func() []config.ChainParametersByEpochConfig { + return []config.ChainParametersByEpochConfig{chainParam} + }, + } + tpn.NetworkShardingCollector = mock.NewNetworkShardingCollectorMock() if check.IfNil(tpn.EpochNotifier) { tpn.EpochStartNotifier = notifier.NewEpochStartSubscriptionHandler() @@ -861,6 +957,9 @@ func (tpn *TestProcessorNode) initTestNodeWithArgs(args ArgTestProcessorNode) { tpn.ShardCoordinator, tpn.OwnAccount.PeerSigHandler, tpn.DataPool.Headers(), + tpn.DataPool.Headers(), + tpn.DataPool.Proofs(), + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, tpn.MainInterceptorsContainer, &testscommon.AlarmSchedulerStub{}, testscommon.NewKeysHandlerSingleSignerMock( @@ -972,6 +1071,7 @@ func (tpn *TestProcessorNode) createFullSCQueryService(gasMap map[string]map[str MinStepValue: "10", MinStakeValue: "1", UnBondPeriod: 1, + UnBondPeriodSupernova: 2, UnBondPeriodInEpochs: 1, NumRoundsWithoutBleed: 1, MaximumPercentageToBleed: 1, @@ -1003,6 +1103,7 @@ func (tpn *TestProcessorNode) createFullSCQueryService(gasMap map[string]map[str ChanceComputer: tpn.NodesCoordinator, ShardCoordinator: tpn.ShardCoordinator, EnableEpochsHandler: tpn.EnableEpochsHandler, + EnableRoundsHandler: tpn.EnableRoundsHandler, NodesCoordinator: tpn.NodesCoordinator, } tpn.EpochNotifier.CheckEpoch(&testscommon.HeaderHandlerStub{ @@ -1089,6 +1190,9 @@ func (tpn *TestProcessorNode) InitializeProcessors(gasMap map[string]map[string] tpn.ShardCoordinator, tpn.OwnAccount.PeerSigHandler, tpn.DataPool.Headers(), + tpn.DataPool.Headers(), + tpn.DataPool.Proofs(), + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, tpn.MainInterceptorsContainer, &testscommon.AlarmSchedulerStub{}, testscommon.NewKeysHandlerSingleSignerMock( @@ -1129,11 +1233,9 @@ func (tpn *TestProcessorNode) initChainHandler() { func (tpn *TestProcessorNode) initEconomicsData(economicsConfig *config.EconomicsConfig) { tpn.EnableEpochs.PenalizedTooMuchGasEnableEpoch = 0 pubKeyConv, _ := pubkeyConverter.NewBech32PubkeyConverter(32, "erd") - cfg := &config.Config{EpochStartConfig: config.EpochStartConfig{RoundsPerEpoch: 14400}} - cfg.GeneralSettings.ChainParametersByEpoch = []config.ChainParametersByEpochConfig{{RoundDuration: 6000}} argsNewEconomicsData := economics.ArgsNewEconomicsData{ Economics: economicsConfig, - GeneralConfig: cfg, + ChainParamsHandler: tpn.ChainParametersHandler, EpochNotifier: tpn.EpochNotifier, EnableEpochsHandler: tpn.EnableEpochsHandler, TxVersionChecker: &testscommon.TxVersionCheckerStub{}, @@ -1155,7 +1257,7 @@ func createDefaultEconomicsConfig() *config.EconomicsConfig { MinimumInflation: 0, YearSettings: []*config.YearSetting{ { - Year: 0, + Year: 1, MaximumInflation: 0.01, }, }, @@ -1189,10 +1291,11 @@ func createDefaultEconomicsConfig() *config.EconomicsConfig { MaxGasHigherFactorAccepted: "10", }, }, - MinGasPrice: minGasPrice, - GasPerDataByte: "1", - GasPriceModifier: 0.01, - MaxGasPriceSetGuardian: "2000000000", + MinGasPrice: minGasPrice, + GasPerDataByte: "1", + GasPriceModifier: 0.01, + MaxGasPriceSetGuardian: "2000000000", + BlockCapacityOverestimationFactor: 200, }, } } @@ -1283,10 +1386,9 @@ func CreateRatingsData() *rating.RatingsData { } ratingDataArgs := rating.RatingsDataArg{ - Config: ratingsConfig, - ChainParametersHolder: &chainParameters.ChainParametersHolderMock{}, - EpochNotifier: &epochNotifier.EpochNotifierStub{}, - RoundDurationMilliseconds: 6000, + Config: ratingsConfig, + ChainParametersHolder: &chainParameters.ChainParametersHolderMock{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, } ratingsData, _ := rating.NewRatingsData(ratingDataArgs) @@ -1332,24 +1434,23 @@ func (tpn *TestProcessorNode) initInterceptors(heartbeatPk string) { cryptoComponents.TxKeyGen = tpn.OwnAccount.KeygenTxSign interceptorDataVerifierArgs := interceptorsFactory.InterceptedDataVerifierFactoryArgs{ - CacheSpan: time.Second * 3, - CacheExpiry: time.Second * 10, + InterceptedDataVerifierConfig: config.InterceptedDataVerifierConfig{ + EnableCaching: false, + }, } if tpn.ShardCoordinator.SelfId() == core.MetachainShardId { argsEpochStart := &metachain.ArgsNewMetaEpochStartTrigger{ - GenesisTime: tpn.RoundHandler.TimeStamp(), - Settings: &config.EpochStartConfig{ - MinRoundsBetweenEpochs: 1000, - RoundsPerEpoch: 10000, - }, - Epoch: 0, - EpochStartNotifier: tpn.EpochStartNotifier, - Storage: tpn.Storage, - Marshalizer: TestMarshalizer, - Hasher: TestHasher, - AppStatusHandler: &statusHandlerMock.AppStatusHandlerStub{}, - DataPool: tpn.DataPool, + GenesisTime: tpn.RoundHandler.TimeStamp(), + Settings: &config.EpochStartConfig{}, + Epoch: 0, + EpochStartNotifier: tpn.EpochStartNotifier, + Storage: tpn.Storage, + Marshalizer: TestMarshalizer, + Hasher: TestHasher, + AppStatusHandler: &statusHandlerMock.AppStatusHandlerStub{}, + DataPool: tpn.DataPool, + ChainParametersHandler: tpn.ChainParametersHandler, } epochStartTrigger, _ := metachain.NewEpochStartTrigger(argsEpochStart) tpn.EpochStartTrigger = &metachain.TestTrigger{} @@ -1389,6 +1490,12 @@ func (tpn *TestProcessorNode) initInterceptors(heartbeatPk string) { HardforkTrigger: tpn.HardforkTrigger, NodeOperationMode: tpn.NodeOperationMode, InterceptedDataVerifierFactory: interceptorsFactory.NewInterceptedDataVerifierFactory(interceptorDataVerifierArgs), + Config: config.Config{ + InterceptedDataVerifier: config.InterceptedDataVerifierConfig{ + CacheSpanInSec: 1, + CacheExpiryInSec: 1, + }, + }, } interceptorContainerFactory, _ := interceptorscontainer.NewMetaInterceptorsContainerFactory(metaInterceptorContainerFactoryArgs) @@ -1419,6 +1526,7 @@ func (tpn *TestProcessorNode) initInterceptors(heartbeatPk string) { RoundHandler: tpn.RoundHandler, AppStatusHandler: &statusHandlerMock.AppStatusHandlerStub{}, EnableEpochsHandler: tpn.EnableEpochsHandler, + CommonConfigsHandler: testscommon.GetDefaultCommonConfigsHandler(), } epochStartTrigger, _ := shardchain.NewEpochStartTrigger(argsShardEpochStart) tpn.EpochStartTrigger = &shardchain.TestTrigger{} @@ -1458,6 +1566,12 @@ func (tpn *TestProcessorNode) initInterceptors(heartbeatPk string) { HardforkTrigger: tpn.HardforkTrigger, NodeOperationMode: tpn.NodeOperationMode, InterceptedDataVerifierFactory: interceptorsFactory.NewInterceptedDataVerifierFactory(interceptorDataVerifierArgs), + Config: config.Config{ + InterceptedDataVerifier: config.InterceptedDataVerifierConfig{ + CacheSpanInSec: 1, + CacheExpiryInSec: 1, + }, + }, } interceptorContainerFactory, _ := interceptorscontainer.NewShardInterceptorsContainerFactory(shardIntereptorContainerFactoryArgs) @@ -1484,6 +1598,8 @@ func (tpn *TestProcessorNode) createHardforkTrigger(heartbeatPk string) []byte { SelfPubKeyBytes: pkBytes, ImportStartHandler: &mock.ImportStartHandlerStub{}, RoundHandler: &mock.RoundHandlerMock{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, } var err error @@ -1508,23 +1624,29 @@ func (tpn *TestProcessorNode) initResolvers() { fullArchivePreferredPeersHolder, _ := p2pFactory.NewPeersHolder([]string{}) resolverContainerFactory := resolverscontainer.FactoryArgs{ - ShardCoordinator: tpn.ShardCoordinator, - MainMessenger: tpn.MainMessenger, - FullArchiveMessenger: tpn.FullArchiveMessenger, - Store: tpn.Storage, - Marshalizer: TestMarshalizer, - DataPools: tpn.DataPool, - Uint64ByteSliceConverter: TestUint64Converter, - DataPacker: dataPacker, - TriesContainer: tpn.TrieContainer, - SizeCheckDelta: 100, - InputAntifloodHandler: &mock.NilAntifloodHandler{}, - OutputAntifloodHandler: &mock.NilAntifloodHandler{}, - NumConcurrentResolvingJobs: 10, - NumConcurrentResolvingTrieNodesJobs: 3, - MainPreferredPeersHolder: preferredPeersHolder, - FullArchivePreferredPeersHolder: fullArchivePreferredPeersHolder, - PayloadValidator: payloadValidator, + ShardCoordinator: tpn.ShardCoordinator, + MainMessenger: tpn.MainMessenger, + FullArchiveMessenger: tpn.FullArchiveMessenger, + Store: tpn.Storage, + Marshalizer: TestMarshalizer, + DataPools: tpn.DataPool, + Uint64ByteSliceConverter: TestUint64Converter, + DataPacker: dataPacker, + TriesContainer: tpn.TrieContainer, + SizeCheckDelta: 100, + InputAntifloodHandler: &mock.NilAntifloodHandler{}, + OutputAntifloodHandler: &mock.NilAntifloodHandler{}, + MainPreferredPeersHolder: preferredPeersHolder, + FullArchivePreferredPeersHolder: fullArchivePreferredPeersHolder, + PayloadValidator: payloadValidator, + AntifloodConfigsHandler: &testscommon.AntifloodConfigsHandlerStub{ + GetCurrentConfigCalled: func() config.AntifloodConfigByRound { + return config.AntifloodConfigByRound{ + NumConcurrentResolverJobs: 10, + NumConcurrentResolvingTrieNodesJobs: 3, + } + }, + }, } var err error @@ -1809,43 +1931,97 @@ func (tpn *TestProcessorNode) initInnerProcessors(gasMap map[string]map[string]u ) processedMiniBlocksTracker := processedMb.NewProcessedMiniBlocksTracker() - fact, err := shard.NewPreProcessorsContainerFactory( - tpn.ShardCoordinator, - tpn.Storage, - TestMarshalizer, - TestHasher, - tpn.DataPool, - TestAddressPubkeyConverter, - tpn.AccntState, - tpn.RequestHandler, - tpn.TxProcessor, - tpn.ScProcessor, - tpn.ScProcessor, - tpn.RewardsProcessor, - tpn.EconomicsData, - tpn.GasHandler, - tpn.BlockTracker, - TestBlockSizeComputationHandler, - TestBalanceComputationHandler, - tpn.EnableEpochsHandler, - txTypeHandler, - scheduledTxsExecutionHandler, - processedMiniBlocksTracker, - tpn.TxExecutionOrderHandler, - ) + argsGasConsumption := block.ArgsGasConsumption{ + EconomicsFee: tpn.EconomicsData, + ShardCoordinator: tpn.ShardCoordinator, + GasHandler: tpn.GasHandler, + BlockCapacityOverestimationFactor: 200, + PercentDecreaseLimitsStep: 10, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + } + gasConsumption, err := block.NewGasConsumption(argsGasConsumption) + if err != nil { + panic(err.Error()) + } + + args := shard.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: tpn.ShardCoordinator, + Store: tpn.Storage, + Marshalizer: TestMarshalizer, + Hasher: TestHasher, + DataPool: tpn.DataPool, + PubkeyConverter: TestAddressPubkeyConverter, + Accounts: tpn.AccntState, + AccountsProposal: tpn.AccntStateProposal, + RequestHandler: tpn.RequestHandler, + TxProcessor: tpn.TxProcessor, + ScProcessor: tpn.ScProcessor, + ScResultProcessor: tpn.ScProcessor, + RewardsTxProcessor: tpn.RewardsProcessor, + EconomicsFee: tpn.EconomicsData, + GasHandler: tpn.GasHandler, + BlockTracker: tpn.BlockTracker, + BlockSizeComputation: TestBlockSizeComputationHandler, + BalanceComputation: TestBalanceComputationHandler, + EnableEpochsHandler: tpn.EnableEpochsHandler, + EpochNotifier: tpn.EpochNotifier, + EnableRoundsHandler: tpn.EnableRoundsHandler, + RoundNotifier: tpn.RoundNotifier, + TxTypeHandler: txTypeHandler, + ScheduledTxsExecutionHandler: scheduledTxsExecutionHandler, + ProcessedMiniBlocksTracker: processedMiniBlocksTracker, + TxExecutionOrderHandler: tpn.TxExecutionOrderHandler, + TxCacheSelectionConfig: config.TxCacheSelectionConfig{ + SelectionGasBandwidthIncreasePercent: 400, + SelectionGasBandwidthIncreaseScheduledPercent: 260, + SelectionGasRequested: 10_000_000_000, + SelectionMaxNumTxs: 30000, + SelectionLoopDurationCheckInterval: 10, + }, + TxVersionChecker: versioning.NewTxVersionChecker(tpn.MinTransactionVersion), + } + fact, err := shard.NewPreProcessorsContainerFactory(args) if err != nil { panic(err.Error()) } + tpn.PreProcessorsFactory = fact tpn.PreProcessorsContainer, _ = fact.Create() + tpn.PreProcessorsProposal, _ = fact.Create() + + blockDataRequesterArgs := coordinator.BlockDataRequestArgs{ + RequestHandler: tpn.RequestHandler, + MiniBlockPool: tpn.DataPool.MiniBlocks(), + PreProcessors: tpn.PreProcessorsContainer, + ShardCoordinator: tpn.ShardCoordinator, + EnableEpochsHandler: tpn.EnableEpochsHandler, + } + + blockDataRequester, err := coordinator.NewBlockDataRequester(blockDataRequesterArgs) + if err != nil { + panic(err.Error()) + } + + blockDataRequesterProposalArgs := coordinator.BlockDataRequestArgs{ + RequestHandler: tpn.RequestHandler, + MiniBlockPool: tpn.DataPool.MiniBlocks(), + PreProcessors: tpn.PreProcessorsProposal, + ShardCoordinator: tpn.ShardCoordinator, + EnableEpochsHandler: tpn.EnableEpochsHandler, + } + + blockDataRequesterProposal, err := coordinator.NewBlockDataRequester(blockDataRequesterProposalArgs) + if err != nil { + panic(err.Error()) + } argsTransactionCoordinator := coordinator.ArgTransactionCoordinator{ Hasher: TestHasher, Marshalizer: TestMarshalizer, ShardCoordinator: tpn.ShardCoordinator, Accounts: tpn.AccntState, - MiniBlockPool: tpn.DataPool.MiniBlocks(), - RequestHandler: tpn.RequestHandler, + DataPool: tpn.DataPool, PreProcessors: tpn.PreProcessorsContainer, + PreProcessorsProposal: tpn.PreProcessorsProposal, InterProcessors: tpn.InterimProcContainer, GasHandler: tpn.GasHandler, FeeHandler: tpn.FeeAccumulator, @@ -1855,10 +2031,15 @@ func (tpn *TestProcessorNode) initInnerProcessors(gasMap map[string]map[string]u TxTypeHandler: txTypeHandler, TransactionsLogProcessor: tpn.TransactionLogProcessor, EnableEpochsHandler: tpn.EnableEpochsHandler, + EnableRoundsHandler: tpn.EnableRoundsHandler, ScheduledTxsExecutionHandler: scheduledTxsExecutionHandler, DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, ProcessedMiniBlocksTracker: processedMiniBlocksTracker, TxExecutionOrderHandler: tpn.TxExecutionOrderHandler, + BlockDataRequester: blockDataRequester, + BlockDataRequesterProposal: blockDataRequesterProposal, + GasComputation: gasConsumption, + AOTSelector: aotSelection.NewDisabledAOTSelector(), } tpn.TxCoordinator, _ = coordinator.NewTransactionCoordinator(argsTransactionCoordinator) scheduledTxsExecutionHandler.SetTransactionCoordinator(tpn.TxCoordinator) @@ -1978,6 +2159,7 @@ func (tpn *TestProcessorNode) initMetaInnerProcessors(gasMap map[string]map[stri MinStepValue: "10", MinStakeValue: "1", UnBondPeriod: 1, + UnBondPeriodSupernova: 2, UnBondPeriodInEpochs: 1, NumRoundsWithoutBleed: 1, MaximumPercentageToBleed: 1, @@ -2009,6 +2191,7 @@ func (tpn *TestProcessorNode) initMetaInnerProcessors(gasMap map[string]map[stri ChanceComputer: &mock.RaterMock{}, ShardCoordinator: tpn.ShardCoordinator, EnableEpochsHandler: tpn.EnableEpochsHandler, + EnableRoundsHandler: tpn.EnableRoundsHandler, NodesCoordinator: tpn.NodesCoordinator, } vmFactory, _ := metaProcess.NewVMContainerFactory(argsVMContainerFactory) @@ -2085,38 +2268,91 @@ func (tpn *TestProcessorNode) initMetaInnerProcessors(gasMap map[string]map[stri ) processedMiniBlocksTracker := processedMb.NewProcessedMiniBlocksTracker() - fact, _ := metaProcess.NewPreProcessorsContainerFactory( - tpn.ShardCoordinator, - tpn.Storage, - TestMarshalizer, - TestHasher, - tpn.DataPool, - tpn.AccntState, - tpn.RequestHandler, - tpn.TxProcessor, - tpn.ScProcessor, - tpn.EconomicsData, - tpn.GasHandler, - tpn.BlockTracker, - TestAddressPubkeyConverter, - TestBlockSizeComputationHandler, - TestBalanceComputationHandler, - tpn.EnableEpochsHandler, - txTypeHandler, - scheduledTxsExecutionHandler, - processedMiniBlocksTracker, - tpn.TxExecutionOrderHandler, - ) + argsGasConsumption := block.ArgsGasConsumption{ + EconomicsFee: tpn.EconomicsData, + ShardCoordinator: tpn.ShardCoordinator, + GasHandler: tpn.GasHandler, + BlockCapacityOverestimationFactor: 200, + PercentDecreaseLimitsStep: 10, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + } + gasConsumption, err := block.NewGasConsumption(argsGasConsumption) + if err != nil { + panic(err.Error()) + } + + args := metaProcess.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: tpn.ShardCoordinator, + Store: tpn.Storage, + Marshalizer: TestMarshalizer, + Hasher: TestHasher, + DataPool: tpn.DataPool, + Accounts: tpn.AccntState, + AccountsProposal: tpn.AccntStateProposal, + RequestHandler: tpn.RequestHandler, + TxProcessor: tpn.TxProcessor, + ScResultProcessor: tpn.ScProcessor, + EconomicsFee: tpn.EconomicsData, + GasHandler: tpn.GasHandler, + BlockTracker: tpn.BlockTracker, + PubkeyConverter: TestAddressPubkeyConverter, + BlockSizeComputation: TestBlockSizeComputationHandler, + BalanceComputation: TestBalanceComputationHandler, + EnableEpochsHandler: tpn.EnableEpochsHandler, + EpochNotifier: tpn.EpochNotifier, + EnableRoundsHandler: tpn.EnableRoundsHandler, + RoundNotifier: tpn.RoundNotifier, + TxTypeHandler: txTypeHandler, + ScheduledTxsExecutionHandler: scheduledTxsExecutionHandler, + ProcessedMiniBlocksTracker: processedMiniBlocksTracker, + TxExecutionOrderHandler: tpn.TxExecutionOrderHandler, + TxCacheSelectionConfig: config.TxCacheSelectionConfig{ + SelectionGasBandwidthIncreasePercent: 400, + SelectionGasBandwidthIncreaseScheduledPercent: 260, + SelectionGasRequested: 10_000_000_000, + SelectionMaxNumTxs: 30000, + SelectionLoopDurationCheckInterval: 10, + }, + TxVersionCheckerHandler: versioning.NewTxVersionChecker(tpn.MinTransactionVersion), + } + fact, _ := metaProcess.NewPreProcessorsContainerFactory(args) tpn.PreProcessorsContainer, _ = fact.Create() + tpn.PreProcessorsProposal, _ = fact.Create() + + blockDataRequesterArgs := coordinator.BlockDataRequestArgs{ + RequestHandler: tpn.RequestHandler, + MiniBlockPool: tpn.DataPool.MiniBlocks(), + PreProcessors: tpn.PreProcessorsContainer, + ShardCoordinator: tpn.ShardCoordinator, + EnableEpochsHandler: tpn.EnableEpochsHandler, + } + + blockDataRequester, err := coordinator.NewBlockDataRequester(blockDataRequesterArgs) + if err != nil { + panic(err.Error()) + } + + blockDataRequesterProposalArgs := coordinator.BlockDataRequestArgs{ + RequestHandler: tpn.RequestHandler, + MiniBlockPool: tpn.DataPool.MiniBlocks(), + PreProcessors: tpn.PreProcessorsProposal, + ShardCoordinator: tpn.ShardCoordinator, + EnableEpochsHandler: tpn.EnableEpochsHandler, + } + + blockDataRequesterProposal, err := coordinator.NewBlockDataRequester(blockDataRequesterProposalArgs) + if err != nil { + panic(err.Error()) + } argsTransactionCoordinator := coordinator.ArgTransactionCoordinator{ Hasher: TestHasher, Marshalizer: TestMarshalizer, ShardCoordinator: tpn.ShardCoordinator, Accounts: tpn.AccntState, - MiniBlockPool: tpn.DataPool.MiniBlocks(), - RequestHandler: tpn.RequestHandler, + DataPool: tpn.DataPool, PreProcessors: tpn.PreProcessorsContainer, + PreProcessorsProposal: tpn.PreProcessorsProposal, InterProcessors: tpn.InterimProcContainer, GasHandler: tpn.GasHandler, FeeHandler: tpn.FeeAccumulator, @@ -2126,10 +2362,15 @@ func (tpn *TestProcessorNode) initMetaInnerProcessors(gasMap map[string]map[stri TxTypeHandler: txTypeHandler, TransactionsLogProcessor: tpn.TransactionLogProcessor, EnableEpochsHandler: tpn.EnableEpochsHandler, + EnableRoundsHandler: tpn.EnableRoundsHandler, ScheduledTxsExecutionHandler: scheduledTxsExecutionHandler, DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, ProcessedMiniBlocksTracker: processedMiniBlocksTracker, TxExecutionOrderHandler: tpn.TxExecutionOrderHandler, + BlockDataRequester: blockDataRequester, + BlockDataRequesterProposal: blockDataRequesterProposal, + GasComputation: gasConsumption, + AOTSelector: aotSelection.NewDisabledAOTSelector(), } tpn.TxCoordinator, _ = coordinator.NewTransactionCoordinator(argsTransactionCoordinator) scheduledTxsExecutionHandler.SetTransactionCoordinator(tpn.TxCoordinator) @@ -2141,7 +2382,7 @@ func (tpn *TestProcessorNode) InitDelegationManager() { return } - systemVM, err := tpn.VMContainer.Get(factory.SystemVirtualMachine) + systemVM, err := tpn.VMContainer.Get(procFactory.SystemVirtualMachine) log.LogIfError(err) codeMetaData := &vmcommon.CodeMetadata{ @@ -2207,7 +2448,7 @@ func (tpn *TestProcessorNode) addMockVm(blockchainHook vmcommon.BlockchainHook) mockVM, _ := mock.NewOneSCExecutorMockVM(blockchainHook, TestHasher) mockVM.GasForOperation = OpGasValueForMockVm - _ = tpn.VMContainer.Add(factory.InternalTestingVM, mockVM) + _ = tpn.VMContainer.Add(procFactory.InternalTestingVM, mockVM) } func (tpn *TestProcessorNode) initBlockProcessor() { @@ -2230,6 +2471,7 @@ func (tpn *TestProcessorNode) initBlockProcessor() { coreComponents.RoundHandlerField = tpn.RoundHandler coreComponents.EconomicsDataField = tpn.EconomicsData coreComponents.RoundNotifierField = tpn.RoundNotifier + coreComponents.ChainParametersHandlerField = tpn.ChainParametersHandler dataComponents := GetDefaultDataComponents() dataComponents.Store = tpn.Storage @@ -2242,19 +2484,30 @@ func (tpn *TestProcessorNode) initBlockProcessor() { tpn.BlockBlackListHandler, tpn.BlockTracker, tpn.NodesSetup.GetStartTime(), + tpn.NodesSetup.GetStartTime()*1000, tpn.EnableEpochsHandler, - tpn.DataPool.Proofs()) + tpn.EnableRoundsHandler, + tpn.DataPool.Proofs(), + tpn.ChainParametersHandler, + tpn.ProcessConfigsHandler, + tpn.ShardCoordinator.SelfId(), + ) } else { tpn.ForkDetector, _ = processSync.NewMetaForkDetector( tpn.RoundHandler, tpn.BlockBlackListHandler, tpn.BlockTracker, tpn.NodesSetup.GetStartTime(), + tpn.NodesSetup.GetStartTime()*1000, tpn.EnableEpochsHandler, - tpn.DataPool.Proofs()) + tpn.EnableRoundsHandler, + tpn.DataPool.Proofs(), + tpn.ChainParametersHandler, + tpn.ProcessConfigsHandler, + ) } - bootstrapComponents := getDefaultBootstrapComponents(tpn.ShardCoordinator, tpn.EnableEpochsHandler) + bootstrapComponents := getDefaultBootstrapComponents(tpn.ShardCoordinator, tpn.EnableEpochsHandler, tpn.EnableRoundsHandler) bootstrapComponents.HdrIntegrityVerifier = tpn.HeaderIntegrityVerifier statusComponents := GetDefaultStatusComponents() @@ -2263,6 +2516,108 @@ func (tpn *TestProcessorNode) initBlockProcessor() { AppStatusHandlerField: &statusHandlerMock.AppStatusHandlerStub{}, } + argsHeadersForBlock := headerForBlock.ArgHeadersForBlock{ + DataPool: tpn.DataPool, + RequestHandler: tpn.RequestHandler, + EnableEpochsHandler: tpn.EnableEpochsHandler, + ShardCoordinator: tpn.ShardCoordinator, + BlockTracker: tpn.BlockTracker, + TxCoordinator: tpn.TxCoordinator, + RoundHandler: tpn.RoundHandler, + ExtraDelayForRequestBlockInfoInMilliseconds: 100, + GenesisNonce: tpn.GenesisBlocks[tpn.ShardCoordinator.SelfId()].GetNonce(), + } + hdrsForBlock, err := headerForBlock.NewHeadersForBlock(argsHeadersForBlock) + if err != nil { + log.Error("initBlockProcessor NewHeadersForBlock", "error", err) + } + + blockDataRequesterArgs := coordinator.BlockDataRequestArgs{ + RequestHandler: tpn.RequestHandler, + MiniBlockPool: tpn.DataPool.MiniBlocks(), + PreProcessors: tpn.PreProcessorsProposal, + ShardCoordinator: tpn.ShardCoordinator, + EnableEpochsHandler: tpn.EnableEpochsHandler, + } + // second instance for proposal missing data fetching to avoid interferences + proposalBlockDataRequester, err := coordinator.NewBlockDataRequester(blockDataRequesterArgs) + if err != nil { + log.LogIfError(err) + } + + mbSelectionSession, err := block.NewMiniBlocksSelectionSession( + tpn.ShardCoordinator.SelfId(), + TestMarshalizer, + TestHasher, + ) + if err != nil { + log.LogIfError(err) + } + + executionResultsTracker := executionTrack.NewExecutionResultsTracker() + tpn.BlocksCache = headersCache.NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + + argsExecutionManager := executionManager.ArgsExecutionManager{ + BlocksCache: tpn.BlocksCache, + ExecutionResultsTracker: executionResultsTracker, + BlockChain: tpn.BlockChain, + Headers: tpn.DataPool.Headers(), + PostProcessTransactions: tpn.DataPool.PostProcessTransactions(), + ExecutedMiniBlocks: tpn.DataPool.ExecutedMiniBlocks(), + StorageService: &storageStubs.ChainStorerStub{}, + Marshaller: TestMarshaller, + ShardCoordinator: &testscommon.ShardsCoordinatorMock{}, + } + tpn.ExecutionManager, err = executionManager.NewExecutionManager(argsExecutionManager) + if err != nil { + log.LogIfError(err) + } + err = process.SetBaseExecutionResult(tpn.ExecutionManager, tpn.BlockChain) + if err != nil { + log.LogIfError(err) + } + + execResultsVerifier, err := block.NewExecutionResultsVerifier(tpn.BlockChain, tpn.ExecutionManager) + if err != nil { + log.LogIfError(err) + } + + inclusionEstimator, err := estimator.NewExecutionResultInclusionEstimator( + config.ExecutionResultInclusionEstimatorConfig{ + SafetyMargin: 110, + MaxResultsPerBlock: 20, + }, + tpn.RoundHandler, + &testscommon.ExecResSizeComputationStub{}, + ) + if err != nil { + log.LogIfError(err) + } + + missingDataArgs := missingData.ResolverArgs{ + HeadersPool: tpn.DataPool.Headers(), + ProofsPool: tpn.DataPool.Proofs(), + RequestHandler: tpn.RequestHandler, + BlockDataRequester: proposalBlockDataRequester, + } + missingDataResolver, err := missingData.NewMissingDataResolver(missingDataArgs) + if err != nil { + log.LogIfError(err) + } + + argsGasConsumption := block.ArgsGasConsumption{ + EconomicsFee: tpn.EconomicsData, + ShardCoordinator: tpn.ShardCoordinator, + GasHandler: tpn.GasHandler, + BlockCapacityOverestimationFactor: 200, + PercentDecreaseLimitsStep: 10, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + } + gasConsumption, err := block.NewGasConsumption(argsGasConsumption) + if err != nil { + log.LogIfError(err) + } + argumentsBase := block.ArgBaseProcessor{ CoreComponents: coreComponents, DataComponents: dataComponents, @@ -2271,6 +2626,7 @@ func (tpn *TestProcessorNode) initBlockProcessor() { StatusCoreComponents: statusCoreComponents, Config: config.Config{}, AccountsDB: accountsDb, + AccountsProposal: tpn.AccntStateProposal, ForkDetector: tpn.ForkDetector, NodesCoordinator: tpn.NodesCoordinator, FeeHandler: tpn.FeeAccumulator, @@ -2282,18 +2638,27 @@ func (tpn *TestProcessorNode) initBlockProcessor() { return nil }, }, - BlockTracker: tpn.BlockTracker, - BlockSizeThrottler: TestBlockSizeThrottler, - HistoryRepository: tpn.HistoryRepository, - GasHandler: tpn.GasHandler, - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - ReceiptsRepository: &testscommon.ReceiptsRepositoryStub{}, - OutportDataProvider: &outport.OutportDataProviderStub{}, - BlockProcessingCutoffHandler: &testscommon.BlockProcessingCutoffStub{}, - ManagedPeersHolder: &testscommon.ManagedPeersHolderStub{}, - SentSignaturesTracker: &testscommon.SentSignatureTrackerStub{}, - StateAccessesCollector: &stateMock.StateAccessesCollectorStub{}, + BlockTracker: tpn.BlockTracker, + BlockSizeThrottler: TestBlockSizeThrottler, + HistoryRepository: tpn.HistoryRepository, + GasHandler: tpn.GasHandler, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + ReceiptsRepository: &testscommon.ReceiptsRepositoryStub{}, + OutportDataProvider: &outport.OutportDataProviderStub{}, + BlockProcessingCutoffHandler: &testscommon.BlockProcessingCutoffStub{}, + ManagedPeersHolder: &testscommon.ManagedPeersHolderStub{}, + SentSignaturesTracker: &testscommon.SentSignatureTrackerStub{}, + StateAccessesCollector: &stateMock.StateAccessesCollectorStub{}, + HeadersForBlock: hdrsForBlock, + MiniBlocksSelectionSession: mbSelectionSession, + ExecutionResultsVerifier: execResultsVerifier, + MissingDataResolver: missingDataResolver, + ExecutionResultsInclusionEstimator: inclusionEstimator, + GasComputation: gasConsumption, + ExecutionManager: tpn.ExecutionManager, + TxExecutionOrderHandler: tpn.TxExecutionOrderHandler, + AOTSelector: aotSelection.NewDisabledAOTSelector(), } if check.IfNil(tpn.EpochStartNotifier) { @@ -2303,18 +2668,16 @@ func (tpn *TestProcessorNode) initBlockProcessor() { if tpn.ShardCoordinator.SelfId() == core.MetachainShardId { if check.IfNil(tpn.EpochStartTrigger) { argsEpochStart := &metachain.ArgsNewMetaEpochStartTrigger{ - GenesisTime: argumentsBase.CoreComponents.RoundHandler().TimeStamp(), - Settings: &config.EpochStartConfig{ - MinRoundsBetweenEpochs: 1000, - RoundsPerEpoch: 10000, - }, - Epoch: 0, - EpochStartNotifier: tpn.EpochStartNotifier, - Storage: tpn.Storage, - Marshalizer: TestMarshalizer, - Hasher: TestHasher, - AppStatusHandler: &statusHandlerMock.AppStatusHandlerStub{}, - DataPool: tpn.DataPool, + GenesisTime: argumentsBase.CoreComponents.RoundHandler().TimeStamp(), + Settings: &config.EpochStartConfig{}, + Epoch: 0, + EpochStartNotifier: tpn.EpochStartNotifier, + Storage: tpn.Storage, + Marshalizer: TestMarshalizer, + Hasher: TestHasher, + AppStatusHandler: &statusHandlerMock.AppStatusHandlerStub{}, + DataPool: tpn.DataPool, + ChainParametersHandler: tpn.ChainParametersHandler, } epochStartTrigger, _ := metachain.NewEpochStartTrigger(argsEpochStart) tpn.EpochStartTrigger = &metachain.TestTrigger{} @@ -2361,10 +2724,12 @@ func (tpn *TestProcessorNode) initBlockProcessor() { GenesisTotalSupply: tpn.EconomicsData.GenesisTotalSupply(), EconomicsDataNotified: economicsDataProvider, StakingV2EnableEpoch: tpn.EnableEpochs.StakingV2EnableEpoch, + EnableEpochsHandler: tpn.EnableEpochsHandler, + ChainParamsHandler: tpn.ChainParametersHandler, } epochEconomics, _ := metachain.NewEndOfEpochEconomicsDataCreator(argsEpochEconomics) - systemVM, errGet := tpn.VMContainer.Get(factory.SystemVirtualMachine) + systemVM, errGet := tpn.VMContainer.Get(procFactory.SystemVirtualMachine) if errGet != nil { log.Error("initBlockProcessor tpn.VMContainer.Get", "error", errGet) } @@ -2462,6 +2827,18 @@ func (tpn *TestProcessorNode) initBlockProcessor() { epochStartSystemSCProcessor, _ := metachain.NewSystemSCProcessor(argsEpochSystemSC) tpn.EpochStartSystemSCProcessor = epochStartSystemSCProcessor + shardInfoCreateDataArgs := block.ShardInfoCreateDataArgs{ + EnableEpochsHandler: tpn.EnableEpochsHandler, + HeadersPool: tpn.DataPool.Headers(), + ProofsPool: tpn.DataPool.Proofs(), + PendingMiniBlocksHandler: &mock.PendingMiniBlocksHandlerStub{}, + BlockTracker: argumentsBase.BlockTracker, + Storage: tpn.Storage, + Marshaller: TestMarshalizer, + } + shardInfoCreator, errShardInfoCreate := block.NewShardInfoCreateData(shardInfoCreateDataArgs) + log.LogIfError(errShardInfoCreate) + arguments := block.ArgMetaProcessor{ ArgBaseProcessor: argumentsBase, SCToProtocol: scToProtocolInstance, @@ -2472,6 +2849,7 @@ func (tpn *TestProcessorNode) initBlockProcessor() { EpochValidatorInfoCreator: epochStartValidatorInfo, ValidatorStatisticsProcessor: tpn.ValidatorStatisticsProcessor, EpochSystemSCProcessor: epochStartSystemSCProcessor, + ShardInfoCreator: shardInfoCreator, } tpn.BlockProcessor, err = block.NewMetaProcessor(arguments) @@ -2499,6 +2877,7 @@ func (tpn *TestProcessorNode) initBlockProcessor() { RoundHandler: tpn.RoundHandler, AppStatusHandler: &statusHandlerMock.AppStatusHandlerStub{}, EnableEpochsHandler: tpn.EnableEpochsHandler, + CommonConfigsHandler: testscommon.GetDefaultCommonConfigsHandler(), } epochStartTrigger, _ := shardchain.NewEpochStartTrigger(argsShardEpochStart) tpn.EpochStartTrigger = &shardchain.TestTrigger{} @@ -2520,6 +2899,18 @@ func (tpn *TestProcessorNode) initBlockProcessor() { if err != nil { panic(fmt.Sprintf("error creating blockprocessor: %s", err.Error())) } + + argsHeadersExecutor := asyncExecution.ArgsHeadersExecutor{ + BlocksCache: tpn.BlocksCache, + ExecutionTracker: executionResultsTracker, + BlockProcessor: tpn.BlockProcessor, + BlockChain: tpn.BlockChain, + } + headerExecutor, err := asyncExecution.NewHeadersExecutor(argsHeadersExecutor) + log.LogIfError(err) + + err = tpn.ExecutionManager.SetHeadersExecutor(headerExecutor) + log.LogIfError(err) } func (tpn *TestProcessorNode) setGenesisBlock() { @@ -2571,12 +2962,14 @@ func (tpn *TestProcessorNode) initNode() { hardforkPubKeyBytes, _ := coreComponents.ValidatorPubKeyConverterField.Decode(hardforkPubKey) coreComponents.HardforkTriggerPubKeyField = hardforkPubKeyBytes + coreComponents.ChainParametersHandlerField = tpn.ChainParametersHandler + dataComponents := GetDefaultDataComponents() dataComponents.BlockChain = tpn.BlockChain dataComponents.DataPool = tpn.DataPool dataComponents.Store = tpn.Storage - bootstrapComponents := getDefaultBootstrapComponents(tpn.ShardCoordinator, tpn.EnableEpochsHandler) + bootstrapComponents := getDefaultBootstrapComponents(tpn.ShardCoordinator, tpn.EnableEpochsHandler, tpn.EnableRoundsHandler) processComponents := GetDefaultProcessComponents() processComponents.BlockProcess = tpn.BlockProcessor @@ -2657,6 +3050,8 @@ func (tpn *TestProcessorNode) initNode() { NumResolveFailureThreshold: 1, DebugLineExpiration: 1000, }, + tpn.EpochStartNotifier, + coreComponents.SyncTimer(), ) log.LogIfError(err) } @@ -2822,7 +3217,12 @@ func (tpn *TestProcessorNode) ProposeBlock( } genesisRound := tpn.BlockChain.GetGenesisHeader().GetRound() - err = blockHeader.SetTimeStamp((round - genesisRound) * uint64(tpn.RoundHandler.TimeDuration().Seconds())) + + if tpn.EnableRoundsHandler.IsFlagEnabledInRound(common.SupernovaRoundFlag, round) { + err = blockHeader.SetTimeStamp((round - genesisRound) * uint64(tpn.RoundHandler.TimeDuration().Milliseconds())) + } else { + err = blockHeader.SetTimeStamp((round - genesisRound) * uint64(tpn.RoundHandler.TimeDuration().Seconds())) + } if err != nil { log.Warn("blockHeader.SetTimeStamp", "error", err.Error()) return nil, nil, nil @@ -2896,7 +3296,9 @@ func (tpn *TestProcessorNode) BroadcastBlock(body data.BodyHandler, header data. pkBytes, _ := publicKey.ToByteArray() - miniBlocks, transactions, _ := tpn.BlockProcessor.MarshalizedDataToBroadcast(header, body) + hash, _ := core.CalculateHash(TestMarshalizer, TestHasher, header) + + miniBlocks, transactions, _ := tpn.BlockProcessor.MarshalizedDataToBroadcast(hash, header, body) _ = tpn.BroadcastMessenger.BroadcastMiniBlocks(miniBlocks, pkBytes) _ = tpn.BroadcastMessenger.BroadcastTransactions(transactions, pkBytes) } @@ -3143,8 +3545,8 @@ func (tpn *TestProcessorNode) MiniBlocksPresent(hashes [][]byte) bool { return true } -func (tpn *TestProcessorNode) initRoundHandler() { - tpn.RoundHandler = &mock.RoundHandlerMock{TimeDurationField: 5 * time.Second} +func (tpn *TestProcessorNode) initRoundHandler(roundTime time.Duration) { + tpn.RoundHandler = &mock.RoundHandlerMock{TimeDurationField: roundTime} } func (tpn *TestProcessorNode) initRequestedItemsHandler() { @@ -3165,7 +3567,9 @@ func (tpn *TestProcessorNode) initBlockTracker() { WhitelistHandler: tpn.WhiteListHandler, FeeHandler: tpn.EconomicsData, EnableEpochsHandler: tpn.EnableEpochsHandler, + EnableRoundsHandler: tpn.EnableRoundsHandler, EpochChangeGracePeriodHandler: TestEpochChangeGracePeriod, + ProcessConfigsHandler: TestProcessConfigsHandler, ProofsPool: tpn.DataPool.Proofs(), } @@ -3326,7 +3730,7 @@ func (tpn *TestProcessorNode) createHeartbeatWithHardforkTrigger() { // CreateEnableEpochsConfig creates enable epochs definitions to be used in tests func CreateEnableEpochsConfig() config.EnableEpochs { return config.EnableEpochs{ - SCDeployEnableEpoch: UnreachableEpoch, + SCDeployEnableEpoch: 1, BuiltInFunctionsEnableEpoch: 0, RelayedTransactionsEnableEpoch: UnreachableEpoch, PenalizedTooMuchGasEnableEpoch: UnreachableEpoch, @@ -3339,7 +3743,7 @@ func CreateEnableEpochsConfig() config.EnableEpochs { GasPriceModifierEnableEpoch: UnreachableEpoch, RepairCallbackEnableEpoch: UnreachableEpoch, BlockGasAndFeesReCheckEnableEpoch: UnreachableEpoch, - StakingV2EnableEpoch: UnreachableEpoch, + StakingV2EnableEpoch: 1, StakeEnableEpoch: 0, DoubleKeyProtectionEnableEpoch: 0, ESDTEnableEpoch: UnreachableEpoch, @@ -3381,7 +3785,6 @@ func CreateEnableEpochsConfig() config.EnableEpochs { IsPayableBySCEnableEpoch: UnreachableEpoch, CleanUpInformativeSCRsEnableEpoch: UnreachableEpoch, StorageAPICostOptimizationEnableEpoch: UnreachableEpoch, - TransformToMultiShardCreateEnableEpoch: UnreachableEpoch, ESDTRegisterAndSetAllRolesEnableEpoch: UnreachableEpoch, ScheduledMiniBlocksEnableEpoch: UnreachableEpoch, FailExecutionOnEveryAPIErrorEnableEpoch: UnreachableEpoch, @@ -3393,12 +3796,14 @@ func CreateEnableEpochsConfig() config.EnableEpochs { SCProcessorV2EnableEpoch: UnreachableEpoch, FixRelayedBaseCostEnableEpoch: UnreachableEpoch, FixRelayedMoveBalanceToNonPayableSCEnableEpoch: UnreachableEpoch, - AndromedaEnableEpoch: UnreachableEpoch, } } // GetDefaultCoreComponents - -func GetDefaultCoreComponents(enableEpochsHandler common.EnableEpochsHandler, epochNotifier process.EpochNotifier) *mock.CoreComponentsStub { +func GetDefaultCoreComponents( + enableEpochsHandler common.EnableEpochsHandler, + epochNotifier process.EpochNotifier, +) *mock.CoreComponentsStub { return &mock.CoreComponentsStub{ InternalMarshalizerField: TestMarshalizer, TxMarshalizerField: TestTxSignMarshalizer, @@ -3425,13 +3830,18 @@ func GetDefaultCoreComponents(enableEpochsHandler common.EnableEpochsHandler, ep RaterField: &testscommon.RaterMock{}, GenesisNodesSetupField: &genesisMocks.NodesSetupStub{}, GenesisTimeField: time.Time{}, + SupernovaGenesisTimeField: time.Time{}, EpochNotifierField: epochNotifier, EnableRoundsHandlerField: &testscommon.EnableRoundsHandlerStub{}, TxVersionCheckField: versioning.NewTxVersionChecker(MinTransactionVersion), ProcessStatusHandlerInternal: &testscommon.ProcessStatusHandlerStub{}, EnableEpochsHandlerField: enableEpochsHandler, EpochChangeGracePeriodHandlerField: TestEpochChangeGracePeriod, + ProcessConfigsHandlerField: TestProcessConfigsHandler, FieldsSizeCheckerField: &testscommon.FieldsSizeCheckerMock{}, + ChainParametersHandlerField: &chainParameters.ChainParametersHandlerStub{}, + CommonConfigsHandlerField: testscommon.GetDefaultCommonConfigsHandler(), + AntifloodConfigsHandlerField: &testscommon.AntifloodConfigsHandlerStub{}, } } @@ -3513,16 +3923,18 @@ func GetDefaultCryptoComponents() *mock.CryptoComponentsStub { // GetDefaultStateComponents - func GetDefaultStateComponents() *testFactory.StateComponentsMock { return &testFactory.StateComponentsMock{ - PeersAcc: &stateMock.AccountsStub{}, - Accounts: &stateMock.AccountsStub{}, - AccountsRepo: &stateMock.AccountsRepositoryStub{}, - Tries: &trieMock.TriesHolderStub{}, + PeersAcc: &stateMock.AccountsStub{}, + Accounts: &stateMock.AccountsStub{}, + AccountsProposal: &stateMock.AccountsStub{}, + AccountsRepo: &stateMock.AccountsRepositoryStub{}, + Tries: &trieMock.TriesHolderStub{}, StorageManagers: map[string]common.StorageManager{ "0": &storageManager.StorageManagerStub{}, dataRetriever.UserAccountsUnit.String(): &storageManager.StorageManagerStub{}, dataRetriever.PeerAccountsUnit.String(): &storageManager.StorageManagerStub{}, }, MissingNodesNotifier: &testscommon.MissingTrieNodesNotifierStub{}, + ChangesCollector: &stateMock.StateAccessesCollectorStub{}, } } @@ -3550,11 +3962,15 @@ func GetDefaultStatusComponents() *mock.StatusComponentsStub { } // getDefaultBootstrapComponents - -func getDefaultBootstrapComponents(shardCoordinator sharding.Coordinator, handler common.EnableEpochsHandler) *mainFactoryMocks.BootstrapComponentsStub { +func getDefaultBootstrapComponents(shardCoordinator sharding.Coordinator, handler common.EnableEpochsHandler, roundsHandler common.EnableRoundsHandler) *mainFactoryMocks.BootstrapComponentsStub { var versionedHeaderFactory nodeFactory.VersionedHeaderFactory headerVersionHandler := &testscommon.HeaderVersionHandlerStub{ - GetVersionCalled: func(epoch uint32) string { + GetVersionCalled: func(epoch uint32, round uint64) string { + if handler.IsFlagEnabledInEpoch(common.SupernovaFlag, epoch) && + roundsHandler.IsFlagEnabledInRound(common.SupernovaRoundFlag, round) { + return "3" + } if handler.IsFlagEnabledInEpoch(common.AndromedaFlag, epoch) { return "2" } @@ -3628,11 +4044,17 @@ func createTxsSender(shardCoordinator storage.ShardCoordinator, messenger txsSen log.LogIfError(err) argsTxsSender := txsSender.ArgsTxsSenderWithAccumulator{ - Marshaller: TestMarshalizer, - ShardCoordinator: shardCoordinator, - NetworkMessenger: messenger, - AccumulatorConfig: txAccumulatorConfig, - DataPacker: dataPacker, + Marshaller: TestMarshalizer, + ShardCoordinator: shardCoordinator, + NetworkMessenger: messenger, + DataPacker: dataPacker, + AntifloodConfigHandler: &testscommon.AntifloodConfigsHandlerStub{ + GetCurrentConfigCalled: func() config.AntifloodConfigByRound { + return config.AntifloodConfigByRound{ + TxAccumulator: txAccumulatorConfig, + } + }, + }, } txsSenderHandler, err := txsSender.NewTxsSenderWithAccumulator(argsTxsSender) log.LogIfError(err) @@ -3707,7 +4129,7 @@ func GetDefaultEnableEpochsConfig() *config.EnableEpochs { StakingV4Step2EnableEpoch: UnreachableEpoch, StakingV4Step3EnableEpoch: UnreachableEpoch, AndromedaEnableEpoch: UnreachableEpoch, - RelayedTransactionsV1V2DisableEpoch: UnreachableEpoch, + SupernovaEnableEpoch: UnreachableEpoch, } } @@ -3716,7 +4138,10 @@ func GetDefaultRoundsConfig() config.RoundConfig { return config.RoundConfig{ RoundActivations: map[string]config.ActivationRoundByName{ "DisableAsyncCallV1": { - Round: "18446744073709551615", + Round: "9999999", + }, + "SupernovaEnableRound": { + Round: "9999999", }, }, } diff --git a/integrationTests/testProcessorNodeWithMultisigner.go b/integrationTests/testProcessorNodeWithMultisigner.go index 9b2150d0f8c..3e757ccd7ad 100644 --- a/integrationTests/testProcessorNodeWithMultisigner.go +++ b/integrationTests/testProcessorNodeWithMultisigner.go @@ -31,6 +31,7 @@ import ( "github.com/multiversx/mx-chain-go/storage/storageunit" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/chainParameters" + consensusMocks "github.com/multiversx/mx-chain-go/testscommon/consensus" "github.com/multiversx/mx-chain-go/testscommon/cryptoMocks" "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" @@ -73,7 +74,7 @@ func CreateNodesWithNodesCoordinatorAndTxKeys( shardConsensusGroupSize int, metaConsensusGroupSize int, ) map[uint32][]*TestProcessorNode { - rater, _ := rating.NewBlockSigningRater(CreateRatingsData()) + rater, _ := rating.NewBlockSigningRater(CreateRatingsData(), &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) coordinatorFactory := &IndexHashedNodesCoordinatorWithRaterFactory{ PeerAccountListAndRatingHandler: rater, } @@ -185,6 +186,7 @@ func CreateNodeWithBLSAndTxKeys( MiniBlockPartialExecutionEnableEpoch: UnreachableEpoch, RefactorPeersMiniBlocksEnableEpoch: UnreachableEpoch, AndromedaEnableEpoch: UnreachableEpoch, + SupernovaEnableEpoch: UnreachableEpoch, } return CreateNode( @@ -247,6 +249,7 @@ func CreateNodesWithNodesCoordinatorFactory( StakingV4Step2EnableEpoch: UnreachableEpoch, StakingV4Step3EnableEpoch: UnreachableEpoch, AndromedaEnableEpoch: UnreachableEpoch, + SupernovaEnableEpoch: UnreachableEpoch, } nodesMap := make(map[uint32][]*TestProcessorNode) @@ -476,6 +479,7 @@ func CreateNodesWithNodesCoordinatorAndHeaderSigVerifier( HeadersPool: &mock.HeadersCacherStub{}, ProofsPool: &dataRetriever.ProofsPoolMock{}, StorageService: &genericMocks.ChainStorerMock{}, + PubKeysHandler: &consensusMocks.SigningHandlerStub{}, } headerSig, _ := headerCheck.NewHeaderSigVerifier(&args) @@ -622,6 +626,7 @@ func CreateNodesWithNodesCoordinatorKeygenAndSingleSigner( HeadersPool: &mock.HeadersCacherStub{}, ProofsPool: &dataRetriever.ProofsPoolMock{}, StorageService: &genericMocks.ChainStorerMock{}, + PubKeysHandler: &consensusMocks.SigningHandlerStub{}, } headerSig, _ := headerCheck.NewHeaderSigVerifier(&args) @@ -764,17 +769,20 @@ func DoConsensusSigningOnBlock( blockHeaderHash, _ := core.CalculateHash(TestMarshalizer, TestHasher, blockHeader) - pubKeysBytes := make([][]byte, len(consensusNodes)) + pubKeysBytes := make([]crypto.PublicKey, len(consensusNodes)) sigShares := make([][]byte, len(consensusNodes)) msig := leaderNode.MultiSigner for i := 0; i < len(consensusNodes); i++ { - pubKeysBytes[i] = []byte(pubKeys[i]) + pubKeysBytes[i], err = TestBlsKeyGen.PublicKeyFromByteArray([]byte(pubKeys[i])) + if err != nil { + log.Error("blockHeader.PublicKeyFromByteArray", "error", err) + } sk, _ := consensusNodes[i].NodeKeys.MainKey.Sk.ToByteArray() sigShares[i], _ = msig.CreateSignatureShare(sk, blockHeaderHash) } - sig, _ := msig.AggregateSigs(pubKeysBytes, sigShares) + sig, _ := msig.AggregateSigsV2(pubKeysBytes, sigShares) err = blockHeader.SetSignature(sig) if err != nil { log.Error("blockHeader.SetSignature", "error", err) @@ -852,7 +860,7 @@ func SyncAllShardsWithRoundBlock( time.Sleep(4 * StepDelay) } -func createMultiSigner(cp CryptoParams) (crypto.MultiSigner, error) { +func createMultiSigner(cp CryptoParams) (crypto.MultiSignerV2, error) { blsHasher, _ := blake2b.NewBlake2bWithSize(hashing.BlsHashSize) llsig := &mclmultisig.BlsMultiSigner{Hasher: blsHasher} return multisig.NewBLSMultisig( diff --git a/integrationTests/testProcessorNodeWithTestWebServer.go b/integrationTests/testProcessorNodeWithTestWebServer.go index 792e43a5045..e7624d7bba2 100644 --- a/integrationTests/testProcessorNodeWithTestWebServer.go +++ b/integrationTests/testProcessorNodeWithTestWebServer.go @@ -7,6 +7,8 @@ import ( "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" + "github.com/multiversx/mx-chain-core-go/core/versioning" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-vm-common-go/parsers" datafield "github.com/multiversx/mx-chain-vm-common-go/parsers/dataField" wasmConfig "github.com/multiversx/mx-chain-vm-go/config" @@ -94,11 +96,10 @@ func createFacadeArg(tpn *TestProcessorNode) nodeFacade.ArgNodeFacade { TrieOperationsDeadlineMilliseconds: 1, EndpointsThrottlers: []config.EndpointsThrottlersConfig{}, }, - FacadeConfig: config.FacadeConfig{}, - ApiRoutesConfig: createTestApiConfig(), - AccountsState: tpn.AccntState, - PeerState: tpn.PeerState, - Blockchain: tpn.BlockChain, + FacadeConfig: config.FacadeConfig{}, + ApiRoutesConfig: createTestApiConfig(), + AccountsStateAPI: tpn.AccntState, + Blockchain: tpn.BlockChain, } } @@ -167,8 +168,9 @@ func createFacadeComponents(tpn *TestProcessorNode) nodeFacade.ApiResolver { log.LogIfError(err) argsDataFieldParser := &datafield.ArgsOperationDataFieldParser{ - AddressLength: TestAddressPubkeyConverter.Len(), - Marshalizer: TestMarshalizer, + AddressLength: TestAddressPubkeyConverter.Len(), + Marshalizer: TestMarshalizer, + RelayedTransactionsV1V2DisableEpoch: tpn.EnableEpochsHandler.GetActivationEpoch(common.RelayedTransactionsV1V2DisableFlag), } dataFieldParser, err := datafield.NewOperationDataFieldParser(argsDataFieldParser) log.LogIfError(err) @@ -241,6 +243,8 @@ func createFacadeComponents(tpn *TestProcessorNode) nodeFacade.ApiResolver { DataFieldParser: dataFieldParser, TxMarshaller: &marshallerMock.MarshalizerMock{}, EnableEpochsHandler: tpn.EnableEpochsHandler, + EnableRoundsHandler: tpn.EnableRoundsHandler, + TxVersionChecker: versioning.NewTxVersionChecker(tpn.MinTransactionVersion), } apiTransactionHandler, err := transactionAPI.NewAPITransactionProcessor(argsApiTransactionProc) log.LogIfError(err) @@ -266,6 +270,7 @@ func createFacadeComponents(tpn *TestProcessorNode) nodeFacade.ApiResolver { EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, ProofsPool: tpn.ProofsPool, BlockChain: tpn.BlockChain, + EnableRoundsHandler: tpn.EnableRoundsHandler, } blockAPIHandler, err := blockAPI.CreateAPIBlockProcessor(argsBlockAPI) log.LogIfError(err) diff --git a/integrationTests/testSyncNode.go b/integrationTests/testSyncNode.go index 41d8d5a1eba..45e040e2821 100644 --- a/integrationTests/testSyncNode.go +++ b/integrationTests/testSyncNode.go @@ -6,6 +6,18 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/process/aotSelection" + "github.com/multiversx/mx-chain-go/process/asyncExecution" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionManager" + + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/asyncExecution/cache" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionTrack" + "github.com/multiversx/mx-chain-go/process/block/headerForBlock" + "github.com/multiversx/mx-chain-go/process/coordinator" + "github.com/multiversx/mx-chain-go/process/estimator" + "github.com/multiversx/mx-chain-go/process/missingData" + "github.com/multiversx/mx-chain-go/common/enablers" "github.com/multiversx/mx-chain-go/common/forking" "github.com/multiversx/mx-chain-go/config" @@ -24,6 +36,7 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/factory" "github.com/multiversx/mx-chain-go/testscommon/outport" statusHandlerMock "github.com/multiversx/mx-chain-go/testscommon/statusHandler" + storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" ) func (tpn *TestProcessorNode) addGenesisBlocksIntoStorage() { @@ -58,6 +71,7 @@ func (tpn *TestProcessorNode) initBlockProcessorWithSync() { coreComponents.InternalMarshalizerField = TestMarshalizer coreComponents.HasherField = TestHasher coreComponents.Uint64ByteSliceConverterField = TestUint64Converter + coreComponents.RoundHandlerField = tpn.RoundHandler coreComponents.EpochNotifierField = tpn.EpochNotifier coreComponents.RoundNotifierField = tpn.RoundNotifier @@ -66,7 +80,7 @@ func (tpn *TestProcessorNode) initBlockProcessorWithSync() { dataComponents.DataPool = tpn.DataPool dataComponents.BlockChain = tpn.BlockChain - bootstrapComponents := getDefaultBootstrapComponents(tpn.ShardCoordinator, tpn.EnableEpochsHandler) + bootstrapComponents := getDefaultBootstrapComponents(tpn.ShardCoordinator, tpn.EnableEpochsHandler, tpn.EnableRoundsHandler) bootstrapComponents.HdrIntegrityVerifier = tpn.HeaderIntegrityVerifier statusComponents := GetDefaultStatusComponents() @@ -75,6 +89,108 @@ func (tpn *TestProcessorNode) initBlockProcessorWithSync() { AppStatusHandlerField: &statusHandlerMock.AppStatusHandlerStub{}, } + argsHeadersForBlock := headerForBlock.ArgHeadersForBlock{ + DataPool: tpn.DataPool, + RequestHandler: tpn.RequestHandler, + EnableEpochsHandler: tpn.EnableEpochsHandler, + ShardCoordinator: tpn.ShardCoordinator, + BlockTracker: tpn.BlockTracker, + TxCoordinator: tpn.TxCoordinator, + RoundHandler: tpn.RoundHandler, + ExtraDelayForRequestBlockInfoInMilliseconds: 100, + GenesisNonce: tpn.GenesisBlocks[tpn.ShardCoordinator.SelfId()].GetNonce(), + } + hdrsForBlock, err := headerForBlock.NewHeadersForBlock(argsHeadersForBlock) + if err != nil { + log.Error("initBlockProcessor NewHeadersForBlock", "error", err) + } + + blockDataRequesterArgs := coordinator.BlockDataRequestArgs{ + RequestHandler: tpn.RequestHandler, + MiniBlockPool: tpn.DataPool.MiniBlocks(), + PreProcessors: tpn.PreProcessorsProposal, + ShardCoordinator: tpn.ShardCoordinator, + EnableEpochsHandler: tpn.EnableEpochsHandler, + } + // second instance for proposal missing data fetching to avoid interferences + proposalBlockDataRequester, err := coordinator.NewBlockDataRequester(blockDataRequesterArgs) + if err != nil { + log.LogIfError(err) + } + + mbSelectionSession, err := block.NewMiniBlocksSelectionSession( + tpn.ShardCoordinator.SelfId(), + TestMarshalizer, + TestHasher, + ) + if err != nil { + log.LogIfError(err) + } + + executionResultsTracker := executionTrack.NewExecutionResultsTracker() + tpn.BlocksCache = cache.NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + + argsExecutionManager := executionManager.ArgsExecutionManager{ + BlocksCache: tpn.BlocksCache, + ExecutionResultsTracker: executionResultsTracker, + BlockChain: tpn.BlockChain, + Headers: tpn.DataPool.Headers(), + PostProcessTransactions: tpn.DataPool.PostProcessTransactions(), + ExecutedMiniBlocks: tpn.DataPool.ExecutedMiniBlocks(), + StorageService: &storageStubs.ChainStorerStub{}, + Marshaller: TestMarshaller, + ShardCoordinator: &testscommon.ShardsCoordinatorMock{}, + } + tpn.ExecutionManager, err = executionManager.NewExecutionManager(argsExecutionManager) + if err != nil { + log.LogIfError(err) + } + err = process.SetBaseExecutionResult(tpn.ExecutionManager, tpn.BlockChain) + if err != nil { + log.LogIfError(err) + } + + execResultsVerifier, err := block.NewExecutionResultsVerifier(tpn.BlockChain, tpn.ExecutionManager) + if err != nil { + log.LogIfError(err) + } + + inclusionEstimator, err := estimator.NewExecutionResultInclusionEstimator( + config.ExecutionResultInclusionEstimatorConfig{ + SafetyMargin: 110, + MaxResultsPerBlock: 20, + }, + tpn.RoundHandler, + &testscommon.ExecResSizeComputationStub{}, + ) + if err != nil { + log.LogIfError(err) + } + + missingDataArgs := missingData.ResolverArgs{ + HeadersPool: tpn.DataPool.Headers(), + ProofsPool: tpn.DataPool.Proofs(), + RequestHandler: tpn.RequestHandler, + BlockDataRequester: proposalBlockDataRequester, + } + missingDataResolver, err := missingData.NewMissingDataResolver(missingDataArgs) + if err != nil { + log.LogIfError(err) + } + + argsGasConsumption := block.ArgsGasConsumption{ + EconomicsFee: tpn.EconomicsData, + ShardCoordinator: tpn.ShardCoordinator, + GasHandler: tpn.GasHandler, + BlockCapacityOverestimationFactor: 200, + PercentDecreaseLimitsStep: 10, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + } + gasConsumption, err := block.NewGasConsumption(argsGasConsumption) + if err != nil { + log.LogIfError(err) + } + argumentsBase := block.ArgBaseProcessor{ CoreComponents: coreComponents, DataComponents: dataComponents, @@ -83,6 +199,7 @@ func (tpn *TestProcessorNode) initBlockProcessorWithSync() { StatusCoreComponents: statusCoreComponents, Config: config.Config{}, AccountsDB: accountsDb, + AccountsProposal: tpn.AccntStateProposal, ForkDetector: nil, NodesCoordinator: tpn.NodesCoordinator, FeeHandler: tpn.FeeAccumulator, @@ -95,18 +212,27 @@ func (tpn *TestProcessorNode) initBlockProcessorWithSync() { return nil }, }, - BlockTracker: tpn.BlockTracker, - BlockSizeThrottler: TestBlockSizeThrottler, - HistoryRepository: tpn.HistoryRepository, - GasHandler: tpn.GasHandler, - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - ReceiptsRepository: &testscommon.ReceiptsRepositoryStub{}, - OutportDataProvider: &outport.OutportDataProviderStub{}, - BlockProcessingCutoffHandler: &testscommon.BlockProcessingCutoffStub{}, - ManagedPeersHolder: &testscommon.ManagedPeersHolderStub{}, - SentSignaturesTracker: &testscommon.SentSignatureTrackerStub{}, - StateAccessesCollector: stateDisabled.NewDisabledStateAccessesCollector(), + BlockTracker: tpn.BlockTracker, + BlockSizeThrottler: TestBlockSizeThrottler, + HistoryRepository: tpn.HistoryRepository, + GasHandler: tpn.GasHandler, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + ReceiptsRepository: &testscommon.ReceiptsRepositoryStub{}, + OutportDataProvider: &outport.OutportDataProviderStub{}, + BlockProcessingCutoffHandler: &testscommon.BlockProcessingCutoffStub{}, + ManagedPeersHolder: &testscommon.ManagedPeersHolderStub{}, + SentSignaturesTracker: &testscommon.SentSignatureTrackerStub{}, + StateAccessesCollector: stateDisabled.NewDisabledStateAccessesCollector(), + HeadersForBlock: hdrsForBlock, + MiniBlocksSelectionSession: mbSelectionSession, + ExecutionResultsVerifier: execResultsVerifier, + MissingDataResolver: missingDataResolver, + ExecutionResultsInclusionEstimator: inclusionEstimator, + GasComputation: gasConsumption, + ExecutionManager: tpn.ExecutionManager, + TxExecutionOrderHandler: tpn.TxExecutionOrderHandler, + AOTSelector: aotSelection.NewDisabledAOTSelector(), } if tpn.ShardCoordinator.SelfId() == core.MetachainShardId { @@ -115,10 +241,27 @@ func (tpn *TestProcessorNode) initBlockProcessorWithSync() { tpn.BlockBlackListHandler, tpn.BlockTracker, 0, + 0, tpn.EnableEpochsHandler, - tpn.DataPool.Proofs()) + tpn.EnableRoundsHandler, + tpn.DataPool.Proofs(), + tpn.ChainParametersHandler, + tpn.ProcessConfigsHandler, + ) argumentsBase.ForkDetector = tpn.ForkDetector argumentsBase.TxCoordinator = &mock.TransactionCoordinatorMock{} + shardInfoCreateDataArgs := block.ShardInfoCreateDataArgs{ + EnableEpochsHandler: tpn.EnableEpochsHandler, + HeadersPool: tpn.DataPool.Headers(), + ProofsPool: tpn.DataPool.Proofs(), + PendingMiniBlocksHandler: &mock.PendingMiniBlocksHandlerStub{}, + BlockTracker: argumentsBase.BlockTracker, + Storage: tpn.Storage, + Marshaller: TestMarshalizer, + } + shardInfoCreator, errShardInfoCreator := block.NewShardInfoCreateData(shardInfoCreateDataArgs) + log.LogIfError(errShardInfoCreator) + arguments := block.ArgMetaProcessor{ ArgBaseProcessor: argumentsBase, SCToProtocol: &mock.SCToProtocolStub{}, @@ -133,6 +276,7 @@ func (tpn *TestProcessorNode) initBlockProcessorWithSync() { }, }, EpochSystemSCProcessor: &testscommon.EpochStartSystemSCStub{}, + ShardInfoCreator: shardInfoCreator, } tpn.BlockProcessor, err = block.NewMetaProcessor(arguments) @@ -142,8 +286,14 @@ func (tpn *TestProcessorNode) initBlockProcessorWithSync() { tpn.BlockBlackListHandler, tpn.BlockTracker, 0, + 0, tpn.EnableEpochsHandler, - tpn.DataPool.Proofs()) + tpn.EnableRoundsHandler, + tpn.DataPool.Proofs(), + tpn.ChainParametersHandler, + tpn.ProcessConfigsHandler, + tpn.ShardCoordinator.SelfId(), + ) argumentsBase.ForkDetector = tpn.ForkDetector argumentsBase.BlockChainHook = tpn.BlockchainHook argumentsBase.TxCoordinator = tpn.TxCoordinator @@ -158,6 +308,18 @@ func (tpn *TestProcessorNode) initBlockProcessorWithSync() { if err != nil { panic(fmt.Sprintf("Error creating blockprocessor: %s", err.Error())) } + + argsHeadersExecutor := asyncExecution.ArgsHeadersExecutor{ + BlocksCache: tpn.BlocksCache, + ExecutionTracker: executionResultsTracker, + BlockProcessor: tpn.BlockProcessor, + BlockChain: tpn.BlockChain, + } + headerExecutor, err := asyncExecution.NewHeadersExecutor(argsHeadersExecutor) + log.LogIfError(err) + + err = tpn.ExecutionManager.SetHeadersExecutor(headerExecutor) + log.LogIfError(err) } func (tpn *TestProcessorNode) createShardBootstrapper() (TestBootstrapper, error) { @@ -167,7 +329,7 @@ func (tpn *TestProcessorNode) createShardBootstrapper() (TestBootstrapper, error ChainHandler: tpn.BlockChain, RoundHandler: tpn.RoundHandler, BlockProcessor: tpn.BlockProcessor, - WaitTime: tpn.RoundHandler.TimeDuration(), + ExecutionManager: tpn.ExecutionManager, Hasher: TestHasher, Marshalizer: TestMarshalizer, ForkDetector: tpn.ForkDetector, @@ -189,8 +351,11 @@ func (tpn *TestProcessorNode) createShardBootstrapper() (TestBootstrapper, error HistoryRepo: &dblookupext.HistoryRepositoryStub{}, ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, ProcessWaitTime: tpn.RoundHandler.TimeDuration(), + ProcessWaitTimeSupernova: tpn.RoundHandler.TimeDuration(), RepopulateTokensSupplies: false, EnableEpochsHandler: tpn.EnableEpochsHandler, + EnableRoundsHandler: tpn.EnableRoundsHandler, + ProcessConfigsHandler: tpn.ProcessConfigsHandler, } argsShardBootstrapper := sync.ArgShardBootstrapper{ @@ -214,7 +379,7 @@ func (tpn *TestProcessorNode) createMetaChainBootstrapper() (TestBootstrapper, e ChainHandler: tpn.BlockChain, RoundHandler: tpn.RoundHandler, BlockProcessor: tpn.BlockProcessor, - WaitTime: tpn.RoundHandler.TimeDuration(), + ExecutionManager: tpn.ExecutionManager, Hasher: TestHasher, Marshalizer: TestMarshalizer, ForkDetector: tpn.ForkDetector, @@ -236,8 +401,11 @@ func (tpn *TestProcessorNode) createMetaChainBootstrapper() (TestBootstrapper, e HistoryRepo: &dblookupext.HistoryRepositoryStub{}, ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, ProcessWaitTime: tpn.RoundHandler.TimeDuration(), + ProcessWaitTimeSupernova: tpn.RoundHandler.TimeDuration(), RepopulateTokensSupplies: false, EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandler: tpn.EnableRoundsHandler, + ProcessConfigsHandler: tpn.ProcessConfigsHandler, } argsMetaBootstrapper := sync.ArgMetaBootstrapper{ diff --git a/integrationTests/vm/delegation/delegationScenarios_test.go b/integrationTests/vm/delegation/delegationScenarios_test.go index c058007455d..0221d995e5f 100644 --- a/integrationTests/vm/delegation/delegationScenarios_test.go +++ b/integrationTests/vm/delegation/delegationScenarios_test.go @@ -1445,7 +1445,7 @@ func deployNewSc( tpn.TransactionLogProcessor.Clean() for _, log := range logs { - for _, event := range log.GetLogEvents() { + for _, event := range log.GetLogHandler().GetLogEvents() { if string(event.GetIdentifier()) == "writeLog" && bytes.Equal(event.GetAddress(), vm.DelegationManagerSCAddress) { tokens := strings.Split(string(event.GetData()), "@") address, _ := hex.DecodeString(tokens[2]) diff --git a/integrationTests/vm/esdt/common.go b/integrationTests/vm/esdt/common.go index 5aa943e551c..f4594e4b506 100644 --- a/integrationTests/vm/esdt/common.go +++ b/integrationTests/vm/esdt/common.go @@ -172,6 +172,7 @@ func CreateNodesAndPrepareBalances(numOfShards int) ([]*integrationTests.TestPro ScheduledMiniBlocksEnableEpoch: integrationTests.UnreachableEpoch, MiniBlockPartialExecutionEnableEpoch: integrationTests.UnreachableEpoch, AndromedaEnableEpoch: integrationTests.UnreachableEpoch, + SupernovaEnableEpoch: integrationTests.UnreachableEpoch, } roundsConfig := testscommon.GetDefaultRoundsConfig() return CreateNodesAndPrepareBalancesWithEpochsAndRoundsConfig( diff --git a/integrationTests/vm/esdt/localFuncs/esdtLocalFunsSC_test.go b/integrationTests/vm/esdt/localFuncs/esdtLocalFunsSC_test.go index a33b882a58c..f0831b6fd42 100644 --- a/integrationTests/vm/esdt/localFuncs/esdtLocalFunsSC_test.go +++ b/integrationTests/vm/esdt/localFuncs/esdtLocalFunsSC_test.go @@ -290,6 +290,7 @@ func testESDTWithTransferRoleAndForwarder(t *testing.T, numShards int) { initialVal := big.NewInt(10000000000) integrationTests.MintAllNodes(nodes, initialVal) + integrationTests.SetRootHashOfGenesisBlocks(nodes) round := uint64(0) nonce := uint64(0) diff --git a/integrationTests/vm/esdt/process/esdtProcess_test.go b/integrationTests/vm/esdt/process/esdtProcess_test.go index 0321da07203..791fa68b8c1 100644 --- a/integrationTests/vm/esdt/process/esdtProcess_test.go +++ b/integrationTests/vm/esdt/process/esdtProcess_test.go @@ -45,6 +45,7 @@ func TestESDTIssueAndTransactionsOnMultiShardEnvironment(t *testing.T) { ScheduledMiniBlocksEnableEpoch: integrationTests.UnreachableEpoch, MiniBlockPartialExecutionEnableEpoch: integrationTests.UnreachableEpoch, AndromedaEnableEpoch: integrationTests.UnreachableEpoch, + SupernovaEnableEpoch: integrationTests.UnreachableEpoch, } nodes := integrationTests.CreateNodesWithEnableEpochs( numOfShards, @@ -178,6 +179,7 @@ func TestESDTCallBurnOnANonBurnableToken(t *testing.T) { MiniBlockPartialExecutionEnableEpoch: integrationTests.UnreachableEpoch, MultiClaimOnDelegationEnableEpoch: integrationTests.UnreachableEpoch, AndromedaEnableEpoch: integrationTests.UnreachableEpoch, + SupernovaEnableEpoch: integrationTests.UnreachableEpoch, } nodes := integrationTests.CreateNodesWithEnableEpochs( @@ -1289,6 +1291,7 @@ func TestExecOnDestWithTokenTransferFromScAtoScBWithIntermediaryExecOnDest_NotEn SCProcessorV2EnableEpoch: integrationTests.UnreachableEpoch, FailExecutionOnEveryAPIErrorEnableEpoch: integrationTests.UnreachableEpoch, AndromedaEnableEpoch: integrationTests.UnreachableEpoch, + SupernovaEnableEpoch: integrationTests.UnreachableEpoch, } arwenVersion := config.WasmVMVersionByEpoch{Version: "v1.4"} vmConfig := &config.VirtualMachineConfig{ @@ -1987,6 +1990,7 @@ func TestIssueAndBurnESDT_MaxGasPerBlockExceeded(t *testing.T) { GlobalMintBurnDisableEpoch: integrationTests.UnreachableEpoch, MaxBlockchainHookCountersEnableEpoch: integrationTests.UnreachableEpoch, AndromedaEnableEpoch: integrationTests.UnreachableEpoch, + SupernovaEnableEpoch: integrationTests.UnreachableEpoch, } nodes := integrationTests.CreateNodesWithEnableEpochs( numOfShards, @@ -2372,6 +2376,7 @@ func TestESDTIssueUnderProtectedKeyWillReturnTokensBack(t *testing.T) { ScheduledMiniBlocksEnableEpoch: integrationTests.UnreachableEpoch, MiniBlockPartialExecutionEnableEpoch: integrationTests.UnreachableEpoch, AndromedaEnableEpoch: integrationTests.UnreachableEpoch, + SupernovaEnableEpoch: integrationTests.UnreachableEpoch, } nodes := integrationTests.CreateNodesWithEnableEpochs( @@ -2397,6 +2402,7 @@ func TestESDTIssueUnderProtectedKeyWillReturnTokensBack(t *testing.T) { initialVal := int64(10000000000) integrationTests.MintAllNodes(nodes, big.NewInt(initialVal)) + integrationTests.SetRootHashOfGenesisBlocks(nodes) round := uint64(0) nonce := uint64(0) diff --git a/integrationTests/vm/esdt/roles/esdtRoles_test.go b/integrationTests/vm/esdt/roles/esdtRoles_test.go index 7601633ddf5..a06ea6d01b6 100644 --- a/integrationTests/vm/esdt/roles/esdtRoles_test.go +++ b/integrationTests/vm/esdt/roles/esdtRoles_test.go @@ -390,6 +390,7 @@ func TestESDTLocalBurnFromAnyoneOfThisToken(t *testing.T) { enableEpochs := config.EnableEpochs{ ScheduledMiniBlocksEnableEpoch: integrationTests.UnreachableEpoch, AndromedaEnableEpoch: integrationTests.UnreachableEpoch, + SupernovaEnableEpoch: integrationTests.UnreachableEpoch, } nodes := integrationTests.CreateNodesWithEnableEpochs( numOfShards, @@ -482,6 +483,7 @@ func TestESDTWithTransferRoleCrossShardShouldWork(t *testing.T) { enableEpochs := config.EnableEpochs{ ScheduledMiniBlocksEnableEpoch: integrationTests.UnreachableEpoch, AndromedaEnableEpoch: integrationTests.UnreachableEpoch, + SupernovaEnableEpoch: integrationTests.UnreachableEpoch, } nodes := integrationTests.CreateNodesWithEnableEpochs( numOfShards, diff --git a/integrationTests/vm/staking/baseTestMetaProcessor.go b/integrationTests/vm/staking/baseTestMetaProcessor.go index a1d5a36b82e..a859a74d68e 100644 --- a/integrationTests/vm/staking/baseTestMetaProcessor.go +++ b/integrationTests/vm/staking/baseTestMetaProcessor.go @@ -25,6 +25,7 @@ import ( "github.com/multiversx/mx-chain-go/sharding/nodesCoordinator" "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/stakingcommon" statusHandlerMock "github.com/multiversx/mx-chain-go/testscommon/statusHandler" @@ -65,6 +66,7 @@ type TestMetaProcessor struct { NodesConfig nodesConfig AccountsAdapter state.AccountsAdapter Marshaller marshal.Marshalizer + DataPool dataRetriever.PoolsHolder TxCacher dataRetriever.TransactionCacher TxCoordinator process.TransactionCoordinator SystemVM vmcommon.VMExecutionHandler @@ -147,7 +149,7 @@ func newTestMetaProcessor( waiting, _ := nc.GetAllWaitingValidatorsPublicKeys(0) shuffledOut, _ := nc.GetAllShuffledOutValidatorsPublicKeys(0) - return &TestMetaProcessor{ + tmp := &TestMetaProcessor{ AccountsAdapter: stateComponents.AccountsAdapter(), Marshaller: coreComponents.InternalMarshalizer(), NodesConfig: nodesConfig{ @@ -177,12 +179,23 @@ func newTestMetaProcessor( ValidatorStatistics: validatorStatisticsProcessor, EpochStartTrigger: epochStartTrigger, BlockChainHandler: dataComponents.Blockchain(), + DataPool: dataComponents.Datapool(), TxCacher: dataComponents.Datapool().CurrentBlockTxs(), TxCoordinator: txCoordinator, SystemVM: systemVM, BlockChainHook: blockChainHook, StakingDataProvider: stakingDataProvider, } + + updateRootHash(tmp.BlockChainHandler, tmp.AccountsAdapter) + return tmp +} + +func updateRootHash(blockChainHandler data.ChainHandler, accountsAdapter state.AccountsAdapter) { + rootHash, _ := accountsAdapter.RootHash() + genesisBlock := blockChainHandler.GetGenesisHeader() + _ = genesisBlock.SetRootHash(rootHash) + _ = blockChainHandler.SetGenesisHeader(genesisBlock) } func saveNodesConfig( @@ -220,10 +233,7 @@ func createEpochStartTrigger( storageService dataRetriever.StorageService, ) integrationTests.TestEpochStartTrigger { argsEpochStart := &metachain.ArgsNewMetaEpochStartTrigger{ - Settings: &config.EpochStartConfig{ - MinRoundsBetweenEpochs: 10, - RoundsPerEpoch: 10, - }, + Settings: &config.EpochStartConfig{}, Epoch: 0, EpochStartNotifier: coreComponents.EpochStartNotifierWithConfirm(), Storage: storageService, @@ -231,6 +241,14 @@ func createEpochStartTrigger( Hasher: coreComponents.Hasher(), AppStatusHandler: &statusHandlerMock.AppStatusHandlerStub{}, DataPool: dataRetrieverMock.NewPoolsHolderMock(), + ChainParametersHandler: &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{ + RoundsPerEpoch: 10, + MinRoundsBetweenEpochs: 10, + }, nil + }, + }, } epochStartTrigger, _ := metachain.NewEpochStartTrigger(argsEpochStart) diff --git a/integrationTests/vm/staking/componentsHolderCreator.go b/integrationTests/vm/staking/componentsHolderCreator.go index 291e5afac31..d46283b8d8a 100644 --- a/integrationTests/vm/staking/componentsHolderCreator.go +++ b/integrationTests/vm/staking/componentsHolderCreator.go @@ -77,11 +77,12 @@ func createCoreComponents() factory.CoreComponentsHolder { enableEpochsHandler, _ := enablers.NewEnableEpochsHandler(configEnableEpochs, epochNotifier) gracePeriod, _ := graceperiod.NewEpochChangeGracePeriod([]config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}) + statusMetrics, _ := statusHandler.NewStatusMetrics(enableEpochsHandler, &testscommon.EnableRoundsHandlerStub{}) return &integrationMocks.CoreComponentsStub{ InternalMarshalizerField: &marshal.GogoProtoMarshalizer{}, HasherField: sha256.NewSha256(), Uint64ByteSliceConverterField: uint64ByteSlice.NewBigEndianConverter(), - StatusHandlerField: statusHandler.NewStatusMetrics(), + StatusHandlerField: statusMetrics, RoundHandlerField: &mock.RoundHandlerMock{RoundTimeDuration: time.Second}, EpochStartNotifierWithConfirmField: notifier.NewEpochStartSubscriptionHandler(), EpochNotifierField: epochNotifier, @@ -95,6 +96,9 @@ func createCoreComponents() factory.CoreComponentsHolder { EnableRoundsHandlerField: &testscommon.EnableRoundsHandlerStub{}, RoundNotifierField: ¬ifierMocks.RoundNotifierStub{}, EpochChangeGracePeriodHandlerField: gracePeriod, + ProcessConfigsHandlerField: testscommon.GetDefaultProcessConfigsHandler(), + CommonConfigsHandlerField: testscommon.GetDefaultCommonConfigsHandler(), + AntifloodConfigsHandlerField: &testscommon.AntifloodConfigsHandlerStub{}, } } @@ -140,7 +144,7 @@ func createBootstrapComponents( ShCoordinator: shardCoordinator, HdrIntegrityVerifier: &mock.HeaderIntegrityVerifierStub{}, VersionedHdrFactory: &testscommon.VersionedHeaderFactoryStub{ - CreateCalled: func(epoch uint32) data.HeaderHandler { + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { return &block.MetaBlock{Epoch: epoch} }, }, @@ -177,8 +181,9 @@ func createStateComponents(coreComponents factory.CoreComponentsHolder) factory. _ = peerAccountsDB.SetSyncer(&mock.AccountsDBSyncerStub{}) return &factoryTests.StateComponentsMock{ - PeersAcc: peerAccountsDB, - Accounts: userAccountsDB, + PeersAcc: peerAccountsDB, + Accounts: userAccountsDB, + AccountsProposal: userAccountsDB, } } diff --git a/integrationTests/vm/staking/metaBlockProcessorCreator.go b/integrationTests/vm/staking/metaBlockProcessorCreator.go index bba0b69bb9e..a276d1e4c4d 100644 --- a/integrationTests/vm/staking/metaBlockProcessorCreator.go +++ b/integrationTests/vm/staking/metaBlockProcessorCreator.go @@ -7,6 +7,20 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/process/aotSelection" + "github.com/multiversx/mx-chain-go/process/asyncExecution" + "github.com/multiversx/mx-chain-go/process/asyncExecution/cache" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionManager" + commonMock "github.com/multiversx/mx-chain-go/testscommon/common" + + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionTrack" + "github.com/multiversx/mx-chain-go/process/block/headerForBlock" + "github.com/multiversx/mx-chain-go/process/coordinator" + "github.com/multiversx/mx-chain-go/process/estimator" + "github.com/multiversx/mx-chain-go/process/factory/containers" + "github.com/multiversx/mx-chain-go/process/missingData" + "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/epochStart" "github.com/multiversx/mx-chain-go/epochStart/metachain" @@ -26,7 +40,7 @@ import ( "github.com/multiversx/mx-chain-go/state/disabled" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/dblookupext" - factory2 "github.com/multiversx/mx-chain-go/testscommon/factory" + testsFactory "github.com/multiversx/mx-chain-go/testscommon/factory" "github.com/multiversx/mx-chain-go/testscommon/integrationtests" "github.com/multiversx/mx-chain-go/testscommon/outport" statusHandlerMock "github.com/multiversx/mx-chain-go/testscommon/statusHandler" @@ -73,40 +87,121 @@ func createMetaBlockProcessor( valInfoCreator := createValidatorInfoCreator(coreComponents, dataComponents, bootstrapComponents.ShardCoordinator()) stakingToPeer := createSCToProtocol(coreComponents, stateComponents, dataComponents.Datapool().CurrentBlockTxs()) + headersForBlock, _ := headerForBlock.NewHeadersForBlock(headerForBlock.ArgHeadersForBlock{ + DataPool: dataComponents.Datapool(), + RequestHandler: &testscommon.RequestHandlerStub{}, + EnableEpochsHandler: coreComponents.EnableEpochsHandler(), + ShardCoordinator: bootstrapComponents.ShardCoordinator(), + BlockTracker: blockTracker, + TxCoordinator: txCoordinator, + RoundHandler: coreComponents.RoundHandler(), + ExtraDelayForRequestBlockInfoInMilliseconds: 100, + GenesisNonce: 0, + }) + + preprocessors := containers.NewPreProcessorsContainer() + blockDataRequesterArgs := coordinator.BlockDataRequestArgs{ + RequestHandler: &testscommon.RequestHandlerStub{}, + MiniBlockPool: dataComponents.Datapool().MiniBlocks(), + PreProcessors: preprocessors, + ShardCoordinator: bootstrapComponents.ShardCoordinator(), + EnableEpochsHandler: coreComponents.EnableEpochsHandler(), + } + // second instance for proposal missing data fetching to avoid interferences + proposalBlockDataRequester, _ := coordinator.NewBlockDataRequester(blockDataRequesterArgs) + + mbSelectionSession, _ := blproc.NewMiniBlocksSelectionSession( + bootstrapComponents.ShardCoordinator().SelfId(), + coreComponents.InternalMarshalizer(), + coreComponents.Hasher(), + ) + + blocksCache := cache.NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + executionResultsTracker := executionTrack.NewExecutionResultsTracker() + execManager, _ := executionManager.NewExecutionManager(executionManager.ArgsExecutionManager{ + BlocksCache: blocksCache, + ExecutionResultsTracker: executionResultsTracker, + BlockChain: dataComponents.Blockchain(), + Headers: dataComponents.Datapool().Headers(), + PostProcessTransactions: dataComponents.Datapool().PostProcessTransactions(), + ExecutedMiniBlocks: dataComponents.Datapool().ExecutedMiniBlocks(), + StorageService: dataComponents.StorageService(), + Marshaller: coreComponents.InternalMarshalizer(), + ShardCoordinator: bootstrapComponents.ShardCoordinator(), + }) + execResultsVerifier, _ := blproc.NewExecutionResultsVerifier(dataComponents.Blockchain(), execManager) + inclusionEstimator, _ := estimator.NewExecutionResultInclusionEstimator( + config.ExecutionResultInclusionEstimatorConfig{ + SafetyMargin: 110, + MaxResultsPerBlock: 20, + }, + coreComponents.RoundHandler(), + &testscommon.ExecResSizeComputationStub{}, + ) + + missingDataArgs := missingData.ResolverArgs{ + HeadersPool: dataComponents.Datapool().Headers(), + ProofsPool: dataComponents.Datapool().Proofs(), + RequestHandler: &testscommon.RequestHandlerStub{}, + BlockDataRequester: proposalBlockDataRequester, + } + missingDataResolver, _ := missingData.NewMissingDataResolver(missingDataArgs) + + shardInfoCreateDataArgs := blproc.ShardInfoCreateDataArgs{ + EnableEpochsHandler: coreComponents.EnableEpochsHandler(), + HeadersPool: dataComponents.Datapool().Headers(), + ProofsPool: dataComponents.Datapool().Proofs(), + PendingMiniBlocksHandler: &mock.PendingMiniBlocksHandlerStub{}, + BlockTracker: blockTracker, + Storage: dataComponents.StorageService(), + Marshaller: coreComponents.InternalMarshalizer(), + } + shardInfoCreator, _ := blproc.NewShardInfoCreateData(shardInfoCreateDataArgs) + args := blproc.ArgMetaProcessor{ ArgBaseProcessor: blproc.ArgBaseProcessor{ CoreComponents: coreComponents, DataComponents: dataComponents, BootstrapComponents: bootstrapComponents, StatusComponents: statusComponents, - StatusCoreComponents: &factory2.StatusCoreComponentsStub{ + StatusCoreComponents: &testsFactory.StatusCoreComponentsStub{ AppStatusHandlerField: &statusHandlerMock.AppStatusHandlerStub{}, }, - AccountsDB: accountsDb, - ForkDetector: &integrationMocks.ForkDetectorStub{}, - NodesCoordinator: nc, - FeeHandler: postprocess.NewFeeAccumulator(), - RequestHandler: &testscommon.RequestHandlerStub{}, - BlockChainHook: blockChainHook, - TxCoordinator: txCoordinator, - EpochStartTrigger: epochStartHandler, - HeaderValidator: headerValidator, - BootStorer: bootStorer, - BlockTracker: blockTracker, - BlockSizeThrottler: &mock.BlockSizeThrottlerStub{}, - HistoryRepository: &dblookupext.HistoryRepositoryStub{}, - VMContainersFactory: metaVMFactory, - VmContainer: vmContainer, - GasHandler: &mock.GasHandlerMock{}, - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - ScheduledMiniBlocksEnableEpoch: 10000, - ProcessedMiniBlocksTracker: processedMb.NewProcessedMiniBlocksTracker(), - OutportDataProvider: &outport.OutportDataProviderStub{}, - ReceiptsRepository: &testscommon.ReceiptsRepositoryStub{}, - ManagedPeersHolder: &testscommon.ManagedPeersHolderStub{}, - BlockProcessingCutoffHandler: &testscommon.BlockProcessingCutoffStub{}, - SentSignaturesTracker: &testscommon.SentSignatureTrackerStub{}, - StateAccessesCollector: disabled.NewDisabledStateAccessesCollector(), + AccountsDB: accountsDb, + AccountsProposal: stateComponents.AccountsAdapterProposal(), + ForkDetector: &integrationMocks.ForkDetectorStub{}, + NodesCoordinator: nc, + FeeHandler: postprocess.NewFeeAccumulator(), + RequestHandler: &testscommon.RequestHandlerStub{}, + BlockChainHook: blockChainHook, + TxCoordinator: txCoordinator, + EpochStartTrigger: epochStartHandler, + HeaderValidator: headerValidator, + BootStorer: bootStorer, + BlockTracker: blockTracker, + BlockSizeThrottler: &mock.BlockSizeThrottlerStub{}, + HistoryRepository: &dblookupext.HistoryRepositoryStub{}, + VMContainersFactory: metaVMFactory, + VmContainer: vmContainer, + GasHandler: &mock.GasHandlerMock{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ScheduledMiniBlocksEnableEpoch: 10000, + ProcessedMiniBlocksTracker: processedMb.NewProcessedMiniBlocksTracker(), + OutportDataProvider: &outport.OutportDataProviderStub{}, + ReceiptsRepository: &testscommon.ReceiptsRepositoryStub{}, + ManagedPeersHolder: &testscommon.ManagedPeersHolderStub{}, + BlockProcessingCutoffHandler: &testscommon.BlockProcessingCutoffStub{}, + SentSignaturesTracker: &testscommon.SentSignatureTrackerStub{}, + StateAccessesCollector: disabled.NewDisabledStateAccessesCollector(), + HeadersForBlock: headersForBlock, + MiniBlocksSelectionSession: mbSelectionSession, + ExecutionResultsVerifier: execResultsVerifier, + MissingDataResolver: missingDataResolver, + ExecutionResultsInclusionEstimator: inclusionEstimator, + GasComputation: &testscommon.GasComputationMock{}, + ExecutionManager: execManager, + TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, + AOTSelector: aotSelection.NewDisabledAOTSelector(), }, SCToProtocol: stakingToPeer, PendingMiniBlocksHandler: &mock.PendingMiniBlocksHandlerStub{}, @@ -120,9 +215,20 @@ func createMetaBlockProcessor( EpochValidatorInfoCreator: valInfoCreator, ValidatorStatisticsProcessor: validatorsInfoCreator, EpochSystemSCProcessor: systemSCProcessor, + ShardInfoCreator: shardInfoCreator, } metaProc, _ := blproc.NewMetaProcessor(args) + + argHeadersExecutor := asyncExecution.ArgsHeadersExecutor{ + BlocksCache: blocksCache, + ExecutionTracker: executionResultsTracker, + BlockProcessor: metaProc, + BlockChain: dataComponents.Blockchain(), + } + headersExecutor, _ := asyncExecution.NewHeadersExecutor(argHeadersExecutor) + _ = execManager.SetHeadersExecutor(headersExecutor) + return metaProc } diff --git a/integrationTests/vm/staking/stakingV4_test.go b/integrationTests/vm/staking/stakingV4_test.go index 077c87c407b..ee522180438 100644 --- a/integrationTests/vm/staking/stakingV4_test.go +++ b/integrationTests/vm/staking/stakingV4_test.go @@ -6,8 +6,10 @@ import ( "testing" "github.com/multiversx/mx-chain-core-go/core" + chainData "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/testscommon/stakingcommon" "github.com/multiversx/mx-chain-go/vm" @@ -111,7 +113,15 @@ func getAllPubKeysFromConfig(nodesCfg nodesConfig) [][]byte { return allPubKeys } -func unStake(t *testing.T, owner []byte, accountsDB state.AccountsAdapter, marshaller marshal.Marshalizer, stake *big.Int) { +func unStake( + t *testing.T, + owner []byte, + accountsDB state.AccountsAdapter, + marshaller marshal.Marshalizer, + stake *big.Int, + txPool dataRetriever.ShardedDataCacherNotifier, + blockchain chainData.ChainHandler, +) { validatorSC := stakingcommon.LoadUserAccount(accountsDB, vm.ValidatorSCAddress) ownerStoredData, _, err := validatorSC.RetrieveValue(owner) require.Nil(t, err) @@ -129,6 +139,37 @@ func unStake(t *testing.T, owner []byte, accountsDB state.AccountsAdapter, marsh require.Nil(t, err) _, err = accountsDB.Commit() require.Nil(t, err) + + updateRootHashAndCallOnExecutedBlock(t, accountsDB, txPool, blockchain) +} + +func updateRootHashAndCallOnExecutedBlock( + t *testing.T, + accountsDB state.AccountsAdapter, + txPool dataRetriever.ShardedDataCacherNotifier, + blockchain chainData.ChainHandler, +) { + newRootHash, err := accountsDB.RootHash() + require.Nil(t, err) + + block := blockchain.GetCurrentBlockHeader() + if block == nil { + block = blockchain.GetGenesisHeader() + err = block.SetRootHash(newRootHash) + require.Nil(t, err) + + err = blockchain.SetGenesisHeader(block) + require.Nil(t, err) + } else { + err = block.SetRootHash(newRootHash) + require.Nil(t, err) + + err = blockchain.SetCurrentBlockHeaderAndRootHash(block, newRootHash) + require.Nil(t, err) + } + + err = txPool.OnExecutedBlock(block, newRootHash) + require.Nil(t, err) } type configNum struct { @@ -477,7 +518,15 @@ func TestStakingV4_UnStakeNodesWithNotEnoughFunds(t *testing.T) { requireSliceContainsNumOfElements(t, getAllPubKeys(currNodesConfig.leaving), getAllPubKeys(owner2Stats.WaitingBlsKeys), 1) // Owner1 will unStake some EGLD => at the end of next epoch, he should not be able to reStake all the nodes - unStake(t, []byte(owner1), node.AccountsAdapter, node.Marshaller, big.NewInt(0.1*nodePrice)) + unStake( + t, + []byte(owner1), + node.AccountsAdapter, + node.Marshaller, + big.NewInt(0.1*nodePrice), + node.DataPool.Transactions(), + node.BlockChainHandler, + ) // 3. ReStake the nodes that were in the queue queue = remove(queue, owner1StakingQueue[0]) @@ -509,7 +558,7 @@ func TestStakingV4_UnStakeNodesWithNotEnoughFunds(t *testing.T) { // Owner3 will unStake EGLD => he will have negative top-up at the selection time => one of his nodes will be unStaked. // His other node should not have been selected => remains in auction. // Meanwhile, owner4 had never unStaked EGLD => his node from auction list will be distributed to waiting - unStake(t, []byte(owner3), node.AccountsAdapter, node.Marshaller, big.NewInt(2*nodePrice)) + unStake(t, []byte(owner3), node.AccountsAdapter, node.Marshaller, big.NewInt(2*nodePrice), node.DataPool.Transactions(), node.BlockChainHandler) // 5. Check config in epoch = staking v4 step3 node.Process(t, 5) diff --git a/integrationTests/vm/staking/systemSCCreator.go b/integrationTests/vm/staking/systemSCCreator.go index 32d42fc550e..44deff0873b 100644 --- a/integrationTests/vm/staking/systemSCCreator.go +++ b/integrationTests/vm/staking/systemSCCreator.go @@ -115,19 +115,19 @@ func createValidatorStatisticsProcessor( peerAccounts state.AccountsAdapter, ) process.ValidatorStatisticsProcessor { argsValidatorsProcessor := peer.ArgValidatorStatisticsProcessor{ - Marshalizer: coreComponents.InternalMarshalizer(), - NodesCoordinator: nc, - ShardCoordinator: shardCoordinator, - DataPool: dataComponents.Datapool(), - StorageService: dataComponents.StorageService(), - PubkeyConv: coreComponents.AddressPubKeyConverter(), - PeerAdapter: peerAccounts, - Rater: coreComponents.Rater(), - RewardsHandler: &epochStartMock.RewardsHandlerStub{}, - NodesSetup: &genesisMocks.NodesSetupStub{}, - MaxComputableRounds: 1, - MaxConsecutiveRoundsOfRatingDecrease: 2000, - EnableEpochsHandler: coreComponents.EnableEpochsHandler(), + Marshalizer: coreComponents.InternalMarshalizer(), + NodesCoordinator: nc, + ShardCoordinator: shardCoordinator, + DataPool: dataComponents.Datapool(), + StorageService: dataComponents.StorageService(), + PubkeyConv: coreComponents.AddressPubKeyConverter(), + PeerAdapter: peerAccounts, + Rater: coreComponents.Rater(), + RewardsHandler: &epochStartMock.RewardsHandlerStub{}, + NodesSetup: &genesisMocks.NodesSetupStub{}, + MaxComputableRounds: 1, + EnableEpochsHandler: coreComponents.EnableEpochsHandler(), + ProcessConfigsHandler: coreComponents.ProcessConfigsHandler(), } validatorStatisticsProcessor, _ := peer.NewValidatorStatisticsProcessor(argsValidatorsProcessor) return validatorStatisticsProcessor @@ -227,6 +227,7 @@ func createVMContainerFactory( MinStepValue: "10", MinStakeValue: "1", UnBondPeriod: 1, + UnBondPeriodSupernova: 2, NumRoundsWithoutBleed: 1, MaximumPercentageToBleed: 1, BleedPercentagePerRound: 1, @@ -255,6 +256,7 @@ func createVMContainerFactory( ValidatorAccountsDB: stateComponents.PeerAccounts(), ChanceComputer: coreComponents.Rater(), EnableEpochsHandler: coreComponents.EnableEpochsHandler(), + EnableRoundsHandler: coreComponents.EnableRoundsHandler(), ShardCoordinator: shardCoordinator, NodesCoordinator: nc, UserAccountsDB: stateComponents.AccountsAdapter(), diff --git a/integrationTests/vm/systemVM/stakingSC_test.go b/integrationTests/vm/systemVM/stakingSC_test.go index 453e6a62264..a0ebf563a4c 100644 --- a/integrationTests/vm/systemVM/stakingSC_test.go +++ b/integrationTests/vm/systemVM/stakingSC_test.go @@ -39,6 +39,7 @@ func TestStakingUnstakingAndUnbondingOnMultiShardEnvironment(t *testing.T) { StakingV4Step2EnableEpoch: integrationTests.UnreachableEpoch, StakingV4Step3EnableEpoch: integrationTests.UnreachableEpoch, AndromedaEnableEpoch: integrationTests.UnreachableEpoch, + SupernovaEnableEpoch: integrationTests.UnreachableEpoch, } nodes := integrationTests.CreateNodesWithEnableEpochs( diff --git a/integrationTests/vm/testInitializer.go b/integrationTests/vm/testInitializer.go index 8e8944c6d6d..c33e8324d74 100644 --- a/integrationTests/vm/testInitializer.go +++ b/integrationTests/vm/testInitializer.go @@ -18,7 +18,6 @@ import ( "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/scheduled" dataTransaction "github.com/multiversx/mx-chain-core-go/data/transaction" - dataTx "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/common/enablers" @@ -52,8 +51,8 @@ import ( "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/storage" "github.com/multiversx/mx-chain-go/storage/storageunit" - "github.com/multiversx/mx-chain-go/storage/txcache" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" commonMocks "github.com/multiversx/mx-chain-go/testscommon/common" dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/dblookupext" @@ -65,6 +64,7 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" "github.com/multiversx/mx-chain-go/testscommon/txDataBuilder" + "github.com/multiversx/mx-chain-go/txcache" "github.com/multiversx/mx-chain-go/vm/systemSmartContracts/defaults" logger "github.com/multiversx/mx-chain-logger-go" vmcommon "github.com/multiversx/mx-chain-vm-common-go" @@ -318,7 +318,7 @@ func CreateAccount(accnts state.AccountsAdapter, pubKey []byte, nonce uint64, ba } func createEconomicsData(enableEpochsConfig config.EnableEpochs, gasPriceModifier float64) (process.EconomicsDataHandler, error) { - maxGasLimitPerBlock := strconv.FormatUint(math.MaxUint64, 10) + maxGasLimitPerBlock := strconv.FormatUint(math.MaxUint64-1, 10) minGasPrice := strconv.FormatUint(1, 10) minGasLimit := strconv.FormatUint(1, 10) testProtocolSustainabilityAddress := "erd1932eft30w753xyvme8d49qejgkjc09n5e49w4mwdjtm0neld797su0dlxp" @@ -326,11 +326,9 @@ func createEconomicsData(enableEpochsConfig config.EnableEpochs, gasPriceModifie realEpochNotifier := forking.NewGenericEpochNotifier() enableEpochsHandler, _ := enablers.NewEnableEpochsHandler(enableEpochsConfig, realEpochNotifier) - cfg := &config.Config{EpochStartConfig: config.EpochStartConfig{RoundsPerEpoch: 14400}} - cfg.GeneralSettings.ChainParametersByEpoch = []config.ChainParametersByEpochConfig{{RoundDuration: 6000}} argsNewEconomicsData := economics.ArgsNewEconomicsData{ - TxVersionChecker: versioning.NewTxVersionChecker(minTransactionVersion), - GeneralConfig: cfg, + TxVersionChecker: versioning.NewTxVersionChecker(minTransactionVersion), + ChainParamsHandler: &chainParameters.ChainParametersHolderMock{}, Economics: &config.EconomicsConfig{ GlobalSettings: config.GlobalSettings{ GenesisTotalSupply: "2000000000000000000000", @@ -727,6 +725,7 @@ func CreateVMAndBlockchainHookMeta( ChanceComputer: &shardingMocks.NodesCoordinatorMock{}, ShardCoordinator: mock.NewMultiShardsCoordinatorMock(1), EnableEpochsHandler: enableEpochsHandler, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, NodesCoordinator: &shardingMocks.NodesCoordinatorMock{}, } vmFactory, err := metachain.NewVMContainerFactory(argVMContainer) @@ -776,6 +775,7 @@ func createSystemSCConfig() *config.SystemSmartContractsConfig { UnJailValue: "2500000000000000000", MinStepValue: "100000000000000000000", UnBondPeriod: 250, + UnBondPeriodSupernova: 2500, UnBondPeriodInEpochs: 1, NumRoundsWithoutBleed: 100, MaximumPercentageToBleed: 0.5, @@ -995,8 +995,9 @@ func CreateTxProcessorWithOneSCExecutorWithVMs( }) dataFieldParser, err := datafield.NewOperationDataFieldParser(&datafield.ArgsOperationDataFieldParser{ - AddressLength: pubkeyConv.Len(), - Marshalizer: integrationtests.TestMarshalizer, + AddressLength: pubkeyConv.Len(), + Marshalizer: integrationtests.TestMarshalizer, + RelayedTransactionsV1V2DisableEpoch: enableEpochsHandler.GetActivationEpoch(common.RelayedTransactionsV1V2DisableFlag), }) if err != nil { return nil, err @@ -1771,7 +1772,7 @@ func GetVmOutput( } // ComputeGasLimit - -func ComputeGasLimit(gasSchedule map[string]map[string]uint64, testContext *VMTestContext, tx *dataTx.Transaction) uint64 { +func ComputeGasLimit(gasSchedule map[string]map[string]uint64, testContext *VMTestContext, tx *dataTransaction.Transaction) uint64 { vmConfig := createDefaultVMConfig() gasScheduleNotifier := mock.NewGasScheduleNotifierMock(gasSchedule) vmContainer, blockChainHook, _ := CreateVMAndBlockchainHookAndDataPool( diff --git a/integrationTests/vm/txsFee/asyncCall_test.go b/integrationTests/vm/txsFee/asyncCall_test.go index 88057f564a7..1e29d1d20b7 100644 --- a/integrationTests/vm/txsFee/asyncCall_test.go +++ b/integrationTests/vm/txsFee/asyncCall_test.go @@ -139,7 +139,7 @@ func TestMinterContractWithAsyncCalls(t *testing.T) { require.Nil(t, err) logs := testContext.TxsLogsProcessor.GetAllCurrentLogs() - event := logs[4].GetLogEvents()[1] + event := logs[4].GetLogHandler().GetLogEvents()[1] require.Equal(t, "internalVMErrors", string(event.GetIdentifier())) require.Contains(t, string(event.GetData()), process.ErrMaxCallsReached.Error()) } diff --git a/integrationTests/vm/txsFee/guardAccount_test.go b/integrationTests/vm/txsFee/guardAccount_test.go index 2aae4ac1448..28e92f38897 100644 --- a/integrationTests/vm/txsFee/guardAccount_test.go +++ b/integrationTests/vm/txsFee/guardAccount_test.go @@ -402,7 +402,7 @@ func TestGuardAccount_ShouldSetGuardianOnANotProtectedAccount(t *testing.T) { allLogs := testContext.TxsLogsProcessor.GetAllCurrentLogs() require.NotNil(t, allLogs) - event := allLogs[0].LogHandler.GetLogEvents()[0] + event := allLogs[0].GetLogHandler().GetLogEvents()[0] require.Equal(t, &transaction.Event{ Address: alice, Identifier: []byte(core.BuiltInFunctionSetGuardian), @@ -434,7 +434,7 @@ func TestGuardAccount_ShouldSetGuardianOnANotProtectedAccount(t *testing.T) { allLogs = testContext.TxsLogsProcessor.GetAllCurrentLogs() require.NotNil(t, allLogs) - event = allLogs[0].LogHandler.GetLogEvents()[0] + event = allLogs[0].GetLogHandler().GetLogEvents()[0] require.Equal(t, &transaction.Event{ Address: alice, Identifier: []byte(core.SignalErrorOperation), @@ -463,7 +463,7 @@ func TestGuardAccount_ShouldSetGuardianOnANotProtectedAccount(t *testing.T) { allLogs = testContext.TxsLogsProcessor.GetAllCurrentLogs() require.NotNil(t, allLogs) - event = allLogs[0].LogHandler.GetLogEvents()[0] + event = allLogs[0].GetLogHandler().GetLogEvents()[0] require.Equal(t, &transaction.Event{ Address: alice, Identifier: []byte(core.BuiltInFunctionGuardAccount), diff --git a/integrationTests/vm/txsFee/multiShard/asyncCallWithChangeOwner_test.go b/integrationTests/vm/txsFee/multiShard/asyncCallWithChangeOwner_test.go index 573370bab26..8d88b94edb5 100644 --- a/integrationTests/vm/txsFee/multiShard/asyncCallWithChangeOwner_test.go +++ b/integrationTests/vm/txsFee/multiShard/asyncCallWithChangeOwner_test.go @@ -92,5 +92,5 @@ func TestDoChangeOwnerCrossShardFromAContract(t *testing.T) { logs = testContextSecondContract.TxsLogsProcessor.GetAllCurrentLogs() require.NotNil(t, logs) - require.Equal(t, core.BuiltInFunctionChangeOwnerAddress, string(logs[0].GetLogEvents()[0].GetIdentifier())) + require.Equal(t, core.BuiltInFunctionChangeOwnerAddress, string(logs[0].GetLogHandler().GetLogEvents()[0].GetIdentifier())) } diff --git a/integrationTests/vm/txsFee/multiShard/asyncESDT_test.go b/integrationTests/vm/txsFee/multiShard/asyncESDT_test.go index 5ea878f2b26..bdd51266c9a 100644 --- a/integrationTests/vm/txsFee/multiShard/asyncESDT_test.go +++ b/integrationTests/vm/txsFee/multiShard/asyncESDT_test.go @@ -83,7 +83,7 @@ func TestAsyncESDTTransferWithSCCallShouldWork(t *testing.T) { require.Nil(t, err) logs := testContextSender.TxsLogsProcessor.GetAllCurrentLogs() - require.Len(t, logs[0].GetLogEvents(), 1) + require.Len(t, logs[0].GetLogHandler().GetLogEvents(), 1) _, err = testContextSender.Accounts.Commit() require.Nil(t, err) diff --git a/integrationTests/vm/txsFee/multiShard/relayedMoveBalance_test.go b/integrationTests/vm/txsFee/multiShard/relayedMoveBalance_test.go index 8e2931ae013..0b8ddc4044f 100644 --- a/integrationTests/vm/txsFee/multiShard/relayedMoveBalance_test.go +++ b/integrationTests/vm/txsFee/multiShard/relayedMoveBalance_test.go @@ -5,12 +5,13 @@ import ( "math/big" "testing" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/integrationTests" "github.com/multiversx/mx-chain-go/integrationTests/vm" "github.com/multiversx/mx-chain-go/integrationTests/vm/txsFee/utils" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" - "github.com/stretchr/testify/require" ) const ( @@ -361,7 +362,7 @@ func testRelayedMoveBalanceRelayerAndInnerTxReceiverShard0SenderShard1(relayedFi // 100000 - rTxFee(163)*gasPrice(10) - innerTxFee(1000*gasPriceModifier(0.1)) = 98270 utils.TestAccount(t, testContextSource.Accounts, relayerAddr, 1, big.NewInt(98270)) - // check inner Tx receiver + // check inner tx receiver innerTxSenderAccount, err := testContextSource.Accounts.GetExistingAccount(sndAddr) require.Nil(t, innerTxSenderAccount) require.NotNil(t, err) @@ -458,7 +459,7 @@ func testMoveBalanceRelayerShard0InnerTxSenderShard1InnerTxReceiverShard2ShouldW // 100000 - rTxFee(164)*gasPrice(10) - innerTxFee(1000*gasPriceModifier(0.1)) = 98270 utils.TestAccount(t, testContextRelayer.Accounts, relayerAddr, 1, big.NewInt(98270)) - // check inner Tx receiver + // check inner tx receiver innerTxSenderAccount, err := testContextRelayer.Accounts.GetExistingAccount(sndAddr) require.Nil(t, innerTxSenderAccount) require.NotNil(t, err) diff --git a/integrationTests/vm/txsFee/multiShard/scCallWithValueTransfer_test.go b/integrationTests/vm/txsFee/multiShard/scCallWithValueTransfer_test.go index c2a7356d1f3..110914c3299 100644 --- a/integrationTests/vm/txsFee/multiShard/scCallWithValueTransfer_test.go +++ b/integrationTests/vm/txsFee/multiShard/scCallWithValueTransfer_test.go @@ -65,6 +65,6 @@ func testDeployContractAndTransferValue(t *testing.T, scProcessorV2EnabledEpoch logs := testContextDst.TxsLogsProcessor.GetAllCurrentLogs() require.Len(t, logs, 1) - generatedLogIdentifier := string(logs[0].LogHandler.GetLogEvents()[0].GetIdentifier()) + generatedLogIdentifier := string(logs[0].GetLogHandler().GetLogEvents()[0].GetIdentifier()) require.Equal(t, generatedLogIdentifier, core.CompletedTxEventIdentifier) } diff --git a/integrationTests/vm/txsFee/scCalls_test.go b/integrationTests/vm/txsFee/scCalls_test.go index 3038fffbbcd..3ad565668a5 100644 --- a/integrationTests/vm/txsFee/scCalls_test.go +++ b/integrationTests/vm/txsFee/scCalls_test.go @@ -402,7 +402,6 @@ func prepareTestContextForEpoch460(tb testing.TB) (*vm.VMTestContext, []byte) { IsPayableBySCEnableEpoch: unreachableEpoch, CleanUpInformativeSCRsEnableEpoch: unreachableEpoch, StorageAPICostOptimizationEnableEpoch: unreachableEpoch, - TransformToMultiShardCreateEnableEpoch: unreachableEpoch, ESDTRegisterAndSetAllRolesEnableEpoch: unreachableEpoch, DoNotReturnOldBlockInBlockchainHookEnableEpoch: unreachableEpoch, AddFailedRelayedTxToInvalidMBsDisableEpoch: unreachableEpoch, @@ -480,7 +479,7 @@ func TestScCallBuyNFT_OneFailedTxAndOneOkTx(t *testing.T) { logs := testContext.TxsLogsProcessor.GetAllCurrentLogs() assert.Equal(t, 2, len(logs)) - logEvents := logs[1].GetLogEvents() + logEvents := logs[1].GetLogHandler().GetLogEvents() assert.Equal(t, 2, len(logEvents)) topics := logEvents[0].GetTopics() @@ -508,7 +507,7 @@ func TestScCallBuyNFT_OneFailedTxAndOneOkTx(t *testing.T) { logs := testContext.TxsLogsProcessor.GetAllCurrentLogs() assert.Equal(t, 3, len(logs)) - logEvents := logs[1].GetLogEvents() + logEvents := logs[1].GetLogHandler().GetLogEvents() assert.Equal(t, 2, len(logEvents)) topics := logEvents[0].GetTopics() @@ -554,7 +553,7 @@ func TestScCallBuyNFT_TwoOkTxs(t *testing.T) { logs := testContext.TxsLogsProcessor.GetAllCurrentLogs() - logEvents := logs[1].GetLogEvents() + logEvents := logs[1].GetLogHandler().GetLogEvents() assert.Equal(t, 2, len(logEvents)) topics := logEvents[0].GetTopics() @@ -582,7 +581,7 @@ func TestScCallBuyNFT_TwoOkTxs(t *testing.T) { logs := testContext.TxsLogsProcessor.GetAllCurrentLogs() - logEvents := logs[1].GetLogEvents() + logEvents := logs[1].GetLogHandler().GetLogEvents() assert.Equal(t, 2, len(logEvents)) topics := logEvents[0].GetTopics() diff --git a/integrationTests/vm/txsFee/validatorSC_test.go b/integrationTests/vm/txsFee/validatorSC_test.go index b8a479086e5..0ef31e94152 100644 --- a/integrationTests/vm/txsFee/validatorSC_test.go +++ b/integrationTests/vm/txsFee/validatorSC_test.go @@ -101,7 +101,7 @@ func checkReturnLog(t *testing.T, testContextMeta *vm.VMTestContext, subStr stri found := false for _, eventLog := range allLogs { - for _, event := range eventLog.GetLogEvents() { + for _, event := range eventLog.GetLogHandler().GetLogEvents() { if string(event.GetIdentifier()) == identifierStr { require.True(t, strings.Contains(string(event.GetTopics()[1]), subStr)) found = true diff --git a/integrationTests/vm/wasm/developerRewards/developerRewards_test.go b/integrationTests/vm/wasm/developerRewards/developerRewards_test.go index a23493abc9f..c1fab45cfa6 100644 --- a/integrationTests/vm/wasm/developerRewards/developerRewards_test.go +++ b/integrationTests/vm/wasm/developerRewards/developerRewards_test.go @@ -35,7 +35,7 @@ func TestClaimDeveloperRewards(t *testing.T) { ownerBalanceAfter := context.GetAccountBalance(&context.Owner).Uint64() require.Equal(t, ownerBalanceBefore-context.LastConsumedFee+reward, ownerBalanceAfter) - events := context.LastLogs[0].GetLogEvents() + events := context.LastLogs[0].GetLogHandler().GetLogEvents() require.Equal(t, "ClaimDeveloperRewards", string(events[0].GetIdentifier())) require.Equal(t, rewardBig.Bytes(), events[0].GetTopics()[0]) require.Equal(t, context.Owner.Address, events[0].GetTopics()[1]) @@ -70,7 +70,7 @@ func TestClaimDeveloperRewards(t *testing.T) { contractBalanceAfter := context.GetAccount(parentContractAddress).GetBalance().Uint64() require.Equal(t, contractBalanceBefore+reward, contractBalanceAfter) - events := context.LastLogs[0].GetLogEvents() + events := context.LastLogs[0].GetLogHandler().GetLogEvents() require.Equal(t, "ClaimDeveloperRewards", string(events[0].GetIdentifier())) require.Equal(t, rewardBig.Bytes(), events[0].GetTopics()[0]) require.Equal(t, parentContractAddress, events[0].GetTopics()[1]) diff --git a/integrationTests/vm/wasm/transfers/transfers_test.go b/integrationTests/vm/wasm/transfers/transfers_test.go index 63e4b120f02..4ae629eabd3 100644 --- a/integrationTests/vm/wasm/transfers/transfers_test.go +++ b/integrationTests/vm/wasm/transfers/transfers_test.go @@ -36,9 +36,9 @@ func TestTransfers_DuplicatedTransferValueEvents(t *testing.T) { err = context.ExecuteSC(&context.Owner, fmt.Sprintf("forwardAskMoney@%s", hex.EncodeToString(vault))) require.Nil(t, err) require.Len(t, context.LastLogs, 1) - require.Len(t, context.LastLogs[0].GetLogEvents(), 5) + require.Len(t, context.LastLogs[0].GetLogHandler().GetLogEvents(), 5) - events := context.LastLogs[0].GetLogEvents() + events := context.LastLogs[0].GetLogHandler().GetLogEvents() require.Equal(t, "transferValueOnly", string(events[0].GetIdentifier())) require.Equal(t, "AsyncCall", string(events[0].GetData())) diff --git a/integrationTests/vm/wasm/utils.go b/integrationTests/vm/wasm/utils.go index 528779e8a7b..f26bf0a4002 100644 --- a/integrationTests/vm/wasm/utils.go +++ b/integrationTests/vm/wasm/utils.go @@ -45,8 +45,8 @@ import ( processTransaction "github.com/multiversx/mx-chain-go/process/transaction" "github.com/multiversx/mx-chain-go/process/transactionLog" "github.com/multiversx/mx-chain-go/state" - "github.com/multiversx/mx-chain-go/storage/txcache" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/dblookupext" "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" @@ -54,6 +54,7 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/integrationtests" "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" + "github.com/multiversx/mx-chain-go/txcache" "github.com/multiversx/mx-chain-go/vm/systemSmartContracts/defaults" vmcommon "github.com/multiversx/mx-chain-vm-common-go" "github.com/multiversx/mx-chain-vm-common-go/parsers" @@ -92,7 +93,7 @@ type TestContext struct { GasSchedule map[string]map[string]uint64 EpochNotifier process.EpochNotifier - EnableRoundsHandler process.EnableRoundsHandler + EnableRoundsHandler common.EnableRoundsHandler RoundNotifier process.RoundNotifier EnableEpochsHandler common.EnableEpochsHandler UnsignexTxHandler process.TransactionFeeHandler @@ -114,7 +115,7 @@ type TestContext struct { LastTxHash []byte SCRForwarder *mock.IntermediateTransactionHandlerMock LastSCResults []*smartContractResult.SmartContractResult - LastLogs []*data.LogData + LastLogs []data.LogDataHandler } type testParticipant struct { @@ -214,11 +215,9 @@ func (context *TestContext) initFeeHandlers() { minGasPrice := strconv.FormatUint(1, 10) minGasLimit := strconv.FormatUint(1, 10) testProtocolSustainabilityAddress := "erd1932eft30w753xyvme8d49qejgkjc09n5e49w4mwdjtm0neld797su0dlxp" - cfg := &config.Config{EpochStartConfig: config.EpochStartConfig{RoundsPerEpoch: 14400}} - cfg.GeneralSettings.ChainParametersByEpoch = []config.ChainParametersByEpochConfig{{RoundDuration: 6000}} argsNewEconomicsData := economics.ArgsNewEconomicsData{ - GeneralConfig: cfg, + ChainParamsHandler: &chainParameters.ChainParametersHolderMock{}, Economics: &config.EconomicsConfig{ GlobalSettings: config.GlobalSettings{ GenesisTotalSupply: "2000000000000000000000", diff --git a/integrationTests/vm/wasm/wasmvm/asyncMockContracts_test.go b/integrationTests/vm/wasm/wasmvm/asyncMockContracts_test.go index f7a3eece169..846c0ee0517 100644 --- a/integrationTests/vm/wasm/wasmvm/asyncMockContracts_test.go +++ b/integrationTests/vm/wasm/wasmvm/asyncMockContracts_test.go @@ -9,7 +9,6 @@ import ( "github.com/multiversx/mx-chain-vm-common-go/txDataBuilder" "github.com/multiversx/mx-chain-vm-go/mock/contracts" "github.com/multiversx/mx-chain-vm-go/testcommon" - test "github.com/multiversx/mx-chain-vm-go/testcommon" "github.com/stretchr/testify/require" ) @@ -18,7 +17,7 @@ var NewAsyncCallType = []byte{1} func TestMockContract_AsyncLegacy_InShard(t *testing.T) { if testing.Short() { - t.Skip("this is not a short test") + t.Skip("this is not a short testcommon") } testConfig := &testcommon.TestConfig{ GasProvided: 2000, @@ -39,7 +38,7 @@ func TestMockContract_AsyncLegacy_InShard(t *testing.T) { InitializeMockContracts( t, net, - test.CreateMockContract(parentAddress). + testcommon.CreateMockContract(parentAddress). WithConfig(testConfig). WithMethods(contracts.WasteGasParentMock), ) @@ -60,7 +59,7 @@ func TestMockContract_AsyncLegacy_InShard(t *testing.T) { func TestMockContract_AsyncLegacy_CrossShard(t *testing.T) { if testing.Short() { - t.Skip("this is not a short test") + t.Skip("this is not a short testcommon") } testMockContract_CrossShard(t, LegacyAsyncCallType) @@ -68,7 +67,7 @@ func TestMockContract_AsyncLegacy_CrossShard(t *testing.T) { func TestMockContract_NewAsync_CrossShard(t *testing.T) { if testing.Short() { - t.Skip("this is not a short test") + t.Skip("this is not a short testcommon") } testMockContract_CrossShard(t, NewAsyncCallType) @@ -113,11 +112,11 @@ func testMockContract_CrossShard(t *testing.T, asyncCallType []byte) { InitializeMockContracts( t, net, - test.CreateMockContractOnShard(parentAddress, 0). + testcommon.CreateMockContractOnShard(parentAddress, 0). WithBalance(testConfig.ParentBalance). WithConfig(testConfig). WithMethods(contracts.PerformAsyncCallParentMock, contracts.CallBackParentMock), - test.CreateMockContractOnShard(childAddress, 1). + testcommon.CreateMockContractOnShard(childAddress, 1). WithBalance(testConfig.ChildBalance). WithConfig(testConfig). WithMethods(contracts.TransferToThirdPartyAsyncChildMock), @@ -139,41 +138,41 @@ func testMockContract_CrossShard(t *testing.T, asyncCallType []byte) { parentHandler, err := net.NodesSharded[0][0].BlockchainHook.GetUserAccount(parentAddress) require.Nil(t, err) - parentValueA, _, err := parentHandler.AccountDataHandler().RetrieveValue(test.ParentKeyA) + parentValueA, _, err := parentHandler.AccountDataHandler().RetrieveValue(testcommon.ParentKeyA) require.Nil(t, err) - require.Equal(t, test.ParentDataA, parentValueA) + require.Equal(t, testcommon.ParentDataA, parentValueA) - parentValueB, _, err := parentHandler.AccountDataHandler().RetrieveValue(test.ParentKeyB) + parentValueB, _, err := parentHandler.AccountDataHandler().RetrieveValue(testcommon.ParentKeyB) require.Nil(t, err) - require.Equal(t, test.ParentDataB, parentValueB) + require.Equal(t, testcommon.ParentDataB, parentValueB) - callbackValue, _, err := parentHandler.AccountDataHandler().RetrieveValue(test.CallbackKey) + callbackValue, _, err := parentHandler.AccountDataHandler().RetrieveValue(testcommon.CallbackKey) require.Nil(t, err) - require.Equal(t, test.CallbackData, callbackValue) + require.Equal(t, testcommon.CallbackData, callbackValue) - originalCallerParent, _, err := parentHandler.AccountDataHandler().RetrieveValue(test.OriginalCallerParent) + originalCallerParent, _, err := parentHandler.AccountDataHandler().RetrieveValue(testcommon.OriginalCallerParent) require.Nil(t, err) require.Equal(t, ownerOfParent.Address, originalCallerParent) - originalCallerCallback, _, err := parentHandler.AccountDataHandler().RetrieveValue(test.OriginalCallerCallback) + originalCallerCallback, _, err := parentHandler.AccountDataHandler().RetrieveValue(testcommon.OriginalCallerCallback) require.Nil(t, err) require.Equal(t, ownerOfParent.Address, originalCallerCallback) childHandler, err := net.NodesSharded[1][0].BlockchainHook.GetUserAccount(childAddress) require.Nil(t, err) - childValue, _, err := childHandler.AccountDataHandler().RetrieveValue(test.ChildKey) + childValue, _, err := childHandler.AccountDataHandler().RetrieveValue(testcommon.ChildKey) require.Nil(t, err) - require.Equal(t, test.ChildData, childValue) + require.Equal(t, testcommon.ChildData, childValue) - originalCallerChild, _, err := childHandler.AccountDataHandler().RetrieveValue(test.OriginalCallerChild) + originalCallerChild, _, err := childHandler.AccountDataHandler().RetrieveValue(testcommon.OriginalCallerChild) require.Nil(t, err) require.Equal(t, ownerOfParent.Address, originalCallerChild) } func TestMockContract_NewAsync_BackTransfer_CrossShard(t *testing.T) { if testing.Short() { - t.Skip("this is not a short test") + t.Skip("this is not a short testcommon") } numberOfShards := 2 @@ -216,19 +215,19 @@ func TestMockContract_NewAsync_BackTransfer_CrossShard(t *testing.T) { InitializeMockContracts( t, net, - test.CreateMockContractOnShard(parentAddress, 0). + testcommon.CreateMockContractOnShard(parentAddress, 0). WithBalance(testConfig.ParentBalance). WithConfig(testConfig). WithCodeMetadata([]byte{0, 0}). WithMethods(contracts.BackTransfer_ParentCallsChild), - test.CreateMockContractOnShard(childAddress, 0). + testcommon.CreateMockContractOnShard(childAddress, 0). WithBalance(testConfig.ChildBalance). WithConfig(testConfig). WithMethods( contracts.BackTransfer_ChildMakesAsync, contracts.BackTransfer_ChildCallback, ), - test.CreateMockContractOnShard(nephewAddress, 1). + testcommon.CreateMockContractOnShard(nephewAddress, 1). WithBalance(testConfig.ChildBalance). WithConfig(testConfig). WithMethods(contracts.WasteGasChildMock), diff --git a/integrationTests/vm/wasm/wasmvm/executeViaBlockchainhook_test.go b/integrationTests/vm/wasm/wasmvm/executeViaBlockchainhook_test.go index 9d12746bff5..23058af0e5b 100644 --- a/integrationTests/vm/wasm/wasmvm/executeViaBlockchainhook_test.go +++ b/integrationTests/vm/wasm/wasmvm/executeViaBlockchainhook_test.go @@ -10,13 +10,12 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/txDataBuilder" "github.com/multiversx/mx-chain-vm-go/mock/contracts" "github.com/multiversx/mx-chain-vm-go/testcommon" - test "github.com/multiversx/mx-chain-vm-go/testcommon" "github.com/stretchr/testify/require" ) func TestExecuteOnDestCtx_BlockchainHook(t *testing.T) { if testing.Short() { - t.Skip("this is not a short test") + t.Skip("this is not a short testscommon") } net := integrationTests.NewTestNetworkSized(t, 1, 1, 1) @@ -51,12 +50,12 @@ func TestExecuteOnDestCtx_BlockchainHook(t *testing.T) { t, net, node.VMContainer, [][]byte{factory.WasmVirtualMachine, fakeVMType}, - test.CreateMockContract(parentAddress). + testcommon.CreateMockContract(parentAddress). WithVMType(factory.WasmVirtualMachine). WithBalance(testConfig.ParentBalance). WithConfig(testConfig). WithMethods(contracts.ExecOnDestCtxParentMock), - test.CreateMockContract(childAddress). + testcommon.CreateMockContract(childAddress). WithVMType(fakeVMType). WithBalance(testConfig.ChildBalance). WithConfig(testConfig). @@ -80,7 +79,7 @@ func TestExecuteOnDestCtx_BlockchainHook(t *testing.T) { childHandler, err := net.NodesSharded[0][0].BlockchainHook.GetUserAccount(childAddress) require.Nil(t, err) - childValue, _, err := childHandler.AccountDataHandler().RetrieveValue(test.ChildKey) + childValue, _, err := childHandler.AccountDataHandler().RetrieveValue(testcommon.ChildKey) require.Nil(t, err) - require.Equal(t, test.ChildData, childValue) + require.Equal(t, testcommon.ChildData, childValue) } diff --git a/integrationTests/vm/wasm/wasmvm/wasmVM_test.go b/integrationTests/vm/wasm/wasmvm/wasmVM_test.go index 73a677def57..b8b826a20b0 100644 --- a/integrationTests/vm/wasm/wasmvm/wasmVM_test.go +++ b/integrationTests/vm/wasm/wasmvm/wasmVM_test.go @@ -13,6 +13,14 @@ import ( "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-core-go/hashing/sha256" "github.com/multiversx/mx-chain-core-go/marshal" + logger "github.com/multiversx/mx-chain-logger-go" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/multiversx/mx-chain-vm-common-go/builtInFunctions" + "github.com/multiversx/mx-chain-vm-common-go/parsers" + "github.com/multiversx/mx-chain-vm-common-go/txDataBuilder" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/common/holders" "github.com/multiversx/mx-chain-go/config" @@ -31,13 +39,6 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/integrationtests" - logger "github.com/multiversx/mx-chain-logger-go" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" - "github.com/multiversx/mx-chain-vm-common-go/builtInFunctions" - "github.com/multiversx/mx-chain-vm-common-go/parsers" - "github.com/multiversx/mx-chain-vm-common-go/txDataBuilder" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) var log = logger.GetOrCreate("wasmVMtest") @@ -972,7 +973,6 @@ func TestCommunityContract_CrossShard_TxProcessor(t *testing.T) { parentCode := codePath + "/parent_old.wasm" logger.ToggleLoggerName(true) - // logger.SetLogLevel("*:TRACE") // Deploy Funder SC in shard 0 funderOwnerAccount, _ := testContextFunderSC.Accounts.LoadAccount(funderOwner) diff --git a/keysManagement/export_test.go b/keysManagement/export_test.go index 42d1ee00317..7b192013873 100644 --- a/keysManagement/export_test.go +++ b/keysManagement/export_test.go @@ -55,6 +55,11 @@ func (holder *managedPeersHolder) GetPeerInfo(pkBytes []byte) *peerInfo { return holder.getPeerInfo(pkBytes) } +// CalcMaxRoundsOfInactivity - +func (holder *managedPeersHolder) CalcMaxRoundsOfInactivity() int { + return holder.calcMaxRoundsOfInactivity() +} + // ManagedPeersHolder - func (handler *keysHandler) ManagedPeersHolder() common.ManagedPeersHolder { return handler.managedPeersHolder diff --git a/keysManagement/managedPeersHolder.go b/keysManagement/managedPeersHolder.go index 39f80f6bbaf..c412697af4c 100644 --- a/keysManagement/managedPeersHolder.go +++ b/keysManagement/managedPeersHolder.go @@ -12,6 +12,9 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" crypto "github.com/multiversx/mx-chain-crypto-go" + cmn "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/common/configs/dto" + "github.com/multiversx/mx-chain-go/process" logger "github.com/multiversx/mx-chain-logger-go" "github.com/multiversx/mx-chain-go/config" @@ -35,17 +38,18 @@ type managedPeersHolder struct { keyGenerator crypto.KeyGenerator p2pKeyGenerator crypto.KeyGenerator isMainMachine bool - maxRoundsOfInactivity int defaultName string defaultIdentity string p2pKeyConverter p2p.P2PKeyConverter + processConfigsHandler cmn.ProcessConfigsHandler + redundancyLevel int } // ArgsManagedPeersHolder represents the argument for the managed peers holder type ArgsManagedPeersHolder struct { KeyGenerator crypto.KeyGenerator P2PKeyGenerator crypto.KeyGenerator - MaxRoundsOfInactivity int + ProcessConfigsHandler cmn.ProcessConfigsHandler PrefsConfig config.Preferences P2PKeyConverter p2p.P2PKeyConverter } @@ -62,14 +66,21 @@ func NewManagedPeersHolder(args ArgsManagedPeersHolder) (*managedPeersHolder, er pids: make(map[core.PeerID]struct{}), keyGenerator: args.KeyGenerator, p2pKeyGenerator: args.P2PKeyGenerator, - isMainMachine: common.IsMainNode(args.MaxRoundsOfInactivity), - maxRoundsOfInactivity: args.MaxRoundsOfInactivity, defaultName: args.PrefsConfig.Preferences.NodeDisplayName, defaultIdentity: args.PrefsConfig.Preferences.Identity, p2pKeyConverter: args.P2PKeyConverter, data: make(map[string]*peerInfo), + redundancyLevel: int(args.PrefsConfig.Preferences.RedundancyLevel), + processConfigsHandler: args.ProcessConfigsHandler, } + maxRoundsOfInactivity := holder.calcMaxRoundsOfInactivity() + err = common.CheckMaxRoundsOfInactivity(maxRoundsOfInactivity) + if err != nil { + return nil, err + } + holder.isMainMachine = common.IsMainNode(maxRoundsOfInactivity) + holder.providedIdentities, err = holder.createProvidedIdentitiesMap(args.PrefsConfig.NamedIdentity) if err != nil { return nil, err @@ -85,17 +96,20 @@ func checkManagedPeersHolderArgs(args ArgsManagedPeersHolder) error { if check.IfNil(args.P2PKeyGenerator) { return fmt.Errorf("%w for args.P2PKeyGenerator", ErrNilKeyGenerator) } - err := common.CheckMaxRoundsOfInactivity(args.MaxRoundsOfInactivity) - if err != nil { - return err - } if check.IfNil(args.P2PKeyConverter) { return fmt.Errorf("%w for args.P2PKeyConverter", ErrNilP2PKeyConverter) } + if check.IfNil(args.ProcessConfigsHandler) { + return process.ErrNilProcessConfigsHandler + } return nil } +func (holder *managedPeersHolder) calcMaxRoundsOfInactivity() int { + return holder.redundancyLevel * int(holder.processConfigsHandler.GetValue(dto.MaxRoundsOfInactivityAccepted)) +} + func (holder *managedPeersHolder) createProvidedIdentitiesMap(namedIdentities []config.NamedIdentity) (map[string]*peerInfo, error) { dataMap := make(map[string]*peerInfo) @@ -290,7 +304,7 @@ func (holder *managedPeersHolder) GetManagedKeysByCurrentNode() map[string]crypt allManagedKeys := make(map[string]crypto.PrivateKey) for pk, pInfo := range holder.data { - shouldAddToMap := pInfo.shouldActAsValidator(holder.maxRoundsOfInactivity) + shouldAddToMap := pInfo.shouldActAsValidator(holder.calcMaxRoundsOfInactivity()) if !shouldAddToMap { continue } @@ -325,7 +339,7 @@ func (holder *managedPeersHolder) IsKeyManagedByCurrentNode(pkBytes []byte) bool return false } - return pInfo.shouldActAsValidator(holder.maxRoundsOfInactivity) + return pInfo.shouldActAsValidator(holder.calcMaxRoundsOfInactivity()) } // IsKeyRegistered returns true if the key is registered (not necessarily managed by the current node) diff --git a/keysManagement/managedPeersHolder_test.go b/keysManagement/managedPeersHolder_test.go index 9a8c66fb849..b12b7ed0563 100644 --- a/keysManagement/managedPeersHolder_test.go +++ b/keysManagement/managedPeersHolder_test.go @@ -18,6 +18,8 @@ import ( "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/keysManagement" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/cryptoMocks" "github.com/multiversx/mx-chain-go/testscommon/p2pmocks" "github.com/stretchr/testify/assert" @@ -55,9 +57,9 @@ func createMockArgsManagedPeersHolder() keysManagement.ArgsManagedPeersHolder { } }, }, - MaxRoundsOfInactivity: 0, PrefsConfig: config.Preferences{ Preferences: config.PreferencesConfig{ + RedundancyLevel: 0, Identity: defaultIdentity, NodeDisplayName: defaultName, }, @@ -67,6 +69,7 @@ func createMockArgsManagedPeersHolder() keysManagement.ArgsManagedPeersHolder { return pid, nil }, }, + ProcessConfigsHandler: &testscommon.ProcessConfigsHandlerStub{}, } } @@ -132,7 +135,7 @@ func TestNewManagedPeersHolder(t *testing.T) { t.Run("negative value", func(t *testing.T) { args := createMockArgsManagedPeersHolder() - args.MaxRoundsOfInactivity = -2 + args.PrefsConfig.Preferences.RedundancyLevel = -2 holder, err := keysManagement.NewManagedPeersHolder(args) assert.Nil(t, holder) @@ -141,7 +144,7 @@ func TestNewManagedPeersHolder(t *testing.T) { }) t.Run("value of 1", func(t *testing.T) { args := createMockArgsManagedPeersHolder() - args.MaxRoundsOfInactivity = 1 + args.PrefsConfig.Preferences.RedundancyLevel = 1 holder, err := keysManagement.NewManagedPeersHolder(args) assert.Nil(t, holder) @@ -175,6 +178,16 @@ func TestNewManagedPeersHolder(t *testing.T) { assert.True(t, errors.Is(err, keysManagement.ErrNilP2PKeyConverter)) assert.True(t, check.IfNil(holder)) }) + t.Run("nil process configs should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgsManagedPeersHolder() + args.ProcessConfigsHandler = nil + holder, err := keysManagement.NewManagedPeersHolder(args) + + require.True(t, errors.Is(err, process.ErrNilProcessConfigsHandler)) + require.True(t, check.IfNil(holder)) + }) t.Run("valid arguments should work", func(t *testing.T) { t.Parallel() @@ -429,8 +442,7 @@ func TestManagedPeersHolder_GetPrivateKey(t *testing.T) { }() argsLocal := createMockArgsManagedPeersHolder() - argsLocal.PrefsConfig.Preferences.RedundancyLevel = 1 - argsLocal.MaxRoundsOfInactivity = 3 + argsLocal.PrefsConfig.Preferences.RedundancyLevel = 3 namedIdentity := config.NamedIdentity{ Identity: testIdentity, NodeName: testName, @@ -603,7 +615,7 @@ func TestManagedPeersHolder_IncrementRoundsWithoutReceivedMessages(t *testing.T) }) t.Run("is secondary machine should increment, if existing", func(t *testing.T) { args := createMockArgsManagedPeersHolder() - args.MaxRoundsOfInactivity = 2 + args.PrefsConfig.Preferences.RedundancyLevel = 2 holder, _ := keysManagement.NewManagedPeersHolder(args) _ = holder.AddManagedPeer(skBytes0) @@ -664,7 +676,7 @@ func TestManagedPeersHolder_ResetRoundsWithoutReceivedMessages(t *testing.T) { }) t.Run("is secondary machine should reset, if existing", func(t *testing.T) { args := createMockArgsManagedPeersHolder() - args.MaxRoundsOfInactivity = 2 + args.PrefsConfig.Preferences.RedundancyLevel = 2 holder, _ := keysManagement.NewManagedPeersHolder(args) _ = holder.AddManagedPeer(skBytes0) pInfo := holder.GetPeerInfo(pkBytes0) @@ -724,7 +736,7 @@ func TestManagedPeersHolder_GetManagedKeysByCurrentNode(t *testing.T) { }) t.Run("is secondary machine should return managed keys", func(t *testing.T) { args := createMockArgsManagedPeersHolder() - args.MaxRoundsOfInactivity = 2 + args.PrefsConfig.Preferences.RedundancyLevel = 2 holder, _ := keysManagement.NewManagedPeersHolder(args) _ = holder.AddManagedPeer(skBytes0) _ = holder.AddManagedPeer(skBytes1) @@ -789,7 +801,7 @@ func TestManagedPeersHolder_IsKeyManagedByCurrentNode(t *testing.T) { }) t.Run("secondary machine", func(t *testing.T) { args := createMockArgsManagedPeersHolder() - args.MaxRoundsOfInactivity = 2 + args.PrefsConfig.Preferences.RedundancyLevel = 2 holder, _ := keysManagement.NewManagedPeersHolder(args) _ = holder.AddManagedPeer(skBytes0) pInfo := holder.GetPeerInfo(pkBytes0) @@ -844,7 +856,7 @@ func TestManagedPeersHolder_IsKeyRegistered(t *testing.T) { }) t.Run("secondary machine", func(t *testing.T) { args := createMockArgsManagedPeersHolder() - args.MaxRoundsOfInactivity = 2 + args.PrefsConfig.Preferences.RedundancyLevel = 2 holder, _ := keysManagement.NewManagedPeersHolder(args) _ = holder.AddManagedPeer(skBytes0) @@ -949,7 +961,7 @@ func TestManagedPeersHolder_IsMultiKeyMode(t *testing.T) { }) t.Run("backup machine mode", func(t *testing.T) { args := createMockArgsManagedPeersHolder() - args.MaxRoundsOfInactivity = 2 + args.PrefsConfig.Preferences.RedundancyLevel = 2 holder, _ := keysManagement.NewManagedPeersHolder(args) assert.False(t, holder.IsMultiKeyMode()) @@ -968,13 +980,13 @@ func TestManagedPeersHolder_GetRedundancyStepInReason(t *testing.T) { }) t.Run("redundancy machine mode but no managed keys", func(t *testing.T) { args := createMockArgsManagedPeersHolder() - args.MaxRoundsOfInactivity = 2 + args.PrefsConfig.Preferences.RedundancyLevel = 2 holder, _ := keysManagement.NewManagedPeersHolder(args) assert.Empty(t, holder.GetRedundancyStepInReason()) }) t.Run("redundancy machine mode with one managed key, main active", func(t *testing.T) { args := createMockArgsManagedPeersHolder() - args.MaxRoundsOfInactivity = 2 + args.PrefsConfig.Preferences.RedundancyLevel = 2 holder, _ := keysManagement.NewManagedPeersHolder(args) _ = holder.AddManagedPeer(skBytes0) @@ -982,10 +994,10 @@ func TestManagedPeersHolder_GetRedundancyStepInReason(t *testing.T) { }) t.Run("redundancy machine mode with one managed key, main inactive", func(t *testing.T) { args := createMockArgsManagedPeersHolder() - args.MaxRoundsOfInactivity = 2 + args.PrefsConfig.Preferences.RedundancyLevel = 2 holder, _ := keysManagement.NewManagedPeersHolder(args) _ = holder.AddManagedPeer(skBytes0) - for i := 0; i < args.MaxRoundsOfInactivity+1; i++ { + for i := 0; i < holder.CalcMaxRoundsOfInactivity()+1; i++ { holder.IncrementRoundsWithoutReceivedMessages(pkBytes0) } @@ -993,7 +1005,7 @@ func TestManagedPeersHolder_GetRedundancyStepInReason(t *testing.T) { }) t.Run("redundancy machine mode with 2 managed keys, main active", func(t *testing.T) { args := createMockArgsManagedPeersHolder() - args.MaxRoundsOfInactivity = 2 + args.PrefsConfig.Preferences.RedundancyLevel = 2 holder, _ := keysManagement.NewManagedPeersHolder(args) _ = holder.AddManagedPeer(skBytes0) _ = holder.AddManagedPeer(skBytes1) @@ -1002,12 +1014,12 @@ func TestManagedPeersHolder_GetRedundancyStepInReason(t *testing.T) { }) t.Run("redundancy machine mode with 2 managed keys, main inactive", func(t *testing.T) { args := createMockArgsManagedPeersHolder() - args.MaxRoundsOfInactivity = 2 + args.PrefsConfig.Preferences.RedundancyLevel = 2 holder, _ := keysManagement.NewManagedPeersHolder(args) _ = holder.AddManagedPeer(skBytes0) _ = holder.AddManagedPeer(skBytes1) - for i := 0; i < args.MaxRoundsOfInactivity+1; i++ { + for i := 0; i < holder.CalcMaxRoundsOfInactivity()+1; i++ { holder.IncrementRoundsWithoutReceivedMessages(pkBytes0) holder.IncrementRoundsWithoutReceivedMessages(pkBytes1) } diff --git a/node/chainSimulator/chainSimulator.go b/node/chainSimulator/chainSimulator.go index 288e7d25a23..3d1ee548ce1 100644 --- a/node/chainSimulator/chainSimulator.go +++ b/node/chainSimulator/chainSimulator.go @@ -6,7 +6,6 @@ import ( "encoding/hex" "errors" "fmt" - "math/big" "sync" "time" @@ -22,6 +21,8 @@ import ( crypto "github.com/multiversx/mx-chain-crypto-go" "github.com/multiversx/mx-chain-crypto-go/signing" "github.com/multiversx/mx-chain-crypto-go/signing/mcl" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/factory" "github.com/multiversx/mx-chain-go/node/chainSimulator/components" @@ -30,7 +31,6 @@ import ( "github.com/multiversx/mx-chain-go/node/chainSimulator/dtos" chainSimulatorErrors "github.com/multiversx/mx-chain-go/node/chainSimulator/errors" "github.com/multiversx/mx-chain-go/node/chainSimulator/process" - logger "github.com/multiversx/mx-chain-logger-go" ) const delaySendTxs = time.Millisecond @@ -45,24 +45,29 @@ type transactionWithResult struct { // ArgsChainSimulator holds the arguments needed to create a new instance of simulator type ArgsChainSimulator struct { - BypassTxSignatureCheck bool - TempDir string - PathToInitialConfig string - NumOfShards uint32 - MinNodesPerShard uint32 - MetaChainMinNodes uint32 - Hysteresis float32 - NumNodesWaitingListShard uint32 - NumNodesWaitingListMeta uint32 - GenesisTimestamp int64 - InitialRound int64 - InitialEpoch uint32 - InitialNonce uint64 - RoundDurationInMillis uint64 - RoundsPerEpoch core.OptionalUint64 - ApiInterface components.APIConfigurator - AlterConfigsFunction func(cfg *config.Configs) - VmQueryDelayAfterStartInMs uint64 + BypassTxSignatureCheck bool + BypassBlockSignatureCheck bool + TempDir string + PathToInitialConfig string + NumOfShards uint32 + MinNodesPerShard uint32 + MetaChainMinNodes uint32 + Hysteresis float32 + NumNodesWaitingListShard uint32 + NumNodesWaitingListMeta uint32 + GenesisTimestamp int64 + InitialRound int64 + InitialEpoch uint32 + InitialNonce uint64 + RoundDurationInMillis uint64 + SupernovaRoundDurationInMillis uint64 + RoundsPerEpoch core.OptionalUint64 + SupernovaRoundsPerEpoch core.OptionalUint64 + ApiInterface components.APIConfigurator + AlterConfigsFunction func(cfg *config.Configs) + VmQueryDelayAfterStartInMs uint64 + CreateBlockMaxTimePercent float64 + BypassCreateBlockTimeCheck bool } // ArgsBaseChainSimulator holds the arguments needed to create a new instance of simulator @@ -115,26 +120,29 @@ func NewBaseChainSimulator(args ArgsBaseChainSimulator) (*simulator, error) { func (s *simulator) createChainHandlers(args ArgsBaseChainSimulator) error { outputConfigs, err := configs.CreateChainSimulatorConfigs(configs.ArgsChainSimulatorConfigs{ - NumOfShards: args.NumOfShards, - OriginalConfigsPath: args.PathToInitialConfig, - GenesisTimeStamp: computeStartTimeBaseOnInitialRound(args.ArgsChainSimulator), - RoundDurationInMillis: args.RoundDurationInMillis, - TempDir: args.TempDir, - MinNodesPerShard: args.MinNodesPerShard, - ConsensusGroupSize: args.ConsensusGroupSize, - MetaChainMinNodes: args.MetaChainMinNodes, - MetaChainConsensusGroupSize: args.MetaChainConsensusGroupSize, - Hysteresis: args.Hysteresis, - RoundsPerEpoch: args.RoundsPerEpoch, - InitialEpoch: args.InitialEpoch, - AlterConfigsFunction: args.AlterConfigsFunction, - NumNodesWaitingListShard: args.NumNodesWaitingListShard, - NumNodesWaitingListMeta: args.NumNodesWaitingListMeta, + NumOfShards: args.NumOfShards, + OriginalConfigsPath: args.PathToInitialConfig, + RoundDurationInMillis: args.RoundDurationInMillis, + SupernovaRoundDurationInMillis: args.SupernovaRoundDurationInMillis, + TempDir: args.TempDir, + MinNodesPerShard: args.MinNodesPerShard, + ConsensusGroupSize: args.ConsensusGroupSize, + MetaChainMinNodes: args.MetaChainMinNodes, + MetaChainConsensusGroupSize: args.MetaChainConsensusGroupSize, + Hysteresis: args.Hysteresis, + RoundsPerEpoch: args.RoundsPerEpoch, + SupernovaRoundsPerEpoch: args.SupernovaRoundsPerEpoch, + InitialEpoch: args.InitialEpoch, + AlterConfigsFunction: args.AlterConfigsFunction, + NumNodesWaitingListShard: args.NumNodesWaitingListShard, + NumNodesWaitingListMeta: args.NumNodesWaitingListMeta, + InitialRound: args.InitialRound, }) if err != nil { return err } + genesisTime := time.Now() monitor := heartbeat.NewHeartbeatMonitor() for idx := -1; idx < int(args.NumOfShards); idx++ { @@ -143,12 +151,12 @@ func (s *simulator) createChainHandlers(args ArgsBaseChainSimulator) error { shardIDStr = "metachain" } - node, errCreate := s.createTestNode(*outputConfigs, args, shardIDStr, monitor) + node, errCreate := s.createTestNode(*outputConfigs, args, shardIDStr, genesisTime, monitor) if errCreate != nil { return errCreate } - chainHandler, errCreate := process.NewBlocksCreator(node, monitor) + chainHandler, errCreate := process.NewBlocksCreator(node, monitor, args.CreateBlockMaxTimePercent, args.BypassCreateBlockTimeCheck) if errCreate != nil { return errCreate } @@ -190,16 +198,20 @@ func (s *simulator) createChainHandlers(args ArgsBaseChainSimulator) error { TimeStamp: uint64(node.GetCoreComponents().RoundHandler().TimeStamp().Unix()), } } else { - epochStartBlockHeader = &block.HeaderV2{ - Header: &block.Header{ - Nonce: args.InitialNonce, - Epoch: args.InitialEpoch, - Round: uint64(args.InitialRound), - TimeStamp: uint64(node.GetCoreComponents().RoundHandler().TimeStamp().Unix()), - }, + epochStartBlockHeader = &block.MetaBlockV3{ + Nonce: args.InitialNonce, + Epoch: args.InitialEpoch, + Round: uint64(args.InitialRound), + TimestampMs: uint64(node.GetCoreComponents().RoundHandler().TimeStamp().Unix()), } } + genesisBlock := node.GetDataComponents().Blockchain().GetGenesisHeader() + err = node.GetDataComponents().Datapool().Transactions().OnExecutedBlock(genesisBlock, genesisBlock.GetRootHash()) + if err != nil { + return err + } + err = node.GetProcessComponents().BlockchainHook().SetEpochStartHeader(epochStartBlockHeader) if err != nil { return err @@ -214,9 +226,6 @@ func (s *simulator) createChainHandlers(args ArgsBaseChainSimulator) error { log.Info("running the chain simulator with the following parameters", "number of shards (including meta)", args.NumOfShards+1, - "round per epoch", outputConfigs.Configs.GeneralConfig.EpochStartConfig.RoundsPerEpoch, - "round duration", time.Millisecond*time.Duration(args.RoundDurationInMillis), - "genesis timestamp", args.GenesisTimestamp, "original config path", args.PathToInitialConfig, "temporary path", args.TempDir) @@ -256,12 +265,12 @@ func (s *simulator) addProofs() { } } -func computeStartTimeBaseOnInitialRound(args ArgsChainSimulator) int64 { - return args.GenesisTimestamp + int64(args.RoundDurationInMillis/1000)*args.InitialRound -} - func (s *simulator) createTestNode( - outputConfigs configs.ArgsConfigsSimulator, args ArgsBaseChainSimulator, shardIDStr string, monitor factory.HeartbeatV2Monitor, + outputConfigs configs.ArgsConfigsSimulator, + args ArgsBaseChainSimulator, + shardIDStr string, + genesisTime time.Time, + monitor factory.HeartbeatV2Monitor, ) (process.NodeHandler, error) { argsTestOnlyProcessorNode := components.ArgsTestOnlyProcessingNode{ Configs: outputConfigs.Configs, @@ -272,6 +281,7 @@ func (s *simulator) createTestNode( ShardIDStr: shardIDStr, APIInterface: args.ApiInterface, BypassTxSignatureCheck: args.BypassTxSignatureCheck, + BypassBlockSignatureCheck: args.BypassBlockSignatureCheck, InitialRound: args.InitialRound, InitialNonce: args.InitialNonce, MinNodesPerShard: args.MinNodesPerShard, @@ -280,6 +290,7 @@ func (s *simulator) createTestNode( MetaChainConsensusGroupSize: args.MetaChainConsensusGroupSize, RoundDurationInMillis: args.RoundDurationInMillis, VmQueryDelayAfterStartInMs: args.VmQueryDelayAfterStartInMs, + GenesisTime: genesisTime, Monitor: monitor, } @@ -389,11 +400,43 @@ func (s *simulator) ForceChangeOfEpoch() error { } func (s *simulator) allNodesCreateBlocks() error { + headers := make(map[uint32]*dtos.BroadcastData, len(s.handlers)) for _, node := range s.handlers { // TODO MX-15150 remove this when we remove all goroutines time.Sleep(2 * time.Millisecond) - err := node.CreateNewBlock() + pair, err := node.CreateNewBlock() + if err != nil { + return err + } + if pair == nil { + continue + } + + headers[pair.Header.GetShardID()] = pair + } + + for shardID, pair := range headers { + messenger := s.nodes[shardID].GetBroadcastMessenger() + + err := messenger.BroadcastHeader(pair.Header, pair.LeaderKey) + if err != nil { + return err + } + + if !check.IfNil(pair.Proof) { + err = s.nodes[shardID].GetBroadcastMessenger().BroadcastEquivalentProof(pair.Proof, pair.LeaderKey) + if err != nil { + return err + } + } + + err = messenger.BroadcastMiniBlocks(pair.MiniBlocksBytes, pair.LeaderKey) + if err != nil { + return err + } + + err = messenger.BroadcastTransactions(pair.TransactionsBytes, pair.LeaderKey) if err != nil { return err } diff --git a/node/chainSimulator/chainSimulator_test.go b/node/chainSimulator/chainSimulator_test.go index d5e8d7baff8..45b656d9ca0 100644 --- a/node/chainSimulator/chainSimulator_test.go +++ b/node/chainSimulator/chainSimulator_test.go @@ -1,48 +1,124 @@ package chainSimulator import ( + "fmt" "math/big" "strings" "testing" "time" "github.com/multiversx/mx-chain-core-go/core" + apiBlock "github.com/multiversx/mx-chain-core-go/data/api" + "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/errors" chainSimulatorCommon "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator" + "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator/staking" "github.com/multiversx/mx-chain-go/node/chainSimulator/components/api" "github.com/multiversx/mx-chain-go/node/chainSimulator/configs" "github.com/multiversx/mx-chain-go/node/chainSimulator/dtos" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/vm" ) const ( - defaultPathToInitialConfig = "../../cmd/node/config/" + defaultPathToInitialConfig = "../../cmd/node/config/" + defaultRoundDurationInMillis = uint64(6000) + defaultSupernovaRoundDurationInMillis = uint64(600) + defaultRoundsPerEpochValue = uint64(20) + defaultSupernovaRoundsPerEpochValue = uint64(40) + defaultNumOfShards = uint32(3) + defaultMinNodesPerShard = uint32(1) + defaultMetaChainMinNodes = uint32(1) ) +var ( + defaultRoundsPerEpoch = core.OptionalUint64{ + HasValue: true, + Value: defaultRoundsPerEpochValue, + } + defaultSupernovaRoundsPerEpoch = core.OptionalUint64{ + HasValue: true, + Value: defaultSupernovaRoundsPerEpochValue, + } +) + +func TestChainSimulatorCheckSupernova(t *testing.T) { + chainSimulator, err := NewChainSimulator(ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: defaultNumOfShards, + RoundDurationInMillis: defaultRoundDurationInMillis, + SupernovaRoundDurationInMillis: defaultSupernovaRoundDurationInMillis, + RoundsPerEpoch: defaultRoundsPerEpoch, + SupernovaRoundsPerEpoch: defaultSupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + AlterConfigsFunction: func(cfg *config.Configs) { + + }, + }) + require.Nil(t, err) + require.NotNil(t, chainSimulator) + + err = chainSimulator.GenerateBlocksUntilEpochIsReached(2) + require.Nil(t, err) + + err = chainSimulator.GenerateBlocks(2) + require.Nil(t, err) + + err = chainSimulator.GenerateBlocks(1) // supernova round activation + require.Nil(t, err) + + err = chainSimulator.GenerateBlocks(1) + require.Nil(t, err) + + err = chainSimulator.GenerateBlocks(50) + require.Nil(t, err) + + time.Sleep(time.Second) + + chainSimulator.Close() +} + func TestNewChainSimulator(t *testing.T) { if testing.Short() { t.Skip("this is not a short test") } - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) + alterConfigsFunc := func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 999999 + cfg.RoundConfig.RoundActivations = map[string]config.ActivationRoundByName{ + "DisableAsyncCallV1": { + Round: "9999999", + }, + "SupernovaEnableRound": { + Round: "9999999", + }, + } + } + chainSimulator, err := NewChainSimulator(ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: core.OptionalUint64{ - HasValue: true, - Value: 20, - }, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 3, - MetaChainMinNodes: 3, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: defaultNumOfShards, + RoundDurationInMillis: defaultRoundDurationInMillis, + SupernovaRoundDurationInMillis: defaultSupernovaRoundDurationInMillis, + RoundsPerEpoch: defaultRoundsPerEpoch, + SupernovaRoundsPerEpoch: defaultSupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 3, + MetaChainMinNodes: 3, + AlterConfigsFunction: alterConfigsFunc, }) require.Nil(t, err) require.NotNil(t, chainSimulator) @@ -65,30 +141,29 @@ func TestChainSimulator_GenerateBlocksShouldWork(t *testing.T) { t.Skip("this is not a short test") } - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) chainSimulator, err := NewChainSimulator(ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: core.OptionalUint64{ - HasValue: true, - Value: 20, - }, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 1, - MetaChainMinNodes: 1, - InitialRound: 200000000, - InitialEpoch: 100, - InitialNonce: 100, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + BypassBlockSignatureCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: defaultNumOfShards, + RoundDurationInMillis: defaultRoundDurationInMillis, + SupernovaRoundDurationInMillis: defaultSupernovaRoundDurationInMillis, + RoundsPerEpoch: defaultRoundsPerEpoch, + SupernovaRoundsPerEpoch: defaultSupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: defaultMinNodesPerShard, + MetaChainMinNodes: defaultMetaChainMinNodes, + InitialRound: 20000, + InitialEpoch: 100, + InitialNonce: 100, AlterConfigsFunction: func(cfg *config.Configs) { // we need to enable this as this test skips a lot of epoch activations events, and it will fail otherwise // because the owner of a BLS key coming from genesis is not set // (the owner is not set at genesis anymore because we do not enable the staking v2 in that phase) cfg.EpochConfig.EnableEpochs.StakingV2EnableEpoch = 0 + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 99999 }, }) require.Nil(t, err) @@ -106,28 +181,85 @@ func TestChainSimulator_GenerateBlocksShouldWork(t *testing.T) { } -func TestChainSimulator_GenerateBlocksAndEpochChangeShouldWork(t *testing.T) { +func TestChainSimulator_VerifyBlockTimestampSupernova(t *testing.T) { if testing.Short() { t.Skip("this is not a short test") } - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 20, + supernovaActivationRound := uint64(220) + + chainSimulator, err := NewChainSimulator(ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: defaultNumOfShards, + RoundDurationInMillis: defaultRoundDurationInMillis, + SupernovaRoundDurationInMillis: defaultSupernovaRoundDurationInMillis, + RoundsPerEpoch: core.OptionalUint64{ + Value: 20, + HasValue: true, + }, + SupernovaRoundsPerEpoch: defaultSupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: defaultMinNodesPerShard, + MetaChainMinNodes: defaultMetaChainMinNodes, + InitialRound: 200, + InitialEpoch: 100, + InitialNonce: 100, + AlterConfigsFunction: func(cfg *config.Configs) { + // we need to enable this as this test skips a lot of epoch activations events, and it will fail otherwise + // because the owner of a BLS key coming from genesis is not set + // (the owner is not set at genesis anymore because we do not enable the staking v2 in that phase) + cfg.EpochConfig.EnableEpochs.StakingV2EnableEpoch = 0 + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 100 + cfg.RoundConfig.RoundActivations[string(common.SupernovaRoundFlag)] = config.ActivationRoundByName{ + Round: fmt.Sprintf("%d", supernovaActivationRound), + } + }, + }) + require.Nil(t, err) + require.NotNil(t, chainSimulator) + defer chainSimulator.Close() + + time.Sleep(time.Second) + + err = chainSimulator.GenerateBlocks(30) + require.Nil(t, err) + + blockBeforeSupernovaRound, err := chainSimulator.GetNodeHandler(0).GetFacadeHandler().GetBlockByRound(supernovaActivationRound-1, apiBlock.BlockQueryOptions{}) + require.Nil(t, err) + + blockS, err := chainSimulator.GetNodeHandler(0).GetFacadeHandler().GetBlockByRound(supernovaActivationRound, apiBlock.BlockQueryOptions{}) + require.Nil(t, err) + + blockAfterSupernovaRound, err := chainSimulator.GetNodeHandler(0).GetFacadeHandler().GetBlockByRound(supernovaActivationRound+1, apiBlock.BlockQueryOptions{}) + require.Nil(t, err) + + diff := blockS.TimestampMs - blockBeforeSupernovaRound.TimestampMs + require.Equal(t, int64(6000), diff) + diff = blockAfterSupernovaRound.TimestampMs - blockS.TimestampMs + require.Equal(t, defaultSupernovaRoundDurationInMillis, uint64(diff)) +} + +func TestChainSimulator_GenerateBlocksAndEpochChangeShouldWork(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") } + chainSimulator, err := NewChainSimulator(ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 100, - MetaChainMinNodes: 100, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: defaultNumOfShards, + RoundDurationInMillis: defaultRoundDurationInMillis, + SupernovaRoundDurationInMillis: defaultSupernovaRoundDurationInMillis, + RoundsPerEpoch: defaultRoundsPerEpoch, + SupernovaRoundsPerEpoch: defaultSupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 100, + MetaChainMinNodes: 100, }) require.Nil(t, err) require.NotNil(t, chainSimulator) @@ -168,23 +300,40 @@ func TestSimulator_TriggerChangeOfEpoch(t *testing.T) { t.Skip("this is not a short test") } - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) roundsPerEpoch := core.OptionalUint64{ HasValue: true, Value: 15000, } + supernovaRoundsPerEpoch := core.OptionalUint64{ + HasValue: true, + Value: 150000, + } + alterConfigsFunc := func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 999999 + cfg.RoundConfig.RoundActivations = map[string]config.ActivationRoundByName{ + "DisableAsyncCallV1": { + Round: "9999999", + }, + "SupernovaEnableRound": { + Round: "9999999", + }, + } + } + chainSimulator, err := NewChainSimulator(ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 100, - MetaChainMinNodes: 100, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: defaultNumOfShards, + RoundDurationInMillis: defaultRoundDurationInMillis, + SupernovaRoundDurationInMillis: defaultSupernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 100, + MetaChainMinNodes: 100, + AlterConfigsFunction: alterConfigsFunc, }) require.Nil(t, err) require.NotNil(t, chainSimulator) @@ -208,28 +357,82 @@ func TestSimulator_TriggerChangeOfEpoch(t *testing.T) { require.Equal(t, uint32(4), currentEpoch) } -func TestChainSimulator_SetState(t *testing.T) { +func TestChainSimulator_ChangeRoundsPerEpoch(t *testing.T) { if testing.Short() { t.Skip("this is not a short test") } - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) roundsPerEpoch := core.OptionalUint64{ HasValue: true, Value: 20, } + supernovaRoundsPerEpoch := core.OptionalUint64{ + HasValue: true, + Value: 30, + } chainSimulator, err := NewChainSimulator(ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 1, - MetaChainMinNodes: 1, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: defaultNumOfShards, + RoundDurationInMillis: defaultRoundDurationInMillis, + SupernovaRoundDurationInMillis: defaultSupernovaRoundDurationInMillis, + RoundsPerEpoch: roundsPerEpoch, + SupernovaRoundsPerEpoch: supernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: defaultMinNodesPerShard, + MetaChainMinNodes: defaultMetaChainMinNodes, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.GeneralConfig.GeneralSettings.ChainParametersByEpoch[0].EnableEpoch = 0 + cfg.GeneralConfig.GeneralSettings.ChainParametersByEpoch[0].RoundsPerEpoch = 10 + cfg.GeneralConfig.GeneralSettings.ChainParametersByEpoch[0].MinRoundsBetweenEpochs = 10 + + cfg.EpochConfig.EnableEpochs.AndromedaEnableEpoch = 3 + cfg.GeneralConfig.GeneralSettings.ChainParametersByEpoch[1].EnableEpoch = 3 + cfg.GeneralConfig.GeneralSettings.ChainParametersByEpoch[1].RoundsPerEpoch = 20 + cfg.GeneralConfig.GeneralSettings.ChainParametersByEpoch[1].MinRoundsBetweenEpochs = 10 + + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 5 + cfg.GeneralConfig.GeneralSettings.ChainParametersByEpoch[2].EnableEpoch = 5 + cfg.GeneralConfig.GeneralSettings.ChainParametersByEpoch[2].RoundsPerEpoch = 30 + cfg.GeneralConfig.GeneralSettings.ChainParametersByEpoch[2].MinRoundsBetweenEpochs = 10 + }, + }) + require.Nil(t, err) + require.NotNil(t, chainSimulator) + + err = chainSimulator.GenerateBlocks(140) + require.Nil(t, err) + + expectedEpoch := uint32(7) + + metaNode := chainSimulator.GetNodeHandler(core.MetachainShardId) + currentEpoch := metaNode.GetProcessComponents().EpochStartTrigger().Epoch() + require.Equal(t, expectedEpoch, currentEpoch) + + defer chainSimulator.Close() + +} + +func TestChainSimulator_SetState(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + chainSimulator, err := NewChainSimulator(ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: defaultNumOfShards, + RoundDurationInMillis: defaultRoundDurationInMillis, + SupernovaRoundDurationInMillis: defaultSupernovaRoundDurationInMillis, + RoundsPerEpoch: defaultRoundsPerEpoch, + SupernovaRoundsPerEpoch: defaultSupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: defaultMinNodesPerShard, + MetaChainMinNodes: defaultMetaChainMinNodes, }) require.Nil(t, err) require.NotNil(t, chainSimulator) @@ -244,23 +447,19 @@ func TestChainSimulator_SetEntireState(t *testing.T) { t.Skip("this is not a short test") } - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 20, - } chainSimulator, err := NewChainSimulator(ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 1, - MetaChainMinNodes: 1, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: defaultNumOfShards, + RoundDurationInMillis: defaultRoundDurationInMillis, + SupernovaRoundDurationInMillis: defaultSupernovaRoundDurationInMillis, + RoundsPerEpoch: defaultRoundsPerEpoch, + SupernovaRoundsPerEpoch: defaultSupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: defaultMinNodesPerShard, + MetaChainMinNodes: defaultMetaChainMinNodes, }) require.Nil(t, err) require.NotNil(t, chainSimulator) @@ -292,23 +491,19 @@ func TestChainSimulator_SetEntireStateWithRemoval(t *testing.T) { t.Skip("this is not a short test") } - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 20, - } chainSimulator, err := NewChainSimulator(ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 1, - MetaChainMinNodes: 1, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: defaultNumOfShards, + RoundDurationInMillis: defaultRoundDurationInMillis, + SupernovaRoundDurationInMillis: defaultSupernovaRoundDurationInMillis, + RoundsPerEpoch: defaultRoundsPerEpoch, + SupernovaRoundsPerEpoch: defaultSupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: defaultMinNodesPerShard, + MetaChainMinNodes: defaultMetaChainMinNodes, }) require.Nil(t, err) require.NotNil(t, chainSimulator) @@ -339,23 +534,19 @@ func TestChainSimulator_GetAccount(t *testing.T) { t.Skip("this is not a short test") } - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 20, - } chainSimulator, err := NewChainSimulator(ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 1, - MetaChainMinNodes: 1, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: defaultNumOfShards, + RoundDurationInMillis: defaultRoundDurationInMillis, + SupernovaRoundDurationInMillis: defaultSupernovaRoundDurationInMillis, + RoundsPerEpoch: defaultRoundsPerEpoch, + SupernovaRoundsPerEpoch: defaultSupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: defaultMinNodesPerShard, + MetaChainMinNodes: defaultMetaChainMinNodes, }) require.Nil(t, err) require.NotNil(t, chainSimulator) @@ -373,23 +564,19 @@ func TestSimulator_SendTransactions(t *testing.T) { t.Skip("this is not a short test") } - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 20, - } chainSimulator, err := NewChainSimulator(ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 1, - MetaChainMinNodes: 1, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: defaultNumOfShards, + RoundDurationInMillis: defaultRoundDurationInMillis, + SupernovaRoundDurationInMillis: defaultSupernovaRoundDurationInMillis, + RoundsPerEpoch: defaultRoundsPerEpoch, + SupernovaRoundsPerEpoch: defaultSupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: defaultMinNodesPerShard, + MetaChainMinNodes: defaultMetaChainMinNodes, }) require.Nil(t, err) require.NotNil(t, chainSimulator) @@ -399,28 +586,114 @@ func TestSimulator_SendTransactions(t *testing.T) { chainSimulatorCommon.CheckGenerateTransactions(t, chainSimulator) } -func TestSimulator_SentMoveBalanceNoGasForFee(t *testing.T) { +func TestSimulator_MoveBalanceCheckReceipt(t *testing.T) { if testing.Short() { t.Skip("this is not a short test") } - startTime := time.Now().Unix() - roundDurationInMillis := uint64(6000) - roundsPerEpoch := core.OptionalUint64{ - HasValue: true, - Value: 20, + chainSimulator, err := NewChainSimulator(ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: defaultNumOfShards, + RoundDurationInMillis: defaultRoundDurationInMillis, + SupernovaRoundDurationInMillis: defaultSupernovaRoundDurationInMillis, + RoundsPerEpoch: defaultRoundsPerEpoch, + SupernovaRoundsPerEpoch: defaultSupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: defaultMinNodesPerShard, + MetaChainMinNodes: defaultMetaChainMinNodes, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV2EnableEpoch = 0 + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = uint32(2) + cfg.RoundConfig.RoundActivations[string(common.SupernovaRoundFlag)] = config.ActivationRoundByName{ + Round: "46", + } + }, + }) + require.Nil(t, err) + require.NotNil(t, chainSimulator) + + defer chainSimulator.Close() + + wallet0, err := chainSimulator.GenerateAndMintWalletAddress(0, chainSimulatorCommon.OneEGLD) + require.Nil(t, err) + err = chainSimulator.GenerateBlocks(1) + require.Nil(t, err) + + ftx := &transaction.Transaction{ + Nonce: 0, + Value: big.NewInt(1), + SndAddr: wallet0.Bytes, + RcvAddr: wallet0.Bytes, + Data: []byte(""), + GasLimit: 100_000, + GasPrice: 1_000_000_000, + ChainID: []byte(configs.ChainID), + Version: 1, + Signature: []byte("010101"), } + + checkReceipts := func(te *testing.T, aB *apiBlock.Block, value string) { + called := false + for _, mb := range aB.MiniBlocks { + if mb.Type == block.ReceiptBlock.String() { + called = true + require.Equal(te, 1, len(mb.Receipts)) + require.Equal(te, value, mb.Receipts[0].Value.String()) + } + } + require.True(te, called) + } + + apiTx, err := chainSimulator.SendTxAndGenerateBlockTilTxIsExecuted(ftx, 10) + require.Nil(t, err) + require.NotNil(t, apiTx) + + blockWithTxs, err := chainSimulator.GetNodeHandler(0).GetFacadeHandler().GetBlockByNonce(apiTx.BlockNonce, apiBlock.BlockQueryOptions{ + WithTransactions: true, + WithLogs: true, + }) + require.Nil(t, err) + require.Equal(t, 2, len(blockWithTxs.MiniBlocks)) + checkReceipts(t, blockWithTxs, "50000000000000") + + err = chainSimulator.GenerateBlocks(50) + require.Nil(t, err) + + ftx.Nonce++ + apiTx, err = chainSimulator.SendTxAndGenerateBlockTilTxIsExecuted(ftx, 10) + require.Nil(t, err) + require.NotNil(t, apiTx) + + blockWithTxs, err = chainSimulator.GetNodeHandler(0).GetFacadeHandler().GetBlockByNonce(apiTx.BlockNonce, apiBlock.BlockQueryOptions{ + WithTransactions: true, + WithLogs: true, + }) + require.Nil(t, err) + require.Equal(t, 2, len(blockWithTxs.MiniBlocks)) + checkReceipts(t, blockWithTxs, "500000000000") +} + +func TestSimulator_SentMoveBalanceNoGasForFee(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + chainSimulator, err := NewChainSimulator(ArgsChainSimulator{ - BypassTxSignatureCheck: true, - TempDir: t.TempDir(), - PathToInitialConfig: defaultPathToInitialConfig, - NumOfShards: 3, - GenesisTimestamp: startTime, - RoundDurationInMillis: roundDurationInMillis, - RoundsPerEpoch: roundsPerEpoch, - ApiInterface: api.NewNoApiInterface(), - MinNodesPerShard: 1, - MetaChainMinNodes: 1, + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: defaultNumOfShards, + RoundDurationInMillis: defaultRoundDurationInMillis, + SupernovaRoundDurationInMillis: defaultSupernovaRoundDurationInMillis, + RoundsPerEpoch: defaultRoundsPerEpoch, + SupernovaRoundsPerEpoch: defaultSupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: defaultMinNodesPerShard, + MetaChainMinNodes: defaultMetaChainMinNodes, }) require.Nil(t, err) require.NotNil(t, chainSimulator) @@ -445,3 +718,136 @@ func TestSimulator_SentMoveBalanceNoGasForFee(t *testing.T) { _, err = chainSimulator.sendTx(ftx) require.True(t, strings.Contains(err.Error(), errors.ErrInsufficientFunds.Error())) } + +func TestSimulator_SendMoveBalanceTxBeforeAndAfterSupernovaWithMoreGasLimit(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + chainSimulator, err := NewChainSimulator(ArgsChainSimulator{ + BypassTxSignatureCheck: true, + BypassCreateBlockTimeCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: defaultNumOfShards, + RoundDurationInMillis: defaultRoundDurationInMillis, + SupernovaRoundDurationInMillis: defaultSupernovaRoundDurationInMillis, + RoundsPerEpoch: defaultRoundsPerEpoch, + SupernovaRoundsPerEpoch: defaultSupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: defaultMinNodesPerShard, + MetaChainMinNodes: defaultMetaChainMinNodes, + CreateBlockMaxTimePercent: 0.25, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = 2 + }, + }) + require.Nil(t, err) + require.NotNil(t, chainSimulator) + + defer chainSimulator.Close() + + chainSimulatorCommon.GenerateMoveBalanceTxsInShardsWithMoreGasLimit(t, chainSimulator) + + err = chainSimulator.GenerateBlocksUntilEpochIsReached(3) + require.Nil(t, err) + + chainSimulatorCommon.GenerateMoveBalanceTxsInShardsWithMoreGasLimit(t, chainSimulator) +} + +func TestChainSimulator_VerifyEconomicsMetricsSupernova(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + supernovaActivationRound := uint64(46) + supernovaActivationEpoch := uint64(2) + + cs, err := NewChainSimulator(ArgsChainSimulator{ + BypassTxSignatureCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: defaultPathToInitialConfig, + NumOfShards: defaultNumOfShards, + RoundDurationInMillis: defaultRoundDurationInMillis, + SupernovaRoundDurationInMillis: defaultSupernovaRoundDurationInMillis, + RoundsPerEpoch: core.OptionalUint64{ + Value: 20, + HasValue: true, + }, + SupernovaRoundsPerEpoch: defaultSupernovaRoundsPerEpoch, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: defaultMinNodesPerShard, + MetaChainMinNodes: defaultMetaChainMinNodes, + AlterConfigsFunction: func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.StakingV2EnableEpoch = 0 + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = uint32(supernovaActivationEpoch) + cfg.RoundConfig.RoundActivations[string(common.SupernovaRoundFlag)] = config.ActivationRoundByName{ + Round: fmt.Sprintf("%d", supernovaActivationRound), + } + }, + }) + require.Nil(t, err) + require.NotNil(t, cs) + defer cs.Close() + + require.Nil(t, cs.GenerateBlocksUntilEpochIsReached(int32(supernovaActivationEpoch))) + + mintValue := big.NewInt(0).Mul(chainSimulatorCommon.OneEGLD, big.NewInt(3000*5)) + wallet1, err := cs.GenerateAndMintWalletAddress(0, mintValue) + require.Nil(t, err) + + _, blsKeys, err := GenerateBlsPrivateKeys(1) + require.Nil(t, err) + + err = cs.GenerateBlocks(1) + require.Nil(t, err) + + nonce := uint64(0) + for currentEpoch := supernovaActivationEpoch + 1; currentEpoch < supernovaActivationEpoch+4; currentEpoch++ { + dataFieldTx1 := fmt.Sprintf("stake@01@%s@%s", blsKeys[0], staking.MockBLSSignature) + tx1Value := big.NewInt(0).Mul(big.NewInt(2501), chainSimulatorCommon.OneEGLD) + tx1 := chainSimulatorCommon.GenerateTransaction(wallet1.Bytes, nonce, vm.ValidatorSCAddress, tx1Value, dataFieldTx1, staking.GasLimitForStakeOperation) + + results, err := cs.SendTxsAndGenerateBlocksTilAreExecuted([]*transaction.Transaction{tx1}, staking.MaxNumOfBlockToGenerateWhenExecutingTx) + require.Nil(t, err) + require.Equal(t, 1, len(results)) + require.NotNil(t, results) + + require.Nil(t, cs.GenerateBlocksUntilEpochIsReached(int32(currentEpoch))) + checkMetrics(t, cs, core.MetachainShardId, currentEpoch) + checkMetrics(t, cs, 0, currentEpoch) + + nonce++ + } +} + +func checkMetrics(t *testing.T, cs ChainSimulator, shardID uint32, expectedEpoch uint64) { + res, err := cs.GetNodeHandler(shardID).GetFacadeHandler().StatusMetrics().EconomicsMetrics() + require.Nil(t, err) + + expectedMetrics := map[string]struct{}{ + common.MetricTotalSupply: {}, + common.MetricInflation: {}, + common.MetricEpochForEconomicsData: {}, + common.MetricTotalFees: {}, + common.MetricDevRewardsInEpoch: {}, + } + + for foundMetric, metricValue := range res { + require.Contains(t, expectedMetrics, foundMetric) + + switch metricVal := metricValue.(type) { + case string: + require.Greater(t, len(metricVal), 1) + case uint64: + require.Equal(t, expectedEpoch, metricValue) + default: + require.Fail(t, "metric value is not a string or uint64") + } + + delete(expectedMetrics, foundMetric) + + } + + require.Empty(t, expectedMetrics, "should've found all expected metrics in the result from facade") +} diff --git a/node/chainSimulator/components/bootstrapComponents_test.go b/node/chainSimulator/components/bootstrapComponents_test.go index 7e4becdc52e..f6d5b21f132 100644 --- a/node/chainSimulator/components/bootstrapComponents_test.go +++ b/node/chainSimulator/components/bootstrapComponents_test.go @@ -111,10 +111,6 @@ func createArgsBootstrapComponentsHolder() ArgsBootstrapComponentsHolder { SetGuardianEpochsDelay: 1, }, Versions: config.VersionsConfig{ - Cache: config.CacheConfig{ - Type: "LRU", - Capacity: 123, - }, DefaultVersion: "1", VersionsByEpochs: []config.VersionByEpochs{ { diff --git a/node/chainSimulator/components/coreComponents.go b/node/chainSimulator/components/coreComponents.go index 11dcb8d33c0..5c0c5f493f5 100644 --- a/node/chainSimulator/components/coreComponents.go +++ b/node/chainSimulator/components/coreComponents.go @@ -3,6 +3,7 @@ package components import ( "bytes" "sync" + "sync/atomic" "time" "github.com/multiversx/mx-chain-go/common" @@ -38,6 +39,7 @@ import ( hashingFactory "github.com/multiversx/mx-chain-core-go/hashing/factory" "github.com/multiversx/mx-chain-core-go/marshal" marshalFactory "github.com/multiversx/mx-chain-core-go/marshal/factory" + commonConfigs "github.com/multiversx/mx-chain-go/common/configs" ) type coreComponentsHolder struct { @@ -62,11 +64,12 @@ type coreComponentsHolder struct { genesisNodesSetup sharding.GenesisNodesSetupHandler nodesShuffler nodesCoordinator.NodesShuffler epochNotifier process.EpochNotifier - enableRoundsHandler process.EnableRoundsHandler + enableRoundsHandler common.EnableRoundsHandler roundNotifier process.RoundNotifier epochStartNotifierWithConfirm factory.EpochStartNotifierWithConfirm chanStopNodeProcess chan endProcess.ArgEndProcess genesisTime time.Time + supernovaGenesisTime time.Time chainID string minTransactionVersion uint32 txVersionChecker process.TxVersionCheckerHandler @@ -80,6 +83,10 @@ type coreComponentsHolder struct { chainParametersHandler process.ChainParametersHandler fieldsSizeChecker common.FieldsSizeChecker epochChangeGracePeriodHandler common.EpochChangeGracePeriodHandler + processConfigsHandler common.ProcessConfigsHandler + epochStartConfigsHandler common.CommonConfigsHandler + antifloodConfigsHandler common.AntifloodConfigsHandler + closingNodeStarted *atomic.Bool } // ArgsCoreComponentsHolder will hold arguments needed for the core components holder @@ -101,13 +108,15 @@ type ArgsCoreComponentsHolder struct { MinNodesMeta uint32 MetaChainConsensusGroupSize uint32 RoundDurationInMs uint64 + GenesisTime time.Time } // CreateCoreComponents will create a new instance of factory.CoreComponentsHolder func CreateCoreComponents(args ArgsCoreComponentsHolder) (*coreComponentsHolder, error) { var err error instance := &coreComponentsHolder{ - closeHandler: NewCloseHandler(), + closeHandler: NewCloseHandler(), + closingNodeStarted: &atomic.Bool{}, } instance.internalMarshaller, err = marshalFactory.NewMarshalizer(args.Config.Marshalizer.Type) @@ -155,8 +164,8 @@ func CreateCoreComponents(args ArgsCoreComponentsHolder) (*coreComponentsHolder, instance.syncTimer = &testscommon.SyncTimerStub{} instance.epochStartNotifierWithConfirm = notifier.NewEpochStartSubscriptionHandler() - instance.chainParametersSubscriber = chainparametersnotifier.NewChainParametersNotifier() chainParametersNotifier := chainparametersnotifier.NewChainParametersNotifier() + instance.chainParametersSubscriber = chainParametersNotifier argsChainParametersHandler := sharding.ArgsChainParametersHolder{ EpochStartEventNotifier: instance.epochStartNotifierWithConfirm, ChainParameters: args.Config.GeneralSettings.ChainParametersByEpoch, @@ -172,29 +181,84 @@ func CreateCoreComponents(args ArgsCoreComponentsHolder) (*coreComponentsHolder, return nil, err } + instance.epochNotifier = forking.NewGenericEpochNotifier() + instance.enableEpochsHandler, err = enablers.NewEnableEpochsHandler(args.EnableEpochsConfig, instance.epochNotifier) + if err != nil { + return nil, err + } + var nodesSetup config.NodesConfig err = core.LoadJsonFile(&nodesSetup, args.NodesSetupPath) if err != nil { return nil, err } + + startTime := computeStartTimeBaseOnInitialRound( + args.GenesisTime, + instance.enableEpochsHandler, + args.InitialRound, + args.RoundDurationInMs, + ) + if nodesSetup.StartTime == 0 { + if instance.enableEpochsHandler.IsFlagEnabledInEpoch(common.SupernovaFlag, 0) { + nodesSetup.StartTime = startTime.UnixMilli() + } else { + nodesSetup.StartTime = startTime.Unix() + } + } + instance.genesisNodesSetup, err = sharding.NewNodesSetup(nodesSetup, instance.chainParametersHandler, instance.addressPubKeyConverter, instance.validatorPubKeyConverter, args.NumShards) if err != nil { return nil, err } + instance.genesisTime = time.Unix(instance.genesisNodesSetup.GetStartTime(), 0) + + log.Debug("chain simulator start time", + "startTime", instance.genesisNodesSetup.GetStartTime(), + "nodesSetup start time", nodesSetup.StartTime, + ) + + instance.roundNotifier = forking.NewGenericRoundNotifier() + instance.enableRoundsHandler, err = enablers.NewEnableRoundsHandler(args.RoundsConfig, instance.roundNotifier) + if err != nil { + return nil, err + } + roundDuration := time.Millisecond * time.Duration(instance.genesisNodesSetup.GetRoundDuration()) - instance.roundHandler = NewManualRoundHandler(instance.genesisNodesSetup.GetStartTime(), roundDuration, args.InitialRound) + supernovaRound := instance.enableRoundsHandler.GetActivationRound(common.SupernovaRoundFlag) - instance.wasmVMChangeLocker = &sync.RWMutex{} - instance.txVersionChecker = versioning.NewTxVersionChecker(args.Config.GeneralSettings.MinTransactionVersion) - instance.epochNotifier = forking.NewGenericEpochNotifier() - instance.enableEpochsHandler, err = enablers.NewEnableEpochsHandler(args.EnableEpochsConfig, instance.epochNotifier) + startTime = instance.genesisTime + instance.supernovaGenesisTime = startTime.Add(time.Duration(supernovaRound-uint64(args.InitialRound)) * roundDuration) + if instance.supernovaGenesisTime.Before(instance.genesisTime) { + instance.supernovaGenesisTime = instance.genesisTime + } + + supernovaActivationEpoch := instance.enableEpochsHandler.GetActivationEpoch(common.SupernovaFlag) + chainParamsForSupernova, err := instance.chainParametersHandler.ChainParametersForEpoch(supernovaActivationEpoch) if err != nil { return nil, err } + argsManualRoundHandler := ArgManualRoundHandler{ + EnableRoundsHandler: instance.enableRoundsHandler, + GenesisTimeStamp: instance.genesisTime.UnixMilli(), + SupernovaGenesisTimeStamp: instance.supernovaGenesisTime.UnixMilli(), + RoundDuration: roundDuration, + SupernovaRoundDuration: time.Duration(chainParamsForSupernova.RoundDuration) * time.Millisecond, + InitialRound: args.InitialRound, + SupernovaStartRound: int64(supernovaRound), + } + instance.roundHandler, err = NewManualRoundHandler(argsManualRoundHandler) + if err != nil { + return nil, err + } + + instance.wasmVMChangeLocker = &sync.RWMutex{} + instance.txVersionChecker = versioning.NewTxVersionChecker(args.Config.GeneralSettings.MinTransactionVersion) + argsEconomicsHandler := economics.ArgsNewEconomicsData{ - GeneralConfig: &args.Config, + ChainParamsHandler: instance.chainParametersHandler, TxVersionChecker: instance.txVersionChecker, Economics: &args.EconomicsConfig, EpochNotifier: instance.epochNotifier, @@ -210,16 +274,15 @@ func CreateCoreComponents(args ArgsCoreComponentsHolder) (*coreComponentsHolder, instance.apiEconomicsData = instance.economicsData instance.ratingsData, err = rating.NewRatingsData(rating.RatingsDataArg{ - EpochNotifier: instance.epochNotifier, - Config: args.RatingConfig, - ChainParametersHolder: instance.chainParametersHandler, - RoundDurationMilliseconds: args.RoundDurationInMs, + EpochNotifier: instance.epochNotifier, + Config: args.RatingConfig, + ChainParametersHolder: instance.chainParametersHandler, }) if err != nil { return nil, err } - instance.rater, err = rating.NewBlockSigningRater(instance.ratingsData) + instance.rater, err = rating.NewBlockSigningRater(instance.ratingsData, instance.enableEpochsHandler) if err != nil { return nil, err } @@ -234,14 +297,7 @@ func CreateCoreComponents(args ArgsCoreComponentsHolder) (*coreComponentsHolder, return nil, err } - instance.roundNotifier = forking.NewGenericRoundNotifier() - instance.enableRoundsHandler, err = enablers.NewEnableRoundsHandler(args.RoundsConfig, instance.roundNotifier) - if err != nil { - return nil, err - } - instance.chanStopNodeProcess = args.ChanStopNodeProcess - instance.genesisTime = time.Unix(instance.genesisNodesSetup.GetStartTime(), 0) instance.chainID = args.Config.GeneralSettings.ChainID instance.minTransactionVersion = args.Config.GeneralSettings.MinTransactionVersion instance.encodedAddressLen, err = computeEncodedAddressLen(instance.addressPubKeyConverter) @@ -264,11 +320,50 @@ func CreateCoreComponents(args ArgsCoreComponentsHolder) (*coreComponentsHolder, } instance.fieldsSizeChecker = fchecker + instance.processConfigsHandler, err = commonConfigs.NewProcessConfigsHandler( + args.Config.GeneralSettings.ProcessConfigsByEpoch, + args.Config.GeneralSettings.ProcessConfigsByRound, + instance.roundNotifier, + ) + if err != nil { + return nil, err + } + + instance.epochStartConfigsHandler, err = commonConfigs.NewCommonConfigsHandler( + args.Config.GeneralSettings.EpochStartConfigsByEpoch, + args.Config.GeneralSettings.EpochStartConfigsByRound, + args.Config.GeneralSettings.ConsensusConfigsByEpoch, + ) + if err != nil { + return nil, err + } + + instance.antifloodConfigsHandler, err = commonConfigs.NewAntifloodConfigsHandler( + args.Config.Antiflood, + instance.roundNotifier, + ) + if err != nil { + return nil, err + } + instance.collectClosableComponents() return instance, nil } +func computeStartTimeBaseOnInitialRound( + genesisTime time.Time, + enableEpochsHandler common.EnableEpochsHandler, + initialRound int64, + roundDurationMs uint64, +) time.Time { + if enableEpochsHandler.IsFlagEnabledInEpoch(common.SupernovaFlag, 0) { + return genesisTime.Add(time.Millisecond * time.Duration(int64(roundDurationMs)*initialRound)) + } + + return genesisTime.Add(time.Second * time.Duration(int64(roundDurationMs/1000)*initialRound)) +} + func computeEncodedAddressLen(converter core.PubkeyConverter) (uint32, error) { emptyAddress := bytes.Repeat([]byte{0}, converter.Len()) encodedEmptyAddress, err := converter.Encode(emptyAddress) @@ -386,7 +481,7 @@ func (c *coreComponentsHolder) EpochNotifier() process.EpochNotifier { } // EnableRoundsHandler will return the enable rounds handler -func (c *coreComponentsHolder) EnableRoundsHandler() process.EnableRoundsHandler { +func (c *coreComponentsHolder) EnableRoundsHandler() common.EnableRoundsHandler { return c.enableRoundsHandler } @@ -410,6 +505,11 @@ func (c *coreComponentsHolder) GenesisTime() time.Time { return c.genesisTime } +// SupernovaGenesisTime will return the genesis time +func (c *coreComponentsHolder) SupernovaGenesisTime() time.Time { + return c.supernovaGenesisTime +} + // ChainID will return the chain id func (c *coreComponentsHolder) ChainID() string { return c.chainID @@ -475,6 +575,26 @@ func (c *coreComponentsHolder) EpochChangeGracePeriodHandler() common.EpochChang return c.epochChangeGracePeriodHandler } +// ProcessConfigsHandler returns process configs handler component +func (c *coreComponentsHolder) ProcessConfigsHandler() common.ProcessConfigsHandler { + return c.processConfigsHandler +} + +// CommonConfigsHandler returns epoch start configs handler component +func (c *coreComponentsHolder) CommonConfigsHandler() common.CommonConfigsHandler { + return c.epochStartConfigsHandler +} + +// AntifloodConfigsHandler returns epoch start configs handler component +func (c *coreComponentsHolder) AntifloodConfigsHandler() common.AntifloodConfigsHandler { + return c.antifloodConfigsHandler +} + +// ClosingNodeStarted returns the atomic bool that signals if the closing of the node has started +func (c *coreComponentsHolder) ClosingNodeStarted() *atomic.Bool { + return c.closingNodeStarted +} + func (c *coreComponentsHolder) collectClosableComponents() { c.closeHandler.AddComponent(c.alarmScheduler) c.closeHandler.AddComponent(c.syncTimer) diff --git a/node/chainSimulator/components/coreComponents_test.go b/node/chainSimulator/components/coreComponents_test.go index eaf1d5ccd23..c89a6d0f429 100644 --- a/node/chainSimulator/components/coreComponents_test.go +++ b/node/chainSimulator/components/coreComponents_test.go @@ -7,6 +7,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data/endProcess" "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/components" "github.com/multiversx/mx-chain-go/config" @@ -52,14 +53,47 @@ func createArgsCoreComponentsHolder() ArgsCoreComponentsHolder { MetachainMinNumNodes: 1, Hysteresis: 0, Adaptivity: false, + RoundsPerEpoch: 200, + MinRoundsBetweenEpochs: 20, }, }, EpochChangeGracePeriodByEpoch: []config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}, + ProcessConfigsByEpoch: []config.ProcessConfigByEpoch{{ + EnableEpoch: 0, + MaxMetaNoncesBehind: 15, + MaxMetaNoncesBehindForGlobalStuck: 30, + MaxShardNoncesBehind: 15, + }}, + ProcessConfigsByRound: []config.ProcessConfigByRound{ + { + EnableRound: 0, + MaxRoundsWithoutNewBlockReceived: 10, + MaxRoundsWithoutCommittedBlock: 10, + MaxRoundsToKeepUnprocessedTransactions: 50, + MaxRoundsToKeepUnprocessedMiniBlocks: 50, + NumFloodingRoundsSlowReacting: 20, + NumFloodingRoundsFastReacting: 30, + NumFloodingRoundsOutOfSpecs: 40, + MaxConsecutiveRoundsOfRatingDecrease: 600, + MaxBlockProcessingTimeMs: 1000, + NumHeadersToRequestInAdvance: 10, + }, + }, + EpochStartConfigsByEpoch: []config.EpochStartConfigByEpoch{ + {EnableEpoch: 0, GracePeriodRounds: 25, ExtraDelayForRequestBlockInfoInMilliseconds: 3000}, + }, + EpochStartConfigsByRound: []config.EpochStartConfigByRound{ + {EnableRound: 0, MaxRoundsWithoutCommittedStartInEpochBlock: 50}, + }, + ConsensusConfigsByEpoch: []config.ConsensusConfigByEpoch{ + {EnableEpoch: 0, NumRoundsToWaitBeforeSignalingChronologyStuck: 10}, + }, }, Hardfork: config.HardforkConfig{ PublicKeyToListenFrom: components.DummyPk, }, - EpochStartConfig: config.EpochStartConfig{RoundsPerEpoch: 14400}, + EpochStartConfig: config.EpochStartConfig{}, + Antiflood: testscommon.GetDefaultAntifloodConfig(), }, EnableEpochsConfig: config.EnableEpochs{}, RoundsConfig: config.RoundConfig{ @@ -67,6 +101,9 @@ func createArgsCoreComponentsHolder() ArgsCoreComponentsHolder { "DisableAsyncCallV1": { Round: "18446744073709551615", }, + "SupernovaEnableRound": { + Round: "9999999", + }, }, }, EconomicsConfig: config.EconomicsConfig{ diff --git a/node/chainSimulator/components/cryptoComponents.go b/node/chainSimulator/components/cryptoComponents.go index 3fcd7e205b7..2c044ea9aa7 100644 --- a/node/chainSimulator/components/cryptoComponents.go +++ b/node/chainSimulator/components/cryptoComponents.go @@ -7,6 +7,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" crypto "github.com/multiversx/mx-chain-crypto-go" "github.com/multiversx/mx-chain-crypto-go/signing/disabled/singlesig" + "github.com/multiversx/mx-chain-go/common" cryptoCommon "github.com/multiversx/mx-chain-go/common/crypto" "github.com/multiversx/mx-chain-go/config" @@ -24,6 +25,7 @@ type ArgsCryptoComponentsHolder struct { CoreComponentsHolder factory.CoreComponentsHolder AllValidatorKeysPemFileName string BypassTxSignatureCheck bool + BypassBlockSignatureCheck bool } type cryptoComponentsHolder struct { @@ -95,7 +97,11 @@ func CreateCryptoComponents(args ArgsCryptoComponentsHolder) (*cryptoComponentsH instance.p2pPublicKey = managedCryptoComponents.P2pPublicKey() instance.p2pPrivateKey = managedCryptoComponents.P2pPrivateKey() instance.p2pSingleSigner = managedCryptoComponents.P2pSingleSigner() - instance.blockSigner = managedCryptoComponents.BlockSigner() + if args.BypassBlockSignatureCheck { + instance.blockSigner = &singlesig.DisabledSingleSig{} + } else { + instance.blockSigner = managedCryptoComponents.BlockSigner() + } instance.multiSignerContainer = managedCryptoComponents.MultiSignerContainer() instance.peerSignatureHandler = managedCryptoComponents.PeerSignatureHandler() @@ -175,7 +181,7 @@ func (c *cryptoComponentsHolder) MultiSignerContainer() cryptoCommon.MultiSigner } // GetMultiSigner will return the multi signer by epoch -func (c *cryptoComponentsHolder) GetMultiSigner(epoch uint32) (crypto.MultiSigner, error) { +func (c *cryptoComponentsHolder) GetMultiSigner(epoch uint32) (crypto.MultiSignerV2, error) { return c.MultiSignerContainer().GetMultiSigner(epoch) } diff --git a/node/chainSimulator/components/cryptoComponents_test.go b/node/chainSimulator/components/cryptoComponents_test.go index 3bba81c9b91..37ce81b83f0 100644 --- a/node/chainSimulator/components/cryptoComponents_test.go +++ b/node/chainSimulator/components/cryptoComponents_test.go @@ -1,13 +1,16 @@ package components import ( + "fmt" "testing" "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-go/common" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/factory" - "github.com/stretchr/testify/require" ) func createArgsCryptoComponentsHolder() ArgsCryptoComponentsHolder { @@ -45,9 +48,13 @@ func createArgsCryptoComponentsHolder() ArgsCryptoComponentsHolder { }, } }, + ProcessConfigsHandlerCalled: func() common.ProcessConfigsHandler { + return &testscommon.ProcessConfigsHandlerStub{} + }, }, AllValidatorKeysPemFileName: "allValidatorKeys.pem", BypassTxSignatureCheck: true, + BypassBlockSignatureCheck: false, } } @@ -76,6 +83,19 @@ func TestCreateCryptoComponents(t *testing.T) { require.Nil(t, comp.Create()) require.Nil(t, comp.Close()) }) + t.Run("should work with bypass blocks sig check", func(t *testing.T) { + t.Parallel() + + args := createArgsCryptoComponentsHolder() + args.BypassBlockSignatureCheck = true + comp, err := CreateCryptoComponents(args) + require.NoError(t, err) + require.NotNil(t, comp) + require.Equal(t, "*singlesig.DisabledSingleSig", fmt.Sprintf("%T", comp.blockSigner)) + + require.Nil(t, comp.Create()) + require.Nil(t, comp.Close()) + }) t.Run("NewCryptoComponentsFactory failure should error", func(t *testing.T) { t.Parallel() diff --git a/node/chainSimulator/components/manualRoundHandler.go b/node/chainSimulator/components/manualRoundHandler.go index 4f951930058..f5a3accd792 100644 --- a/node/chainSimulator/components/manualRoundHandler.go +++ b/node/chainSimulator/components/manualRoundHandler.go @@ -1,25 +1,53 @@ package components import ( + "errors" "sync/atomic" "time" + + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/common" ) +var errNilEnableRoundsHandler = errors.New("nil enable rounds handler") + +// ArgManualRoundHandler is the DTO used to create a new instance of manualRoundHandler +type ArgManualRoundHandler struct { + EnableRoundsHandler common.EnableRoundsHandler + GenesisTimeStamp int64 + SupernovaGenesisTimeStamp int64 + RoundDuration time.Duration + SupernovaRoundDuration time.Duration + InitialRound int64 + SupernovaStartRound int64 +} + type manualRoundHandler struct { - index int64 - genesisTimeStamp int64 - roundDuration time.Duration - initialRound int64 + index int64 + genesisTimeStamp int64 + supernovaGenesisTimeStamp int64 + roundDuration time.Duration + supernovaRoundDuration time.Duration + initialRound int64 + enableRoundsHandler common.EnableRoundsHandler + supernovaStartRound int64 } // NewManualRoundHandler returns a manual round handler instance -func NewManualRoundHandler(genesisTimeStamp int64, roundDuration time.Duration, initialRound int64) *manualRoundHandler { - return &manualRoundHandler{ - genesisTimeStamp: genesisTimeStamp, - roundDuration: roundDuration, - index: initialRound, - initialRound: initialRound, +func NewManualRoundHandler(args ArgManualRoundHandler) (*manualRoundHandler, error) { + if check.IfNil(args.EnableRoundsHandler) { + return nil, errNilEnableRoundsHandler } + return &manualRoundHandler{ + genesisTimeStamp: args.GenesisTimeStamp, + roundDuration: args.RoundDuration, + index: args.InitialRound, + initialRound: args.InitialRound, + enableRoundsHandler: args.EnableRoundsHandler, + supernovaGenesisTimeStamp: args.SupernovaGenesisTimeStamp, + supernovaRoundDuration: args.SupernovaRoundDuration, + supernovaStartRound: args.SupernovaStartRound, + }, nil } // IncrementIndex will increment the current round index @@ -48,16 +76,33 @@ func (handler *manualRoundHandler) UpdateRound(_ time.Time, _ time.Time) { // TimeStamp returns the time based of the genesis timestamp and the current round func (handler *manualRoundHandler) TimeStamp() time.Time { - rounds := atomic.LoadInt64(&handler.index) - timeFromGenesis := handler.roundDuration * time.Duration(rounds) - timestamp := time.Unix(handler.genesisTimeStamp, 0).Add(timeFromGenesis) - timestamp = time.Unix(timestamp.Unix()-int64(handler.roundDuration.Seconds())*handler.initialRound, 0) + roundIndex := atomic.LoadInt64(&handler.index) + if !handler.isSupernovaActive(roundIndex) { + timeFromGenesis := handler.roundDuration * time.Duration(roundIndex) + timestamp := time.UnixMilli(handler.genesisTimeStamp).Add(timeFromGenesis) + timestamp = time.UnixMilli(timestamp.UnixMilli() - handler.roundDuration.Milliseconds()*handler.initialRound) + return timestamp + } + + supernovaGenesisRound := handler.enableRoundsHandler.GetActivationRound(common.SupernovaRoundFlag) + roundsDiff := uint64(roundIndex) - supernovaGenesisRound + timeFromSupernovaGenesis := handler.supernovaRoundDuration * time.Duration(roundsDiff) + timestamp := time.UnixMilli(handler.supernovaGenesisTimeStamp).Add(timeFromSupernovaGenesis) return timestamp } +func (handler *manualRoundHandler) isSupernovaActive(roundIndex int64) bool { + return handler.enableRoundsHandler.IsFlagEnabledInRound(common.SupernovaRoundFlag, uint64(roundIndex)) +} + // TimeDuration returns the provided time duration for this instance func (handler *manualRoundHandler) TimeDuration() time.Duration { - return handler.roundDuration + roundIndex := atomic.LoadInt64(&handler.index) + if !handler.isSupernovaActive(roundIndex) { + return handler.roundDuration + } + + return handler.supernovaRoundDuration } // RemainingTime returns the max time as the start time is not taken into account @@ -65,6 +110,20 @@ func (handler *manualRoundHandler) RemainingTime(_ time.Time, maxTime time.Durat return maxTime } +// GetTimeStampForRound returns the timestamp for round +func (handler *manualRoundHandler) GetTimeStampForRound(round uint64) uint64 { + if int64(round) <= handler.supernovaStartRound { + genesisTimestamp := time.UnixMilli(handler.genesisTimeStamp) + roundTimeStampMs := genesisTimestamp.Add(time.Duration(int64(round)-handler.initialRound) * handler.roundDuration).UnixMilli() + return uint64(roundTimeStampMs) + } + + genesisTimestamp := time.UnixMilli(handler.supernovaGenesisTimeStamp) + roundTimeStampMs := genesisTimestamp.Add(time.Duration(int64(round)-handler.supernovaStartRound) * handler.supernovaRoundDuration).UnixMilli() + return uint64(roundTimeStampMs) + +} + // IsInterfaceNil returns true if there is no value under the interface func (handler *manualRoundHandler) IsInterfaceNil() bool { return handler == nil diff --git a/node/chainSimulator/components/manualRoundHandler_test.go b/node/chainSimulator/components/manualRoundHandler_test.go index 8a866d6ccec..d8f202a370c 100644 --- a/node/chainSimulator/components/manualRoundHandler_test.go +++ b/node/chainSimulator/components/manualRoundHandler_test.go @@ -4,14 +4,41 @@ import ( "testing" "time" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/testscommon" "github.com/stretchr/testify/require" ) +func getMockArgManulRoundHandler() ArgManualRoundHandler { + return ArgManualRoundHandler{ + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + GenesisTimeStamp: 100, + SupernovaGenesisTimeStamp: 120, + RoundDuration: time.Second, + SupernovaRoundDuration: time.Millisecond * 500, + InitialRound: 0, + } +} + func TestNewManualRoundHandler(t *testing.T) { t.Parallel() - handler := NewManualRoundHandler(100, time.Second, 0) - require.NotNil(t, handler) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + handler, err := NewManualRoundHandler(getMockArgManulRoundHandler()) + require.NotNil(t, handler) + require.NoError(t, err) + }) + t.Run("nil EnableRoundsHandler", func(t *testing.T) { + t.Parallel() + + args := getMockArgManulRoundHandler() + args.EnableRoundsHandler = nil + handler, err := NewManualRoundHandler(args) + require.Nil(t, handler) + require.Equal(t, errNilEnableRoundsHandler, err) + }) } func TestManualRoundHandler_IsInterfaceNil(t *testing.T) { @@ -20,7 +47,7 @@ func TestManualRoundHandler_IsInterfaceNil(t *testing.T) { var handler *manualRoundHandler require.True(t, handler.IsInterfaceNil()) - handler = NewManualRoundHandler(100, time.Second, 0) + handler, _ = NewManualRoundHandler(getMockArgManulRoundHandler()) require.False(t, handler.IsInterfaceNil()) } @@ -30,15 +57,62 @@ func TestManualRoundHandler_Operations(t *testing.T) { genesisTime := time.Now() providedIndex := int64(0) providedRoundDuration := time.Second - handler := NewManualRoundHandler(genesisTime.Unix(), providedRoundDuration, providedIndex) + args := getMockArgManulRoundHandler() + args.GenesisTimeStamp = genesisTime.UnixMilli() + args.RoundDuration = providedRoundDuration + args.InitialRound = providedIndex + supernovaRound := uint64(20) + args.SupernovaGenesisTimeStamp = genesisTime.Add(time.Duration(supernovaRound) * args.RoundDuration).UnixMilli() + args.EnableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return round == supernovaRound + }, + GetActivationRoundCalled: func(flag common.EnableRoundFlag) uint64 { + return supernovaRound + }, + } + + handler, _ := NewManualRoundHandler(args) require.Equal(t, providedIndex, handler.Index()) handler.IncrementIndex() require.Equal(t, providedIndex+1, handler.Index()) - expectedTimestamp := time.Unix(handler.genesisTimeStamp, 0).Add(providedRoundDuration) + expectedTimestamp := time.UnixMilli(handler.genesisTimeStamp).Add(providedRoundDuration) require.Equal(t, expectedTimestamp, handler.TimeStamp()) require.Equal(t, providedRoundDuration, handler.TimeDuration()) providedMaxTime := time.Minute require.Equal(t, providedMaxTime, handler.RemainingTime(time.Now(), providedMaxTime)) require.False(t, handler.BeforeGenesis()) handler.UpdateRound(time.Now(), time.Now()) // for coverage only + + // revert the handler.IncrementIndex() call + handler.RevertOneRound() + + // force supernova round + for i := uint64(0); i < supernovaRound; i++ { + handler.IncrementIndex() + } + require.Equal(t, args.SupernovaGenesisTimeStamp, handler.TimeStamp().UnixMilli()) + require.Equal(t, args.SupernovaRoundDuration.Milliseconds(), handler.TimeDuration().Milliseconds()) +} + +func TestManualRoundHandler_GetTimeStampForRound(t *testing.T) { + t.Parallel() + + arg := ArgManualRoundHandler{ + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + GenesisTimeStamp: 1000, + SupernovaGenesisTimeStamp: 1500, + RoundDuration: time.Second, + SupernovaRoundDuration: time.Millisecond * 500, + InitialRound: 0, + SupernovaStartRound: 100, + } + handler, err := NewManualRoundHandler(arg) + require.NoError(t, err) + + res := handler.GetTimeStampForRound(10) + require.Equal(t, uint64(11000), res) + + res = handler.GetTimeStampForRound(120) + require.Equal(t, uint64(11500), res) } diff --git a/node/chainSimulator/components/memoryComponents.go b/node/chainSimulator/components/memoryComponents.go index 7cd6dcd92bd..6efbde24a03 100644 --- a/node/chainSimulator/components/memoryComponents.go +++ b/node/chainSimulator/components/memoryComponents.go @@ -34,8 +34,8 @@ func CreateMemUnitForTries() storage.Storer { func (store *trieStorage) SetEpochForPutOperation(_ uint32) { } -// GetFromOldEpochsWithoutAddingToCache tries to get directly the key -func (store *trieStorage) GetFromOldEpochsWithoutAddingToCache(key []byte, _ uint32) ([]byte, core.OptionalUint32, error) { +// GetWithoutAddingToCache tries to get directly the key +func (store *trieStorage) GetWithoutAddingToCache(key []byte, _ uint32) ([]byte, core.OptionalUint32, error) { value, err := store.Get(key) return value, core.OptionalUint32{}, err diff --git a/node/chainSimulator/components/memoryComponents_test.go b/node/chainSimulator/components/memoryComponents_test.go index 37bf0d4ba7e..199fcd5dbbe 100644 --- a/node/chainSimulator/components/memoryComponents_test.go +++ b/node/chainSimulator/components/memoryComponents_test.go @@ -22,7 +22,7 @@ func TestCreateMemUnitForTries(t *testing.T) { require.NoError(t, memUnit.PutInEpoch(key, data, 0)) require.NoError(t, memUnit.PutInEpochWithoutCache(key, data, 0)) - value, _, err := memUnit.GetFromOldEpochsWithoutAddingToCache(key, 10) + value, _, err := memUnit.GetWithoutAddingToCache(key, 10) require.NoError(t, err) require.Equal(t, data, value) diff --git a/node/chainSimulator/components/nodeFacade.go b/node/chainSimulator/components/nodeFacade.go index f8f5c30f9e9..5a2ed05043a 100644 --- a/node/chainSimulator/components/nodeFacade.go +++ b/node/chainSimulator/components/nodeFacade.go @@ -118,13 +118,13 @@ func (node *testOnlyProcessingNode) createFacade(configs config.Configs, apiInte RestAPIServerDebugMode: flagsConfig.EnableRestAPIServerDebugMode, WsAntifloodConfig: configs.GeneralConfig.WebServerAntiflood, FacadeConfig: config.FacadeConfig{ - RestApiInterface: restApiInterface, - PprofEnabled: flagsConfig.EnablePprof, + RestApiInterface: restApiInterface, + PprofEnabled: flagsConfig.EnablePprof, + TxCacheSelectionConfig: configs.GeneralConfig.TxCacheSelection, }, - ApiRoutesConfig: *configs.ApiRoutesConfig, - AccountsState: node.StateComponentsHolder.AccountsAdapter(), - PeerState: node.StateComponentsHolder.PeerAccounts(), - Blockchain: node.DataComponentsHolder.Blockchain(), + ApiRoutesConfig: *configs.ApiRoutesConfig, + AccountsStateAPI: node.StateComponentsHolder.AccountsAdapter(), + Blockchain: node.DataComponentsHolder.Blockchain(), } ef, err := facade.NewNodeFacade(argNodeFacade) @@ -162,6 +162,7 @@ func (node *testOnlyProcessingNode) createHttpServer(configs config.Configs) err } func (node *testOnlyProcessingNode) createMetrics(configs config.Configs) error { + currentChainParameters := node.CoreComponentsHolder.ChainParametersHandler().CurrentChainParameters() err := metrics.InitMetrics( node.StatusCoreComponents.AppStatusHandler(), node.CryptoComponentsHolder.PublicKeyString(), @@ -170,7 +171,7 @@ func (node *testOnlyProcessingNode) createMetrics(configs config.Configs) error node.CoreComponentsHolder.GenesisNodesSetup(), configs.FlagsConfig.Version, configs.EconomicsConfig, - configs.GeneralConfig.EpochStartConfig.RoundsPerEpoch, + currentChainParameters, node.CoreComponentsHolder.MinTransactionVersion(), ) diff --git a/node/chainSimulator/components/processComponents.go b/node/chainSimulator/components/processComponents.go index 5d7e2603beb..4c82f1d9118 100644 --- a/node/chainSimulator/components/processComponents.go +++ b/node/chainSimulator/components/processComponents.go @@ -8,6 +8,8 @@ import ( "time" "github.com/multiversx/mx-chain-core-go/core/partitioning" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/common/forking" "github.com/multiversx/mx-chain-go/common/ordering" @@ -28,7 +30,6 @@ import ( "github.com/multiversx/mx-chain-go/storage/cache" "github.com/multiversx/mx-chain-go/update" "github.com/multiversx/mx-chain-go/update/trigger" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" ) // ArgsProcessComponentsHolder will hold the components needed for process components @@ -70,6 +71,7 @@ type processComponentsHolder struct { epochStartNotifier factory.EpochStartNotifier forkDetector process.ForkDetector blockProcessor process.BlockProcessor + executionManager process.ExecutionManager blackListHandler process.TimeCacher bootStorer process.BootStorer headerSigVerifier process.InterceptedHeaderSigVerifier @@ -101,6 +103,7 @@ type processComponentsHolder struct { sentSignatureTracker process.SentSignaturesTracker epochStartSystemSCProcessor process.EpochStartSystemSCProcessor blockchainHook process.BlockChainHookWithAccountsAdapter + aotSelector process.AOTTransactionSelector managedProcessComponentsCloser io.Closer } @@ -148,6 +151,7 @@ func CreateProcessComponents(args ArgsProcessComponentsHolder) (*processComponen Marshalizer: args.CoreComponents.InternalMarshalizer(), Store: args.DataComponents.StorageService(), Uint64ByteSliceConverter: args.CoreComponents.Uint64ByteSliceConverter(), + DataPool: args.DataComponents.Datapool(), } historyRepositoryFactory, err := dbLookupFactory.NewHistoryRepositoryFactory(historyRepoFactoryArgs) if err != nil { @@ -285,6 +289,8 @@ func CreateProcessComponents(args ArgsProcessComponentsHolder) (*processComponen sentSignatureTracker: managedProcessComponents.SentSignaturesTracker(), epochStartSystemSCProcessor: managedProcessComponents.EpochSystemSCProcessor(), blockchainHook: managedProcessComponents.BlockchainHook(), + aotSelector: managedProcessComponents.AOTSelector(), + executionManager: managedProcessComponents.ExecutionManager(), managedProcessComponentsCloser: managedProcessComponents, } @@ -375,6 +381,11 @@ func (p *processComponentsHolder) BlockProcessor() process.BlockProcessor { return p.blockProcessor } +// ExecutionManager will return the execution manager +func (p *processComponentsHolder) ExecutionManager() process.ExecutionManager { + return p.executionManager +} + // BlackListHandler will return the black list handler func (p *processComponentsHolder) BlackListHandler() process.TimeCacher { return p.blackListHandler @@ -530,6 +541,11 @@ func (p *processComponentsHolder) BlockchainHook() process.BlockChainHookWithAcc return p.blockchainHook } +// AOTSelector returns the AOT transaction selector +func (p *processComponentsHolder) AOTSelector() process.AOTTransactionSelector { + return p.aotSelector +} + // Close will call the Close methods on all inner components func (p *processComponentsHolder) Close() error { return p.managedProcessComponentsCloser.Close() diff --git a/node/chainSimulator/components/processComponents_test.go b/node/chainSimulator/components/processComponents_test.go index 98d33013fa3..b9241ea6396 100644 --- a/node/chainSimulator/components/processComponents_test.go +++ b/node/chainSimulator/components/processComponents_test.go @@ -25,7 +25,9 @@ import ( "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/bootstrapMocks" "github.com/multiversx/mx-chain-go/testscommon/chainParameters" + "github.com/multiversx/mx-chain-go/testscommon/commonmocks" "github.com/multiversx/mx-chain-go/testscommon/components" + consensusMocks "github.com/multiversx/mx-chain-go/testscommon/consensus" "github.com/multiversx/mx-chain-go/testscommon/cryptoMocks" "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" @@ -110,7 +112,8 @@ func createArgsProcessComponentsHolder() ArgsProcessComponentsHolder { MinStakeValue: "1", UnJailValue: "1", MinStepValue: "1", - UnBondPeriod: 0, + UnBondPeriod: 1, + UnBondPeriodSupernova: 2, NumRoundsWithoutBleed: 0, MaximumPercentageToBleed: 0, BleedPercentagePerRound: 0, @@ -177,6 +180,11 @@ func createArgsProcessComponentsHolder() ArgsProcessComponentsHolder { PathHdl: &testscommon.PathManagerStub{}, ProcessStatusHandlerInternal: &testscommon.ProcessStatusHandlerStub{}, EpochChangeGracePeriodHandlerField: gracePeriod, + ChainParametersHandlerField: &chainParameters.ChainParametersHandlerStub{}, + ChainParametersSubscriberField: &commonmocks.ChainParametersNotifierStub{}, + ProcessConfigsHandlerField: testscommon.GetDefaultProcessConfigsHandler(), + CommonConfigsHandlerField: testscommon.GetDefaultCommonConfigsHandler(), + AntifloodConfigsHandlerField: &testscommon.AntifloodConfigsHandlerStub{}, }, CryptoComponents: &mock.CryptoComponentsStub{ BlKeyGen: &cryptoMocks.KeyGenStub{}, @@ -194,6 +202,7 @@ func createArgsProcessComponentsHolder() ArgsProcessComponentsHolder { MsgSigVerifier: &testscommon.MessageSignVerifierMock{}, ManagedPeersHolderField: &testscommon.ManagedPeersHolderStub{}, KeysHandlerField: &testscommon.KeysHandlerStub{}, + SigHandler: &consensusMocks.SigningHandlerStub{}, }, NetworkComponents: &mock.NetworkComponentsStub{ Messenger: &p2pmocks.MessengerStub{}, @@ -230,6 +239,10 @@ func createArgsProcessComponentsHolder() ArgsProcessComponentsHolder { }, }, }, + FeeSettings: config.FeeSettings{ + BlockCapacityOverestimationFactor: 200, + PercentDecreaseLimitsStep: 10, + }, }, ConfigurationPathsHolder: config.ConfigurationPathsHolder{ Genesis: "../../../integrationTests/factory/testdata/genesis.json", @@ -249,7 +262,7 @@ func TestCreateProcessComponents(t *testing.T) { t.Run("should work", func(t *testing.T) { comp, err := CreateProcessComponents(createArgsProcessComponentsHolder()) - require.NoError(t, err) + require.Nil(t, err) require.NotNil(t, comp) require.Nil(t, comp.Create()) @@ -415,6 +428,7 @@ func TestProcessComponentsHolder_Getters(t *testing.T) { require.NotNil(t, comp.AccountsParser()) require.NotNil(t, comp.ReceiptsRepository()) require.NotNil(t, comp.EpochSystemSCProcessor()) + require.NotNil(t, comp.ExecutionManager()) require.Nil(t, comp.CheckSubcomponents()) require.Empty(t, comp.String()) diff --git a/node/chainSimulator/components/stateComponents.go b/node/chainSimulator/components/stateComponents.go index 68aa95e577f..7ab36c2ce2e 100644 --- a/node/chainSimulator/components/stateComponents.go +++ b/node/chainSimulator/components/stateComponents.go @@ -4,6 +4,7 @@ import ( "io" chainData "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" @@ -26,6 +27,7 @@ type stateComponentsHolder struct { peerAccount state.AccountsAdapter accountsAdapter state.AccountsAdapter accountsAdapterAPI state.AccountsAdapter + accountsAdapterProposal state.AccountsAdapter accountsRepository state.AccountsRepository triesContainer common.TriesHolder triesStorageManager map[string]common.StorageManager @@ -68,6 +70,7 @@ func CreateStateComponents(args ArgsStateComponents) (*stateComponentsHolder, er peerAccount: stateComp.PeerAccounts(), accountsAdapter: stateComp.AccountsAdapter(), accountsAdapterAPI: stateComp.AccountsAdapterAPI(), + accountsAdapterProposal: stateComp.AccountsAdapterProposal(), accountsRepository: stateComp.AccountsRepository(), triesContainer: stateComp.TriesContainer(), triesStorageManager: stateComp.TrieStorageManagers(), @@ -92,6 +95,11 @@ func (s *stateComponentsHolder) AccountsAdapterAPI() state.AccountsAdapter { return s.accountsAdapterAPI } +// AccountsAdapterProposal will return accounts adapter for proposal +func (s *stateComponentsHolder) AccountsAdapterProposal() state.AccountsAdapter { + return s.accountsAdapterProposal +} + // AccountsRepository will return accounts repository func (s *stateComponentsHolder) AccountsRepository() state.AccountsRepository { return s.accountsRepository diff --git a/node/chainSimulator/components/stateComponents_test.go b/node/chainSimulator/components/stateComponents_test.go index 5422d2ea352..e791b7d9e73 100644 --- a/node/chainSimulator/components/stateComponents_test.go +++ b/node/chainSimulator/components/stateComponents_test.go @@ -87,6 +87,7 @@ func TestStateComponentsHolder_Getters(t *testing.T) { require.NotNil(t, comp.PeerAccounts()) require.NotNil(t, comp.AccountsAdapter()) + require.NotNil(t, comp.AccountsAdapterProposal()) require.NotNil(t, comp.AccountsAdapterAPI()) require.NotNil(t, comp.AccountsRepository()) require.NotNil(t, comp.TriesContainer()) diff --git a/node/chainSimulator/components/statusComponents.go b/node/chainSimulator/components/statusComponents.go index ab561e25c2c..4b3f5bb6de1 100644 --- a/node/chainSimulator/components/statusComponents.go +++ b/node/chainSimulator/components/statusComponents.go @@ -65,6 +65,8 @@ func CreateStatusComponents(shardID uint32, appStatusHandler core.AppStatusHandl HostDriversArgs: hostDriverArgs, EventNotifierFactoryArgs: &outportFactory.EventNotifierFactoryArgs{}, ElasticIndexerFactoryArgs: makeElasticIndexerArgs(external, coreComponents), + EnableEpochsHandler: coreComponents.EnableEpochsHandler(), + EnableRoundsHandler: coreComponents.EnableRoundsHandler(), }) if err != nil { return nil, err @@ -145,7 +147,6 @@ func makeElasticIndexerArgs(external config.ExternalConfig, coreComponents proce ValidatorPubkeyConverter: coreComponents.ValidatorPubKeyConverter(), EnabledIndexes: elasticSearchConfig.EnabledIndexes, Denomination: 18, - UseKibana: elasticSearchConfig.UseKibana, ImportDB: false, HeaderMarshaller: coreComponents.InternalMarshalizer(), } diff --git a/node/chainSimulator/components/statusComponents_test.go b/node/chainSimulator/components/statusComponents_test.go index 24f3b4595c1..d4638402c7f 100644 --- a/node/chainSimulator/components/statusComponents_test.go +++ b/node/chainSimulator/components/statusComponents_test.go @@ -9,19 +9,29 @@ import ( "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" mxErrors "github.com/multiversx/mx-chain-go/errors" + "github.com/multiversx/mx-chain-go/factory" "github.com/multiversx/mx-chain-go/integrationTests/mock" "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/statusHandler" "github.com/stretchr/testify/require" ) +func createDefaultCoreComponents() factory.CoreComponentsHandler { + return &mock.CoreComponentsStub{ + EnableEpochsHandlerField: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandlerField: &testscommon.EnableRoundsHandlerStub{}, + } +} + func TestCreateStatusComponents(t *testing.T) { t.Parallel() t.Run("should work", func(t *testing.T) { t.Parallel() - comp, err := CreateStatusComponents(0, &statusHandler.AppStatusHandlerStub{}, 5, config.ExternalConfig{}, &mock.CoreComponentsStub{}) + comp, err := CreateStatusComponents(0, &statusHandler.AppStatusHandlerStub{}, 5, config.ExternalConfig{}, createDefaultCoreComponents()) require.NoError(t, err) require.NotNil(t, comp) @@ -31,7 +41,7 @@ func TestCreateStatusComponents(t *testing.T) { t.Run("nil app status handler should error", func(t *testing.T) { t.Parallel() - comp, err := CreateStatusComponents(0, nil, 5, config.ExternalConfig{}, &mock.CoreComponentsStub{}) + comp, err := CreateStatusComponents(0, nil, 5, config.ExternalConfig{}, createDefaultCoreComponents()) require.Equal(t, core.ErrNilAppStatusHandler, err) require.Nil(t, comp) }) @@ -43,7 +53,7 @@ func TestStatusComponentsHolder_IsInterfaceNil(t *testing.T) { var comp *statusComponentsHolder require.True(t, comp.IsInterfaceNil()) - comp, _ = CreateStatusComponents(0, &statusHandler.AppStatusHandlerStub{}, 5, config.ExternalConfig{}, &mock.CoreComponentsStub{}) + comp, _ = CreateStatusComponents(0, &statusHandler.AppStatusHandlerStub{}, 5, config.ExternalConfig{}, createDefaultCoreComponents()) require.False(t, comp.IsInterfaceNil()) require.Nil(t, comp.Close()) } @@ -51,7 +61,7 @@ func TestStatusComponentsHolder_IsInterfaceNil(t *testing.T) { func TestStatusComponentsHolder_Getters(t *testing.T) { t.Parallel() - comp, err := CreateStatusComponents(0, &statusHandler.AppStatusHandlerStub{}, 5, config.ExternalConfig{}, &mock.CoreComponentsStub{}) + comp, err := CreateStatusComponents(0, &statusHandler.AppStatusHandlerStub{}, 5, config.ExternalConfig{}, createDefaultCoreComponents()) require.NoError(t, err) require.NotNil(t, comp.OutportHandler()) @@ -65,7 +75,7 @@ func TestStatusComponentsHolder_Getters(t *testing.T) { func TestStatusComponentsHolder_SetForkDetector(t *testing.T) { t.Parallel() - comp, err := CreateStatusComponents(0, &statusHandler.AppStatusHandlerStub{}, 5, config.ExternalConfig{}, &mock.CoreComponentsStub{}) + comp, err := CreateStatusComponents(0, &statusHandler.AppStatusHandlerStub{}, 5, config.ExternalConfig{}, createDefaultCoreComponents()) require.NoError(t, err) err = comp.SetForkDetector(nil) @@ -83,7 +93,7 @@ func TestStatusComponentsHolder_StartPolling(t *testing.T) { t.Run("nil fork detector should error", func(t *testing.T) { t.Parallel() - comp, err := CreateStatusComponents(0, &statusHandler.AppStatusHandlerStub{}, 5, config.ExternalConfig{}, &mock.CoreComponentsStub{}) + comp, err := CreateStatusComponents(0, &statusHandler.AppStatusHandlerStub{}, 5, config.ExternalConfig{}, createDefaultCoreComponents()) require.NoError(t, err) err = comp.StartPolling() @@ -92,7 +102,7 @@ func TestStatusComponentsHolder_StartPolling(t *testing.T) { t.Run("NewAppStatusPolling failure should error", func(t *testing.T) { t.Parallel() - comp, err := CreateStatusComponents(0, &statusHandler.AppStatusHandlerStub{}, 0, config.ExternalConfig{}, &mock.CoreComponentsStub{}) + comp, err := CreateStatusComponents(0, &statusHandler.AppStatusHandlerStub{}, 0, config.ExternalConfig{}, createDefaultCoreComponents()) require.NoError(t, err) err = comp.SetForkDetector(&mock.ForkDetectorStub{}) @@ -114,7 +124,7 @@ func TestStatusComponentsHolder_StartPolling(t *testing.T) { wasSetUInt64ValueCalled.SetValue(true) }, } - comp, err := CreateStatusComponents(0, appStatusHandler, providedStatusPollingIntervalSec, config.ExternalConfig{}, &mock.CoreComponentsStub{}) + comp, err := CreateStatusComponents(0, appStatusHandler, providedStatusPollingIntervalSec, config.ExternalConfig{}, createDefaultCoreComponents()) require.NoError(t, err) forkDetector := &mock.ForkDetectorStub{ diff --git a/node/chainSimulator/components/statusCoreComponents_test.go b/node/chainSimulator/components/statusCoreComponents_test.go index 3040a863ca9..e14801b5f58 100644 --- a/node/chainSimulator/components/statusCoreComponents_test.go +++ b/node/chainSimulator/components/statusCoreComponents_test.go @@ -3,15 +3,18 @@ package components import ( "testing" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/factory" "github.com/multiversx/mx-chain-go/factory/mock" mockTests "github.com/multiversx/mx-chain-go/integrationTests/mock" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/commonmocks" "github.com/multiversx/mx-chain-go/testscommon/components" "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/genesisMocks" - "github.com/stretchr/testify/require" ) func createArgs() (config.Configs, factory.CoreComponentsHolder) { @@ -42,11 +45,14 @@ func createArgs() (config.Configs, factory.CoreComponentsHolder) { } return cfg, &mock.CoreComponentsMock{ - EconomicsHandler: &economicsmocks.EconomicsHandlerMock{}, - IntMarsh: &testscommon.MarshallerStub{}, - UInt64ByteSliceConv: &mockTests.Uint64ByteSliceConverterMock{}, - NodesConfig: &genesisMocks.NodesSetupStub{}, - RatingsConfig: &testscommon.RatingsInfoMock{}, + EconomicsHandler: &economicsmocks.EconomicsHandlerMock{}, + IntMarsh: &testscommon.MarshallerStub{}, + UInt64ByteSliceConv: &mockTests.Uint64ByteSliceConverterMock{}, + NodesConfig: &genesisMocks.NodesSetupStub{}, + RatingsConfig: &testscommon.RatingsInfoMock{}, + ChainParametersSubscriberField: &commonmocks.ChainParametersNotifierStub{}, + EnableEpochsHandlerField: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandlerField: &testscommon.EnableRoundsHandlerStub{}, } } diff --git a/node/chainSimulator/components/storageService.go b/node/chainSimulator/components/storageService.go index 3a3f42f28b6..98b56917cca 100644 --- a/node/chainSimulator/components/storageService.go +++ b/node/chainSimulator/components/storageService.go @@ -30,6 +30,8 @@ func CreateStore(numOfShards uint32) dataRetriever.StorageService { store.AddStorer(dataRetriever.ResultsHashesByTxHashUnit, CreateMemUnit()) store.AddStorer(dataRetriever.TrieEpochRootHashUnit, CreateMemUnit()) store.AddStorer(dataRetriever.ProofsUnit, CreateMemUnit()) + store.AddStorer(dataRetriever.ExecutionResultsUnit, CreateMemUnit()) + store.AddStorer(dataRetriever.StateAccessesUnit, CreateMemUnit()) for i := uint32(0); i < numOfShards; i++ { hdrNonceHashDataUnit := dataRetriever.ShardHdrNonceHashDataUnit + dataRetriever.UnitType(i) diff --git a/node/chainSimulator/components/storageService_test.go b/node/chainSimulator/components/storageService_test.go index c6df371fc1d..d5bfe3d2243 100644 --- a/node/chainSimulator/components/storageService_test.go +++ b/node/chainSimulator/components/storageService_test.go @@ -38,7 +38,9 @@ func TestCreateStore(t *testing.T) { dataRetriever.TrieEpochRootHashUnit, dataRetriever.ShardHdrNonceHashDataUnit, dataRetriever.ProofsUnit, + dataRetriever.ExecutionResultsUnit, dataRetriever.UnitType(101), // shard 2 + dataRetriever.StateAccessesUnit, } all := store.GetAllStorers() diff --git a/node/chainSimulator/components/testOnlyProcessingNode.go b/node/chainSimulator/components/testOnlyProcessingNode.go index 8e6148f40f5..3360e56a6bc 100644 --- a/node/chainSimulator/components/testOnlyProcessingNode.go +++ b/node/chainSimulator/components/testOnlyProcessingNode.go @@ -6,14 +6,22 @@ import ( "errors" "fmt" "math/big" + "time" + + "github.com/multiversx/mx-chain-core-go/core" + chainData "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/data/endProcess" "github.com/multiversx/mx-chain-go/api/shared" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/consensus" "github.com/multiversx/mx-chain-go/consensus/spos/sposFactory" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/dataRetriever/blockchain" dataRetrieverFactory "github.com/multiversx/mx-chain-go/dataRetriever/factory" + "github.com/multiversx/mx-chain-go/debug/handler" "github.com/multiversx/mx-chain-go/facade" "github.com/multiversx/mx-chain-go/factory" bootstrapComp "github.com/multiversx/mx-chain-go/factory/bootstrap" @@ -24,10 +32,6 @@ import ( "github.com/multiversx/mx-chain-go/sharding" "github.com/multiversx/mx-chain-go/sharding/nodesCoordinator" "github.com/multiversx/mx-chain-go/state" - - "github.com/multiversx/mx-chain-core-go/core" - chainData "github.com/multiversx/mx-chain-core-go/data" - "github.com/multiversx/mx-chain-core-go/data/endProcess" ) // ArgsTestOnlyProcessingNode represents the DTO struct for the NewTestOnlyProcessingNode constructor function @@ -45,12 +49,14 @@ type ArgsTestOnlyProcessingNode struct { NumShards uint32 ShardIDStr string BypassTxSignatureCheck bool + BypassBlockSignatureCheck bool MinNodesPerShard uint32 ConsensusGroupSize uint32 MinNodesMeta uint32 MetaChainConsensusGroupSize uint32 RoundDurationInMillis uint64 VmQueryDelayAfterStartInMs uint64 + GenesisTime time.Time } type testOnlyProcessingNode struct { @@ -107,6 +113,7 @@ func NewTestOnlyProcessingNode(args ArgsTestOnlyProcessingNode) (*testOnlyProces MetaChainConsensusGroupSize: args.MetaChainConsensusGroupSize, RoundDurationInMs: args.RoundDurationInMillis, RatingConfig: *args.Configs.RatingsConfig, + GenesisTime: args.GenesisTime, }) if err != nil { return nil, err @@ -123,6 +130,7 @@ func NewTestOnlyProcessingNode(args ArgsTestOnlyProcessingNode) (*testOnlyProces Preferences: *args.Configs.PreferencesConfig, CoreComponentsHolder: instance.CoreComponentsHolder, BypassTxSignatureCheck: args.BypassTxSignatureCheck, + BypassBlockSignatureCheck: args.BypassBlockSignatureCheck, AllValidatorKeysPemFileName: args.Configs.ConfigurationPathsHolder.AllValidatorKeys, }) if err != nil { @@ -260,11 +268,41 @@ func NewTestOnlyProcessingNode(args ArgsTestOnlyProcessingNode) (*testOnlyProces return nil, err } + err = instance.createInterceptorDebugHandler(args.Configs) + if err != nil { + return nil, err + } + instance.collectClosableComponents(args.APIInterface) return instance, nil } +func (node *testOnlyProcessingNode) createInterceptorDebugHandler(configs config.Configs) error { + debugHandler, err := handler.NewInterceptorDebugHandler(configs.GeneralConfig.Debug.InterceptorResolver, node.CoreComponentsHolder.SyncTimer()) + if err != nil { + return err + } + + node.CoreComponentsHolder.EpochStartNotifierWithConfirm().RegisterHandler(debugHandler.EpochStartEventHandler()) + + var errFound error + node.ProcessComponentsHolder.InterceptorsContainer().Iterate(func(key string, interceptor process.Interceptor) bool { + err = interceptor.SetInterceptedDebugHandler(debugHandler) + if err != nil { + errFound = err + return false + } + + return true + }) + if errFound != nil { + return fmt.Errorf("%w while setting up debugger on interceptors", errFound) + } + + return nil +} + func (node *testOnlyProcessingNode) createBlockChain(selfShardID uint32) error { var err error if selfShardID == core.MetachainShardId { @@ -281,6 +319,7 @@ func (node *testOnlyProcessingNode) createNodesCoordinator(pref config.Preferenc node.CoreComponentsHolder.GenesisNodesSetup(), generalConfig.EpochStartConfig, node.CoreComponentsHolder.ChanStopNodeProcess(), + node.CoreComponentsHolder.ChainParametersHandler(), ) if err != nil { return err @@ -335,6 +374,9 @@ func (node *testOnlyProcessingNode) createBroadcastMessenger() error { node.ProcessComponentsHolder.ShardCoordinator(), node.CryptoComponentsHolder.PeerSignatureHandler(), node.DataComponentsHolder.Datapool().Headers(), + node.DataComponentsHolder.Datapool().Headers(), + node.DataComponentsHolder.Datapool().Proofs(), + node.CoreComponentsHolder.EnableEpochsHandler(), node.ProcessComponentsHolder.InterceptorsContainer(), node.CoreComponentsHolder.AlarmScheduler(), node.CryptoComponentsHolder.KeysHandler(), @@ -398,7 +440,7 @@ func (node *testOnlyProcessingNode) GetStatusCoreComponents() factory.StatusCore return node.StatusCoreComponents } -// NetworkComponents will return the network components +// GetNetworkComponents will return the network components func (node *testOnlyProcessingNode) GetNetworkComponents() factory.NetworkComponentsHolder { return node.NetworkComponentsHolder } @@ -500,10 +542,50 @@ func (node *testOnlyProcessingNode) SetStateForAddress(address []byte, addressSt return err } - _, err = accountsAdapter.Commit() + newRootHash, err := accountsAdapter.Commit() + node.setBlockchainRootHashIfSupernovaIsActive(newRootHash) + return err } +func (node *testOnlyProcessingNode) setBlockchainRootHashIfSupernovaIsActive( + rootHash []byte, +) { + if !node.CoreComponentsHolder.EnableRoundsHandler().IsFlagEnabled(common.SupernovaRoundFlag) { + return + } + + header := node.ChainHandler.GetLastExecutedBlockHeader() + _, hash, _ := node.ChainHandler.GetLastExecutedBlockInfo() + node.ChainHandler.SetLastExecutedBlockHeaderAndRootHash(header, hash, rootHash) + + lastExecutionResult := node.ChainHandler.GetLastExecutionResult() + + metaResult, isMeta := lastExecutionResult.(*block.MetaExecutionResult) + if isMeta { + metaResult.ExecutionResult.BaseExecutionResult.RootHash = rootHash + node.ChainHandler.SetLastExecutionResult(metaResult) + return + } + + shardResult, isShard := lastExecutionResult.(*block.ExecutionResult) + if isShard { + shardResult.BaseExecutionResult.RootHash = rootHash + node.ChainHandler.SetLastExecutionResult(shardResult) + return + } + + updatedLastExecutionResult := &block.BaseExecutionResult{ + HeaderHash: lastExecutionResult.GetHeaderHash(), + HeaderNonce: lastExecutionResult.GetHeaderNonce(), + HeaderRound: lastExecutionResult.GetHeaderRound(), + HeaderEpoch: lastExecutionResult.GetHeaderEpoch(), + RootHash: rootHash, + GasUsed: lastExecutionResult.GetGasUsed(), + } + node.ChainHandler.SetLastExecutionResult(updatedLastExecutionResult) +} + // RemoveAccount will remove the account for the given address func (node *testOnlyProcessingNode) RemoveAccount(address []byte) error { accountsAdapter := node.StateComponentsHolder.AccountsAdapter() diff --git a/node/chainSimulator/components/testOnlyProcessingNode_test.go b/node/chainSimulator/components/testOnlyProcessingNode_test.go index 801c11585e9..65f93c45c18 100644 --- a/node/chainSimulator/components/testOnlyProcessingNode_test.go +++ b/node/chainSimulator/components/testOnlyProcessingNode_test.go @@ -2,38 +2,42 @@ package components import ( "errors" - "github.com/multiversx/mx-chain-go/node/chainSimulator/components/heartbeat" "math/big" "strings" "testing" "time" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/data/endProcess" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/node/chainSimulator/components/api" + "github.com/multiversx/mx-chain-go/node/chainSimulator/components/heartbeat" "github.com/multiversx/mx-chain-go/node/chainSimulator/configs" "github.com/multiversx/mx-chain-go/node/chainSimulator/dtos" + "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/factory" "github.com/multiversx/mx-chain-go/testscommon/state" - - "github.com/multiversx/mx-chain-core-go/data/endProcess" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) var expectedErr = errors.New("expected error") func createMockArgsTestOnlyProcessingNode(t *testing.T) ArgsTestOnlyProcessingNode { outputConfigs, err := configs.CreateChainSimulatorConfigs(configs.ArgsChainSimulatorConfigs{ - NumOfShards: 3, - OriginalConfigsPath: "../../../cmd/node/config/", - GenesisTimeStamp: 0, - RoundDurationInMillis: 6000, - TempDir: t.TempDir(), - MinNodesPerShard: 1, - MetaChainMinNodes: 1, - ConsensusGroupSize: 1, - MetaChainConsensusGroupSize: 1, + NumOfShards: 3, + OriginalConfigsPath: "../../../cmd/node/config/", + RoundDurationInMillis: 6000, + SupernovaRoundDurationInMillis: 600, + TempDir: t.TempDir(), + MinNodesPerShard: 1, + MetaChainMinNodes: 1, + ConsensusGroupSize: 1, + MetaChainConsensusGroupSize: 1, }) require.Nil(t, err) @@ -68,6 +72,8 @@ func TestNewTestOnlyProcessingNode(t *testing.T) { }) t.Run("try commit a block", func(t *testing.T) { args := createMockArgsTestOnlyProcessingNode(t) + genesisTime := time.Now() + args.GenesisTime = genesisTime node, err := NewTestOnlyProcessingNode(args) assert.Nil(t, err) assert.NotNil(t, node) @@ -75,9 +81,29 @@ func TestNewTestOnlyProcessingNode(t *testing.T) { newHeader, err := node.ProcessComponentsHolder.BlockProcessor().CreateNewHeader(1, 1) assert.Nil(t, err) + rootHash, err := node.GetStateComponents().AccountsAdapter().RootHash() + if err != nil { + log.Error("node.GetStateComponents().AccountsAdapter().RootHash()", "err", err) + } + + genesisHeader := node.GetDataComponents().Blockchain().GetGenesisHeader() + err = genesisHeader.SetRootHash(rootHash) + require.Nil(t, err) + + err = node.GetDataComponents().Blockchain().SetGenesisHeader(genesisHeader) + require.Nil(t, err) + + err = node.GetDataComponents().Datapool().Transactions().OnExecutedBlock(genesisHeader, rootHash) + require.Nil(t, err) + err = newHeader.SetPrevHash(node.ChainHandler.GetGenesisHeaderHash()) assert.Nil(t, err) + // Set timestamp before processing (pre-supernova: header stores seconds) + expectedTimestampMs := node.GetCoreComponents().RoundHandler().GetTimeStampForRound(1) + err = newHeader.SetTimeStamp(expectedTimestampMs / 1000) + assert.Nil(t, err) + header, block, err := node.ProcessComponentsHolder.BlockProcessor().CreateBlock(newHeader, func() bool { return true }) @@ -473,3 +499,107 @@ func TestTestOnlyProcessingNode_Getters(t *testing.T) { require.NotNil(t, node.GetFacadeHandler()) require.NotNil(t, node.GetStatusCoreComponents()) } + +func TestTestOnlyProcessingNode_SetBlockchainRootHashWithMetaExecutionResult(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + args := createMockArgsTestOnlyProcessingNode(t) + args.Configs.RoundConfig.RoundActivations[string(common.SupernovaRoundFlag)] = config.ActivationRoundByName{ + Round: "0", + Options: []string{}, + } + node, err := NewTestOnlyProcessingNode(args) + require.NoError(t, err) + + expectedRootHash := []byte("new-root-hash") + + metaResult := &block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("header-hash"), + HeaderNonce: 1, + HeaderRound: 1, + RootHash: []byte("initial-root-hash"), + }, + }, + } + + var capturedResult data.BaseExecutionResultHandler + chainHandlerStub := &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return nil + }, + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return 0, nil, nil + }, + SetLastExecutedBlockHeaderAndRootHashCalled: func(header data.HeaderHandler, blockHash []byte, rootHash []byte) { + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return metaResult + }, + SetLastExecutionResultCalled: func(result data.BaseExecutionResultHandler) { + capturedResult = result + }, + } + node.ChainHandler = chainHandlerStub + + node.setBlockchainRootHashIfSupernovaIsActive(expectedRootHash) + + require.NotNil(t, capturedResult) + capturedMetaResult, ok := capturedResult.(*block.MetaExecutionResult) + require.True(t, ok) + require.Equal(t, expectedRootHash, capturedMetaResult.ExecutionResult.BaseExecutionResult.RootHash) +} + +func TestTestOnlyProcessingNode_SetBlockchainRootHashWithShardExecutionResult(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + args := createMockArgsTestOnlyProcessingNode(t) + args.Configs.RoundConfig.RoundActivations[string(common.SupernovaRoundFlag)] = config.ActivationRoundByName{ + Round: "0", + Options: []string{}, + } + node, err := NewTestOnlyProcessingNode(args) + require.NoError(t, err) + + expectedRootHash := []byte("new-root-hash") + + shardResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("header-hash"), + HeaderNonce: 1, + HeaderRound: 1, + RootHash: []byte("initial-root-hash"), + }, + } + + var capturedResult data.BaseExecutionResultHandler + chainHandlerStub := &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return nil + }, + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return 0, nil, nil + }, + SetLastExecutedBlockHeaderAndRootHashCalled: func(header data.HeaderHandler, blockHash []byte, rootHash []byte) { + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return shardResult + }, + SetLastExecutionResultCalled: func(result data.BaseExecutionResultHandler) { + capturedResult = result + }, + } + node.ChainHandler = chainHandlerStub + + node.setBlockchainRootHashIfSupernovaIsActive(expectedRootHash) + + require.NotNil(t, capturedResult) + capturedShardResult, ok := capturedResult.(*block.ExecutionResult) + require.True(t, ok) + require.Equal(t, expectedRootHash, capturedShardResult.BaseExecutionResult.RootHash) +} diff --git a/node/chainSimulator/configs/configs.go b/node/chainSimulator/configs/configs.go index e3ea5958dcc..bc140aca7bf 100644 --- a/node/chainSimulator/configs/configs.go +++ b/node/chainSimulator/configs/configs.go @@ -5,12 +5,14 @@ import ( "encoding/hex" "encoding/json" "encoding/pem" + "fmt" "math/big" "os" "path" "strconv" "strings" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/common/factory" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/genesis/data" @@ -39,25 +41,29 @@ const ( // ChainSimulatorConsensusGroupSize defines the size of the consensus group for chain simulator ChainSimulatorConsensusGroupSize = 1 allValidatorsPemFileName = "allValidatorsKeys.pem" + + numRoundsAfterSupernovaEnableEpoch = 5 ) // ArgsChainSimulatorConfigs holds all the components needed to create the chain simulator configs type ArgsChainSimulatorConfigs struct { - NumOfShards uint32 - OriginalConfigsPath string - GenesisTimeStamp int64 - RoundDurationInMillis uint64 - TempDir string - MinNodesPerShard uint32 - ConsensusGroupSize uint32 - MetaChainMinNodes uint32 - MetaChainConsensusGroupSize uint32 - Hysteresis float32 - InitialEpoch uint32 - RoundsPerEpoch core.OptionalUint64 - NumNodesWaitingListShard uint32 - NumNodesWaitingListMeta uint32 - AlterConfigsFunction func(cfg *config.Configs) + NumOfShards uint32 + OriginalConfigsPath string + RoundDurationInMillis uint64 + SupernovaRoundDurationInMillis uint64 + TempDir string + MinNodesPerShard uint32 + ConsensusGroupSize uint32 + MetaChainMinNodes uint32 + MetaChainConsensusGroupSize uint32 + Hysteresis float32 + InitialEpoch uint32 + InitialRound int64 + RoundsPerEpoch core.OptionalUint64 + SupernovaRoundsPerEpoch core.OptionalUint64 + NumNodesWaitingListShard uint32 + NumNodesWaitingListMeta uint32 + AlterConfigsFunction func(cfg *config.Configs) } // ArgsConfigsSimulator holds the configs for the chain simulator @@ -121,10 +127,12 @@ func CreateChainSimulatorConfigs(args ArgsChainSimulatorConfigs) (*ArgsConfigsSi configs.GeneralConfig.EpochStartConfig.ExtraDelayForRequestBlockInfoInMilliseconds = 1 configs.GeneralConfig.EpochStartConfig.GenesisEpoch = args.InitialEpoch - configs.GeneralConfig.EpochStartConfig.MinRoundsBetweenEpochs = 1 if args.RoundsPerEpoch.HasValue { - configs.GeneralConfig.EpochStartConfig.RoundsPerEpoch = int64(args.RoundsPerEpoch.Value) + for idx := 0; idx < len(configs.GeneralConfig.GeneralSettings.ChainParametersByEpoch); idx++ { + configs.GeneralConfig.GeneralSettings.ChainParametersByEpoch[idx].RoundsPerEpoch = int64(args.RoundsPerEpoch.Value) + configs.GeneralConfig.GeneralSettings.ChainParametersByEpoch[idx].MinRoundsBetweenEpochs = 1 + } } gasScheduleName, err := GetLatestGasScheduleFilename(configs.ConfigurationPathsHolder.GasScheduleDirectoryName) @@ -132,6 +140,9 @@ func CreateChainSimulatorConfigs(args ArgsChainSimulatorConfigs) (*ArgsConfigsSi return nil, err } + // disable caching, as message deduplication will break the flow when blocks are generated very fast + configs.GeneralConfig.InterceptedDataVerifier.EnableCaching = false + updateConfigsChainParameters(args, configs) node.ApplyArchCustomConfigs(configs) @@ -141,6 +152,8 @@ func CreateChainSimulatorConfigs(args ArgsChainSimulatorConfigs) (*ArgsConfigsSi configs.GeneralConfig.GeneralSettings.ChainParametersByEpoch[1].EnableEpoch = configs.EpochConfig.EnableEpochs.AndromedaEnableEpoch } + updateSupernovaConfigs(configs, args) + return &ArgsConfigsSimulator{ Configs: *configs, ValidatorsPrivateKeys: privateKeys, @@ -149,6 +162,69 @@ func CreateChainSimulatorConfigs(args ArgsChainSimulatorConfigs) (*ArgsConfigsSi }, nil } +func updateSupernovaConfigs(configs *config.Configs, args ArgsChainSimulatorConfigs) { + supernovaEpoch := configs.EpochConfig.EnableEpochs.SupernovaEnableEpoch // may be altered by AlterConfigFunction + configs.GeneralConfig.GeneralSettings.ChainParametersByEpoch[2].EnableEpoch = supernovaEpoch + + if args.SupernovaRoundsPerEpoch.HasValue { + configs.GeneralConfig.GeneralSettings.ChainParametersByEpoch[2].RoundsPerEpoch = int64(args.SupernovaRoundsPerEpoch.Value) + } + + // update supernova round duration + configs.GeneralConfig.GeneralSettings.ChainParametersByEpoch[2].EnableEpoch = configs.EpochConfig.EnableEpochs.SupernovaEnableEpoch + + if args.SupernovaRoundDurationInMillis > 0 { + configs.GeneralConfig.GeneralSettings.ChainParametersByEpoch[2].RoundDuration = args.SupernovaRoundDurationInMillis + } + isSupernovaFromGenesis := configs.EpochConfig.EnableEpochs.SupernovaEnableEpoch == 0 + if isSupernovaFromGenesis { + // if supernova is from genesis, remove other ChainParametersByEpoch entries + configs.GeneralConfig.GeneralSettings.ChainParametersByEpoch = []config.ChainParametersByEpochConfig{ + configs.GeneralConfig.GeneralSettings.ChainParametersByEpoch[2], + } + } + + if !args.RoundsPerEpoch.HasValue { + return + } + + hasCorrectActivationRound := false + diff := int(supernovaEpoch) - int(args.InitialEpoch) + if diff >= 0 { + + correctRoundActivationForSupernova := args.InitialRound + int64(diff)*int64(args.RoundsPerEpoch.Value) + numRoundsAfterSupernovaEnableEpoch + supernovaActivationRound, _ := strconv.ParseInt(configs.RoundConfig.RoundActivations[string(common.SupernovaRoundFlag)].Round, 10, 64) + if supernovaActivationRound > correctRoundActivationForSupernova { + diffRounds := supernovaActivationRound - correctRoundActivationForSupernova + diffIsGreaterThanAnEpoch := diffRounds > int64(args.SupernovaRoundsPerEpoch.Value)-numRoundsAfterSupernovaEnableEpoch + if !diffIsGreaterThanAnEpoch { + hasCorrectActivationRound = true + } + } + } + + // update supernova round for the new rounds per epoch config + newRoundsPerEpoch := args.RoundsPerEpoch.Value + newSupernovaRound := uint64(supernovaEpoch)*newRoundsPerEpoch + numRoundsAfterSupernovaEnableEpoch + if isSupernovaFromGenesis { + // if supernova is from genesis, the round should be 0 as well + newSupernovaRound = 0 + } + + if !hasCorrectActivationRound || newSupernovaRound == 0 { + oldOptions := configs.RoundConfig.RoundActivations[string(common.SupernovaRoundFlag)].Options + configs.RoundConfig.RoundActivations[string(common.SupernovaRoundFlag)] = config.ActivationRoundByName{ + Round: fmt.Sprintf("%d", newSupernovaRound), + Options: oldOptions, + } + + } + + configs.GeneralConfig.Versions.VersionsByEpochs[2].StartEpoch = configs.EpochConfig.EnableEpochs.SupernovaEnableEpoch + supernovaRound, _ := strconv.ParseUint(configs.RoundConfig.RoundActivations[string(common.SupernovaRoundFlag)].Round, 10, 64) + configs.GeneralConfig.Versions.VersionsByEpochs[2].StartRound = supernovaRound +} + func updateConfigsChainParameters(args ArgsChainSimulatorConfigs, configs *config.Configs) { for idx := 0; idx < len(configs.GeneralConfig.GeneralSettings.ChainParametersByEpoch); idx++ { configs.GeneralConfig.GeneralSettings.ChainParametersByEpoch[idx].ShardMinNumNodes = args.MinNodesPerShard @@ -186,10 +262,14 @@ func SetMaxNumberOfNodesInConfigs(cfg *config.Configs, eligibleNodes uint32, wai // SetQuickJailRatingConfig will set the rating config in a way that leads to rapid jailing of a node func SetQuickJailRatingConfig(cfg *config.Configs) { - cfg.RatingsConfig.ShardChain.RatingStepsByEpoch[0].ConsecutiveMissedBlocksPenalty = 100 - cfg.RatingsConfig.ShardChain.RatingStepsByEpoch[0].HoursToMaxRatingFromStartRating = 1 - cfg.RatingsConfig.MetaChain.RatingStepsByEpoch[0].ConsecutiveMissedBlocksPenalty = 100 - cfg.RatingsConfig.MetaChain.RatingStepsByEpoch[0].HoursToMaxRatingFromStartRating = 1 + for i := range cfg.RatingsConfig.ShardChain.RatingStepsByEpoch { + cfg.RatingsConfig.ShardChain.RatingStepsByEpoch[i].ConsecutiveMissedBlocksPenalty = 100 + cfg.RatingsConfig.ShardChain.RatingStepsByEpoch[i].HoursToMaxRatingFromStartRating = 1 + } + for i := range cfg.RatingsConfig.MetaChain.RatingStepsByEpoch { + cfg.RatingsConfig.MetaChain.RatingStepsByEpoch[i].ConsecutiveMissedBlocksPenalty = 100 + cfg.RatingsConfig.MetaChain.RatingStepsByEpoch[i].HoursToMaxRatingFromStartRating = 1 + } } // SetStakingV4ActivationEpochs configures activation epochs for Staking V4. @@ -206,6 +286,8 @@ func SetStakingV4ActivationEpochs(cfg *config.Configs, initialEpoch uint32) { // Set the MaxNodesChange enable epoch for index 2 cfg.EpochConfig.EnableEpochs.MaxNodesChangeEnableEpoch[2].EpochEnable = initialEpoch + 2 cfg.SystemSCConfig.StakingSystemSCConfig.NodeLimitPercentage = 1 + + cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = initialEpoch + 10 } func generateGenesisFile(args ArgsChainSimulatorConfigs, configs *config.Configs) (*dtos.InitialWalletKeys, error) { @@ -296,7 +378,6 @@ func generateValidatorsKeyAndUpdateFiles( } nodes.RoundDuration = args.RoundDurationInMillis - nodes.StartTime = args.GenesisTimeStamp nodes.Hysteresis = 0 diff --git a/node/chainSimulator/configs/configs_test.go b/node/chainSimulator/configs/configs_test.go index 03e464c5f36..f1601230c17 100644 --- a/node/chainSimulator/configs/configs_test.go +++ b/node/chainSimulator/configs/configs_test.go @@ -3,7 +3,10 @@ package configs import ( "testing" + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/integrationTests/realcomponents" + "github.com/multiversx/mx-chain-go/testscommon" "github.com/stretchr/testify/require" ) @@ -14,18 +17,42 @@ func TestNewProcessorRunnerChainArguments(t *testing.T) { } outputConfig, err := CreateChainSimulatorConfigs(ArgsChainSimulatorConfigs{ - NumOfShards: 3, - OriginalConfigsPath: "../../../cmd/node/config", - RoundDurationInMillis: 6000, - GenesisTimeStamp: 0, - TempDir: t.TempDir(), - MetaChainMinNodes: 1, - MinNodesPerShard: 1, - ConsensusGroupSize: 1, - MetaChainConsensusGroupSize: 1, + NumOfShards: 3, + OriginalConfigsPath: "../../../cmd/node/config", + RoundDurationInMillis: 6000, + SupernovaRoundDurationInMillis: 600, + TempDir: t.TempDir(), + MetaChainMinNodes: 1, + MinNodesPerShard: 1, + ConsensusGroupSize: 1, + MetaChainConsensusGroupSize: 1, }) require.Nil(t, err) pr := realcomponents.NewProcessorRunner(t, outputConfig.Configs) pr.Close(t) } + +func TestUpdateSupernovaConfigs(t *testing.T) { + t.Parallel() + + configs, err := testscommon.CreateTestConfigs(t.TempDir(), "../../../cmd/node/config") + require.Nil(t, err) + + chainSimulatorCfg := ArgsChainSimulatorConfigs{ + RoundsPerEpoch: core.OptionalUint64{ + Value: 20, + HasValue: true, + }, + SupernovaRoundsPerEpoch: core.OptionalUint64{ + Value: 200, + HasValue: true, + }, + SupernovaRoundDurationInMillis: 600, + } + + updateSupernovaConfigs(configs, chainSimulatorCfg) + require.Equal(t, uint64(600), configs.GeneralConfig.GeneralSettings.ChainParametersByEpoch[2].RoundDuration) + require.Equal(t, configs.EpochConfig.EnableEpochs.SupernovaEnableEpoch, configs.GeneralConfig.GeneralSettings.ChainParametersByEpoch[2].EnableEpoch) + require.Equal(t, "45", configs.RoundConfig.RoundActivations[string(common.SupernovaRoundFlag)].Round) +} diff --git a/node/chainSimulator/dtos/pair.go b/node/chainSimulator/dtos/pair.go new file mode 100644 index 00000000000..4660ade7907 --- /dev/null +++ b/node/chainSimulator/dtos/pair.go @@ -0,0 +1,12 @@ +package dtos + +import "github.com/multiversx/mx-chain-core-go/data" + +// BroadcastData holds data to be broadcasted +type BroadcastData struct { + Header data.HeaderHandler + LeaderKey []byte + Proof data.HeaderProofHandler + MiniBlocksBytes map[uint32][]byte + TransactionsBytes map[string][][]byte +} diff --git a/node/chainSimulator/interface.go b/node/chainSimulator/interface.go index 0c93ad65b1f..314b7e787ad 100644 --- a/node/chainSimulator/interface.go +++ b/node/chainSimulator/interface.go @@ -10,7 +10,7 @@ import ( // ChainHandler defines what a chain handler should be able to do type ChainHandler interface { IncrementRound() - CreateNewBlock() error + CreateNewBlock() (*dtos.BroadcastData, error) IsInterfaceNil() bool } diff --git a/node/chainSimulator/process/processor.go b/node/chainSimulator/process/processor.go index 50305440e76..9612c4cc22e 100644 --- a/node/chainSimulator/process/processor.go +++ b/node/chainSimulator/process/processor.go @@ -7,33 +7,46 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" dataBlock "github.com/multiversx/mx-chain-core-go/data/block" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/common" heartbeatData "github.com/multiversx/mx-chain-go/heartbeat/data" "github.com/multiversx/mx-chain-go/node/chainSimulator/configs" + "github.com/multiversx/mx-chain-go/node/chainSimulator/dtos" + "github.com/multiversx/mx-chain-go/process/asyncExecution/cache" "github.com/multiversx/mx-chain-go/sharding/nodesCoordinator" - logger "github.com/multiversx/mx-chain-logger-go" ) var log = logger.GetOrCreate("process-block") +const defaultCreateBlockTimePercent = 0.25 + type manualRoundHandler interface { IncrementIndex() } type blocksCreator struct { - nodeHandler NodeHandler - monitor HeartbeatMonitorWithSet + nodeHandler NodeHandler + monitor HeartbeatMonitorWithSet + createBlockMaxTimePercent float64 + bypassCreateBlockTimeCheck bool } // NewBlocksCreator will create a new instance of blocksCreator -func NewBlocksCreator(nodeHandler NodeHandler, monitor HeartbeatMonitorWithSet) (*blocksCreator, error) { +func NewBlocksCreator(nodeHandler NodeHandler, monitor HeartbeatMonitorWithSet, createBlockHaveTimePercent float64, bypassHaveTime bool) (*blocksCreator, error) { if check.IfNil(nodeHandler) { return nil, ErrNilNodeHandler } + // If not set, set the default time percentage of the round duration + if createBlockHaveTimePercent == 0 { + createBlockHaveTimePercent = defaultCreateBlockTimePercent + } return &blocksCreator{ - nodeHandler: nodeHandler, - monitor: monitor, + nodeHandler: nodeHandler, + monitor: monitor, + createBlockMaxTimePercent: createBlockHaveTimePercent, + bypassCreateBlockTimeCheck: bypassHaveTime, }, nil } @@ -44,10 +57,43 @@ func (creator *blocksCreator) IncrementRound() { manual.IncrementIndex() creator.nodeHandler.GetStatusCoreComponents().AppStatusHandler().SetUInt64Value(common.MetricCurrentRound, uint64(roundHandler.Index())) + creator.nodeHandler.GetStatusCoreComponents().AppStatusHandler().SetUInt64Value(common.MetricRoundDuration, uint64(roundHandler.TimeDuration().Milliseconds())) +} + +func (creator *blocksCreator) createHeaderBasedOnRound(round uint64, nonce uint64) (data.HeaderHandler, error) { + coreComponents := creator.nodeHandler.GetCoreComponents() + processComponents := creator.nodeHandler.GetProcessComponents() + + if coreComponents.EnableRoundsHandler().IsFlagEnabledInRound(common.SupernovaRoundFlag, round) { + return processComponents.BlockProcessor().CreateNewHeaderProposal(round, nonce) + } + + return processComponents.BlockProcessor().CreateNewHeader(round, nonce) +} + +func (creator *blocksCreator) createBlock(header data.HeaderHandler) (data.HeaderHandler, data.BodyHandler, error) { + processComponents := creator.nodeHandler.GetProcessComponents() + haveTime := func() bool { + return true + } + + if header.IsHeaderV3() { + if !creator.bypassCreateBlockTimeCheck { + roundDuration := creator.nodeHandler.GetCoreComponents().RoundHandler().TimeDuration() + allowedDuration := time.Duration(float64(roundDuration) * creator.createBlockMaxTimePercent) + startTime := time.Now() + haveTime = func() bool { + return time.Since(startTime) < allowedDuration + } + } + return processComponents.BlockProcessor().CreateBlockProposal(header, haveTime) + } + + return processComponents.BlockProcessor().CreateBlock(header, haveTime) } // CreateNewBlock creates and process a new block -func (creator *blocksCreator) CreateNewBlock() error { +func (creator *blocksCreator) CreateNewBlock() (*dtos.BroadcastData, error) { processComponents := creator.nodeHandler.GetProcessComponents() cryptoComponents := creator.nodeHandler.GetCryptoComponents() coreComponents := creator.nodeHandler.GetCoreComponents() @@ -55,43 +101,55 @@ func (creator *blocksCreator) CreateNewBlock() error { nonce, _, prevHash, prevRandSeed, epoch, prevHeader := creator.getPreviousHeaderData() round := coreComponents.RoundHandler().Index() - newHeader, err := bp.CreateNewHeader(uint64(round), nonce+1) + + newHeader, err := creator.createHeaderBasedOnRound(uint64(round), nonce+1) if err != nil { - return err + return nil, err } shardID := creator.nodeHandler.GetShardCoordinator().SelfId() err = newHeader.SetShardID(shardID) if err != nil { - return err + return nil, err } err = newHeader.SetPrevHash(prevHash) if err != nil { - return err + return nil, err } err = newHeader.SetPrevRandSeed(prevRandSeed) if err != nil { - return err + return nil, err } err = newHeader.SetChainID([]byte(configs.ChainID)) if err != nil { - return err + return nil, err } headerCreationTime := coreComponents.RoundHandler().TimeStamp() - err = newHeader.SetTimeStamp(uint64(headerCreationTime.Unix())) + + headerCreationTimeStamp := headerCreationTime.Unix() + if coreComponents.EnableEpochsHandler().IsFlagEnabledInEpoch(common.SupernovaFlag, newHeader.GetEpoch()) { + headerCreationTimeStamp = headerCreationTime.UnixMilli() + } + + err = newHeader.SetTimeStamp(uint64(headerCreationTimeStamp)) if err != nil { - return err + return nil, err } leader, validators, err := processComponents.NodesCoordinator().ComputeConsensusGroup(prevRandSeed, newHeader.GetRound(), shardID, epoch) if err != nil { - return err + return nil, err } + log.Debug("Leader in current block", + "shardID", newHeader.GetShardID(), + "round", newHeader.GetRound(), + "leader", leader.PubKey()) + pubKeyBitmap := GeneratePubKeyBitmap(len(validators)) for idx, validator := range validators { isManaged := cryptoComponents.KeysHandler().IsKeyManagedByCurrentNode(validator.PubKey()) @@ -101,13 +159,15 @@ func (creator *blocksCreator) CreateNewBlock() error { err = UnsetBitInBitmap(idx, pubKeyBitmap) if err != nil { - return err + return nil, err } } - err = newHeader.SetPubKeysBitmap(pubKeyBitmap) - if err != nil { - return err + if !newHeader.IsHeaderV3() { + err = newHeader.SetPubKeysBitmap(pubKeyBitmap) + if err != nil { + return nil, err + } } isManaged := cryptoComponents.KeysHandler().IsKeyManagedByCurrentNode(leader.PubKey()) @@ -115,26 +175,24 @@ func (creator *blocksCreator) CreateNewBlock() error { log.Debug("cannot propose block - leader bls key is missing", "leader key", leader.PubKey(), "shard", creator.nodeHandler.GetShardCoordinator().SelfId()) - return nil + return nil, nil } signingHandler := cryptoComponents.ConsensusSigningHandler() randSeed, err := signingHandler.CreateSignatureForPublicKey(newHeader.GetPrevRandSeed(), leader.PubKey()) if err != nil { - return err + return nil, err } err = newHeader.SetRandSeed(randSeed) if err != nil { - return err + return nil, err } enableEpochHandler := coreComponents.EnableEpochsHandler() - header, block, err := bp.CreateBlock(newHeader, func() bool { - return true - }) + header, block, err := creator.createBlock(newHeader) if err != nil { - return err + return nil, err } prevHeaderStartOfEpoch := false @@ -145,45 +203,82 @@ func (creator *blocksCreator) CreateNewBlock() error { creator.updatePeerShardMapper(header.GetEpoch()) } - headerProof, err := creator.ApplySignaturesAndGetProof(header, prevHeader, enableEpochHandler, validators, leader, pubKeyBitmap) + headerHash, err := core.CalculateHash(creator.nodeHandler.GetCoreComponents().InternalMarshalizer(), creator.nodeHandler.GetCoreComponents().Hasher(), header) if err != nil { - return err + return nil, err } - err = bp.CommitBlock(header, block) - if err != nil { - return err + if newHeader.IsHeaderV3() { + err = creator.setHeaderSignatures(header, leader.PubKey(), validators, pubKeyBitmap) + if err != nil { + return nil, err + } + + headerOutput, err := common.PrettifyStruct(header) + if err == nil { + log.Debug("Proposed header sent", "header", headerOutput) + } + + err = creator.nodeHandler.GetProcessComponents().BlockProcessor().VerifyBlockProposal(header, block, func() time.Duration { + return time.Second + }) + if err != nil { + return nil, err + } + + headerHash, err = core.CalculateHash(creator.nodeHandler.GetCoreComponents().InternalMarshalizer(), creator.nodeHandler.GetCoreComponents().Hasher(), header) + if err != nil { + return nil, err + } + + pair := cache.HeaderBodyPair{ + Header: header, + Body: block, + HeaderHash: headerHash, + } + err = creator.nodeHandler.GetProcessComponents().ExecutionManager().AddPairForExecution(pair) + if err != nil { + return nil, err + } } - err = creator.setHeartBeat(header) + headerProof, err := creator.ApplySignaturesAndGetProof(header, prevHeader, enableEpochHandler, validators, leader, pubKeyBitmap) if err != nil { - return err + return nil, err } - miniBlocks, transactions, err := bp.MarshalizedDataToBroadcast(header, block) - if err != nil { - return err + var miniBlocks map[uint32][]byte + var transactions map[string][][]byte + if header.IsHeaderV3() { + miniBlocks, transactions, err = bp.MarshalizedDataToBroadcast(headerHash, header, block) + if err != nil { + return nil, err + } } - messenger := creator.nodeHandler.GetBroadcastMessenger() - err = messenger.BroadcastHeader(header, leader.PubKey()) + err = bp.CommitBlock(header, block) if err != nil { - return err + return nil, err } - if !check.IfNil(headerProof) { - err = messenger.BroadcastEquivalentProof(headerProof, leader.PubKey()) + if !header.IsHeaderV3() { + miniBlocks, transactions, err = bp.MarshalizedDataToBroadcast(headerHash, header, block) if err != nil { - return err + return nil, err } } - err = messenger.BroadcastMiniBlocks(miniBlocks, leader.PubKey()) + err = creator.setHeartBeat(header) if err != nil { - return err + return nil, err } - - return messenger.BroadcastTransactions(transactions, leader.PubKey()) + return &dtos.BroadcastData{ + Header: header, + LeaderKey: leader.PubKey(), + Proof: headerProof, + MiniBlocksBytes: miniBlocks, + TransactionsBytes: transactions, + }, nil } func (creator *blocksCreator) updatePeerShardMapper( @@ -221,9 +316,16 @@ func (creator *blocksCreator) ApplySignaturesAndGetProof( ) (*dataBlock.HeaderProof, error) { nilPrevHeader := check.IfNil(prevHeader) - err := creator.setHeaderSignatures(header, leader.PubKey(), validators) - if err != nil { - return nil, err + correctPubKeyBitmap := header.GetPubKeysBitmap() + if header.IsHeaderV3() { + correctPubKeyBitmap = pubKeyBitmap + } + + if !header.IsHeaderV3() { + err := creator.setHeaderSignatures(header, leader.PubKey(), validators, correctPubKeyBitmap) + if err != nil { + return nil, err + } } coreComponents := creator.nodeHandler.GetCoreComponents() @@ -235,7 +337,8 @@ func (creator *blocksCreator) ApplySignaturesAndGetProof( } pubKeys := extractValidatorPubKeys(validators) - newHeaderSig, err := creator.generateAggregatedSignature(headerHash, header.GetEpoch(), header.GetPubKeysBitmap(), pubKeys) + + newHeaderSig, err := creator.generateAggregatedSignature(headerHash, header.GetEpoch(), correctPubKeyBitmap, pubKeys) if err != nil { return nil, err } @@ -322,6 +425,7 @@ func (creator *blocksCreator) setHeaderSignatures( header data.HeaderHandler, blsKeyBytes []byte, validators []nodesCoordinator.Validator, + correctPubKeyBitmap []byte, ) error { headerClone := header.ShallowClone() _ = headerClone.SetPubKeysBitmap(nil) @@ -335,7 +439,7 @@ func (creator *blocksCreator) setHeaderSignatures( headerHash := creator.nodeHandler.GetCoreComponents().Hasher().Compute(string(marshalizedHdr)) pubKeys := extractValidatorPubKeys(validators) - sig, err := creator.generateAggregatedSignature(headerHash, header.GetEpoch(), header.GetPubKeysBitmap(), pubKeys) + sig, err := creator.generateAggregatedSignature(headerHash, header.GetEpoch(), correctPubKeyBitmap, pubKeys) if err != nil { return err } diff --git a/node/chainSimulator/process/processor_test.go b/node/chainSimulator/process/processor_test.go index 2d52cebcb72..d5c637aef8c 100644 --- a/node/chainSimulator/process/processor_test.go +++ b/node/chainSimulator/process/processor_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/hashing" @@ -37,14 +38,14 @@ func TestNewBlocksCreator(t *testing.T) { t.Run("nil node handler should error", func(t *testing.T) { t.Parallel() - creator, err := chainSimulatorProcess.NewBlocksCreator(nil, heartbeat.NewHeartbeatMonitor()) + creator, err := chainSimulatorProcess.NewBlocksCreator(nil, heartbeat.NewHeartbeatMonitor(), 0, true) require.Equal(t, chainSimulatorProcess.ErrNilNodeHandler, err) require.Nil(t, creator) }) t.Run("should work", func(t *testing.T) { t.Parallel() - creator, err := chainSimulatorProcess.NewBlocksCreator(&chainSimulator.NodeHandlerMock{}, heartbeat.NewHeartbeatMonitor()) + creator, err := chainSimulatorProcess.NewBlocksCreator(&chainSimulator.NodeHandlerMock{}, heartbeat.NewHeartbeatMonitor(), 0, true) require.NoError(t, err) require.NotNil(t, creator) }) @@ -53,10 +54,10 @@ func TestNewBlocksCreator(t *testing.T) { func TestBlocksCreator_IsInterfaceNil(t *testing.T) { t.Parallel() - creator, _ := chainSimulatorProcess.NewBlocksCreator(nil, heartbeat.NewHeartbeatMonitor()) + creator, _ := chainSimulatorProcess.NewBlocksCreator(nil, heartbeat.NewHeartbeatMonitor(), 0, true) require.True(t, creator.IsInterfaceNil()) - creator, _ = chainSimulatorProcess.NewBlocksCreator(&chainSimulator.NodeHandlerMock{}, heartbeat.NewHeartbeatMonitor()) + creator, _ = chainSimulatorProcess.NewBlocksCreator(&chainSimulator.NodeHandlerMock{}, heartbeat.NewHeartbeatMonitor(), 0, true) require.False(t, creator.IsInterfaceNil()) } @@ -84,14 +85,16 @@ func TestBlocksCreator_IncrementRound(t *testing.T) { return &testsFactory.StatusCoreComponentsStub{ AppStatusHandlerField: &statusHandler.AppStatusHandlerStub{ SetUInt64ValueHandler: func(key string, value uint64) { - wasSetUInt64ValueCalled = true - require.Equal(t, common.MetricCurrentRound, key) + + if key == common.MetricCurrentRound { + wasSetUInt64ValueCalled = true + } }, }, } }, } - creator, err := chainSimulatorProcess.NewBlocksCreator(nodeHandler, heartbeat.NewHeartbeatMonitor()) + creator, err := chainSimulatorProcess.NewBlocksCreator(nodeHandler, heartbeat.NewHeartbeatMonitor(), 0, true) require.NoError(t, err) creator.IncrementRound() @@ -123,11 +126,25 @@ func TestBlocksCreator_CreateNewBlock(t *testing.T) { }, } } + nodeHandler.GetCoreComponentsCalled = func() factory.CoreComponentsHolder { + return &testsFactory.CoreComponentsHolderStub{ + EnableRoundsHandlerCalled: func() common.EnableRoundsHandler { + return &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return false + }, + } + }, + RoundHandlerCalled: func() consensus.RoundHandler { + return &testscommon.RoundHandlerMock{} + }, + } + } - creator, err := chainSimulatorProcess.NewBlocksCreator(nodeHandler, heartbeat.NewHeartbeatMonitor()) + creator, err := chainSimulatorProcess.NewBlocksCreator(nodeHandler, heartbeat.NewHeartbeatMonitor(), 0, true) require.NoError(t, err) - err = creator.CreateNewBlock() + _, err = creator.CreateNewBlock() require.Equal(t, expectedErr, err) }) t.Run("SetShardID failure should error", func(t *testing.T) { @@ -232,10 +249,10 @@ func TestBlocksCreator_CreateNewBlock(t *testing.T) { }, } } - creator, err := chainSimulatorProcess.NewBlocksCreator(nodeHandler, heartbeat.NewHeartbeatMonitor()) + creator, err := chainSimulatorProcess.NewBlocksCreator(nodeHandler, heartbeat.NewHeartbeatMonitor(), 0, true) require.NoError(t, err) - err = creator.CreateNewBlock() + _, err = creator.CreateNewBlock() require.Equal(t, expectedErr, err) }) t.Run("key not managed by the current node should return nil", func(t *testing.T) { @@ -251,10 +268,10 @@ func TestBlocksCreator_CreateNewBlock(t *testing.T) { }, } } - creator, err := chainSimulatorProcess.NewBlocksCreator(nodeHandler, heartbeat.NewHeartbeatMonitor()) + creator, err := chainSimulatorProcess.NewBlocksCreator(nodeHandler, heartbeat.NewHeartbeatMonitor(), 0, true) require.NoError(t, err) - err = creator.CreateNewBlock() + _, err = creator.CreateNewBlock() require.NoError(t, err) }) t.Run("CreateSignatureForPublicKey failure should error", func(t *testing.T) { @@ -272,10 +289,10 @@ func TestBlocksCreator_CreateNewBlock(t *testing.T) { }, } } - creator, err := chainSimulatorProcess.NewBlocksCreator(nodeHandler, heartbeat.NewHeartbeatMonitor()) + creator, err := chainSimulatorProcess.NewBlocksCreator(nodeHandler, heartbeat.NewHeartbeatMonitor(), 0, true) require.NoError(t, err) - err = creator.CreateNewBlock() + _, err = creator.CreateNewBlock() require.Equal(t, expectedErr, err) }) t.Run("SetRandSeed failure should error", func(t *testing.T) { @@ -325,12 +342,22 @@ func TestBlocksCreator_CreateNewBlock(t *testing.T) { EnableEpochsHandlerCalled: func() common.EnableEpochsHandler { return &enableEpochsHandlerMock.EnableEpochsHandlerStub{} }, + EnableRoundsHandlerCalled: func() common.EnableRoundsHandler { + return &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return false + }, + } + }, + HasherCalled: func() hashing.Hasher { + return &testscommon.HasherStub{} + }, } } - creator, err := chainSimulatorProcess.NewBlocksCreator(nodeHandler, heartbeat.NewHeartbeatMonitor()) + creator, err := chainSimulatorProcess.NewBlocksCreator(nodeHandler, heartbeat.NewHeartbeatMonitor(), 0, true) require.NoError(t, err) - err = creator.CreateNewBlock() + _, err = creator.CreateNewBlock() require.Equal(t, expectedErr, err) }) t.Run("setHeaderSignatures.Reset failure should error", func(t *testing.T) { @@ -348,10 +375,10 @@ func TestBlocksCreator_CreateNewBlock(t *testing.T) { }, } } - creator, err := chainSimulatorProcess.NewBlocksCreator(nodeHandler, heartbeat.NewHeartbeatMonitor()) + creator, err := chainSimulatorProcess.NewBlocksCreator(nodeHandler, heartbeat.NewHeartbeatMonitor(), 0, true) require.NoError(t, err) - err = creator.CreateNewBlock() + _, err = creator.CreateNewBlock() require.Equal(t, expectedErr, err) }) t.Run("setHeaderSignatures.CreateSignatureShareForPublicKey failure should error", func(t *testing.T) { @@ -369,10 +396,10 @@ func TestBlocksCreator_CreateNewBlock(t *testing.T) { }, } } - creator, err := chainSimulatorProcess.NewBlocksCreator(nodeHandler, heartbeat.NewHeartbeatMonitor()) + creator, err := chainSimulatorProcess.NewBlocksCreator(nodeHandler, heartbeat.NewHeartbeatMonitor(), 0, true) require.NoError(t, err) - err = creator.CreateNewBlock() + _, err = creator.CreateNewBlock() require.Equal(t, expectedErr, err) }) t.Run("setHeaderSignatures.AggregateSigs failure should error", func(t *testing.T) { @@ -390,10 +417,10 @@ func TestBlocksCreator_CreateNewBlock(t *testing.T) { }, } } - creator, err := chainSimulatorProcess.NewBlocksCreator(nodeHandler, heartbeat.NewHeartbeatMonitor()) + creator, err := chainSimulatorProcess.NewBlocksCreator(nodeHandler, heartbeat.NewHeartbeatMonitor(), 0, true) require.NoError(t, err) - err = creator.CreateNewBlock() + _, err = creator.CreateNewBlock() require.Equal(t, expectedErr, err) }) t.Run("setHeaderSignatures.SetSignature failure should error", func(t *testing.T) { @@ -512,36 +539,19 @@ func TestBlocksCreator_CreateNewBlock(t *testing.T) { }, }, &block.Body{}, nil }, - MarshalizedDataToBroadcastCalled: func(header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) { + MarshalizedDataToBroadcastCalled: func(hash []byte, header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) { return nil, nil, expectedErr }, } testCreateNewBlock(t, blockProcess, expectedErr) }) - t.Run("BroadcastHeader failure should error", func(t *testing.T) { - t.Parallel() - - nodeHandler := getNodeHandler() - nodeHandler.GetBroadcastMessengerCalled = func() consensus.BroadcastMessenger { - return &testsConsensus.BroadcastMessengerMock{ - BroadcastHeaderCalled: func(handler data.HeaderHandler, bytes []byte) error { - return expectedErr - }, - } - } - creator, err := chainSimulatorProcess.NewBlocksCreator(nodeHandler, heartbeat.NewHeartbeatMonitor()) - require.NoError(t, err) - - err = creator.CreateNewBlock() - require.Equal(t, expectedErr, err) - }) t.Run("should work", func(t *testing.T) { t.Parallel() - creator, err := chainSimulatorProcess.NewBlocksCreator(getNodeHandler(), heartbeat.NewHeartbeatMonitor()) + creator, err := chainSimulatorProcess.NewBlocksCreator(getNodeHandler(), heartbeat.NewHeartbeatMonitor(), 0, true) require.NoError(t, err) - err = creator.CreateNewBlock() + _, err = creator.CreateNewBlock() require.NoError(t, err) }) } @@ -555,10 +565,37 @@ func testCreateNewBlock(t *testing.T, blockProcess process.BlockProcessor, expec NodesCoord: nc, } } - creator, err := chainSimulatorProcess.NewBlocksCreator(nodeHandler, heartbeat.NewHeartbeatMonitor()) + nodeHandler.GetCoreComponentsCalled = func() factory.CoreComponentsHolder { + return &testsFactory.CoreComponentsHolderStub{ + EnableRoundsHandlerCalled: func() common.EnableRoundsHandler { + return &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return false + }, + } + }, + EnableEpochsHandlerCalled: func() common.EnableEpochsHandler { + return &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(_ core.EnableEpochFlag, _ uint32) bool { + return false + }, + } + }, + RoundHandlerCalled: func() consensus.RoundHandler { + return &testscommon.RoundHandlerMock{} + }, + InternalMarshalizerCalled: func() marshal.Marshalizer { + return &testscommon.MarshallerStub{} + }, + HasherCalled: func() hashing.Hasher { + return &testscommon.HasherStub{} + }, + } + } + creator, err := chainSimulatorProcess.NewBlocksCreator(nodeHandler, heartbeat.NewHeartbeatMonitor(), 0, true) require.NoError(t, err) - err = creator.CreateNewBlock() + _, err = creator.CreateNewBlock() require.Equal(t, expectedErr, err) } @@ -586,6 +623,13 @@ func getNodeHandler() *chainSimulator.NodeHandlerMock { EnableEpochsHandlerCalled: func() common.EnableEpochsHandler { return &enableEpochsHandlerMock.EnableEpochsHandlerStub{} }, + EnableRoundsHandlerCalled: func() common.EnableRoundsHandler { + return &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return false + }, + } + }, } }, GetProcessComponentsCalled: func() factory.ProcessComponentsHolder { @@ -602,7 +646,7 @@ func getNodeHandler() *chainSimulator.NodeHandlerMock { }, }, &block.Body{}, nil }, - MarshalizedDataToBroadcastCalled: func(header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) { + MarshalizedDataToBroadcastCalled: func(hash []byte, header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) { return make(map[uint32][]byte), make(map[string][][]byte), nil }, }, diff --git a/node/external/blockAPI/apiBlockFactory_test.go b/node/external/blockAPI/apiBlockFactory_test.go index 43e41a65173..4f39d8a3c2c 100644 --- a/node/external/blockAPI/apiBlockFactory_test.go +++ b/node/external/blockAPI/apiBlockFactory_test.go @@ -48,6 +48,7 @@ func createMockArgsAPIBlockProc() *ArgAPIBlockProcessor { EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, ProofsPool: &dataRetrieverTestCommon.ProofsPoolMock{}, BlockChain: chainHandler, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, } } @@ -190,6 +191,7 @@ func TestCreateAPIBlockProcessorNilArgs(t *testing.T) { _, err := CreateAPIBlockProcessor(arguments) assert.Equal(t, errNilEnableEpochsHandler, err) }) + t.Run("NilProofsPool", func(t *testing.T) { t.Parallel() @@ -209,6 +211,15 @@ func TestCreateAPIBlockProcessorNilArgs(t *testing.T) { _, err := CreateAPIBlockProcessor(arguments) assert.Equal(t, process.ErrNilBlockChain, err) }) + t.Run("NilEnableRoundsHandler", func(t *testing.T) { + t.Parallel() + + arguments := createMockArgsAPIBlockProc() + arguments.EnableRoundsHandler = nil + + _, err := CreateAPIBlockProcessor(arguments) + assert.Equal(t, process.ErrNilEnableRoundsHandler, err) + }) } func TestGetBlockByHash_KeyNotFound(t *testing.T) { diff --git a/node/external/blockAPI/baseBlock.go b/node/external/blockAPI/baseBlock.go index 0a4e03b6aba..5faccf460c5 100644 --- a/node/external/blockAPI/baseBlock.go +++ b/node/external/blockAPI/baseBlock.go @@ -62,12 +62,13 @@ type baseAPIBlockProcessor struct { enableEpochsHandler common.EnableEpochsHandler proofsPool dataRetriever.ProofsPool blockchain data.ChainHandler + enableRoundsHandler common.EnableRoundsHandler } var log = logger.GetOrCreate("node/blockAPI") -func (bap *baseAPIBlockProcessor) getIntrashardMiniblocksFromReceiptsStorage(header data.HeaderHandler, headerHash []byte, options api.BlockQueryOptions) ([]*api.MiniBlock, error) { - receiptsHolder, err := bap.receiptsRepository.LoadReceipts(header, headerHash) +func (bap *baseAPIBlockProcessor) getIntrashardMiniblocksFromReceiptsStorage(receiptsHash []byte, header data.HeaderHandler, headerHash []byte, options api.BlockQueryOptions) ([]*api.MiniBlock, error) { + receiptsHolder, err := bap.receiptsRepository.LoadReceipts(receiptsHash, header, headerHash) if err != nil { return nil, err } @@ -114,6 +115,60 @@ func (bap *baseAPIBlockProcessor) convertMiniblockFromReceiptsStorageToApiMinibl return miniblockAPI, nil } +func (bap *baseAPIBlockProcessor) getMbsAndNumTxsAsyncExecution( + blockHeader data.HeaderHandler, + mbHeaders []data.MiniBlockHeaderHandler, + options api.BlockQueryOptions, +) ([]*api.MiniBlock, uint32, error) { + numOfTxs := uint32(0) + miniblocks := make([]*api.MiniBlock, 0) + for _, mb := range mbHeaders { + mbType := block.Type(mb.GetTypeInt32()) + if mbType == block.PeerBlock { + continue + } + + numOfTxs += mb.GetTxCount() + + miniblockAPI := &api.MiniBlock{ + Hash: hex.EncodeToString(mb.GetHash()), + Type: mbType.String(), + ProcessingType: block.ProcessingType(mb.GetProcessingType()).String(), + ConstructionState: block.MiniBlockState(mb.GetConstructionState()).String(), + SourceShard: mb.GetSenderShardID(), + DestinationShard: mb.GetReceiverShardID(), + IndexOfFirstTxProcessed: mb.GetIndexOfFirstTxProcessed(), + IndexOfLastTxProcessed: mb.GetIndexOfLastTxProcessed(), + } + if options.WithTransactions { + miniBlockCopy := mb + err := bap.getAndAttachTxsToMbAsyncExecution(miniBlockCopy, blockHeader, miniblockAPI, options) + if err != nil { + return nil, 0, err + } + } + + miniblocks = append(miniblocks, miniblockAPI) + } + + return miniblocks, numOfTxs, nil +} + +func (bap *baseAPIBlockProcessor) getAndAttachTxsToMbAsyncExecution( + mbHeader data.MiniBlockHeaderHandler, + header data.HeaderHandler, + apiMiniblock *api.MiniBlock, + options api.BlockQueryOptions, +) error { + miniblockHash := mbHeader.GetHash() + miniBlock, err := bap.getMiniblockByHashAndEpoch(miniblockHash, header.GetEpoch()) + if err != nil { + return err + } + + return bap.getAndAttachTxsToMbByEpoch(miniblockHash, miniBlock, header, apiMiniblock, 0, int32(mbHeader.GetTxCount())-1, options) +} + func (bap *baseAPIBlockProcessor) getAndAttachTxsToMb( mbHeader data.MiniBlockHeaderHandler, header data.HeaderHandler, @@ -179,7 +234,7 @@ func (bap *baseAPIBlockProcessor) getAndAttachTxsToMbByEpoch( case block.InvalidBlock: apiMiniblock.Transactions, err = bap.getTxsFromMiniblock(miniBlock, miniblockHash, header, transaction.TxTypeInvalid, dataRetriever.TransactionUnit, firstProcessedTxIndex, lastProcessedTxIndex) case block.ReceiptBlock: - apiMiniblock.Receipts, err = bap.getReceiptsFromMiniblock(miniBlock, header.GetEpoch()) + apiMiniblock.Receipts, err = bap.getReceiptsFromMiniblock(miniBlock, header.GetEpoch(), header.GetRound()) } if err != nil { @@ -196,8 +251,16 @@ func (bap *baseAPIBlockProcessor) getAndAttachTxsToMbByEpoch( return nil } -func (bap *baseAPIBlockProcessor) getReceiptsFromMiniblock(miniblock *block.MiniBlock, epoch uint32) ([]*transaction.ApiReceipt, error) { - storer, err := bap.store.GetStorer(dataRetriever.UnsignedTransactionUnit) +func (bap *baseAPIBlockProcessor) getReceiptsStorerUnitType(round uint64) dataRetriever.UnitType { + if bap.enableRoundsHandler.IsFlagEnabledInRound(common.SupernovaRoundFlag, round) { + return dataRetriever.ReceiptsUnit + } + return dataRetriever.UnsignedTransactionUnit +} + +func (bap *baseAPIBlockProcessor) getReceiptsFromMiniblock(miniblock *block.MiniBlock, epoch uint32, round uint64) ([]*transaction.ApiReceipt, error) { + unit := bap.getReceiptsStorerUnitType(round) + storer, err := bap.store.GetStorer(unit) if err != nil { return nil, err } @@ -471,7 +534,7 @@ func (bap *baseAPIBlockProcessor) apiBlockToOutportPool(apiBlock *api.Block) (*o SmartContractResults: make(map[string]*outport.SCRInfo), InvalidTxs: make(map[string]*outport.TxInfo), Rewards: make(map[string]*outport.RewardInfo), - Logs: make([]*outport.LogData, 0), + Logs: make([]*transaction.LogData, 0), } var err error @@ -517,7 +580,7 @@ func (bap *baseAPIBlockProcessor) addLogsToPool(tx *transaction.ApiTransactionRe }) } - pool.Logs = append(pool.Logs, &outport.LogData{ + pool.Logs = append(pool.Logs, &transaction.LogData{ TxHash: tx.Hash, Log: &transaction.Log{ Address: logAddressBytes, @@ -668,3 +731,161 @@ func proofToAPIProof(proof data.HeaderProofHandler) *api.HeaderProof { IsStartOfEpoch: proof.GetIsStartOfEpoch(), } } + +func (bap *baseAPIBlockProcessor) addMbsAndNumTxsAsyncExecution(apiBlock *api.Block, blockHeader data.HeaderHandler, headerHash []byte, options api.BlockQueryOptions) error { + executionResultBytes, err := bap.getFromStorerWithEpoch(dataRetriever.ExecutionResultsUnit, headerHash, blockHeader.GetEpoch()) + if err != nil { + // do not return a partial block if the execution result is missing + return errBlockNotFound + } + + executionResultHandler, err := process.UnmarshalExecutionResult(bap.marshalizer, executionResultBytes) + if err != nil { + return err + } + + // get mbs before execution + mbsBeforeExecution, _, err := bap.getMbsAndTxsIfMissingExecutionResult(blockHeader, options) + if err != nil { + return err + } + // get miniblocks after execution + mbsAfterExecution, totalExecutedTxs, err := bap.getMbsAndNumTxsAsyncExecution(blockHeader, executionResultHandler.GetMiniBlockHeadersHandlers(), options) + if err != nil { + return err + } + + executedTxsMap := putAllTxsFromMbsInAMap(mbsAfterExecution) + mbsBeforeExecutionAndCleanup := removeExecutedTxsFromMbs(mbsBeforeExecution, executedTxsMap) + + allMbs := append(mbsBeforeExecutionAndCleanup, mbsAfterExecution...) + receiptsHash := executionResultHandler.GetReceiptsHash() + intraMb, err := bap.getIntrashardMiniblocksFromReceiptsStorage(receiptsHash, blockHeader, headerHash, options) + if err != nil { + return err + } + + if len(intraMb) > 0 { + allMbs = append(allMbs, intraMb...) + } + + allMbs = filterOutDuplicatedMiniblocks(allMbs) + + apiBlock.MiniBlocks = allMbs + apiBlock.NumTxs = totalExecutedTxs + apiBlock.AccumulatedFees = executionResultHandler.GetAccumulatedFees().String() + apiBlock.DeveloperFees = executionResultHandler.GetDeveloperFees().String() + + return nil +} + +func (bap *baseAPIBlockProcessor) getMbsAndTxsIfMissingExecutionResult(blockHeader data.HeaderHandler, options api.BlockQueryOptions) ([]*api.MiniBlock, uint32, error) { + miniblocks, numTxs, errG := bap.getMbsAndNumTxsAsyncExecution(blockHeader, blockHeader.GetMiniBlockHeaderHandlers(), options) + if errG != nil { + return nil, 0, errG + } + + // all transactions will have status pending + for _, miniBlock := range miniblocks { + for _, tx := range miniBlock.Transactions { + tx.Status = transaction.TxStatusPending + } + } + + return miniblocks, numTxs, nil +} + +func removeExecutedTxsFromMbs(mbs []*api.MiniBlock, executedTxsMap map[string]*transaction.ApiTransactionResult) []*api.MiniBlock { + for _, mb := range mbs { + newTxs := make([]*transaction.ApiTransactionResult, 0, len(mb.Transactions)) + for idx := 0; idx < len(mb.Transactions); idx++ { + _, found := executedTxsMap[mb.Transactions[idx].Hash] + if found { + continue + } else { + mb.Transactions[idx].Status = transaction.TxStatusNotExecutable + newTxs = append(newTxs, mb.Transactions[idx]) + } + } + mb.Transactions = newTxs + } + + newMbs := make([]*api.MiniBlock, 0, len(mbs)) + for _, mb := range mbs { + if len(mb.Transactions) == 0 { + continue + } + newMbs = append(newMbs, mb) + } + + return newMbs +} + +func putAllTxsFromMbsInAMap(mbs []*api.MiniBlock) map[string]*transaction.ApiTransactionResult { + txsMap := make(map[string]*transaction.ApiTransactionResult) + for _, mb := range mbs { + for _, tx := range mb.Transactions { + txsMap[tx.Hash] = tx + } + } + + return txsMap +} + +func addExecutionResultsAndLastExecutionResults(header data.HeaderHandler, apiBlock *api.Block) { + if !header.IsHeaderV3() { + return + } + + lastExecutionResult := header.GetLastExecutionResultHandler() + apiBlock.LastExecutionResult = &api.ExecutionResult{} + + switch executionResult := lastExecutionResult.(type) { + case *block.MetaExecutionResultInfo: + updateExecutionResultMeta(executionResult.ExecutionResult, apiBlock.LastExecutionResult) + case *block.ExecutionResultInfo: + updateExecutionResult(executionResult.ExecutionResult, apiBlock.LastExecutionResult) + } + + currentExecutionResults := header.GetExecutionResultsHandlers() + if len(currentExecutionResults) == 0 { + return + } + + apiBlock.ExecutionResults = make([]*api.ExecutionResult, len(currentExecutionResults)) + for i, currentExecutionResult := range currentExecutionResults { + apiBlock.ExecutionResults[i] = &api.ExecutionResult{} + updateExecutionResult(currentExecutionResult, apiBlock.ExecutionResults[i]) + } +} + +func updateExecutionResultMeta(executionResult *block.BaseMetaExecutionResult, apiExecutionResult *api.ExecutionResult) { + updateExecutionResult(executionResult, apiExecutionResult) + + apiExecutionResult.ValidatorStatsRootHash = hex.EncodeToString(executionResult.ValidatorStatsRootHash) + apiExecutionResult.AccumulatedFeesInEpoch = executionResult.AccumulatedFeesInEpoch.String() + apiExecutionResult.DevFeesInEpoch = executionResult.DevFeesInEpoch.String() + +} + +func updateExecutionResult(executionResult data.BaseExecutionResultHandler, apiExecutionResult *api.ExecutionResult) { + apiExecutionResult.HeaderHash = hex.EncodeToString(executionResult.GetHeaderHash()) + apiExecutionResult.HeaderNonce = executionResult.GetHeaderNonce() + apiExecutionResult.HeaderRound = executionResult.GetHeaderRound() + apiExecutionResult.HeaderEpoch = executionResult.GetHeaderEpoch() + apiExecutionResult.RootHash = hex.EncodeToString(executionResult.GetRootHash()) + apiExecutionResult.ExecutedTxCount = getNumExecutedTx(executionResult) +} + +type getExecutedTxHandler interface { + GetExecutedTxCount() uint64 +} + +func getNumExecutedTx(executionResult data.BaseExecutionResultHandler) uint64 { + handler, ok := executionResult.(getExecutedTxHandler) + if !ok { + return 0 + } + + return handler.GetExecutedTxCount() +} diff --git a/node/external/blockAPI/baseBlock_test.go b/node/external/blockAPI/baseBlock_test.go index 9169f309e64..17da7812424 100644 --- a/node/external/blockAPI/baseBlock_test.go +++ b/node/external/blockAPI/baseBlock_test.go @@ -46,6 +46,7 @@ func createBaseBlockProcessor() *baseAPIBlockProcessor { logsFacade: &testscommon.LogsFacadeStub{}, receiptsRepository: &testscommon.ReceiptsRepositoryStub{}, enableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + enableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, } } @@ -72,7 +73,7 @@ func TestBaseBlockGetIntraMiniblocksSCRS(t *testing.T) { _ = storer.Put(scrHash, scResultBytes) baseAPIBlockProc.receiptsRepository = &testscommon.ReceiptsRepositoryStub{ - LoadReceiptsCalled: func(header data.HeaderHandler, headerHash []byte) (common.ReceiptsHolder, error) { + LoadReceiptsCalled: func(_ []byte, header data.HeaderHandler, headerHash []byte) (common.ReceiptsHolder, error) { return holders.NewReceiptsHolder([]*block.MiniBlock{miniblock}), nil }, } @@ -88,7 +89,7 @@ func TestBaseBlockGetIntraMiniblocksSCRS(t *testing.T) { } blockHeader := &block.Header{ReceiptsHash: []byte("aaaa"), Epoch: 0} - intraMbs, err := baseAPIBlockProc.getIntrashardMiniblocksFromReceiptsStorage(blockHeader, []byte{}, api.BlockQueryOptions{WithTransactions: true}) + intraMbs, err := baseAPIBlockProc.getIntrashardMiniblocksFromReceiptsStorage(blockHeader.GetReceiptsHash(), blockHeader, []byte{}, api.BlockQueryOptions{WithTransactions: true}) require.Nil(t, err) require.Equal(t, &api.MiniBlock{ Hash: "f4add7b23eb83cf290422b0f6b770e3007b8ed3cd9683797fc90c8b4881f27bd", @@ -133,7 +134,7 @@ func TestBaseBlockGetIntraMiniblocksReceipts(t *testing.T) { _ = storer.Put(receiptHash, receiptBytes) baseAPIBlockProc.receiptsRepository = &testscommon.ReceiptsRepositoryStub{ - LoadReceiptsCalled: func(header data.HeaderHandler, headerHash []byte) (common.ReceiptsHolder, error) { + LoadReceiptsCalled: func(_ []byte, header data.HeaderHandler, headerHash []byte) (common.ReceiptsHolder, error) { return holders.NewReceiptsHolder([]*block.MiniBlock{miniblock}), nil }, } @@ -153,7 +154,7 @@ func TestBaseBlockGetIntraMiniblocksReceipts(t *testing.T) { } blockHeader := &block.Header{ReceiptsHash: []byte("aaaa"), Epoch: 0} - intraMbs, err := baseAPIBlockProc.getIntrashardMiniblocksFromReceiptsStorage(blockHeader, []byte{}, api.BlockQueryOptions{WithTransactions: true}) + intraMbs, err := baseAPIBlockProc.getIntrashardMiniblocksFromReceiptsStorage(blockHeader.GetReceiptsHash(), blockHeader, []byte{}, api.BlockQueryOptions{WithTransactions: true}) require.Nil(t, err) require.Equal(t, &api.MiniBlock{ Hash: "596545f64319f2fcf8e0ebae06f40f3353d603f6070255588a48018c7b30c951", @@ -598,3 +599,511 @@ func TestBaseBlock_getAndAttachTxsToMb_MiniblockTxBlockgetFromStore(t *testing.T require.Nil(t, resp) require.Equal(t, expectedErr, err) } + +func TestBaseBlock_AddExecutionResults(t *testing.T) { + t.Parallel() + + t.Run("shard header v2 will do nothing", func(t *testing.T) { + apiBlock := &api.Block{} + header := &block.HeaderV2{} + + addExecutionResultsAndLastExecutionResults(header, apiBlock) + require.Equal(t, &api.Block{}, apiBlock) + }) + + t.Run("meta block will do nothing", func(t *testing.T) { + apiBlock := &api.Block{} + header := &block.MetaBlock{} + + addExecutionResultsAndLastExecutionResults(header, apiBlock) + require.Equal(t, &api.Block{}, apiBlock) + }) + + t.Run("shard header v3", func(t *testing.T) { + apiBlock := &api.Block{} + header := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash"), + HeaderNonce: 1, + HeaderRound: 2, + HeaderEpoch: 3, + RootHash: []byte("root_hash"), + }, + }, + ExecutionResults: []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), + HeaderNonce: 11, + HeaderRound: 22, + HeaderEpoch: 33, + RootHash: []byte("root_hash1"), + }, + }, + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash2"), + HeaderNonce: 111, + HeaderRound: 222, + HeaderEpoch: 333, + RootHash: []byte("root_hash2"), + }, + }, + }, + } + + addExecutionResultsAndLastExecutionResults(header, apiBlock) + require.Equal(t, &api.Block{ + LastExecutionResult: &api.ExecutionResult{ + HeaderHash: "68617368", + HeaderNonce: 1, + HeaderRound: 2, + HeaderEpoch: 3, + RootHash: "726f6f745f68617368", + }, + ExecutionResults: []*api.ExecutionResult{ + { + HeaderHash: "6861736831", + HeaderNonce: 11, + HeaderRound: 22, + HeaderEpoch: 33, + RootHash: "726f6f745f6861736831", + }, + { + HeaderHash: "6861736832", + HeaderNonce: 111, + HeaderRound: 222, + HeaderEpoch: 333, + RootHash: "726f6f745f6861736832", + }, + }, + }, apiBlock) + }) + + t.Run("meta block v3", func(t *testing.T) { + apiBlock := &api.Block{} + header := &block.MetaBlockV3{ + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + ValidatorStatsRootHash: []byte("validators_root_hash"), + AccumulatedFeesInEpoch: big.NewInt(2), + DevFeesInEpoch: big.NewInt(3), + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash"), + HeaderNonce: 1, + HeaderRound: 2, + HeaderEpoch: 3, + RootHash: []byte("root_hash"), + }, + }, + }, + ExecutionResults: []*block.MetaExecutionResult{ + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), + HeaderNonce: 11, + HeaderRound: 22, + HeaderEpoch: 33, + RootHash: []byte("root_hash1"), + }, + }, + }, + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash2"), + HeaderNonce: 111, + HeaderRound: 222, + HeaderEpoch: 333, + RootHash: []byte("root_hash2"), + }, + }, + }, + }, + } + + addExecutionResultsAndLastExecutionResults(header, apiBlock) + require.Equal(t, &api.Block{ + LastExecutionResult: &api.ExecutionResult{ + HeaderHash: "68617368", + HeaderNonce: 1, + HeaderRound: 2, + HeaderEpoch: 3, + RootHash: "726f6f745f68617368", + ValidatorStatsRootHash: "76616c696461746f72735f726f6f745f68617368", + AccumulatedFeesInEpoch: "2", + DevFeesInEpoch: "3", + }, + ExecutionResults: []*api.ExecutionResult{ + { + HeaderHash: "6861736831", + HeaderNonce: 11, + HeaderRound: 22, + HeaderEpoch: 33, + RootHash: "726f6f745f6861736831", + }, + { + HeaderHash: "6861736832", + HeaderNonce: 111, + HeaderRound: 222, + HeaderEpoch: 333, + RootHash: "726f6f745f6861736832", + }, + }, + }, apiBlock) + }) + +} + +func TestBaseAPIBlockProcessor_AddMbsAndNumTxsAsyncExecutionBasedOnExecutionResult(t *testing.T) { + t.Parallel() + + baseAPIBlockProc := createBaseBlockProcessor() + baseAPIBlockProc.txStatusComputer = &mock.StatusComputerStub{ + ComputeStatusWhenInStorageKnowingMiniblockCalled: func(mbType block.Type, tx *transaction.ApiTransactionResult) (transaction.TxStatus, error) { + return transaction.TxStatusSuccess, nil + }, + } + + // Create mock execution result + executionResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("header_hash"), + HeaderNonce: 100, + HeaderRound: 1000, + HeaderEpoch: 5, + RootHash: []byte("root_hash"), + GasUsed: 50000, + }, + ReceiptsHash: []byte("receipts_hash"), + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("mb_hash_2"), + SenderShardID: 0, + ReceiverShardID: 1, + TxCount: 1, + }, + }, + DeveloperFees: big.NewInt(100), + AccumulatedFees: big.NewInt(1000), + ExecutedTxCount: 2, + } + + mb1 := &block.MiniBlock{ + TxHashes: [][]byte{ + []byte("tx_hash_1"), + []byte("tx_hash_2"), + }, + } + mbBytes, _ := baseAPIBlockProc.marshalizer.Marshal(mb1) + + mb2 := &block.MiniBlock{ + TxHashes: [][]byte{ + []byte("tx_hash_2"), + }, + } + mb2Bytes, _ := baseAPIBlockProc.marshalizer.Marshal(mb2) + + tx1 := &transaction.Transaction{ + Nonce: 1, + } + tx1Bytes, _ := baseAPIBlockProc.marshalizer.Marshal(tx1) + + executionResultBytes, _ := baseAPIBlockProc.marshalizer.Marshal(executionResult) + + count := 0 + baseAPIBlockProc.store = &storageMocks.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageMocks.StorerStub{ + GetFromEpochCalled: func(key []byte, epoch uint32) ([]byte, error) { + if string(key) == "header_hash" { + return executionResultBytes, nil + } + if string(key) == "mb_hash_1" { + return mbBytes, nil + } + if string(key) == "mb_hash_2" { + return mb2Bytes, nil + } + + return nil, errors.New("not found") + }, + GetBulkFromEpochCalled: func(keys [][]byte, epoch uint32) ([]data.KeyValuePair, error) { + if count == 0 { + count++ + return []data.KeyValuePair{ + { + Key: []byte("tx_hash_1"), + Value: tx1Bytes, + }, + { + Key: []byte("tx_hash_2"), + Value: tx1Bytes, + }, + }, nil + } + return []data.KeyValuePair{ + { + Key: []byte("tx_hash_1"), + Value: tx1Bytes, + }, + }, nil + + }, + }, nil + }, + } + + blockHeader := &block.Header{ + Nonce: 100, + Round: 1000, + Epoch: 5, + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("mb_hash_1"), + SenderShardID: 0, + ReceiverShardID: 1, + TxCount: 2, + }, + }, + } + + apiBlock := &api.Block{ + Nonce: 100, + Round: 1000, + Epoch: 5, + } + + baseAPIBlockProc.apiTransactionHandler = &mock.TransactionAPIHandlerStub{ + UnmarshalTransactionCalled: func(txBytes []byte, txType transaction.TxType, epoch uint32) (*transaction.ApiTransactionResult, error) { + return &transaction.ApiTransactionResult{ + Hash: "tx_hash_1", + Status: transaction.TxStatusSuccess, + }, nil + }, + } + + err := baseAPIBlockProc.addMbsAndNumTxsAsyncExecution( + apiBlock, + blockHeader, + []byte("header_hash"), + api.BlockQueryOptions{WithTransactions: true}, + ) + + require.NoError(t, err) + require.NotNil(t, apiBlock.MiniBlocks) + require.Equal(t, "1000", apiBlock.AccumulatedFees) + require.Equal(t, "100", apiBlock.DeveloperFees) + require.Equal(t, 2, len(apiBlock.MiniBlocks)) + require.Equal(t, transaction.TxStatusNotExecutable, apiBlock.MiniBlocks[0].Transactions[0].Status) + require.Equal(t, transaction.TxStatusSuccess, apiBlock.MiniBlocks[1].Transactions[0].Status) +} + +func TestBaseAPIBlockProcessor_AddMbsAndNumTxsAsyncExecutionBasedOnExecutionResult_NoExecutionResult(t *testing.T) { + t.Parallel() + + baseAPIBlockProc := createBaseBlockProcessor() + + blockHeader := &block.Header{ + Nonce: 100, + Round: 1000, + Epoch: 5, + } + + baseAPIBlockProc.store = &storageMocks.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageMocks.StorerStub{ + GetFromEpochCalled: func(key []byte, epoch uint32) ([]byte, error) { + return nil, errors.New("not found") + }, + }, nil + }, + } + + apiBlock := &api.Block{ + Nonce: 100, + Round: 1000, + Epoch: 5, + } + + baseAPIBlockProc.apiTransactionHandler = &mock.TransactionAPIHandlerStub{ + UnmarshalTransactionCalled: func(txBytes []byte, txType transaction.TxType, epoch uint32) (*transaction.ApiTransactionResult, error) { + return &transaction.ApiTransactionResult{ + Hash: "tx_hash_1", + Status: transaction.TxStatusPending, + }, nil + }, + } + + err := baseAPIBlockProc.addMbsAndNumTxsAsyncExecution( + apiBlock, + blockHeader, + []byte("header_hash"), + api.BlockQueryOptions{WithTransactions: true}, + ) + require.Equal(t, errBlockNotFound, err) +} + +func TestBaseAPIBlockProcessor_AddMbsAndNumTxsAsyncExecutionBasedOnExecutionResult_UnmarshalError(t *testing.T) { + t.Parallel() + + baseAPIBlockProc := createBaseBlockProcessor() + baseAPIBlockProc.txStatusComputer = &mock.StatusComputerStub{ + ComputeStatusWhenInStorageKnowingMiniblockCalled: func(mbType block.Type, tx *transaction.ApiTransactionResult) (transaction.TxStatus, error) { + return transaction.TxStatusSuccess, nil + }, + } + + invalidExecutionResultBytes := []byte("invalid_data") + + baseAPIBlockProc.store = &storageMocks.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageMocks.StorerStub{ + GetFromEpochCalled: func(key []byte, epoch uint32) ([]byte, error) { + if string(key) == "header_hash" { + return invalidExecutionResultBytes, nil + } + return nil, errors.New("not found") + }, + }, nil + }, + } + + blockHeader := &block.Header{ + Nonce: 100, + Round: 1000, + Epoch: 5, + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("mb_hash_1"), + SenderShardID: 0, + ReceiverShardID: 1, + TxCount: 1, + }, + }, + } + + apiBlock := &api.Block{ + Nonce: 100, + Round: 1000, + Epoch: 5, + } + + err := baseAPIBlockProc.addMbsAndNumTxsAsyncExecution( + apiBlock, + blockHeader, + []byte("header_hash"), + api.BlockQueryOptions{WithTransactions: true}, + ) + + require.Error(t, err) +} + +func TestBaseAPIBlockProcessor_AddMbsAndNumTxsAsyncExecutionBasedOnExecutionResult_GetMbsError(t *testing.T) { + t.Parallel() + + baseAPIBlockProc := createBaseBlockProcessor() + baseAPIBlockProc.txStatusComputer = &mock.StatusComputerStub{ + ComputeStatusWhenInStorageKnowingMiniblockCalled: func(mbType block.Type, tx *transaction.ApiTransactionResult) (transaction.TxStatus, error) { + return transaction.TxStatusSuccess, nil + }, + } + + executionResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("header_hash"), + HeaderNonce: 100, + HeaderRound: 1000, + HeaderEpoch: 5, + RootHash: []byte("root_hash"), + GasUsed: 50000, + }, + ReceiptsHash: []byte("receipts_hash"), + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("mb_hash_1"), + SenderShardID: 0, + ReceiverShardID: 1, + TxCount: 1, + }, + }, + DeveloperFees: big.NewInt(100), + AccumulatedFees: big.NewInt(1000), + ExecutedTxCount: 1, + } + + executionResultBytes, _ := baseAPIBlockProc.marshalizer.Marshal(executionResult) + + baseAPIBlockProc.store = &storageMocks.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageMocks.StorerStub{ + GetFromEpochCalled: func(key []byte, epoch uint32) ([]byte, error) { + if string(key) == "header_hash" { + return executionResultBytes, nil + } + if string(key) == "mb_hash_1" { + return nil, errors.New("miniblock not found") + } + return nil, errors.New("not found") + }, + }, nil + }, + } + + blockHeader := &block.Header{ + Nonce: 100, + Round: 1000, + Epoch: 5, + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("mb_hash_1"), + SenderShardID: 0, + ReceiverShardID: 1, + TxCount: 1, + }, + }, + } + + apiBlock := &api.Block{ + Nonce: 100, + Round: 1000, + Epoch: 5, + } + + err := baseAPIBlockProc.addMbsAndNumTxsAsyncExecution( + apiBlock, + blockHeader, + []byte("header_hash"), + api.BlockQueryOptions{WithTransactions: true}, + ) + + require.Error(t, err) + require.Contains(t, err.Error(), "miniblock not found") +} + +func TestGetNumExecutedTx(t *testing.T) { + t.Parallel() + + t.Run("meta execution result should work ok", func(t *testing.T) { + t.Parallel() + + metaExecutionResult := &block.MetaExecutionResult{ + ExecutedTxCount: 100, + } + require.Equal(t, uint64(100), getNumExecutedTx(metaExecutionResult)) + }) + t.Run("execution result should work ok ", func(t *testing.T) { + metaExecutionResult := &block.ExecutionResult{ + ExecutedTxCount: 100, + } + require.Equal(t, uint64(100), getNumExecutedTx(metaExecutionResult)) + }) + t.Run("base execution results should return 0", func(t *testing.T) { + t.Parallel() + + baseExecutionResult := &block.BaseExecutionResult{} + require.Equal(t, uint64(0), getNumExecutedTx(baseExecutionResult)) + }) +} diff --git a/node/external/blockAPI/blockArgs.go b/node/external/blockAPI/blockArgs.go index 4646ed1f2f8..bf9f7a2f91d 100644 --- a/node/external/blockAPI/blockArgs.go +++ b/node/external/blockAPI/blockArgs.go @@ -32,6 +32,7 @@ type ArgAPIBlockProcessor struct { AccountsRepository state.AccountsRepository ScheduledTxsExecutionHandler process.ScheduledTxsExecutionHandler EnableEpochsHandler common.EnableEpochsHandler + EnableRoundsHandler common.EnableRoundsHandler ProofsPool dataRetriever.ProofsPool BlockChain data.ChainHandler } diff --git a/node/external/blockAPI/check.go b/node/external/blockAPI/check.go index c1e9e404a56..fdea291a129 100644 --- a/node/external/blockAPI/check.go +++ b/node/external/blockAPI/check.go @@ -69,6 +69,9 @@ func checkNilArg(arg *ArgAPIBlockProcessor) error { if check.IfNil(arg.BlockChain) { return process.ErrNilBlockChain } + if check.IfNil(arg.EnableRoundsHandler) { + return process.ErrNilEnableRoundsHandler + } return core.CheckHandlerCompatibility(arg.EnableEpochsHandler, []core.EnableEpochFlag{ common.RefactorPeersMiniBlocksFlag, diff --git a/node/external/blockAPI/interface.go b/node/external/blockAPI/interface.go index 68279d3df49..3a8495b6499 100644 --- a/node/external/blockAPI/interface.go +++ b/node/external/blockAPI/interface.go @@ -46,6 +46,6 @@ type logsFacade interface { } type receiptsRepository interface { - LoadReceipts(header data.HeaderHandler, headerHash []byte) (common.ReceiptsHolder, error) + LoadReceipts(receiptsHash []byte, header data.HeaderHandler, headerHash []byte) (common.ReceiptsHolder, error) IsInterfaceNil() bool } diff --git a/node/external/blockAPI/internalBlock.go b/node/external/blockAPI/internalBlock.go index 7e737448b9d..4b6bd7045b9 100644 --- a/node/external/blockAPI/internalBlock.go +++ b/node/external/blockAPI/internalBlock.go @@ -188,8 +188,7 @@ func (ibp *internalBlockProcessor) GetInternalStartOfEpochValidatorsInfo(epoch u return nil, err } - metaBlock := &block.MetaBlock{} - err = ibp.marshalizer.Unmarshal(metaBlock, blockBytes) + metaBlock, err := process.UnmarshalHeader(core.MetachainShardId, ibp.marshalizer, blockBytes) if err != nil { return nil, err } @@ -279,9 +278,8 @@ func (ibp *internalBlockProcessor) getMiniBlockByHash(hash []byte, epoch uint32) return miniBlock, nil } -func (ibp *internalBlockProcessor) convertMetaBlockBytesToInternalBlock(blockBytes []byte) (*block.MetaBlock, error) { - blockHeader := &block.MetaBlock{} - err := ibp.marshalizer.Unmarshal(blockHeader, blockBytes) +func (ibp *internalBlockProcessor) convertMetaBlockBytesToInternalBlock(blockBytes []byte) (data.HeaderHandler, error) { + blockHeader, err := process.UnmarshalHeader(core.MetachainShardId, ibp.marshalizer, blockBytes) if err != nil { return nil, err } diff --git a/node/external/blockAPI/metaBlock.go b/node/external/blockAPI/metaBlock.go index e9bbd955914..1a34dce633f 100644 --- a/node/external/blockAPI/metaBlock.go +++ b/node/external/blockAPI/metaBlock.go @@ -4,11 +4,13 @@ import ( "encoding/hex" "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/alteredAccount" "github.com/multiversx/mx-chain-core-go/data/api" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" + "github.com/multiversx/mx-chain-go/process" ) type metaAPIBlockProcessor struct { @@ -40,6 +42,7 @@ func newMetaApiBlockProcessor(arg *ArgAPIBlockProcessor, emptyReceiptsHash []byt enableEpochsHandler: arg.EnableEpochsHandler, proofsPool: arg.ProofsPool, blockchain: arg.BlockChain, + enableRoundsHandler: arg.EnableRoundsHandler, }, } } @@ -92,8 +95,7 @@ func (mbp *metaAPIBlockProcessor) GetBlockByHash(hash []byte, options api.BlockQ return nil, err } - blockHeader := &block.MetaBlock{} - err = mbp.marshalizer.Unmarshal(blockHeader, blockBytes) + blockHeader, err := process.UnmarshalHeader(core.MetachainShardId, mbp.marshalizer, blockBytes) if err != nil { return nil, err } @@ -170,41 +172,119 @@ func (mbp *metaAPIBlockProcessor) getHashAndBlockBytesFromStorerByNonce(params a } func (mbp *metaAPIBlockProcessor) convertMetaBlockBytesToAPIBlock(hash []byte, blockBytes []byte, options api.BlockQueryOptions) (*api.Block, error) { - blockHeader := &block.MetaBlock{} - err := mbp.marshalizer.Unmarshal(blockHeader, blockBytes) + blockHeader, err := process.UnmarshalMetaHeader(mbp.marshalizer, blockBytes) if err != nil { return nil, err } + notarizedBlocks := make([]*api.NotarizedBlock, 0, len(blockHeader.GetShardInfoHandlers())) + for _, shardData := range blockHeader.GetShardInfoHandlers() { + notarizedBlock := &api.NotarizedBlock{ + Hash: hex.EncodeToString(shardData.GetHeaderHash()), + Nonce: shardData.GetNonce(), + Round: shardData.GetRound(), + Shard: shardData.GetShardID(), + } + + notarizedBlocks = append(notarizedBlocks, notarizedBlock) + } + + timestampSec, timestampMs, err := common.GetHeaderTimestamps(blockHeader, mbp.enableEpochsHandler) + if err != nil { + return nil, err + } + + apiMetaBlock := &api.Block{ + Nonce: blockHeader.GetNonce(), + Round: blockHeader.GetRound(), + Epoch: blockHeader.GetEpoch(), + Shard: core.MetachainShardId, + Hash: hex.EncodeToString(hash), + PrevBlockHash: hex.EncodeToString(blockHeader.GetPrevHash()), + NotarizedBlocks: notarizedBlocks, + AccumulatedFees: blockHeader.GetAccumulatedFees().String(), + DeveloperFees: blockHeader.GetDeveloperFees().String(), + AccumulatedFeesInEpoch: blockHeader.GetAccumulatedFeesInEpoch().String(), + DeveloperFeesInEpoch: blockHeader.GetDevFeesInEpoch().String(), + Timestamp: int64(timestampSec), + TimestampMs: int64(timestampMs), + Status: BlockStatusOnChain, + LeaderSignature: hex.EncodeToString(blockHeader.GetLeaderSignature()), + ChainID: string(blockHeader.GetChainID()), + SoftwareVersion: hex.EncodeToString(blockHeader.GetSoftwareVersion()), + ReceiptsHash: hex.EncodeToString(blockHeader.GetReceiptsHash()), + Reserved: blockHeader.GetReserved(), + RandSeed: hex.EncodeToString(blockHeader.GetRandSeed()), + PrevRandSeed: hex.EncodeToString(blockHeader.GetPrevRandSeed()), + } + + if !blockHeader.IsHeaderV3() { + apiMetaBlock.PubKeyBitmap = hex.EncodeToString(blockHeader.GetPubKeysBitmap()) + apiMetaBlock.Signature = hex.EncodeToString(blockHeader.GetSignature()) + + err = mbp.addMbsAndNumTxsV1(apiMetaBlock, blockHeader, hash, options) + } else { + // async execution + err = mbp.addMbsAndNumTxsAsyncExecution(apiMetaBlock, blockHeader, hash, options) + } + if err != nil { + return nil, err + } + + addExtraFieldsLastExecutionResultMeta(blockHeader, apiMetaBlock) + addScheduledInfoInBlock(blockHeader, apiMetaBlock) + addStartOfEpochInfoInBlock(blockHeader, apiMetaBlock) + + addExecutionResultsAndLastExecutionResults(blockHeader, apiMetaBlock) + + err = mbp.addProof(hash, blockHeader, apiMetaBlock) + if err != nil { + return nil, err + } + + return apiMetaBlock, nil +} + +func (mbp *metaAPIBlockProcessor) getMbsAndNumTxs(blockHeader data.MetaHeaderHandler, options api.BlockQueryOptions) ([]*api.MiniBlock, uint32, error) { numOfTxs := uint32(0) miniblocks := make([]*api.MiniBlock, 0) - for _, mb := range blockHeader.MiniBlockHeaders { - if mb.Type == block.PeerBlock { + for _, mb := range blockHeader.GetMiniBlockHeaderHandlers() { + mbType := block.Type(mb.GetTypeInt32()) + if mbType == block.PeerBlock { continue } - numOfTxs += mb.TxCount + numOfTxs += mb.GetTxCount() miniblockAPI := &api.MiniBlock{ - Hash: hex.EncodeToString(mb.Hash), - Type: mb.Type.String(), - SourceShard: mb.SenderShardID, - DestinationShard: mb.ReceiverShardID, + Hash: hex.EncodeToString(mb.GetHash()), + Type: mbType.String(), + SourceShard: mb.GetSenderShardID(), + DestinationShard: mb.GetReceiverShardID(), } if options.WithTransactions { miniBlockCopy := mb - err = mbp.getAndAttachTxsToMb(&miniBlockCopy, blockHeader, miniblockAPI, options) + err := mbp.getAndAttachTxsToMb(miniBlockCopy, blockHeader, miniblockAPI, options) if err != nil { - return nil, err + return nil, 0, err } } miniblocks = append(miniblocks, miniblockAPI) } - intraMb, err := mbp.getIntrashardMiniblocksFromReceiptsStorage(blockHeader, hash, options) + return miniblocks, numOfTxs, nil +} + +func (mbp *metaAPIBlockProcessor) addMbsAndNumTxsV1(apiBlock *api.Block, blockHeader data.MetaHeaderHandler, headerHash []byte, options api.BlockQueryOptions) error { + miniblocks, numOfTxs, err := mbp.getMbsAndNumTxs(blockHeader, options) if err != nil { - return nil, err + return err + } + + intraMb, err := mbp.getIntrashardMiniblocksFromReceiptsStorage(blockHeader.GetReceiptsHash(), blockHeader, headerHash, options) + if err != nil { + return err } if len(intraMb) > 0 { @@ -213,113 +293,97 @@ func (mbp *metaAPIBlockProcessor) convertMetaBlockBytesToAPIBlock(hash []byte, b miniblocks = filterOutDuplicatedMiniblocks(miniblocks) - notarizedBlocks := make([]*api.NotarizedBlock, 0, len(blockHeader.ShardInfo)) - for _, shardData := range blockHeader.ShardInfo { - notarizedBlock := &api.NotarizedBlock{ - Hash: hex.EncodeToString(shardData.HeaderHash), - Nonce: shardData.Nonce, - Round: shardData.Round, - Shard: shardData.ShardID, - } + apiBlock.MiniBlocks = miniblocks + apiBlock.NumTxs = numOfTxs - notarizedBlocks = append(notarizedBlocks, notarizedBlock) - } + return nil +} - apiMetaBlock := &api.Block{ - Nonce: blockHeader.Nonce, - Round: blockHeader.Round, - Epoch: blockHeader.Epoch, - Shard: core.MetachainShardId, - Hash: hex.EncodeToString(hash), - PrevBlockHash: hex.EncodeToString(blockHeader.PrevHash), - NumTxs: numOfTxs, - NotarizedBlocks: notarizedBlocks, - MiniBlocks: miniblocks, - AccumulatedFees: blockHeader.AccumulatedFees.String(), - DeveloperFees: blockHeader.DeveloperFees.String(), - AccumulatedFeesInEpoch: blockHeader.AccumulatedFeesInEpoch.String(), - DeveloperFeesInEpoch: blockHeader.DevFeesInEpoch.String(), - Timestamp: int64(blockHeader.GetTimeStamp()), - TimestampMs: int64(common.ConvertTimeStampSecToMs(blockHeader.GetTimeStamp())), - StateRootHash: hex.EncodeToString(blockHeader.RootHash), - Status: BlockStatusOnChain, - PubKeyBitmap: hex.EncodeToString(blockHeader.GetPubKeysBitmap()), - Signature: hex.EncodeToString(blockHeader.GetSignature()), - LeaderSignature: hex.EncodeToString(blockHeader.GetLeaderSignature()), - ChainID: string(blockHeader.GetChainID()), - SoftwareVersion: hex.EncodeToString(blockHeader.GetSoftwareVersion()), - ReceiptsHash: hex.EncodeToString(blockHeader.GetReceiptsHash()), - Reserved: blockHeader.GetReserved(), - RandSeed: hex.EncodeToString(blockHeader.GetRandSeed()), - PrevRandSeed: hex.EncodeToString(blockHeader.GetPrevRandSeed()), - } +func addExtraFieldsLastExecutionResultMeta(metaHeader data.MetaHeaderHandler, apiBlock *api.Block) { + if !metaHeader.IsHeaderV3() { + apiBlock.AccumulatedFees = metaHeader.GetAccumulatedFees().String() + apiBlock.DeveloperFees = metaHeader.GetDeveloperFees().String() - addScheduledInfoInBlock(blockHeader, apiMetaBlock) - addStartOfEpochInfoInBlock(blockHeader, apiMetaBlock) + apiBlock.AccumulatedFeesInEpoch = metaHeader.GetAccumulatedFeesInEpoch().String() + apiBlock.DeveloperFeesInEpoch = metaHeader.GetDevFeesInEpoch().String() + apiBlock.StateRootHash = hex.EncodeToString(metaHeader.GetRootHash()) - err = mbp.addProof(hash, blockHeader, apiMetaBlock) - if err != nil { - return nil, err + return } - return apiMetaBlock, nil + metaExecutionResult, ok := metaHeader.GetLastExecutionResultHandler().(data.LastMetaExecutionResultHandler) + if !ok { + log.Warn("mbp.addExtraFields cannot cast last execution result handler to data.LastMetaExecutionResultHandler") + return + } + + apiBlock.AccumulatedFeesInEpoch = metaExecutionResult.GetExecutionResultHandler().GetAccumulatedFeesInEpoch().String() + apiBlock.DeveloperFeesInEpoch = metaExecutionResult.GetExecutionResultHandler().GetDevFeesInEpoch().String() + apiBlock.StateRootHash = hex.EncodeToString(metaExecutionResult.GetExecutionResultHandler().GetRootHash()) } -func addStartOfEpochInfoInBlock(metaBlock *block.MetaBlock, apiBlock *api.Block) { - if !metaBlock.IsStartOfEpochBlock() { +func addStartOfEpochInfoInBlock(metaHeader data.MetaHeaderHandler, apiBlock *api.Block) { + if !metaHeader.IsStartOfEpochBlock() { return } - epochStartEconomics := metaBlock.EpochStart.Economics + epochStartEconomics := metaHeader.GetEpochStartHandler().GetEconomicsHandler() apiBlock.EpochStartInfo = &api.EpochStartInfo{ - TotalSupply: epochStartEconomics.TotalSupply.String(), - TotalToDistribute: epochStartEconomics.TotalToDistribute.String(), - TotalNewlyMinted: epochStartEconomics.TotalNewlyMinted.String(), - RewardsPerBlock: epochStartEconomics.RewardsPerBlock.String(), - RewardsForProtocolSustainability: epochStartEconomics.RewardsForProtocolSustainability.String(), - NodePrice: epochStartEconomics.NodePrice.String(), - PrevEpochStartRound: epochStartEconomics.PrevEpochStartRound, - PrevEpochStartHash: hex.EncodeToString(epochStartEconomics.PrevEpochStartHash), + TotalSupply: epochStartEconomics.GetTotalSupply().String(), + TotalToDistribute: epochStartEconomics.GetTotalToDistribute().String(), + TotalNewlyMinted: epochStartEconomics.GetTotalNewlyMinted().String(), + RewardsPerBlock: epochStartEconomics.GetRewardsPerBlock().String(), + RewardsForProtocolSustainability: epochStartEconomics.GetRewardsForProtocolSustainability().String(), + NodePrice: epochStartEconomics.GetNodePrice().String(), + PrevEpochStartRound: epochStartEconomics.GetPrevEpochStartRound(), + PrevEpochStartHash: hex.EncodeToString(epochStartEconomics.GetPrevEpochStartHash()), } - if len(metaBlock.EpochStart.LastFinalizedHeaders) == 0 { + lastFinalizedHeaderHandlers := metaHeader.GetEpochStartHandler().GetLastFinalizedHeaderHandlers() + if len(lastFinalizedHeaderHandlers) == 0 { return } - epochStartShardsData := metaBlock.EpochStart.LastFinalizedHeaders - apiBlock.EpochStartShardsData = make([]*api.EpochStartShardData, 0, len(epochStartShardsData)) - for _, epochStartShardData := range epochStartShardsData { + apiBlock.EpochStartShardsData = make([]*api.EpochStartShardData, 0, len(lastFinalizedHeaderHandlers)) + for _, epochStartShardData := range lastFinalizedHeaderHandlers { addEpochStartShardDataForMeta(epochStartShardData, apiBlock) } } -func addEpochStartShardDataForMeta(epochStartShardData block.EpochStartShardData, apiBlock *api.Block) { +func addEpochStartShardDataForMeta(epochStartShardData data.EpochStartShardDataHandler, apiBlock *api.Block) { shardData := &api.EpochStartShardData{ - ShardID: epochStartShardData.ShardID, - Epoch: epochStartShardData.Epoch, - Round: epochStartShardData.Round, - Nonce: epochStartShardData.Nonce, - HeaderHash: hex.EncodeToString(epochStartShardData.HeaderHash), - RootHash: hex.EncodeToString(epochStartShardData.RootHash), - ScheduledRootHash: hex.EncodeToString(epochStartShardData.ScheduledRootHash), - FirstPendingMetaBlock: hex.EncodeToString(epochStartShardData.FirstPendingMetaBlock), - LastFinishedMetaBlock: hex.EncodeToString(epochStartShardData.LastFinishedMetaBlock), + ShardID: epochStartShardData.GetShardID(), + Epoch: epochStartShardData.GetEpoch(), + Round: epochStartShardData.GetRound(), + Nonce: epochStartShardData.GetNonce(), + HeaderHash: hex.EncodeToString(epochStartShardData.GetHeaderHash()), + RootHash: hex.EncodeToString(epochStartShardData.GetRootHash()), + FirstPendingMetaBlock: hex.EncodeToString(epochStartShardData.GetFirstPendingMetaBlock()), + LastFinishedMetaBlock: hex.EncodeToString(epochStartShardData.GetLastFinishedMetaBlock()), + } + + epochStartData, ok := epochStartShardData.(*block.EpochStartShardData) + if ok { + shardData.ScheduledRootHash = hex.EncodeToString(epochStartData.ScheduledRootHash) } apiBlock.EpochStartShardsData = append(apiBlock.EpochStartShardsData, shardData) - if len(epochStartShardData.PendingMiniBlockHeaders) == 0 { + pendingMiniBlockHeaders := epochStartShardData.GetPendingMiniBlockHeaderHandlers() + if len(pendingMiniBlockHeaders) == 0 { return } - shardData.PendingMiniBlockHeaders = make([]*api.MiniBlock, 0, len(epochStartShardData.PendingMiniBlockHeaders)) - for _, pendingMb := range epochStartShardData.PendingMiniBlockHeaders { + shardData.PendingMiniBlockHeaders = make([]*api.MiniBlock, 0, len(pendingMiniBlockHeaders)) + for _, pendingMb := range pendingMiniBlockHeaders { + pendingMbType := block.Type(pendingMb.GetTypeInt32()) + shardData.PendingMiniBlockHeaders = append(shardData.PendingMiniBlockHeaders, &api.MiniBlock{ - Hash: hex.EncodeToString(pendingMb.Hash), - SourceShard: pendingMb.SenderShardID, - DestinationShard: pendingMb.ReceiverShardID, - Type: pendingMb.Type.String(), + Hash: hex.EncodeToString(pendingMb.GetHash()), + SourceShard: pendingMb.GetSenderShardID(), + DestinationShard: pendingMb.GetReceiverShardID(), + Type: pendingMbType.String(), }) } } diff --git a/node/external/blockAPI/shardBlock.go b/node/external/blockAPI/shardBlock.go index af209e2459a..43f1d15b95f 100644 --- a/node/external/blockAPI/shardBlock.go +++ b/node/external/blockAPI/shardBlock.go @@ -3,6 +3,7 @@ package blockAPI import ( "encoding/hex" + "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/alteredAccount" "github.com/multiversx/mx-chain-core-go/data/api" "github.com/multiversx/mx-chain-core-go/data/block" @@ -41,6 +42,7 @@ func newShardApiBlockProcessor(arg *ArgAPIBlockProcessor, emptyReceiptsHash []by enableEpochsHandler: arg.EnableEpochsHandler, proofsPool: arg.ProofsPool, blockchain: arg.BlockChain, + enableRoundsHandler: arg.EnableRoundsHandler, }, } } @@ -177,9 +179,80 @@ func (sbp *shardAPIBlockProcessor) convertShardBlockBytesToAPIBlock(hash []byte, return nil, err } + timestampSec, timestampMs, err := common.GetHeaderTimestamps(blockHeader, sbp.enableEpochsHandler) + if err != nil { + return nil, err + } + + apiBlock := &api.Block{ + Nonce: blockHeader.GetNonce(), + Round: blockHeader.GetRound(), + Epoch: blockHeader.GetEpoch(), + Shard: blockHeader.GetShardID(), + Hash: hex.EncodeToString(hash), + PrevBlockHash: hex.EncodeToString(blockHeader.GetPrevHash()), + AccumulatedFees: blockHeader.GetAccumulatedFees().String(), + DeveloperFees: blockHeader.GetDeveloperFees().String(), + Timestamp: int64(timestampSec), + TimestampMs: int64(timestampMs), + Status: BlockStatusOnChain, + LeaderSignature: hex.EncodeToString(blockHeader.GetLeaderSignature()), + ChainID: string(blockHeader.GetChainID()), + SoftwareVersion: hex.EncodeToString(blockHeader.GetSoftwareVersion()), + Reserved: blockHeader.GetReserved(), + RandSeed: hex.EncodeToString(blockHeader.GetRandSeed()), + PrevRandSeed: hex.EncodeToString(blockHeader.GetPrevRandSeed()), + } + + if blockHeader.IsHeaderV3() { + err = sbp.addMbsAndNumTxsAsyncExecution(apiBlock, blockHeader, hash, options) + } else { + apiBlock.PubKeyBitmap = hex.EncodeToString(blockHeader.GetPubKeysBitmap()) + apiBlock.Signature = hex.EncodeToString(blockHeader.GetSignature()) + + err = sbp.addMbsAndNumTxsV1(apiBlock, blockHeader, hash, options) + } + if err != nil { + return nil, err + } + + statusFilters := filters.NewStatusFilters(sbp.selfShardID) + statusFilters.ApplyStatusFilters(apiBlock.MiniBlocks) + + addExtraFieldsLastExecutionResult(blockHeader, apiBlock) + addScheduledInfoInBlock(blockHeader, apiBlock) + err = sbp.addProof(hash, blockHeader, apiBlock) + if err != nil { + return nil, err + } + + addExecutionResultsAndLastExecutionResults(blockHeader, apiBlock) + + return apiBlock, nil +} + +func addExtraFieldsLastExecutionResult(blockHeader data.HeaderHandler, apiBlock *api.Block) { + if !blockHeader.IsHeaderV3() { + apiBlock.AccumulatedFees = blockHeader.GetAccumulatedFees().String() + apiBlock.DeveloperFees = blockHeader.GetDeveloperFees().String() + apiBlock.StateRootHash = hex.EncodeToString(blockHeader.GetRootHash()) + apiBlock.ReceiptsHash = hex.EncodeToString(blockHeader.GetReceiptsHash()) + + return + } + + lastShardExecutionResult, ok := blockHeader.GetLastExecutionResultHandler().(data.LastShardExecutionResultHandler) + if !ok { + log.Warn("mbp.addExtraFields cannot cast last execution result handler to data.LastShardExecutionResultHandler") + return + } + + apiBlock.StateRootHash = hex.EncodeToString(lastShardExecutionResult.GetExecutionResultHandler().GetRootHash()) +} + +func (sbp *shardAPIBlockProcessor) addMbsAndNumTxsV1(apiBlock *api.Block, blockHeader data.HeaderHandler, headerHash []byte, options api.BlockQueryOptions) error { numOfTxs := uint32(0) miniblocks := make([]*api.MiniBlock, 0) - for _, mb := range blockHeader.GetMiniBlockHeaderHandlers() { if block.Type(mb.GetTypeInt32()) == block.PeerBlock { continue @@ -199,59 +272,27 @@ func (sbp *shardAPIBlockProcessor) convertShardBlockBytesToAPIBlock(hash []byte, } if options.WithTransactions { miniBlockCopy := mb - err = sbp.getAndAttachTxsToMb(miniBlockCopy, blockHeader, miniblockAPI, options) + err := sbp.getAndAttachTxsToMb(miniBlockCopy, blockHeader, miniblockAPI, options) if err != nil { - return nil, err + return err } } miniblocks = append(miniblocks, miniblockAPI) } - intraMb, err := sbp.getIntrashardMiniblocksFromReceiptsStorage(blockHeader, hash, options) + intraMb, err := sbp.getIntrashardMiniblocksFromReceiptsStorage(blockHeader.GetReceiptsHash(), blockHeader, headerHash, options) if err != nil { - return nil, err + return err } miniblocks = append(miniblocks, intraMb...) miniblocks = filterOutDuplicatedMiniblocks(miniblocks) - statusFilters := filters.NewStatusFilters(sbp.selfShardID) - statusFilters.ApplyStatusFilters(miniblocks) + apiBlock.MiniBlocks = miniblocks + apiBlock.NumTxs = numOfTxs - apiBlock := &api.Block{ - Nonce: blockHeader.GetNonce(), - Round: blockHeader.GetRound(), - Epoch: blockHeader.GetEpoch(), - Shard: blockHeader.GetShardID(), - Hash: hex.EncodeToString(hash), - PrevBlockHash: hex.EncodeToString(blockHeader.GetPrevHash()), - NumTxs: numOfTxs, - MiniBlocks: miniblocks, - AccumulatedFees: blockHeader.GetAccumulatedFees().String(), - DeveloperFees: blockHeader.GetDeveloperFees().String(), - Timestamp: int64(blockHeader.GetTimeStamp()), - TimestampMs: int64(common.ConvertTimeStampSecToMs(blockHeader.GetTimeStamp())), - Status: BlockStatusOnChain, - StateRootHash: hex.EncodeToString(blockHeader.GetRootHash()), - PubKeyBitmap: hex.EncodeToString(blockHeader.GetPubKeysBitmap()), - Signature: hex.EncodeToString(blockHeader.GetSignature()), - LeaderSignature: hex.EncodeToString(blockHeader.GetLeaderSignature()), - ChainID: string(blockHeader.GetChainID()), - SoftwareVersion: hex.EncodeToString(blockHeader.GetSoftwareVersion()), - ReceiptsHash: hex.EncodeToString(blockHeader.GetReceiptsHash()), - Reserved: blockHeader.GetReserved(), - RandSeed: hex.EncodeToString(blockHeader.GetRandSeed()), - PrevRandSeed: hex.EncodeToString(blockHeader.GetPrevRandSeed()), - } - - addScheduledInfoInBlock(blockHeader, apiBlock) - err = sbp.addProof(hash, blockHeader, apiBlock) - if err != nil { - return nil, err - } - - return apiBlock, nil + return nil } // IsInterfaceNil returns true if underlying object is nil diff --git a/node/external/blockAPI/shardBlock_test.go b/node/external/blockAPI/shardBlock_test.go index 8503f388445..287bae80b53 100644 --- a/node/external/blockAPI/shardBlock_test.go +++ b/node/external/blockAPI/shardBlock_test.go @@ -3,6 +3,7 @@ package blockAPI import ( "encoding/hex" "encoding/json" + "errors" "math/big" "testing" @@ -768,3 +769,121 @@ func areAlteredAccountsResponsesTheSame(first []*alteredAccount.AlteredAccount, return true } + +func TestShardAPIBlockProcessor_ConvertShardBlockBytesToAPIBlock_HeaderV3(t *testing.T) { + t.Parallel() + + shardID := uint32(3) + headerHash := []byte("d08089f2ab739520598fd7aeed08c427460fe94f286383047f3f61951afc4e00") + + storerMock := genericMocks.NewStorerMock() + + blockProc := createMockShardAPIProcessor( + shardID, + headerHash, + storerMock, + true, + false, + ) + + // Create HeaderV3 with execution results + headerV3 := &block.HeaderV3{ + Nonce: 100, + Round: 1000, + ShardID: 3, + Epoch: 5, + PrevHash: []byte("prev_hash"), + LastExecutionResult: &block.ExecutionResultInfo{ + NotarizedInRound: 100, + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("exec_header_hash"), + HeaderNonce: 99, + HeaderRound: 999, + HeaderEpoch: 5, + RootHash: []byte("root_hash"), + }, + }, + ExecutionResults: []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("exec_result_hash"), + HeaderNonce: 100, + HeaderRound: 1000, + HeaderEpoch: 5, + RootHash: []byte("exec_root_hash"), + GasUsed: 50000, + }, + ReceiptsHash: []byte("receipts_hash"), + MiniBlockHeaders: []block.MiniBlockHeader{}, + DeveloperFees: big.NewInt(100), + AccumulatedFees: big.NewInt(1000), + ExecutedTxCount: 0, + }, + }, + } + + headerBytes, _ := blockProc.marshalizer.Marshal(headerV3) + _ = storerMock.Put(headerHash, headerBytes) + + executionResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: headerHash, + HeaderNonce: 100, + HeaderRound: 1000, + HeaderEpoch: 5, + RootHash: []byte("root_hash"), + GasUsed: 50000, + }, + ReceiptsHash: []byte("receipts_hash"), + MiniBlockHeaders: []block.MiniBlockHeader{}, + DeveloperFees: big.NewInt(100), + AccumulatedFees: big.NewInt(1000), + ExecutedTxCount: 0, + } + executionResultBytes, _ := blockProc.marshalizer.Marshal(executionResult) + + blockProc.store = &storageMocks.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageMocks.StorerStub{ + GetFromEpochCalled: func(key []byte, epoch uint32) ([]byte, error) { + if string(key) == string(headerHash) { + return executionResultBytes, nil + } + return nil, errors.New("not found") + }, + }, nil + }, + } + + apiBlock, err := blockProc.convertShardBlockBytesToAPIBlock( + headerHash, + headerBytes, + api.BlockQueryOptions{WithTransactions: true}, + ) + + require.NoError(t, err) + require.NotNil(t, apiBlock) + require.Equal(t, uint64(100), apiBlock.Nonce) + require.Equal(t, uint64(1000), apiBlock.Round) + require.Equal(t, uint32(5), apiBlock.Epoch) + require.Equal(t, uint32(3), apiBlock.Shard) + require.Equal(t, hex.EncodeToString(headerHash), apiBlock.Hash) + require.Equal(t, "1000", apiBlock.AccumulatedFees) + require.Equal(t, "100", apiBlock.DeveloperFees) + require.Equal(t, hex.EncodeToString([]byte("root_hash")), apiBlock.StateRootHash) + + require.NotNil(t, apiBlock.LastExecutionResult) + require.Equal(t, "657865635f6865616465725f68617368", apiBlock.LastExecutionResult.HeaderHash) + require.Equal(t, uint64(99), apiBlock.LastExecutionResult.HeaderNonce) + require.Equal(t, uint64(999), apiBlock.LastExecutionResult.HeaderRound) + require.Equal(t, uint32(5), apiBlock.LastExecutionResult.HeaderEpoch) + require.Equal(t, "726f6f745f68617368", apiBlock.LastExecutionResult.RootHash) + + require.NotNil(t, apiBlock.ExecutionResults) + require.Len(t, apiBlock.ExecutionResults, 1) + require.Equal(t, "657865635f726573756c745f68617368", apiBlock.ExecutionResults[0].HeaderHash) + require.Equal(t, uint64(100), apiBlock.ExecutionResults[0].HeaderNonce) + require.Equal(t, uint64(1000), apiBlock.ExecutionResults[0].HeaderRound) + require.Equal(t, uint32(5), apiBlock.ExecutionResults[0].HeaderEpoch) + require.Equal(t, "657865635f726f6f745f68617368", apiBlock.ExecutionResults[0].RootHash) +} diff --git a/node/external/interface.go b/node/external/interface.go index 31835469fc7..f47a5c3abc9 100644 --- a/node/external/interface.go +++ b/node/external/interface.go @@ -3,12 +3,14 @@ package external import ( "context" + "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/api" "github.com/multiversx/mx-chain-core-go/data/smartContractResult" "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" txSimData "github.com/multiversx/mx-chain-go/process/transactionEvaluator/data" + "github.com/multiversx/mx-chain-go/state" vmcommon "github.com/multiversx/mx-chain-vm-common-go" ) @@ -28,6 +30,8 @@ type StatusMetricsHandler interface { EconomicsMetrics() (map[string]interface{}, error) ConfigMetrics() (map[string]interface{}, error) EnableEpochsMetrics() (map[string]interface{}, error) + EnableEpochsMetricsV2() map[string]uint32 + EnableRoundsMetrics() map[string]uint64 NetworkMetrics() (map[string]interface{}, error) RatingsMetrics() (map[string]interface{}, error) BootstrapMetrics() (map[string]interface{}, error) @@ -69,6 +73,8 @@ type APITransactionHandler interface { GetLastPoolNonceForSender(sender string) (uint64, error) GetTransactionsPoolNonceGapsForSender(sender string, senderAccountNonce uint64) (*common.TransactionsPoolNonceGapsForSenderApiResponse, error) UnmarshalTransaction(txBytes []byte, txType transaction.TxType, epoch uint32) (*transaction.ApiTransactionResult, error) + GetSelectedTransactions(selectionOptions common.TxSelectionOptionsAPI, blockchain data.ChainHandler, accountsAdapter state.AccountsAdapter) (*common.TransactionsSelectionSimulationResult, error) + GetVirtualNonce(address string) (*common.VirtualNonceOfAccountResponse, error) PopulateComputedFields(tx *transaction.ApiTransactionResult) UnmarshalReceipt(receiptBytes []byte) (*transaction.ApiReceipt, error) IsInterfaceNil() bool diff --git a/node/external/nodeApiResolver.go b/node/external/nodeApiResolver.go index 70e645b347a..98f1c829d67 100644 --- a/node/external/nodeApiResolver.go +++ b/node/external/nodeApiResolver.go @@ -7,6 +7,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + coreData "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/alteredAccount" "github.com/multiversx/mx-chain-core-go/data/api" "github.com/multiversx/mx-chain-core-go/data/smartContractResult" @@ -220,6 +221,16 @@ func (nar *nodeApiResolver) GetTransactionsPoolNonceGapsForSender(sender string, return nar.apiTransactionHandler.GetTransactionsPoolNonceGapsForSender(sender, senderAccountNonce) } +// GetSelectedTransactions will simulate a SelectTransactions, and it will return the corresponding hash of each selected transaction +func (nar *nodeApiResolver) GetSelectedTransactions(selectionOptionsAPI common.TxSelectionOptionsAPI, blockchain coreData.ChainHandler, accountsAdapter state.AccountsAdapter) (*common.TransactionsSelectionSimulationResult, error) { + return nar.apiTransactionHandler.GetSelectedTransactions(selectionOptionsAPI, blockchain, accountsAdapter) +} + +// GetVirtualNonce will return the virtual nonce of the account +func (nar *nodeApiResolver) GetVirtualNonce(address string) (*common.VirtualNonceOfAccountResponse, error) { + return nar.apiTransactionHandler.GetVirtualNonce(address) +} + // GetBlockByHash will return the block with the given hash and optionally with transactions func (nar *nodeApiResolver) GetBlockByHash(hash string, options api.BlockQueryOptions) (*api.Block, error) { decodedHash, err := hex.DecodeString(hash) diff --git a/node/external/nodeApiResolver_test.go b/node/external/nodeApiResolver_test.go index 5a1cec19787..07693c24aee 100644 --- a/node/external/nodeApiResolver_test.go +++ b/node/external/nodeApiResolver_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/multiversx/mx-chain-core-go/core" + coreData "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/api" "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-go/common" @@ -18,6 +19,7 @@ import ( "github.com/multiversx/mx-chain-go/node/mock" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/sharding/nodesCoordinator" + "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/genesisMocks" "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" @@ -578,6 +580,98 @@ func TestNodeApiResolver_GetTransactionsPoolNonceGapsForSender(t *testing.T) { }) } +func TestNodeApiResolver_GetSelectedTransactions(t *testing.T) { + t.Parallel() + + t.Run("should error", func(t *testing.T) { + t.Parallel() + + expectedErr := errors.New("expected error") + arg := createMockArgs() + arg.APITransactionHandler = &mock.TransactionAPIHandlerStub{ + GetSelectedTransactionsCalled: func(selectionOptions common.TxSelectionOptionsAPI, blockchain coreData.ChainHandler, accountsAdapter state.AccountsAdapter) (*common.TransactionsSelectionSimulationResult, error) { + return nil, expectedErr + }, + } + + nar, _ := external.NewNodeApiResolver(arg) + res, err := nar.GetSelectedTransactions(nil, nil, nil) + require.Nil(t, res) + require.Equal(t, expectedErr, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + expectedTxs := []common.Transaction{ + { + TxFields: map[string]interface{}{ + "hash": "txHash1", + }, + }, + { + TxFields: map[string]interface{}{ + "hash": "txHash2", + }, + }, + } + expectedResult := &common.TransactionsSelectionSimulationResult{ + Transactions: expectedTxs, + } + arg := createMockArgs() + arg.APITransactionHandler = &mock.TransactionAPIHandlerStub{ + GetSelectedTransactionsCalled: func(selectionOptions common.TxSelectionOptionsAPI, blockchain coreData.ChainHandler, accountsAdapter state.AccountsAdapter) (*common.TransactionsSelectionSimulationResult, error) { + return expectedResult, nil + }, + } + + nar, _ := external.NewNodeApiResolver(arg) + res, err := nar.GetSelectedTransactions(nil, nil, nil) + require.NoError(t, err) + require.Equal(t, expectedResult, res) + }) +} + +func TestNodeApiResolver_GetVirtualNonce(t *testing.T) { + t.Parallel() + + t.Run("should error", func(t *testing.T) { + t.Parallel() + + expectedErr := errors.New("expected error") + arg := createMockArgs() + arg.APITransactionHandler = &mock.TransactionAPIHandlerStub{ + GetVirtualNonceCalled: func(address string) (*common.VirtualNonceOfAccountResponse, error) { + return nil, expectedErr + }, + } + + nar, _ := external.NewNodeApiResolver(arg) + res, err := nar.GetVirtualNonce("alice") + require.Nil(t, res) + require.Equal(t, expectedErr, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + expectedResult := &common.VirtualNonceOfAccountResponse{ + VirtualNonce: 0, + } + arg := createMockArgs() + arg.APITransactionHandler = &mock.TransactionAPIHandlerStub{ + GetVirtualNonceCalled: func(address string) (*common.VirtualNonceOfAccountResponse, error) { + return expectedResult, nil + }, + } + + nar, _ := external.NewNodeApiResolver(arg) + res, err := nar.GetVirtualNonce("") + require.NoError(t, err) + require.Equal(t, expectedResult, res) + }) +} + func TestNodeApiResolver_GetGenesisNodesPubKeys(t *testing.T) { t.Parallel() diff --git a/node/external/timemachine/fee/feeComputer_test.go b/node/external/timemachine/fee/feeComputer_test.go index a76f33c1a12..6fc95fb2d0d 100644 --- a/node/external/timemachine/fee/feeComputer_test.go +++ b/node/external/timemachine/fee/feeComputer_test.go @@ -2,7 +2,6 @@ package fee import ( "encoding/hex" - "github.com/multiversx/mx-chain-go/config" "math/big" "sync" "testing" @@ -13,6 +12,7 @@ import ( "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/economics" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" "github.com/stretchr/testify/assert" @@ -21,13 +21,11 @@ import ( func createEconomicsData() process.EconomicsDataHandler { economicsConfig := testscommon.GetEconomicsConfig() - cfg := &config.Config{EpochStartConfig: config.EpochStartConfig{RoundsPerEpoch: 14400}} - cfg.GeneralSettings.ChainParametersByEpoch = []config.ChainParametersByEpochConfig{{RoundDuration: 6000}} economicsData, _ := economics.NewEconomicsData(economics.ArgsNewEconomicsData{ - TxVersionChecker: &testscommon.TxVersionCheckerStub{}, - Economics: &economicsConfig, - GeneralConfig: cfg, - EpochNotifier: &epochNotifier.EpochNotifierStub{}, + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + Economics: &economicsConfig, + ChainParamsHandler: &chainParameters.ChainParametersHolderMock{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{ IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { if flag == common.PenalizedTooMuchGasFlag { diff --git a/node/external/timemachine/fee/memoryFootprint/memory_test.go b/node/external/timemachine/fee/memoryFootprint/memory_test.go index b57acec16bc..5f653ec0c58 100644 --- a/node/external/timemachine/fee/memoryFootprint/memory_test.go +++ b/node/external/timemachine/fee/memoryFootprint/memory_test.go @@ -2,7 +2,6 @@ package memoryFootprint import ( "fmt" - "github.com/multiversx/mx-chain-go/config" "runtime" "testing" @@ -12,6 +11,7 @@ import ( "github.com/multiversx/mx-chain-go/node/external/timemachine/fee" "github.com/multiversx/mx-chain-go/process/economics" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" "github.com/stretchr/testify/require" @@ -30,11 +30,9 @@ func TestFeeComputer_MemoryFootprint(t *testing.T) { journal.before = getMemStats() economicsConfig := testscommon.GetEconomicsConfig() - cfg := &config.Config{EpochStartConfig: config.EpochStartConfig{RoundsPerEpoch: 14400}} - cfg.GeneralSettings.ChainParametersByEpoch = []config.ChainParametersByEpochConfig{{RoundDuration: 6000}} economicsData, _ := economics.NewEconomicsData(economics.ArgsNewEconomicsData{ - GeneralConfig: cfg, - Economics: &economicsConfig, + ChainParamsHandler: &chainParameters.ChainParametersHolderMock{}, + Economics: &economicsConfig, EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{ IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { if flag == common.PenalizedTooMuchGasFlag { diff --git a/node/external/transactionAPI/apiTransactionArgs.go b/node/external/transactionAPI/apiTransactionArgs.go index 1e4099390fd..f274fc07a51 100644 --- a/node/external/transactionAPI/apiTransactionArgs.go +++ b/node/external/transactionAPI/apiTransactionArgs.go @@ -6,6 +6,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data/typeConverters" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/dblookupext" @@ -30,4 +31,6 @@ type ArgAPITransactionProcessor struct { DataFieldParser DataFieldParser TxMarshaller marshal.Marshalizer EnableEpochsHandler common.EnableEpochsHandler + EnableRoundsHandler common.EnableRoundsHandler + TxVersionChecker process.TxVersionCheckerHandler } diff --git a/node/external/transactionAPI/apiTransactionProcessor.go b/node/external/transactionAPI/apiTransactionProcessor.go index 5f5c47a50cc..61e97dc0f6b 100644 --- a/node/external/transactionAPI/apiTransactionProcessor.go +++ b/node/external/transactionAPI/apiTransactionProcessor.go @@ -1,6 +1,7 @@ package transactionAPI import ( + "bytes" "encoding/hex" "errors" "fmt" @@ -16,15 +17,20 @@ import ( "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-core-go/data/typeConverters" "github.com/multiversx/mx-chain-core-go/marshal" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/common/holders" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/dblookupext" + "github.com/multiversx/mx-chain-go/factory/disabled" "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/block/preprocess" "github.com/multiversx/mx-chain-go/process/smartContract" "github.com/multiversx/mx-chain-go/process/txstatus" "github.com/multiversx/mx-chain-go/sharding" - "github.com/multiversx/mx-chain-go/storage/txcache" - logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/state" + "github.com/multiversx/mx-chain-go/txcache" ) var log = logger.GetOrCreate("node/transactionAPI") @@ -46,6 +52,8 @@ type apiTransactionProcessor struct { refundDetector *refundDetector gasUsedAndFeeProcessor *gasUsedAndFeeProcessor enableEpochsHandler common.EnableEpochsHandler + enableRoundsHandler common.EnableRoundsHandler + txVersionChecker process.TxVersionCheckerHandler } // NewAPITransactionProcessor will create a new instance of apiTransactionProcessor @@ -71,6 +79,7 @@ func NewAPITransactionProcessor(args *ArgAPITransactionProcessor) (*apiTransacti args.LogsFacade, args.ShardCoordinator, args.DataFieldParser, + args.EnableRoundsHandler, ) refundDetectorInstance := NewRefundDetector() @@ -99,6 +108,8 @@ func NewAPITransactionProcessor(args *ArgAPITransactionProcessor) (*apiTransacti refundDetector: refundDetectorInstance, gasUsedAndFeeProcessor: gasUsedAndFeeProc, enableEpochsHandler: args.EnableEpochsHandler, + enableRoundsHandler: args.EnableRoundsHandler, + txVersionChecker: args.TxVersionChecker, }, nil } @@ -169,11 +180,23 @@ func (atp *apiTransactionProcessor) GetTransaction(txHash string, withResults bo } func (atp *apiTransactionProcessor) doGetTransaction(hash []byte, withResults bool) (*transaction.ApiTransactionResult, error) { - tx := atp.optionallyGetTransactionFromPool(hash) - if tx != nil { - return tx, nil + txFromPool := atp.optionallyGetTransactionFromPool(hash) + if txFromPool != nil { + // we don't return immediately because storage/history takes priority if it succeeds + txFromStorage, err := atp.fetchFromStorageOrHistory(hash, withResults) + if err == nil { + return txFromStorage, nil + } + + // storage/history failed, but pool has it + return txFromPool, nil } + // no pool entry — just return storage/history result + return atp.fetchFromStorageOrHistory(hash, withResults) +} + +func (atp *apiTransactionProcessor) fetchFromStorageOrHistory(hash []byte, withResults bool) (*transaction.ApiTransactionResult, error) { if atp.historyRepository.IsEnabled() { return atp.lookupHistoricalTransaction(hash, withResults) } @@ -310,6 +333,41 @@ func (atp *apiTransactionProcessor) GetTransactionsPoolNonceGapsForSender(sender }, nil } +// GetSelectedTransactions will simulate a SelectTransactions, and it will return the corresponding hash of each selected transaction +func (atp *apiTransactionProcessor) GetSelectedTransactions(selectionOptions common.TxSelectionOptionsAPI, blockchain data.ChainHandler, accountsAdapter state.AccountsAdapter) (*common.TransactionsSelectionSimulationResult, error) { + err := atp.recreateTrie(blockchain, accountsAdapter) + if err != nil { + return nil, err + } + + selectedTransactions, err := atp.selectTransactions(accountsAdapter, selectionOptions) + if err != nil { + return nil, err + } + + return &common.TransactionsSelectionSimulationResult{ + Transactions: selectedTransactions, + }, nil +} + +// GetVirtualNonce will return the virtual nonce of an account +func (atp *apiTransactionProcessor) GetVirtualNonce(address string) (*common.VirtualNonceOfAccountResponse, error) { + pubKey, err := atp.addressPubKeyConverter.Decode(address) + if err != nil { + return nil, fmt.Errorf("%s, %w", ErrInvalidAddress.Error(), err) + } + + virtualNonce, latestCommittedBlockResponse, err := atp.getVirtualNonceWithBlockInfo(pubKey) + if err != nil { + return nil, err + } + + return &common.VirtualNonceOfAccountResponse{ + VirtualNonce: virtualNonce, + LatestCommittedBlock: *latestCommittedBlockResponse, + }, nil +} + func (atp *apiTransactionProcessor) extractRequestedTxInfoFromObj(txObj interface{}, txType transaction.TxType, txHash []byte, requestedFieldsHandler fieldsHandler) common.Transaction { txResult := atp.getApiResultFromObj(txObj, txType) @@ -390,13 +448,19 @@ func (atp *apiTransactionProcessor) extractRequestedTxInfo(wrappedTx *txcache.Wr } func (atp *apiTransactionProcessor) getFieldGettersForTx(wrappedTx *txcache.WrappedTransaction) map[string]interface{} { + senderAddr := "" + if len(wrappedTx.Tx.GetSndAddr()) != 0 { + senderAddr = atp.addressPubKeyConverter.SilentEncode(wrappedTx.Tx.GetSndAddr(), log) + } + var fieldGetters = map[string]interface{}{ hashField: hex.EncodeToString(wrappedTx.TxHash), nonceField: wrappedTx.Tx.GetNonce(), - senderField: atp.addressPubKeyConverter.SilentEncode(wrappedTx.Tx.GetSndAddr(), log), + senderField: senderAddr, receiverField: atp.addressPubKeyConverter.SilentEncode(wrappedTx.Tx.GetRcvAddr(), log), gasLimitField: wrappedTx.Tx.GetGasLimit(), gasPriceField: wrappedTx.Tx.GetGasPrice(), + ppu: wrappedTx.PricePerUnit, rcvUsernameField: wrappedTx.Tx.GetRcvUserName(), dataField: wrappedTx.Tx.GetData(), valueField: getTxValue(wrappedTx), @@ -424,6 +488,155 @@ func (atp *apiTransactionProcessor) getFieldGettersForTx(wrappedTx *txcache.Wrap return fieldGetters } +func (atp *apiTransactionProcessor) recreateTrie(blockchain data.ChainHandler, accountStateAPI state.AccountsAdapter) error { + if accountStateAPI == nil { + return ErrNilAccountStateAPI + } + + if blockchain == nil { + return ErrNilBlockchain + } + + currentRootHash, err := atp.getCurrentRootHash(blockchain) + if err != nil { + return err + } + + blockHeader := blockchain.GetCurrentBlockHeader() + if blockHeader == nil { + return ErrNilBlockHeader + } + + epoch := blockHeader.GetEpoch() + rootHashHolder := holders.NewRootHashHolder(currentRootHash, core.OptionalUint32{Value: epoch, HasValue: true}) + + // TODO: keep in mind that the selection simulation can be affected by other API requests which might alter the trie + err = accountStateAPI.RecreateTrie(rootHashHolder) + if err != nil { + return err + } + + return nil +} + +func (atp *apiTransactionProcessor) getCurrentRootHash( + blockchain data.ChainHandler, +) ([]byte, error) { + blockHeader := blockchain.GetCurrentBlockHeader() + if blockHeader == nil { + return nil, ErrNilBlockHeader + } + + if blockHeader.IsHeaderV3() { + return getHeaderV3RootHash(blockchain) + } + + currentRootHash := blockchain.GetCurrentBlockRootHash() + if currentRootHash == nil { + return nil, ErrNilCurrentRootHash + } + + return currentRootHash, nil +} + +func getHeaderV3RootHash( + blockchain data.ChainHandler, +) ([]byte, error) { + _, _, lastExecutedRootHash := blockchain.GetLastExecutedBlockInfo() + if len(lastExecutedRootHash) != 0 { + return lastExecutedRootHash, nil + } + + blockHeader := blockchain.GetCurrentBlockHeader() + + // is first header v3, last executed block info might not be updated yet + // rootHash can be taken from last execution results directly from header + // on creation, the first header v3 has the last execution info from last header v2 + // and it should be available + lastExecutionResult, err := common.ExtractBaseExecutionResultHandler(blockHeader.GetLastExecutionResultHandler()) + if err != nil { + return nil, err + } + + return lastExecutionResult.GetRootHash(), nil +} + +func (atp *apiTransactionProcessor) selectTransactions(accountsAdapter state.AccountsAdapter, selectionOptions common.TxSelectionOptionsAPI) ([]common.Transaction, error) { + cacheId := process.ShardCacherIdentifier(atp.shardCoordinator.SelfId(), atp.shardCoordinator.SelfId()) + cache := atp.dataPool.Transactions().ShardDataStore(cacheId) + txCache, ok := cache.(*txcache.TxCache) + if !ok { + log.Warn("apiTransactionProcessor.selectTransactions could not cast to TxCache") + return nil, ErrCouldNotCastToTxCache + } + + // TODO use the right object, not a disabled one + txProcessor := disabled.TxProcessor{} + argsSelectionSession := preprocess.ArgsSelectionSession{ + AccountsAdapter: accountsAdapter, + TransactionsProcessor: &txProcessor, + TxVersionCheckerHandler: atp.txVersionChecker, + } + + selectionSession, err := preprocess.NewSelectionSession(argsSelectionSession) + if err != nil { + log.Warn("apiTransactionProcessor.selectTransactions could not create SelectionSession") + return nil, err + } + + selectedTxs, _, err := txCache.SimulateSelectTransactions(selectionSession, selectionOptions, 0) + if err != nil { + log.Warn("apiTransactionProcessor.selectTransactions could not SelectTransactions") + return nil, err + } + + return atp.extractTransactions(selectedTxs, selectionOptions), nil +} + +func (atp *apiTransactionProcessor) extractTransactions(txs []*txcache.WrappedTransaction, selectionOptions common.TxSelectionOptionsAPI) []common.Transaction { + requestedFieldsHandler := newFieldsHandler(selectionOptions.GetRequestedFields()) + + transactions := make([]common.Transaction, len(txs)) + for i, tx := range txs { + transactions[i] = atp.extractRequestedTxInfo(tx, requestedFieldsHandler) + + } + + return transactions +} + +func (atp *apiTransactionProcessor) getVirtualNonceWithBlockInfo( + address []byte, +) (uint64, *common.LatestCommittedBlockResponse, error) { + cacheId := process.ShardCacherIdentifier(atp.shardCoordinator.SelfId(), atp.shardCoordinator.SelfId()) + cache := atp.dataPool.Transactions().ShardDataStore(cacheId) + txCache, ok := cache.(*txcache.TxCache) + if !ok { + log.Warn("apiTransactionProcessor.getVirtualNonceWithBlockInfo could not cast to TxCache") + return 0, nil, ErrCouldNotCastToTxCache + } + + // the SelectionSession is used in this flow for fallbacks (e.g. the account does not exist in the proposed blocks, unexpected errors etc.) + + // TODO use the right information below + // these variables will also be used for the response + // NOTE: should not remain like this + var latestCommittedBlockHash []byte + var currentNonce uint64 + + virtualNonce, rootHash, err := txCache.GetVirtualNonceAndRootHash(address) + if err != nil { + log.Warn("apiTransactionProcessor.getVirtualNonceWithBlockInfo could not get virtual nonce") + return 0, nil, err + } + + return virtualNonce, &common.LatestCommittedBlockResponse{ + Nonce: currentNonce, + Hash: hex.EncodeToString(latestCommittedBlockHash), + RootHash: hex.EncodeToString(rootHash), + }, nil +} + func (atp *apiTransactionProcessor) fetchTxsForSender(sender string, senderShard uint32) []*txcache.WrappedTransaction { cacheId := process.ShardCacherIdentifier(senderShard, senderShard) cache := atp.dataPool.Transactions().ShardDataStore(cacheId) @@ -526,6 +739,10 @@ func (atp *apiTransactionProcessor) computeTimestampForRound(round uint64) int64 secondsSinceGenesis := round * atp.roundDuration timestamp := atp.genesisTime.Add(time.Duration(secondsSinceGenesis) * time.Millisecond) + if atp.enableEpochsHandler.IsFlagEnabled(common.SupernovaFlag) { + return timestamp.UnixMilli() + } + return timestamp.Unix() } @@ -540,12 +757,66 @@ func (atp *apiTransactionProcessor) computeTimestampForRoundAsMs(round uint64) i return timestamp.UnixMilli() } +func (atp *apiTransactionProcessor) checkExecutionResultAndTx(miniblockMetadata *dblookupext.MiniblockMetadata) (bool, error) { + isSupernovaEnabled := atp.enableRoundsHandler.IsFlagEnabledInRound(common.SupernovaRoundFlag, miniblockMetadata.Round) + if !isSupernovaEnabled { + return true, nil + } + + headerHash := miniblockMetadata.GetHeaderHash() + executionResultsStorer, errG := atp.storageService.GetStorer(dataRetriever.ExecutionResultsUnit) + if errG != nil { + return false, errG + } + + executionResultsBytes, err := executionResultsStorer.GetFromEpoch(headerHash, miniblockMetadata.GetEpoch()) + if err != nil { + return false, err + } + + if atp.shardCoordinator.SelfId() == core.MetachainShardId { + // we cannot have unexecutable txs on metachain + return true, nil + } + mbHeaders, err := atp.getMbHeadersFromExecutionResultBytes(executionResultsBytes) + if err != nil { + return false, err + } + + // check if the transaction miniblock metadata has a mb header on execution result + // if yes - the transaction was executed + // if no - the transaction was proposed but not executed + currentTxIsExecuted := false + for _, mbHeader := range mbHeaders { + if bytes.Equal(mbHeader.Hash, miniblockMetadata.MiniblockHash) { + currentTxIsExecuted = true + break + } + } + return currentTxIsExecuted, nil +} + +func (atp *apiTransactionProcessor) getMbHeadersFromExecutionResultBytes(executionResultBytes []byte) ([]block.MiniBlockHeader, error) { + executResult := &block.ExecutionResult{} + err := atp.marshalizer.Unmarshal(executResult, executionResultBytes) + if err != nil { + return nil, err + } + + return executResult.GetMiniBlockHeaders(), nil +} + func (atp *apiTransactionProcessor) lookupHistoricalTransaction(hash []byte, withResults bool) (*transaction.ApiTransactionResult, error) { miniblockMetadata, err := atp.historyRepository.GetMiniblockMetadataByTxHash(hash) if err != nil { return nil, fmt.Errorf("%s: %w", ErrTransactionNotFound.Error(), err) } + isExecuted, err := atp.checkExecutionResultAndTx(miniblockMetadata) + if err != nil { + return nil, fmt.Errorf("%s: %w", ErrTransactionNotFound.Error(), err) + } + txBytes, txType, found := atp.getTxBytesFromStorageByEpoch(hash, miniblockMetadata.Epoch) if !found { log.Warn("lookupHistoricalTransaction(): unexpected condition, cannot find transaction in storage") @@ -573,6 +844,11 @@ func (atp *apiTransactionProcessor) lookupHistoricalTransaction(hash []byte, wit return nil, fmt.Errorf("%s: %w", ErrNilStatusComputer.Error(), err) } + if !isExecuted { + tx.Status = transaction.TxStatusNotExecutable + return tx, nil + } + if ok, _ := statusComputer.SetStatusIfIsRewardReverted( tx, block.Type(miniblockMetadata.Type), @@ -585,7 +861,7 @@ func (atp *apiTransactionProcessor) lookupHistoricalTransaction(hash []byte, wit block.Type(miniblockMetadata.Type), tx) if withResults { - err = atp.transactionResultsProcessor.putResultsInTransaction(hash, tx, miniblockMetadata.Epoch) + err = atp.transactionResultsProcessor.putResultsInTransaction(hash, tx, miniblockMetadata.Epoch, miniblockMetadata.Round) if err != nil { return nil, err } diff --git a/node/external/transactionAPI/apiTransactionProcessor_test.go b/node/external/transactionAPI/apiTransactionProcessor_test.go index ecf2a0b9c0f..5abf818c1a8 100644 --- a/node/external/transactionAPI/apiTransactionProcessor_test.go +++ b/node/external/transactionAPI/apiTransactionProcessor_test.go @@ -21,27 +21,44 @@ import ( "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-core-go/data/vm" "github.com/multiversx/mx-chain-core-go/marshal" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + datafield "github.com/multiversx/mx-chain-vm-common-go/parsers/dataField" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/common/holders" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/dblookupext" "github.com/multiversx/mx-chain-go/node/mock" "github.com/multiversx/mx-chain-go/process" processMocks "github.com/multiversx/mx-chain-go/process/mock" "github.com/multiversx/mx-chain-go/storage" - "github.com/multiversx/mx-chain-go/storage/txcache" "github.com/multiversx/mx-chain-go/testscommon" dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" dblookupextMock "github.com/multiversx/mx-chain-go/testscommon/dblookupext" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/genericMocks" "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" + stateMock "github.com/multiversx/mx-chain-go/testscommon/state" storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" "github.com/multiversx/mx-chain-go/testscommon/txcachemocks" - datafield "github.com/multiversx/mx-chain-vm-common-go/parsers/dataField" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/txcache" ) +const maxTrackedBlocks = 100 +const numTxsSelected = 30_000 +const gasRequested = 10_000_000_000 +const loopDurationCheckInterval = 10 + +func haveTimeTrueForSelection() bool { + return true +} + +var oneEGLD = big.NewInt(1000000000000000000) +var expectedErr = errors.New("expected error") + func createMockArgAPITransactionProcessor() *ArgAPITransactionProcessor { return &ArgAPITransactionProcessor{ RoundDuration: 0, @@ -57,12 +74,14 @@ func createMockArgAPITransactionProcessor() *ArgAPITransactionProcessor { TxTypeHandler: &testscommon.TxTypeHandlerMock{}, LogsFacade: &testscommon.LogsFacadeStub{}, DataFieldParser: &testscommon.DataFieldParserStub{ - ParseCalled: func(dataField []byte, sender, receiver []byte, _ uint32) *datafield.ResponseParseData { + ParseCalled: func(dataField []byte, sender, receiver []byte, _ uint32, _ uint32) *datafield.ResponseParseData { return &datafield.ResponseParseData{} }, }, TxMarshaller: &marshallerMock.MarshalizerMock{}, EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, } } @@ -203,6 +222,24 @@ func TestNewAPITransactionProcessor(t *testing.T) { _, err := NewAPITransactionProcessor(arguments) require.Equal(t, process.ErrNilEnableEpochsHandler, err) }) + t.Run("NilEnableRoundsHandler", func(t *testing.T) { + t.Parallel() + + arguments := createMockArgAPITransactionProcessor() + arguments.EnableRoundsHandler = nil + + _, err := NewAPITransactionProcessor(arguments) + require.Equal(t, process.ErrNilEnableRoundsHandler, err) + }) + t.Run("NilTransactionVersionChecker", func(t *testing.T) { + t.Parallel() + + arguments := createMockArgAPITransactionProcessor() + arguments.TxVersionChecker = nil + + _, err := NewAPITransactionProcessor(arguments) + require.Equal(t, process.ErrNilTransactionVersionChecker, err) + }) } func TestNode_GetTransactionInvalidHashShouldErr(t *testing.T) { @@ -213,6 +250,20 @@ func TestNode_GetTransactionInvalidHashShouldErr(t *testing.T) { assert.Error(t, err) } +func TestNode_GetTransactionFoundInPoolAndStorageShouldReturnFromStorage(t *testing.T) { + n, chainStorer, dataPool, _ := createAPITransactionProc(t, 42, false) + + txA := &transaction.Transaction{Nonce: 7, SndAddr: []byte("bob"), RcvAddr: []byte("alice")} + dataPool.Transactions().AddData([]byte("a"), txA, 42, "1") + + internalMarshalizer := &mock.MarshalizerFake{} + _ = chainStorer.Transactions.PutWithMarshalizer([]byte("a"), txA, internalMarshalizer) + + actualA, err := n.GetTransaction(hex.EncodeToString([]byte("a")), false) + require.Nil(t, err) + require.Equal(t, transaction.TxStatusSuccess, actualA.Status) +} + func TestNode_GetTransactionFromPool(t *testing.T) { t.Parallel() @@ -356,12 +407,14 @@ func TestNode_GetSCRs(t *testing.T) { TxTypeHandler: &testscommon.TxTypeHandlerMock{}, LogsFacade: &testscommon.LogsFacadeStub{}, DataFieldParser: &testscommon.DataFieldParserStub{ - ParseCalled: func(dataField []byte, sender, receiver []byte, _ uint32) *datafield.ResponseParseData { + ParseCalled: func(dataField []byte, sender, receiver []byte, _ uint32, _ uint32) *datafield.ResponseParseData { return &datafield.ResponseParseData{} }, }, EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), TxMarshaller: &mock.MarshalizerFake{}, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, } apiTransactionProc, _ := NewAPITransactionProcessor(args) @@ -498,6 +551,182 @@ func testWithMissingStorer(missingUnit dataRetriever.UnitType) func(t *testing.T } } +func TestNode_GetTransactionCheckExecutionResults(t *testing.T) { + t.Parallel() + + t.Run("get transaction by hash, unexecutable tx", func(t *testing.T) { + marshalizer := &mock.MarshalizerFake{} + txHash := hex.EncodeToString([]byte("txHash")) + tx := &transaction.Transaction{Nonce: 7, SndAddr: []byte("alice"), RcvAddr: []byte("bob")} + scResultHash := []byte("scHash") + + resultHashesByTxHash := &dblookupext.ResultsHashesByTxHash{ + ScResultsHashesAndEpoch: []*dblookupext.ScResultsHashesAndEpoch{ + { + Epoch: 0, + ScResultsHashes: [][]byte{scResultHash}, + }, + }, + } + + executionResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{}, + } + + chainStorer := &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + switch unitType { + case dataRetriever.TransactionUnit: + return &storageStubs.StorerStub{ + GetFromEpochCalled: func(key []byte, epoch uint32) ([]byte, error) { + return marshalizer.Marshal(tx) + }, + }, nil + case dataRetriever.ExecutionResultsUnit: + return &storageStubs.StorerStub{ + GetFromEpochCalled: func(key []byte, epoch uint32) ([]byte, error) { + return marshalizer.Marshal(executionResult) + }, + }, nil + default: + return nil, storage.ErrKeyNotFound + } + }, + } + + proposedMBHash := []byte("proposedMBHash") + historyRepo := &dblookupextMock.HistoryRepositoryStub{ + GetMiniblockMetadataByTxHashCalled: func(hash []byte) (*dblookupext.MiniblockMetadata, error) { + return &dblookupext.MiniblockMetadata{ + MiniblockHash: proposedMBHash, + Round: 100, + }, nil + }, + GetEventsHashesByTxHashCalled: func(hash []byte, epoch uint32) (*dblookupext.ResultsHashesByTxHash, error) { + return resultHashesByTxHash, nil + }, + } + + args := &ArgAPITransactionProcessor{ + RoundDuration: 0, + GenesisTime: time.Time{}, + Marshalizer: &mock.MarshalizerFake{}, + AddressPubKeyConverter: &testscommon.PubkeyConverterMock{}, + ShardCoordinator: &mock.ShardCoordinatorMock{}, + HistoryRepository: historyRepo, + StorageService: chainStorer, + DataPool: dataRetrieverMock.NewPoolsHolderMock(), + Uint64ByteSliceConverter: mock.NewNonceHashConverterMock(), + FeeComputer: &testscommon.FeeComputerStub{}, + TxTypeHandler: &testscommon.TxTypeHandlerMock{}, + LogsFacade: &testscommon.LogsFacadeStub{}, + DataFieldParser: &testscommon.DataFieldParserStub{ + ParseCalled: func(dataField []byte, sender, receiver []byte, _ uint32, _ uint32) *datafield.ResponseParseData { + return &datafield.ResponseParseData{} + }, + }, + TxMarshaller: &marshallerMock.MarshalizerMock{}, + EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return true + }, + }, + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + } + apiTransactionProc, _ := NewAPITransactionProcessor(args) + + apiTx, err := apiTransactionProc.GetTransaction(txHash, true) + require.Nil(t, err) + require.Equal(t, transaction.TxStatusNotExecutable, apiTx.Status) + }) + + t.Run("get transaction by hash, transaction is execution", func(t *testing.T) { + marshalizer := &mock.MarshalizerFake{} + txHash := hex.EncodeToString([]byte("txHash")) + tx := &transaction.Transaction{Nonce: 7, SndAddr: []byte("alice"), RcvAddr: []byte("bob")} + + miniblockHash := []byte("mbHash") + executionResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{}, + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("h"), + }, + { + Hash: miniblockHash, + }, + }, + } + + chainStorer := &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + switch unitType { + case dataRetriever.TransactionUnit: + return &storageStubs.StorerStub{ + GetFromEpochCalled: func(key []byte, epoch uint32) ([]byte, error) { + return marshalizer.Marshal(tx) + }, + }, nil + case dataRetriever.ExecutionResultsUnit: + return &storageStubs.StorerStub{ + GetFromEpochCalled: func(key []byte, epoch uint32) ([]byte, error) { + return marshalizer.Marshal(executionResult) + }, + }, nil + default: + return nil, storage.ErrKeyNotFound + } + }, + } + + historyRepo := &dblookupextMock.HistoryRepositoryStub{ + GetMiniblockMetadataByTxHashCalled: func(hash []byte) (*dblookupext.MiniblockMetadata, error) { + return &dblookupext.MiniblockMetadata{ + MiniblockHash: miniblockHash, + Round: 100, + }, nil + }, + GetEventsHashesByTxHashCalled: func(hash []byte, epoch uint32) (*dblookupext.ResultsHashesByTxHash, error) { + return nil, dblookupext.ErrNotFoundInStorage + }, + } + + args := &ArgAPITransactionProcessor{ + RoundDuration: 0, + GenesisTime: time.Time{}, + Marshalizer: &mock.MarshalizerFake{}, + AddressPubKeyConverter: &testscommon.PubkeyConverterMock{}, + ShardCoordinator: &mock.ShardCoordinatorMock{}, + HistoryRepository: historyRepo, + StorageService: chainStorer, + DataPool: dataRetrieverMock.NewPoolsHolderMock(), + Uint64ByteSliceConverter: mock.NewNonceHashConverterMock(), + FeeComputer: &testscommon.FeeComputerStub{}, + TxTypeHandler: &testscommon.TxTypeHandlerMock{}, + LogsFacade: &testscommon.LogsFacadeStub{}, + DataFieldParser: &testscommon.DataFieldParserStub{ + ParseCalled: func(dataField []byte, sender, receiver []byte, _ uint32, _ uint32) *datafield.ResponseParseData { + return &datafield.ResponseParseData{} + }, + }, + TxMarshaller: &marshallerMock.MarshalizerMock{}, + EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return true + }, + }, + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + } + apiTransactionProc, _ := NewAPITransactionProcessor(args) + + apiTx, err := apiTransactionProc.GetTransaction(txHash, true) + require.Nil(t, err) + require.Equal(t, transaction.TxStatusSuccess, apiTx.Status) + }) +} + func TestNode_GetTransactionWithResultsFromStorage(t *testing.T) { t.Parallel() @@ -574,12 +803,14 @@ func TestNode_GetTransactionWithResultsFromStorage(t *testing.T) { TxTypeHandler: &testscommon.TxTypeHandlerMock{}, LogsFacade: &testscommon.LogsFacadeStub{}, DataFieldParser: &testscommon.DataFieldParserStub{ - ParseCalled: func(dataField []byte, sender, receiver []byte, _ uint32) *datafield.ResponseParseData { + ParseCalled: func(dataField []byte, sender, receiver []byte, _ uint32, _ uint32) *datafield.ResponseParseData { return &datafield.ResponseParseData{} }, }, TxMarshaller: &marshallerMock.MarshalizerMock{}, EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, } apiTransactionProc, _ := NewAPITransactionProcessor(args) @@ -867,9 +1098,10 @@ func TestApiTransactionProcessor_GetTransactionsPool(t *testing.T) { func createTx(hash []byte, sender string, nonce uint64) *txcache.WrappedTransaction { tx := &transaction.Transaction{ - SndAddr: []byte(sender), - Nonce: nonce, - Value: big.NewInt(100000 + int64(nonce)), + SndAddr: []byte(sender), + Nonce: nonce, + Value: big.NewInt(100000 + int64(nonce)), + GasLimit: 50000, } return &txcache.WrappedTransaction{ @@ -892,7 +1124,11 @@ func TestApiTransactionProcessor_GetTransactionsPoolForSender(t *testing.T) { CountThreshold: math.MaxUint32, CountPerSenderThreshold: math.MaxUint32, NumItemsToPreemptivelyEvict: 1, - }, txcachemocks.NewMempoolHostMock()) + TxCacheBoundsConfig: config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: 33_554_432, + MaxTrackedBlocks: maxTrackedBlocks, + }, + }, txcachemocks.NewMempoolHostMock(), 0) require.NoError(t, err) @@ -909,7 +1145,13 @@ func TestApiTransactionProcessor_GetTransactionsPoolForSender(t *testing.T) { CountThreshold: math.MaxUint32, CountPerSenderThreshold: math.MaxUint32, NumItemsToPreemptivelyEvict: 1, - }, txcachemocks.NewMempoolHostMock()) + TxCacheBoundsConfig: config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: 33_554_432, + MaxTrackedBlocks: maxTrackedBlocks, + }, + }, txcachemocks.NewMempoolHostMock(), 0) + require.NoError(t, err) + txCacheWithMeta.AddTx(createTx(txHash3, sender, 4)) txCacheWithMeta.AddTx(createTx(txHash4, sender, 5)) @@ -995,7 +1237,12 @@ func TestApiTransactionProcessor_GetLastPoolNonceForSender(t *testing.T) { CountThreshold: math.MaxUint32, CountPerSenderThreshold: math.MaxUint32, NumItemsToPreemptivelyEvict: 1, - }, txcachemocks.NewMempoolHostMock()) + TxCacheBoundsConfig: config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: 33_554_432, + MaxTrackedBlocks: maxTrackedBlocks, + }, + }, txcachemocks.NewMempoolHostMock(), 0) + txCacheIntraShard.AddTx(createTx(txHash2, sender, 3)) txCacheIntraShard.AddTx(createTx(txHash0, sender, 1)) txCacheIntraShard.AddTx(createTx(txHash1, sender, 2)) @@ -1047,7 +1294,11 @@ func TestApiTransactionProcessor_GetTransactionsPoolNonceGapsForSender(t *testin CountThreshold: math.MaxUint32, CountPerSenderThreshold: math.MaxUint32, NumItemsToPreemptivelyEvict: 1, - }, txcachemocks.NewMempoolHostMock()) + TxCacheBoundsConfig: config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: 33_554_432, + MaxTrackedBlocks: maxTrackedBlocks, + }, + }, txcachemocks.NewMempoolHostMock(), 0) require.NoError(t, err) @@ -1059,7 +1310,11 @@ func TestApiTransactionProcessor_GetTransactionsPoolNonceGapsForSender(t *testin CountThreshold: math.MaxUint32, CountPerSenderThreshold: math.MaxUint32, NumItemsToPreemptivelyEvict: 1, - }, txcachemocks.NewMempoolHostMock()) + TxCacheBoundsConfig: config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: 33_554_432, + MaxTrackedBlocks: maxTrackedBlocks, + }, + }, txcachemocks.NewMempoolHostMock(), 0) require.NoError(t, err) @@ -1136,6 +1391,422 @@ func TestApiTransactionProcessor_GetTransactionsPoolNonceGapsForSender(t *testin }, res) } +func TestApiTransactionProcessor_GetSelectedTransactions(t *testing.T) { + t.Parallel() + + cache, err := txcache.NewTxCache(txcache.ConfigSourceMe{ + Name: "test", + NumChunks: 4, + NumBytesThreshold: 1_048_576, // 1 MB + NumBytesPerSenderThreshold: 1_048_576, // 1 MB + CountThreshold: math.MaxUint32, + CountPerSenderThreshold: math.MaxUint32, + NumItemsToPreemptivelyEvict: 1, + TxCacheBoundsConfig: config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: 33_554_432, + MaxTrackedBlocks: maxTrackedBlocks, + }, + }, txcachemocks.NewMempoolHostMock(), 0) + + require.NoError(t, err) + + cache.AddTx(createTx([]byte("hash1"), "alice", 0)) + cache.AddTx(createTx([]byte("hash2"), "bob", 0)) + cache.AddTx(createTx([]byte("hash3"), "alice", 1)) + cache.AddTx(createTx([]byte("hash4"), "bob", 1)) + + require.Equal(t, uint64(4), cache.CountTx()) + + err = cache.OnExecutedBlock(&block.Header{}, []byte("rootHash")) + require.NoError(t, err) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + args := createMockArgAPITransactionProcessor() + args.DataPool = &dataRetrieverMock.PoolsHolderStub{ + TransactionsCalled: func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + ShardDataStoreCalled: func(cacheID string) storage.Cacher { + if cacheID == "1" { // self shard + return cache + } + return nil + }, + } + }, + } + atp, err := NewAPITransactionProcessor(args) + require.NoError(t, err) + require.NotNil(t, atp) + + accountsAdapter := &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return []byte("rootHash"), nil + }, + GetExistingAccountCalled: func(addressContainer []byte) (vmcommon.AccountHandler, error) { + if bytes.Equal(addressContainer, []byte("alice")) { + return &stateMock.AccountWrapMock{ + Balance: oneEGLD, + }, nil + } + if bytes.Equal(addressContainer, []byte("bob")) { + return &stateMock.AccountWrapMock{ + Balance: oneEGLD, + }, nil + } + + return nil, nil + }, + RecreateTrieCalled: func(options common.RootHashHolder) error { + return nil + }, + } + + blockchainMock := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.Header{} + }, + GetCurrentBlockRootHashCalled: func() []byte { + return []byte("root hash") + }, + } + + options, _ := holders.NewTxSelectionOptions( + gasRequested, + numTxsSelected, + loopDurationCheckInterval, + haveTimeTrueForSelection, + ) + + selectionOptionsAPI := holders.NewTxSelectionOptionsAPI( + options, + "hash", + ) + + selectedTxs, err := atp.GetSelectedTransactions(selectionOptionsAPI, blockchainMock, accountsAdapter) + require.NoError(t, err) + require.Len(t, selectedTxs.Transactions, 4) + }) + + t.Run("should work and contain requested fields", func(t *testing.T) { + t.Parallel() + + args := createMockArgAPITransactionProcessor() + args.DataPool = &dataRetrieverMock.PoolsHolderStub{ + TransactionsCalled: func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + ShardDataStoreCalled: func(cacheID string) storage.Cacher { + if cacheID == "1" { // self shard + return cache + } + return nil + }, + } + }, + } + + atp, err := NewAPITransactionProcessor(args) + require.NoError(t, err) + require.NotNil(t, atp) + + accountsAdapter := &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return []byte("rootHash"), nil + }, + GetExistingAccountCalled: func(addressContainer []byte) (vmcommon.AccountHandler, error) { + if bytes.Equal(addressContainer, []byte("alice")) { + return &stateMock.AccountWrapMock{ + Balance: oneEGLD, + }, nil + } + if bytes.Equal(addressContainer, []byte("bob")) { + return &stateMock.AccountWrapMock{ + Balance: oneEGLD, + }, nil + } + + return nil, nil + }, + RecreateTrieCalled: func(options common.RootHashHolder) error { + return nil + }, + } + + blockchainMock := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.Header{} + }, + GetCurrentBlockRootHashCalled: func() []byte { + return []byte("root hash") + }, + } + + options, _ := holders.NewTxSelectionOptions( + gasRequested, + numTxsSelected, + loopDurationCheckInterval, + haveTimeTrueForSelection, + ) + + selectionOptionsAPI := holders.NewTxSelectionOptionsAPI( + options, + "hash,sender,relayer,nonce,ppu", + ) + + selectedTxs, err := atp.GetSelectedTransactions(selectionOptionsAPI, blockchainMock, accountsAdapter) + require.NoError(t, err) + require.Len(t, selectedTxs.Transactions, 4) + + for _, tx := range selectedTxs.Transactions { + _, ok := tx.TxFields["sender"] + require.True(t, ok) + + _, ok = tx.TxFields["relayer"] + require.False(t, ok) + + _, ok = tx.TxFields["nonce"] + require.True(t, ok) + + _, ok = tx.TxFields["ppu"] + require.True(t, ok) + } + }) + + t.Run("should return error from SelectTransactions", func(t *testing.T) { + t.Parallel() + + args := createMockArgAPITransactionProcessor() + args.DataPool = &dataRetrieverMock.PoolsHolderStub{ + TransactionsCalled: func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + ShardDataStoreCalled: func(cacheID string) storage.Cacher { + if cacheID == "1" { // self shard + return cache + } + return nil + }, + } + }, + } + + atp, err := NewAPITransactionProcessor(args) + require.NoError(t, err) + require.NotNil(t, atp) + + accountsAdapter := &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return nil, expectedErr + }, + } + + options, _ := holders.NewTxSelectionOptions( + gasRequested, + numTxsSelected, + loopDurationCheckInterval, + haveTimeTrueForSelection, + ) + + selectionOptionsAPI := holders.NewTxSelectionOptionsAPI( + options, + "hash", + ) + + _, err = atp.GetSelectedTransactions(selectionOptionsAPI, nil, accountsAdapter) + require.Equal(t, ErrNilBlockchain, err) + }) + + t.Run("should return ErrNilAccountsAdapter error", func(t *testing.T) { + t.Parallel() + + args := createMockArgAPITransactionProcessor() + args.DataPool = &dataRetrieverMock.PoolsHolderStub{ + TransactionsCalled: func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + ShardDataStoreCalled: func(cacheID string) storage.Cacher { + if cacheID == "1" { // self shard + return cache + } + return nil + }, + } + }, + } + + blockchainMock := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.Header{} + }, + GetCurrentBlockRootHashCalled: func() []byte { + return []byte("root hash") + }, + } + + atp, err := NewAPITransactionProcessor(args) + require.NoError(t, err) + require.NotNil(t, atp) + + options, _ := holders.NewTxSelectionOptions( + gasRequested, + numTxsSelected, + loopDurationCheckInterval, + haveTimeTrueForSelection, + ) + + selectionOptionsAPI := holders.NewTxSelectionOptionsAPI( + options, + "hash", + ) + + selectedTxs, err := atp.GetSelectedTransactions(selectionOptionsAPI, blockchainMock, nil) + require.Equal(t, ErrNilAccountStateAPI, err) + require.Nil(t, selectedTxs) + }) + + t.Run("should return ErrCouldNotCastToTxCache error", func(t *testing.T) { + t.Parallel() + + args := createMockArgAPITransactionProcessor() + args.DataPool = &dataRetrieverMock.PoolsHolderStub{ + TransactionsCalled: func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + ShardDataStoreCalled: func(cacheID string) storage.Cacher { + return nil + }, + } + }, + } + + accountsAdapter := &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return []byte("rootHash"), nil + }, + GetExistingAccountCalled: func(addressContainer []byte) (vmcommon.AccountHandler, error) { + if bytes.Equal(addressContainer, []byte("alice")) { + return &stateMock.AccountWrapMock{ + Balance: oneEGLD, + }, nil + } + if bytes.Equal(addressContainer, []byte("bob")) { + return &stateMock.AccountWrapMock{ + Balance: oneEGLD, + }, nil + } + + return nil, nil + }, + RecreateTrieCalled: func(options common.RootHashHolder) error { + return nil + }, + } + + atp, err := NewAPITransactionProcessor(args) + require.NoError(t, err) + require.NotNil(t, atp) + + options, _ := holders.NewTxSelectionOptions( + gasRequested, + numTxsSelected, + loopDurationCheckInterval, + haveTimeTrueForSelection, + ) + + blockchainMock := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.Header{} + }, + GetCurrentBlockRootHashCalled: func() []byte { + return []byte("root hash") + }, + } + + selectionOptionsAPI := holders.NewTxSelectionOptionsAPI( + options, + "hash", + ) + + selectedTxs, err := atp.GetSelectedTransactions(selectionOptionsAPI, blockchainMock, accountsAdapter) + require.Equal(t, ErrCouldNotCastToTxCache, err) + require.Nil(t, selectedTxs) + }) +} + +func TestApiTransactionProcessor_GetVirtualNonce(t *testing.T) { + t.Parallel() + + cache, err := txcache.NewTxCache(txcache.ConfigSourceMe{ + Name: "test", + NumChunks: 4, + NumBytesThreshold: 1_048_576, // 1 MB + NumBytesPerSenderThreshold: 1_048_576, // 1 MB + CountThreshold: math.MaxUint32, + CountPerSenderThreshold: math.MaxUint32, + NumItemsToPreemptivelyEvict: 1, + TxCacheBoundsConfig: config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: 33_554_432, + MaxTrackedBlocks: maxTrackedBlocks, + }, + }, txcachemocks.NewMempoolHostMock(), 0) + + require.NoError(t, err) + + t.Run("should return ErrCouldNotCastToTxCache error", func(t *testing.T) { + t.Parallel() + + args := createMockArgAPITransactionProcessor() + args.DataPool = &dataRetrieverMock.PoolsHolderStub{ + TransactionsCalled: func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + ShardDataStoreCalled: func(cacheID string) storage.Cacher { + return nil + }, + } + }, + } + args.AddressPubKeyConverter = &testscommon.PubkeyConverterMock{ + DecodeCalled: func(humanReadable string) ([]byte, error) { + return []byte(humanReadable), nil + }, + } + + atp, err := NewAPITransactionProcessor(args) + require.NoError(t, err) + require.NotNil(t, atp) + + virtualNonce, err := atp.GetVirtualNonce("alice") + require.Equal(t, ErrCouldNotCastToTxCache, err) + require.Nil(t, virtualNonce) + }) + + t.Run("should return error from decoding", func(t *testing.T) { + t.Parallel() + + args := createMockArgAPITransactionProcessor() + args.DataPool = &dataRetrieverMock.PoolsHolderStub{ + TransactionsCalled: func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + ShardDataStoreCalled: func(cacheID string) storage.Cacher { + return cache + }, + } + }, + } + args.AddressPubKeyConverter = &testscommon.PubkeyConverterMock{ + DecodeCalled: func(humanReadable string) ([]byte, error) { + return nil, expectedErr + }, + } + + atp, err := NewAPITransactionProcessor(args) + require.NoError(t, err) + require.NotNil(t, atp) + + virtualNonce, err := atp.GetVirtualNonce("alice") + require.ErrorContains(t, err, expectedErr.Error()) + require.Nil(t, virtualNonce) + }) +} + func createAPITransactionProc(t *testing.T, epoch uint32, withDbLookupExt bool) (*apiTransactionProcessor, *genericMocks.ChainStorerMock, *dataRetrieverMock.PoolsHolderMock, *dblookupextMock.HistoryRepositoryStub) { chainStorer := genericMocks.NewChainStorerMock(epoch) dataPool := dataRetrieverMock.NewPoolsHolderMock() @@ -1146,7 +1817,7 @@ func createAPITransactionProc(t *testing.T, epoch uint32, withDbLookupExt bool) }, } dataFieldParser := &testscommon.DataFieldParserStub{ - ParseCalled: func(dataField []byte, sender, receiver []byte, _ uint32) *datafield.ResponseParseData { + ParseCalled: func(dataField []byte, sender, receiver []byte, _ uint32, _ uint32) *datafield.ResponseParseData { if strings.Contains(string(dataField), "relayed") { return &datafield.ResponseParseData{ IsRelayed: true, @@ -1176,6 +1847,8 @@ func createAPITransactionProc(t *testing.T, epoch uint32, withDbLookupExt bool) return flag == common.RelayedTransactionsV1V2DisableFlag }, }, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, } apiTransactionProc, err := NewAPITransactionProcessor(args) require.Nil(t, err) @@ -1409,3 +2082,140 @@ func TestApiTransactionProcessor_PopulateComputedFields(t *testing.T) { require.Equal(t, "SCDeployment", apiTx.ProcessingTypeOnDestination) require.Equal(t, "1000", apiTx.InitiallyPaidFee) } + +func Test_GetCurrentRootHash(t *testing.T) { + t.Parallel() + + t.Run("should fail if nil current header", func(t *testing.T) { + t.Parallel() + + arguments := createMockArgAPITransactionProcessor() + + processor, _ := NewAPITransactionProcessor(arguments) + + chainHandler := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return nil + }, + } + + retRootHash, err := processor.getCurrentRootHash(chainHandler) + require.Equal(t, ErrNilBlockHeader, err) + require.Nil(t, retRootHash) + }) + + t.Run("before header v3, should fail if not able to get current root hash", func(t *testing.T) { + t.Parallel() + + arguments := createMockArgAPITransactionProcessor() + + processor, _ := NewAPITransactionProcessor(arguments) + + header := &block.HeaderV2{} + + chainHandler := &testscommon.ChainHandlerStub{ + GetCurrentBlockRootHashCalled: func() []byte { + return nil + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + } + + retRootHash, err := processor.getCurrentRootHash(chainHandler) + require.Equal(t, ErrNilCurrentRootHash, err) + require.Nil(t, retRootHash) + }) + + t.Run("before header v3 should get root hash from current chain handler root hash", func(t *testing.T) { + t.Parallel() + + arguments := createMockArgAPITransactionProcessor() + + processor, _ := NewAPITransactionProcessor(arguments) + + expRootHash := []byte("expRootHash") + + header := &block.HeaderV2{} + + chainHandler := &testscommon.ChainHandlerStub{ + GetCurrentBlockRootHashCalled: func() []byte { + return expRootHash + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return 0, []byte{}, []byte{} + }, + } + + retRootHash, err := processor.getCurrentRootHash(chainHandler) + require.Nil(t, err) + require.Equal(t, expRootHash, retRootHash) + }) + + t.Run("on header v3 should get root hash from last executed block info", func(t *testing.T) { + t.Parallel() + + arguments := createMockArgAPITransactionProcessor() + + processor, _ := NewAPITransactionProcessor(arguments) + + expRootHash := []byte("expRootHash") + + header := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash1"), + RootHash: []byte("rootHash1"), + }, + }, + } + + chainHandler := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return 0, []byte{}, expRootHash + }, + } + + retRootHash, err := processor.getCurrentRootHash(chainHandler) + require.Nil(t, err) + require.Equal(t, expRootHash, retRootHash) + }) + + t.Run("on first header v3 should get root hash from last execution result", func(t *testing.T) { + t.Parallel() + + arguments := createMockArgAPITransactionProcessor() + + processor, _ := NewAPITransactionProcessor(arguments) + + expRootHash := []byte("rootHash1") + + header := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash1"), + RootHash: expRootHash, + }, + }, + } + + chainHandler := &testscommon.ChainHandlerStub{ + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return 0, []byte{}, []byte{} // root hash not set + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + } + + retRootHash, err := processor.getCurrentRootHash(chainHandler) + require.Nil(t, err) + require.Equal(t, expRootHash, retRootHash) + }) +} diff --git a/node/external/transactionAPI/apiTransactionResults.go b/node/external/transactionAPI/apiTransactionResults.go index d4a89edfd15..cee8205b3c4 100644 --- a/node/external/transactionAPI/apiTransactionResults.go +++ b/node/external/transactionAPI/apiTransactionResults.go @@ -9,6 +9,8 @@ import ( "github.com/multiversx/mx-chain-core-go/data/smartContractResult" "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-core-go/marshal" + + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/dblookupext" "github.com/multiversx/mx-chain-go/node/filters" @@ -25,6 +27,7 @@ type apiTransactionResultsProcessor struct { shardCoordinator sharding.Coordinator refundDetector *refundDetector logsFacade LogsFacade + enableRoundsHandler common.EnableRoundsHandler } func newAPITransactionResultProcessor( @@ -36,6 +39,7 @@ func newAPITransactionResultProcessor( logsFacade LogsFacade, shardCoordinator sharding.Coordinator, dataFieldParser DataFieldParser, + enableRoundsHandler common.EnableRoundsHandler, ) *apiTransactionResultsProcessor { refundDetector := NewRefundDetector() @@ -49,10 +53,11 @@ func newAPITransactionResultProcessor( refundDetector: refundDetector, logsFacade: logsFacade, dataFieldParser: dataFieldParser, + enableRoundsHandler: enableRoundsHandler, } } -func (arp *apiTransactionResultsProcessor) putResultsInTransaction(hash []byte, tx *transaction.ApiTransactionResult, epoch uint32) error { +func (arp *apiTransactionResultsProcessor) putResultsInTransaction(hash []byte, tx *transaction.ApiTransactionResult, epoch uint32, round uint64) error { // TODO: Note that the following call produces an effect even if the function "putResultsInTransaction" results in an error. // TODO: Refactor this package to use less functions with side-effects. arp.loadLogsIntoTransaction(hash, tx, epoch) @@ -67,14 +72,14 @@ func (arp *apiTransactionResultsProcessor) putResultsInTransaction(hash []byte, } if len(resultsHashes.ReceiptsHash) > 0 { - return arp.putReceiptInTransaction(tx, resultsHashes.ReceiptsHash, epoch) + return arp.putReceiptInTransaction(tx, resultsHashes.ReceiptsHash, epoch, round) } return arp.putSmartContractResultsInTransaction(tx, resultsHashes.ScResultsHashesAndEpoch) } -func (arp *apiTransactionResultsProcessor) putReceiptInTransaction(tx *transaction.ApiTransactionResult, receiptHash []byte, epoch uint32) error { - rec, err := arp.getReceiptFromStorage(receiptHash, epoch) +func (arp *apiTransactionResultsProcessor) putReceiptInTransaction(tx *transaction.ApiTransactionResult, receiptHash []byte, epoch uint32, round uint64) error { + rec, err := arp.getReceiptFromStorage(receiptHash, epoch, round) if err != nil { return fmt.Errorf("%w: %v, hash = %s", errCannotLoadReceipts, err, hex.EncodeToString(receiptHash)) } @@ -83,8 +88,9 @@ func (arp *apiTransactionResultsProcessor) putReceiptInTransaction(tx *transacti return nil } -func (arp *apiTransactionResultsProcessor) getReceiptFromStorage(hash []byte, epoch uint32) (*transaction.ApiReceipt, error) { - receiptsStorer, err := arp.storageService.GetStorer(dataRetriever.UnsignedTransactionUnit) +func (arp *apiTransactionResultsProcessor) getReceiptFromStorage(hash []byte, epoch uint32, round uint64) (*transaction.ApiReceipt, error) { + unitType := arp.getReceiptsStorerUnitType(round) + receiptsStorer, err := arp.storageService.GetStorer(unitType) if err != nil { return nil, err } @@ -97,6 +103,13 @@ func (arp *apiTransactionResultsProcessor) getReceiptFromStorage(hash []byte, ep return arp.txUnmarshaller.unmarshalReceipt(receiptBytes) } +func (arp *apiTransactionResultsProcessor) getReceiptsStorerUnitType(round uint64) dataRetriever.UnitType { + if arp.enableRoundsHandler.IsFlagEnabledInRound(common.SupernovaRoundFlag, round) { + return dataRetriever.ReceiptsUnit + } + return dataRetriever.UnsignedTransactionUnit +} + func (arp *apiTransactionResultsProcessor) putSmartContractResultsInTransaction( tx *transaction.ApiTransactionResult, scrHashesEpoch []*dblookupext.ScResultsHashesAndEpoch, @@ -123,7 +136,7 @@ func (arp *apiTransactionResultsProcessor) getSmartContractResultsInTransactionB return nil, fmt.Errorf("%w: %v, hash = %s", errCannotLoadContractResults, err, hex.EncodeToString(scrHash)) } - scrAPI := arp.adaptSmartContractResult(scrHash, scr) + scrAPI := arp.adaptSmartContractResult(scrHash, scr, epoch) arp.loadLogsIntoContractResults(scrHash, epoch, scrAPI) @@ -171,7 +184,7 @@ func (arp *apiTransactionResultsProcessor) getScrFromStorage(hash []byte, epoch return scr, nil } -func (arp *apiTransactionResultsProcessor) adaptSmartContractResult(scrHash []byte, scr *smartContractResult.SmartContractResult) *transaction.ApiSmartContractResult { +func (arp *apiTransactionResultsProcessor) adaptSmartContractResult(scrHash []byte, scr *smartContractResult.SmartContractResult, epoch uint32) *transaction.ApiSmartContractResult { isRefund := arp.refundDetector.IsRefund(RefundDetectorInput{ Value: scr.Value.String(), Data: scr.Data, @@ -201,7 +214,7 @@ func (arp *apiTransactionResultsProcessor) adaptSmartContractResult(scrHash []by apiSCR.RelayerAddr, _ = arp.addressPubKeyConverter.Encode(scr.RelayerAddr) apiSCR.OriginalSender, _ = arp.addressPubKeyConverter.Encode(scr.OriginalSender) - res := arp.dataFieldParser.Parse(scr.Data, scr.GetSndAddr(), scr.GetRcvAddr(), arp.shardCoordinator.NumberOfShards()) + res := arp.dataFieldParser.Parse(scr.Data, scr.GetSndAddr(), scr.GetRcvAddr(), arp.shardCoordinator.NumberOfShards(), epoch) apiSCR.Operation = res.Operation apiSCR.Function = res.Function apiSCR.ESDTValues = res.ESDTValues diff --git a/node/external/transactionAPI/apiTransactionResults_test.go b/node/external/transactionAPI/apiTransactionResults_test.go index c21eef1cc8b..d978d094254 100644 --- a/node/external/transactionAPI/apiTransactionResults_test.go +++ b/node/external/transactionAPI/apiTransactionResults_test.go @@ -11,6 +11,10 @@ import ( "github.com/multiversx/mx-chain-core-go/data/receipt" "github.com/multiversx/mx-chain-core-go/data/smartContractResult" "github.com/multiversx/mx-chain-core-go/data/transaction" + datafield "github.com/multiversx/mx-chain-vm-common-go/parsers/dataField" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/dblookupext" "github.com/multiversx/mx-chain-go/node/mock" @@ -21,8 +25,6 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/genericMocks" "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" - datafield "github.com/multiversx/mx-chain-vm-common-go/parsers/dataField" - "github.com/stretchr/testify/require" ) func TestPutEventsInTransactionReceipt(t *testing.T) { @@ -59,7 +61,7 @@ func TestPutEventsInTransactionReceipt(t *testing.T) { pubKeyConverter := &testscommon.PubkeyConverterMock{} logsFacade := &testscommon.LogsFacadeStub{} dataFieldParser := &testscommon.DataFieldParserStub{ - ParseCalled: func(dataField []byte, sender, receiver []byte, _ uint32) *datafield.ResponseParseData { + ParseCalled: func(dataField []byte, sender, receiver []byte, _ uint32, _ uint32) *datafield.ResponseParseData { return &datafield.ResponseParseData{} }, } @@ -71,9 +73,11 @@ func TestPutEventsInTransactionReceipt(t *testing.T) { shardCoordinator, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, ) - n := newAPITransactionResultProcessor(pubKeyConverter, historyRepo, dataStore, marshalizerdMock, txUnmarshalerAndPreparer, logsFacade, shardCoordinator, dataFieldParser) + enableRoundsHandler := &testscommon.EnableRoundsHandlerStub{} + n := newAPITransactionResultProcessor(pubKeyConverter, historyRepo, dataStore, marshalizerdMock, txUnmarshalerAndPreparer, logsFacade, shardCoordinator, dataFieldParser, enableRoundsHandler) epoch := uint32(0) + round := uint64(1) tx := &transaction.ApiTransactionResult{} @@ -87,7 +91,7 @@ func TestPutEventsInTransactionReceipt(t *testing.T) { SndAddr: encodedSndAddr, } - err = n.putResultsInTransaction(txHash, tx, epoch) + err = n.putResultsInTransaction(txHash, tx, epoch, round) require.Nil(t, err) require.Equal(t, expectedRecAPI, tx.Receipt) } @@ -96,6 +100,7 @@ func TestApiTransactionProcessor_PutResultsInTransactionWhenNoResultsShouldWork( t.Parallel() epoch := uint32(0) + round := uint64(1) historyRepo := &dbLookupExtMock.HistoryRepositoryStub{ GetEventsHashesByTxHashCalled: func(hash []byte, epoch uint32) (*dblookupext.ResultsHashesByTxHash, error) { return nil, dblookupext.ErrNotFoundInStorage @@ -103,12 +108,14 @@ func TestApiTransactionProcessor_PutResultsInTransactionWhenNoResultsShouldWork( } dataFieldParser := &testscommon.DataFieldParserStub{ - ParseCalled: func(dataField []byte, sender, receiver []byte, _ uint32) *datafield.ResponseParseData { + ParseCalled: func(dataField []byte, sender, receiver []byte, _ uint32, _ uint32) *datafield.ResponseParseData { return &datafield.ResponseParseData{} }, } shardCoordinator := mock.NewOneShardCoordinatorMock() + + enableRoundsHandler := &testscommon.EnableRoundsHandlerStub{} n := newAPITransactionResultProcessor( testscommon.RealWorldBech32PubkeyConverter, historyRepo, @@ -124,10 +131,11 @@ func TestApiTransactionProcessor_PutResultsInTransactionWhenNoResultsShouldWork( &testscommon.LogsFacadeStub{}, shardCoordinator, dataFieldParser, + enableRoundsHandler, ) tx := &transaction.ApiTransactionResult{} - err := n.putResultsInTransaction([]byte("txHash"), tx, epoch) + err := n.putResultsInTransaction([]byte("txHash"), tx, epoch, round) require.Nil(t, err) require.Empty(t, tx.SmartContractResults) } @@ -136,6 +144,7 @@ func TestPutEventsInTransactionSmartContractResults(t *testing.T) { t.Parallel() testEpoch := uint32(0) + testRound := uint64(1) testTxHash := []byte("txHash") scrHash1 := []byte("scrHash1") scrHash2 := []byte("scrHash2") @@ -224,7 +233,7 @@ func TestPutEventsInTransactionSmartContractResults(t *testing.T) { } dataFieldParser := &testscommon.DataFieldParserStub{ - ParseCalled: func(dataField []byte, sender, receiver []byte, _ uint32) *datafield.ResponseParseData { + ParseCalled: func(dataField []byte, sender, receiver []byte, _ uint32, _ uint32) *datafield.ResponseParseData { return &datafield.ResponseParseData{} }, } @@ -237,7 +246,8 @@ func TestPutEventsInTransactionSmartContractResults(t *testing.T) { shardCoordinator, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, ) - n := newAPITransactionResultProcessor(pubKeyConverter, historyRepo, dataStore, marshalizerdMock, txUnmarshalerAndPreparer, logsFacade, shardCoordinator, dataFieldParser) + enableRoundsHandler := &testscommon.EnableRoundsHandlerStub{} + n := newAPITransactionResultProcessor(pubKeyConverter, historyRepo, dataStore, marshalizerdMock, txUnmarshalerAndPreparer, logsFacade, shardCoordinator, dataFieldParser, enableRoundsHandler) encodedSndAddr, err := pubKeyConverter.Encode(scr1.SndAddr) require.Nil(t, err) @@ -279,7 +289,7 @@ func TestPutEventsInTransactionSmartContractResults(t *testing.T) { } tx := &transaction.ApiTransactionResult{} - err = n.putResultsInTransaction(testTxHash, tx, testEpoch) + err = n.putResultsInTransaction(testTxHash, tx, testEpoch, testRound) require.Nil(t, err) require.Equal(t, expectedSCRS, tx.SmartContractResults) } @@ -288,6 +298,7 @@ func TestPutLogsInTransaction(t *testing.T) { t.Parallel() testEpoch := uint32(7) + testRound := uint64(70) testTxHash := []byte("txHash") logs := &transaction.ApiLogs{ @@ -328,7 +339,7 @@ func TestPutLogsInTransaction(t *testing.T) { } dataFieldParser := &testscommon.DataFieldParserStub{ - ParseCalled: func(dataField []byte, sender, receiver []byte, _ uint32) *datafield.ResponseParseData { + ParseCalled: func(dataField []byte, sender, receiver []byte, _ uint32, _ uint32) *datafield.ResponseParseData { return &datafield.ResponseParseData{} }, } @@ -341,12 +352,50 @@ func TestPutLogsInTransaction(t *testing.T) { shardCoordinator, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, ) - n := newAPITransactionResultProcessor(pubKeyConverter, historyRepo, dataStore, marshalizerMock, txUnmarshalerAndPreparer, logsFacade, shardCoordinator, dataFieldParser) + enableRoundsHandler := &testscommon.EnableRoundsHandlerStub{} + n := newAPITransactionResultProcessor(pubKeyConverter, historyRepo, dataStore, marshalizerMock, txUnmarshalerAndPreparer, logsFacade, shardCoordinator, dataFieldParser, enableRoundsHandler) tx := &transaction.ApiTransactionResult{} - err := n.putResultsInTransaction(testTxHash, tx, testEpoch) + err := n.putResultsInTransaction(testTxHash, tx, testEpoch, testRound) // TODO: Note that "putResultsInTransaction" produces an effect on "tx" even if it returns an error. // TODO: Refactor this package to use less functions with side-effects. require.Errorf(t, err, "local err") require.Equal(t, logs, tx.Logs) } + +func TestPutReceiptInTransactionAfterSupernovaWillReturnError(t *testing.T) { + t.Parallel() + + dataStore := &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + switch unitType { + case dataRetriever.ReceiptsUnit: + return nil, expectedErr + default: + require.Fail(t, "should have not been called") + return nil, nil + } + }, + } + enableRoundsHandler := &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return true + }, + } + + n := newAPITransactionResultProcessor( + &testscommon.PubkeyConverterMock{}, + &dbLookupExtMock.HistoryRepositoryStub{}, + dataStore, + &mock.MarshalizerFake{}, + &txUnmarshaller{}, + &testscommon.LogsFacadeStub{}, + mock.NewOneShardCoordinatorMock(), + &testscommon.DataFieldParserStub{}, + enableRoundsHandler, + ) + + tx := &transaction.ApiTransactionResult{} + err := n.putReceiptInTransaction(tx, []byte("hash"), 2, 20) + require.Error(t, err, expectedErr) +} diff --git a/node/external/transactionAPI/check.go b/node/external/transactionAPI/check.go index 012aae77618..23066a6294e 100644 --- a/node/external/transactionAPI/check.go +++ b/node/external/transactionAPI/check.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/process" ) @@ -50,6 +51,12 @@ func checkNilArgs(arg *ArgAPITransactionProcessor) error { if check.IfNil(arg.EnableEpochsHandler) { return process.ErrNilEnableEpochsHandler } + if check.IfNil(arg.EnableRoundsHandler) { + return process.ErrNilEnableRoundsHandler + } + if check.IfNil(arg.TxVersionChecker) { + return process.ErrNilTransactionVersionChecker + } return nil } diff --git a/node/external/transactionAPI/errors.go b/node/external/transactionAPI/errors.go index 105d6c3e930..56bf8494817 100644 --- a/node/external/transactionAPI/errors.go +++ b/node/external/transactionAPI/errors.go @@ -34,3 +34,18 @@ var ErrInvalidAddress = errors.New("invalid address") // ErrDBLookExtensionIsNotEnabled signals that the db look extension is not enabled var ErrDBLookExtensionIsNotEnabled = errors.New("db look extension is not enabled") + +// ErrCouldNotCastToTxCache signals that an error occurred while casting to TxCache +var ErrCouldNotCastToTxCache = errors.New("wrong type conversion") + +// ErrNilBlockHeader signals that the returned block header is nil +var ErrNilBlockHeader = errors.New("nil block header") + +// ErrNilCurrentRootHash signals that the current root hash returned is nil +var ErrNilCurrentRootHash = errors.New("nil current root hash") + +// ErrNilAccountStateAPI signals that a nil account state api has been provided +var ErrNilAccountStateAPI = errors.New("nil account state api") + +// ErrNilBlockchain signals that a nil blockchain has been provided +var ErrNilBlockchain = errors.New("nil blockchain") diff --git a/node/external/transactionAPI/fieldsHandler.go b/node/external/transactionAPI/fieldsHandler.go index f631de38a82..ae39ad742f0 100644 --- a/node/external/transactionAPI/fieldsHandler.go +++ b/node/external/transactionAPI/fieldsHandler.go @@ -11,6 +11,7 @@ const ( receiverField = "receiver" gasLimitField = "gaslimit" gasPriceField = "gasprice" + ppu = "ppu" rcvUsernameField = "receiverusername" dataField = "data" valueField = "value" diff --git a/node/external/transactionAPI/fieldsHandler_test.go b/node/external/transactionAPI/fieldsHandler_test.go index 75b3ae6f81a..56ad2477678 100644 --- a/node/external/transactionAPI/fieldsHandler_test.go +++ b/node/external/transactionAPI/fieldsHandler_test.go @@ -14,7 +14,7 @@ func Test_newFieldsHandler(t *testing.T) { fh := newFieldsHandler("") require.Equal(t, fieldsHandler{map[string]struct{}{hashField: {}}}, fh) - providedFields := "nOnCe,sender,receiver,gasLimit,GASprice,receiverusername,data,value,signature,guardian,guardiansignature,sendershard,receivershard" + providedFields := "nOnCe,sender,receiver,gasLimit,GASprice,receiverusername,data,value,signature,guardian,guardiansignature,sendershard,receivershard,ppu" splitFields := strings.Split(providedFields, separator) fh = newFieldsHandler(providedFields) for _, field := range splitFields { diff --git a/node/external/transactionAPI/gasUsedAndFeeProcessor_test.go b/node/external/transactionAPI/gasUsedAndFeeProcessor_test.go index caf8e12fda1..aa023c9c499 100644 --- a/node/external/transactionAPI/gasUsedAndFeeProcessor_test.go +++ b/node/external/transactionAPI/gasUsedAndFeeProcessor_test.go @@ -2,7 +2,6 @@ package transactionAPI import ( "encoding/hex" - "github.com/multiversx/mx-chain-go/config" "math/big" "testing" @@ -16,6 +15,7 @@ import ( "github.com/multiversx/mx-chain-go/process/economics" "github.com/multiversx/mx-chain-go/process/smartContract" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" "github.com/stretchr/testify/require" @@ -23,10 +23,8 @@ import ( func createEconomicsData(enableEpochsHandler common.EnableEpochsHandler) process.EconomicsDataHandler { economicsConfig := testscommon.GetEconomicsConfig() - cfg := &config.Config{EpochStartConfig: config.EpochStartConfig{RoundsPerEpoch: 14400}} - cfg.GeneralSettings.ChainParametersByEpoch = []config.ChainParametersByEpochConfig{{RoundDuration: 6000}} economicsData, _ := economics.NewEconomicsData(economics.ArgsNewEconomicsData{ - GeneralConfig: cfg, + ChainParamsHandler: &chainParameters.ChainParametersHolderMock{}, Economics: &economicsConfig, EnableEpochsHandler: enableEpochsHandler, TxVersionChecker: &testscommon.TxVersionCheckerStub{}, diff --git a/node/external/transactionAPI/interface.go b/node/external/transactionAPI/interface.go index a32cac06184..cb64f15f9ab 100644 --- a/node/external/transactionAPI/interface.go +++ b/node/external/transactionAPI/interface.go @@ -28,5 +28,5 @@ type LogsFacade interface { // DataFieldParser defines what a data field parser should be able to do type DataFieldParser interface { - Parse(dataField []byte, sender, receiver []byte, numOfShards uint32) *datafield.ResponseParseData + Parse(dataField []byte, sender, receiver []byte, numOfShards uint32, epoch uint32) *datafield.ResponseParseData } diff --git a/node/external/transactionAPI/unmarshaller.go b/node/external/transactionAPI/unmarshaller.go index 42b2b21354c..ec5bc195c28 100644 --- a/node/external/transactionAPI/unmarshaller.go +++ b/node/external/transactionAPI/unmarshaller.go @@ -98,7 +98,7 @@ func (tu *txUnmarshaller) unmarshalTransaction( return nil, err } - res := tu.dataFieldParser.Parse(apiTx.Data, apiTx.Tx.GetSndAddr(), apiTx.Tx.GetRcvAddr(), tu.shardCoordinator.NumberOfShards()) + res := tu.dataFieldParser.Parse(apiTx.Data, apiTx.Tx.GetSndAddr(), apiTx.Tx.GetRcvAddr(), tu.shardCoordinator.NumberOfShards(), txEpoch) apiTx.Operation = res.Operation apiTx.Function = res.Function apiTx.ESDTValues = res.ESDTValues diff --git a/node/metrics/metrics.go b/node/metrics/metrics.go index e0fd13341c2..1680a9fffff 100644 --- a/node/metrics/metrics.go +++ b/node/metrics/metrics.go @@ -14,7 +14,6 @@ import ( logger "github.com/multiversx/mx-chain-logger-go" ) -const millisecondsInSecond = 1000 const initUint = uint64(0) const initInt = int64(0) const initString = "" @@ -30,6 +29,7 @@ func InitBaseMetrics(appStatusHandler core.AppStatusHandler) error { appStatusHandler.SetUInt64Value(common.MetricSynchronizedRound, initUint) appStatusHandler.SetUInt64Value(common.MetricNonce, initUint) + appStatusHandler.SetUInt64Value(common.MetricLastExecutedNonce, initUint) appStatusHandler.SetUInt64Value(common.MetricBlockTimestamp, initUint) appStatusHandler.SetUInt64Value(common.MetricBlockTimestampMs, initUint) appStatusHandler.SetUInt64Value(common.MetricCountConsensus, initUint) @@ -151,7 +151,6 @@ func InitConfigMetrics( appStatusHandler.SetUInt64Value(common.MetricFrontRunningProtectionEnableEpoch, uint64(enableEpochs.FrontRunningProtectionEnableEpoch)) appStatusHandler.SetUInt64Value(common.MetricIsPayableBySCEnableEpoch, uint64(enableEpochs.IsPayableBySCEnableEpoch)) appStatusHandler.SetUInt64Value(common.MetricStorageAPICostOptimizationEnableEpoch, uint64(enableEpochs.StorageAPICostOptimizationEnableEpoch)) - appStatusHandler.SetUInt64Value(common.MetricTransformToMultiShardCreateEnableEpoch, uint64(enableEpochs.TransformToMultiShardCreateEnableEpoch)) appStatusHandler.SetUInt64Value(common.MetricESDTRegisterAndSetAllRolesEnableEpoch, uint64(enableEpochs.ESDTRegisterAndSetAllRolesEnableEpoch)) appStatusHandler.SetUInt64Value(common.MetricDoNotReturnOldBlockInBlockchainHookEnableEpoch, uint64(enableEpochs.DoNotReturnOldBlockInBlockchainHookEnableEpoch)) appStatusHandler.SetUInt64Value(common.MetricAddFailedRelayedTxToInvalidMBsDisableEpoch, uint64(enableEpochs.AddFailedRelayedTxToInvalidMBsDisableEpoch)) @@ -213,6 +212,8 @@ func InitConfigMetrics( appStatusHandler.SetUInt64Value(common.MetricAutomaticActivationOfNodesDisableEpoch, uint64(enableEpochs.AutomaticActivationOfNodesDisableEpoch)) appStatusHandler.SetUInt64Value(common.MetricFixGetBalanceEnableEpoch, uint64(enableEpochs.FixGetBalanceEnableEpoch)) appStatusHandler.SetUInt64Value(common.MetricRelayedTransactionsV1V2DisableEpoch, uint64(enableEpochs.RelayedTransactionsV1V2DisableEpoch)) + appStatusHandler.SetUInt64Value(common.MetricTailInflationEnableEpoch, uint64(economicsConfig.GlobalSettings.TailInflation.EnableEpoch)) + appStatusHandler.SetUInt64Value(common.MetricSupernovaEnableEpoch, uint64(enableEpochs.SupernovaEnableEpoch)) for i, nodesChangeConfig := range enableEpochs.MaxNodesChangeEnableEpoch { epochEnable := fmt.Sprintf("%s%d%s", common.MetricMaxNodesChangeEnableEpoch, i, common.EpochEnableSuffix) @@ -274,6 +275,53 @@ func InitRatingsMetrics(appStatusHandler core.AppStatusHandler, ratingsConfig co return nil } +// InitInitialMetrics will set initial metrics for status handler (before bootstrapping process is completed) +func InitInitialMetrics( + appStatusHandler core.AppStatusHandler, + pubkeyStr string, + nodesConfig sharding.GenesisNodesSetupHandler, + version string, + economicsConfig *config.EconomicsConfig, + minTransactionVersion uint32, +) error { + if check.IfNil(appStatusHandler) { + return ErrNilAppStatusHandler + } + if nodesConfig == nil { + return fmt.Errorf("nil nodes config when initializing metrics") + } + if economicsConfig == nil { + return fmt.Errorf("nil economics config when initializing metrics") + } + + isSyncing := uint64(1) + + leaderPercentage := float64(0) + rewardsConfigs := make([]config.EpochRewardSettings, len(economicsConfig.RewardsSettings.RewardsConfigByEpoch)) + _ = copy(rewardsConfigs, economicsConfig.RewardsSettings.RewardsConfigByEpoch) + + sort.Slice(rewardsConfigs, func(i, j int) bool { + return rewardsConfigs[i].EpochEnable < rewardsConfigs[j].EpochEnable + }) + + if len(rewardsConfigs) > 0 { + leaderPercentage = rewardsConfigs[0].LeaderPercentage + } + + appStatusHandler.SetStringValue(common.MetricPublicKeyBlockSign, pubkeyStr) + appStatusHandler.SetStringValue(common.MetricAppVersion, version) + appStatusHandler.SetStringValue(common.MetricCrossCheckBlockHeight, "N/A") + appStatusHandler.SetUInt64Value(common.MetricCrossCheckBlockHeightMeta, 0) + appStatusHandler.SetUInt64Value(common.MetricIsSyncing, isSyncing) + appStatusHandler.SetStringValue(common.MetricLeaderPercentage, fmt.Sprintf("%f", leaderPercentage)) + appStatusHandler.SetUInt64Value(common.MetricDenomination, uint64(economicsConfig.GlobalSettings.Denomination)) + + appStatusHandler.SetUInt64Value(common.MetricStartTime, uint64(nodesConfig.GetStartTime())) + appStatusHandler.SetUInt64Value(common.MetricMinTransactionVersion, uint64(minTransactionVersion)) + + return nil +} + // InitMetrics will init metrics for status handler func InitMetrics( appStatusHandler core.AppStatusHandler, @@ -283,7 +331,7 @@ func InitMetrics( nodesConfig sharding.GenesisNodesSetupHandler, version string, economicsConfig *config.EconomicsConfig, - roundsPerEpoch int64, + chainParameters config.ChainParametersByEpochConfig, minTransactionVersion uint32, ) error { if check.IfNil(appStatusHandler) { @@ -301,7 +349,7 @@ func InitMetrics( shardId := uint64(shardCoordinator.SelfId()) numOfShards := uint64(shardCoordinator.NumberOfShards()) - roundDuration := nodesConfig.GetRoundDuration() + roundDuration := chainParameters.RoundDuration isSyncing := uint64(1) leaderPercentage := float64(0) @@ -320,9 +368,9 @@ func InitMetrics( appStatusHandler.SetUInt64Value(common.MetricShardId, shardId) appStatusHandler.SetUInt64Value(common.MetricNumShardsWithoutMetachain, numOfShards) appStatusHandler.SetStringValue(common.MetricNodeType, string(nodeType)) - appStatusHandler.SetUInt64Value(common.MetricRoundTime, roundDuration/millisecondsInSecond) + appStatusHandler.SetUInt64Value(common.MetricRoundTime, roundDuration) appStatusHandler.SetStringValue(common.MetricAppVersion, version) - appStatusHandler.SetUInt64Value(common.MetricRoundsPerEpoch, uint64(roundsPerEpoch)) + appStatusHandler.SetUInt64Value(common.MetricRoundsPerEpoch, uint64(chainParameters.RoundsPerEpoch)) appStatusHandler.SetStringValue(common.MetricCrossCheckBlockHeight, "0") for i := uint32(0); i < shardCoordinator.NumberOfShards(); i++ { key := fmt.Sprintf("%s_%d", common.MetricCrossCheckBlockHeight, i) @@ -333,12 +381,12 @@ func InitMetrics( appStatusHandler.SetStringValue(common.MetricLeaderPercentage, fmt.Sprintf("%f", leaderPercentage)) appStatusHandler.SetUInt64Value(common.MetricDenomination, uint64(economicsConfig.GlobalSettings.Denomination)) - appStatusHandler.SetUInt64Value(common.MetricShardConsensusGroupSize, uint64(nodesConfig.GetShardConsensusGroupSize())) - appStatusHandler.SetUInt64Value(common.MetricMetaConsensusGroupSize, uint64(nodesConfig.GetMetaConsensusGroupSize())) - appStatusHandler.SetUInt64Value(common.MetricNumNodesPerShard, uint64(nodesConfig.MinNumberOfShardNodes())) - appStatusHandler.SetUInt64Value(common.MetricNumMetachainNodes, uint64(nodesConfig.MinNumberOfMetaNodes())) + appStatusHandler.SetUInt64Value(common.MetricShardConsensusGroupSize, uint64(chainParameters.ShardConsensusGroupSize)) + appStatusHandler.SetUInt64Value(common.MetricMetaConsensusGroupSize, uint64(chainParameters.MetachainConsensusGroupSize)) + appStatusHandler.SetUInt64Value(common.MetricNumNodesPerShard, uint64(chainParameters.ShardMinNumNodes)) + appStatusHandler.SetUInt64Value(common.MetricNumMetachainNodes, uint64(chainParameters.MetachainMinNumNodes)) appStatusHandler.SetUInt64Value(common.MetricStartTime, uint64(nodesConfig.GetStartTime())) - appStatusHandler.SetUInt64Value(common.MetricRoundDuration, nodesConfig.GetRoundDuration()) + appStatusHandler.SetUInt64Value(common.MetricRoundDuration, roundDuration) appStatusHandler.SetUInt64Value(common.MetricMinTransactionVersion, uint64(minTransactionVersion)) var consensusGroupSize uint32 diff --git a/node/metrics/metrics_test.go b/node/metrics/metrics_test.go index 09932718675..a6c3c405fe1 100644 --- a/node/metrics/metrics_test.go +++ b/node/metrics/metrics_test.go @@ -23,6 +23,7 @@ func TestInitBaseMetrics(t *testing.T) { expectedKeys := []string{ common.MetricSynchronizedRound, common.MetricNonce, + common.MetricLastExecutedNonce, common.MetricBlockTimestamp, common.MetricBlockTimestampMs, common.MetricCountConsensus, @@ -160,7 +161,6 @@ func TestInitConfigMetrics(t *testing.T) { IsPayableBySCEnableEpoch: 52, CleanUpInformativeSCRsEnableEpoch: 53, StorageAPICostOptimizationEnableEpoch: 54, - TransformToMultiShardCreateEnableEpoch: 55, ESDTRegisterAndSetAllRolesEnableEpoch: 56, DoNotReturnOldBlockInBlockchainHookEnableEpoch: 57, AddFailedRelayedTxToInvalidMBsDisableEpoch: 58, @@ -222,6 +222,7 @@ func TestInitConfigMetrics(t *testing.T) { AutomaticActivationOfNodesDisableEpoch: 114, FixGetBalanceEnableEpoch: 115, RelayedTransactionsV1V2DisableEpoch: 116, + SupernovaEnableEpoch: 118, // tail inflation 117 comes from EconomicsConfig MaxNodesChangeEnableEpoch: []config.MaxNodesChangeConfig{ { EpochEnable: 0, @@ -291,7 +292,6 @@ func TestInitConfigMetrics(t *testing.T) { "erd_is_payable_by_sc_enable_epoch": uint32(52), "erd_cleanup_informative_scrs_enable_epoch": uint32(53), "erd_storage_api_cost_optimization_enable_epoch": uint32(54), - "erd_transform_to_multi_shard_create_enable_epoch": uint32(55), "erd_esdt_register_and_set_all_roles_enable_epoch": uint32(56), "erd_do_not_returns_old_block_in_blockchain_hook_enable_epoch": uint32(57), "erd_add_failed_relayed_tx_to_invalid_mbs_enable_epoch": uint32(58), @@ -353,6 +353,8 @@ func TestInitConfigMetrics(t *testing.T) { "erd_automatic_activation_of_nodes_disable_epoch": uint32(114), "erd_fix_get_balance_enable_epoch": uint32(115), "erd_relayed_transactions_v1_v2_disable_epoch": uint32(116), + "erd_tail_inflation_enable_epoch": uint32(117), + "erd_supernova_enable_epoch": uint32(118), "erd_max_nodes_change_enable_epoch": nil, "erd_total_supply": "12345", "erd_hysteresis": "0.100000", @@ -366,6 +368,9 @@ func TestInitConfigMetrics(t *testing.T) { economicsConfig := config.EconomicsConfig{ GlobalSettings: config.GlobalSettings{ GenesisTotalSupply: "12345", + TailInflation: config.TailInflationSettings{ + EnableEpoch: 117, + }, }, } @@ -524,6 +529,7 @@ func TestInitMetrics(t *testing.T) { appStatusHandler := &statusHandler.AppStatusHandlerStub{} pubkeyString := "pub key" nodeType := core.NodeTypeValidator + shardCoordinator := &testscommon.ShardsCoordinatorMock{ NoShards: 3, SelfIDCalled: func() uint32 { @@ -580,33 +586,41 @@ func TestInitMetrics(t *testing.T) { }, } roundsPerEpoch := int64(200) + currentChainParameters := config.ChainParametersByEpochConfig{ + RoundsPerEpoch: roundsPerEpoch, + RoundDuration: 6000, + ShardMinNumNodes: uint32(402), + MetachainMinNumNodes: uint32(401), + ShardConsensusGroupSize: uint32(63), + MetachainConsensusGroupSize: uint32(400), + } minTransactionVersion := uint32(1) t.Run("nil app status handler should error", func(t *testing.T) { t.Parallel() - err := InitMetrics(nil, pubkeyString, nodeType, shardCoordinator, nodesSetup, version, economicsConfigs, roundsPerEpoch, minTransactionVersion) + err := InitMetrics(nil, pubkeyString, nodeType, shardCoordinator, nodesSetup, version, economicsConfigs, currentChainParameters, minTransactionVersion) assert.Equal(t, ErrNilAppStatusHandler, err) }) t.Run("nil shard coordinator should error", func(t *testing.T) { t.Parallel() expectedErrorString := "nil shard coordinator when initializing metrics" - err := InitMetrics(appStatusHandler, pubkeyString, nodeType, nil, nodesSetup, version, economicsConfigs, roundsPerEpoch, minTransactionVersion) + err := InitMetrics(appStatusHandler, pubkeyString, nodeType, nil, nodesSetup, version, economicsConfigs, currentChainParameters, minTransactionVersion) assert.Equal(t, expectedErrorString, err.Error()) }) t.Run("nil nodes configs should error", func(t *testing.T) { t.Parallel() expectedErrorString := "nil nodes config when initializing metrics" - err := InitMetrics(appStatusHandler, pubkeyString, nodeType, shardCoordinator, nil, version, economicsConfigs, roundsPerEpoch, minTransactionVersion) + err := InitMetrics(appStatusHandler, pubkeyString, nodeType, shardCoordinator, nil, version, economicsConfigs, currentChainParameters, minTransactionVersion) assert.Equal(t, expectedErrorString, err.Error()) }) t.Run("nil economics configs should error", func(t *testing.T) { t.Parallel() expectedErrorString := "nil economics config when initializing metrics" - err := InitMetrics(appStatusHandler, pubkeyString, nodeType, shardCoordinator, nodesSetup, version, nil, roundsPerEpoch, minTransactionVersion) + err := InitMetrics(appStatusHandler, pubkeyString, nodeType, shardCoordinator, nodesSetup, version, nil, currentChainParameters, minTransactionVersion) assert.Equal(t, expectedErrorString, err.Error()) }) t.Run("should work", func(t *testing.T) { @@ -622,7 +636,7 @@ func TestInitMetrics(t *testing.T) { }, } - err := InitMetrics(localStatusHandler, pubkeyString, nodeType, shardCoordinator, nodesSetup, version, economicsConfigs, roundsPerEpoch, minTransactionVersion) + err := InitMetrics(localStatusHandler, pubkeyString, nodeType, shardCoordinator, nodesSetup, version, economicsConfigs, currentChainParameters, minTransactionVersion) assert.Nil(t, err) expectedValues := map[string]interface{}{ @@ -630,7 +644,7 @@ func TestInitMetrics(t *testing.T) { common.MetricShardId: uint64(shardCoordinator.SelfId()), common.MetricNumShardsWithoutMetachain: uint64(shardCoordinator.NoShards), common.MetricNodeType: string(nodeType), - common.MetricRoundTime: uint64(6), + common.MetricRoundTime: uint64(6000), common.MetricAppVersion: version, common.MetricRoundsPerEpoch: uint64(roundsPerEpoch), common.MetricCrossCheckBlockHeight: "0", @@ -676,7 +690,7 @@ func TestInitMetrics(t *testing.T) { }, } - err := InitMetrics(localStatusHandler, pubkeyString, nodeType, localShardCoordinator, nodesSetup, version, economicsConfigs, roundsPerEpoch, minTransactionVersion) + err := InitMetrics(localStatusHandler, pubkeyString, nodeType, localShardCoordinator, nodesSetup, version, economicsConfigs, currentChainParameters, minTransactionVersion) assert.Nil(t, err) expectedValues := map[string]interface{}{ @@ -684,7 +698,7 @@ func TestInitMetrics(t *testing.T) { common.MetricShardId: uint64(localShardCoordinator.SelfId()), common.MetricNumShardsWithoutMetachain: uint64(localShardCoordinator.NoShards), common.MetricNodeType: string(nodeType), - common.MetricRoundTime: uint64(6), + common.MetricRoundTime: uint64(6000), common.MetricAppVersion: version, common.MetricRoundsPerEpoch: uint64(roundsPerEpoch), common.MetricCrossCheckBlockHeight: "0", @@ -730,7 +744,7 @@ func TestInitMetrics(t *testing.T) { }, } - err := InitMetrics(localStatusHandler, pubkeyString, nodeType, localShardCoordinator, nodesSetup, version, economicsConfigs, roundsPerEpoch, minTransactionVersion) + err := InitMetrics(localStatusHandler, pubkeyString, nodeType, localShardCoordinator, nodesSetup, version, economicsConfigs, currentChainParameters, minTransactionVersion) assert.Nil(t, err) assert.Equal(t, uint64(0), keys[common.MetricConsensusGroupSize]) diff --git a/node/mock/apiTransactionHandlerStub.go b/node/mock/apiTransactionHandlerStub.go index 0a1aa6f38ad..8350fe308f8 100644 --- a/node/mock/apiTransactionHandlerStub.go +++ b/node/mock/apiTransactionHandlerStub.go @@ -1,8 +1,10 @@ package mock import ( + coreData "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/state" ) // TransactionAPIHandlerStub - @@ -13,6 +15,8 @@ type TransactionAPIHandlerStub struct { GetLastPoolNonceForSenderCalled func(sender string) (uint64, error) GetTransactionsPoolNonceGapsForSenderCalled func(sender string, senderAccountNonce uint64) (*common.TransactionsPoolNonceGapsForSenderApiResponse, error) UnmarshalTransactionCalled func(txBytes []byte, txType transaction.TxType, epoch uint32) (*transaction.ApiTransactionResult, error) + GetSelectedTransactionsCalled func(selectionOptions common.TxSelectionOptionsAPI, blockchain coreData.ChainHandler, accountsAdapter state.AccountsAdapter) (*common.TransactionsSelectionSimulationResult, error) + GetVirtualNonceCalled func(address string) (*common.VirtualNonceOfAccountResponse, error) UnmarshalReceiptCalled func(receiptBytes []byte) (*transaction.ApiReceipt, error) PopulateComputedFieldsCalled func(tx *transaction.ApiTransactionResult) GetSCRsByTxHashCalled func(txHash string, scrHash string) ([]*transaction.ApiSmartContractResult, error) @@ -27,6 +31,15 @@ func (tas *TransactionAPIHandlerStub) GetSCRsByTxHash(txHash string, scrHash str return nil, nil } +// GetVirtualNonce - +func (tas *TransactionAPIHandlerStub) GetVirtualNonce(address string) (*common.VirtualNonceOfAccountResponse, error) { + if tas.GetVirtualNonceCalled != nil { + return tas.GetVirtualNonceCalled(address) + } + + return nil, nil +} + // GetTransaction - func (tas *TransactionAPIHandlerStub) GetTransaction(hash string, withResults bool) (*transaction.ApiTransactionResult, error) { if tas.GetTransactionCalled != nil { @@ -72,6 +85,15 @@ func (tas *TransactionAPIHandlerStub) GetTransactionsPoolNonceGapsForSender(send return nil, nil } +// GetSelectedTransactions - +func (tas *TransactionAPIHandlerStub) GetSelectedTransactions(selectionOptions common.TxSelectionOptionsAPI, blockchain coreData.ChainHandler, accountsAdapter state.AccountsAdapter) (*common.TransactionsSelectionSimulationResult, error) { + if tas.GetSelectedTransactionsCalled != nil { + return tas.GetSelectedTransactionsCalled(selectionOptions, blockchain, accountsAdapter) + } + + return nil, nil +} + // UnmarshalTransaction - func (tas *TransactionAPIHandlerStub) UnmarshalTransaction(txBytes []byte, txType transaction.TxType, epoch uint32) (*transaction.ApiTransactionResult, error) { if tas.UnmarshalTransactionCalled != nil { diff --git a/node/mock/blockTrackerStub.go b/node/mock/blockTrackerStub.go index 1fc03429b4c..5f2bbc1cd31 100644 --- a/node/mock/blockTrackerStub.go +++ b/node/mock/blockTrackerStub.go @@ -2,6 +2,7 @@ package mock import ( "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/process" ) @@ -12,7 +13,7 @@ type BlockTrackerStub struct { AddSelfNotarizedHeaderCalled func(shardID uint32, selfNotarizedHeader data.HeaderHandler, selfNotarizedHeaderHash []byte) CheckBlockAgainstRoundHandlerCalled func(headerHandler data.HeaderHandler) error CheckBlockAgainstFinalCalled func(headerHandler data.HeaderHandler) error - CheckBlockAgainstWhitelistCalled func(interceptedData process.InterceptedData) bool + CheckAgainstWhitelistCalled func(interceptedData process.InterceptedData) bool CleanupHeadersBehindNonceCalled func(shardID uint32, selfNotarizedNonce uint64, crossNotarizedNonce uint64) ComputeLongestChainCalled func(shardID uint32, header data.HeaderHandler) ([]data.HeaderHandler, [][]byte) ComputeLongestMetaChainFromLastNotarizedCalled func() ([]data.HeaderHandler, [][]byte, error) @@ -27,6 +28,7 @@ type BlockTrackerStub struct { GetTrackedHeadersForAllShardsCalled func() map[uint32][]data.HeaderHandler GetTrackedHeadersWithNonceCalled func(shardID uint32, nonce uint64) ([]data.HeaderHandler, [][]byte) IsShardStuckCalled func(shardId uint32) bool + IsOwnShardStuckCalled func() bool ShouldSkipMiniBlocksCreationFromSelfCalled func() bool RegisterCrossNotarizedHeadersHandlerCalled func(handler func(shardID uint32, headers []data.HeaderHandler, headersHashes [][]byte)) RegisterSelfNotarizedFromCrossHeadersHandlerCalled func(handler func(shardID uint32, headers []data.HeaderHandler, headersHashes [][]byte)) @@ -35,6 +37,25 @@ type BlockTrackerStub struct { RemoveLastNotarizedHeadersCalled func() RestoreToGenesisCalled func() ShouldAddHeaderCalled func(headerHandler data.HeaderHandler) bool + ComputeOwnShardStuckCalled func(lastExecutionResultsInfo data.BaseExecutionResultHandler, currentNonce uint64) + CheckProofAgainstFinalCalled func(proof data.HeaderProofHandler) error + CheckProofAgainstRoundHandlerCalled func(proof data.HeaderProofHandler) error +} + +// CheckProofAgainstFinal - +func (bts *BlockTrackerStub) CheckProofAgainstFinal(proof data.HeaderProofHandler) error { + if bts.CheckProofAgainstFinalCalled != nil { + return bts.CheckProofAgainstFinalCalled(proof) + } + return nil +} + +// CheckProofAgainstRoundHandler - +func (bts *BlockTrackerStub) CheckProofAgainstRoundHandler(proof data.HeaderProofHandler) error { + if bts.CheckProofAgainstRoundHandlerCalled != nil { + return bts.CheckProofAgainstRoundHandlerCalled(proof) + } + return nil } // AddTrackedHeader - @@ -76,10 +97,10 @@ func (bts *BlockTrackerStub) CheckBlockAgainstFinal(headerHandler data.HeaderHan return nil } -// CheckBlockAgainstWhitelist - -func (bts *BlockTrackerStub) CheckBlockAgainstWhitelist(interceptedData process.InterceptedData) bool { - if bts.CheckBlockAgainstWhitelistCalled != nil { - return bts.CheckBlockAgainstWhitelistCalled(interceptedData) +// CheckAgainstWhitelist - +func (bts *BlockTrackerStub) CheckAgainstWhitelist(interceptedData process.InterceptedData) bool { + if bts.CheckAgainstWhitelistCalled != nil { + return bts.CheckAgainstWhitelistCalled(interceptedData) } return false @@ -210,6 +231,15 @@ func (bts *BlockTrackerStub) IsShardStuck(shardId uint32) bool { return false } +// IsOwnShardStuck - +func (bts *BlockTrackerStub) IsOwnShardStuck() bool { + if bts.IsOwnShardStuckCalled != nil { + return bts.IsOwnShardStuckCalled() + } + + return false +} + // ShouldSkipMiniBlocksCreationFromSelf - func (bts *BlockTrackerStub) ShouldSkipMiniBlocksCreationFromSelf() bool { if bts.ShouldSkipMiniBlocksCreationFromSelfCalled != nil { @@ -270,6 +300,13 @@ func (bts *BlockTrackerStub) ShouldAddHeader(headerHandler data.HeaderHandler) b return true } +// ComputeOwnShardStuck - +func (bts *BlockTrackerStub) ComputeOwnShardStuck(lastExecutionResultsInfo data.BaseExecutionResultHandler, currentNonce uint64) { + if bts.ComputeOwnShardStuckCalled != nil { + bts.ComputeOwnShardStuckCalled(lastExecutionResultsInfo, currentNonce) + } +} + // IsInterfaceNil - func (bts *BlockTrackerStub) IsInterfaceNil() bool { return bts == nil diff --git a/node/mock/endOfEpochTriggerStub.go b/node/mock/endOfEpochTriggerStub.go index 97625515d8b..84a32f1f734 100644 --- a/node/mock/endOfEpochTriggerStub.go +++ b/node/mock/endOfEpochTriggerStub.go @@ -18,6 +18,42 @@ type EpochStartTriggerStub struct { UpdateCalled func(round uint64, nonce uint64) ProcessedCalled func(header data.HeaderHandler) EpochStartRoundCalled func() uint64 + ShouldProposeEpochChangeCalled func(round uint64, nonce uint64) bool + SetEpochChangeCalled func(round uint64) + SetEpochChangeProposedCalled func(value bool) + GetEpochChangeProposedCalled func() bool +} + +// SetEpochChange - +func (e *EpochStartTriggerStub) SetEpochChange(round uint64) { + if e.SetEpochChangeCalled != nil { + e.SetEpochChangeCalled(round) + } +} + +// ShouldProposeEpochChange - +func (e *EpochStartTriggerStub) ShouldProposeEpochChange(round uint64, nonce uint64) bool { + if e.ShouldProposeEpochChangeCalled != nil { + return e.ShouldProposeEpochChangeCalled(round, nonce) + } + + return false +} + +// SetEpochChangeProposed - +func (e *EpochStartTriggerStub) SetEpochChangeProposed(value bool) { + if e.SetEpochChangeProposedCalled != nil { + e.SetEpochChangeProposedCalled(value) + } +} + +// GetEpochChangeProposed - +func (e *EpochStartTriggerStub) GetEpochChangeProposed() bool { + if e.GetEpochChangeProposedCalled != nil { + return e.GetEpochChangeProposedCalled() + } + + return false } // RevertStateToBlock - diff --git a/node/mock/factory/coreComponentsStub.go b/node/mock/factory/coreComponentsStub.go index 80bcc8ac16d..47dad2d66ab 100644 --- a/node/mock/factory/coreComponentsStub.go +++ b/node/mock/factory/coreComponentsStub.go @@ -1,6 +1,7 @@ package factory import ( + "sync/atomic" "time" "github.com/multiversx/mx-chain-core-go/core" @@ -43,7 +44,7 @@ type CoreComponentsMock struct { RatingHandler sharding.PeerAccountListAndRatingHandler NodesConfig sharding.GenesisNodesSetupHandler EpochChangeNotifier process.EpochNotifier - EnableRoundsHandlerField process.EnableRoundsHandler + EnableRoundsHandlerField common.EnableRoundsHandler EpochNotifierWithConfirm factory.EpochStartNotifierWithConfirm ChanStopProcess chan endProcess.ArgEndProcess Shuffler nodesCoordinator.NodesShuffler @@ -58,6 +59,10 @@ type CoreComponentsMock struct { ChainParametersSubscriberField process.ChainParametersSubscriber FieldsSizeCheckerField common.FieldsSizeChecker EpochChangeGracePeriodHandlerField common.EpochChangeGracePeriodHandler + ProcessConfigsHandlerField common.ProcessConfigsHandler + CommonConfigsHandlerField common.CommonConfigsHandler + AntifloodConfigsHandlerField common.AntifloodConfigsHandler + ClosingNodeStartedField *atomic.Bool } // Create - @@ -142,7 +147,7 @@ func (ccm *CoreComponentsMock) RoundNotifier() process.RoundNotifier { } // EnableRoundsHandler - -func (ccm *CoreComponentsMock) EnableRoundsHandler() process.EnableRoundsHandler { +func (ccm *CoreComponentsMock) EnableRoundsHandler() common.EnableRoundsHandler { return ccm.EnableRoundsHandlerField } @@ -156,6 +161,11 @@ func (ccm *CoreComponentsMock) GenesisTime() time.Time { return ccm.StartTime } +// SupernovaGenesisTime - +func (ccm *CoreComponentsMock) SupernovaGenesisTime() time.Time { + return ccm.StartTime +} + // InternalMarshalizer - func (ccm *CoreComponentsMock) InternalMarshalizer() marshal.Marshalizer { return ccm.IntMarsh @@ -283,6 +293,30 @@ func (ccm *CoreComponentsMock) EpochChangeGracePeriodHandler() common.EpochChang return ccm.EpochChangeGracePeriodHandlerField } +// ProcessConfigsHandler - +func (ccm *CoreComponentsMock) ProcessConfigsHandler() common.ProcessConfigsHandler { + return ccm.ProcessConfigsHandlerField +} + +// CommonConfigsHandler - +func (ccm *CoreComponentsMock) CommonConfigsHandler() common.CommonConfigsHandler { + return ccm.CommonConfigsHandlerField +} + +// AntifloodConfigsHandler - +func (ccm *CoreComponentsMock) AntifloodConfigsHandler() common.AntifloodConfigsHandler { + return ccm.AntifloodConfigsHandlerField +} + +// ClosingNodeStarted - +func (ccm *CoreComponentsMock) ClosingNodeStarted() *atomic.Bool { + if ccm.ClosingNodeStartedField == nil { + ccm.ClosingNodeStartedField = &atomic.Bool{} + } + + return ccm.ClosingNodeStartedField +} + // IsInterfaceNil - func (ccm *CoreComponentsMock) IsInterfaceNil() bool { return ccm == nil diff --git a/node/mock/factory/cryptoComponentsStub.go b/node/mock/factory/cryptoComponentsStub.go index 7c200169cbc..96e237df5ae 100644 --- a/node/mock/factory/cryptoComponentsStub.go +++ b/node/mock/factory/cryptoComponentsStub.go @@ -112,7 +112,7 @@ func (ccm *CryptoComponentsMock) SetMultiSignerContainer(ms cryptoCommon.MultiSi } // GetMultiSigner - -func (ccm *CryptoComponentsMock) GetMultiSigner(epoch uint32) (crypto.MultiSigner, error) { +func (ccm *CryptoComponentsMock) GetMultiSigner(epoch uint32) (crypto.MultiSignerV2, error) { ccm.mutMultiSig.RLock() defer ccm.mutMultiSig.RUnlock() diff --git a/node/mock/headerIntegrityVerifierStub.go b/node/mock/headerIntegrityVerifierStub.go index 3d793b89924..5fa049cd3bf 100644 --- a/node/mock/headerIntegrityVerifierStub.go +++ b/node/mock/headerIntegrityVerifierStub.go @@ -5,7 +5,7 @@ import "github.com/multiversx/mx-chain-core-go/data" // HeaderIntegrityVerifierStub - type HeaderIntegrityVerifierStub struct { VerifyCalled func(header data.HeaderHandler) error - GetVersionCalled func(epoch uint32) string + GetVersionCalled func(epoch uint32, round uint64) string } // Verify - @@ -18,9 +18,9 @@ func (h *HeaderIntegrityVerifierStub) Verify(header data.HeaderHandler) error { } // GetVersion - -func (h *HeaderIntegrityVerifierStub) GetVersion(epoch uint32) string { +func (h *HeaderIntegrityVerifierStub) GetVersion(epoch uint32, round uint64) string { if h.GetVersionCalled != nil { - return h.GetVersionCalled(epoch) + return h.GetVersionCalled(epoch, round) } return "version" diff --git a/node/mock/rounderMock.go b/node/mock/rounderMock.go index f6d933fcbe1..9cf1f1a4f42 100644 --- a/node/mock/rounderMock.go +++ b/node/mock/rounderMock.go @@ -8,12 +8,13 @@ import ( type RoundHandlerMock struct { index int64 - IndexCalled func() int64 - TimeDurationCalled func() time.Duration - TimeStampCalled func() time.Time - UpdateRoundCalled func(time.Time, time.Time) - RemainingTimeCalled func(startTime time.Time, maxTime time.Duration) time.Duration - BeforeGenesisCalled func() bool + IndexCalled func() int64 + TimeDurationCalled func() time.Duration + TimeStampCalled func() time.Time + UpdateRoundCalled func(time.Time, time.Time) + RemainingTimeCalled func(startTime time.Time, maxTime time.Duration) time.Duration + BeforeGenesisCalled func() bool + GetTimeStampForRoundCalled func(round uint64) uint64 } // BeforeGenesis - @@ -73,6 +74,15 @@ func (rndm *RoundHandlerMock) RemainingTime(startTime time.Time, maxTime time.Du return 4000 * time.Millisecond } +// GetTimeStampForRound - +func (rndm *RoundHandlerMock) GetTimeStampForRound(round uint64) uint64 { + if rndm.GetTimeStampForRoundCalled != nil { + return rndm.GetTimeStampForRoundCalled(round) + } + + return 0 +} + // IsInterfaceNil returns true if there is no value under the interface func (rndm *RoundHandlerMock) IsInterfaceNil() bool { return rndm == nil diff --git a/node/mock/statusComputerHandlerStub.go b/node/mock/statusComputerHandlerStub.go index c56633cf9a7..6a3b0825817 100644 --- a/node/mock/statusComputerHandlerStub.go +++ b/node/mock/statusComputerHandlerStub.go @@ -7,10 +7,15 @@ import ( // StatusComputerStub - type StatusComputerStub struct { + ComputeStatusWhenInStorageKnowingMiniblockCalled func(mbType block.Type, tx *transaction.ApiTransactionResult) (transaction.TxStatus, error) } // ComputeStatusWhenInStorageKnowingMiniblock - -func (scs *StatusComputerStub) ComputeStatusWhenInStorageKnowingMiniblock(_ block.Type, _ *transaction.ApiTransactionResult) (transaction.TxStatus, error) { +func (scs *StatusComputerStub) ComputeStatusWhenInStorageKnowingMiniblock(mbType block.Type, tx *transaction.ApiTransactionResult) (transaction.TxStatus, error) { + if scs.ComputeStatusWhenInStorageKnowingMiniblockCalled != nil { + return scs.ComputeStatusWhenInStorageKnowingMiniblockCalled(mbType, tx) + } + return "", nil } diff --git a/node/mock/syncTimerStub.go b/node/mock/syncTimerStub.go deleted file mode 100644 index b3d60be3061..00000000000 --- a/node/mock/syncTimerStub.go +++ /dev/null @@ -1,38 +0,0 @@ -package mock - -import ( - "time" -) - -// SyncTimerStub - -type SyncTimerStub struct { -} - -// StartSyncingTime - -func (sts *SyncTimerStub) StartSyncingTime() { -} - -// ClockOffset - -func (sts *SyncTimerStub) ClockOffset() time.Duration { - return time.Second -} - -// FormattedCurrentTime - -func (sts *SyncTimerStub) FormattedCurrentTime() string { - return "" -} - -// CurrentTime - -func (sts *SyncTimerStub) CurrentTime() time.Time { - return time.Now() -} - -// Close - -func (sts *SyncTimerStub) Close() error { - return nil -} - -// IsInterfaceNil returns true if there is no value under the interface -func (sts *SyncTimerStub) IsInterfaceNil() bool { - return sts == nil -} diff --git a/node/mock/txLogsProcessorMock.go b/node/mock/txLogsProcessorMock.go index 3158e836493..4d7b7cca215 100644 --- a/node/mock/txLogsProcessorMock.go +++ b/node/mock/txLogsProcessorMock.go @@ -2,6 +2,7 @@ package mock import ( "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-go/process" ) @@ -12,8 +13,8 @@ type TxLogProcessorMock struct { } // GetLogFromCache - -func (t *TxLogProcessorMock) GetLogFromCache(_ []byte) (*data.LogData, bool) { - return &data.LogData{}, false +func (t *TxLogProcessorMock) GetLogFromCache(_ []byte) (data.LogDataHandler, bool) { + return &transaction.LogData{}, false } // EnableLogToBeSavedInCache - diff --git a/node/node.go b/node/node.go index 8692b7ea158..7a789815b12 100644 --- a/node/node.go +++ b/node/node.go @@ -16,7 +16,6 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/api" - "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/endProcess" "github.com/multiversx/mx-chain-core-go/data/esdt" "github.com/multiversx/mx-chain-core-go/data/guardians" @@ -807,6 +806,7 @@ func (n *Node) commonTransactionValidation( whiteListRequest, n.coreComponents.AddressPubKeyConverter(), n.coreComponents.TxVersionChecker(), + n.coreComponents.EnableEpochsHandler(), common.MaxTxNonceDeltaAllowed, ) @@ -1246,7 +1246,7 @@ func (n *Node) GetEpochStartDataAPI(epoch uint32) (*common.EpochStartDataAPI, er if epoch == 0 { // for the first epoch, epoch start identifier isn't committed. Therefore, return the genesis info genesisHeader := n.dataComponents.Blockchain().GetGenesisHeader() - return prepareEpochStartDataResponse(genesisHeader), nil + return n.prepareEpochStartDataResponse(genesisHeader), nil } if n.bootstrapComponents.ShardCoordinator().SelfId() == core.MetachainShardId { @@ -1273,7 +1273,7 @@ func (n *Node) getShardFirstNonceOfEpoch(epoch uint32) (*common.EpochStartDataAP return nil, err } - return prepareEpochStartDataResponse(header), nil + return n.prepareEpochStartDataResponse(header), nil } func (n *Node) getMetaFirstNonceOfEpoch(epoch uint32) (*common.EpochStartDataAPI, error) { @@ -1288,25 +1288,46 @@ func (n *Node) getMetaFirstNonceOfEpoch(epoch uint32) (*common.EpochStartDataAPI return nil, fmt.Errorf("cannot load epoch start block for epoch %d (%w)", epoch, err) } - var metaBlock block.MetaBlock - err = n.coreComponents.InternalMarshalizer().Unmarshal(&metaBlock, result) + metaBlock, err := process.UnmarshalMetaHeader(n.coreComponents.InternalMarshalizer(), result) if err != nil { return nil, err } - return prepareEpochStartDataResponse(&metaBlock), nil + return n.prepareEpochStartDataResponse(metaBlock), nil +} + +func (n *Node) getLastExecutionRootHashOnHeader( + header data.HeaderHandler, +) []byte { + rootHash := header.GetRootHash() + if !header.IsHeaderV3() { + return rootHash + } + + lastExecRes, err := common.ExtractBaseExecutionResultHandler(header.GetLastExecutionResultHandler()) + if err != nil { + // this should not happen, last execution result should be set on header v3 + log.Error("failed to get last execution result on header", "error", err) + return rootHash + } + + return lastExecRes.GetRootHash() } -func prepareEpochStartDataResponse(header data.HeaderHandler) *common.EpochStartDataAPI { +func (n *Node) prepareEpochStartDataResponse(header data.HeaderHandler) *common.EpochStartDataAPI { + timestampSec, timestampMs, _ := common.GetHeaderTimestamps(header, n.coreComponents.EnableEpochsHandler()) + + rootHash := n.getLastExecutionRootHashOnHeader(header) + response := &common.EpochStartDataAPI{ Nonce: header.GetNonce(), Round: header.GetRound(), Shard: header.GetShardID(), - Timestamp: int64(header.GetTimeStamp()), - TimestampMs: int64(common.ConvertTimeStampSecToMs(header.GetTimeStamp())), + Timestamp: int64(timestampSec), + TimestampMs: int64(timestampMs), Epoch: header.GetEpoch(), PrevBlockHash: hex.EncodeToString(header.GetPrevHash()), - StateRootHash: hex.EncodeToString(header.GetRootHash()), + StateRootHash: hex.EncodeToString(rootHash), } if header.GetAdditionalData() != nil { diff --git a/node/nodeBlocks.go b/node/nodeBlocks.go index fba0d9e6a1c..afa36a55555 100644 --- a/node/nodeBlocks.go +++ b/node/nodeBlocks.go @@ -3,6 +3,7 @@ package node import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" ) @@ -89,13 +90,57 @@ func (n *Node) getBlockHeaderInEpochByHash(headerHash []byte, epoch core.Optiona return header, nil } +// TODO: refactor to remove duplicated code sc query func (n *Node) getBlockRootHash(headerHash []byte, header data.HeaderHandler) []byte { + if header.IsHeaderV3() { + return n.getBlockRootHashV3(headerHash, header) + } + blockRootHash, err := n.processComponents.ScheduledTxsExecutionHandler().GetScheduledRootHashForHeaderWithEpoch( headerHash, header.GetEpoch()) + if err == nil { + return blockRootHash + } + + return header.GetRootHash() +} + +func (n *Node) getBlockRootHashV3( + headerHash []byte, + header data.HeaderHandler, +) []byte { + rootHash, err := n.getRootHashByExecutionResult(headerHash) + if err == nil { + return rootHash + } + + lastExecutionResult, err := common.ExtractBaseExecutionResultHandler(header.GetLastExecutionResultHandler()) + if err != nil { + log.Error("getBlockRootHashV3: failed to get root hash for header v3, using root hash directly from header", "error", err) + return header.GetRootHash() + } + + return lastExecutionResult.GetRootHash() +} + +func (n *Node) getRootHashByExecutionResult( + currentHeaderHash []byte, +) ([]byte, error) { + execResStorer, err := n.dataComponents.StorageService().GetStorer(dataRetriever.ExecutionResultsUnit) + if err != nil { + return nil, err + } + + execResBytes, err := execResStorer.Get(currentHeaderHash) if err != nil { - blockRootHash = header.GetRootHash() + return nil, err + } + + execRes, err := process.UnmarshalExecutionResult(n.coreComponents.InternalMarshalizer(), execResBytes) + if err != nil { + return nil, err } - return blockRootHash + return execRes.GetRootHash(), nil } diff --git a/node/nodeDebugFactory/interceptedDebugHandler.go b/node/nodeDebugFactory/interceptedDebugHandler.go index 694a84ee232..6caa24a00dd 100644 --- a/node/nodeDebugFactory/interceptedDebugHandler.go +++ b/node/nodeDebugFactory/interceptedDebugHandler.go @@ -2,11 +2,11 @@ package nodeDebugFactory import ( "fmt" - "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/debug/factory" + "github.com/multiversx/mx-chain-go/ntp" "github.com/multiversx/mx-chain-go/process" ) @@ -20,6 +20,8 @@ func CreateInterceptedDebugHandler( resolvers dataRetriever.ResolversContainer, requesters dataRetriever.RequestersContainer, config config.InterceptorResolverDebugConfig, + epochNotifier process.EpochStartEventNotifier, + syncTimer ntp.SyncTimer, ) error { if check.IfNil(node) { return ErrNilNodeWrapper @@ -33,12 +35,17 @@ func CreateInterceptedDebugHandler( if check.IfNil(requesters) { return ErrNilRequestersContainer } + if check.IfNil(epochNotifier) { + return process.ErrNilEpochNotifier + } - debugHandler, err := factory.NewInterceptorDebuggerFactory(config) + debugHandler, err := factory.NewInterceptorDebuggerFactory(config, syncTimer) if err != nil { return err } + epochNotifier.RegisterHandler(debugHandler.EpochStartEventHandler()) + var errFound error interceptors.Iterate(func(key string, interceptor process.Interceptor) bool { err = interceptor.SetInterceptedDebugHandler(debugHandler) diff --git a/node/nodeDebugFactory/interceptedDebugHandler_test.go b/node/nodeDebugFactory/interceptedDebugHandler_test.go index 786d46e5d3a..c1f7b9da1ac 100644 --- a/node/nodeDebugFactory/interceptedDebugHandler_test.go +++ b/node/nodeDebugFactory/interceptedDebugHandler_test.go @@ -25,6 +25,8 @@ func TestCreateInterceptedDebugHandler_NilNodeWrapperShouldErr(t *testing.T) { &dataRetrieverTests.ResolversContainerStub{}, &dataRetrieverTests.RequestersContainerStub{}, config.InterceptorResolverDebugConfig{}, + &mock.EpochStartNotifierStub{}, + &testscommon.SyncTimerStub{}, ) assert.Equal(t, ErrNilNodeWrapper, err) @@ -39,6 +41,8 @@ func TestCreateInterceptedDebugHandler_NilInterceptorsShouldErr(t *testing.T) { &dataRetrieverTests.ResolversContainerStub{}, &dataRetrieverTests.RequestersFinderStub{}, config.InterceptorResolverDebugConfig{}, + &mock.EpochStartNotifierStub{}, + &testscommon.SyncTimerStub{}, ) assert.Equal(t, ErrNilInterceptorContainer, err) @@ -53,6 +57,8 @@ func TestCreateInterceptedDebugHandler_NilResolversShouldErr(t *testing.T) { nil, &dataRetrieverTests.RequestersFinderStub{}, config.InterceptorResolverDebugConfig{}, + &mock.EpochStartNotifierStub{}, + &testscommon.SyncTimerStub{}, ) assert.Equal(t, ErrNilResolverContainer, err) @@ -67,6 +73,8 @@ func TestCreateInterceptedDebugHandler_NilRequestersShouldErr(t *testing.T) { &dataRetrieverTests.ResolversContainerStub{}, nil, config.InterceptorResolverDebugConfig{}, + &mock.EpochStartNotifierStub{}, + &testscommon.SyncTimerStub{}, ) assert.Equal(t, ErrNilRequestersContainer, err) @@ -84,6 +92,8 @@ func TestCreateInterceptedDebugHandler_InvalidDebugConfigShouldErr(t *testing.T) Enabled: true, CacheSize: 0, }, + &mock.EpochStartNotifierStub{}, + &testscommon.SyncTimerStub{}, ) assert.NotNil(t, err) @@ -128,6 +138,8 @@ func TestCreateInterceptedDebugHandler_SettingOnInterceptorsErrShouldErr(t *test config.InterceptorResolverDebugConfig{ Enabled: false, }, + &mock.EpochStartNotifierStub{}, + &testscommon.SyncTimerStub{}, ) assert.True(t, errors.Is(err, expectedErr)) @@ -176,6 +188,8 @@ func TestCreateInterceptedDebugHandler_SettingOnResolverErrShouldErr(t *testing. config.InterceptorResolverDebugConfig{ Enabled: false, }, + &mock.EpochStartNotifierStub{}, + &testscommon.SyncTimerStub{}, ) assert.True(t, errors.Is(err, expectedErr)) @@ -220,6 +234,8 @@ func TestCreateInterceptedDebugHandler_ShouldWork(t *testing.T) { config.InterceptorResolverDebugConfig{ Enabled: false, }, + &mock.EpochStartNotifierStub{}, + &testscommon.SyncTimerStub{}, ) assert.Nil(t, err) diff --git a/node/nodeHelper.go b/node/nodeHelper.go index 2e0099cc3d6..4bdae6aa0f8 100644 --- a/node/nodeHelper.go +++ b/node/nodeHelper.go @@ -2,7 +2,6 @@ package node import ( "errors" - "time" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-go/common" @@ -63,7 +62,7 @@ func CreateNode( return nil, err } - genesisTime := time.Unix(coreComponents.GenesisNodesSetup().GetStartTime(), 0) + genesisTime := common.GetGenesisStartTimeFromUnixTimestamp(coreComponents.GenesisNodesSetup().GetStartTime(), coreComponents.EnableEpochsHandler()) var nd *Node nd, err = NewNode( @@ -109,6 +108,8 @@ func CreateNode( processComponents.ResolversContainer(), processComponents.RequestersFinder(), config.Debug.InterceptorResolver, + nd.coreComponents.EpochStartNotifierWithConfirm(), + nd.coreComponents.SyncTimer(), ) if err != nil { return nil, err diff --git a/node/nodeRunner.go b/node/nodeRunner.go index e807e5c25d8..dabbe529bac 100644 --- a/node/nodeRunner.go +++ b/node/nodeRunner.go @@ -10,6 +10,7 @@ import ( "path/filepath" "runtime" "strconv" + "sync/atomic" "syscall" "time" @@ -191,7 +192,6 @@ func printEnableEpochs(configs *config.Configs) { log.Debug(readEpochFor("payable by smart contract"), "epoch", enableEpochs.IsPayableBySCEnableEpoch) log.Debug(readEpochFor("cleanup informative only SCRs"), "epoch", enableEpochs.CleanUpInformativeSCRsEnableEpoch) log.Debug(readEpochFor("storage API cost optimization"), "epoch", enableEpochs.StorageAPICostOptimizationEnableEpoch) - log.Debug(readEpochFor("transform to multi shard create on esdt"), "epoch", enableEpochs.TransformToMultiShardCreateEnableEpoch) log.Debug(readEpochFor("esdt: enable epoch for esdt register and set all roles function"), "epoch", enableEpochs.ESDTRegisterAndSetAllRolesEnableEpoch) log.Debug(readEpochFor("scheduled mini blocks"), "epoch", enableEpochs.ScheduledMiniBlocksEnableEpoch) log.Debug(readEpochFor("correct jailed not unstaked if empty queue"), "epoch", enableEpochs.CorrectJailedNotUnstakedEmptyQueueEpoch) @@ -318,6 +318,12 @@ func (nr *nodeRunner) executeOneComponentCreationCycle( return true, err } + log.Debug("creating metrics before bootstrap") + err = nr.createMetricsBeforeBootrapping(managedStatusCoreComponents, managedCoreComponents, managedCryptoComponents) + if err != nil { + return true, err + } + log.Debug("creating disabled API services") webServerHandler, err := nr.createHttpServer(managedStatusCoreComponents) if err != nil { @@ -372,6 +378,7 @@ func (nr *nodeRunner) executeOneComponentCreationCycle( managedCoreComponents.GenesisNodesSetup(), configs.GeneralConfig.EpochStartConfig, managedCoreComponents.ChanStopNodeProcess(), + managedCoreComponents.ChainParametersHandler(), ) if err != nil { return true, err @@ -566,6 +573,10 @@ func (nr *nodeRunner) executeOneComponentCreationCycle( sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + // closeComponentsDelay is needed because of pruning. To avoid removing data that will be needed when the node restarts, + // block pruning and then wait for a duration of one round before closing the components so that the info that is needed + // for the restart is persisted to storage. + closeComponentsDelay := time.Millisecond * time.Duration(managedCoreComponents.ChainParametersHandler().CurrentChainParameters().RoundDuration) nextOperation := waitForSignal( sigs, managedCoreComponents.ChanStopNodeProcess(), @@ -574,6 +585,10 @@ func (nr *nodeRunner) executeOneComponentCreationCycle( webServerHandler, currentNode, goRoutinesNumberStart, + managedCoreComponents.ClosingNodeStarted(), + closeComponentsDelay, + managedConsensusComponents, + managedProcessComponents.ExecutionManager(), ) return nextOperation == nextOperationShouldStop, nil @@ -758,11 +773,11 @@ func (nr *nodeRunner) createApiFacade( RestApiInterface: flagsConfig.RestApiInterface, PprofEnabled: flagsConfig.EnablePprof, P2PPrometheusMetricsEnabled: flagsConfig.P2PPrometheusMetricsEnabled, + TxCacheSelectionConfig: configs.GeneralConfig.TxCacheSelection, }, - ApiRoutesConfig: *configs.ApiRoutesConfig, - AccountsState: currentNode.stateComponents.AccountsAdapter(), - PeerState: currentNode.stateComponents.PeerAccounts(), - Blockchain: currentNode.dataComponents.Blockchain(), + ApiRoutesConfig: *configs.ApiRoutesConfig, + AccountsStateAPI: currentNode.stateComponents.AccountsAdapterAPI(), + Blockchain: currentNode.dataComponents.Blockchain(), } ef, err := facade.NewNodeFacade(argNodeFacade) @@ -819,28 +834,32 @@ func (nr *nodeRunner) createHttpServer(managedStatusCoreComponents mainFactory.S return httpServerWrapper, nil } -func (nr *nodeRunner) createMetrics( +func (nr *nodeRunner) createMetricsBeforeBootrapping( statusCoreComponents mainFactory.StatusCoreComponentsHolder, coreComponents mainFactory.CoreComponentsHolder, cryptoComponents mainFactory.CryptoComponentsHolder, - bootstrapComponents mainFactory.BootstrapComponentsHolder, ) error { - err := metrics.InitMetrics( + err := metrics.InitInitialMetrics( statusCoreComponents.AppStatusHandler(), cryptoComponents.PublicKeyString(), - bootstrapComponents.NodeType(), - bootstrapComponents.ShardCoordinator(), coreComponents.GenesisNodesSetup(), nr.configs.FlagsConfig.Version, nr.configs.EconomicsConfig, - nr.configs.GeneralConfig.EpochStartConfig.RoundsPerEpoch, coreComponents.MinTransactionVersion(), ) - if err != nil { return err } + nr.setMetricsAtInit(statusCoreComponents, coreComponents) + + return nil +} + +func (nr *nodeRunner) setMetricsAtInit( + statusCoreComponents mainFactory.StatusCoreComponentsHolder, + coreComponents mainFactory.CoreComponentsHolder, +) { metrics.SaveStringMetric(statusCoreComponents.AppStatusHandler(), common.MetricNodeDisplayName, nr.configs.PreferencesConfig.Preferences.NodeDisplayName) metrics.SaveStringMetric(statusCoreComponents.AppStatusHandler(), common.MetricRedundancyLevel, fmt.Sprintf("%d", nr.configs.PreferencesConfig.Preferences.RedundancyLevel)) metrics.SaveStringMetric(statusCoreComponents.AppStatusHandler(), common.MetricRedundancyIsMainActive, common.MetricValueNA) @@ -859,6 +878,37 @@ func (nr *nodeRunner) createMetrics( metrics.SaveStringMetric(statusCoreComponents.AppStatusHandler(), common.MetricPeerType, core.ObserverPeer.String()) metrics.SaveStringMetric(statusCoreComponents.AppStatusHandler(), common.MetricPeerSubType, core.FullHistoryObserver.String()) } +} + +func (nr *nodeRunner) createMetrics( + statusCoreComponents mainFactory.StatusCoreComponentsHolder, + coreComponents mainFactory.CoreComponentsHolder, + cryptoComponents mainFactory.CryptoComponentsHolder, + bootstrapComponents mainFactory.BootstrapComponentsHolder, +) error { + + chainParameters, err := coreComponents.ChainParametersHandler().ChainParametersForEpoch(bootstrapComponents.EpochBootstrapParams().Epoch()) + if err != nil { + return err + } + + err = metrics.InitMetrics( + statusCoreComponents.AppStatusHandler(), + cryptoComponents.PublicKeyString(), + bootstrapComponents.NodeType(), + bootstrapComponents.ShardCoordinator(), + coreComponents.GenesisNodesSetup(), + nr.configs.FlagsConfig.Version, + nr.configs.EconomicsConfig, + chainParameters, + coreComponents.MinTransactionVersion(), + ) + + if err != nil { + return err + } + + nr.setMetricsAtInit(statusCoreComponents, coreComponents) return nil } @@ -983,6 +1033,10 @@ func waitForSignal( httpServer shared.UpgradeableHttpServerHandler, currentNode *Node, goRoutinesNumberStart int, + closingNodeStarted *atomic.Bool, + closeComponentsDelay time.Duration, + consensusComponentsCloser io.Closer, + executionManagerCloser io.Closer, ) nextOperationForNode { var sig endProcess.ArgEndProcess reshuffled := false @@ -1005,13 +1059,13 @@ func waitForSignal( chanCloseComponents := make(chan struct{}) go func() { - closeAllComponents(healthService, facade, httpServer, currentNode, chanCloseComponents) + closeAllComponents(healthService, facade, httpServer, currentNode, chanCloseComponents, closingNodeStarted, closeComponentsDelay, consensusComponentsCloser, executionManagerCloser) }() select { case <-chanCloseComponents: log.Debug("Closed all components gracefully") - case <-time.After(maxTimeToClose): + case <-time.After(maxTimeToClose + closeComponentsDelay): log.Warn("force closing the node", "error", "closeAllComponents did not finish on time", "stack", goroutines.GetGoRoutines()) @@ -1057,7 +1111,7 @@ func (nr *nodeRunner) logInformation( "ShardId", shardIdString, "TotalShards", bootstrapComponents.ShardCoordinator().NumberOfShards(), "AppVersion", nr.configs.FlagsConfig.Version, - "GenesisTimeStamp", coreComponents.GenesisTime().Unix(), + "GenesisTimeStamp", common.GetGenesisUnixTimestampFromStartTime(coreComponents.GenesisTime(), coreComponents.EnableEpochsHandler()), ) sessionInfoFileOutput += "\nStarted with parameters:\n" @@ -1219,6 +1273,7 @@ func (nr *nodeRunner) CreateManagedProcessComponents( Marshalizer: coreComponents.InternalMarshalizer(), Store: dataComponents.StorageService(), Uint64ByteSliceConverter: coreComponents.Uint64ByteSliceConverter(), + DataPool: dataComponents.Datapool(), } historyRepositoryFactory, err := dbLookupFactory.NewHistoryRepositoryFactory(historyRepoFactoryArgs) if err != nil { @@ -1436,8 +1491,7 @@ func (nr *nodeRunner) CreateManagedNetworkComponents( MainConfig: *nr.configs.GeneralConfig, RatingsConfig: *nr.configs.RatingsConfig, StatusHandler: statusCoreComponents.AppStatusHandler(), - Marshalizer: coreComponents.InternalMarshalizer(), - Syncer: coreComponents.SyncTimer(), + CoreComponents: coreComponents, PreferredPeersSlices: nr.configs.PreferencesConfig.Preferences.PreferredConnections, BootstrapWaitTime: common.TimeToWaitForP2PBootstrap, NodeOperationMode: common.NormalOperation, @@ -1578,7 +1632,21 @@ func closeAllComponents( httpServer shared.UpgradeableHttpServerHandler, node *Node, chanCloseComponents chan struct{}, + closingNodeStarted *atomic.Bool, + closeComponentsDelay time.Duration, + consensusComponentsCloser io.Closer, + executionManagerCloser io.Closer, ) { + closingNodeStarted.Store(true) + // stop pruning, but wait a bit before closing the components to let the node finish processing the current block + time.Sleep(closeComponentsDelay) + + log.Debug("stopping consensus...") + log.LogIfError(consensusComponentsCloser.Close()) + + log.Debug("stopping async execution...") + log.LogIfError(executionManagerCloser.Close()) + log.Debug("closing health service...") err := healthService.Close() log.LogIfError(err) diff --git a/node/nodeRunner_test.go b/node/nodeRunner_test.go index 5d0e9a7666c..54d8ab96d04 100644 --- a/node/nodeRunner_test.go +++ b/node/nodeRunner_test.go @@ -3,18 +3,20 @@ package node import ( "os" "path" + "sync/atomic" "syscall" "testing" "time" "github.com/multiversx/mx-chain-core-go/data/endProcess" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/node/mock" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/api" - logger "github.com/multiversx/mx-chain-logger-go" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) const originalConfigsPath = "../cmd/node/config" @@ -173,6 +175,18 @@ func TestWaitForSignal(t *testing.T) { return nil }, } + consensusClosableComponent := &mock.CloserStub{ + CloseCalled: func() error { + closedCalled["consensus"] = struct{}{} + return nil + }, + } + executionManagerClosableComponent := &mock.CloserStub{ + CloseCalled: func() error { + closedCalled["executionManager"] = struct{}{} + return nil + }, + } n, _ := NewNode() n.closableComponents = append(n.closableComponents, internalNodeClosableComponent1) n.closableComponents = append(n.closableComponents, internalNodeClosableComponent2) @@ -196,6 +210,10 @@ func TestWaitForSignal(t *testing.T) { httpClosableComponent, n, 1, + &atomic.Bool{}, + time.Millisecond, + consensusClosableComponent, + executionManagerClosableComponent, ) assert.Equal(t, nextOperationShouldStop, nextOperation) @@ -222,6 +240,10 @@ func TestWaitForSignal(t *testing.T) { httpClosableComponent, n, 1, + &atomic.Bool{}, + time.Millisecond, + consensusClosableComponent, + executionManagerClosableComponent, ) assert.Equal(t, nextOperationShouldRestart, nextOperation) @@ -250,6 +272,10 @@ func TestWaitForSignal(t *testing.T) { httpClosableComponent, n, 1, + &atomic.Bool{}, + time.Millisecond, + consensusClosableComponent, + executionManagerClosableComponent, ) close(functionFinished) }() @@ -290,6 +316,10 @@ func TestWaitForSignal(t *testing.T) { httpClosableComponent, n, 1, + &atomic.Bool{}, + time.Millisecond, + consensusClosableComponent, + executionManagerClosableComponent, ) // these exceptions appear because the delayedComponent prevented the call of the first 2 components @@ -319,6 +349,10 @@ func TestWaitForSignal(t *testing.T) { httpClosableComponent, n, 1, + &atomic.Bool{}, + time.Millisecond, + consensusClosableComponent, + executionManagerClosableComponent, ) // these exceptions appear because the delayedComponent prevented the call of the first 2 components @@ -331,7 +365,7 @@ func TestWaitForSignal(t *testing.T) { } func checkCloseCalledMap(tb testing.TB, closedCalled map[string]struct{}, exceptions ...string) { - allKeys := []string{"healthService", "facade", "http", "node closable component 1", "node closable component 2"} + allKeys := []string{"consensus", "executionManager", "healthService", "facade", "http", "node closable component 1", "node closable component 2"} numKeys := 0 for _, key := range allKeys { if contains(key, exceptions) { diff --git a/node/nodeTesting.go b/node/nodeTesting.go index bcd15052e21..ced78315866 100644 --- a/node/nodeTesting.go +++ b/node/nodeTesting.go @@ -12,6 +12,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data/batch" "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-crypto-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process/factory" "github.com/multiversx/mx-chain-go/process/txsSender" @@ -25,7 +26,7 @@ var currentSendingGoRoutines = int32(0) var minTxGasPrice = uint64(100) var minTxGasLimit = uint64(1000) -//TODO remove this file and adapt integration tests using GenerateAndSendBulkTransactions +// TODO remove this file and adapt integration tests using GenerateAndSendBulkTransactions // GenerateAndSendBulkTransactions is a method for generating and propagating a set // of transactions to be processed. It is mainly used for demo purposes @@ -116,7 +117,7 @@ func (n *Node) GenerateAndSendBulkTransactions( whiteList(txs) } - //the topic identifier is made of the current shard id and sender's shard id + // the topic identifier is made of the current shard id and sender's shard id identifier := factory.TransactionTopic + n.processComponents.ShardCoordinator().CommunicationIdentifier(senderShardId) packets, err := dataPacker.PackDataInChunks(txsBuff, common.MaxBulkTransactionSize) diff --git a/node/node_test.go b/node/node_test.go index 37bd64f2d7d..762dbe08c70 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -5356,20 +5356,21 @@ func getDefaultCoreComponents() *nodeMockFactory.CoreComponentsMock { MinTransactionVersionCalled: func() uint32 { return 1 }, - WDTimer: &testscommon.WatchdogMock{}, - Alarm: &testscommon.AlarmSchedulerStub{}, - NtpTimer: &testscommon.SyncTimerStub{}, - RoundHandlerField: &testscommon.RoundHandlerMock{}, - EconomicsHandler: &economicsmocks.EconomicsHandlerMock{}, - APIEconomicsHandler: &economicsmocks.EconomicsHandlerMock{}, - RatingsConfig: &testscommon.RatingsInfoMock{}, - RatingHandler: &testscommon.RaterMock{}, - NodesConfig: &genesisMocks.NodesSetupStub{}, - StartTime: time.Time{}, - EpochChangeNotifier: &epochNotifier.EpochNotifierStub{}, - TxVersionCheckHandler: versioning.NewTxVersionChecker(0), - EnableEpochsHandlerField: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(common.RelayedTransactionsV3Flag), - FieldsSizeCheckerField: &testscommon.FieldsSizeCheckerMock{}, + WDTimer: &testscommon.WatchdogMock{}, + Alarm: &testscommon.AlarmSchedulerStub{}, + NtpTimer: &testscommon.SyncTimerStub{}, + RoundHandlerField: &testscommon.RoundHandlerMock{}, + EconomicsHandler: &economicsmocks.EconomicsHandlerMock{}, + APIEconomicsHandler: &economicsmocks.EconomicsHandlerMock{}, + RatingsConfig: &testscommon.RatingsInfoMock{}, + RatingHandler: &testscommon.RaterMock{}, + NodesConfig: &genesisMocks.NodesSetupStub{}, + StartTime: time.Time{}, + EpochChangeNotifier: &epochNotifier.EpochNotifierStub{}, + TxVersionCheckHandler: versioning.NewTxVersionChecker(0), + EnableEpochsHandlerField: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(common.RelayedTransactionsV3Flag), + FieldsSizeCheckerField: &testscommon.FieldsSizeCheckerMock{}, + AntifloodConfigsHandlerField: &testscommon.AntifloodConfigsHandlerStub{}, } } diff --git a/ntp/errors.go b/ntp/errors.go index a6ccda1f84b..ba15a4b1606 100644 --- a/ntp/errors.go +++ b/ntp/errors.go @@ -6,3 +6,6 @@ import ( // ErrIndexOutOfBounds is raised when an out of bound index is used var ErrIndexOutOfBounds = errors.New("index is out of bounds") + +// ErrNoClockOffsets is raised when no clock offsets are available +var ErrNoClockOffsets = errors.New("no clock offsets available") diff --git a/ntp/export_test.go b/ntp/export_test.go index f8aae1b01cd..60921dca533 100644 --- a/ntp/export_test.go +++ b/ntp/export_test.go @@ -6,8 +6,8 @@ import ( "github.com/beevik/ntp" ) -// OutOfBoundsDuration - -const OutOfBoundsDuration = outOfBoundsDuration +// NumRequestsFromHost - +var NumRequestsFromHost = numRequestsFromHost // Query - func (s *syncTime) Query() func(options NTPOptions, hostIndex int) (*ntp.Response, error) { @@ -29,17 +29,27 @@ func (s *syncTime) Sync() { s.sync() } -// GetClockOffsetsWithoutEdges - -func (s *syncTime) GetClockOffsetsWithoutEdges(clockOffsets []time.Duration) []time.Duration { - return s.getClockOffsetsWithoutEdges(clockOffsets) +// TriggerSync - +func (s *syncTime) TriggerSync() { + s.triggerSync() } -// GetHarmonicMean - -func (s *syncTime) GetHarmonicMean(clockOffsets []time.Duration) time.Duration { - return s.getHarmonicMean(clockOffsets) +// GetMedianOffset - +func (s *syncTime) GetMedianOffset(clockOffsets []time.Duration) (time.Duration, error) { + return s.getMedianOffset(clockOffsets) } // GetSleepTime - func (s *syncTime) GetSleepTime() time.Duration { return s.getSleepTime() } + +// SetLastSyncTime - +func (s *syncTime) SetLastSyncTime(t time.Time) { + s.mut.Lock() + s.lastSyncTime = t + s.mut.Unlock() +} + +// SyncCooldownDuration - +var SyncCooldownDuration = syncCooldown diff --git a/ntp/interface.go b/ntp/interface.go index 60b840c1d4b..23f02b909bd 100644 --- a/ntp/interface.go +++ b/ntp/interface.go @@ -7,6 +7,7 @@ import ( // SyncTimer defines an interface for time synchronization type SyncTimer interface { Close() error + ForceSync() StartSyncingTime() ClockOffset() time.Duration FormattedCurrentTime() string diff --git a/ntp/syncTime.go b/ntp/syncTime.go index b3893e81534..92c38aefce7 100644 --- a/ntp/syncTime.go +++ b/ntp/syncTime.go @@ -13,8 +13,10 @@ import ( "github.com/beevik/ntp" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/closing" + logger "github.com/multiversx/mx-chain-logger-go" + "golang.org/x/sync/singleflight" + "github.com/multiversx/mx-chain-go/config" - "github.com/multiversx/mx-chain-logger-go" ) var _ SyncTimer = (*syncTime)(nil) @@ -25,9 +27,6 @@ var log = logger.GetOrCreate("ntp") // numRequestsFromHost represents the number of requests to be done from each host const numRequestsFromHost = 10 -// cuttingOutPercent [0, 1) represents the percent of received clock offsets to be removed from the edges (min and max) -const cuttingOutPercent = 0.3 - // minResponsesPercent (0, 1] represents the minimum percent of responses, from all requests done, needed to set a new // clock offset const minResponsesPercent = 0.25 @@ -39,7 +38,15 @@ const maxOffsetPercent = 0.2 // minTimeout represents the minimum time in milliseconds to wait for a response from a host after a NTP request const minTimeout = 100 -const outOfBoundsDuration = time.Second +// maxAllowedNTPQueryResponseTimeMS specifies the maximum duration (in milliseconds) +// allowed for an NTP query. If a query takes longer than this limit, its response will be disregarded. +const maxAllowedNTPQueryResponseTimeMS = 200 + +const syncKey = "ntp-sync" + +// syncCooldown represents the minimum time between two consecutive syncs. +// This prevents excessive NTP queries when ForceSync is called frequently. +const syncCooldown = 10 * time.Minute // NTPOptions defines configuration options for a NTP query type NTPOptions struct { @@ -50,18 +57,6 @@ type NTPOptions struct { Port int } -// NewNTPGoogleConfig creates an NTPConfig object that configures NTP to use a predefined list of hosts. This is useful -// for tests, for example, to avoid loading a configuration file just to have a NTPConfig -func NewNTPGoogleConfig() config.NTPConfig { - return config.NTPConfig{ - Hosts: []string{"time.google.com", "time.cloudflare.com", "time.apple.com", "time.windows.com"}, - Port: 123, - Version: 0, - TimeoutMilliseconds: 100, - SyncPeriodSeconds: 3600, - } -} - // NewNTPOptions creates a new NTPOptions object func NewNTPOptions(ntpConfig config.NTPConfig) NTPOptions { ntpConfig.TimeoutMilliseconds = core.MaxInt(minTimeout, ntpConfig.TimeoutMilliseconds) @@ -95,12 +90,16 @@ func queryNTP(options NTPOptions, hostIndex int) (*ntp.Response, error) { // syncTime defines an object for time synchronization type syncTime struct { - mut sync.RWMutex - clockOffset time.Duration - syncPeriod time.Duration - ntpOptions NTPOptions - query func(options NTPOptions, hostIndex int) (*ntp.Response, error) - cancelFunc func() + mut sync.RWMutex + clockOffset time.Duration + syncPeriod time.Duration + ntpOptions NTPOptions + query func(options NTPOptions, hostIndex int) (*ntp.Response, error) + cancelFunc func() + outOfBoundsThreshold time.Duration + lastSyncTime time.Time + + sf singleflight.Group } // NewSyncTime creates a syncTime object. The customQueryFunc argument allows the caller to set a different NTP-querying @@ -115,10 +114,11 @@ func NewSyncTime( } s := syncTime{ - clockOffset: 0, - syncPeriod: time.Duration(ntpConfig.SyncPeriodSeconds) * time.Second, - query: queryFunc, - ntpOptions: NewNTPOptions(ntpConfig), + clockOffset: 0, + syncPeriod: time.Duration(ntpConfig.SyncPeriodSeconds) * time.Second, + query: queryFunc, + ntpOptions: NewNTPOptions(ntpConfig), + outOfBoundsThreshold: time.Duration(ntpConfig.OutOfBoundsThreshold) * time.Millisecond, } return &s @@ -134,7 +134,7 @@ func (s *syncTime) StartSyncingTime() { func (s *syncTime) startSync(ctx context.Context) { for { - s.sync() + s.triggerSync() select { case <-ctx.Done(): @@ -157,13 +157,63 @@ func (s *syncTime) getSleepTime() time.Duration { return s.syncPeriod + time.Duration(offset) } -// sync method does the time synchronization and sets the harmonic mean offset difference between local time +// ForceSync will trigger ntp sync and does not wait for completion +// it will not trigger if sync already in progress or if the cooldown period has not elapsed +func (s *syncTime) ForceSync() { + if s.isCooldown() { + return + } + + ch := s.sf.DoChan(syncKey, func() (any, error) { + s.sync() + return nil, nil + }) + + select { + case <-ch: + default: + log.Debug("ForceSync ignored: sync already in progress") + } +} + +// triggerSync will trigger sync and waits for completion +// this is called periodically in the ntp sync loop +func (s *syncTime) triggerSync() { + if s.isCooldown() { + return + } + + ch := s.sf.DoChan(syncKey, func() (any, error) { + s.sync() + return nil, nil + }) + + <-ch +} + +func (s *syncTime) isCooldown() bool { + s.mut.RLock() + elapsed := time.Since(s.lastSyncTime) + s.mut.RUnlock() + + if elapsed < syncCooldown { + log.Debug("sync trigger ignored: cooldown active", "remaining", syncCooldown-elapsed) + return true + } + + return false +} + +// sync method does the time synchronization and sets the median offset difference between local time // and servers time which have been used in synchronization func (s *syncTime) sync() { clockOffsets := make([]time.Duration, 0) + for hostIndex := 0; hostIndex < len(s.ntpOptions.Hosts); hostIndex++ { for requests := 0; requests < numRequestsFromHost; requests++ { + startTime := time.Now() response, err := s.query(s.ntpOptions, hostIndex) + duration := time.Since(startTime) if err != nil { log.Debug("sync.query", "host", s.ntpOptions.Hosts[hostIndex], @@ -173,7 +223,16 @@ func (s *syncTime) sync() { continue } - log.Trace("sync.query", + if duration.Milliseconds() > maxAllowedNTPQueryResponseTimeMS { + log.Debug("sync.query exceeds maximum allowed response time", + "host", s.ntpOptions.Hosts[hostIndex], + "port", s.ntpOptions.Port, + "duration", duration, + "maxAllowedNTPQueryResponseTimeMS", maxAllowedNTPQueryResponseTimeMS) + continue + } + + log.Debug("sync.query", "host", s.ntpOptions.Hosts[hostIndex], "reference time", response.ReferenceTime.Format("Mon Jan 2 15:04:05 MST 2006"), "time", response.Time.Format("Mon Jan 2 15:04:05 MST 2006"), @@ -186,7 +245,7 @@ func (s *syncTime) sync() { } numTotalRequests := len(s.ntpOptions.Hosts) * numRequestsFromHost - minClockOffsetsToAllowUpdate := math.Ceil(float64(numTotalRequests) * minResponsesPercent / (1 - cuttingOutPercent)) + minClockOffsetsToAllowUpdate := math.Ceil(float64(numTotalRequests) * minResponsesPercent) if len(clockOffsets) < int(minClockOffsetsToAllowUpdate) { log.Debug("sync.setClockOffset NOT done", "clock offsets", len(clockOffsets), @@ -195,58 +254,46 @@ func (s *syncTime) sync() { return } - clockOffsetsWithoutEdges := s.getClockOffsetsWithoutEdges(clockOffsets) - clockOffsetHarmonicMean := s.getHarmonicMean(clockOffsetsWithoutEdges) - isOutOfBounds := core.AbsDuration(clockOffsetHarmonicMean)-outOfBoundsDuration > 0 - if isOutOfBounds { - log.Error("syncTime.sync: clock offset is out of expected bounds", - "clock offset harmonic mean", clockOffsetHarmonicMean) - + clockOffset, err := s.getMedianOffset(clockOffsets) + if err != nil { + log.Debug("sync.getMedianOffset", "error", err.Error()) return } - s.setClockOffset(clockOffsetHarmonicMean) + isClockOffsetOutOfBounds := core.AbsDuration(clockOffset) > s.outOfBoundsThreshold + + if isClockOffsetOutOfBounds { + log.Warn("syncTime.sync: clock offset is out of expected bounds", + "clock offset median", clockOffset, + "outOfBoundsThreshold", s.outOfBoundsThreshold, + ) + } + + s.setClockOffset(clockOffset) + + s.mut.Lock() + s.lastSyncTime = time.Now() + s.mut.Unlock() log.Debug("sync.setClockOffset done", "num clock offsets", len(clockOffsets), - "num clock offsets without edges", len(clockOffsetsWithoutEdges), - "clock offset harmonic mean", clockOffsetHarmonicMean) + "clock offset median", clockOffset) } -func (s *syncTime) getClockOffsetsWithoutEdges(clockOffsets []time.Duration) []time.Duration { +func (s *syncTime) getMedianOffset(clockOffsets []time.Duration) (time.Duration, error) { + if len(clockOffsets) == 0 { + return time.Duration(0), ErrNoClockOffsets + } sort.Slice(clockOffsets, func(i, j int) bool { return clockOffsets[i] < clockOffsets[j] }) - cuttingOutPercentPerEdge := cuttingOutPercent / 2 - startIndex := math.Floor(float64(len(clockOffsets)) * cuttingOutPercentPerEdge) - endIndex := math.Ceil(float64(len(clockOffsets)) * (1 - cuttingOutPercentPerEdge)) - - return clockOffsets[int(startIndex):int(endIndex)] -} - -func (s *syncTime) getHarmonicMean(clockOffsets []time.Duration) time.Duration { - inverseClockOffsetSum := float64(0) - for index, clockOffset := range clockOffsets { - if clockOffset == 0 { - return time.Duration(0) - } - - inverseClockOffsetSum += 1 / float64(clockOffset) - - log.Trace("getHarmonicMean", - "index", index, - "clock offset", clockOffset, - "inverse clock offset sum", inverseClockOffsetSum) + n := len(clockOffsets) + middleIndex := n / 2 + if n%2 == 0 { + return (clockOffsets[middleIndex-1] + clockOffsets[middleIndex]) / 2, nil } - - if inverseClockOffsetSum == 0 { - return time.Duration(0) - } - - harmonicMean := float64(len(clockOffsets)) / inverseClockOffsetSum - //TODO: figure out why do we need to add 0.5 here - return time.Duration(harmonicMean + 0.5) + return clockOffsets[middleIndex], nil } // ClockOffset method gets the current time offset diff --git a/ntp/syncTime_test.go b/ntp/syncTime_test.go index 11defc88ea3..245a21c3370 100644 --- a/ntp/syncTime_test.go +++ b/ntp/syncTime_test.go @@ -4,14 +4,17 @@ import ( "errors" "fmt" "sync" + "sync/atomic" "testing" "time" beevikNtp "github.com/beevik/ntp" - "github.com/multiversx/mx-chain-go/config" - "github.com/multiversx/mx-chain-go/ntp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/ntp" + "github.com/multiversx/mx-chain-go/testscommon" ) var responseMock1 *beevikNtp.Response @@ -96,14 +99,13 @@ func TestHandleErrorInDoSync(t *testing.T) { st.Sync() assert.Equal(t, st.ClockOffset(), time.Duration(1234)) - } func TestValueInDoSync(t *testing.T) { responseMock2 = &beevikNtp.Response{ClockOffset: 23456} failNtpMock2 = false - st := ntp.NewSyncTime(config.NTPConfig{Hosts: []string{""}, SyncPeriodSeconds: 1}, queryMock2) + st := ntp.NewSyncTime(config.NTPConfig{Hosts: []string{""}, SyncPeriodSeconds: 1, OutOfBoundsThreshold: 200}, queryMock2) assert.Equal(t, st.ClockOffset(), time.Millisecond*0) st.Sync() @@ -120,7 +122,7 @@ func TestGetOffset(t *testing.T) { responseMock3 = &beevikNtp.Response{ClockOffset: 23456} failNtpMock3 = false - st := ntp.NewSyncTime(config.NTPConfig{Hosts: []string{""}, SyncPeriodSeconds: 1}, queryMock3) + st := ntp.NewSyncTime(config.NTPConfig{Hosts: []string{""}, SyncPeriodSeconds: 1, OutOfBoundsThreshold: 200}, queryMock3) assert.Equal(t, st.ClockOffset(), time.Millisecond*0) st.Sync() @@ -160,11 +162,11 @@ func TestCallQueryShouldErrIndexOutOfBounds(t *testing.T) { } func TestCallQueryShouldWork(t *testing.T) { - //TODO fix this test + // TODO fix this test t.Skip("rework this test as to not rely on the internet connection") t.Parallel() - ntpConfig := ntp.NewNTPGoogleConfig() + ntpConfig := testscommon.NewNTPGoogleConfig() ntpOptions := ntp.NewNTPOptions(ntpConfig) st := ntp.NewSyncTime(ntpConfig, nil) query := st.Query() @@ -177,86 +179,25 @@ func TestCallQueryShouldWork(t *testing.T) { func TestNtpHostIsChange(t *testing.T) { t.Parallel() - ntpConfig := config.NTPConfig{Hosts: []string{"host1", "host2", "host3"}, SyncPeriodSeconds: 1} + ntpConfig := config.NTPConfig{Hosts: []string{"host1", "host2", "host3"}, SyncPeriodSeconds: 1, OutOfBoundsThreshold: 1200} st := ntp.NewSyncTime(ntpConfig, queryMock5) st.Sync() - //HostIndex will be equal with 1 and time offset will be a second + // HostIndex will be equal with 1 and time offset will be a second assert.Equal(t, time.Second, st.ClockOffset()) } -func TestSyncShouldNotUpdateClockOffset(t *testing.T) { +func TestSyncShouldUpdateClockOffsetWhenEnoughResponses(t *testing.T) { t.Parallel() - ntpConfig := config.NTPConfig{Hosts: []string{"host1", "host2", "host3"}, SyncPeriodSeconds: 1} + // queryMock6: host 0 succeeds (ClockOffset = 1s), hosts 1 and 2 fail + // 10 successful responses out of 30 total (33%) exceeds minResponsesPercent (25%) + ntpConfig := config.NTPConfig{Hosts: []string{"host1", "host2", "host3"}, SyncPeriodSeconds: 1, OutOfBoundsThreshold: 1200} st := ntp.NewSyncTime(ntpConfig, queryMock6) st.SetClockOffset(time.Millisecond) st.Sync() - assert.Equal(t, time.Millisecond, st.ClockOffset()) -} - -func TestGetClockOffsetsWithoutEdges(t *testing.T) { - t.Parallel() - - st := ntp.NewSyncTime(config.NTPConfig{SyncPeriodSeconds: 1}, nil) - - clockOffsets := make([]time.Duration, 0) - clockOffsetsWithoutEdges := st.GetClockOffsetsWithoutEdges(clockOffsets) - require.Equal(t, 0, len(clockOffsetsWithoutEdges)) - - clockOffsets = []time.Duration{100} - clockOffsetsWithoutEdges = st.GetClockOffsetsWithoutEdges(clockOffsets) - require.Equal(t, 1, len(clockOffsetsWithoutEdges)) - - clockOffsets = []time.Duration{100, 54} - clockOffsetsWithoutEdges = st.GetClockOffsetsWithoutEdges(clockOffsets) - require.Equal(t, 2, len(clockOffsetsWithoutEdges)) - assert.Equal(t, time.Duration(54), clockOffsetsWithoutEdges[0]) - assert.Equal(t, time.Duration(100), clockOffsetsWithoutEdges[1]) - - clockOffsets = []time.Duration{100, 54, 2} - clockOffsetsWithoutEdges = st.GetClockOffsetsWithoutEdges(clockOffsets) - require.Equal(t, 3, len(clockOffsetsWithoutEdges)) - assert.Equal(t, time.Duration(2), clockOffsetsWithoutEdges[0]) - assert.Equal(t, time.Duration(54), clockOffsetsWithoutEdges[1]) - assert.Equal(t, time.Duration(100), clockOffsetsWithoutEdges[2]) - - clockOffsets = []time.Duration{100, 54, 2, 52} - clockOffsetsWithoutEdges = st.GetClockOffsetsWithoutEdges(clockOffsets) - require.Equal(t, 4, len(clockOffsetsWithoutEdges)) - assert.Equal(t, time.Duration(2), clockOffsetsWithoutEdges[0]) - assert.Equal(t, time.Duration(52), clockOffsetsWithoutEdges[1]) - assert.Equal(t, time.Duration(54), clockOffsetsWithoutEdges[2]) - assert.Equal(t, time.Duration(100), clockOffsetsWithoutEdges[3]) - - clockOffsets = []time.Duration{100, 54, 12, 52, 16, 1, 70} - clockOffsetsWithoutEdges = st.GetClockOffsetsWithoutEdges(clockOffsets) - require.Equal(t, 5, len(clockOffsetsWithoutEdges)) - assert.Equal(t, time.Duration(12), clockOffsetsWithoutEdges[0]) - assert.Equal(t, time.Duration(16), clockOffsetsWithoutEdges[1]) - assert.Equal(t, time.Duration(52), clockOffsetsWithoutEdges[2]) - assert.Equal(t, time.Duration(54), clockOffsetsWithoutEdges[3]) - assert.Equal(t, time.Duration(70), clockOffsetsWithoutEdges[4]) -} - -func TestGetHarmonicMean(t *testing.T) { - t.Parallel() - - st := ntp.NewSyncTime(config.NTPConfig{SyncPeriodSeconds: 1}, nil) - - clockOffsets := make([]time.Duration, 0) - harmonicMean := st.GetHarmonicMean(clockOffsets) - assert.Equal(t, time.Duration(0), harmonicMean) - - clockOffsets = []time.Duration{2, 0, 3} - harmonicMean = st.GetHarmonicMean(clockOffsets) - assert.Equal(t, time.Duration(0), harmonicMean) - - // harmonic mean for 4, 1, 4 is equal with: 3 / (1/4 + 1/1 + 1/4) = 3 / 1.5 = 2 - clockOffsets = []time.Duration{4, 1, 4} - harmonicMean = st.GetHarmonicMean(clockOffsets) - assert.Equal(t, time.Duration(2), harmonicMean) + assert.Equal(t, time.Second, st.ClockOffset()) } func TestGetSleepTime(t *testing.T) { @@ -264,7 +205,7 @@ func TestGetSleepTime(t *testing.T) { syncPeriodSeconds := 3600 givenTime := time.Duration(syncPeriodSeconds) * time.Second - st := ntp.NewSyncTime(config.NTPConfig{SyncPeriodSeconds: syncPeriodSeconds}, nil) + st := ntp.NewSyncTime(config.NTPConfig{SyncPeriodSeconds: syncPeriodSeconds, OutOfBoundsThreshold: 200}, nil) minSleepTime := time.Duration(float64(givenTime) - float64(givenTime)*0.2) maxSleepTime := time.Duration(float64(givenTime) + float64(givenTime)*0.2) @@ -282,41 +223,659 @@ func TestCallQueryShouldNotUpdateOnOutOfBoundValuesPositive(t *testing.T) { st := ntp.NewSyncTime( config.NTPConfig{ - SyncPeriodSeconds: 3600, - Hosts: []string{"host1"}, + SyncPeriodSeconds: 3600, + Hosts: []string{"host1"}, + OutOfBoundsThreshold: 1, + }, + func(options ntp.NTPOptions, hostIndex int) (*beevikNtp.Response, error) { + time.Sleep(2 * time.Millisecond) + + return &beevikNtp.Response{ + ClockOffset: 1 + time.Millisecond, + }, nil + }, + ) + + currentValue := 10 * time.Millisecond + st.SetClockOffset(currentValue) + st.Sync() + + expValue := 1 + time.Millisecond + + assert.Equal(t, expValue, st.ClockOffset()) +} + +func TestCallQueryShouldUpdateOnOutOfBoundValuesPositiveIfDurationNotOutOfBounds(t *testing.T) { + t.Parallel() + + st := ntp.NewSyncTime( + config.NTPConfig{ + SyncPeriodSeconds: 3600, + Hosts: []string{"host1"}, + OutOfBoundsThreshold: 1, }, func(options ntp.NTPOptions, hostIndex int) (*beevikNtp.Response, error) { return &beevikNtp.Response{ - ClockOffset: ntp.OutOfBoundsDuration + time.Nanosecond, + ClockOffset: 1 + time.Millisecond, }, nil }, ) - currentValue := time.Microsecond + currentValue := 10 * time.Millisecond st.SetClockOffset(currentValue) st.Sync() - assert.Equal(t, currentValue, st.ClockOffset()) + assert.NotEqual(t, currentValue, st.ClockOffset()) } -func TestCallQueryShouldNotUpdateOnOutOfBoundValuesNegative(t *testing.T) { +func TestCallQueryShouldUpdateOnOutOfBoundValuesNegative(t *testing.T) { t.Parallel() st := ntp.NewSyncTime( config.NTPConfig{ - SyncPeriodSeconds: 3600, - Hosts: []string{"host1"}, + SyncPeriodSeconds: 3600, + Hosts: []string{"host1"}, + OutOfBoundsThreshold: 2, }, func(options ntp.NTPOptions, hostIndex int) (*beevikNtp.Response, error) { return &beevikNtp.Response{ - ClockOffset: -ntp.OutOfBoundsDuration - 2*time.Nanosecond, + ClockOffset: -2 - 2*time.Millisecond, }, nil }, ) - currentValue := time.Microsecond + currentValue := 2 * 10 * time.Millisecond st.SetClockOffset(currentValue) st.Sync() - assert.Equal(t, currentValue, st.ClockOffset()) + expValue := -2 - 2*time.Millisecond + + assert.Equal(t, expValue, st.ClockOffset()) +} + +func TestCall_Sync_AcceptedBoundsChecks(t *testing.T) { + t.Parallel() + + t.Run("response time within accepted bounds, should set new offset", func(t *testing.T) { + t.Parallel() + + st := ntp.NewSyncTime( + config.NTPConfig{ + SyncPeriodSeconds: 3600, + Hosts: []string{"host1"}, + OutOfBoundsThreshold: 2, + }, + func(options ntp.NTPOptions, hostIndex int) (*beevikNtp.Response, error) { + return &beevikNtp.Response{ + ClockOffset: 1 * time.Millisecond, + }, nil + }, + ) + + currentValue := 3 * time.Millisecond + st.SetClockOffset(currentValue) + st.Sync() + + expClockOffset := 1 * time.Millisecond + assert.Equal(t, expClockOffset, st.ClockOffset()) + }) + + t.Run("response time within accepted bounds, clock offset not within accepted bounds, should set new offset", func(t *testing.T) { + t.Parallel() + + st := ntp.NewSyncTime( + config.NTPConfig{ + SyncPeriodSeconds: 3600, + Hosts: []string{"host1"}, + OutOfBoundsThreshold: 2, + }, + func(options ntp.NTPOptions, hostIndex int) (*beevikNtp.Response, error) { + return &beevikNtp.Response{ + ClockOffset: 4 * time.Millisecond, + }, nil + }, + ) + + currentValue := 3 * time.Millisecond + st.SetClockOffset(currentValue) + st.Sync() + + expClockOffset := 4 * time.Millisecond + assert.Equal(t, expClockOffset, st.ClockOffset()) + }) + + t.Run("response time not within accepted bounds, clock offset not within accepted bounds, should set new offset", func(t *testing.T) { + t.Parallel() + + st := ntp.NewSyncTime( + config.NTPConfig{ + SyncPeriodSeconds: 3600, + Hosts: []string{"host1"}, + OutOfBoundsThreshold: 2, + }, + func(options ntp.NTPOptions, hostIndex int) (*beevikNtp.Response, error) { + time.Sleep(5 * time.Millisecond) + + return &beevikNtp.Response{ + ClockOffset: 4 * time.Millisecond, + }, nil + }, + ) + + currentValue := 3 * time.Millisecond + st.SetClockOffset(currentValue) + st.Sync() + + expClockOffset := 4 * time.Millisecond + assert.Equal(t, expClockOffset, st.ClockOffset()) + }) + + t.Run("response time not within accepted bounds, clock offset within accepted bounds, should set new offset", func(t *testing.T) { + t.Parallel() + + st := ntp.NewSyncTime( + config.NTPConfig{ + SyncPeriodSeconds: 3600, + Hosts: []string{"host1"}, + OutOfBoundsThreshold: 2, + }, + func(options ntp.NTPOptions, hostIndex int) (*beevikNtp.Response, error) { + time.Sleep(5 * time.Millisecond) + + return &beevikNtp.Response{ + ClockOffset: 1 * time.Millisecond, + }, nil + }, + ) + + currentValue := 3 * time.Millisecond + st.SetClockOffset(currentValue) + st.Sync() + + expClockOffset := 1 * time.Millisecond + assert.Equal(t, expClockOffset, st.ClockOffset()) + }) + + t.Run("no successful response times, should set new offset", func(t *testing.T) { + t.Parallel() + + st := ntp.NewSyncTime( + config.NTPConfig{ + SyncPeriodSeconds: 3600, + Hosts: []string{"host1"}, + OutOfBoundsThreshold: 2, + }, + func(options ntp.NTPOptions, hostIndex int) (*beevikNtp.Response, error) { + return nil, errors.New("err") + }, + ) + + currentValue := 3 * time.Millisecond + st.SetClockOffset(currentValue) + st.Sync() + + expClockOffset := 3 * time.Millisecond + assert.Equal(t, expClockOffset, st.ClockOffset()) + }) +} + +func TestSyncTime_ForceSync(t *testing.T) { + t.Parallel() + + t.Run("ForceSync should work", func(t *testing.T) { + t.Parallel() + + var numCalls atomic.Uint32 + + st := ntp.NewSyncTime( + config.NTPConfig{ + SyncPeriodSeconds: 3600, + Hosts: []string{"host1"}, + OutOfBoundsThreshold: 2, + }, + func(options ntp.NTPOptions, hostIndex int) (*beevikNtp.Response, error) { + numCalls.Add(1) + + time.Sleep(1 * time.Millisecond) + + return &beevikNtp.Response{ + ClockOffset: 1 * time.Millisecond, + }, nil + }, + ) + + currentValue := 3 * time.Millisecond + st.SetClockOffset(currentValue) + + st.ForceSync() + + require.Eventually(t, func() bool { + return st.ClockOffset() == time.Millisecond && + numCalls.Load() == uint32(ntp.NumRequestsFromHost) + }, time.Second, 5*time.Millisecond) + + expClockOffset := 1 * time.Millisecond + assert.Equal(t, expClockOffset, st.ClockOffset()) + + require.Equal(t, ntp.NumRequestsFromHost, int(numCalls.Load())) + }) + + t.Run("TriggerSync should not trigger multiple times", func(t *testing.T) { + t.Parallel() + + numCalls := &atomic.Uint32{} + + st := ntp.NewSyncTime( + config.NTPConfig{ + SyncPeriodSeconds: 3600, + Hosts: []string{"host1"}, + OutOfBoundsThreshold: 2, + }, + func(options ntp.NTPOptions, hostIndex int) (*beevikNtp.Response, error) { + numCalls.Add(1) + + time.Sleep(2 * time.Millisecond) + + return &beevikNtp.Response{ + ClockOffset: 1 * time.Millisecond, + }, nil + }, + ) + + currentValue := 3 * time.Millisecond + st.SetClockOffset(currentValue) + + // multiple calls should trigger multiple times + st.TriggerSync() + st.TriggerSync() + st.TriggerSync() + st.TriggerSync() + + expClockOffset := 1 * time.Millisecond + assert.Equal(t, expClockOffset, st.ClockOffset()) + + // should not trigger multiple times due to cooldown + require.Equal(t, ntp.NumRequestsFromHost, int(numCalls.Load())) + }) + + t.Run("direct trigger should not trigger if already in progress", func(t *testing.T) { + t.Parallel() + + numCalls := &atomic.Uint32{} + + st := ntp.NewSyncTime( + config.NTPConfig{ + SyncPeriodSeconds: 3600, + Hosts: []string{"host1"}, + OutOfBoundsThreshold: 2, + }, + func(options ntp.NTPOptions, hostIndex int) (*beevikNtp.Response, error) { + numCalls.Add(1) + + time.Sleep(10 * time.Millisecond) + + return &beevikNtp.Response{ + ClockOffset: 1 * time.Millisecond, + }, nil + }, + ) + + // multiple ForceSync calls should not trigger if already syncing + go st.TriggerSync() + st.ForceSync() + st.ForceSync() + st.ForceSync() + + time.Sleep(time.Duration(ntp.NumRequestsFromHost*10+10) * time.Millisecond) + + require.Equal(t, ntp.NumRequestsFromHost, int(numCalls.Load())) + }) + + t.Run("ForceSync should skip during cooldown", func(t *testing.T) { + t.Parallel() + + numCalls := &atomic.Uint32{} + + st := ntp.NewSyncTime( + config.NTPConfig{ + SyncPeriodSeconds: 3600, + Hosts: []string{"host1"}, + OutOfBoundsThreshold: 2, + }, + func(options ntp.NTPOptions, hostIndex int) (*beevikNtp.Response, error) { + numCalls.Add(1) + + return &beevikNtp.Response{ + ClockOffset: 1 * time.Millisecond, + }, nil + }, + ) + + // set lastSyncTime to now, so cooldown is active + st.SetLastSyncTime(time.Now()) + + st.ForceSync() + + time.Sleep(50 * time.Millisecond) + + require.Equal(t, uint32(0), numCalls.Load()) + }) + + t.Run("ForceSync should work after cooldown expires", func(t *testing.T) { + t.Parallel() + + numCalls := &atomic.Uint32{} + + st := ntp.NewSyncTime( + config.NTPConfig{ + SyncPeriodSeconds: 3600, + Hosts: []string{"host1"}, + OutOfBoundsThreshold: 2, + }, + func(options ntp.NTPOptions, hostIndex int) (*beevikNtp.Response, error) { + numCalls.Add(1) + + return &beevikNtp.Response{ + ClockOffset: 1 * time.Millisecond, + }, nil + }, + ) + + // set lastSyncTime in the past, so cooldown has expired + st.SetLastSyncTime(time.Now().Add(-ntp.SyncCooldownDuration - time.Second)) + + st.ForceSync() + + time.Sleep(50 * time.Millisecond) + + require.Equal(t, uint32(ntp.NumRequestsFromHost), numCalls.Load()) + }) + + t.Run("triggerSync should skip during cooldown", func(t *testing.T) { + t.Parallel() + + numCalls := &atomic.Uint32{} + + st := ntp.NewSyncTime( + config.NTPConfig{ + SyncPeriodSeconds: 3600, + Hosts: []string{"host1"}, + OutOfBoundsThreshold: 2, + }, + func(options ntp.NTPOptions, hostIndex int) (*beevikNtp.Response, error) { + numCalls.Add(1) + + return &beevikNtp.Response{ + ClockOffset: 1 * time.Millisecond, + }, nil + }, + ) + + // set lastSyncTime to now, so cooldown is active + st.SetLastSyncTime(time.Now()) + + // triggerSync should still skip during cooldown + st.TriggerSync() + + require.Equal(t, uint32(0), numCalls.Load()) + }) + + t.Run("ForceSync followed by TriggerSync within cooldown should result in a single sync", func(t *testing.T) { + t.Parallel() + + numCalls := &atomic.Uint32{} + + st := ntp.NewSyncTime( + config.NTPConfig{ + SyncPeriodSeconds: 3600, + Hosts: []string{"host1"}, + OutOfBoundsThreshold: 2, + }, + + func(options ntp.NTPOptions, hostIndex int) (*beevikNtp.Response, error) { + numCalls.Add(1) + return &beevikNtp.Response{ + ClockOffset: 1 * time.Millisecond, + }, nil + }, + ) + + // call ForceSync to perform a sync and start cooldown + st.ForceSync() + time.Sleep(50 * time.Millisecond) + + st.TriggerSync() + time.Sleep(50 * time.Millisecond) + + // only one sync operation should have been performed + require.Equal(t, uint32(ntp.NumRequestsFromHost), numCalls.Load()) + }) +} + +// On local machine, seems like average query time is ~35ms, e.g.: +// Avg response time from host: time.google.com is 42.928837ms +// Avg response time from host: time.cloudflare.com is 13.877162ms +// Avg response time from host: time.apple.com is 37.257168ms +// Avg response time from host: time.windows.com is 48.33448ms +// Global average response time is 35.599412ms +func TestCallQueryShouldWorkMeasurements(t *testing.T) { + t.Skip("use this test only for local benchmarks, not for remote tests, since it relies on internet connection") + t.Parallel() + + ntpConfig := testscommon.NewNTPGoogleConfig() + ntpOptions := ntp.NewNTPOptions(ntpConfig) + st := ntp.NewSyncTime(ntpConfig, nil) + + query := st.Query() + + numRequestsFromHost := 10 + timeDurations := make(map[string][]time.Duration, len(ntpOptions.Hosts)) + + var totalGlobalDuration time.Duration + var totalRequests int + + for hostIndex := 0; hostIndex < len(ntpOptions.Hosts); hostIndex++ { + for requests := 0; requests < numRequestsFromHost; requests++ { + + hostName := ntpOptions.Hosts[hostIndex] + startTime := time.Now() + response, err := query(ntpOptions, hostIndex) + duration := time.Since(startTime) + + fmt.Printf("-> Query from host: %s function execution time: %s\n", hostName, duration) + + require.NotNil(t, response) + require.Nil(t, err) + + timeDurations[hostName] = append(timeDurations[hostName], duration) + + totalGlobalDuration += duration + totalRequests++ + } + } + + for _, hostName := range ntpOptions.Hosts { + durations := timeDurations[hostName] + var totalDuration time.Duration + for _, d := range durations { + totalDuration += d + } + avgTimePerHost := totalDuration / time.Duration(len(durations)) + fmt.Printf("Avg response time from host: %s is %s\n", hostName, avgTimePerHost) + } + + avgGlobalTime := totalGlobalDuration / time.Duration(totalRequests) + fmt.Printf("Global average response time is %s\n", avgGlobalTime) +} + +func TestGetMedianOffset(t *testing.T) { + t.Parallel() + + st := ntp.NewSyncTime(config.NTPConfig{SyncPeriodSeconds: 1, OutOfBoundsThreshold: 200}, nil) + + t.Run("empty slice should return error", func(t *testing.T) { + t.Parallel() + + offset, err := st.GetMedianOffset([]time.Duration{}) + require.Equal(t, ntp.ErrNoClockOffsets, err) + require.Equal(t, time.Duration(0), offset) + }) + + t.Run("single element", func(t *testing.T) { + t.Parallel() + + offset, err := st.GetMedianOffset([]time.Duration{42 * time.Millisecond}) + require.Nil(t, err) + require.Equal(t, 42*time.Millisecond, offset) + }) + + t.Run("single negative element", func(t *testing.T) { + t.Parallel() + + offset, err := st.GetMedianOffset([]time.Duration{-500 * time.Microsecond}) + require.Nil(t, err) + require.Equal(t, -500*time.Microsecond, offset) + }) + + t.Run("odd count returns true middle element", func(t *testing.T) { + t.Parallel() + + // Sorted: 10, 20, 30 \u2192 middle index = 1 \u2192 20 + clockOffsets := []time.Duration{30 * time.Millisecond, 10 * time.Millisecond, 20 * time.Millisecond} + offset, err := st.GetMedianOffset(clockOffsets) + require.Nil(t, err) + require.Equal(t, 20*time.Millisecond, offset) + }) + + t.Run("even count returns average of two middle elements", func(t *testing.T) { + t.Parallel() + + // Sorted: 10, 20, 30, 40 \u2192 median = (20 + 30) / 2 = 25 + clockOffsets := []time.Duration{40 * time.Millisecond, 10 * time.Millisecond, 30 * time.Millisecond, 20 * time.Millisecond} + offset, err := st.GetMedianOffset(clockOffsets) + require.Nil(t, err) + require.Equal(t, 25*time.Millisecond, offset) + }) + + t.Run("all identical values", func(t *testing.T) { + t.Parallel() + + val := 100 * time.Microsecond + clockOffsets := []time.Duration{val, val, val, val, val} + offset, err := st.GetMedianOffset(clockOffsets) + require.Nil(t, err) + require.Equal(t, val, offset) + }) + + t.Run("all negative values", func(t *testing.T) { + t.Parallel() + + // Sorted: -50, -30, -20, -10, -5 -> middle index = 2 -> -20 + clockOffsets := []time.Duration{ + -10 * time.Millisecond, + -50 * time.Millisecond, + -20 * time.Millisecond, + -5 * time.Millisecond, + -30 * time.Millisecond, + } + offset, err := st.GetMedianOffset(clockOffsets) + require.Nil(t, err) + require.Equal(t, -20*time.Millisecond, offset) + }) + + t.Run("mixed positive and negative values", func(t *testing.T) { + t.Parallel() + + // Sorted: -30, -10, 5, 20, 40 -> middle index = 2 -> 5 + clockOffsets := []time.Duration{ + 20 * time.Millisecond, + -10 * time.Millisecond, + 40 * time.Millisecond, + -30 * time.Millisecond, + 5 * time.Millisecond, + } + offset, err := st.GetMedianOffset(clockOffsets) + require.Nil(t, err) + require.Equal(t, 5*time.Millisecond, offset) + }) + + t.Run("two elements returns average of both", func(t *testing.T) { + t.Parallel() + + // Sorted: -6, 10 -> median = (-6 + 10) / 2 = 2 + clockOffsets := []time.Duration{10 * time.Millisecond, -6 * time.Millisecond} + offset, err := st.GetMedianOffset(clockOffsets) + require.Nil(t, err) + require.Equal(t, 2*time.Millisecond, offset) + }) + + t.Run("zero offset among values", func(t *testing.T) { + t.Parallel() + + // Sorted: -10, 0, 10 -> middle index = 1 -> 0 + clockOffsets := []time.Duration{10 * time.Millisecond, 0, -10 * time.Millisecond} + offset, err := st.GetMedianOffset(clockOffsets) + require.Nil(t, err) + require.Equal(t, time.Duration(0), offset) + }) + + t.Run("realistic mixed positive and negative NTP offsets", func(t *testing.T) { + t.Parallel() + + // 30 elements (even count): median = average of sorted[14] and sorted[15] + // sorted[14] = -709033ns, sorted[15] = -706902ns -> (-709033 + -706902) / 2 = -707967 + expectedValue := -707967 * time.Nanosecond + clockOffsets := []time.Duration{ + -1855712 * time.Nanosecond, + -1621517 * time.Nanosecond, + -1682624 * time.Nanosecond, + -1732382 * time.Nanosecond, + -1793740 * time.Nanosecond, + -1739692 * time.Nanosecond, + -1791143 * time.Nanosecond, + -1680870 * time.Nanosecond, + -1674741 * time.Nanosecond, + -1678761 * time.Nanosecond, + 431740 * time.Nanosecond, + 384421 * time.Nanosecond, + 496821 * time.Nanosecond, + 289701 * time.Nanosecond, + 505729 * time.Nanosecond, + 551695 * time.Nanosecond, + 264902 * time.Nanosecond, + 336397 * time.Nanosecond, + 426982 * time.Nanosecond, + 349654 * time.Nanosecond, + -717224 * time.Nanosecond, + -706902 * time.Nanosecond, + -709033 * time.Nanosecond, + -613281 * time.Nanosecond, + -705814 * time.Nanosecond, + -691355 * time.Nanosecond, + -602491 * time.Nanosecond, + -733157 * time.Nanosecond, + -754736 * time.Nanosecond, + -732048 * time.Nanosecond, + } + + offset, err := st.GetMedianOffset(clockOffsets) + require.Nil(t, err) + require.Equal(t, expectedValue, offset) + }) + + t.Run("outlier does not affect median", func(t *testing.T) { + t.Parallel() + + // Sorted: -1, 10, 11, 12, 1000000 -> middle index = 2 -> 11 + clockOffsets := []time.Duration{ + 10 * time.Millisecond, + 12 * time.Millisecond, + 1000000 * time.Millisecond, + -1 * time.Millisecond, + 11 * time.Millisecond, + } + offset, err := st.GetMedianOffset(clockOffsets) + require.Nil(t, err) + require.Equal(t, 11*time.Millisecond, offset) + }) } diff --git a/outport/factory/outportFactory.go b/outport/factory/outportFactory.go index a137175053e..64c5671737d 100644 --- a/outport/factory/outportFactory.go +++ b/outport/factory/outportFactory.go @@ -6,7 +6,7 @@ import ( outportcore "github.com/multiversx/mx-chain-core-go/data/outport" indexerFactory "github.com/multiversx/mx-chain-es-indexer-go/process/factory" - + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/outport" ) @@ -18,6 +18,8 @@ type OutportFactoryArgs struct { ElasticIndexerFactoryArgs indexerFactory.ArgsIndexerFactory EventNotifierFactoryArgs *EventNotifierFactoryArgs HostDriversArgs []ArgsHostDriverFactory + EnableEpochsHandler common.EnableEpochsHandler + EnableRoundsHandler common.EnableRoundsHandler } // CreateOutport will create a new instance of OutportHandler @@ -32,7 +34,12 @@ func CreateOutport(args *OutportFactoryArgs) (outport.OutportHandler, error) { IsInImportDBMode: args.IsImportDB, } - outportHandler, err := outport.NewOutport(args.RetrialInterval, cfg) + outportHandler, err := outport.NewOutport( + args.RetrialInterval, + cfg, + args.EnableEpochsHandler, + args.EnableRoundsHandler, + ) if err != nil { return nil, err } diff --git a/outport/factory/outportFactory_test.go b/outport/factory/outportFactory_test.go index 94d8c38839c..c2b71202a9d 100644 --- a/outport/factory/outportFactory_test.go +++ b/outport/factory/outportFactory_test.go @@ -9,24 +9,27 @@ import ( indexerFactory "github.com/multiversx/mx-chain-es-indexer-go/process/factory" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/outport" - "github.com/multiversx/mx-chain-go/outport/factory" notifierFactory "github.com/multiversx/mx-chain-go/outport/factory" "github.com/multiversx/mx-chain-go/process/mock" - "github.com/multiversx/mx-chain-storage-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" + "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" "github.com/stretchr/testify/require" ) -func createMockArgsOutportHandler(indexerEnabled, notifierEnabled bool) *factory.OutportFactoryArgs { +func createMockArgsOutportHandler(indexerEnabled, notifierEnabled bool) *notifierFactory.OutportFactoryArgs { mockElasticArgs := indexerFactory.ArgsIndexerFactory{ Enabled: indexerEnabled, } mockNotifierArgs := ¬ifierFactory.EventNotifierFactoryArgs{ Enabled: notifierEnabled, } - return &factory.OutportFactoryArgs{ + return ¬ifierFactory.OutportFactoryArgs{ RetrialInterval: time.Second, ElasticIndexerFactoryArgs: mockElasticArgs, EventNotifierFactoryArgs: mockNotifierArgs, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, } } @@ -34,19 +37,19 @@ func TestNewIndexerFactory(t *testing.T) { t.Parallel() tests := []struct { name string - argsFunc func() *factory.OutportFactoryArgs + argsFunc func() *notifierFactory.OutportFactoryArgs exError error }{ { name: "NilArgsOutportFactory", - argsFunc: func() *factory.OutportFactoryArgs { + argsFunc: func() *notifierFactory.OutportFactoryArgs { return nil }, exError: outport.ErrNilArgsOutportFactory, }, { name: "invalid retrial duration", - argsFunc: func() *factory.OutportFactoryArgs { + argsFunc: func() *notifierFactory.OutportFactoryArgs { args := createMockArgsOutportHandler(false, false) args.RetrialInterval = 0 return args @@ -55,7 +58,7 @@ func TestNewIndexerFactory(t *testing.T) { }, { name: "AllOkShouldWork", - argsFunc: func() *factory.OutportFactoryArgs { + argsFunc: func() *notifierFactory.OutportFactoryArgs { return createMockArgsOutportHandler(false, false) }, exError: nil, @@ -64,7 +67,7 @@ func TestNewIndexerFactory(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := factory.CreateOutport(tt.argsFunc()) + _, err := notifierFactory.CreateOutport(tt.argsFunc()) require.True(t, errors.Is(err, tt.exError)) }) } @@ -73,22 +76,22 @@ func TestNewIndexerFactory(t *testing.T) { func TestCreateOutport_EnabledDriversNilMockArgsExpectErrorSubscribingDrivers(t *testing.T) { t.Parallel() tests := []struct { - argsFunc func() *factory.OutportFactoryArgs + argsFunc func() *notifierFactory.OutportFactoryArgs }{ { - argsFunc: func() *factory.OutportFactoryArgs { + argsFunc: func() *notifierFactory.OutportFactoryArgs { return createMockArgsOutportHandler(true, false) }, }, { - argsFunc: func() *factory.OutportFactoryArgs { + argsFunc: func() *notifierFactory.OutportFactoryArgs { return createMockArgsOutportHandler(false, true) }, }, } for _, currTest := range tests { - _, err := factory.CreateOutport(currTest.argsFunc()) + _, err := notifierFactory.CreateOutport(currTest.argsFunc()) require.NotNil(t, err) } } @@ -98,7 +101,7 @@ func TestCreateOutport_SubscribeNotifierDriver(t *testing.T) { args.EventNotifierFactoryArgs.Marshaller = &mock.MarshalizerMock{} args.EventNotifierFactoryArgs.RequestTimeoutSec = 1 - outPort, err := factory.CreateOutport(args) + outPort, err := notifierFactory.CreateOutport(args) require.Nil(t, err) defer func(c outport.OutportHandler) { @@ -109,7 +112,7 @@ func TestCreateOutport_SubscribeNotifierDriver(t *testing.T) { } func TestCreateOutport_SubscribeMultipleHostDrivers(t *testing.T) { - args := &factory.OutportFactoryArgs{ + args := ¬ifierFactory.OutportFactoryArgs{ RetrialInterval: time.Second, EventNotifierFactoryArgs: ¬ifierFactory.EventNotifierFactoryArgs{ Enabled: false, @@ -119,7 +122,7 @@ func TestCreateOutport_SubscribeMultipleHostDrivers(t *testing.T) { }, HostDriversArgs: []notifierFactory.ArgsHostDriverFactory{ { - Marshaller: &testscommon.MarshalizerMock{}, + Marshaller: &marshallerMock.MarshalizerMock{}, HostConfig: config.HostDriversConfig{ Enabled: true, URL: "ws://localhost", @@ -129,7 +132,7 @@ func TestCreateOutport_SubscribeMultipleHostDrivers(t *testing.T) { }, }, { - Marshaller: &testscommon.MarshalizerMock{}, + Marshaller: &marshallerMock.MarshalizerMock{}, HostConfig: config.HostDriversConfig{ Enabled: false, URL: "ws://localhost", @@ -139,7 +142,7 @@ func TestCreateOutport_SubscribeMultipleHostDrivers(t *testing.T) { }, }, { - Marshaller: &testscommon.MarshalizerMock{}, + Marshaller: &marshallerMock.MarshalizerMock{}, HostConfig: config.HostDriversConfig{ Enabled: true, URL: "ws://localhost", @@ -149,9 +152,11 @@ func TestCreateOutport_SubscribeMultipleHostDrivers(t *testing.T) { }, }, }, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, } - outPort, err := factory.CreateOutport(args) + outPort, err := notifierFactory.CreateOutport(args) require.Nil(t, err) defer func() { @@ -162,7 +167,7 @@ func TestCreateOutport_SubscribeMultipleHostDrivers(t *testing.T) { } func TestCreateAndSubscribeDriversShouldReturnError(t *testing.T) { - args := &factory.OutportFactoryArgs{ + args := ¬ifierFactory.OutportFactoryArgs{ RetrialInterval: time.Second, EventNotifierFactoryArgs: ¬ifierFactory.EventNotifierFactoryArgs{ Enabled: false, @@ -172,7 +177,7 @@ func TestCreateAndSubscribeDriversShouldReturnError(t *testing.T) { }, HostDriversArgs: []notifierFactory.ArgsHostDriverFactory{ { - Marshaller: &testscommon.MarshalizerMock{}, + Marshaller: &marshallerMock.MarshalizerMock{}, HostConfig: config.HostDriversConfig{ Enabled: true, URL: "localhost", @@ -182,9 +187,11 @@ func TestCreateAndSubscribeDriversShouldReturnError(t *testing.T) { }, }, }, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, } - outPort, err := factory.CreateOutport(args) + outPort, err := notifierFactory.CreateOutport(args) require.Nil(t, outPort) require.ErrorIs(t, err, data.ErrInvalidWebSocketHostMode) } diff --git a/outport/mock/transactionsFeeHandlerMock.go b/outport/mock/transactionsFeeHandlerMock.go new file mode 100644 index 00000000000..3999da14164 --- /dev/null +++ b/outport/mock/transactionsFeeHandlerMock.go @@ -0,0 +1,22 @@ +package mock + +import "github.com/multiversx/mx-chain-core-go/data/outport" + +// TransactionsFeeHandlerMock - +type TransactionsFeeHandlerMock struct { + PutFeeAndGasUsedCalled func(pool *outport.TransactionPool, epoch uint32) error +} + +// PutFeeAndGasUsed - +func (tfh *TransactionsFeeHandlerMock) PutFeeAndGasUsed(pool *outport.TransactionPool, epoch uint32) error { + if tfh.PutFeeAndGasUsedCalled != nil { + return tfh.PutFeeAndGasUsedCalled(pool, epoch) + } + + return nil +} + +// IsInterfaceNil - +func (tfh *TransactionsFeeHandlerMock) IsInterfaceNil() bool { + return tfh == nil +} diff --git a/outport/notifier/eventNotifier_test.go b/outport/notifier/eventNotifier_test.go index 3533a37be71..973d84eb9ab 100644 --- a/outport/notifier/eventNotifier_test.go +++ b/outport/notifier/eventNotifier_test.go @@ -135,7 +135,7 @@ func TestSaveBlock(t *testing.T) { SmartContractResults: map[string]*outport.SCRInfo{ scrHash1: nil, }, - Logs: []*outport.LogData{ + Logs: []*transaction.LogData{ { TxHash: txHash1, Log: &transaction.Log{}, diff --git a/outport/outport.go b/outport/outport.go index f611d56363b..2f886b6936e 100644 --- a/outport/outport.go +++ b/outport/outport.go @@ -8,6 +8,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-core-go/core/check" outportcore "github.com/multiversx/mx-chain-core-go/data/outport" @@ -20,30 +21,45 @@ const maxTimeForDriverCall = time.Second * 30 const minimumRetrialInterval = time.Millisecond * 10 type outport struct { - mutex sync.RWMutex - drivers []Driver - retrialInterval time.Duration - chanClose chan struct{} - logHandler func(logLevel logger.LogLevel, message string, args ...interface{}) - timeForDriverCall time.Duration - messageCounter uint64 - config outportcore.OutportConfig + mutex sync.RWMutex + drivers []Driver + retrialInterval time.Duration + chanClose chan struct{} + logHandler func(logLevel logger.LogLevel, message string, args ...interface{}) + timeForDriverCall time.Duration + messageCounter uint64 + config outportcore.OutportConfig + enableEpochsHandler common.EnableEpochsHandler + enableRoundsHandler common.EnableRoundsHandler } // NewOutport will create a new instance of proxy -func NewOutport(retrialInterval time.Duration, cfg outportcore.OutportConfig) (*outport, error) { +func NewOutport( + retrialInterval time.Duration, + cfg outportcore.OutportConfig, + enableEpochsHandler common.EnableEpochsHandler, + enableRoundsHandler common.EnableRoundsHandler, +) (*outport, error) { if retrialInterval < minimumRetrialInterval { return nil, fmt.Errorf("%w, provided: %d, minimum: %d", ErrInvalidRetrialInterval, retrialInterval, minimumRetrialInterval) } + if check.IfNil(enableEpochsHandler) { + return nil, process.ErrNilEnableEpochsHandler + } + if check.IfNil(enableRoundsHandler) { + return nil, process.ErrNilEnableRoundsHandler + } return &outport{ - drivers: make([]Driver, 0), - mutex: sync.RWMutex{}, - retrialInterval: retrialInterval, - chanClose: make(chan struct{}), - logHandler: log.Log, - timeForDriverCall: maxTimeForDriverCall, - config: cfg, + drivers: make([]Driver, 0), + mutex: sync.RWMutex{}, + retrialInterval: retrialInterval, + chanClose: make(chan struct{}), + logHandler: log.Log, + timeForDriverCall: maxTimeForDriverCall, + config: cfg, + enableEpochsHandler: enableEpochsHandler, + enableRoundsHandler: enableRoundsHandler, }, nil } @@ -57,7 +73,7 @@ func (o *outport) SaveBlock(args *outportcore.OutportBlockWithHeaderAndBody) err } for _, driver := range o.drivers { - blockData, err := prepareBlockData(args.HeaderDataWithBody, driver) + blockData, err := o.prepareBlockData(args.HeaderDataWithBody, driver) if err != nil { return err } @@ -69,7 +85,7 @@ func (o *outport) SaveBlock(args *outportcore.OutportBlockWithHeaderAndBody) err return nil } -func prepareBlockData( +func (o *outport) prepareBlockData( headerBodyData *outportcore.HeaderDataWithBody, driver Driver, ) (*outportcore.BlockData, error) { @@ -95,6 +111,11 @@ func prepareBlockData( } } + _, timestampMs, err := common.GetHeaderTimestamps(headerBodyData.Header, o.enableEpochsHandler) + if err != nil { + return nil, err + } + return &outportcore.BlockData{ ShardID: headerBodyData.Header.GetShardID(), HeaderBytes: headerBytes, @@ -103,7 +124,8 @@ func prepareBlockData( Body: body, IntraShardMiniBlocks: headerBodyData.IntraShardMiniBlocks, HeaderProof: proof, - TimestampMs: common.ConvertTimeStampSecToMs(headerBodyData.Header.GetTimeStamp()), + TimestampMs: timestampMs, + Results: headerBodyData.Results, }, nil } @@ -167,7 +189,7 @@ func (o *outport) RevertIndexedBlock(headerDataWithBody *outportcore.HeaderDataW defer o.mutex.RUnlock() for _, driver := range o.drivers { - blockData, err := prepareBlockData(headerDataWithBody, driver) + blockData, err := o.prepareBlockData(headerDataWithBody, driver) if err != nil { return err } diff --git a/outport/outport_test.go b/outport/outport_test.go index a87fef5c69b..3846c2ad990 100644 --- a/outport/outport_test.go +++ b/outport/outport_test.go @@ -13,6 +13,8 @@ import ( "github.com/multiversx/mx-chain-core-go/data/block" outportcore "github.com/multiversx/mx-chain-core-go/data/outport" "github.com/multiversx/mx-chain-go/outport/mock" + "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" logger "github.com/multiversx/mx-chain-logger-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -35,13 +37,13 @@ func TestNewOutport(t *testing.T) { t.Parallel() t.Run("invalid retrial time should error", func(t *testing.T) { - outportHandler, err := NewOutport(0, outportcore.OutportConfig{}) + outportHandler, err := NewOutport(0, outportcore.OutportConfig{}, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) assert.True(t, errors.Is(err, ErrInvalidRetrialInterval)) assert.True(t, check.IfNil(outportHandler)) }) t.Run("should work", func(t *testing.T) { - outportHandler, err := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}) + outportHandler, err := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) assert.Nil(t, err) assert.False(t, check.IfNil(outportHandler)) @@ -70,7 +72,7 @@ func TestOutport_SaveAccounts(t *testing.T) { return nil }, } - outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}) + outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) numLogDebugCalled := uint32(0) outportHandler.logHandler = func(logLevel logger.LogLevel, message string, args ...interface{}) { if logLevel == logger.LogError { @@ -116,7 +118,7 @@ func TestOutport_SaveBlock(t *testing.T) { return nil }, } - outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}) + outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) numLogDebugCalled := uint32(0) outportHandler.logHandler = func(logLevel logger.LogLevel, message string, args ...interface{}) { if logLevel == logger.LogError { @@ -162,7 +164,7 @@ func TestOutport_SaveRoundsInfo(t *testing.T) { return nil }, } - outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}) + outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) numLogDebugCalled := uint32(0) outportHandler.logHandler = func(logLevel logger.LogLevel, message string, args ...interface{}) { if logLevel == logger.LogError { @@ -207,7 +209,7 @@ func TestOutport_SaveValidatorsPubKeys(t *testing.T) { return nil }, } - outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}) + outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) numLogDebugCalled := uint32(0) outportHandler.logHandler = func(logLevel logger.LogLevel, message string, args ...interface{}) { if logLevel == logger.LogError { @@ -254,7 +256,7 @@ func TestOutport_SaveValidatorsRating(t *testing.T) { return nil }, } - outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}) + outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) numLogDebugCalled := uint32(0) outportHandler.logHandler = func(logLevel logger.LogLevel, message string, args ...interface{}) { if logLevel == logger.LogError { @@ -301,7 +303,7 @@ func TestOutport_RevertIndexedBlock(t *testing.T) { return nil }, } - outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}) + outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) numLogDebugCalled := uint32(0) outportHandler.logHandler = func(logLevel logger.LogLevel, message string, args ...interface{}) { if logLevel == logger.LogError { @@ -349,7 +351,7 @@ func TestOutport_FinalizedBlock(t *testing.T) { return nil }, } - outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}) + outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) numLogDebugCalled := uint32(0) outportHandler.logHandler = func(logLevel logger.LogLevel, message string, args ...interface{}) { if logLevel == logger.LogError { @@ -378,7 +380,7 @@ func TestOutport_SubscribeDriver(t *testing.T) { t.Parallel() t.Run("nil driver should error", func(t *testing.T) { - outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}) + outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) require.False(t, outportHandler.HasDrivers()) @@ -387,7 +389,7 @@ func TestOutport_SubscribeDriver(t *testing.T) { require.False(t, outportHandler.HasDrivers()) }) t.Run("should work", func(t *testing.T) { - outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}) + outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) require.False(t, outportHandler.HasDrivers()) @@ -400,7 +402,7 @@ func TestOutport_SubscribeDriver(t *testing.T) { func TestOutport_Close(t *testing.T) { t.Parallel() - outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}) + outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) localErr := errors.New("local err") driver1 := &mock.DriverStub{ @@ -424,7 +426,7 @@ func TestOutport_Close(t *testing.T) { func TestOutport_CloseWhileDriverIsStuckInContinuousErrors(t *testing.T) { t.Parallel() - outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}) + outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) localErr := errors.New("driver stuck in error") driver1 := &mock.DriverStub{ @@ -512,7 +514,7 @@ func TestOutport_SaveBlockDriverStuck(t *testing.T) { t.Parallel() currentCounter := uint64(778) - outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}) + outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) outportHandler.messageCounter = currentCounter outportHandler.timeForDriverCall = time.Second logErrorCalled := atomic.Flag{} @@ -547,7 +549,7 @@ func TestOutport_SaveBlockDriverIsNotStuck(t *testing.T) { t.Parallel() currentCounter := uint64(778) - outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}) + outportHandler, _ := NewOutport(minimumRetrialInterval, outportcore.OutportConfig{}, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) outportHandler.messageCounter = currentCounter outportHandler.timeForDriverCall = time.Second numLogDebugCalled := uint32(0) @@ -595,7 +597,7 @@ func TestOutport_SettingsRequestAndReceive(t *testing.T) { }, } - outportHandler, _ := NewOutport(time.Second, outportcore.OutportConfig{}) + outportHandler, _ := NewOutport(time.Second, outportcore.OutportConfig{}, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) err := outportHandler.SubscribeDriver(driver) assert.Equal(t, expectedErr, err) require.False(t, outportHandler.HasDrivers()) @@ -624,7 +626,7 @@ func TestOutport_SettingsRequestAndReceive(t *testing.T) { }, } - outportHandler, _ := NewOutport(time.Second, outportcore.OutportConfig{}) + outportHandler, _ := NewOutport(time.Second, outportcore.OutportConfig{}, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) err := outportHandler.SubscribeDriver(driver) assert.Nil(t, err) @@ -654,7 +656,7 @@ func TestOutport_SettingsRequestAndReceive(t *testing.T) { providedConfig := outportcore.OutportConfig{ IsInImportDBMode: true, } - outportHandler, _ := NewOutport(time.Second, providedConfig) + outportHandler, _ := NewOutport(time.Second, providedConfig, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) err := outportHandler.SubscribeDriver(driver) assert.Nil(t, err) assert.True(t, outportHandler.HasDrivers()) diff --git a/outport/process/alteredaccounts/alteredAccountsProvider_test.go b/outport/process/alteredaccounts/alteredAccountsProvider_test.go index c6fce787c71..6721f023759 100644 --- a/outport/process/alteredaccounts/alteredAccountsProvider_test.go +++ b/outport/process/alteredaccounts/alteredAccountsProvider_test.go @@ -530,7 +530,7 @@ func testExtractAlteredAccountsFromPoolShouldReturnErrorWhenCastingToVmCommonUse aap, _ := NewAlteredAccountsProvider(args) res, err := aap.ExtractAlteredAccountsFromPool(&outportcore.TransactionPool{ - Logs: []*outportcore.LogData{ + Logs: []*transaction.LogData{ { TxHash: "hash", Log: &transaction.Log{ @@ -582,7 +582,7 @@ func testExtractAlteredAccountsFromPoolShouldIncludeESDT(t *testing.T) { aap, _ := NewAlteredAccountsProvider(args) res, err := aap.ExtractAlteredAccountsFromPool(&outportcore.TransactionPool{ - Logs: []*outportcore.LogData{ + Logs: []*transaction.LogData{ { TxHash: "hash", Log: &transaction.Log{ @@ -648,7 +648,7 @@ func testExtractAlteredAccountsFromPoolShouldIncludeNFT(t *testing.T) { aap, _ := NewAlteredAccountsProvider(args) res, err := aap.ExtractAlteredAccountsFromPool(&outportcore.TransactionPool{ - Logs: []*outportcore.LogData{ + Logs: []*transaction.LogData{ { TxHash: "hash", Log: &transaction.Log{ @@ -716,7 +716,7 @@ func testExtractAlteredAccountsFromPoolShouldNotIncludeReceiverAddressIfNftCreat }, }, }, - Logs: []*outportcore.LogData{ + Logs: []*transaction.LogData{ { TxHash: "hh", Log: &transaction.Log{ @@ -784,7 +784,7 @@ func testExtractAlteredAccountsFromPoolShouldIncludeDestinationFromTokensLogsTop aap, _ := NewAlteredAccountsProvider(args) res, err := aap.ExtractAlteredAccountsFromPool(&outportcore.TransactionPool{ - Logs: []*outportcore.LogData{ + Logs: []*transaction.LogData{ { TxHash: "hash0", Log: &transaction.Log{ @@ -864,7 +864,7 @@ func testExtractAlteredAccountsFromPoolAddressHasBalanceChangeEsdtAndfNft(t *tes }, }, }, - Logs: []*outportcore.LogData{ + Logs: []*transaction.LogData{ { TxHash: "hash0", Log: &transaction.Log{ @@ -944,7 +944,7 @@ func testExtractAlteredAccountsFromPoolMultiTransferEventV2(t *testing.T) { }, }, }, - Logs: []*outportcore.LogData{ + Logs: []*transaction.LogData{ { TxHash: "hash0", Log: &transaction.Log{ @@ -1085,7 +1085,7 @@ func testExtractAlteredAccountsFromPoolAddressHasMultipleNfts(t *testing.T) { }, }, }, - Logs: []*outportcore.LogData{ + Logs: []*transaction.LogData{ { TxHash: "hash0", Log: &transaction.Log{ @@ -1192,7 +1192,7 @@ func testExtractAlteredAccountsFromPoolESDTTransferBalanceNotChanged(t *testing. }, }, }, - Logs: []*outportcore.LogData{ + Logs: []*transaction.LogData{ { TxHash: "txHash", Log: &transaction.Log{ @@ -1405,7 +1405,7 @@ func textExtractAlteredAccountsFromPoolNftCreate(t *testing.T) { }, }, }, - Logs: []*outportcore.LogData{ + Logs: []*transaction.LogData{ { TxHash: "txHash", Log: &transaction.Log{ diff --git a/outport/process/errors.go b/outport/process/errors.go index 853705659aa..86dff1fc919 100644 --- a/outport/process/errors.go +++ b/outport/process/errors.go @@ -63,5 +63,8 @@ var ErrNilExecutedTxHashes = errors.New("nil executed tx hashes map") // ErrIndexOutOfBounds signals that an index is out of bounds var ErrIndexOutOfBounds = errors.New("index out of bounds") -// ErrNilProofsPool signals that a nil proofs pool was used -var ErrNilProofsPool = errors.New("nil proofs pool") +// ErrNilRoundHandler signals that an operation has been attempted to or with a nil RoundHandler implementation +var ErrNilRoundHandler = errors.New("nil RoundHandler") + +// ErrNilRewardsGetter signals that a nil rewards getter has been provided +var ErrNilRewardsGetter = errors.New("nil RewardsGetter") diff --git a/outport/process/factory/check.go b/outport/process/factory/check.go index 426bfe19f50..ef1b1655409 100644 --- a/outport/process/factory/check.go +++ b/outport/process/factory/check.go @@ -2,9 +2,11 @@ package factory import ( "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/outport/process" "github.com/multiversx/mx-chain-go/outport/process/alteredaccounts" "github.com/multiversx/mx-chain-go/outport/process/transactionsfee" + processS "github.com/multiversx/mx-chain-go/process" ) func checkArgOutportDataProviderFactory(arg ArgOutportDataProviderFactory) error { @@ -20,9 +22,6 @@ func checkArgOutportDataProviderFactory(arg ArgOutportDataProviderFactory) error if check.IfNil(arg.EsdtDataStorageHandler) { return alteredaccounts.ErrNilESDTDataStorageHandler } - if check.IfNil(arg.TransactionsStorer) { - return transactionsfee.ErrNilStorage - } if check.IfNil(arg.EconomicsData) { return transactionsfee.ErrNilTransactionFeeCalculator } @@ -41,17 +40,23 @@ func checkArgOutportDataProviderFactory(arg ArgOutportDataProviderFactory) error if check.IfNil(arg.Hasher) { return process.ErrNilHasher } - if check.IfNil(arg.MbsStorer) { - return process.ErrNilStorer - } if check.IfNil(arg.EnableEpochsHandler) { return process.ErrNilEnableEpochsHandler } if check.IfNil(arg.ExecutionOrderGetter) { return process.ErrNilExecutionOrderGetter } - if check.IfNil(arg.ProofsPool) { - return process.ErrNilProofsPool + if check.IfNil(arg.DataPool) { + return dataRetriever.ErrNilDataPoolHolder + } + if check.IfNil(arg.RoundHandler) { + return process.ErrNilRoundHandler + } + if check.IfNil(arg.RewardsGetter) { + return process.ErrNilRewardsGetter + } + if check.IfNil(arg.StorageService) { + return processS.ErrNilStorageService } return nil diff --git a/outport/process/factory/check_test.go b/outport/process/factory/check_test.go index 18406020573..31e7c88562a 100644 --- a/outport/process/factory/check_test.go +++ b/outport/process/factory/check_test.go @@ -3,9 +3,11 @@ package factory import ( "testing" + dataRetrieverReal "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/outport/process" "github.com/multiversx/mx-chain-go/outport/process/alteredaccounts" "github.com/multiversx/mx-chain-go/outport/process/transactionsfee" + proc "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/testscommon" commonMocks "github.com/multiversx/mx-chain-go/testscommon/common" "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" @@ -25,17 +27,18 @@ func createArgOutportDataProviderFactory() ArgOutportDataProviderFactory { AccountsDB: &state.AccountsStub{}, Marshaller: &marshallerMock.MarshalizerMock{}, EsdtDataStorageHandler: &testscommon.EsdtStorageHandlerStub{}, - TransactionsStorer: &genericMocks.StorerMock{}, ShardCoordinator: &testscommon.ShardsCoordinatorMock{}, TxCoordinator: &testscommon.TransactionCoordinatorMock{}, NodesCoordinator: &shardingMocks.NodesCoordinatorMock{}, GasConsumedProvider: &testscommon.GasHandlerStub{}, EconomicsData: &economicsmocks.EconomicsHandlerMock{}, Hasher: &testscommon.KeccakMock{}, - MbsStorer: &genericMocks.StorerMock{}, EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, ExecutionOrderGetter: &commonMocks.TxExecutionOrderHandlerStub{}, - ProofsPool: &dataRetriever.ProofsPoolMock{}, + DataPool: &dataRetriever.PoolsHolderMock{}, + RoundHandler: &testscommon.RoundHandlerMock{}, + RewardsGetter: &testscommon.RewardsCreatorStub{}, + StorageService: &genericMocks.ChainStorerMock{}, } } @@ -58,10 +61,6 @@ func TestCheckArgCreateOutportDataProvider(t *testing.T) { arg.EsdtDataStorageHandler = nil require.Equal(t, alteredaccounts.ErrNilESDTDataStorageHandler, checkArgOutportDataProviderFactory(arg)) - arg = createArgOutportDataProviderFactory() - arg.TransactionsStorer = nil - require.Equal(t, transactionsfee.ErrNilStorage, checkArgOutportDataProviderFactory(arg)) - arg = createArgOutportDataProviderFactory() arg.ShardCoordinator = nil require.Equal(t, transactionsfee.ErrNilShardCoordinator, checkArgOutportDataProviderFactory(arg)) @@ -87,8 +86,20 @@ func TestCheckArgCreateOutportDataProvider(t *testing.T) { require.Equal(t, process.ErrNilHasher, checkArgOutportDataProviderFactory(arg)) arg = createArgOutportDataProviderFactory() - arg.ProofsPool = nil - require.Equal(t, process.ErrNilProofsPool, checkArgOutportDataProviderFactory(arg)) + arg.DataPool = nil + require.Equal(t, dataRetrieverReal.ErrNilDataPoolHolder, checkArgOutportDataProviderFactory(arg)) + + arg = createArgOutportDataProviderFactory() + arg.RoundHandler = nil + require.Equal(t, process.ErrNilRoundHandler, checkArgOutportDataProviderFactory(arg)) + + arg = createArgOutportDataProviderFactory() + arg.RewardsGetter = nil + require.Equal(t, process.ErrNilRewardsGetter, checkArgOutportDataProviderFactory(arg)) + + arg = createArgOutportDataProviderFactory() + arg.StorageService = nil + require.Equal(t, proc.ErrNilStorageService, checkArgOutportDataProviderFactory(arg)) arg = createArgOutportDataProviderFactory() require.Nil(t, checkArgOutportDataProviderFactory(arg)) diff --git a/outport/process/factory/outportDataProviderFactory.go b/outport/process/factory/outportDataProviderFactory.go index c8523e2f9f0..5680c0c28a7 100644 --- a/outport/process/factory/outportDataProviderFactory.go +++ b/outport/process/factory/outportDataProviderFactory.go @@ -18,7 +18,6 @@ import ( "github.com/multiversx/mx-chain-go/sharding" "github.com/multiversx/mx-chain-go/sharding/nodesCoordinator" "github.com/multiversx/mx-chain-go/state" - "github.com/multiversx/mx-chain-go/storage" ) // ArgOutportDataProviderFactory holds the arguments needed for creating a new instance of outport.DataProviderOutport @@ -29,18 +28,19 @@ type ArgOutportDataProviderFactory struct { AccountsDB state.AccountsAdapter Marshaller marshal.Marshalizer EsdtDataStorageHandler vmcommon.ESDTNFTStorageHandler - TransactionsStorer storage.Storer ShardCoordinator sharding.Coordinator TxCoordinator processTxs.TransactionCoordinator NodesCoordinator nodesCoordinator.NodesCoordinator GasConsumedProvider process.GasConsumedProvider EconomicsData process.EconomicsDataHandler Hasher hashing.Hasher - MbsStorer storage.Storer EnableEpochsHandler common.EnableEpochsHandler ExecutionOrderGetter common.ExecutionOrderGetter - ProofsPool dataRetriever.ProofsPool + DataPool dataRetriever.PoolsHolder StateAccessesCollector state.StateAccessesCollector + RoundHandler process.RoundHandler + RewardsGetter process.EpochRewardsGetter + StorageService dataRetriever.StorageService } // CreateOutportDataProvider will create a new instance of outport.DataProviderOutport @@ -64,9 +64,14 @@ func CreateOutportDataProvider(arg ArgOutportDataProviderFactory) (outport.DataP return nil, err } + transactionsStorer, err := arg.StorageService.GetStorer(dataRetriever.TransactionUnit) + if err != nil { + return nil, err + } + transactionsFeeProc, err := transactionsfee.NewTransactionsFeeProcessor(transactionsfee.ArgTransactionsFeeProcessor{ Marshaller: arg.Marshaller, - TransactionsStorer: arg.TransactionsStorer, + TransactionsStorer: transactionsStorer, ShardCoordinator: arg.ShardCoordinator, TxFeeCalculator: arg.EconomicsData, PubKeyConverter: arg.AddressConverter, @@ -89,8 +94,11 @@ func CreateOutportDataProvider(arg ArgOutportDataProviderFactory) (outport.DataP ExecutionOrderHandler: arg.ExecutionOrderGetter, Hasher: arg.Hasher, Marshaller: arg.Marshaller, - ProofsPool: arg.ProofsPool, + DataPool: arg.DataPool, EnableEpochsHandler: arg.EnableEpochsHandler, StateAccessesCollector: arg.StateAccessesCollector, + RoundHandler: arg.RoundHandler, + RewardsGetter: arg.RewardsGetter, + StorageService: arg.StorageService, }) } diff --git a/outport/process/factory/outportDataProviderFactory_test.go b/outport/process/factory/outportDataProviderFactory_test.go index 644cc8917df..97f811d8292 100644 --- a/outport/process/factory/outportDataProviderFactory_test.go +++ b/outport/process/factory/outportDataProviderFactory_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/multiversx/mx-chain-go/outport/process/alteredaccounts" + "github.com/multiversx/mx-chain-go/testscommon/genericMocks" "github.com/stretchr/testify/require" ) @@ -36,6 +37,7 @@ func TestCreateOutportDataProvider(t *testing.T) { t.Parallel() arg := createArgOutportDataProviderFactory() + arg.StorageService = genericMocks.NewChainStorerMock(0) arg.HasDrivers = true provider, err := CreateOutportDataProvider(arg) diff --git a/outport/process/interface.go b/outport/process/interface.go index 0c8b332aa31..fde76ed2d2e 100644 --- a/outport/process/interface.go +++ b/outport/process/interface.go @@ -5,6 +5,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/alteredAccount" + "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/outport" "github.com/multiversx/mx-chain-go/outport/process/alteredaccounts/shared" ) @@ -30,6 +31,12 @@ type GasConsumedProvider interface { IsInterfaceNil() bool } +// RoundHandler defines the actions which should be handled by a round implementation +type RoundHandler interface { + GetTimeStampForRound(round uint64) uint64 + IsInterfaceNil() bool +} + // EconomicsDataHandler defines the functionality needed for economics data type EconomicsDataHandler interface { ComputeGasUnitsFromRefundValue(tx data.TransactionWithFeeHandler, refundValue *big.Int, epoch uint32) uint64 @@ -40,3 +47,9 @@ type EconomicsDataHandler interface { IsInterfaceNil() bool MaxGasLimitPerBlock(shardID uint32) uint64 } + +// EpochRewardsGetter defines what a rewards getter should be able to do +type EpochRewardsGetter interface { + GetRewardsTxs(body *block.Body) map[string]data.TransactionHandler + IsInterfaceNil() bool +} diff --git a/outport/process/outportDataProvider.go b/outport/process/outportDataProvider.go index c5b0e9a4662..4fcdb336092 100644 --- a/outport/process/outportDataProvider.go +++ b/outport/process/outportDataProvider.go @@ -1,9 +1,9 @@ package process import ( + "bytes" "encoding/hex" "fmt" - "math/big" "github.com/multiversx/mx-chain-core-go/core" @@ -14,9 +14,11 @@ import ( "github.com/multiversx/mx-chain-core-go/data/receipt" "github.com/multiversx/mx-chain-core-go/data/rewardTx" "github.com/multiversx/mx-chain-core-go/data/smartContractResult" + "github.com/multiversx/mx-chain-core-go/data/stateChange" "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/storage" logger "github.com/multiversx/mx-chain-logger-go" "github.com/multiversx/mx-chain-go/common" @@ -43,9 +45,12 @@ type ArgOutportDataProvider struct { Marshaller marshal.Marshalizer Hasher hashing.Hasher ExecutionOrderHandler common.ExecutionOrderGetter - ProofsPool dataRetriever.ProofsPool + DataPool dataRetriever.PoolsHolder EnableEpochsHandler common.EnableEpochsHandler StateAccessesCollector state.StateAccessesCollector + RoundHandler RoundHandler + RewardsGetter EpochRewardsGetter + StorageService dataRetriever.StorageService } // ArgPrepareOutportSaveBlockData holds the arguments needed for prepare outport save block data @@ -60,6 +65,7 @@ type ArgPrepareOutportSaveBlockData struct { NotarizedHeadersHashes []string HighestFinalBlockNonce uint64 HighestFinalBlockHash []byte + ScheduledRootHash []byte } type outportDataProvider struct { @@ -74,9 +80,12 @@ type outportDataProvider struct { executionOrderHandler common.ExecutionOrderGetter marshaller marshal.Marshalizer hasher hashing.Hasher - proofsPool dataRetriever.ProofsPool + dataPool dataRetriever.PoolsHolder enableEpochsHandler common.EnableEpochsHandler - StateAccessesCollector state.StateAccessesCollector + stateAccessesCollector state.StateAccessesCollector + roundHandler RoundHandler + rewardsGetter EpochRewardsGetter + storageService dataRetriever.StorageService } // NewOutportDataProvider will create a new instance of outportDataProvider @@ -93,9 +102,12 @@ func NewOutportDataProvider(arg ArgOutportDataProvider) (*outportDataProvider, e executionOrderHandler: arg.ExecutionOrderHandler, marshaller: arg.Marshaller, hasher: arg.Hasher, - proofsPool: arg.ProofsPool, + dataPool: arg.DataPool, enableEpochsHandler: arg.EnableEpochsHandler, - StateAccessesCollector: arg.StateAccessesCollector, + stateAccessesCollector: arg.StateAccessesCollector, + roundHandler: arg.RoundHandler, + rewardsGetter: arg.RewardsGetter, + storageService: arg.StorageService, }, nil } @@ -108,7 +120,7 @@ func (odp *outportDataProvider) PrepareOutportSaveBlockData(arg ArgPrepareOutpor return nil, ErrNilBodyHandler } - pool, err := odp.createPool(arg.RewardsTxs) + pool, err := odp.createPool(arg.RewardsTxs, arg.Header) if err != nil { return nil, err } @@ -118,16 +130,19 @@ func (odp *outportDataProvider) PrepareOutportSaveBlockData(arg ArgPrepareOutpor return nil, fmt.Errorf("transactionsFeeProcessor.PutFeeAndGasUsed %w", err) } - orderedTxHashes, foundTxHashes := odp.setExecutionOrderInTransactionPool(pool) + if !arg.Header.IsHeaderV3() { + items := odp.executionOrderHandler.GetItems() + orderedTxHashes, foundTxHashes := odp.setExecutionOrderInTransactionPool(pool, items) - executedTxs, err := collectExecutedTxHashes(arg.Body, arg.Header) - if err != nil { - log.Warn("PrepareOutportSaveBlockData - collectExecutedTxHashes", "error", err) - } + executedTxs, errC := collectExecutedTxHashes(arg.Body, arg.Header) + if errC != nil { + log.Warn("PrepareOutportSaveBlockData - collectExecutedTxHashes", "error", errC) + } - err = checkTxOrder(orderedTxHashes, executedTxs, foundTxHashes) - if err != nil { - log.Warn("PrepareOutportSaveBlockData - checkTxOrder", "error", err.Error()) + err = checkTxOrder(orderedTxHashes, executedTxs, foundTxHashes) + if err != nil { + log.Warn("PrepareOutportSaveBlockData - checkTxOrder", "error", err.Error()) + } } alteredAccounts, err := odp.alteredAccountsProvider.ExtractAlteredAccountsFromPool(pool, shared.AlteredAccountsOptions{ @@ -142,23 +157,24 @@ func (odp *outportDataProvider) PrepareOutportSaveBlockData(arg ArgPrepareOutpor return nil, err } - intraMiniBlocks, err := odp.getIntraShardMiniBlocks(arg.Body) + intraMiniBlocks, err := odp.getIntraShardMiniBlocks(arg.Body, arg.Header) + if err != nil { + return nil, err + } + + results, err := odp.prepareExecutionResultsData(arg) if err != nil { return nil, err } + stateAccessesForBlock, stateAccessesDeprecated := odp.getStateAccesses(arg.Header, arg.HeaderHash, arg.ScheduledRootHash) outportBlock := &outportcore.OutportBlockWithHeaderAndBody{ OutportBlock: &outportcore.OutportBlock{ - ShardID: odp.shardID, - BlockData: nil, // this will be filled with specific data for each driver - TransactionPool: pool, - HeaderGasConsumption: &outportcore.HeaderGasConsumption{ - GasProvided: odp.gasConsumedProvider.TotalGasProvidedWithScheduled(), - GasRefunded: odp.gasConsumedProvider.TotalGasRefunded(), - GasPenalized: odp.gasConsumedProvider.TotalGasPenalized(), - MaxGasPerBlock: odp.economicsData.MaxGasLimitPerBlock(odp.shardID), - }, - StateAccesses: odp.StateAccessesCollector.GetCollectedAccesses(), + ShardID: odp.shardID, + BlockData: nil, // this will be filled with specific data for each driver + TransactionPool: pool, + StateAccesses: stateAccessesDeprecated, + StateAccessesForBlock: stateAccessesForBlock, AlteredAccounts: alteredAccounts, NotarizedHeadersHashes: arg.NotarizedHeadersHashes, NumberOfShards: odp.numOfShards, @@ -174,13 +190,23 @@ func (odp *outportDataProvider) PrepareOutportSaveBlockData(arg ArgPrepareOutpor Header: arg.Header, HeaderHash: arg.HeaderHash, IntraShardMiniBlocks: intraMiniBlocks, + Results: results, }, } + if !arg.Header.IsHeaderV3() { + outportBlock.OutportBlock.HeaderGasConsumption = &outportcore.HeaderGasConsumption{ + GasProvided: odp.gasConsumedProvider.TotalGasProvidedWithScheduled(), + GasRefunded: odp.gasConsumedProvider.TotalGasRefunded(), + GasPenalized: odp.gasConsumedProvider.TotalGasPenalized(), + MaxGasPerBlock: odp.economicsData.MaxGasLimitPerBlock(odp.shardID), + } + } + if odp.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, arg.Header.GetEpoch()) { - headerProof, err := odp.proofsPool.GetProof(arg.Header.GetShardID(), arg.HeaderHash) - if err != nil { - return nil, err + headerProof, errG := odp.dataPool.Proofs().GetProof(arg.Header.GetShardID(), arg.HeaderHash) + if errG != nil { + return nil, errG } outportBlock.HeaderDataWithBody.HeaderProof = headerProof @@ -189,6 +215,169 @@ func (odp *outportDataProvider) PrepareOutportSaveBlockData(arg ArgPrepareOutpor return outportBlock, nil } +func (odp *outportDataProvider) getStateAccesses( + header data.HeaderHandler, + headerHash []byte, + scheduledRootHash []byte, +) (map[string]*outportcore.StateAccessesForBlock, map[string]*stateChange.StateAccesses) { + stateAccessesForBlock := make(map[string]*outportcore.StateAccessesForBlock) + if !header.IsHeaderV3() { + rootHash := header.GetRootHash() + if len(scheduledRootHash) != 0 && !bytes.Equal(rootHash, scheduledRootHash) { + rootHash = scheduledRootHash + } + + stateAccesses := odp.getStateAccessForRootHash(rootHash) + stateAccessesForBlock[hex.EncodeToString(headerHash)] = &outportcore.StateAccessesForBlock{StateAccesses: stateAccesses} + // for backward compatibility, in case some driver is still using StateAccesses instead of StateAccessesForBlock + return stateAccessesForBlock, stateAccesses + } + + executionResults := header.GetExecutionResultsHandlers() + for _, execResult := range executionResults { + stateAccesses := odp.getStateAccessForRootHash(execResult.GetRootHash()) + stateAccessesForBlock[hex.EncodeToString(execResult.GetHeaderHash())] = &outportcore.StateAccessesForBlock{StateAccesses: stateAccesses} + } + return stateAccessesForBlock, nil +} + +func (odp *outportDataProvider) getStateAccessForRootHash(rootHash []byte) map[string]*stateChange.StateAccesses { + stateAccessesMap := odp.stateAccessesCollector.GetStateAccessesForRootHash(rootHash) + if len(stateAccessesMap) == 0 { + return nil + } + odp.stateAccessesCollector.RemoveStateAccessesForRootHash(rootHash) + return stateAccessesMap +} + +func (odp *outportDataProvider) prepareExecutionResultsData(args ArgPrepareOutportSaveBlockData) (map[string]*outportcore.ExecutionResultData, error) { + results := make(map[string]*outportcore.ExecutionResultData) + if !args.Header.IsHeaderV3() { + return results, nil + } + + isMeta := odp.shardID == core.MetachainShardId + for _, executionResult := range args.Header.GetExecutionResultsHandlers() { + headerHash := executionResult.GetHeaderHash() + + body, err := common.GetCachedBody(odp.dataPool.ExecutedMiniBlocks(), odp.marshaller, executionResult) + if err != nil { + return nil, err + } + intraMbs, err := common.GetCachedMbs(odp.dataPool.ExecutedMiniBlocks(), odp.marshaller, headerHash) + if err != nil { + return nil, err + } + + cachedTxs, err := common.GetCachedIntermediateTxs(odp.dataPool.PostProcessTransactions(), headerHash) + if err != nil { + return nil, err + } + + odp.putInMapTxsFromBody(body, cachedTxs) + + if isMeta && hasRewardsOnBody(body) { + cachedTxs[block.RewardsBlock] = odp.rewardsGetter.GetRewardsTxs(body) + } + + cachedLogs, err := common.GetCachedLogs(odp.dataPool.PostProcessTransactions(), headerHash) + if err != nil { + return nil, err + } + pool, err := odp.createPoolWithWrappedTxs(cachedTxs, cachedLogs) + if err != nil { + return nil, err + } + + err = odp.addInPoolUnexecutableTransactions(headerHash, pool) + if err != nil { + return nil, fmt.Errorf("addInPoolUnexecutableTransactions: %w", err) + } + + err = odp.transactionsFeeProcessor.PutFeeAndGasUsed(pool, executionResult.GetHeaderEpoch()) + if err != nil { + return nil, fmt.Errorf("transactionsFeeProcessor.PutFeeAndGasUsed %w", err) + } + + orderedTxHashes, err := common.GetCachedOrderedTxHashes(odp.dataPool.PostProcessTransactions(), headerHash) + if err != nil { + return nil, err + } + _, _ = odp.setExecutionOrderInTransactionPool(pool, orderedTxHashes) + + alteredAccounts, err := odp.alteredAccountsProvider.ExtractAlteredAccountsFromPool(pool, shared.AlteredAccountsOptions{ + WithAdditionalOutportData: true, + }) + if err != nil { + return nil, fmt.Errorf("alteredAccountsProvider.ExtractAlteredAccountsFromPool %w", err) + } + + headerGasData, err := common.GetCacheHeaderGasData(odp.dataPool.PostProcessTransactions(), headerHash) + if err != nil { + return nil, err + } + + encodedHash := hex.EncodeToString(headerHash) + executionResultData := &outportcore.ExecutionResultData{ + HeaderNonce: executionResult.GetHeaderNonce(), + Body: body, + IntraShardMiniBlocks: intraMbs, + TransactionPool: pool, + AlteredAccounts: alteredAccounts, + TimestampMs: odp.roundHandler.GetTimeStampForRound(executionResult.GetHeaderRound()), + HeaderGasConsumption: headerGasData, + } + + results[encodedHash] = executionResultData + } + + return results, nil +} + +func (odp *outportDataProvider) addInPoolUnexecutableTransactions(headerHash []byte, pool *outportcore.TransactionPool) error { + unexecutableTxHashes, err := common.GetCachedUnexecutableTxHashes(odp.dataPool.PostProcessTransactions(), headerHash) + if err != nil { + return err + } + if len(unexecutableTxHashes) == 0 { + return nil + } + + cacheID := process.ShardCacherIdentifier(odp.shardID, odp.shardID) + txCache := odp.dataPool.Transactions().ShardDataStore(cacheID) + if check.IfNil(txCache) { + return nil + } + + pool.UnexecutableTransactions = make(map[string]*transaction.Transaction) + for _, txHash := range unexecutableTxHashes { + txI, found := txCache.Get(txHash) + if !found { + log.Warn("addInPoolUnexecutableTransactions - cannot find unexecutable tx in tx cache", "txHash", txHash) + continue + } + tx, ok := txI.(*transaction.Transaction) + if !ok { + log.Warn("addInPoolUnexecutableTransactions - cannot cast object from cache to transaction", "txHash", txHash, "type", fmt.Sprintf("%T", txI)) + continue + } + + pool.UnexecutableTransactions[hex.EncodeToString(txHash)] = tx + } + + return nil +} + +func hasRewardsOnBody(body *block.Body) bool { + for _, mb := range body.MiniBlocks { + if mb.Type == block.RewardsBlock { + return true + } + } + + return false +} + func collectExecutedTxHashes(bodyHandler data.BodyHandler, headerHandler data.HeaderHandler) (map[string]struct{}, error) { executedTxHashes := make(map[string]struct{}) mbHeaders := headerHandler.GetMiniBlockHeaderHandlers() @@ -243,8 +432,8 @@ func extractExecutedTxsFromMb(mbHeader data.MiniBlockHeaderHandler, miniBlock *b func (odp *outportDataProvider) setExecutionOrderInTransactionPool( pool *outportcore.TransactionPool, + orderedTxHashes [][]byte, ) ([][]byte, int) { - orderedTxHashes := odp.executionOrderHandler.GetItems() if pool == nil { return orderedTxHashes, 0 } @@ -354,72 +543,58 @@ func findLeaderIndex(blsKeys []string, leaderBlsKey string) uint64 { return 0 } -func (odp *outportDataProvider) createPool(rewardsTxs map[string]data.TransactionHandler) (*outportcore.TransactionPool, error) { - if odp.shardID == core.MetachainShardId { - return odp.createPoolForMeta(rewardsTxs) +func (odp *outportDataProvider) createPool(rewardsTxs map[string]data.TransactionHandler, header data.HeaderHandler) (*outportcore.TransactionPool, error) { + if header.IsHeaderV3() { + // transactions will be indexed after execution + return &outportcore.TransactionPool{}, nil } - return odp.createPoolForShard() -} - -func (odp *outportDataProvider) createPoolForShard() (*outportcore.TransactionPool, error) { - txs, err := getTxs(odp.txCoordinator.GetAllCurrentUsedTxs(block.TxBlock)) - if err != nil { - return nil, err + grouped := map[block.Type]map[string]data.TransactionHandler{ + block.TxBlock: odp.txCoordinator.GetAllCurrentUsedTxs(block.TxBlock), + block.SmartContractResultBlock: odp.txCoordinator.GetAllCurrentUsedTxs(block.SmartContractResultBlock), } - scrs, err := getScrs(odp.txCoordinator.GetAllCurrentUsedTxs(block.SmartContractResultBlock)) - if err != nil { - return nil, err - } + logs := odp.txCoordinator.GetAllCurrentLogs() - rewards, err := getRewards(odp.txCoordinator.GetAllCurrentUsedTxs(block.RewardsBlock)) - if err != nil { - return nil, err + if odp.shardID == core.MetachainShardId { + grouped[block.RewardsBlock] = rewardsTxs + return odp.createPoolWithWrappedTxs(grouped, logs) } - invalidTxs, err := getTxs(odp.txCoordinator.GetAllCurrentUsedTxs(block.InvalidBlock)) - if err != nil { - return nil, err - } + grouped[block.ReceiptBlock] = odp.txCoordinator.GetAllCurrentUsedTxs(block.ReceiptBlock) + grouped[block.InvalidBlock] = odp.txCoordinator.GetAllCurrentUsedTxs(block.InvalidBlock) + grouped[block.RewardsBlock] = odp.txCoordinator.GetAllCurrentUsedTxs(block.RewardsBlock) + + return odp.createPoolWithWrappedTxs(grouped, logs) +} - receipts, err := getReceipts(odp.txCoordinator.GetAllCurrentUsedTxs(block.ReceiptBlock)) +func (odp *outportDataProvider) createPoolWithWrappedTxs(groupedTxs map[block.Type]map[string]data.TransactionHandler, logsData []data.LogDataHandler) (*outportcore.TransactionPool, error) { + txs, err := getTxs(groupedTxs[block.TxBlock]) if err != nil { return nil, err } - logs, err := getLogs(odp.txCoordinator.GetAllCurrentLogs()) + scrs, err := getScrs(groupedTxs[block.SmartContractResultBlock]) if err != nil { return nil, err } - return &outportcore.TransactionPool{ - Transactions: txs, - SmartContractResults: scrs, - Rewards: rewards, - InvalidTxs: invalidTxs, - Receipts: receipts, - Logs: logs, - }, nil -} - -func (odp *outportDataProvider) createPoolForMeta(rewardsTxs map[string]data.TransactionHandler) (*outportcore.TransactionPool, error) { - txs, err := getTxs(odp.txCoordinator.GetAllCurrentUsedTxs(block.TxBlock)) + rewards, err := getRewards(groupedTxs[block.RewardsBlock]) if err != nil { return nil, err } - scrs, err := getScrs(odp.txCoordinator.GetAllCurrentUsedTxs(block.SmartContractResultBlock)) + invalidTxs, err := getTxs(groupedTxs[block.InvalidBlock]) if err != nil { return nil, err } - rewards, err := getRewards(rewardsTxs) + receipts, err := getReceipts(groupedTxs[block.ReceiptBlock]) if err != nil { return nil, err } - logs, err := getLogs(odp.txCoordinator.GetAllCurrentLogs()) + logs, err := getLogs(logsData) if err != nil { return nil, err } @@ -428,6 +603,8 @@ func (odp *outportDataProvider) createPoolForMeta(rewardsTxs map[string]data.Tra Transactions: txs, SmartContractResults: scrs, Rewards: rewards, + InvalidTxs: invalidTxs, + Receipts: receipts, Logs: logs, }, nil } @@ -517,17 +694,17 @@ func getReceipts(receipts map[string]data.TransactionHandler) (map[string]*recei return ret, nil } -func getLogs(logs []*data.LogData) ([]*outportcore.LogData, error) { - ret := make([]*outportcore.LogData, len(logs)) +func getLogs(logs []data.LogDataHandler) ([]*transaction.LogData, error) { + ret := make([]*transaction.LogData, len(logs)) for idx, logData := range logs { - txHashHex := getHexEncodedHash(logData.TxHash) - log, castOk := logData.LogHandler.(*transaction.Log) + txHashHex := getHexEncodedHash(logData.GetTxHash()) + log, castOk := logData.GetLogHandler().(*transaction.Log) if !castOk { return nil, fmt.Errorf("%w, hash: %s", errCannotCastLog, txHashHex) } - ret[idx] = &outportcore.LogData{ + ret[idx] = &transaction.LogData{ TxHash: txHashHex, Log: log, } @@ -540,7 +717,13 @@ func (odp *outportDataProvider) IsInterfaceNil() bool { return odp == nil } -func (odp *outportDataProvider) getIntraShardMiniBlocks(bodyHandler data.BodyHandler) ([]*block.MiniBlock, error) { +func (odp *outportDataProvider) getIntraShardMiniBlocks(bodyHandler data.BodyHandler, headerHandler data.HeaderHandler) ([]*block.MiniBlock, error) { + if headerHandler.IsHeaderV3() { + // skip intra-shard miniblocks. + // they are returned later when this block’s execution result is included in a future block. + return []*block.MiniBlock{}, nil + } + body, err := outportcore.GetBody(bodyHandler) if err != nil { return nil, err @@ -575,3 +758,106 @@ func (odp *outportDataProvider) filterOutDuplicatedMiniBlocks(miniBlocksFromBody return filteredMiniBlocks, nil } + +func (odp *outportDataProvider) putInMapTxsFromBody(body *block.Body, txs map[block.Type]map[string]data.TransactionHandler) { + for _, t := range []block.Type{block.TxBlock, block.SmartContractResultBlock, block.RewardsBlock} { + if txs[t] == nil { + txs[t] = make(map[string]data.TransactionHandler) + } + } + + for _, mb := range body.MiniBlocks { + isCrossSCRBlockFromMe := mb.Type == block.SmartContractResultBlock && mb.SenderShardID == odp.shardID + if isCrossSCRBlockFromMe { + continue + } + + storeByType, found := getDataStoreForType(odp.dataPool, mb.Type) + if !found { + continue + } + + strCache := process.ShardCacherIdentifier(mb.SenderShardID, mb.ReceiverShardID) + cache := storeByType.ShardDataStore(strCache) + if check.IfNil(cache) { + log.Debug("putInMapTxsFromBody cannot find shard data store", "senderShardID", mb.SenderShardID, "receiverShardID", mb.ReceiverShardID, "type", mb.Type) + continue + } + + for idx, txHash := range mb.TxHashes { + txI, err := odp.getTxFromCacheOrStorage(cache, txHash, mb.Type) + if err != nil { + log.Warn("putInMapTxsFromBody cannot find tx", "txHash", txHash, "idx", idx, "err", err) + continue + } + + txs[mb.Type][string(txHash)] = txI.(data.TransactionHandler) + } + } +} + +func (odp *outportDataProvider) getTxFromCacheOrStorage( + cacher storage.Cacher, + txHash []byte, + mbType block.Type, +) (interface{}, error) { + if tx, found := cacher.Get(txHash); found { + return tx, nil + } + + unit, err := process.GetStorageUnitByBlockType(mbType) + if err != nil { + return nil, err + } + + storer, err := odp.storageService.GetStorer(unit) + if err != nil { + return nil, err + } + + txBytes, err := storer.Get(txHash) + if err != nil { + return nil, err + } + + txObj, err := odp.createTxObject(mbType) + if err != nil { + return nil, err + } + + err = odp.marshaller.Unmarshal(txObj, txBytes) + if err != nil { + return nil, err + } + + return txObj, nil +} + +func (odp *outportDataProvider) createTxObject(mbType block.Type) (interface{}, error) { + switch mbType { + case block.SmartContractResultBlock: + return &smartContractResult.SmartContractResult{}, nil + case block.RewardsBlock: + return &rewardTx.RewardTx{}, nil + case block.TxBlock: + return &transaction.Transaction{}, nil + default: + return nil, common.ErrWrongTypeAssertion + } +} + +func getDataStoreForType( + dataPool dataRetriever.PoolsHolder, + mbType block.Type, +) (dataRetriever.ShardedDataCacherNotifier, bool) { + switch mbType { + case block.SmartContractResultBlock: + return dataPool.UnsignedTransactions(), true + case block.TxBlock: + return dataPool.Transactions(), true + case block.RewardsBlock: + return dataPool.RewardTransactions(), true + default: + return nil, false + } +} diff --git a/outport/process/outportDataProvider_test.go b/outport/process/outportDataProvider_test.go index ba561a9239e..3b28f145186 100644 --- a/outport/process/outportDataProvider_test.go +++ b/outport/process/outportDataProvider_test.go @@ -7,11 +7,20 @@ import ( "fmt" "testing" + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/alteredAccount" "github.com/multiversx/mx-chain-core-go/data/block" outportcore "github.com/multiversx/mx-chain-core-go/data/outport" "github.com/multiversx/mx-chain-core-go/data/rewardTx" "github.com/multiversx/mx-chain-core-go/data/smartContractResult" "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-go/common" + dr "github.com/multiversx/mx-chain-go/dataRetriever" + "github.com/multiversx/mx-chain-go/outport/process/alteredaccounts/shared" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/storage" + "github.com/multiversx/mx-chain-go/testscommon/cache" "github.com/stretchr/testify/require" "github.com/multiversx/mx-chain-go/outport/mock" @@ -25,6 +34,7 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" "github.com/multiversx/mx-chain-go/testscommon/state" + storageMocks "github.com/multiversx/mx-chain-go/testscommon/storage" ) func createArgOutportDataProvider() ArgOutportDataProvider { @@ -48,9 +58,10 @@ func createArgOutportDataProvider() ArgOutportDataProvider { ExecutionOrderHandler: &commonMocks.TxExecutionOrderHandlerStub{}, Marshaller: &marshallerMock.MarshalizerMock{}, Hasher: &hashingMocks.HasherMock{}, - ProofsPool: &dataRetriever.ProofsPoolMock{}, + DataPool: &dataRetriever.PoolsHolderMock{}, EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStubWithNoFlagsDefined(), StateAccessesCollector: &state.StateAccessesCollectorStub{}, + RoundHandler: &testscommon.RoundHandlerMock{}, } } @@ -261,6 +272,185 @@ func Test_extractExecutedTxsFromMb(t *testing.T) { }) } +func TestOutportDataProvider_getTxFromCacheOrStorage(t *testing.T) { + t.Parallel() + + t.Run("cache hit returns tx without storage", func(t *testing.T) { + txHash := []byte("tx-hash") + expectedTx := &transaction.Transaction{Nonce: 7} + cacher := cache.NewCacherMock() + cacher.Put(txHash, expectedTx, 0) + + arg := createArgOutportDataProvider() + arg.StorageService = &storageMocks.ChainStorerStub{ + GetStorerCalled: func(unitType dr.UnitType) (storage.Storer, error) { + t.Fatalf("unexpected GetStorer call for unit %v", unitType) + return nil, errors.New("unexpected") + }, + } + outportDataP, _ := NewOutportDataProvider(arg) + + res, err := outportDataP.getTxFromCacheOrStorage(cacher, txHash, block.TxBlock) + require.NoError(t, err) + require.Same(t, expectedTx, res) + }) + + t.Run("cache miss uses storage and unmarshals", func(t *testing.T) { + txHash := []byte("tx-hash") + expectedTx := &transaction.Transaction{Nonce: 42} + marshaller := &marshallerMock.MarshalizerMock{} + txBytes, err := marshaller.Marshal(expectedTx) + require.NoError(t, err) + + storer := &storageMocks.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + require.Equal(t, txHash, key) + return txBytes, nil + }, + } + arg := createArgOutportDataProvider() + arg.Marshaller = marshaller + arg.StorageService = &storageMocks.ChainStorerStub{ + GetStorerCalled: func(unitType dr.UnitType) (storage.Storer, error) { + require.Equal(t, dr.TransactionUnit, unitType) + return storer, nil + }, + } + outportDataP, _ := NewOutportDataProvider(arg) + + res, err := outportDataP.getTxFromCacheOrStorage(cache.NewCacherMock(), txHash, block.TxBlock) + require.NoError(t, err) + require.IsType(t, &transaction.Transaction{}, res) + require.Equal(t, expectedTx.Nonce, res.(*transaction.Transaction).Nonce) + }) + + t.Run("cache miss SCR uses storage and unmarshals", func(t *testing.T) { + txHash := []byte("scr-hash") + expectedTx := &smartContractResult.SmartContractResult{Nonce: 11} + marshaller := &marshallerMock.MarshalizerMock{} + txBytes, err := marshaller.Marshal(expectedTx) + require.NoError(t, err) + + storer := &storageMocks.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + require.Equal(t, txHash, key) + return txBytes, nil + }, + } + arg := createArgOutportDataProvider() + arg.Marshaller = marshaller + arg.StorageService = &storageMocks.ChainStorerStub{ + GetStorerCalled: func(unitType dr.UnitType) (storage.Storer, error) { + require.Equal(t, dr.UnsignedTransactionUnit, unitType) + return storer, nil + }, + } + outportDataP, _ := NewOutportDataProvider(arg) + + res, err := outportDataP.getTxFromCacheOrStorage(cache.NewCacherMock(), txHash, block.SmartContractResultBlock) + require.NoError(t, err) + require.IsType(t, &smartContractResult.SmartContractResult{}, res) + require.Equal(t, expectedTx.Nonce, res.(*smartContractResult.SmartContractResult).Nonce) + }) + + t.Run("cache miss rewards uses storage and unmarshals", func(t *testing.T) { + txHash := []byte("reward-hash") + expectedTx := &rewardTx.RewardTx{Round: 7} + marshaller := &marshallerMock.MarshalizerMock{} + txBytes, err := marshaller.Marshal(expectedTx) + require.NoError(t, err) + + storer := &storageMocks.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + require.Equal(t, txHash, key) + return txBytes, nil + }, + } + arg := createArgOutportDataProvider() + arg.Marshaller = marshaller + arg.StorageService = &storageMocks.ChainStorerStub{ + GetStorerCalled: func(unitType dr.UnitType) (storage.Storer, error) { + require.Equal(t, dr.RewardTransactionUnit, unitType) + return storer, nil + }, + } + outportDataP, _ := NewOutportDataProvider(arg) + + res, err := outportDataP.getTxFromCacheOrStorage(cache.NewCacherMock(), txHash, block.RewardsBlock) + require.NoError(t, err) + require.IsType(t, &rewardTx.RewardTx{}, res) + require.Equal(t, expectedTx.Round, res.(*rewardTx.RewardTx).Round) + }) + + t.Run("get storer error", func(t *testing.T) { + arg := createArgOutportDataProvider() + arg.StorageService = &storageMocks.ChainStorerStub{ + GetStorerCalled: func(unitType dr.UnitType) (storage.Storer, error) { + return nil, errors.New("get storer error") + }, + } + outportDataP, _ := NewOutportDataProvider(arg) + + _, err := outportDataP.getTxFromCacheOrStorage(cache.NewCacherMock(), []byte("tx-hash"), block.TxBlock) + require.Error(t, err) + }) + + t.Run("storer get error", func(t *testing.T) { + storer := &storageMocks.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + return nil, errors.New("get error") + }, + } + arg := createArgOutportDataProvider() + arg.StorageService = &storageMocks.ChainStorerStub{ + GetStorerCalled: func(unitType dr.UnitType) (storage.Storer, error) { + return storer, nil + }, + } + outportDataP, _ := NewOutportDataProvider(arg) + + _, err := outportDataP.getTxFromCacheOrStorage(cache.NewCacherMock(), []byte("tx-hash"), block.TxBlock) + require.Error(t, err) + }) + + t.Run("create tx object error for unsupported block type", func(t *testing.T) { + storer := &storageMocks.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + return []byte(`{"Nonce":1}`), nil + }, + } + arg := createArgOutportDataProvider() + arg.StorageService = &storageMocks.ChainStorerStub{ + GetStorerCalled: func(unitType dr.UnitType) (storage.Storer, error) { + return storer, nil + }, + } + outportDataP, _ := NewOutportDataProvider(arg) + + _, err := outportDataP.getTxFromCacheOrStorage(cache.NewCacherMock(), []byte("tx-hash"), block.PeerBlock) + require.Error(t, err) + }) + + t.Run("unmarshal error", func(t *testing.T) { + storer := &storageMocks.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + return []byte(`{"Nonce":1}`), nil + }, + } + arg := createArgOutportDataProvider() + arg.Marshaller = &marshallerMock.MarshalizerMock{Fail: true} + arg.StorageService = &storageMocks.ChainStorerStub{ + GetStorerCalled: func(unitType dr.UnitType) (storage.Storer, error) { + return storer, nil + }, + } + outportDataP, _ := NewOutportDataProvider(arg) + + _, err := outportDataP.getTxFromCacheOrStorage(cache.NewCacherMock(), []byte("tx-hash"), block.TxBlock) + require.Error(t, err) + }) +} + func Test_setExecutionOrderInTransactionPool(t *testing.T) { t.Parallel() @@ -274,7 +464,7 @@ func Test_setExecutionOrderInTransactionPool(t *testing.T) { }, } - orderedHashes, numProcessed := odp.setExecutionOrderInTransactionPool(nil) + orderedHashes, numProcessed := odp.setExecutionOrderInTransactionPool(nil, txHashes) require.Equal(t, 0, numProcessed) require.Equal(t, len(txHashes), len(orderedHashes)) for i := 0; i < len(txHashes); i++ { @@ -291,13 +481,8 @@ func Test_setExecutionOrderInTransactionPool(t *testing.T) { } txHashes := createRandTxHashes(10) - odp.executionOrderHandler = &commonMocks.TxExecutionOrderHandlerStub{ - GetItemsCalled: func() [][]byte { - return txHashes - }, - } - orderedHashes, numProcessed := odp.setExecutionOrderInTransactionPool(pool) + orderedHashes, numProcessed := odp.setExecutionOrderInTransactionPool(pool, txHashes) require.Equal(t, 0, numProcessed) require.Equal(t, len(txHashes), len(orderedHashes)) }) @@ -329,13 +514,8 @@ func Test_setExecutionOrderInTransactionPool(t *testing.T) { } txHashes := createRandTxHashes(10) - odp.executionOrderHandler = &commonMocks.TxExecutionOrderHandlerStub{ - GetItemsCalled: func() [][]byte { - return txHashes - }, - } - orderedHashes, numProcessed := odp.setExecutionOrderInTransactionPool(pool) + orderedHashes, numProcessed := odp.setExecutionOrderInTransactionPool(pool, txHashes) require.Equal(t, 0, numProcessed) require.Equal(t, len(txHashes), len(orderedHashes)) }) @@ -368,13 +548,7 @@ func Test_setExecutionOrderInTransactionPool(t *testing.T) { }, } - odp.executionOrderHandler = &commonMocks.TxExecutionOrderHandlerStub{ - GetItemsCalled: func() [][]byte { - return txHashes - }, - } - - orderedHashes, numProcessed := odp.setExecutionOrderInTransactionPool(pool) + orderedHashes, numProcessed := odp.setExecutionOrderInTransactionPool(pool, txHashes) require.Equal(t, 3, numProcessed) require.Equal(t, uint32(0), pool.Transactions[hex.EncodeToString(txHashes[0])].GetExecutionOrder()) require.Equal(t, uint32(1), pool.SmartContractResults[hex.EncodeToString(txHashes[1])].GetExecutionOrder()) @@ -409,13 +583,7 @@ func Test_setExecutionOrderInTransactionPool(t *testing.T) { }, } - odp.executionOrderHandler = &commonMocks.TxExecutionOrderHandlerStub{ - GetItemsCalled: func() [][]byte { - return txHashes - }, - } - - orderedHashes, numProcessed := odp.setExecutionOrderInTransactionPool(pool) + orderedHashes, numProcessed := odp.setExecutionOrderInTransactionPool(pool, txHashes) require.Equal(t, 3, numProcessed) require.Equal(t, uint32(7), pool.Transactions[hex.EncodeToString(txHashes[7])].GetExecutionOrder()) require.Equal(t, uint32(8), pool.SmartContractResults[hex.EncodeToString(txHashes[8])].GetExecutionOrder()) @@ -634,3 +802,554 @@ func createRandTxHashes(numTxHashes int) [][]byte { return txHashes } + +func TestPrepareExecutionResultsData(t *testing.T) { + t.Parallel() + + t.Run("should return no execution result data header v1", func(t *testing.T) { + t.Parallel() + + arg := createArgOutportDataProvider() + outportDataP, _ := NewOutportDataProvider(arg) + + results, err := outportDataP.prepareExecutionResultsData(ArgPrepareOutportSaveBlockData{ + Header: &block.Header{}, + }) + require.Nil(t, err) + require.Len(t, results, 0) + }) + + t.Run("should return no execution result data header v3 without execution results", func(t *testing.T) { + t.Parallel() + + arg := createArgOutportDataProvider() + outportDataP, _ := NewOutportDataProvider(arg) + + results, err := outportDataP.prepareExecutionResultsData(ArgPrepareOutportSaveBlockData{ + Header: &block.HeaderV3{}, + }) + require.Nil(t, err) + require.Len(t, results, 0) + }) + + t.Run("cannot get cached body should error", func(t *testing.T) { + t.Parallel() + + arg := createArgOutportDataProvider() + arg.DataPool = dataRetriever.NewPoolsHolderMock() + outportDataP, _ := NewOutportDataProvider(arg) + + results, err := outportDataP.prepareExecutionResultsData(ArgPrepareOutportSaveBlockData{ + Header: &block.HeaderV3{ + ExecutionResults: []*block.ExecutionResult{ + nil, + }, + }, + }) + require.Equal(t, common.ErrNilBaseExecutionResult, err) + require.Len(t, results, 0) + }) + + t.Run("cannot get cached intra shard mbs", func(t *testing.T) { + arg := createArgOutportDataProvider() + arg.DataPool = dataRetriever.NewPoolsHolderMock() + outportDataP, _ := NewOutportDataProvider(arg) + + results, err := outportDataP.prepareExecutionResultsData(ArgPrepareOutportSaveBlockData{ + Header: &block.HeaderV3{ + ExecutionResults: []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash"), + }, + }, + }, + }, + }) + require.True(t, errors.Is(err, common.ErrMissingMiniBlock)) + require.Len(t, results, 0) + }) + + t.Run("cannot get cached transactions", func(t *testing.T) { + arg := createArgOutportDataProvider() + arg.DataPool = dataRetriever.NewPoolsHolderMock() + + headerHash := []byte("hash") + intraMbs := make([]*block.MiniBlock, 0) + + arg.DataPool.ExecutedMiniBlocks().Put(headerHash, intraMbs, 0) + outportDataP, _ := NewOutportDataProvider(arg) + + results, err := outportDataP.prepareExecutionResultsData(ArgPrepareOutportSaveBlockData{ + Header: &block.HeaderV3{ + ExecutionResults: []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: headerHash, + }, + }, + }, + }, + }) + require.True(t, errors.Is(err, common.ErrMissingCachedTransactions)) + require.Len(t, results, 0) + }) + + t.Run("cannot get cached logs", func(t *testing.T) { + arg := createArgOutportDataProvider() + arg.DataPool = dataRetriever.NewPoolsHolderMock() + + headerHash := []byte("hash") + intraMbs := make([]*block.MiniBlock, 0) + arg.DataPool.ExecutedMiniBlocks().Put(headerHash, intraMbs, 0) + + cachedTxs := make(map[block.Type]map[string]data.TransactionHandler) + arg.DataPool.PostProcessTransactions().Put(headerHash, cachedTxs, 1) + + outportDataP, _ := NewOutportDataProvider(arg) + + results, err := outportDataP.prepareExecutionResultsData(ArgPrepareOutportSaveBlockData{ + Header: &block.HeaderV3{ + ExecutionResults: []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: headerHash, + }, + }, + }, + }, + }) + require.True(t, errors.Is(err, common.ErrMissingCachedLogs)) + require.Len(t, results, 0) + }) + + t.Run("cannot create pool", func(t *testing.T) { + arg := createArgOutportDataProvider() + arg.DataPool = dataRetriever.NewPoolsHolderMock() + + headerHash := []byte("hash") + intraMbs := make([]*block.MiniBlock, 0) + arg.DataPool.ExecutedMiniBlocks().Put(headerHash, intraMbs, 0) + + logsKey := common.PrepareLogEventsKey(headerHash) + logsSlice := make([]data.LogDataHandler, 0) + arg.DataPool.PostProcessTransactions().Put(logsKey, logsSlice, 0) + + cachedTxs := make(map[block.Type]map[string]data.TransactionHandler) + cachedTxs[block.TxBlock] = make(map[string]data.TransactionHandler) + // wrong type + cachedTxs[block.TxBlock]["hash"] = &smartContractResult.SmartContractResult{} + arg.DataPool.PostProcessTransactions().Put(headerHash, cachedTxs, 1) + + outportDataP, _ := NewOutportDataProvider(arg) + + results, err := outportDataP.prepareExecutionResultsData(ArgPrepareOutportSaveBlockData{ + Header: &block.HeaderV3{ + ExecutionResults: []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: headerHash, + }, + }, + }, + }, + }) + require.True(t, errors.Is(err, errCannotCastTransaction)) + require.Len(t, results, 0) + }) + + t.Run("cannot put gas used and fee", func(t *testing.T) { + arg := createArgOutportDataProvider() + localError := errors.New("local error") + arg.TransactionsFeeProcessor = &mock.TransactionsFeeHandlerMock{ + PutFeeAndGasUsedCalled: func(pool *outportcore.TransactionPool, epoch uint32) error { + return localError + }, + } + arg.DataPool = dataRetriever.NewPoolsHolderMock() + + headerHash := []byte("hash") + intraMbs := make([]*block.MiniBlock, 0) + arg.DataPool.ExecutedMiniBlocks().Put(headerHash, intraMbs, 0) + + logsKey := common.PrepareLogEventsKey(headerHash) + logsSlice := make([]data.LogDataHandler, 0) + arg.DataPool.PostProcessTransactions().Put(logsKey, logsSlice, 0) + arg.DataPool.PostProcessTransactions().Put(common.PrepareUnexecutableTxHashesKey(headerHash), make([][]byte, 0), 0) + + cachedTxs := make(map[block.Type]map[string]data.TransactionHandler) + cachedTxs[block.TxBlock] = make(map[string]data.TransactionHandler) + arg.DataPool.PostProcessTransactions().Put(headerHash, cachedTxs, 1) + + outportDataP, _ := NewOutportDataProvider(arg) + + results, err := outportDataP.prepareExecutionResultsData(ArgPrepareOutportSaveBlockData{ + Header: &block.HeaderV3{ + ExecutionResults: []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: headerHash, + }, + }, + }, + }, + }) + require.True(t, errors.Is(err, localError)) + require.Len(t, results, 0) + }) + + t.Run("cannot extract altered accounts", func(t *testing.T) { + arg := createArgOutportDataProvider() + localError := errors.New("local error") + arg.AlteredAccountsProvider = &testscommon.AlteredAccountsProviderStub{ + ExtractAlteredAccountsFromPoolCalled: func(txPool *outportcore.TransactionPool, options shared.AlteredAccountsOptions) (map[string]*alteredAccount.AlteredAccount, error) { + return nil, localError + }, + } + arg.DataPool = dataRetriever.NewPoolsHolderMock() + + headerHash := []byte("hash") + intraMbs := make([]*block.MiniBlock, 0) + arg.DataPool.ExecutedMiniBlocks().Put(headerHash, intraMbs, 0) + + logsKey := common.PrepareLogEventsKey(headerHash) + logsSlice := make([]data.LogDataHandler, 0) + arg.DataPool.PostProcessTransactions().Put(logsKey, logsSlice, 0) + + cachedTxs := make(map[block.Type]map[string]data.TransactionHandler) + cachedTxs[block.TxBlock] = make(map[string]data.TransactionHandler) + arg.DataPool.PostProcessTransactions().Put(headerHash, cachedTxs, 1) + arg.DataPool.PostProcessTransactions().Put(common.PrepareUnexecutableTxHashesKey(headerHash), make([][]byte, 0), 0) + + key := common.PrepareOrderedTxHashesKey(headerHash) + arg.DataPool.PostProcessTransactions().Put(key, [][]byte{[]byte("a")}, 1) + + outportDataP, _ := NewOutportDataProvider(arg) + + results, err := outportDataP.prepareExecutionResultsData(ArgPrepareOutportSaveBlockData{ + Header: &block.HeaderV3{ + ExecutionResults: []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: headerHash, + }, + }, + }, + }, + }) + require.True(t, errors.Is(err, localError)) + require.Len(t, results, 0) + }) + + t.Run("cannot get header gas data should err", func(t *testing.T) { + arg := createArgOutportDataProvider() + arg.DataPool = dataRetriever.NewPoolsHolderMock() + arg.AlteredAccountsProvider = &testscommon.AlteredAccountsProviderStub{ + ExtractAlteredAccountsFromPoolCalled: func(txPool *outportcore.TransactionPool, options shared.AlteredAccountsOptions) (map[string]*alteredAccount.AlteredAccount, error) { + return map[string]*alteredAccount.AlteredAccount{ + "s": {}, + }, nil + }, + } + + headerHash := []byte("hash") + intraMbs := make([]*block.MiniBlock, 0) + intraMbs = append(intraMbs, &block.MiniBlock{}) + arg.DataPool.ExecutedMiniBlocks().Put(headerHash, intraMbs, 0) + + logsKey := common.PrepareLogEventsKey(headerHash) + logsSlice := make([]data.LogDataHandler, 0) + arg.DataPool.PostProcessTransactions().Put(logsKey, logsSlice, 0) + arg.DataPool.PostProcessTransactions().Put(common.PrepareUnexecutableTxHashesKey(headerHash), make([][]byte, 0), 0) + + cachedTxs := make(map[block.Type]map[string]data.TransactionHandler) + cachedTxs[block.TxBlock] = make(map[string]data.TransactionHandler) + arg.DataPool.PostProcessTransactions().Put(headerHash, cachedTxs, 1) + + key := common.PrepareOrderedTxHashesKey(headerHash) + arg.DataPool.PostProcessTransactions().Put(key, [][]byte{[]byte("a")}, 1) + + outportDataP, _ := NewOutportDataProvider(arg) + + results, err := outportDataP.prepareExecutionResultsData(ArgPrepareOutportSaveBlockData{ + Header: &block.HeaderV3{ + ExecutionResults: []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: headerHash, + HeaderNonce: 10, + }, + }, + }, + }, + }) + require.True(t, errors.Is(err, common.ErrMissingHeaderGasData)) + require.Len(t, results, 0) + }) + + t.Run("should work", func(t *testing.T) { + arg := createArgOutportDataProvider() + arg.DataPool = dataRetriever.NewPoolsHolderMock() + arg.AlteredAccountsProvider = &testscommon.AlteredAccountsProviderStub{ + ExtractAlteredAccountsFromPoolCalled: func(txPool *outportcore.TransactionPool, options shared.AlteredAccountsOptions) (map[string]*alteredAccount.AlteredAccount, error) { + return map[string]*alteredAccount.AlteredAccount{ + "s": {}, + }, nil + }, + } + + headerHash := []byte("hash") + intraMbs := make([]*block.MiniBlock, 0) + intraMbs = append(intraMbs, &block.MiniBlock{}) + arg.DataPool.ExecutedMiniBlocks().Put(headerHash, intraMbs, 0) + + logsKey := common.PrepareLogEventsKey(headerHash) + logsSlice := make([]data.LogDataHandler, 0) + arg.DataPool.PostProcessTransactions().Put(logsKey, logsSlice, 0) + arg.DataPool.PostProcessTransactions().Put(common.PrepareUnexecutableTxHashesKey(headerHash), make([][]byte, 0), 0) + + cachedTxs := make(map[block.Type]map[string]data.TransactionHandler) + cachedTxs[block.TxBlock] = make(map[string]data.TransactionHandler) + arg.DataPool.PostProcessTransactions().Put(headerHash, cachedTxs, 1) + arg.DataPool.PostProcessTransactions().Put(common.PrepareHeaderGasDataKey(headerHash), &outportcore.HeaderGasConsumption{}, 0) + + key := common.PrepareOrderedTxHashesKey(headerHash) + arg.DataPool.PostProcessTransactions().Put(key, [][]byte{[]byte("a")}, 1) + + outportDataP, _ := NewOutportDataProvider(arg) + + results, err := outportDataP.prepareExecutionResultsData(ArgPrepareOutportSaveBlockData{ + Header: &block.HeaderV3{ + ExecutionResults: []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: headerHash, + HeaderNonce: 10, + }, + }, + }, + }, + }) + require.Nil(t, err) + require.Len(t, results, 1) + require.Len(t, results[hex.EncodeToString(headerHash)].IntraShardMiniBlocks, 1) + require.Len(t, results[hex.EncodeToString(headerHash)].AlteredAccounts, 1) + require.Equal(t, uint64(10), results[hex.EncodeToString(headerHash)].HeaderNonce) + }) +} + +func TestPutInMapTxsFromBody(t *testing.T) { + t.Parallel() + + arg := createArgOutportDataProvider() + + dataPool := dataRetriever.NewPoolsHolderMock() + txsMap := make(map[block.Type]map[string]data.TransactionHandler) + shardID := uint32(0) + + tx1H, tx2H, tx3H := []byte("tx1"), []byte("tx2"), []byte("tx3") + + tx1 := &transaction.Transaction{} + cacheID := process.ShardCacherIdentifier(0, 1) + dataPool.Transactions().AddData(tx1H, tx1, 1, cacheID) + + tx2 := &rewardTx.RewardTx{} + cacheID = process.ShardCacherIdentifier(core.MetachainShardId, 0) + dataPool.RewardTransactions().AddData(tx2H, tx2, 1, cacheID) + + tx3 := &smartContractResult.SmartContractResult{} + cacheID = process.ShardCacherIdentifier(2, 0) + dataPool.UnsignedTransactions().AddData(tx3H, tx3, 1, cacheID) + + arg.DataPool = dataPool + outportDataP, err := NewOutportDataProvider(arg) + require.Nil(t, err) + + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + SenderShardID: shardID, + ReceiverShardID: 2, + Type: block.SmartContractResultBlock, + // this will be ignored + }, + { + SenderShardID: shardID, + ReceiverShardID: 1, + Type: block.TxBlock, + TxHashes: [][]byte{tx1H}, + }, + { + SenderShardID: core.MetachainShardId, + ReceiverShardID: shardID, + TxHashes: [][]byte{tx2H}, + Type: block.RewardsBlock, + }, + { + SenderShardID: 2, + ReceiverShardID: shardID, + Type: block.SmartContractResultBlock, + TxHashes: [][]byte{tx3H}, + }, + }, + } + + outportDataP.putInMapTxsFromBody(body, txsMap) + + txPool, scrPool, rewardPool := txsMap[block.TxBlock], txsMap[block.SmartContractResultBlock], txsMap[block.RewardsBlock] + txFromPool := txPool[string(tx1H)].(*transaction.Transaction) + require.Equal(t, tx1, txFromPool) + + rewordFromPool := rewardPool[string(tx2H)].(*rewardTx.RewardTx) + require.Equal(t, tx2, rewordFromPool) + + scrFromPool := scrPool[string(tx3H)].(*smartContractResult.SmartContractResult) + require.Equal(t, tx3, scrFromPool) +} + +func TestOutportDataProvider_GetRewards(t *testing.T) { + arg := createArgOutportDataProvider() + arg.ShardCoordinator = &testscommon.ShardsCoordinatorMock{ + CurrentShard: common.MetachainShardId, + } + arg.DataPool = dataRetriever.NewPoolsHolderMock() + arg.AlteredAccountsProvider = &testscommon.AlteredAccountsProviderStub{ + ExtractAlteredAccountsFromPoolCalled: func(txPool *outportcore.TransactionPool, options shared.AlteredAccountsOptions) (map[string]*alteredAccount.AlteredAccount, error) { + return map[string]*alteredAccount.AlteredAccount{ + "s": {}, + }, nil + }} + + rewardsMbHash := []byte("rewardsMbHash") + rewardMb := &block.MiniBlock{ + Type: block.RewardsBlock, + } + called := false + + arg.RewardsGetter = &testscommon.RewardsCreatorStub{ + GetRewardsTxsCalled: func(body *block.Body) map[string]data.TransactionHandler { + called = true + require.Equal(t, &block.Body{MiniBlocks: []*block.MiniBlock{rewardMb}}, body) + return map[string]data.TransactionHandler{} + }, + } + + headerHash := []byte("hash") + intraMbs := make([]*block.MiniBlock, 0) + intraMbs = append(intraMbs, &block.MiniBlock{}) + arg.DataPool.ExecutedMiniBlocks().Put(headerHash, intraMbs, 0) + + rewardMbBytes, _ := arg.Marshaller.Marshal(rewardMb) + + arg.DataPool.ExecutedMiniBlocks().Put(rewardsMbHash, rewardMbBytes, 0) + + logsKey := common.PrepareLogEventsKey(headerHash) + logsSlice := make([]data.LogDataHandler, 0) + arg.DataPool.PostProcessTransactions().Put(logsKey, logsSlice, 0) + + cachedTxs := make(map[block.Type]map[string]data.TransactionHandler) + cachedTxs[block.TxBlock] = make(map[string]data.TransactionHandler) + arg.DataPool.PostProcessTransactions().Put(headerHash, cachedTxs, 1) + + key := common.PrepareOrderedTxHashesKey(headerHash) + arg.DataPool.PostProcessTransactions().Put(key, [][]byte{[]byte("a")}, 1) + arg.DataPool.PostProcessTransactions().Put(common.PrepareUnexecutableTxHashesKey(headerHash), make([][]byte, 0), 0) + arg.DataPool.PostProcessTransactions().Put(common.PrepareHeaderGasDataKey(headerHash), &outportcore.HeaderGasConsumption{}, 0) + + outportDataP, _ := NewOutportDataProvider(arg) + + results, err := outportDataP.prepareExecutionResultsData(ArgPrepareOutportSaveBlockData{ + Header: &block.MetaBlockV3{ + ExecutionResults: []*block.MetaExecutionResult{ + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: headerHash, + HeaderNonce: 10, + }, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: rewardsMbHash, + Type: block.RewardsBlock, + }, + }, + }, + }, + }, + }) + require.True(t, called) + require.Nil(t, err) + require.Len(t, results, 1) +} + +func TestAddInPoolUnexecutableTransactions(t *testing.T) { + t.Parallel() + + t.Run("cannot cache unexecutable tx hashes", func(t *testing.T) { + arg := createArgOutportDataProvider() + arg.DataPool = &dataRetriever.PoolsHolderStub{ + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + return []byte("a"), false + }, + } + }, + } + + outportDataP, _ := NewOutportDataProvider(arg) + + headerHash := []byte("hash") + + pool := &outportcore.TransactionPool{} + err := outportDataP.addInPoolUnexecutableTransactions(headerHash, pool) + require.ErrorIs(t, err, common.ErrMissingUnexecutableTxHash) + }) + + t.Run("no unexecutable txs should return nil", func(t *testing.T) { + arg := createArgOutportDataProvider() + arg.DataPool = &dataRetriever.PoolsHolderStub{ + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + return make([][]byte, 0), true + }, + } + }, + } + + outportDataP, _ := NewOutportDataProvider(arg) + + headerHash := []byte("hash") + + pool := &outportcore.TransactionPool{} + err := outportDataP.addInPoolUnexecutableTransactions(headerHash, pool) + require.NoError(t, err) + require.Nil(t, pool.UnexecutableTransactions) + }) + + t.Run("should work", func(t *testing.T) { + arg := createArgOutportDataProvider() + arg.DataPool = dataRetriever.NewPoolsHolderMock() + + headerHash := []byte("hash") + txHash1, txHash2, txHash3 := []byte("a"), []byte("b"), []byte("c") + tx := &transaction.Transaction{Nonce: 1} + + arg.DataPool.PostProcessTransactions().Put(common.PrepareUnexecutableTxHashesKey(headerHash), [][]byte{txHash1, txHash2, txHash3}, 1) + + cacheID := process.ShardCacherIdentifier(0, 0) + arg.DataPool.Transactions().AddData(txHash2, &smartContractResult.SmartContractResult{}, 1, cacheID) + arg.DataPool.Transactions().AddData(txHash3, tx, 1, cacheID) + + outportDataP, _ := NewOutportDataProvider(arg) + + pool := &outportcore.TransactionPool{} + err := outportDataP.addInPoolUnexecutableTransactions(headerHash, pool) + require.NoError(t, err) + require.Equal(t, 1, len(pool.UnexecutableTransactions)) + require.Equal(t, tx, pool.UnexecutableTransactions[hex.EncodeToString(txHash3)]) + }) + +} diff --git a/outport/process/transactionsfee/dataHolders.go b/outport/process/transactionsfee/dataHolders.go index ef2f1a3bba4..82e399f7ca1 100644 --- a/outport/process/transactionsfee/dataHolders.go +++ b/outport/process/transactionsfee/dataHolders.go @@ -5,6 +5,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data" outportcore "github.com/multiversx/mx-chain-core-go/data/outport" + "github.com/multiversx/mx-chain-core-go/data/transaction" ) type txHandlerWithFeeInfo interface { @@ -15,7 +16,7 @@ type txHandlerWithFeeInfo interface { type transactionWithResults struct { txHandlerWithFeeInfo scrs []txHandlerWithFeeInfo - log *data.LogData + log data.LogDataHandler } type transactionsAndScrsHolder struct { @@ -50,9 +51,9 @@ func prepareTransactionsAndScrs(txPool *outportcore.TransactionPool) *transactio continue } - txWithResults.log = &data.LogData{ - LogHandler: txLog.Log, - TxHash: txHash, + txWithResults.log = &transaction.LogData{ + Log: txLog.Log, + TxHash: txHash, } } diff --git a/outport/process/transactionsfee/dataHolders_test.go b/outport/process/transactionsfee/dataHolders_test.go index 00ddb466575..5a718b6e2e0 100644 --- a/outport/process/transactionsfee/dataHolders_test.go +++ b/outport/process/transactionsfee/dataHolders_test.go @@ -56,7 +56,7 @@ func TestTransactionsAndScrsHolder(t *testing.T) { }, }, }, - Logs: []*outportcore.LogData{ + Logs: []*transaction.LogData{ { Log: &transaction.Log{Address: []byte("addr")}, TxHash: hex.EncodeToString(txHash), diff --git a/outport/process/transactionsfee/interface.go b/outport/process/transactionsfee/interface.go index 551ee59d1e2..b78f8e20741 100644 --- a/outport/process/transactionsfee/interface.go +++ b/outport/process/transactionsfee/interface.go @@ -23,5 +23,5 @@ type transactionGetter interface { } type dataFieldParser interface { - Parse(dataField []byte, sender, receiver []byte, numOfShards uint32) *datafield.ResponseParseData + Parse(dataField []byte, sender, receiver []byte, numOfShards uint32, epoch uint32) *datafield.ResponseParseData } diff --git a/outport/process/transactionsfee/transactionChecker.go b/outport/process/transactionsfee/transactionChecker.go index fd56d0c202b..c1b2c5586a5 100644 --- a/outport/process/transactionsfee/transactionChecker.go +++ b/outport/process/transactionsfee/transactionChecker.go @@ -14,7 +14,7 @@ import ( ) func (tep *transactionsFeeProcessor) isESDTOperationWithSCCall(tx data.TransactionHandler) bool { - res := tep.dataFieldParser.Parse(tx.GetData(), tx.GetSndAddr(), tx.GetRcvAddr(), tep.shardCoordinator.NumberOfShards()) + res := tep.dataFieldParser.Parse(tx.GetData(), tx.GetSndAddr(), tx.GetRcvAddr(), tep.shardCoordinator.NumberOfShards(), tep.enableEpochsHandler.GetCurrentEpoch()) isESDTTransferOperation := res.Operation == core.BuiltInFunctionESDTTransfer || res.Operation == core.BuiltInFunctionESDTNFTTransfer || res.Operation == core.BuiltInFunctionMultiESDTNFTTransfer diff --git a/outport/process/transactionsfee/transactionsFeeProcessor.go b/outport/process/transactionsfee/transactionsFeeProcessor.go index 728d625cfa6..da7cecaea1c 100644 --- a/outport/process/transactionsfee/transactionsFeeProcessor.go +++ b/outport/process/transactionsfee/transactionsFeeProcessor.go @@ -51,8 +51,9 @@ func NewTransactionsFeeProcessor(arg ArgTransactionsFeeProcessor) (*transactions } parser, err := datafield.NewOperationDataFieldParser(&datafield.ArgsOperationDataFieldParser{ - AddressLength: arg.PubKeyConverter.Len(), - Marshalizer: arg.Marshaller, + AddressLength: arg.PubKeyConverter.Len(), + Marshalizer: arg.Marshaller, + RelayedTransactionsV1V2DisableEpoch: arg.EnableEpochsHandler.GetActivationEpoch(common.RelayedTransactionsV1V2DisableFlag), }) if err != nil { return nil, err @@ -169,6 +170,7 @@ func (tep *transactionsFeeProcessor) prepareTxWithResults( } if totalRefunds.Cmp(big.NewInt(0)) > 0 { + txWithResults.GetFeeInfo().SetHadRefund() tep.setGasUsedAndFeeBasedOnRefundValue(txWithResults, userTx, totalRefunds, epoch) } @@ -259,12 +261,12 @@ func (tep *transactionsFeeProcessor) prepareTxWithResultsBasedOnLogs( return } - res := tep.dataFieldParser.Parse(tx.GetData(), tx.GetSndAddr(), tx.GetRcvAddr(), tep.shardCoordinator.NumberOfShards()) + res := tep.dataFieldParser.Parse(tx.GetData(), tx.GetSndAddr(), tx.GetRcvAddr(), tep.shardCoordinator.NumberOfShards(), epoch) if check.IfNilReflect(txWithResults.log) || (res.Function == "" && res.Operation == datafield.OperationTransfer) { return } - for _, event := range txWithResults.log.GetLogEvents() { + for _, event := range txWithResults.log.GetLogHandler().GetLogEvents() { if core.WriteLogIdentifier == string(event.GetIdentifier()) && !hasRefund { tep.setGasUsedAndFeeBasedOnRefundValue(txWithResults, userTx, big.NewInt(0), epoch) continue @@ -283,8 +285,6 @@ func (tep *transactionsFeeProcessor) setGasUsedAndFeeBasedOnRefundValue( refund *big.Int, epoch uint32, ) { - txWithResults.GetFeeInfo().SetHadRefund() - isValidUserTxAfterBaseCostActivation := !check.IfNil(userTx) && tep.enableEpochsHandler.IsFlagEnabledInEpoch(common.FixRelayedBaseCostFlag, epoch) if isValidUserTxAfterBaseCostActivation && !common.IsValidRelayedTxV3(txWithResults.GetTxHandler()) { gasUsed, fee := tep.txFeeCalculator.ComputeGasUsedAndFeeBasedOnRefundValue(userTx, refund) diff --git a/outport/process/transactionsfee/transactionsFeeProcessor_test.go b/outport/process/transactionsfee/transactionsFeeProcessor_test.go index ad861cedd82..1c1a1dc7c67 100644 --- a/outport/process/transactionsfee/transactionsFeeProcessor_test.go +++ b/outport/process/transactionsfee/transactionsFeeProcessor_test.go @@ -2,7 +2,6 @@ package transactionsfee import ( "encoding/hex" - "github.com/multiversx/mx-chain-go/config" "math/big" "testing" @@ -16,11 +15,11 @@ import ( "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/economics" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" "github.com/multiversx/mx-chain-go/testscommon/genericMocks" "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" - logger "github.com/multiversx/mx-chain-logger-go" "github.com/stretchr/testify/require" ) @@ -28,10 +27,8 @@ var pubKeyConverter, _ = pubkeyConverter.NewBech32PubkeyConverter(32, "erd") func createEconomicsData(enableEpochsHandler common.EnableEpochsHandler) process.EconomicsDataHandler { economicsConfig := testscommon.GetEconomicsConfig() - cfg := &config.Config{EpochStartConfig: config.EpochStartConfig{RoundsPerEpoch: 14400}} - cfg.GeneralSettings.ChainParametersByEpoch = []config.ChainParametersByEpochConfig{{RoundDuration: 6000}} economicsData, _ := economics.NewEconomicsData(economics.ArgsNewEconomicsData{ - GeneralConfig: cfg, + ChainParamsHandler: &chainParameters.ChainParametersHolderMock{}, Economics: &economicsConfig, EnableEpochsHandler: enableEpochsHandler, TxVersionChecker: &testscommon.TxVersionCheckerStub{}, @@ -277,7 +274,7 @@ func TestPutFeeAndGasUsedLogWithErrorAndInformative(t *testing.T) { tx1Hash: tx1, tx2Hash: tx2, "t3": {Transaction: &transaction.Transaction{}, FeeInfo: &outportcore.FeeInfo{Fee: big.NewInt(0)}}}, - Logs: []*outportcore.LogData{ + Logs: []*transaction.LogData{ { Log: &transaction.Log{ Events: []*transaction.Event{ @@ -469,8 +466,6 @@ func silentDecodeAddress(address string) []byte { func TestPutFeeAndGasUsedScrWithRefundNoTx(t *testing.T) { t.Parallel() - _ = logger.SetLogLevel("*:TRACE") - txHash := []byte("relayedTx") scrWithRefund := []byte("scrWithRefund") @@ -499,6 +494,8 @@ func TestPutFeeAndGasUsedScrWithRefundNoTx(t *testing.T) { wasCalled := false txsFeeProc, err := NewTransactionsFeeProcessor(arg) + require.Nil(t, err) + txsFeeProc.log = &testscommon.LoggerStub{ TraceCalled: func(message string, args ...interface{}) { wasCalled = true @@ -644,7 +641,7 @@ func TestMoveBalanceWithSignalError(t *testing.T) { Transactions: map[string]*outportcore.TxInfo{ hex.EncodeToString(txHash): initialTx, }, - Logs: []*outportcore.LogData{ + Logs: []*transaction.LogData{ { Log: &transaction.Log{ Events: []*transaction.Event{ diff --git a/p2p/constants.go b/p2p/constants.go index 8a6db9caeb4..e1f43075ac8 100644 --- a/p2p/constants.go +++ b/p2p/constants.go @@ -30,3 +30,9 @@ const DefaultWithScaleResourceLimiter = p2p.DefaultWithScaleResourceLimiter // BroadcastMethod defines the broadcast method of the message type BroadcastMethod = p2p.BroadcastMethod + +// Broadcast defines a broadcast message +const Broadcast = p2p.Broadcast + +// Direct defines a direct message +const Direct = p2p.Direct diff --git a/process/aotSelection/aotSelector.go b/process/aotSelection/aotSelector.go new file mode 100644 index 00000000000..1724bcd33a6 --- /dev/null +++ b/process/aotSelection/aotSelector.go @@ -0,0 +1,498 @@ +package aotSelection + +import ( + "bytes" + "context" + "fmt" + "sync" + "time" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + logger "github.com/multiversx/mx-chain-logger-go" + + commonConsensus "github.com/multiversx/mx-chain-go/common/consensus" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/common/holders" + "github.com/multiversx/mx-chain-go/consensus" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/block/preprocess" + "github.com/multiversx/mx-chain-go/sharding" + "github.com/multiversx/mx-chain-go/sharding/nodesCoordinator" + "github.com/multiversx/mx-chain-go/state" + "github.com/multiversx/mx-chain-go/storage" + "github.com/multiversx/mx-chain-go/storage/cache" +) + +var log = logger.GetOrCreate("process/aotSelection") + +const ( + maxSelectionDuration = 500 * time.Millisecond + defaultSelectionTimeout = 150 * time.Millisecond + defaultMaxTxsPerBlock = 30000 + defaultLoopDurationCheckInterval = 100 + defaultCacheSize = 10 + // margin to account for txcache's interval-based timeout checking + selectionTimeoutMargin = 0.85 +) + +// AOTSelectorArgs holds the arguments needed to create an AOT selector +type AOTSelectorArgs struct { + NodesCoordinator nodesCoordinator.NodesCoordinator + ShardCoordinator sharding.Coordinator + KeysHandler consensus.KeysHandler + NodeRedundancy consensus.NodeRedundancyHandler + TxCache preprocess.TxCache + AccountsAdapter state.AccountsAdapter + TransactionProcessor process.TransactionProcessor + TxVersionChecker process.TxVersionCheckerHandler + BlockChain data.ChainHandler + EconomicsDataHandler process.EconomicsDataHandler + + // Configuration + SelectionTimeout time.Duration + CacheSize int + MaxTxsPerBlock int + LoopDurationCheckInterval int +} + +type aotSelector struct { + nodesCoordinator nodesCoordinator.NodesCoordinator + shardCoordinator sharding.Coordinator + keysHandler consensus.KeysHandler + nodeRedundancy consensus.NodeRedundancyHandler + txCache preprocess.TxCache + accountsAdapter state.AccountsAdapter + transactionProcessor process.TransactionProcessor + txVersionChecker process.TxVersionCheckerHandler + blockChain data.ChainHandler + economicsDataHandler process.EconomicsDataHandler + + cache storage.Cacher + selectionTimeout time.Duration + maxTxsPerBlock int + loopDurationCheckInterval int + + // Synchronization + selectionMut sync.Mutex + cancelFunc context.CancelFunc + ongoingNonce uint64 + resultChan chan *process.AOTSelectionResult + lastEpoch uint32 + closed bool +} + +// NewAOTSelector creates a new AOT selector instance +func NewAOTSelector(args AOTSelectorArgs) (*aotSelector, error) { + if check.IfNil(args.NodesCoordinator) { + return nil, process.ErrNilNodesCoordinator + } + if check.IfNil(args.ShardCoordinator) { + return nil, process.ErrNilShardCoordinator + } + if check.IfNil(args.KeysHandler) { + return nil, process.ErrNilKeysHandler + } + if check.IfNil(args.NodeRedundancy) { + return nil, process.ErrNilNodeRedundancyHandler + } + if check.IfNil(args.TxCache) { + return nil, process.ErrNilTxCache + } + if check.IfNil(args.AccountsAdapter) { + return nil, process.ErrNilAccountsAdapter + } + if check.IfNil(args.TransactionProcessor) { + return nil, process.ErrNilTxProcessor + } + if check.IfNil(args.TxVersionChecker) { + return nil, process.ErrNilTransactionVersionChecker + } + if check.IfNil(args.BlockChain) { + return nil, process.ErrNilBlockChain + } + if check.IfNil(args.EconomicsDataHandler) { + return nil, process.ErrNilEconomicsData + } + + selectionTimeout := args.SelectionTimeout + if selectionTimeout <= 0 || selectionTimeout > maxSelectionDuration { + log.Debug("NewAOTSelector: invalid selection timeout, using default", "provided", selectionTimeout, "default", defaultSelectionTimeout) + selectionTimeout = defaultSelectionTimeout + } + + cacheSize := args.CacheSize + if cacheSize <= 0 { + log.Debug("NewAOTSelector: invalid cache size, using default", "provided", cacheSize, "default", defaultCacheSize) + cacheSize = defaultCacheSize + } + + maxTxsPerBlock := args.MaxTxsPerBlock + if maxTxsPerBlock <= 0 { + log.Debug("NewAOTSelector: invalid maxTxsPerBlock, using default", "provided", maxTxsPerBlock, "default", defaultMaxTxsPerBlock) + maxTxsPerBlock = defaultMaxTxsPerBlock + } + + loopDurationCheckInterval := args.LoopDurationCheckInterval + if loopDurationCheckInterval <= 0 { + log.Debug("NewAOTSelector: invalid loopDurationCheckInterval, using default", "provided", loopDurationCheckInterval, "default", defaultLoopDurationCheckInterval) + loopDurationCheckInterval = defaultLoopDurationCheckInterval + } + + // Use existing LRU cache from storage/cache + lruCache, err := cache.NewLRUCache(cacheSize) + if err != nil { + return nil, err + } + + return &aotSelector{ + nodesCoordinator: args.NodesCoordinator, + shardCoordinator: args.ShardCoordinator, + keysHandler: args.KeysHandler, + nodeRedundancy: args.NodeRedundancy, + txCache: args.TxCache, + accountsAdapter: args.AccountsAdapter, + transactionProcessor: args.TransactionProcessor, + txVersionChecker: args.TxVersionChecker, + blockChain: args.BlockChain, + economicsDataHandler: args.EconomicsDataHandler, + cache: lruCache, + selectionTimeout: selectionTimeout, + maxTxsPerBlock: maxTxsPerBlock, + loopDurationCheckInterval: loopDurationCheckInterval, + }, nil +} + +// TriggerAOTSelection triggers AOT for the next block after commit +// Checks if node is leader in round N+1 OR N+2 (if N+1 fails) +func (s *aotSelector) TriggerAOTSelection(committedHeader data.HeaderHandler, currentRound uint64) { + if check.IfNil(committedHeader) { + log.Debug("TriggerAOTSelection: nil header, skipping") + return + } + + // Only process for shard nodes (metachain does not select transactions) + if s.shardCoordinator.SelfId() == core.MetachainShardId { + log.Trace("TriggerAOTSelection: metachain node, skipping") + return + } + + epoch := committedHeader.GetEpoch() + // Detect epoch change and invalidate cache + s.selectionMut.Lock() + if s.closed { + s.selectionMut.Unlock() + log.Debug("TriggerAOTSelection: selector is closed, skipping") + return + } + previousEpoch := s.lastEpoch + epochChanged := s.lastEpoch != epoch && s.lastEpoch != 0 + s.lastEpoch = epoch + if epochChanged { + log.Debug("TriggerAOTSelection: epoch changed, cleared cache and cancelled ongoing selection", + "previousEpoch", previousEpoch, + "newEpoch", epoch) + s.cancelOngoingSelectionNoLock() + s.cache.Clear() + } + s.selectionMut.Unlock() + + randomness := committedHeader.GetRandSeed() + nextNonce := committedHeader.GetNonce() + 1 + nextRound := currentRound + 1 + + // Check if we could be leader in either of the next two rounds + isLeaderNextRound := s.isSelfLeaderForRound(randomness, nextRound, epoch) + isLeaderRoundAfter := s.isSelfLeaderForRound(randomness, nextRound+1, epoch) + + if !isLeaderNextRound && !isLeaderRoundAfter { + log.Trace("TriggerAOTSelection: not a potential leader, skipping", + "nextRound", nextRound, + "nextNonce", nextNonce) + return + } + + log.Debug("TriggerAOTSelection: starting AOT selection", + "nextNonce", nextNonce, + "nextRound", nextRound, + "isLeaderNextRound", isLeaderNextRound, + "isLeaderRoundAfter", isLeaderRoundAfter) + + // Spawn goroutine for background selection + go s.runAOTSelection(nextNonce, randomness) +} + +// isSelfLeaderForRound checks if self is leader for a specific round using nodesCoordinator directly +func (s *aotSelector) isSelfLeaderForRound(randomness []byte, round uint64, epoch uint32) bool { + shardId := s.shardCoordinator.SelfId() + + // Use nodesCoordinator directly - no separate LeaderPredictor + leader, _, err := s.nodesCoordinator.ComputeConsensusGroup(randomness, round, shardId, epoch) + if err != nil { + log.Trace("isSelfLeaderForRound: ComputeConsensusGroup failed", "error", err) + return false + } + + // Check single key + selfPubKey := s.keysHandler.IsOriginalPublicKeyOfTheNode(leader.PubKey()) + isSelfLeader := selfPubKey && commonConsensus.ShouldConsiderSelfKeyInConsensus(s.nodeRedundancy) + + // Check multi-key + isMultiKeyLeader := s.keysHandler.IsKeyManagedByCurrentNode(leader.PubKey()) + + return isSelfLeader || isMultiKeyLeader +} + +// GetPreSelectedTransactions retrieves cached selection for the given nonce +// If selection is ongoing, waits for completion with timeout (we need the result anyway) +func (s *aotSelector) GetPreSelectedTransactions(blockNonce uint64) (*process.AOTSelectionResult, bool) { + s.selectionMut.Lock() + if s.closed { + s.selectionMut.Unlock() + return nil, false + } + if s.ongoingNonce == blockNonce && s.resultChan != nil { + resultChan := s.resultChan + timeout := s.selectionTimeout + s.selectionMut.Unlock() + + // Wait for ongoing selection to complete with timeout + select { + case result, ok := <-resultChan: + if ok && result != nil { + return result, true + } + // Channel closed or nil result - fall through to cache check + case <-time.After(timeout): + log.Debug("GetPreSelectedTransactions: timeout on channel, checking cache", + "blockNonce", blockNonce) + } + + // Fallback to cache (result may have been cached before channel issue) + return s.getFromCache(blockNonce) + } + s.selectionMut.Unlock() + + return s.getFromCache(blockNonce) +} + +func (s *aotSelector) getFromCache(blockNonce uint64) (*process.AOTSelectionResult, bool) { + cacheKey := []byte(fmt.Sprintf("%d", blockNonce)) + val, ok := s.cache.Get(cacheKey) + if !ok { + return nil, false + } + result, isResult := val.(*process.AOTSelectionResult) + if !isResult || result == nil { + return nil, false + } + return result, true +} + +// prepareAccountsForSelection ensures the accounts adapter is synced to the correct state +func (s *aotSelector) prepareAccountsForSelection() error { + prevHeader := s.blockChain.GetCurrentBlockHeader() + if prevHeader == nil { + log.Debug("prepareAccountsForSelection: no current header (genesis), skipping preparation") + return nil + } + + prevHeaderHash := s.blockChain.GetCurrentBlockHeaderHash() + + lastExecResHandler, err := common.GetOrCreateLastExecutionResultForPrevHeader(prevHeader, prevHeaderHash) + if err != nil { + return err + } + + rootHash := lastExecResHandler.GetRootHash() + rootHashHolder := holders.NewDefaultRootHashesHolder(rootHash) + + return s.accountsAdapter.RecreateTrieIfNeeded(rootHashHolder) +} + +// CancelOngoingSelection cancels any ongoing AOT selection +// Called before OnProposed/OnExecuted to avoid conflicts +func (s *aotSelector) CancelOngoingSelection() { + s.selectionMut.Lock() + defer s.selectionMut.Unlock() + + s.cancelOngoingSelectionNoLock() +} + +func (s *aotSelector) cancelOngoingSelectionNoLock() { + if s.closed { + return + } + + if s.cancelFunc != nil { + s.cancelFunc() + s.cancelFunc = nil + } +} + +func (s *aotSelector) cleanupSelectionState(resultChan chan *process.AOTSelectionResult) { + s.selectionMut.Lock() + // Only clean up if this is still the active selection + if s.resultChan == resultChan { + s.ongoingNonce = 0 + s.resultChan = nil + // Don't nil cancelFunc here - context cleanup is handled by defer cancelFunc() + } + s.selectionMut.Unlock() +} + +func haveTimeWithCancel(ctx context.Context, startTime time.Time, timeBudget time.Duration) func() bool { + return func() bool { + if time.Since(startTime) >= timeBudget { + return false + } + select { + case <-ctx.Done(): + return false + default: + return true + } + } +} + +// runAOTSelection performs the actual transaction selection in a background goroutine +func (s *aotSelector) runAOTSelection(targetNonce uint64, randomness []byte) { + s.selectionMut.Lock() + if s.closed { + s.selectionMut.Unlock() + log.Debug("runAOTSelection: selector is closed, aborting") + return + } + + // Cancel any previous ongoing selection before starting new one + if s.cancelFunc != nil { + s.cancelFunc() + } + + // Create new context for this selection - NO timeout here, timeout is handled + // separately via time.Since() to ensure consistent timing with selection start + ctx, cancelFunc := context.WithCancel(context.Background()) + s.cancelFunc = cancelFunc + s.resultChan = make(chan *process.AOTSelectionResult, 1) + s.ongoingNonce = targetNonce + resultChan := s.resultChan + s.selectionMut.Unlock() + + // Ensure context is always cancelled to release resources + defer cancelFunc() + defer s.cleanupSelectionState(resultChan) + + startTime := time.Now() + selectionBudget := time.Duration(float64(s.selectionTimeout) * selectionTimeoutMargin) + + log.Debug("runAOTSelection: starting", "targetNonce", targetNonce, "selectionBudget", selectionBudget) + + // Prepare accounts adapter with correct state before selection + err := s.prepareAccountsForSelection() + if err != nil { + log.Debug("runAOTSelection: failed to prepare accounts", "error", err) + s.sendResult(resultChan, nil) + return + } + + // Create selection session + session, err := preprocess.NewSelectionSession(preprocess.ArgsSelectionSession{ + AccountsAdapter: s.accountsAdapter, + TransactionsProcessor: s.transactionProcessor, + TxVersionCheckerHandler: s.txVersionChecker, + }) + if err != nil { + log.Debug("runAOTSelection: failed to create selection session", "error", err) + s.sendResult(resultChan, nil) + return + } + + // Get gas limit + maxGas := s.economicsDataHandler.MaxGasLimitPerBlock(s.shardCoordinator.SelfId()) + overestimationFactor := s.economicsDataHandler.BlockCapacityOverestimationFactor() + maxGas = maxGas * overestimationFactor / 100 + + options, err := holders.NewTxSelectionOptions( + maxGas, + s.maxTxsPerBlock, + s.loopDurationCheckInterval, + haveTimeWithCancel(ctx, startTime, selectionBudget), + ) + if err != nil { + log.Debug("runAOTSelection: failed to create selection options", "error", err) + s.sendResult(resultChan, nil) + return + } + + // select transactions from tx cache + wrappedTxs, accumulatedGas, err := s.txCache.SimulateSelectTransactions(session, options, targetNonce) + if err != nil { + log.Debug("runAOTSelection: selection failed", "error", err) + s.sendResult(resultChan, nil) + return + } + + // Check if cancelled (could have been cancelled during selection) + select { + case <-ctx.Done(): + log.Debug("runAOTSelection: cancelled", "targetNonce", targetNonce) + s.sendResult(resultChan, nil) + return + default: + } + + // Extract transaction hashes + txHashes := make([][]byte, len(wrappedTxs)) + for i, wrappedTx := range wrappedTxs { + txHashes[i] = bytes.Clone(wrappedTx.TxHash) + } + + result := &process.AOTSelectionResult{ + TxHashes: txHashes, + GasProvided: accumulatedGas, + PredictedBlockNonce: targetNonce, + Randomness: bytes.Clone(randomness), + SelectionTimestamp: time.Now(), + } + + // Store in cache + cacheKey := []byte(fmt.Sprintf("%d", targetNonce)) + s.cache.Put(cacheKey, result, 1) + + log.Info("runAOTSelection: completed", + "targetNonce", targetNonce, + "numTxs", len(txHashes), + "gasProvided", accumulatedGas, + "duration", time.Since(startTime)) + + s.sendResult(resultChan, result) +} + +// sendResult sends result and closes the channel to signal completion to all waiters +func (s *aotSelector) sendResult(resultChan chan *process.AOTSelectionResult, result *process.AOTSelectionResult) { + select { + case resultChan <- result: + default: + } + close(resultChan) +} + +// Close gracefully shuts down the AOT selector +// It cancels any ongoing selection and prevents new selections from starting +func (s *aotSelector) Close() error { + s.selectionMut.Lock() + defer s.selectionMut.Unlock() + + s.cancelOngoingSelectionNoLock() + s.closed = true + s.cache.Clear() + log.Debug("aotSelector: closed") + return nil +} + +// IsInterfaceNil returns true if there is no value under the interface +func (s *aotSelector) IsInterfaceNil() bool { + return s == nil +} diff --git a/process/aotSelection/aotSelector_test.go b/process/aotSelection/aotSelector_test.go new file mode 100644 index 00000000000..441feb7cea2 --- /dev/null +++ b/process/aotSelection/aotSelector_test.go @@ -0,0 +1,990 @@ +package aotSelection + +import ( + "context" + "errors" + "fmt" + "sync" + "testing" + "time" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/common" + consensusMock "github.com/multiversx/mx-chain-go/consensus/mock" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/mock" + "github.com/multiversx/mx-chain-go/sharding/nodesCoordinator" + "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" + "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" + stateMock "github.com/multiversx/mx-chain-go/testscommon/state" + "github.com/multiversx/mx-chain-go/testscommon/txcachestubs" + "github.com/multiversx/mx-chain-go/txcache" +) + +func createDefaultArgs() AOTSelectorArgs { + return AOTSelectorArgs{ + NodesCoordinator: &shardingMocks.NodesCoordinatorStub{}, + ShardCoordinator: &mock.ShardCoordinatorStub{ + SelfIdCalled: func() uint32 { return 0 }, + }, + KeysHandler: &testscommon.KeysHandlerStub{}, + NodeRedundancy: &consensusMock.NodeRedundancyHandlerStub{}, + TxCache: &txcachestubs.TxCacheStub{}, + AccountsAdapter: &stateMock.AccountsStub{}, + TransactionProcessor: &testscommon.TxProcessorStub{}, + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + BlockChain: &testscommon.ChainHandlerStub{}, + EconomicsDataHandler: &economicsmocks.EconomicsHandlerMock{ + BlockCapacityOverestimationFactorCalled: func() uint64 { + return 200 // 2x overestimation + }, + }, + SelectionTimeout: 100 * time.Millisecond, + CacheSize: 5, + MaxTxsPerBlock: 30000, + LoopDurationCheckInterval: 10, + } +} + +func createLeaderArgs(leaderPubKey []byte) AOTSelectorArgs { + args := createDefaultArgs() + args.NodesCoordinator = &shardingMocks.NodesCoordinatorStub{ + ComputeConsensusGroupCalled: func(_ []byte, _ uint64, _ uint32, _ uint32) (nodesCoordinator.Validator, []nodesCoordinator.Validator, error) { + return shardingMocks.NewValidatorMock(leaderPubKey, 1, 0), nil, nil + }, + } + args.KeysHandler = &testscommon.KeysHandlerStub{ + IsOriginalPublicKeyOfTheNodeCalled: func(_ []byte) bool { + return true + }, + IsKeyManagedByCurrentNodeCalled: func(_ []byte) bool { + return false + }, + } + args.NodeRedundancy = &consensusMock.NodeRedundancyHandlerStub{ + IsRedundancyNodeCalled: func() bool { return false }, + } + return args +} + +func createHeader(nonce uint64, randSeed []byte, epoch uint32) *testscommon.HeaderHandlerStub { + return &testscommon.HeaderHandlerStub{ + GetNonceCalled: func() uint64 { return nonce }, + GetRandSeedCalled: func() []byte { return randSeed }, + EpochField: epoch, + } +} + +func TestNewAOTSelector(t *testing.T) { + t.Parallel() + + t.Run("nil NodesCoordinator should error", func(t *testing.T) { + t.Parallel() + args := createDefaultArgs() + args.NodesCoordinator = nil + sel, err := NewAOTSelector(args) + require.Nil(t, sel) + require.Equal(t, process.ErrNilNodesCoordinator, err) + }) + + t.Run("nil ShardCoordinator should error", func(t *testing.T) { + t.Parallel() + args := createDefaultArgs() + args.ShardCoordinator = nil + sel, err := NewAOTSelector(args) + require.Nil(t, sel) + require.Equal(t, process.ErrNilShardCoordinator, err) + }) + + t.Run("nil KeysHandler should error", func(t *testing.T) { + t.Parallel() + args := createDefaultArgs() + args.KeysHandler = nil + sel, err := NewAOTSelector(args) + require.Nil(t, sel) + require.Equal(t, process.ErrNilKeysHandler, err) + }) + + t.Run("nil TxCache should error", func(t *testing.T) { + t.Parallel() + args := createDefaultArgs() + args.TxCache = nil + sel, err := NewAOTSelector(args) + require.Nil(t, sel) + require.Equal(t, process.ErrNilTxCache, err) + }) + + t.Run("nil AccountsAdapter should error", func(t *testing.T) { + t.Parallel() + args := createDefaultArgs() + args.AccountsAdapter = nil + sel, err := NewAOTSelector(args) + require.Nil(t, sel) + require.Equal(t, process.ErrNilAccountsAdapter, err) + }) + + t.Run("nil TransactionProcessor should error", func(t *testing.T) { + t.Parallel() + args := createDefaultArgs() + args.TransactionProcessor = nil + sel, err := NewAOTSelector(args) + require.Nil(t, sel) + require.Equal(t, process.ErrNilTxProcessor, err) + }) + + t.Run("nil TxVersionChecker should error", func(t *testing.T) { + t.Parallel() + args := createDefaultArgs() + args.TxVersionChecker = nil + sel, err := NewAOTSelector(args) + require.Nil(t, sel) + require.Equal(t, process.ErrNilTransactionVersionChecker, err) + }) + + t.Run("nil BlockChain should error", func(t *testing.T) { + t.Parallel() + args := createDefaultArgs() + args.BlockChain = nil + sel, err := NewAOTSelector(args) + require.Nil(t, sel) + require.Equal(t, process.ErrNilBlockChain, err) + }) + + t.Run("nil EconomicsDataHandler should error", func(t *testing.T) { + t.Parallel() + args := createDefaultArgs() + args.EconomicsDataHandler = nil + sel, err := NewAOTSelector(args) + require.Nil(t, sel) + require.Equal(t, process.ErrNilEconomicsData, err) + }) + + t.Run("nil NodeRedundancy should error", func(t *testing.T) { + t.Parallel() + args := createDefaultArgs() + args.NodeRedundancy = nil + sel, err := NewAOTSelector(args) + require.Equal(t, process.ErrNilNodeRedundancyHandler, err) + require.Nil(t, sel) + }) + + t.Run("should use defaults for zero config values", func(t *testing.T) { + t.Parallel() + args := createDefaultArgs() + args.SelectionTimeout = 0 + args.CacheSize = 0 + args.MaxTxsPerBlock = 0 + args.LoopDurationCheckInterval = 0 + sel, err := NewAOTSelector(args) + require.NoError(t, err) + require.NotNil(t, sel) + require.Equal(t, defaultSelectionTimeout, sel.selectionTimeout) + require.Equal(t, defaultMaxTxsPerBlock, sel.maxTxsPerBlock) + require.Equal(t, defaultLoopDurationCheckInterval, sel.loopDurationCheckInterval) + }) + + t.Run("should work with all valid args", func(t *testing.T) { + t.Parallel() + args := createDefaultArgs() + sel, err := NewAOTSelector(args) + require.NoError(t, err) + require.NotNil(t, sel) + require.Equal(t, 100*time.Millisecond, sel.selectionTimeout) + require.Equal(t, 30000, sel.maxTxsPerBlock) + require.Equal(t, 10, sel.loopDurationCheckInterval) + }) +} + +func TestAOTSelector_TriggerAOTSelectionNilHeader(t *testing.T) { + t.Parallel() + + args := createDefaultArgs() + sel, _ := NewAOTSelector(args) + require.NotPanics(t, func() { + sel.TriggerAOTSelection(nil, 10) + }) +} + +func TestAOTSelector_TriggerAOTSelectionMetachainSkips(t *testing.T) { + t.Parallel() + + selectionStarted := false + args := createDefaultArgs() + args.ShardCoordinator = &mock.ShardCoordinatorStub{ + SelfIdCalled: func() uint32 { return core.MetachainShardId }, + } + args.TxCache = &txcachestubs.TxCacheStub{ + SimulateSelectTransactionsCalled: func(_ txcache.SelectionSession, _ common.TxSelectionOptions, _ uint64) ([]*txcache.WrappedTransaction, uint64, error) { + selectionStarted = true + return nil, 0, nil + }, + } + sel, _ := NewAOTSelector(args) + + sel.TriggerAOTSelection(createHeader(10, []byte("randomness"), 1), 100) + time.Sleep(50 * time.Millisecond) + require.False(t, selectionStarted) +} + +func TestAOTSelector_TriggerAOTSelectionNotLeaderSkips(t *testing.T) { + t.Parallel() + + selectionStarted := false + args := createDefaultArgs() + args.NodesCoordinator = &shardingMocks.NodesCoordinatorStub{ + ComputeConsensusGroupCalled: func(_ []byte, _ uint64, _ uint32, _ uint32) (nodesCoordinator.Validator, []nodesCoordinator.Validator, error) { + return shardingMocks.NewValidatorMock([]byte("other-leader-key"), 1, 0), nil, nil + }, + } + args.KeysHandler = &testscommon.KeysHandlerStub{ + IsOriginalPublicKeyOfTheNodeCalled: func(_ []byte) bool { + return false + }, + IsKeyManagedByCurrentNodeCalled: func(_ []byte) bool { + return false + }, + } + args.TxCache = &txcachestubs.TxCacheStub{ + SimulateSelectTransactionsCalled: func(_ txcache.SelectionSession, _ common.TxSelectionOptions, _ uint64) ([]*txcache.WrappedTransaction, uint64, error) { + selectionStarted = true + return nil, 0, nil + }, + } + sel, _ := NewAOTSelector(args) + + sel.TriggerAOTSelection(createHeader(10, []byte("randomness"), 1), 100) + time.Sleep(50 * time.Millisecond) + require.False(t, selectionStarted) +} + +func TestAOTSelector_TriggerAOTSelectionLeaderNextRound(t *testing.T) { + t.Parallel() + + selectionDone := make(chan struct{}) + args := createLeaderArgs([]byte("self-leader-key")) + args.TxCache = &txcachestubs.TxCacheStub{ + SimulateSelectTransactionsCalled: func(_ txcache.SelectionSession, _ common.TxSelectionOptions, _ uint64) ([]*txcache.WrappedTransaction, uint64, error) { + close(selectionDone) + return []*txcache.WrappedTransaction{ + {TxHash: []byte("tx1")}, + {TxHash: []byte("tx2")}, + }, 50000, nil + }, + } + sel, _ := NewAOTSelector(args) + + sel.TriggerAOTSelection(createHeader(10, []byte("randomness"), 1), 100) + + select { + case <-selectionDone: + case <-time.After(time.Second): + t.Fatal("selection goroutine was not started") + } +} + +func TestAOTSelector_TriggerAOTSelectionLeaderRoundN2Only(t *testing.T) { + t.Parallel() + + selectionDone := make(chan struct{}) + leaderPubKey := []byte("self-leader-key") + otherPubKey := []byte("other-leader-key") + + args := createDefaultArgs() + args.NodesCoordinator = &shardingMocks.NodesCoordinatorStub{ + ComputeConsensusGroupCalled: func(_ []byte, round uint64, _ uint32, _ uint32) (nodesCoordinator.Validator, []nodesCoordinator.Validator, error) { + // round 101 (N+1): other leader; round 102 (N+2): self leader + if round == 101 { + return shardingMocks.NewValidatorMock(otherPubKey, 1, 0), nil, nil + } + return shardingMocks.NewValidatorMock(leaderPubKey, 1, 0), nil, nil + }, + } + args.KeysHandler = &testscommon.KeysHandlerStub{ + IsOriginalPublicKeyOfTheNodeCalled: func(pk []byte) bool { + return string(pk) == string(leaderPubKey) + }, + IsKeyManagedByCurrentNodeCalled: func(_ []byte) bool { + return false + }, + } + args.NodeRedundancy = &consensusMock.NodeRedundancyHandlerStub{ + IsRedundancyNodeCalled: func() bool { return false }, + } + args.TxCache = &txcachestubs.TxCacheStub{ + SimulateSelectTransactionsCalled: func(_ txcache.SelectionSession, _ common.TxSelectionOptions, _ uint64) ([]*txcache.WrappedTransaction, uint64, error) { + close(selectionDone) + return nil, 0, nil + }, + } + sel, _ := NewAOTSelector(args) + + sel.TriggerAOTSelection(createHeader(10, []byte("randomness"), 1), 100) + + select { + case <-selectionDone: + case <-time.After(time.Second): + t.Fatal("selection goroutine was not started for N+2 leader") + } +} + +func TestAOTSelector_IsSelfLeaderForRoundComputeError(t *testing.T) { + t.Parallel() + + args := createDefaultArgs() + args.NodesCoordinator = &shardingMocks.NodesCoordinatorStub{ + ComputeConsensusGroupCalled: func(_ []byte, _ uint64, _ uint32, _ uint32) (nodesCoordinator.Validator, []nodesCoordinator.Validator, error) { + return nil, nil, errors.New("compute error") + }, + } + sel, _ := NewAOTSelector(args) + require.False(t, sel.isSelfLeaderForRound([]byte("rand"), 10, 1)) +} + +func TestAOTSelector_IsSelfLeaderForRoundSingleKeyMatch(t *testing.T) { + t.Parallel() + + args := createLeaderArgs([]byte("my-key")) + sel, _ := NewAOTSelector(args) + require.True(t, sel.isSelfLeaderForRound([]byte("rand"), 10, 1)) +} + +func TestAOTSelector_IsSelfLeaderForRoundMultiKeyMatch(t *testing.T) { + t.Parallel() + + leaderPubKey := []byte("managed-key") + args := createDefaultArgs() + args.NodesCoordinator = &shardingMocks.NodesCoordinatorStub{ + ComputeConsensusGroupCalled: func(_ []byte, _ uint64, _ uint32, _ uint32) (nodesCoordinator.Validator, []nodesCoordinator.Validator, error) { + return shardingMocks.NewValidatorMock(leaderPubKey, 1, 0), nil, nil + }, + } + args.KeysHandler = &testscommon.KeysHandlerStub{ + IsOriginalPublicKeyOfTheNodeCalled: func(_ []byte) bool { + return false + }, + IsKeyManagedByCurrentNodeCalled: func(_ []byte) bool { + return true // but is managed multi-key + }, + } + sel, _ := NewAOTSelector(args) + require.True(t, sel.isSelfLeaderForRound([]byte("rand"), 10, 1)) +} + +func TestAOTSelector_IsSelfLeaderForRoundNoKeyMatch(t *testing.T) { + t.Parallel() + + args := createDefaultArgs() + args.NodesCoordinator = &shardingMocks.NodesCoordinatorStub{ + ComputeConsensusGroupCalled: func(_ []byte, _ uint64, _ uint32, _ uint32) (nodesCoordinator.Validator, []nodesCoordinator.Validator, error) { + return shardingMocks.NewValidatorMock([]byte("other-key"), 1, 0), nil, nil + }, + } + args.KeysHandler = &testscommon.KeysHandlerStub{ + IsOriginalPublicKeyOfTheNodeCalled: func(_ []byte) bool { + return false + }, + IsKeyManagedByCurrentNodeCalled: func(_ []byte) bool { + return false + }, + } + sel, _ := NewAOTSelector(args) + require.False(t, sel.isSelfLeaderForRound([]byte("rand"), 10, 1)) +} + +func TestAOTSelector_GetPreSelectedTransactionsCacheMiss(t *testing.T) { + t.Parallel() + + args := createDefaultArgs() + sel, _ := NewAOTSelector(args) + result, found := sel.GetPreSelectedTransactions(42) + require.Nil(t, result) + require.False(t, found) +} + +func TestAOTSelector_GetPreSelectedTransactionsCacheHit(t *testing.T) { + t.Parallel() + + args := createDefaultArgs() + sel, _ := NewAOTSelector(args) + + expected := &process.AOTSelectionResult{ + TxHashes: [][]byte{[]byte("tx1"), []byte("tx2")}, + GasProvided: 50000, + PredictedBlockNonce: 42, + Randomness: []byte("rand"), + SelectionTimestamp: time.Now(), + } + cacheKey := []byte(fmt.Sprintf("%d", 42)) + sel.cache.Put(cacheKey, expected, 1) + + result, found := sel.GetPreSelectedTransactions(42) + require.True(t, found) + require.NotNil(t, result) + require.Equal(t, expected.TxHashes, result.TxHashes) + require.Equal(t, expected.GasProvided, result.GasProvided) + require.Equal(t, uint64(42), result.PredictedBlockNonce) +} + +func TestAOTSelector_GetPreSelectedTransactionsWaitsForOngoing(t *testing.T) { + t.Parallel() + + args := createDefaultArgs() + sel, _ := NewAOTSelector(args) + + expected := &process.AOTSelectionResult{ + TxHashes: [][]byte{[]byte("tx1")}, + GasProvided: 1000, + PredictedBlockNonce: 55, + } + + // Simulate an ongoing selection + sel.selectionMut.Lock() + sel.ongoingNonce = 55 + sel.resultChan = make(chan *process.AOTSelectionResult, 1) + resultChan := sel.resultChan + sel.selectionMut.Unlock() + + var gotResult *process.AOTSelectionResult + var gotFound bool + done := make(chan struct{}) + go func() { + gotResult, gotFound = sel.GetPreSelectedTransactions(55) + close(done) + }() + + // Give the goroutine time to start waiting + time.Sleep(20 * time.Millisecond) + + // Deliver the result + resultChan <- expected + + select { + case <-done: + require.True(t, gotFound) + require.NotNil(t, gotResult) + require.Equal(t, expected.TxHashes, gotResult.TxHashes) + case <-time.After(time.Second): + t.Fatal("GetPreSelectedTransactions did not return after result was delivered") + } +} + +func TestAOTSelector_GetPreSelectedTransactionsOngoingForDifferentNonce(t *testing.T) { + t.Parallel() + + args := createDefaultArgs() + sel, _ := NewAOTSelector(args) + + // Setup ongoing selection for nonce 55 + sel.selectionMut.Lock() + sel.ongoingNonce = 55 + sel.resultChan = make(chan *process.AOTSelectionResult, 1) + sel.selectionMut.Unlock() + + // Query for different nonce should go to cache (miss) + result, found := sel.GetPreSelectedTransactions(99) + require.Nil(t, result) + require.False(t, found) +} + +func TestAOTSelector_CancelOngoingSelectionNoOp(t *testing.T) { + t.Parallel() + + args := createDefaultArgs() + sel, _ := NewAOTSelector(args) + require.NotPanics(t, func() { + sel.CancelOngoingSelection() + }) +} + +func TestAOTSelector_CancelOngoingSelectionCancelsContext(t *testing.T) { + t.Parallel() + + args := createDefaultArgs() + sel, _ := NewAOTSelector(args) + + // Create a context and set it as the cancelFunc + ctx, cancelFunc := context.WithCancel(context.Background()) + sel.selectionMut.Lock() + sel.cancelFunc = cancelFunc + sel.selectionMut.Unlock() + + sel.CancelOngoingSelection() + + select { + case <-ctx.Done(): + // context cancelled as expected + default: + t.Fatal("context was not cancelled") + } + + // Second call should not panic (cancelFunc is nil now) + require.NotPanics(t, func() { + sel.CancelOngoingSelection() + }) +} + +func TestAOTSelector_RunAOTSelectionSuccess(t *testing.T) { + t.Parallel() + + args := createDefaultArgs() + args.TxCache = &txcachestubs.TxCacheStub{ + SimulateSelectTransactionsCalled: func(_ txcache.SelectionSession, _ common.TxSelectionOptions, _ uint64) ([]*txcache.WrappedTransaction, uint64, error) { + return []*txcache.WrappedTransaction{ + {TxHash: []byte("hash1")}, + {TxHash: []byte("hash2")}, + {TxHash: []byte("hash3")}, + }, 75000, nil + }, + } + sel, _ := NewAOTSelector(args) + + sel.runAOTSelection(0, []byte("randomness")) + + result, found := sel.GetPreSelectedTransactions(0) + require.True(t, found) + require.NotNil(t, result) + require.Equal(t, 3, len(result.TxHashes)) + require.Equal(t, []byte("hash1"), result.TxHashes[0]) + require.Equal(t, []byte("hash2"), result.TxHashes[1]) + require.Equal(t, []byte("hash3"), result.TxHashes[2]) + require.Equal(t, uint64(75000), result.GasProvided) + require.Equal(t, uint64(0), result.PredictedBlockNonce) + require.Equal(t, []byte("randomness"), result.Randomness) +} + +func TestAOTSelector_RunAOTSelectionError(t *testing.T) { + t.Parallel() + + args := createDefaultArgs() + args.TxCache = &txcachestubs.TxCacheStub{ + SimulateSelectTransactionsCalled: func(_ txcache.SelectionSession, _ common.TxSelectionOptions, _ uint64) ([]*txcache.WrappedTransaction, uint64, error) { + return nil, 0, errors.New("selection failed") + }, + } + sel, _ := NewAOTSelector(args) + + sel.runAOTSelection(0, []byte("randomness")) + + result, found := sel.GetPreSelectedTransactions(0) + require.Nil(t, result) + require.False(t, found) +} + +func TestAOTSelector_RunAOTSelectionCancelled(t *testing.T) { + t.Parallel() + + cancelAfterSelect := make(chan struct{}) + args := createDefaultArgs() + args.TxCache = &txcachestubs.TxCacheStub{ + SimulateSelectTransactionsCalled: func(_ txcache.SelectionSession, _ common.TxSelectionOptions, _ uint64) ([]*txcache.WrappedTransaction, uint64, error) { + // Wait for the cancel signal before returning + <-cancelAfterSelect + return []*txcache.WrappedTransaction{ + {TxHash: []byte("hash1")}, + }, 1000, nil + }, + } + sel, _ := NewAOTSelector(args) + + done := make(chan struct{}) + go func() { + sel.runAOTSelection(0, []byte("randomness")) + close(done) + }() + + // Wait for the goroutine to initialize + time.Sleep(20 * time.Millisecond) + + // Cancel and then unblock the selection + sel.CancelOngoingSelection() + close(cancelAfterSelect) + + select { + case <-done: + case <-time.After(time.Second): + t.Fatal("runAOTSelection did not finish after cancel") + } + + result, found := sel.GetPreSelectedTransactions(0) + require.Nil(t, result) + require.False(t, found) +} + +func TestAOTSelector_RunAOTSelectionUsesEconomicsGasWhenZero(t *testing.T) { + t.Parallel() + + economicsGasCalled := false + args := createDefaultArgs() + args.EconomicsDataHandler = &economicsmocks.EconomicsHandlerMock{ + MaxGasLimitPerBlockCalled: func(_ uint32) uint64 { + economicsGasCalled = true + return 2000000000 + }, + BlockCapacityOverestimationFactorCalled: func() uint64 { + return 100 + }, + } + args.TxCache = &txcachestubs.TxCacheStub{ + SimulateSelectTransactionsCalled: func(_ txcache.SelectionSession, _ common.TxSelectionOptions, _ uint64) ([]*txcache.WrappedTransaction, uint64, error) { + return nil, 0, nil + }, + } + sel, _ := NewAOTSelector(args) + + sel.runAOTSelection(0, []byte("randomness")) + require.True(t, economicsGasCalled) +} + +func TestAOTSelector_RunAOTSelectionEmptyResult(t *testing.T) { + t.Parallel() + + args := createDefaultArgs() + args.TxCache = &txcachestubs.TxCacheStub{ + SimulateSelectTransactionsCalled: func(_ txcache.SelectionSession, _ common.TxSelectionOptions, _ uint64) ([]*txcache.WrappedTransaction, uint64, error) { + return []*txcache.WrappedTransaction{}, 0, nil + }, + } + sel, _ := NewAOTSelector(args) + + sel.runAOTSelection(0, []byte("randomness")) + + result, found := sel.GetPreSelectedTransactions(0) + require.True(t, found) + require.NotNil(t, result) + require.Equal(t, 0, len(result.TxHashes)) + require.Equal(t, uint64(0), result.GasProvided) +} + +func TestAOTSelector_RunAOTSelectionCleansUpOngoingState(t *testing.T) { + t.Parallel() + + args := createDefaultArgs() + args.TxCache = &txcachestubs.TxCacheStub{ + SimulateSelectTransactionsCalled: func(_ txcache.SelectionSession, _ common.TxSelectionOptions, _ uint64) ([]*txcache.WrappedTransaction, uint64, error) { + return nil, 0, nil + }, + } + sel, _ := NewAOTSelector(args) + + sel.runAOTSelection(0, []byte("randomness")) + + sel.selectionMut.Lock() + require.Equal(t, uint64(0), sel.ongoingNonce) + require.Nil(t, sel.resultChan) + sel.selectionMut.Unlock() +} + +func TestAOTSelector_IsInterfaceNil(t *testing.T) { + t.Parallel() + + var sel *aotSelector + require.True(t, sel.IsInterfaceNil()) + + args := createDefaultArgs() + sel, _ = NewAOTSelector(args) + require.False(t, sel.IsInterfaceNil()) +} + +func TestAOTSelector_ConcurrentTriggerCancelGet(t *testing.T) { + t.Parallel() + + args := createLeaderArgs([]byte("self-leader-key")) + args.TxCache = &txcachestubs.TxCacheStub{ + SimulateSelectTransactionsCalled: func(_ txcache.SelectionSession, _ common.TxSelectionOptions, _ uint64) ([]*txcache.WrappedTransaction, uint64, error) { + time.Sleep(10 * time.Millisecond) + return []*txcache.WrappedTransaction{{TxHash: []byte("tx1")}}, 1000, nil + }, + } + sel, _ := NewAOTSelector(args) + + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(3) + + go func(nonce uint64) { + defer wg.Done() + sel.TriggerAOTSelection(createHeader(nonce, []byte("rand"), 1), nonce+100) + }(uint64(i)) + + go func() { + defer wg.Done() + sel.CancelOngoingSelection() + }() + + go func(nonce uint64) { + defer wg.Done() + sel.GetPreSelectedTransactions(nonce) + }(uint64(i)) + } + + wg.Wait() +} + +func TestAOTSelector_TriggerAOTSelectionEpochChangeClearsCache(t *testing.T) { + t.Parallel() + + args := createLeaderArgs([]byte("self-leader-key")) + args.TxCache = &txcachestubs.TxCacheStub{ + SimulateSelectTransactionsCalled: func(_ txcache.SelectionSession, _ common.TxSelectionOptions, _ uint64) ([]*txcache.WrappedTransaction, uint64, error) { + return []*txcache.WrappedTransaction{ + {TxHash: []byte("tx1")}, + }, 1000, nil + }, + } + sel, err := NewAOTSelector(args) + require.NoError(t, err) + + // Trigger at epoch 1 to set lastEpoch + sel.TriggerAOTSelection(createHeader(10, []byte("randomness"), 1), 100) + time.Sleep(100 * time.Millisecond) // wait for goroutine to complete + + // Verify cache has an entry for nonce 11 + result, found := sel.GetPreSelectedTransactions(11) + require.True(t, found) + require.NotNil(t, result) + + // Trigger at epoch 2 - should clear cache + sel.TriggerAOTSelection(createHeader(20, []byte("randomness2"), 2), 200) + time.Sleep(50 * time.Millisecond) + + // The old entry at nonce 11 should be gone + result, found = sel.GetPreSelectedTransactions(11) + require.False(t, found) + require.Nil(t, result) +} + +func TestAOTSelector_TriggerAOTSelectionEpochChangeCancelsOngoing(t *testing.T) { + t.Parallel() + + selectionStarted := make(chan struct{}, 1) + selectionBlocked := make(chan struct{}) + firstSelectionDone := make(chan struct{}) + callCount := 0 + var callMut sync.Mutex + args := createLeaderArgs([]byte("self-leader-key")) + args.TxCache = &txcachestubs.TxCacheStub{ + SimulateSelectTransactionsCalled: func(_ txcache.SelectionSession, _ common.TxSelectionOptions, _ uint64) ([]*txcache.WrappedTransaction, uint64, error) { + callMut.Lock() + callCount++ + isFirst := callCount == 1 + callMut.Unlock() + if isFirst { + selectionStarted <- struct{}{} + <-selectionBlocked + defer close(firstSelectionDone) + } + return []*txcache.WrappedTransaction{{TxHash: []byte("tx1")}}, 1000, nil + }, + } + sel, err := NewAOTSelector(args) + require.NoError(t, err) + + // Set lastEpoch to 1 first (need a non-zero lastEpoch for detection) + sel.selectionMut.Lock() + sel.lastEpoch = 1 + sel.selectionMut.Unlock() + + // Trigger selection at epoch 1 (starts a goroutine that blocks) + sel.TriggerAOTSelection(createHeader(10, []byte("randomness"), 1), 100) + + // Wait for selection goroutine to start + select { + case <-selectionStarted: + case <-time.After(time.Second): + t.Fatal("selection goroutine was not started") + } + + // Now trigger at epoch 2 - should cancel the ongoing selection + sel.TriggerAOTSelection(createHeader(20, []byte("randomness2"), 2), 200) + + // Unblock the first selection goroutine and wait for it to complete + close(selectionBlocked) + select { + case <-firstSelectionDone: + case <-time.After(time.Second): + t.Fatal("first selection did not complete in time") + } + + // The result from the cancelled selection at nonce 11 should not be cached + // (because cancel was triggered before the selection finished storing) + result, found := sel.GetPreSelectedTransactions(11) + require.False(t, found) + require.Nil(t, result) +} + +func TestAOTSelector_TriggerAOTSelectionSameEpochKeepsCache(t *testing.T) { + t.Parallel() + + callCount := 0 + args := createLeaderArgs([]byte("self-leader-key")) + args.TxCache = &txcachestubs.TxCacheStub{ + SimulateSelectTransactionsCalled: func(_ txcache.SelectionSession, _ common.TxSelectionOptions, _ uint64) ([]*txcache.WrappedTransaction, uint64, error) { + callCount++ + return []*txcache.WrappedTransaction{ + {TxHash: []byte(fmt.Sprintf("tx-%d", callCount))}, + }, 1000, nil + }, + } + sel, err := NewAOTSelector(args) + require.NoError(t, err) + + // Trigger at epoch 1 + sel.TriggerAOTSelection(createHeader(10, []byte("randomness"), 1), 100) + time.Sleep(100 * time.Millisecond) + + // Verify cache has entry for nonce 11 + result, found := sel.GetPreSelectedTransactions(11) + require.True(t, found) + require.NotNil(t, result) + firstTxHash := result.TxHashes[0] + + // Trigger again at same epoch 1 with different nonce + sel.TriggerAOTSelection(createHeader(11, []byte("randomness2"), 1), 101) + time.Sleep(100 * time.Millisecond) + + // Old entry at nonce 11 should still be in cache (not cleared) + result, found = sel.GetPreSelectedTransactions(11) + require.True(t, found) + require.NotNil(t, result) + require.Equal(t, firstTxHash, result.TxHashes[0]) +} + +func TestAOTSelector_Close(t *testing.T) { + t.Parallel() + + args := createDefaultArgs() + sel, err := NewAOTSelector(args) + require.NoError(t, err) + + // Close should not panic + err = sel.Close() + require.NoError(t, err) + + // Second close should be idempotent + err = sel.Close() + require.NoError(t, err) +} + +func TestAOTSelector_CloseStopsOngoingSelection(t *testing.T) { + t.Parallel() + + selectionStarted := make(chan struct{}, 1) + selectionBlocked := make(chan struct{}) + + args := createLeaderArgs([]byte("self-leader-key")) + args.TxCache = &txcachestubs.TxCacheStub{ + SimulateSelectTransactionsCalled: func(_ txcache.SelectionSession, _ common.TxSelectionOptions, _ uint64) ([]*txcache.WrappedTransaction, uint64, error) { + select { + case selectionStarted <- struct{}{}: + default: + } + // Block until cancelled or signaled + <-selectionBlocked + return []*txcache.WrappedTransaction{{TxHash: []byte("tx1")}}, 1000, nil + }, + } + sel, err := NewAOTSelector(args) + require.NoError(t, err) + + // Trigger selection + sel.TriggerAOTSelection(createHeader(10, []byte("randomness"), 1), 100) + + // Wait for selection to start + select { + case <-selectionStarted: + case <-time.After(time.Second): + t.Fatal("selection did not start in time") + } + + // Close should cancel ongoing selection + err = sel.Close() + require.NoError(t, err) + + // Unblock the selection + close(selectionBlocked) + + // Operations after close should be no-ops + sel.TriggerAOTSelection(createHeader(11, []byte("randomness2"), 1), 101) + result, found := sel.GetPreSelectedTransactions(11) + require.False(t, found) + require.Nil(t, result) +} + +func TestAOTSelector_ConcurrentCancelDoesNotPanic(t *testing.T) { + t.Parallel() + + args := createLeaderArgs([]byte("self-leader-key")) + args.TxCache = &txcachestubs.TxCacheStub{ + SimulateSelectTransactionsCalled: func(_ txcache.SelectionSession, _ common.TxSelectionOptions, _ uint64) ([]*txcache.WrappedTransaction, uint64, error) { + // Simulate some work + time.Sleep(5 * time.Millisecond) + return []*txcache.WrappedTransaction{{TxHash: []byte("tx1")}}, 1000, nil + }, + } + sel, _ := NewAOTSelector(args) + + // Run many concurrent triggers and cancels - this used to cause double-close panic + var wg sync.WaitGroup + for i := 0; i < 50; i++ { + wg.Add(3) + + go func(nonce uint64) { + defer wg.Done() + sel.TriggerAOTSelection(createHeader(nonce, []byte("rand"), 1), nonce+100) + }(uint64(i)) + + go func() { + defer wg.Done() + sel.CancelOngoingSelection() + }() + + go func(nonce uint64) { + defer wg.Done() + sel.GetPreSelectedTransactions(nonce) + }(uint64(i)) + } + + // This should not panic + require.NotPanics(t, func() { + wg.Wait() + }) + + // Close should also not panic + require.NotPanics(t, func() { + _ = sel.Close() + }) +} + +func TestAOTSelector_GetPreSelectedTransactionsTimeout(t *testing.T) { + t.Parallel() + + args := createLeaderArgs([]byte("self-leader-key")) + args.SelectionTimeout = 50 * time.Millisecond + args.TxCache = &txcachestubs.TxCacheStub{ + SimulateSelectTransactionsCalled: func(_ txcache.SelectionSession, _ common.TxSelectionOptions, _ uint64) ([]*txcache.WrappedTransaction, uint64, error) { + // Block longer than timeout + time.Sleep(200 * time.Millisecond) + return []*txcache.WrappedTransaction{{TxHash: []byte("tx1")}}, 1000, nil + }, + } + sel, _ := NewAOTSelector(args) + + // Trigger selection + sel.TriggerAOTSelection(createHeader(10, []byte("randomness"), 1), 100) + + // Small delay to ensure selection starts + time.Sleep(10 * time.Millisecond) + + // GetPreSelectedTransactions should timeout and return false + start := time.Now() + result, found := sel.GetPreSelectedTransactions(11) + elapsed := time.Since(start) + + require.False(t, found) + require.Nil(t, result) + // Should return within timeout + some margin + require.Less(t, elapsed, 100*time.Millisecond) +} diff --git a/process/aotSelection/disabled.go b/process/aotSelection/disabled.go new file mode 100644 index 00000000000..ecfa4469298 --- /dev/null +++ b/process/aotSelection/disabled.go @@ -0,0 +1,38 @@ +package aotSelection + +import ( + "github.com/multiversx/mx-chain-core-go/data" + + "github.com/multiversx/mx-chain-go/process" +) + +type disabledAOTSelector struct{} + +// NewDisabledAOTSelector creates a new disabled AOT selector +// Used when AOT selection is explicitly disabled by configuration +func NewDisabledAOTSelector() *disabledAOTSelector { + return &disabledAOTSelector{} +} + +// TriggerAOTSelection does nothing for disabled selector +func (d *disabledAOTSelector) TriggerAOTSelection(_ data.HeaderHandler, _ uint64) { +} + +// GetPreSelectedTransactions returns false for disabled selector +func (d *disabledAOTSelector) GetPreSelectedTransactions(_ uint64) (*process.AOTSelectionResult, bool) { + return nil, false +} + +// CancelOngoingSelection does nothing for disabled selector +func (d *disabledAOTSelector) CancelOngoingSelection() { +} + +// Close does nothing for disabled selector +func (d *disabledAOTSelector) Close() error { + return nil +} + +// IsInterfaceNil returns true if there is no value under the interface +func (d *disabledAOTSelector) IsInterfaceNil() bool { + return d == nil +} diff --git a/process/aotSelection/disabled_test.go b/process/aotSelection/disabled_test.go new file mode 100644 index 00000000000..71f8c0523e1 --- /dev/null +++ b/process/aotSelection/disabled_test.go @@ -0,0 +1,65 @@ +package aotSelection + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/testscommon" +) + +func TestNewDisabledAOTSelector(t *testing.T) { + t.Parallel() + + sel := NewDisabledAOTSelector() + assert.NotNil(t, sel) + assert.False(t, sel.IsInterfaceNil()) +} + +func TestDisabledAOTSelector_TriggerAOTSelectionDoesNothing(t *testing.T) { + t.Parallel() + + sel := NewDisabledAOTSelector() + header := &testscommon.HeaderHandlerStub{ + GetNonceCalled: func() uint64 { return 10 }, + GetRandSeedCalled: func() []byte { return []byte("randomness") }, + EpochField: 1, + } + // Should not panic + sel.TriggerAOTSelection(header, 100) + sel.TriggerAOTSelection(nil, 100) +} + +func TestDisabledAOTSelector_GetPreSelectedTransactionsReturnsFalse(t *testing.T) { + t.Parallel() + + sel := NewDisabledAOTSelector() + result, found := sel.GetPreSelectedTransactions(42) + assert.Nil(t, result) + assert.False(t, found) +} + +func TestDisabledAOTSelector_CancelOngoingSelectionDoesNothing(t *testing.T) { + t.Parallel() + + sel := NewDisabledAOTSelector() + // Should not panic + sel.CancelOngoingSelection() +} + +func TestDisabledAOTSelector_IsInterfaceNil(t *testing.T) { + t.Parallel() + + var sel *disabledAOTSelector + assert.True(t, sel.IsInterfaceNil()) + + sel = NewDisabledAOTSelector() + assert.False(t, sel.IsInterfaceNil()) +} + +func TestDisabledAOTSelector_ImplementsInterface(t *testing.T) { + t.Parallel() + + var _ process.AOTTransactionSelector = (*disabledAOTSelector)(nil) +} diff --git a/process/asyncExecution/cache/errors.go b/process/asyncExecution/cache/errors.go new file mode 100644 index 00000000000..13a047f5560 --- /dev/null +++ b/process/asyncExecution/cache/errors.go @@ -0,0 +1,7 @@ +package cache + +import "errors" + +var ( + ErrCacheIsFull = errors.New("async execution headers's cache is full") +) diff --git a/process/asyncExecution/cache/headerBodyCache.go b/process/asyncExecution/cache/headerBodyCache.go new file mode 100644 index 00000000000..bebed3ef771 --- /dev/null +++ b/process/asyncExecution/cache/headerBodyCache.go @@ -0,0 +1,122 @@ +package cache + +import ( + "sort" + "sync" + + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" + logger "github.com/multiversx/mx-chain-logger-go" +) + +var log = logger.GetOrCreate("process/asyncExecution/cache") + +const ( + defaultMaxQueueSize = 1000 +) + +type headerBodyCache struct { + mutex sync.RWMutex + cacheByNonce map[uint64]HeaderBodyPair + maxCacheSize int +} + +// NewHeaderBodyCache will create a new instance of cache +func NewHeaderBodyCache(config config.HeaderBodyCacheConfig) *headerBodyCache { + cacheSize := config.Capacity + if cacheSize == 0 { + cacheSize = defaultMaxQueueSize + } + + return &headerBodyCache{ + cacheByNonce: make(map[uint64]HeaderBodyPair), + mutex: sync.RWMutex{}, + maxCacheSize: cacheSize, + } +} + +// AddOrReplace will add or replace the provided pair based on header's nonce +func (c *headerBodyCache) AddOrReplace(pair HeaderBodyPair) error { + if check.IfNil(pair.Header) { + return common.ErrNilHeaderHandler + } + if check.IfNil(pair.Body) { + return data.ErrNilBlockBody + } + if pair.HeaderHash == nil { + return common.ErrNilHeaderHash + } + + c.mutex.Lock() + defer c.mutex.Unlock() + + headerNonce := pair.Header.GetNonce() + _, found := c.cacheByNonce[headerNonce] + if len(c.cacheByNonce) >= c.maxCacheSize && !found { + log.Warn("async execution cache is full", + "current size", len(c.cacheByNonce), + "max size", c.maxCacheSize, + "rejected nonce", headerNonce, + ) + return ErrCacheIsFull + } + + c.cacheByNonce[headerNonce] = pair + + log.Debug("headerBodyCache.AddOrReplace - block has been added", + "round", pair.Header.GetRound(), + "nonce", pair.Header.GetNonce(), + "hash", pair.HeaderHash, + "cache size", len(c.cacheByNonce)) + + return nil +} + +// GetByNonce will return the pair based on the provided nonce +func (c *headerBodyCache) GetByNonce(nonce uint64) (HeaderBodyPair, bool) { + c.mutex.RLock() + defer c.mutex.RUnlock() + + pair, found := c.cacheByNonce[nonce] + + return pair, found +} + +// RemoveAtNonceAndHigher will remove all pairs with the provided nonce or higher +func (c *headerBodyCache) RemoveAtNonceAndHigher(providedNonce uint64) []uint64 { + nonces := make([]uint64, 0) + c.mutex.Lock() + for nonce := range c.cacheByNonce { + if nonce >= providedNonce { + delete(c.cacheByNonce, nonce) + nonces = append(nonces, nonce) + } + } + c.mutex.Unlock() + + sort.Slice(nonces, func(i, j int) bool { return nonces[i] < nonces[j] }) + + return nonces +} + +// Remove will remove a pair by provided nonce +func (c *headerBodyCache) Remove(nonce uint64) { + c.mutex.Lock() + defer c.mutex.Unlock() + + delete(c.cacheByNonce, nonce) +} + +// Clean will cleanup the cache +func (c *headerBodyCache) Clean() { + c.mutex.Lock() + defer c.mutex.Unlock() + c.cacheByNonce = make(map[uint64]HeaderBodyPair) +} + +// IsInterfaceNil returns true if there is no value under the interface +func (c *headerBodyCache) IsInterfaceNil() bool { + return c == nil +} diff --git a/process/asyncExecution/cache/headerBodyCache_test.go b/process/asyncExecution/cache/headerBodyCache_test.go new file mode 100644 index 00000000000..0b3c3b297b3 --- /dev/null +++ b/process/asyncExecution/cache/headerBodyCache_test.go @@ -0,0 +1,273 @@ +package cache + +import ( + "testing" + "time" + + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" +) + +func TestNewHeaderBodyCache(t *testing.T) { + t.Parallel() + + c := NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + require.NotNil(t, c) + require.False(t, c.IsInterfaceNil()) +} + +func TestHeaderBodyCache_AddOrReplace(t *testing.T) { + t.Parallel() + + t.Run("nil header", func(t *testing.T) { + t.Parallel() + c := NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + err := c.AddOrReplace(HeaderBodyPair{ + Header: nil, + Body: &block.Body{}, + }) + require.Equal(t, common.ErrNilHeaderHandler, err) + }) + + t.Run("nil body", func(t *testing.T) { + t.Parallel() + c := NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + err := c.AddOrReplace(HeaderBodyPair{ + Header: &block.HeaderV3{}, + Body: nil, + }) + require.Equal(t, data.ErrNilBlockBody, err) + }) + + t.Run("success", func(t *testing.T) { + t.Parallel() + c := NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + + headerNonce := uint64(10) + pair := HeaderBodyPair{ + Header: &block.HeaderV3{ + Nonce: headerNonce, + }, + Body: &block.Body{}, + HeaderHash: []byte("a"), + } + + err := c.AddOrReplace(pair) + require.Nil(t, err) + + retrievedPair, found := c.GetByNonce(headerNonce) + require.True(t, found) + require.Equal(t, pair, retrievedPair) + }) + + t.Run("replace existing", func(t *testing.T) { + t.Parallel() + c := NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + + headerNonce := uint64(10) + pair1 := HeaderBodyPair{ + Header: &block.HeaderV3{ + Nonce: headerNonce, + }, + Body: &block.Body{}, + HeaderHash: []byte("a"), + } + + err := c.AddOrReplace(pair1) + require.Nil(t, err) + + pair2 := HeaderBodyPair{ + Header: &block.HeaderV3{ + Nonce: headerNonce, + }, + Body: &block.Body{}, + HeaderHash: []byte("a"), + } + + err = c.AddOrReplace(pair2) + require.Nil(t, err) + + retrievedPair, found := c.GetByNonce(headerNonce) + require.True(t, found) + require.Equal(t, pair2, retrievedPair) + }) +} + +func TestHeaderBodyCache_GetByNonce(t *testing.T) { + t.Parallel() + + c := NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + + pair := HeaderBodyPair{ + Header: &block.HeaderV3{ + Nonce: 5, + }, + Body: &block.Body{}, + HeaderHash: []byte("a"), + } + _ = c.AddOrReplace(pair) + + retrieved, found := c.GetByNonce(5) + require.True(t, found) + require.Equal(t, pair, retrieved) + + _, found = c.GetByNonce(999) + require.False(t, found) +} + +func TestHeaderBodyCache_RemoveAtNonceAndHigher(t *testing.T) { + t.Parallel() + + c := NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + nonces := []uint64{1, 2, 3, 4, 5, 10} + + for _, n := range nonces { + _ = c.AddOrReplace(HeaderBodyPair{ + Header: &block.HeaderV3{ + Nonce: n, + }, + Body: &block.Body{}, + HeaderHash: []byte("a"), + }) + } + + removed := c.RemoveAtNonceAndHigher(4) + require.Equal(t, []uint64{4, 5, 10}, removed) + + for _, n := range []uint64{1, 2, 3} { + _, found := c.GetByNonce(n) + require.True(t, found, "nonce %d should exist", n) + } + + for _, n := range []uint64{4, 5, 10} { + _, found := c.GetByNonce(n) + require.False(t, found, "nonce %d should be removed", n) + } +} + +func TestHeaderBodyCache_Remove(t *testing.T) { + t.Parallel() + + c := NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + _ = c.AddOrReplace(HeaderBodyPair{ + Header: &block.HeaderV3{ + Nonce: 10, + }, + Body: &block.Body{}, + HeaderHash: []byte("a"), + }) + + c.Remove(10) + _, found := c.GetByNonce(10) + require.False(t, found) +} + +func TestHeaderBodyCache_Clean(t *testing.T) { + t.Parallel() + + c := NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + _ = c.AddOrReplace(HeaderBodyPair{ + Header: &block.HeaderV3{ + Nonce: 10, + }, + Body: &block.Body{}, + HeaderHash: []byte("a"), + }) + + c.Clean() + _, found := c.GetByNonce(10) + require.False(t, found) +} + +func TestHeaderBodyCache_ConcurrentAccess(t *testing.T) { + t.Parallel() + + c := NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + numGoroutines := 100 + done := make(chan struct{}) + + for i := 0; i < numGoroutines; i++ { + go func(idx int) { + nonce := uint64(idx) + _ = c.AddOrReplace(HeaderBodyPair{ + Header: &block.HeaderV3{ + Nonce: nonce, + }, + Body: &block.Body{}, + HeaderHash: []byte("a"), + }) + _, _ = c.GetByNonce(nonce) + + if idx%2 == 0 { + c.Remove(nonce) + } + done <- struct{}{} + }(i) + } + + timeout := time.After(2 * time.Second) + for i := 0; i < numGoroutines; i++ { + select { + case <-done: + case <-timeout: + t.Fatal("timeout waiting for goroutines") + } + } +} + +func TestBlocksCache_AddOrReplace_Rejection(t *testing.T) { + t.Parallel() + + hq := NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + hq.maxCacheSize = 5 + + for i := uint64(1); i <= 5; i++ { + pair := HeaderBodyPair{ + Header: &block.Header{Nonce: i}, + Body: &block.Body{}, + HeaderHash: []byte("a"), + } + err := hq.AddOrReplace(pair) + require.Nil(t, err) + } + + pair := HeaderBodyPair{ + Header: &block.Header{Nonce: 6}, + Body: &block.Body{}, + HeaderHash: []byte("a"), + } + err := hq.AddOrReplace(pair) + require.Equal(t, ErrCacheIsFull, err) + require.Equal(t, 5, len(hq.cacheByNonce)) +} + +func TestBlocksCache_AddOrReplace_ReplacementAllowed(t *testing.T) { + t.Parallel() + + hq := NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + hq.maxCacheSize = 5 + + for i := uint64(1); i <= 5; i++ { + pair := HeaderBodyPair{ + Header: &block.Header{Nonce: i}, + Body: &block.Body{}, + HeaderHash: []byte("a"), + } + err := hq.AddOrReplace(pair) + require.Nil(t, err) + } + + pair := HeaderBodyPair{ + Header: &block.Header{Nonce: 3, Round: 100}, + Body: &block.Body{}, + HeaderHash: []byte("a"), + } + err := hq.AddOrReplace(pair) + require.Nil(t, err) + require.Equal(t, 5, len(hq.cacheByNonce)) + require.Equal(t, uint64(100), hq.cacheByNonce[pair.Header.GetNonce()].Header.GetRound()) +} diff --git a/process/asyncExecution/cache/pair.go b/process/asyncExecution/cache/pair.go new file mode 100644 index 00000000000..4936459cfe4 --- /dev/null +++ b/process/asyncExecution/cache/pair.go @@ -0,0 +1,10 @@ +package cache + +import "github.com/multiversx/mx-chain-core-go/data" + +// HeaderBodyPair groups a block header and its corresponding body. +type HeaderBodyPair struct { + HeaderHash []byte + Header data.HeaderHandler + Body data.BodyHandler +} diff --git a/process/asyncExecution/disabled/headersExecutor.go b/process/asyncExecution/disabled/headersExecutor.go new file mode 100644 index 00000000000..11f2af68337 --- /dev/null +++ b/process/asyncExecution/disabled/headersExecutor.go @@ -0,0 +1,36 @@ +package disabled + +type headersExecutor struct { +} + +// NewHeadersExecutor returns a new instance of disabled headers executor +func NewHeadersExecutor() *headersExecutor { + return &headersExecutor{} +} + +// StartExecution does nothing as it is disabled +func (he *headersExecutor) StartExecution() { +} + +// PauseExecution does nothing as it is disabled +func (he *headersExecutor) PauseExecution() { +} + +// ResumeExecution does nothing as it is disabled +func (he *headersExecutor) ResumeExecution() { +} + +// GetSignalProcessCompletionChan returns nil as it is disabled +func (he *headersExecutor) GetSignalProcessCompletionChan() chan uint64 { + return nil +} + +// Close always returns nil as it is disabled +func (he *headersExecutor) Close() error { + return nil +} + +// IsInterfaceNil returns true if there is no value under the interface +func (he *headersExecutor) IsInterfaceNil() bool { + return he == nil +} diff --git a/process/asyncExecution/disabled/headersExecutor_test.go b/process/asyncExecution/disabled/headersExecutor_test.go new file mode 100644 index 00000000000..c275abcc9e1 --- /dev/null +++ b/process/asyncExecution/disabled/headersExecutor_test.go @@ -0,0 +1,23 @@ +package disabled + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestHeadersExecutor(t *testing.T) { + t.Parallel() + + he := NewHeadersExecutor() + require.NotNil(t, he) + require.False(t, he.IsInterfaceNil()) + + // Call all methods to ensure 100% coverage + he.StartExecution() + he.PauseExecution() + he.ResumeExecution() + + err := he.Close() + require.NoError(t, err) +} diff --git a/process/asyncExecution/errors.go b/process/asyncExecution/errors.go new file mode 100644 index 00000000000..c2e389a01d4 --- /dev/null +++ b/process/asyncExecution/errors.go @@ -0,0 +1,18 @@ +package asyncExecution + +import "errors" + +// ErrNilHeadersCache signals that a nil headers cache has been provided +var ErrNilHeadersCache = errors.New("nil headers cache") + +// ErrNilExecutionTracker signals that a nil execution tracker has been provided +var ErrNilExecutionTracker = errors.New("nil execution tracker") + +// ErrNilBlockProcessor signals that a nil block processor has been provided +var ErrNilBlockProcessor = errors.New("nil block processor") + +// ErrNilExecutionResult signals that a nil execution result has been provided +var ErrNilExecutionResult = errors.New("nil execution result") + +// ErrContextMismatch signals that a block context does not match the current execution state +var ErrContextMismatch = errors.New("block context mismatch with current execution state") diff --git a/process/asyncExecution/executionManager/errors.go b/process/asyncExecution/executionManager/errors.go new file mode 100644 index 00000000000..c8e9065f039 --- /dev/null +++ b/process/asyncExecution/executionManager/errors.go @@ -0,0 +1,18 @@ +package executionManager + +import "errors" + +// ErrNilHeadersExecutor signals that a nil headers executor has been provided +var ErrNilHeadersExecutor = errors.New("nil headers executor") + +// ErrNilBlocksCache signals that a nil blocks cache has been provided +var ErrNilBlocksCache = errors.New("nil blocks cache") + +// ErrNilExecutionResultsTracker signals that a nil execution results tracker has been provided +var ErrNilExecutionResultsTracker = errors.New("nil execution results tracker") + +// ErrNilBlockchain signals that a nil blockchain has been provided +var ErrNilBlockchain = errors.New("nil blockchain") + +// ErrNilHeadersPool signals that a nil headers pool has been provided +var ErrNilHeadersPool = errors.New("nil headers pool") diff --git a/process/asyncExecution/executionManager/executionManager.go b/process/asyncExecution/executionManager/executionManager.go new file mode 100644 index 00000000000..4287d693fb1 --- /dev/null +++ b/process/asyncExecution/executionManager/executionManager.go @@ -0,0 +1,324 @@ +package executionManager + +import ( + "sync" + + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/marshal" + logger "github.com/multiversx/mx-chain-logger-go" + + "github.com/multiversx/mx-chain-go/dataRetriever" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/asyncExecution/cache" + "github.com/multiversx/mx-chain-go/process/asyncExecution/disabled" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionTrack" + "github.com/multiversx/mx-chain-go/sharding" + "github.com/multiversx/mx-chain-go/storage" +) + +var log = logger.GetOrCreate("process/asyncExecution/executionManager") + +// ArgsExecutionManager holds all the components needed to create a new instance of executionManager +type ArgsExecutionManager struct { + BlocksCache process.BlocksCache + ExecutionResultsTracker process.ExecutionResultsTracker + BlockChain data.ChainHandler + Headers dataRetriever.HeadersPool + PostProcessTransactions storage.Cacher + ExecutedMiniBlocks storage.Cacher + StorageService dataRetriever.StorageService + Marshaller marshal.Marshalizer + ShardCoordinator sharding.Coordinator +} + +type executionManager struct { + mut sync.RWMutex + headersExecutor process.HeadersExecutor + blocksCache process.BlocksCache + executionResultsTracker process.ExecutionResultsTracker + blockChain data.ChainHandler + headers dataRetriever.HeadersPool + postProcessTransactions storage.Cacher + executedMiniBlocks storage.Cacher + storageService dataRetriever.StorageService + marshaller marshal.Marshalizer + shardCoordinator sharding.Coordinator +} + +// NewExecutionManager creates a new instance of executionManager +func NewExecutionManager(args ArgsExecutionManager) (*executionManager, error) { + if check.IfNil(args.BlocksCache) { + return nil, ErrNilBlocksCache + } + if check.IfNil(args.ExecutionResultsTracker) { + return nil, ErrNilExecutionResultsTracker + } + if check.IfNil(args.BlockChain) { + return nil, ErrNilBlockchain + } + if check.IfNil(args.Headers) { + return nil, ErrNilHeadersPool + } + if check.IfNil(args.PostProcessTransactions) { + return nil, process.ErrNilPostProcessTransactionsCache + } + if check.IfNil(args.ExecutedMiniBlocks) { + return nil, process.ErrNilExecutedMiniBlocksCache + } + if check.IfNil(args.StorageService) { + return nil, process.ErrNilStorage + } + if check.IfNil(args.Marshaller) { + return nil, process.ErrNilMarshalizer + } + if check.IfNil(args.ShardCoordinator) { + return nil, process.ErrNilShardCoordinator + } + + instance := &executionManager{ + headersExecutor: disabled.NewHeadersExecutor(), + blocksCache: args.BlocksCache, + executionResultsTracker: args.ExecutionResultsTracker, + blockChain: args.BlockChain, + headers: args.Headers, + postProcessTransactions: args.PostProcessTransactions, + executedMiniBlocks: args.ExecutedMiniBlocks, + storageService: args.StorageService, + marshaller: args.Marshaller, + shardCoordinator: args.ShardCoordinator, + } + + return instance, nil +} + +// StartExecution starts the executor to begin processing blocks from the queue +func (em *executionManager) StartExecution() { + em.mut.Lock() + defer em.mut.Unlock() + + log.Debug("starting headers execution...") + em.headersExecutor.StartExecution() +} + +// SetHeadersExecutor sets the headers executor +func (em *executionManager) SetHeadersExecutor(executor process.HeadersExecutor) error { + if check.IfNil(executor) { + return ErrNilHeadersExecutor + } + + em.mut.Lock() + defer em.mut.Unlock() + + em.headersExecutor = executor + + return nil +} + +// AddPairForExecution adds or replaces a header-body pair in the blocks queue +func (em *executionManager) AddPairForExecution(pair cache.HeaderBodyPair) error { + // lock the internal mutex to avoid any concurrent removal requests + em.mut.Lock() + defer em.mut.Unlock() + + lastExecutedBlock := em.blockChain.GetLastExecutedBlockHeader() + if !check.IfNil(lastExecutedBlock) && + lastExecutedBlock.GetNonce() >= pair.Header.GetNonce() { + err := process.UpdateContextForReplacedHeader( + pair.Header, + em, + em.blockChain, + em.headers, + em.postProcessTransactions, + em.executedMiniBlocks, + em.storageService, + em.marshaller, + em.shardCoordinator.SelfId(), + ) + if err != nil { + return err + } + } + + return em.blocksCache.AddOrReplace(pair) +} + +// GetPendingExecutionResults calls the same method from executionResultsTracker +func (em *executionManager) GetPendingExecutionResults() ([]data.BaseExecutionResultHandler, error) { + return em.executionResultsTracker.GetPendingExecutionResults() +} + +// GetLastNotarizedExecutionResult will return the last notarized execution result +func (em *executionManager) GetLastNotarizedExecutionResult() (data.BaseExecutionResultHandler, error) { + return em.executionResultsTracker.GetLastNotarizedExecutionResult() +} + +// SetLastNotarizedResult calls the same method from executionResultsTracker +func (em *executionManager) SetLastNotarizedResult(executionResult data.BaseExecutionResultHandler) error { + return em.executionResultsTracker.SetLastNotarizedResult(executionResult) +} + +// CleanConfirmedExecutionResults calls the same method from executionResultsTracker +func (em *executionManager) CleanConfirmedExecutionResults(header data.HeaderHandler) error { + for _, executionResult := range header.GetExecutionResultsHandlers() { + em.blocksCache.Remove(executionResult.GetHeaderNonce()) + } + + return em.executionResultsTracker.CleanConfirmedExecutionResults(header) +} + +// CleanOnConsensusReached calls the same method from executionResultsTracker +func (em *executionManager) CleanOnConsensusReached(headerHash []byte, header data.HeaderHandler) { + if check.IfNil(header) { + return + } + + em.executionResultsTracker.CleanOnConsensusReached(headerHash, header) + em.blocksCache.RemoveAtNonceAndHigher(header.GetNonce() + 1) +} + +// RemoveAtNonceAndHigher removes the header-body pair at the specified nonce +// and all pairs with higher nonces from the queue. +// if anything was processed after that nonce, remove the execution results +// and rollback blockchain state +func (em *executionManager) RemoveAtNonceAndHigher(nonce uint64) error { + em.mut.Lock() + defer em.mut.Unlock() + + lastNotarizedResult, err := em.executionResultsTracker.GetLastNotarizedExecutionResult() + if err != nil { + return err + } + + lastNotarizedNonce := lastNotarizedResult.GetHeaderNonce() + nonceToRemove := nonce + if nonce <= lastNotarizedNonce { + nonceToRemove = lastNotarizedNonce + 1 + log.Debug("attempting to remove nonce lower than last notarized execution result, updating it", + "nonce provided", nonce, + "last notarized nonce", lastNotarizedNonce, + "removing from nonce", nonceToRemove, + ) + } + + // first pause the headers executor which will block it from popping new headers + // but allow it to finish anything currently processing + em.headersExecutor.PauseExecution() + + // remove from queue + _ = em.blocksCache.RemoveAtNonceAndHigher(nonceToRemove) + err = em.executionResultsTracker.RemoveFromNonce(nonceToRemove) + if err != nil { + return err + } + + // update blockchain with the last executed header, similar to headersExecution + err = em.updateBlockchainAfterRemoval(lastNotarizedResult) + if err != nil { + return err + } + + // resume execution + em.headersExecutor.ResumeExecution() + + return nil +} + +// RemovePendingExecutionResultsFromNonce will remove the execution result with the provided nonce and all execution results with higher nonces +func (em *executionManager) RemovePendingExecutionResultsFromNonce(nonce uint64) error { + return em.executionResultsTracker.RemoveFromNonce(nonce) +} + +// PopDismissedResults returns all batches of dismissed execution results and clears the internal queue +func (em *executionManager) PopDismissedResults() []executionTrack.DismissedBatch { + return em.executionResultsTracker.PopDismissedResults() +} + +// ResetAndResumeExecution resets the managed components to the last notarized result and resumes execution +func (em *executionManager) ResetAndResumeExecution(lastNotarizedResult data.BaseExecutionResultHandler) error { + if check.IfNil(lastNotarizedResult) { + return process.ErrNilLastExecutionResultHandler + } + + em.mut.Lock() + defer em.mut.Unlock() + + // even though the headers executor might already be paused, safe to try it one more time + em.headersExecutor.PauseExecution() + + em.executionResultsTracker.Clean(lastNotarizedResult) + + em.blocksCache.Clean() + + em.headersExecutor.ResumeExecution() + + return nil +} + +func (em *executionManager) updateBlockchainAfterRemoval(lastNotarizedResult data.BaseExecutionResultHandler) error { + lastExecutedHeaderHash := lastNotarizedResult.GetHeaderHash() + lastExecutedHeaderNonce := lastNotarizedResult.GetHeaderNonce() + lastExecutedHeaderRootHash := lastNotarizedResult.GetRootHash() + pendingExecutionResults, err := em.executionResultsTracker.GetPendingExecutionResults() + if err != nil { + return err + } + + // if there are still pending execution results, use the last one that was executed + lastExecutionResult := lastNotarizedResult + if len(pendingExecutionResults) > 0 { + lastPending := pendingExecutionResults[len(pendingExecutionResults)-1] + lastExecutedHeaderHash = lastPending.GetHeaderHash() + lastExecutedHeaderNonce = lastPending.GetHeaderNonce() + lastExecutedHeaderRootHash = lastPending.GetRootHash() + + lastExecutionResult = lastPending + } + + header, err := process.GetHeader(lastExecutedHeaderHash, em.headers, em.storageService, em.marshaller, em.shardCoordinator.SelfId()) + if err != nil { + log.Debug("executionmanager.updateBlockchainAfterRemoval: could not find header in pool or storage", + "hash", lastExecutedHeaderHash, + "nonce", lastExecutedHeaderNonce, + "error", err, + ) + return err + } + + // update blockchain + em.blockChain.SetFinalBlockInfo( + lastExecutedHeaderNonce, + lastExecutedHeaderHash, + lastExecutedHeaderRootHash, + ) + + em.blockChain.SetLastExecutedBlockHeaderAndRootHash(header, lastExecutedHeaderHash, lastExecutedHeaderRootHash) + em.blockChain.SetLastExecutionResult(lastExecutionResult) + + return nil +} + +// GetSignalProcessCompletionChan returns the channel used to signal the sync loop after execution completes +func (em *executionManager) GetSignalProcessCompletionChan() chan uint64 { + em.mut.RLock() + defer em.mut.RUnlock() + + return em.headersExecutor.GetSignalProcessCompletionChan() +} + +// Close closes the execution manager and all its components +func (em *executionManager) Close() error { + log.Debug("closing execution manager") + + err := em.headersExecutor.Close() + if err != nil { + log.Warn("executionManager.Close - failed to close headers executor", "error", err) + } + + return nil +} + +// IsInterfaceNil returns true if there is no value under the interface +func (em *executionManager) IsInterfaceNil() bool { + return em == nil +} diff --git a/process/asyncExecution/executionManager/executionManager_test.go b/process/asyncExecution/executionManager/executionManager_test.go new file mode 100644 index 00000000000..bd9bdc525ea --- /dev/null +++ b/process/asyncExecution/executionManager/executionManager_test.go @@ -0,0 +1,1308 @@ +package executionManager_test + +import ( + "bytes" + "errors" + "sync" + "testing" + + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/dataRetriever" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/asyncExecution/cache" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionManager" + "github.com/multiversx/mx-chain-go/process/mock" + "github.com/multiversx/mx-chain-go/storage" + "github.com/multiversx/mx-chain-go/testscommon" + testCache "github.com/multiversx/mx-chain-go/testscommon/cache" + "github.com/multiversx/mx-chain-go/testscommon/pool" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" + storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" +) + +var errExpected = errors.New("expected error") + +func createMockArgs() executionManager.ArgsExecutionManager { + return executionManager.ArgsExecutionManager{ + BlocksCache: &processMocks.BlocksCacheMock{}, + ExecutionResultsTracker: &processMocks.ExecutionTrackerStub{}, + BlockChain: &testscommon.ChainHandlerMock{}, + Headers: &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.HeaderV3{}, nil + }, + }, + PostProcessTransactions: &testCache.CacherStub{}, + ExecutedMiniBlocks: &testCache.CacherStub{}, + StorageService: &storageStubs.ChainStorerStub{}, + Marshaller: &mock.MarshalizerMock{}, + ShardCoordinator: &mock.ShardCoordinatorStub{}, + } +} + +func TestNewExecutionManager(t *testing.T) { + t.Parallel() + + t.Run("nil blocks cache should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.BlocksCache = nil + + em, err := executionManager.NewExecutionManager(args) + require.Nil(t, em) + require.Equal(t, executionManager.ErrNilBlocksCache, err) + }) + + t.Run("nil execution results tracker should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.ExecutionResultsTracker = nil + + em, err := executionManager.NewExecutionManager(args) + require.Nil(t, em) + require.Equal(t, executionManager.ErrNilExecutionResultsTracker, err) + }) + + t.Run("nil blockchain should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.BlockChain = nil + + em, err := executionManager.NewExecutionManager(args) + require.Nil(t, em) + require.Equal(t, executionManager.ErrNilBlockchain, err) + }) + + t.Run("nil headers pool should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.Headers = nil + + em, err := executionManager.NewExecutionManager(args) + require.Nil(t, em) + require.Equal(t, executionManager.ErrNilHeadersPool, err) + }) + + t.Run("nil storage service should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.StorageService = nil + + em, err := executionManager.NewExecutionManager(args) + require.Nil(t, em) + require.Equal(t, process.ErrNilStorage, err) + }) + + t.Run("nil marshaller should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.Marshaller = nil + + em, err := executionManager.NewExecutionManager(args) + require.Nil(t, em) + require.Equal(t, process.ErrNilMarshalizer, err) + }) + + t.Run("nil shard coordinator should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.ShardCoordinator = nil + + em, err := executionManager.NewExecutionManager(args) + require.Nil(t, em) + require.Equal(t, process.ErrNilShardCoordinator, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + + em, err := executionManager.NewExecutionManager(args) + require.NoError(t, err) + require.NotNil(t, em) + }) +} + +func TestExecutionManager_IsInterfaceNil(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.Headers = nil + em, _ := executionManager.NewExecutionManager(args) + require.True(t, em.IsInterfaceNil()) + + em, _ = executionManager.NewExecutionManager(createMockArgs()) + require.False(t, em.IsInterfaceNil()) +} + +func TestExecutionManager_StartExecution(t *testing.T) { + t.Parallel() + + args := createMockArgs() + em, _ := executionManager.NewExecutionManager(args) + + startCalled := false + mockExecutor := &processMocks.HeadersExecutorMock{ + StartExecutionCalled: func() { + startCalled = true + }, + } + _ = em.SetHeadersExecutor(mockExecutor) + + em.StartExecution() + require.True(t, startCalled) +} + +func TestExecutionManager_SetHeadersExecutor(t *testing.T) { + t.Parallel() + + t.Run("nil headers executor should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + em, _ := executionManager.NewExecutionManager(args) + + err := em.SetHeadersExecutor(nil) + require.Equal(t, executionManager.ErrNilHeadersExecutor, err) + }) + + t.Run("valid headers executor should work", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + em, _ := executionManager.NewExecutionManager(args) + + mockExecutor := &processMocks.HeadersExecutorMock{} + err := em.SetHeadersExecutor(mockExecutor) + require.NoError(t, err) + }) +} + +func TestExecutionManager_AddPairForExecution(t *testing.T) { + t.Parallel() + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + addOrReplaceCalled := false + args.BlocksCache = &processMocks.BlocksCacheMock{ + AddOrReplaceCalled: func(pair cache.HeaderBodyPair) error { + addOrReplaceCalled = true + require.NotNil(t, pair.Header) + return nil + }, + } + em, _ := executionManager.NewExecutionManager(args) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{Nonce: 1}, + Body: &block.Body{}, + } + err := em.AddPairForExecution(pair) + require.NoError(t, err) + require.True(t, addOrReplaceCalled) + }) + + t.Run("if getting the pending execution results fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV3{ + Nonce: 10, + } + }, + } + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return nil, errExpected + }, + } + + em, _ := executionManager.NewExecutionManager(args) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{Nonce: 9}, + Body: &block.Body{}, + } + + err := em.AddPairForExecution(pair) + require.Equal(t, errExpected, err) + }) + + t.Run("if getting the last notarized execution result fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV3{ + Nonce: 10, + } + }, + } + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{}, nil + }, + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return nil, errExpected + }, + } + + em, _ := executionManager.NewExecutionManager(args) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{Nonce: 9}, + Body: &block.Body{}, + } + + err := em.AddPairForExecution(pair) + require.Equal(t, errExpected, err) + }) + + t.Run("should return err if execution result of previous header is not found", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV3{ + Nonce: 10, + } + }, + } + + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{}, nil + }, + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return &block.BaseExecutionResult{ + HeaderNonce: 8, + RootHash: []byte("rootHash"), + HeaderHash: []byte("hashX"), + }, nil + }, + } + + em, _ := executionManager.NewExecutionManager(args) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: 9, + PrevHash: []byte("wrongHash"), + }, + Body: &block.Body{}, + } + + err := em.AddPairForExecution(pair) + require.Equal(t, process.ErrExecutionResultNotFound, err) + }) + + t.Run("if extracting the header from pool fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + counter := 0 + args := createMockArgs() + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV3{ + Nonce: 10, + } + }, + SetLastExecutedBlockHeaderAndRootHashCalled: func(header data.HeaderHandler, blockHash []byte, rootHash []byte) { + if bytes.Equal(blockHash, []byte("lastNotarizedExecResultHash")) { + counter += 1 + } + }, + SetLastExecutionResultCalled: func(result data.BaseExecutionResultHandler) { + if bytes.Equal(result.GetHeaderHash(), []byte("lastNotarizedExecResultHash")) { + counter += 1 + } + }, + } + + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{}, nil + }, + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return &block.BaseExecutionResult{ + RootHash: []byte("rootHash"), + HeaderHash: []byte("lastNotarizedExecResultHash"), + }, nil + }, + } + + args.Headers = &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, errors.New("fetch error") + }, + } + args.StorageService = &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + return nil, errExpected + }, + }, nil + }, + } + + em, _ := executionManager.NewExecutionManager(args) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: 9, + PrevHash: []byte("lastNotarizedExecResultHash"), + }, + Body: &block.Body{}, + } + + err := em.AddPairForExecution(pair) + require.ErrorIs(t, err, process.ErrMissingHeader) + require.Equal(t, 0, counter) + }) + + t.Run("if removing from pending execution results fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + counter := 0 + args := createMockArgs() + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV3{ + Nonce: 10, + } + }, + SetLastExecutedBlockHeaderAndRootHashCalled: func(header data.HeaderHandler, blockHash []byte, rootHash []byte) { + if bytes.Equal(blockHash, []byte("lastNotarizedExecResultHash")) { + counter += 1 + } + }, + SetLastExecutionResultCalled: func(result data.BaseExecutionResultHandler) { + if bytes.Equal(result.GetHeaderHash(), []byte("lastNotarizedExecResultHash")) { + counter += 1 + } + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + RootHash: []byte("rootHash"), + HeaderHash: []byte("lastExecResultHash"), + }, + } + }, + } + + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{}, nil + }, + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return &block.BaseExecutionResult{ + RootHash: []byte("rootHash"), + HeaderHash: []byte("lastNotarizedExecResultHash"), + }, nil + }, + RemoveFromNonceCalled: func(nonce uint64) error { + return errExpected + }, + } + + em, _ := executionManager.NewExecutionManager(args) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: 9, + PrevHash: []byte("lastNotarizedExecResultHash"), + }, + Body: &block.Body{}, + } + + err := em.AddPairForExecution(pair) + require.Equal(t, errExpected, err) + require.Equal(t, 2, counter) + }) + + t.Run("should work when the execution results of the previous header are notarized", func(t *testing.T) { + t.Parallel() + + counter := 0 + args := createMockArgs() + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV3{ + Nonce: 10, + } + }, + SetLastExecutedBlockHeaderAndRootHashCalled: func(header data.HeaderHandler, blockHash []byte, rootHash []byte) { + if bytes.Equal(blockHash, []byte("lastNotarizedExecResultHash")) { + counter += 1 + } + }, + SetLastExecutionResultCalled: func(result data.BaseExecutionResultHandler) { + if bytes.Equal(result.GetHeaderHash(), []byte("lastNotarizedExecResultHash")) { + counter += 1 + } + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + RootHash: []byte("rootHash"), + HeaderHash: []byte("lastExecResultHash"), + }, + } + }, + } + + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{}, nil + }, + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return &block.BaseExecutionResult{ + RootHash: []byte("rootHash"), + HeaderHash: []byte("lastNotarizedExecResultHash"), + }, nil + }, + RemoveFromNonceCalled: func(nonce uint64) error { + counter += 1 + return nil + }, + } + + em, _ := executionManager.NewExecutionManager(args) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: 9, + PrevHash: []byte("lastNotarizedExecResultHash"), + }, + Body: &block.Body{}, + } + + err := em.AddPairForExecution(pair) + require.Nil(t, err) + require.Equal(t, 3, counter) + }) + + t.Run("should work if there are pending execution results of previous header", func(t *testing.T) { + t.Parallel() + + counter := 0 + args := createMockArgs() + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV3{ + Nonce: 10, + } + }, + SetLastExecutedBlockHeaderAndRootHashCalled: func(header data.HeaderHandler, blockHash []byte, rootHash []byte) { + if bytes.Equal(blockHash, []byte("hashY")) { + counter += 1 + } + }, + SetLastExecutionResultCalled: func(result data.BaseExecutionResultHandler) { + if bytes.Equal(result.GetHeaderHash(), []byte("hashY")) { + counter += 1 + } + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + RootHash: []byte("rootHash"), + HeaderHash: []byte("lastExecResultHash"), + }, + } + }, + } + + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{ + &block.BaseExecutionResult{ + HeaderNonce: 7, + RootHash: []byte("rootHash"), + HeaderHash: []byte("hashX"), + }, + &block.BaseExecutionResult{ + HeaderNonce: 8, + RootHash: []byte("rootHash"), + HeaderHash: []byte("hashY"), + }, + }, nil + }, + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return &block.BaseExecutionResult{ + HeaderNonce: 6, + RootHash: []byte("rootHash"), + HeaderHash: []byte("lastNotarizedExecResultHash"), + }, nil + }, + RemoveFromNonceCalled: func(nonce uint64) error { + counter += 1 + return nil + }, + } + + em, _ := executionManager.NewExecutionManager(args) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: 9, + PrevHash: []byte("hashY"), + }, + Body: &block.Body{}, + } + + err := em.AddPairForExecution(pair) + require.Nil(t, err) + require.Equal(t, 3, counter) + }) +} + +func TestExecutionManager_GetPendingExecutionResults(t *testing.T) { + t.Parallel() + + args := createMockArgs() + expectedResults := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{}, + } + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return expectedResults, nil + }, + } + em, _ := executionManager.NewExecutionManager(args) + + results, err := em.GetPendingExecutionResults() + require.NoError(t, err) + require.Equal(t, expectedResults, results) +} + +func TestExecutionManager_SetLastNotarizedResult(t *testing.T) { + t.Parallel() + + args := createMockArgs() + setLastNotarizedCalled := false + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + SetLastNotarizedResultCalled: func(executionResult data.BaseExecutionResultHandler) error { + setLastNotarizedCalled = true + return nil + }, + } + em, _ := executionManager.NewExecutionManager(args) + + execResult := &block.ExecutionResult{} + err := em.SetLastNotarizedResult(execResult) + require.NoError(t, err) + require.True(t, setLastNotarizedCalled) +} + +func TestExecutionManager_CleanConfirmedExecutionResults(t *testing.T) { + t.Parallel() + + args := createMockArgs() + cleanCalled := false + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + CleanConfirmedExecutionResultsCalled: func(header data.HeaderHandler) error { + cleanCalled = true + return nil + }, + } + em, _ := executionManager.NewExecutionManager(args) + + header := &block.Header{Nonce: 1} + err := em.CleanConfirmedExecutionResults(header) + require.NoError(t, err) + require.True(t, cleanCalled) +} + +func TestExecutionManager_RemoveAtNonceAndHigher(t *testing.T) { + t.Parallel() + + t.Run("error getting last notarized result should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return nil, errExpected + }, + } + em, _ := executionManager.NewExecutionManager(args) + + err := em.RemoveAtNonceAndHigher(5) + require.Equal(t, errExpected, err) + }) + + t.Run("nonce lower than last notarized should adjust nonce", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + pauseCalled := false + resumeCalled := false + removeFromNonceCalled := false + + lastNotarizedExecResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 10, + HeaderHash: []byte("hash10"), + RootHash: []byte("root10"), + }, + } + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return lastNotarizedExecResult, nil + }, + RemoveFromNonceCalled: func(nonce uint64) error { + removeFromNonceCalled = true + require.Equal(t, uint64(11), nonce) + return nil + }, + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{}, nil + }, + } + args.BlocksCache = &processMocks.BlocksCacheMock{ + RemoveAtNonceAndHigherCalled: func(nonce uint64) []uint64 { + // Should be adjusted to lastNotarizedNonce + 1 = 11 + require.Equal(t, uint64(11), nonce) + return []uint64{11} + }, + } + header := &block.Header{Nonce: 10} + args.Headers = &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return header, nil + }, + } + chainMock := &testscommon.ChainHandlerMock{} + args.BlockChain = chainMock + + em, _ := executionManager.NewExecutionManager(args) + mockExecutor := &processMocks.HeadersExecutorMock{ + PauseExecutionCalled: func() { + pauseCalled = true + }, + ResumeExecutionCalled: func() { + resumeCalled = true + }, + } + _ = em.SetHeadersExecutor(mockExecutor) + + err := em.RemoveAtNonceAndHigher(5) // Lower than last notarized (10) + require.NoError(t, err) + require.True(t, pauseCalled) + require.True(t, resumeCalled) + require.True(t, removeFromNonceCalled) + + // Verify blockchain was updated to last notarized state + nonce, hash, rootHash := chainMock.GetFinalBlockInfo() + require.Equal(t, uint64(10), nonce) + require.Equal(t, []byte("hash10"), hash) + require.Equal(t, []byte("root10"), rootHash) + }) + + t.Run("nonce still in cache should still perform cleanup", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + pauseCalled := false + resumeCalled := false + removeFromNonceCalled := false + + lastNotarizedExecResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 9, + HeaderHash: []byte("hash9"), + RootHash: []byte("root9"), + }, + } + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return lastNotarizedExecResult, nil + }, + RemoveFromNonceCalled: func(nonce uint64) error { + removeFromNonceCalled = true + require.Equal(t, uint64(10), nonce) + return nil + }, + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{}, nil + }, + } + args.BlocksCache = &processMocks.BlocksCacheMock{ + RemoveAtNonceAndHigherCalled: func(nonce uint64) []uint64 { + require.Equal(t, uint64(10), nonce) + // First removed nonce matches the requested nonce (block still in cache) + return []uint64{10, 11, 12} + }, + } + header := &block.Header{Nonce: 9} + args.Headers = &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return header, nil + }, + } + chainMock := &testscommon.ChainHandlerMock{} + args.BlockChain = chainMock + + em, _ := executionManager.NewExecutionManager(args) + mockExecutor := &processMocks.HeadersExecutorMock{ + PauseExecutionCalled: func() { + pauseCalled = true + }, + ResumeExecutionCalled: func() { + resumeCalled = true + }, + } + _ = em.SetHeadersExecutor(mockExecutor) + + err := em.RemoveAtNonceAndHigher(10) + require.NoError(t, err) + require.True(t, pauseCalled) + require.True(t, resumeCalled) + require.True(t, removeFromNonceCalled) + + // Verify blockchain was updated to last notarized state + nonce, hash, rootHash := chainMock.GetFinalBlockInfo() + require.Equal(t, uint64(9), nonce) + require.Equal(t, []byte("hash9"), hash) + require.Equal(t, []byte("root9"), rootHash) + + retLastExecutionResult := chainMock.GetLastExecutionResult() + require.Equal(t, lastNotarizedExecResult, retLastExecutionResult) + }) + + t.Run("nonce already popped should clean tracker and update blockchain", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + pauseCalled := false + resumeCalled := false + removeFromNonceCalled := false + + lastNotarizedExecResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 5, + HeaderHash: []byte("hash5"), + RootHash: []byte("root5"), + }, + } + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return lastNotarizedExecResult, nil + }, + RemoveFromNonceCalled: func(nonce uint64) error { + removeFromNonceCalled = true + require.Equal(t, uint64(10), nonce) + return nil + }, + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{}, nil + }, + } + args.BlocksCache = &processMocks.BlocksCacheMock{ + RemoveAtNonceAndHigherCalled: func(nonce uint64) []uint64 { + require.Equal(t, uint64(10), nonce) + // First removed nonce does NOT match the requested nonce + return []uint64{11, 12} + }, + } + header := &block.Header{Nonce: 5} + args.Headers = &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return header, nil + }, + } + chainMock := &testscommon.ChainHandlerMock{} + args.BlockChain = chainMock + + em, _ := executionManager.NewExecutionManager(args) + mockExecutor := &processMocks.HeadersExecutorMock{ + PauseExecutionCalled: func() { + pauseCalled = true + }, + ResumeExecutionCalled: func() { + resumeCalled = true + }, + } + _ = em.SetHeadersExecutor(mockExecutor) + + err := em.RemoveAtNonceAndHigher(10) + require.NoError(t, err) + require.True(t, pauseCalled) + require.True(t, resumeCalled) + require.True(t, removeFromNonceCalled) + + // Verify blockchain was updated + nonce, hash, rootHash := chainMock.GetFinalBlockInfo() + require.Equal(t, uint64(5), nonce) + require.Equal(t, []byte("hash5"), hash) + require.Equal(t, []byte("root5"), rootHash) + + retLastExecutionResult := chainMock.GetLastExecutionResult() + require.Equal(t, lastNotarizedExecResult, retLastExecutionResult) + }) + + t.Run("nonce in cache and already processed should clean tracker and update blockchain", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + pauseCalled := false + resumeCalled := false + removeFromNonceCalled := false + + // Block at nonce 10 was processed, execution result exists as pending + pendingExecResult := &block.BaseExecutionResult{ + HeaderNonce: 10, + HeaderHash: []byte("hash10"), + RootHash: []byte("root10"), + } + lastNotarizedExecResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 9, + HeaderHash: []byte("hash9"), + RootHash: []byte("root9"), + }, + } + removePendingCalled := false + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return lastNotarizedExecResult, nil + }, + RemoveFromNonceCalled: func(nonce uint64) error { + removeFromNonceCalled = true + require.Equal(t, uint64(10), nonce) + removePendingCalled = true + return nil + }, + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + if removePendingCalled { + // after RemoveFromNonce, pending is empty + return []data.BaseExecutionResultHandler{}, nil + } + // before removal, pending has the processed result + return []data.BaseExecutionResultHandler{pendingExecResult}, nil + }, + } + args.BlocksCache = &processMocks.BlocksCacheMock{ + RemoveAtNonceAndHigherCalled: func(nonce uint64) []uint64 { + require.Equal(t, uint64(10), nonce) + // Block was still in cache but was already processed by headersExecutor + return []uint64{10} + }, + } + header := &block.Header{Nonce: 9} + args.Headers = &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return header, nil + }, + } + chainMock := &testscommon.ChainHandlerMock{} + args.BlockChain = chainMock + + em, _ := executionManager.NewExecutionManager(args) + mockExecutor := &processMocks.HeadersExecutorMock{ + PauseExecutionCalled: func() { + pauseCalled = true + }, + ResumeExecutionCalled: func() { + resumeCalled = true + }, + } + _ = em.SetHeadersExecutor(mockExecutor) + + err := em.RemoveAtNonceAndHigher(10) + require.NoError(t, err) + require.True(t, pauseCalled) + require.True(t, resumeCalled) + require.True(t, removeFromNonceCalled) + + // Verify blockchain was rolled back to last notarized state + nonce, hash, rootHash := chainMock.GetFinalBlockInfo() + require.Equal(t, uint64(9), nonce) + require.Equal(t, []byte("hash9"), hash) + require.Equal(t, []byte("root9"), rootHash) + + retLastExecutionResult := chainMock.GetLastExecutionResult() + require.Equal(t, lastNotarizedExecResult, retLastExecutionResult) + }) + + t.Run("error from tracker remove should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 5, + }, + }, nil + }, + RemoveFromNonceCalled: func(nonce uint64) error { + return errExpected + }, + } + args.BlocksCache = &processMocks.BlocksCacheMock{ + RemoveAtNonceAndHigherCalled: func(nonce uint64) []uint64 { + // First removed nonce does NOT match + return []uint64{11, 12} + }, + } + em, _ := executionManager.NewExecutionManager(args) + + err := em.RemoveAtNonceAndHigher(10) + require.Equal(t, errExpected, err) + }) + + t.Run("error getting header from pool should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 5, + HeaderHash: []byte("hash5"), + }, + }, nil + }, + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{}, nil + }, + } + args.BlocksCache = &processMocks.BlocksCacheMock{ + RemoveAtNonceAndHigherCalled: func(nonce uint64) []uint64 { + return []uint64{11, 12} + }, + } + args.Headers = &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, errExpected + }, + } + args.StorageService = &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + return nil, errExpected + }, + }, nil + }, + } + args.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV3{ + Nonce: 10, + } + }, + } + + em, _ := executionManager.NewExecutionManager(args) + + err := em.RemoveAtNonceAndHigher(10) + require.ErrorIs(t, err, process.ErrMissingHeader) + }) + + t.Run("with pending execution results should use last pending", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + + lastExecutionResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 7, + HeaderHash: []byte("hash7"), + RootHash: []byte("root7"), + }, + } + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 5, + HeaderHash: []byte("hash5"), + RootHash: []byte("root5"), + }, + }, nil + }, + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 6, + HeaderHash: []byte("hash6"), + RootHash: []byte("root6"), + }, + }, + lastExecutionResult, + }, nil + }, + } + args.BlocksCache = &processMocks.BlocksCacheMock{ + RemoveAtNonceAndHigherCalled: func(nonce uint64) []uint64 { + return []uint64{11, 12} + }, + } + header := &block.Header{Nonce: 7} + args.Headers = &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + require.Equal(t, []byte("hash7"), hash) + return header, nil + }, + } + chainMock := &testscommon.ChainHandlerMock{} + args.BlockChain = chainMock + + em, _ := executionManager.NewExecutionManager(args) + + err := em.RemoveAtNonceAndHigher(10) + require.NoError(t, err) + + // Verify blockchain was updated with last pending + nonce, hash, rootHash := chainMock.GetFinalBlockInfo() + require.Equal(t, uint64(7), nonce) + require.Equal(t, []byte("hash7"), hash) + require.Equal(t, []byte("root7"), rootHash) + + lastExecHeader := chainMock.GetLastExecutedBlockHeader() + require.Equal(t, header, lastExecHeader) + + retLastExecutionResult := chainMock.GetLastExecutionResult() + require.Equal(t, lastExecutionResult, retLastExecutionResult) + }) + + t.Run("error getting pending execution results should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 5, + HeaderHash: []byte("hash5"), + }, + }, nil + }, + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return nil, errExpected + }, + } + args.BlocksCache = &processMocks.BlocksCacheMock{ + RemoveAtNonceAndHigherCalled: func(nonce uint64) []uint64 { + // First removed nonce does NOT match + return []uint64{11, 12} + }, + } + em, _ := executionManager.NewExecutionManager(args) + wasPauseExecutionCalled := false + wasResumeExecutionCalled := false + err := em.SetHeadersExecutor(&processMocks.HeadersExecutorMock{ + PauseExecutionCalled: func() { + wasPauseExecutionCalled = true + }, + ResumeExecutionCalled: func() { + wasResumeExecutionCalled = true + }, + }) + require.NoError(t, err) + + err = em.RemoveAtNonceAndHigher(10) + require.Equal(t, errExpected, err) + require.True(t, wasPauseExecutionCalled) + require.False(t, wasResumeExecutionCalled) + }) +} + +func TestExecutionManager_ResetAndResumeExecution(t *testing.T) { + t.Parallel() + + t.Run("nil last execution result should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + em, _ := executionManager.NewExecutionManager(args) + require.NotNil(t, em) + + err := em.ResetAndResumeExecution(nil) + require.Equal(t, process.ErrNilLastExecutionResultHandler, err) + }) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 5, + HeaderHash: []byte("hash5"), + }, + }, nil + }, + } + em, _ := executionManager.NewExecutionManager(args) + wasResumeExecutionCalled := false + err := em.SetHeadersExecutor(&processMocks.HeadersExecutorMock{ + ResumeExecutionCalled: func() { + wasResumeExecutionCalled = true + }, + }) + require.NoError(t, err) + + err = em.ResetAndResumeExecution(&block.BaseExecutionResult{}) + require.NoError(t, err) + require.True(t, wasResumeExecutionCalled) + }) +} + +func TestExecutionManager_Close(t *testing.T) { + t.Parallel() + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + executorCloseCalled := false + + mockExecutor := &processMocks.HeadersExecutorMock{ + CloseCalled: func() error { + executorCloseCalled = true + return nil + }, + } + em, _ := executionManager.NewExecutionManager(args) + _ = em.SetHeadersExecutor(mockExecutor) + + err := em.Close() + require.NoError(t, err) + require.True(t, executorCloseCalled) + }) + + t.Run("error closing headers executor", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + mockExecutor := &processMocks.HeadersExecutorMock{ + CloseCalled: func() error { + return errExpected + }, + } + em, _ := executionManager.NewExecutionManager(args) + _ = em.SetHeadersExecutor(mockExecutor) + + err := em.Close() + require.NoError(t, err) + }) +} + +func TestExecutionManager_Concurrency(t *testing.T) { + require.NotPanics(t, func() { + t.Parallel() + + args := createMockArgs() + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{}, nil + }, + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return &block.ExecutionResult{}, nil + }, + } + args.BlockChain = &testscommon.ChainHandlerStub{} + em, _ := executionManager.NewExecutionManager(args) + + const numCalls = 100 + wg := sync.WaitGroup{} + wg.Add(numCalls) + + for i := 0; i < numCalls; i++ { + go func(idx int) { + defer wg.Done() + + switch idx % 6 { + case 0: + pair := cache.HeaderBodyPair{ + Header: &block.HeaderV3{Nonce: uint64(idx)}, + Body: &block.Body{}, + } + _ = em.AddPairForExecution(pair) + case 1: + _ = em.RemoveAtNonceAndHigher(uint64(idx)) + case 2: + _, _ = em.GetPendingExecutionResults() + case 3: + _ = em.SetHeadersExecutor(&processMocks.HeadersExecutorMock{}) + case 4: + _ = em.SetLastNotarizedResult(&block.BaseExecutionResult{}) + case 5: + _ = em.CleanConfirmedExecutionResults(&block.HeaderV3{Nonce: uint64(idx)}) + case 6: + require.Fail(t, "should not happen") + } + }(i) + } + + wg.Wait() + }) +} + +func TestExecutionManager_CleanOnConsensusReached(t *testing.T) { + t.Parallel() + + t.Run("should call tracker and remove higher nonce blocks from cache", func(t *testing.T) { + t.Parallel() + + trackerCalled := false + var trackerHash []byte + var trackerNonce uint64 + var removedNonce uint64 + + args := createMockArgs() + args.ExecutionResultsTracker = &processMocks.ExecutionTrackerStub{ + CleanOnConsensusReachedCalled: func(headerHash []byte, header data.HeaderHandler) { + trackerCalled = true + trackerHash = headerHash + trackerNonce = header.GetNonce() + }, + } + args.BlocksCache = &processMocks.BlocksCacheMock{ + RemoveAtNonceAndHigherCalled: func(nonce uint64) []uint64 { + removedNonce = nonce + return []uint64{} + }, + } + + em, err := executionManager.NewExecutionManager(args) + require.NoError(t, err) + + committedNonce := uint64(10) + committedHash := []byte("committedHash") + em.CleanOnConsensusReached(committedHash, &block.HeaderV3{Nonce: committedNonce}) + + require.True(t, trackerCalled) + require.Equal(t, committedHash, trackerHash) + require.Equal(t, committedNonce, trackerNonce) + require.Equal(t, committedNonce+1, removedNonce) + }) +} diff --git a/process/asyncExecution/executionTrack/error.go b/process/asyncExecution/executionTrack/error.go new file mode 100644 index 00000000000..4fc97f3a5b7 --- /dev/null +++ b/process/asyncExecution/executionTrack/error.go @@ -0,0 +1,23 @@ +package executionTrack + +import "errors" + +var ( + // ErrNilExecutionResult signals that a nil execution results has been provided + ErrNilExecutionResult = errors.New("nil execution result") + + // ErrDifferentNoncesConfirmedExecutionResults signals that the confirmed execution results have different nonces + ErrDifferentNoncesConfirmedExecutionResults = errors.New("confirmed execution results have different nonces") + + // ErrCannotFindExecutionResult signals that execution result is not found + ErrCannotFindExecutionResult = errors.New("cannot find execution result") + + // ErrWrongExecutionResultNonce signals that the nonce of execution result is wrong + ErrWrongExecutionResultNonce = errors.New("wrong execution result nonce") + + // ErrNilLastNotarizedExecutionResult signals that last notarized execution result is nil + ErrNilLastNotarizedExecutionResult = errors.New("nil last notarized execution result") + + // ErrExecutionResultMismatch signals an execution result mismatch + ErrExecutionResultMismatch = errors.New("execution result mismatch") +) diff --git a/process/asyncExecution/executionTrack/executionResultsTracker.go b/process/asyncExecution/executionTrack/executionResultsTracker.go new file mode 100644 index 00000000000..bc1e7e7d06b --- /dev/null +++ b/process/asyncExecution/executionTrack/executionResultsTracker.go @@ -0,0 +1,436 @@ +package executionTrack + +import ( + "bytes" + "encoding/hex" + "fmt" + "sort" + "sync" + + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + logger "github.com/multiversx/mx-chain-logger-go" +) + +var log = logger.GetOrCreate("process/asyncExecution/executionTrack") + +const maxDismissedBatches = 100 + +// DismissedBatch holds a group of dismissed execution results alongside the anchor +// result that preceded them. The anchor's root hash is the "previous root hash" +// for the first dismissed result's EWL OldRoot entry. +type DismissedBatch struct { + AnchorResult data.BaseExecutionResultHandler + Results []data.BaseExecutionResultHandler +} + +type executionResultsTracker struct { + lastNotarizedResult data.BaseExecutionResultHandler + mutex sync.RWMutex + executionResultsByHash map[string]data.BaseExecutionResultHandler + nonceHash *nonceHash + lastExecutedResultHash []byte + consensusCommittedHashes map[uint64][]byte // tracks which hash was committed by consensus for each nonce + dismissedBatches []DismissedBatch +} + +// NewExecutionResultsTracker will create a new instance of *executionResultsTracker +func NewExecutionResultsTracker() *executionResultsTracker { + return &executionResultsTracker{ + executionResultsByHash: make(map[string]data.BaseExecutionResultHandler), + nonceHash: newNonceHash(), + consensusCommittedHashes: make(map[uint64][]byte), + } +} + +// AddExecutionResult will add the provided execution result in tracker. +// It returns true if the execution result was added, false if it was rejected +// because consensus already committed a different block for this nonce. +func (ert *executionResultsTracker) AddExecutionResult(executionResult data.BaseExecutionResultHandler) (bool, error) { + if executionResult == nil { + return false, ErrNilExecutionResult + } + + ert.mutex.Lock() + defer ert.mutex.Unlock() + if ert.lastNotarizedResult == nil { + return false, ErrNilLastNotarizedExecutionResult + } + + if ert.lastNotarizedResult.GetHeaderNonce() >= executionResult.GetHeaderNonce() { + return false, fmt.Errorf("%w nonce(%d) is lower than last notarized nonce(%d)", ErrWrongExecutionResultNonce, executionResult.GetHeaderNonce(), ert.lastNotarizedResult.GetHeaderNonce()) + } + // Check if consensus already committed a different block for this nonce + committedHash, hasCommitted := ert.consensusCommittedHashes[executionResult.GetHeaderNonce()] + if hasCommitted && !bytes.Equal(committedHash, executionResult.GetHeaderHash()) { + log.Debug("AddExecutionResult: rejecting result because consensus committed different block", + "nonce", executionResult.GetHeaderNonce(), + "result_hash", executionResult.GetHeaderHash(), + "committed_hash", committedHash, + ) + return false, nil // Reject without error - this is expected in race scenarios + } + + lastExecutedResult, err := ert.getLastExecutionResult() + if err != nil { + return false, err + } + + last := lastExecutedResult.GetHeaderNonce() + current := executionResult.GetHeaderNonce() + isNextOrSameNonce := current == last || current == last+1 + if !isNextOrSameNonce { + return false, fmt.Errorf("%w nonce(%d) should be equal to the subsequent nonce after last executed(%d)", ErrWrongExecutionResultNonce, executionResult.GetHeaderNonce(), lastExecutedResult.GetHeaderNonce()) + } + + executionResultByHash := ert.nonceHash.getHashByNonce(executionResult.GetHeaderNonce()) + if len(executionResultByHash) > 0 { + delete(ert.executionResultsByHash, executionResultByHash) + } + + ert.executionResultsByHash[string(executionResult.GetHeaderHash())] = executionResult + ert.nonceHash.addNonceHash(executionResult.GetHeaderNonce(), string(executionResult.GetHeaderHash())) + + ert.lastExecutedResultHash = executionResult.GetHeaderHash() + + return true, nil +} + +func (ert *executionResultsTracker) getLastExecutionResult() (data.BaseExecutionResultHandler, error) { + if ert.lastNotarizedResult == nil { + return nil, ErrNilLastNotarizedExecutionResult + } + + if ert.lastExecutedResultHash == nil { + return nil, fmt.Errorf("%w last executed result hash is not set", ErrCannotFindExecutionResult) + } + + if bytes.Equal(ert.lastExecutedResultHash, ert.lastNotarizedResult.GetHeaderHash()) { + return ert.lastNotarizedResult, nil + } + + lastExecutedResults, found := ert.executionResultsByHash[string(ert.lastExecutedResultHash)] + if !found { + return nil, fmt.Errorf("%w hash(%s)", ErrCannotFindExecutionResult, ert.lastExecutedResultHash) + } + + return lastExecutedResults, nil +} + +// GetPendingExecutionResults will return the pending execution results +func (ert *executionResultsTracker) GetPendingExecutionResults() ([]data.BaseExecutionResultHandler, error) { + ert.mutex.RLock() + defer ert.mutex.RUnlock() + + return ert.getPendingExecutionResults() +} + +func (ert *executionResultsTracker) getPendingExecutionResults() ([]data.BaseExecutionResultHandler, error) { + executionResults := make([]data.BaseExecutionResultHandler, 0, len(ert.executionResultsByHash)) + for _, executionResult := range ert.executionResultsByHash { + executionResults = append(executionResults, executionResult) + } + + if len(executionResults) == 0 { + return executionResults, nil + } + + sort.Slice(executionResults, func(i, j int) bool { + return executionResults[i].GetHeaderNonce() < executionResults[j].GetHeaderNonce() + }) + + firstElementHasCorrectNonce := executionResults[0].GetHeaderNonce() == ert.lastNotarizedResult.GetHeaderNonce()+1 + if !firstElementHasCorrectNonce { + return nil, ErrDifferentNoncesConfirmedExecutionResults + } + + for idx := 0; idx < len(executionResults)-1; idx++ { + hasConsecutiveNonces := executionResults[idx].GetHeaderNonce()+1 == executionResults[idx+1].GetHeaderNonce() + if !hasConsecutiveNonces { + return nil, ErrDifferentNoncesConfirmedExecutionResults + } + } + + return executionResults, nil +} + +// GetPendingExecutionResultByHash will return the execution results by hash +func (ert *executionResultsTracker) GetPendingExecutionResultByHash(hash []byte) (data.BaseExecutionResultHandler, error) { + ert.mutex.RLock() + defer ert.mutex.RUnlock() + + result, found := ert.executionResultsByHash[string(hash)] + if !found { + return nil, fmt.Errorf("%w with hash: '%s'", ErrCannotFindExecutionResult, hex.EncodeToString(hash)) + } + + return result, nil +} + +// GetPendingExecutionResultByNonce will return the execution results by nonce +func (ert *executionResultsTracker) GetPendingExecutionResultByNonce(nonce uint64) (data.BaseExecutionResultHandler, error) { + ert.mutex.RLock() + defer ert.mutex.RUnlock() + + return ert.getPendingExecutionResultsByNonce(nonce) +} + +func (ert *executionResultsTracker) getPendingExecutionResultsByNonce(nonce uint64) (data.BaseExecutionResultHandler, error) { + hash := ert.nonceHash.getHashByNonce(nonce) + result, found := ert.executionResultsByHash[hash] + if !found { + return nil, fmt.Errorf("%w with hash: '%s'", ErrCannotFindExecutionResult, hex.EncodeToString([]byte(hash))) + } + + return result, nil +} + +// CleanConfirmedExecutionResults will clean the confirmed execution results +func (ert *executionResultsTracker) CleanConfirmedExecutionResults(header data.HeaderHandler) error { + ert.mutex.Lock() + defer ert.mutex.Unlock() + + headerExecutionResults := header.GetExecutionResultsHandlers() + + return ert.cleanConfirmedExecutionResults(headerExecutionResults) +} + +func (ert *executionResultsTracker) cleanConfirmedExecutionResults(headerExecutionResults []data.BaseExecutionResultHandler) error { + if len(headerExecutionResults) == 0 { + return nil + } + + pendingExecutionResult, err := ert.getPendingExecutionResults() + if err != nil { + return err + } + + lastMatchingResultNonce := ert.lastNotarizedResult.GetHeaderNonce() + lastMatchingHash := ert.lastNotarizedResult.GetHeaderHash() + for idx, executionResultFromHeader := range headerExecutionResults { + if idx >= len(pendingExecutionResult) { + // missing execution result + return fmt.Errorf("%w, executon result nod found in peding executon results, last matching result nonce: %d", ErrCannotFindExecutionResult, lastMatchingResultNonce) + } + + executionResultFromTracker := pendingExecutionResult[idx] + sameHash := string(executionResultFromTracker.GetHeaderHash()) == string(executionResultFromHeader.GetHeaderHash()) + sameNonce := executionResultFromTracker.GetHeaderNonce() == executionResultFromHeader.GetHeaderNonce() + sameRound := executionResultFromTracker.GetHeaderRound() == executionResultFromHeader.GetHeaderRound() + sameRootHash := string(executionResultFromTracker.GetRootHash()) == string(executionResultFromHeader.GetRootHash()) + areEqual := sameHash && sameNonce && sameRound && sameRootHash + if !areEqual { + ert.lastExecutedResultHash = lastMatchingHash + + // Compute anchor for dismissed batch + var anchor data.BaseExecutionResultHandler + if idx > 0 { + anchor = pendingExecutionResult[idx-1] + } else { + anchor = ert.lastNotarizedResult + } + ert.addDismissedBatch(DismissedBatch{ + AnchorResult: anchor, + Results: pendingExecutionResult[idx:], + }) + + // different execution result should clean everything starting from this execution result and return CleanResultMismatch + ert.cleanExecutionResults(pendingExecutionResult[idx:]) + + return fmt.Errorf("%w, last matching result nonce: %d", ErrExecutionResultMismatch, lastMatchingResultNonce) + } + + lastMatchingResultNonce = executionResultFromHeader.GetHeaderNonce() + lastMatchingHash = executionResultFromHeader.GetHeaderHash() + } + + ert.cleanExecutionResults(headerExecutionResults) + ert.lastNotarizedResult = headerExecutionResults[len(headerExecutionResults)-1] + + return nil +} + +func (ert *executionResultsTracker) cleanExecutionResults(executionResult []data.BaseExecutionResultHandler) { + ert.removeExecutionResultsFromMaps(executionResult) + for _, result := range executionResult { + delete(ert.consensusCommittedHashes, result.GetHeaderNonce()) + } +} + +// removeExecutionResultsFromMaps removes execution results from the internal maps +// but preserves consensusCommittedHashes entries to continue blocking stale results +func (ert *executionResultsTracker) removeExecutionResultsFromMaps(executionResult []data.BaseExecutionResultHandler) { + for _, result := range executionResult { + delete(ert.executionResultsByHash, string(result.GetHeaderHash())) + ert.nonceHash.removeByNonce(result.GetHeaderNonce()) + } +} + +// CleanOnConsensusReached will clean the execution results tracker when consensus is reached +// If the pending execution result for the given nonce has a different hash than the one that +// passed consensus, remove it and all higher nonces from the tracker. +// It also records the committed hash to prevent stale results from being added later. +func (ert *executionResultsTracker) CleanOnConsensusReached(headerHash []byte, header data.HeaderHandler) { + if check.IfNil(header) { + return + } + + ert.mutex.Lock() + defer ert.mutex.Unlock() + + if header.IsHeaderV3() { + // record the committed hash to prevent stale results being added later + ert.consensusCommittedHashes[header.GetNonce()] = headerHash + } + + pendingExecutionResult, err := ert.getPendingExecutionResultsByNonce(header.GetNonce()) + if err != nil { + return + } + + if bytes.Equal(pendingExecutionResult.GetHeaderHash(), headerHash) { + return + } + + _ = ert.removePendingFromNonceUnprotected(header.GetNonce()) +} + +// GetLastNotarizedExecutionResult will return the last notarized execution result +func (ert *executionResultsTracker) GetLastNotarizedExecutionResult() (data.BaseExecutionResultHandler, error) { + ert.mutex.RLock() + defer ert.mutex.RUnlock() + if ert.lastNotarizedResult == nil { + return nil, ErrNilLastNotarizedExecutionResult + } + + return ert.lastNotarizedResult, nil +} + +// SetLastNotarizedResult will set the last notarized execution result +func (ert *executionResultsTracker) SetLastNotarizedResult(executionResult data.BaseExecutionResultHandler) error { + if executionResult == nil { + return ErrNilExecutionResult + } + + ert.mutex.Lock() + ert.lastNotarizedResult = executionResult + ert.lastExecutedResultHash = executionResult.GetHeaderHash() + ert.mutex.Unlock() + + return nil +} + +// RemoveFromNonce will remove the execution result with the provided nonce and all execution results with higher nonces +func (ert *executionResultsTracker) RemoveFromNonce(nonce uint64) error { + ert.mutex.Lock() + defer ert.mutex.Unlock() + + return ert.removePendingFromNonceUnprotected(nonce) +} + +// Clean cleans the execution results tracker and sets its state to the last notarized result provided +func (ert *executionResultsTracker) Clean(lastNotarizedResult data.BaseExecutionResultHandler) { + if check.IfNil(lastNotarizedResult) { + return + } + + ert.mutex.Lock() + defer ert.mutex.Unlock() + + pending, _ := ert.getPendingExecutionResults() + if len(pending) > 0 { + ert.addDismissedBatch(DismissedBatch{ + AnchorResult: ert.lastNotarizedResult, + Results: pending, + }) + } + + ert.executionResultsByHash = make(map[string]data.BaseExecutionResultHandler) + ert.nonceHash = newNonceHash() + + ert.lastNotarizedResult = lastNotarizedResult + ert.lastExecutedResultHash = lastNotarizedResult.GetHeaderHash() +} + +func (ert *executionResultsTracker) removePendingFromNonceUnprotected(nonce uint64) error { + pendingExecutionResult, err := ert.getPendingExecutionResults() + if err != nil { + return err + } + + // find all execution results with nonce >= nonceToRemove + var resultsToRemove []data.BaseExecutionResultHandler + for _, result := range pendingExecutionResult { + resultNonce := result.GetHeaderNonce() + if resultNonce < nonce { + continue + } + + resultsToRemove = append(resultsToRemove, result) + } + + if len(resultsToRemove) == 0 { + return nil + } + + // Compute anchor: last pending result before the dismissal point + var anchor data.BaseExecutionResultHandler + for _, result := range pendingExecutionResult { + if result.GetHeaderNonce() < nonce { + anchor = result + } + } + if anchor == nil { + anchor = ert.lastNotarizedResult + } + ert.addDismissedBatch(DismissedBatch{ + AnchorResult: anchor, + Results: resultsToRemove, + }) + + // Remove from executionResultsByHash and nonceHash, but preserve consensusCommittedHashes + // to continue blocking stale results from being added for these nonces + ert.removeExecutionResultsFromMaps(resultsToRemove) + + pendingExecutionResults, err := ert.getPendingExecutionResults() + if err != nil { + return err + } + + if len(pendingExecutionResults) > 0 { + lastPendingExecResult := pendingExecutionResults[len(pendingExecutionResults)-1] + ert.lastExecutedResultHash = lastPendingExecResult.GetHeaderHash() + + return nil + } + + // if no pending left, set the last executed as the last notarized + ert.lastExecutedResultHash = ert.lastNotarizedResult.GetHeaderHash() + + return nil +} + +func (ert *executionResultsTracker) addDismissedBatch(batch DismissedBatch) { + if len(ert.dismissedBatches) >= maxDismissedBatches { + log.Warn("dismissed batches queue is full, dropping oldest batch", + "maxDismissedBatches", maxDismissedBatches, + "droppedResults", len(ert.dismissedBatches[0].Results), + ) + ert.dismissedBatches = ert.dismissedBatches[1:] + } + ert.dismissedBatches = append(ert.dismissedBatches, batch) +} + +// PopDismissedResults returns all batches of dismissed execution results and clears the internal queue +func (ert *executionResultsTracker) PopDismissedResults() []DismissedBatch { + ert.mutex.Lock() + defer ert.mutex.Unlock() + batches := ert.dismissedBatches + ert.dismissedBatches = nil + return batches +} + +// IsInterfaceNil returns true if there is no value under the interface +func (ert *executionResultsTracker) IsInterfaceNil() bool { + return ert == nil +} diff --git a/process/asyncExecution/executionTrack/executionResultsTracker_test.go b/process/asyncExecution/executionTrack/executionResultsTracker_test.go new file mode 100644 index 00000000000..9de89760a5d --- /dev/null +++ b/process/asyncExecution/executionTrack/executionResultsTracker_test.go @@ -0,0 +1,1060 @@ +package executionTrack + +import ( + "errors" + "fmt" + "testing" + + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/stretchr/testify/require" +) + +func TestNewExecutionResultsTracker(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + require.False(t, tracker.IsInterfaceNil()) +} + +func TestSetAndGetLastNotarizedResult(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + + _, err := tracker.GetLastNotarizedExecutionResult() + require.Equal(t, ErrNilLastNotarizedExecutionResult, err) + + err = tracker.SetLastNotarizedResult(nil) + require.Equal(t, ErrNilExecutionResult, err) + + execResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 10, + }, + } + err = tracker.SetLastNotarizedResult(execResult) + require.NoError(t, err) + + lastNotarizedResult, err := tracker.GetLastNotarizedExecutionResult() + require.NoError(t, err) + require.Equal(t, execResult, lastNotarizedResult) +} + +func TestAddExecutionResult_AllBranches(t *testing.T) { + t.Parallel() + + t.Run("nil execution result", func(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + + added, err := tracker.AddExecutionResult(nil) + require.False(t, added) + require.ErrorIs(t, err, ErrNilExecutionResult) + }) + + t.Run("nil last notarized result", func(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + + execResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), HeaderNonce: 1, + }, + } + added, err := tracker.AddExecutionResult(execResult) + require.False(t, added) + require.ErrorIs(t, err, ErrNilLastNotarizedExecutionResult) + }) + + t.Run("execution result nonce lower than last notarized", func(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + tracker.lastNotarizedResult = &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 10, + }, + } + + execResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash2"), + HeaderNonce: 9, + }, + } + added, err := tracker.AddExecutionResult(execResult) + require.False(t, added) + require.Error(t, err) + require.True(t, errors.Is(err, ErrWrongExecutionResultNonce)) + require.Contains(t, err.Error(), "is lower than last notarized nonce") + }) + + t.Run("execution result nonce not equal to the subsequent nonce after last executed", func(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + _ = tracker.SetLastNotarizedResult(&block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 10, + HeaderHash: []byte("hh"), + }, + }) + + execResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash3"), + HeaderNonce: 12, + }, + } + added, err := tracker.AddExecutionResult(execResult) + require.False(t, added) + require.Error(t, err) + require.True(t, errors.Is(err, ErrWrongExecutionResultNonce)) + require.Contains(t, err.Error(), "should be equal to the subsequent nonce after last executed") + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + _ = tracker.SetLastNotarizedResult(&block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hh"), + HeaderNonce: 10, + }, + }) + + execResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash3"), + HeaderNonce: 11, + }, + } + added, err := tracker.AddExecutionResult(execResult) + require.True(t, added) + require.Nil(t, err) + }) + + t.Run("cannot find execution result", func(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + tracker.lastNotarizedResult = &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 10, + }, + } + + execResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash3"), + HeaderNonce: 13, + }, + } + tracker.lastExecutedResultHash = []byte("h1") + added, err := tracker.AddExecutionResult(execResult) + require.False(t, added) + require.Error(t, err) + require.True(t, errors.Is(err, ErrCannotFindExecutionResult)) + }) +} + +func TestAddExecutionResultAndCleanShouldWork(t *testing.T) { + t.Parallel() + + t.Run("header with no execution results", func(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + err := tracker.SetLastNotarizedResult(&block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), + HeaderNonce: 10, + }, + }) + require.Nil(t, err) + + header := &block.HeaderV3{} + errC := tracker.CleanConfirmedExecutionResults(header) + require.Nil(t, errC) + }) + + t.Run("header with 2 execution results", func(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + err := tracker.SetLastNotarizedResult(&block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), + HeaderNonce: 10, + }, + }) + require.Nil(t, err) + + executionResults := []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash2"), + HeaderNonce: 11, + }, + }, + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash3"), + HeaderNonce: 12, + }, + }, + } + + header := &block.HeaderV3{ + ExecutionResults: executionResults, + } + + _, err = tracker.AddExecutionResult(executionResults[0]) + require.Nil(t, err) + _, err = tracker.AddExecutionResult(executionResults[1]) + require.Nil(t, err) + + errC := tracker.CleanConfirmedExecutionResults(header) + require.Nil(t, errC) + + results, errG := tracker.GetPendingExecutionResults() + require.Nil(t, errG) + require.Equal(t, 0, len(results)) + }) + + t.Run("clean result not found", func(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + err := tracker.SetLastNotarizedResult(&block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), + HeaderNonce: 10, + }, + }) + require.Nil(t, err) + + header := &block.HeaderV3{ + ExecutionResults: []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash22"), + HeaderNonce: 22, + }, + }, + }, + } + + errC := tracker.CleanConfirmedExecutionResults(header) + require.True(t, errors.Is(errC, ErrCannotFindExecutionResult)) + }) +} + +func TestAddExecutionResultAndCleanDifferentResultsFromHeader(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + + err := tracker.SetLastNotarizedResult(&block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash0"), + HeaderNonce: 10, + }, + }) + require.Nil(t, err) + + executionResult1 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), + HeaderNonce: 11, + }, + } + _, err = tracker.AddExecutionResult(executionResult1) + require.Nil(t, err) + _, err = tracker.AddExecutionResult(&block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash2"), + HeaderNonce: 12, + }, + }) + require.Nil(t, err) + _, err = tracker.AddExecutionResult(&block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash3"), + HeaderNonce: 13, + }, + }) + require.Nil(t, err) + + header := &block.HeaderV3{ + ExecutionResults: []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), + HeaderNonce: 11, + }, + }, + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("different"), + HeaderNonce: 12, + }, + }, + }, + } + + err = tracker.CleanConfirmedExecutionResults(header) + require.True(t, errors.Is(err, ErrExecutionResultMismatch)) + + // check that everything before the mismatch was kept inside tracker + results, err := tracker.GetPendingExecutionResults() + require.Nil(t, err) + require.Equal(t, 1, len(results)) + require.Equal(t, executionResult1, results[0]) +} + +func TestExecutionResultsTracker_GetPendingExecutionResultByHashAndHash(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + + err := tracker.SetLastNotarizedResult(&block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash0"), + HeaderNonce: 10, + }, + }) + require.Nil(t, err) + + executionResult1 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), + HeaderNonce: 11, + }, + } + _, err = tracker.AddExecutionResult(executionResult1) + require.Nil(t, err) + + res, err := tracker.GetPendingExecutionResultByHash([]byte("hh")) + require.Nil(t, res) + require.True(t, errors.Is(err, ErrCannotFindExecutionResult)) + + res, err = tracker.GetPendingExecutionResultByHash([]byte("hash1")) + require.Nil(t, err) + require.Equal(t, executionResult1, res) + + res, err = tracker.GetPendingExecutionResultByNonce(10) + require.Nil(t, res) + require.True(t, errors.Is(err, ErrCannotFindExecutionResult)) + + res, err = tracker.GetPendingExecutionResultByNonce(11) + require.Nil(t, err) + require.Equal(t, executionResult1, res) +} + +func TestExecutionResultsTracker_RemoveFromNonce(t *testing.T) { + t.Parallel() + + t.Run("getPendingExecutionResults error should error", func(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + err := tracker.SetLastNotarizedResult(&block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash0"), + HeaderNonce: 10, + }, + }) + require.Nil(t, err) + + // Add execution result with nonce 12 (skipping 11) to create an inconsistent state + // This will cause getPendingExecutionResults to return an error + tracker.executionResultsByHash["hash2"] = &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash2"), + HeaderNonce: 12, + }, + } + tracker.nonceHash.addNonceHash(12, "hash2") + + err = tracker.RemoveFromNonce(12) + require.True(t, errors.Is(err, ErrDifferentNoncesConfirmedExecutionResults)) + }) + + t.Run("remove single execution result should update lastExecutedResultHash to last notarized", func(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + lastNotarizedHash := []byte("hash0") + err := tracker.SetLastNotarizedResult(&block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: lastNotarizedHash, + HeaderNonce: 10, + }, + }) + require.NoError(t, err) + + executionResult1 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), + HeaderNonce: 11, + }, + } + _, err = tracker.AddExecutionResult(executionResult1) + require.NoError(t, err) + + err = tracker.RemoveFromNonce(11) + require.NoError(t, err) + + pending, err := tracker.GetPendingExecutionResults() + require.NoError(t, err) + require.Equal(t, 0, len(pending)) + lastNotarizedExecRes, err := tracker.GetLastNotarizedExecutionResult() + require.NoError(t, err) + require.Equal(t, lastNotarizedHash, lastNotarizedExecRes.GetHeaderHash()) + }) + + t.Run("remove from middle hash should remove that hash and all with higher nonces", func(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + err := tracker.SetLastNotarizedResult(&block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash0"), + HeaderNonce: 10, + }, + }) + require.NoError(t, err) + + executionResult1 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), + HeaderNonce: 11, + }, + } + _, err = tracker.AddExecutionResult(executionResult1) + require.NoError(t, err) + + executionResult2 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash2"), + HeaderNonce: 12, + }, + } + _, err = tracker.AddExecutionResult(executionResult2) + require.NoError(t, err) + + executionResult3 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash3"), + HeaderNonce: 13, + }, + } + _, err = tracker.AddExecutionResult(executionResult3) + require.NoError(t, err) + + executionResult4 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash4"), + HeaderNonce: 14, + }, + } + _, err = tracker.AddExecutionResult(executionResult4) + require.NoError(t, err) + + // Remove from hash2 (nonce 12), should keep only hash1 (nonce 11) + err = tracker.RemoveFromNonce(12) + require.NoError(t, err) + + pending, err := tracker.GetPendingExecutionResults() + require.NoError(t, err) + require.Equal(t, 1, len(pending)) + require.Equal(t, executionResult1, pending[0]) + require.Equal(t, []byte("hash1"), tracker.lastExecutedResultHash) + + // Verify removed results + _, err = tracker.GetPendingExecutionResultByHash([]byte("hash2")) + require.True(t, errors.Is(err, ErrCannotFindExecutionResult)) + _, err = tracker.GetPendingExecutionResultByHash([]byte("hash3")) + require.True(t, errors.Is(err, ErrCannotFindExecutionResult)) + _, err = tracker.GetPendingExecutionResultByHash([]byte("hash4")) + require.True(t, errors.Is(err, ErrCannotFindExecutionResult)) + + // Verify kept result + result, err := tracker.GetPendingExecutionResultByHash([]byte("hash1")) + require.NoError(t, err) + require.Equal(t, executionResult1, result) + }) + + t.Run("remove a missing nonce should remove the higher ones", func(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + err := tracker.SetLastNotarizedResult(&block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash0"), + HeaderNonce: 10, + }, + }) + require.NoError(t, err) + + executionResult1 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), + HeaderNonce: 11, + }, + } + _, err = tracker.AddExecutionResult(executionResult1) + require.NoError(t, err) + + executionResult2 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash2"), + HeaderNonce: 12, + }, + } + _, err = tracker.AddExecutionResult(executionResult2) + require.NoError(t, err) + + // Remove from nonce 8(missing) should remove all + err = tracker.RemoveFromNonce(8) + require.NoError(t, err) + + pending, err := tracker.GetPendingExecutionResults() + require.NoError(t, err) + require.Equal(t, 0, len(pending)) + + // Verify removed results + _, err = tracker.GetPendingExecutionResultByHash([]byte("hash1")) + require.True(t, errors.Is(err, ErrCannotFindExecutionResult)) + _, err = tracker.GetPendingExecutionResultByHash([]byte("hash2")) + require.True(t, errors.Is(err, ErrCannotFindExecutionResult)) + }) +} + +func TestExecutionResultsTracker_Clean(t *testing.T) { + t.Parallel() + + t.Run("nil last notarized result should early exit", func(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + tracker.Clean(nil) + }) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + _ = tracker.SetLastNotarizedResult(&block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash0"), + HeaderNonce: 10, + }, + }) + _, _ = tracker.AddExecutionResult(&block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), + HeaderNonce: 11, + }, + }) + _, _ = tracker.AddExecutionResult(&block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash2"), + HeaderNonce: 12, + }, + }) + + newLast := &block.BaseExecutionResult{ + HeaderHash: []byte("hash_new"), + HeaderNonce: 2, + } + tracker.Clean(newLast) + + pending, err := tracker.GetPendingExecutionResults() + require.NoError(t, err) + require.Equal(t, 0, len(pending)) + + lastExec, err := tracker.GetLastNotarizedExecutionResult() + require.NoError(t, err) + require.Equal(t, newLast, lastExec) + }) +} + +func TestExecutionResultsTracker_PopDismissedResults_EmptyByDefault(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + batches := tracker.PopDismissedResults() + require.Nil(t, batches) +} + +func TestExecutionResultsTracker_PopDismissedResults_OnCleanOnConsensusReached(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + + lastNotarized := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash0"), + HeaderNonce: 10, + RootHash: []byte("rootHash0"), + }, + } + _ = tracker.SetLastNotarizedResult(lastNotarized) + + exec1 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), + HeaderNonce: 11, + RootHash: []byte("rootHash1"), + }, + } + exec2 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash2"), + HeaderNonce: 12, + RootHash: []byte("rootHash2"), + }, + } + _, _ = tracker.AddExecutionResult(exec1) + _, _ = tracker.AddExecutionResult(exec2) + + // Consensus commits nonce 11 with a different hash - dismisses nonce 11 and higher + header := &block.HeaderV3{ + Nonce: 11, + } + tracker.CleanOnConsensusReached([]byte("different_hash"), header) + + batches := tracker.PopDismissedResults() + require.Len(t, batches, 1) + require.Len(t, batches[0].Results, 2) + // Anchor should be lastNotarized (no pending results before nonce 11) + require.Equal(t, lastNotarized.GetRootHash(), batches[0].AnchorResult.GetRootHash()) + require.Equal(t, exec1.GetRootHash(), batches[0].Results[0].GetRootHash()) + require.Equal(t, exec2.GetRootHash(), batches[0].Results[1].GetRootHash()) + + // Second pop should return nil (queue drained) + require.Nil(t, tracker.PopDismissedResults()) +} + +func TestExecutionResultsTracker_PopDismissedResults_AnchorIsLastPendingBeforeDismissal(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + + lastNotarized := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash0"), + HeaderNonce: 10, + RootHash: []byte("rootHash0"), + }, + } + _ = tracker.SetLastNotarizedResult(lastNotarized) + + exec1 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), + HeaderNonce: 11, + RootHash: []byte("rootHash1"), + }, + } + exec2 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash2"), + HeaderNonce: 12, + RootHash: []byte("rootHash2"), + }, + } + exec3 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash3"), + HeaderNonce: 13, + RootHash: []byte("rootHash3"), + }, + } + _, _ = tracker.AddExecutionResult(exec1) + _, _ = tracker.AddExecutionResult(exec2) + _, _ = tracker.AddExecutionResult(exec3) + + // RemoveFromNonce(12) should dismiss exec2 and exec3, keeping exec1 + _ = tracker.RemoveFromNonce(12) + + batches := tracker.PopDismissedResults() + require.Len(t, batches, 1) + require.Len(t, batches[0].Results, 2) + // Anchor should be exec1 (last pending result before nonce 12) + require.Equal(t, exec1.GetRootHash(), batches[0].AnchorResult.GetRootHash()) + require.Equal(t, exec2.GetRootHash(), batches[0].Results[0].GetRootHash()) + require.Equal(t, exec3.GetRootHash(), batches[0].Results[1].GetRootHash()) +} + +func TestExecutionResultsTracker_PopDismissedResults_OnCleanConfirmedMismatch(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + + lastNotarized := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash0"), + HeaderNonce: 10, + RootHash: []byte("rootHash0"), + }, + } + _ = tracker.SetLastNotarizedResult(lastNotarized) + + exec1 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), + HeaderNonce: 11, + RootHash: []byte("rootHash1"), + }, + } + exec2 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash2"), + HeaderNonce: 12, + RootHash: []byte("rootHash2"), + }, + } + exec3 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash3"), + HeaderNonce: 13, + RootHash: []byte("rootHash3"), + }, + } + _, _ = tracker.AddExecutionResult(exec1) + _, _ = tracker.AddExecutionResult(exec2) + _, _ = tracker.AddExecutionResult(exec3) + + // Header confirms exec1 matches, but exec2 mismatches at idx=1 + header := &block.HeaderV3{ + ExecutionResults: []*block.ExecutionResult{ + exec1, + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("different"), + HeaderNonce: 12, + RootHash: []byte("differentRoot"), + }, + }, + }, + } + err := tracker.CleanConfirmedExecutionResults(header) + require.ErrorIs(t, err, ErrExecutionResultMismatch) + + batches := tracker.PopDismissedResults() + require.Len(t, batches, 1) + require.Len(t, batches[0].Results, 2) // exec2 and exec3 dismissed + // Anchor should be exec1 (last matching result, at idx=0 the preceding pending result) + require.Equal(t, exec1.GetRootHash(), batches[0].AnchorResult.GetRootHash()) + require.Equal(t, exec2.GetRootHash(), batches[0].Results[0].GetRootHash()) + require.Equal(t, exec3.GetRootHash(), batches[0].Results[1].GetRootHash()) +} + +func TestExecutionResultsTracker_PopDismissedResults_OnCleanConfirmedMismatchAtFirstIndex(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + + lastNotarized := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash0"), + HeaderNonce: 10, + RootHash: []byte("rootHash0"), + }, + } + _ = tracker.SetLastNotarizedResult(lastNotarized) + + exec1 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), + HeaderNonce: 11, + RootHash: []byte("rootHash1"), + }, + } + _, _ = tracker.AddExecutionResult(exec1) + + // Header has mismatch at idx=0 + header := &block.HeaderV3{ + ExecutionResults: []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("different"), + HeaderNonce: 11, + RootHash: []byte("differentRoot"), + }, + }, + }, + } + err := tracker.CleanConfirmedExecutionResults(header) + require.ErrorIs(t, err, ErrExecutionResultMismatch) + + batches := tracker.PopDismissedResults() + require.Len(t, batches, 1) + require.Len(t, batches[0].Results, 1) + // Anchor should be lastNotarized (mismatch at idx=0) + require.Equal(t, lastNotarized.GetRootHash(), batches[0].AnchorResult.GetRootHash()) + require.Equal(t, exec1.GetRootHash(), batches[0].Results[0].GetRootHash()) +} + +func TestExecutionResultsTracker_PopDismissedResults_OnClean(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + + lastNotarized := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash0"), + HeaderNonce: 10, + RootHash: []byte("rootHash0"), + }, + } + _ = tracker.SetLastNotarizedResult(lastNotarized) + + exec1 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), + HeaderNonce: 11, + RootHash: []byte("rootHash1"), + }, + } + exec2 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash2"), + HeaderNonce: 12, + RootHash: []byte("rootHash2"), + }, + } + _, _ = tracker.AddExecutionResult(exec1) + _, _ = tracker.AddExecutionResult(exec2) + + newNotarized := &block.BaseExecutionResult{ + HeaderHash: []byte("new"), + HeaderNonce: 50, + RootHash: []byte("newRoot"), + } + tracker.Clean(newNotarized) + + batches := tracker.PopDismissedResults() + require.Len(t, batches, 1) + require.Len(t, batches[0].Results, 2) + // Anchor should be the OLD lastNotarized (before Clean overwrote it) + require.Equal(t, lastNotarized.GetRootHash(), batches[0].AnchorResult.GetRootHash()) + require.Equal(t, exec1.GetRootHash(), batches[0].Results[0].GetRootHash()) + require.Equal(t, exec2.GetRootHash(), batches[0].Results[1].GetRootHash()) +} + +func TestExecutionResultsTracker_PopDismissedResults_ConfirmedNotDismissed(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + + lastNotarized := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash0"), + HeaderNonce: 10, + RootHash: []byte("rootHash0"), + }, + } + _ = tracker.SetLastNotarizedResult(lastNotarized) + + exec1 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), + HeaderNonce: 11, + RootHash: []byte("rootHash1"), + }, + } + _, _ = tracker.AddExecutionResult(exec1) + + // Confirm exec1 successfully - should NOT appear in dismissed + header := &block.HeaderV3{ + ExecutionResults: []*block.ExecutionResult{exec1}, + } + err := tracker.CleanConfirmedExecutionResults(header) + require.NoError(t, err) + + batches := tracker.PopDismissedResults() + require.Nil(t, batches) +} + +func TestExecutionResultsTracker_PopDismissedResults_MultipleBatches(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + + lastNotarized := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash0"), + HeaderNonce: 10, + RootHash: []byte("rootHash0"), + }, + } + _ = tracker.SetLastNotarizedResult(lastNotarized) + + exec1 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), + HeaderNonce: 11, + RootHash: []byte("rootHash1"), + }, + } + exec2 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash2"), + HeaderNonce: 12, + RootHash: []byte("rootHash2"), + }, + } + exec3 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash3"), + HeaderNonce: 13, + RootHash: []byte("rootHash3"), + }, + } + _, _ = tracker.AddExecutionResult(exec1) + _, _ = tracker.AddExecutionResult(exec2) + _, _ = tracker.AddExecutionResult(exec3) + + // First dismissal: remove nonce 13 and higher - dismisses exec3 + _ = tracker.RemoveFromNonce(13) + + // Second dismissal: remove nonce 12 and higher - dismisses exec2 + _ = tracker.RemoveFromNonce(12) + + // Should get 2 separate batches with different anchors + batches := tracker.PopDismissedResults() + require.Len(t, batches, 2) + + // Batch 1: exec3 dismissed, anchor = exec2 (last pending before nonce 13) + require.Equal(t, exec2.GetRootHash(), batches[0].AnchorResult.GetRootHash()) + require.Len(t, batches[0].Results, 1) + require.Equal(t, exec3.GetRootHash(), batches[0].Results[0].GetRootHash()) + + // Batch 2: exec2 dismissed, anchor = exec1 (last pending before nonce 12) + require.Equal(t, exec1.GetRootHash(), batches[1].AnchorResult.GetRootHash()) + require.Len(t, batches[1].Results, 1) + require.Equal(t, exec2.GetRootHash(), batches[1].Results[0].GetRootHash()) +} + +func TestExecutionResultsTracker_PopDismissedResults_IndependentSources(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + + lastNotarized := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash0"), + HeaderNonce: 10, + RootHash: []byte("rootHash0"), + }, + } + _ = tracker.SetLastNotarizedResult(lastNotarized) + + exec1 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), + HeaderNonce: 11, + RootHash: []byte("rootHash1"), + }, + } + exec2 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash2"), + HeaderNonce: 12, + RootHash: []byte("rootHash2"), + }, + } + exec3 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash3"), + HeaderNonce: 13, + RootHash: []byte("rootHash3"), + }, + } + _, _ = tracker.AddExecutionResult(exec1) + _, _ = tracker.AddExecutionResult(exec2) + _, _ = tracker.AddExecutionResult(exec3) + + // Source 1: CleanOnConsensusReached dismisses nonce 12+ (consensus committed different hash for 12) + header := &block.HeaderV3{Nonce: 12} + tracker.CleanOnConsensusReached([]byte("different_hash"), header) + + // Source 2: RemoveFromNonce dismisses the remaining exec1 + _ = tracker.RemoveFromNonce(11) + + batches := tracker.PopDismissedResults() + require.Len(t, batches, 2) + + // Batch 0: from CleanOnConsensusReached - dismissed exec2+exec3, anchor = exec1 + require.Equal(t, exec1.GetRootHash(), batches[0].AnchorResult.GetRootHash()) + require.Len(t, batches[0].Results, 2) + require.Equal(t, exec2.GetRootHash(), batches[0].Results[0].GetRootHash()) + require.Equal(t, exec3.GetRootHash(), batches[0].Results[1].GetRootHash()) + + // Batch 1: from RemoveFromNonce - dismissed exec1, anchor = lastNotarized + require.Equal(t, lastNotarized.GetRootHash(), batches[1].AnchorResult.GetRootHash()) + require.Len(t, batches[1].Results, 1) + require.Equal(t, exec1.GetRootHash(), batches[1].Results[0].GetRootHash()) +} + +func TestExecutionResultsTracker_DismissedBatchesOverflow(t *testing.T) { + t.Parallel() + + tracker := NewExecutionResultsTracker() + + lastNotarized := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash0"), + HeaderNonce: 10, + RootHash: []byte("rootHash0"), + }, + } + _ = tracker.SetLastNotarizedResult(lastNotarized) + + // Fill the queue to capacity by repeatedly adding+removing execution results + for i := 0; i < maxDismissedBatches+5; i++ { + nonce := uint64(11) + exec := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte(fmt.Sprintf("hash_%d", i)), + HeaderNonce: nonce, + RootHash: []byte(fmt.Sprintf("root_%d", i)), + }, + } + _, _ = tracker.AddExecutionResult(exec) + _ = tracker.RemoveFromNonce(nonce) + } + + batches := tracker.PopDismissedResults() + // Should be capped at maxDismissedBatches, oldest batches dropped + require.Len(t, batches, maxDismissedBatches) + + // The surviving batches should be the last maxDismissedBatches ones (oldest dropped) + // The first surviving batch should be from iteration index 5 (0-4 were dropped) + firstSurvivingIdx := 5 + require.Equal(t, + []byte(fmt.Sprintf("root_%d", firstSurvivingIdx)), + batches[0].Results[0].GetRootHash(), + ) + // All anchors should be lastNotarized (no pending results with nonce < 11 exist) + for i, batch := range batches { + require.Equal(t, lastNotarized.GetRootHash(), batch.AnchorResult.GetRootHash(), + "batch %d should have lastNotarized as anchor", i) + } + + // The last surviving batch should be from the last iteration + lastIdx := maxDismissedBatches + 5 - 1 + require.Equal(t, + []byte(fmt.Sprintf("root_%d", lastIdx)), + batches[maxDismissedBatches-1].Results[0].GetRootHash(), + ) + + // After pop, queue should be empty + require.Nil(t, tracker.PopDismissedResults()) +} diff --git a/process/asyncExecution/executionTrack/interface.go b/process/asyncExecution/executionTrack/interface.go new file mode 100644 index 00000000000..c5834aee14d --- /dev/null +++ b/process/asyncExecution/executionTrack/interface.go @@ -0,0 +1,11 @@ +package executionTrack + +import ( + "github.com/multiversx/mx-chain-core-go/data" +) + +// HeaderWithExecutionResults defines what a header with execution results should be able to do +type HeaderWithExecutionResults interface { + data.HeaderHandler + GetExecutionResults() []data.ExecutionResultHandler +} diff --git a/process/asyncExecution/executionTrack/nonceHashes.go b/process/asyncExecution/executionTrack/nonceHashes.go new file mode 100644 index 00000000000..c0a0bded8cc --- /dev/null +++ b/process/asyncExecution/executionTrack/nonceHashes.go @@ -0,0 +1,24 @@ +package executionTrack + +// nonceHash this structure is not thread-safe +type nonceHash struct { + nonceHashMap map[uint64]string +} + +func newNonceHash() *nonceHash { + return &nonceHash{ + nonceHashMap: make(map[uint64]string), + } +} + +func (nh *nonceHash) addNonceHash(nonce uint64, hash string) { + nh.nonceHashMap[nonce] = hash +} + +func (nh *nonceHash) getHashByNonce(nonce uint64) string { + return nh.nonceHashMap[nonce] +} + +func (nh *nonceHash) removeByNonce(nonce uint64) { + delete(nh.nonceHashMap, nonce) +} diff --git a/process/asyncExecution/executionTrack/nonceHashes_test.go b/process/asyncExecution/executionTrack/nonceHashes_test.go new file mode 100644 index 00000000000..d0739b0c5a7 --- /dev/null +++ b/process/asyncExecution/executionTrack/nonceHashes_test.go @@ -0,0 +1,23 @@ +package executionTrack + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewNonceHash(t *testing.T) { + t.Parallel() + + nh := newNonceHash() + + nh.addNonceHash(1, "h1") + nh.addNonceHash(2, "h2") + nh.addNonceHash(3, "h3") + + nh.removeByNonce(2) + + require.Equal(t, "h1", nh.getHashByNonce(1)) + require.Equal(t, "", nh.getHashByNonce(2)) + require.Equal(t, "h3", nh.getHashByNonce(3)) +} diff --git a/process/asyncExecution/export_test.go b/process/asyncExecution/export_test.go new file mode 100644 index 00000000000..d6a16cd376e --- /dev/null +++ b/process/asyncExecution/export_test.go @@ -0,0 +1,8 @@ +package asyncExecution + +import "github.com/multiversx/mx-chain-go/process/asyncExecution/cache" + +// Process - +func (he *headersExecutor) Process(pair cache.HeaderBodyPair) error { + return he.process(pair) +} diff --git a/process/asyncExecution/headersExecutor.go b/process/asyncExecution/headersExecutor.go new file mode 100644 index 00000000000..088c10907f8 --- /dev/null +++ b/process/asyncExecution/headersExecutor.go @@ -0,0 +1,457 @@ +package asyncExecution + +import ( + "bytes" + "context" + "errors" + "sync" + "time" + + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + logger "github.com/multiversx/mx-chain-logger-go" + + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/asyncExecution/cache" +) + +var log = logger.GetOrCreate("process/asyncExecution") + +const timeToSleep = time.Millisecond * 5 +const timeToSleepOnError = time.Millisecond * 300 +const maxRetryAttempts = 10 +const maxBackoffTime = time.Second * 5 + +// ArgsHeadersExecutor holds all the components needed to create a new instance of *headersExecutor +type ArgsHeadersExecutor struct { + BlocksCache BlocksCache + ExecutionTracker ExecutionResultsHandler + BlockProcessor BlockProcessor + BlockChain data.ChainHandler + SignalProcessCompletionChan chan uint64 +} + +type headersExecutor struct { + blocksCache BlocksCache + executionTracker ExecutionResultsHandler + blockProcessor BlockProcessor + blockChain data.ChainHandler + cancelFunc context.CancelFunc + mutPaused sync.Mutex + isPaused bool + processingDone chan struct{} + signalProcessCompletionChan chan uint64 +} + +// NewHeadersExecutor will create a new instance of *headersExecutor +func NewHeadersExecutor(args ArgsHeadersExecutor) (*headersExecutor, error) { + if check.IfNil(args.BlocksCache) { + return nil, ErrNilHeadersCache + } + if check.IfNil(args.ExecutionTracker) { + return nil, ErrNilExecutionTracker + } + if check.IfNil(args.BlockProcessor) { + return nil, ErrNilBlockProcessor + } + if check.IfNil(args.BlockChain) { + return nil, process.ErrNilBlockChain + } + + instance := &headersExecutor{ + blocksCache: args.BlocksCache, + executionTracker: args.ExecutionTracker, + blockProcessor: args.BlockProcessor, + blockChain: args.BlockChain, + signalProcessCompletionChan: args.SignalProcessCompletionChan, + } + + return instance, nil +} + +// StartExecution starts a goroutine to continuously process blocks from the queue +// and add their results to the execution tracker until cancelled or closed. +func (he *headersExecutor) StartExecution() { + ctx, cancelFunc := context.WithCancel(context.Background()) + he.cancelFunc = cancelFunc + + go he.start(ctx) +} + +// PauseExecution pauses the execution and waits for any ongoing block processing to complete. +// It returns only after the processing loop has acknowledged the pause, guaranteeing +// that no block execution is in flight. +func (he *headersExecutor) PauseExecution() { + log.Debug("headersExecutor.PauseExecution: pausing execution") + + if he.cancelFunc == nil { + log.Debug("headersExecutor.PauseExecution: execution not started yet or already closed") + return + } + + he.mutPaused.Lock() + if he.isPaused { + he.mutPaused.Unlock() + return + } + + he.isPaused = true + ch := make(chan struct{}) + he.processingDone = ch + he.mutPaused.Unlock() + + // Block until the processing loop acknowledges the pause by closing this channel. + // This guarantees no block execution is in flight when PauseExecution returns. + <-ch +} + +// ResumeExecution resumes the execution +func (he *headersExecutor) ResumeExecution() { + log.Debug("headersExecutor.ResumeExecution: resuming execution") + + he.mutPaused.Lock() + defer he.mutPaused.Unlock() + + he.isPaused = false + + // If PauseExecution is waiting for acknowledgement but we're resuming first, + // close the channel to unblock it. + if he.processingDone != nil { + close(he.processingDone) + he.processingDone = nil + } +} + +// acknowledgePause checks if a pause has been requested and, if so, closes the +// processingDone channel to unblock PauseExecution. This must only be called +// between block processing iterations, ensuring no execution is in flight. +func (he *headersExecutor) acknowledgePause() { + he.mutPaused.Lock() + defer he.mutPaused.Unlock() + + if !he.isPaused { + return + } + + if he.processingDone != nil { + close(he.processingDone) + he.processingDone = nil + } +} + +func (he *headersExecutor) start(ctx context.Context) { + log.Debug("headersExecutor.start: starting execution") + + for { + select { + case <-ctx.Done(): + he.acknowledgePause() + return + default: + } + + he.acknowledgePause() + he.mutPaused.Lock() + isPaused := he.isPaused + he.mutPaused.Unlock() + + if isPaused { + time.Sleep(timeToSleep) + continue + } + + lastExecutedNonce, lastExecutedHeaderHash, _ := he.blockChain.GetLastExecutedBlockInfo() + if len(lastExecutedHeaderHash) == 0 { + he.acknowledgePause() + time.Sleep(timeToSleep) + continue + } + + // check if we need to execute another block for the same nonce (replacement block) + headerBodyPair, ok := he.blocksCache.GetByNonce(lastExecutedNonce) + if !ok { + // Either block not in cache (genesis or cleaned) or same hash (already executed) + // Try to get the next block to execute + headerBodyPair, ok = he.blocksCache.GetByNonce(lastExecutedNonce + 1) + if !ok { + he.acknowledgePause() + time.Sleep(timeToSleep) + continue + } + } + + if headerBodyPair.Header.GetNonce() == lastExecutedNonce && !bytes.Equal(lastExecutedHeaderHash, headerBodyPair.HeaderHash) { + // Different block at same nonce - this is a replacement block and needs to be executed + log.Debug("headersExecutor.start: detected replacement block at same nonce", + "nonce", lastExecutedNonce, + "executed_hash", lastExecutedHeaderHash, + "replacement_hash", headerBodyPair.HeaderHash, + ) + } + + if bytes.Equal(lastExecutedHeaderHash, headerBodyPair.HeaderHash) { + // Already executed this block, try to get the next one + headerBodyPair, ok = he.blocksCache.GetByNonce(lastExecutedNonce + 1) + if !ok { + he.acknowledgePause() + time.Sleep(timeToSleep) + continue + } + } + + err := he.process(headerBodyPair) + if err != nil { + if errors.Is(err, ErrContextMismatch) { + he.acknowledgePause() + time.Sleep(timeToSleep) + continue + } + he.handleProcessError(ctx, headerBodyPair) + } + } +} + +func (he *headersExecutor) handleProcessError(ctx context.Context, pair cache.HeaderBodyPair) { + retryCount := 0 + backoffTime := timeToSleepOnError + + for retryCount < maxRetryAttempts { + he.mutPaused.Lock() + isPaused := he.isPaused + he.mutPaused.Unlock() + + if isPaused { + return + } + + pairFromQueue, ok := he.blocksCache.GetByNonce(pair.Header.GetNonce()) + if ok && !bytes.Equal(pair.HeaderHash, pairFromQueue.HeaderHash) { + // continue the processing (pop the next header from queue) + return + } + + select { + case <-ctx.Done(): + return + default: + he.mutPaused.Lock() + isPausedRetry := he.isPaused + he.mutPaused.Unlock() + + if isPausedRetry { + return + } + + // Exponential backoff with maximum limit + time.Sleep(backoffTime) + backoffTime = backoffTime * 2 + if backoffTime > maxBackoffTime { + backoffTime = maxBackoffTime + } + + he.mutPaused.Lock() + isPausedRetry = he.isPaused + he.mutPaused.Unlock() + + if isPausedRetry { + return + } + + // retry with the same pair + err := he.process(pair) + if err == nil { + log.Debug("headersExecutor.handleProcessError - retry succeeded", + "nonce", pair.Header.GetNonce(), + "retry_count", retryCount) + return + } + + retryCount++ + log.Warn("headersExecutor.handleProcessError - retry failed", + "nonce", pair.Header.GetNonce(), + "retry_count", retryCount, + "max_retries", maxRetryAttempts, + "err", err) + } + } + + log.Error("headersExecutor.handleProcessError - max retries exceeded, skipping block", + "nonce", pair.Header.GetNonce(), + "max_retries", maxRetryAttempts) +} + +func (he *headersExecutor) process(pair cache.HeaderBodyPair) error { + ok := he.checkLastExecutionResultContext(pair.Header, pair.HeaderHash) + if !ok { + return ErrContextMismatch + } + + executionResult, err := he.blockProcessor.ProcessBlockProposal(pair.Header, pair.HeaderHash, pair.Body) + if err != nil { + log.Warn("headersExecutor.process process block failed", + "nonce", pair.Header.GetNonce(), + "prevHash", pair.Header.GetPrevHash(), + "err", err, + ) + return err + } + + // Validate execution result + if check.IfNil(executionResult) { + log.Warn("headersExecutor.process - nil execution result received", + "nonce", pair.Header.GetNonce()) + he.blockProcessor.RevertBlockProposalState() + return ErrNilExecutionResult + } + + ok = he.checkLastExecutionResultContext(pair.Header, pair.HeaderHash) + if !ok { + he.blockProcessor.RevertBlockProposalState() + return nil + } + + lastCommittedBlockHash := he.blockChain.GetCurrentBlockHeaderHash() + lastCommittedBlockHeader := he.blockChain.GetCurrentBlockHeader() + if !check.IfNil(lastCommittedBlockHeader) && + executionResult.GetHeaderNonce() == lastCommittedBlockHeader.GetNonce() && + !bytes.Equal(executionResult.GetHeaderHash(), lastCommittedBlockHash) { + log.Debug("headersExecutor.process - execution result header hash does not match last committed block hash", + "nonce", pair.Header.GetNonce(), + "exec_header_hash", executionResult.GetHeaderHash(), + "committed_block_hash", lastCommittedBlockHash, + ) + he.blockProcessor.RevertBlockProposalState() + return nil + } + + lastExecutionResult := he.blockChain.GetLastExecutionResult() + if !check.IfNil(lastExecutionResult) { + if !bytes.Equal(lastExecutionResult.GetHeaderHash(), pair.Header.GetPrevHash()) { + log.Error("headersExecutor.process - header hash mismatch") + he.blockProcessor.RevertBlockProposalState() + return nil + } + } + + // All post-execution checks passed, commit the state now + err = he.blockProcessor.CommitBlockProposalState(pair.Header) + if err != nil { + log.Warn("headersExecutor.process commit block proposal state failed", + "nonce", pair.Header.GetNonce(), + "err", err, + ) + he.blockProcessor.RevertBlockProposalState() + return err + } + + // Add to execution tracker only after state is committed, so the tracker never + // holds a result whose state was not persisted. + added, err := he.executionTracker.AddExecutionResult(executionResult) + if err != nil { + log.Warn("headersExecutor.process add execution result failed", + "nonce", pair.Header.GetNonce(), + "err", err, + ) + return err + } + if !added { + // Result was rejected because consensus already committed a different block for this nonce. + // State was already committed but the corrective flow on the next processing iteration + // will recreate the trie from the expected root hash. + log.Debug("headersExecutor.process execution result not added, skipping blockchain updates", + "nonce", pair.Header.GetNonce(), + ) + return nil + } + + he.blockProcessor.PruneTrieAsyncHeader() + + he.blockChain.SetFinalBlockInfo( + executionResult.GetHeaderNonce(), + executionResult.GetHeaderHash(), + executionResult.GetRootHash(), + ) + + he.blockChain.SetLastExecutedBlockHeaderAndRootHash(pair.Header, executionResult.GetHeaderHash(), executionResult.GetRootHash()) + he.blockChain.SetLastExecutionResult(executionResult) + + he.signalProcessCompletion(pair.Header.GetNonce()) + + log.Debug("headersExecutor.process completed", + "nonce", pair.Header.GetNonce(), + "exec nonce", executionResult.GetHeaderNonce(), + "exec rootHash", executionResult.GetRootHash(), + ) + + return nil +} + +func (he *headersExecutor) signalProcessCompletion(currentNonce uint64) { + if he.signalProcessCompletionChan == nil { + return + } + + select { + case he.signalProcessCompletionChan <- currentNonce: + default: + } +} + +func (he *headersExecutor) checkLastExecutionResultContext( + currentHeader data.HeaderHandler, + currentHeaderHash []byte, +) bool { + if check.IfNil(currentHeader) { + return false + } + + lastExecutionResult := he.blockChain.GetLastExecutionResult() + if check.IfNil(lastExecutionResult) { + return true + } + + if process.IsReplacementBlockForExecution(currentHeader, currentHeaderHash, lastExecutionResult) { + return true + } + + if currentHeader.GetNonce() != lastExecutionResult.GetHeaderNonce()+1 { + log.Debug("headersExecutor.process: concurrent revert event", + "previous nonce", lastExecutionResult.GetHeaderNonce(), + "current nonce", currentHeader.GetNonce(), + "err", process.ErrWrongNonceInBlock, + ) + return false + } + + lastExecResultHeaderHash := lastExecutionResult.GetHeaderHash() + + if !bytes.Equal(lastExecResultHeaderHash, currentHeader.GetPrevHash()) { + log.Debug("headersExecutor.process: concurrent revert event", + "header previous hash", currentHeader.GetPrevHash(), + "last execution result hash", lastExecResultHeaderHash, + "err", process.ErrBlockHashDoesNotMatch, + ) + return false + } + + return true +} + +// GetSignalProcessCompletionChan returns the channel used to signal the sync loop after execution completes +func (he *headersExecutor) GetSignalProcessCompletionChan() chan uint64 { + return he.signalProcessCompletionChan +} + +// Close will close the blocks execution loop +func (he *headersExecutor) Close() error { + if he.cancelFunc != nil { + he.cancelFunc() + } + + return nil +} + +// IsInterfaceNil returns true if there is no value under the interface +func (he *headersExecutor) IsInterfaceNil() bool { + return he == nil +} diff --git a/process/asyncExecution/headersExecutor_test.go b/process/asyncExecution/headersExecutor_test.go new file mode 100644 index 00000000000..1e8777a7fce --- /dev/null +++ b/process/asyncExecution/headersExecutor_test.go @@ -0,0 +1,1239 @@ +package asyncExecution + +import ( + "errors" + "sync" + "sync/atomic" + "testing" + "time" + + atomicCore "github.com/multiversx/mx-chain-core-go/core/atomic" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/config" + + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/asyncExecution/cache" + "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" +) + +var errExpected = errors.New("expected error") + +func createMockArgs() ArgsHeadersExecutor { + headerCache := cache.NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + + return ArgsHeadersExecutor{ + BlocksCache: headerCache, + ExecutionTracker: &processMocks.ExecutionTrackerStub{}, + BlockProcessor: &processMocks.BlockProcessorStub{}, + BlockChain: &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.BaseExecutionResult{} + }, + }, + } +} + +func TestNewHeadersExecutor(t *testing.T) { + t.Parallel() + + t.Run("nil headers queue", func(t *testing.T) { + args := createMockArgs() + args.BlocksCache = nil + + _, err := NewHeadersExecutor(args) + require.Equal(t, ErrNilHeadersCache, err) + }) + + t.Run("nil execution tracker", func(t *testing.T) { + args := createMockArgs() + args.ExecutionTracker = nil + + _, err := NewHeadersExecutor(args) + require.Equal(t, ErrNilExecutionTracker, err) + }) + + t.Run("nil block processor", func(t *testing.T) { + args := createMockArgs() + args.BlockProcessor = nil + + _, err := NewHeadersExecutor(args) + require.Equal(t, ErrNilBlockProcessor, err) + }) + + t.Run("nil chain handler", func(t *testing.T) { + args := createMockArgs() + args.BlockChain = nil + + _, err := NewHeadersExecutor(args) + require.Equal(t, process.ErrNilBlockChain, err) + }) + + t.Run("should work", func(t *testing.T) { + args := createMockArgs() + + executor, err := NewHeadersExecutor(args) + require.NoError(t, err) + require.NotNil(t, executor) + require.False(t, executor.IsInterfaceNil()) + + err = executor.Close() + require.NoError(t, err) + }) +} + +func TestHeadersExecutor_StartAndClose(t *testing.T) { + t.Parallel() + + var prevHash = []byte("prevHash") + calledProcessBlock := uint32(0) + calledAddExecutionResult := uint32(0) + args := createMockArgs() + blocksCache := cache.NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + args.BlocksCache = blocksCache + executedNonce := uint64(1) + executedHash := prevHash + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV3{ + Nonce: executedNonce, + } + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.BaseExecutionResult{ + HeaderNonce: executedNonce, + HeaderHash: executedHash, + } + }, + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return executedNonce, executedHash, nil + }, + SetLastExecutedBlockHeaderAndRootHashCalled: func(header data.HeaderHandler, blockHash []byte, rootHash []byte) { + executedNonce = header.GetNonce() + executedHash = blockHash + }, + } + + args.BlockProcessor = &processMocks.BlockProcessorStub{ + ProcessBlockProposalCalled: func(handler data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + atomic.AddUint32(&calledProcessBlock, 1) + return &block.BaseExecutionResult{ + HeaderNonce: handler.GetNonce(), + HeaderHash: headerHash, + }, nil + }, + } + args.ExecutionTracker = &processMocks.ExecutionTrackerStub{ + AddExecutionResultCalled: func(executionResult data.BaseExecutionResultHandler) (bool, error) { + atomic.AddUint32(&calledAddExecutionResult, 1) + return true, nil + }, + } + + executor, err := NewHeadersExecutor(args) + require.NoError(t, err) + + executor.StartExecution() + + err = blocksCache.AddOrReplace(cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: 2, + PrevHash: prevHash, + }, + Body: &block.Body{}, + HeaderHash: []byte("a"), + }) + require.NoError(t, err) + + time.Sleep(time.Millisecond * 100) + + err = executor.Close() + require.NoError(t, err) + require.Equal(t, uint32(1), atomic.LoadUint32(&calledProcessBlock)) + require.Equal(t, uint32(1), atomic.LoadUint32(&calledAddExecutionResult)) + +} + +func TestHeadersExecutor_ProcessBlock(t *testing.T) { + t.Parallel() + + var prevHash = []byte("prevHash") + var currentHash = []byte("currentHash") + + t.Run("pause/resume should work", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + cntWasPopCalled := uint32(0) + lastExecutedBlockNonce := 0 + + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + if lastExecutedBlockNonce > 0 { + return &block.HeaderV3{ + Nonce: uint64(lastExecutedBlockNonce), + PrevHash: prevHash, + } + } + return &block.HeaderV3{ + Nonce: 0, + } + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + if lastExecutedBlockNonce > 0 { + return &block.BaseExecutionResult{ + HeaderNonce: uint64(lastExecutedBlockNonce), + HeaderHash: currentHash, + } + } + return &block.BaseExecutionResult{ + HeaderNonce: 0, + HeaderHash: prevHash, + } + }, + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + if lastExecutedBlockNonce > 0 { + return uint64(lastExecutedBlockNonce), currentHash, nil + } + return 0, prevHash, nil + }, + SetLastExecutedBlockHeaderAndRootHashCalled: func(header data.HeaderHandler, blockHash []byte, rootHash []byte) { + lastExecutedBlockNonce = int(header.GetNonce()) + }, + } + args.BlocksCache = &processMocks.BlocksCacheMock{ + GetByNonceCalled: func(nonce uint64) (cache.HeaderBodyPair, bool) { + atomic.AddUint32(&cntWasPopCalled, 1) + + return cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: 1, + PrevHash: prevHash, + }, + Body: &block.Body{}, + }, true + }, + } + + wasProcessBlockProposalCalled := atomicCore.Flag{} + args.BlockProcessor = &processMocks.BlockProcessorStub{ + ProcessBlockProposalCalled: func(handler data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + wasProcessBlockProposalCalled.SetValue(true) + time.Sleep(time.Millisecond * 500) // force pause to be called first + + return &block.BaseExecutionResult{ + HeaderNonce: handler.GetNonce(), + HeaderHash: headerHash, + }, nil + }, + } + + executor, err := NewHeadersExecutor(args) + require.NoError(t, err) + + executor.PauseExecution() // coverage, should early exit + + executor.StartExecution() + + // allow some Pop operations + time.Sleep(time.Millisecond * 200) + + require.True(t, wasProcessBlockProposalCalled.IsSet()) // require this here so we know the processing is in progress + + executor.PauseExecution() // blocks until any ongoing processing completes + cntWasPopCalledAtPause := atomic.LoadUint32(&cntWasPopCalled) + + executor.PauseExecution() // coverage, already paused + + cntWasPopCalledBeforeResume := atomic.LoadUint32(&cntWasPopCalled) + require.Equal(t, cntWasPopCalledAtPause, cntWasPopCalledBeforeResume) + + executor.ResumeExecution() + + time.Sleep(time.Millisecond * 200) + + cntWasPopCalledAfterResume := atomic.LoadUint32(&cntWasPopCalled) + require.Greater(t, cntWasPopCalledAfterResume, cntWasPopCalledBeforeResume) + + err = executor.Close() + require.NoError(t, err) + }) + + t.Run("concurrent pause/resume should work", func(t *testing.T) { + t.Parallel() + + require.NotPanics(t, func() { + args := createMockArgs() + args.BlockProcessor = &processMocks.BlockProcessorStub{ + ProcessBlockProposalCalled: func(handler data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + return &block.BaseExecutionResult{ + HeaderHash: headerHash, + }, nil + }, + } + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.BaseExecutionResult{} + }, + } + + executor, err := NewHeadersExecutor(args) + require.NoError(t, err) + + executor.StartExecution() + + wg := sync.WaitGroup{} + numCalls := 100 + wg.Add(numCalls) + for i := 0; i < numCalls; i++ { + go func(idx int) { + defer wg.Done() + + switch idx % 2 { + case 0: + executor.PauseExecution() + case 1: + executor.ResumeExecution() + default: + require.Fail(t, "should not happen") + } + + time.Sleep(time.Millisecond * 3) + }(i) + } + + wg.Wait() + + err = executor.Close() + require.NoError(t, err) + }) + }) + + t.Run("add execution result error", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + blocksCache := cache.NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + args.BlocksCache = blocksCache + wasAddExecutionResultCalled := atomicCore.Flag{} + executedNonce := uint64(0) + executedHash := prevHash + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV3{ + Nonce: executedNonce, + } + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.BaseExecutionResult{ + HeaderNonce: executedNonce, + HeaderHash: executedHash, + } + }, + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return executedNonce, executedHash, nil + }, + SetLastExecutedBlockHeaderAndRootHashCalled: func(header data.HeaderHandler, blockHash []byte, rootHash []byte) { + executedNonce = header.GetNonce() + executedHash = blockHash + }, + } + args.BlockProcessor = &processMocks.BlockProcessorStub{ + ProcessBlockProposalCalled: func(handler data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + return &block.BaseExecutionResult{ + HeaderNonce: handler.GetNonce(), + HeaderHash: headerHash, + }, nil + }, + } + args.ExecutionTracker = &processMocks.ExecutionTrackerStub{ + AddExecutionResultCalled: func(executionResult data.BaseExecutionResultHandler) (bool, error) { + if wasAddExecutionResultCalled.IsSet() { + // return nil after first call to allow processing to complete + return true, nil + } + wasAddExecutionResultCalled.SetValue(true) + return false, errExpected + }, + } + + executor, err := NewHeadersExecutor(args) + require.NoError(t, err) + + err = blocksCache.AddOrReplace(cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: 1, + PrevHash: prevHash, + }, + Body: &block.Body{}, + HeaderHash: []byte("a"), + }) + require.NoError(t, err) + + executor.StartExecution() + + // allow Pop operation + time.Sleep(time.Millisecond * 100) + + err = executor.Close() + require.NoError(t, err) + require.True(t, wasAddExecutionResultCalled.IsSet()) + }) + + t.Run("block processing error, after retry should work", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + blocksCache := cache.NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + count := 0 + countAddResult := 0 + args.BlocksCache = blocksCache + wg := &sync.WaitGroup{} + wg.Add(1) + nonce := uint64(0) + executedHash := prevHash + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV3{ + Nonce: nonce, + } + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.BaseExecutionResult{ + HeaderNonce: nonce, + HeaderHash: executedHash, + } + }, + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return nonce, executedHash, nil + }, + SetLastExecutedBlockHeaderAndRootHashCalled: func(header data.HeaderHandler, blockHash []byte, rootHash []byte) { + nonce = header.GetNonce() + executedHash = blockHash + }, + } + args.BlockProcessor = &processMocks.BlockProcessorStub{ + ProcessBlockProposalCalled: func(handler data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + if count == 1 { + return &block.BaseExecutionResult{ + HeaderNonce: handler.GetNonce(), + HeaderHash: headerHash, + }, nil + } + count++ + return nil, errExpected + }, + } + args.ExecutionTracker = &processMocks.ExecutionTrackerStub{ + AddExecutionResultCalled: func(executionResult data.BaseExecutionResultHandler) (bool, error) { + countAddResult++ + wg.Done() + return true, nil + }, + } + + executor, err := NewHeadersExecutor(args) + require.NoError(t, err) + + executor.StartExecution() + + err = blocksCache.AddOrReplace(cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: 1, + PrevHash: prevHash, + }, + Body: &block.Body{}, + HeaderHash: []byte("a"), + }) + require.NoError(t, err) + + wg.Wait() + err = executor.Close() + require.NoError(t, err) + require.Equal(t, 1, countAddResult) + }) + + t.Run("block processing error, different hash should early exit", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + blocksCache := cache.NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + + count := 0 + countAddResult := 0 + nonce := uint64(0) + executedHash := prevHash + args.BlocksCache = blocksCache + wg := &sync.WaitGroup{} + wg.Add(1) + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV3{ + Nonce: nonce, + } + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.BaseExecutionResult{ + HeaderNonce: nonce, + HeaderHash: executedHash, + } + }, + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return nonce, executedHash, nil + }, + SetLastExecutedBlockHeaderAndRootHashCalled: func(header data.HeaderHandler, blockHash []byte, rootHash []byte) { + nonce = header.GetNonce() + executedHash = blockHash + }, + } + args.BlockProcessor = &processMocks.BlockProcessorStub{ + ProcessBlockProposalCalled: func(handler data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + time.Sleep(time.Millisecond) + if handler.GetRound() == 1 { + return nil, errExpected + } + + count++ + return &block.BaseExecutionResult{ + HeaderNonce: handler.GetNonce(), + HeaderHash: headerHash, + }, nil + }, + } + args.ExecutionTracker = &processMocks.ExecutionTrackerStub{ + AddExecutionResultCalled: func(executionResult data.BaseExecutionResultHandler) (bool, error) { + countAddResult++ + wg.Done() + return true, nil + }, + } + + executor, err := NewHeadersExecutor(args) + require.NoError(t, err) + + executor.StartExecution() + + err = blocksCache.AddOrReplace(cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: 1, + Round: 1, + PrevHash: prevHash, + }, + Body: &block.Body{}, + HeaderHash: []byte("a"), + }) + require.NoError(t, err) + + time.Sleep(time.Millisecond) + err = blocksCache.AddOrReplace(cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: 1, + Round: 2, + PrevHash: prevHash, + }, + Body: &block.Body{}, + HeaderHash: currentHash, + }) + require.NoError(t, err) + + wg.Wait() + + require.Equal(t, 1, count) + }) +} + +func TestHeadersExecutor_Process(t *testing.T) { + t.Parallel() + + var testPrevHash = []byte("prevHash") + var testDifferentHash = []byte("differentHash") + var testCommittedHash = []byte("committedHash") + var testChangedHash = []byte("changedHash") + var testNonce = uint64(1) + + t.Run("should return error on failing to process block", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + + expectedErr := errors.New("expected error") + args.BlockProcessor = &processMocks.BlockProcessorStub{ + ProcessBlockProposalCalled: func(handler data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + return nil, expectedErr + }, + } + + executor, _ := NewHeadersExecutor(args) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: testNonce, + }, + Body: &block.Body{}, + } + + err := executor.Process(pair) + require.Equal(t, expectedErr, err) + }) + + t.Run("should return error on failing to add execution results to execution tracker", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + countAddResult := 0 + + expectedErr := errors.New("expected error") + args.BlockProcessor = &processMocks.BlockProcessorStub{ + ProcessBlockProposalCalled: func(handler data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + return &block.BaseExecutionResult{ + HeaderHash: headerHash, + }, nil + }, + } + args.ExecutionTracker = &processMocks.ExecutionTrackerStub{ + AddExecutionResultCalled: func(executionResult data.BaseExecutionResultHandler) (bool, error) { + countAddResult++ + return false, expectedErr + }, + } + + executor, _ := NewHeadersExecutor(args) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: testNonce, + }, + Body: &block.Body{}, + } + + err := executor.Process(pair) + require.Equal(t, expectedErr, err) + }) + + t.Run("should call CommitBlockProposalState when all checks pass", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + + commitCalled := false + revertCalled := false + args.BlockProcessor = &processMocks.BlockProcessorStub{ + ProcessBlockProposalCalled: func(handler data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + return &block.BaseExecutionResult{ + HeaderHash: headerHash, + }, nil + }, + CommitBlockProposalStateCalled: func(headerHandler data.HeaderHandler) error { + commitCalled = true + return nil + }, + RevertBlockProposalStateCalled: func() { + revertCalled = true + }, + } + args.ExecutionTracker = &processMocks.ExecutionTrackerStub{ + AddExecutionResultCalled: func(executionResult data.BaseExecutionResultHandler) (bool, error) { + return true, nil + }, + } + + executor, _ := NewHeadersExecutor(args) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: testNonce, + }, + Body: &block.Body{}, + } + + err := executor.Process(pair) + require.Nil(t, err) + require.True(t, commitCalled) + require.False(t, revertCalled) + }) + + t.Run("should call RevertBlockProposalState when nil execution result is returned", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + + revertCalled := false + commitCalled := false + args.BlockProcessor = &processMocks.BlockProcessorStub{ + ProcessBlockProposalCalled: func(handler data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + return nil, nil + }, + CommitBlockProposalStateCalled: func(headerHandler data.HeaderHandler) error { + commitCalled = true + return nil + }, + RevertBlockProposalStateCalled: func() { + revertCalled = true + }, + } + + executor, _ := NewHeadersExecutor(args) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: testNonce, + }, + Body: &block.Body{}, + } + + err := executor.Process(pair) + require.Equal(t, ErrNilExecutionResult, err) + require.True(t, revertCalled) + require.False(t, commitCalled) + }) + + t.Run("should call RevertBlockProposalState when context mismatch after processing", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + + revertCalled := false + commitCalled := false + callCount := 0 + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + callCount++ + if callCount == 1 { + // First call (before processing) - return valid context + return &block.BaseExecutionResult{ + HeaderNonce: 0, + HeaderHash: testPrevHash, + } + } + // Second call (after processing) - return mismatched context to trigger revert + return &block.BaseExecutionResult{ + HeaderNonce: 5, + HeaderHash: testDifferentHash, + } + }, + } + args.BlockProcessor = &processMocks.BlockProcessorStub{ + ProcessBlockProposalCalled: func(handler data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + return &block.BaseExecutionResult{ + HeaderHash: headerHash, + }, nil + }, + CommitBlockProposalStateCalled: func(headerHandler data.HeaderHandler) error { + commitCalled = true + return nil + }, + RevertBlockProposalStateCalled: func() { + revertCalled = true + }, + } + + executor, _ := NewHeadersExecutor(args) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: testNonce, + PrevHash: testPrevHash, + }, + Body: &block.Body{}, + } + + err := executor.Process(pair) + require.Nil(t, err) + require.True(t, revertCalled) + require.False(t, commitCalled) + }) + + t.Run("should call RevertBlockProposalState when committed block hash differs", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + + revertCalled := false + commitCalled := false + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.BaseExecutionResult{} + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.Header{ + Nonce: testNonce, + } + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return testCommittedHash + }, + } + args.BlockProcessor = &processMocks.BlockProcessorStub{ + ProcessBlockProposalCalled: func(handler data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + return &block.BaseExecutionResult{ + HeaderNonce: testNonce, + HeaderHash: testDifferentHash, + }, nil + }, + CommitBlockProposalStateCalled: func(headerHandler data.HeaderHandler) error { + commitCalled = true + return nil + }, + RevertBlockProposalStateCalled: func() { + revertCalled = true + }, + } + + executor, _ := NewHeadersExecutor(args) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: testNonce, + }, + Body: &block.Body{}, + } + + err := executor.Process(pair) + require.Nil(t, err) + require.True(t, revertCalled) + require.False(t, commitCalled) + }) + + t.Run("should call RevertBlockProposalState when last execution result header hash mismatches prevHash", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + + revertCalled := false + commitCalled := false + getLastExecResultCallCount := 0 + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + getLastExecResultCallCount++ + if getLastExecResultCallCount <= 2 { + // First two calls (checkLastExecutionResultContext before and after processing): + // return matching context so those checks pass + return &block.BaseExecutionResult{ + HeaderNonce: 0, + HeaderHash: testPrevHash, + } + } + // Third call (explicit prevHash comparison): + // return mismatched header hash to trigger the revert + return &block.BaseExecutionResult{ + HeaderNonce: 0, + HeaderHash: testChangedHash, + } + }, + } + args.BlockProcessor = &processMocks.BlockProcessorStub{ + ProcessBlockProposalCalled: func(handler data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + return &block.BaseExecutionResult{ + HeaderHash: headerHash, + }, nil + }, + CommitBlockProposalStateCalled: func(headerHandler data.HeaderHandler) error { + commitCalled = true + return nil + }, + RevertBlockProposalStateCalled: func() { + revertCalled = true + }, + } + + executor, _ := NewHeadersExecutor(args) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: testNonce, + PrevHash: testPrevHash, + }, + Body: &block.Body{}, + } + + err := executor.Process(pair) + require.Nil(t, err) + require.True(t, revertCalled) + require.False(t, commitCalled) + }) + + t.Run("should call RevertBlockProposalState when CommitBlockProposalState fails", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + + revertCalled := false + expectedErr := errors.New("commit failed") + args.BlockProcessor = &processMocks.BlockProcessorStub{ + ProcessBlockProposalCalled: func(handler data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + return &block.BaseExecutionResult{ + HeaderHash: headerHash, + }, nil + }, + CommitBlockProposalStateCalled: func(headerHandler data.HeaderHandler) error { + return expectedErr + }, + RevertBlockProposalStateCalled: func() { + revertCalled = true + }, + } + + executor, _ := NewHeadersExecutor(args) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: testNonce, + }, + Body: &block.Body{}, + } + + err := executor.Process(pair) + require.Equal(t, expectedErr, err) + require.True(t, revertCalled) + }) + + t.Run("should not call RevertBlockProposalState when AddExecutionResult fails after commit", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + + commitCalled := false + revertCalled := false + expectedErr := errors.New("add result failed") + args.BlockProcessor = &processMocks.BlockProcessorStub{ + ProcessBlockProposalCalled: func(handler data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + return &block.BaseExecutionResult{ + HeaderHash: headerHash, + }, nil + }, + CommitBlockProposalStateCalled: func(headerHandler data.HeaderHandler) error { + commitCalled = true + return nil + }, + RevertBlockProposalStateCalled: func() { + revertCalled = true + }, + } + args.ExecutionTracker = &processMocks.ExecutionTrackerStub{ + AddExecutionResultCalled: func(executionResult data.BaseExecutionResultHandler) (bool, error) { + return false, expectedErr + }, + } + + executor, _ := NewHeadersExecutor(args) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: testNonce, + }, + Body: &block.Body{}, + } + + err := executor.Process(pair) + require.Equal(t, expectedErr, err) + require.True(t, commitCalled) + require.False(t, revertCalled) + }) + + t.Run("should not call RevertBlockProposalState when AddExecutionResult rejects after commit", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + + commitCalled := false + revertCalled := false + args.BlockProcessor = &processMocks.BlockProcessorStub{ + ProcessBlockProposalCalled: func(handler data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + return &block.BaseExecutionResult{ + HeaderHash: headerHash, + }, nil + }, + CommitBlockProposalStateCalled: func(headerHandler data.HeaderHandler) error { + commitCalled = true + return nil + }, + RevertBlockProposalStateCalled: func() { + revertCalled = true + }, + } + args.ExecutionTracker = &processMocks.ExecutionTrackerStub{ + AddExecutionResultCalled: func(executionResult data.BaseExecutionResultHandler) (bool, error) { + return false, nil + }, + } + + setFinalBlockInfoCalled := false + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.BaseExecutionResult{} + }, + SetFinalBlockInfoCalled: func(nonce uint64, headerHash, rootHash []byte) { + setFinalBlockInfoCalled = true + }, + } + + executor, _ := NewHeadersExecutor(args) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: testNonce, + }, + Body: &block.Body{}, + } + + err := executor.Process(pair) + require.Nil(t, err) + require.True(t, commitCalled) + require.False(t, revertCalled) + require.False(t, setFinalBlockInfoCalled) + }) + + t.Run("should add execution result info to blockchain handler", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + + args.BlockProcessor = &processMocks.BlockProcessorStub{ + ProcessBlockProposalCalled: func(handler data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + return &block.BaseExecutionResult{ + HeaderHash: headerHash, + }, nil + }, + } + args.ExecutionTracker = &processMocks.ExecutionTrackerStub{ + AddExecutionResultCalled: func(executionResult data.BaseExecutionResultHandler) (bool, error) { + return true, nil + }, + } + + setFinalBlockInfoCalled := false + setLastExecutedBlockInfoCalled := false + setLastExecutionResultCalled := false + args.BlockChain = &testscommon.ChainHandlerStub{ + SetFinalBlockInfoCalled: func(nonce uint64, headerHash, rootHash []byte) { + setFinalBlockInfoCalled = true + }, + SetLastExecutedBlockHeaderAndRootHashCalled: func(header data.HeaderHandler, headerHash, rootHash []byte) { + setLastExecutedBlockInfoCalled = true + }, + SetLastExecutionResultCalled: func(result data.BaseExecutionResultHandler) { + setLastExecutionResultCalled = true + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.BaseExecutionResult{} + }, + } + + executor, _ := NewHeadersExecutor(args) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: testNonce, + }, + Body: &block.Body{}, + } + + err := executor.Process(pair) + require.Nil(t, err) + + require.True(t, setFinalBlockInfoCalled) + require.True(t, setLastExecutedBlockInfoCalled) + require.True(t, setLastExecutionResultCalled) + }) +} + +func TestHeadersExecutor_GetSignalProcessCompletionChan(t *testing.T) { + t.Parallel() + + t.Run("should return nil when channel is not set", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.SignalProcessCompletionChan = nil + + executor, err := NewHeadersExecutor(args) + require.NoError(t, err) + + ch := executor.GetSignalProcessCompletionChan() + require.Nil(t, ch) + + err = executor.Close() + require.NoError(t, err) + }) + + t.Run("should return channel when set", func(t *testing.T) { + t.Parallel() + + signalChan := make(chan uint64, 1) + args := createMockArgs() + args.SignalProcessCompletionChan = signalChan + + executor, err := NewHeadersExecutor(args) + require.NoError(t, err) + + ch := executor.GetSignalProcessCompletionChan() + require.NotNil(t, ch) + require.Equal(t, signalChan, ch) + + err = executor.Close() + require.NoError(t, err) + }) +} + +func TestHeadersExecutor_SignalProcessCompletion(t *testing.T) { + t.Parallel() + + t.Run("should signal channel on successful process", func(t *testing.T) { + t.Parallel() + + signalChan := make(chan uint64, 1) + args := createMockArgs() + args.SignalProcessCompletionChan = signalChan + + args.BlockProcessor = &processMocks.BlockProcessorStub{ + ProcessBlockProposalCalled: func(handler data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + return &block.BaseExecutionResult{ + HeaderNonce: handler.GetNonce(), + HeaderHash: headerHash, + }, nil + }, + } + args.ExecutionTracker = &processMocks.ExecutionTrackerStub{ + AddExecutionResultCalled: func(executionResult data.BaseExecutionResultHandler) (bool, error) { + return true, nil + }, + } + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return nil // Return nil so checkLastExecutionResultContext passes + }, + } + + executor, err := NewHeadersExecutor(args) + require.NoError(t, err) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: 5, + }, + Body: &block.Body{}, + HeaderHash: []byte("hash"), + } + + err = executor.Process(pair) + require.NoError(t, err) + + // Check that signal was sent + select { + case nonce := <-signalChan: + require.Equal(t, uint64(5), nonce) + default: + require.Fail(t, "expected signal on channel") + } + + err = executor.Close() + require.NoError(t, err) + }) + + t.Run("should not block when channel is full", func(t *testing.T) { + t.Parallel() + + signalChan := make(chan uint64, 1) + // Pre-fill the channel + signalChan <- 1 + + args := createMockArgs() + args.SignalProcessCompletionChan = signalChan + + args.BlockProcessor = &processMocks.BlockProcessorStub{ + ProcessBlockProposalCalled: func(handler data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + return &block.BaseExecutionResult{ + HeaderNonce: handler.GetNonce(), + HeaderHash: headerHash, + }, nil + }, + } + args.ExecutionTracker = &processMocks.ExecutionTrackerStub{ + AddExecutionResultCalled: func(executionResult data.BaseExecutionResultHandler) (bool, error) { + return true, nil + }, + } + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return nil // Return nil so checkLastExecutionResultContext passes + }, + } + + executor, err := NewHeadersExecutor(args) + require.NoError(t, err) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: 5, + }, + Body: &block.Body{}, + HeaderHash: []byte("hash"), + } + + // Should not block even though channel is full + done := make(chan struct{}) + go func() { + err := executor.Process(pair) + require.NoError(t, err) + close(done) + }() + + select { + case <-done: + // Success - process completed without blocking + case <-time.After(time.Second): + require.Fail(t, "process blocked when channel was full") + } + + err = executor.Close() + require.NoError(t, err) + }) + + t.Run("should not panic when channel is nil", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.SignalProcessCompletionChan = nil + + args.BlockProcessor = &processMocks.BlockProcessorStub{ + ProcessBlockProposalCalled: func(handler data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + return &block.BaseExecutionResult{ + HeaderNonce: handler.GetNonce(), + HeaderHash: headerHash, + }, nil + }, + } + args.ExecutionTracker = &processMocks.ExecutionTrackerStub{ + AddExecutionResultCalled: func(executionResult data.BaseExecutionResultHandler) (bool, error) { + return true, nil + }, + } + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return nil // Return nil so checkLastExecutionResultContext passes + }, + } + + executor, err := NewHeadersExecutor(args) + require.NoError(t, err) + + pair := cache.HeaderBodyPair{ + Header: &block.Header{ + Nonce: 5, + }, + Body: &block.Body{}, + HeaderHash: []byte("hash"), + } + + require.NotPanics(t, func() { + err := executor.Process(pair) + require.NoError(t, err) + }) + + err = executor.Close() + require.NoError(t, err) + }) +} diff --git a/process/asyncExecution/interface.go b/process/asyncExecution/interface.go new file mode 100644 index 00000000000..23012a6d742 --- /dev/null +++ b/process/asyncExecution/interface.go @@ -0,0 +1,30 @@ +package asyncExecution + +import ( + "github.com/multiversx/mx-chain-core-go/data" + + "github.com/multiversx/mx-chain-go/process/asyncExecution/cache" +) + +// BlocksCache defines what a block queue should be able to do +type BlocksCache interface { + GetByNonce(nonce uint64) (cache.HeaderBodyPair, bool) + AddOrReplace(pair cache.HeaderBodyPair) error + Remove(nonce uint64) + IsInterfaceNil() bool +} + +// ExecutionResultsHandler defines what an execution results handler should be able to do +type ExecutionResultsHandler interface { + AddExecutionResult(executionResult data.BaseExecutionResultHandler) (bool, error) + IsInterfaceNil() bool +} + +// BlockProcessor defines what a block processor should be able to do +type BlockProcessor interface { + ProcessBlockProposal(header data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) + CommitBlockProposalState(headerHandler data.HeaderHandler) error + RevertBlockProposalState() + PruneTrieAsyncHeader() + IsInterfaceNil() bool +} diff --git a/process/block/argProcessor.go b/process/block/argProcessor.go index e418426b0f0..6c1d09a90b9 100644 --- a/process/block/argProcessor.go +++ b/process/block/argProcessor.go @@ -1,6 +1,8 @@ package block import ( + "sync/atomic" + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/typeConverters" @@ -28,11 +30,13 @@ type coreComponentsHolder interface { EpochNotifier() process.EpochNotifier EnableEpochsHandler() common.EnableEpochsHandler RoundNotifier() process.RoundNotifier - EnableRoundsHandler() process.EnableRoundsHandler + EnableRoundsHandler() common.EnableRoundsHandler EpochChangeGracePeriodHandler() common.EpochChangeGracePeriodHandler + ProcessConfigsHandler() common.ProcessConfigsHandler RoundHandler() consensus.RoundHandler EconomicsData() process.EconomicsDataHandler ProcessStatusHandler() common.ProcessStatusHandler + ClosingNodeStarted() *atomic.Bool IsInterfaceNil() bool } @@ -69,34 +73,44 @@ type ArgBaseProcessor struct { StatusComponents statusComponentsHolder StatusCoreComponents statusCoreComponentsHolder - Config config.Config - PrefsConfig config.Preferences - AccountsDB map[state.AccountsDbIdentifier]state.AccountsAdapter - ForkDetector process.ForkDetector - NodesCoordinator nodesCoordinator.NodesCoordinator - FeeHandler process.TransactionFeeHandler - RequestHandler process.RequestHandler - BlockChainHook process.BlockChainHookHandler - TxCoordinator process.TransactionCoordinator - EpochStartTrigger process.EpochStartTriggerHandler - HeaderValidator process.HeaderConstructionValidator - BootStorer process.BootStorer - BlockTracker process.BlockTracker - BlockSizeThrottler process.BlockSizeThrottler - Version string - HistoryRepository dblookupext.HistoryRepository - VMContainersFactory process.VirtualMachinesContainerFactory - VmContainer process.VirtualMachinesContainer - GasHandler gasConsumedProvider - OutportDataProvider outport.DataProviderOutport - ScheduledTxsExecutionHandler process.ScheduledTxsExecutionHandler - ScheduledMiniBlocksEnableEpoch uint32 - ProcessedMiniBlocksTracker process.ProcessedMiniBlocksTracker - ReceiptsRepository receiptsRepository - BlockProcessingCutoffHandler cutoff.BlockProcessingCutoffHandler - ManagedPeersHolder common.ManagedPeersHolder - SentSignaturesTracker process.SentSignaturesTracker - StateAccessesCollector state.StateAccessesCollector + Config config.Config + PrefsConfig config.Preferences + AccountsDB map[state.AccountsDbIdentifier]state.AccountsAdapter + AccountsProposal state.AccountsAdapter + ForkDetector process.ForkDetector + NodesCoordinator nodesCoordinator.NodesCoordinator + FeeHandler process.TransactionFeeHandler + RequestHandler process.RequestHandler + BlockChainHook process.BlockChainHookHandler + TxCoordinator process.TransactionCoordinator + EpochStartTrigger process.EpochStartTriggerHandler + HeaderValidator process.HeaderConstructionValidator + BootStorer process.BootStorer + BlockTracker process.BlockTracker + BlockSizeThrottler process.BlockSizeThrottler + Version string + HistoryRepository dblookupext.HistoryRepository + VMContainersFactory process.VirtualMachinesContainerFactory + VmContainer process.VirtualMachinesContainer + GasHandler gasConsumedProvider + OutportDataProvider outport.DataProviderOutport + ScheduledTxsExecutionHandler process.ScheduledTxsExecutionHandler + ScheduledMiniBlocksEnableEpoch uint32 + ProcessedMiniBlocksTracker process.ProcessedMiniBlocksTracker + ReceiptsRepository receiptsRepository + BlockProcessingCutoffHandler cutoff.BlockProcessingCutoffHandler + ManagedPeersHolder common.ManagedPeersHolder + SentSignaturesTracker process.SentSignaturesTracker + StateAccessesCollector state.StateAccessesCollector + HeadersForBlock HeadersForBlock + ExecutionResultsInclusionEstimator process.InclusionEstimator + MiniBlocksSelectionSession MiniBlocksSelectionSession + ExecutionResultsVerifier ExecutionResultsVerifier + MissingDataResolver MissingDataResolver + GasComputation process.GasComputation + ExecutionManager process.ExecutionManager + TxExecutionOrderHandler common.TxExecutionOrderHandler + AOTSelector process.AOTTransactionSelector } // ArgShardProcessor holds all dependencies required by the process data factory in order to create @@ -117,4 +131,5 @@ type ArgMetaProcessor struct { EpochValidatorInfoCreator process.EpochStartValidatorInfoCreator EpochSystemSCProcessor process.EpochStartSystemSCProcessor ValidatorStatisticsProcessor process.ValidatorStatisticsProcessor + ShardInfoCreator process.ShardInfoCreator } diff --git a/process/block/baseProcess.go b/process/block/baseProcess.go index 5b433ea4d3a..506e1e2a0cc 100644 --- a/process/block/baseProcess.go +++ b/process/block/baseProcess.go @@ -4,10 +4,12 @@ import ( "bytes" "context" "encoding/hex" + "errors" "fmt" "math/big" "sort" "sync" + "sync/atomic" "time" "github.com/multiversx/mx-chain-core-go/core" @@ -43,32 +45,48 @@ import ( "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/state/factory" "github.com/multiversx/mx-chain-go/state/parsers" + "github.com/multiversx/mx-chain-go/storage" "github.com/multiversx/mx-chain-go/storage/storageunit" ) +const ( + cleanupHeadersDelta = 5 + waitForExecutionResultsCheckInterval = 5 * time.Millisecond + + maxGapForEWLThreshold = 250 // cap to prevent pathological configs + ewlEntriesPerResult = 2 // OldRoot + NewRoot per commit + ewlTolerancePercent = 130 // 30% tolerance over expected size + ewlThresholdMinBaseline = 10 // minimum baseline to avoid false resets on small gaps +) + var log = logger.GetOrCreate("process/block") +// CrossShardIncomingMbsCreationResult represents the result of creating cross-shard mini blocks +type CrossShardIncomingMbsCreationResult struct { + HeaderFinished bool + HasMissingData bool + PendingMiniBlocks []block.MiniblockAndHash + AddedMiniBlocks []block.MiniblockAndHash +} + type hashAndHdr struct { hdr data.HeaderHandler hash []byte } -type nonceAndHashInfo struct { - hash []byte - nonce uint64 -} - -type hdrInfo struct { - usedInBlock bool - hdr data.HeaderHandler - hasProof bool - hasProofRequested bool +type splitTxsResult struct { + incomingMiniBlocks []data.MiniBlockHeaderHandler + outGoingMiniBlocks []data.MiniBlockHeaderHandler + incomingTransactions map[string][]data.TransactionHandler + outgoingTransactionHashes [][]byte + outgoingTransactions []data.TransactionHandler } type baseProcessor struct { shardCoordinator sharding.Coordinator nodesCoordinator nodesCoordinator.NodesCoordinator accountsDB map[state.AccountsDbIdentifier]state.AccountsAdapter + accountsProposal state.AccountsAdapter forkDetector process.ForkDetector hasher hashing.Hasher marshalizer marshal.Marshalizer @@ -87,7 +105,7 @@ type baseProcessor struct { dataPool dataRetriever.PoolsHolder feeHandler process.TransactionFeeHandler blockChain data.ChainHandler - hdrsForCurrBlock *hdrForBlock + hdrsForCurrBlock HeadersForBlock genesisNonce uint64 mutProcessDebugger sync.RWMutex processDebugger process.Debugger @@ -110,13 +128,14 @@ type baseProcessor struct { epochNotifier process.EpochNotifier enableEpochsHandler common.EnableEpochsHandler roundNotifier process.RoundNotifier - enableRoundsHandler process.EnableRoundsHandler + enableRoundsHandler common.EnableRoundsHandler vmContainerFactory process.VirtualMachinesContainerFactory vmContainer process.VirtualMachinesContainer gasConsumedProvider gasConsumedProvider economicsData process.EconomicsDataHandler epochChangeGracePeriodHandler common.EpochChangeGracePeriodHandler stateAccessesCollector state.StateAccessesCollector + processConfigsHandler common.ProcessConfigsHandler processDataTriesOnCommitEpoch bool lastRestartNonce uint64 @@ -126,10 +145,23 @@ type baseProcessor struct { mutNonceOfFirstCommittedBlock sync.RWMutex nonceOfFirstCommittedBlock core.OptionalUint64 - extraDelayRequestBlockInfo time.Duration - proofsPool dataRetriever.ProofsPool - chRcvAllHdrs chan bool + proofsPool dataRetriever.ProofsPool + executionResultsInclusionEstimator process.InclusionEstimator + miniBlocksSelectionSession MiniBlocksSelectionSession + executionResultsVerifier ExecutionResultsVerifier + missingDataResolver MissingDataResolver + gasComputation process.GasComputation + executionManager process.ExecutionManager + txExecutionOrderHandler common.TxExecutionOrderHandler + aotSelector process.AOTTransactionSelector + maxProposalNonceGap uint64 + ewlResetThreshold int + closingNodeStarted *atomic.Bool + + lastPrunedHeaderHash []byte + lastPrunedHeaderNonce uint64 + mutLastPrunedHeader sync.RWMutex } type bootStorerDataArgs struct { @@ -143,6 +175,103 @@ type bootStorerDataArgs struct { epochStartTriggerConfigKey []byte } +// NewBaseProcessor will create a new instance of baseProcessor +func NewBaseProcessor(arguments ArgBaseProcessor) (*baseProcessor, error) { + err := checkProcessorParameters(arguments) + if err != nil { + return nil, err + } + + processDebugger, err := createDisabledProcessDebugger() + if err != nil { + return nil, err + } + + genesisHdr := arguments.DataComponents.Blockchain().GetGenesisHeader() + if check.IfNil(genesisHdr) { + return nil, fmt.Errorf("%w for genesis header in DataComponents.Blockchain", process.ErrNilHeaderHandler) + } + + maxProposalNonceGap := arguments.Config.GeneralSettings.MaxProposalNonceGap + if maxProposalNonceGap < defaultMaxProposalNonceGap { + maxProposalNonceGap = defaultMaxProposalNonceGap + } + + ewlResetThreshold := computeEWLResetThreshold(maxProposalNonceGap) + + base := &baseProcessor{ + accountsDB: arguments.AccountsDB, + accountsProposal: arguments.AccountsProposal, + blockSizeThrottler: arguments.BlockSizeThrottler, + forkDetector: arguments.ForkDetector, + hasher: arguments.CoreComponents.Hasher(), + marshalizer: arguments.CoreComponents.InternalMarshalizer(), + store: arguments.DataComponents.StorageService(), + shardCoordinator: arguments.BootstrapComponents.ShardCoordinator(), + feeHandler: arguments.FeeHandler, + nodesCoordinator: arguments.NodesCoordinator, + uint64Converter: arguments.CoreComponents.Uint64ByteSliceConverter(), + requestHandler: arguments.RequestHandler, + appStatusHandler: arguments.StatusCoreComponents.AppStatusHandler(), + blockChainHook: arguments.BlockChainHook, + txCoordinator: arguments.TxCoordinator, + epochStartTrigger: arguments.EpochStartTrigger, + headerValidator: arguments.HeaderValidator, + roundHandler: arguments.CoreComponents.RoundHandler(), + bootStorer: arguments.BootStorer, + blockTracker: arguments.BlockTracker, + dataPool: arguments.DataComponents.Datapool(), + blockChain: arguments.DataComponents.Blockchain(), + outportHandler: arguments.StatusComponents.OutportHandler(), + genesisNonce: genesisHdr.GetNonce(), + versionedHeaderFactory: arguments.BootstrapComponents.VersionedHeaderFactory(), + headerIntegrityVerifier: arguments.BootstrapComponents.HeaderIntegrityVerifier(), + historyRepo: arguments.HistoryRepository, + epochNotifier: arguments.CoreComponents.EpochNotifier(), + enableEpochsHandler: arguments.CoreComponents.EnableEpochsHandler(), + roundNotifier: arguments.CoreComponents.RoundNotifier(), + enableRoundsHandler: arguments.CoreComponents.EnableRoundsHandler(), + epochChangeGracePeriodHandler: arguments.CoreComponents.EpochChangeGracePeriodHandler(), + vmContainerFactory: arguments.VMContainersFactory, + vmContainer: arguments.VmContainer, + processDataTriesOnCommitEpoch: arguments.Config.Debug.EpochStart.ProcessDataTrieOnCommitEpoch, + gasConsumedProvider: arguments.GasHandler, + economicsData: arguments.CoreComponents.EconomicsData(), + scheduledTxsExecutionHandler: arguments.ScheduledTxsExecutionHandler, + pruningDelay: pruningDelay, + processedMiniBlocksTracker: arguments.ProcessedMiniBlocksTracker, + receiptsRepository: arguments.ReceiptsRepository, + processDebugger: processDebugger, + outportDataProvider: arguments.OutportDataProvider, + processStatusHandler: arguments.CoreComponents.ProcessStatusHandler(), + blockProcessingCutoffHandler: arguments.BlockProcessingCutoffHandler, + managedPeersHolder: arguments.ManagedPeersHolder, + sentSignaturesTracker: arguments.SentSignaturesTracker, + stateAccessesCollector: arguments.StateAccessesCollector, + proofsPool: arguments.DataComponents.Datapool().Proofs(), + hdrsForCurrBlock: arguments.HeadersForBlock, + processConfigsHandler: arguments.CoreComponents.ProcessConfigsHandler(), + executionResultsInclusionEstimator: arguments.ExecutionResultsInclusionEstimator, + miniBlocksSelectionSession: arguments.MiniBlocksSelectionSession, + executionResultsVerifier: arguments.ExecutionResultsVerifier, + missingDataResolver: arguments.MissingDataResolver, + gasComputation: arguments.GasComputation, + executionManager: arguments.ExecutionManager, + txExecutionOrderHandler: arguments.TxExecutionOrderHandler, + aotSelector: arguments.AOTSelector, + maxProposalNonceGap: maxProposalNonceGap, + ewlResetThreshold: ewlResetThreshold, + closingNodeStarted: arguments.CoreComponents.ClosingNodeStarted(), + } + + err = base.OnExecutedBlock(genesisHdr, genesisHdr.GetRootHash()) + if err != nil { + return nil, err + } + + return base, nil +} + func checkForNils( headerHandler data.HeaderHandler, bodyHandler data.BodyHandler, @@ -172,8 +301,7 @@ func (bp *baseProcessor) checkBlockValidity( if check.IfNil(currentBlockHeader) { if headerHandler.GetNonce() == bp.genesisNonce+1 { // first block after genesis if bytes.Equal(headerHandler.GetPrevHash(), bp.blockChain.GetGenesisHeaderHash()) { - // TODO: add genesis block verification - return nil + return bp.checkTimestamp(headerHandler) } log.Debug("hash does not match", @@ -227,6 +355,24 @@ func (bp *baseProcessor) checkBlockValidity( return process.ErrEpochDoesNotMatch } + return bp.checkTimestamp(headerHandler) +} + +func (bp *baseProcessor) checkTimestamp(headerHandler data.HeaderHandler) error { + _, headerTimestampMs, err := common.GetHeaderTimestamps(headerHandler, bp.enableEpochsHandler) + if err != nil { + return err + } + + expectedTimestampMs := bp.roundHandler.GetTimeStampForRound(headerHandler.GetRound()) + if headerTimestampMs != expectedTimestampMs { + log.Debug("timestamp does not match", + "expected timestamp ms", expectedTimestampMs, + "received timestamp ms", headerTimestampMs) + + return process.ErrInvalidTimestamp + } + return nil } @@ -255,6 +401,29 @@ func (bp *baseProcessor) checkScheduledRootHash(headerHandler data.HeaderHandler return nil } +// checkHeaderExecutionResultNonceGap validates the nonce gap between a header's nonce +// and its last execution result's header nonce +func (bp *baseProcessor) checkHeaderExecutionResultNonceGap(header data.HeaderHandler) error { + headerNonce := header.GetNonce() + lastExecutionResultNonce := common.GetLastExecutionResultNonce(header) + + if lastExecutionResultNonce > headerNonce { + return process.ErrInvalidLastExecutionResult + } + + nonceGap := headerNonce - lastExecutionResultNonce + if nonceGap > bp.maxProposalNonceGap { + return fmt.Errorf("%w: header nonce %d has nonce gap of %d from last execution result nonce %d, max allowed gap is %d", + process.ErrNonceGapTooLarge, + headerNonce, + nonceGap, + lastExecutionResultNonce, + bp.maxProposalNonceGap) + } + + return nil +} + // verifyStateRoot verifies the state root hash given as parameter against the // Merkle trie root hash stored for accounts and returns if equal or not func (bp *baseProcessor) verifyStateRoot(rootHash []byte) bool { @@ -286,6 +455,7 @@ func (bp *baseProcessor) requestHeadersIfMissing( return err } + lastNotarizedHdrEpoch := prevHdr.GetEpoch() lastNotarizedHdrRound := prevHdr.GetRound() lastNotarizedHdrNonce := prevHdr.GetNonce() @@ -323,7 +493,8 @@ func (bp *baseProcessor) requestHeadersIfMissing( for _, nonce := range missingNonces { bp.addHeaderIntoTrackerPool(nonce, shardId) - go bp.requestHeaderByShardAndNonce(shardId, nonce) + go bp.requestHeaderIfNeeded(nonce, shardId) + go bp.requestProofIfNeeded(nonce, shardId, lastNotarizedHdrEpoch) } return nil @@ -416,10 +587,6 @@ func displayHeader( "", "Rand seed", logger.DisplayByteSlice(headerHandler.GetRandSeed())}), - display.NewLineData(false, []string{ - "", - "Pub keys bitmap", - hex.EncodeToString(headerHandler.GetPubKeysBitmap())}), display.NewLineData(false, []string{ "", "Signature", @@ -428,14 +595,6 @@ func displayHeader( "", "Leader's Signature", logger.DisplayByteSlice(headerHandler.GetLeaderSignature())}), - display.NewLineData(false, []string{ - "", - "Scheduled root hash", - logger.DisplayByteSlice(scheduledRootHash)}), - display.NewLineData(false, []string{ - "", - "Root hash", - logger.DisplayByteSlice(headerHandler.GetRootHash())}), display.NewLineData(false, []string{ "", "Validator stats root hash", @@ -444,12 +603,48 @@ func displayHeader( "", "Receipts hash", logger.DisplayByteSlice(headerHandler.GetReceiptsHash())}), - display.NewLineData(true, []string{ + display.NewLineData(!headerHandler.IsHeaderV3(), []string{ "", "Epoch start meta hash", logger.DisplayByteSlice(epochStartMetaHash)}), } + if headerHandler.IsHeaderV3() { + if isMetaHeader { + logLines = append(logLines, display.NewLineData(false, []string{ + "", + "Is epoch change proposed", + fmt.Sprintf("%t", metaHeader.IsEpochChangeProposed())})) + } + + lastExecResult, _ := common.GetLastBaseExecutionResultHandler(headerHandler) + if !check.IfNil(lastExecResult) { + horizontalLineAfterLastExecResult := len(headerHandler.GetExecutionResultsHandlers()) == 0 + logLines = append(logLines, display.NewLineData(horizontalLineAfterLastExecResult, []string{ + "", + "Last execution result", + logger.DisplayByteSlice(lastExecResult.GetHeaderHash())})) + } + + for idx, execRes := range headerHandler.GetExecutionResultsHandlers() { + shouldAddHorizontalLine := idx == len(headerHandler.GetExecutionResultsHandlers())-1 + logLines = append(logLines, display.NewLineData(shouldAddHorizontalLine, []string{ + "", + fmt.Sprintf("Execution result %d", execRes.GetHeaderNonce()), + logger.DisplayByteSlice(execRes.GetHeaderHash())})) + } + } else { + logLines = append(logLines, + display.NewLineData(false, []string{ + "", + "Scheduled root hash", + logger.DisplayByteSlice(scheduledRootHash)}), + display.NewLineData(false, []string{ + "", + "Root hash", + logger.DisplayByteSlice(headerHandler.GetRootHash())})) + } + if hasProofInfo { logLines = append(logLines, display.NewLineData(false, []string{ @@ -488,12 +683,14 @@ func displayHeader( // checkProcessorParameters will check the input parameters values func checkProcessorParameters(arguments ArgBaseProcessor) error { - for key := range arguments.AccountsDB { if check.IfNil(arguments.AccountsDB[key]) { return process.ErrNilAccountsAdapter } } + if check.IfNil(arguments.AccountsProposal) { + return fmt.Errorf("%w for proposal", process.ErrNilAccountsAdapter) + } if check.IfNil(arguments.DataComponents) { return process.ErrNilDataComponentsHolder } @@ -630,13 +827,57 @@ func checkProcessorParameters(arguments ArgBaseProcessor) error { if check.IfNil(arguments.SentSignaturesTracker) { return process.ErrNilSentSignatureTracker } + if check.IfNil(arguments.StateAccessesCollector) { + return process.ErrNilStateAccessesCollector + } + if check.IfNil(arguments.HeadersForBlock) { + return process.ErrNilHeadersForBlock + } + if check.IfNil(arguments.DataComponents.Datapool()) { + return process.ErrNilDataPoolHolder + } + if check.IfNil(arguments.DataComponents.Datapool().Headers()) { + return process.ErrNilHeadersDataPool + } + if check.IfNil(arguments.DataComponents.Datapool().Transactions()) { + return process.ErrNilTransactionPool + } + if check.IfNil(arguments.DataComponents.Datapool().DirectSentTransactions()) { + return process.ErrNilDirectSentCache + } + if check.IfNil(arguments.ExecutionResultsInclusionEstimator) { + return process.ErrNilExecutionResultsInclusionEstimator + } + if check.IfNil(arguments.MiniBlocksSelectionSession) { + return process.ErrNilMiniBlocksSelectionSession + } + if check.IfNil(arguments.ExecutionResultsVerifier) { + return process.ErrNilExecutionResultsVerifier + } + if check.IfNil(arguments.MissingDataResolver) { + return process.ErrNilMissingDataResolver + } + if check.IfNil(arguments.GasComputation) { + return process.ErrNilGasComputation + } + if check.IfNil(arguments.ExecutionManager) { + return process.ErrNilExecutionManager + } + if check.IfNil(arguments.TxExecutionOrderHandler) { + return process.ErrNilTxExecutionOrderHandler + } + if check.IfNil(arguments.AOTSelector) { + return process.ErrNilAOTSelector + } + if arguments.CoreComponents.ClosingNodeStarted() == nil { + return process.ErrNilClosingNodeStartedFlag + } return nil } func (bp *baseProcessor) createBlockStarted() error { - bp.hdrsForCurrBlock.resetMissingHdrs() - bp.hdrsForCurrBlock.initMaps() + bp.hdrsForCurrBlock.Reset() scheduledGasAndFees := bp.scheduledTxsExecutionHandler.GetScheduledGasAndFees() bp.txCoordinator.CreateBlockStarted() bp.feeHandler.CreateBlockStarted(scheduledGasAndFees) @@ -660,89 +901,9 @@ func (bp *baseProcessor) verifyFees(header data.HeaderHandler) error { return nil } -func (bp *baseProcessor) filterHeadersWithoutProofs() (map[string]*hdrInfo, error) { - removedNonces := make(map[uint32]map[uint64]struct{}) - noncesWithProofs := make(map[uint32]map[uint64]struct{}) - shardIDs := common.GetShardIDs(bp.shardCoordinator.NumberOfShards()) - for shard := range shardIDs { - removedNonces[shard] = make(map[uint64]struct{}) - noncesWithProofs[shard] = make(map[uint64]struct{}) - } - filteredHeadersInfo := make(map[string]*hdrInfo) - - for hdrHash, headerInfo := range bp.hdrsForCurrBlock.hdrHashAndInfo { - if bp.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, headerInfo.hdr.GetEpoch()) { - if bp.hasMissingProof(headerInfo, hdrHash) { - removedNonces[headerInfo.hdr.GetShardID()][headerInfo.hdr.GetNonce()] = struct{}{} - continue - } - - noncesWithProofs[headerInfo.hdr.GetShardID()][headerInfo.hdr.GetNonce()] = struct{}{} - filteredHeadersInfo[hdrHash] = bp.hdrsForCurrBlock.hdrHashAndInfo[hdrHash] - continue - } - - filteredHeadersInfo[hdrHash] = bp.hdrsForCurrBlock.hdrHashAndInfo[hdrHash] - } - - for shard, nonces := range removedNonces { - for nonce := range nonces { - if _, ok := noncesWithProofs[shard][nonce]; !ok { - return nil, fmt.Errorf("%w for shard %d and nonce %d", process.ErrMissingHeaderProof, shard, nonce) - } - } - } - - return filteredHeadersInfo, nil -} - -func (bp *baseProcessor) computeHeadersForCurrentBlock(usedInBlock bool) (map[uint32][]data.HeaderHandler, error) { - hdrsForCurrentBlock := make(map[uint32][]data.HeaderHandler) - - hdrHashAndInfo, err := bp.filterHeadersWithoutProofs() - if err != nil { - return nil, err - } - - for hdrHash, headerInfo := range hdrHashAndInfo { - if headerInfo.usedInBlock != usedInBlock { - continue - } - - if bp.hasMissingProof(headerInfo, hdrHash) { - return nil, fmt.Errorf("%w for header with hash %s", process.ErrMissingHeaderProof, hex.EncodeToString([]byte(hdrHash))) - } - - hdrsForCurrentBlock[headerInfo.hdr.GetShardID()] = append(hdrsForCurrentBlock[headerInfo.hdr.GetShardID()], headerInfo.hdr) - } - - return hdrsForCurrentBlock, nil -} - -func (bp *baseProcessor) computeHeadersForCurrentBlockInfo(usedInBlock bool) (map[uint32][]*nonceAndHashInfo, error) { - hdrsForCurrentBlockInfo := make(map[uint32][]*nonceAndHashInfo) - - for metaBlockHash, headerInfo := range bp.hdrsForCurrBlock.hdrHashAndInfo { - if headerInfo.usedInBlock != usedInBlock { - continue - } - - if bp.hasMissingProof(headerInfo, metaBlockHash) { - return nil, fmt.Errorf("%w for header with hash %s", process.ErrMissingHeaderProof, hex.EncodeToString([]byte(metaBlockHash))) - } - - hdrsForCurrentBlockInfo[headerInfo.hdr.GetShardID()] = append(hdrsForCurrentBlockInfo[headerInfo.hdr.GetShardID()], - &nonceAndHashInfo{nonce: headerInfo.hdr.GetNonce(), hash: []byte(metaBlockHash)}) - } - - return hdrsForCurrentBlockInfo, nil -} - // TODO: remove bool parameter and give instead the set to sort func (bp *baseProcessor) sortHeadersForCurrentBlockByNonce(usedInBlock bool) (map[uint32][]data.HeaderHandler, error) { - bp.hdrsForCurrBlock.mutHdrsForBlock.RLock() - hdrsForCurrentBlock, err := bp.computeHeadersForCurrentBlock(usedInBlock) - bp.hdrsForCurrBlock.mutHdrsForBlock.RUnlock() + hdrsForCurrentBlock, err := bp.hdrsForCurrBlock.ComputeHeadersForCurrentBlock(usedInBlock) if err != nil { return nil, err } @@ -756,9 +917,7 @@ func (bp *baseProcessor) sortHeadersForCurrentBlockByNonce(usedInBlock bool) (ma } func (bp *baseProcessor) sortHeaderHashesForCurrentBlockByNonce(usedInBlock bool) (map[uint32][][]byte, error) { - bp.hdrsForCurrBlock.mutHdrsForBlock.RLock() - hdrsForCurrentBlockInfo, err := bp.computeHeadersForCurrentBlockInfo(usedInBlock) - bp.hdrsForCurrBlock.mutHdrsForBlock.RUnlock() + hdrsForCurrentBlockInfo, err := bp.hdrsForCurrBlock.ComputeHeadersForCurrentBlockInfo(usedInBlock) if err != nil { return nil, err } @@ -766,7 +925,7 @@ func (bp *baseProcessor) sortHeaderHashesForCurrentBlockByNonce(usedInBlock bool for _, hdrsForShard := range hdrsForCurrentBlockInfo { if len(hdrsForShard) > 1 { sort.Slice(hdrsForShard, func(i, j int) bool { - return hdrsForShard[i].nonce < hdrsForShard[j].nonce + return hdrsForShard[i].GetNonce() < hdrsForShard[j].GetNonce() }) } } @@ -774,20 +933,36 @@ func (bp *baseProcessor) sortHeaderHashesForCurrentBlockByNonce(usedInBlock bool hdrsHashesForCurrentBlock := make(map[uint32][][]byte, len(hdrsForCurrentBlockInfo)) for shardId, hdrsForShard := range hdrsForCurrentBlockInfo { for _, hdrForShard := range hdrsForShard { - hdrsHashesForCurrentBlock[shardId] = append(hdrsHashesForCurrentBlock[shardId], hdrForShard.hash) + hdrsHashesForCurrentBlock[shardId] = append(hdrsHashesForCurrentBlock[shardId], hdrForShard.GetHash()) } } return hdrsHashesForCurrentBlock, nil } -func (bp *baseProcessor) hasMissingProof(headerInfo *hdrInfo, hdrHash string) bool { - isFlagEnabledForHeader := bp.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, headerInfo.hdr.GetEpoch()) && headerInfo.hdr.GetNonce() >= 1 - if !isFlagEnabledForHeader { - return false +func (bp *baseProcessor) createMiniBlockHeaderHandlersForExecutionResults(body *block.Body) (int, []data.MiniBlockHeaderHandler, error) { + if len(body.MiniBlocks) == 0 { + return 0, nil, nil + } + + totalTxCount := 0 + miniBlockHeaderHandlers := make([]data.MiniBlockHeaderHandler, len(body.MiniBlocks)) + var err error + var txCount int + for i := 0; i < len(body.MiniBlocks); i++ { + txCount, miniBlockHeaderHandlers[i], err = bp.createMbHeaderWithoutReservedFields(body.MiniBlocks[i]) + if err != nil { + return 0, nil, err + } + totalTxCount += txCount + + err = setMiniBlockHeaderReservedFieldForExecutionResults(miniBlockHeaderHandlers[i]) + if err != nil { + return 0, nil, err + } } - return !bp.proofsPool.HasProof(headerInfo.hdr.GetShardID(), []byte(hdrHash)) + return totalTxCount, miniBlockHeaderHandlers, nil } func (bp *baseProcessor) createMiniBlockHeaderHandlers( @@ -800,23 +975,14 @@ func (bp *baseProcessor) createMiniBlockHeaderHandlers( totalTxCount := 0 miniBlockHeaderHandlers := make([]data.MiniBlockHeaderHandler, len(body.MiniBlocks)) - + var err error + var txCount int for i := 0; i < len(body.MiniBlocks); i++ { - txCount := len(body.MiniBlocks[i].TxHashes) - totalTxCount += txCount - - miniBlockHash, err := core.CalculateHash(bp.marshalizer, bp.hasher, body.MiniBlocks[i]) + txCount, miniBlockHeaderHandlers[i], err = bp.createMbHeaderWithoutReservedFields(body.MiniBlocks[i]) if err != nil { return 0, nil, err } - - miniBlockHeaderHandlers[i] = &block.MiniBlockHeader{ - Hash: miniBlockHash, - SenderShardID: body.MiniBlocks[i].SenderShardID, - ReceiverShardID: body.MiniBlocks[i].ReceiverShardID, - TxCount: uint32(txCount), - Type: body.MiniBlocks[i].Type, - } + totalTxCount += txCount err = bp.setMiniBlockHeaderReservedField(body.MiniBlocks[i], miniBlockHeaderHandlers[i], processedMiniBlocksDestMeInfo) if err != nil { @@ -827,6 +993,46 @@ func (bp *baseProcessor) createMiniBlockHeaderHandlers( return totalTxCount, miniBlockHeaderHandlers, nil } +func (bp *baseProcessor) createMbHeaderWithoutReservedFields(miniBlock *block.MiniBlock) (int, data.MiniBlockHeaderHandler, error) { + miniBlockHash, err := core.CalculateHash(bp.marshalizer, bp.hasher, miniBlock) + if err != nil { + return 0, nil, err + } + txCount := len(miniBlock.TxHashes) + + mbHeaderHandler := &block.MiniBlockHeader{ + Hash: miniBlockHash, + SenderShardID: miniBlock.SenderShardID, + ReceiverShardID: miniBlock.ReceiverShardID, + TxCount: uint32(txCount), + Type: miniBlock.Type, + } + + return txCount, mbHeaderHandler, nil +} + +func setMiniBlockHeaderReservedFieldForExecutionResults( + miniBlockHeaderHandler data.MiniBlockHeaderHandler, +) error { + + err := miniBlockHeaderHandler.SetIndexOfFirstTxProcessed(0) + if err != nil { + return err + } + + err = miniBlockHeaderHandler.SetIndexOfLastTxProcessed(int32(miniBlockHeaderHandler.GetTxCount()) - 1) + if err != nil { + return err + } + + err = miniBlockHeaderHandler.SetProcessingType(int32(block.Normal)) + if err != nil { + return err + } + + return miniBlockHeaderHandler.SetConstructionState(int32(block.Final)) +} + func (bp *baseProcessor) setMiniBlockHeaderReservedField( miniBlock *block.MiniBlock, miniBlockHeaderHandler data.MiniBlockHeaderHandler, @@ -941,11 +1147,15 @@ func isPartiallyExecuted( return processedMiniBlockInfo != nil && !processedMiniBlockInfo.FullyProcessed } -// check if header has the same miniblocks as presented in body -func (bp *baseProcessor) checkHeaderBodyCorrelation(miniBlockHeaders []data.MiniBlockHeaderHandler, body *block.Body) error { - mbHashesFromHdr := make(map[string]data.MiniBlockHeaderHandler, len(miniBlockHeaders)) +// check if header has the same mini blocks as presented in body +func (bp *baseProcessor) checkHeaderBodyCorrelationProposal(miniBlockHeaders []data.MiniBlockHeaderHandler, body *block.Body) error { + mbHashesFromHdr := make(map[string]struct{}, len(miniBlockHeaders)) for i := 0; i < len(miniBlockHeaders); i++ { - mbHashesFromHdr[string(miniBlockHeaders[i].GetHash())] = miniBlockHeaders[i] + if miniBlockHeaders[i] == nil { + return process.ErrNilMiniBlockHeader + } + + mbHashesFromHdr[string(miniBlockHeaders[i].GetHash())] = struct{}{} } if len(miniBlockHeaders) != len(body.MiniBlocks) { @@ -956,11 +1166,17 @@ func (bp *baseProcessor) checkHeaderBodyCorrelation(miniBlockHeaders []data.Mini return process.ErrDuplicatedHashInBlock } + var mbHdr data.MiniBlockHeaderHandler + var miniBlock *block.MiniBlock for i := 0; i < len(body.MiniBlocks); i++ { - miniBlock := body.MiniBlocks[i] + miniBlock = body.MiniBlocks[i] + mbHdr = miniBlockHeaders[i] if miniBlock == nil { return process.ErrNilMiniBlock } + if mbHdr == nil { + return process.ErrNilMiniBlockHeader + } mbHash, err := core.CalculateHash(bp.marshalizer, bp.hasher, miniBlock) if err != nil { @@ -968,28 +1184,104 @@ func (bp *baseProcessor) checkHeaderBodyCorrelation(miniBlockHeaders []data.Mini } mbHashStr := string(mbHash) - mbHdr, ok := mbHashesFromHdr[mbHashStr] + _, ok := mbHashesFromHdr[mbHashStr] if !ok { return process.ErrHeaderBodyMismatch } - if mbHdr.GetTxCount() != uint32(len(miniBlock.TxHashes)) { - return process.ErrHeaderBodyMismatch + err = checkMiniBlockWithMiniBlockHeader(mbHash, mbHdr, miniBlock) + if err != nil { + return err } - if mbHdr.GetReceiverShardID() != miniBlock.ReceiverShardID { - return process.ErrHeaderBodyMismatch + delete(mbHashesFromHdr, mbHashStr) + } + + return bp.checkMiniBlocksConstructionProposal(miniBlockHeaders) +} + +func (bp *baseProcessor) checkMiniBlocksConstructionProposal(miniBlockHeaders []data.MiniBlockHeaderHandler) error { + for i := 0; i < len(miniBlockHeaders); i++ { + // for Supernova all miniBlocks not part of an execution result need to have construction state Proposed + if miniBlockHeaders[i].GetConstructionState() != int32(block.Proposed) { + return process.ErrWrongMiniBlockConstructionState + } + if miniBlockHeaders[i].GetProcessingType() != int32(block.Normal) { + return process.ErrWrongMiniBlockProcessingType + } + } + return nil +} + +func checkMiniBlockWithMiniBlockHeader(mbHash []byte, mbHdr data.MiniBlockHeaderHandler, miniBlock *block.MiniBlock) error { + if !bytes.Equal(mbHash, mbHdr.GetHash()) { + return process.ErrHeaderBodyMismatch + } + + if mbHdr.GetTxCount() != uint32(len(miniBlock.TxHashes)) { + return process.ErrHeaderBodyMismatch + } + + if mbHdr.GetReceiverShardID() != miniBlock.ReceiverShardID { + return fmt.Errorf("%w: different mb receiver shard ID", process.ErrHeaderBodyMismatch) + } + + if mbHdr.GetSenderShardID() != miniBlock.SenderShardID { + return fmt.Errorf("%w: different mb sender shard ID", process.ErrHeaderBodyMismatch) + } + return nil +} + +// check if header has the same mini blocks as presented in body +func (bp *baseProcessor) checkHeaderBodyCorrelation(miniBlockHeaders []data.MiniBlockHeaderHandler, body *block.Body) error { + mbHashesFromHdr := make(map[string]struct{}, len(miniBlockHeaders)) + for i := 0; i < len(miniBlockHeaders); i++ { + if miniBlockHeaders[i] == nil { + return process.ErrNilMiniBlockHeader + } + + mbHashesFromHdr[string(miniBlockHeaders[i].GetHash())] = struct{}{} + } + + if len(miniBlockHeaders) != len(body.MiniBlocks) { + return process.ErrHeaderBodyMismatch + } + + if len(mbHashesFromHdr) != len(miniBlockHeaders) { + return process.ErrDuplicatedHashInBlock + } + + var mbHdr data.MiniBlockHeaderHandler + var miniBlock *block.MiniBlock + var mbHash []byte + var err error + for i := 0; i < len(body.MiniBlocks); i++ { + miniBlock = body.MiniBlocks[i] + mbHdr = miniBlockHeaders[i] + if miniBlock == nil { + return process.ErrNilMiniBlock + } + + mbHash, err = core.CalculateHash(bp.marshalizer, bp.hasher, miniBlock) + if err != nil { + return err } - if mbHdr.GetSenderShardID() != miniBlock.SenderShardID { + mbHashStr := string(mbHash) + _, ok := mbHashesFromHdr[mbHashStr] + if !ok { return process.ErrHeaderBodyMismatch } - err = process.CheckIfIndexesAreOutOfBound(mbHdr.GetIndexOfFirstTxProcessed(), mbHdr.GetIndexOfLastTxProcessed(), miniBlock) + err = checkMiniBlockWithMiniBlockHeader(mbHash, mbHdr, miniBlock) if err != nil { return err } + err = process.CheckIfIndexesAreOutOfBound(mbHdr.GetIndexOfFirstTxProcessed(), mbHdr.GetIndexOfLastTxProcessed(), miniBlock) + if err != nil { + return err + } err = checkConstructionStateAndIndexesCorrectness(mbHdr) if err != nil { return err @@ -1039,50 +1331,6 @@ func (bp *baseProcessor) checkScheduledMiniBlocksValidity(headerHandler data.Hea return nil } -// requestMissingFinalityAttestingHeaders requests the headers needed to accept the current selected headers for -// processing the current block. It requests the finality headers greater than the highest header, for given shard, -// related to the block which should be processed -// this method should be called only under the mutex protection: hdrsForCurrBlock.mutHdrsForBlock -func (bp *baseProcessor) requestMissingFinalityAttestingHeaders( - shardID uint32, - finality uint32, -) uint32 { - requestedHeaders := uint32(0) - missingFinalityAttestingHeaders := uint32(0) - - highestHdrNonce := bp.hdrsForCurrBlock.highestHdrNonce[shardID] - if highestHdrNonce == uint64(0) { - return missingFinalityAttestingHeaders - } - - headersPool := bp.dataPool.Headers() - lastFinalityAttestingHeader := highestHdrNonce + uint64(finality) - for i := highestHdrNonce + 1; i <= lastFinalityAttestingHeader; i++ { - headers, headersHashes, err := headersPool.GetHeadersByNonceAndShardId(i, shardID) - if err != nil { - missingFinalityAttestingHeaders++ - requestedHeaders++ - go bp.requestHeaderByShardAndNonce(shardID, i) - continue - } - - for index := range headers { - bp.hdrsForCurrBlock.hdrHashAndInfo[string(headersHashes[index])] = &hdrInfo{ - hdr: headers[index], - usedInBlock: false, - } - } - } - - if requestedHeaders > 0 { - log.Debug("requested missing finality attesting headers", - "num headers", requestedHeaders, - "shard", shardID) - } - - return missingFinalityAttestingHeaders -} - func (bp *baseProcessor) requestHeaderByShardAndNonce(shardID uint32, nonce uint64) { if shardID == core.MetachainShardId { bp.requestHandler.RequestMetaHeaderByNonce(nonce) @@ -1091,30 +1339,14 @@ func (bp *baseProcessor) requestHeaderByShardAndNonce(shardID uint32, nonce uint } } +func (bp *baseProcessor) cleanExecutionResultsFromTracker(header data.HeaderHandler) error { + return bp.executionManager.CleanConfirmedExecutionResults(header) +} + func (bp *baseProcessor) cleanupPools(headerHandler data.HeaderHandler) { noncesToPrevFinal := bp.getNoncesToFinal(headerHandler) + 1 - bp.cleanupBlockTrackerPools(noncesToPrevFinal) - - highestPrevFinalBlockNonce := bp.forkDetector.GetHighestFinalBlockNonce() - if highestPrevFinalBlockNonce > 0 { - highestPrevFinalBlockNonce-- - } - bp.removeHeadersBehindNonceFromPools( - true, - bp.shardCoordinator.SelfId(), - highestPrevFinalBlockNonce, - ) - - if common.IsFlagEnabledAfterEpochsStartBlock(headerHandler, bp.enableEpochsHandler, common.AndromedaFlag) { - err := bp.dataPool.Proofs().CleanupProofsBehindNonce(bp.shardCoordinator.SelfId(), highestPrevFinalBlockNonce) - if err != nil { - log.Warn("failed to cleanup notarized proofs behind nonce", - "nonce", noncesToPrevFinal, - "shardID", bp.shardCoordinator.SelfId(), - "error", err) - } - } + bp.cleanupPoolsForSelf(noncesToPrevFinal) if bp.shardCoordinator.SelfId() == core.MetachainShardId { for shardID := uint32(0); shardID < bp.shardCoordinator.NumberOfShards(); shardID++ { @@ -1124,6 +1356,32 @@ func (bp *baseProcessor) cleanupPools(headerHandler data.HeaderHandler) { bp.cleanupPoolsForCrossShard(core.MetachainShardId, noncesToPrevFinal) } + bp.cleanupBlockTrackerPools(noncesToPrevFinal) + + for _, executionResult := range headerHandler.GetExecutionResultsHandlers() { + executionResultHeaderHash := executionResult.GetHeaderHash() + // cleanup all intra shard miniblocks + bp.dataPool.MiniBlocks().Remove(executionResultHeaderHash) + // cleanup all log events + bp.dataPool.PostProcessTransactions().Remove(common.PrepareLogEventsKey(executionResultHeaderHash)) + // cleanup all unexecuted transaction from txpool + bp.cleanupUnexecutableTxsFromPool(executionResultHeaderHash) + } +} + +func (bp *baseProcessor) cleanupUnexecutableTxsFromPool(headerHash []byte) { + unexecutableTxHashes, err := common.GetCachedUnexecutableTxHashes(bp.dataPool.PostProcessTransactions(), headerHash) + if err != nil { + log.Warn("bp.cleanupPools cannot get unexecutable tx hashes", "err", err) + return + } + + cacheID := process.ShardCacherIdentifier(bp.shardCoordinator.SelfId(), bp.shardCoordinator.SelfId()) + for _, txHash := range unexecutableTxHashes { + bp.dataPool.Transactions().RemoveData(txHash, cacheID) + } + + bp.dataPool.PostProcessTransactions().Remove(common.PrepareUnexecutableTxHashesKey(headerHash)) } func (bp *baseProcessor) cleanupPoolsForCrossShard( @@ -1139,23 +1397,60 @@ func (bp *baseProcessor) cleanupPoolsForCrossShard( return } + bp.cleanupPoolsForHeader(shardID, crossNotarizedHeader, false) +} + +func (bp *baseProcessor) cleanupPoolsForSelf(noncesToPrevFinal uint64) { + shardID := bp.shardCoordinator.SelfId() + selfNotarizedHeader, _, err := bp.blockTracker.GetSelfNotarizedHeader(shardID, noncesToPrevFinal) + if err != nil { + displayCleanupErrorMessage("cleanupPoolsForSelf", + shardID, + noncesToPrevFinal, + err) + return + } + + bp.cleanupPoolsForHeader(shardID, selfNotarizedHeader, true) +} + +func (bp *baseProcessor) cleanupPoolsForHeader( + shardID uint32, + header data.HeaderHandler, + removeBody bool, +) { + firstNonceToBeRemoved, shouldRemove := getFirstNonceToBeRemoved(header) + if !shouldRemove { + return + } + bp.removeHeadersBehindNonceFromPools( - false, + removeBody, shardID, - crossNotarizedHeader.GetNonce(), + firstNonceToBeRemoved, ) - if common.IsFlagEnabledAfterEpochsStartBlock(crossNotarizedHeader, bp.enableEpochsHandler, common.AndromedaFlag) { - err = bp.dataPool.Proofs().CleanupProofsBehindNonce(shardID, noncesToPrevFinal) + if common.IsFlagEnabledAfterEpochsStartBlock(header, bp.enableEpochsHandler, common.AndromedaFlag) { + err := bp.dataPool.Proofs().CleanupProofsBehindNonce(shardID, firstNonceToBeRemoved) if err != nil { log.Warn("failed to cleanup notarized proofs behind nonce", - "nonce", noncesToPrevFinal, + "firstNonceToBeRemoved", firstNonceToBeRemoved, "shardID", shardID, "error", err) } } } +func getFirstNonceToBeRemoved(headerHandler data.HeaderHandler) (uint64, bool) { + nonceToBeRemoved := common.GetFirstExecutionResultNonce(headerHandler) + if nonceToBeRemoved <= cleanupHeadersDelta { + return 0, false + } + + nonceToBeRemoved -= cleanupHeadersDelta + return nonceToBeRemoved, true +} + func (bp *baseProcessor) removeHeadersBehindNonceFromPools( shouldRemoveBlockBody bool, shardId uint32, @@ -1196,7 +1491,7 @@ func (bp *baseProcessor) removeBlocksBody(nonce uint64, shardId uint32) { } func (bp *baseProcessor) removeBlockBodyOfHeader(headerHandler data.HeaderHandler) error { - bodyHandler, err := bp.requestBlockBodyHandler.GetBlockBodyFromPool(headerHandler) + bodyHandler, err := bp.requestBlockBodyHandler.GetProposedAndExecutedMiniBlockHeaders(headerHandler) if err != nil { return err } @@ -1223,25 +1518,61 @@ func (bp *baseProcessor) removeBlockDataFromPools(headerHandler data.HeaderHandl return nil } -func (bp *baseProcessor) removeTxsFromPools(header data.HeaderHandler, body *block.Body) error { - newBody, err := bp.getFinalMiniBlocks(header, body) +func (bp *baseProcessor) removeTxsFromPools(headerHash []byte, header data.HeaderHandler, body *block.Body) error { + newBody, _, err := bp.getFinalMiniBlocks(headerHash, header, body) if err != nil { return err } - return bp.txCoordinator.RemoveTxsFromPool(newBody) + rootHashHolder, err := bp.extractRootHashForCleanup(header) + if err != nil { + return err + } + + return bp.txCoordinator.RemoveTxsFromPool(newBody, rootHashHolder) +} + +func (bp *baseProcessor) marshalledBodyToBroadcast(body *block.Body) map[uint32][]byte { + bodies := make(map[uint32]block.MiniBlockSlice) + + for _, miniBlock := range body.MiniBlocks { + if miniBlock.SenderShardID != bp.shardCoordinator.SelfId() || + miniBlock.ReceiverShardID == bp.shardCoordinator.SelfId() { + continue + } + bodies[miniBlock.ReceiverShardID] = append(bodies[miniBlock.ReceiverShardID], miniBlock) + } + + marshalledData := make(map[uint32][]byte, len(bodies)) + for shardId, subsetBlockBody := range bodies { + buff, err := bp.marshalizer.Marshal(&block.Body{MiniBlocks: subsetBlockBody}) + if err != nil { + log.Error("metaProcessor.MarshalizedDataToBroadcast.Marshal", "error", err.Error()) + continue + } + marshalledData[shardId] = buff + } + + return marshalledData } -func (bp *baseProcessor) getFinalMiniBlocks(header data.HeaderHandler, body *block.Body) (*block.Body, error) { +func (bp *baseProcessor) getFinalMiniBlocks(headerHash []byte, header data.HeaderHandler, body *block.Body) (*block.Body, map[string]block.MiniBlockSlice, error) { + if header.IsHeaderV3() { + return bp.getFinalMiniBlocksFromExecutionResults(header) + } + if !bp.enableEpochsHandler.IsFlagEnabled(common.ScheduledMiniBlocksFlag) { - return body, nil + return body, + map[string]block.MiniBlockSlice{ + string(headerHash): body.MiniBlocks, // all miniBlocks are from the current header + }, nil } var miniBlocks block.MiniBlockSlice if len(body.MiniBlocks) != len(header.GetMiniBlockHeaderHandlers()) { log.Warn("baseProcessor.getFinalMiniBlocks: num of mini blocks and mini blocks headers does not match", "num of mb", len(body.MiniBlocks), "num of mbh", len(header.GetMiniBlockHeaderHandlers())) - return nil, process.ErrNumOfMiniBlocksAndMiniBlocksHeadersMismatch + return nil, nil, process.ErrNumOfMiniBlocksAndMiniBlocksHeadersMismatch } for index, miniBlock := range body.MiniBlocks { @@ -1254,7 +1585,54 @@ func (bp *baseProcessor) getFinalMiniBlocks(header data.HeaderHandler, body *blo miniBlocks = append(miniBlocks, miniBlock) } - return &block.Body{MiniBlocks: miniBlocks}, nil + return &block.Body{MiniBlocks: miniBlocks}, + map[string]block.MiniBlockSlice{ + string(headerHash): miniBlocks, // all miniBlocks are from the current header + }, nil +} + +func (bp *baseProcessor) getFinalMiniBlocksFromExecutionResults( + header data.HeaderHandler, +) (*block.Body, map[string]block.MiniBlockSlice, error) { + var miniBlocks block.MiniBlockSlice + miniBlocksMap := make(map[string]block.MiniBlockSlice) + + baseExecutionResults := header.GetExecutionResultsHandlers() + if len(baseExecutionResults) == 0 { + return &block.Body{}, miniBlocksMap, nil + } + + executedMiniBlocksCache := bp.dataPool.ExecutedMiniBlocks() + for _, baseExecutionResult := range baseExecutionResults { + miniBlockHeaderHandlers, err := common.GetMiniBlocksHeaderHandlersFromExecResult(baseExecutionResult) + if err != nil { + return nil, nil, err + } + + headerHash := baseExecutionResult.GetHeaderHash() + for _, miniBlockHeaderHandler := range miniBlockHeaderHandlers { + mbHash := miniBlockHeaderHandler.GetHash() + cachedMiniBlock, found := executedMiniBlocksCache.Get(mbHash) + if !found { + log.Warn("mini block from execution result not cached after execution", + "mini block hash", mbHash) + return nil, nil, process.ErrMissingMiniBlock + } + + cachedMiniBlockBytes := cachedMiniBlock.([]byte) + + miniBlock := &block.MiniBlock{} + err = bp.marshalizer.Unmarshal(miniBlock, cachedMiniBlockBytes) + if err != nil { + return nil, nil, err + } + + miniBlocksMap[string(headerHash)] = append(miniBlocksMap[string(headerHash)], miniBlock) + miniBlocks = append(miniBlocks, miniBlock) + } + } + + return &block.Body{MiniBlocks: miniBlocks}, miniBlocksMap, nil } func (bp *baseProcessor) cleanupBlockTrackerPools(noncesToPrevFinal uint64) { @@ -1279,7 +1657,7 @@ func (bp *baseProcessor) cleanupBlockTrackerPoolsForShard(shardID uint32, nonces return } - selfNotarizedNonce := selfNotarizedHeader.GetNonce() + selfNotarizedNonce := common.GetLastExecutionResultNonce(selfNotarizedHeader) crossNotarizedNonce := uint64(0) if shardID != bp.shardCoordinator.SelfId() { @@ -1292,7 +1670,7 @@ func (bp *baseProcessor) cleanupBlockTrackerPoolsForShard(shardID uint32, nonces return } - crossNotarizedNonce = crossNotarizedHeader.GetNonce() + crossNotarizedNonce = common.GetLastExecutionResultNonce(crossNotarizedHeader) } bp.blockTracker.CleanupHeadersBehindNonce( @@ -1332,7 +1710,7 @@ func (bp *baseProcessor) prepareDataForBootStorer(args bootStorerDataArgs) { } elapsedTime := time.Since(startTime) - if elapsedTime >= common.PutInStorerMaxTime { + if elapsedTime >= bp.getPutInStorerMaxTime() { log.Warn("saveDataForBootStorer", "elapsed time", elapsedTime) } } @@ -1417,7 +1795,7 @@ func (bp *baseProcessor) getLastSelfNotarizedHeadersForShard(shardID uint32) *bo } headerInfo := &bootstrapStorage.BootstrapHeaderInfo{ - ShardId: lastSelfNotarizedHeader.GetShardID(), + ShardId: shardID, Nonce: lastSelfNotarizedHeader.GetNonce(), Hash: lastSelfNotarizedHeaderHash, } @@ -1447,7 +1825,7 @@ func (bp *baseProcessor) getNoncesToFinal(headerHandler data.HeaderHandler) uint } noncesToFinal := uint64(0) - finalBlockNonce := bp.forkDetector.GetHighestFinalBlockNonce() + finalBlockNonce := bp.getFinalBlockNonce(headerHandler) if currentBlockNonce > finalBlockNonce { noncesToFinal = currentBlockNonce - finalBlockNonce } @@ -1455,6 +1833,29 @@ func (bp *baseProcessor) getNoncesToFinal(headerHandler data.HeaderHandler) uint return noncesToFinal } +func (bp *baseProcessor) getFinalBlockNonce( + headerHandler data.HeaderHandler, +) uint64 { + finalBlockNonce := bp.forkDetector.GetHighestFinalBlockNonce() + if !headerHandler.IsHeaderV3() { + return finalBlockNonce + } + + finalHeaderHandler, err := bp.dataPool.Headers().GetHeaderByHash(bp.forkDetector.GetHighestFinalBlockHash()) + if err != nil { + log.Debug("getFinalBlockNonce: could not find header", + "hash", bp.forkDetector.GetHighestFinalBlockHash(), + ) + return finalBlockNonce + } + + if !finalHeaderHandler.IsHeaderV3() { + return finalHeaderHandler.GetNonce() + } + + return common.GetLastExecutionResultNonce(finalHeaderHandler) +} + // DecodeBlockBody method decodes block body from a given byte array func (bp *baseProcessor) DecodeBlockBody(dta []byte) data.BodyHandler { body := &block.Body{} @@ -1473,8 +1874,10 @@ func (bp *baseProcessor) DecodeBlockBody(dta []byte) data.BodyHandler { func (bp *baseProcessor) saveBody(body *block.Body, header data.HeaderHandler, headerHash []byte) { startTime := time.Now() - - bp.txCoordinator.SaveTxsToStorage(body) + err := bp.saveProposedTxsToStorage(header, body) + if err != nil { + log.Error("saveBody.saveProposedTxsToStorage", "error", err.Error()) + } log.Trace("saveBody.SaveTxsToStorage", "time", time.Since(startTime)) var errNotCritical error @@ -1496,9 +1899,8 @@ func (bp *baseProcessor) saveBody(body *block.Body, header data.HeaderHandler, h log.Trace("saveBody.Put -> MiniBlockUnit", "time", time.Since(startTime), "hash", miniBlockHash) } - receiptsHolder := holders.NewReceiptsHolder(bp.txCoordinator.GetCreatedInShardMiniBlocks()) - errNotCritical = bp.receiptsRepository.SaveReceipts(receiptsHolder, header, headerHash) - if errNotCritical != nil { + if !header.IsHeaderV3() { + errNotCritical = bp.saveReceiptsForHeader(header, headerHash) logging.LogErrAsWarnExceptAsDebugIfClosingError(log, errNotCritical, "saveBody(), error on receiptsRepository.SaveReceipts()", "err", errNotCritical) @@ -1507,11 +1909,105 @@ func (bp *baseProcessor) saveBody(body *block.Body, header data.HeaderHandler, h bp.scheduledTxsExecutionHandler.SaveStateIfNeeded(headerHash) elapsedTime := time.Since(startTime) - if elapsedTime >= common.PutInStorerMaxTime { + if elapsedTime >= bp.getPutInStorerMaxTime() { log.Warn("saveBody", "elapsed time", elapsedTime) } } +func (bp *baseProcessor) saveProposedTxsToStorage(header data.HeaderHandler, body *block.Body) error { + if !header.IsHeaderV3() { + bp.txCoordinator.SaveTxsToStorage(body) + return nil + } + + separatedBodies := process.SeparateBodyByType(body) + for blockType, blockBody := range separatedBodies { + dataPool, err := process.GetDataPoolByBlockType(blockType, bp.dataPool) + if err != nil { + return err + } + + unit, err := process.GetStorageUnitByBlockType(blockType) + if err != nil { + return err + } + + storer, err := bp.store.GetStorer(unit) + if err != nil { + return err + } + + for i := 0; i < len(blockBody.MiniBlocks); i++ { + miniBlock := blockBody.MiniBlocks[i] + err = bp.saveTxsToStorage(dataPool, storer, miniBlock) + if err != nil { + return err + } + } + } + + return nil +} + +func (bp *baseProcessor) saveTxsToStorage(dataPool dataRetriever.ShardedDataCacherNotifier, storer storage.Storer, miniBlock *block.MiniBlock) error { + if miniBlock.Type == block.PeerBlock { + // peer infos need special treatment as they do not implement TransactionHandler + return bp.savePeerInfoToStorage(dataPool, storer, miniBlock) + } + + txHashes := miniBlock.TxHashes + senderShardID := miniBlock.SenderShardID + receiverShardID := miniBlock.ReceiverShardID + method := process.SearchMethodPeekWithFallbackSearchFirst + + for _, txHash := range txHashes { + tx, err := process.GetTransactionHandlerFromPool(senderShardID, receiverShardID, txHash, dataPool, method) + if err != nil { + log.Warn("baseProcessor.saveTxsToStorage cannot get transaction from pool", "error", err.Error()) + continue + } + + marshalledTx, err := bp.marshalizer.Marshal(tx) + if err != nil { + return err + } + + err = storer.Put(txHash, marshalledTx) + if err != nil { + return err + } + } + + return nil +} + +func (bp *baseProcessor) savePeerInfoToStorage(dataPool dataRetriever.ShardedDataCacherNotifier, storer storage.Storer, miniBlock *block.MiniBlock) error { + hashes := miniBlock.TxHashes + for _, hash := range hashes { + val, ok := dataPool.SearchFirstData(hash) + if !ok { + return dataRetriever.ErrValidatorInfoNotFound + } + + validatorInfo, ok := val.(*state.ShardValidatorInfo) + if !ok { + return fmt.Errorf("%w for hash %s", process.ErrInvalidTxInPool, hex.EncodeToString(hash)) + } + + buff, err := bp.marshalizer.Marshal(validatorInfo) + if err != nil { + return err + } + + err = storer.Put(hash, buff) + if err != nil { + return err + } + } + + return nil +} + func (bp *baseProcessor) saveShardHeader(header data.HeaderHandler, headerHash []byte, marshalizedHeader []byte) { startTime := time.Now() @@ -1535,7 +2031,7 @@ func (bp *baseProcessor) saveShardHeader(header data.HeaderHandler, headerHash [ bp.saveProof(headerHash, header) elapsedTime := time.Since(startTime) - if elapsedTime >= common.PutInStorerMaxTime { + if elapsedTime >= bp.getPutInStorerMaxTime() { log.Warn("saveShardHeader", "elapsed time", elapsedTime) } } @@ -1562,11 +2058,19 @@ func (bp *baseProcessor) saveMetaHeader(header data.HeaderHandler, headerHash [] bp.saveProof(headerHash, header) elapsedTime := time.Since(startTime) - if elapsedTime >= common.PutInStorerMaxTime { + if elapsedTime >= bp.getPutInStorerMaxTime() { log.Warn("saveMetaHeader", "elapsed time", elapsedTime) } } +func (bp *baseProcessor) getPutInStorerMaxTime() time.Duration { + if bp.enableEpochsHandler.IsFlagEnabled(common.SupernovaFlag) { + return common.PutInStorerMaxTimeSupernova + } + + return common.PutInStorerMaxTime +} + func (bp *baseProcessor) saveProof( hash []byte, header data.HeaderHandler, @@ -1619,7 +2123,7 @@ func (bp *baseProcessor) setFinalizedHeaderHashInIndexer(hdrHash []byte) { } func (bp *baseProcessor) updateStateStorage( - finalHeader data.HeaderHandler, + finalHeaderNonce uint64, currRootHash []byte, prevRootHash []byte, accounts state.AccountsAdapter, @@ -1633,7 +2137,7 @@ func (bp *baseProcessor) updateStateStorage( } accounts.CancelPrune(prevRootHash, state.NewRoot) - accounts.PruneTrie(prevRootHash, state.OldRoot, bp.getPruningHandler(finalHeader.GetNonce())) + accounts.PruneTrie(prevRootHash, state.OldRoot, bp.getPruningHandler(finalHeaderNonce)) } // RevertCurrentBlock reverts the current block for cleanup failed process @@ -1642,6 +2146,28 @@ func (bp *baseProcessor) RevertCurrentBlock() { bp.revertScheduledInfo() } +// RevertHeaderV3OnCommit reverts the current block for commit failed +func (bp *baseProcessor) RevertHeaderV3OnCommit(headerHandler data.HeaderHandler) { + if check.IfNil(headerHandler) || !headerHandler.IsHeaderV3() { + return + } + + headerNonce := headerHandler.GetNonce() + err := bp.executionManager.RemoveAtNonceAndHigher(headerNonce) + if err != nil { + log.Debug("baseProcessor.revertCurrentBlockV3", "err", err) + lastExecResult, errGet := common.GetLastBaseExecutionResultHandler(headerHandler) + if errGet != nil { + log.Error("baseProcessor.revertCurrentBlockV3.GetLastBaseExecutionResultHandler", "err", errGet) + return + } + errReset := bp.executionManager.ResetAndResumeExecution(lastExecResult) + if errReset != nil { + log.Debug("baseProcessor.revertCurrentBlockV3.ResetAndResumeExecution", "err", errReset) + } + } +} + func (bp *baseProcessor) revertAccountState() { for key := range bp.accountsDB { err := bp.accountsDB[key].RevertToSnapshot(0) @@ -1653,6 +2179,11 @@ func (bp *baseProcessor) revertAccountState() { func (bp *baseProcessor) revertScheduledInfo() { header, headerHash := bp.getLastCommittedHeaderAndHash() + if header.IsHeaderV3() { + // v3 headers don't have scheduled info + return + } + err := bp.scheduledTxsExecutionHandler.RollBackToBlock(headerHash) if err != nil { log.Trace("baseProcessor.revertScheduledInfo", "error", err.Error()) @@ -1697,7 +2228,16 @@ func (bp *baseProcessor) RevertAccountsDBToSnapshot(accountsSnapshot map[state.A } } -func (bp *baseProcessor) commitAll(headerHandler data.HeaderHandler) error { +func (bp *baseProcessor) commitState(headerHandler data.HeaderHandler) error { + startTime := time.Now() + defer func() { + elapsedTime := time.Since(startTime) + log.Debug("elapsed time to commit accounts state", + "time [s]", elapsedTime, + "header nonce", headerHandler.GetNonce(), + ) + }() + if headerHandler.IsStartOfEpochBlock() { return bp.commitInLastEpoch(headerHandler.GetEpoch()) } @@ -1736,7 +2276,9 @@ func (bp *baseProcessor) commitInEpoch(currentEpoch uint32, epochToCommit uint32 return nil } -// PruneStateOnRollback recreates the state tries to the root hashes indicated by the provided headers +// PruneStateOnRollback recreates the state tries to the root hashes indicated by the provided headers. +// Not called for V3 headers: shouldAllowRollback returns false for V3 in baseSync.go. +// V3 block dismissal is handled via cancelPruneForDismissedExecutionResults. func (bp *baseProcessor) PruneStateOnRollback(currHeader data.HeaderHandler, currHeaderHash []byte, prevHeader data.HeaderHandler, prevHeaderHash []byte) { for key := range bp.accountsDB { if !bp.accountsDB[key].IsPruningEnabled() { @@ -1783,6 +2325,13 @@ func (bp *baseProcessor) getPruningHandler(finalHeaderNonce uint64) state.Prunin return state.NewPruningHandler(state.DisableDataRemoval) } + if bp.closingNodeStarted.Load() { + log.Debug("will skip pruning as closing node already started", + "finalHeaderNonce", finalHeaderNonce, + ) + return state.NewPruningHandler(state.DisableDataRemoval) + } + return state.NewPruningHandler(state.EnableDataRemoval) } @@ -1885,32 +2434,6 @@ func (bp *baseProcessor) RestoreBlockBodyIntoPools(bodyHandler data.BodyHandler) return nil } -func (bp *baseProcessor) requestMiniBlocksIfNeeded(headerHandler data.HeaderHandler) { - lastCrossNotarizedHeader, _, err := bp.blockTracker.GetLastCrossNotarizedHeader(headerHandler.GetShardID()) - if err != nil { - log.Debug("requestMiniBlocksIfNeeded.GetLastCrossNotarizedHeader", - "shard", headerHandler.GetShardID(), - "error", err.Error()) - return - } - - isHeaderOutOfRequestRange := headerHandler.GetNonce() > lastCrossNotarizedHeader.GetNonce()+process.MaxHeadersToRequestInAdvance - if isHeaderOutOfRequestRange { - return - } - - waitTime := bp.extraDelayRequestBlockInfo - roundDifferences := bp.roundHandler.Index() - int64(headerHandler.GetRound()) - if roundDifferences > 1 { - waitTime = 0 - } - - // waiting for late broadcast of mini blocks and transactions to be done and received - time.Sleep(waitTime) - - bp.txCoordinator.RequestMiniBlocksAndTransactions(headerHandler) -} - func (bp *baseProcessor) recordBlockInHistory(blockHeaderHash []byte, blockHeader data.HeaderHandler, blockBody data.BodyHandler) { scrResultsFromPool := bp.txCoordinator.GetAllCurrentUsedTxs(block.SmartContractResultBlock) receiptsFromPool := bp.txCoordinator.GetAllCurrentUsedTxs(block.ReceiptBlock) @@ -1940,7 +2463,7 @@ func (bp *baseProcessor) addHeaderIntoTrackerPool(nonce uint64, shardID uint32) } } -func (bp *baseProcessor) commitTrieEpochRootHashIfNeeded(metaBlock *block.MetaBlock, rootHash []byte) error { +func (bp *baseProcessor) commitTrieEpochRootHashIfNeeded(metaBlock data.MetaHeaderHandler, rootHash []byte) error { trieEpochRootHashStorageUnit, err := bp.store.GetStorer(dataRetriever.TrieEpochRootHashUnit) if err != nil { return err @@ -1959,7 +2482,7 @@ func (bp *baseProcessor) commitTrieEpochRootHashIfNeeded(metaBlock *block.MetaBl return fmt.Errorf("%w for user accounts state", process.ErrNilAccountsAdapter) } - epochBytes := bp.uint64Converter.ToByteSlice(uint64(metaBlock.Epoch)) + epochBytes := bp.uint64Converter.ToByteSlice(uint64(metaBlock.GetEpoch())) err = trieEpochRootHashStorageUnit.Put(epochBytes, rootHash) if err != nil { @@ -2046,7 +2569,7 @@ func (bp *baseProcessor) commitTrieEpochRootHashIfNeeded(metaBlock *block.MetaBl stats := []interface{}{ "shard", bp.shardCoordinator.SelfId(), - "epoch", metaBlock.Epoch, + "epoch", metaBlock.GetEpoch(), "sum", balanceSum.String(), "processDataTries", processDataTries, "numCodeLeaves", numCodeLeaves, @@ -2088,7 +2611,12 @@ func (bp *baseProcessor) unmarshalUserAccount( return userAccount, nil } -// Close - closes all underlying components +// ProposedDirectSentTransactionsToBroadcast creates marshaled intra-shard transactions received via direct-send for broadcasting +func (bp *baseProcessor) ProposedDirectSentTransactionsToBroadcast(proposedBody data.BodyHandler) map[string][][]byte { + return bp.txCoordinator.ProposedDirectSentTransactionsToBroadcast(proposedBody) +} + +// Close closes all underlying components func (bp *baseProcessor) Close() error { var err1, err2, err3 error if !check.IfNil(bp.vmContainer) { @@ -2109,7 +2637,9 @@ func (bp *baseProcessor) Close() error { // ProcessScheduledBlock processes a scheduled block func (bp *baseProcessor) ProcessScheduledBlock(headerHandler data.HeaderHandler, bodyHandler data.BodyHandler, haveTime func() time.Duration) error { var err error - bp.processStatusHandler.SetBusy("baseProcessor.ProcessScheduledBlock") + if !bp.processStatusHandler.TrySetBusy("baseProcessor.ProcessScheduledBlock") { + return process.ErrBlockProcessorBusy + } defer func() { if err != nil { bp.RevertCurrentBlock() @@ -2331,6 +2861,115 @@ func (bp *baseProcessor) setNonceOfFirstCommittedBlock(nonce uint64) { bp.nonceOfFirstCommittedBlock.Value = nonce } +// OnProposedBlock calls the OnProposedBlock from transactions pool +func (bp *baseProcessor) OnProposedBlock( + proposedBody data.BodyHandler, + proposedHeader data.HeaderHandler, + proposedHash []byte, +) error { + // this should be removed once OnProposedBlock accepts bodyHandler + proposedBodyPtr, ok := proposedBody.(*block.Body) + if !ok { + return process.ErrWrongTypeAssertion + } + + lastExecResHandler, err := bp.prepareAccountsForProposal() + if err != nil { + return err + } + + accountsProvider, err := state.NewAccountsEphemeralProvider(bp.accountsProposal) + if err != nil { + return err + } + + return bp.dataPool.Transactions().OnProposedBlock(proposedHash, proposedBodyPtr, proposedHeader, accountsProvider, lastExecResHandler.GetHeaderHash()) +} + +// OnBackfilledBlock adds a previously consensus-agreed block as tracked without breadcrumb validation. +func (bp *baseProcessor) OnBackfilledBlock( + proposedBody data.BodyHandler, + proposedHeader data.HeaderHandler, + proposedHash []byte, +) error { + proposedBodyPtr, ok := proposedBody.(*block.Body) + if !ok { + return process.ErrWrongTypeAssertion + } + + return bp.dataPool.Transactions().OnBackfilledBlock(proposedHash, proposedBodyPtr, proposedHeader) +} + +func (bp *baseProcessor) prepareAccountsForProposal() (data.BaseExecutionResultHandler, error) { + prevHeader := bp.blockChain.GetCurrentBlockHeader() + prevHeaderHash := bp.blockChain.GetCurrentBlockHeaderHash() + if check.IfNil(prevHeader) || len(prevHeaderHash) == 0 { + return nil, process.ErrNilHeaderHandler + } + lastExecResHandler, err := common.GetOrCreateLastExecutionResultForPrevHeader(prevHeader, prevHeaderHash) + if err != nil { + return nil, err + } + rootHash := lastExecResHandler.GetRootHash() + rootHashHolder := holders.NewDefaultRootHashesHolder(rootHash) + err = bp.accountsProposal.RecreateTrieIfNeeded(rootHashHolder) + if err != nil { + return nil, err + } + + return lastExecResHandler, nil +} + +// OnExecutedBlock notifies the underlying TxCache +func (bp *baseProcessor) OnExecutedBlock(header data.HeaderHandler, rootHash []byte) error { + bp.appStatusHandler.SetUInt64Value(common.MetricNumTrackedBlocks, bp.dataPool.Transactions().GetNumTrackedBlocks()) + bp.appStatusHandler.SetUInt64Value(common.MetricNumTrackedAccounts, bp.dataPool.Transactions().GetNumTrackedAccounts()) + + err := bp.dataPool.Transactions().OnExecutedBlock(header, rootHash) + if err != nil { + log.Error("baseProcessor.OnExecutedBlock", "err", err) + return err + } + + return nil +} + +func (bp *baseProcessor) recreateTrieIfNeeded() error { + rootHash := bp.blockChain.GetCurrentBlockRootHash() + if len(rootHash) == 0 { + genesisBlock := bp.blockChain.GetGenesisHeader() + rootHash = genesisBlock.GetRootHash() + } + + rh := holders.NewDefaultRootHashesHolder(rootHash) + err := bp.accountsProposal.RecreateTrieIfNeeded(rh) + if err != nil { + log.Error("baseProcessor.recreateTrieIfNeeded", "err", err) + return err + } + + return nil +} + +func (bp *baseProcessor) extractRootHashForCleanup(header data.HeaderHandler) (common.RootHashHolder, error) { + if header.IsHeaderV3() { + latestExecutionResult, err := common.GetLastBaseExecutionResultHandler(header) + if err != nil { + return nil, err + } + + return holders.NewDefaultRootHashesHolder(latestExecutionResult.GetRootHash()), nil + } + + additionalData := header.GetAdditionalData() + if additionalData == nil { + rootHash := bp.blockChain.GetCurrentBlockRootHash() + return holders.NewDefaultRootHashesHolder(rootHash), nil + } + + return holders.NewDefaultRootHashesHolder(additionalData.GetScheduledRootHash()), nil +} + func (bp *baseProcessor) checkSentSignaturesAtCommitTime(header data.HeaderHandler) error { _, validatorsGroup, err := headerCheck.ComputeConsensusGroup(header, bp.nodesCoordinator) if err != nil { @@ -2360,57 +2999,1233 @@ func (bp *baseProcessor) getHeaderHash(header data.HeaderHandler) ([]byte, error return bp.hasher.Compute(string(marshalledHeader)), nil } -func (bp *baseProcessor) requestProofIfNeeded(currentHeaderHash []byte, header data.HeaderHandler) bool { - if !bp.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, header.GetEpoch()) { - return false - } - if bp.proofsPool.HasProof(header.GetShardID(), currentHeaderHash) { - _, ok := bp.hdrsForCurrBlock.hdrHashAndInfo[string(currentHeaderHash)] - if ok { - bp.hdrsForCurrBlock.hdrHashAndInfo[string(currentHeaderHash)].hasProof = true - } - - return true +func (bp *baseProcessor) computeOwnShardStuckIfNeeded(header data.HeaderHandler) error { + if !header.IsHeaderV3() { + return nil } - _, ok := bp.hdrsForCurrBlock.hdrHashAndInfo[string(currentHeaderHash)] - if !ok { - bp.hdrsForCurrBlock.hdrHashAndInfo[string(currentHeaderHash)] = &hdrInfo{ - hdr: header, - } + lastExecResultsHandler, err := common.GetLastBaseExecutionResultHandler(header) + if err != nil { + return err } - bp.hdrsForCurrBlock.hdrHashAndInfo[string(currentHeaderHash)].hasProofRequested = true - bp.hdrsForCurrBlock.missingProofs++ - go bp.requestHandler.RequestEquivalentProofByHash(header.GetShardID(), currentHeaderHash) + bp.blockTracker.ComputeOwnShardStuck(lastExecResultsHandler, header.GetNonce()) + return nil +} + +func (bp *baseProcessor) updateGasConsumptionLimitsIfNeeded() { + if !bp.blockTracker.IsOwnShardStuck() { + bp.gasComputation.ResetIncomingLimit() + bp.gasComputation.ResetOutgoingLimit() + + return + } + + // shard is stuck, zeroing the limits + bp.gasComputation.ZeroIncomingLimit() + bp.gasComputation.ZeroOutgoingLimit() +} + +func (bp *baseProcessor) getMaxRoundsWithoutBlockReceived(round uint64) uint64 { + maxRoundsWithoutNewBlockReceived := bp.processConfigsHandler.GetMaxRoundsWithoutNewBlockReceivedByRound(round) + return uint64(maxRoundsWithoutNewBlockReceived) +} + +func (bp *baseProcessor) saveExecutedData(header data.HeaderHandler) error { + if !header.IsHeaderV3() { + return nil + } + + executionResults := header.GetExecutionResultsHandlers() + for _, execResult := range executionResults { + err := bp.saveExecutionResult(execResult) + if err != nil { + return err + } + + err = bp.saveReceiptsForExecutionResult(execResult) + if err != nil { + return err + } + + err = bp.saveMiniBlocksFromExecutionResults(execResult) + if err != nil { + return err + } + + err = bp.saveIntermediateTxs(execResult.GetHeaderHash()) + if err != nil { + return err + } + } + + return nil +} + +func (bp *baseProcessor) saveExecutionResult( + execResult data.BaseExecutionResultHandler, +) error { + if check.IfNil(execResult) { + return process.ErrNilExecutionResultHandler + } + + headerHash := execResult.GetHeaderHash() + execResBytes, err := bp.marshalizer.Marshal(execResult) + if err != nil { + return err + } + + return bp.store.Put(dataRetriever.ExecutionResultsUnit, headerHash, execResBytes) +} + +func (bp *baseProcessor) cleanPostProcessCache(header data.HeaderHandler) error { + executionResults := header.GetExecutionResultsHandlers() + postProcessTxsCache := bp.dataPool.PostProcessTransactions() + executedMbs := bp.dataPool.ExecutedMiniBlocks() + for _, execResult := range executionResults { + err := process.CleanCachesForExecutionResult(execResult, postProcessTxsCache, executedMbs) + if err != nil { + return err + } + } + + return nil +} + +func (bp *baseProcessor) saveMiniBlocksFromExecutionResults(baseExecutionResult data.BaseExecutionResultHandler) error { + miniBlockHeaderHandlers, err := common.GetMiniBlocksHeaderHandlersFromExecResult(baseExecutionResult) + if err != nil { + return err + } + + return bp.putMiniBlocksIntoStorage(miniBlockHeaderHandlers) +} + +func (bp *baseProcessor) putMiniBlocksIntoStorage(miniBlockHeaderHandlers []data.MiniBlockHeaderHandler) error { + if len(miniBlockHeaderHandlers) == 0 { + return nil + } + + miniBlockStorer, err := bp.store.GetStorer(dataRetriever.MiniBlockUnit) + if err != nil { + return err + } + + executedMiniBlocksCache := bp.dataPool.ExecutedMiniBlocks() + for _, miniBlockHeaderHandler := range miniBlockHeaderHandlers { + mbHash := miniBlockHeaderHandler.GetHash() + // do not save the cross-shard incoming mini blocks + selfShardID := bp.shardCoordinator.SelfId() + isCrossShardIncoming := miniBlockHeaderHandler.GetReceiverShardID() == selfShardID && + miniBlockHeaderHandler.GetSenderShardID() != selfShardID + if isCrossShardIncoming { + continue + } + + cachedMiniBlock, found := executedMiniBlocksCache.Get(mbHash) + if !found { + log.Warn("mini block from execution result not cached after execution", + "mini block hash", mbHash) + return process.ErrMissingMiniBlock + } + + cachedMiniBlockBytes, ok := cachedMiniBlock.([]byte) + if !ok { + return process.ErrWrongTypeAssertion + } + + errPut := miniBlockStorer.Put(mbHash, cachedMiniBlockBytes) + if errPut != nil { + return errPut + } + } + + return nil +} + +func (bp *baseProcessor) cacheIntraShardMiniBlocks(headerHash []byte, miniBlocks []*block.MiniBlock) error { + // TODO: analyse better ways to estimate cached data size, without marshalling + marshalledMbsSize, err := process.GetMarshaledSliceSize(miniBlocks, bp.marshalizer) + if err != nil { + return err + } + + bp.dataPool.ExecutedMiniBlocks().Put(headerHash, miniBlocks, marshalledMbsSize) + + return nil +} + +func (bp *baseProcessor) saveReceiptsForHeader(header data.HeaderHandler, headerHash []byte) error { + miniBlocks := bp.txCoordinator.GetCreatedInShardMiniBlocks() + if len(miniBlocks) == 0 { + return nil + } + + receiptsHolder := holders.NewReceiptsHolder(miniBlocks) + return bp.receiptsRepository.SaveReceipts(receiptsHolder, header, headerHash) +} + +func (bp *baseProcessor) saveReceiptsForExecutionResult( + execResult data.BaseExecutionResultHandler, +) error { + miniBlocks, err := bp.getMiniBlocksForReceiptsV3(execResult) + if err != nil { + return err + } + + if len(miniBlocks) == 0 { + return nil + } + + receiptsHolder := holders.NewReceiptsHolder(miniBlocks) + return bp.receiptsRepository.SaveReceiptsForExecResult(receiptsHolder, execResult) +} + +func (bp *baseProcessor) getMiniBlocksForReceiptsV3(execResult data.BaseExecutionResultHandler) ([]*block.MiniBlock, error) { + headerHash := execResult.GetHeaderHash() + + executedMiniBlocksCache := bp.dataPool.ExecutedMiniBlocks() + miniBlocksBuff, ok := executedMiniBlocksCache.Get(headerHash) + if !ok { + log.Warn("miniblocks not found in dataPool", "hash", headerHash) + return make([]*block.MiniBlock, 0), nil + } + + receiptsMiniBlocks, ok := miniBlocksBuff.([]*block.MiniBlock) + if !ok { + return nil, fmt.Errorf("%w for GetCachedMbs", process.ErrWrongTypeAssertion) + } + + return receiptsMiniBlocks, nil +} + +func (bp *baseProcessor) cacheLogEvents(headerHash []byte, logs []data.LogDataHandler) error { + logsMarshalledSize, err := process.GetMarshaledSliceSize(logs, bp.marshalizer) + if err != nil { + return err + } + + key := common.PrepareLogEventsKey(headerHash) + bp.dataPool.PostProcessTransactions().Put(key, logs, logsMarshalledSize) + + return nil +} + +func (bp *baseProcessor) cacheExecutedMiniBlocks(body *block.Body, miniBlockHeaders []data.MiniBlockHeaderHandler) error { + for i, mbHeader := range miniBlockHeaders { + miniBlockHash := mbHeader.GetHash() + marshalledMiniBlock, err := bp.marshalizer.Marshal(body.MiniBlocks[i]) + if err != nil { + return err + } + + bp.dataPool.ExecutedMiniBlocks().Put(miniBlockHash, marshalledMiniBlock, len(marshalledMiniBlock)) + } + + return nil +} + +func (bp *baseProcessor) cacheIntermediateTxsForHeader(headerHash []byte) error { + intermediateTxs := bp.txCoordinator.GetAllIntermediateTxs() + intermediateTxsSize, err := bp.getAllIntermediateTxsSize(intermediateTxs) + if err != nil { + return err + } + + bp.dataPool.PostProcessTransactions().Put(headerHash, intermediateTxs, intermediateTxsSize) + + return nil +} + +func (bp *baseProcessor) getAllIntermediateTxsSize( + allTxs map[block.Type]map[string]data.TransactionHandler, +) (int, error) { + size := 0 + + for _, txs := range allTxs { + numTxs := len(txs) + + oneTxSize := 0 + + var err error + if numTxs == 0 { + continue + } + + oneTxSize, err = getOneTxSizeFromMap(txs, bp.marshalizer) + if err != nil { + return 0, err + } + + size += oneTxSize * numTxs + } + + return size, nil +} + +func getOneTxSizeFromMap( + txs map[string]data.TransactionHandler, + marshaller marshal.Marshalizer, +) (int, error) { + for _, tx := range txs { + // get size of first tx, if any + marshalledTx, err := marshaller.Marshal(tx) + if err != nil { + return 0, err + } + + return len(marshalledTx), nil + } + + return 0, nil +} + +func (bp *baseProcessor) saveIntermediateTxs(headerHash []byte) error { + postProcessTxsCache := bp.dataPool.PostProcessTransactions() + cachedIntermediateTxs, ok := postProcessTxsCache.Get(headerHash) + if !ok { + log.Warn("saveIntermediateTxs: intermediateTxs not found in dataPool", "hash", headerHash) + return fmt.Errorf("%w for header %s", process.ErrMissingHeader, hex.EncodeToString(headerHash)) + } + + cachedIntermediateTxsMap, ok := cachedIntermediateTxs.(map[block.Type]map[string]data.TransactionHandler) + if !ok { + log.Warn("saveIntermediateTxs: intermediateTxs cannot cast to concrete type", "hash", headerHash) + } + + for blockType, cachedTransactionsMap := range cachedIntermediateTxsMap { + err := bp.putTransactionsIntoStorage(blockType, cachedTransactionsMap) + if err != nil { + return err + } + } + + return nil +} + +func (bp *baseProcessor) putTransactionsIntoStorage(blockType block.Type, cachedTransactionsMap map[string]data.TransactionHandler) error { + unit, err := getStorageUnitFromBlockType(blockType) + if err != nil { + return err + } + + storer, errGetStorer := bp.store.GetStorer(unit) + if errGetStorer != nil { + return errGetStorer + } + + for txHash, txHandler := range cachedTransactionsMap { + err = bp.putOneTransactionIntoStorage(storer, []byte(txHash), txHandler) + if err != nil { + return err + } + } + + return nil +} + +func (bp *baseProcessor) putOneTransactionIntoStorage( + storer storage.Storer, + txHash []byte, + tx data.TransactionHandler, +) error { + if check.IfNil(tx) { + return process.ErrNilTransaction + } + + buff, errMarshal := bp.marshalizer.Marshal(tx) + if errMarshal != nil { + return errMarshal + } + + return storer.Put(txHash, buff) +} + +func getStorageUnitFromBlockType(blockType block.Type) (dataRetriever.UnitType, error) { + switch blockType { + case block.TxBlock, block.InvalidBlock: + return dataRetriever.TransactionUnit, nil + case block.SmartContractResultBlock: + return dataRetriever.UnsignedTransactionUnit, nil + case block.ReceiptBlock: + return dataRetriever.ReceiptsUnit, nil + case block.RewardsBlock: + return dataRetriever.UnsignedTransactionUnit, nil + } + return 0, process.ErrInvalidBlockType +} + +func (bp *baseProcessor) waitForExecutionResultsVerification( + header data.HeaderHandler, + haveTime func() time.Duration, +) error { + isWaiting := false + for { + err := bp.executionResultsVerifier.VerifyHeaderExecutionResults(header) + if !errors.Is(err, process.ErrExecutionResultsNumberMismatch) { + return err + } + + remainingTime := haveTime() + if remainingTime <= 0 { + log.Debug("waitForExecutionResultsVerification: timed out waiting for execution results", + "header nonce", header.GetNonce(), + ) + return err + } + + if !isWaiting { + isWaiting = true + log.Debug("waitForExecutionResultsVerification: waiting for execution results", + "header nonce", header.GetNonce(), + "remaining time", remainingTime, + ) + } + + time.Sleep(min(waitForExecutionResultsCheckInterval, remainingTime)) + } +} + +func (bp *baseProcessor) checkInclusionEstimationForExecutionResults(header data.HeaderHandler) error { + prevBlockLastExecutionResult, err := process.GetPrevBlockLastExecutionResult(bp.blockChain) + if err != nil { + return err + } + + lastResultData, err := process.CreateDataForInclusionEstimation(prevBlockLastExecutionResult) + if err != nil { + return err + } + executionResults := header.GetExecutionResultsHandlers() + sanitizedExecutionResults := bp.excludeRevertedExecutionResultsForHeader(header, executionResults) + if len(sanitizedExecutionResults) != len(executionResults) { + log.Warn("non canonical execution result included", + "sanitized num results", len(sanitizedExecutionResults), + "header num results", len(executionResults), + ) + return process.ErrNonCanonicalExecutionResultIncluded + } + allowed := bp.executionResultsInclusionEstimator.Decide(lastResultData, executionResults, header.GetRound()) + bp.updateInclusionEstimatorMetrics(len(executionResults), allowed) + if allowed != len(executionResults) { + log.Warn("number of execution results included in the header is not correct", + "expected", allowed, + "actual", len(executionResults), + ) + return process.ErrInvalidNumberOfExecutionResultsInHeader + } + + return nil +} + +func (bp *baseProcessor) addExecutionResultsOnHeader(header data.HeaderHandler) error { + pendingExecutionResults, err := bp.executionManager.GetPendingExecutionResults() + if err != nil { + return err + } + + lastExecutionResultHandler, err := process.GetPrevBlockLastExecutionResult(bp.blockChain) + if err != nil { + return err + } + + lastNotarizedExecutionResultInfo, err := process.CreateDataForInclusionEstimation(lastExecutionResultHandler) + if err != nil { + return err + } + + sanitizedExecutionResults := bp.excludeRevertedExecutionResultsForHeader(header, pendingExecutionResults) + var lastExecutionResultForCurrentBlock data.LastExecutionResultHandler + numToInclude := bp.executionResultsInclusionEstimator.Decide(lastNotarizedExecutionResultInfo, sanitizedExecutionResults, header.GetRound()) + + executionResultsToInclude := sanitizedExecutionResults[:numToInclude] + lastExecutionResultForCurrentBlock = lastExecutionResultHandler + if len(executionResultsToInclude) > 0 { + lastExecutionResult := executionResultsToInclude[len(executionResultsToInclude)-1] + lastExecutionResultForCurrentBlock, err = process.CreateLastExecutionResultInfoFromExecutionResult(header.GetRound(), lastExecutionResult, bp.shardCoordinator.SelfId()) + if err != nil { + return err + } + } + + err = header.SetLastExecutionResultHandler(lastExecutionResultForCurrentBlock) + if err != nil { + return err + } + + return header.SetExecutionResultsHandlers(executionResultsToInclude) +} + +func (bp *baseProcessor) excludeRevertedExecutionResultsForHeader( + header data.HeaderHandler, + pendingExecutionResults []data.BaseExecutionResultHandler, +) []data.BaseExecutionResultHandler { + // only last pending execution result can be wrong if the block is reverted + // check that and exclude if needed + if len(pendingExecutionResults) == 0 { + return pendingExecutionResults + } + + lastPendingExecutionResult := pendingExecutionResults[len(pendingExecutionResults)-1] + if lastPendingExecutionResult.GetHeaderNonce() == header.GetNonce() { + // last pending execution result cannot be for the current header that is being constructed + // so we need to exclude it + return pendingExecutionResults[:len(pendingExecutionResults)-1] + } + + // only headers that have passed consensus are stored + _, err := process.GetHeaderFromStorage( + header.GetShardID(), + lastPendingExecutionResult.GetHeaderHash(), + bp.marshalizer, + bp.store, + ) + + if err != nil { + return pendingExecutionResults[:len(pendingExecutionResults)-1] + } + + return pendingExecutionResults +} + +func (bp *baseProcessor) createMbsCrossShardDstMe( + currentBlockHash []byte, + currentBlock data.HeaderHandler, + miniBlockProcessingInfo map[string]*processedMb.ProcessedMiniBlockInfo, +) (*CrossShardIncomingMbsCreationResult, error) { + currMiniBlocksAdded, pendingMiniBlocks, currNumTxsAdded, hdrFinished, missingData, errCreate := bp.txCoordinator.CreateMbsCrossShardDstMe( + currentBlock, + miniBlockProcessingInfo, + ) + if errCreate != nil { + return nil, errCreate + } + + if !hdrFinished { + log.Debug("block cannot be fully processed", + "round", currentBlock.GetRound(), + "nonce", currentBlock.GetNonce(), + "hash", currentBlockHash, + "num mbs added", len(currMiniBlocksAdded), + "num txs added", currNumTxsAdded, + "has missing data", missingData) + } + + return &CrossShardIncomingMbsCreationResult{ + HeaderFinished: hdrFinished, + HasMissingData: missingData, + PendingMiniBlocks: pendingMiniBlocks, + AddedMiniBlocks: currMiniBlocksAdded, + }, nil +} + +func (bp *baseProcessor) revertGasForCrossShardDstMeMiniBlocks(added, pending []block.MiniblockAndHash) { + miniBlockHashesToRevert := make([][]byte, 0, len(added)) + for _, mbAndHash := range added { + miniBlockHashesToRevert = append(miniBlockHashesToRevert, mbAndHash.Hash) + } + for _, mbAndHash := range pending { + miniBlockHashesToRevert = append(miniBlockHashesToRevert, mbAndHash.Hash) + } + + bp.gasComputation.RevertIncomingMiniBlocks(miniBlockHashesToRevert) +} + +func (bp *baseProcessor) setCurrentBlockInfo( + header data.HeaderHandler, + headerHash []byte, + rootHash []byte, +) error { + if header.IsHeaderV3() { + bp.executionManager.CleanOnConsensusReached(headerHash, header) + // last executed info and header will be set on headers executor in async mode + return bp.blockChain.SetCurrentBlockHeader(header) + } + + err := bp.blockChain.SetCurrentBlockHeaderAndRootHash(header, rootHash) + if err != nil { + return err + } + + // set also last executed block info and header + // this will be useful at transition to Supernova with headers v3 + bp.blockChain.SetLastExecutedBlockHeaderAndRootHash(header, headerHash, rootHash) + bp.executionManager.CleanOnConsensusReached(headerHash, header) + + // before header v3, create and set execution result in tracker + lastExecResHandler, err := common.GetOrCreateLastExecutionResultForPrevHeader(header, headerHash) + if err != nil { + return err + } + + return bp.executionManager.SetLastNotarizedResult(lastExecResHandler) +} + +func (bp *baseProcessor) getLastExecutedRootHash( + header data.HeaderHandler, +) []byte { + var rootHash []byte + if !header.IsHeaderV3() { + rootHash = bp.getRootHash() + return rootHash + } + + lastExecutionResult, err := common.GetLastBaseExecutionResultHandler(header) + if err != nil { + log.Warn("failed to get last execution result for header", "err", err) + _, _, rootHash = bp.blockChain.GetLastExecutedBlockInfo() + return rootHash + } + + return lastExecutionResult.GetRootHash() +} + +// requestProofIfNeeded will request proof if Andromeda flag activated and not already in pool +func (bp *baseProcessor) requestProofIfNeeded( + nonce uint64, + shardID uint32, + epoch uint32, +) { + if !bp.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, epoch) { + return + } + + proofsPool := bp.dataPool.Proofs() + _, err := proofsPool.GetProofByNonce(nonce, shardID) + if err == nil { + // proof already in pool, no need to request it + return + } + + bp.requestHandler.RequestEquivalentProofByNonce(shardID, nonce) +} + +func (bp *baseProcessor) requestHeadersFromHeaderIfNeeded( + lastHeader data.HeaderHandler, +) { + lastRound := lastHeader.GetRound() + shardID := lastHeader.GetShardID() + + shouldRequestCrossHeaders := bp.roundHandler.Index() > int64(lastRound+bp.getMaxRoundsWithoutBlockReceived(lastRound)) + + if !shouldRequestCrossHeaders { + return + } + + fromNonce := lastHeader.GetNonce() + 1 + toNonce := fromNonce + bp.processConfigsHandler.GetNumHeadersToRequestInAdvance(lastRound) + + for nonce := fromNonce; nonce <= toNonce; nonce++ { + bp.requestHeaderIfNeeded(nonce, shardID) + bp.requestProofIfNeeded(nonce, shardID, lastHeader.GetEpoch()) + } +} + +func (bp *baseProcessor) requestHeaderIfNeeded( + nonce uint64, + shardID uint32, +) { + headersPool := bp.dataPool.Headers() + _, _, err := headersPool.GetHeadersByNonceAndShardId(nonce, shardID) + if err == nil { + // header already in pool, no need to request it + return + } + + bp.requestHeaderByShardAndNonce(shardID, nonce) +} + +func (bp *baseProcessor) verifyGasLimit(header data.HeaderHandler, miniBlocks block.MiniBlockSlice) error { + splitRes, err := bp.splitTransactionsForHeader(header, miniBlocks) + if err != nil { + return err + } + + err = bp.checkMetaOutgoingResults(header, splitRes) + if err != nil { + return err + } + + bp.gasComputation.Reset() + _, numPendingMiniBlocks, err := bp.gasComputation.AddIncomingMiniBlocks(splitRes.incomingMiniBlocks, splitRes.incomingTransactions) + if err != nil { + return err + } + + // for meta, both splitRes.outgoingTransactionHashes and splitRes.outgoingTransactions should be empty, checked on checkMetaOutgoingResults + addedTxHashes, pendingMiniBlocksAdded, err := bp.gasComputation.AddOutgoingTransactions(splitRes.outgoingTransactionHashes, splitRes.outgoingTransactions) + if err != nil { + return err + } + if len(addedTxHashes) != len(splitRes.outgoingTransactionHashes) { + return fmt.Errorf("%w, outgoing transactions exceeded the limit", process.ErrInvalidMaxGasLimitPerMiniBlock) + } + + if numPendingMiniBlocks != len(pendingMiniBlocksAdded) { + return fmt.Errorf("%w, incoming mini blocks exceeded the limit", process.ErrInvalidMaxGasLimitPerMiniBlock) + } + + return nil +} + +func (bp *baseProcessor) checkMetaOutgoingResults( + header data.HeaderHandler, + splitRes *splitTxsResult, +) error { + _, ok := header.(data.MetaHeaderHandler) + if !ok { + return nil + } + + numOutGoingMBs := len(splitRes.outGoingMiniBlocks) + if numOutGoingMBs != 0 { + return fmt.Errorf("%w, received: %d", errInvalidNumOutGoingMBInMetaHdrProposal, numOutGoingMBs) + } + + numOutGoingTxs := len(splitRes.outgoingTransactions) + if numOutGoingTxs != 0 { + return fmt.Errorf("%w in metaProcessor.verifyGasLimit, received: %d", + errInvalidNumOutGoingTxsInMetaHdrProposal, + numOutGoingTxs, + ) + } + + return nil +} + +func (bp *baseProcessor) splitTransactionsForHeader(header data.HeaderHandler, miniBlocks block.MiniBlockSlice) (*splitTxsResult, error) { + if len(header.GetMiniBlockHeaderHandlers()) != len(miniBlocks) { + return nil, errInvalidMiniBlocks + } + + incomingMiniBlocks := make([]data.MiniBlockHeaderHandler, 0) + outGoingMiniBlocks := make([]data.MiniBlockHeaderHandler, 0) + outgoingTransactionHashes := make([][]byte, 0) + incomingTransactions := make(map[string][]data.TransactionHandler) + outgoingTransactions := make([]data.TransactionHandler, 0) + for mbIdx, mb := range header.GetMiniBlockHeaderHandlers() { + txHashes := miniBlocks[mbIdx].TxHashes + txsForMb, err := bp.getTransactionsForMiniBlock(mb, txHashes) + if err != nil { + return nil, err + } + + if mb.GetSenderShardID() == bp.shardCoordinator.SelfId() { + outgoingTransactionHashes = append(outgoingTransactionHashes, txHashes...) + outgoingTransactions = append(outgoingTransactions, txsForMb...) + outGoingMiniBlocks = append(outGoingMiniBlocks, mb) + continue + } + + incomingMiniBlocks = append(incomingMiniBlocks, mb) + incomingTransactions[string(mb.GetHash())] = txsForMb + } + + return &splitTxsResult{ + incomingMiniBlocks: incomingMiniBlocks, + outGoingMiniBlocks: outGoingMiniBlocks, + incomingTransactions: incomingTransactions, + outgoingTransactionHashes: outgoingTransactionHashes, + outgoingTransactions: outgoingTransactions, + }, nil +} + +func (bp *baseProcessor) getTransactionsForMiniBlock( + miniBlock data.MiniBlockHeaderHandler, + txHashes [][]byte, +) ([]data.TransactionHandler, error) { + mbType := miniBlock.GetTypeInt32() + if mbType == int32(block.RewardsBlock) || mbType == int32(block.PeerBlock) { + // rewards and validator info have 0 gas limit, thus they should be included anyway without checking their transactions + return make([]data.TransactionHandler, 0), nil + } + + pool, err := process.GetDataPoolByBlockType(block.Type(miniBlock.GetTypeInt32()), bp.dataPool) + if err != nil { + return nil, err + } + + txs := make([]data.TransactionHandler, len(txHashes)) + for idx, txHash := range txHashes { + txs[idx], err = process.GetTransactionHandlerFromPool( + miniBlock.GetSenderShardID(), + miniBlock.GetReceiverShardID(), + txHash, + pool, + process.SearchMethodSearchFirst, + ) + if err != nil { + return nil, err + } + } + + return txs, nil +} + +func (bp *baseProcessor) getLastExecutionResultHeader( + currentHeader data.HeaderHandler, +) (data.HeaderHandler, error) { + if !currentHeader.IsHeaderV3() { + return currentHeader, nil + } + + lastExecutionResult, err := common.GetLastBaseExecutionResultHandler(currentHeader) + if err != nil { + return nil, err + } + + return process.GetHeader( + lastExecutionResult.GetHeaderHash(), + bp.dataPool.Headers(), + bp.store, + bp.marshalizer, + currentHeader.GetShardID(), + ) +} + +func (bp *baseProcessor) checkAndUpdateContextBeforeExecution(header data.HeaderHandler, headerHash []byte) error { + lastExecutionResult := bp.blockChain.GetLastExecutionResult() + if process.IsReplacementBlockForExecution(header, headerHash, lastExecutionResult) { + err := process.UpdateContextForReplacedHeader( + header, + bp.executionManager, + bp.blockChain, + bp.dataPool.Headers(), + bp.dataPool.PostProcessTransactions(), + bp.dataPool.ExecutedMiniBlocks(), + bp.store, + bp.marshalizer, + bp.shardCoordinator.SelfId(), + ) + if err != nil { + return err + } + } + + lastExecutedNonce, lastExecutedHash, lastExecutedRootHash := bp.blockChain.GetLastExecutedBlockInfo() + if !bytes.Equal(header.GetPrevHash(), lastExecutedHash) { + log.Debug("checkContextBeforeExecution: hash does not match", + "lastExecutedHash", lastExecutedHash, + "prevHash", header.GetPrevHash(), + ) + return process.ErrBlockHashDoesNotMatch + } + if header.GetNonce() != lastExecutedNonce+1 { + log.Debug("checkContextBeforeExecution: nonce does not match", + "lastExecutedNonce+1", lastExecutedNonce+1, + "nonce", header.GetNonce(), + ) + return process.ErrWrongNonceInBlock + } + + err := bp.checkAndUpdateAccountsRootHash(state.UserAccountsState, lastExecutedRootHash) + if err != nil { + return err + } + + return bp.checkPeerAccountsRootHash(header.GetShardID()) +} + +func (bp *baseProcessor) checkPeerAccountsRootHash( + shardID uint32, +) error { + if shardID != core.MetachainShardId { + return nil + } + + lastExecutedPeerRootHash, err := bp.getLastValidatorStatsRootHash() + if err != nil { + return err + } + + return bp.checkAndUpdateAccountsRootHash(state.PeerAccountsState, lastExecutedPeerRootHash) +} + +func (bp *baseProcessor) getLastValidatorStatsRootHash() ([]byte, error) { + lastExecutedHeader := bp.blockChain.GetLastExecutedBlockHeader() + + if !lastExecutedHeader.IsHeaderV3() { + lastExecutedHeaderMeta, ok := lastExecutedHeader.(data.MetaHeaderHandler) + if !ok { + return nil, process.ErrWrongTypeAssertion + } + + return lastExecutedHeaderMeta.GetValidatorStatsRootHash(), nil + } + + lastExecutedResult := bp.blockChain.GetLastExecutionResult() + + lastMetaExecResult, ok := lastExecutedResult.(data.BaseMetaExecutionResultHandler) + if !ok { + return nil, process.ErrWrongTypeAssertion + } + + return lastMetaExecResult.GetValidatorStatsRootHash(), nil +} + +func (bp *baseProcessor) checkAndUpdateAccountsRootHash( + accountsStateID state.AccountsDbIdentifier, + lastExecutedRootHash []byte, +) error { + lastCommittedRootHash, err := bp.accountsDB[accountsStateID].RootHash() + if err != nil { + return err + } + + if bytes.Equal(lastCommittedRootHash, lastExecutedRootHash) { + return nil + } + + lastCommittedRootHash, err = bp.recreateAccountsTrie(accountsStateID, lastExecutedRootHash) + if err != nil { + return err + } + + if !bytes.Equal(lastCommittedRootHash, lastExecutedRootHash) { + log.Debug("checkContextBeforeExecution: rootHash does not match", + "lastExecutedRootHash", lastExecutedRootHash, + "lastCommittedRootHash", lastCommittedRootHash, + ) + return process.ErrRootStateDoesNotMatch + } + + return nil +} + +func (bp *baseProcessor) recreateAccountsTrie( + trieStateId state.AccountsDbIdentifier, + lastExecutedRootHash []byte, +) ([]byte, error) { + rootHashHolder := holders.NewDefaultRootHashesHolder(lastExecutedRootHash) + err := bp.accountsDB[trieStateId].RecreateTrie(rootHashHolder) + if err != nil { + return nil, err + } + + return bp.accountsDB[trieStateId].RootHash() +} + +func (bp *baseProcessor) getCrossShardIncomingMiniBlocksFromBody(body *block.Body) []*block.MiniBlock { + miniBlocks := make([]*block.MiniBlock, 0) - return false + isReceiverCurrentShard := false + for _, mb := range body.MiniBlocks { + isReceiverCurrentShard = mb.ReceiverShardID == bp.shardCoordinator.SelfId() || mb.ReceiverShardID == core.AllShardId + if isReceiverCurrentShard && mb.SenderShardID != bp.shardCoordinator.SelfId() { + miniBlocks = append(miniBlocks, mb) + } + } + return miniBlocks +} + +func (bp *baseProcessor) collectMiniBlocks( + headerHash []byte, + body *block.Body, +) ([]data.MiniBlockHeaderHandler, int, []byte, error) { + bodyAfterExecution := bp.createBlockBodyAfterExecution(body) + receiptHash, err := bp.txCoordinator.CreateReceiptsHash() + if err != nil { + return nil, 0, nil, err + } + + // remove the self-receipts and self smart contract results mini blocks - similar to Pre-Supernova + sanitizedBodyAfterExecution := deleteSelfReceiptsMiniBlocks(bodyAfterExecution) + totalTxCount, miniBlockHeaderHandlers, err := bp.createMiniBlockHeaderHandlersForExecutionResults(sanitizedBodyAfterExecution) + if err != nil { + return nil, 0, nil, err + } + + intraMiniBlocks := bp.txCoordinator.GetCreatedInShardMiniBlocks() + err = bp.cacheIntraShardMiniBlocks(headerHash, intraMiniBlocks) + if err != nil { + return nil, 0, nil, err + } + + err = bp.cacheExecutedMiniBlocks(sanitizedBodyAfterExecution, miniBlockHeaderHandlers) + if err != nil { + return nil, 0, nil, err + } + + return miniBlockHeaderHandlers, totalTxCount, receiptHash, nil +} + +func (bp *baseProcessor) createBlockBodyAfterExecution( + proposedBody *block.Body, +) *block.Body { + crossShardIncomingMiniBlocks := bp.getCrossShardIncomingMiniBlocksFromBody(proposedBody) + miniBlocksFromSelf := bp.txCoordinator.GetCreatedMiniBlocksFromMe() + postProcessMiniBlocks := bp.txCoordinator.CreatePostProcessMiniBlocks() + + allMiniBlocks := make([]*block.MiniBlock, 0, len(crossShardIncomingMiniBlocks)+len(miniBlocksFromSelf)+len(postProcessMiniBlocks)) + allMiniBlocks = append(allMiniBlocks, crossShardIncomingMiniBlocks...) + allMiniBlocks = append(allMiniBlocks, miniBlocksFromSelf...) + allMiniBlocks = append(allMiniBlocks, postProcessMiniBlocks...) + + bodyAfterExecution := &block.Body{MiniBlocks: allMiniBlocks} + + return bodyAfterExecution +} + +func (bp *baseProcessor) cacheOrderedTxHashes(headerHash []byte) { + executionOrderKey := common.PrepareOrderedTxHashesKey(headerHash) + items := bp.txExecutionOrderHandler.GetItems() + + size := len(items) * common.HashSize // number of items * length of a transaction hash + bp.dataPool.PostProcessTransactions().Put(executionOrderKey, items, size) +} + +func (bp *baseProcessor) cacheHeaderGasData(headerHash []byte) { + headerGasData := &outportcore.HeaderGasConsumption{ + GasProvided: bp.gasConsumedProvider.TotalGasProvidedWithScheduled(), + GasPenalized: bp.gasConsumedProvider.TotalGasPenalized(), + GasRefunded: bp.gasConsumedProvider.TotalGasRefunded(), + MaxGasPerBlock: bp.economicsData.MaxGasLimitPerBlock(bp.shardCoordinator.SelfId()), + } + + key := common.PrepareHeaderGasDataKey(headerHash) + bp.dataPool.PostProcessTransactions().Put(key, headerGasData, headerGasData.Size()) +} + +func (bp *baseProcessor) cacheUnexecutableTxHashes(headerHash []byte) { + unexecutableTxHashes := bp.txCoordinator.GetUnExecutableTransactions() + + hashes := make([][]byte, 0, len(unexecutableTxHashes)) + for txHash := range unexecutableTxHashes { + hashes = append(hashes, []byte(txHash)) + } + + key := common.PrepareUnexecutableTxHashesKey(headerHash) + + size := len(unexecutableTxHashes) * common.HashSize + bp.dataPool.PostProcessTransactions().Put(key, hashes, size) +} + +func (bp *baseProcessor) getBlockBodyFromPool( + header data.HeaderHandler, + miniBlockHeaderHandlers []data.MiniBlockHeaderHandler, +) (data.BodyHandler, error) { + miniBlocksPool := bp.dataPool.MiniBlocks() + var miniBlocks block.MiniBlockSlice + + for _, mbHeader := range miniBlockHeaderHandlers { + obj, hashInPool := miniBlocksPool.Get(mbHeader.GetHash()) + if !hashInPool { + continue + } + + miniBlock, typeOk := obj.(*block.MiniBlock) + if !typeOk { + return nil, process.ErrWrongTypeAssertion + } + + miniBlocks = append(miniBlocks, miniBlock) + } + + return &block.Body{MiniBlocks: miniBlocks}, nil } -func (bp *baseProcessor) checkReceivedProofIfAttestingIsNeeded(proof data.HeaderProofHandler) { - bp.hdrsForCurrBlock.mutHdrsForBlock.Lock() - hdrHashAndInfo, ok := bp.hdrsForCurrBlock.hdrHashAndInfo[string(proof.GetHeaderHash())] +func (bp *baseProcessor) getHeaderFromHash( + isHeaderV3 bool, + headerHash []byte, + shardID uint32, +) (data.HeaderHandler, error) { + if isHeaderV3 { + return process.GetHeader(headerHash, bp.dataPool.Headers(), bp.store, bp.marshalizer, shardID) + } + + headerInfo, ok := bp.hdrsForCurrBlock.GetHeaderInfo(string(headerHash)) if !ok { - bp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() - return // proof not missing + return nil, process.ErrMissingHeader + } + + return headerInfo.GetHeader(), nil +} + +func getProposedAndExecutedMiniBlockHeaders( + header data.HeaderHandler, +) ([]data.MiniBlockHeaderHandler, error) { + if !header.IsHeaderV3() { + return header.GetMiniBlockHeaderHandlers(), nil + } + + execResultsMiniBlockHeaders, err := common.GetMiniBlockHeadersFromExecResult(header) + if err != nil { + return nil, err } - isWaitingForProofs := hdrHashAndInfo.hasProofRequested - if !isWaitingForProofs { - bp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() + miniBlockHeaders := header.GetMiniBlockHeaderHandlers() + miniBlockHeaders = append(miniBlockHeaders, execResultsMiniBlockHeaders...) + + return miniBlockHeaders, nil +} + +func (bp *baseProcessor) updateInclusionEstimatorMetrics(executionResultsLen int, allowed int) { + rejected, err := core.SafeSubUint64(uint64(executionResultsLen), uint64(allowed)) + if err != nil { + rejected = uint64(executionResultsLen) + } + + bp.appStatusHandler.SetUInt64Value(common.MetricNumInclusionEstimationRejected, rejected) +} + +func (bp *baseProcessor) saveEpochStartEconomicsMetrics(epochStartMetaBlock data.MetaHeaderHandler) { + economics := epochStartMetaBlock.GetEpochStartHandler().GetEconomicsHandler() + + bp.appStatusHandler.SetStringValue(common.MetricTotalSupply, economics.GetTotalSupply().String()) + bp.appStatusHandler.SetStringValue(common.MetricInflation, economics.GetTotalNewlyMinted().String()) + bp.appStatusHandler.SetUInt64Value(common.MetricEpochForEconomicsData, uint64(epochStartMetaBlock.GetEpoch())) + + if epochStartMetaBlock.IsHeaderV3() { + // fee metrics for meta block v3 will be handled separately when propose epoch change block is processed + return + } + + bp.appStatusHandler.SetStringValue(common.MetricTotalFees, epochStartMetaBlock.GetAccumulatedFeesInEpoch().String()) + bp.appStatusHandler.SetStringValue(common.MetricDevRewardsInEpoch, epochStartMetaBlock.GetDevFeesInEpoch().String()) +} + +func (bp *baseProcessor) cleanupDismissedEWLEntries() { + dismissedBatches := bp.executionManager.PopDismissedResults() + + if len(dismissedBatches) > 0 { + totalDismissed := 0 + for _, batch := range dismissedBatches { + totalDismissed += len(batch.Results) + } + log.Debug("cleanupDismissedEWLEntries: draining dismissed batches", + "batches", len(dismissedBatches), + "totalDismissed", totalDismissed, + ) + + bp.blockProcessor.cancelPruneForDismissedExecutionResults(dismissedBatches) + bp.resetLastPrunedHeader() + } + + bp.checkEWLSizeAndReset() +} + +// checkEWLSizeAndReset is a safety net (Layer 3). If the EWL size exceeds the +// precomputed threshold, it resets pruning to prevent unbounded memory growth. +func (bp *baseProcessor) checkEWLSizeAndReset() { + for key, accountsDb := range bp.accountsDB { + if !accountsDb.IsPruningEnabled() { + continue + } + ewlSize := accountsDb.GetEvictionWaitingListSize() + if ewlSize > bp.ewlResetThreshold { + log.Warn("EWL cache size exceeds threshold, resetting pruning", + "accountsDB", key, + "ewlSize", ewlSize, + "threshold", bp.ewlResetThreshold, + ) + accountsDb.ResetPruning() + bp.resetLastPrunedHeader() + } + } +} + +func computeEWLResetThreshold(maxProposalNonceGap uint64) int { + gap := maxProposalNonceGap + if gap > maxGapForEWLThreshold { + gap = maxGapForEWLThreshold + } + expected := gap * ewlEntriesPerResult + return int(expected*ewlTolerancePercent/100) + ewlThresholdMinBaseline +} + +// cancelPruneForRootHashTransition cancels pruning for a root hash transition from prev to current. +// It issues CancelPrune for currentRootHash as NewRoot and prevRootHash as OldRoot. +func cancelPruneForRootHashTransition(accountsDb state.AccountsAdapter, prevRootHash, currentRootHash []byte) { + if len(prevRootHash) == 0 || len(currentRootHash) == 0 { return } + if bytes.Equal(prevRootHash, currentRootHash) { + return + } + accountsDb.CancelPrune(currentRootHash, state.NewRoot) + accountsDb.CancelPrune(prevRootHash, state.OldRoot) +} + +func (bp *baseProcessor) resetLastPrunedHeader() { + bp.mutLastPrunedHeader.Lock() + bp.lastPrunedHeaderHash = nil + bp.lastPrunedHeaderNonce = 0 + bp.mutLastPrunedHeader.Unlock() +} + +// PruneTrieAsyncHeader will trigger trie pruning for header from async execution flow +func (bp *baseProcessor) PruneTrieAsyncHeader() { + bp.mutLastPrunedHeader.Lock() + defer bp.mutLastPrunedHeader.Unlock() + + header := bp.blockChain.GetCurrentBlockHeader() + headerHash := bp.blockChain.GetCurrentBlockHeaderHash() + + if len(bp.lastPrunedHeaderHash) == 0 { + // last pruned header hash not set, trigger prune trie for the provided header + bp.blockProcessor.pruneTrieHeaderV3(header) + bp.lastPrunedHeaderHash = headerHash + bp.lastPrunedHeaderNonce = header.GetNonce() + return + } + + // extra check by nonce + if header.GetNonce() <= bp.lastPrunedHeaderNonce { + return + } + + err := bp.pruneTrieForHeadersUnprotected(headerHash, header) + if err != nil { + // there was an error while fetching intermediate headers + // reset pruning context + bp.blockProcessor.resetPruning() + } + + bp.lastPrunedHeaderHash = headerHash + bp.lastPrunedHeaderNonce = header.GetNonce() +} + +func (bp *baseProcessor) pruneTrieForHeadersUnprotected( + headerHash []byte, + header data.HeaderHandler, +) error { + if bytes.Equal(headerHash, bp.lastPrunedHeaderHash) { + return nil + } - hdrHashAndInfo.hasProof = true - bp.hdrsForCurrBlock.missingProofs-- + headersToPrune := make([]data.HeaderHandler, 0) + headersToPrune = append(headersToPrune, header) + + lastPrunedHeaderHash := bp.lastPrunedHeaderHash + walkerHash := header.GetPrevHash() + + for !bytes.Equal(walkerHash, lastPrunedHeaderHash) { + // headers pool is cleaned on consensus flow based on last execution result + // included on the committed header (plus some delta), so intermediate headers + // should be available in pool, since trie pruning is triggered from + // execution flow; if there are no included blocks from execution flow + // (and not pruning triggered) headers will not be removed from pool + header, err := process.GetHeader( + walkerHash, + bp.dataPool.Headers(), + bp.store, + bp.marshalizer, + header.GetShardID(), + ) + if err != nil { + log.Warn("failed to get intermediate header for pruning", "error", err) + return err + } - missingMetaHdrs := bp.hdrsForCurrBlock.missingHdrs - missingFinalityAttestingMetaHdrs := bp.hdrsForCurrBlock.missingFinalityAttestingHdrs - missingProofs := bp.hdrsForCurrBlock.missingProofs - bp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() + headersToPrune = append(headersToPrune, header) - allMissingMetaHeadersReceived := missingMetaHdrs == 0 && missingFinalityAttestingMetaHdrs == 0 && missingProofs == 0 - if allMissingMetaHeadersReceived { - bp.chRcvAllHdrs <- true + walkerHash = header.GetPrevHash() } + + for i := len(headersToPrune) - 1; i >= 0; i-- { + header := headersToPrune[i] + bp.blockProcessor.pruneTrieHeaderV3(header) + } + + return nil } diff --git a/process/block/baseProcessHeaderV3_test.go b/process/block/baseProcessHeaderV3_test.go new file mode 100644 index 00000000000..e902dc4d44a --- /dev/null +++ b/process/block/baseProcessHeaderV3_test.go @@ -0,0 +1,916 @@ +package block + +import ( + "errors" + "testing" + + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/dataRetriever" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/storage" + "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/cache" + commonMocks "github.com/multiversx/mx-chain-go/testscommon/common" + dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" + "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" + commonStorage "github.com/multiversx/mx-chain-go/testscommon/storage" +) + +var ( + errExpected = errors.New("expected error") + headerHash = []byte("headerHash") +) + +func getDefaultBaseProcessor() *baseProcessor { + return &baseProcessor{ + marshalizer: &marshallerMock.MarshalizerStub{}, + shardCoordinator: &testscommon.ShardsCoordinatorMock{}, + receiptsRepository: &testscommon.ReceiptsRepositoryStub{ + SaveReceiptsCalled: func(holder common.ReceiptsHolder, header data.HeaderHandler, headerHash []byte) error { + return nil + }, + }, + txCoordinator: &testscommon.TransactionCoordinatorMock{ + GetAllIntermediateTxsCalled: func() map[block.Type]map[string]data.TransactionHandler { + return make(map[block.Type]map[string]data.TransactionHandler) + }, + }, + store: &commonStorage.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &commonStorage.StorerStub{}, nil + }, + }, + dataPool: &dataRetrieverMock.PoolsHolderStub{ + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + return []byte("marshalled map"), true + }, + } + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + return nil, false + }, + } + }, + }, + } +} + +func getHeaderHandlerWithMiniBlocksHeaders(miniBlocksHeaders []block.MiniBlockHeader) *testscommon.HeaderHandlerStub { + return &testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { + return true + }, + GetExecutionResultsHandlersCalled: func() []data.BaseExecutionResultHandler { + return []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + MiniBlockHeaders: miniBlocksHeaders, + }, + } + }, + } +} + +func TestBaseProcessor_cacheExecutedMiniBlocks(t *testing.T) { + t.Parallel() + + t.Run("marshal error", func(t *testing.T) { + t.Parallel() + + bp := &baseProcessor{ + marshalizer: &marshallerMock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + return nil, errExpected + }, + }, + hasher: &testscommon.HasherStub{}, + shardCoordinator: &testscommon.ShardsCoordinatorMock{}, + enableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + processedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + dataPool: &dataRetrieverMock.PoolsHolderStub{ + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{ + PutCalled: func(key []byte, value interface{}, sizeInBytes int) (evicted bool) { + require.Fail(t, "should not be called") + return false + }, + } + }, + }, + } + + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + {}, + }, + } + mbsHandlers := []data.MiniBlockHeaderHandler{ + &block.MiniBlockHeader{}, + } + err := bp.cacheExecutedMiniBlocks(body, mbsHandlers) + require.Equal(t, errExpected, err) + }) + t.Run("should add executed mini blocks", func(t *testing.T) { + t.Parallel() + + wasPutCalled := false + bp := &baseProcessor{ + marshalizer: &marshallerMock.MarshalizerStub{}, + hasher: &testscommon.HasherStub{}, + shardCoordinator: &testscommon.ShardsCoordinatorMock{}, + enableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + processedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + dataPool: &dataRetrieverMock.PoolsHolderStub{ + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{ + PutCalled: func(key []byte, value interface{}, sizeInBytes int) (evicted bool) { + wasPutCalled = true + return false + }, + } + }, + }, + } + + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + {}, + }, + } + mbsHandlers := []data.MiniBlockHeaderHandler{ + &block.MiniBlockHeader{}, + } + err := bp.cacheExecutedMiniBlocks(body, mbsHandlers) + require.NoError(t, err) + require.True(t, wasPutCalled) + }) +} + +func TestBaseProcessor_cacheIntermediateTxsForHeader(t *testing.T) { + t.Parallel() + + t.Run("marshal error", func(t *testing.T) { + t.Parallel() + + bp := &baseProcessor{ + marshalizer: &marshallerMock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + return nil, errExpected + }, + }, + txCoordinator: &testscommon.TransactionCoordinatorMock{ + GetAllIntermediateTxsCalled: func() map[block.Type]map[string]data.TransactionHandler { + allTxs := make(map[block.Type]map[string]data.TransactionHandler) + allTxs[block.TxBlock] = map[string]data.TransactionHandler{ + "txHash1": &transaction.Transaction{}, + } + + return allTxs + }, + }, + } + + err := bp.cacheIntermediateTxsForHeader(headerHash) + require.Equal(t, errExpected, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + wasPutCalled := false + bp := &baseProcessor{ + marshalizer: &marshallerMock.MarshalizerStub{}, + txCoordinator: &testscommon.TransactionCoordinatorMock{}, + dataPool: &dataRetrieverMock.PoolsHolderStub{ + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{ + PutCalled: func(key []byte, value interface{}, sizeInBytes int) (evicted bool) { + wasPutCalled = true + return false + }, + } + }, + }, + txExecutionOrderHandler: &commonMocks.TxExecutionOrderHandlerStub{}, + } + + err := bp.cacheIntermediateTxsForHeader(headerHash) + require.NoError(t, err) + require.True(t, wasPutCalled) + }) + + t.Run("should work with proto marshaller", func(t *testing.T) { + t.Parallel() + + wasPutCalled := false + bp := &baseProcessor{ + marshalizer: &marshal.GogoProtoMarshalizer{}, + txCoordinator: &testscommon.TransactionCoordinatorMock{}, + dataPool: &dataRetrieverMock.PoolsHolderStub{ + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{ + PutCalled: func(key []byte, value interface{}, sizeInBytes int) (evicted bool) { + wasPutCalled = true + return false + }, + } + }, + }, + txExecutionOrderHandler: &commonMocks.TxExecutionOrderHandlerStub{}, + } + + err := bp.cacheIntermediateTxsForHeader(headerHash) + require.NoError(t, err) + require.True(t, wasPutCalled) + }) +} + +func TestBaseProcessor_saveExecutedData(t *testing.T) { + t.Parallel() + + t.Run("not header v3", func(t *testing.T) { + t.Parallel() + + bp := &baseProcessor{} + header := &testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { + return false + }, + } + + err := bp.saveExecutedData(header) + require.Nil(t, err) + }) + t.Run("header v3 with no execution results", func(t *testing.T) { + t.Parallel() + + bp := getDefaultBaseProcessor() + header := &testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { + return true + }, + GetExecutionResultsHandlersCalled: func() []data.BaseExecutionResultHandler { + return []data.BaseExecutionResultHandler{} + }, + } + + err := bp.saveExecutedData(header) + require.NoError(t, err) + }) + t.Run("saveMiniBlocksFromExecutionResults path", func(t *testing.T) { + t.Run("extractMiniBlocksHeaderHandlersFromExecResult cast failure for meta execution result", func(t *testing.T) { + t.Parallel() + + bp := getDefaultBaseProcessor() + header := &testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { + return true + }, + GetExecutionResultsHandlersCalled: func() []data.BaseExecutionResultHandler { + return []data.BaseExecutionResultHandler{ + &block.BaseExecutionResult{}, // shard execution result + } + }, + GetShardIDCalled: func() uint32 { + return common.MetachainShardId // meta + }, + } + + err := bp.saveExecutedData(header) + require.Equal(t, process.ErrWrongTypeAssertion, err) + }) + t.Run("putMiniBlocksIntoStorage early exit, empty mini block handlers", func(t *testing.T) { + t.Parallel() + + bp := getDefaultBaseProcessor() + bp.store = &commonStorage.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + require.Fail(t, "should not be called") + return nil, nil + }, + } + + header := getHeaderHandlerWithMiniBlocksHeaders([]block.MiniBlockHeader{}) + err := bp.saveExecutedData(header) + require.NoError(t, err) + }) + t.Run("putMiniBlocksIntoStorage returns error on GetStorer", func(t *testing.T) { + t.Parallel() + + bp := getDefaultBaseProcessor() + bp.store = &commonStorage.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return nil, errExpected + }, + } + header := getHeaderHandlerWithMiniBlocksHeaders([]block.MiniBlockHeader{{}}) + + err := bp.saveExecutedData(header) + require.Equal(t, errExpected, err) + }) + t.Run("putMiniBlocksIntoStorage does not find a mini block in cache", func(t *testing.T) { + t.Parallel() + + bp := getDefaultBaseProcessor() + header := getHeaderHandlerWithMiniBlocksHeaders([]block.MiniBlockHeader{{}}) + + err := bp.saveExecutedData(header) + require.Equal(t, process.ErrMissingMiniBlock, err) + }) + t.Run("putMiniBlocksIntoStorage cross-shard incoming should delete only", func(t *testing.T) { + t.Parallel() + + getCalls := 0 + bp := getDefaultBaseProcessor() + bp.dataPool = &dataRetrieverMock.PoolsHolderStub{ + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + getCalls++ + if getCalls > 1 { + // only called once for receipts saving + require.Fail(t, "should not be called") + } + return nil, false + }, + } + }, + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + return []byte("marshalled map"), true + }, + } + }, + } + bp.shardCoordinator = &testscommon.ShardsCoordinatorMock{ + SelfIDCalled: func() uint32 { + return 1 + }, + } + header := &testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { + return true + }, + GetExecutionResultsHandlersCalled: func() []data.BaseExecutionResultHandler { + return []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + MiniBlockHeaders: []block.MiniBlockHeader{ + { + ReceiverShardID: 1, + SenderShardID: 0, + }, + }, + }, + } + }, + } + + err := bp.saveExecutedData(header) + require.NoError(t, err) + }) + t.Run("putMiniBlocksIntoStorage fails to add into storer", func(t *testing.T) { + t.Parallel() + + bp := getDefaultBaseProcessor() + bp.store = &commonStorage.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &commonStorage.StorerStub{ + PutCalled: func(key, data []byte) error { + return errExpected + }, + }, nil + }, + } + + numCalls := 0 + bp.dataPool = &dataRetrieverMock.PoolsHolderStub{ + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + if numCalls == 0 { + numCalls++ + return []*block.MiniBlock{}, true + } + return []byte("data"), true + }, + } + }, + } + + header := getHeaderHandlerWithMiniBlocksHeaders([]block.MiniBlockHeader{{}}) + + err := bp.saveExecutedData(header) + require.Equal(t, errExpected, err) + }) + }) + t.Run("saveReceiptsForHeader path", func(t *testing.T) { + t.Run("miniBlocksToSelf not found in cache do not save receipts", func(t *testing.T) { + t.Parallel() + + bp := getDefaultBaseProcessor() + bp.receiptsRepository = &testscommon.ReceiptsRepositoryStub{ + SaveReceiptsCalled: func(holder common.ReceiptsHolder, header data.HeaderHandler, headerHash []byte) error { + require.Fail(t, "should not be called") + return nil + }, + } + header := getHeaderHandlerWithMiniBlocksHeaders([]block.MiniBlockHeader{}) + + err := bp.saveReceiptsForHeader(header, headerHash) + require.Nil(t, err) + }) + // removal is done on save mini blocks + t.Run("saves receipts OK", func(t *testing.T) { + t.Parallel() + + savedCalled := false + marshaller := &marshallerMock.MarshalizerMock{} + miniBlocks := []*block.MiniBlock{ + { + TxHashes: [][]byte{[]byte("txHash1")}, + }, + { + TxHashes: [][]byte{[]byte("txHash2")}, + }, + } + + bp := getDefaultBaseProcessor() + bp.marshalizer = marshaller + bp.txCoordinator = &testscommon.TransactionCoordinatorMock{ + GetCreatedInShardMiniBlocksCalled: func() []*block.MiniBlock { + return miniBlocks + }, + } + bp.receiptsRepository = &testscommon.ReceiptsRepositoryStub{ + SaveReceiptsCalled: func(holder common.ReceiptsHolder, header data.HeaderHandler, headerHash []byte) error { + savedCalled = true + require.Equal(t, miniBlocks, holder.GetMiniblocks()) + return nil + }, + } + header := getHeaderHandlerWithMiniBlocksHeaders([]block.MiniBlockHeader{}) + + err := bp.saveReceiptsForHeader(header, headerHash) + require.Nil(t, err) + require.True(t, savedCalled) + }) + }) + t.Run("saveIntermediateTxs path", func(t *testing.T) { + t.Run("header not found in the cache", func(t *testing.T) { + t.Parallel() + + bp := getDefaultBaseProcessor() + bp.store = &commonStorage.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + require.Fail(t, "should not be called") + return nil, nil + }, + } + bp.dataPool = &dataRetrieverMock.PoolsHolderStub{ + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + return nil, false + }, + } + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + return nil, false + }, + } + }, + } + header := getHeaderHandlerWithMiniBlocksHeaders([]block.MiniBlockHeader{}) + + err := bp.saveExecutedData(header) + require.True(t, errors.Is(err, process.ErrMissingHeader)) + }) + t.Run("putTransactionsIntoStorage fails due to invalid block type", func(t *testing.T) { + t.Parallel() + + bp := getDefaultBaseProcessor() + bp.store = &commonStorage.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + require.Fail(t, "should not be called") + return nil, nil + }, + } + bp.dataPool = &dataRetrieverMock.PoolsHolderStub{ + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + txsMap := make(map[block.Type]map[string]data.TransactionHandler) + txsMap[block.PeerBlock] = map[string]data.TransactionHandler{} // should never have PeerBlock + return txsMap, true + }, + } + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + return nil, false + }, + } + }, + } + header := getHeaderHandlerWithMiniBlocksHeaders([]block.MiniBlockHeader{}) + + err := bp.saveExecutedData(header) + require.Equal(t, process.ErrInvalidBlockType, err) + }) + t.Run("putTransactionsIntoStorage fails due to GetStorer issue", func(t *testing.T) { + t.Parallel() + + bp := getDefaultBaseProcessor() + bp.store = &commonStorage.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + if unitType == dataRetriever.TransactionUnit { + return nil, errExpected + } + + return &commonStorage.StorerStub{}, nil + }, + } + bp.dataPool = &dataRetrieverMock.PoolsHolderStub{ + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + txsMap := make(map[block.Type]map[string]data.TransactionHandler) + txsMap[block.TxBlock] = map[string]data.TransactionHandler{} // force TransactionUnit + return txsMap, true + }, + } + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + return nil, false + }, + } + }, + } + header := getHeaderHandlerWithMiniBlocksHeaders([]block.MiniBlockHeader{}) + + err := bp.saveExecutedData(header) + require.Equal(t, errExpected, err) + }) + t.Run("putOneTransactionIntoStorage fails due to nil transaction", func(t *testing.T) { + t.Parallel() + + bp := getDefaultBaseProcessor() + bp.dataPool = &dataRetrieverMock.PoolsHolderStub{ + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + txsMap := make(map[block.Type]map[string]data.TransactionHandler) + txsMap[block.TxBlock] = map[string]data.TransactionHandler{ + "hash": nil, + } + return txsMap, true + }, + } + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + return nil, false + }, + } + }, + } + header := getHeaderHandlerWithMiniBlocksHeaders([]block.MiniBlockHeader{}) + + err := bp.saveExecutedData(header) + require.Equal(t, process.ErrNilTransaction, err) + }) + t.Run("putOneTransactionIntoStorage fails due to marshal error", func(t *testing.T) { + t.Parallel() + + bp := getDefaultBaseProcessor() + bp.dataPool = &dataRetrieverMock.PoolsHolderStub{ + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + txsMap := make(map[block.Type]map[string]data.TransactionHandler) + txsMap[block.TxBlock] = map[string]data.TransactionHandler{ + "hash": &transaction.Transaction{}, + } + return txsMap, true + }, + } + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + return nil, false + }, + } + }, + } + bp.marshalizer = &marshallerMock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + return nil, errExpected + }, + } + header := getHeaderHandlerWithMiniBlocksHeaders([]block.MiniBlockHeader{}) + + err := bp.saveExecutedData(header) + require.Equal(t, errExpected, err) + }) + }) + t.Run("should work and move all", func(t *testing.T) { + t.Parallel() + + cntPutCalled := 0 + wasRemoveCalledForTxs := false + getCalls := 0 + bp := &baseProcessor{ + receiptsRepository: &testscommon.ReceiptsRepositoryStub{ + SaveReceiptsCalled: func(holder common.ReceiptsHolder, header data.HeaderHandler, headerHash []byte) error { + return nil + }, + }, + store: &commonStorage.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &commonStorage.StorerStub{ + PutCalled: func(key, data []byte) error { + cntPutCalled++ + return nil + }, + }, nil + }, + PutCalled: func(unitType dataRetriever.UnitType, key, value []byte) error { + if unitType == dataRetriever.ExecutionResultsUnit { + cntPutCalled++ + return nil + } + + return nil + }, + }, + dataPool: &dataRetrieverMock.PoolsHolderStub{ + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + txsMap := make(map[block.Type]map[string]data.TransactionHandler) + txsMap[block.SmartContractResultBlock] = map[string]data.TransactionHandler{ + "hashSCR": &transaction.Transaction{}, + } + txsMap[block.RewardsBlock] = map[string]data.TransactionHandler{ + "hashReward": &transaction.Transaction{}, // for coverage + } + txsMap[block.ReceiptBlock] = map[string]data.TransactionHandler{ + "hashReward": &transaction.Transaction{}, // for coverage + } + return txsMap, true + }, + RemoveCalled: func(key []byte) { + wasRemoveCalledForTxs = true + }, + } + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + getCalls++ + if getCalls == 1 { + return nil, false + } + return []byte("marshalled mb"), true + }, + } + }, + }, + marshalizer: &marshallerMock.MarshalizerMock{}, + shardCoordinator: &testscommon.ShardsCoordinatorMock{}, + } + header := &testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { + return true + }, + GetExecutionResultsHandlersCalled: func() []data.BaseExecutionResultHandler { + return []data.BaseExecutionResultHandler{ + &block.MetaExecutionResult{ + MiniBlockHeaders: []block.MiniBlockHeader{ + {}, + }, + }, + } + }, + GetShardIDCalled: func() uint32 { + return common.MetachainShardId + }, + } + + err := bp.saveExecutedData(header) + require.NoError(t, err) + require.False(t, wasRemoveCalledForTxs) + require.Equal(t, 5, cntPutCalled) // 3 types of tx blocks + one for mbs + one for exec result + }) +} + +func TestBaseProcessor_cleanPostProcessCache(t *testing.T) { + t.Parallel() + t.Run("no execution results on header should not remove", func(t *testing.T) { + header := &block.HeaderV3{} + bp := getDefaultBaseProcessor() + cacher := &cache.CacherStub{ + RemoveCalled: func(key []byte) { + require.Fail(t, "should not be called") + }, + } + bp.dataPool = &dataRetrieverMock.PoolsHolderStub{ + PostProcessTransactionsCalled: func() storage.Cacher { + return cacher + }, + } + + err := bp.cleanPostProcessCache(header) + require.NoError(t, err) + }) + t.Run("header v2 should not remove", func(t *testing.T) { + header := &block.HeaderV2{} + bp := getDefaultBaseProcessor() + cacher := &cache.CacherStub{ + RemoveCalled: func(key []byte) { + require.Fail(t, "should not be called") + }, + } + bp.dataPool = &dataRetrieverMock.PoolsHolderStub{ + PostProcessTransactionsCalled: func() storage.Cacher { + return cacher + }, + } + + err := bp.cleanPostProcessCache(header) + require.NoError(t, err) + }) + t.Run("should remove from cache for each execution result", func(t *testing.T) { + headerHashes := []string{"hash1", "hash2"} + header := &testscommon.HeaderHandlerStub{ + GetExecutionResultsHandlersCalled: func() []data.BaseExecutionResultHandler { + return []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte(headerHashes[0]), + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("mb1"), + }, + }, + }, + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte(headerHashes[1]), + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("mb2"), + }, + }, + }, + } + }, + } + + expectedRemovedKeys := []string{ + "hash1", + "executionhash1", + "logshash1", + "gashash1", + "hash1", + "mb1", + "hash2", + "executionhash2", + "logshash2", + "gashash2", + "hash2", + "mb2", + } + + bp := getDefaultBaseProcessor() + removedKeys := make([]string, 0) + cacher := &cache.CacherStub{ + RemoveCalled: func(key []byte) { + removedKeys = append(removedKeys, string(key)) + }, + } + bp.dataPool = &dataRetrieverMock.PoolsHolderStub{ + PostProcessTransactionsCalled: func() storage.Cacher { + return cacher + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return cacher + }, + } + + err := bp.cleanPostProcessCache(header) + require.NoError(t, err) + require.Equal(t, expectedRemovedKeys, removedKeys) + }) +} + +func TestBaseProcessor_setCurrentBlockInfoV3CallsCleanOnConsensusReached(t *testing.T) { + t.Parallel() + + t.Run("V3 header should call CleanOnConsensusReached and set current block header on blockchain", func(t *testing.T) { + t.Parallel() + + cleanCalled := false + var receivedHash []byte + var receivedNonce uint64 + setHeaderCalled := false + + bp := &baseProcessor{ + executionManager: &processMocks.ExecutionManagerMock{ + CleanOnConsensusReachedCalled: func(headerHash []byte, header data.HeaderHandler) { + cleanCalled = true + receivedHash = headerHash + receivedNonce = header.GetNonce() + }, + }, + blockChain: &testscommon.ChainHandlerStub{ + SetCurrentBlockHeaderCalled: func(header data.HeaderHandler) error { + setHeaderCalled = true + return nil + }, + }, + } + + header := &testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { + return true + }, + GetNonceCalled: func() uint64 { + return 10 + }, + } + + committedHash := []byte("committedHash") + err := bp.setCurrentBlockInfo(header, committedHash, []byte("rootHash")) + require.NoError(t, err) + require.True(t, cleanCalled) + require.Equal(t, committedHash, receivedHash) + require.True(t, setHeaderCalled) + require.Equal(t, uint64(10), receivedNonce) + }) +} + +func TestBaseProcessor_saveExecutionResult(t *testing.T) { + t.Parallel() + + cntPutCalled := 0 + + bp := &baseProcessor{ + marshalizer: &marshallerMock.MarshalizerMock{}, + store: &commonStorage.ChainStorerStub{ + PutCalled: func(unitType dataRetriever.UnitType, key, value []byte) error { + if unitType == dataRetriever.ExecutionResultsUnit { + cntPutCalled++ + return nil + } + + return nil + }, + }, + } + + execRes := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 1, + HeaderRound: 2, + }, + } + + err := bp.SaveExecutionResult(execRes) + require.NoError(t, err) + + execResMeta := &block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 1, + HeaderRound: 2, + }, + }, + } + + err = bp.SaveExecutionResult(execResMeta) + require.NoError(t, err) + + require.Equal(t, 2, cntPutCalled) +} diff --git a/process/block/baseProcess_test.go b/process/block/baseProcess_test.go index 76243457170..b6a1ea05464 100644 --- a/process/block/baseProcess_test.go +++ b/process/block/baseProcess_test.go @@ -11,22 +11,36 @@ import ( "strconv" "strings" "sync" + "sync/atomic" "testing" "time" "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/core/keyValStorage" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/rewardTx" "github.com/multiversx/mx-chain-core-go/data/scheduled" + "github.com/multiversx/mx-chain-core-go/data/smartContractResult" "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-core-go/data/typeConverters/uint64ByteSlice" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "golang.org/x/exp/slices" + + "github.com/multiversx/mx-chain-go/common/holders" + "github.com/multiversx/mx-chain-go/process/aotSelection" + headersCache "github.com/multiversx/mx-chain-go/process/asyncExecution/cache" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionManager" + + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionTrack" + "github.com/multiversx/mx-chain-go/process/estimator" + "github.com/multiversx/mx-chain-go/process/missingData" + "github.com/multiversx/mx-chain-go/testscommon/mbSelection" + "github.com/multiversx/mx-chain-go/testscommon/pool" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/common/graceperiod" @@ -37,8 +51,10 @@ import ( "github.com/multiversx/mx-chain-go/process" blproc "github.com/multiversx/mx-chain-go/process/block" "github.com/multiversx/mx-chain-go/process/block/bootstrapStorage" + "github.com/multiversx/mx-chain-go/process/block/headerForBlock" "github.com/multiversx/mx-chain-go/process/block/processedMb" "github.com/multiversx/mx-chain-go/process/coordinator" + "github.com/multiversx/mx-chain-go/process/factory/containers" "github.com/multiversx/mx-chain-go/process/mock" "github.com/multiversx/mx-chain-go/sharding/nodesCoordinator" "github.com/multiversx/mx-chain-go/state" @@ -93,16 +109,101 @@ func createArgBaseProcessor( startHeaders := createGenesisBlocks(mock.NewOneShardCoordinatorMock()) accountsDb := make(map[state.AccountsDbIdentifier]state.AccountsAdapter) - accountsDb[state.UserAccountsState] = &stateMock.AccountsStub{ + accounts := &stateMock.AccountsStub{ RootHashCalled: func() ([]byte, error) { return nil, nil }, + RecreateTrieIfNeededCalled: func(options common.RootHashHolder) error { + return nil + }, + CommitCalled: func() ([]byte, error) { + return nil, nil + }, } + accountsDb[state.UserAccountsState] = accounts statusCoreComponents := &factory.StatusCoreComponentsStub{ AppStatusHandlerField: &statusHandlerMock.AppStatusHandlerStub{}, } + blockTracker := mock.NewBlockTrackerMock(bootstrapComponents.ShardCoordinator(), startHeaders) + var headersForBlock blproc.HeadersForBlock = &testscommon.HeadersForBlockMock{} + if !check.IfNil(coreComponents) && !check.IfNil(bootstrapComponents) && !check.IfNil(dataComponents) { + headersForBlock, _ = headerForBlock.NewHeadersForBlock(headerForBlock.ArgHeadersForBlock{ + DataPool: dataComponents.DataPool, + RequestHandler: &testscommon.RequestHandlerStub{}, + EnableEpochsHandler: coreComponents.EnableEpochsHandler(), + ShardCoordinator: bootstrapComponents.ShardCoordinator(), + BlockTracker: blockTracker, + TxCoordinator: &testscommon.TransactionCoordinatorMock{}, + RoundHandler: coreComponents.RoundHandler(), + ExtraDelayForRequestBlockInfoInMilliseconds: 100, + GenesisNonce: 0, + }) + } + + var blockDataRequester process.BlockDataRequester + var inclusionEstimator process.InclusionEstimator + var execManager process.ExecutionManager + var mbSelectionSession blproc.MiniBlocksSelectionSession + var execResultsVerifier blproc.ExecutionResultsVerifier + var missingDataResolver blproc.MissingDataResolver + if check.IfNil(dataComponents) || check.IfNil(dataComponents.Datapool()) || check.IfNil(coreComponents) || check.IfNil(bootstrapComponents) { + inclusionEstimator = &processMocks.InclusionEstimatorMock{} + mbSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{} + execResultsVerifier = &processMocks.ExecutionResultsVerifierMock{} + missingDataResolver = &processMocks.MissingDataResolverMock{} + } else { + preprocContainer := containers.NewPreProcessorsContainer() + blockDataRequesterArgs := coordinator.BlockDataRequestArgs{ + RequestHandler: &testscommon.RequestHandlerStub{}, + MiniBlockPool: dataComponents.Datapool().MiniBlocks(), + PreProcessors: preprocContainer, + ShardCoordinator: bootstrapComponents.ShardCoordinator(), + EnableEpochsHandler: coreComponents.EnableEpochsHandler(), + } + // second instance for proposal missing data fetching to avoid interferences + blockDataRequester, _ = coordinator.NewBlockDataRequester(blockDataRequesterArgs) + + mbSelectionSession, _ = blproc.NewMiniBlocksSelectionSession( + bootstrapComponents.ShardCoordinator().SelfId(), + coreComponents.InternalMarshalizer(), + coreComponents.Hasher(), + ) + + blocksCache := headersCache.NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + executionResultsTracker := executionTrack.NewExecutionResultsTracker() + _ = executionResultsTracker.SetLastNotarizedResult(&block.ExecutionResult{}) + execManager, _ = executionManager.NewExecutionManager(executionManager.ArgsExecutionManager{ + BlocksCache: blocksCache, + ExecutionResultsTracker: executionResultsTracker, + BlockChain: dataComponents.BlockChain, + Headers: dataComponents.DataPool.Headers(), + PostProcessTransactions: dataComponents.DataPool.PostProcessTransactions(), + ExecutedMiniBlocks: dataComponents.DataPool.ExecutedMiniBlocks(), + StorageService: dataComponents.StorageService(), + Marshaller: coreComponents.InternalMarshalizer(), + ShardCoordinator: bootstrapComponents.ShardCoordinator(), + }) + execResultsVerifier, _ = blproc.NewExecutionResultsVerifier(dataComponents.BlockChain, execManager) + inclusionEstimator, _ = estimator.NewExecutionResultInclusionEstimator( + config.ExecutionResultInclusionEstimatorConfig{ + SafetyMargin: 110, + MaxResultsPerBlock: 20, + }, + coreComponents.RoundHandler(), + &testscommon.ExecResSizeComputationStub{}, + ) + + missingDataArgs := missingData.ResolverArgs{ + HeadersPool: dataComponents.DataPool.Headers(), + ProofsPool: dataComponents.DataPool.Proofs(), + RequestHandler: &testscommon.RequestHandlerStub{}, + BlockDataRequester: blockDataRequester, + } + missingDataResolver, _ = missingData.NewMissingDataResolver(missingDataArgs) + } + return blproc.ArgBaseProcessor{ CoreComponents: coreComponents, DataComponents: dataComponents, @@ -111,6 +212,7 @@ func createArgBaseProcessor( StatusCoreComponents: statusCoreComponents, Config: config.Config{}, AccountsDB: accountsDb, + AccountsProposal: accounts, ForkDetector: &mock.ForkDetectorMock{}, NodesCoordinator: nodesCoordinatorInstance, FeeHandler: &mock.FeeAccumulatorStub{}, @@ -124,20 +226,36 @@ func createArgBaseProcessor( return nil }, }, - BlockTracker: mock.NewBlockTrackerMock(bootstrapComponents.ShardCoordinator(), startHeaders), - BlockSizeThrottler: &mock.BlockSizeThrottlerStub{}, - Version: "softwareVersion", - HistoryRepository: &dblookupext.HistoryRepositoryStub{}, - GasHandler: &mock.GasHandlerMock{}, - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - OutportDataProvider: &outport.OutportDataProviderStub{}, - ScheduledMiniBlocksEnableEpoch: 2, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - ReceiptsRepository: &testscommon.ReceiptsRepositoryStub{}, - BlockProcessingCutoffHandler: &testscommon.BlockProcessingCutoffStub{}, - ManagedPeersHolder: &testscommon.ManagedPeersHolderStub{}, - SentSignaturesTracker: &testscommon.SentSignatureTrackerStub{}, - StateAccessesCollector: disabled.NewDisabledStateAccessesCollector(), + BlockTracker: blockTracker, + BlockSizeThrottler: &mock.BlockSizeThrottlerStub{}, + Version: "softwareVersion", + HistoryRepository: &dblookupext.HistoryRepositoryStub{}, + GasHandler: &mock.GasHandlerMock{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + OutportDataProvider: &outport.OutportDataProviderStub{}, + ScheduledMiniBlocksEnableEpoch: 2, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + ReceiptsRepository: &testscommon.ReceiptsRepositoryStub{}, + BlockProcessingCutoffHandler: &testscommon.BlockProcessingCutoffStub{}, + ManagedPeersHolder: &testscommon.ManagedPeersHolderStub{}, + SentSignaturesTracker: &testscommon.SentSignatureTrackerStub{}, + StateAccessesCollector: disabled.NewDisabledStateAccessesCollector(), + HeadersForBlock: headersForBlock, + MiniBlocksSelectionSession: mbSelectionSession, + ExecutionResultsVerifier: execResultsVerifier, + MissingDataResolver: missingDataResolver, + ExecutionResultsInclusionEstimator: inclusionEstimator, + GasComputation: &testscommon.GasComputationMock{ + AddIncomingMiniBlocksCalled: func(miniBlocks []data.MiniBlockHeaderHandler, transactions map[string][]data.TransactionHandler) (int, int, error) { + return len(miniBlocks), 0, nil + }, + AddOutgoingTransactionsCalled: func(txHashes [][]byte, transactions []data.TransactionHandler) ([][]byte, []data.MiniBlockHeaderHandler, error) { + return txHashes, nil, nil + }, + }, + ExecutionManager: execManager, + TxExecutionOrderHandler: &commonMocks.TxExecutionOrderHandlerStub{}, + AOTSelector: aotSelection.NewDisabledAOTSelector(), } } @@ -163,139 +281,45 @@ func generateTestUnit() storage.Storer { return storer } -func createShardedDataChacherNotifier( - handler data.TransactionHandler, - testHash []byte, -) func() dataRetriever.ShardedDataCacherNotifier { - return func() dataRetriever.ShardedDataCacherNotifier { - return &testscommon.ShardedDataStub{ - ShardDataStoreCalled: func(id string) (c storage.Cacher) { - return &cache.CacherStub{ - PeekCalled: func(key []byte) (value interface{}, ok bool) { - if reflect.DeepEqual(key, testHash) { - return handler, true - } - return nil, false - }, - KeysCalled: func() [][]byte { - return [][]byte{[]byte("key1"), []byte("key2")} - }, - LenCalled: func() int { - return 0 - }, - MaxSizeCalled: func() int { - return 1000 - }, - } - }, - RemoveSetOfDataFromPoolCalled: func(keys [][]byte, id string) {}, - SearchFirstDataCalled: func(key []byte) (value interface{}, ok bool) { - if reflect.DeepEqual(key, []byte("tx1_hash")) { - return handler, true - } - return nil, false - }, - AddDataCalled: func(key []byte, data interface{}, sizeInBytes int, cacheId string) { - }, - } - } -} +func initDataPool() *dataRetrieverMock.PoolsHolderStub { + transactionsPool := testscommon.NewShardedDataCacheNotifierMock() + unsignedTransactionsPool := testscommon.NewShardedDataCacheNotifierMock() + rewardTransactionsPool := testscommon.NewShardedDataCacheNotifierMock() + validatorsInfoPool := testscommon.NewShardedDataCacheNotifierMock() -func initDataPool(testHash []byte) *dataRetrieverMock.PoolsHolderStub { - rwdTx := &rewardTx.RewardTx{ - Round: 1, - Epoch: 0, - Value: big.NewInt(10), - RcvAddr: []byte("receiver"), - } - txCalled := createShardedDataChacherNotifier(&transaction.Transaction{Nonce: 10}, testHash) - unsignedTxCalled := createShardedDataChacherNotifier(&transaction.Transaction{Nonce: 10}, testHash) - rewardTransactionsCalled := createShardedDataChacherNotifier(rwdTx, testHash) + metablocksPool := cache.NewCacherStub() + miniblocksPool := cache.NewCacherStub() + headersPool := &mock.HeadersCacherStub{} + proofsPool := proofscache.NewProofsPool(3, 100) + executedMBs := cache.NewCacherStub() + postProcessTxs := cache.NewCacherStub() + directSentTxs := cache.NewCacherStub() sdp := &dataRetrieverMock.PoolsHolderStub{ - TransactionsCalled: txCalled, - UnsignedTransactionsCalled: unsignedTxCalled, - RewardTransactionsCalled: rewardTransactionsCalled, + TransactionsCalled: func() dataRetriever.ShardedDataCacherNotifier { return transactionsPool }, + UnsignedTransactionsCalled: func() dataRetriever.ShardedDataCacherNotifier { return unsignedTransactionsPool }, + RewardTransactionsCalled: func() dataRetriever.ShardedDataCacherNotifier { return rewardTransactionsPool }, + ValidatorsInfoCalled: func() dataRetriever.ShardedDataCacherNotifier { return validatorsInfoPool }, MetaBlocksCalled: func() storage.Cacher { - return &cache.CacherStub{ - GetCalled: func(key []byte) (value interface{}, ok bool) { - if reflect.DeepEqual(key, []byte("tx1_hash")) { - return &transaction.Transaction{Nonce: 10}, true - } - return nil, false - }, - KeysCalled: func() [][]byte { - return nil - }, - LenCalled: func() int { - return 0 - }, - MaxSizeCalled: func() int { - return 1000 - }, - PeekCalled: func(key []byte) (value interface{}, ok bool) { - if reflect.DeepEqual(key, []byte("tx1_hash")) { - return &transaction.Transaction{Nonce: 10}, true - } - return nil, false - }, - RegisterHandlerCalled: func(i func(key []byte, value interface{})) {}, - RemoveCalled: func(key []byte) {}, - } + return metablocksPool }, MiniBlocksCalled: func() storage.Cacher { - cs := cache.NewCacherStub() - cs.RegisterHandlerCalled = func(i func(key []byte, value interface{})) { - } - cs.GetCalled = func(key []byte) (value interface{}, ok bool) { - if bytes.Equal([]byte("bbb"), key) { - return make(block.MiniBlockSlice, 0), true - } - - return nil, false - } - cs.PeekCalled = func(key []byte) (value interface{}, ok bool) { - if bytes.Equal([]byte("bbb"), key) { - return make(block.MiniBlockSlice, 0), true - } - - return nil, false - } - cs.RegisterHandlerCalled = func(i func(key []byte, value interface{})) {} - cs.RemoveCalled = func(key []byte) {} - cs.LenCalled = func() int { - return 0 - } - cs.MaxSizeCalled = func() int { - return 300 - } - cs.KeysCalled = func() [][]byte { - return nil - } - return cs + return miniblocksPool }, HeadersCalled: func() dataRetriever.HeadersPool { - cs := &mock.HeadersCacherStub{} - cs.RegisterHandlerCalled = func(i func(header data.HeaderHandler, key []byte)) { - } - cs.GetHeaderByHashCalled = func(hash []byte) (data.HeaderHandler, error) { - return nil, process.ErrMissingHeader - } - cs.RemoveHeaderByHashCalled = func(key []byte) { - } - cs.LenCalled = func() int { - return 0 - } - cs.MaxSizeCalled = func() int { - return 1000 - } - cs.NoncesCalled = func(shardId uint32) []uint64 { - return nil - } - return cs + return headersPool }, ProofsCalled: func() dataRetriever.ProofsPool { - return proofscache.NewProofsPool(3, 100) + return proofsPool + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return executedMBs + }, + PostProcessTransactionsCalled: func() storage.Cacher { + return postProcessTxs + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return directSentTxs }, } @@ -305,6 +329,7 @@ func initDataPool(testHash []byte) *dataRetrieverMock.PoolsHolderStub { func initStore() *dataRetriever.ChainStorer { store := dataRetriever.NewChainStorer() store.AddStorer(dataRetriever.TransactionUnit, generateTestUnit()) + store.AddStorer(dataRetriever.UnsignedTransactionUnit, generateTestUnit()) store.AddStorer(dataRetriever.MiniBlockUnit, generateTestUnit()) store.AddStorer(dataRetriever.RewardTransactionUnit, generateTestUnit()) store.AddStorer(dataRetriever.MetaBlockUnit, generateTestUnit()) @@ -393,6 +418,7 @@ func createComponentHolderMocks() ( _ = blkc.SetGenesisHeader(&block.Header{Nonce: 0}) gracePeriod, _ := graceperiod.NewEpochChangeGracePeriod([]config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}) + coreComponents := &mock.CoreComponentsMock{ IntMarsh: &mock.MarshalizerMock{}, Hash: &mock.HasherStub{}, @@ -405,11 +431,13 @@ func createComponentHolderMocks() ( RoundNotifierField: &epochNotifier.RoundNotifierStub{}, EnableRoundsHandlerField: &testscommon.EnableRoundsHandlerStub{}, EpochChangeGracePeriodHandlerField: gracePeriod, + ProcessConfigsHandlerField: testscommon.GetDefaultProcessConfigsHandler(), + ClosingNodeStartedField: &atomic.Bool{}, } dataComponents := &mock.DataComponentsMock{ Storage: initStore(), - DataPool: initDataPool([]byte("")), + DataPool: initDataPool(), BlockChain: blkc, } @@ -417,7 +445,7 @@ func createComponentHolderMocks() ( Coordinator: mock.NewOneShardCoordinatorMock(), HdrIntegrityVerifier: &mock.HeaderIntegrityVerifierStub{}, VersionedHdrFactory: &testscommon.VersionedHeaderFactoryStub{ - CreateCalled: func(epoch uint32) data.HeaderHandler { + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { return &block.Header{} }, }, @@ -445,15 +473,40 @@ func createMockTransactionCoordinatorArguments( accountAdapter state.AccountsAdapter, poolsHolder dataRetriever.PoolsHolder, preProcessorsContainer process.PreProcessorsContainer, + preProcessorsContainerProposal process.PreProcessorsContainer, ) coordinator.ArgTransactionCoordinator { + + shardCoordinator := mock.NewMultiShardsCoordinatorMock(3) + enableEpochsHandler := enableEpochsHandlerMock.NewEnableEpochsHandlerStub() + enableRoundsHandler := &testscommon.EnableRoundsHandlerStub{} + + blockDataRequesterArgs := coordinator.BlockDataRequestArgs{ + RequestHandler: &testscommon.RequestHandlerStub{}, + MiniBlockPool: poolsHolder.MiniBlocks(), + PreProcessors: preProcessorsContainer, + ShardCoordinator: shardCoordinator, + EnableEpochsHandler: enableEpochsHandler, + } + + blockDataRequester, _ := coordinator.NewBlockDataRequester(blockDataRequesterArgs) + + blockDataRequesterArgsProposal := coordinator.BlockDataRequestArgs{ + RequestHandler: &testscommon.RequestHandlerStub{}, + MiniBlockPool: poolsHolder.MiniBlocks(), + PreProcessors: preProcessorsContainerProposal, + ShardCoordinator: shardCoordinator, + EnableEpochsHandler: enableEpochsHandler, + } + blockDataRequesterProposal, _ := coordinator.NewBlockDataRequester(blockDataRequesterArgsProposal) + argsTransactionCoordinator := coordinator.ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accountAdapter, - MiniBlockPool: poolsHolder.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: preProcessorsContainer, + Hasher: &hashingMocks.HasherMock{}, + Marshalizer: &mock.MarshalizerMock{}, + ShardCoordinator: shardCoordinator, + Accounts: accountAdapter, + DataPool: poolsHolder, + PreProcessors: preProcessorsContainer, + PreProcessorsProposal: preProcessorsContainerProposal, InterProcessors: &mock.InterimProcessorContainerMock{ KeysCalled: func() []block.Type { return []block.Type{block.SmartContractResultBlock} @@ -466,11 +519,23 @@ func createMockTransactionCoordinatorArguments( EconomicsFee: &economicsmocks.EconomicsHandlerMock{}, TxTypeHandler: &testscommon.TxTypeHandlerMock{}, TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + EnableEpochsHandler: enableEpochsHandler, + EnableRoundsHandler: enableRoundsHandler, ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, TxExecutionOrderHandler: &commonMocks.TxExecutionOrderHandlerStub{}, + BlockDataRequester: blockDataRequester, + BlockDataRequesterProposal: blockDataRequesterProposal, + GasComputation: &testscommon.GasComputationMock{ + AddIncomingMiniBlocksCalled: func(miniBlocks []data.MiniBlockHeaderHandler, transactions map[string][]data.TransactionHandler) (int, int, error) { + return len(miniBlocks), 0, nil + }, + AddOutgoingTransactionsCalled: func(txHashes [][]byte, transactions []data.TransactionHandler) ([][]byte, []data.MiniBlockHeaderHandler, error) { + return txHashes, nil, nil + }, + }, + AOTSelector: aotSelection.NewDisabledAOTSelector(), } return argsTransactionCoordinator @@ -554,6 +619,25 @@ func TestCheckProcessorNilParameters(t *testing.T) { }, expectedErr: process.ErrNilStorage, }, + { + args: func() blproc.ArgBaseProcessor { + dataCompCopy := *dataComponents + dataCompCopy.DataPool = &dataRetrieverMock.PoolsHolderStub{ + TransactionsCalled: func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataCacheNotifierMock{} + }, + HeadersCalled: func() dataRetriever.HeadersPool { + return &pool.HeadersPoolStub{} + }, + ProofsCalled: func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{} + }, + } + args := createArgBaseProcessor(coreComponents, &dataCompCopy, bootstrapComponents, statusComponents) + return args + }, + expectedErr: process.ErrNilDirectSentCache, + }, { args: func() blproc.ArgBaseProcessor { args := createArgBaseProcessor(coreComponents, dataComponents, bootstrapComponents, statusComponents) @@ -824,12 +908,69 @@ func TestCheckProcessorNilParameters(t *testing.T) { }, expectedErr: process.ErrNilSentSignatureTracker, }, + { + args: func() blproc.ArgBaseProcessor { + args := createArgBaseProcessor(coreComponents, dataComponents, bootstrapComponents, statusComponents) + args.ExecutionResultsInclusionEstimator = nil + return args + }, + expectedErr: process.ErrNilExecutionResultsInclusionEstimator, + }, + { + args: func() blproc.ArgBaseProcessor { + args := createArgBaseProcessor(coreComponents, dataComponents, bootstrapComponents, statusComponents) + args.ExecutionManager = nil + return args + }, + expectedErr: process.ErrNilExecutionManager, + }, + { + args: func() blproc.ArgBaseProcessor { + args := createArgBaseProcessor(coreComponents, dataComponents, bootstrapComponents, statusComponents) + args.MiniBlocksSelectionSession = nil + return args + }, + expectedErr: process.ErrNilMiniBlocksSelectionSession, + }, + { + args: func() blproc.ArgBaseProcessor { + args := createArgBaseProcessor(coreComponents, dataComponents, bootstrapComponents, statusComponents) + args.ExecutionResultsVerifier = nil + return args + }, + expectedErr: process.ErrNilExecutionResultsVerifier, + }, + { + args: func() blproc.ArgBaseProcessor { + args := createArgBaseProcessor(coreComponents, dataComponents, bootstrapComponents, statusComponents) + args.MissingDataResolver = nil + return args + }, + expectedErr: process.ErrNilMissingDataResolver, + }, + { + args: func() blproc.ArgBaseProcessor { + args := createArgBaseProcessor(coreComponents, dataComponents, bootstrapComponents, statusComponents) + args.GasComputation = nil + return args + }, + expectedErr: process.ErrNilGasComputation, + }, { args: func() blproc.ArgBaseProcessor { return createArgBaseProcessor(coreComponents, dataComponents, bootstrapComponents, statusComponents) }, expectedErr: nil, }, + { + args: func() blproc.ArgBaseProcessor { + coreCompCopy := *coreComponents + coreCompCopy.ClosingNodeStartedField = nil + args := createArgBaseProcessor(&coreCompCopy, dataComponents, bootstrapComponents, statusComponents) + return args + }, + expectedErr: process.ErrNilClosingNodeStartedFlag, + }, } for _, test := range tests { @@ -903,6 +1044,166 @@ func TestBlockProcessor_CheckBlockValidity(t *testing.T) { assert.Nil(t, err) } +func TestBlockProcessor_CheckBlockValidityTimestamp(t *testing.T) { + t.Parallel() + + t.Run("genesis+1 block with valid timestamp should pass", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + coreComponents.Hash = &hashingMocks.HasherMock{} + coreComponents.RoundField = &testscommon.RoundHandlerMock{ + GetTimeStampForRoundCalled: func(round uint64) uint64 { + return round * 6000 // 6s per round in ms + }, + } + + blkc := createTestBlockchain() + dataComponents.BlockChain = blkc + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + bp, _ := blproc.NewShardProcessor(arguments) + + body := &block.Body{} + hdr := &block.Header{} + hdr.Nonce = 1 + hdr.Round = 1 + hdr.TimeStamp = 6 // 6 seconds = 6000ms + hdr.PrevHash = []byte("") + + err := bp.CheckBlockValidity(hdr, body) + assert.Nil(t, err) + }) + + t.Run("genesis+1 block with invalid timestamp should fail", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + coreComponents.Hash = &hashingMocks.HasherMock{} + coreComponents.RoundField = &testscommon.RoundHandlerMock{ + GetTimeStampForRoundCalled: func(round uint64) uint64 { + return round * 6000 + }, + } + + blkc := createTestBlockchain() + dataComponents.BlockChain = blkc + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + bp, _ := blproc.NewShardProcessor(arguments) + + body := &block.Body{} + hdr := &block.Header{} + hdr.Nonce = 1 + hdr.Round = 1 + hdr.TimeStamp = 999 // wrong timestamp + hdr.PrevHash = []byte("") + + err := bp.CheckBlockValidity(hdr, body) + assert.Equal(t, process.ErrInvalidTimestamp, err) + }) + + t.Run("non-genesis block with valid timestamp should pass", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + coreComponents.Hash = &hashingMocks.HasherMock{} + coreComponents.RoundField = &testscommon.RoundHandlerMock{ + GetTimeStampForRoundCalled: func(round uint64) uint64 { + return round * 6000 + }, + } + + blkc := createTestBlockchain() + prevHash := []byte("X") + blkc.GetCurrentBlockHeaderCalled = func() data.HeaderHandler { + return &block.Header{Round: 1, Nonce: 1} + } + blkc.GetCurrentBlockHeaderHashCalled = func() []byte { + return prevHash + } + dataComponents.BlockChain = blkc + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + bp, _ := blproc.NewShardProcessor(arguments) + + body := &block.Body{} + hdr := &block.Header{} + hdr.Nonce = 2 + hdr.Round = 2 + hdr.TimeStamp = 12 // 12 seconds = 12000ms + hdr.PrevHash = prevHash + hdr.PrevRandSeed = []byte("") + + err := bp.CheckBlockValidity(hdr, body) + assert.Nil(t, err) + }) + + t.Run("non-genesis block with invalid timestamp should fail", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + coreComponents.Hash = &hashingMocks.HasherMock{} + coreComponents.RoundField = &testscommon.RoundHandlerMock{ + GetTimeStampForRoundCalled: func(round uint64) uint64 { + return round * 6000 + }, + } + + blkc := createTestBlockchain() + prevHash := []byte("X") + blkc.GetCurrentBlockHeaderCalled = func() data.HeaderHandler { + return &block.Header{Round: 1, Nonce: 1} + } + blkc.GetCurrentBlockHeaderHashCalled = func() []byte { + return prevHash + } + dataComponents.BlockChain = blkc + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + bp, _ := blproc.NewShardProcessor(arguments) + + body := &block.Body{} + hdr := &block.Header{} + hdr.Nonce = 2 + hdr.Round = 2 + hdr.TimeStamp = 999 // wrong timestamp + hdr.PrevHash = prevHash + hdr.PrevRandSeed = []byte("") + + err := bp.CheckBlockValidity(hdr, body) + assert.Equal(t, process.ErrInvalidTimestamp, err) + }) + + t.Run("other checks still fail before timestamp check", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + coreComponents.Hash = &hashingMocks.HasherMock{} + coreComponents.RoundField = &testscommon.RoundHandlerMock{ + GetTimeStampForRoundCalled: func(round uint64) uint64 { + return round * 6000 + }, + } + + blkc := createTestBlockchain() + blkc.GetCurrentBlockHeaderCalled = func() data.HeaderHandler { + return &block.Header{Round: 1, Nonce: 1} + } + blkc.GetCurrentBlockHeaderHashCalled = func() []byte { + return []byte("X") + } + dataComponents.BlockChain = blkc + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + bp, _ := blproc.NewShardProcessor(arguments) + + body := &block.Body{} + hdr := &block.Header{} + hdr.Nonce = 2 + hdr.Round = 0 // invalid round + hdr.TimeStamp = 999 + + err := bp.CheckBlockValidity(hdr, body) + assert.Equal(t, process.ErrLowerRoundInBlock, err) + }) +} + func TestVerifyStateRoot_ShouldWork(t *testing.T) { t.Parallel() rootHash := []byte("root hash to be tested") @@ -1099,7 +1400,7 @@ func TestBaseProcessor_RemoveHeadersBehindNonceFromPools(t *testing.T) { t.Parallel() removeFromDataPoolWasCalled := false - dataPool := initDataPool([]byte("")) + dataPool := initDataPool() dataPool.HeadersCalled = func() dataRetriever.HeadersPool { cs := &mock.HeadersCacherStub{} cs.RegisterHandlerCalled = func(i func(header data.HeaderHandler, key []byte)) { @@ -1735,96 +2036,197 @@ func TestBlockProcessor_RequestHeadersIfMissingShouldWorkWhenSortedHeadersListIs func TestBlockProcessor_RequestHeadersIfMissingShouldWork(t *testing.T) { t.Parallel() - var requestedNonces []uint64 - var mutRequestedNonces sync.Mutex + t.Run("without andromeda activated, should request only headers", func(t *testing.T) { + t.Parallel() - coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() - roundHandler := &mock.RoundHandlerMock{} - coreComponents.RoundField = roundHandler - arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + var requestedNonces []uint64 + var mutRequestedNonces sync.Mutex - requestHandlerStub := &testscommon.RequestHandlerStub{ - RequestMetaHeaderByNonceCalled: func(nonce uint64) { - mutRequestedNonces.Lock() - requestedNonces = append(requestedNonces, nonce) - mutRequestedNonces.Unlock() - }, - } - arguments.RequestHandler = requestHandlerStub - sp, _ := blproc.NewShardProcessor(arguments) + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() - sortedHeaders := make([]data.HeaderHandler, 0) + coreComponents.EnableEpochsHandlerField = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag != common.AndromedaFlag + }, + } - crossNotarizedHeader := &block.MetaBlock{ - Nonce: 5, - Round: 5, - } - arguments.BlockTracker.AddCrossNotarizedHeader(core.MetachainShardId, crossNotarizedHeader, []byte("hash")) + roundHandler := &mock.RoundHandlerMock{} + coreComponents.RoundField = roundHandler + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - hdr1 := &block.MetaBlock{ - Nonce: 1, - Round: 1, - } - sortedHeaders = append(sortedHeaders, hdr1) + requestProofCalls := 0 + requestHandlerStub := &testscommon.RequestHandlerStub{ + RequestMetaHeaderByNonceCalled: func(nonce uint64) { + mutRequestedNonces.Lock() + requestedNonces = append(requestedNonces, nonce) + mutRequestedNonces.Unlock() + }, + RequestEquivalentProofByNonceCalled: func(headerShard uint32, headerNonce uint64) { + mutRequestedNonces.Lock() + requestProofCalls++ + mutRequestedNonces.Unlock() + }, + } + arguments.RequestHandler = requestHandlerStub + sp, _ := blproc.NewShardProcessor(arguments) - hdr2 := &block.MetaBlock{ - Nonce: 8, - Round: 8, - } - sortedHeaders = append(sortedHeaders, hdr2) + sortedHeaders := make([]data.HeaderHandler, 0) - hdr3 := &block.MetaBlock{ - Nonce: 10, - Round: 10, - } - sortedHeaders = append(sortedHeaders, hdr3) + crossNotarizedHeader := &block.MetaBlock{ + Nonce: 5, + Round: 5, + } + arguments.BlockTracker.AddCrossNotarizedHeader(core.MetachainShardId, crossNotarizedHeader, []byte("hash")) - requestedNonces = make([]uint64, 0) - roundHandler.RoundIndex = 15 - _ = sp.RequestHeadersIfMissing(sortedHeaders, core.MetachainShardId) - time.Sleep(100 * time.Millisecond) - mutRequestedNonces.Lock() - sort.Slice(requestedNonces, func(i, j int) bool { - return requestedNonces[i] < requestedNonces[j] - }) - mutRequestedNonces.Unlock() - expectedNonces := []uint64{6, 7, 9, 11, 12, 13} - assert.Equal(t, expectedNonces, requestedNonces) + hdr1 := &block.MetaBlock{ + Nonce: 1, + Round: 1, + } + sortedHeaders = append(sortedHeaders, hdr1) - requestedNonces = make([]uint64, 0) - roundHandler.RoundIndex = process.MaxHeaderRequestsAllowed + 10 - _ = sp.RequestHeadersIfMissing(sortedHeaders, core.MetachainShardId) - time.Sleep(100 * time.Millisecond) - mutRequestedNonces.Lock() - sort.Slice(requestedNonces, func(i, j int) bool { - return requestedNonces[i] < requestedNonces[j] + hdr2 := &block.MetaBlock{ + Nonce: 8, + Round: 8, + } + sortedHeaders = append(sortedHeaders, hdr2) + + hdr3 := &block.MetaBlock{ + Nonce: 10, + Round: 10, + } + sortedHeaders = append(sortedHeaders, hdr3) + + requestedNonces = make([]uint64, 0) + roundHandler.RoundIndex = 15 + _ = sp.RequestHeadersIfMissing(sortedHeaders, core.MetachainShardId) + time.Sleep(100 * time.Millisecond) + mutRequestedNonces.Lock() + sort.Slice(requestedNonces, func(i, j int) bool { + return requestedNonces[i] < requestedNonces[j] + }) + mutRequestedNonces.Unlock() + expectedNonces := []uint64{6, 7, 9, 11, 12, 13} + assert.Equal(t, expectedNonces, requestedNonces) + assert.Equal(t, 0, requestProofCalls) + + requestedNonces = make([]uint64, 0) + roundHandler.RoundIndex = process.MaxHeaderRequestsAllowed + 10 + _ = sp.RequestHeadersIfMissing(sortedHeaders, core.MetachainShardId) + time.Sleep(100 * time.Millisecond) + mutRequestedNonces.Lock() + sort.Slice(requestedNonces, func(i, j int) bool { + return requestedNonces[i] < requestedNonces[j] + }) + mutRequestedNonces.Unlock() + expectedNonces = []uint64{6, 7, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25} + assert.Equal(t, expectedNonces, requestedNonces) + assert.Equal(t, 0, requestProofCalls) }) - mutRequestedNonces.Unlock() - expectedNonces = []uint64{6, 7, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25} - assert.Equal(t, expectedNonces, requestedNonces) -} -func TestBlockProcessor_RequestHeadersIfMissingShouldAddHeaderIntoTrackerPool(t *testing.T) { - t.Parallel() + t.Run("with andromeda activated, should request also proofs if needed", func(t *testing.T) { + t.Parallel() - var addedNonces []uint64 - poolsHolderStub := initDataPool([]byte("")) - poolsHolderStub.HeadersCalled = func() dataRetriever.HeadersPool { - return &mock.HeadersCacherStub{ - GetHeaderByNonceAndShardIdCalled: func(hdrNonce uint64, shardId uint32) ([]data.HeaderHandler, [][]byte, error) { - addedNonces = append(addedNonces, hdrNonce) - return []data.HeaderHandler{&block.MetaBlock{Nonce: 1}}, [][]byte{[]byte("hash")}, nil + var requestedNonces []uint64 + var mutRequestedNonces sync.Mutex + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + + dataPool := initDataPool() + dataPool.ProofsCalled = func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{ + GetProofByNonceCalled: func(headerNonce uint64, shardID uint32) (data.HeaderProofHandler, error) { + return nil, errors.New("err") + }, + } + } + dataComponents.DataPool = dataPool + + coreComponents.EnableEpochsHandlerField = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.AndromedaFlag }, } - } - coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() - dataComponents.DataPool = poolsHolderStub - roundHandler := &mock.RoundHandlerMock{} - coreComponents.RoundField = roundHandler - arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + roundHandler := &mock.RoundHandlerMock{} + coreComponents.RoundField = roundHandler + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - sp, _ := blproc.NewShardProcessor(arguments) + requestProofCalls := 0 + requestHandlerStub := &testscommon.RequestHandlerStub{ + RequestMetaHeaderByNonceCalled: func(nonce uint64) { + mutRequestedNonces.Lock() + requestedNonces = append(requestedNonces, nonce) + mutRequestedNonces.Unlock() + }, + RequestEquivalentProofByNonceCalled: func(headerShard uint32, headerNonce uint64) { + mutRequestedNonces.Lock() + requestProofCalls++ + mutRequestedNonces.Unlock() + }, + } + arguments.RequestHandler = requestHandlerStub + sp, _ := blproc.NewShardProcessor(arguments) + + sortedHeaders := make([]data.HeaderHandler, 0) + + crossNotarizedHeader := &block.MetaBlock{ + Nonce: 5, + Round: 5, + } + arguments.BlockTracker.AddCrossNotarizedHeader(core.MetachainShardId, crossNotarizedHeader, []byte("hash")) + + hdr1 := &block.MetaBlock{ + Nonce: 1, + Round: 1, + } + sortedHeaders = append(sortedHeaders, hdr1) + + hdr2 := &block.MetaBlock{ + Nonce: 8, + Round: 8, + } + sortedHeaders = append(sortedHeaders, hdr2) + + hdr3 := &block.MetaBlock{ + Nonce: 10, + Round: 10, + } + sortedHeaders = append(sortedHeaders, hdr3) + + requestedNonces = make([]uint64, 0) + roundHandler.RoundIndex = 15 + _ = sp.RequestHeadersIfMissing(sortedHeaders, core.MetachainShardId) + time.Sleep(100 * time.Millisecond) + mutRequestedNonces.Lock() + sort.Slice(requestedNonces, func(i, j int) bool { + return requestedNonces[i] < requestedNonces[j] + }) + mutRequestedNonces.Unlock() + expectedNonces := []uint64{6, 7, 9, 11, 12, 13} + assert.Equal(t, expectedNonces, requestedNonces) + assert.Equal(t, len(expectedNonces), requestProofCalls) + + requestProofCalls = 0 + requestedNonces = make([]uint64, 0) + roundHandler.RoundIndex = process.MaxHeaderRequestsAllowed + 10 + _ = sp.RequestHeadersIfMissing(sortedHeaders, core.MetachainShardId) + time.Sleep(100 * time.Millisecond) + mutRequestedNonces.Lock() + sort.Slice(requestedNonces, func(i, j int) bool { + return requestedNonces[i] < requestedNonces[j] + }) + mutRequestedNonces.Unlock() + expectedNonces = []uint64{6, 7, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25} + assert.Equal(t, expectedNonces, requestedNonces) + assert.Equal(t, len(expectedNonces), requestProofCalls) + }) +} + +func TestBlockProcessor_RequestHeadersIfMissingShouldAddHeaderIntoTrackerPool(t *testing.T) { + t.Parallel() + + var mutRequestedNonces sync.Mutex + var addedNonces map[uint64]struct{} sortedHeaders := make([]data.HeaderHandler, 0) @@ -1832,7 +2234,6 @@ func TestBlockProcessor_RequestHeadersIfMissingShouldAddHeaderIntoTrackerPool(t Nonce: 5, Round: 5, } - arguments.BlockTracker.AddCrossNotarizedHeader(core.MetachainShardId, crossNotarizedHeader, []byte("hash")) hdr1 := &block.MetaBlock{ Nonce: 1, @@ -1852,13 +2253,48 @@ func TestBlockProcessor_RequestHeadersIfMissingShouldAddHeaderIntoTrackerPool(t } sortedHeaders = append(sortedHeaders, hdr3) - addedNonces = make([]uint64, 0) + expectedAddedNonces := []uint64{6, 7, 9} + + addedNonces = make(map[uint64]struct{}) + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + roundHandler := &mock.RoundHandlerMock{} + coreComponents.RoundField = roundHandler + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + arguments.BlockTracker.AddCrossNotarizedHeader(core.MetachainShardId, crossNotarizedHeader, []byte("hash")) + + wg := &sync.WaitGroup{} + wg.Add(len(expectedAddedNonces)) + + requestHandlerStub := &testscommon.RequestHandlerStub{ + RequestMetaHeaderByNonceCalled: func(nonce uint64) { + mutRequestedNonces.Lock() + addedNonces[nonce] = struct{}{} + mutRequestedNonces.Unlock() + + wg.Done() + }, + } + arguments.RequestHandler = requestHandlerStub roundHandler.RoundIndex = 12 + + sp, _ := blproc.NewShardProcessor(arguments) + _ = sp.RequestHeadersIfMissing(sortedHeaders, core.MetachainShardId) - expectedAddedNonces := []uint64{6, 7, 9} - assert.Equal(t, expectedAddedNonces, addedNonces) + wg.Wait() + + // check if nonces were requested + // requests are not necessarily in order + mutRequestedNonces.Lock() + for _, nonce := range expectedAddedNonces { + _, ok := addedNonces[nonce] + assert.True(t, ok) + } + mutRequestedNonces.Unlock() } func TestAddHeaderIntoTrackerPool_ShouldWork(t *testing.T) { @@ -1867,7 +2303,7 @@ func TestAddHeaderIntoTrackerPool_ShouldWork(t *testing.T) { var wasCalled bool shardID := core.MetachainShardId nonce := uint64(1) - poolsHolderStub := initDataPool([]byte("")) + poolsHolderStub := initDataPool() poolsHolderStub.HeadersCalled = func() dataRetriever.HeadersPool { return &mock.HeadersCacherStub{ GetHeaderByNonceAndShardIdCalled: func(hdrNonce uint64, shardId uint32) ([]data.HeaderHandler, [][]byte, error) { @@ -2215,6 +2651,29 @@ func TestBaseProcessor_updateState(t *testing.T) { assert.Equal(t, []byte(strconv.Itoa(len(headers)-2)), cancelPruneRootHash) } +func TestBaseProcessor_ProcessScheduledBlockShouldErrWhenProcessorBusy(t *testing.T) { + t.Parallel() + + arguments := CreateMockArguments(createComponentHolderMocks()) + processHandler := arguments.CoreComponents.ProcessStatusHandler() + mockProcessHandler := processHandler.(*testscommon.ProcessStatusHandlerStub) + mockProcessHandler.TrySetBusyCalled = func(reason string) bool { + return false + } + setIdleCalled := false + mockProcessHandler.SetIdleCalled = func() { + setIdleCalled = true + } + + bp, _ := blproc.NewShardProcessor(arguments) + + err := bp.ProcessScheduledBlock( + &block.MetaBlock{}, &block.Body{}, haveTime, + ) + require.Equal(t, process.ErrBlockProcessorBusy, err) + require.False(t, setIdleCalled, "SetIdle should not be called when TrySetBusy fails") +} + func TestBaseProcessor_ProcessScheduledBlockShouldFail(t *testing.T) { t.Parallel() @@ -2228,14 +2687,14 @@ func TestBaseProcessor_ProcessScheduledBlockShouldFail(t *testing.T) { mockProcessHandler.SetIdleCalled = func() { busyIdleCalled = append(busyIdleCalled, idleIdentifier) } - mockProcessHandler.SetBusyCalled = func(reason string) { + mockProcessHandler.TrySetBusyCalled = func(reason string) bool { busyIdleCalled = append(busyIdleCalled, busyIdentifier) + return true } - localErr := errors.New("execute all err") scheduledTxsExec := &testscommon.ScheduledTxsExecutionStub{ ExecuteAllCalled: func(func() time.Duration) error { - return localErr + return expectedError }, } @@ -2246,7 +2705,7 @@ func TestBaseProcessor_ProcessScheduledBlockShouldFail(t *testing.T) { &block.MetaBlock{}, &block.Body{}, haveTime, ) - assert.Equal(t, localErr, err) + assert.Equal(t, expectedError, err) assert.Equal(t, []string{busyIdentifier, idleIdentifier}, busyIdleCalled) }) t.Run("get root hash fail", func(t *testing.T) { @@ -2259,14 +2718,14 @@ func TestBaseProcessor_ProcessScheduledBlockShouldFail(t *testing.T) { mockProcessHandler.SetIdleCalled = func() { busyIdleCalled = append(busyIdleCalled, idleIdentifier) } - mockProcessHandler.SetBusyCalled = func(reason string) { + mockProcessHandler.TrySetBusyCalled = func(reason string) bool { busyIdleCalled = append(busyIdleCalled, busyIdentifier) + return true } - localErr := errors.New("root hash err") accounts := &stateMock.AccountsStub{ RootHashCalled: func() ([]byte, error) { - return nil, localErr + return nil, expectedError }, } arguments.AccountsDB[state.UserAccountsState] = accounts @@ -2277,7 +2736,7 @@ func TestBaseProcessor_ProcessScheduledBlockShouldFail(t *testing.T) { &block.MetaBlock{}, &block.Body{}, haveTime, ) - assert.Equal(t, localErr, err) + assert.Equal(t, expectedError, err) assert.Equal(t, []string{busyIdentifier, idleIdentifier}, busyIdleCalled) }) } @@ -2341,8 +2800,9 @@ func TestBaseProcessor_ProcessScheduledBlockShouldWork(t *testing.T) { mockProcessHandler.SetIdleCalled = func() { busyIdleCalled = append(busyIdleCalled, idleIdentifier) } - mockProcessHandler.SetBusyCalled = func(reason string) { + mockProcessHandler.TrySetBusyCalled = func(reason string) bool { busyIdleCalled = append(busyIdleCalled, busyIdentifier) + return true } arguments.AccountsDB[state.UserAccountsState] = accounts @@ -2598,7 +3058,7 @@ func TestBaseProcessor_getFinalMiniBlocks(t *testing.T) { arguments := CreateMockArguments(createComponentHolderMocks()) bp, _ := blproc.NewShardProcessor(arguments) - body, err := bp.GetFinalMiniBlocks(&block.MetaBlock{}, &block.Body{}) + body, _, err := bp.GetFinalMiniBlocks([]byte("hash"), &block.MetaBlock{}, &block.Body{}) assert.Nil(t, err) assert.Equal(t, &block.Body{}, body) }) @@ -2611,7 +3071,7 @@ func TestBaseProcessor_getFinalMiniBlocks(t *testing.T) { arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) bp, _ := blproc.NewShardProcessor(arguments) - body, err := bp.GetFinalMiniBlocks(&block.MetaBlock{}, &block.Body{}) + body, _, err := bp.GetFinalMiniBlocks([]byte("hash"), &block.MetaBlock{}, &block.Body{}) assert.Nil(t, err) assert.Equal(t, &block.Body{}, body) }) @@ -2654,7 +3114,7 @@ func TestBaseProcessor_getFinalMiniBlocks(t *testing.T) { expectedBody := &block.Body{MiniBlocks: block.MiniBlockSlice{mb2}} - retBody, err := bp.GetFinalMiniBlocks(metaBlock, body) + retBody, _, err := bp.GetFinalMiniBlocks([]byte("hash"), metaBlock, body) assert.Nil(t, err) assert.Equal(t, expectedBody, retBody) }) @@ -3061,6 +3521,10 @@ func TestBaseProcessor_getPruningHandler(t *testing.T) { bp.SetLastRestartNonce(1) ph = bp.GetPruningHandler(14) assert.True(t, ph.IsPruningEnabled()) + + bp.SetClosingNodeStarted(true) + ph = bp.GetPruningHandler(14) + assert.False(t, ph.IsPruningEnabled()) } func TestBaseProcessor_getPruningHandlerSetsDefaulPruningDelay(t *testing.T) { @@ -3216,110 +3680,6 @@ func TestBaseProcessor_CheckSentSignaturesAtCommitTime(t *testing.T) { }) } -func TestBaseProcessor_FilterHeadersWithoutProofs(t *testing.T) { - t.Parallel() - - headersForCurrentBlock := map[string]data.HeaderHandler{ - "hash0": &testscommon.HeaderHandlerStub{ - EpochField: 12, - GetNonceCalled: func() uint64 { - return 1 - }, - GetShardIDCalled: func() uint32 { - return 0 - }, - }, - "hash1": &testscommon.HeaderHandlerStub{ - EpochField: 12, - GetNonceCalled: func() uint64 { - return 1 - }, - GetShardIDCalled: func() uint32 { - return 1 - }, - }, - "hash2": &testscommon.HeaderHandlerStub{ - EpochField: 12, // no proof for this one, should be marked for deletion - GetNonceCalled: func() uint64 { - return 2 - }, - GetShardIDCalled: func() uint32 { - return 0 - }, - }, - "hash3": &testscommon.HeaderHandlerStub{ - EpochField: 1, // flag not active, for coverage only - GetNonceCalled: func() uint64 { - return 2 - }, - GetShardIDCalled: func() uint32 { - return 1 - }, - }, - } - coreComp, dataComp, bootstrapComp, statusComp := createComponentHolderMocks() - bootstrapComp.Coordinator = &mock.ShardCoordinatorStub{ - NumberOfShardsCalled: func() uint32 { - return 2 - }, - } - coreComp.EnableEpochsHandlerField = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ - IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { - return epoch == 12 - }, - } - dataPool := initDataPool([]byte("")) - dataPool.ProofsCalled = func() dataRetriever.ProofsPool { - return &dataRetrieverMock.ProofsPoolMock{ - HasProofCalled: func(shardID uint32, headerHash []byte) bool { - return string(headerHash) != "hash2" - }, - } - } - dataComp.DataPool = dataPool - arguments := CreateMockArguments(coreComp, dataComp, bootstrapComp, statusComp) - bp, _ := blproc.NewShardProcessor(arguments) - - for hash, header := range headersForCurrentBlock { - bp.SetHdrForCurrentBlock([]byte(hash), header, true) - } - - // this call should fail because header with nonce 2 from shard 0 (hash2) does not have proof - // and there is no other header with the same nonce and proof - headersWithProofs, err := bp.FilterHeadersWithoutProofs() - require.True(t, errors.Is(err, process.ErrMissingHeaderProof)) - require.Nil(t, headersWithProofs) - - // add one more header with same nonce as hash2, but this one has proof - bp.SetHdrForCurrentBlock( - []byte("hash4"), - &testscommon.HeaderHandlerStub{ - EpochField: 12, // same nonce as above, but this one has proof - GetNonceCalled: func() uint64 { - return 2 - }, - GetShardIDCalled: func() uint32 { - return 0 - }, - }, - true, - ) - - // this call should succeed, as for nonce 2 in shard 0 we have 2 headers, hash2 and hash4, but hash4 has proof - headersWithProofs, err = bp.FilterHeadersWithoutProofs() - require.NoError(t, err) - require.Equal(t, 4, len(headersWithProofs)) - - returnedHashes := make([]string, 0, len(headersWithProofs)) - for hash := range headersWithProofs { - returnedHashes = append(returnedHashes, hash) - } - slices.Sort(returnedHashes) - - expectedSortedHashes := []string{"hash0", "hash1", "hash3", "hash4"} - require.Equal(t, expectedSortedHashes, returnedHashes) -} - func TestBaseProcessor_DisplayHeader(t *testing.T) { t.Parallel() @@ -3358,6 +3718,52 @@ func TestBaseProcessor_DisplayHeader(t *testing.T) { IsStartOfEpoch: false, } + lines := blproc.DisplayHeader(header, proof) + require.Equal(t, 22, len(lines)) + }) + t.Run("shard header V3 with proof info", func(t *testing.T) { + t.Parallel() + + header := &block.HeaderV3{ + ChainID: []byte("1"), + Epoch: 2, + Round: 3, + TimestampMs: 4, + Nonce: 5, + PrevHash: []byte("prevHash"), + PrevRandSeed: []byte("prevRandSeed"), + RandSeed: []byte("randSeed"), + LeaderSignature: []byte("leaderSig"), + ReceiptsHash: []byte("receiptsHash"), + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("lastExecResult"), + }, + }, + ExecutionResults: []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("execResult0"), + }, + }, + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("execResult1"), + }, + }, + }, + } + proof := &block.HeaderProof{ + PubKeysBitmap: []byte("bitmap"), + AggregatedSignature: []byte("sig"), + HeaderHash: []byte("prevHash"), + HeaderEpoch: 2, + HeaderNonce: 4, + HeaderShardId: 0, + HeaderRound: 2, + IsStartOfEpoch: false, + } + lines := blproc.DisplayHeader(header, proof) require.Equal(t, 23, len(lines)) }) @@ -3390,6 +3796,2919 @@ func TestBaseProcessor_DisplayHeader(t *testing.T) { } lines := blproc.DisplayHeader(header, proof) - require.Equal(t, 23, len(lines)) + require.Equal(t, 22, len(lines)) + }) +} + +func TestBaseProcessor_computeOwnShardStuckIfNeeded(t *testing.T) { + t.Parallel() + + t.Run("header is not V3, should exit early without error", func(t *testing.T) { + baseProcessor := blproc.CreateBaseProcessorWithMockedTracker(&mock.BlockTrackerMock{ + ComputeOwnShardStuckCalled: func(_ data.BaseExecutionResultHandler, _ uint64) { + require.Fail(t, "should not be called") + }, + }) + header := &block.Header{} + + err := baseProcessor.ComputeOwnShardStuckIfNeeded(header) + assert.Nil(t, err) + }) + + t.Run("header is V3 but last executed results is nil", func(t *testing.T) { + header := &block.HeaderV3{ + LastExecutionResult: nil, + } + baseProcessor := blproc.CreateBaseProcessorWithMockedTracker(&mock.BlockTrackerMock{ + ComputeOwnShardStuckCalled: func(_ data.BaseExecutionResultHandler, _ uint64) { + require.Fail(t, "should not be called") + }, + }) + + err := baseProcessor.ComputeOwnShardStuckIfNeeded(header) + assert.Equal(t, process.ErrNilLastExecutionResultHandler, err) + }) + + t.Run("header is metablock v3, last executed result is nil", func(t *testing.T) { + header := &block.MetaBlockV3{ + LastExecutionResult: nil, + } + + baseProcessor := blproc.CreateBaseProcessorWithMockedTracker(&mock.BlockTrackerMock{ + ComputeOwnShardStuckCalled: func(_ data.BaseExecutionResultHandler, _ uint64) { + require.Fail(t, "should not be called") + }, + }) + + err := baseProcessor.ComputeOwnShardStuckIfNeeded(header) + assert.Equal(t, process.ErrNilLastExecutionResultHandler, err) + }) + + t.Run("valid shard header v3 with valid last execution result", func(t *testing.T) { + baseExecutionResults := &block.BaseExecutionResult{ + HeaderHash: []byte("hash"), + HeaderNonce: 100, + HeaderRound: 200, + RootHash: []byte("rootHash"), + } + header := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + NotarizedInRound: 201, + ExecutionResult: baseExecutionResults, + }, + } + called := false + baseProcessor := blproc.CreateBaseProcessorWithMockedTracker(&mock.BlockTrackerMock{ + ComputeOwnShardStuckCalled: func(_ data.BaseExecutionResultHandler, _ uint64) { + called = true + }, + }) + + err := baseProcessor.ComputeOwnShardStuckIfNeeded(header) + assert.Nil(t, err) + require.True(t, called) + }) +} + +func TestBaseProcessor_updateGasConsumptionLimitsIfNeeded(t *testing.T) { + t.Parallel() + + isOwnShardStuck := false + bp := blproc.CreateBaseProcessorWithMockedTracker(&mock.BlockTrackerMock{ + IsOwnShardStuckCalled: func() bool { + return isOwnShardStuck + }, + }) + wasResetIncomingLimitCalled := false + wasResetOutgoingLimitCalled := false + wasZeroIncomingLimitCalled := false + wasZeroOutgoingLimitCalled := false + bp.SetGasComputation(&testscommon.GasComputationMock{ + ResetIncomingLimitCalled: func() { + wasResetIncomingLimitCalled = true + }, + ResetOutgoingLimitCalled: func() { + wasResetOutgoingLimitCalled = true + }, + ZeroIncomingLimitCalled: func() { + wasZeroIncomingLimitCalled = true + }, + ZeroOutgoingLimitCalled: func() { + wasZeroOutgoingLimitCalled = true + }, + }) + + require.False(t, wasResetIncomingLimitCalled) + require.False(t, wasResetOutgoingLimitCalled) + require.False(t, wasZeroIncomingLimitCalled) + require.False(t, wasZeroOutgoingLimitCalled) + + bp.UpdateGasConsumptionLimitsIfNeeded() + require.True(t, wasResetIncomingLimitCalled) + require.True(t, wasResetOutgoingLimitCalled) + require.False(t, wasZeroIncomingLimitCalled) + require.False(t, wasZeroOutgoingLimitCalled) + + // set the Reset.* variables to false again + wasResetIncomingLimitCalled = false + wasResetOutgoingLimitCalled = false + + // set the shard is stuck to true + isOwnShardStuck = true + + bp.UpdateGasConsumptionLimitsIfNeeded() + require.False(t, wasResetIncomingLimitCalled) + require.False(t, wasResetOutgoingLimitCalled) + require.True(t, wasZeroIncomingLimitCalled) + require.True(t, wasZeroOutgoingLimitCalled) +} + +func TestCheckHeaderBodyCorrelationProposal(t *testing.T) { + t.Parallel() + + t.Run("different number of miniblock headers and miniblocks should error ", func(t *testing.T) { + t.Parallel() + + arguments := CreateMockArguments(createComponentHolderMocks()) + bp, _ := blproc.NewShardProcessor(arguments) + + err := bp.CheckHeaderBodyCorrelationProposal( + nil, + &block.Body{MiniBlocks: []*block.MiniBlock{ + {SenderShardID: 0}, + }}, + ) + require.Equal(t, process.ErrHeaderBodyMismatch, err) + }) + + t.Run("nil miniblock should error", func(t *testing.T) { + arguments := CreateMockArguments(createComponentHolderMocks()) + bp, _ := blproc.NewShardProcessor(arguments) + + mbHeaders := make([]data.MiniBlockHeaderHandler, 1) + mbHeaders[0] = &block.MiniBlockHeader{} + + err := bp.CheckHeaderBodyCorrelationProposal( + mbHeaders, + &block.Body{MiniBlocks: []*block.MiniBlock{ + nil, + }}, + ) + require.Equal(t, process.ErrNilMiniBlock, err) + }) + + t.Run("nil miniblock header should error", func(t *testing.T) { + arguments := CreateMockArguments(createComponentHolderMocks()) + bp, _ := blproc.NewShardProcessor(arguments) + + mbHeaders := make([]data.MiniBlockHeaderHandler, 1) + mbHeaders[0] = nil + + err := bp.CheckHeaderBodyCorrelationProposal( + mbHeaders, + &block.Body{MiniBlocks: []*block.MiniBlock{ + {}, + }}, + ) + require.Equal(t, process.ErrNilMiniBlockHeader, err) + }) + t.Run("different hash mb header and miniblock", func(t *testing.T) { + arguments := CreateMockArguments(createComponentHolderMocks()) + bp, _ := blproc.NewShardProcessor(arguments) + + mbHeaders := make([]data.MiniBlockHeaderHandler, 1) + mbHeaders[0] = &block.MiniBlockHeader{ + Hash: []byte("hash"), + } + + err := bp.CheckHeaderBodyCorrelationProposal( + mbHeaders, + &block.Body{MiniBlocks: []*block.MiniBlock{ + {}, + }}, + ) + require.Equal(t, process.ErrHeaderBodyMismatch, err) + }) + + t.Run("different tx count mb header and miniblock", func(t *testing.T) { + arguments := CreateMockArguments(createComponentHolderMocks()) + bp, _ := blproc.NewShardProcessor(arguments) + + miniBlock := &block.MiniBlock{} + + mbHash, _ := core.CalculateHash(arguments.CoreComponents.InternalMarshalizer(), arguments.CoreComponents.Hasher(), miniBlock) + + mbHeaders := make([]data.MiniBlockHeaderHandler, 1) + mbHeaders[0] = &block.MiniBlockHeader{ + Hash: mbHash, + TxCount: 1, + } + + err := bp.CheckHeaderBodyCorrelationProposal( + mbHeaders, + &block.Body{MiniBlocks: []*block.MiniBlock{ + miniBlock, + }}, + ) + require.Equal(t, process.ErrHeaderBodyMismatch, err) + }) + + t.Run("different receiver shard mb header and mini block", func(t *testing.T) { + arguments := CreateMockArguments(createComponentHolderMocks()) + bp, _ := blproc.NewShardProcessor(arguments) + + miniBlock := &block.MiniBlock{ + ReceiverShardID: 2, + } + + mbHash, _ := core.CalculateHash(arguments.CoreComponents.InternalMarshalizer(), arguments.CoreComponents.Hasher(), miniBlock) + + mbHeaders := make([]data.MiniBlockHeaderHandler, 1) + mbHeaders[0] = &block.MiniBlockHeader{ + Hash: mbHash, + ReceiverShardID: 1, + } + + err := bp.CheckHeaderBodyCorrelationProposal( + mbHeaders, + &block.Body{MiniBlocks: []*block.MiniBlock{ + miniBlock, + }}, + ) + require.ErrorIs(t, err, process.ErrHeaderBodyMismatch) + }) + + t.Run("different sender shard mb header and miniblock", func(t *testing.T) { + arguments := CreateMockArguments(createComponentHolderMocks()) + bp, _ := blproc.NewShardProcessor(arguments) + + miniBlock := &block.MiniBlock{ + SenderShardID: 0, + ReceiverShardID: 2, + } + + mbHash, _ := core.CalculateHash(arguments.CoreComponents.InternalMarshalizer(), arguments.CoreComponents.Hasher(), miniBlock) + + mbHeaders := make([]data.MiniBlockHeaderHandler, 1) + mbHeaders[0] = &block.MiniBlockHeader{ + Hash: mbHash, + SenderShardID: 2, + ReceiverShardID: 2, + } + + err := bp.CheckHeaderBodyCorrelationProposal( + mbHeaders, + &block.Body{MiniBlocks: []*block.MiniBlock{ + miniBlock, + }}, + ) + require.ErrorIs(t, err, process.ErrHeaderBodyMismatch) + }) + + t.Run("wrong construction state should error", func(t *testing.T) { + arguments := CreateMockArguments(createComponentHolderMocks()) + bp, _ := blproc.NewShardProcessor(arguments) + + miniBlock := &block.MiniBlock{ + SenderShardID: 0, + ReceiverShardID: 2, + } + + mbHash, _ := core.CalculateHash(arguments.CoreComponents.InternalMarshalizer(), arguments.CoreComponents.Hasher(), miniBlock) + + mbHeaders := make([]data.MiniBlockHeaderHandler, 1) + mbHeaders[0] = &block.MiniBlockHeader{ + Hash: mbHash, + SenderShardID: 0, + ReceiverShardID: 2, + } + _ = mbHeaders[0].SetConstructionState(int32(block.PartialExecuted)) + + err := bp.CheckHeaderBodyCorrelationProposal( + mbHeaders, + &block.Body{MiniBlocks: []*block.MiniBlock{ + miniBlock, + }}, + ) + require.Equal(t, process.ErrWrongMiniBlockConstructionState, err) + }) + + t.Run("wrong processing type should error", func(t *testing.T) { + arguments := CreateMockArguments(createComponentHolderMocks()) + bp, _ := blproc.NewShardProcessor(arguments) + + miniBlock := &block.MiniBlock{ + SenderShardID: 0, + ReceiverShardID: 2, + } + + mbHash, _ := core.CalculateHash(arguments.CoreComponents.InternalMarshalizer(), arguments.CoreComponents.Hasher(), miniBlock) + + mbHeaders := make([]data.MiniBlockHeaderHandler, 1) + mbHeaders[0] = &block.MiniBlockHeader{ + Hash: mbHash, + SenderShardID: 0, + ReceiverShardID: 2, + } + _ = mbHeaders[0].SetConstructionState(int32(block.Proposed)) + _ = mbHeaders[0].SetProcessingType(int32(block.Scheduled)) + + err := bp.CheckHeaderBodyCorrelationProposal( + mbHeaders, + &block.Body{MiniBlocks: []*block.MiniBlock{ + miniBlock, + }}, + ) + require.Equal(t, process.ErrWrongMiniBlockProcessingType, err) + }) + + t.Run("should work", func(t *testing.T) { + arguments := CreateMockArguments(createComponentHolderMocks()) + bp, _ := blproc.NewShardProcessor(arguments) + + miniBlock := &block.MiniBlock{ + SenderShardID: 0, + ReceiverShardID: 2, + } + + mbHash, _ := core.CalculateHash(arguments.CoreComponents.InternalMarshalizer(), arguments.CoreComponents.Hasher(), miniBlock) + + mbHeaders := make([]data.MiniBlockHeaderHandler, 1) + mbHeaders[0] = &block.MiniBlockHeader{ + Hash: mbHash, + SenderShardID: 0, + ReceiverShardID: 2, + } + _ = mbHeaders[0].SetConstructionState(int32(block.Proposed)) + _ = mbHeaders[0].SetProcessingType(int32(block.Normal)) + + err := bp.CheckHeaderBodyCorrelationProposal( + mbHeaders, + &block.Body{MiniBlocks: []*block.MiniBlock{ + miniBlock, + }}, + ) + require.NoError(t, err) + }) +} + +func TestBaseProcessor_GetFinalMiniBlocksFromExecutionResult(t *testing.T) { + t.Parallel() + + t.Run("no execution results, should return empty body", func(t *testing.T) { + t.Parallel() + + arguments := CreateMockArguments(createComponentHolderMocks()) + bp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{} + + body, _, err := bp.GetFinalMiniBlocksFromExecutionResults(header) + require.Nil(t, err) + require.Equal(t, &block.Body{}, body) + }) + + t.Run("should fail if miniblock not found in cache", func(t *testing.T) { + t.Parallel() + + executedMBs := &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + return nil, false + }, + } + dataPool := initDataPool() + dataPool.ExecutedMiniBlocksCalled = func() storage.Cacher { + return executedMBs + } + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataComponents.DataPool = dataPool + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + bp, _ := blproc.NewShardProcessor(arguments) + + executionResults := []*block.ExecutionResult{ + { + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("mbHash1"), + ReceiverShardID: 1, + SenderShardID: 0, + }, + { + Hash: []byte("mbHash2"), + ReceiverShardID: 1, + SenderShardID: 0, + }, + }, + }, + } + header := &block.HeaderV3{ + ExecutionResults: executionResults, + } + + body, _, err := bp.GetFinalMiniBlocksFromExecutionResults(header) + require.Equal(t, process.ErrMissingMiniBlock, err) + require.Nil(t, body) + }) + + t.Run("should fail if miniblock not marshalled properly", func(t *testing.T) { + t.Parallel() + + executedMBs := &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + return []byte("invalid miniblock"), true + }, + } + dataPool := initDataPool() + dataPool.ExecutedMiniBlocksCalled = func() storage.Cacher { + return executedMBs + } + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataComponents.DataPool = dataPool + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + bp, _ := blproc.NewShardProcessor(arguments) + + executionResults := []*block.ExecutionResult{ + { + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("mbHash1"), + ReceiverShardID: 1, + SenderShardID: 0, + }, + }, + }, + } + header := &block.HeaderV3{ + ExecutionResults: executionResults, + } + + body, _, err := bp.GetFinalMiniBlocksFromExecutionResults(header) + require.Error(t, err) // unmarshall err + require.Nil(t, body) + }) + + t.Run("should work for shard header", func(t *testing.T) { + t.Parallel() + + marshalizer := &mock.MarshalizerMock{ + Fail: false, + } + + mb1 := &block.MiniBlock{ + TxHashes: [][]byte{[]byte("txHash1")}, + ReceiverShardID: 1, + SenderShardID: 2, + } + + executedMBs := &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + marshalledMb, _ := marshalizer.Marshal(mb1) + return marshalledMb, true + }, + } + dataPool := initDataPool() + dataPool.ExecutedMiniBlocksCalled = func() storage.Cacher { + return executedMBs + } + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataComponents.DataPool = dataPool + coreComponents.IntMarsh = marshalizer + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + bp, _ := blproc.NewShardProcessor(arguments) + + executionResults := []*block.ExecutionResult{ + { + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("mbHash1"), + ReceiverShardID: 1, + SenderShardID: 0, + }, + }, + }, + } + header := &block.HeaderV3{ + ExecutionResults: executionResults, + } + + body, _, err := bp.GetFinalMiniBlocksFromExecutionResults(header) + require.Nil(t, err) + require.Equal(t, &block.Body{ + MiniBlocks: []*block.MiniBlock{mb1}, + }, body) + }) + + t.Run("should work for meta block", func(t *testing.T) { + t.Parallel() + + marshalizer := &mock.MarshalizerMock{ + Fail: false, + } + + mb1 := &block.MiniBlock{ + TxHashes: [][]byte{[]byte("txHash1")}, + ReceiverShardID: 1, + SenderShardID: 2, + } + + executedMBs := &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + marshalledMb, _ := marshalizer.Marshal(mb1) + return marshalledMb, true + }, + } + dataPool := initDataPool() + dataPool.ExecutedMiniBlocksCalled = func() storage.Cacher { + return executedMBs + } + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataComponents.DataPool = dataPool + coreComponents.IntMarsh = marshalizer + + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + mp, _ := blproc.NewMetaProcessor(arguments) + + executionResults := []*block.MetaExecutionResult{ + { + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("mbHash1"), + ReceiverShardID: 1, + SenderShardID: 0, + }, + }, + }, + } + header := &block.MetaBlockV3{ + ExecutionResults: executionResults, + } + + body, _, err := mp.GetFinalMiniBlocksFromExecutionResults(header) + require.Nil(t, err) + require.Equal(t, &block.Body{ + MiniBlocks: []*block.MiniBlock{mb1}, + }, body) + }) +} + +func TestBaseProcessor_GetFinalBlockNonce(t *testing.T) { + t.Parallel() + + t.Run("should return fork detector final nonce, if current block not header v3", func(t *testing.T) { + t.Parallel() + + finalHash := []byte("finalHash") + finalNonce := uint64(10) + + header := &block.Header{ + Nonce: 11, + } + + dataPool := initDataPool() + dataPool.HeadersCalled = func() dataRetriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, expectedError + }, + } + } + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataComponents.DataPool = dataPool + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ForkDetector = &mock.ForkDetectorMock{ + GetHighestFinalBlockNonceCalled: func() uint64 { + return finalNonce + }, + GetHighestFinalBlockHashCalled: func() []byte { + return finalHash + }, + } + + bp, _ := blproc.NewShardProcessor(arguments) + + retNoncesToFinal := bp.GetFinalBlockNonce(header) + require.Equal(t, finalNonce, retNoncesToFinal) + }) + + t.Run("should return fork detector final nonce, if failed to get header from pool", func(t *testing.T) { + t.Parallel() + + finalHash := []byte("finalHash") + finalNonce := uint64(10) + + header := &block.HeaderV3{ + Nonce: 11, + } + + dataPool := initDataPool() + dataPool.HeadersCalled = func() dataRetriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, expectedError + }, + } + } + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataComponents.DataPool = dataPool + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ForkDetector = &mock.ForkDetectorMock{ + GetHighestFinalBlockNonceCalled: func() uint64 { + return finalNonce + }, + GetHighestFinalBlockHashCalled: func() []byte { + return finalHash + }, + } + + bp, _ := blproc.NewShardProcessor(arguments) + + retNoncesToFinal := bp.GetFinalBlockNonce(header) + require.Equal(t, finalNonce, retNoncesToFinal) + }) + + t.Run("should return last final nonce, if final block not header v3", func(t *testing.T) { + t.Parallel() + + finalHash := []byte("finalHash") + + header := &block.HeaderV3{ + Nonce: 11, + } + + finalNonce := uint64(10) + + finalHeader := &block.Header{ + Nonce: finalNonce, + } + + dataPool := initDataPool() + dataPool.HeadersCalled = func() dataRetriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, finalHash) { + return finalHeader, nil + } + + return nil, expectedError + }, + } + } + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataComponents.DataPool = dataPool + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ForkDetector = &mock.ForkDetectorMock{ + GetHighestFinalBlockNonceCalled: func() uint64 { + return finalNonce + }, + GetHighestFinalBlockHashCalled: func() []byte { + return finalHash + }, + } + + bp, _ := blproc.NewShardProcessor(arguments) + + retNoncesToFinal := bp.GetFinalBlockNonce(header) + require.Equal(t, finalNonce, retNoncesToFinal) + }) + + t.Run("should return last executed final nonce, if header v3", func(t *testing.T) { + t.Parallel() + finalHash := []byte("finalHash") + + header := &block.HeaderV3{ + Nonce: 11, + } + + finalNonce := uint64(10) + finalExecResNonce := uint64(9) + + finalHeader := &block.HeaderV3{ + Nonce: finalNonce, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: finalExecResNonce, + }, + }, + } + + dataPool := initDataPool() + dataPool.HeadersCalled = func() dataRetriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, finalHash) { + return finalHeader, nil + } + + return nil, expectedError + }, + } + } + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataComponents.DataPool = dataPool + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ForkDetector = &mock.ForkDetectorMock{ + GetHighestFinalBlockNonceCalled: func() uint64 { + return finalNonce + }, + GetHighestFinalBlockHashCalled: func() []byte { + return finalHash + }, + } + + bp, _ := blproc.NewShardProcessor(arguments) + + retNoncesToFinal := bp.GetFinalBlockNonce(header) + require.Equal(t, finalExecResNonce, retNoncesToFinal) + }) +} + +func TestBaseProcessor_RecreateTrieIfNeeded(t *testing.T) { + t.Parallel() + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockRootHashCalled: func() []byte { + return []byte("rootHash") + }, + } + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + accounts := &stateMock.AccountsStub{ + RecreateTrieIfNeededCalled: func(options common.RootHashHolder) error { + if bytes.Equal(options.GetRootHash(), []byte("rootHash")) { + return nil + } + return expectedErr + }, + } + arguments.AccountsProposal = accounts + bp, err := blproc.NewShardProcessor(arguments) + + require.NoError(t, err) + + err = bp.RecreateTrieIfNeeded() + require.NoError(t, err) + }) + + t.Run("should return error from accounts proposal", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + accounts := &stateMock.AccountsStub{ + RecreateTrieIfNeededCalled: func(options common.RootHashHolder) error { + return expectedErr + }, + } + arguments.AccountsProposal = accounts + bp, err := blproc.NewShardProcessor(arguments) + + require.NoError(t, err) + + err = bp.RecreateTrieIfNeeded() + require.Equal(t, expectedErr, err) + }) +} + +func TestBaseProcessor_OnExecutedBlock(t *testing.T) { + t.Parallel() + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetGenesisHeaderCalled: func() data.HeaderHandler { + return &block.Header{ + RootHash: []byte("genesisRootHash"), + } + }, + } + + dataPool := initDataPool() + dataPool.TransactionsCalled = func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataCacheNotifierMock{ + OnExecutedBlockCalled: func(blockHeader data.HeaderHandler, rootHash []byte) error { + if bytes.Equal(rootHash, []byte("rootHash")) || bytes.Equal(rootHash, []byte("genesisRootHash")) { + return nil + } + return expectedErr + }, + } + } + dataComponents.DataPool = dataPool + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + err = bp.OnExecutedBlock(&block.Header{}, []byte("rootHash")) + require.NoError(t, err) + }) + + t.Run("should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetGenesisHeaderCalled: func() data.HeaderHandler { + return &block.Header{ + RootHash: []byte("genesisRootHash"), + } + }, + } + + dataPool := initDataPool() + dataPool.TransactionsCalled = func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataCacheNotifierMock{ + OnExecutedBlockCalled: func(blockHeader data.HeaderHandler, rootHash []byte) error { + if bytes.Equal(rootHash, []byte("genesisRootHash")) { + return nil + } + return expectedErr + }, + } + } + dataComponents.DataPool = dataPool + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + err = bp.OnExecutedBlock(nil, []byte("hash")) + require.Equal(t, expectedErr, err) + }) +} + +func TestBaseProcessor_RequestProof(t *testing.T) { + t.Parallel() + + t.Run("should not request if flag not enabled", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + coreComponents.EnableEpochsHandlerField = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag != common.AndromedaFlag + }, + } + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + requestCalled := false + arguments.RequestHandler = &testscommon.RequestHandlerStub{ + RequestEquivalentProofByNonceCalled: func(headerShard uint32, headerNonce uint64) { + requestCalled = true + }, + } + + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + bp.RequestProofIfNeeded(10, 1, 2) + + require.False(t, requestCalled) + }) + + t.Run("should not request if proof already in pool", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataPool := initDataPool() + dataPool.ProofsCalled = func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{ + GetProofByNonceCalled: func(headerNonce uint64, shardID uint32) (data.HeaderProofHandler, error) { + return &block.HeaderProof{}, nil + }, + } + } + dataComponents.DataPool = dataPool + + coreComponents.EnableEpochsHandlerField = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.AndromedaFlag + }, + } + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + requestCalled := false + arguments.RequestHandler = &testscommon.RequestHandlerStub{ + RequestEquivalentProofByNonceCalled: func(headerShard uint32, headerNonce uint64) { + requestCalled = true + }, + } + + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + bp.RequestProofIfNeeded(10, 1, 2) + + require.False(t, requestCalled) + }) + + t.Run("should request if flag enabled and proof not already in pool", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + coreComponents.EnableEpochsHandlerField = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.AndromedaFlag + }, + } + + dataPool := initDataPool() + dataPool.ProofsCalled = func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{ + GetProofByNonceCalled: func(headerNonce uint64, shardID uint32) (data.HeaderProofHandler, error) { + return nil, errors.New("fetch err") + }, + } + } + dataComponents.DataPool = dataPool + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + requestCalled := false + arguments.RequestHandler = &testscommon.RequestHandlerStub{ + RequestEquivalentProofByNonceCalled: func(headerShard uint32, headerNonce uint64) { + requestCalled = true + }, + } + + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + bp.RequestProofIfNeeded(10, 1, 2) + + require.True(t, requestCalled) + }) +} + +func TestBaseProcessor_RequestHeadersFromHeaderIfNeeded(t *testing.T) { + t.Parallel() + + t.Run("header not already in pool, should request", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + coreComponents.RoundField = &testscommon.RoundHandlerMock{ + IndexCalled: func() int64 { + return 20 + }, + } + coreComponents.ProcessConfigsHandlerField = &testscommon.ProcessConfigsHandlerStub{ + GetMaxRoundsWithoutNewBlockReceivedByRoundCalled: func(round uint64) uint32 { + return 5 + }, + } + + headersPool := &mock.HeadersCacherStub{ + GetHeaderByNonceAndShardIdCalled: func(hdrNonce uint64, shardId uint32) ([]data.HeaderHandler, [][]byte, error) { + return make([]data.HeaderHandler, 0), [][]byte{}, errors.New("some err") + }, + } + dataPool := initDataPool() + dataPool.HeadersCalled = func() dataRetriever.HeadersPool { + return headersPool + } + dataComponents.DataPool = dataPool + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + numCalls := 0 + arguments.RequestHandler = &testscommon.RequestHandlerStub{ + RequestShardHeaderByNonceCalled: func(shardID uint32, nonce uint64) { + numCalls++ + }, + } + + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + header := &block.HeaderV3{ + Round: 10, + ShardID: 2, + } + + bp.RequestHeadersFromHeaderIfNeeded(header) + + require.Equal(t, 11, numCalls) // starting from next header + 10 given by constant + }) + + t.Run("header already in pool, should not request", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + coreComponents.RoundField = &testscommon.RoundHandlerMock{ + IndexCalled: func() int64 { + return 20 + }, + } + coreComponents.ProcessConfigsHandlerField = &testscommon.ProcessConfigsHandlerStub{ + GetMaxRoundsWithoutNewBlockReceivedByRoundCalled: func(round uint64) uint32 { + return 5 + }, + } + + headersPool := &mock.HeadersCacherStub{ + GetHeaderByNonceAndShardIdCalled: func(hdrNonce uint64, shardId uint32) ([]data.HeaderHandler, [][]byte, error) { + return make([]data.HeaderHandler, 0), [][]byte{}, nil + }, + } + dataPool := initDataPool() + dataPool.HeadersCalled = func() dataRetriever.HeadersPool { + return headersPool + } + dataComponents.DataPool = dataPool + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + requestCalled := false + arguments.RequestHandler = &testscommon.RequestHandlerStub{ + RequestShardHeaderByNonceCalled: func(shardID uint32, nonce uint64) { + requestCalled = true + }, + } + + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + header := &block.HeaderV3{ + Round: 10, + ShardID: 2, + } + + bp.RequestHeadersFromHeaderIfNeeded(header) + + require.False(t, requestCalled) + }) +} + +func TestBaseProcessor_extractRootHashForCleanup(t *testing.T) { + t.Parallel() + + t.Run("should return ErrNilLastExecutionResultHandler error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + rootHashHolder, err := bp.ExtractRootHashForCleanup(&block.HeaderV3{}) + require.Nil(t, rootHashHolder) + require.Equal(t, process.ErrNilLastExecutionResultHandler, err) + }) + + t.Run("should work for HeaderV3", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + expectedRootHash := holders.NewDefaultRootHashesHolder([]byte("rootHash")) + + rootHashHolder, err := bp.ExtractRootHashForCleanup(&block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + RootHash: []byte("rootHash"), + }, + }, + }) + require.Nil(t, err) + require.Equal(t, expectedRootHash, rootHashHolder) + }) + + t.Run("should work for other HeaderV2", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + expectedRootHash := holders.NewDefaultRootHashesHolder([]byte("rootHash")) + + rootHashHolder, err := bp.ExtractRootHashForCleanup(&block.HeaderV2{ + ScheduledRootHash: []byte("rootHash"), + }) + require.Nil(t, err) + require.Equal(t, expectedRootHash, rootHashHolder) + }) + + t.Run("should work for other HeaderV1", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + + blkc, _ := blockchain.NewBlockChain(&statusHandlerMock.AppStatusHandlerStub{}) + err := blkc.SetGenesisHeader(&block.Header{Nonce: 0}) + require.Nil(t, err) + + err = blkc.SetCurrentBlockHeaderAndRootHash(&block.Header{}, []byte("rootHash")) + require.Nil(t, err) + + dataComponents.BlockChain = blkc + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + expectedRootHash := holders.NewDefaultRootHashesHolder([]byte("rootHash")) + rootHashHolder, err := bp.ExtractRootHashForCleanup(&block.Header{}) + require.Nil(t, err) + require.Equal(t, expectedRootHash, rootHashHolder) + }) +} + +func TestBaseProcessor_saveProposedTxsToStorage(t *testing.T) { + t.Parallel() + + t.Run("should call tx coordinator if not header/metaBlock v3", func(t *testing.T) { + t.Parallel() + + saveTxsToStorageCalled := 0 + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + SaveTxsToStorageCalled: func(body *block.Body) { + saveTxsToStorageCalled++ + }, + } + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + err = bp.SaveProposedTxsToStorage(&block.HeaderV2{}, &block.Body{}) + require.Nil(t, err) + require.Equal(t, 1, saveTxsToStorageCalled) + + err = bp.SaveProposedTxsToStorage(&block.MetaBlock{}, &block.Body{}) + require.Nil(t, err) + require.Equal(t, 2, saveTxsToStorageCalled) + + err = bp.SaveProposedTxsToStorage(&block.HeaderV3{}, &block.Body{}) + require.Nil(t, err) + require.Equal(t, 2, saveTxsToStorageCalled) + + err = bp.SaveProposedTxsToStorage(&block.MetaBlockV3{}, &block.Body{}) + require.Nil(t, err) + require.Equal(t, 2, saveTxsToStorageCalled) + }) + + t.Run("headerV3 should save txs from cache to storage", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + keys := [][]byte{[]byte("tx1"), []byte("tx2"), []byte("tx3"), []byte("tx4"), []byte("tx5")} + txs := map[string]data.TransactionHandler{ + string(keys[0]): &transaction.Transaction{}, + string(keys[1]): &transaction.Transaction{}, + string(keys[2]): &transaction.Transaction{}, + string(keys[3]): &rewardTx.RewardTx{}, + string(keys[4]): &smartContractResult.SmartContractResult{}, + } + marshalledTxs := make(map[string][]byte) + for k, v := range txs { + txsBytes, err := coreComponents.IntMarsh.Marshal(v) + require.NoError(t, err) + marshalledTxs[k] = txsBytes + } + + dataPools := dataComponents.DataPool + dataPools.Transactions().AddData(keys[0], txs[string(keys[0])], 100, "0") + dataPools.Transactions().AddData(keys[1], txs[string(keys[1])], 100, "0") + dataPools.Transactions().AddData(keys[2], txs[string(keys[2])], 100, "0") + dataPools.RewardTransactions().AddData(keys[3], txs[string(keys[3])], 100, "0") + dataPools.UnsignedTransactions().AddData(keys[4], txs[string(keys[4])], 100, "0") + storer := dataComponents.Storage + + blockBody := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{keys[0], keys[1]}, + Type: block.TxBlock, + }, + { + TxHashes: [][]byte{keys[2]}, + Type: block.InvalidBlock, + }, + { + TxHashes: [][]byte{keys[3]}, + Type: block.RewardsBlock, + }, + { + TxHashes: [][]byte{keys[4]}, + Type: block.SmartContractResultBlock, + }, + }, + } + header := &block.HeaderV3{} + + err = bp.SaveProposedTxsToStorage(header, blockBody) + require.Nil(t, err) + + val, err := storer.Get(dataRetriever.TransactionUnit, keys[0]) + require.NoError(t, err) + require.Equal(t, marshalledTxs[string(keys[0])], val) + + val, err = storer.Get(dataRetriever.TransactionUnit, keys[1]) + require.NoError(t, err) + require.Equal(t, marshalledTxs[string(keys[1])], val) + + val, err = storer.Get(dataRetriever.TransactionUnit, keys[2]) + require.NoError(t, err) + require.Equal(t, marshalledTxs[string(keys[2])], val) + + val, err = storer.Get(dataRetriever.RewardTransactionUnit, keys[3]) + require.NoError(t, err) + require.Equal(t, marshalledTxs[string(keys[3])], val) + + val, err = storer.Get(dataRetriever.UnsignedTransactionUnit, keys[4]) + require.NoError(t, err) + require.Equal(t, marshalledTxs[string(keys[4])], val) + }) + + t.Run("headerV3 should save peer info from cache to storage", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + keys := [][]byte{[]byte("peer1"), []byte("peer2")} + validatorInfos := map[string]*state.ShardValidatorInfo{ + string(keys[0]): { + PublicKey: []byte("pubKey1"), + ShardId: 0, + }, + string(keys[1]): { + PublicKey: []byte("pubKey2"), + ShardId: 1, + }, + } + marshalledValidatorInfos := make(map[string][]byte) + for k, v := range validatorInfos { + validatorInfoBytes, err := coreComponents.IntMarsh.Marshal(v) + require.NoError(t, err) + marshalledValidatorInfos[k] = validatorInfoBytes + } + + dataPools := dataComponents.DataPool + dataPools.ValidatorsInfo().AddData(keys[0], validatorInfos[string(keys[0])], 100, "0") + dataPools.ValidatorsInfo().AddData(keys[1], validatorInfos[string(keys[1])], 100, "0") + storer := dataComponents.Storage + + blockBody := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{keys[0], keys[1]}, + Type: block.PeerBlock, + }, + }, + } + header := &block.HeaderV3{} + + err = bp.SaveProposedTxsToStorage(header, blockBody) + require.Nil(t, err) + + val, err := storer.Get(dataRetriever.UnsignedTransactionUnit, keys[0]) + require.NoError(t, err) + require.Equal(t, marshalledValidatorInfos[string(keys[0])], val) + + val, err = storer.Get(dataRetriever.UnsignedTransactionUnit, keys[1]) + require.NoError(t, err) + require.Equal(t, marshalledValidatorInfos[string(keys[1])], val) + }) + + t.Run("headerV3 should return error if peer info not found in cache", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + key := []byte("peer1") + + blockBody := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{key}, + Type: block.PeerBlock, + }, + }, + } + header := &block.HeaderV3{} + + err = bp.SaveProposedTxsToStorage(header, blockBody) + require.Equal(t, dataRetriever.ErrValidatorInfoNotFound, err) + }) + + t.Run("headerV3 should return error if invalid type in cache for peer block", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + key := []byte("peer1") + // Add wrong type to the validator info pool + dataPools := dataComponents.DataPool + dataPools.ValidatorsInfo().AddData(key, &transaction.Transaction{}, 100, "0") + + blockBody := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{key}, + Type: block.PeerBlock, + }, + }, + } + header := &block.HeaderV3{} + + err = bp.SaveProposedTxsToStorage(header, blockBody) + require.ErrorIs(t, err, process.ErrInvalidTxInPool) + }) + + t.Run("headerV3 should return error if marshal fails for peer block", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + coreComponents.IntMarsh = &marshallerMock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + // Fail marshal only for ShardValidatorInfo + if _, ok := obj.(*state.ShardValidatorInfo); ok { + return nil, expectedErr + } + return []byte("marshalled"), nil + }, + } + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + key := []byte("peer1") + validatorInfo := &state.ShardValidatorInfo{ + PublicKey: []byte("pubKey1"), + ShardId: 0, + } + + dataPools := dataComponents.DataPool + dataPools.ValidatorsInfo().AddData(key, validatorInfo, 100, "0") + + blockBody := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{key}, + Type: block.PeerBlock, + }, + }, + } + header := &block.HeaderV3{} + + err = bp.SaveProposedTxsToStorage(header, blockBody) + require.Equal(t, expectedErr, err) + }) + + t.Run("headerV3 should return error if storer.Put fails for peer block", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + + // Create a storer that fails on Put + dataComponents.Storage = &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{ + PutCalled: func(key, data []byte) error { + return expectedErr + }, + }, nil + }, + } + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + key := []byte("peer1") + validatorInfo := &state.ShardValidatorInfo{ + PublicKey: []byte("pubKey1"), + ShardId: 0, + } + + dataPools := dataComponents.DataPool + dataPools.ValidatorsInfo().AddData(key, validatorInfo, 100, "0") + + blockBody := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{key}, + Type: block.PeerBlock, + }, + }, + } + header := &block.HeaderV3{} + + err = bp.SaveProposedTxsToStorage(header, blockBody) + require.Equal(t, expectedErr, err) + }) +} + +func TestBaseProcessor_checkContextBeforeExecution(t *testing.T) { + t.Parallel() + + t.Run("should return error from getting the accountsDB root hash", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{ + Nonce: 1, + } + }, + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return 1, []byte("hash1"), []byte("rootHash1") + }, + } + + accounts := &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return nil, expectedErr + }, + } + + arguments.AccountsDB[state.UserAccountsState] = accounts + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + err = bp.CheckContextBeforeExecution(&block.HeaderV3{ + Nonce: 2, + PrevHash: []byte("hash1"), + }, []byte("headerHash")) + require.Equal(t, expectedErr, err) + }) + + t.Run("should return ErrBlockHashDoesNotMatch error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + + blkc, _ := blockchain.NewBlockChain(&statusHandlerMock.AppStatusHandlerStub{}) + err := blkc.SetGenesisHeader(&block.Header{}) + require.NoError(t, err) + + blkc.SetLastExecutedBlockHeaderAndRootHash( + &block.HeaderV3{}, + []byte("hashX"), + []byte("rootHash"), + ) + + dataComponents.BlockChain = blkc + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + accounts := &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return []byte("rootHash"), nil + }, + } + arguments.AccountsDB[state.UserAccountsState] = accounts + + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + err = bp.CheckContextBeforeExecution(&block.HeaderV3{ + PrevHash: []byte("hash"), + }, []byte("headerHash")) + require.Equal(t, process.ErrBlockHashDoesNotMatch, err) + }) + + t.Run("should return ErrWrongNonceInBlock error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + + blkc, _ := blockchain.NewBlockChain(&statusHandlerMock.AppStatusHandlerStub{}) + err := blkc.SetGenesisHeader(&block.Header{}) + require.NoError(t, err) + + blkc.SetLastExecutedBlockHeaderAndRootHash( + &block.HeaderV3{ + Nonce: 0, + }, + []byte("hash"), + []byte("rootHash"), + ) + + dataComponents.BlockChain = blkc + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + accounts := &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return []byte("rootHash"), nil + }, + } + arguments.AccountsDB[state.UserAccountsState] = accounts + + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + err = bp.CheckContextBeforeExecution(&block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 2, + }, []byte("headerHash")) + require.Equal(t, process.ErrWrongNonceInBlock, err) + }) + + t.Run("should return ErrRootStateDoesNotMatch error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + + blkc, _ := blockchain.NewBlockChain(&statusHandlerMock.AppStatusHandlerStub{}) + err := blkc.SetGenesisHeader(&block.Header{}) + require.NoError(t, err) + + blkc.SetLastExecutedBlockHeaderAndRootHash( + &block.HeaderV3{ + Nonce: 1, + }, + []byte("hash"), + []byte("rootHash"), + ) + + dataComponents.BlockChain = blkc + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + accounts := &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return []byte("rootHashX"), nil + }, + RecreateTrieCalled: func(options common.RootHashHolder) error { + return nil + }, + } + arguments.AccountsDB[state.UserAccountsState] = accounts + + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + err = bp.CheckContextBeforeExecution(&block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 2, + }, []byte("headerHash")) + require.Equal(t, process.ErrRootStateDoesNotMatch, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + + blkc, _ := blockchain.NewBlockChain(&statusHandlerMock.AppStatusHandlerStub{}) + err := blkc.SetGenesisHeader(&block.Header{}) + require.NoError(t, err) + + blkc.SetLastExecutedBlockHeaderAndRootHash( + &block.HeaderV3{ + Nonce: 1, + }, + []byte("hash"), + []byte("rootHash"), + ) + + dataComponents.BlockChain = blkc + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + accounts := &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return []byte("rootHash"), nil + }, + } + arguments.AccountsDB[state.UserAccountsState] = accounts + + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + err = bp.CheckContextBeforeExecution(&block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 2, + }, []byte("headerHash")) + require.Nil(t, err) + }) +} + +func TestBaseProcess_collectMiniBlocks(t *testing.T) { + t.Parallel() + + t.Run("if creating receipts hash fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + txCoordinatorMock := createTxCoordinatorMock() + txCoordinatorMock.CreateReceiptsHashCalled = func() ([]byte, error) { + return nil, expectedErr + } + + arguments.TxCoordinator = &txCoordinatorMock + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + _, _, _, err = bp.CollectMiniBlocks([]byte("hash"), &block.Body{}) + require.Equal(t, expectedErr, err) + }) + + t.Run("should remove self receipt mini blocks", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + txCoordinatorMock := testscommon.TransactionCoordinatorMock{} + txCoordinatorMock.CreatePostProcessMiniBlocksCalled = func() block.MiniBlockSlice { + return block.MiniBlockSlice{ + { + TxHashes: [][]byte{ + []byte("txHash1"), + []byte("txHash2"), + }, + Type: block.ReceiptBlock, // this mini block should be removed + ReceiverShardID: uint32(0), + SenderShardID: uint32(0), + }, + { + TxHashes: [][]byte{ + []byte("txHash3"), + []byte("txHash4"), + }, + }, + } + } + + arguments.TxCoordinator = &txCoordinatorMock + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + miniBlockHeaderHandlers, totalTxCount, receiptHash, err := bp.CollectMiniBlocks([]byte("hash"), &block.Body{}) + require.NoError(t, err) + require.Equal(t, 1, len(miniBlockHeaderHandlers)) + require.Equal(t, 2, totalTxCount) + require.Equal(t, []byte("receiptHash"), receiptHash) + }) + + t.Run("if hashing fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + err := coreComponents.SetInternalMarshalizer(&marshallerMock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + return nil, expectedErr + }, + }) + require.Nil(t, err) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + txCoordinatorMock := testscommon.TransactionCoordinatorMock{} + txCoordinatorMock.CreatePostProcessMiniBlocksCalled = func() block.MiniBlockSlice { + return block.MiniBlockSlice{ + { + TxHashes: [][]byte{ + []byte("txHash1"), + []byte("txHash2"), + }, + Type: block.ReceiptBlock, // this mini block should be removed + ReceiverShardID: uint32(0), + SenderShardID: uint32(0), + }, + { + TxHashes: [][]byte{ + []byte("txHash3"), + []byte("txHash4"), + }, + }, + } + } + + arguments.TxCoordinator = &txCoordinatorMock + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + _, _, _, err = bp.CollectMiniBlocks([]byte("hash"), &block.Body{}) + require.Equal(t, expectedErr, err) + }) + + t.Run("sanitized mini blocks should be cached", func(t *testing.T) { + t.Parallel() + + expectedValue := 0 + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataPool := initDataPool() + dataPool.ExecutedMiniBlocksCalled = func() storage.Cacher { + return &cache.CacherStub{ + PutCalled: func(key []byte, value interface{}, sizeInBytes int) (evicted bool) { + expectedValue++ + return false + }, + } + } + dataComponents.DataPool = dataPool + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + txCoordinatorMock := testscommon.TransactionCoordinatorMock{} + txCoordinatorMock.CreatePostProcessMiniBlocksCalled = func() block.MiniBlockSlice { + return block.MiniBlockSlice{ + { + TxHashes: [][]byte{ + []byte("txHash1"), + []byte("txHash2"), + }, + Type: block.ReceiptBlock, // this mini block should be removed + ReceiverShardID: uint32(0), + SenderShardID: uint32(0), + }, + { + TxHashes: [][]byte{ + []byte("txHash3"), + []byte("txHash4"), + }, + }, + } + } + + arguments.TxCoordinator = &txCoordinatorMock + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + miniBlockHeaderHandlers, totalTxCount, receiptHash, err := bp.CollectMiniBlocks([]byte("hash"), &block.Body{}) + require.NoError(t, err) + require.Equal(t, 1, len(miniBlockHeaderHandlers)) + require.Equal(t, 2, totalTxCount) + require.Equal(t, []byte("receiptHash"), receiptHash) + require.Equal(t, 2, expectedValue) + }) + + t.Run("existing intra shard mini blocks should be cached", func(t *testing.T) { + t.Parallel() + + expectedValue := 0 + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataPool := initDataPool() + dataPool.ExecutedMiniBlocksCalled = func() storage.Cacher { + return &cache.CacherStub{ + PutCalled: func(key []byte, value interface{}, sizeInBytes int) (evicted bool) { + expectedValue++ + return false + }, + } + } + dataComponents.DataPool = dataPool + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + txCoordinatorMock := testscommon.TransactionCoordinatorMock{} + txCoordinatorMock.GetCreatedInShardMiniBlocksCalled = func() []*block.MiniBlock { + return []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("txHash1"), + }, + }, + } + } + + arguments.TxCoordinator = &txCoordinatorMock + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + headerHash := []byte("headerHash") + miniBlockHeaderHandlers, totalTxCount, receiptHash, err := bp.CollectMiniBlocks(headerHash, &block.Body{}) + require.NoError(t, err) + require.Equal(t, 0, len(miniBlockHeaderHandlers)) + require.Equal(t, 0, totalTxCount) + require.Equal(t, []byte("receiptHash"), receiptHash) + + require.Equal(t, 1, expectedValue) + }) +} + +func TestBaseProcessor_CacheIntraShardMiniBlocks(t *testing.T) { + t.Parallel() + + t.Run("should work with proto marshaller", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + + _ = coreComponents.SetInternalMarshalizer(&marshal.GogoProtoMarshalizer{}) + + executedMBs := cache.NewCacherStub() + + wasCalled := false + executedMBs.PutCalled = func(key []byte, value interface{}, sizeInBytes int) (evicted bool) { + wasCalled = true + return + } + + dataPool := initDataPool() + dataPool.ExecutedMiniBlocksCalled = func() storage.Cacher { + return executedMBs + } + dataComponents.DataPool = dataPool + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + bp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + headerHash := []byte("headerHash") + miniBlocks := []*block.MiniBlock{} + + err = bp.CacheIntraShardMiniBlocks(headerHash, miniBlocks) + require.Nil(t, err) + + require.True(t, wasCalled) + }) +} + +func TestBaseProcessor_excludeRevertedExecutionResultsForHeader(t *testing.T) { + t.Parallel() + + t.Run("should work in case of no pending execution results", func(t *testing.T) { + t.Parallel() + + bp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{}) + require.NoError(t, err) + + header := &block.HeaderV3{} + sanitizedPendingExecResults := bp.ExcludeRevertedExecutionResultsForHeader( + header, + []data.BaseExecutionResultHandler{}, + ) + require.Equal(t, 0, len(sanitizedPendingExecResults)) + }) + + t.Run("should remove last execution result if its header nonce is equal to the nonce of the created block", func(t *testing.T) { + t.Parallel() + + bp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{}) + require.NoError(t, err) + + headerNonce := uint64(1) + header := &block.HeaderV3{ + Nonce: headerNonce, + } + + pendingExecutionResults := []data.BaseExecutionResultHandler{ + &block.BaseExecutionResult{ + HeaderNonce: headerNonce, + }, + } + + sanitizedPendingExecResults := bp.ExcludeRevertedExecutionResultsForHeader( + header, + pendingExecutionResults, + ) + require.Equal(t, 0, len(sanitizedPendingExecResults)) + }) + + t.Run("should remove last execution result if if getting the execution result's header from storage fails", func(t *testing.T) { + t.Parallel() + + bp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "store": &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return nil, expectedErr + }, + }, + "marshalizer": &testscommon.MarshallerStub{}, + }) + require.NoError(t, err) + + header := &block.HeaderV3{ + Nonce: 2, + } + + pendingExecutionResults := []data.BaseExecutionResultHandler{ + &block.BaseExecutionResult{ + HeaderHash: []byte("wrongHash"), + HeaderNonce: 1, + }, + } + + sanitizedPendingExecResults := bp.ExcludeRevertedExecutionResultsForHeader( + header, + pendingExecutionResults, + ) + require.Equal(t, 0, len(sanitizedPendingExecResults)) + }) + + t.Run("should not remove valid execution results", func(t *testing.T) { + t.Parallel() + + bp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "store": &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + return nil, nil + }, + }, nil + }, + }, + "marshalizer": &testscommon.MarshallerStub{ + UnmarshalCalled: func(obj interface{}, buff []byte) error { + return nil + }, + }, + }) + require.NoError(t, err) + + header := &block.HeaderV3{ + Nonce: 2, + } + + pendingExecutionResults := []data.BaseExecutionResultHandler{ + &block.BaseExecutionResult{ + HeaderHash: []byte("hash"), + HeaderNonce: 1, + }, + } + + sanitizedPendingExecResults := bp.ExcludeRevertedExecutionResultsForHeader( + header, + pendingExecutionResults, + ) + require.Equal(t, pendingExecutionResults, sanitizedPendingExecResults) + }) +} + +func TestBaseProcessor_ProposedDirectSentTransactionsToBroadcast(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + wasProposedDirectSentTransactionsToBroadcastCalled := false + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + ProposedDirectSentTransactionsToBroadcastCalled: func(proposedBody data.BodyHandler) map[string][][]byte { + wasProposedDirectSentTransactionsToBroadcastCalled = true + return nil + }, + } + bp, _ := blproc.NewShardProcessor(arguments) + + _ = bp.ProposedDirectSentTransactionsToBroadcast(nil) + require.True(t, wasProposedDirectSentTransactionsToBroadcastCalled) +} + +func TestBaseProcessor_Close(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + bp, _ := blproc.NewShardProcessor(arguments) + + require.NoError(t, bp.Close()) +} + +func TestBaseProcessor_WaitForExecutionResultsVerification(t *testing.T) { + t.Parallel() + + t.Run("should return nil when verification succeeds on first call", func(t *testing.T) { + t.Parallel() + + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.ExecutionResultsVerifier = &processMocks.ExecutionResultsVerifierMock{ + VerifyHeaderExecutionResultsCalled: func(header data.HeaderHandler) error { + return nil + }, + } + bp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + header := &block.HeaderV3{Nonce: 1} + err = bp.WaitForExecutionResultsVerification(header, func() time.Duration { return time.Second }) + require.Nil(t, err) + }) + + t.Run("should return non-retryable error immediately", func(t *testing.T) { + t.Parallel() + + callCount := atomic.Int32{} + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.ExecutionResultsVerifier = &processMocks.ExecutionResultsVerifierMock{ + VerifyHeaderExecutionResultsCalled: func(header data.HeaderHandler) error { + callCount.Add(1) + return process.ErrExecutionResultDoesNotMatch + }, + } + bp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + header := &block.HeaderV3{Nonce: 1} + err = bp.WaitForExecutionResultsVerification(header, func() time.Duration { return time.Second }) + require.ErrorIs(t, err, process.ErrExecutionResultDoesNotMatch) + require.Equal(t, int32(1), callCount.Load()) + }) + + t.Run("should retry on mismatch then succeed", func(t *testing.T) { + t.Parallel() + + callCount := atomic.Int32{} + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.ExecutionResultsVerifier = &processMocks.ExecutionResultsVerifierMock{ + VerifyHeaderExecutionResultsCalled: func(header data.HeaderHandler) error { + count := callCount.Add(1) + if count < 3 { + return process.ErrExecutionResultsNumberMismatch + } + return nil + }, + } + bp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + header := &block.HeaderV3{Nonce: 1} + err = bp.WaitForExecutionResultsVerification(header, func() time.Duration { return time.Second }) + require.Nil(t, err) + require.Equal(t, int32(3), callCount.Load()) + }) + + t.Run("should timeout and return mismatch error when haveTime expires", func(t *testing.T) { + t.Parallel() + + callCount := atomic.Int32{} + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.ExecutionResultsVerifier = &processMocks.ExecutionResultsVerifierMock{ + VerifyHeaderExecutionResultsCalled: func(header data.HeaderHandler) error { + callCount.Add(1) + return process.ErrExecutionResultsNumberMismatch + }, + } + bp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + header := &block.HeaderV3{Nonce: 1} + deadline := time.Now().Add(25 * time.Millisecond) + err = bp.WaitForExecutionResultsVerification(header, func() time.Duration { return time.Until(deadline) }) + require.ErrorIs(t, err, process.ErrExecutionResultsNumberMismatch) + require.Greater(t, callCount.Load(), int32(1)) + }) + + t.Run("should return mismatch error immediately when haveTime returns zero", func(t *testing.T) { + t.Parallel() + + callCount := atomic.Int32{} + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.ExecutionResultsVerifier = &processMocks.ExecutionResultsVerifierMock{ + VerifyHeaderExecutionResultsCalled: func(header data.HeaderHandler) error { + callCount.Add(1) + return process.ErrExecutionResultsNumberMismatch + }, + } + bp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + header := &block.HeaderV3{Nonce: 1} + err = bp.WaitForExecutionResultsVerification(header, func() time.Duration { return 0 }) + require.ErrorIs(t, err, process.ErrExecutionResultsNumberMismatch) + require.Equal(t, int32(1), callCount.Load()) + }) + + t.Run("should return mismatch error immediately when haveTime returns negative", func(t *testing.T) { + t.Parallel() + + callCount := atomic.Int32{} + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.ExecutionResultsVerifier = &processMocks.ExecutionResultsVerifierMock{ + VerifyHeaderExecutionResultsCalled: func(header data.HeaderHandler) error { + callCount.Add(1) + return process.ErrExecutionResultsNumberMismatch + }, + } + bp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + header := &block.HeaderV3{Nonce: 1} + err = bp.WaitForExecutionResultsVerification(header, func() time.Duration { return -time.Second }) + require.ErrorIs(t, err, process.ErrExecutionResultsNumberMismatch) + require.Equal(t, int32(1), callCount.Load()) + }) +} + +func TestBaseProcessor_PruneTrieAsyncHeader(t *testing.T) { + t.Parallel() + + // header 1 + + headerHash1 := []byte("headerHash1") + rootHash10 := []byte("rootHash10") + rootHash11 := []byte("rootHash11") + + baseExecRes10 := &block.BaseExecutionResult{RootHash: rootHash10} + baseExecRes11 := &block.BaseExecutionResult{RootHash: rootHash11} + executionResultsHandlers := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: baseExecRes10, + }, + &block.ExecutionResult{ + BaseExecutionResult: baseExecRes11, + }, + } + header1 := &block.HeaderV3{ + Nonce: 8, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: baseExecRes11, + }, + } + _ = header1.SetExecutionResultsHandlers(executionResultsHandlers) + + // header 2 + + headerHash2 := []byte("headerHash2") + rootHash20 := []byte("rootHash20") + rootHash21 := []byte("rootHash21") + rootHash22 := []byte("rootHash22") + rootHash23 := []byte("rootHash23") + + baseExecRes20 := &block.BaseExecutionResult{RootHash: rootHash20} + baseExecRes21 := &block.BaseExecutionResult{RootHash: rootHash21} + baseExecRes22 := &block.BaseExecutionResult{RootHash: rootHash22} + baseExecRes23 := &block.BaseExecutionResult{RootHash: rootHash23} + executionResultsHandlers2 := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: baseExecRes20, + }, + &block.ExecutionResult{ + BaseExecutionResult: baseExecRes21, + }, + &block.ExecutionResult{ + BaseExecutionResult: baseExecRes22, + }, + &block.ExecutionResult{ + BaseExecutionResult: baseExecRes23, + }, + } + + header2 := &block.HeaderV3{ + Nonce: 9, + PrevHash: headerHash1, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: baseExecRes23, + }, + } + _ = header2.SetExecutionResultsHandlers(executionResultsHandlers2) + + // header 3 + + headerHash3 := []byte("headerHash3") + rootHash30 := []byte("rootHash30") + rootHash31 := []byte("rootHash31") + + baseExecRes30 := &block.BaseExecutionResult{RootHash: rootHash30} + baseExecRes31 := &block.BaseExecutionResult{RootHash: rootHash31} + executionResultsHandlers3 := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: baseExecRes30, + }, + &block.ExecutionResult{ + BaseExecutionResult: baseExecRes31, + }, + } + + header3 := &block.HeaderV3{ + Nonce: 10, + PrevHash: headerHash2, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: baseExecRes31, + }, + } + _ = header3.SetExecutionResultsHandlers(executionResultsHandlers3) + + // header 4 + + headerHash4 := []byte("headerHash4") + header4 := &block.HeaderV3{ + Nonce: 11, + PrevHash: headerHash3, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: baseExecRes31, + }, + } + + // header 5 + + headerHash5 := []byte("headerHash5") + header5 := &block.HeaderV3{ + Nonce: 12, + PrevHash: headerHash4, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: baseExecRes31, + }, + } + + // header 6 + + headerHash6 := []byte("headerHash6") + rootHash60 := []byte("rootHash60") + rootHash61 := []byte("rootHash61") + + baseExecRes60 := &block.BaseExecutionResult{RootHash: rootHash60} + baseExecRes61 := &block.BaseExecutionResult{RootHash: rootHash61} + executionResultsHandlers6 := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: baseExecRes60, + }, + &block.ExecutionResult{ + BaseExecutionResult: baseExecRes61, + }, + } + + header6 := &block.HeaderV3{ + Nonce: 13, + PrevHash: headerHash5, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: baseExecRes61, + }, + } + _ = header6.SetExecutionResultsHandlers(executionResultsHandlers6) + + // header 7 + + headerHash7 := []byte("headerHash7") + rootHash70 := []byte("rootHash70") + + baseExecRes70 := &block.BaseExecutionResult{RootHash: rootHash70} + baseExecRes71 := &block.BaseExecutionResult{RootHash: rootHash70} // no roothash change + executionResultsHandlers7 := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: baseExecRes70, + }, + &block.ExecutionResult{ + BaseExecutionResult: baseExecRes71, + }, + } + + header7 := &block.HeaderV3{ + Nonce: 14, + PrevHash: headerHash6, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: baseExecRes71, + }, + } + _ = header7.SetExecutionResultsHandlers(executionResultsHandlers7) + + // header 8 + + headerHash8 := []byte("headerHash8") + + baseExecRes80 := &block.BaseExecutionResult{RootHash: rootHash70} // not roothash change + header8 := &block.HeaderV3{ + Nonce: 15, + PrevHash: headerHash7, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: baseExecRes80, + }, + } + + // header 9 + + headerHash9 := []byte("headerHash9") + rootHash91 := []byte("rootHash91") + + baseExecRes90 := &block.BaseExecutionResult{RootHash: rootHash70} // no roothash change + baseExecRes91 := &block.BaseExecutionResult{RootHash: rootHash91} // roothash changed + executionResultsHandlers9 := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: baseExecRes90, + }, + &block.ExecutionResult{ + BaseExecutionResult: baseExecRes91, + }, + } + + header9 := &block.HeaderV3{ + Nonce: 16, + PrevHash: headerHash8, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: baseExecRes91, + }, + } + _ = header9.SetExecutionResultsHandlers(executionResultsHandlers9) + + t.Run("last pruned header not set, should trigger provided header", func(t *testing.T) { + t.Parallel() + + cancelPruneCalled := false + pruneTrieCalled := false + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { + return true + }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + cancelPruneCalled = true + }, + PruneTrieCalled: func(rootHash []byte, identifier state.TriePruningIdentifier, handler state.PruningHandler) { + pruneTrieCalled = true + }, + } + + blkc := createTestBlockchain() + blkc.GetCurrentBlockHeaderCalled = func() data.HeaderHandler { + return header1 + } + blkc.GetCurrentBlockHeaderHashCalled = func() []byte { + return headerHash1 + } + dataComponents.BlockChain = blkc + + bp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + require.Nil(t, bp.GetLastPrunedHash()) + + _ = header1.SetExecutionResultsHandlers(executionResultsHandlers) + bp.PruneTrieAsyncHeader() + + require.True(t, cancelPruneCalled) + require.True(t, pruneTrieCalled) + + require.Equal(t, headerHash1, bp.GetLastPrunedHash()) + }) + + t.Run("header nonce lower than last pruned header, should not trigger", func(t *testing.T) { + t.Parallel() + + cancelPruneCalled := false + pruneTrieCalled := false + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { + return true + }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + cancelPruneCalled = true + }, + PruneTrieCalled: func(rootHash []byte, identifier state.TriePruningIdentifier, handler state.PruningHandler) { + pruneTrieCalled = true + }, + } + + blkc := createTestBlockchain() + blkc.GetCurrentBlockHeaderCalled = func() data.HeaderHandler { + return header2 + } + blkc.GetCurrentBlockHeaderHashCalled = func() []byte { + return headerHash2 + } + dataComponents.BlockChain = blkc + + bp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + bp.SetLastPrunedNonce(10) + bp.SetLastPrunedHash(headerHash3) + + bp.PruneTrieAsyncHeader() + require.False(t, cancelPruneCalled) + require.False(t, pruneTrieCalled) + + require.Equal(t, headerHash3, bp.GetLastPrunedHash()) + }) + + t.Run("intermediate headers with included execution results", func(t *testing.T) { + t.Parallel() + + cancelPruneRootHashes := make([][]byte, 0) + pruneTrieRootHashes := make([][]byte, 0) + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { + return true + }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + cancelPruneRootHashes = append(cancelPruneRootHashes, rootHash) + }, + PruneTrieCalled: func(rootHash []byte, identifier state.TriePruningIdentifier, handler state.PruningHandler) { + pruneTrieRootHashes = append(pruneTrieRootHashes, rootHash) + }, + } + + headersPool := &mock.HeadersCacherStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, headerHash1) { + return header1, nil + } + if bytes.Equal(hash, headerHash2) { + return header2, nil + } + if bytes.Equal(hash, headerHash3) { + return header3, nil + } + + return nil, errors.New("header not found") + }, + } + dataPool := initDataPool() + dataPool.HeadersCalled = func() dataRetriever.HeadersPool { + return headersPool + } + dataComponents.DataPool = dataPool + + blkc := createTestBlockchain() + blkc.GetCurrentBlockHeaderCalled = func() data.HeaderHandler { + return header3 + } + blkc.GetCurrentBlockHeaderHashCalled = func() []byte { + return headerHash3 + } + dataComponents.BlockChain = blkc + + bp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + bp.SetLastPrunedHash(headerHash1) + + bp.PruneTrieAsyncHeader() + + require.Equal(t, 6, len(cancelPruneRootHashes)) + require.Equal(t, rootHash11, cancelPruneRootHashes[0]) + require.Equal(t, rootHash20, cancelPruneRootHashes[1]) + require.Equal(t, rootHash21, cancelPruneRootHashes[2]) + require.Equal(t, rootHash22, cancelPruneRootHashes[3]) + require.Equal(t, rootHash23, cancelPruneRootHashes[4]) + require.Equal(t, rootHash30, cancelPruneRootHashes[5]) + + require.Equal(t, 6, len(pruneTrieRootHashes)) + require.Equal(t, rootHash11, pruneTrieRootHashes[0]) + require.Equal(t, rootHash20, pruneTrieRootHashes[1]) + require.Equal(t, rootHash21, pruneTrieRootHashes[2]) + require.Equal(t, rootHash22, pruneTrieRootHashes[3]) + require.Equal(t, rootHash23, pruneTrieRootHashes[4]) + require.Equal(t, rootHash30, pruneTrieRootHashes[5]) + + require.Equal(t, headerHash3, bp.GetLastPrunedHash()) + + // another call for the same current header should not trigger prune + bp.PruneTrieAsyncHeader() + + require.Equal(t, 6, len(cancelPruneRootHashes)) + require.Equal(t, 6, len(pruneTrieRootHashes)) + require.Equal(t, headerHash3, bp.GetLastPrunedHash()) + }) + + t.Run("intermediate headers without included execution results", func(t *testing.T) { + t.Parallel() + + cancelPruneRootHashes := make([][]byte, 0) + pruneTrieRootHashes := make([][]byte, 0) + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { + return true + }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + cancelPruneRootHashes = append(cancelPruneRootHashes, rootHash) + }, + PruneTrieCalled: func(rootHash []byte, identifier state.TriePruningIdentifier, handler state.PruningHandler) { + pruneTrieRootHashes = append(pruneTrieRootHashes, rootHash) + }, + } + + headersPool := &mock.HeadersCacherStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, headerHash3) { + return header3, nil + } + if bytes.Equal(hash, headerHash4) { + return header4, nil + } + if bytes.Equal(hash, headerHash5) { + return header5, nil + } + if bytes.Equal(hash, headerHash6) { + return header6, nil + } + + return nil, errors.New("header not found") + }, + } + dataPool := initDataPool() + dataPool.HeadersCalled = func() dataRetriever.HeadersPool { + return headersPool + } + dataComponents.DataPool = dataPool + + blkc := createTestBlockchain() + blkc.GetCurrentBlockHeaderCalled = func() data.HeaderHandler { + return header6 + } + blkc.GetCurrentBlockHeaderHashCalled = func() []byte { + return headerHash6 + } + dataComponents.BlockChain = blkc + + bp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + bp.SetLastPrunedHash(headerHash3) + + bp.PruneTrieAsyncHeader() + + require.Equal(t, 2, len(cancelPruneRootHashes)) + require.Equal(t, rootHash31, cancelPruneRootHashes[0]) + require.Equal(t, rootHash60, cancelPruneRootHashes[1]) + + require.Equal(t, 2, len(pruneTrieRootHashes)) + require.Equal(t, rootHash31, pruneTrieRootHashes[0]) + require.Equal(t, rootHash60, pruneTrieRootHashes[1]) + + require.Equal(t, headerHash6, bp.GetLastPrunedHash()) + + // another call for the same current header should not trigger prune + bp.PruneTrieAsyncHeader() + + require.Equal(t, 2, len(cancelPruneRootHashes)) + require.Equal(t, 2, len(pruneTrieRootHashes)) + require.Equal(t, headerHash6, bp.GetLastPrunedHash()) + }) + + t.Run("intermediate headers with included execution results, no roothash change", func(t *testing.T) { + t.Parallel() + + cancelPruneRootHashes := make([][]byte, 0) + pruneTrieRootHashes := make([][]byte, 0) + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { + return true + }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + cancelPruneRootHashes = append(cancelPruneRootHashes, rootHash) + }, + PruneTrieCalled: func(rootHash []byte, identifier state.TriePruningIdentifier, handler state.PruningHandler) { + pruneTrieRootHashes = append(pruneTrieRootHashes, rootHash) + }, + } + + headersPool := &mock.HeadersCacherStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, headerHash6) { + return header6, nil + } + if bytes.Equal(hash, headerHash7) { + return header7, nil + } + if bytes.Equal(hash, headerHash8) { + return header8, nil + } + if bytes.Equal(hash, headerHash9) { + return header9, nil + } + + return nil, errors.New("header not found") + }, + } + dataPool := initDataPool() + dataPool.HeadersCalled = func() dataRetriever.HeadersPool { + return headersPool + } + dataComponents.DataPool = dataPool + + blkc := createTestBlockchain() + blkc.GetCurrentBlockHeaderCalled = func() data.HeaderHandler { + return header9 + } + blkc.GetCurrentBlockHeaderHashCalled = func() []byte { + return headerHash9 + } + dataComponents.BlockChain = blkc + + bp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + bp.SetLastPrunedHash(headerHash5) + + bp.PruneTrieAsyncHeader() + + require.Equal(t, 3, len(cancelPruneRootHashes)) + require.Equal(t, rootHash60, cancelPruneRootHashes[0]) + require.Equal(t, rootHash61, cancelPruneRootHashes[1]) + require.Equal(t, rootHash70, cancelPruneRootHashes[2]) + + require.Equal(t, 3, len(pruneTrieRootHashes)) + require.Equal(t, rootHash60, pruneTrieRootHashes[0]) + require.Equal(t, rootHash61, pruneTrieRootHashes[1]) + require.Equal(t, rootHash70, pruneTrieRootHashes[2]) + + require.Equal(t, headerHash9, bp.GetLastPrunedHash()) + + // another call for the same current header should not trigger prune + bp.PruneTrieAsyncHeader() + + require.Equal(t, 3, len(cancelPruneRootHashes)) + require.Equal(t, 3, len(pruneTrieRootHashes)) + require.Equal(t, headerHash9, bp.GetLastPrunedHash()) + }) +} + +func TestComputeEWLResetThreshold(t *testing.T) { + t.Parallel() + + t.Run("gap 0 should return minimum baseline", func(t *testing.T) { + t.Parallel() + // gap=0 -> expected=0*2=0, 0*130/100=0, +10 = 10 + require.Equal(t, 10, blproc.ComputeEWLResetThreshold(0)) + }) + t.Run("gap 1", func(t *testing.T) { + t.Parallel() + // gap=1 -> expected=1*2=2, 2*130/100=2, +10 = 12 + require.Equal(t, 12, blproc.ComputeEWLResetThreshold(1)) + }) + t.Run("default gap 10", func(t *testing.T) { + t.Parallel() + // gap=10 -> expected=10*2=20, 20*130/100=26, +10 = 36 + require.Equal(t, 36, blproc.ComputeEWLResetThreshold(10)) + }) + t.Run("gap above cap should be clamped", func(t *testing.T) { + t.Parallel() + // gap=500 clamped to 250 -> expected=250*2=500, 500*130/100=650, +10 = 660 + require.Equal(t, 660, blproc.ComputeEWLResetThreshold(500)) + require.Equal(t, 660, blproc.ComputeEWLResetThreshold(1000)) + }) + t.Run("gap at cap boundary", func(t *testing.T) { + t.Parallel() + require.Equal(t, 660, blproc.ComputeEWLResetThreshold(250)) + require.Equal(t, blproc.ComputeEWLResetThreshold(250), blproc.ComputeEWLResetThreshold(251)) + }) +} + +func TestCancelPruneForRootHashTransition(t *testing.T) { + t.Parallel() + + t.Run("different hashes should call CancelPrune for both", func(t *testing.T) { + t.Parallel() + cancelPruneCalls := make([]struct { + rootHash []byte + identifier state.TriePruningIdentifier + }, 0) + accountsStub := &stateMock.AccountsStub{ + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + cancelPruneCalls = append(cancelPruneCalls, struct { + rootHash []byte + identifier state.TriePruningIdentifier + }{rootHash, identifier}) + }, + } + + blproc.CancelPruneForRootHashTransition(accountsStub, []byte("prev"), []byte("curr")) + + require.Len(t, cancelPruneCalls, 2) + require.Equal(t, []byte("curr"), cancelPruneCalls[0].rootHash) + require.Equal(t, state.NewRoot, cancelPruneCalls[0].identifier) + require.Equal(t, []byte("prev"), cancelPruneCalls[1].rootHash) + require.Equal(t, state.OldRoot, cancelPruneCalls[1].identifier) + }) + t.Run("equal hashes should not call CancelPrune", func(t *testing.T) { + t.Parallel() + accountsStub := &stateMock.AccountsStub{ + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + require.Fail(t, "CancelPrune should not be called for equal hashes") + }, + } + blproc.CancelPruneForRootHashTransition(accountsStub, []byte("same"), []byte("same")) + }) + t.Run("empty prev hash should not call CancelPrune", func(t *testing.T) { + t.Parallel() + accountsStub := &stateMock.AccountsStub{ + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + require.Fail(t, "CancelPrune should not be called when prev is empty") + }, + } + blproc.CancelPruneForRootHashTransition(accountsStub, nil, []byte("curr")) + blproc.CancelPruneForRootHashTransition(accountsStub, []byte{}, []byte("curr")) + }) + t.Run("empty current hash should not call CancelPrune", func(t *testing.T) { + t.Parallel() + accountsStub := &stateMock.AccountsStub{ + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + require.Fail(t, "CancelPrune should not be called when current is empty") + }, + } + blproc.CancelPruneForRootHashTransition(accountsStub, []byte("prev"), nil) + blproc.CancelPruneForRootHashTransition(accountsStub, []byte("prev"), []byte{}) + }) +} + +func TestCleanupDismissedEWLEntries(t *testing.T) { + t.Parallel() + + t.Run("empty dismissed queue should only run size check", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { return true }, + GetEvictionWaitingListSizeCalled: func() int { return 0 }, + } + arguments.ExecutionManager = &processMocks.ExecutionManagerMock{ + PopDismissedResultsCalled: func() []executionTrack.DismissedBatch { return nil }, + } + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + // should not panic, should not call CancelPrune + sp.CleanupDismissedEWLEntries() + }) + t.Run("dismissed batches should trigger CancelPrune and reset last pruned header", func(t *testing.T) { + t.Parallel() + + cancelPruneCalls := 0 + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { return true }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + cancelPruneCalls++ + }, + GetEvictionWaitingListSizeCalled: func() int { return 0 }, + } + popCalled := false + arguments.ExecutionManager = &processMocks.ExecutionManagerMock{ + PopDismissedResultsCalled: func() []executionTrack.DismissedBatch { + if popCalled { + return nil + } + popCalled = true + return []executionTrack.DismissedBatch{ + { + AnchorResult: &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R0")}, + }, + Results: []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R1")}, + }, + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R2")}, + }, + }, + }, + } + }, + } + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + sp.SetLastPrunedHash([]byte("someHash")) + sp.SetLastPrunedNonce(100) + + sp.CleanupDismissedEWLEntries() + + // Two transitions: R0->R1 and R1->R2, each producing 2 CancelPrune calls = 4 total + require.Equal(t, 4, cancelPruneCalls) + // Last pruned header should be reset + require.Nil(t, sp.GetLastPrunedHash()) + }) +} + +func TestCheckEWLSizeAndReset(t *testing.T) { + t.Parallel() + + t.Run("ewl size below threshold should not trigger reset", func(t *testing.T) { + t.Parallel() + + resetCalled := false + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { return true }, + GetEvictionWaitingListSizeCalled: func() int { return 5 }, + ResetPruningCalled: func() { + resetCalled = true + }, + } + arguments.ExecutionManager = &processMocks.ExecutionManagerMock{ + PopDismissedResultsCalled: func() []executionTrack.DismissedBatch { return nil }, + } + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + // default gap=10 -> threshold=36, ewlSize=5 < 36 + sp.CheckEWLSizeAndReset() + require.False(t, resetCalled) + }) + t.Run("ewl size above threshold should trigger reset and clear last pruned header", func(t *testing.T) { + t.Parallel() + + resetCalled := false + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { return true }, + GetEvictionWaitingListSizeCalled: func() int { return 1000 }, + ResetPruningCalled: func() { + resetCalled = true + }, + } + arguments.ExecutionManager = &processMocks.ExecutionManagerMock{ + PopDismissedResultsCalled: func() []executionTrack.DismissedBatch { return nil }, + } + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + sp.SetLastPrunedHash([]byte("someHash")) + sp.SetLastPrunedNonce(50) + + // default gap=10 -> threshold=36, ewlSize=1000 > 36 + sp.CheckEWLSizeAndReset() + require.True(t, resetCalled) + require.Nil(t, sp.GetLastPrunedHash()) + }) + t.Run("pruning disabled should skip reset even if size would exceed", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { return false }, + GetEvictionWaitingListSizeCalled: func() int { + require.Fail(t, "should not check EWL size when pruning is disabled") + return 0 + }, + ResetPruningCalled: func() { + require.Fail(t, "should not reset when pruning is disabled") + }, + } + arguments.ExecutionManager = &processMocks.ExecutionManagerMock{ + PopDismissedResultsCalled: func() []executionTrack.DismissedBatch { return nil }, + } + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + sp.CheckEWLSizeAndReset() }) } diff --git a/process/block/displayBlock.go b/process/block/displayBlock.go index 176114deab9..862a159a6e5 100644 --- a/process/block/displayBlock.go +++ b/process/block/displayBlock.go @@ -13,10 +13,11 @@ import ( "github.com/multiversx/mx-chain-core-go/display" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" - logger "github.com/multiversx/mx-chain-logger-go" ) type transactionCounter struct { @@ -110,7 +111,12 @@ func (txc *transactionCounter) headerExecuted(hdr data.HeaderHandler) { func (txc *transactionCounter) getProcessedTxCount(hdr data.HeaderHandler) int32 { currentBlockTxs := int32(0) - for _, miniBlockHeaderHandler := range hdr.GetMiniBlockHeaderHandlers() { + processedMiniBlocks, err := common.GetMiniBlockHeadersFromExecResult(hdr) + if err != nil { + return 0 + } + + for _, miniBlockHeaderHandler := range processedMiniBlocks { if miniBlockHeaderHandler.GetTypeInt32() == int32(block.PeerBlock) { continue } diff --git a/process/block/displayMetaBlock.go b/process/block/displayMetaBlock.go index d43cb9290e6..35023c49685 100644 --- a/process/block/displayMetaBlock.go +++ b/process/block/displayMetaBlock.go @@ -9,9 +9,10 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/display" + "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" - "github.com/multiversx/mx-chain-logger-go" ) type transactionCountersProvider interface { @@ -54,20 +55,19 @@ func (hc *headersCounter) countShardMBHeaders(numShardMBHeaders int) { hc.shardMBHeaderCounterMutex.Unlock() } -func (hc *headersCounter) calculateNumOfShardMBHeaders(header *block.MetaBlock) { +func (hc *headersCounter) calculateNumOfShardMBHeaders(header data.MetaHeaderHandler) { hc.shardMBHeaderCounterMutex.Lock() hc.shardMBHeadersCurrentBlockProcessed = 0 hc.shardMBHeaderCounterMutex.Unlock() - for i := 0; i < len(header.ShardInfo); i++ { - shardData := header.ShardInfo[i] - hc.countShardMBHeaders(len(shardData.ShardMiniBlockHeaders)) + for _, shardData := range header.GetShardInfoHandlers() { + hc.countShardMBHeaders(len(shardData.GetShardMiniBlockHeaderHandlers())) } } func (hc *headersCounter) displayLogInfo( countersProvider transactionCountersProvider, - header *block.MetaBlock, + header data.MetaHeaderHandler, body *block.Body, headerHash []byte, numShardHeadersFromPool int, @@ -112,7 +112,7 @@ func (hc *headersCounter) displayLogInfo( } func (hc *headersCounter) createDisplayableMetaHeader( - header *block.MetaBlock, + header data.MetaHeaderHandler, headerProof data.HeaderProofHandler, ) ([]string, []*display.LineData) { @@ -137,33 +137,32 @@ func (hc *headersCounter) createDisplayableMetaHeader( metaLines = append(metaLines, lines...) metaLines = hc.displayShardInfo(metaLines, header) + metaLines = hc.displayShardInfoProposal(metaLines, header) return tableHeader, metaLines } -func (hc *headersCounter) displayShardInfo(lines []*display.LineData, header *block.MetaBlock) []*display.LineData { - for i := 0; i < len(header.ShardInfo); i++ { - shardData := header.ShardInfo[i] - +func (hc *headersCounter) displayShardInfo(lines []*display.LineData, header data.MetaHeaderHandler) []*display.LineData { + for _, shardData := range header.GetShardInfoHandlers() { lines = append(lines, display.NewLineData(false, []string{ - fmt.Sprintf("ShardData_%d", shardData.ShardID), + fmt.Sprintf("ShardData_%d", shardData.GetShardID()), "Header hash", - logger.DisplayByteSlice(shardData.HeaderHash)})) + logger.DisplayByteSlice(shardData.GetHeaderHash())})) - if len(shardData.ShardMiniBlockHeaders) == 0 { + if len(shardData.GetShardMiniBlockHeaderHandlers()) == 0 { lines = append(lines, display.NewLineData(false, []string{ "", "ShardMiniBlockHeaders", ""})) } - for j := 0; j < len(shardData.ShardMiniBlockHeaders); j++ { - if j == 0 || j >= len(shardData.ShardMiniBlockHeaders)-1 { - senderShard := shardData.ShardMiniBlockHeaders[j].SenderShardID - receiverShard := shardData.ShardMiniBlockHeaders[j].ReceiverShardID + for j := 0; j < len(shardData.GetShardMiniBlockHeaderHandlers()); j++ { + if j == 0 || j >= len(shardData.GetShardMiniBlockHeaderHandlers())-1 { + senderShard := shardData.GetShardMiniBlockHeaderHandlers()[j].GetSenderShardID() + receiverShard := shardData.GetShardMiniBlockHeaderHandlers()[j].GetReceiverShardID() lines = append(lines, display.NewLineData(false, []string{ "", fmt.Sprintf("%d ShardMiniBlockHeaderHash_%d_%d", j+1, senderShard, receiverShard), - logger.DisplayByteSlice(shardData.ShardMiniBlockHeaders[j].Hash)})) + logger.DisplayByteSlice(shardData.GetShardMiniBlockHeaderHandlers()[j].GetHash())})) } else if j == 1 { lines = append(lines, display.NewLineData(false, []string{ "", @@ -179,6 +178,32 @@ func (hc *headersCounter) displayShardInfo(lines []*display.LineData, header *bl return lines } +func (hc *headersCounter) displayShardInfoProposal(lines []*display.LineData, header data.MetaHeaderHandler) []*display.LineData { + for _, shardDataProposal := range header.GetShardInfoProposalHandlers() { + lines = append(lines, []*display.LineData{ + display.NewLineData(false, []string{ + fmt.Sprintf("ShardDataProposal_%d", shardDataProposal.GetShardID()), + "Hash", + logger.DisplayByteSlice(shardDataProposal.GetHeaderHash()), + }), + display.NewLineData(false, []string{ + "", + "Epoch", + fmt.Sprintf("%d", shardDataProposal.GetEpoch()), + }), + display.NewLineData(false, []string{ + "", + "Nonce", + fmt.Sprintf("%d", shardDataProposal.GetNonce()), + }), + }...) + + lines[len(lines)-1].HorizontalRuleAfter = true + } + + return lines +} + func (hc *headersCounter) displayTxBlockBody( lines []*display.LineData, header data.HeaderHandler, @@ -246,14 +271,14 @@ func (hc *headersCounter) getNumShardMBHeadersTotalProcessed() uint64 { } func displayEpochStartMetaBlock( - block *block.MetaBlock, + block data.MetaHeaderHandler, headerProof data.HeaderProofHandler, ) []*display.LineData { lines := displayHeader(block, headerProof) - economicsLines := displayEconomicsData(block.EpochStart.Economics) + economicsLines := displayEconomicsData(block.GetEpochStartHandler().GetEconomicsHandler()) lines = append(lines, economicsLines...) - for _, shardData := range block.EpochStart.LastFinalizedHeaders { + for _, shardData := range block.GetEpochStartHandler().GetLastFinalizedHeaderHandlers() { shardDataLines := displayEpochStartShardData(shardData) lines = append(lines, shardDataLines...) } @@ -261,36 +286,36 @@ func displayEpochStartMetaBlock( return lines } -func displayEpochStartShardData(shardData block.EpochStartShardData) []*display.LineData { +func displayEpochStartShardData(shardData data.EpochStartShardDataHandler) []*display.LineData { lines := []*display.LineData{ display.NewLineData(false, []string{ - fmt.Sprintf("EpochStartShardData - Shard %d", shardData.ShardID), + fmt.Sprintf("EpochStartShardData - Shard %d", shardData.GetShardID()), "Round", - fmt.Sprintf("%d", shardData.Round)}), + fmt.Sprintf("%d", shardData.GetRound())}), display.NewLineData(false, []string{ "", "Nonce", - fmt.Sprintf("%d", shardData.Nonce)}), + fmt.Sprintf("%d", shardData.GetNonce())}), display.NewLineData(false, []string{ "", "FirstPendingMetaBlock", - logger.DisplayByteSlice(shardData.FirstPendingMetaBlock)}), + logger.DisplayByteSlice(shardData.GetFirstPendingMetaBlock())}), display.NewLineData(false, []string{ "", "LastFinishedMetaBlock", - logger.DisplayByteSlice(shardData.LastFinishedMetaBlock)}), + logger.DisplayByteSlice(shardData.GetLastFinishedMetaBlock())}), display.NewLineData(false, []string{ "", "HeaderHash", - logger.DisplayByteSlice(shardData.HeaderHash)}), + logger.DisplayByteSlice(shardData.GetHeaderHash())}), display.NewLineData(false, []string{ "", "RootHash", - logger.DisplayByteSlice(shardData.RootHash)}), + logger.DisplayByteSlice(shardData.GetRootHash())}), display.NewLineData(false, []string{ "", "PendingMiniBlockHeaders count", - fmt.Sprintf("%d", len(shardData.PendingMiniBlockHeaders))}), + fmt.Sprintf("%d", len(shardData.GetPendingMiniBlockHeaderHandlers()))}), } lines[len(lines)-1].HorizontalRuleAfter = true @@ -298,35 +323,35 @@ func displayEpochStartShardData(shardData block.EpochStartShardData) []*display. return lines } -func displayEconomicsData(economics block.Economics) []*display.LineData { +func displayEconomicsData(economics data.EconomicsHandler) []*display.LineData { return []*display.LineData{ display.NewLineData(false, []string{ "Epoch Start - Economics", "PrevEpochStartHash", - logger.DisplayByteSlice(economics.PrevEpochStartHash)}), + logger.DisplayByteSlice(economics.GetPrevEpochStartHash())}), display.NewLineData(false, []string{ "", "NodePrice", - economics.NodePrice.String()}), + economics.GetNodePrice().String()}), display.NewLineData(false, []string{ "", "RewardsPerBlock", - economics.RewardsPerBlock.String()}), + economics.GetRewardsPerBlock().String()}), display.NewLineData(false, []string{ "", "GenesisTotalSupply", - economics.TotalSupply.String()}), + economics.GetTotalSupply().String()}), display.NewLineData(false, []string{ "", "TotalNewlyMinted", - economics.TotalNewlyMinted.String()}), + economics.GetTotalNewlyMinted().String()}), display.NewLineData(false, []string{ "", "TotalToDistribute", - economics.TotalToDistribute.String()}), + economics.GetTotalToDistribute().String()}), display.NewLineData(true, []string{ "", "PrevEpochStartRound", - fmt.Sprintf("%d", economics.PrevEpochStartRound)}), + fmt.Sprintf("%d", economics.GetPrevEpochStartRound())}), } } diff --git a/process/block/errors.go b/process/block/errors.go new file mode 100644 index 00000000000..3c67155c122 --- /dev/null +++ b/process/block/errors.go @@ -0,0 +1,11 @@ +package block + +import "errors" + +var errInvalidNumOutGoingMBInMetaHdrProposal = errors.New("invalid number of outgoing miniblocks in meta header proposal, should be zero") + +var errInvalidNumOutGoingTxsInMetaHdrProposal = errors.New("invalid number of outgoing transactions in meta header proposal, should be zero") + +var errNilPreviousHeader = errors.New("nil previous header") + +var errInvalidMiniBlocks = errors.New("invalid mini blocks") diff --git a/process/block/executionResultsVerifier.go b/process/block/executionResultsVerifier.go new file mode 100644 index 00000000000..5de45233a14 --- /dev/null +++ b/process/block/executionResultsVerifier.go @@ -0,0 +1,187 @@ +package block + +import ( + "fmt" + "sync" + + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/process" +) + +// executionResultsVerifier is a struct that checks the execution results of a shard header +type executionResultsVerifier struct { + blockChain data.ChainHandler + executionManager process.ExecutionManager + + lastVerifiedHeaderWithError data.HeaderHandler + mutLastVerifiedHeaderWithError sync.RWMutex +} + +// NewExecutionResultsVerifier creates a new instance of executionResultsVerifier +func NewExecutionResultsVerifier(blockChain data.ChainHandler, executionManager process.ExecutionManager) (*executionResultsVerifier, error) { + if check.IfNil(blockChain) { + return nil, process.ErrNilBlockChain + } + if check.IfNil(executionManager) { + return nil, process.ErrNilExecutionManager + } + return &executionResultsVerifier{ + blockChain: blockChain, + executionManager: executionManager, + }, nil +} + +// VerifyHeaderExecutionResults checks the execution results of a shard header +func (erc *executionResultsVerifier) VerifyHeaderExecutionResults(header data.HeaderHandler) error { + if check.IfNil(header) { + return process.ErrNilHeaderHandler + } + if !header.IsHeaderV3() { + return process.ErrInvalidHeader + } + if header.GetNonce() < 1 { + return nil + } + + return erc.verifyExecutionResults(header) +} + +func (erc *executionResultsVerifier) verifyExecutionResults( + header data.HeaderHandler, +) error { + err := erc.verifyLastExecutionResultInfoMatchesLastExecutionResult(header) + if err != nil { + return err + } + + // if header is received, the notarized execution results should already be available in the tracker + // if not all present, then verify fails + executionResults := header.GetExecutionResultsHandlers() + pendingExecutionResults, err := erc.executionManager.GetPendingExecutionResults() + if err != nil { + return err + } + + if len(pendingExecutionResults) < len(executionResults) { + if erc.setLastVerifiedHeaderWithErrorIfNeeded(header) { + log.Debug("verifyExecutionResults", + "pendingExecutionResults", len(pendingExecutionResults), + "header executionResults", len(executionResults), + "header nonce", header.GetNonce(), + ) + } + return process.ErrExecutionResultsNumberMismatch + } + + for i := 0; i < len(executionResults)-1; i++ { + if executionResults[i].GetHeaderNonce() != executionResults[i+1].GetHeaderNonce()-1 { + return process.ErrExecutionResultsNonConsecutive + } + } + + for i, headerExecRes := range executionResults { + if !headerExecRes.Equal(pendingExecutionResults[i]) { + headerExecResOutput, _ := common.PrettifyStruct(headerExecRes) + pendingExecResOutput, _ := common.PrettifyStruct(pendingExecutionResults[i]) + + log.Debug("verifyExecutionResults: results not matching", + "header nonce", header.GetNonce(), + "header execution result", headerExecResOutput, + "pending execution result", pendingExecResOutput, + ) + + return process.ErrExecutionResultDoesNotMatch + } + } + + return nil +} + +func (erc *executionResultsVerifier) setLastVerifiedHeaderWithErrorIfNeeded(header data.HeaderHandler) bool { + erc.mutLastVerifiedHeaderWithError.RLock() + lastVerifiedHeader := erc.lastVerifiedHeaderWithError + erc.mutLastVerifiedHeaderWithError.RUnlock() + + if !check.IfNil(lastVerifiedHeader) && + lastVerifiedHeader.GetNonce() == header.GetNonce() && + lastVerifiedHeader.GetRound() == header.GetRound() { + return false + } + + erc.mutLastVerifiedHeaderWithError.Lock() + erc.lastVerifiedHeaderWithError = header + erc.mutLastVerifiedHeaderWithError.Unlock() + return true +} + +func (erc *executionResultsVerifier) verifyLastExecutionResultInfoMatchesLastExecutionResult( + header data.HeaderHandler, +) error { + executionResults := header.GetExecutionResultsHandlers() + lastExecutionResultInfo := header.GetLastExecutionResultHandler() + shardID := header.GetShardID() + if check.IfNil(lastExecutionResultInfo) { + return fmt.Errorf("%w: for current block", process.ErrNilLastExecutionResultHandler) + } + + prevLastExecutionResultInfo, err := process.GetPrevBlockLastExecutionResult(erc.blockChain) + if err != nil { + return err + } + + // if no execution results are present, we only check if the last execution result info matches the previous reported one + if len(executionResults) == 0 { + if !lastExecutionResultInfo.Equal(prevLastExecutionResultInfo) { + return process.ErrExecutionResultDoesNotMatch + } + + return nil + } + + lastExecResult := executionResults[len(executionResults)-1] + lastExecResultInfo, err := process.CreateLastExecutionResultInfoFromExecutionResult(header.GetRound(), lastExecResult, shardID) + if err != nil { + return err + } + + if !lastExecutionResultInfo.Equal(lastExecResultInfo) { + return process.ErrExecutionResultDoesNotMatch + } + + err = erc.checkFirstExecutionResultAgainstPrevBlock(prevLastExecutionResultInfo, executionResults) + if err != nil { + return err + } + + return nil +} + +func (erc *executionResultsVerifier) checkFirstExecutionResultAgainstPrevBlock( + prevLastExecutionResultsHandler data.LastExecutionResultHandler, + executionResults []data.BaseExecutionResultHandler, +) error { + var prevNonce uint64 + switch prev := prevLastExecutionResultsHandler.(type) { + case *block.ExecutionResultInfo: + prevNonce = prev.GetExecutionResult().GetHeaderNonce() + case *block.MetaExecutionResultInfo: + prevNonce = prev.GetExecutionResult().GetHeaderNonce() + default: + return process.ErrWrongTypeAssertion + } + // if execution results are present, we check if the previous last execution result info matches the first execution result + if executionResults[0].GetHeaderNonce() != prevNonce+1 { + return fmt.Errorf("%w for first execution result", process.ErrExecutionResultsNonConsecutive) + } + + return nil +} + +// IsInterfaceNil returns true if there is no value under the interface +func (erc *executionResultsVerifier) IsInterfaceNil() bool { + return erc == nil +} diff --git a/process/block/executionResultsVerifier_test.go b/process/block/executionResultsVerifier_test.go new file mode 100644 index 00000000000..6fb674be3f2 --- /dev/null +++ b/process/block/executionResultsVerifier_test.go @@ -0,0 +1,732 @@ +package block + +import ( + "fmt" + "math/big" + "testing" + + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/testscommon" +) + +func TestNewExecutionResultsVerifier(t *testing.T) { + t.Parallel() + + blockchain := &testscommon.ChainHandlerMock{} + executionManager := &processMocks.ExecutionManagerMock{} + t.Run("nil blockchain", func(t *testing.T) { + t.Parallel() + erv, err := NewExecutionResultsVerifier(nil, nil) + require.Equal(t, process.ErrNilBlockChain, err) + require.Nil(t, erv) + }) + t.Run("nil execution manager", func(t *testing.T) { + t.Parallel() + erv, err := NewExecutionResultsVerifier(blockchain, nil) + require.Equal(t, process.ErrNilExecutionManager, err) + require.Nil(t, erv) + }) + t.Run("valid parameters", func(t *testing.T) { + t.Parallel() + erv, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + require.NotNil(t, erv) + require.Equal(t, blockchain, erv.blockChain) + require.Equal(t, executionManager, erv.executionManager) + }) +} + +func TestExecutionResultsVerifier_verifyLastExecutionResultInfoMatchesLastExecutionResult(t *testing.T) { + t.Parallel() + + notarizedInRound := uint64(3) + t.Run("nil last execution result info", func(t *testing.T) { + t.Parallel() + header := createDummyPrevShardHeaderV2() + blockchain := &testscommon.ChainHandlerStub{} + executionManager := &processMocks.ExecutionManagerMock{} + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + + err = erc.verifyLastExecutionResultInfoMatchesLastExecutionResult(header) + require.ErrorIs(t, err, process.ErrNilLastExecutionResultHandler) + }) + t.Run("prev header get error", func(t *testing.T) { + t.Parallel() + header := createShardHeaderV3WithExecutionResults() + + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return nil + }, + } + executionManager := &processMocks.ExecutionManagerMock{} + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + + err = erc.verifyLastExecutionResultInfoMatchesLastExecutionResult(header) + require.Equal(t, process.ErrNilHeaderHandler, err) + }) + t.Run("no execution results, keeps last execution results", func(t *testing.T) { + t.Parallel() + prevHeaderHash := []byte("prevHeaderHash") + prevHeader := createShardHeaderV3WithExecutionResults() + header := &block.HeaderV3{ + PrevHash: prevHeader.GetPrevHash(), + Nonce: prevHeader.GetNonce() + 1, + Round: prevHeader.GetRound() + 1, + ShardID: prevHeader.GetShardID(), + LastExecutionResult: prevHeader.LastExecutionResult, + } + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return prevHeader + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return prevHeaderHash + }, + } + executionManager := &processMocks.ExecutionManagerMock{} + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + + err = erc.verifyLastExecutionResultInfoMatchesLastExecutionResult(header) + require.NoError(t, err) + }) + t.Run("last execution result info does not match previous block, nil execution results", func(t *testing.T) { + t.Parallel() + prevHeaderHash := []byte("prevHeaderHash") + prevHeader := createShardHeaderV3WithExecutionResults() + lastExecutionResult := createLastExecutionResultShard() + lastExecutionResult.ExecutionResult.HeaderHash = []byte("differentHeaderHash") // Modify to ensure mismatch + header := &block.HeaderV3{ + PrevHash: prevHeaderHash, + Nonce: prevHeader.GetNonce() + 1, + Round: prevHeader.GetRound() + 1, + ShardID: prevHeader.GetShardID(), + LastExecutionResult: lastExecutionResult, + } + + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return prevHeader + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return prevHeaderHash + }, + } + executionManager := &processMocks.ExecutionManagerMock{} + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + + err = erc.verifyLastExecutionResultInfoMatchesLastExecutionResult(header) + require.Equal(t, process.ErrExecutionResultDoesNotMatch, err) + }) + t.Run("last execution result info (shard) matches previous block, nil execution results", func(t *testing.T) { + t.Parallel() + prevHeaderHash := []byte("prevHeaderHash") + prevHeader := createShardHeaderV3WithExecutionResults() + lastExecutionResult := createLastExecutionResultShard() + header := &block.HeaderV3{ + PrevHash: prevHeaderHash, + Nonce: prevHeader.GetNonce() + 1, + Round: prevHeader.GetRound() + 1, + ShardID: prevHeader.GetShardID(), + LastExecutionResult: lastExecutionResult, + } + + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return prevHeader + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return prevHeaderHash + }, + } + executionManager := &processMocks.ExecutionManagerMock{} + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + + err = erc.verifyLastExecutionResultInfoMatchesLastExecutionResult(header) + require.NoError(t, err) + }) + t.Run("last execution result info (meta) matches previous block, nil execution results", func(t *testing.T) { + t.Parallel() + + prevHeaderHash := []byte("prevHeaderHash") + prevHeader := createMetaHeaderV3WithExecutionResults() + lastExecutionResult := createLastExecutionResultMeta() + header := &block.MetaBlockV3{ + PrevHash: prevHeaderHash, + Nonce: prevHeader.GetNonce() + 1, + Round: prevHeader.GetRound() + 1, + LastExecutionResult: lastExecutionResult, + } + + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return createMetaHeaderV3WithExecutionResults() + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return prevHeaderHash + }, + } + executionManager := &processMocks.ExecutionManagerMock{} + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + + err = erc.verifyLastExecutionResultInfoMatchesLastExecutionResult(header) + require.NoError(t, err) + }) + t.Run("create last execution result from execution result error (nil execution result)", func(t *testing.T) { + t.Parallel() + prevHeaderHash := []byte("prevHash") + header := &block.HeaderV3{ + PrevHash: prevHeaderHash, + Nonce: 1, + Round: 2, + ShardID: 0, + ExecutionResults: make([]*block.ExecutionResult, 4), + LastExecutionResult: &block.ExecutionResultInfo{}, // No last execution result info + } + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return prevHeaderHash + }, + } + executionManager := &processMocks.ExecutionManagerMock{} + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + + err = erc.verifyLastExecutionResultInfoMatchesLastExecutionResult(header) + require.Equal(t, process.ErrNilExecutionResultHandler, err) + }) + t.Run("execution results mismatch", func(t *testing.T) { + t.Parallel() + header := createShardHeaderV3WithExecutionResults() + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("headerHash") + }, + } + executionManager := &processMocks.ExecutionManagerMock{} + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + + err = erc.verifyLastExecutionResultInfoMatchesLastExecutionResult(header) + require.Equal(t, process.ErrExecutionResultDoesNotMatch, err) + }) + t.Run("first execution result not consecutive to prevHeader last execution result", func(t *testing.T) { + t.Parallel() + prevHeader := createShardHeaderV3WithMultipleExecutionResults(1, 5) + prevHeader.LastExecutionResult.ExecutionResult.HeaderNonce = big.NewInt(10).Uint64() + header := createShardHeaderV3WithMultipleExecutionResults(3, 6) + header.ExecutionResults[0].BaseExecutionResult.HeaderNonce = 100 // Modify to ensure mismatch + + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return prevHeader + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return header.GetPrevHash() + }, + } + executionManager := &processMocks.ExecutionManagerMock{} + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + + err = erc.verifyLastExecutionResultInfoMatchesLastExecutionResult(header) + require.ErrorIs(t, err, process.ErrExecutionResultsNonConsecutive) + }) + t.Run("valid execution results for shard", func(t *testing.T) { + t.Parallel() + prevHeader := createShardHeaderV3WithExecutionResults() + prevHeader.LastExecutionResult.ExecutionResult.HeaderNonce = prevHeader.LastExecutionResult.ExecutionResult.HeaderNonce - 1 + header := createShardHeaderV3WithExecutionResults() + header.ExecutionResults[len(header.ExecutionResults)-1] = createDummyShardExecutionResult() + lastExecutionResult, err := process.CreateLastExecutionResultInfoFromExecutionResult(notarizedInRound, header.ExecutionResults[len(header.ExecutionResults)-1], header.GetShardID()) + require.Nil(t, err) + header.LastExecutionResult = lastExecutionResult.(*block.ExecutionResultInfo) + + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return prevHeader + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return header.GetPrevHash() + }, + } + executionManager := &processMocks.ExecutionManagerMock{} + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + + err = erc.verifyLastExecutionResultInfoMatchesLastExecutionResult(header) + require.NoError(t, err) + }) +} + +func TestExecutionResultsVerifier_VerifyHeaderExecutionResults(t *testing.T) { + t.Parallel() + + t.Run("nil header", func(t *testing.T) { + t.Parallel() + blockchain := &testscommon.ChainHandlerStub{} + executionManager := &processMocks.ExecutionManagerMock{} + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + + err = erc.VerifyHeaderExecutionResults(nil) + require.Equal(t, process.ErrNilHeaderHandler, err) + }) + + t.Run("no execution results", func(t *testing.T) { + t.Parallel() + header := &block.HeaderV3{ + PrevHash: []byte("prevHash"), + Nonce: 1, + Round: 2, + ShardID: 0, + LastExecutionResult: createLastExecutionResultShard(), + ExecutionResults: nil, + } + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("headerHash") + }, + } + executionManager := &processMocks.ExecutionManagerMock{} + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + + err = erc.VerifyHeaderExecutionResults(header) + require.NoError(t, err) + }) + t.Run("invalid header type", func(t *testing.T) { + t.Parallel() + header := &block.Header{ + Nonce: 1, + Round: 2, + RootHash: []byte("rootHash"), + ShardID: 0, + } + blockchain := &testscommon.ChainHandlerStub{} + executionManager := &processMocks.ExecutionManagerMock{} + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + + err = erc.VerifyHeaderExecutionResults(header) + require.Equal(t, process.ErrInvalidHeader, err) + }) + t.Run("always valid for genesis header (nonce < 0)", func(t *testing.T) { + t.Parallel() + header := &block.HeaderV3{ + PrevHash: []byte("prevHash"), + Nonce: 0, + Round: 0, + ShardID: 0, + LastExecutionResult: createLastExecutionResultShard(), + ExecutionResults: nil, + } + blockchain := &testscommon.ChainHandlerStub{} + executionManager := &processMocks.ExecutionManagerMock{} + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + + err = erc.VerifyHeaderExecutionResults(header) + require.NoError(t, err) + }) + t.Run("header with nil last execution results", func(t *testing.T) { + t.Parallel() + header := &block.HeaderV3{ + PrevHash: []byte("prevHash"), + Nonce: 1, + Round: 2, + ShardID: 0, + LastExecutionResult: nil, // No last execution result info + ExecutionResults: nil, + } + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("headerHash") + }, + } + executionManager := &processMocks.ExecutionManagerMock{} + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + + err = erc.VerifyHeaderExecutionResults(header) + require.ErrorIs(t, err, process.ErrNilLastExecutionResultHandler) + }) + t.Run("error getting pending execution results", func(t *testing.T) { + t.Parallel() + + header := createShardHeaderV3WithExecutionResults() + prevHeader := createPrevShardHeaderV3WithoutExecutionResults() + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return prevHeader + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return header.GetPrevHash() + }, + } + + lastExecutionResult, err := process.CreateLastExecutionResultInfoFromExecutionResult(header.GetRound(), header.ExecutionResults[len(header.ExecutionResults)-1], header.GetShardID()) + require.NoError(t, err) + header.LastExecutionResult = lastExecutionResult.(*block.ExecutionResultInfo) + + expectedErr := fmt.Errorf("error getting pending execution results") + executionManager := &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return nil, expectedErr + }, + } + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + + err = erc.VerifyHeaderExecutionResults(header) + require.Equal(t, expectedErr, err) + }) + t.Run("execution results number mismatch", func(t *testing.T) { + t.Parallel() + + header := createShardHeaderV3WithExecutionResults() + prevHeader := createPrevShardHeaderV3WithoutExecutionResults() + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return prevHeader + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return header.GetPrevHash() + }, + } + + lastExecutionResult, err := process.CreateLastExecutionResultInfoFromExecutionResult(header.GetRound(), header.ExecutionResults[len(header.ExecutionResults)-1], header.GetShardID()) + require.NoError(t, err) + header.LastExecutionResult = lastExecutionResult.(*block.ExecutionResultInfo) + executionManager := &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return nil, nil // less than expected number of execution results + }, + } + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + + err = erc.VerifyHeaderExecutionResults(header) + require.Equal(t, process.ErrExecutionResultsNumberMismatch, err) + }) + t.Run("execution results mismatch", func(t *testing.T) { + t.Parallel() + + prevHeader := createPrevShardHeaderV3WithoutExecutionResults() + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return prevHeader + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("prevHeaderHash") + }, + } + + header := createShardHeaderV3WithExecutionResults() + lastExecutionResult, err := process.CreateLastExecutionResultInfoFromExecutionResult(header.GetRound(), header.ExecutionResults[len(header.ExecutionResults)-1], header.GetShardID()) + require.NoError(t, err) + header.LastExecutionResult = lastExecutionResult.(*block.ExecutionResultInfo) + + executionManager := &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + result := make([]data.BaseExecutionResultHandler, len(header.ExecutionResults)) + for i := range header.ExecutionResults { + differentResult := *header.ExecutionResults[i] + differentBase := *differentResult.BaseExecutionResult + differentBase.GasUsed = 100000 // Modify to ensure mismatch + differentResult.BaseExecutionResult = &differentBase + result[i] = &differentResult + } + return result, nil + }, + } + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + + err = erc.VerifyHeaderExecutionResults(header) + require.Equal(t, process.ErrExecutionResultDoesNotMatch, err) + }) + t.Run("execution results not consecutive", func(t *testing.T) { + t.Parallel() + + prevHeader := createShardHeaderV3WithMultipleExecutionResults(1, 1) + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return prevHeader + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("prevHeaderHash") + }, + } + + header := createShardHeaderV3WithMultipleExecutionResults(3, 2) + // remove middle execution results, so they become not consecutive + header.ExecutionResults = []*block.ExecutionResult{header.ExecutionResults[0], header.ExecutionResults[2]} + executionManager := &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + result := make([]data.BaseExecutionResultHandler, len(header.ExecutionResults)) + for i := range header.ExecutionResults { + result[i] = header.ExecutionResults[i] + } + + return result, nil + }, + } + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + + err = erc.VerifyHeaderExecutionResults(header) + require.Equal(t, process.ErrExecutionResultsNonConsecutive, err) + }) + t.Run("execution results consecutive", func(t *testing.T) { + t.Parallel() + + header := createShardHeaderV3WithMultipleExecutionResults(3, 2) + prevHeader := createShardHeaderV3WithMultipleExecutionResults(1, 1) + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return prevHeader + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return header.GetPrevHash() + }, + } + + executionManager := &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + result := make([]data.BaseExecutionResultHandler, len(header.ExecutionResults)) + for i := range header.ExecutionResults { + result[i] = header.ExecutionResults[i] + } + + return result, nil + }, + } + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + + err = erc.VerifyHeaderExecutionResults(header) + require.NoError(t, err) + }) + t.Run("valid execution results for shard", func(t *testing.T) { + t.Parallel() + prevHeader := createPrevShardHeaderV3WithoutExecutionResults() + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return prevHeader + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("prevHeaderHash") + }, + } + + header := createShardHeaderV3WithExecutionResults() + lastExecutionResult, err := process.CreateLastExecutionResultInfoFromExecutionResult(header.GetRound(), header.ExecutionResults[len(header.ExecutionResults)-1], header.GetShardID()) + require.NoError(t, err) + header.LastExecutionResult = lastExecutionResult.(*block.ExecutionResultInfo) + + executionManager := &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + result := make([]data.BaseExecutionResultHandler, len(header.ExecutionResults)) + for i := range header.ExecutionResults { + result[i] = header.ExecutionResults[i] + } + + return result, nil + }, + } + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + require.NoError(t, err) + + err = erc.VerifyHeaderExecutionResults(header) + require.NoError(t, err) + }) +} + +func TestExecutionResultsVerifier_IsInterfaceNil(t *testing.T) { + t.Parallel() + var erc ExecutionResultsVerifier + require.True(t, check.IfNil(erc)) + + executionManager := &processMocks.ExecutionManagerMock{} + blockchain := &testscommon.ChainHandlerStub{} + erc, err := NewExecutionResultsVerifier(blockchain, executionManager) + + require.Nil(t, err) + require.False(t, check.IfNil(erc)) +} + +func createDummyShardExecutionResults(numResults int, firstNonce uint64) []*block.ExecutionResult { + results := make([]*block.ExecutionResult, numResults) + for i := 0; i < numResults; i++ { + results[i] = &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash"), + HeaderNonce: firstNonce + uint64(i), + HeaderRound: firstNonce + uint64(i) + 1, + GasUsed: 10000000, + RootHash: []byte("rootHash"), + }, + ReceiptsHash: []byte("receiptsHash"), + MiniBlockHeaders: nil, + DeveloperFees: big.NewInt(100), + AccumulatedFees: big.NewInt(200), + ExecutedTxCount: 100, + } + } + return results +} + +func createDummyShardExecutionResult() *block.ExecutionResult { + return &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash"), + HeaderNonce: 1, + HeaderRound: 2, + GasUsed: 10000000, + RootHash: []byte("rootHash"), + }, + ReceiptsHash: []byte("receiptsHash"), + MiniBlockHeaders: nil, + DeveloperFees: big.NewInt(100), + AccumulatedFees: big.NewInt(200), + ExecutedTxCount: 100, + } +} + +func createDummyMetaExecutionResult() *block.MetaExecutionResult { + return &block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash"), + HeaderNonce: 1, + HeaderRound: 2, + GasUsed: 20000000, + RootHash: []byte("rootHash"), + }, + ValidatorStatsRootHash: []byte("validatorStatsRootHash"), + AccumulatedFeesInEpoch: big.NewInt(300), + DevFeesInEpoch: big.NewInt(400), + }, + ReceiptsHash: []byte("receiptsHash"), + DeveloperFees: big.NewInt(500), + AccumulatedFees: big.NewInt(600), + ExecutedTxCount: 200, + } +} + +func createDummyPrevShardHeaderV2() *block.HeaderV2 { + return &block.HeaderV2{ + Header: &block.Header{ + Nonce: 1, + Round: 2, + RootHash: []byte("prevRootHash"), + }, + } +} + +func createLastExecutionResultShard() *block.ExecutionResultInfo { + return &block.ExecutionResultInfo{ + NotarizedInRound: 3, + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash"), + HeaderNonce: 1, + HeaderRound: 2, + RootHash: []byte("rootHash"), + }, + } +} + +func createLastExecutionResultMeta() *block.MetaExecutionResultInfo { + return &block.MetaExecutionResultInfo{ + NotarizedInRound: 3, + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash"), + HeaderNonce: 1, + HeaderRound: 2, + RootHash: []byte("rootHash"), + }, + ValidatorStatsRootHash: []byte("validatorStatsRootHash"), + AccumulatedFeesInEpoch: big.NewInt(300), + DevFeesInEpoch: big.NewInt(400), + }, + } +} + +func createShardHeaderV3WithMultipleExecutionResults(numResults int, firstNonce uint64) *block.HeaderV3 { + executionResults := createDummyShardExecutionResults(numResults, firstNonce) + lastExecResult, _ := process.CreateLastExecutionResultInfoFromExecutionResult(3+uint64(numResults)+firstNonce, executionResults[len(executionResults)-1], 0) + + return &block.HeaderV3{ + PrevHash: []byte("prevHash"), + Nonce: 2 + uint64(numResults) + firstNonce, + Round: 3 + uint64(numResults) + firstNonce, + ShardID: 0, + ExecutionResults: executionResults, + LastExecutionResult: lastExecResult.(*block.ExecutionResultInfo), + } +} + +func createShardHeaderV3WithExecutionResults() *block.HeaderV3 { + executionResult := createDummyShardExecutionResult() + lastExecutionResult := createLastExecutionResultShard() + return &block.HeaderV3{ + PrevHash: []byte("prevHash"), + Nonce: 2, + Round: 3, + ShardID: 0, + ExecutionResults: []*block.ExecutionResult{executionResult}, + LastExecutionResult: lastExecutionResult, + } +} + +func createPrevShardHeaderV3WithoutExecutionResults() *block.HeaderV3 { + lastExecutionResults := createLastExecutionResultShard() + lastExecutionResults.ExecutionResult.HeaderNonce = 0 + lastExecutionResults.ExecutionResult.HeaderRound = 1 + + return &block.HeaderV3{ + PrevHash: []byte("prevHash"), + Nonce: 1, + Round: 2, + ShardID: 0, + ExecutionResults: nil, + LastExecutionResult: lastExecutionResults, + } +} + +func createMetaHeaderV3WithExecutionResults() *block.MetaBlockV3 { + executionResults := createDummyMetaExecutionResult() + lastExecutionResult := createLastExecutionResultMeta() + return &block.MetaBlockV3{ + Nonce: 3, + Round: 4, + LastExecutionResult: lastExecutionResult, + ExecutionResults: []*block.MetaExecutionResult{executionResults}, + } +} diff --git a/process/block/export_test.go b/process/block/export_test.go index 750ff2ee15c..d3ee76c2490 100644 --- a/process/block/export_test.go +++ b/process/block/export_test.go @@ -1,7 +1,9 @@ package block import ( + "math/big" "sync" + "sync/atomic" "time" "github.com/multiversx/mx-chain-core-go/core" @@ -13,16 +15,31 @@ import ( "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/process/aotSelection" + commonMocks "github.com/multiversx/mx-chain-go/testscommon/common" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/common/configs" "github.com/multiversx/mx-chain-go/common/graceperiod" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/asyncExecution" + "github.com/multiversx/mx-chain-go/process/asyncExecution/cache" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionManager" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionTrack" "github.com/multiversx/mx-chain-go/process/block/bootstrapStorage" "github.com/multiversx/mx-chain-go/process/block/processedMb" + "github.com/multiversx/mx-chain-go/process/coordinator" + "github.com/multiversx/mx-chain-go/process/estimator" + "github.com/multiversx/mx-chain-go/process/factory/containers" + "github.com/multiversx/mx-chain-go/process/missingData" "github.com/multiversx/mx-chain-go/process/mock" "github.com/multiversx/mx-chain-go/state" + "github.com/multiversx/mx-chain-go/state/disabled" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/dblookupext" + "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" "github.com/multiversx/mx-chain-go/testscommon/factory" @@ -34,6 +51,18 @@ import ( storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" ) +// HashAndHdr - +type HashAndHdr = hashAndHdr + +// UsedShardHeadersInfo - +type UsedShardHeadersInfo = usedShardHeadersInfo + +// EpochStartDataWrapper - +type EpochStartDataWrapper = epochStartDataWrapper + +// ErrNilPreviousHdr - +var ErrNilPreviousHdr = errNilPreviousHeader + // ComputeHeaderHash - func (bp *baseProcessor) ComputeHeaderHash(hdr data.HeaderHandler) ([]byte, error) { return core.CalculateHash(bp.marshalizer, bp.hasher, hdr) @@ -66,6 +95,11 @@ func (bp *baseProcessor) GetPruningHandler(finalHeaderNonce uint64) state.Prunin return bp.getPruningHandler(finalHeaderNonce) } +// SetClosingNodeStarted - +func (bp *baseProcessor) SetClosingNodeStarted(val bool) { + bp.closingNodeStarted.Store(val) +} + // SetLastRestartNonce - func (bp *baseProcessor) SetLastRestartNonce(lastRestartNonce uint64) { bp.lastRestartNonce = lastRestartNonce @@ -76,29 +110,19 @@ func (bp *baseProcessor) CommitTrieEpochRootHashIfNeeded(metaBlock *block.MetaBl return bp.commitTrieEpochRootHashIfNeeded(metaBlock, rootHash) } -// FilterHeadersWithoutProofs - -func (bp *baseProcessor) FilterHeadersWithoutProofs() (map[string]*hdrInfo, error) { - return bp.filterHeadersWithoutProofs() -} - -// ReceivedMetaBlock - -func (sp *shardProcessor) ReceivedMetaBlock(header data.HeaderHandler, metaBlockHash []byte) { - sp.receivedMetaBlock(header, metaBlockHash) -} - // CreateMiniBlocks - func (sp *shardProcessor) CreateMiniBlocks(haveTime func() bool) (*block.Body, map[string]*processedMb.ProcessedMiniBlockInfo, error) { return sp.createMiniBlocks(haveTime, []byte("random")) } // GetOrderedProcessedMetaBlocksFromHeader - -func (sp *shardProcessor) GetOrderedProcessedMetaBlocksFromHeader(header data.HeaderHandler) ([]data.HeaderHandler, error) { +func (sp *shardProcessor) GetOrderedProcessedMetaBlocksFromHeader(header data.HeaderHandler) ([]data.HeaderHandler, []*hashAndHdr, error) { return sp.getOrderedProcessedMetaBlocksFromHeader(header) } // UpdateCrossShardInfo - -func (sp *shardProcessor) UpdateCrossShardInfo(processedMetaHdrs []data.HeaderHandler) error { - return sp.updateCrossShardInfo(processedMetaHdrs) +func (sp *shardProcessor) UpdateCrossShardInfo(processedMetaHdrs []data.HeaderHandler, partialProcessedMetaBlocks []*hashAndHdr) error { + return sp.updateCrossShardInfo(processedMetaHdrs, partialProcessedMetaBlocks) } // UpdateStateStorage - @@ -110,6 +134,11 @@ func (sp *shardProcessor) UpdateStateStorage(finalHeaders []data.HeaderHandler, sp.updateState(finalHeaders, currShardHeader, currentHeaderHash) } +// PruneTrieHeaderV3 - +func (sp *shardProcessor) PruneTrieHeaderV3(header data.HeaderHandler) { + sp.pruneTrieHeaderV3(header) +} + // NewShardProcessorEmptyWith3shards - func NewShardProcessorEmptyWith3shards( tdp dataRetriever.PoolsHolder, @@ -128,7 +157,30 @@ func NewShardProcessorEmptyWith3shards( accountsDb := make(map[state.AccountsDbIdentifier]state.AccountsAdapter) accountsDb[state.UserAccountsState] = &stateMock.AccountsStub{} + gracePeriod, _ := graceperiod.NewEpochChangeGracePeriod([]config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}) + processConfigsHandler, _ := configs.NewProcessConfigsHandler([]config.ProcessConfigByEpoch{{ + EnableEpoch: 0, + MaxMetaNoncesBehind: 15, + MaxMetaNoncesBehindForGlobalStuck: 30, + MaxShardNoncesBehind: 15, + }}, + []config.ProcessConfigByRound{ + { + EnableRound: 0, + MaxRoundsWithoutNewBlockReceived: 10, + MaxRoundsToKeepUnprocessedMiniBlocks: 50, + MaxRoundsToKeepUnprocessedTransactions: 50, + NumFloodingRoundsSlowReacting: 20, + NumFloodingRoundsFastReacting: 30, + NumFloodingRoundsOutOfSpecs: 40, + MaxConsecutiveRoundsOfRatingDecrease: 600, + MaxBlockProcessingTimeMs: 1000, + }, + }, + &epochNotifier.RoundNotifierStub{}, + ) + coreComponents := &mock.CoreComponentsMock{ IntMarsh: &mock.MarshalizerMock{}, Hash: &hashingMocks.HasherMock{}, @@ -141,6 +193,8 @@ func NewShardProcessorEmptyWith3shards( RoundNotifierField: &epochNotifier.RoundNotifierStub{}, EnableRoundsHandlerField: &testscommon.EnableRoundsHandlerStub{}, EpochChangeGracePeriodHandlerField: gracePeriod, + ProcessConfigsHandlerField: processConfigsHandler, + ClosingNodeStartedField: &atomic.Bool{}, } dataComponents := &mock.DataComponentsMock{ Storage: &storageStubs.ChainStorerStub{}, @@ -158,6 +212,63 @@ func NewShardProcessorEmptyWith3shards( statusCoreComponents := &factory.StatusCoreComponentsStub{ AppStatusHandlerField: &statusHandlerMock.AppStatusHandlerStub{}, } + preprocessors := containers.NewPreProcessorsContainer() + blockDataRequesterArgs := coordinator.BlockDataRequestArgs{ + RequestHandler: &testscommon.RequestHandlerStub{}, + MiniBlockPool: dataComponents.Datapool().MiniBlocks(), + PreProcessors: preprocessors, + ShardCoordinator: boostrapComponents.ShardCoordinator(), + EnableEpochsHandler: coreComponents.EnableEpochsHandler(), + } + // second instance for proposal missing data fetching to avoid interferences + proposalBlockDataRequester, _ := coordinator.NewBlockDataRequester(blockDataRequesterArgs) + + mbSelectionSession, _ := NewMiniBlocksSelectionSession( + boostrapComponents.ShardCoordinator().SelfId(), + coreComponents.InternalMarshalizer(), + coreComponents.Hasher(), + ) + + blocksCache := cache.NewHeaderBodyCache(config.HeaderBodyCacheConfig{}) + executionResultsTracker := executionTrack.NewExecutionResultsTracker() + execManager, _ := executionManager.NewExecutionManager(executionManager.ArgsExecutionManager{ + BlocksCache: blocksCache, + ExecutionResultsTracker: executionResultsTracker, + BlockChain: dataComponents.BlockChain, + Headers: dataComponents.Datapool().Headers(), + PostProcessTransactions: dataComponents.DataPool.PostProcessTransactions(), + ExecutedMiniBlocks: dataComponents.DataPool.ExecutedMiniBlocks(), + StorageService: dataComponents.StorageService(), + Marshaller: coreComponents.InternalMarshalizer(), + ShardCoordinator: boostrapComponents.ShardCoordinator(), + }) + execResultsVerifier, _ := NewExecutionResultsVerifier(dataComponents.BlockChain, execManager) + inclusionEstimator, _ := estimator.NewExecutionResultInclusionEstimator( + config.ExecutionResultInclusionEstimatorConfig{ + SafetyMargin: 110, + MaxResultsPerBlock: 20, + }, + coreComponents.RoundHandler(), + &testscommon.ExecResSizeComputationStub{}, + ) + + missingDataArgs := missingData.ResolverArgs{ + HeadersPool: dataComponents.DataPool.Headers(), + ProofsPool: dataComponents.DataPool.Proofs(), + RequestHandler: &testscommon.RequestHandlerStub{}, + BlockDataRequester: proposalBlockDataRequester, + } + missingDataResolver, _ := missingData.NewMissingDataResolver(missingDataArgs) + + argsGasConsumption := ArgsGasConsumption{ + EconomicsFee: &economicsmocks.EconomicsHandlerMock{}, + ShardCoordinator: boostrapComponents.ShardCoordinator(), + GasHandler: &mock.GasHandlerMock{}, + BlockCapacityOverestimationFactor: 200, + PercentDecreaseLimitsStep: 10, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + } + gasComputation, _ := NewGasConsumption(argsGasConsumption) arguments := ArgShardProcessor{ ArgBaseProcessor: ArgBaseProcessor{ @@ -167,6 +278,7 @@ func NewShardProcessorEmptyWith3shards( StatusComponents: statusComponents, StatusCoreComponents: statusCoreComponents, AccountsDB: accountsDb, + AccountsProposal: &stateMock.AccountsStub{}, ForkDetector: &mock.ForkDetectorMock{}, NodesCoordinator: nodesCoordinator, FeeHandler: &mock.FeeAccumulatorStub{}, @@ -180,32 +292,51 @@ func NewShardProcessorEmptyWith3shards( return nil }, }, - BlockTracker: mock.NewBlockTrackerMock(shardCoordinator, genesisBlocks), - BlockSizeThrottler: &mock.BlockSizeThrottlerStub{}, - Version: "softwareVersion", - HistoryRepository: &dblookupext.HistoryRepositoryStub{}, - GasHandler: &mock.GasHandlerMock{}, - OutportDataProvider: &outport.OutportDataProviderStub{}, - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - ReceiptsRepository: &testscommon.ReceiptsRepositoryStub{}, - BlockProcessingCutoffHandler: &testscommon.BlockProcessingCutoffStub{}, - ManagedPeersHolder: &testscommon.ManagedPeersHolderStub{}, - SentSignaturesTracker: &testscommon.SentSignatureTrackerStub{}, + BlockTracker: mock.NewBlockTrackerMock(shardCoordinator, genesisBlocks), + BlockSizeThrottler: &mock.BlockSizeThrottlerStub{}, + Version: "softwareVersion", + HistoryRepository: &dblookupext.HistoryRepositoryStub{}, + GasHandler: &mock.GasHandlerMock{}, + OutportDataProvider: &outport.OutportDataProviderStub{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + ReceiptsRepository: &testscommon.ReceiptsRepositoryStub{}, + BlockProcessingCutoffHandler: &testscommon.BlockProcessingCutoffStub{}, + ManagedPeersHolder: &testscommon.ManagedPeersHolderStub{}, + SentSignaturesTracker: &testscommon.SentSignatureTrackerStub{}, + StateAccessesCollector: disabled.NewDisabledStateAccessesCollector(), + HeadersForBlock: &testscommon.HeadersForBlockMock{}, + MiniBlocksSelectionSession: mbSelectionSession, + ExecutionResultsVerifier: execResultsVerifier, + MissingDataResolver: missingDataResolver, + ExecutionResultsInclusionEstimator: inclusionEstimator, + GasComputation: gasComputation, + ExecutionManager: execManager, + TxExecutionOrderHandler: &commonMocks.TxExecutionOrderHandlerStub{}, + AOTSelector: aotSelection.NewDisabledAOTSelector(), }, } shardProc, err := NewShardProcessor(arguments) - return shardProc, err -} + if err != nil { + return nil, err + } -// RequestBlockHeaders - -func (mp *metaProcessor) RequestBlockHeaders(header *block.MetaBlock) (uint32, uint32, uint32) { - return mp.requestShardHeaders(header) -} + argsHeaderExecutor := asyncExecution.ArgsHeadersExecutor{ + BlocksCache: blocksCache, + ExecutionTracker: executionResultsTracker, + BlockProcessor: shardProc, + BlockChain: dataComponents.BlockChain, + } + headersExecutor, err := asyncExecution.NewHeadersExecutor(argsHeaderExecutor) + if err != nil { + return nil, err + } + err = execManager.SetHeadersExecutor(headersExecutor) + if err != nil { + return nil, err + } -// ReceivedShardHeader - -func (mp *metaProcessor) ReceivedShardHeader(header data.HeaderHandler, shardHeaderHash []byte) { - mp.receivedShardHeader(header, shardHeaderHash) + return shardProc, nil } // GetDataPool - @@ -213,47 +344,19 @@ func (mp *metaProcessor) GetDataPool() dataRetriever.PoolsHolder { return mp.dataPool } -// AddHdrHashToRequestedList - -func (mp *metaProcessor) AddHdrHashToRequestedList(hdr data.HeaderHandler, hdrHash []byte) { - mp.hdrsForCurrBlock.mutHdrsForBlock.Lock() - defer mp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() - - if mp.hdrsForCurrBlock.hdrHashAndInfo == nil { - mp.hdrsForCurrBlock.hdrHashAndInfo = make(map[string]*hdrInfo) - } - - if mp.hdrsForCurrBlock.highestHdrNonce == nil { - mp.hdrsForCurrBlock.highestHdrNonce = make(map[uint32]uint64, mp.shardCoordinator.NumberOfShards()) - } - - mp.hdrsForCurrBlock.hdrHashAndInfo[string(hdrHash)] = &hdrInfo{hdr: hdr, usedInBlock: true} - mp.hdrsForCurrBlock.missingHdrs++ -} - // IsHdrMissing - func (mp *metaProcessor) IsHdrMissing(hdrHash []byte) bool { - mp.hdrsForCurrBlock.mutHdrsForBlock.RLock() - defer mp.hdrsForCurrBlock.mutHdrsForBlock.RUnlock() - - hdrInfoValue, ok := mp.hdrsForCurrBlock.hdrHashAndInfo[string(hdrHash)] + hdrInfoValue, ok := mp.hdrsForCurrBlock.GetHeaderInfo(string(hdrHash)) if !ok { return true } - return check.IfNil(hdrInfoValue.hdr) + return check.IfNil(hdrInfoValue.GetHeader()) } // CreateShardInfo - -func (mp *metaProcessor) CreateShardInfo() ([]data.ShardDataHandler, error) { - return mp.createShardInfo() -} - -// RequestMissingFinalityAttestingShardHeaders - -func (mp *metaProcessor) RequestMissingFinalityAttestingShardHeaders() uint32 { - mp.hdrsForCurrBlock.mutHdrsForBlock.Lock() - defer mp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() - - return mp.requestMissingFinalityAttestingShardHeaders() +func (mp *metaProcessor) CreateShardInfo(metaHeader data.MetaHeaderHandler) ([]data.ShardDataHandler, error) { + return mp.createShardInfo(metaHeader) } // SaveMetricCrossCheckBlockHeight - @@ -311,13 +414,11 @@ func (bp *baseProcessor) RequestHeadersIfMissing(sortedHdrs []data.HeaderHandler // SetShardBlockFinality - func (mp *metaProcessor) SetShardBlockFinality(val uint32) { - mp.hdrsForCurrBlock.mutHdrsForBlock.Lock() mp.shardBlockFinality = val - mp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() } // SaveLastNotarizedHeader - -func (mp *metaProcessor) SaveLastNotarizedHeader(header *block.MetaBlock) error { +func (mp *metaProcessor) SaveLastNotarizedHeader(header data.MetaHeaderHandler) error { return mp.saveLastNotarizedHeader(header) } @@ -341,11 +442,6 @@ func (bp *baseProcessor) IsHdrConstructionValid(currHdr, prevHdr data.HeaderHand return bp.headerValidator.IsHeaderConstructionValid(currHdr, prevHdr) } -// ChRcvAllHdrs - -func (mp *metaProcessor) ChRcvAllHdrs() chan bool { - return mp.chRcvAllHdrs -} - // UpdateShardsHeadersNonce - func (mp *metaProcessor) UpdateShardsHeadersNonce(key uint32, value uint64) { mp.updateShardHeadersNonce(key, value) @@ -376,17 +472,6 @@ func (sp *shardProcessor) GetHashAndHdrStruct(header data.HeaderHandler, hash [] return &hashAndHdr{header, hash} } -// RequestMissingFinalityAttestingHeaders - -func (sp *shardProcessor) RequestMissingFinalityAttestingHeaders() uint32 { - sp.hdrsForCurrBlock.mutHdrsForBlock.Lock() - defer sp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() - - return sp.requestMissingFinalityAttestingHeaders( - core.MetachainShardId, - sp.metaBlockFinality, - ) -} - // CheckMetaHeadersValidityAndFinality - func (sp *shardProcessor) CheckMetaHeadersValidityAndFinality() error { return sp.checkMetaHeadersValidityAndFinality() @@ -397,6 +482,10 @@ func (sp *shardProcessor) CreateAndProcessMiniBlocksDstMe( haveTime func() bool, ) (block.MiniBlockSlice, uint32, uint32, error) { createAndProcessInfo, err := sp.createAndProcessMiniBlocksDstMe(haveTime) + if err != nil { + return nil, 0, 0, err + } + return createAndProcessInfo.miniBlocks, createAndProcessInfo.numHdrsAdded, createAndProcessInfo.numTxsAdded, err } @@ -434,41 +523,6 @@ func (sp *shardProcessor) GetAllMiniBlockDstMeFromMeta( return sp.getAllMiniBlockDstMeFromMeta(header) } -// SetHdrForCurrentBlock - -func (bp *baseProcessor) SetHdrForCurrentBlock(headerHash []byte, headerHandler data.HeaderHandler, usedInBlock bool) { - bp.hdrsForCurrBlock.mutHdrsForBlock.Lock() - bp.hdrsForCurrBlock.hdrHashAndInfo[string(headerHash)] = &hdrInfo{hdr: headerHandler, usedInBlock: usedInBlock} - bp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() -} - -// SetHighestHdrNonceForCurrentBlock - -func (bp *baseProcessor) SetHighestHdrNonceForCurrentBlock(shardId uint32, value uint64) { - bp.hdrsForCurrBlock.mutHdrsForBlock.Lock() - bp.hdrsForCurrBlock.highestHdrNonce[shardId] = value - bp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() -} - -// LastNotarizedHeaderInfo - -type LastNotarizedHeaderInfo struct { - Header data.HeaderHandler - Hash []byte - NotarizedBasedOnProof bool - HasProof bool -} - -// SetLastNotarizedHeaderForShard - -func (bp *baseProcessor) SetLastNotarizedHeaderForShard(shardId uint32, info *LastNotarizedHeaderInfo) { - bp.hdrsForCurrBlock.mutHdrsForBlock.Lock() - lastNotarizedShardInfo := &lastNotarizedHeaderInfo{ - header: info.Header, - hash: info.Hash, - notarizedBasedOnProof: info.NotarizedBasedOnProof, - hasProof: info.HasProof, - } - bp.hdrsForCurrBlock.lastNotarizedShardHeaders[shardId] = lastNotarizedShardInfo - bp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() -} - // CreateBlockStarted - func (bp *baseProcessor) CreateBlockStarted() error { return bp.createBlockStarted() @@ -544,7 +598,12 @@ func (bp *baseProcessor) UpdateState( prevRootHash []byte, accounts state.AccountsAdapter, ) { - bp.updateStateStorage(finalHeader, rootHash, prevRootHash, accounts) + bp.updateStateStorage(finalHeader.GetNonce(), rootHash, prevRootHash, accounts) +} + +// UpdateState - +func (mp *metaProcessor) UpdateState(metaBlock data.MetaHeaderHandler, metaBlockHash []byte) { + mp.updateState(metaBlock, metaBlockHash) } // GasAndFeesDelta - @@ -581,8 +640,8 @@ func (bp *baseProcessor) GetIndexOfFirstMiniBlockToBeExecuted(header data.Header } // GetFinalMiniBlocks - -func (bp *baseProcessor) GetFinalMiniBlocks(header data.HeaderHandler, body *block.Body) (*block.Body, error) { - return bp.getFinalMiniBlocks(header, body) +func (bp *baseProcessor) GetFinalMiniBlocks(headerHash []byte, header data.HeaderHandler, body *block.Body) (*block.Body, map[string]block.MiniBlockSlice, error) { + return bp.getFinalMiniBlocks(headerHash, header, body) } // GetScheduledMiniBlocksFromMe - @@ -689,145 +748,496 @@ func (bp *baseProcessor) CheckSentSignaturesAtCommitTime(header data.HeaderHandl } // GetHdrForBlock - -func (mp *metaProcessor) GetHdrForBlock() *hdrForBlock { +func (mp *metaProcessor) GetHdrForBlock() HeadersForBlock { return mp.hdrsForCurrBlock } -// ChannelReceiveAllHeaders - -func (mp *metaProcessor) ChannelReceiveAllHeaders() chan bool { - return mp.chRcvAllHdrs +// GetHdrForBlock - +func (sp *shardProcessor) GetHdrForBlock() HeadersForBlock { + return sp.hdrsForCurrBlock } -// ComputeExistingAndRequestMissingShardHeaders - -func (mp *metaProcessor) ComputeExistingAndRequestMissingShardHeaders(metaBlock *block.MetaBlock) (uint32, uint32, uint32) { - return mp.computeExistingAndRequestMissingShardHeaders(metaBlock) +// PendingMiniBlocksAfterSelection - +type PendingMiniBlocksAfterSelection = pendingBlocksAfterSelection + +// GetHeaderHash - +func (p *PendingMiniBlocksAfterSelection) GetHeaderHash() []byte { + return p.headerHash } -// ComputeExistingAndRequestMissingMetaHeaders - -func (sp *shardProcessor) ComputeExistingAndRequestMissingMetaHeaders(header data.ShardHeaderHandler) (uint32, uint32, uint32) { - return sp.computeExistingAndRequestMissingMetaHeaders(header) +// GetHeader - +func (p *PendingMiniBlocksAfterSelection) GetHeader() data.HeaderHandler { + return p.header } -// GetHdrForBlock - -func (sp *shardProcessor) GetHdrForBlock() *hdrForBlock { - return sp.hdrsForCurrBlock +// GetMiniBlocksAndHashes - +func (p *PendingMiniBlocksAfterSelection) GetMiniBlocksAndHashes() map[string]*block.MiniBlock { + return p.pendingMiniBlocks } -// ChannelReceiveAllHeaders - -func (sp *shardProcessor) ChannelReceiveAllHeaders() chan bool { - return sp.chRcvAllHdrs +// SelectIncomingMiniBlocks - +func (sp *shardProcessor) SelectIncomingMiniBlocks( + lastCrossNotarizedMetaHdr data.HeaderHandler, + orderedMetaBlocks []data.HeaderHandler, + orderedMetaBlocksHashes [][]byte, + haveTime func() bool, +) ([]*PendingMiniBlocksAfterSelection, error) { + return sp.selectIncomingMiniBlocks(lastCrossNotarizedMetaHdr, orderedMetaBlocks, orderedMetaBlocksHashes, haveTime) } -// InitMaps - -func (hfb *hdrForBlock) InitMaps() { - hfb.initMaps() - hfb.resetMissingHdrs() +// DisplayHeader - +func DisplayHeader( + headerHandler data.HeaderHandler, + headerProof data.HeaderProofHandler, +) []*display.LineData { + return displayHeader(headerHandler, headerProof) } -// Clone - -func (hfb *hdrForBlock) Clone() *hdrForBlock { - return hfb +// CreateBaseProcessorWithMockedTracker - +func CreateBaseProcessorWithMockedTracker(tracker process.BlockTracker) *baseProcessor { + return &baseProcessor{ + blockTracker: tracker, + } } -// SetNumMissingHdrs - -func (hfb *hdrForBlock) SetNumMissingHdrs(num uint32) { - hfb.mutHdrsForBlock.Lock() - hfb.missingHdrs = num - hfb.mutHdrsForBlock.Unlock() +// SetGasComputation +func (bp *baseProcessor) SetGasComputation(instance process.GasComputation) { + bp.gasComputation = instance } -// SetNumMissingFinalityAttestingHdrs - -func (hfb *hdrForBlock) SetNumMissingFinalityAttestingHdrs(num uint32) { - hfb.mutHdrsForBlock.Lock() - hfb.missingFinalityAttestingHdrs = num - hfb.mutHdrsForBlock.Unlock() +// UpdateGasConsumptionLimitsIfNeeded - +func (bp *baseProcessor) UpdateGasConsumptionLimitsIfNeeded() { + bp.updateGasConsumptionLimitsIfNeeded() } -// SetHighestHdrNonce - -func (hfb *hdrForBlock) SetHighestHdrNonce(shardId uint32, nonce uint64) { - hfb.mutHdrsForBlock.Lock() - hfb.highestHdrNonce[shardId] = nonce - hfb.mutHdrsForBlock.Unlock() +// ComputeOwnShardStuckIfNeeded - +func (bp *baseProcessor) ComputeOwnShardStuckIfNeeded(header data.HeaderHandler) error { + return bp.computeOwnShardStuckIfNeeded(header) } -// HdrInfo - -type HdrInfo struct { - UsedInBlock bool - Hdr data.HeaderHandler +// SetMiniBlockSelectionSession - +func (bp *baseProcessor) SetMiniBlockSelectionSession(session MiniBlocksSelectionSession) { + bp.miniBlocksSelectionSession = session } -// SetHdrHashAndInfo - -func (hfb *hdrForBlock) SetHdrHashAndInfo(hash string, info *HdrInfo) { - hfb.mutHdrsForBlock.Lock() - hfb.hdrHashAndInfo[hash] = &hdrInfo{ - hdr: info.Hdr, - usedInBlock: info.UsedInBlock, - } - hfb.mutHdrsForBlock.Unlock() +// CheckHeaderBodyCorrelationProposal - +func (bp *baseProcessor) CheckHeaderBodyCorrelationProposal(miniBlockHeaders []data.MiniBlockHeaderHandler, body *block.Body) error { + return bp.checkHeaderBodyCorrelationProposal(miniBlockHeaders, body) } -// GetHdrHashMap - -func (hfb *hdrForBlock) GetHdrHashMap() map[string]data.HeaderHandler { - m := make(map[string]data.HeaderHandler) +// GetFinalMiniBlocksFromExecutionResults - +func (bp *baseProcessor) GetFinalMiniBlocksFromExecutionResults( + header data.HeaderHandler, +) (*block.Body, map[string]block.MiniBlockSlice, error) { + return bp.getFinalMiniBlocksFromExecutionResults(header) +} - hfb.mutHdrsForBlock.RLock() - for hash, hi := range hfb.hdrHashAndInfo { - m[hash] = hi.hdr - } - hfb.mutHdrsForBlock.RUnlock() +// GetFinalBlockNonce - +func (bp *baseProcessor) GetFinalBlockNonce(headerHandler data.HeaderHandler) uint64 { + return bp.getFinalBlockNonce(headerHandler) +} - return m +// RequestProofIfNeeded - +func (bp *baseProcessor) RequestProofIfNeeded( + nonce uint64, + shardID uint32, + epoch uint32, +) { + bp.requestProofIfNeeded(nonce, shardID, epoch) } -// GetHighestHdrNonce - -func (hfb *hdrForBlock) GetHighestHdrNonce() map[uint32]uint64 { - m := make(map[uint32]uint64) +// VerifyCrossShardMiniBlockDstMe - +func (sp *shardProcessor) VerifyCrossShardMiniBlockDstMe(header data.ShardHeaderHandler) error { + return sp.verifyCrossShardMiniBlockDstMe(header) +} - hfb.mutHdrsForBlock.RLock() - for shardId, nonce := range hfb.highestHdrNonce { - m[shardId] = nonce - } - hfb.mutHdrsForBlock.RUnlock() +// AddCrossShardMiniBlocksDstMeToMap - +func (sp *shardProcessor) AddCrossShardMiniBlocksDstMeToMap( + header data.ShardHeaderHandler, + referencedMetaBlockHash []byte, + referencedMetaHeaderHandler data.HeaderHandler, + lastCrossNotarizedHeader data.HeaderHandler, + miniBlockMetaHashes map[string][]byte, +) error { + return sp.addCrossShardMiniBlocksDstMeToMap(header, referencedMetaBlockHash, referencedMetaHeaderHandler, lastCrossNotarizedHeader, miniBlockMetaHashes) +} + +// CheckInclusionEstimationForExecutionResults - +func (sp *shardProcessor) CheckInclusionEstimationForExecutionResults(header data.HeaderHandler) error { + return sp.checkInclusionEstimationForExecutionResults(header) +} + +// CheckMetaHeadersValidityAndFinalityProposal - +func (sp *shardProcessor) CheckMetaHeadersValidityAndFinalityProposal(header data.ShardHeaderHandler) error { + return sp.checkMetaHeadersValidityAndFinalityProposal(header) +} - return m +// VerifyGasLimit - +func (sp *shardProcessor) VerifyGasLimit(header data.ShardHeaderHandler, miniBlocks block.MiniBlockSlice) error { + return sp.verifyGasLimit(header, miniBlocks) } -// GetMissingHdrs - -func (hfb *hdrForBlock) GetMissingHdrs() uint32 { - hfb.mutHdrsForBlock.RLock() - defer hfb.mutHdrsForBlock.RUnlock() +// CheckEpochStartInfoAvailableIfNeeded - +func (sp *shardProcessor) CheckEpochStartInfoAvailableIfNeeded(header data.ShardHeaderHandler) error { + return sp.checkEpochStartInfoAvailableIfNeeded(header) +} - return hfb.missingHdrs +// HeadersPool - +func (sp *shardProcessor) HeadersPool() dataRetriever.HeadersPool { + return sp.dataPool.Headers() } -// GetMissingFinalityAttestingHdrs - -func (hfb *hdrForBlock) GetMissingFinalityAttestingHdrs() uint32 { - hfb.mutHdrsForBlock.RLock() - defer hfb.mutHdrsForBlock.RUnlock() +// ProofsPool - +func (sp *shardProcessor) ProofsPool() dataRetriever.ProofsPool { + return sp.dataPool.Proofs() +} - return hfb.missingFinalityAttestingHdrs +// DataPool - +func (sp *shardProcessor) DataPool() dataRetriever.PoolsHolder { + return sp.dataPool } -// GetHdrHashAndInfo - -func (hfb *hdrForBlock) GetHdrHashAndInfo() map[string]*HdrInfo { - hfb.mutHdrsForBlock.RLock() - defer hfb.mutHdrsForBlock.RUnlock() +// ShouldDisableOutgoingTxs - +func ShouldDisableOutgoingTxs(enableEpochsHandler common.EnableEpochsHandler, enableRoundsHandler common.EnableRoundsHandler) bool { + return shouldDisableOutgoingTxs(enableEpochsHandler, enableRoundsHandler) +} - m := make(map[string]*HdrInfo) - for hash, hi := range hfb.hdrHashAndInfo { - m[hash] = &HdrInfo{ - UsedInBlock: hi.usedInBlock, - Hdr: hi.hdr, - } +// ShouldEpochStartInfoBeAvailable - +func (sp *shardProcessor) ShouldEpochStartInfoBeAvailable(header data.ShardHeaderHandler) bool { + return sp.shouldEpochStartInfoBeAvailable(header) +} + +// CollectExecutionResults - +func (sp *shardProcessor) CollectExecutionResults(headerHash []byte, header data.HeaderHandler, body *block.Body) (data.BaseExecutionResultHandler, error) { + return sp.collectExecutionResults(headerHash, header, body) +} + +// AddExecutionResultsOnHeader - +func (sp *shardProcessor) AddExecutionResultsOnHeader(shardHeader data.HeaderHandler) error { + return sp.addExecutionResultsOnHeader(shardHeader) +} + +// GetCrossShardIncomingMiniBlocksFromBody - +func (sp *shardProcessor) GetCrossShardIncomingMiniBlocksFromBody(body *block.Body) []*block.MiniBlock { + return sp.getCrossShardIncomingMiniBlocksFromBody(body) +} + +// GetLastExecutionResultHeader - +func (sp *shardProcessor) GetLastExecutionResultHeader( + currentHeader data.HeaderHandler, +) (data.HeaderHandler, error) { + return sp.getLastExecutionResultHeader(currentHeader) +} + +// HasExecutionResultsForProposedEpochChange - +func (mp *metaProcessor) HasExecutionResultsForProposedEpochChange(headerHandler data.MetaHeaderHandler) (bool, error) { + return mp.hasExecutionResultsForProposedEpochChange(headerHandler) +} + +// CheckEpochCorrectnessV3 - +func (mp *metaProcessor) CheckEpochCorrectnessV3( + headerHandler data.MetaHeaderHandler, +) error { + return mp.checkEpochCorrectnessV3(headerHandler) +} + +// CheckShardInfoValidity - +func (mp *metaProcessor) CheckShardInfoValidity( + metaHeaderHandler data.MetaHeaderHandler, + usedShardHeadersInfo *usedShardHeadersInfo, +) error { + return mp.checkShardInfoValidity(metaHeaderHandler, usedShardHeadersInfo) +} + +// CheckHeadersSequenceCorrectness - +func (mp *metaProcessor) CheckHeadersSequenceCorrectness(hdrsForShard []ShardHeaderInfo, lastNotarizedHeaderInfoForShard ShardHeaderInfo) error { + return mp.checkHeadersSequenceCorrectness(hdrsForShard, lastNotarizedHeaderInfoForShard) +} + +// CheckShardHeadersValidityAndFinalityProposal - +func (mp *metaProcessor) CheckShardHeadersValidityAndFinalityProposal( + metaHeaderHandler data.MetaHeaderHandler, +) error { + return mp.checkShardHeadersValidityAndFinalityProposal(metaHeaderHandler) +} + +// GetLastExecutionResultsRootHash - +func (bp *baseProcessor) GetLastExecutedRootHash( + header data.HeaderHandler, +) []byte { + return bp.getLastExecutedRootHash(header) +} + +// RequestHeadersForShardIfNeeded - +func (bp *baseProcessor) RequestHeadersFromHeaderIfNeeded( + lastHeader data.HeaderHandler, +) { + bp.requestHeadersFromHeaderIfNeeded(lastHeader) +} + +// CacheIntraShardMiniBlocks - +func (bp *baseProcessor) CacheIntraShardMiniBlocks(headerHash []byte, mbs block.MiniBlockSlice) error { + return bp.cacheIntraShardMiniBlocks(headerHash, mbs) +} + +// GetHaveTimeForProposal - +func GetHaveTimeForProposal(startTime time.Time, maxDuration time.Duration) func() time.Duration { + return getHaveTimeForProposal(startTime, maxDuration) +} + +// ConstructPartialShardBlockProcessorForTest - +func ConstructPartialShardBlockProcessorForTest(subcomponents map[string]interface{}) (*shardProcessor, error) { + sp := &shardProcessor{} + err := factory.ConstructPartialComponentForTest(sp, subcomponents) + if err != nil { + return nil, err } + return sp, err +} - return m +// ConstructPartialMetaBlockProcessorForTest - +func ConstructPartialMetaBlockProcessorForTest(subcomponents map[string]interface{}) (*metaProcessor, error) { + mp := &metaProcessor{} + err := factory.ConstructPartialComponentForTest(mp, subcomponents) + if err != nil { + return nil, err + } + return mp, err } -// DisplayHeader - -func DisplayHeader( - headerHandler data.HeaderHandler, - headerProof data.HeaderProofHandler, -) []*display.LineData { - return displayHeader(headerHandler, headerProof) +// SetEpochStartData - +func (mp *metaProcessor) SetEpochStartData(epochStartData *EpochStartDataWrapper) { + mp.epochStartDataWrapper = epochStartData +} + +// GetTxCountExecutionResults - +func GetTxCountExecutionResults(metaHeader data.MetaHeaderHandler) (uint32, error) { + return getTxCountExecutionResults(metaHeader) +} + +// PrepareBlockHeaderInternalMapForValidatorProcessor - +func (mp *metaProcessor) PrepareBlockHeaderInternalMapForValidatorProcessor(metaHeader data.MetaHeaderHandler) { + mp.prepareBlockHeaderInternalMapForValidatorProcessor(metaHeader) +} + +// UpdatePeerState - +func (mp *metaProcessor) UpdatePeerState( + header data.MetaHeaderHandler, + cache map[string]data.HeaderHandler, +) ([]byte, error) { + return mp.updatePeerState(header, cache) +} + +// HasStartOfEpochExecutionResults - +func (mp *metaProcessor) HasStartOfEpochExecutionResults(metaHeader data.MetaHeaderHandler) (bool, error) { + return mp.hasStartOfEpochExecutionResults(metaHeader) +} + +// HasRewardOrPeerMiniBlocksFromMeta - +func HasRewardOrPeerMiniBlocksFromMeta(miniBlockHeaders []data.MiniBlockHeaderHandler) bool { + return hasRewardOrPeerMiniBlocksFromMeta(miniBlockHeaders) +} + +// CreateProposalMiniBlocks - +func (mp *metaProcessor) CreateProposalMiniBlocks(haveTime func() bool) error { + return mp.createProposalMiniBlocks(haveTime) +} + +// SelectIncomingMiniBlocksForProposal - +func (mp *metaProcessor) SelectIncomingMiniBlocksForProposal( + haveTime func() bool, +) error { + return mp.selectIncomingMiniBlocksForProposal(haveTime) +} + +// SelectIncomingMiniBlocks - +func (mp *metaProcessor) SelectIncomingMiniBlocks( + lastShardHdrs map[uint32]ShardHeaderInfo, + orderedHdrs []data.HeaderHandler, + orderedHdrsHashes [][]byte, + maxNumHeadersFromSameShard uint32, + haveTime func() bool, +) error { + return mp.selectIncomingMiniBlocks(lastShardHdrs, orderedHdrs, orderedHdrsHashes, maxNumHeadersFromSameShard, haveTime) +} + +// VerifyEpochStartData - +func (mp *metaProcessor) VerifyEpochStartData( + headerHandler data.MetaHeaderHandler, +) bool { + return mp.verifyEpochStartData(headerHandler) +} + +// PrepareEpochStartBodyForTrigger - +func (mp *metaProcessor) PrepareEpochStartBodyForTrigger(header data.MetaHeaderHandler, body *block.Body) (*block.Body, error) { + return mp.prepareEpochStartBodyForTrigger(header, body) +} + +// CommitEpochStart - +func (mp *metaProcessor) CommitEpochStart(header data.MetaHeaderHandler, body *block.Body) error { + return mp.commitEpochStart(header, body) +} + +// RecreateTrieIfNeeded - +func (bp *baseProcessor) RecreateTrieIfNeeded() error { + return bp.recreateTrieIfNeeded() +} + +// ExtractRootHashForCleanup - +func (bp *baseProcessor) ExtractRootHashForCleanup(header data.HeaderHandler) (common.RootHashHolder, error) { + return bp.extractRootHashForCleanup(header) +} + +// CheckContextBeforeExecution - +func (bp *baseProcessor) CheckContextBeforeExecution(header data.HeaderHandler, headerHash []byte) error { + return bp.checkAndUpdateContextBeforeExecution(header, headerHash) +} + +// SaveProposedTxsToStorage - +func (bp *baseProcessor) SaveProposedTxsToStorage(header data.HeaderHandler, body *block.Body) error { + return bp.saveProposedTxsToStorage(header, body) +} + +// ProcessIfFirstBlockAfterEpochStartBlockV3 - +func (mp *metaProcessor) ProcessIfFirstBlockAfterEpochStartBlockV3() error { + return mp.processIfFirstBlockAfterEpochStartBlockV3() +} + +// ProcessEpochStartProposeBlock - +func (mp *metaProcessor) ProcessEpochStartProposeBlock(metaHeader data.MetaHeaderHandler, body *block.Body) (data.BaseExecutionResultHandler, error) { + return mp.processEpochStartProposeBlock(metaHeader, body) +} + +// ProcessEconomicsDataForEpochStartProposeBlock - +func (mp *metaProcessor) ProcessEconomicsDataForEpochStartProposeBlock(metaHeader data.MetaHeaderHandler) error { + return mp.processEconomicsDataForEpochStartProposeBlock(metaHeader) +} + +// CreateExecutionResult - +func (mp *metaProcessor) CreateExecutionResult( + miniBlockHeaderHandlers []data.MiniBlockHeaderHandler, + header data.MetaHeaderHandler, + headerHash []byte, + receiptHash []byte, + valStatRootHash []byte, + totalTxCount int, +) (data.BaseExecutionResultHandler, error) { + return mp.createExecutionResult(miniBlockHeaderHandlers, header, headerHash, receiptHash, valStatRootHash, totalTxCount) +} + +// CollectExecutionResults - +func (mp *metaProcessor) CollectExecutionResults( + headerHash []byte, + header data.MetaHeaderHandler, + body *block.Body, + valStatRootHash []byte, +) (data.BaseExecutionResultHandler, error) { + return mp.collectExecutionResults(headerHash, header, body, valStatRootHash) +} + +// CollectExecutionResultsEpochStartProposal - +func (mp *metaProcessor) CollectExecutionResultsEpochStartProposal( + headerHash []byte, + header data.MetaHeaderHandler, + constructedBody *block.Body, + valStatRootHash []byte, +) (data.BaseExecutionResultHandler, error) { + return mp.collectExecutionResultsEpochStartProposal(headerHash, header, constructedBody, valStatRootHash) +} + +// CollectMiniBlocks - +func (bp *baseProcessor) CollectMiniBlocks( + headerHash []byte, + body *block.Body, +) ([]data.MiniBlockHeaderHandler, int, []byte, error) { + return bp.collectMiniBlocks(headerHash, body) +} + +// GetCurrentlyAccumulatedFees - +func (mp *metaProcessor) GetCurrentlyAccumulatedFees(metaHdr data.MetaHeaderHandler) (*big.Int, *big.Int, error) { + return mp.getCurrentlyAccumulatedFees(metaHdr) +} + +// GetOrderedProcessedMetaBlocksFromMiniBlockHashesV3 - +func (sp *shardProcessor) GetOrderedProcessedMetaBlocksFromMiniBlockHashesV3( + header data.HeaderHandler, + miniBlockHashes map[int][]byte, +) ([]data.HeaderHandler, []*hashAndHdr, error) { + return sp.getOrderedProcessedMetaBlocksFromMiniBlockHashesV3(header, miniBlockHashes) +} + +// ExcludeRevertedExecutionResultsForHeader - +func (bp *baseProcessor) ExcludeRevertedExecutionResultsForHeader( + header data.HeaderHandler, + pendingExecutionResults []data.BaseExecutionResultHandler, +) []data.BaseExecutionResultHandler { + return bp.excludeRevertedExecutionResultsForHeader(header, pendingExecutionResults) +} + +// SaveExecutionResult - +func (bp *baseProcessor) SaveExecutionResult( + execResult data.BaseExecutionResultHandler, +) error { + return bp.saveExecutionResult(execResult) +} + +// WaitForExecutionResultsVerification - +func (bp *baseProcessor) WaitForExecutionResultsVerification( + header data.HeaderHandler, + haveTime func() time.Duration, +) error { + return bp.waitForExecutionResultsVerification(header, haveTime) +} + +// SetLastPrunedNonce - +func (bp *baseProcessor) SetLastPrunedNonce(nonce uint64) { + bp.mutLastPrunedHeader.Lock() + bp.lastPrunedHeaderNonce = nonce + bp.mutLastPrunedHeader.Unlock() +} + +// SetLastPrunedHash - +func (bp *baseProcessor) SetLastPrunedHash(hash []byte) { + bp.mutLastPrunedHeader.Lock() + bp.lastPrunedHeaderHash = hash + bp.mutLastPrunedHeader.Unlock() +} + +// GetLastPrunedHash - +func (bp *baseProcessor) GetLastPrunedHash() []byte { + bp.mutLastPrunedHeader.RLock() + lastPrunedHeaderHash := bp.lastPrunedHeaderHash + bp.mutLastPrunedHeader.RUnlock() + + return lastPrunedHeaderHash +} + +// CleanupDismissedEWLEntries - +func (bp *baseProcessor) CleanupDismissedEWLEntries() { + bp.cleanupDismissedEWLEntries() +} + +// CheckEWLSizeAndReset - +func (bp *baseProcessor) CheckEWLSizeAndReset() { + bp.checkEWLSizeAndReset() +} + +// ComputeEWLResetThreshold - +func ComputeEWLResetThreshold(maxProposalNonceGap uint64) int { + return computeEWLResetThreshold(maxProposalNonceGap) +} + +// CancelPruneForRootHashTransition - +func CancelPruneForRootHashTransition(accountsDb state.AccountsAdapter, prevRootHash, currentRootHash []byte) { + cancelPruneForRootHashTransition(accountsDb, prevRootHash, currentRootHash) +} + +// CancelPruneForDismissedExecutionResults - +func (sp *shardProcessor) CancelPruneForDismissedExecutionResults(batches []executionTrack.DismissedBatch) { + sp.cancelPruneForDismissedExecutionResults(batches) +} + +// CancelPruneForDismissedExecutionResults - +func (mp *metaProcessor) CancelPruneForDismissedExecutionResults(batches []executionTrack.DismissedBatch) { + mp.cancelPruneForDismissedExecutionResults(batches) } diff --git a/process/block/gasConsumption.go b/process/block/gasConsumption.go new file mode 100644 index 00000000000..12f4ce17bff --- /dev/null +++ b/process/block/gasConsumption.go @@ -0,0 +1,730 @@ +package block + +import ( + "bytes" + "fmt" + "strings" + "sync" + + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + + "golang.org/x/exp/slices" + + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/block/preprocess" +) + +const ( + incoming = "incoming" + outgoing = "outgoing" + pending = "pending" +) + +const ( + zeroLimitsFactor = uint64(0) + minPercentLimitsFactor = uint64(10) // 10% + maxPercentLimitsFactor = uint64(500) // 500% + percentSplitBlock = uint64(50) // 50% + initialLastIndex = -1 +) + +// TODO: rename to include size check + +// ArgsGasConsumption holds the arguments needed to create a gasConsumption instance +type ArgsGasConsumption struct { + EconomicsFee process.FeeHandler + ShardCoordinator process.ShardCoordinator + GasHandler process.GasHandler + BlockCapacityOverestimationFactor uint64 + PercentDecreaseLimitsStep uint64 + BlockSizeComputation preprocess.BlockSizeComputationHandler +} + +// gasConsumption implements the GasComputation interface for managing gas limits during block creation +type gasConsumption struct { + mut sync.RWMutex + economicsFee process.FeeHandler + shardCoordinator process.ShardCoordinator + gasHandler process.GasHandler + blockSizeComputation preprocess.BlockSizeComputationHandler + totalGasConsumed map[string]uint64 + gasConsumedByMiniBlock map[string]uint64 + numTxsPerMiniBlock map[string]uint32 + pendingMiniBlocks []data.MiniBlockHeaderHandler + transactionsForPendingMiniBlocks map[string][]data.TransactionHandler + incomingLimitFactor uint64 + outgoingLimitFactor uint64 + decreaseStep uint64 + initialOverestimationFactor uint64 + percentDecreaseLimitsStep uint64 +} + +// NewGasConsumption creates a new instance of gasConsumption +func NewGasConsumption(args ArgsGasConsumption) (*gasConsumption, error) { + if check.IfNil(args.EconomicsFee) { + return nil, process.ErrNilEconomicsFeeHandler + } + if check.IfNil(args.ShardCoordinator) { + return nil, process.ErrNilShardCoordinator + } + if check.IfNil(args.GasHandler) { + return nil, process.ErrNilGasHandler + } + if check.IfNil(args.BlockSizeComputation) { + return nil, process.ErrNilBlockSizeComputationHandler + } + if args.BlockCapacityOverestimationFactor <= minPercentLimitsFactor || args.BlockCapacityOverestimationFactor > maxPercentLimitsFactor { + return nil, fmt.Errorf("%w for BlockCapacityOverestimationFactor, received %d, min allowed %d, max allowed %d", + process.ErrInvalidValue, + args.BlockCapacityOverestimationFactor, + minPercentLimitsFactor, + maxPercentLimitsFactor) + } + + return &gasConsumption{ + economicsFee: args.EconomicsFee, + shardCoordinator: args.ShardCoordinator, + gasHandler: args.GasHandler, + blockSizeComputation: args.BlockSizeComputation, + totalGasConsumed: make(map[string]uint64), + gasConsumedByMiniBlock: make(map[string]uint64), + numTxsPerMiniBlock: make(map[string]uint32), + transactionsForPendingMiniBlocks: make(map[string][]data.TransactionHandler), + incomingLimitFactor: args.BlockCapacityOverestimationFactor, + outgoingLimitFactor: args.BlockCapacityOverestimationFactor, + decreaseStep: args.BlockCapacityOverestimationFactor * args.PercentDecreaseLimitsStep / 100, + percentDecreaseLimitsStep: args.PercentDecreaseLimitsStep, + initialOverestimationFactor: args.BlockCapacityOverestimationFactor, + }, nil +} + +// AddIncomingMiniBlocks verifies if an incoming mini block and its transactions can be included within gas limits. +// returns the last mini block index included, the number of pending mini blocks left and error if needed +// This must be called first, before AddOutgoingTransactions! +func (gc *gasConsumption) AddIncomingMiniBlocks( + miniBlocks []data.MiniBlockHeaderHandler, + transactions map[string][]data.TransactionHandler, +) (lastMiniBlockIndex int, pendingMiniBlocks int, err error) { + if len(miniBlocks) == 0 || len(transactions) == 0 { + return initialLastIndex, 0, nil + } + + gc.mut.Lock() + defer gc.mut.Unlock() + + if gc.incomingLimitFactor == zeroLimitsFactor { + return initialLastIndex, 0, nil + } + + // if we already have some pending mini blocks, the new ones should only be appended as pending + if len(gc.pendingMiniBlocks) > 0 { + errSavePending := gc.savePendingMiniBlocksNoLock(miniBlocks, transactions) + if errSavePending != nil { + return initialLastIndex, 0, errSavePending + } + + return initialLastIndex, len(miniBlocks), nil + } + + bandwidthForIncomingMiniBlocks := gc.getGasLimitForOneDirection(incoming, gc.shardCoordinator.SelfId()) + + lastMiniBlockIndex = initialLastIndex + shouldSavePending := false + for i := 0; i < len(miniBlocks); i++ { + mbType := miniBlocks[i].GetTypeInt32() + if mbType == int32(block.RewardsBlock) || mbType == int32(block.PeerBlock) { + // rewards and validator info have 0 gas limit, thus they should be included anyway without checking their transactions + gc.addMiniBlockToBlockSizeComputation(miniBlocks[i]) + lastMiniBlockIndex = i + continue + } + + shouldSavePending, err = gc.addIncomingMiniBlock(miniBlocks[i], transactions, bandwidthForIncomingMiniBlocks) + if shouldSavePending { + // saving pending starting with idx i, as it was not included either + errSavePending := gc.savePendingMiniBlocksNoLock(miniBlocks[i:], transactions) + if errSavePending != nil { + return lastMiniBlockIndex, 0, errSavePending + } + + return lastMiniBlockIndex, len(gc.pendingMiniBlocks), err + } + if err != nil { + return lastMiniBlockIndex, 0, err + } + + lastMiniBlockIndex = i + } + + return lastMiniBlockIndex, 0, nil +} + +func (gc *gasConsumption) addMiniBlockToBlockSizeComputation( + mb data.MiniBlockHeaderHandler, +) { + gc.blockSizeComputation.AddNumMiniBlocks(1) + gc.blockSizeComputation.AddNumTxs(int(mb.GetTxCount())) + + gc.numTxsPerMiniBlock[string(mb.GetHash())] = mb.GetTxCount() +} + +func (gc *gasConsumption) savePendingMiniBlocksNoLock( + miniBlocks []data.MiniBlockHeaderHandler, + transactions map[string][]data.TransactionHandler, +) error { + for _, mb := range miniBlocks { + hashStr := string(mb.GetHash()) + transactionsForMB, found := transactions[hashStr] + if !found { + log.Warn("could not find transaction for pending mini block", "hash", mb.GetHash()) + return fmt.Errorf("%w, could not find mini block hash in transactions map", process.ErrInvalidValue) + } + gasConsumedByPendingMb, errCheckPending := gc.checkGasConsumedByMiniBlock(mb, transactionsForMB) + if errCheckPending != nil { + log.Warn("failed to check gas consumed by pending mini block", "hash", mb.GetHash(), "error", errCheckPending) + return errCheckPending + } + + gc.transactionsForPendingMiniBlocks[hashStr] = transactions[hashStr] + gc.totalGasConsumed[pending] += gasConsumedByPendingMb + } + + gc.pendingMiniBlocks = append(gc.pendingMiniBlocks, miniBlocks...) + + return nil +} + +// RevertIncomingMiniBlocks gets a list of mini block hashes and removes them from the local state +func (gc *gasConsumption) RevertIncomingMiniBlocks(miniBlockHashes [][]byte) { + if len(miniBlockHashes) == 0 { + return + } + + gc.mut.Lock() + defer gc.mut.Unlock() + + for _, miniBlockHash := range miniBlockHashes { + // do not check here if it was found or not, as some pending mini blocks may be missing from this map + gasConsumedByMb := gc.gasConsumedByMiniBlock[string(miniBlockHash)] + delete(gc.gasConsumedByMiniBlock, string(miniBlockHash)) + + isPending, idxInPendingSlice := gc.isPendingMiniBlock(miniBlockHash) + if isPending { + gc.revertPendingMiniBlock(miniBlockHash, idxInPendingSlice) + gc.totalGasConsumed[pending] -= gasConsumedByMb + continue + } + + gc.revertBlockSizeLimits(miniBlockHash) + + // if the mini block is not pending, remove it from the total gas consumed + gc.totalGasConsumed[incoming] -= gasConsumedByMb + } +} + +func (gc *gasConsumption) revertBlockSizeLimits( + miniBlockHash []byte, +) { + gc.blockSizeComputation.DecNumMiniBlocks(1) + + numTxsPerMiniBlock := gc.numTxsPerMiniBlock[string(miniBlockHash)] + delete(gc.numTxsPerMiniBlock, string(miniBlockHash)) + gc.blockSizeComputation.DecNumTxs(int(numTxsPerMiniBlock)) +} + +func (gc *gasConsumption) isPendingMiniBlock(blockHash []byte) (bool, int) { + for idx, miniBlock := range gc.pendingMiniBlocks { + if bytes.Equal(miniBlock.GetHash(), blockHash) { + return true, idx + } + } + + return false, initialLastIndex +} + +func (gc *gasConsumption) revertPendingMiniBlock(miniBlockHash []byte, idxInPendingSlice int) { + // if the mini block was saved as pending, remove its transactions + // and remove it from pending slice + delete(gc.transactionsForPendingMiniBlocks, string(miniBlockHash)) + gc.pendingMiniBlocks = slices.Delete(gc.pendingMiniBlocks, idxInPendingSlice, idxInPendingSlice+1) +} + +func (gc *gasConsumption) addIncomingMiniBlock( + mb data.MiniBlockHeaderHandler, + transactions map[string][]data.TransactionHandler, + bandwidthForIncomingMiniBlocks uint64, +) (bool, error) { + if mb == nil { + return false, nil + } + + mbHash := mb.GetHash() + numTxs := mb.GetTxCount() + + transactionsForMB, found := transactions[string(mbHash)] + if !found { + // do not save any pending mini blocks, as this one is invalid + return false, fmt.Errorf("%w, could not find mini block hash in transactions map", process.ErrInvalidValue) + } + + if int(numTxs) != len(transactionsForMB) { + // do not save any pending mini blocks, as this one is invalid + return false, fmt.Errorf("%w, the provided mini block does not match the number of transactions provided", process.ErrInvalidValue) + } + + gasConsumedByMB, err := gc.checkGasConsumedByMiniBlock(mb, transactionsForMB) + if err != nil { + return false, err + } + + gc.gasConsumedByMiniBlock[string(mbHash)] = gasConsumedByMB + + mbsGasLimitReached := gc.totalGasConsumed[incoming]+gasConsumedByMB > bandwidthForIncomingMiniBlocks + mbsSizeLimitReached := gc.blockSizeComputation.IsMaxBlockSizeWithoutThrottleReached(1, int(numTxs)) + mbsLimitReached := mbsGasLimitReached || mbsSizeLimitReached + + if !mbsLimitReached { + // limit not reached, continue + // this method might be called either from handling all mini blocks, + // either from handling pending, where the pending ones + // should have continuous indexes after the ones already included + gc.totalGasConsumed[incoming] += gasConsumedByMB + + gc.addMiniBlockToBlockSizeComputation(mb) + + return false, nil + } + + return true, nil +} + +func (gc *gasConsumption) checkGasConsumedByMiniBlock(mb data.MiniBlockHeaderHandler, transactionsForMB []data.TransactionHandler) (uint64, error) { + gasConsumedByMB := uint64(0) + for i := 0; i < len(transactionsForMB); i++ { + tx := transactionsForMB[i] + if check.IfNil(tx) { + continue + } + _, gasConsumedInReceiverShard, err := gc.gasHandler.ComputeGasProvidedByTx(mb.GetSenderShardID(), mb.GetReceiverShardID(), tx) + if err != nil { + // do not save any pending mini blocks, as this one is invalid + return 0, err + } + + maxGasLimitPerTx := gc.economicsFee.MaxGasLimitPerTx() + if gasConsumedInReceiverShard > maxGasLimitPerTx || tx.GetGasLimit() > maxGasLimitPerTx { + // this should not happen, transactions with higher gas should have been already rejected + // return the last saved mini block, and the proper error, without saving the rest of mini blocks + // do not save any pending mini blocks, as this one is invalid + return 0, process.ErrMaxGasLimitPerTransactionIsReached + } + + gasConsumedByMB += gasConsumedInReceiverShard + } + + maxGasLimitPerMB := gc.maxGasLimitPerMiniBlock(mb.GetReceiverShardID()) + if gasConsumedByMB <= maxGasLimitPerMB { + return gasConsumedByMB, nil + } + + // if the limit for mini block is reached and: + // - there is only one tx that satisfied the MaxGasLimitPerTx, allow it into the block + // - there are more than one tx, return error + if len(transactionsForMB) == 1 { + return gasConsumedByMB, nil + } + + return 0, process.ErrMaxGasLimitPerMiniBlockIsReached +} + +func (gc *gasConsumption) addPendingIncomingMiniBlocks() ([]data.MiniBlockHeaderHandler, error) { + addedMiniBlocks := make([]data.MiniBlockHeaderHandler, 0) + // checking if any pending mini blocks are left to fill the block + hasPendingMiniBlocks := len(gc.pendingMiniBlocks) > 0 + if !hasPendingMiniBlocks { + return addedMiniBlocks, nil + } + + // won't return error, but don't add them further + // most probably will never happen + if gc.incomingLimitFactor == zeroLimitsFactor { + return addedMiniBlocks, nil + } + + bandwidthForIncomingMiniBlocks := gc.getGasLimitForOneDirection(incoming, gc.shardCoordinator.SelfId()) + bandwidthForIncomingMiniBlocks += gc.getGasLeftFromTransactions() + lastIndexAdded := 0 + for i := 0; i < len(gc.pendingMiniBlocks); i++ { + mb := gc.pendingMiniBlocks[i] + gasConsumedByIncomingBefore := gc.totalGasConsumed[incoming] + shouldSavePending, err := gc.addIncomingMiniBlock(mb, gc.transactionsForPendingMiniBlocks, bandwidthForIncomingMiniBlocks) + if err != nil { + return nil, err + } + // if should save pending, it means the limit was reached, thus break the loop + if shouldSavePending { + break + } + + addedMiniBlocks = append(addedMiniBlocks, mb) + lastIndexAdded = i + + gasConsumedByIncomingAfter := gc.totalGasConsumed[incoming] + gasConsumedByMiniBlock := gasConsumedByIncomingAfter - gasConsumedByIncomingBefore + if gasConsumedByMiniBlock <= gc.totalGasConsumed[pending] { + gc.totalGasConsumed[pending] -= gasConsumedByMiniBlock + } + } + + gc.pendingMiniBlocks = gc.pendingMiniBlocks[lastIndexAdded+1:] + + return addedMiniBlocks, nil +} + +// AddOutgoingTransactions verifies the outgoing transactions and returns: +// - the index of the last valid transaction +// - the pending mini blocks added if any +// - error if so +// +// only returns error if a transaction is invalid, with too much gas +// This method assumes that incoming mini blocks were already handled, trying to add any remaining pending ones at the end +func (gc *gasConsumption) AddOutgoingTransactions( + txHashes [][]byte, + transactions []data.TransactionHandler, +) (addedTxHashes [][]byte, pendingMiniBlocksAdded []data.MiniBlockHeaderHandler, err error) { + if len(transactions) != len(txHashes) { + return nil, nil, process.ErrInvalidValue + } + + gc.mut.Lock() + defer gc.mut.Unlock() + + if gc.outgoingLimitFactor == 0 { + return make([][]byte, 0), make([]data.MiniBlockHeaderHandler, 0), nil + } + + skippedSenders := make(map[string]struct{}) + addedHashes := make([][]byte, 0) + for i := 0; i < len(transactions); i++ { + _, shouldSkipSender := skippedSenders[string(transactions[i].GetSndAddr())] + if shouldSkipSender { + continue + } + + shouldSkipSender = gc.addOutgoingTransaction(transactions[i]) + if shouldSkipSender { + skippedSenders[string(transactions[i].GetSndAddr())] = struct{}{} + continue + } + + gc.blockSizeComputation.AddNumTxs(1) + + addedHashes = append(addedHashes, txHashes[i]) + } + + if len(addedHashes) > 0 { + gc.blockSizeComputation.AddNumMiniBlocks(1) + } + + // reaching this point means that transactions were added and the limit for outgoing was not reached + pendingMiniBlocksAdded, err = gc.addPendingIncomingMiniBlocks() + return addedHashes, pendingMiniBlocksAdded, err +} + +// must be called under mutex protection +func (gc *gasConsumption) addOutgoingTransaction( + tx data.TransactionHandler, +) bool { + if check.IfNil(tx) { + return false + } + + senderShard := gc.shardCoordinator.SelfId() + receiverShard := gc.shardCoordinator.ComputeId(tx.GetRcvAddr()) + gasConsumedInSenderShard, gasConsumedInReceiverShard, err := gc.checkGasConsumedByTx(senderShard, receiverShard, tx) + if err != nil { + log.Warn("addOutgoingTransaction.checkGasConsumedByTx failed", "error", err) + return true + } + + return gc.checkShardsLimits(senderShard, receiverShard, gasConsumedInSenderShard, gasConsumedInReceiverShard) +} + +func (gc *gasConsumption) checkGasConsumedByTx( + senderShard uint32, + receiverShard uint32, + tx data.TransactionHandler, +) (uint64, uint64, error) { + gasConsumedInSenderShard, gasConsumedInReceiverShard, err := gc.gasHandler.ComputeGasProvidedByTx(senderShard, receiverShard, tx) + if err != nil { + return 0, 0, err + } + maxGasLimitPerTx := gc.economicsFee.MaxGasLimitPerTx() + if gasConsumedInSenderShard > maxGasLimitPerTx || + gasConsumedInReceiverShard > maxGasLimitPerTx || + tx.GetGasLimit() > maxGasLimitPerTx { + // this should not happen, transactions with higher gas should have been already rejected + // return the last saved transaction, and the proper error, without saving the rest of transactions + return 0, 0, process.ErrMaxGasLimitPerTransactionIsReached + } + + return gasConsumedInSenderShard, gasConsumedInReceiverShard, nil +} + +func (gc *gasConsumption) checkShardsLimits( + senderShard uint32, + receiverShard uint32, + gasConsumedInSenderShard uint64, + gasConsumedInReceiverShard uint64, +) bool { + isCrossShard := senderShard != receiverShard + + gasKeyOutgoingIntra := gc.getGasKeyForOutgoingShard(senderShard) + gasKeyOutgoingCross := gc.getGasKeyForOutgoingShard(receiverShard) + bandwidthForOutgoingTransactionsIntra := gc.getGasLimitForOneDirection(gasKeyOutgoingIntra, senderShard) + // if mini blocks are already handled, use the space left only for intra shard limit + bandwidthForOutgoingTransactionsIntra += gc.getGasLeftFromMiniBlocks(senderShard) + + txsLimitReachedIntra := gc.totalGasConsumed[gasKeyOutgoingIntra]+gasConsumedInSenderShard > bandwidthForOutgoingTransactionsIntra + txsLimitReachedCross := false + if isCrossShard { + bandwidthForOutgoingCrossTransactions := gc.getGasLimitForOneDirection(gasKeyOutgoingCross, receiverShard) + txsLimitReachedCross = gc.totalGasConsumed[gasKeyOutgoingCross]+gasConsumedInReceiverShard > bandwidthForOutgoingCrossTransactions + } + + txsSizeLimit := gc.blockSizeComputation.IsMaxBlockSizeWithoutThrottleReached(1, 1) + + // if one of the limits is reached, do not count the remaining gas + // also return true so sender will be skipped + if txsLimitReachedCross || txsLimitReachedIntra || txsSizeLimit { + return true + } + + // add the consumed gas, for receiver shard too if needed + gc.totalGasConsumed[gasKeyOutgoingIntra] += gasConsumedInSenderShard + if isCrossShard { + gc.totalGasConsumed[gasKeyOutgoingCross] += gasConsumedInReceiverShard + } + + return false +} + +func (gc *gasConsumption) getGasLeftFromMiniBlocks(senderShard uint32) uint64 { + bandwidthForIncomingMiniBlocks := gc.getGasLimitForOneDirection(incoming, senderShard) + gasConsumedByIncomingMiniBlocks := gc.totalGasConsumed[incoming] + if gasConsumedByIncomingMiniBlocks >= bandwidthForIncomingMiniBlocks { + return 0 + } + + return bandwidthForIncomingMiniBlocks - gasConsumedByIncomingMiniBlocks +} + +func (gc *gasConsumption) getGasLeftFromTransactions() uint64 { + gasKeyOutgoingIntra := gc.getGasKeyForOutgoingShard(gc.shardCoordinator.SelfId()) + bandwidthForOutgoingIntra := gc.getGasLimitForOneDirection(gasKeyOutgoingIntra, gc.shardCoordinator.SelfId()) + gasConsumedByOutgoingIntra := gc.totalGasConsumed[gasKeyOutgoingIntra] + if gasConsumedByOutgoingIntra >= bandwidthForOutgoingIntra { + return 0 + } + + return bandwidthForOutgoingIntra - gasConsumedByOutgoingIntra +} + +// GetBandwidthForTransactions returns the total bandwidth left for transactions after mini blocks selection +func (gc *gasConsumption) GetBandwidthForTransactions() uint64 { + gc.mut.RLock() + defer gc.mut.RUnlock() + + gasLeftFromMiniBlocks := gc.getGasLeftFromMiniBlocks(gc.shardCoordinator.SelfId()) + gasLeftFromTransactions := gc.getGasLeftFromTransactions() + return gasLeftFromMiniBlocks + gasLeftFromTransactions +} + +// TotalGasConsumedInSelfShard returns the total gas consumed for both incoming and outgoing transactions in self shard +func (gc *gasConsumption) TotalGasConsumedInSelfShard() uint64 { + gc.mut.RLock() + defer gc.mut.RUnlock() + + totalGasConsumedInSelfShard := gc.totalGasConsumed[incoming] + gasKeyOutgoingIntra := gc.getGasKeyForOutgoingShard(gc.shardCoordinator.SelfId()) + totalGasConsumedInSelfShard += gc.totalGasConsumed[gasKeyOutgoingIntra] + + return totalGasConsumedInSelfShard +} + +// TotalGasConsumedInShard returns the total gas consumed for outgoing transactions in the provided shard +func (gc *gasConsumption) TotalGasConsumedInShard(shard uint32) uint64 { + gc.mut.RLock() + defer gc.mut.RUnlock() + + gasKeyOutgoingCross := gc.getGasKeyForOutgoingShard(shard) + return gc.totalGasConsumed[gasKeyOutgoingCross] +} + +// CanAddPendingIncomingMiniBlocks returns true if more pending incoming mini blocks can be added without reaching the block limits +func (gc *gasConsumption) CanAddPendingIncomingMiniBlocks() bool { + gc.mut.RLock() + defer gc.mut.RUnlock() + + totalGasToBeConsumedByPending := uint64(0) + totalGasConsumed := uint64(0) + for typeOfGas, gasConsumed := range gc.totalGasConsumed { + if typeOfGas == pending { + totalGasToBeConsumedByPending += gasConsumed + continue + } + + totalGasConsumed += gasConsumed + } + + maxGasLimitPerBlock := gc.maxGasLimitPerBlock(incoming, gc.shardCoordinator.SelfId()) + if maxGasLimitPerBlock <= totalGasConsumed { + return false + } + + gasLeft := maxGasLimitPerBlock - totalGasConsumed + return totalGasToBeConsumedByPending < gasLeft +} + +// DecreaseIncomingLimit reduces the gas limit for incoming mini blocks by a configured percentage +func (gc *gasConsumption) DecreaseIncomingLimit() { + gc.mut.Lock() + defer gc.mut.Unlock() + + if gc.incomingLimitFactor == zeroLimitsFactor { + return + } + + if gc.incomingLimitFactor <= zeroLimitsFactor || + gc.incomingLimitFactor <= gc.decreaseStep { + gc.incomingLimitFactor = zeroLimitsFactor + return + } + + gc.incomingLimitFactor = gc.incomingLimitFactor - gc.decreaseStep +} + +// DecreaseOutgoingLimit reduces the gas limit for outgoing transactions by a configured percentage +func (gc *gasConsumption) DecreaseOutgoingLimit() { + gc.mut.Lock() + defer gc.mut.Unlock() + + if gc.outgoingLimitFactor == zeroLimitsFactor { + return + } + + if gc.outgoingLimitFactor <= zeroLimitsFactor || + gc.outgoingLimitFactor <= gc.decreaseStep { + gc.outgoingLimitFactor = zeroLimitsFactor + return + } + + gc.outgoingLimitFactor = gc.outgoingLimitFactor - gc.decreaseStep +} + +// ZeroIncomingLimit sets the incoming limit factor to 0, effectively disabling incoming mini blocks +func (gc *gasConsumption) ZeroIncomingLimit() { + gc.mut.Lock() + defer gc.mut.Unlock() + + log.Debug("setting incoming limit to zero...") + + gc.incomingLimitFactor = zeroLimitsFactor +} + +// ZeroOutgoingLimit sets the outgoing limit factor to 0, effectively disabling outgoing transactions +func (gc *gasConsumption) ZeroOutgoingLimit() { + gc.mut.Lock() + defer gc.mut.Unlock() + + log.Debug("setting outgoing limit to zero...") + + gc.outgoingLimitFactor = 0 +} + +// ResetIncomingLimit resets the gas limit for incoming mini blocks to its initial value +func (gc *gasConsumption) ResetIncomingLimit() { + gc.mut.Lock() + defer gc.mut.Unlock() + + gc.incomingLimitFactor = gc.initialOverestimationFactor +} + +// ResetOutgoingLimit resets the gas limit for outgoing transactions to its initial value +func (gc *gasConsumption) ResetOutgoingLimit() { + gc.mut.Lock() + defer gc.mut.Unlock() + + gc.outgoingLimitFactor = gc.initialOverestimationFactor +} + +// Reset clears all gas consumption data except for the limits factors +func (gc *gasConsumption) Reset() { + gc.mut.Lock() + defer gc.mut.Unlock() + + gc.totalGasConsumed = make(map[string]uint64) + gc.gasConsumedByMiniBlock = make(map[string]uint64) + gc.numTxsPerMiniBlock = make(map[string]uint32) + gc.pendingMiniBlocks = make([]data.MiniBlockHeaderHandler, 0) + gc.transactionsForPendingMiniBlocks = make(map[string][]data.TransactionHandler, 0) + + gc.blockSizeComputation.Init() +} + +// GetPendingMiniBlocks returns the pending mini blocks +func (gc *gasConsumption) GetPendingMiniBlocks() []data.MiniBlockHeaderHandler { + gc.mut.RLock() + defer gc.mut.RUnlock() + + pendingMbs := make([]data.MiniBlockHeaderHandler, len(gc.pendingMiniBlocks)) + copy(pendingMbs, gc.pendingMiniBlocks) + + return pendingMbs +} + +func (gc *gasConsumption) getGasLimitForOneDirection(gasType string, shardID uint32) uint64 { + totalBlockLimit := gc.maxGasLimitPerBlock(gasType, shardID) + if shardID != gc.shardCoordinator.SelfId() { + // allow the full block for receiver shard + return totalBlockLimit + } + + return totalBlockLimit * percentSplitBlock / 100 +} + +// must be called under mutex protection as it access blockLimitFactor +func (gc *gasConsumption) maxGasLimitPerBlock(gasType string, shardID uint32) uint64 { + limitFactor := gc.incomingLimitFactor + if strings.Contains(gasType, outgoing) { + limitFactor = gc.outgoingLimitFactor + } + + isCrossShard := shardID != gc.shardCoordinator.SelfId() + if isCrossShard { + return gc.economicsFee.MaxGasLimitPerBlockForSafeCrossShard() * limitFactor / 100 + } + + return gc.economicsFee.MaxGasLimitPerBlock(shardID) * limitFactor / 100 +} + +// must be called under mutex protection as it access miniBlockLimitFactor +func (gc *gasConsumption) maxGasLimitPerMiniBlock(shardID uint32) uint64 { + isCrossShard := shardID != gc.shardCoordinator.SelfId() + if isCrossShard { + return gc.economicsFee.MaxGasLimitPerMiniBlockForSafeCrossShard() + } + + return gc.economicsFee.MaxGasLimitPerMiniBlock(shardID) +} + +func (gc *gasConsumption) getGasKeyForOutgoingShard(shard uint32) string { + return fmt.Sprintf("%s_%d", outgoing, shard) +} + +// IsInterfaceNil checks if the interface is nil +func (gc *gasConsumption) IsInterfaceNil() bool { + return gc == nil +} diff --git a/process/block/gasConsumption_test.go b/process/block/gasConsumption_test.go new file mode 100644 index 00000000000..b18a15cd384 --- /dev/null +++ b/process/block/gasConsumption_test.go @@ -0,0 +1,1202 @@ +package block_test + +import ( + "errors" + "fmt" + "strings" + "sync" + "testing" + + "github.com/multiversx/mx-chain-core-go/data" + coreBlock "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-go/process/block" + "github.com/multiversx/mx-chain-go/process/mock" + "github.com/multiversx/mx-chain-go/testscommon" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" +) + +const ( + maxGasLimitPerBlock = uint64(400) + maxGasLimitPerMiniBlock = uint64(100) + maxGasLimitPerTx = uint64(10) +) + +var expectedError = errors.New("expected error") + +func getMockArgsGasConsumption() block.ArgsGasConsumption { + return block.ArgsGasConsumption{ + EconomicsFee: &economicsmocks.EconomicsHandlerMock{ + MaxGasLimitPerBlockCalled: func(shardID uint32) uint64 { + return maxGasLimitPerBlock + }, + MaxGasLimitPerMiniBlockCalled: func(shardID uint32) uint64 { + return maxGasLimitPerMiniBlock + }, + MaxGasLimitPerTxCalled: func() uint64 { + return maxGasLimitPerTx + }, + MaxGasLimitPerBlockForSafeCrossShardCalled: func() uint64 { + return maxGasLimitPerBlock + }, + }, + ShardCoordinator: &mock.ShardCoordinatorStub{}, + GasHandler: &mock.GasHandlerMock{ + ComputeGasProvidedByTxCalled: func(txSenderShardId uint32, txReceiverSharedId uint32, txHandler data.TransactionHandler) (uint64, uint64, error) { + return txHandler.GetGasLimit(), txHandler.GetGasLimit(), nil + }, + }, + BlockCapacityOverestimationFactor: 200, + PercentDecreaseLimitsStep: 10, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + } +} + +func generateTxs(gasLimitPerTx uint64, numTxs uint32) ([][]byte, []data.TransactionHandler) { + txs := make([]data.TransactionHandler, 0, numTxs) + txHashes := make([][]byte, 0, numTxs) + for i := uint32(0); i < numTxs; i++ { + txs = append(txs, &transaction.Transaction{ + GasLimit: gasLimitPerTx, + }) + txHashes = append(txHashes, []byte(fmt.Sprintf("hash_%d", i))) + } + + return txHashes, txs +} + +func generateTxsForMb(gasLimitPerTx uint64, numTxs uint32) []data.TransactionHandler { + _, txs := generateTxs(gasLimitPerTx, numTxs) + return txs +} + +func generateTxsForMiniBlocks(mbs []data.MiniBlockHeaderHandler) map[string][]data.TransactionHandler { + txs := make(map[string][]data.TransactionHandler, len(mbs)) + for _, mb := range mbs { + _, txs[string(mb.GetHash())] = generateTxs(maxGasLimitPerTx, mb.GetTxCount()) + } + + return txs +} + +func generateMiniBlocks(numOfMiniBlocks int, numTxsInMiniBlock uint32) []data.MiniBlockHeaderHandler { + mbs := make([]data.MiniBlockHeaderHandler, numOfMiniBlocks) + for i := 0; i < numOfMiniBlocks; i++ { + mbs[i] = &coreBlock.MiniBlockHeader{ + Hash: []byte(fmt.Sprintf("mb%d", i)), + TxCount: numTxsInMiniBlock, + } + } + return mbs +} + +func TestNewGasConsumption(t *testing.T) { + t.Parallel() + + t.Run("nil economics handler should error", func(t *testing.T) { + t.Parallel() + + args := getMockArgsGasConsumption() + args.EconomicsFee = nil + gc, err := block.NewGasConsumption(args) + require.Nil(t, gc) + require.Equal(t, process.ErrNilEconomicsFeeHandler, err) + }) + t.Run("nil shard coordinator should error", func(t *testing.T) { + t.Parallel() + + args := getMockArgsGasConsumption() + args.ShardCoordinator = nil + gc, err := block.NewGasConsumption(args) + require.Nil(t, gc) + require.Equal(t, process.ErrNilShardCoordinator, err) + }) + t.Run("nil gas handler should error", func(t *testing.T) { + t.Parallel() + + args := getMockArgsGasConsumption() + args.GasHandler = nil + gc, err := block.NewGasConsumption(args) + require.Nil(t, gc) + require.Equal(t, process.ErrNilGasHandler, err) + }) + t.Run("invalid block overestimation factor should error", func(t *testing.T) { + t.Parallel() + + args := getMockArgsGasConsumption() + args.BlockCapacityOverestimationFactor = 5 + gc, err := block.NewGasConsumption(args) + require.Nil(t, gc) + require.True(t, errors.Is(err, process.ErrInvalidValue)) + }) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + gc, err := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + require.NoError(t, err) + }) +} + +func TestGasConsumption_IsInterfaceNil(t *testing.T) { + t.Parallel() + + args := getMockArgsGasConsumption() + args.EconomicsFee = nil + gc, _ := block.NewGasConsumption(args) + require.True(t, gc.IsInterfaceNil()) + + gc, _ = block.NewGasConsumption(getMockArgsGasConsumption()) + require.False(t, gc.IsInterfaceNil()) +} + +func TestGasConsumption_AddIncomingMiniBlocks(t *testing.T) { + t.Parallel() + + t.Run("empty mini blocks should early exit", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(nil, nil) + require.NoError(t, err) + require.Zero(t, pendingMbs) + require.Equal(t, -1, lastMbIndex) + }) + t.Run("missing txs for mini block", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks([]data.MiniBlockHeaderHandler{&coreBlock.MiniBlockHeader{}}, map[string][]data.TransactionHandler{"mb1": generateTxsForMb(maxGasLimitPerTx, 1)}) + require.True(t, errors.Is(err, process.ErrInvalidValue)) + require.True(t, strings.Contains(err.Error(), "could not find mini block hash in transactions map")) + require.Zero(t, pendingMbs) + require.Equal(t, -1, lastMbIndex) + }) + t.Run("number of txs for mini block differ from the ones in mini block", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + mbs := generateMiniBlocks(1, 5) + txs := generateTxsForMiniBlocks(mbs) + txs[string(mbs[0].GetHash())] = txs[string(mbs[0].GetHash())][1:] // remove one tx + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txs) + require.True(t, errors.Is(err, process.ErrInvalidValue)) + require.True(t, strings.Contains(err.Error(), "the provided mini block does not match the number of transactions provided")) + require.Zero(t, pendingMbs) + require.Equal(t, -1, lastMbIndex) + }) + t.Run("ComputeGasProvidedByTx fails", func(t *testing.T) { + t.Parallel() + + args := getMockArgsGasConsumption() + args.GasHandler = &mock.GasHandlerMock{ + ComputeGasProvidedByTxCalled: func(txSenderShardId uint32, txReceiverSharedId uint32, txHandler data.TransactionHandler) (uint64, uint64, error) { + return 0, 0, expectedError + }, + } + gc, _ := block.NewGasConsumption(args) + require.NotNil(t, gc) + + mbs := generateMiniBlocks(1, 5) + txs := generateTxsForMiniBlocks(mbs) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txs) + require.Equal(t, expectedError, err) + require.Zero(t, pendingMbs) + require.Equal(t, -1, lastMbIndex) + }) + t.Run("one tx exceeds the maximum gas limit per tx", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + mbs := generateMiniBlocks(1, 5) + txs := generateTxsForMiniBlocks(mbs) + txs[string(mbs[0].GetHash())][0] = generateTxsForMb(maxGasLimitPerTx+1, 1)[0] // overwrite first tx + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txs) + require.Equal(t, process.ErrMaxGasLimitPerTransactionIsReached, err) + require.Zero(t, pendingMbs) + require.Equal(t, -1, lastMbIndex) + }) + t.Run("mini block limit exceeded", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + mbs := generateMiniBlocks(1, 21) // too many txs + txs := generateTxsForMiniBlocks(mbs) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txs) + require.Equal(t, process.ErrMaxGasLimitPerMiniBlockIsReached, err) + require.Zero(t, pendingMbs) + require.Equal(t, -1, lastMbIndex) + }) + t.Run("tx limit exceeded", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + mbs := generateMiniBlocks(1, 1) + txs := generateTxsForMiniBlocks(mbs) + providedGasLimitForTx := maxGasLimitPerTx + 1 + _, txs[string(mbs[0].GetHash())] = generateTxs(providedGasLimitForTx, mbs[0].GetTxCount()) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txs) + require.Equal(t, process.ErrMaxGasLimitPerTransactionIsReached, err) + require.Zero(t, pendingMbs) + require.Equal(t, -1, lastMbIndex) + }) + t.Run("should work within limits and multiple calls", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + mbs := generateMiniBlocks(3, 5) + txs := generateTxsForMiniBlocks(mbs) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txs) + require.NoError(t, err) + require.Zero(t, pendingMbs) // no pending mini blocks, we are within limits + require.Equal(t, len(mbs)-1, lastMbIndex) + + // should allow multiple calls + lastMbIndex, pendingMbs, err = gc.AddIncomingMiniBlocks(mbs, txs) + require.NoError(t, err) + require.Zero(t, pendingMbs) // no pending mini blocks, we are still within limits + require.Equal(t, len(mbs)-1, lastMbIndex) + + require.Equal(t, 30*maxGasLimitPerTx, gc.TotalGasConsumedInSelfShard()) // 3 mbs of 5 txs from 2 different calls + }) + t.Run("should work with one tx that has more gas than max gas per mini block limit", func(t *testing.T) { + t.Parallel() + + args := getMockArgsGasConsumption() + args.EconomicsFee = &economicsmocks.EconomicsHandlerMock{ + MaxGasLimitPerBlockCalled: func(shardID uint32) uint64 { + return maxGasLimitPerBlock + }, + MaxGasLimitPerMiniBlockCalled: func(shardID uint32) uint64 { + return maxGasLimitPerMiniBlock + }, + MaxGasLimitPerTxCalled: func() uint64 { + // allow txs with higher limit than one mini block + return maxGasLimitPerMiniBlock * 2 + }, + MaxGasLimitPerBlockForSafeCrossShardCalled: func() uint64 { + return maxGasLimitPerBlock + }, + } + gc, _ := block.NewGasConsumption(args) + require.NotNil(t, gc) + + mbs := generateMiniBlocks(1, 1) + txs := generateTxsForMiniBlocks(mbs) + // generate one tx that exceeds the mini block limit + providedGasLimitForTx := maxGasLimitPerMiniBlock + 1 + _, txs[string(mbs[0].GetHash())] = generateTxs(providedGasLimitForTx, mbs[0].GetTxCount()) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txs) + require.NoError(t, err) + require.Zero(t, pendingMbs) // no pending mini blocks, we are within limits + require.Equal(t, len(mbs)-1, lastMbIndex) + + require.Equal(t, providedGasLimitForTx, gc.TotalGasConsumedInSelfShard()) // 1 mb of 1 tx + }) + t.Run("should work and save pending mini blocks", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + // maxGasLimitPerBlock = 400 + // half of it * factor (200% by default) will be used for mini blocks + // thus 400 is the total max limit for mini blocks + // 5 txs in each mb with a gas limit of 10 => gasLimitPerMb = 50 + // so the limit will be reached after 8 mini blocks + // calling with 10 mini blocks should add 8 to the block and save 2 as pending + mbs := generateMiniBlocks(10, 5) + txs := generateTxsForMiniBlocks(mbs) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txs) + require.NoError(t, err) + require.Equal(t, 2, pendingMbs) // 2 pending mini blocks + require.Equal(t, 7, lastMbIndex) // last index saved 7 + + require.Equal(t, maxGasLimitPerTx*8*5, gc.TotalGasConsumedInSelfShard()) // 8 mbs of 5 txs each + + // full space for txs left although mini blocks reached the limit + bandwidthForTxs := gc.GetBandwidthForTransactions() + require.Equal(t, maxGasLimitPerBlock, bandwidthForTxs) + + pending := gc.GetPendingMiniBlocks() + require.Len(t, pending, 2) + + // calling again after already saving pending should append them as pending + mbs = generateMiniBlocks(2, 5) + txs = generateTxsForMiniBlocks(mbs) + lastMbIndex, pendingMbs, err = gc.AddIncomingMiniBlocks(mbs, txs) + require.NoError(t, err) + require.Equal(t, len(mbs), pendingMbs) // all saved as pending + require.Equal(t, -1, lastMbIndex) // all saved as pending + pending = gc.GetPendingMiniBlocks() + require.Len(t, pending, 4) + }) +} + +func TestGasConsumption_AddOutgoingTransactions(t *testing.T) { + t.Parallel() + + t.Run("different lengths should error", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + txHashes, txs := generateTxs(maxGasLimitPerTx, 10) + txHashes = txHashes[:len(txs)-1] + _, _, err := gc.AddOutgoingTransactions(txHashes, txs) + require.Equal(t, process.ErrInvalidValue, err) + }) + t.Run("ComputeGasProvidedByTx fails", func(t *testing.T) { + t.Parallel() + + args := getMockArgsGasConsumption() + args.GasHandler = &mock.GasHandlerMock{ + ComputeGasProvidedByTxCalled: func(txSenderShardId uint32, txReceiverSharedId uint32, txHandler data.TransactionHandler) (uint64, uint64, error) { + return 0, 0, expectedError + }, + } + gc, _ := block.NewGasConsumption(args) + require.NotNil(t, gc) + + addedTxs, addedPendingMbs, err := gc.AddOutgoingTransactions(generateTxs(maxGasLimitPerTx, 1)) + require.NoError(t, err) + require.Zero(t, len(addedTxs)) + require.Zero(t, len(addedPendingMbs)) + }) + t.Run("one tx exceeds the maximum gas limit per tx", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + addedTxs, addedPendingMbs, err := gc.AddOutgoingTransactions(generateTxs(maxGasLimitPerTx+1, 1)) + require.NoError(t, err) + require.Zero(t, len(addedTxs)) + require.Zero(t, len(addedPendingMbs)) + }) + t.Run("should work within limits, no pending mbs, multiple calls", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + txHashes, txs := generateTxs(maxGasLimitPerTx, 10) + addedTxs, addedPendingMbs, err := gc.AddOutgoingTransactions(txHashes, txs) + require.NoError(t, err) + require.Equal(t, len(txs), len(addedTxs)) + require.Zero(t, len(addedPendingMbs)) + + require.Equal(t, 10*maxGasLimitPerTx, gc.TotalGasConsumedInSelfShard()) + + addedTxs, addedPendingMbs, err = gc.AddOutgoingTransactions(txHashes, txs) + require.NoError(t, err) + require.Equal(t, len(txs), len(addedTxs)) + + require.Equal(t, 20*maxGasLimitPerTx, gc.TotalGasConsumedInSelfShard()) + require.Zero(t, len(addedPendingMbs)) + }) + t.Run("should work within limits and continue adding pending mini blocks to fill the block", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + // maxGasLimitPerBlock = 400 + // half of it * factor (200% by default) will be used for mini blocks + // thus 400 is the total max limit for mini blocks + // 5 txs in each mb with a gas limit of 10 => gasLimitPerMb = 50 + // adding 11 mbs will lead to adding 8 and saving 3 as pending + mbs := generateMiniBlocks(11, 5) + txsInMBs := generateTxsForMiniBlocks(mbs) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txsInMBs) + require.NoError(t, err) + require.Equal(t, 3, pendingMbs) // 3 pending mini blocks + require.Equal(t, 7, lastMbIndex) // last index saved 7 + + pending := gc.GetPendingMiniBlocks() + require.Len(t, pending, 3) + + // maxGasLimitPerBlock = 400 + // half of it * factor (200% by default) will be used for txs + // thus 400 is the total max limit for transactions + // will add all as there is space left from mini blocks + // adding 30 txs will lead to an empty space of 100 worth of gas (enough only for 2 more blocks) + txHashes, txs := generateTxs(maxGasLimitPerTx, 30) + addedTxs, addedPendingMbs, err := gc.AddOutgoingTransactions(txHashes, txs) + require.NoError(t, err) + require.Equal(t, len(txs), len(addedTxs)) // added all + require.Equal(t, 2, len(addedPendingMbs)) // added 2 pending mbs + + require.Equal(t, maxGasLimitPerBlock*2, gc.TotalGasConsumedInSelfShard()) // *2 due to the 200% factor + + pending = gc.GetPendingMiniBlocks() + require.Len(t, pending, 1) // one mb was saved as pending, not enough space + }) + t.Run("should work with empty transactions and continue adding pending mini blocks to fill the block", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + // maxGasLimitPerBlock = 400 + // half of it * factor (200% by default) will be used for mini blocks + // thus 400 is the total max limit for mini blocks + // 5 txs in each mb with a gas limit of 10 => gasLimitPerMb = 50 + // adding 10 mbs will lead to adding 8 and saving 2 as pending + mbs := generateMiniBlocks(10, 5) + txsInMBs := generateTxsForMiniBlocks(mbs) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txsInMBs) + require.NoError(t, err) + require.Equal(t, 2, pendingMbs) // 2 pending mini blocks + require.Equal(t, 7, lastMbIndex) // last index saved 7 + + pending := gc.GetPendingMiniBlocks() + require.Len(t, pending, 2) + + addedTxs, addedPendingMbs, err := gc.AddOutgoingTransactions(nil, nil) + require.NoError(t, err) + require.Zero(t, len(addedTxs)) + require.Equal(t, 2, len(addedPendingMbs)) // added the 2 pending mini blocks + + expectedTotalConsumed := 5 * 10 * maxGasLimitPerTx // initial 10 blocks of 5 txs + require.Equal(t, expectedTotalConsumed, gc.TotalGasConsumedInSelfShard()) + + pending = gc.GetPendingMiniBlocks() + require.Len(t, pending, 0) + }) + t.Run("should work with multiple destination shards", func(t *testing.T) { + t.Parallel() + + cnt := uint64(0) + increaseCnt := func() { + cnt++ + } + numCrossShardTxs := uint64(30) + args := getMockArgsGasConsumption() + args.ShardCoordinator = &mock.ShardCoordinatorStub{ + ComputeIdCalled: func(address []byte) uint32 { + defer increaseCnt() + if cnt < numCrossShardTxs { + return 1 // first 40 txs going to shard 1, won't exceed the limit + } + + return 0 + }, + } + args.GasHandler = &mock.GasHandlerMock{ + ComputeGasProvidedByTxCalled: func(txSenderShardId uint32, txReceiverSharedId uint32, txHandler data.TransactionHandler) (uint64, uint64, error) { + if txSenderShardId == txReceiverSharedId { + return maxGasLimitPerTx, maxGasLimitPerTx, nil + } + + return maxGasLimitPerTx / 2, maxGasLimitPerTx / 2, nil + }, + } + gc, _ := block.NewGasConsumption(args) + require.NotNil(t, gc) + + // maxGasLimitPerBlock = 400 + // half of it * factor (200% by default) will be used for txs + // thus 400 is the total max limit for transactions + // will add all as there is space left from mini blocks + totalTxs := uint64(50) + numIntraTxs := totalTxs - numCrossShardTxs + txHashes, txs := generateTxs(maxGasLimitPerTx, uint32(totalTxs)) + addedTxs, _, err := gc.AddOutgoingTransactions(txHashes, txs) + require.NoError(t, err) + require.Equal(t, len(txs), len(addedTxs)) + + gasConsumedInSelf := numCrossShardTxs*maxGasLimitPerTx/2 + numIntraTxs*maxGasLimitPerTx + require.Equal(t, gasConsumedInSelf, gc.TotalGasConsumedInSelfShard()) + require.Equal(t, numCrossShardTxs*maxGasLimitPerTx/2, gc.TotalGasConsumedInShard(1)) + }) + t.Run("should work with max limits", func(t *testing.T) { + t.Parallel() + + args := getMockArgsGasConsumption() + rcvAddress := []byte("receiver") + args.ShardCoordinator = &mock.ShardCoordinatorStub{ + ComputeIdCalled: func(address []byte) uint32 { + if string(address) == string(rcvAddress) { + return 1 + } + + return 0 + }, + } + economicsFeeMock, ok := args.EconomicsFee.(*economicsmocks.EconomicsHandlerMock) + require.True(t, ok) + economicsFeeMock.MaxGasLimitPerTxCalled = func() uint64 { + return maxGasLimitPerBlock + } + + args.GasHandler = &mock.GasHandlerMock{ + ComputeGasProvidedByTxCalled: func(txSenderShardId uint32, txReceiverSharedId uint32, txHandler data.TransactionHandler) (uint64, uint64, error) { + return maxGasLimitPerTx, maxGasLimitPerBlock, nil + }, + } + gc, _ := block.NewGasConsumption(args) + require.NotNil(t, gc) + + // maxGasLimitPerBlock = 400 + // half of it * factor (200% by default) will be used for txs + // thus 400 is the total max limit for block + // will add both transactions + txHashes, txs := generateTxs(maxGasLimitPerBlock, 2) + txs[0].SetRcvAddr(rcvAddress) + txs[1].SetRcvAddr(rcvAddress) + addedTxs, _, err := gc.AddOutgoingTransactions(txHashes, txs) + require.NoError(t, err) + require.Equal(t, len(txs), len(addedTxs)) + + require.Equal(t, 2*maxGasLimitPerTx, gc.TotalGasConsumedInSelfShard()) + require.Equal(t, 2*maxGasLimitPerBlock, gc.TotalGasConsumedInShard(1)) + }) +} + +func TestGasConsumption_Reset(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + // maxGasLimitPerBlock = 400 + // half of it * factor (200% by default) will be used for mini blocks + // thus 400 is the total max limit for mini blocks + // 5 txs in each mb with a gas limit of 10 => gasLimitPerMb = 50 + // adding 10 mbs will lead to adding 8 and saving 2 as pending + mbs := generateMiniBlocks(10, 5) + txsInMBs := generateTxsForMiniBlocks(mbs) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txsInMBs) + require.NoError(t, err) + require.Equal(t, 2, pendingMbs) // 2 pending mini blocks + require.Equal(t, 7, lastMbIndex) // last index saved 7 + + // maxGasLimitPerBlock = 400 + // half of it * factor (200% by default) will be used for txs + // thus 400 is the total max limit for transactions + // will add all as there is space left from mini blocks + // adding 30 txs will lead to an empty space of 100 worth of gas (enough for 2 more blocks) + txHashes, txs := generateTxs(maxGasLimitPerTx, 30) + addedTxs, _, err := gc.AddOutgoingTransactions(txHashes, txs) + require.NoError(t, err) + require.Equal(t, len(txs), len(addedTxs)) // added all + + // call reset, block is full + gc.Reset() + require.Equal(t, uint64(0), gc.TotalGasConsumedInSelfShard()) +} + +func TestGasConsumption_DecreaseOutgoingLimit(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + // calling a lot of times to reach min limit and simulate possible overflow + for i := 0; i < 100; i++ { + gc.DecreaseOutgoingLimit() + } + + // outgoing limit should be at lowest, 0 + txHashes, txs := generateTxs(maxGasLimitPerTx, 3) + addedTxs, _, err := gc.AddOutgoingTransactions(txHashes, txs) + require.NoError(t, err) + require.Zero(t, len(addedTxs)) // nothing added + + // calling reset should not reset the block limit + gc.Reset() + + // calling reset should reset the limit + gc.ResetOutgoingLimit() + gc.Reset() // required to reset the state + + txHashes, txs = generateTxs(maxGasLimitPerTx, 30) + addedTxs, _, err = gc.AddOutgoingTransactions(txHashes, txs) + require.NoError(t, err) + require.Equal(t, len(txs), len(addedTxs)) // added all +} + +func TestGasConsumption_DecreaseIncomingLimit(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + // calling a lot of times to reach min limit and simulate possible overflow + for i := 0; i < 100; i++ { + gc.DecreaseIncomingLimit() + } + + // incoming limit should be at lowest, 0 + mbs := generateMiniBlocks(2, 2) + txsForMBs := generateTxsForMiniBlocks(mbs) + lastMBIndex, pendingMBs, err := gc.AddIncomingMiniBlocks(mbs, txsForMBs) + require.NoError(t, err) + require.Equal(t, -1, lastMBIndex) // nothing added + require.Zero(t, pendingMBs) // nothing pending + + // calling reset should not reset the block limit + gc.Reset() + + // should be ok to add txs, only the limit for incoming was decreased + txHashes, txs := generateTxs(maxGasLimitPerTx, 30) + addedTxs, _, err := gc.AddOutgoingTransactions(txHashes, txs) + require.NoError(t, err) + require.Equal(t, len(txs), len(addedTxs)) // added all + + // calling reset should reset the limit + gc.ResetIncomingLimit() + gc.Reset() // required to reset the state + + mbs = generateMiniBlocks(10, 5) + txsForMBs = generateTxsForMiniBlocks(mbs) + lastMBIndex, pendingMBs, err = gc.AddIncomingMiniBlocks(mbs, txsForMBs) + require.NoError(t, err) + require.Equal(t, 7, lastMBIndex) // added 7 + require.Equal(t, 2, pendingMBs) // 2 pending + + // zeroing the limit should not allow adding more pending mini blocks after transactions + gc.ZeroIncomingLimit() + txHashes, txs = generateTxs(maxGasLimitPerTx, 30) + addedTxs, _, err = gc.AddOutgoingTransactions(txHashes, txs) + require.NoError(t, err) + require.Equal(t, len(txs), len(addedTxs)) // added all + require.Equal(t, 2, len(gc.GetPendingMiniBlocks())) // still have the 2 mini blocks as pending +} + +func TestGasConsumption_ZeroOutgoingLimit(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + // set the outgoing limit to zero + gc.ZeroOutgoingLimit() + + // outgoing limit should be at 0, no txs should be added + txHashes, txs := generateTxs(maxGasLimitPerTx, 3) + addedTxs, _, err := gc.AddOutgoingTransactions(txHashes, txs) + require.NoError(t, err) + require.Equal(t, 0, len(addedTxs)) // no txs added + + // calling reset should not reset the block limit + gc.Reset() + + // adding txs should still not be allowed + txHashes, txs = generateTxs(maxGasLimitPerTx, 3) + addedTxs, _, err = gc.AddOutgoingTransactions(txHashes, txs) + require.NoError(t, err) + require.Equal(t, 0, len(addedTxs)) // no txs added + + // calling ResetOutgoingLimit should restore the limit + gc.ResetOutgoingLimit() + gc.Reset() // required to reset the state + + txHashes, txs = generateTxs(maxGasLimitPerTx, 30) + addedTxs, _, err = gc.AddOutgoingTransactions(txHashes, txs) + require.NoError(t, err) + require.Equal(t, len(txs), len(addedTxs)) // added all +} + +func TestGasConsumption_ZeroIncomingLimit(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + // set the incoming limit to zero + gc.ZeroIncomingLimit() + + // incoming limit should be at 0, no mini blocks should be added + mbs := generateMiniBlocks(2, 2) + txsForMBs := generateTxsForMiniBlocks(mbs) + lastMBIndex, pendingMBs, err := gc.AddIncomingMiniBlocks(mbs, txsForMBs) + require.NoError(t, err) + require.Equal(t, -1, lastMBIndex) // no mbs added + require.Zero(t, pendingMBs) // no pending mbs + + // calling reset should not reset the block limit + gc.Reset() + + // adding mbs should still not be allowed + mbs = generateMiniBlocks(2, 2) + txsForMBs = generateTxsForMiniBlocks(mbs) + lastMBIndex, pendingMBs, err = gc.AddIncomingMiniBlocks(mbs, txsForMBs) + require.NoError(t, err) + require.Equal(t, -1, lastMBIndex) // no mbs added + require.Zero(t, pendingMBs) // no pending mbs + + // should be ok to add txs, only the limit for incoming was zeroed + txHashes, txs := generateTxs(maxGasLimitPerTx, 30) + addedTxs, _, err := gc.AddOutgoingTransactions(txHashes, txs) + require.NoError(t, err) + require.Equal(t, len(txs), len(addedTxs)) // added all + + // calling ResetIncomingLimit should restore the limit + gc.ResetIncomingLimit() + gc.Reset() // required to reset the state + + mbs = generateMiniBlocks(2, 10) + txsForMBs = generateTxsForMiniBlocks(mbs) + lastMBIndex, pendingMBs, err = gc.AddIncomingMiniBlocks(mbs, txsForMBs) + require.NoError(t, err) + require.Equal(t, 1, lastMBIndex) // added all + require.Zero(t, pendingMBs) // added all +} + +func TestGasConsumption_RevertIncomingMiniBlocks(t *testing.T) { + t.Parallel() + + t.Run("empty mini block hashes should early exit", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + gc.RevertIncomingMiniBlocks(nil) + }) + + t.Run("revert non-pending mini block should decrease total gas consumed", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + // add mini blocks that will be within limits + mbs := generateMiniBlocks(3, 5) + txs := generateTxsForMiniBlocks(mbs) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txs) + require.NoError(t, err) + require.Equal(t, 2, lastMbIndex) + require.Zero(t, pendingMbs) + + totalGasBeforeRevert := gc.TotalGasConsumedInSelfShard() + expectedGasPerMb := maxGasLimitPerTx * 5 // 5 txs per mini block + + // revert the first mini block and one none existing for coverage + gc.RevertIncomingMiniBlocks([][]byte{mbs[0].GetHash(), []byte("non-existent-hash")}) + + // total gas should decrease by the gas consumed by the first mini block + require.Equal(t, totalGasBeforeRevert-expectedGasPerMb, gc.TotalGasConsumedInSelfShard()) + + // revert the second and third mini blocks + gc.RevertIncomingMiniBlocks([][]byte{mbs[1].GetHash(), mbs[2].GetHash()}) + + // total gas should be zero now + require.Equal(t, uint64(0), gc.TotalGasConsumedInSelfShard()) + }) + + t.Run("revert pending mini block at first position", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + // add more mini blocks than the limit to create pending ones + mbs := generateMiniBlocks(10, 5) + txs := generateTxsForMiniBlocks(mbs) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txs) + require.NoError(t, err) + require.Equal(t, 7, lastMbIndex) + require.Equal(t, 2, pendingMbs) + + // get pending mini blocks + pendingMiniBlocks := gc.GetPendingMiniBlocks() + require.Len(t, pendingMiniBlocks, 2) + + totalGasBeforeRevert := gc.TotalGasConsumedInSelfShard() + + // revert the first pending mini block + firstPendingHash := mbs[8].GetHash() + gc.RevertIncomingMiniBlocks([][]byte{firstPendingHash}) + + // total gas should stay the same, only a pending mini block was removed + require.Equal(t, totalGasBeforeRevert, gc.TotalGasConsumedInSelfShard()) + + // check that pending mini blocks were updated + updatedPendingMiniBlocks := gc.GetPendingMiniBlocks() + require.Len(t, updatedPendingMiniBlocks, 1) + require.Equal(t, mbs[9].GetHash(), updatedPendingMiniBlocks[0].GetHash()) + }) + + t.Run("revert pending mini block at last position", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + // add more mini blocks than the limit to create pending ones + mbs := generateMiniBlocks(10, 5) + txs := generateTxsForMiniBlocks(mbs) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txs) + require.NoError(t, err) + require.Equal(t, 7, lastMbIndex) + require.Equal(t, 2, pendingMbs) + + // get pending mini blocks + pendingMiniBlocks := gc.GetPendingMiniBlocks() + require.Len(t, pendingMiniBlocks, 2) + + totalGasBeforeRevert := gc.TotalGasConsumedInSelfShard() + + // revert the last pending mini block (index 9, which is the last in pending) + lastPendingHash := mbs[9].GetHash() + gc.RevertIncomingMiniBlocks([][]byte{lastPendingHash}) + + // total gas should stay the same, only a pending mini block was removed + require.Equal(t, totalGasBeforeRevert, gc.TotalGasConsumedInSelfShard()) + + // check that pending mini blocks were updated + updatedPendingMiniBlocks := gc.GetPendingMiniBlocks() + require.Len(t, updatedPendingMiniBlocks, 1) + require.Equal(t, mbs[8].GetHash(), updatedPendingMiniBlocks[0].GetHash()) + }) + + t.Run("revert pending mini block at middle position", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + // add more mini blocks than the limit to create 3 pending ones + mbs := generateMiniBlocks(11, 5) + txs := generateTxsForMiniBlocks(mbs) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txs) + require.NoError(t, err) + require.Equal(t, 7, lastMbIndex) + require.Equal(t, 3, pendingMbs) + + // get pending mini blocks + pendingMiniBlocks := gc.GetPendingMiniBlocks() + require.Len(t, pendingMiniBlocks, 3) + + totalGasBeforeRevert := gc.TotalGasConsumedInSelfShard() + + // revert the middle pending mini block (index 9, which is the middle in pending) + middlePendingHash := mbs[9].GetHash() + gc.RevertIncomingMiniBlocks([][]byte{middlePendingHash}) + + // total gas should stay the same, only a pending mini block was removed + require.Equal(t, totalGasBeforeRevert, gc.TotalGasConsumedInSelfShard()) + + // check that pending mini blocks were updated + updatedPendingMiniBlocks := gc.GetPendingMiniBlocks() + require.Len(t, updatedPendingMiniBlocks, 2) + require.Equal(t, mbs[8].GetHash(), updatedPendingMiniBlocks[0].GetHash()) + require.Equal(t, mbs[10].GetHash(), updatedPendingMiniBlocks[1].GetHash()) + }) + + t.Run("revert multiple mini blocks with mixed pending and non-pending", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + // add mini blocks, some will be pending + mbs := generateMiniBlocks(10, 5) + txs := generateTxsForMiniBlocks(mbs) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txs) + require.NoError(t, err) + require.Equal(t, 7, lastMbIndex) + require.Equal(t, 2, pendingMbs) + + totalGasBeforeRevert := gc.TotalGasConsumedInSelfShard() + expectedGasPerMb := maxGasLimitPerTx * 5 + + // revert both a non-pending mini block (index 0) and a pending one (index 8) + hashesToRevert := [][]byte{ + mbs[0].GetHash(), // non-pending + mbs[8].GetHash(), // pending (first in pending) + } + gc.RevertIncomingMiniBlocks(hashesToRevert) + + // total gas should decrease only by the non-pending mini block + require.Equal(t, totalGasBeforeRevert-expectedGasPerMb, gc.TotalGasConsumedInSelfShard()) + + // pending mini blocks should be reduced by one + updatedPendingMiniBlocks := gc.GetPendingMiniBlocks() + require.Len(t, updatedPendingMiniBlocks, 1) + require.Equal(t, mbs[9].GetHash(), updatedPendingMiniBlocks[0].GetHash()) + }) + + t.Run("revert all pending mini blocks", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + // add mini blocks with pending ones + mbs := generateMiniBlocks(10, 5) + txs := generateTxsForMiniBlocks(mbs) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txs) + require.NoError(t, err) + require.Equal(t, 7, lastMbIndex) + require.Equal(t, 2, pendingMbs) + + totalGasBeforeRevert := gc.TotalGasConsumedInSelfShard() + + // revert all pending mini blocks + hashesToRevert := [][]byte{ + mbs[8].GetHash(), + mbs[9].GetHash(), + } + gc.RevertIncomingMiniBlocks(hashesToRevert) + + // total gas should stay the same, only pending mini blocks were removed + require.Equal(t, totalGasBeforeRevert, gc.TotalGasConsumedInSelfShard()) + + // no pending mini blocks should remain + updatedPendingMiniBlocks := gc.GetPendingMiniBlocks() + require.Len(t, updatedPendingMiniBlocks, 0) + }) +} + +func TestGasConsumption_ConcurrentOps(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + require.NotPanics(t, func() { + mbs := generateMiniBlocks(1, 2) + mbsHashes := make([][]byte, len(mbs)) + for _, mb := range mbs { + mbsHashes = append(mbsHashes, mb.GetHash()) + } + txsInMBs := generateTxsForMiniBlocks(mbs) + + txHashes, txs := generateTxs(maxGasLimitPerTx, 3) + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + numCalls := 1000 + wg := sync.WaitGroup{} + wg.Add(numCalls) + + for i := 0; i < numCalls; i++ { + go func(idx int) { + switch idx % 12 { + case 0: + _, _, _ = gc.AddOutgoingTransactions(txHashes, txs) + case 1: + _, _, _ = gc.AddIncomingMiniBlocks(mbs, txsInMBs) + case 2: + gc.Reset() + case 3: + gc.DecreaseOutgoingLimit() + case 4: + gc.DecreaseIncomingLimit() + case 5: + gc.ResetOutgoingLimit() + case 6: + gc.ResetIncomingLimit() + case 7: + gc.TotalGasConsumedInSelfShard() + case 8: + _ = gc.GetPendingMiniBlocks() + case 9: + gc.ZeroIncomingLimit() + case 10: + gc.ZeroOutgoingLimit() + case 11: + gc.RevertIncomingMiniBlocks(mbsHashes) + default: + require.Fail(t, "should have not been called") + } + + wg.Done() + }(i) + } + + wg.Wait() + }) +} + +func TestGasConsumption_CanAddPendingIncomingMiniBlocks(t *testing.T) { + t.Parallel() + + t.Run("no pending mini blocks, block partially filled should return true", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + // add some mini blocks but no pending ones + mbs := generateMiniBlocks(3, 5) + txs := generateTxsForMiniBlocks(mbs) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txs) + require.NoError(t, err) + require.Equal(t, 2, lastMbIndex) + require.Zero(t, pendingMbs) + + // no pending mini blocks, enough space left, should return true + require.True(t, gc.CanAddPendingIncomingMiniBlocks()) + }) + + t.Run("pending mini blocks can fit in remaining space", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + // add mini blocks to create some pending ones + // maxGasLimitPerBlock = 400, half * 200% = 400 for incoming + // 5 txs * 10 gas = 50 per mb + // 8 mbs will consume 400, leaving 2 as pending with total 100 gas + mbs := generateMiniBlocks(10, 5) + txs := generateTxsForMiniBlocks(mbs) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txs) + require.NoError(t, err) + require.Equal(t, 7, lastMbIndex) + require.Equal(t, 2, pendingMbs) + + // pending mini blocks consume 100 gas (2 mbs * 5 txs * 10 gas) + // max limit is 400, consumed is 400, but we have the full tx space available (400) + require.True(t, gc.CanAddPendingIncomingMiniBlocks()) + }) + + t.Run("pending mini blocks cannot fit in remaining space", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + // add mini blocks to create pending ones + mbs := generateMiniBlocks(10, 5) + txs := generateTxsForMiniBlocks(mbs) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txs) + require.NoError(t, err) + require.Equal(t, 7, lastMbIndex) + require.Equal(t, 2, pendingMbs) + + // now fill the transaction space to leave very little room + // maxGasLimitPerBlock = 400, half * 200% = 400 for txs + // add 39 txs (390 gas), leaving only 10 gas available + // already pending mbs need 100 gas, so one more mini block won't fit + txHashes, txsOutgoing := generateTxs(maxGasLimitPerTx, 39) + _, _, err = gc.AddOutgoingTransactions(txHashes, txsOutgoing) + require.NoError(t, err) + + // pending mini blocks consume 100 gas + // total consumed = 400 (incoming) + 390 (outgoing) = 790 + // total block limit is 400 * 2 = 800 + // gasLeft = 800 - 790 = 10 + // maxGasLimitPerMiniBlock (100) > gasLeft (10) + require.False(t, gc.CanAddPendingIncomingMiniBlocks()) + }) + + t.Run("block is full, no space for pending", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + // add mini blocks to create pending ones + mbs := generateMiniBlocks(10, 5) + txs := generateTxsForMiniBlocks(mbs) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txs) + require.NoError(t, err) + require.Equal(t, 7, lastMbIndex) + require.Equal(t, 2, pendingMbs) + + // fill the transaction space completely + txHashes, txsOutgoing := generateTxs(maxGasLimitPerTx, 40) + _, _, err = gc.AddOutgoingTransactions(txHashes, txsOutgoing) + require.NoError(t, err) + + require.False(t, gc.CanAddPendingIncomingMiniBlocks()) + }) + + t.Run("pending mini blocks exactly fit in remaining space", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + // add mini blocks to create pending ones + mbs := generateMiniBlocks(10, 5) + txs := generateTxsForMiniBlocks(mbs) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txs) + require.NoError(t, err) + require.Equal(t, 7, lastMbIndex) + require.Equal(t, 2, pendingMbs) + + // add 30 txs (300 gas) to leave exactly 100 gas + txHashes, txsOutgoing := generateTxs(maxGasLimitPerTx, 30) + _, _, err = gc.AddOutgoingTransactions(txHashes, txsOutgoing) + require.NoError(t, err) + + // pending mini blocks consume 100 gas + // total consumed = 400 (incoming) + 300 (outgoing) = 700 + // total block limit = 800 + // gasLeft = 800 - 700 = 100 + // space enough for already added pending mini blocks + require.False(t, gc.CanAddPendingIncomingMiniBlocks()) + }) + + t.Run("after incoming limit is zeroed, should return false", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + // add mini blocks to create pending ones + mbs := generateMiniBlocks(10, 5) + txs := generateTxsForMiniBlocks(mbs) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txs) + require.NoError(t, err) + require.Equal(t, 7, lastMbIndex) + require.Equal(t, 2, pendingMbs) + + // zero the incoming limit + gc.ZeroIncomingLimit() + + require.False(t, gc.CanAddPendingIncomingMiniBlocks()) + }) + + t.Run("with transactions added leaving enough space for pending", func(t *testing.T) { + t.Parallel() + + gc, _ := block.NewGasConsumption(getMockArgsGasConsumption()) + require.NotNil(t, gc) + + // add mini blocks to create pending ones + mbs := generateMiniBlocks(10, 5) + txs := generateTxsForMiniBlocks(mbs) + lastMbIndex, pendingMbs, err := gc.AddIncomingMiniBlocks(mbs, txs) + require.NoError(t, err) + require.Equal(t, 7, lastMbIndex) + require.Equal(t, 2, pendingMbs) + + // add only 10 txs (100 gas), leaving 300 gas available + txHashes, txsOutgoing := generateTxs(maxGasLimitPerTx, 10) + _, _, err = gc.AddOutgoingTransactions(txHashes, txsOutgoing) + require.NoError(t, err) + + // pending mini blocks consume 100 gas + // total consumed = 400 (incoming) + 100 (outgoing) = 500 + // total block limit = 800 + // gasLeft = 800 - 500 = 300 + // should allow 2 more mini blocks + require.True(t, gc.CanAddPendingIncomingMiniBlocks()) + }) +} diff --git a/process/block/hdrForBlock.go b/process/block/hdrForBlock.go deleted file mode 100644 index da443cf4aab..00000000000 --- a/process/block/hdrForBlock.go +++ /dev/null @@ -1,60 +0,0 @@ -package block - -import ( - "sync" - - "github.com/multiversx/mx-chain-core-go/data" -) - -type lastNotarizedHeaderInfo struct { - header data.HeaderHandler - hash []byte - notarizedBasedOnProof bool - hasProof bool -} - -type hdrForBlock struct { - missingHdrs uint32 - missingFinalityAttestingHdrs uint32 - missingProofs uint32 - highestHdrNonce map[uint32]uint64 - mutHdrsForBlock sync.RWMutex - hdrHashAndInfo map[string]*hdrInfo - lastNotarizedShardHeaders map[uint32]*lastNotarizedHeaderInfo -} - -func newHdrForBlock() *hdrForBlock { - return &hdrForBlock{ - hdrHashAndInfo: make(map[string]*hdrInfo), - highestHdrNonce: make(map[uint32]uint64), - lastNotarizedShardHeaders: make(map[uint32]*lastNotarizedHeaderInfo), - } -} - -func (hfb *hdrForBlock) initMaps() { - hfb.mutHdrsForBlock.Lock() - hfb.hdrHashAndInfo = make(map[string]*hdrInfo) - hfb.highestHdrNonce = make(map[uint32]uint64) - hfb.lastNotarizedShardHeaders = make(map[uint32]*lastNotarizedHeaderInfo) - hfb.mutHdrsForBlock.Unlock() -} - -func (hfb *hdrForBlock) resetMissingHdrs() { - hfb.mutHdrsForBlock.Lock() - hfb.missingHdrs = 0 - hfb.missingFinalityAttestingHdrs = 0 - hfb.missingProofs = 0 - hfb.mutHdrsForBlock.Unlock() -} - -func (hfb *hdrForBlock) getHdrHashMap() map[string]data.HeaderHandler { - m := make(map[string]data.HeaderHandler) - - hfb.mutHdrsForBlock.RLock() - for hash, hi := range hfb.hdrHashAndInfo { - m[hash] = hi.hdr - } - hfb.mutHdrsForBlock.RUnlock() - - return m -} diff --git a/process/block/headerForBlock/export_test.go b/process/block/headerForBlock/export_test.go new file mode 100644 index 00000000000..f1547738d94 --- /dev/null +++ b/process/block/headerForBlock/export_test.go @@ -0,0 +1,50 @@ +package headerForBlock + +import ( + "github.com/multiversx/mx-chain-core-go/data" +) + +// NewHeaderInfo - +func NewHeaderInfo( + hdr data.HeaderHandler, + usedInBlock bool, + hasProof bool, + hasProofRequested bool, +) *headerInfo { + return newHeaderInfo(hdr, usedInBlock, hasProof, hasProofRequested) +} + +// NewEmptyHeaderInfo - +func NewEmptyHeaderInfo() *headerInfo { + return newEmptyHeaderInfo() +} + +// NewLastNotarizedHeaderInfo - +func NewLastNotarizedHeaderInfo( + header data.HeaderHandler, + hash []byte, + notarizedBasedOnProof bool, + hasProof bool, +) *lastNotarizedHeaderInfo { + return newLastNotarizedHeaderInfo(header, hash, notarizedBasedOnProof, hasProof) +} + +// FilterHeadersWithoutProofs - +func (hfb *headersForBlock) FilterHeadersWithoutProofs() (map[string]HeaderInfo, error) { + return hfb.filterHeadersWithoutProofs() +} + +// RequestMissingAndUpdateBasedOnCrossShardData - +func (hfb *headersForBlock) RequestMissingAndUpdateBasedOnCrossShardData(cd crossShardMetaData) { + hfb.requestMissingAndUpdateBasedOnCrossShardData(cd) +} + +// ComputeExistingAndRequestMissingShardHeaders - +func (hfb *headersForBlock) ComputeExistingAndRequestMissingShardHeaders(metaBlock data.MetaHeaderHandler) { + hfb.computeExistingAndRequestMissingShardHeaders(metaBlock) +} + +// UpdateLastNotarizedBlockForShard - +func (hfb *headersForBlock) UpdateLastNotarizedBlockForShard(hdr data.ShardHeaderHandler, headerHash []byte) { + hfb.updateLastNotarizedBlockForShard(hdr, headerHash) +} diff --git a/process/block/headerForBlock/headerInfo.go b/process/block/headerForBlock/headerInfo.go new file mode 100644 index 00000000000..2a869bd9fae --- /dev/null +++ b/process/block/headerForBlock/headerInfo.go @@ -0,0 +1,109 @@ +package headerForBlock + +import ( + "sync" + + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" +) + +type headerInfo struct { + sync.RWMutex + hdr data.HeaderHandler + usedInBlock bool + hasProof bool + hasProofRequested bool +} + +// newHeaderInfo returns a new instance of headerInfo +func newHeaderInfo( + hdr data.HeaderHandler, + usedInBlock bool, + hasProof bool, + hasProofRequested bool, +) *headerInfo { + return &headerInfo{ + hdr: hdr, + usedInBlock: usedInBlock, + hasProof: hasProof, + hasProofRequested: hasProofRequested, + } +} + +// newEmptyHeaderInfo returns a new instance of headerInfo with nothing set +func newEmptyHeaderInfo() *headerInfo { + return &headerInfo{} +} + +// GetHeader returns the header +func (hi *headerInfo) GetHeader() data.HeaderHandler { + hi.RLock() + defer hi.RUnlock() + + return hi.hdr +} + +// UsedInBlock returns the usedInBlock field +func (hi *headerInfo) UsedInBlock() bool { + hi.RLock() + defer hi.RUnlock() + + return hi.usedInBlock +} + +// HasProof returns the hasProof field +func (hi *headerInfo) HasProof() bool { + hi.RLock() + defer hi.RUnlock() + + return hi.hasProof +} + +// HasProofRequested returns the hasProofRequested field +func (hi *headerInfo) HasProofRequested() bool { + hi.RLock() + defer hi.RUnlock() + + return hi.hasProofRequested +} + +// SetUsedInBlock sets the usedInBlock field +func (hi *headerInfo) SetUsedInBlock(usedInBlock bool) { + hi.Lock() + defer hi.Unlock() + + hi.usedInBlock = usedInBlock +} + +// SetHeader sets the header +func (hi *headerInfo) SetHeader(hdr data.HeaderHandler) { + if check.IfNil(hdr) { + return + } + + hi.Lock() + defer hi.Unlock() + + hi.hdr = hdr +} + +// SetHasProof sets the hasProof field +func (hi *headerInfo) SetHasProof(hasProof bool) { + hi.Lock() + defer hi.Unlock() + + hi.hasProof = hasProof +} + +// SetHasProofRequested sets the hasProofRequested field +func (hi *headerInfo) SetHasProofRequested(hasProofRequested bool) { + hi.Lock() + defer hi.Unlock() + + hi.hasProofRequested = hasProofRequested +} + +// IsInterfaceNil returns true if there is no value under the interface +func (hi *headerInfo) IsInterfaceNil() bool { + return hi == nil +} diff --git a/process/block/headerForBlock/headerInfo_test.go b/process/block/headerForBlock/headerInfo_test.go new file mode 100644 index 00000000000..343b8a1b691 --- /dev/null +++ b/process/block/headerForBlock/headerInfo_test.go @@ -0,0 +1,100 @@ +package headerForBlock_test + +import ( + "sync" + "testing" + + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/process/block/headerForBlock" + "github.com/stretchr/testify/require" +) + +func TestNewHeaderInfo(t *testing.T) { + t.Parallel() + + header := &block.Header{Nonce: 1} + hi := headerForBlock.NewHeaderInfo(header, true, true, false) + require.NotNil(t, hi) + require.Equal(t, header, hi.GetHeader()) + require.True(t, hi.UsedInBlock()) + require.True(t, hi.HasProof()) + require.False(t, hi.HasProofRequested()) +} + +func TestNewEmptyHeaderInfo(t *testing.T) { + t.Parallel() + + hi := headerForBlock.NewEmptyHeaderInfo() + require.NotNil(t, hi) +} + +func TestHeaderInfo_GettersAndSetters(t *testing.T) { + t.Parallel() + + header1 := &block.Header{Nonce: 1} + hi := headerForBlock.NewEmptyHeaderInfo() + + // Test SetHeader and GetHeader + hi.SetHeader(nil) // coverage only + hi.SetHeader(header1) + require.Equal(t, header1, hi.GetHeader()) + + // Test SetUsedInBlock and UsedInBlock + hi.SetUsedInBlock(true) + require.True(t, hi.UsedInBlock()) + + // Test SetHasProof and HasProof + hi.SetHasProof(true) + require.True(t, hi.HasProof()) + + // Test SetHasProofRequested and HasProofRequested + hi.SetHasProofRequested(true) + require.True(t, hi.HasProofRequested()) +} + +func TestHeaderInfo_IsInterfaceNil(t *testing.T) { + t.Parallel() + + hi := headerForBlock.NewEmptyHeaderInfo() + require.False(t, hi.IsInterfaceNil()) +} + +func TestHeaderInfo_Concurrency(t *testing.T) { + require.NotPanics(t, func() { + t.Parallel() + + hi := headerForBlock.NewEmptyHeaderInfo() + + numCalls := 100 + var wg sync.WaitGroup + wg.Add(numCalls) + + for i := 0; i < numCalls; i++ { + go func(i int) { + switch i % 8 { + case 0: + hi.SetHeader(&block.Header{Nonce: uint64(i)}) + case 1: + hi.SetUsedInBlock(true) + case 2: + hi.SetHasProof(true) + case 3: + hi.SetHasProofRequested(true) + case 4: + hi.GetHeader() + case 5: + hi.UsedInBlock() + case 6: + hi.HasProof() + case 7: + hi.HasProofRequested() + default: + require.Fail(t, "should have not been called") + } + wg.Done() + }(i) + } + + wg.Wait() + }) +} diff --git a/process/block/headerForBlock/headersForBlock.go b/process/block/headerForBlock/headersForBlock.go new file mode 100644 index 00000000000..ed275d879c5 --- /dev/null +++ b/process/block/headerForBlock/headersForBlock.go @@ -0,0 +1,853 @@ +package headerForBlock + +import ( + "bytes" + "encoding/hex" + "fmt" + "sync" + "time" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + logger "github.com/multiversx/mx-chain-logger-go" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/consensus" + "github.com/multiversx/mx-chain-go/dataRetriever" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/sharding" +) + +var log = logger.GetOrCreate("headersForBlock") + +// ArgHeadersForBlock is the DTO that holds data needed to create a new instance of headersForBlock +type ArgHeadersForBlock struct { + DataPool dataRetriever.PoolsHolder + RequestHandler process.RequestHandler + EnableEpochsHandler common.EnableEpochsHandler + ShardCoordinator sharding.Coordinator + BlockTracker process.BlockTracker + TxCoordinator process.TransactionCoordinator + RoundHandler consensus.RoundHandler + ExtraDelayForRequestBlockInfoInMilliseconds int + GenesisNonce uint64 +} + +type headersForBlock struct { + dataPool dataRetriever.PoolsHolder + requestHandler process.RequestHandler + enableEpochsHandler common.EnableEpochsHandler + shardCoordinator sharding.Coordinator + blockTracker process.BlockTracker + txCoordinator process.TransactionCoordinator + roundHandler process.RoundHandler + extraDelayRequestBlockInfo time.Duration + chRcvAllHdrs chan bool + genesisNonce uint64 + blockFinality uint32 + missingHdrs uint32 + missingFinalityAttestingHdrs uint32 + missingProofs uint32 + highestHdrNonce map[uint32]uint64 + mutHdrsForBlock sync.RWMutex + hdrHashAndInfo map[string]HeaderInfo + lastNotarizedShardHeaders map[uint32]LastNotarizedHeaderInfoHandler +} + +type crossShardMetaData interface { + GetNonce() uint64 + GetShardID() uint32 + GetHeaderHash() []byte +} + +// NewHeadersForBlock returns a new instance of headersForBlock +func NewHeadersForBlock(args ArgHeadersForBlock) (*headersForBlock, error) { + err := checkArgs(args) + if err != nil { + return nil, err + } + + instance := &headersForBlock{ + dataPool: args.DataPool, + requestHandler: args.RequestHandler, + enableEpochsHandler: args.EnableEpochsHandler, + shardCoordinator: args.ShardCoordinator, + blockTracker: args.BlockTracker, + txCoordinator: args.TxCoordinator, + roundHandler: args.RoundHandler, + extraDelayRequestBlockInfo: time.Duration(args.ExtraDelayForRequestBlockInfoInMilliseconds) * time.Millisecond, + genesisNonce: args.GenesisNonce, + blockFinality: process.BlockFinality, + chRcvAllHdrs: make(chan bool, 1), + hdrHashAndInfo: make(map[string]HeaderInfo), + highestHdrNonce: make(map[uint32]uint64), + lastNotarizedShardHeaders: make(map[uint32]LastNotarizedHeaderInfoHandler), + } + + instance.dataPool.Headers().RegisterHandler(instance.receivedMetaBlock) + instance.dataPool.Headers().RegisterHandler(instance.receivedShardBlock) + instance.dataPool.Proofs().RegisterHandler(instance.checkReceivedProofIfAttestingIsNeeded) + + return instance, nil +} + +func checkArgs(args ArgHeadersForBlock) error { + if check.IfNil(args.DataPool) { + return process.ErrNilDataPoolHolder + } + if check.IfNil(args.DataPool.Headers()) { + return process.ErrNilHeadersDataPool + } + if check.IfNil(args.DataPool.Proofs()) { + return process.ErrNilProofsPool + } + if check.IfNil(args.RequestHandler) { + return process.ErrNilRequestHandler + } + if check.IfNil(args.EnableEpochsHandler) { + return process.ErrNilEnableEpochsHandler + } + if check.IfNil(args.ShardCoordinator) { + return process.ErrNilShardCoordinator + } + if check.IfNil(args.BlockTracker) { + return process.ErrNilBlockTracker + } + if check.IfNil(args.TxCoordinator) { + return process.ErrNilTransactionCoordinator + } + if check.IfNil(args.RoundHandler) { + return process.ErrNilRoundHandler + } + + return nil +} + +// AddHeaderUsedInBlock adds the provided header info for the provided hash as used in block +func (hfb *headersForBlock) AddHeaderUsedInBlock( + hash string, + header data.HeaderHandler, +) { + hfb.mutHdrsForBlock.Lock() + defer hfb.mutHdrsForBlock.Unlock() + + hfb.hdrHashAndInfo[hash] = newHeaderInfo(header, true, false, false) +} + +// AddHeaderNotUsedInBlock adds the provided header info for the provided hash as not used in block +func (hfb *headersForBlock) AddHeaderNotUsedInBlock( + hash string, + header data.HeaderHandler, +) { + hfb.mutHdrsForBlock.Lock() + defer hfb.mutHdrsForBlock.Unlock() + + hfb.hdrHashAndInfo[hash] = newHeaderInfo(header, false, false, false) +} + +// GetHeaderInfo returns the header info for the provided hash +func (hfb *headersForBlock) GetHeaderInfo(hash string) (HeaderInfo, bool) { + hfb.mutHdrsForBlock.RLock() + defer hfb.mutHdrsForBlock.RUnlock() + + hi, found := hfb.hdrHashAndInfo[hash] + return hi, found +} + +// GetHeadersInfoMap returns the internal header hash map +func (hfb *headersForBlock) GetHeadersInfoMap() map[string]HeaderInfo { + hfb.mutHdrsForBlock.RLock() + defer hfb.mutHdrsForBlock.RUnlock() + + return hfb.getHeadersInfoMapUnprotected() +} + +func (hfb *headersForBlock) getHeadersInfoMapUnprotected() map[string]HeaderInfo { + mapCopy := make(map[string]HeaderInfo, len(hfb.hdrHashAndInfo)) + for hash, hi := range hfb.hdrHashAndInfo { + mapCopy[hash] = hi + } + + return mapCopy +} + +// GetHeadersMap returns a map of headers with their hashes +func (hfb *headersForBlock) GetHeadersMap() map[string]data.HeaderHandler { + hfb.mutHdrsForBlock.RLock() + defer hfb.mutHdrsForBlock.RUnlock() + + headersMap := make(map[string]data.HeaderHandler, len(hfb.hdrHashAndInfo)) + for hash, hi := range hfb.hdrHashAndInfo { + headersMap[hash] = hi.GetHeader() + } + + return headersMap +} + +// ComputeHeadersForCurrentBlock returns a map of header handlers for current block +func (hfb *headersForBlock) ComputeHeadersForCurrentBlock(usedInBlock bool) (map[uint32][]data.HeaderHandler, error) { + hdrsForCurrentBlock := make(map[uint32][]data.HeaderHandler) + + hfb.mutHdrsForBlock.RLock() + defer hfb.mutHdrsForBlock.RUnlock() + + hdrHashAndInfo, err := hfb.filterHeadersWithoutProofs() + if err != nil { + return nil, err + } + + for hdrHash, hi := range hdrHashAndInfo { + if hi.UsedInBlock() != usedInBlock { + continue + } + + hdr := hi.GetHeader() + if hfb.hasMissingProof(hdr, hdrHash) { + return nil, fmt.Errorf("%w for header with hash %s", process.ErrMissingHeaderProof, hex.EncodeToString([]byte(hdrHash))) + } + + hdrsForCurrentBlock[hdr.GetShardID()] = append(hdrsForCurrentBlock[hdr.GetShardID()], hdr) + } + + return hdrsForCurrentBlock, nil +} + +func (hfb *headersForBlock) filterHeadersWithoutProofs() (map[string]HeaderInfo, error) { + removedNonces := make(map[uint32]map[uint64]struct{}) + noncesWithProofs := make(map[uint32]map[uint64]struct{}) + shardIDs := common.GetShardIDs(hfb.shardCoordinator.NumberOfShards()) + for shard := range shardIDs { + removedNonces[shard] = make(map[uint64]struct{}) + noncesWithProofs[shard] = make(map[uint64]struct{}) + } + filteredHeadersInfo := make(map[string]HeaderInfo) + + hdrHashAndInfo := hfb.getHeadersInfoMapUnprotected() + for hdrHash, hi := range hdrHashAndInfo { + hdr := hi.GetHeader() + if hfb.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, hdr.GetEpoch()) { + if hfb.hasMissingProof(hdr, hdrHash) { + removedNonces[hdr.GetShardID()][hdr.GetNonce()] = struct{}{} + continue + } + + noncesWithProofs[hdr.GetShardID()][hdr.GetNonce()] = struct{}{} + filteredHeadersInfo[hdrHash] = hi + continue + } + + filteredHeadersInfo[hdrHash] = hi + } + + for shard, nonces := range removedNonces { + for nonce := range nonces { + if _, ok := noncesWithProofs[shard][nonce]; !ok { + return nil, fmt.Errorf("%w for shard %d and nonce %d", process.ErrMissingHeaderProof, shard, nonce) + } + } + } + + return filteredHeadersInfo, nil +} + +// ComputeHeadersForCurrentBlockInfo returns a map of nonce and hash infos for the current block +func (hfb *headersForBlock) ComputeHeadersForCurrentBlockInfo(usedInBlock bool) (map[uint32][]NonceAndHashInfo, error) { + hdrsForCurrentBlockInfo := make(map[uint32][]NonceAndHashInfo) + + hfb.mutHdrsForBlock.RLock() + defer hfb.mutHdrsForBlock.RUnlock() + + hdrHashAndInfo := hfb.getHeadersInfoMapUnprotected() + for metaBlockHash, hi := range hdrHashAndInfo { + if hi.UsedInBlock() != usedInBlock { + continue + } + + hdr := hi.GetHeader() + if hfb.hasMissingProof(hdr, metaBlockHash) { + return nil, fmt.Errorf("%w for header with hash %s", process.ErrMissingHeaderProof, hex.EncodeToString([]byte(metaBlockHash))) + } + + hdrsForCurrentBlockInfo[hdr.GetShardID()] = append(hdrsForCurrentBlockInfo[hdr.GetShardID()], + &nonceAndHashInfo{nonce: hdr.GetNonce(), hash: []byte(metaBlockHash)}) + } + + return hdrsForCurrentBlockInfo, nil +} + +func (hfb *headersForBlock) hasMissingProof(header data.HeaderHandler, hdrHash string) bool { + isFlagEnabledForHeader := hfb.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, header.GetEpoch()) && header.GetNonce() >= 1 + if !isFlagEnabledForHeader { + return false + } + + return !hfb.dataPool.Proofs().HasProof(header.GetShardID(), []byte(hdrHash)) +} + +// Reset resets the internal state of the component +func (hfb *headersForBlock) Reset() { + hfb.mutHdrsForBlock.Lock() + defer hfb.mutHdrsForBlock.Unlock() + hfb.resetMissingHeaders() + hfb.initMaps() +} + +func (hfb *headersForBlock) initMaps() { + hfb.hdrHashAndInfo = make(map[string]HeaderInfo) + hfb.highestHdrNonce = make(map[uint32]uint64) + hfb.lastNotarizedShardHeaders = make(map[uint32]LastNotarizedHeaderInfoHandler) +} + +func (hfb *headersForBlock) resetMissingHeaders() { + hfb.missingHdrs = 0 + hfb.missingFinalityAttestingHdrs = 0 + hfb.missingProofs = 0 +} + +func (hfb *headersForBlock) setHasProof(hash string) { + _, ok := hfb.hdrHashAndInfo[hash] + if !ok { + hfb.hdrHashAndInfo[hash] = newEmptyHeaderInfo() + } + + hfb.hdrHashAndInfo[hash].SetHasProof(true) +} + +func (hfb *headersForBlock) setHasProofRequested(hash string) { + _, ok := hfb.hdrHashAndInfo[hash] + if !ok { + hfb.hdrHashAndInfo[hash] = newEmptyHeaderInfo() + } + + hfb.hdrHashAndInfo[hash].SetHasProofRequested(true) + hfb.missingProofs++ +} + +// RequestShardHeaders requests the needed shard headers for the provided header +func (hfb *headersForBlock) RequestShardHeaders(metaBlock data.MetaHeaderHandler) { + _ = core.EmptyChannel(hfb.chRcvAllHdrs) + + if len(metaBlock.GetShardInfoHandlers()) == 0 && len(metaBlock.GetShardInfoProposalHandlers()) == 0 { + return + } + + hfb.computeExistingAndRequestMissingShardHeaders(metaBlock) +} + +// RequestMetaHeaders requests the needed meta headers for the provided header +func (hfb *headersForBlock) RequestMetaHeaders(shardHeader data.ShardHeaderHandler) { + _ = core.EmptyChannel(hfb.chRcvAllHdrs) + + if len(shardHeader.GetMetaBlockHashes()) == 0 { + return + } + + hfb.computeExistingAndRequestMissingMetaHeaders(shardHeader) +} + +// GetMissingData returns the missing data +func (hfb *headersForBlock) GetMissingData() (uint32, uint32, uint32) { + hfb.mutHdrsForBlock.RLock() + defer hfb.mutHdrsForBlock.RUnlock() + + return hfb.missingHdrs, hfb.missingProofs, hfb.missingFinalityAttestingHdrs +} + +// WaitForHeadersIfNeeded waits for any missing headers +func (hfb *headersForBlock) WaitForHeadersIfNeeded(haveTime func() time.Duration) error { + hfb.mutHdrsForBlock.RLock() + requestedHdrs := hfb.missingHdrs + requestedFinalityAttestingHdrs := hfb.missingFinalityAttestingHdrs + requestedProofs := hfb.missingProofs + hfb.mutHdrsForBlock.RUnlock() + haveMissingMetaHeaders := requestedHdrs > 0 || requestedFinalityAttestingHdrs > 0 || requestedProofs > 0 + if !haveMissingMetaHeaders { + return nil + } + + if requestedHdrs > 0 { + log.Debug("requested missing headers", + "num headers", requestedHdrs, + ) + } + if requestedFinalityAttestingHdrs > 0 { + log.Debug("requested missing finality attesting headers", + "num finality meta headers", requestedFinalityAttestingHdrs, + ) + } + if requestedProofs > 0 { + log.Debug("requested missing header proofs", + "num proofs", requestedProofs, + ) + } + + err := hfb.waitForHdrHashes(haveTime()) + hfb.mutHdrsForBlock.Lock() + defer hfb.mutHdrsForBlock.Unlock() + missingHdrs := hfb.missingHdrs + missingProofs := hfb.missingProofs + if requestedHdrs > 0 { + log.Debug("received missing headers", + "num headers", requestedHdrs-missingHdrs, + ) + } + + if requestedProofs > 0 { + log.Debug("received missing header proofs", + "num proofs", requestedProofs-missingProofs, + ) + } + + hfb.resetMissingHeaders() + + return err +} + +func (hfb *headersForBlock) waitForHdrHashes(waitTime time.Duration) error { + select { + case <-hfb.chRcvAllHdrs: + return nil + case <-time.After(waitTime): + return process.ErrTimeIsOut + } +} + +func (hfb *headersForBlock) computeExistingAndRequestMissingMetaHeaders(header data.ShardHeaderHandler) { + hfb.mutHdrsForBlock.Lock() + defer hfb.mutHdrsForBlock.Unlock() + + metaBlockHashes := header.GetMetaBlockHashes() + lastMetaBlockNonceWithProof := uint64(0) + for i := 0; i < len(metaBlockHashes); i++ { + hdr, err := process.GetMetaHeaderFromPool( + metaBlockHashes[i], + hfb.dataPool.Headers()) + + if err != nil { + hfb.missingHdrs++ + hfb.hdrHashAndInfo[string(metaBlockHashes[i])] = newHeaderInfo(nil, true, false, false) + + go hfb.requestHandler.RequestMetaHeader(metaBlockHashes[i]) + continue + } + + hfb.hdrHashAndInfo[string(metaBlockHashes[i])] = newHeaderInfo(hdr, true, false, false) + + if hdr.GetNonce() > hfb.highestHdrNonce[core.MetachainShardId] { + hfb.highestHdrNonce[core.MetachainShardId] = hdr.GetNonce() + } + + shouldConsiderProofsForNotarization := hfb.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, hdr.GetEpoch()) + if !shouldConsiderProofsForNotarization { + continue + } + + hfb.requestProofIfNeeded(metaBlockHashes[i], hdr) + + if hdr.GetNonce() > lastMetaBlockNonceWithProof { + lastMetaBlockNonceWithProof = hdr.GetNonce() + } + } + + if hfb.missingHdrs == 0 && lastMetaBlockNonceWithProof == 0 { + hfb.missingFinalityAttestingHdrs = hfb.requestMissingFinalityAttestingHeaders( + core.MetachainShardId, + hfb.blockFinality, + ) + } +} + +func (hfb *headersForBlock) requestMissingAndUpdateBasedOnCrossShardData(cd crossShardMetaData) { + if cd.GetNonce() == hfb.genesisNonce { + lastCrossNotarizedHeaderForShard, found := hfb.lastNotarizedShardHeaders[cd.GetShardID()] + if !found { + log.Warn("requestMissingAndUpdateBasedOnCrossShardData could not find last notarized", "shard", cd.GetShardID()) + return + } + if !bytes.Equal(lastCrossNotarizedHeaderForShard.GetHash(), cd.GetHeaderHash()) { + log.Warn("genesis hash mismatch", + "last notarized hash", lastCrossNotarizedHeaderForShard.GetHash(), + "genesis nonce", hfb.genesisNonce, + "genesis hash", cd.GetHeaderHash()) + } + return + } + + hdr, err := process.GetShardHeaderFromPool( + cd.GetHeaderHash(), + hfb.dataPool.Headers()) + + if err != nil { + hfb.missingHdrs++ + hfb.hdrHashAndInfo[string(cd.GetHeaderHash())] = newHeaderInfo(nil, true, false, false) + + go hfb.requestHandler.RequestShardHeader(cd.GetShardID(), cd.GetHeaderHash()) + return + } + + hfb.hdrHashAndInfo[string(cd.GetHeaderHash())] = newHeaderInfo(hdr, true, false, false) + + hfb.requestProofIfNeeded(cd.GetHeaderHash(), hdr) + + if common.IsEpochChangeBlockForFlagActivation(hdr, hfb.enableEpochsHandler, common.AndromedaFlag) { + return + } + + if hdr.GetNonce() > hfb.highestHdrNonce[cd.GetShardID()] { + hfb.highestHdrNonce[cd.GetShardID()] = hdr.GetNonce() + } + + hfb.updateLastNotarizedBlockForShard(hdr, cd.GetHeaderHash()) +} + +func (hfb *headersForBlock) computeExistingAndRequestMissingBasedOnShardData(shardData []data.ShardDataHandler) { + for _, sd := range shardData { + hfb.requestMissingAndUpdateBasedOnCrossShardData(sd) + } +} + +func (hfb *headersForBlock) computeExistingAndRequestMissingBasedOnShardDataProposal(shardDataProposal []data.ShardDataProposalHandler) { + for _, sdp := range shardDataProposal { + hfb.requestMissingAndUpdateBasedOnCrossShardData(sdp) + } +} + +func (hfb *headersForBlock) computeExistingAndRequestMissingShardHeaders(metaBlock data.MetaHeaderHandler) { + hfb.mutHdrsForBlock.Lock() + defer hfb.mutHdrsForBlock.Unlock() + + if metaBlock.IsHeaderV3() { + hfb.computeExistingAndRequestMissingBasedOnShardDataProposal(metaBlock.GetShardInfoProposalHandlers()) + + hfb.computeExistingAndRequestMissingBasedOnShardData(metaBlock.GetShardInfoHandlers()) + return + } + + hfb.computeExistingAndRequestMissingBasedOnShardData(metaBlock.GetShardInfoHandlers()) + if hfb.missingHdrs == 0 { + hfb.missingFinalityAttestingHdrs = hfb.requestMissingFinalityAttestingShardHeaders() + } +} + +func (hfb *headersForBlock) requestProofIfNeeded(currentHeaderHash []byte, header data.HeaderHandler) bool { + if !hfb.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, header.GetEpoch()) { + return false + } + if hfb.dataPool.Proofs().HasProof(header.GetShardID(), currentHeaderHash) { + hfb.setHasProof(string(currentHeaderHash)) + + return true + } + + hfb.setHasProofRequested(string(currentHeaderHash)) + go hfb.requestHandler.RequestEquivalentProofByHash(header.GetShardID(), currentHeaderHash) + + return false +} + +// requestMissingFinalityAttestingHeaders requests the headers needed to accept the current selected headers for +// processing the current block. It requests the finality headers greater than the highest header, for given shard, +// related to the block which should be processed +// this method should be called only under the mutex protection: hdrsForCurrBlock.mutHdrsForBlock +func (hfb *headersForBlock) requestMissingFinalityAttestingHeaders( + shardID uint32, + finality uint32, +) uint32 { + requestedHeaders := uint32(0) + missingFinalityAttestingHeaders := uint32(0) + + highestHdrNonce := hfb.highestHdrNonce[shardID] + if highestHdrNonce == uint64(0) { + return missingFinalityAttestingHeaders + } + + headersPool := hfb.dataPool.Headers() + lastFinalityAttestingHeader := highestHdrNonce + uint64(finality) + for i := highestHdrNonce + 1; i <= lastFinalityAttestingHeader; i++ { + headers, headersHashes, err := headersPool.GetHeadersByNonceAndShardId(i, shardID) + if err != nil { + missingFinalityAttestingHeaders++ + requestedHeaders++ + go hfb.requestHeaderByShardAndNonce(shardID, i) + continue + } + + for index := range headers { + hfb.hdrHashAndInfo[string(headersHashes[index])] = newHeaderInfo( + headers[index], + false, + false, + false, + ) + } + } + + if requestedHeaders > 0 { + log.Debug("requested missing finality attesting headers", + "num headers", requestedHeaders, + "shard", shardID) + } + + return missingFinalityAttestingHeaders +} + +func (hfb *headersForBlock) requestHeaderByShardAndNonce(shardID uint32, nonce uint64) { + if shardID == core.MetachainShardId { + hfb.requestHandler.RequestMetaHeaderByNonce(nonce) + } else { + hfb.requestHandler.RequestShardHeaderByNonce(shardID, nonce) + } +} + +func (hfb *headersForBlock) checkReceivedProofIfAttestingIsNeeded(proof data.HeaderProofHandler) { + hfb.mutHdrsForBlock.Lock() + hInfo, ok := hfb.hdrHashAndInfo[string(proof.GetHeaderHash())] + if !ok { + hfb.mutHdrsForBlock.Unlock() + return // proof not missing + } + + isWaitingForProofs := hInfo.HasProofRequested() + if !isWaitingForProofs { + hfb.mutHdrsForBlock.Unlock() + return + } + + hInfo.SetHasProof(true) + hInfo.SetHasProofRequested(false) + + if hfb.missingProofs > 0 { + hfb.missingProofs-- + } + + missingHdrs := hfb.missingHdrs + missingFinalityAttestingHdrs := hfb.missingFinalityAttestingHdrs + missingProofs := hfb.missingProofs + hfb.mutHdrsForBlock.Unlock() + + allMissingHeadersReceived := missingHdrs == 0 && missingFinalityAttestingHdrs == 0 && missingProofs == 0 + if allMissingHeadersReceived { + select { + case hfb.chRcvAllHdrs <- true: + default: + } + } +} + +// receivedShardBlock is a call back function which is called when a new header +// is added in the headers pool +func (hfb *headersForBlock) receivedShardBlock(headerHandler data.HeaderHandler, shardHeaderHash []byte) { + shardHeader, ok := headerHandler.(data.ShardHeaderHandler) + if !ok { + return + } + + log.Trace("received shard header from network", + "shard", shardHeader.GetShardID(), + "round", shardHeader.GetRound(), + "nonce", shardHeader.GetNonce(), + "hash", shardHeaderHash, + ) + + hfb.mutHdrsForBlock.Lock() + + missingHdrs := hfb.missingHdrs + missingFinalityAttestingHdrs := hfb.missingFinalityAttestingHdrs + haveMissingShardHeaders := missingHdrs > 0 || missingFinalityAttestingHdrs > 0 + if haveMissingShardHeaders { + hdrInfoForHash, found := hfb.hdrHashAndInfo[string(shardHeaderHash)] + headerInfoIsNotNil := found && !check.IfNil(hdrInfoForHash) + headerIsMissing := headerInfoIsNotNil && !check.IfNil(hdrInfoForHash) && check.IfNil(hdrInfoForHash.GetHeader()) + hasProof := headerInfoIsNotNil && hdrInfoForHash.HasProof() + hasProofRequested := headerInfoIsNotNil && hdrInfoForHash.HasProofRequested() + if headerIsMissing { + hdrInfoForHash.SetHeader(shardHeader) + hfb.missingHdrs-- + + if shardHeader.GetNonce() > hfb.highestHdrNonce[shardHeader.GetShardID()] { + hfb.highestHdrNonce[shardHeader.GetShardID()] = shardHeader.GetNonce() + } + hfb.updateLastNotarizedBlockForShard(shardHeader, shardHeaderHash) + + if !hasProof && !hasProofRequested { + hfb.requestProofIfNeeded(shardHeaderHash, shardHeader) + } + } + + if hfb.missingHdrs == 0 { + hfb.missingFinalityAttestingHdrs = hfb.requestMissingFinalityAttestingShardHeaders() + if hfb.missingFinalityAttestingHdrs == 0 { + log.Debug("received all missing finality attesting shard headers") + } + } + + var missingProofs uint32 + missingHdrs = hfb.missingHdrs + missingProofs = hfb.missingProofs + missingFinalityAttestingHdrs = hfb.missingFinalityAttestingHdrs + hfb.mutHdrsForBlock.Unlock() + + allMissingShardHeadersReceived := missingHdrs == 0 && missingFinalityAttestingHdrs == 0 && missingProofs == 0 + if allMissingShardHeadersReceived { + select { + case hfb.chRcvAllHdrs <- true: + default: + } + } + } else { + hfb.mutHdrsForBlock.Unlock() + } + + go hfb.requestMiniBlocksIfNeeded(headerHandler) +} + +// requestMissingFinalityAttestingShardHeaders requests the headers needed to accept the current selected headers for +// processing the current block. It requests the shardBlockFinality headers greater than the highest shard header, +// for the given shard, related to the block which should be processed +// this method should be called only under the mutex protection: hdrsForCurrBlock.mutHdrsForBlock +func (hfb *headersForBlock) requestMissingFinalityAttestingShardHeaders() uint32 { + missingFinalityAttestingShardHeaders := uint32(0) + + for shardId := uint32(0); shardId < hfb.shardCoordinator.NumberOfShards(); shardId++ { + lastNotarizedShardHeader, found := hfb.lastNotarizedShardHeaders[shardId] + missingFinalityAttestingHeaders := uint32(0) + if found && lastNotarizedShardHeader != nil && !lastNotarizedShardHeader.NotarizedBasedOnProof() { + missingFinalityAttestingHeaders = hfb.requestMissingFinalityAttestingHeaders( + shardId, + hfb.blockFinality, + ) + } + missingFinalityAttestingShardHeaders += missingFinalityAttestingHeaders + } + + return missingFinalityAttestingShardHeaders +} + +// receivedMetaBlock is a callback function when a new metablock was received +// upon receiving, it parses the new metablock and requests miniblocks and transactions +// which destination is the current shard +func (hfb *headersForBlock) receivedMetaBlock(headerHandler data.HeaderHandler, metaBlockHash []byte) { + metaBlock, ok := headerHandler.(data.MetaHeaderHandler) + if !ok { + return + } + + log.Trace("received meta block from network", + "round", metaBlock.GetRound(), + "nonce", metaBlock.GetNonce(), + "hash", metaBlockHash, + ) + + hfb.mutHdrsForBlock.Lock() + + missingHdrs := hfb.missingHdrs + missingFinalityAttestingHdrs := hfb.missingFinalityAttestingHdrs + haveMissingMetaHeaders := missingHdrs > 0 || missingFinalityAttestingHdrs > 0 + if haveMissingMetaHeaders { + hdrInfoForHash, found := hfb.hdrHashAndInfo[string(metaBlockHash)] + headerInfoIsNotNil := found && !check.IfNil(hdrInfoForHash) + headerIsMissing := headerInfoIsNotNil && !check.IfNil(hdrInfoForHash) && check.IfNil(hdrInfoForHash.GetHeader()) + hasProof := headerInfoIsNotNil && hdrInfoForHash.HasProof() + hasProofRequested := headerInfoIsNotNil && hdrInfoForHash.HasProofRequested() + if headerIsMissing { + hdrInfoForHash.SetHeader(metaBlock) + hfb.missingHdrs-- + + if metaBlock.GetNonce() > hfb.highestHdrNonce[core.MetachainShardId] { + hfb.highestHdrNonce[core.MetachainShardId] = metaBlock.GetNonce() + } + + if !hasProof && !hasProofRequested { + hfb.requestProofIfNeeded(metaBlockHash, metaBlock) + } + } + + if hfb.missingHdrs == 0 { + hfb.checkFinalityRequestingMissing(metaBlock) + + if hfb.missingFinalityAttestingHdrs == 0 { + log.Debug("received all missing finality attesting meta headers") + } + } + + missingHdrs = hfb.missingHdrs + missingFinalityAttestingHdrs = hfb.missingFinalityAttestingHdrs + missingProofs := hfb.missingProofs + hfb.mutHdrsForBlock.Unlock() + + allMissingMetaHeadersReceived := missingHdrs == 0 && missingFinalityAttestingHdrs == 0 && missingProofs == 0 + if allMissingMetaHeadersReceived { + select { + case hfb.chRcvAllHdrs <- true: + default: + } + } + } else { + hfb.mutHdrsForBlock.Unlock() + } + + go hfb.requestMiniBlocksIfNeeded(headerHandler) +} + +func (hfb *headersForBlock) checkFinalityRequestingMissing(metaBlock data.MetaHeaderHandler) { + shouldConsiderProofsForNotarization := hfb.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, metaBlock.GetEpoch()) + if !shouldConsiderProofsForNotarization { + hfb.missingFinalityAttestingHdrs = hfb.requestMissingFinalityAttestingHeaders( + core.MetachainShardId, + hfb.blockFinality, + ) + } +} + +func (hfb *headersForBlock) updateLastNotarizedBlockForShard(hdr data.ShardHeaderHandler, headerHash []byte) { + lastNotarizedForShard, found := hfb.lastNotarizedShardHeaders[hdr.GetShardID()] + if !found || lastNotarizedForShard == nil { + lastNotarizedForShard = newLastNotarizedHeaderInfo(hdr, headerHash, false, false) + hfb.lastNotarizedShardHeaders[hdr.GetShardID()] = lastNotarizedForShard + } + + if hdr.GetNonce() >= lastNotarizedForShard.GetHeader().GetNonce() { + notarizedBasedOnProofs := hfb.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, hdr.GetEpoch()) + hasProof := false + if notarizedBasedOnProofs { + hasProof = hfb.dataPool.Proofs().HasProof(hdr.GetShardID(), headerHash) + } + + lastNotarizedForShard.SetHeader(hdr) + lastNotarizedForShard.SetHash(headerHash) + lastNotarizedForShard.SetNotarizedBasedOnProof(notarizedBasedOnProofs) + lastNotarizedForShard.SetHasProof(hasProof) + } +} + +func (hfb *headersForBlock) requestMiniBlocksIfNeeded(headerHandler data.HeaderHandler) { + lastCrossNotarizedHeader, _, err := hfb.blockTracker.GetLastCrossNotarizedHeader(headerHandler.GetShardID()) + if err != nil { + log.Debug("requestMiniBlocksIfNeeded.GetLastCrossNotarizedHeader", + "shard", headerHandler.GetShardID(), + "error", err.Error()) + return + } + + isHeaderOutOfRequestRange := headerHandler.GetNonce() > lastCrossNotarizedHeader.GetNonce()+process.MaxHeadersToRequestInAdvance + if isHeaderOutOfRequestRange { + return + } + + waitTime := hfb.extraDelayRequestBlockInfo + roundDifferences := hfb.roundHandler.Index() - int64(headerHandler.GetRound()) + if roundDifferences > 1 { + waitTime = 0 + } + + // waiting for late broadcast of mini blocks and transactions to be done and received + time.Sleep(waitTime) + + hfb.txCoordinator.RequestMiniBlocksAndTransactions(headerHandler) +} + +// IsInterfaceNil returns true if underlying object is nil +func (hfb *headersForBlock) IsInterfaceNil() bool { + return hfb == nil +} diff --git a/process/block/headerForBlock/headersForBlock_test.go b/process/block/headerForBlock/headersForBlock_test.go new file mode 100644 index 00000000000..d1da9b42c8f --- /dev/null +++ b/process/block/headerForBlock/headersForBlock_test.go @@ -0,0 +1,1046 @@ +package headerForBlock_test + +import ( + "errors" + "fmt" + "slices" + "sync" + "testing" + "time" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/common" + "github.com/stretchr/testify/require" + + retriever "github.com/multiversx/mx-chain-go/dataRetriever" + "github.com/multiversx/mx-chain-go/integrationTests/mock" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/block/headerForBlock" + "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" + "github.com/multiversx/mx-chain-go/testscommon/headersForBlockMocks" + "github.com/multiversx/mx-chain-go/testscommon/pool" +) + +var errorExpected = errors.New("expected error") + +func createMockArgs() headerForBlock.ArgHeadersForBlock { + return headerForBlock.ArgHeadersForBlock{ + DataPool: dataRetriever.NewPoolsHolderMock(), + RequestHandler: &testscommon.RequestHandlerStub{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + ShardCoordinator: &testscommon.ShardsCoordinatorMock{}, + BlockTracker: &mock.BlockTrackerStub{}, + TxCoordinator: &testscommon.TransactionCoordinatorMock{}, + RoundHandler: &testscommon.RoundHandlerMock{}, + ExtraDelayForRequestBlockInfoInMilliseconds: 10, + GenesisNonce: 12345, + } +} + +func TestNewHeadersForBlock(t *testing.T) { + t.Parallel() + + t.Run("nil DataPool", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.DataPool = nil + + hfb, err := headerForBlock.NewHeadersForBlock(args) + require.Equal(t, process.ErrNilDataPoolHolder, err) + require.Nil(t, hfb) + }) + t.Run("nil Headers", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.DataPool = &dataRetriever.PoolsHolderStub{} + + hfb, err := headerForBlock.NewHeadersForBlock(args) + require.Equal(t, process.ErrNilHeadersDataPool, err) + require.Nil(t, hfb) + }) + t.Run("nil Proofs", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.DataPool = &dataRetriever.PoolsHolderStub{ + HeadersCalled: func() retriever.HeadersPool { + return &pool.HeadersPoolStub{} + }, + } + + hfb, err := headerForBlock.NewHeadersForBlock(args) + require.Equal(t, process.ErrNilProofsPool, err) + require.Nil(t, hfb) + }) + t.Run("nil RequestHandler", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.RequestHandler = nil + + hfb, err := headerForBlock.NewHeadersForBlock(args) + require.Equal(t, process.ErrNilRequestHandler, err) + require.Nil(t, hfb) + }) + t.Run("nil EnableEpochsHandler", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.EnableEpochsHandler = nil + + hfb, err := headerForBlock.NewHeadersForBlock(args) + require.Equal(t, process.ErrNilEnableEpochsHandler, err) + require.Nil(t, hfb) + }) + t.Run("nil ShardCoordinator", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.ShardCoordinator = nil + + hfb, err := headerForBlock.NewHeadersForBlock(args) + require.Equal(t, process.ErrNilShardCoordinator, err) + require.Nil(t, hfb) + }) + t.Run("nil BlockTracker", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.BlockTracker = nil + + hfb, err := headerForBlock.NewHeadersForBlock(args) + require.Equal(t, process.ErrNilBlockTracker, err) + require.Nil(t, hfb) + }) + t.Run("nil TxCoordinator", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.TxCoordinator = nil + + hfb, err := headerForBlock.NewHeadersForBlock(args) + require.Equal(t, process.ErrNilTransactionCoordinator, err) + require.Nil(t, hfb) + }) + t.Run("nil RoundHandler", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.RoundHandler = nil + + hfb, err := headerForBlock.NewHeadersForBlock(args) + require.Error(t, err) + require.Nil(t, hfb) + }) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + hfb, err := headerForBlock.NewHeadersForBlock(createMockArgs()) + require.NoError(t, err) + require.NotNil(t, hfb) + }) +} + +func TestHeadersForBlock_IsInterfaceNil(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.DataPool = nil + hfb, _ := headerForBlock.NewHeadersForBlock(args) + require.True(t, hfb.IsInterfaceNil()) + + hfb, _ = headerForBlock.NewHeadersForBlock(createMockArgs()) + require.False(t, hfb.IsInterfaceNil()) +} + +func TestHeadersForBlock_AddHeaderUsedInBlock(t *testing.T) { + t.Parallel() + + hfb, err := headerForBlock.NewHeadersForBlock(createMockArgs()) + require.NoError(t, err) + + hfb.AddHeaderUsedInBlock( + "hash1", + &testscommon.HeaderHandlerStub{}, + ) + + hi, found := hfb.GetHeaderInfo("hash1") + require.True(t, found) + require.NotNil(t, hi.GetHeader()) +} + +func TestHeadersForBlock_GetHeadersInfoMap(t *testing.T) { + t.Parallel() + + hfb, err := headerForBlock.NewHeadersForBlock(createMockArgs()) + require.NoError(t, err) + + hfb.AddHeaderUsedInBlock( + "hash1", + &testscommon.HeaderHandlerStub{ + GetNonceCalled: func() uint64 { + return 1 + }, + }, + ) + hfb.AddHeaderUsedInBlock( + "hash2", + &testscommon.HeaderHandlerStub{ + GetNonceCalled: func() uint64 { + return 2 + }, + }, + ) + + infoMap := hfb.GetHeadersInfoMap() + require.Len(t, infoMap, 2) + info, ok := infoMap["hash1"] + require.True(t, ok) + require.Equal(t, uint64(1), info.GetHeader().GetNonce()) + info, ok = infoMap["hash2"] + require.True(t, ok) + require.Equal(t, uint64(2), info.GetHeader().GetNonce()) +} + +func TestHeadersForBlock_GetHeadersMap(t *testing.T) { + t.Parallel() + + hfb, err := headerForBlock.NewHeadersForBlock(createMockArgs()) + require.NoError(t, err) + + hfb.AddHeaderUsedInBlock( + "hash1", + &testscommon.HeaderHandlerStub{ + GetNonceCalled: func() uint64 { + return 1 + }, + }, + ) + hfb.AddHeaderUsedInBlock( + "hash2", + &testscommon.HeaderHandlerStub{ + GetNonceCalled: func() uint64 { + return 2 + }, + }, + ) + + headersMap := hfb.GetHeadersMap() + require.Len(t, headersMap, 2) + header, ok := headersMap["hash1"] + require.True(t, ok) + require.Equal(t, uint64(1), header.GetNonce()) + header, ok = headersMap["hash2"] + require.True(t, ok) + require.Equal(t, uint64(2), header.GetNonce()) +} + +func TestHeadersForBlock_Reset(t *testing.T) { + t.Parallel() + + hfb, err := headerForBlock.NewHeadersForBlock(createMockArgs()) + require.NoError(t, err) + + hfb.AddHeaderUsedInBlock("hash1", &testscommon.HeaderHandlerStub{}) + hfb.AddHeaderUsedInBlock("hash2", &testscommon.HeaderHandlerStub{}) + + hfb.Reset() + + hfbMap := hfb.GetHeadersInfoMap() + require.Empty(t, hfbMap) +} + +func TestHeadersForBlock_RequestAndWaitHeaders(t *testing.T) { + t.Parallel() + + t.Run("request meta headers should work", testRequestAndWaitHeaders(true)) + t.Run("request shard headers should work", testRequestAndWaitHeaders(false)) +} + +func testRequestAndWaitHeaders(requestMetaHeaders bool) func(t *testing.T) { + return func(t *testing.T) { + t.Parallel() + + // - one header exists in cache with proof (idx 1) + // - one header exists in cache without proof (idx 2) + // - one proof exists in cache without header (idx 3) + // - one header is missing completely (idx 4) + + td := createTestData(5, requestMetaHeaders) + + args := createMockArgs() + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true // return true so finality is based on proofs + }, + } + poolsHolder, ok := args.DataPool.(*dataRetriever.PoolsHolderMock) + require.True(t, ok) + + headersPoolStub := createPoolsHolderForHeaderRequests() + poolsHolder.SetHeadersPool(headersPoolStub) + headersPool := poolsHolder.Headers() + // adding the existing headers + headersPool.AddHeader(td[1].headerHash, td[1].header) + headersPool.AddHeader(td[2].headerHash, td[2].header) + + proofsPoolStub := createProofsPoolForHeaderRequests() + poolsHolder.SetProofsPool(proofsPoolStub) + proofsPool := poolsHolder.Proofs() + // adding existing proofs + proofsPool.AddProof(&block.HeaderProof{ + HeaderHash: td[1].headerHash, + }) + proofsPool.AddProof(&block.HeaderProof{ + HeaderHash: td[3].headerHash, + }) + + args.BlockTracker = &mock.BlockTrackerStub{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return td[0].header, nil, nil + }, + } + + hfb, err := headerForBlock.NewHeadersForBlock(args) + require.NoError(t, err) + + if requestMetaHeaders { + referencedMetaHeaders := [][]byte{td[1].headerHash, td[2].headerHash, td[3].headerHash, td[4].headerHash} + header := &block.HeaderV2{ + Header: &block.Header{ + MetaBlockHashes: referencedMetaHeaders, + }, + } + hfb.RequestMetaHeaders(&block.HeaderV2{}) // for coverage only, no shard data + hfb.RequestMetaHeaders(header) + } else { + referencedHeaders := []*headerData{td[1], td[2], td[3], td[4]} + shardInfo := createShardInfo(referencedHeaders) + metaBlock := &block.MetaBlock{ + ShardInfo: shardInfo, + } + hfb.RequestShardHeaders(&block.MetaBlock{}) // for coverage only, no shard data + hfb.RequestShardHeaders(metaBlock) + } + + missingHdrs, missingProofs, missingFinalityAttesting := hfb.GetMissingData() + require.Equal(t, uint32(2), missingHdrs) + require.Equal(t, uint32(1), missingProofs) // only one proof missing here as the second one will be observed when header is received + require.Zero(t, missingFinalityAttesting) + + go func() { + time.Sleep(time.Millisecond * 200) + // simulate receiving missing stuff + proofsPool.AddProof(&block.HeaderProof{ + HeaderHash: td[2].headerHash, + }) + + headersPool.AddHeader(td[3].headerHash, td[3].header) + + headersPool.AddHeader(td[4].headerHash, td[4].header) + proofsPool.AddProof(&block.HeaderProof{ + HeaderHash: td[4].headerHash, + }) + }() + + err = hfb.WaitForHeadersIfNeeded(func() time.Duration { + return time.Second * 2 + }) + require.NoError(t, err) + + missingHdrs, missingProofs, missingFinalityAttesting = hfb.GetMissingData() + require.Zero(t, missingHdrs) + require.Zero(t, missingProofs) + require.Zero(t, missingFinalityAttesting) + } +} + +func TestHeadersForBlock_GetHeaderInfo(t *testing.T) { + t.Parallel() + + td := createTestData(3, false) + + args := createMockArgs() + args.ShardCoordinator = &testscommon.ShardsCoordinatorMock{ + NoShards: 1, + } + poolsHolder, ok := args.DataPool.(*dataRetriever.PoolsHolderMock) + require.True(t, ok) + + headersPoolStub := createPoolsHolderForHeaderRequests() + poolsHolder.SetHeadersPool(headersPoolStub) + headersPool := poolsHolder.Headers() + // adding the existing header + headersPool.AddHeader(td[1].headerHash, td[1].header) + + args.BlockTracker = &mock.BlockTrackerStub{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return td[0].header, nil, nil + }, + } + + hfb, err := headerForBlock.NewHeadersForBlock(args) + require.NoError(t, err) + + referencedHeaders := []*headerData{td[1]} + shardInfo := createShardInfo(referencedHeaders) + metaBlock := &block.MetaBlock{ + ShardInfo: shardInfo, + } + hfb.RequestShardHeaders(metaBlock) + + missingHdrs, missingProofs, missingFinalityAttesting := hfb.GetMissingData() + require.Zero(t, missingHdrs) + require.Zero(t, missingProofs) + require.Equal(t, uint32(1), missingFinalityAttesting) + + go func() { + time.Sleep(time.Millisecond * 200) + // simulate receiving missing stuff + headersPool.AddHeader(td[2].headerHash, td[2].header) + }() + + err = hfb.WaitForHeadersIfNeeded(func() time.Duration { + return time.Second * 2 + }) + require.NoError(t, err) + + missingHdrs, missingProofs, missingFinalityAttesting = hfb.GetMissingData() + require.Zero(t, missingHdrs) + require.Zero(t, missingProofs) + require.Zero(t, missingFinalityAttesting) +} + +func TestBaseProcessor_FilterHeadersWithoutProofs(t *testing.T) { + t.Parallel() + + headersForCurrentBlock := map[string]data.HeaderHandler{ + "hash0": &testscommon.HeaderHandlerStub{ + EpochField: 12, + GetNonceCalled: func() uint64 { + return 1 + }, + GetShardIDCalled: func() uint32 { + return 0 + }, + }, + "hash1": &testscommon.HeaderHandlerStub{ + EpochField: 12, + GetNonceCalled: func() uint64 { + return 1 + }, + GetShardIDCalled: func() uint32 { + return 1 + }, + }, + "hash2": &testscommon.HeaderHandlerStub{ + EpochField: 12, // no proof for this one, should be marked for deletion + GetNonceCalled: func() uint64 { + return 2 + }, + GetShardIDCalled: func() uint32 { + return 0 + }, + }, + "hash3": &testscommon.HeaderHandlerStub{ + EpochField: 1, // flag not active, for coverage only + GetNonceCalled: func() uint64 { + return 2 + }, + GetShardIDCalled: func() uint32 { + return 1 + }, + }, + } + + args := createMockArgs() + args.ShardCoordinator = &testscommon.ShardsCoordinatorMock{ + NoShards: 2, + } + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return epoch == 12 + }, + } + poolsHolder, ok := args.DataPool.(*dataRetriever.PoolsHolderMock) + require.True(t, ok) + proofsPoolStub := &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return string(headerHash) != "hash2" + }, + } + poolsHolder.SetProofsPool(proofsPoolStub) + + hfb, _ := headerForBlock.NewHeadersForBlock(args) + + for hash, header := range headersForCurrentBlock { + hfb.AddHeaderUsedInBlock(hash, header) + } + + // this call should fail because header with nonce 2 from shard 0 (hash2) does not have proof + // and there is no other header with the same nonce and proof + headersWithProofs, err := hfb.FilterHeadersWithoutProofs() + require.True(t, errors.Is(err, process.ErrMissingHeaderProof)) + require.Nil(t, headersWithProofs) + + // add one more header with same nonce as hash2, but this one has proof + hfb.AddHeaderUsedInBlock( + "hash4", + &testscommon.HeaderHandlerStub{ + EpochField: 12, // same nonce as above, but this one has proof + GetNonceCalled: func() uint64 { + return 2 + }, + GetShardIDCalled: func() uint32 { + return 0 + }, + }, + ) + + // this call should succeed, as for nonce 2 in shard 0 we have 2 headers, hash2 and hash4, but hash4 has proof + headersWithProofs, err = hfb.FilterHeadersWithoutProofs() + require.NoError(t, err) + require.Equal(t, 4, len(headersWithProofs)) + + returnedHashes := make([]string, 0, len(headersWithProofs)) + for hash := range headersWithProofs { + returnedHashes = append(returnedHashes, hash) + } + slices.Sort(returnedHashes) + + expectedSortedHashes := []string{"hash0", "hash1", "hash3", "hash4"} + require.Equal(t, expectedSortedHashes, returnedHashes) +} + +func TestHeadersForBlock_requestMissingAndUpdateBasedOnCrossShardData(t *testing.T) { + t.Parallel() + + t.Run("could not find last notarized on genesis nonce", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.GenesisNonce = 0 + hfb, err := headerForBlock.NewHeadersForBlock(args) + require.NoError(t, err) + + counter := 0 + hfb.RequestMissingAndUpdateBasedOnCrossShardData( + &headersForBlockMocks.CrossShardMetaDataMock{ + GetShardIdCalled: func() uint32 { + counter++ + return 0 + }, + }, + ) + + // GetShardIdCalled should be called twice on this flow + require.Equal(t, 2, counter) + }) + + t.Run("found it, but with different hashes", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.GenesisNonce = 0 + hfb, err := headerForBlock.NewHeadersForBlock(args) + require.NoError(t, err) + + hfb.UpdateLastNotarizedBlockForShard(&block.HeaderV3{ + ShardID: 1, + }, []byte("hash")) + + counter := 0 + hfb.RequestMissingAndUpdateBasedOnCrossShardData( + &headersForBlockMocks.CrossShardMetaDataMock{ + GetShardIdCalled: func() uint32 { + return 1 + }, + GetHeaderHashCalled: func() []byte { + counter++ + return []byte("wrongHash") + }, + }, + ) + + // GetHeaderHashCalled should be called twice on this flow + require.Equal(t, 2, counter) + }) + + t.Run("should increase missing headers and call RequestShardHeader", func(t *testing.T) { + t.Parallel() + + counter := 0 + var mutRequestShardHeader sync.Mutex + wg := &sync.WaitGroup{} + wg.Add(1) + + args := createMockArgs() + args.RequestHandler = &testscommon.RequestHandlerStub{ + RequestShardHeaderCalled: func(shardID uint32, hash []byte) { + mutRequestShardHeader.Lock() + counter++ + mutRequestShardHeader.Unlock() + + wg.Done() + }, + } + args.DataPool = &dataRetriever.PoolsHolderStub{ + HeadersCalled: func() retriever.HeadersPool { + return &pool.HeadersPoolStub{GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, errorExpected + }} + }, + ProofsCalled: func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{} + }, + } + + hfb, err := headerForBlock.NewHeadersForBlock(args) + require.NoError(t, err) + + hfb.RequestMissingAndUpdateBasedOnCrossShardData(&block.ShardData{ + Nonce: 1, + HeaderHash: []byte("hash"), + }) + + wg.Wait() + + mutRequestShardHeader.Lock() + require.Equal(t, 1, counter) + mutRequestShardHeader.Unlock() + }) + + t.Run("should request proofs", func(t *testing.T) { + t.Parallel() + + counter := 0 + var mutRequestEquivalentProof sync.Mutex + wg := &sync.WaitGroup{} + wg.Add(1) + + args := createMockArgs() + args.RequestHandler = &testscommon.RequestHandlerStub{ + RequestEquivalentProofByHashCalled: func(shardID uint32, hash []byte) { + mutRequestEquivalentProof.Lock() + counter++ + mutRequestEquivalentProof.Unlock() + + wg.Done() + }, + } + args.DataPool = &dataRetriever.PoolsHolderStub{ + HeadersCalled: func() retriever.HeadersPool { + return &pool.HeadersPoolStub{GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.HeaderV3{}, nil + }} + }, + ProofsCalled: func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return false + }, + } + }, + } + + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + } + + hfb, err := headerForBlock.NewHeadersForBlock(args) + require.NoError(t, err) + + hfb.RequestMissingAndUpdateBasedOnCrossShardData(&block.ShardData{ + Nonce: 1, + HeaderHash: []byte("hash"), + }) + + wg.Wait() + + mutRequestEquivalentProof.Lock() + require.Equal(t, 1, counter) + mutRequestEquivalentProof.Unlock() + }) +} + +func TestHeadersForBlock_computeExistingAndRequestMissingShardHeaders(t *testing.T) { + t.Parallel() + + t.Run("should work for headerV3", func(t *testing.T) { + t.Parallel() + + shardInfoHandlers := []block.ShardDataProposal{ + { + HeaderHash: []byte("hash1"), + Nonce: 1, + }, + { + HeaderHash: []byte("hash2"), + Nonce: 2, + }, + { + HeaderHash: []byte("hash3"), + Nonce: 3, + }, + } + + counter := 0 + var mutRequestShardHeader sync.Mutex + wg := &sync.WaitGroup{} + wg.Add(len(shardInfoHandlers)) + + args := createMockArgs() + args.RequestHandler = &testscommon.RequestHandlerStub{ + RequestShardHeaderCalled: func(shardID uint32, hash []byte) { + mutRequestShardHeader.Lock() + counter++ + mutRequestShardHeader.Unlock() + + wg.Done() + }, + } + args.DataPool = &dataRetriever.PoolsHolderStub{ + HeadersCalled: func() retriever.HeadersPool { + return &pool.HeadersPoolStub{GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, errorExpected + }} + }, + ProofsCalled: func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{} + }, + } + + hfb, err := headerForBlock.NewHeadersForBlock(args) + require.NoError(t, err) + + metaBlockV3 := &block.MetaBlockV3{ + ShardInfoProposal: shardInfoHandlers, + } + hfb.ComputeExistingAndRequestMissingShardHeaders(metaBlockV3) + + wg.Wait() + + mutRequestShardHeader.Lock() + // counter should be incremented on the RequestShardHeader call for each shard info handler + require.Equal(t, 3, counter) + mutRequestShardHeader.Unlock() + }) + + t.Run("should work for other headers", func(t *testing.T) { + t.Parallel() + + shardInfoHandlers := []block.ShardData{ + { + HeaderHash: []byte("hash1"), + Nonce: 1, + }, + { + HeaderHash: []byte("hash2"), + Nonce: 2, + }, + { + HeaderHash: []byte("hash3"), + Nonce: 3, + }, + } + + counter := 0 + var mutRequestShardHeader sync.Mutex + wg := &sync.WaitGroup{} + wg.Add(len(shardInfoHandlers)) + + args := createMockArgs() + args.RequestHandler = &testscommon.RequestHandlerStub{ + RequestShardHeaderCalled: func(shardID uint32, hash []byte) { + mutRequestShardHeader.Lock() + counter++ + mutRequestShardHeader.Unlock() + + wg.Done() + }, + } + args.DataPool = &dataRetriever.PoolsHolderStub{ + HeadersCalled: func() retriever.HeadersPool { + return &pool.HeadersPoolStub{GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, errorExpected + }} + }, + ProofsCalled: func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{} + }, + } + + hfb, err := headerForBlock.NewHeadersForBlock(args) + require.NoError(t, err) + + metaBlock := &block.MetaBlock{ + ShardInfo: shardInfoHandlers, + } + hfb.ComputeExistingAndRequestMissingShardHeaders(metaBlock) + + wg.Wait() + + mutRequestShardHeader.Lock() + // counter should be incremented on the RequestShardHeader call for each shard info handler + require.Equal(t, 3, counter) + mutRequestShardHeader.Unlock() + }) +} + +func TestHeadersForBlock_AddHeaderNotUsedInBlock(t *testing.T) { + t.Parallel() + + args := createMockArgs() + + hfb, err := headerForBlock.NewHeadersForBlock(args) + require.NoError(t, err) + + hfb.AddHeaderNotUsedInBlock("hash", &block.HeaderV3{}) + header, found := hfb.GetHeaderInfo("hash") + require.True(t, found) + require.False(t, header.UsedInBlock()) +} + +func TestHeadersForBlock_ComputeHeadersForCurrentBlock(t *testing.T) { + t.Parallel() + + t.Run("if nonce has no proof, missing proof error should be returned", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.DataPool = &dataRetriever.PoolsHolderStub{ + ProofsCalled: func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return string(headerHash) == "hash1" + }, + } + }, + HeadersCalled: func() retriever.HeadersPool { + return &pool.HeadersPoolStub{} + }, + } + + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + } + + hfb, err := headerForBlock.NewHeadersForBlock(args) + require.NoError(t, err) + + hfb.AddHeaderUsedInBlock("hash1", &block.HeaderV3{ + Nonce: 1, + ShardID: 4294967295, + }) + hfb.AddHeaderNotUsedInBlock("hash2", &block.HeaderV3{ + Nonce: 2, + ShardID: 4294967295, + }) + + _, err = hfb.ComputeHeadersForCurrentBlock(true) + require.ErrorContains(t, err, process.ErrMissingHeaderProof.Error()) + + _, err = hfb.ComputeHeadersForCurrentBlock(false) + require.ErrorContains(t, err, process.ErrMissingHeaderProof.Error()) + + _, err = hfb.ComputeHeadersForCurrentBlockInfo(true) + require.Nil(t, err) + _, err = hfb.ComputeHeadersForCurrentBlockInfo(false) + require.ErrorContains(t, err, process.ErrMissingHeaderProof.Error()) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.DataPool = &dataRetriever.PoolsHolderStub{ + ProofsCalled: func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + }, + HeadersCalled: func() retriever.HeadersPool { + return &pool.HeadersPoolStub{} + }, + } + + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + } + + hfb, err := headerForBlock.NewHeadersForBlock(args) + require.NoError(t, err) + + hfb.AddHeaderUsedInBlock("hash1", &block.HeaderV3{ + Nonce: 1, + ShardID: common.MetachainShardId, + }) + hfb.AddHeaderNotUsedInBlock("hash2", &block.HeaderV3{ + Nonce: 2, + ShardID: common.MetachainShardId, + }) + + headers, err := hfb.ComputeHeadersForCurrentBlock(true) + require.NoError(t, err) + require.Equal(t, 1, len(headers)) + + headersInfo, err := hfb.ComputeHeadersForCurrentBlockInfo(true) + require.NoError(t, err) + require.Equal(t, uint64(1), headersInfo[common.MetachainShardId][0].GetNonce()) + require.Equal(t, []byte("hash1"), headersInfo[common.MetachainShardId][0].GetHash()) + + headers, err = hfb.ComputeHeadersForCurrentBlock(false) + require.NoError(t, err) + require.Equal(t, 1, len(headers)) + + headersInfo, err = hfb.ComputeHeadersForCurrentBlockInfo(false) + require.NoError(t, err) + require.Equal(t, uint64(2), headersInfo[common.MetachainShardId][0].GetNonce()) + require.Equal(t, []byte("hash2"), headersInfo[common.MetachainShardId][0].GetHash()) + }) +} + +type headerData struct { + header data.HeaderHandler + headerHash []byte +} + +func createPoolsHolderForHeaderRequests() retriever.HeadersPool { + headersInPool := make(map[string]data.HeaderHandler) + mutHeadersInPool := sync.RWMutex{} + errNotFound := errors.New("header not found") + + handlers := make([]func(header data.HeaderHandler, shardHeaderHash []byte), 0) + mutHandlers := sync.RWMutex{} + + return &pool.HeadersPoolStub{ + AddCalled: func(headerHash []byte, header data.HeaderHandler) { + mutHeadersInPool.Lock() + headersInPool[string(headerHash)] = header + mutHeadersInPool.Unlock() + + mutHandlers.RLock() + defer mutHandlers.RUnlock() + for _, handler := range handlers { + handler(header, headerHash) + } + }, + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + mutHeadersInPool.RLock() + defer mutHeadersInPool.RUnlock() + if h, ok := headersInPool[string(hash)]; ok { + return h, nil + } + return nil, errNotFound + }, + GetHeaderByNonceAndShardIdCalled: func(hdrNonce uint64, shardId uint32) ([]data.HeaderHandler, [][]byte, error) { + mutHeadersInPool.RLock() + defer mutHeadersInPool.RUnlock() + for hash, h := range headersInPool { + if h.GetNonce() == hdrNonce && h.GetShardID() == shardId { + return []data.HeaderHandler{h}, [][]byte{[]byte(hash)}, nil + } + } + return nil, nil, errNotFound + }, + RegisterHandlerCalled: func(handler func(header data.HeaderHandler, shardHeaderHash []byte)) { + mutHandlers.Lock() + defer mutHandlers.Unlock() + handlers = append(handlers, handler) + }, + } +} + +func createProofsPoolForHeaderRequests() retriever.ProofsPool { + proofsInPool := make(map[string]data.HeaderProofHandler) + mutProofsInPool := sync.RWMutex{} + + handlers := make([]func(header data.HeaderProofHandler), 0) + mutHandlers := sync.RWMutex{} + + return &dataRetriever.ProofsPoolMock{ + AddProofCalled: func(headerProof data.HeaderProofHandler) bool { + mutProofsInPool.Lock() + proofsInPool[string(headerProof.GetHeaderHash())] = headerProof + mutProofsInPool.Unlock() + + mutHandlers.RLock() + defer mutHandlers.RUnlock() + for _, handler := range handlers { + handler(headerProof) + } + + return true + }, + HasProofCalled: func(_ uint32, headerHash []byte) bool { + mutProofsInPool.RLock() + defer mutProofsInPool.RUnlock() + _, ok := proofsInPool[string(headerHash)] + return ok + }, + RegisterHandlerCalled: func(handler func(headerProof data.HeaderProofHandler)) { + mutHandlers.Lock() + defer mutHandlers.Unlock() + handlers = append(handlers, handler) + }, + } +} + +func createTestData(numHeaders uint32, requestMetaHeaders bool) []*headerData { + testData := make([]*headerData, numHeaders) + for i := uint32(0); i < numHeaders; i++ { + if requestMetaHeaders { + testData[i] = &headerData{ + header: &block.MetaBlock{ + Round: 100, + Nonce: uint64(i), + }, + headerHash: []byte(fmt.Sprintf("hash%d", i)), + } + + continue + } + + testData[i] = &headerData{ + header: &block.HeaderV2{ + Header: &block.Header{ + ShardID: 0, + Round: 100, + Nonce: uint64(i), + }, + }, + headerHash: []byte(fmt.Sprintf("hash%d", i)), + } + } + + return testData +} + +func createShardInfo(referencedHeaders []*headerData) []block.ShardData { + shardData := make([]block.ShardData, len(referencedHeaders)) + for i, h := range referencedHeaders { + shardData[i] = block.ShardData{ + HeaderHash: h.headerHash, + Round: h.header.GetRound(), + PrevHash: h.header.GetPrevHash(), + Nonce: h.header.GetNonce(), + ShardID: h.header.GetShardID(), + } + } + + return shardData +} diff --git a/process/block/headerForBlock/interface.go b/process/block/headerForBlock/interface.go new file mode 100644 index 00000000000..130445037af --- /dev/null +++ b/process/block/headerForBlock/interface.go @@ -0,0 +1,36 @@ +package headerForBlock + +import "github.com/multiversx/mx-chain-core-go/data" + +// HeaderInfo holds the information about a header +type HeaderInfo interface { + GetHeader() data.HeaderHandler + UsedInBlock() bool + HasProof() bool + HasProofRequested() bool + SetUsedInBlock(bool) + SetHeader(data.HeaderHandler) + SetHasProof(bool) + SetHasProofRequested(bool) + IsInterfaceNil() bool +} + +// LastNotarizedHeaderInfoHandler is an interface that has the methods for the last notarized header info +type LastNotarizedHeaderInfoHandler interface { + GetHeader() data.HeaderHandler + GetHash() []byte + SetHeader(hdr data.HeaderHandler) + SetHash(hash []byte) + HasProof() bool + SetHasProof(hasProof bool) + NotarizedBasedOnProof() bool + SetNotarizedBasedOnProof(notarizedBasedOnProof bool) + IsInterfaceNil() bool +} + +// NonceAndHashInfo defines a component that holds nonce and hash +type NonceAndHashInfo interface { + GetNonce() uint64 + GetHash() []byte + IsInterfaceNil() bool +} diff --git a/process/block/headerForBlock/lastNotarizedHeaderInfo.go b/process/block/headerForBlock/lastNotarizedHeaderInfo.go new file mode 100644 index 00000000000..b2b3630032b --- /dev/null +++ b/process/block/headerForBlock/lastNotarizedHeaderInfo.go @@ -0,0 +1,95 @@ +package headerForBlock + +import ( + "sync" + + "github.com/multiversx/mx-chain-core-go/data" +) + +type lastNotarizedHeaderInfo struct { + sync.RWMutex + header data.HeaderHandler + hash []byte + notarizedBasedOnProof bool + hasProof bool +} + +// newLastNotarizedHeaderInfo returns a new instance of lastNotarizedHeaderInfo +func newLastNotarizedHeaderInfo( + header data.HeaderHandler, + hash []byte, + notarizedBasedOnProof bool, + hasProof bool, +) *lastNotarizedHeaderInfo { + return &lastNotarizedHeaderInfo{ + header: header, + hash: hash, + notarizedBasedOnProof: notarizedBasedOnProof, + hasProof: hasProof, + } +} + +// GetHeader returns the header +func (lnhi *lastNotarizedHeaderInfo) GetHeader() data.HeaderHandler { + lnhi.RLock() + defer lnhi.RUnlock() + + return lnhi.header +} + +// GetHash returns the header hash +func (lnhi *lastNotarizedHeaderInfo) GetHash() []byte { + lnhi.RLock() + defer lnhi.RUnlock() + + return lnhi.hash +} + +// SetHeader sets the header +func (lnhi *lastNotarizedHeaderInfo) SetHeader(hdr data.HeaderHandler) { + lnhi.Lock() + lnhi.header = hdr + lnhi.Unlock() +} + +// SetHash sets the header hash +func (lnhi *lastNotarizedHeaderInfo) SetHash(hash []byte) { + lnhi.Lock() + lnhi.hash = hash + lnhi.Unlock() +} + +// HasProof returns the hasProof field +func (lnhi *lastNotarizedHeaderInfo) HasProof() bool { + lnhi.RLock() + defer lnhi.RUnlock() + + return lnhi.hasProof +} + +// SetHasProof sets the hasProof field +func (lnhi *lastNotarizedHeaderInfo) SetHasProof(hasProof bool) { + lnhi.Lock() + lnhi.hasProof = hasProof + lnhi.Unlock() +} + +// NotarizedBasedOnProof returns the notarizedBasedOnProof field +func (lnhi *lastNotarizedHeaderInfo) NotarizedBasedOnProof() bool { + lnhi.RLock() + defer lnhi.RUnlock() + + return lnhi.notarizedBasedOnProof +} + +// SetNotarizedBasedOnProof sets the notarizedBasedOnProof field +func (lnhi *lastNotarizedHeaderInfo) SetNotarizedBasedOnProof(notarizedBasedOnProof bool) { + lnhi.Lock() + lnhi.notarizedBasedOnProof = notarizedBasedOnProof + lnhi.Unlock() +} + +// IsInterfaceNil returns true if there is no value under the interface +func (lnhi *lastNotarizedHeaderInfo) IsInterfaceNil() bool { + return lnhi == nil +} diff --git a/process/block/headerForBlock/lastNotarizedHeaderInfo_test.go b/process/block/headerForBlock/lastNotarizedHeaderInfo_test.go new file mode 100644 index 00000000000..b020acd5c62 --- /dev/null +++ b/process/block/headerForBlock/lastNotarizedHeaderInfo_test.go @@ -0,0 +1,105 @@ +package headerForBlock_test + +import ( + "fmt" + "sync" + "testing" + + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/process/block/headerForBlock" + "github.com/stretchr/testify/require" +) + +func TestNewLastNotarizedHeaderInfo(t *testing.T) { + t.Parallel() + + header := &block.Header{Nonce: 1} + hash := []byte("hash") + lnhi := headerForBlock.NewLastNotarizedHeaderInfo(header, hash, true, true) + require.NotNil(t, lnhi) + require.Equal(t, header, lnhi.GetHeader()) + require.Equal(t, hash, lnhi.GetHash()) + require.True(t, lnhi.NotarizedBasedOnProof()) + require.True(t, lnhi.HasProof()) +} + +func TestLastNotarizedHeaderInfo_GettersAndSetters(t *testing.T) { + t.Parallel() + + header1 := &block.Header{Nonce: 1} + header2 := &block.Header{Nonce: 2} + hash1 := []byte("hash1") + hash2 := []byte("hash2") + + lnhi := headerForBlock.NewLastNotarizedHeaderInfo(header1, hash1, false, false) + + // Test Get/Set Header + lnhi.SetHeader(header2) + require.Equal(t, header2, lnhi.GetHeader()) + + // Test Get/Set Hash + lnhi.SetHash(hash2) + require.Equal(t, hash2, lnhi.GetHash()) + + // Test NotarizedBasedOnProof + lnhi.SetNotarizedBasedOnProof(true) + require.True(t, lnhi.NotarizedBasedOnProof()) + + lnhi.SetNotarizedBasedOnProof(false) + require.False(t, lnhi.NotarizedBasedOnProof()) + + // Test HasProof + lnhi.SetHasProof(true) + require.True(t, lnhi.HasProof()) + + lnhi.SetHasProof(false) + require.False(t, lnhi.HasProof()) +} + +func TestLastNotarizedHeaderInfo_IsInterfaceNil(t *testing.T) { + t.Parallel() + + header := &block.Header{Nonce: 1} + lnhi := headerForBlock.NewLastNotarizedHeaderInfo(header, []byte("hash"), true, true) + require.False(t, lnhi.IsInterfaceNil()) +} + +func TestLastNotarizedHeaderInfo_Concurrency(t *testing.T) { + require.NotPanics(t, func() { + t.Parallel() + + lnhi := headerForBlock.NewLastNotarizedHeaderInfo(&block.Header{Nonce: 1}, []byte("hash"), false, false) + + numCalls := 100 + var wg sync.WaitGroup + wg.Add(numCalls) + + for i := 0; i < numCalls; i++ { + go func(i int) { + switch i % 8 { + case 0: + lnhi.SetHeader(&block.Header{Nonce: uint64(i)}) + case 1: + lnhi.SetHash([]byte(fmt.Sprintf("hash_%d", i))) + case 2: + lnhi.SetNotarizedBasedOnProof(true) + case 3: + lnhi.SetHasProof(true) + case 4: + lnhi.GetHeader() + case 5: + lnhi.GetHash() + case 6: + lnhi.NotarizedBasedOnProof() + case 7: + lnhi.HasProof() + default: + require.Fail(t, "should have not been called") + } + wg.Done() + }(i) + } + + wg.Wait() + }) +} diff --git a/process/block/headerForBlock/nonceAndHashInfo.go b/process/block/headerForBlock/nonceAndHashInfo.go new file mode 100644 index 00000000000..53c87d13064 --- /dev/null +++ b/process/block/headerForBlock/nonceAndHashInfo.go @@ -0,0 +1,21 @@ +package headerForBlock + +type nonceAndHashInfo struct { + hash []byte + nonce uint64 +} + +// GetNonce returns the nonce +func (nahi *nonceAndHashInfo) GetNonce() uint64 { + return nahi.nonce +} + +// GetHash returns the hash +func (nahi *nonceAndHashInfo) GetHash() []byte { + return nahi.hash +} + +// IsInterfaceNil returns true if there is no value under the interface +func (nahi *nonceAndHashInfo) IsInterfaceNil() bool { + return nahi == nil +} diff --git a/process/block/helpers/txcleanup.go b/process/block/helpers/txcleanup.go new file mode 100644 index 00000000000..e3001ffad93 --- /dev/null +++ b/process/block/helpers/txcleanup.go @@ -0,0 +1,23 @@ +package helpers + +import ( + "crypto/sha256" + "encoding/binary" + + "github.com/multiversx/mx-chain-core-go/data/block" +) + +// ComputeRandomnessForCleanup returns the randomness for bad transactions removal +// TODO Maybe find a simpler solution for randomness calculation +func ComputeRandomnessForCleanup(body *block.Body) uint64 { + randomness := uint64(0) + + if len(body.MiniBlocks) != 0 && len(body.MiniBlocks[0].TxHashes) != 0 { + firstHash := body.MiniBlocks[0].TxHashes[len(body.MiniBlocks[0].TxHashes)-1] + hashSum := sha256.Sum256(firstHash) + + randomness = binary.LittleEndian.Uint64(hashSum[:8]) % uint64(len(body.MiniBlocks[0].TxHashes)) + } + + return randomness +} diff --git a/process/block/helpers/txcleanup_test.go b/process/block/helpers/txcleanup_test.go new file mode 100644 index 00000000000..8f5b227702f --- /dev/null +++ b/process/block/helpers/txcleanup_test.go @@ -0,0 +1,60 @@ +package helpers + +import ( + "fmt" + "testing" + + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/stretchr/testify/assert" +) + +func getMockBody(hash []byte, numtxsPerPool int) *block.Body { + body := &block.Body{} + selfShard := 0 + numShards := 3 + + for i := 0; i < numShards; i++ { + txHashes := [][]byte{} + + for j := 0; j < numtxsPerPool; j++ { + txHashes = append(txHashes, fmt.Appendf(nil, "hash-%d-%d", i, j)) + } + + if i == 0 { + txHashes = append(txHashes, hash) + } + + body.MiniBlocks = append(body.MiniBlocks, &block.MiniBlock{ + SenderShardID: uint32(selfShard), + ReceiverShardID: uint32(i), + TxHashes: txHashes, + }) + } + + return body +} + +func TestComputeRandomnessForTxCleaning(t *testing.T) { + t.Parallel() + + t.Run("randomness", func(t *testing.T) { + body := getMockBody([]byte("alice1-hash"), 5) + assert.Equal(t, ComputeRandomnessForCleanup(body), uint64(3)) + body = getMockBody([]byte("bob1-hash"), 5) + assert.Equal(t, ComputeRandomnessForCleanup(body), uint64(5)) + body = getMockBody([]byte("bob4-hash"), 3) + assert.Equal(t, ComputeRandomnessForCleanup(body), uint64(0)) + body = getMockBody([]byte("alice1-hash"), 17) + assert.Equal(t, ComputeRandomnessForCleanup(body), uint64(3)) + body = getMockBody([]byte("bob1-hash"), 17) + assert.Equal(t, ComputeRandomnessForCleanup(body), uint64(5)) + body = getMockBody([]byte("48602391228527b14364f15eb9145e62ad0a338816b4d8bee01344e158508911"), 180) + assert.Equal(t, ComputeRandomnessForCleanup(body), uint64(9)) + body = getMockBody([]byte("8b5fa093cb63c61977a55856086e34ef7f8a00c8187f85b1ab2286714016768b"), 180) + assert.Equal(t, ComputeRandomnessForCleanup(body), uint64(14)) + body = getMockBody([]byte("9ac874ca88e071db6fa64e654d95480761a1ea10aac0e8e99db7df5cfdfd3157"), 25000) + assert.Equal(t, ComputeRandomnessForCleanup(body), uint64(19779)) + body = getMockBody([]byte("4f8a89dd647ee6f936949eb407935901a2e2fd84f6b76c532fea0c839fe01f19"), 25000) + assert.Equal(t, ComputeRandomnessForCleanup(body), uint64(2701)) + }) +} diff --git a/process/block/interceptedBlocks/common.go b/process/block/interceptedBlocks/common.go index e58e6f8a447..cdd6133caa5 100644 --- a/process/block/interceptedBlocks/common.go +++ b/process/block/interceptedBlocks/common.go @@ -89,7 +89,7 @@ func checkHeaderHandler( ) error { equivalentMessagesEnabled := enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, hdr.GetEpoch()) - if len(hdr.GetPubKeysBitmap()) == 0 && !equivalentMessagesEnabled { + if !hdr.IsHeaderV3() && len(hdr.GetPubKeysBitmap()) == 0 && !equivalentMessagesEnabled { return process.ErrNilPubKeysBitmap } if len(hdr.GetPrevHash()) == 0 { @@ -98,7 +98,7 @@ func checkHeaderHandler( if len(hdr.GetSignature()) == 0 && !equivalentMessagesEnabled { return process.ErrNilSignature } - if len(hdr.GetRootHash()) == 0 { + if !hdr.IsHeaderV3() && len(hdr.GetRootHash()) == 0 { return process.ErrNilRootHash } if len(hdr.GetRandSeed()) == 0 { @@ -108,7 +108,11 @@ func checkHeaderHandler( return process.ErrNilPrevRandSeed } - return hdr.CheckFieldsForNil() + err := hdr.CheckFieldsForNil() + if err != nil { + return err + } + return hdr.CheckFieldsIntegrity() } func checkMetaShardInfo( diff --git a/process/block/interceptedBlocks/common_test.go b/process/block/interceptedBlocks/common_test.go index 95ef832773c..5c0129f610d 100644 --- a/process/block/interceptedBlocks/common_test.go +++ b/process/block/interceptedBlocks/common_test.go @@ -1,6 +1,7 @@ package interceptedBlocks import ( + "math/big" "testing" "github.com/multiversx/mx-chain-core-go/core" @@ -70,6 +71,175 @@ func createDefaultHeaderHandler() *testscommon.HeaderHandlerStub { } } +func createValidHeaderV3ToTest() *block.HeaderV3 { + return &block.HeaderV3{ + Nonce: 101, + Round: 1200, + Epoch: 1, + PrevHash: []byte("prev hash"), + PrevRandSeed: []byte("prev rand seed"), + RandSeed: []byte("rand seed"), + LeaderSignature: []byte("leader signature"), + SoftwareVersion: []byte("v1.0.0"), + + ExecutionResults: []*block.ExecutionResult{ + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("header hash"), + HeaderNonce: 98, + HeaderRound: 1001, + HeaderEpoch: 0, + RootHash: []byte("root hash"), + }, + }, + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("header hash"), + HeaderNonce: 99, + HeaderRound: 1020, + HeaderEpoch: 0, + RootHash: []byte("root hash"), + }, + }, + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("header hash"), + HeaderNonce: 100, + HeaderRound: 1100, + HeaderEpoch: 1, + RootHash: []byte("root hash"), + }, + }, + }, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("header hash"), + HeaderNonce: 100, + HeaderRound: 1100, + HeaderEpoch: 1, + RootHash: []byte("root hash"), + }, + NotarizedInRound: 1199, + }, + } +} + +func createValidMetaHeaderV3ToTest() *block.MetaBlockV3 { + return &block.MetaBlockV3{ + Nonce: 42, + Epoch: 2, + Round: 15, + TimestampMs: 123456789, + PrevHash: []byte("prev_hash"), + PrevRandSeed: []byte("prev_seed"), + RandSeed: []byte("new_seed"), + ChainID: []byte("chain-id"), + SoftwareVersion: []byte("v1.0.0"), + LeaderSignature: []byte("leader_signature"), + + MiniBlockHeaders: []block.MiniBlockHeader{ + {Hash: []byte("meta-to-s0"), SenderShardID: core.MetachainShardId, ReceiverShardID: 0}, + {Hash: []byte("meta-to-s1"), SenderShardID: core.MetachainShardId, ReceiverShardID: 1}, + }, + + ShardInfo: []block.ShardData{ + { + ShardID: 0, + Round: 10, + Nonce: 41, + Epoch: 1, + HeaderHash: []byte("shard0-hash"), + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + {SenderShardID: 0, ReceiverShardID: 1, Hash: []byte("s0-to-s1")}, + }, + }, + { + ShardID: 1, + Round: 11, + Nonce: 40, + Epoch: 1, + HeaderHash: []byte("shard1-hash"), + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + {SenderShardID: 1, ReceiverShardID: 0, Hash: []byte("s1-to-s0")}, + }, + }, + }, + ShardInfoProposal: []block.ShardDataProposal{ + {ShardID: 0, HeaderHash: []byte("shard-0-hash"), Nonce: 41, Round: 10, Epoch: 1}, + {ShardID: 1, HeaderHash: []byte("shard-1-hash"), Nonce: 40, Round: 11, Epoch: 1}, + }, + ExecutionResults: []*block.MetaExecutionResult{ + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hdr-hash-10"), + HeaderNonce: 39, + HeaderRound: 10, + HeaderEpoch: 1, + RootHash: []byte("root-hash-10"), + }, + AccumulatedFeesInEpoch: big.NewInt(1000), + DevFeesInEpoch: big.NewInt(100), + ValidatorStatsRootHash: []byte("validator-stats-root-hash"), + }, + AccumulatedFees: big.NewInt(1000), + DeveloperFees: big.NewInt(100), + ReceiptsHash: []byte("receipts hash"), + }, + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hdr-hash-11"), + HeaderNonce: 40, + HeaderRound: 11, + HeaderEpoch: 1, + RootHash: []byte("root-hash-11"), + }, + AccumulatedFeesInEpoch: big.NewInt(2000), + DevFeesInEpoch: big.NewInt(200), + ValidatorStatsRootHash: []byte("validator-stats-root-hash"), + }, + AccumulatedFees: big.NewInt(2000), + DeveloperFees: big.NewInt(200), + ReceiptsHash: []byte("receipts hash"), + }, + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hdr-hash-last"), + HeaderNonce: 41, + HeaderRound: 12, + HeaderEpoch: 2, + RootHash: []byte("root-hash-last"), + }, + AccumulatedFeesInEpoch: big.NewInt(3000), + DevFeesInEpoch: big.NewInt(300), + ValidatorStatsRootHash: []byte("validator-stats-root-hash"), + }, + AccumulatedFees: big.NewInt(3000), + DeveloperFees: big.NewInt(300), + ReceiptsHash: []byte("receipts hash"), + }, + }, + + LastExecutionResult: &block.MetaExecutionResultInfo{ + NotarizedInRound: 14, + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hdr-hash-last"), + HeaderNonce: 41, + HeaderRound: 12, + HeaderEpoch: 2, + RootHash: []byte("root-hash-last"), + }, + AccumulatedFeesInEpoch: big.NewInt(3000), + DevFeesInEpoch: big.NewInt(300), + ValidatorStatsRootHash: []byte("validator-stats-root-hash"), + }, + }, + } +} + // -------- checkBlockHeaderArgument func TestCheckBlockHeaderArgument_NilArgumentShouldErr(t *testing.T) { @@ -356,6 +526,151 @@ func TestCheckHeaderHandler_ShouldWork(t *testing.T) { assert.Nil(t, err) } +func TestCheckHeaderHandler_HeaderV3_CheckFieldsIntegrityErrors(t *testing.T) { + t.Parallel() + + enableEpochsHandler := enableEpochsHandlerMock.NewEnableEpochsHandlerStub() + enableEpochsHandler.IsFlagEnabledInEpochCalled = func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + } + t.Run("nil header", func(t *testing.T) { + t.Parallel() + var hv3 *block.HeaderV3 + err := checkHeaderHandler(hv3, enableEpochsHandler) + assert.Error(t, err) + }) + t.Run("not nil receipt hash field", func(t *testing.T) { + t.Parallel() + hv3 := createValidHeaderV3ToTest() + hv3.ReceiptsHash = []byte("not nil receipts hash") + err := checkHeaderHandler(hv3, enableEpochsHandler) + assert.True(t, hv3.IsHeaderV3()) + assert.Error(t, err) + assert.ErrorIs(t, err, data.ErrNotNilValue) + }) + t.Run("not nil reserved field", func(t *testing.T) { + t.Parallel() + hv3 := createValidHeaderV3ToTest() + hv3.Reserved = []byte("not nil reserved") + err := checkHeaderHandler(hv3, enableEpochsHandler) + assert.Error(t, err) + assert.ErrorIs(t, err, data.ErrNotNilValue) + }) + t.Run("nil last execution result", func(t *testing.T) { + t.Parallel() + hv3 := &block.HeaderV3{ + Nonce: 2, + Round: 2, + Epoch: 2, + } + err := checkHeaderHandler(hv3, enableEpochsHandler) + assert.Error(t, err) + }) + t.Run("invalid execution results", func(t *testing.T) { + t.Parallel() + hv3 := createValidHeaderV3ToTest() + hv3.ExecutionResults[0].BaseExecutionResult = nil + err := checkHeaderHandler(hv3, enableEpochsHandler) + assert.Error(t, err) + }) + t.Run("invalid last execution result", func(t *testing.T) { + t.Parallel() + hv3 := createValidHeaderV3ToTest() + hv3.LastExecutionResult.ExecutionResult = nil + err := checkHeaderHandler(hv3, enableEpochsHandler) + assert.Error(t, err) + }) + t.Run("invalid round in last execution result", func(t *testing.T) { + t.Parallel() + hv3 := createValidHeaderV3ToTest() + hv3.LastExecutionResult.NotarizedInRound = 1300 + err := checkHeaderHandler(hv3, enableEpochsHandler) + assert.Error(t, err) + }) +} +func TestCheckHeaderHandler_HeaderV3_CheckFieldsIntegrityWorks(t *testing.T) { + t.Parallel() + hdr := createValidHeaderV3ToTest() + enableEpochsHandler := enableEpochsHandlerMock.NewEnableEpochsHandlerStub() + enableEpochsHandler.IsFlagEnabledInEpochCalled = func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + } + err := checkHeaderHandler(hdr, enableEpochsHandler) + assert.Nil(t, err) +} + +func TestCheckHeaderHandler_MetaHeaderV3_CheckFieldsIntegrityErrors(t *testing.T) { + t.Parallel() + + enableEpochsHandler := enableEpochsHandlerMock.NewEnableEpochsHandlerStub() + enableEpochsHandler.IsFlagEnabledInEpochCalled = func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + } + t.Run("nil header", func(t *testing.T) { + t.Parallel() + var meta *block.MetaBlockV3 + err := checkHeaderHandler(meta, enableEpochsHandler) + assert.Error(t, err) + }) + t.Run("not nil reserved field", func(t *testing.T) { + t.Parallel() + meta := createValidMetaHeaderV3ToTest() + meta.Reserved = []byte("not nil reserved") + err := checkHeaderHandler(meta, enableEpochsHandler) + assert.Error(t, err) + assert.ErrorIs(t, err, data.ErrNotNilValue) + }) + t.Run("nil last execution result", func(t *testing.T) { + t.Parallel() + meta := &block.MetaBlockV3{ + Nonce: 2, + Round: 2, + Epoch: 2, + } + err := checkHeaderHandler(meta, enableEpochsHandler) + assert.Error(t, err) + }) + t.Run("invalid execution results", func(t *testing.T) { + t.Parallel() + meta := createValidMetaHeaderV3ToTest() + meta.ExecutionResults[0].ExecutionResult = nil + err := checkHeaderHandler(meta, enableEpochsHandler) + assert.Error(t, err) + }) + t.Run("invalid last execution result", func(t *testing.T) { + t.Parallel() + meta := createValidMetaHeaderV3ToTest() + meta.LastExecutionResult.ExecutionResult = nil + err := checkHeaderHandler(meta, enableEpochsHandler) + assert.Error(t, err) + }) + t.Run("invalid round in last execution result", func(t *testing.T) { + t.Parallel() + meta := createValidMetaHeaderV3ToTest() + meta.LastExecutionResult.NotarizedInRound = 1300 + err := checkHeaderHandler(meta, enableEpochsHandler) + assert.Error(t, err) + }) + t.Run("invalid shard info", func(t *testing.T) { + t.Parallel() + meta := createValidMetaHeaderV3ToTest() + meta.ShardInfoProposal = make([]block.ShardDataProposal, 0) + err := checkHeaderHandler(meta, enableEpochsHandler) + assert.Error(t, err) + }) +} + +func TestCheckHeaderHandler_MetaHeaderV3_CheckFieldsIntegrityWorks(t *testing.T) { + t.Parallel() + hdr := createValidMetaHeaderV3ToTest() + enableEpochsHandler := enableEpochsHandlerMock.NewEnableEpochsHandlerStub() + enableEpochsHandler.IsFlagEnabledInEpochCalled = func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + } + err := checkHeaderHandler(hdr, enableEpochsHandler) + assert.Nil(t, err) +} + // ------- checkMetaShardInfo func TestCheckMetaShardInfo_WithNilOrEmptyShouldReturnNil(t *testing.T) { diff --git a/process/block/interceptedBlocks/interceptedBlockHeader.go b/process/block/interceptedBlocks/interceptedBlockHeader.go index 181a23f5ac0..255e32ef5df 100644 --- a/process/block/interceptedBlocks/interceptedBlockHeader.go +++ b/process/block/interceptedBlocks/interceptedBlockHeader.go @@ -5,6 +5,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/hashing" logger "github.com/multiversx/mx-chain-logger-go" @@ -83,6 +84,11 @@ func (inHdr *InterceptedHeader) CheckValidity() error { return inHdr.verifySignatures() } +// ShouldAllowDuplicates returns if this type of intercepted data should allow duplicates +func (inHdr *InterceptedHeader) ShouldAllowDuplicates() bool { + return false +} + func (inHdr *InterceptedHeader) verifySignatures() error { err := inHdr.sigVerifier.VerifyRandSeedAndLeaderSignature(inHdr.hdr) if err != nil { @@ -141,7 +147,7 @@ func (inHdr *InterceptedHeader) integrity() error { return err } - if !inHdr.validityAttester.CheckBlockAgainstWhitelist(inHdr) { + if !inHdr.validityAttester.CheckAgainstWhitelist(inHdr) { err = inHdr.validityAttester.CheckBlockAgainstFinal(inHdr.HeaderHandler()) if err != nil { return err @@ -158,6 +164,18 @@ func (inHdr *InterceptedHeader) integrity() error { return err } + if inHdr.hdr.IsHeaderV3() { + for i, result := range inHdr.hdr.GetExecutionResultsHandlers() { + executionResult, ok := result.(*block.ExecutionResult) + if !ok { + return fmt.Errorf("failed to cast execution result at index %d to block.ExecutionResult", i) + } + err = checkMiniBlocksHeaders(executionResult.GetMiniBlockHeadersHandlers(), inHdr.shardCoordinator) + if err != nil { + return err + } + } + } return nil } diff --git a/process/block/interceptedBlocks/interceptedBlockHeader_test.go b/process/block/interceptedBlocks/interceptedBlockHeader_test.go index e3e03707f49..24ce863d610 100644 --- a/process/block/interceptedBlocks/interceptedBlockHeader_test.go +++ b/process/block/interceptedBlocks/interceptedBlockHeader_test.go @@ -10,6 +10,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data" dataBlock "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -22,6 +23,7 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/consensus" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" ) var testMarshalizer = &mock.MarshalizerMock{} @@ -95,6 +97,89 @@ func createMockShardHeader() *dataBlock.Header { } } +func createDefaultShardArgumentWithV3Support() *interceptedBlocks.ArgInterceptedBlockHeader { + gracePeriod, _ := graceperiod.NewEpochChangeGracePeriod([]config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}) + arg := &interceptedBlocks.ArgInterceptedBlockHeader{ + ShardCoordinator: mock.NewOneShardCoordinatorMock(), + Hasher: testHasher, + Marshalizer: &marshal.GogoProtoMarshalizer{}, + HeaderSigVerifier: &consensus.HeaderSigVerifierMock{}, + HeaderIntegrityVerifier: &mock.HeaderIntegrityVerifierStub{}, + ValidityAttester: &mock.ValidityAttesterStub{}, + EpochStartTrigger: &mock.EpochStartTriggerStub{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + }, + EpochChangeGracePeriodHandler: gracePeriod, + } + hdr := createMockShardHeaderV3() + arg.HdrBuff, _ = arg.Marshalizer.Marshal(hdr) + + return arg +} + +func createMockShardHeaderV3() *dataBlock.HeaderV3 { + return &dataBlock.HeaderV3{ + Nonce: hdrNonce, + PrevHash: []byte("prev hash"), + PrevRandSeed: []byte("prev rand seed"), + RandSeed: []byte("rand seed"), + ShardID: hdrShardId, + TimestampMs: 0, + Round: hdrRound, + Epoch: hdrEpoch, + BlockBodyType: dataBlock.TxBlock, + LeaderSignature: []byte("signature"), + MiniBlockHeaders: nil, + PeerChanges: nil, + MetaBlockHashes: nil, + TxCount: 0, + ChainID: []byte("chain ID"), + SoftwareVersion: []byte("version"), + LastExecutionResult: &dataBlock.ExecutionResultInfo{ + ExecutionResult: &dataBlock.BaseExecutionResult{ + HeaderHash: []byte("header hash"), + HeaderNonce: hdrNonce - 1, + HeaderRound: hdrRound - 1, + HeaderEpoch: hdrEpoch, + RootHash: []byte("root hash"), + }, + NotarizedInRound: hdrRound - 1, + }, + ExecutionResults: []*dataBlock.ExecutionResult{ + &dataBlock.ExecutionResult{ + BaseExecutionResult: &dataBlock.BaseExecutionResult{ + HeaderHash: []byte("header hash"), + HeaderNonce: hdrNonce - 3, + HeaderRound: hdrRound - 3, + HeaderEpoch: hdrEpoch - 1, + RootHash: []byte("root hash"), + }, + }, + &dataBlock.ExecutionResult{ + BaseExecutionResult: &dataBlock.BaseExecutionResult{ + HeaderHash: []byte("header hash"), + HeaderNonce: hdrNonce - 2, + HeaderRound: hdrRound - 2, + HeaderEpoch: hdrEpoch - 1, + RootHash: []byte("root hash"), + }, + }, + &dataBlock.ExecutionResult{ + BaseExecutionResult: &dataBlock.BaseExecutionResult{ + HeaderHash: []byte("header hash"), + HeaderNonce: hdrNonce - 1, + HeaderRound: hdrRound - 1, + HeaderEpoch: hdrEpoch, + RootHash: []byte("root hash"), + }, + }, + }, + } +} + // ------- TestNewInterceptedHeader func TestNewInterceptedHeader_NilArgumentShouldErr(t *testing.T) { @@ -274,7 +359,7 @@ func TestInterceptedHeader_CheckValidityLeaderSignatureOkWithFlagActiveShouldWor assert.True(t, wasVerifySignatureCalled) } -func TestInterceptedHeader_ErrorInMiniBlockShouldErr(t *testing.T) { +func TestInterceptedHeader_CheckValidityErrorInMiniBlockShouldErr(t *testing.T) { t.Parallel() hdr := createMockShardHeader() @@ -300,6 +385,26 @@ func TestInterceptedHeader_ErrorInMiniBlockShouldErr(t *testing.T) { err = inHdr.CheckValidity() assert.Equal(t, process.ErrInvalidShardId, err) + +} + +func TestInterceptedHeader_CheckValidityIntegrityVerifierErrorShouldErr(t *testing.T) { + t.Parallel() + + arg := createDefaultShardArgumentWithV2Support() + arg.HeaderIntegrityVerifier = &mock.HeaderIntegrityVerifierStub{ + VerifyCalled: func(header data.HeaderHandler) error { + return errors.New("expected error") + }, + } + inHdr, err := interceptedBlocks.NewInterceptedHeader(arg) + require.Nil(t, err) + require.NotNil(t, inHdr) + + err = inHdr.CheckValidity() + + assert.Error(t, err) + assert.Contains(t, err.Error(), "expected error") } func TestInterceptedHeader_CheckValidityShouldWork(t *testing.T) { @@ -315,6 +420,233 @@ func TestInterceptedHeader_CheckValidityShouldWork(t *testing.T) { assert.Nil(t, err) } +func TestInterceptedHeader_CheckValidityExecutionResultIsEpochCorrectMetaChain(t *testing.T) { + t.Parallel() + + t.Run("should work with epoch start round >= epoch finality attesting round", func(t *testing.T) { + t.Parallel() + + arg := createDefaultShardArgumentWithV3Support() + arg.ShardCoordinator = &mock.CoordinatorStub{ + NumberOfShardsCalled: func() uint32 { + return uint32(4) + }, + SelfIdCalled: func() uint32 { + return core.MetachainShardId + }, + } + arg.EpochStartTrigger = &mock.EpochStartTriggerStub{ + EpochStartRoundCalled: func() uint64 { + return hdrRound - 1 + }, + EpochFinalityAttestingRoundCalled: func() uint64 { + return hdrRound - 2 + }, + } + inHdr, _ := interceptedBlocks.NewInterceptedHeader(arg) + _ = inHdr.HeaderHandler().SetShardID(1) + + err := inHdr.CheckValidity() + assert.Nil(t, err) + }) + + t.Run("should work with header epoch < epoch start trigger epoch", func(t *testing.T) { + t.Parallel() + + arg := createDefaultShardArgumentWithV3Support() + arg.ShardCoordinator = &mock.CoordinatorStub{ + NumberOfShardsCalled: func() uint32 { + return uint32(4) + }, + SelfIdCalled: func() uint32 { + return core.MetachainShardId + }, + } + arg.EpochStartTrigger = &mock.EpochStartTriggerStub{ + EpochStartRoundCalled: func() uint64 { + return hdrRound - 2 + }, + EpochFinalityAttestingRoundCalled: func() uint64 { + return hdrRound - 1 + }, + EpochCalled: func() uint32 { + return hdrEpoch - 1 + }, + } + inHdr, _ := interceptedBlocks.NewInterceptedHeader(arg) + _ = inHdr.HeaderHandler().SetShardID(1) + + err := inHdr.CheckValidity() + assert.Nil(t, err) + }) + + t.Run("should work with header round <= epoch start round", func(t *testing.T) { + t.Parallel() + + arg := createDefaultShardArgumentWithV3Support() + arg.ShardCoordinator = &mock.CoordinatorStub{ + NumberOfShardsCalled: func() uint32 { + return uint32(4) + }, + SelfIdCalled: func() uint32 { + return core.MetachainShardId + }, + } + arg.EpochStartTrigger = &mock.EpochStartTriggerStub{ + EpochStartRoundCalled: func() uint64 { + return hdrRound + }, + EpochFinalityAttestingRoundCalled: func() uint64 { + return hdrRound + 1 + }, + EpochCalled: func() uint32 { + return hdrEpoch + 1 + }, + } + inHdr, _ := interceptedBlocks.NewInterceptedHeader(arg) + _ = inHdr.HeaderHandler().SetShardID(1) + + err := inHdr.CheckValidity() + assert.Nil(t, err) + }) + t.Run("should work with header round <= epoch finality attesting round + grace period", func(t *testing.T) { + t.Parallel() + + arg := createDefaultShardArgumentWithV3Support() + arg.ShardCoordinator = &mock.CoordinatorStub{ + NumberOfShardsCalled: func() uint32 { + return uint32(4) + }, + SelfIdCalled: func() uint32 { + return core.MetachainShardId + }, + } + arg.EpochStartTrigger = &mock.EpochStartTriggerStub{ + EpochStartRoundCalled: func() uint64 { + return hdrRound - 1 + }, + EpochFinalityAttestingRoundCalled: func() uint64 { + return hdrRound + 1 + }, + EpochCalled: func() uint32 { + return hdrEpoch + 1 + }, + } + gracePeriod, _ := graceperiod.NewEpochChangeGracePeriod([]config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}) + arg.EpochChangeGracePeriodHandler = gracePeriod + inHdr, _ := interceptedBlocks.NewInterceptedHeader(arg) + _ = inHdr.HeaderHandler().SetShardID(1) + + err := inHdr.CheckValidity() + assert.Nil(t, err) + }) + + t.Run("should fail with grace period for epoch error", func(t *testing.T) { + t.Parallel() + + arg := createDefaultShardArgumentWithV3Support() + arg.ShardCoordinator = &mock.CoordinatorStub{ + NumberOfShardsCalled: func() uint32 { + return uint32(4) + }, + SelfIdCalled: func() uint32 { + return core.MetachainShardId + }, + } + arg.EpochStartTrigger = &mock.EpochStartTriggerStub{ + EpochStartRoundCalled: func() uint64 { + return hdrRound - 1 + }, + EpochFinalityAttestingRoundCalled: func() uint64 { + return hdrRound + 1 + }, + EpochCalled: func() uint32 { + return hdrEpoch + 1 + }, + } + + arg.EpochChangeGracePeriodHandler = &processMocks.GracePeriodErrStub{} + inHdr, _ := interceptedBlocks.NewInterceptedHeader(arg) + _ = inHdr.HeaderHandler().SetShardID(1) + + err := inHdr.CheckValidity() + assert.Error(t, err) + }) + t.Run("should fail with header round > epoch finality attesting round + grace period", func(t *testing.T) { + t.Parallel() + + arg := createDefaultShardArgumentWithV3Support() + arg.ShardCoordinator = &mock.CoordinatorStub{ + NumberOfShardsCalled: func() uint32 { + return uint32(4) + }, + SelfIdCalled: func() uint32 { + return core.MetachainShardId + }, + } + arg.EpochStartTrigger = &mock.EpochStartTriggerStub{ + EpochStartRoundCalled: func() uint64 { + return hdrRound - 3 + }, + EpochFinalityAttestingRoundCalled: func() uint64 { + return hdrRound - 2 + }, + EpochCalled: func() uint32 { + return hdrEpoch + 1 + }, + } + gracePeriod, _ := graceperiod.NewEpochChangeGracePeriod([]config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 0}}) + arg.EpochChangeGracePeriodHandler = gracePeriod + inHdr, _ := interceptedBlocks.NewInterceptedHeader(arg) + _ = inHdr.HeaderHandler().SetShardID(1) + + err := inHdr.CheckValidity() + assert.Error(t, err) + }) + +} +func TestInterceptedHeader_CheckValidityExecutionResultMiniblockErrorInHeaderV3ShouldErr(t *testing.T) { + t.Parallel() + + arg := createDefaultShardArgumentWithV3Support() + inHdr, err := interceptedBlocks.NewInterceptedHeader(arg) + assert.Nil(t, err) + assert.NotNil(t, inHdr) + assert.True(t, inHdr.HeaderHandler().IsHeaderV3()) + badShardId := uint32(28) + mbs := []dataBlock.MiniBlockHeader{ + { + Hash: make([]byte, 0), + SenderShardID: badShardId, + ReceiverShardID: 0, + TxCount: 0, + Type: 0, + }, + } + mbHandlers := make([]data.MiniBlockHeaderHandler, len(mbs)) + for i := range mbs { + tmp := mbs[i] + mbHandlers[i] = &tmp + } + _ = inHdr.HeaderHandler().(*dataBlock.HeaderV3).ExecutionResults[1].SetMiniBlockHeadersHandlers(mbHandlers) + err = inHdr.CheckValidity() + + assert.Error(t, err) + assert.Equal(t, process.ErrInvalidShardId, err) +} + +func TestInterceptedHeader_CheckValidityShouldWorkHeaderV3(t *testing.T) { + t.Parallel() + + arg := createDefaultShardArgumentWithV3Support() + inHdr, err := interceptedBlocks.NewInterceptedHeader(arg) + assert.Nil(t, err) + assert.NotNil(t, inHdr) + assert.True(t, inHdr.HeaderHandler().IsHeaderV3()) + err = inHdr.CheckValidity() + + assert.Nil(t, err) +} func TestInterceptedHeader_CheckAgainstRoundHandlerErrorsShouldErr(t *testing.T) { t.Parallel() @@ -364,6 +696,7 @@ func TestInterceptedHeader_Getters(t *testing.T) { hash := testHasher.Compute(string(arg.HdrBuff)) assert.Equal(t, hash, inHdr.Hash()) + require.False(t, inHdr.ShouldAllowDuplicates()) } // ------- IsInterfaceNil diff --git a/process/block/interceptedBlocks/interceptedEquivalentProof.go b/process/block/interceptedBlocks/interceptedEquivalentProof.go index 3dd2f24165a..e4dccfd7fe3 100644 --- a/process/block/interceptedBlocks/interceptedEquivalentProof.go +++ b/process/block/interceptedBlocks/interceptedEquivalentProof.go @@ -32,6 +32,7 @@ type ArgInterceptedEquivalentProof struct { Proofs dataRetriever.ProofsPool ProofSizeChecker common.FieldsSizeChecker KeyRWMutexHandler sync.KeyRWMutexHandler + ValidityAttester process.ValidityAttester } type interceptedEquivalentProof struct { @@ -44,6 +45,7 @@ type interceptedEquivalentProof struct { hash []byte proofSizeChecker common.FieldsSizeChecker km sync.KeyRWMutexHandler + validityAttester process.ValidityAttester } // NewInterceptedEquivalentProof returns a new instance of interceptedEquivalentProof @@ -70,6 +72,7 @@ func NewInterceptedEquivalentProof(args ArgInterceptedEquivalentProof) (*interce proofSizeChecker: args.ProofSizeChecker, hash: hash, km: args.KeyRWMutexHandler, + validityAttester: args.ValidityAttester, }, nil } @@ -98,6 +101,9 @@ func checkArgInterceptedEquivalentProof(args ArgInterceptedEquivalentProof) erro if check.IfNil(args.KeyRWMutexHandler) { return process.ErrNilKeyRWMutexHandler } + if check.IfNil(args.ValidityAttester) { + return process.ErrNilValidityAttester + } return nil } @@ -144,6 +150,18 @@ func (iep *interceptedEquivalentProof) CheckValidity() error { return err } + if !iep.validityAttester.CheckAgainstWhitelist(iep) { + err = iep.validityAttester.CheckProofAgainstFinal(iep.proof) + if err != nil { + return err + } + } + + err = iep.validityAttester.CheckProofAgainstRoundHandler(iep.proof) + if err != nil { + return err + } + headerHash := string(iep.proof.GetHeaderHash()) iep.km.Lock(headerHash) defer iep.km.Unlock(headerHash) @@ -168,6 +186,11 @@ func (iep *interceptedEquivalentProof) CheckValidity() error { return nil } +// ShouldAllowDuplicates returns if this type of intercepted data should allow duplicates +func (iep *interceptedEquivalentProof) ShouldAllowDuplicates() bool { + return true // duplicates are treated separately +} + func (iep *interceptedEquivalentProof) integrity() error { if !iep.proofSizeChecker.IsProofSizeValid(iep.proof) { return ErrInvalidProof diff --git a/process/block/interceptedBlocks/interceptedEquivalentProof_test.go b/process/block/interceptedBlocks/interceptedEquivalentProof_test.go index bd2d741db18..12aebf66f97 100644 --- a/process/block/interceptedBlocks/interceptedEquivalentProof_test.go +++ b/process/block/interceptedBlocks/interceptedEquivalentProof_test.go @@ -10,13 +10,14 @@ import ( coreSync "github.com/multiversx/mx-chain-core-go/core/sync" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" - errErd "github.com/multiversx/mx-chain-go/errors" logger "github.com/multiversx/mx-chain-logger-go" "github.com/stretchr/testify/require" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/consensus/mock" + errErd "github.com/multiversx/mx-chain-go/errors" "github.com/multiversx/mx-chain-go/process" + processMock "github.com/multiversx/mx-chain-go/process/mock" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/consensus" "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" @@ -73,6 +74,7 @@ func createMockArgInterceptedEquivalentProof() ArgInterceptedEquivalentProof { Hasher: &hashingMocks.HasherMock{}, ProofSizeChecker: &testscommon.FieldsSizeCheckerMock{}, KeyRWMutexHandler: coreSync.NewKeyRWMutex(), + ValidityAttester: &processMock.ValidityAttesterStub{}, } } @@ -174,6 +176,15 @@ func TestNewInterceptedEquivalentProof(t *testing.T) { require.Equal(t, process.ErrNilKeyRWMutexHandler, err) require.Nil(t, iep) }) + t.Run("nil ValidityAttester should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgInterceptedEquivalentProof() + args.ValidityAttester = nil + iep, err := NewInterceptedEquivalentProof(args) + require.Equal(t, process.ErrNilValidityAttester, err) + require.Nil(t, iep) + }) t.Run("should work", func(t *testing.T) { t.Parallel() @@ -208,6 +219,76 @@ func TestInterceptedEquivalentProof_CheckValidity(t *testing.T) { err = iep.CheckValidity() require.Equal(t, ErrInvalidProof, err) }) + t.Run("CheckProofAgainstFinal error should be propagated", func(t *testing.T) { + t.Parallel() + + args := createMockArgInterceptedEquivalentProof() + args.ValidityAttester = &processMock.ValidityAttesterStub{ + CheckProofAgainstFinalCalled: func(proof data.HeaderProofHandler) error { + return expectedErr + }, + } + + iep, err := NewInterceptedEquivalentProof(args) + require.NoError(t, err) + + err = iep.CheckValidity() + require.Equal(t, expectedErr, err) + }) + t.Run("CheckProofAgainstRoundHandler error should be propagated", func(t *testing.T) { + t.Parallel() + + args := createMockArgInterceptedEquivalentProof() + args.ValidityAttester = &processMock.ValidityAttesterStub{ + CheckProofAgainstRoundHandlerCalled: func(proof data.HeaderProofHandler) error { + return expectedErr + }, + } + + iep, err := NewInterceptedEquivalentProof(args) + require.NoError(t, err) + + err = iep.CheckValidity() + require.Equal(t, expectedErr, err) + }) + t.Run("whitelisted proof should bypass CheckProofAgainstFinal", func(t *testing.T) { + t.Parallel() + + args := createMockArgInterceptedEquivalentProof() + args.ValidityAttester = &processMock.ValidityAttesterStub{ + CheckAgainstWhitelistCalled: func(interceptedData process.InterceptedData) bool { + return true + }, + CheckProofAgainstFinalCalled: func(proof data.HeaderProofHandler) error { + return expectedErr + }, + } + + iep, err := NewInterceptedEquivalentProof(args) + require.NoError(t, err) + + err = iep.CheckValidity() + require.NoError(t, err) + }) + t.Run("whitelisted proof should still enforce round handler check", func(t *testing.T) { + t.Parallel() + + args := createMockArgInterceptedEquivalentProof() + args.ValidityAttester = &processMock.ValidityAttesterStub{ + CheckAgainstWhitelistCalled: func(interceptedData process.InterceptedData) bool { + return true + }, + CheckProofAgainstRoundHandlerCalled: func(proof data.HeaderProofHandler) error { + return expectedErr + }, + } + + iep, err := NewInterceptedEquivalentProof(args) + require.NoError(t, err) + + err = iep.CheckValidity() + require.Equal(t, expectedErr, err) + }) t.Run("already exiting proof should error", func(t *testing.T) { t.Parallel() @@ -374,4 +455,5 @@ func TestInterceptedEquivalentProof_Getters(t *testing.T) { logger.DisplayByteSlice(proof.AggregatedSignature), logger.DisplayByteSlice(proof.HeaderHash)) require.Equal(t, expectedStr, iep.String()) + require.True(t, iep.ShouldAllowDuplicates()) } diff --git a/process/block/interceptedBlocks/interceptedMetaBlockHeader.go b/process/block/interceptedBlocks/interceptedMetaBlockHeader.go index 0f85553807d..e06848b97e3 100644 --- a/process/block/interceptedBlocks/interceptedMetaBlockHeader.go +++ b/process/block/interceptedBlocks/interceptedMetaBlockHeader.go @@ -7,7 +7,6 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/hashing" - "github.com/multiversx/mx-chain-core-go/marshal" logger "github.com/multiversx/mx-chain-logger-go" "github.com/multiversx/mx-chain-go/common" @@ -40,7 +39,7 @@ func NewInterceptedMetaHeader(arg *ArgInterceptedBlockHeader) (*InterceptedMetaH return nil, err } - hdr, err := createMetaHdr(arg.Marshalizer, arg.HdrBuff) + hdr, err := process.UnmarshalMetaHeader(arg.Marshalizer, arg.HdrBuff) if err != nil { return nil, err } @@ -60,18 +59,6 @@ func NewInterceptedMetaHeader(arg *ArgInterceptedBlockHeader) (*InterceptedMetaH return inHdr, nil } -func createMetaHdr(marshalizer marshal.Marshalizer, hdrBuff []byte) (*block.MetaBlock, error) { - hdr := &block.MetaBlock{ - ShardInfo: make([]block.ShardData, 0), - } - err := marshalizer.Unmarshal(hdr, hdrBuff) - if err != nil { - return nil, err - } - - return hdr, nil -} - func (imh *InterceptedMetaHeader) processFields(txBuff []byte) { imh.hash = imh.hasher.Compute(string(txBuff)) } @@ -95,7 +82,7 @@ func (imh *InterceptedMetaHeader) CheckValidity() error { return err } - if !imh.validityAttester.CheckBlockAgainstWhitelist(imh) { + if !imh.validityAttester.CheckAgainstWhitelist(imh) { err = imh.validityAttester.CheckBlockAgainstFinal(imh.HeaderHandler()) if err != nil { return err @@ -129,6 +116,11 @@ func (imh *InterceptedMetaHeader) CheckValidity() error { return imh.integrityVerifier.Verify(imh.hdr) } +// ShouldAllowDuplicates returns if this type of intercepted data should allow duplicates +func (imh *InterceptedMetaHeader) ShouldAllowDuplicates() bool { + return false +} + func (imh *InterceptedMetaHeader) isMetaHeaderEpochOutOfRange() bool { if imh.shardCoordinator.SelfId() == core.MetachainShardId { return false @@ -153,7 +145,24 @@ func (imh *InterceptedMetaHeader) integrity() error { return err } - return checkMiniBlocksHeaders(imh.hdr.GetMiniBlockHeaderHandlers(), imh.shardCoordinator) + err = checkMiniBlocksHeaders(imh.hdr.GetMiniBlockHeaderHandlers(), imh.shardCoordinator) + if err != nil { + return err + } + + if imh.hdr.IsHeaderV3() { + for i, result := range imh.hdr.GetExecutionResultsHandlers() { + executionResult, ok := result.(*block.MetaExecutionResult) + if !ok { + return fmt.Errorf("failed to cast execution result at index %d to block.MetaExecutionResult", i) + } + err = checkMiniBlocksHeaders(executionResult.GetMiniBlockHeadersHandlers(), imh.shardCoordinator) + if err != nil { + return err + } + } + } + return nil } // IsForCurrentShard always returns true diff --git a/process/block/interceptedBlocks/interceptedMetaBlockHeader_test.go b/process/block/interceptedBlocks/interceptedMetaBlockHeader_test.go index 3b5fe2fb22a..5bfaec4869d 100644 --- a/process/block/interceptedBlocks/interceptedMetaBlockHeader_test.go +++ b/process/block/interceptedBlocks/interceptedMetaBlockHeader_test.go @@ -27,7 +27,18 @@ func createDefaultMetaArgument() *interceptedBlocks.ArgInterceptedBlockHeader { return createMetaArgumentWithShardCoordinator(shardCoordinator) } -func createMetaArgumentWithShardCoordinator(shardCoordinator sharding.Coordinator) *interceptedBlocks.ArgInterceptedBlockHeader { +func createDefaultMetaV3Argument() *interceptedBlocks.ArgInterceptedBlockHeader { + shardCoordinator := mock.NewMultiShardsCoordinatorMock(3) + arg := createMetaV3ArgumentWithShardCoordinator(shardCoordinator) + arg.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + } + return arg +} + +func createMetaArgumentWithShardCoordinatorAndHeader(shardCoordinator sharding.Coordinator, hdr data.MetaHeaderHandler) *interceptedBlocks.ArgInterceptedBlockHeader { gracePeriod, _ := graceperiod.NewEpochChangeGracePeriod([]config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}) arg := &interceptedBlocks.ArgInterceptedBlockHeader{ ShardCoordinator: shardCoordinator, @@ -44,13 +55,21 @@ func createMetaArgumentWithShardCoordinator(shardCoordinator sharding.Coordinato EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, EpochChangeGracePeriodHandler: gracePeriod, } - - hdr := createMockMetaHeader() arg.HdrBuff, _ = testMarshalizer.Marshal(hdr) return arg } +func createMetaArgumentWithShardCoordinator(shardCoordinator sharding.Coordinator) *interceptedBlocks.ArgInterceptedBlockHeader { + hdr := createMockMetaHeader() + return createMetaArgumentWithShardCoordinatorAndHeader(shardCoordinator, hdr) +} + +func createMetaV3ArgumentWithShardCoordinator(shardCoordinator sharding.Coordinator) *interceptedBlocks.ArgInterceptedBlockHeader { + hdr := createMockMetaHeaderV3() + return createMetaArgumentWithShardCoordinatorAndHeader(shardCoordinator, hdr) +} + func createMockMetaHeader() *dataBlock.MetaBlock { return &dataBlock.MetaBlock{ Nonce: hdrNonce, @@ -75,6 +94,122 @@ func createMockMetaHeader() *dataBlock.MetaBlock { } } +func createMockMetaHeaderV3() *dataBlock.MetaBlockV3 { + return &dataBlock.MetaBlockV3{ + Nonce: 42, + Epoch: 2, + Round: 15, + TimestampMs: 123456789, + PrevHash: []byte("prev_hash"), + PrevRandSeed: []byte("prev_seed"), + RandSeed: []byte("new_seed"), + ChainID: []byte("chain-id"), + SoftwareVersion: []byte("v1.0.0"), + LeaderSignature: []byte("leader_signature"), + + MiniBlockHeaders: []dataBlock.MiniBlockHeader{ + {Hash: []byte("meta-to-s0"), SenderShardID: core.MetachainShardId, ReceiverShardID: 0}, + {Hash: []byte("meta-to-s1"), SenderShardID: core.MetachainShardId, ReceiverShardID: 1}, + }, + + ShardInfo: []dataBlock.ShardData{ + { + ShardID: 0, + Round: 10, + Nonce: 41, + Epoch: 1, + HeaderHash: []byte("shard0-hash"), + ShardMiniBlockHeaders: []dataBlock.MiniBlockHeader{ + {SenderShardID: 0, ReceiverShardID: 1, Hash: []byte("s0-to-s1")}, + }, + }, + { + ShardID: 1, + Round: 11, + Nonce: 40, + Epoch: 1, + HeaderHash: []byte("shard1-hash"), + ShardMiniBlockHeaders: []dataBlock.MiniBlockHeader{ + {SenderShardID: 1, ReceiverShardID: 0, Hash: []byte("s1-to-s0")}, + }, + }, + }, + ShardInfoProposal: []dataBlock.ShardDataProposal{ + {ShardID: 0, HeaderHash: []byte("shard-0-hash"), Nonce: 41, Round: 10, Epoch: 1}, + {ShardID: 1, HeaderHash: []byte("shard-1-hash"), Nonce: 40, Round: 11, Epoch: 1}, + }, + ExecutionResults: []*dataBlock.MetaExecutionResult{ + { + ExecutionResult: &dataBlock.BaseMetaExecutionResult{ + BaseExecutionResult: &dataBlock.BaseExecutionResult{ + HeaderHash: []byte("hdr-hash-10"), + HeaderNonce: 39, + HeaderRound: 10, + HeaderEpoch: 1, + RootHash: []byte("root-hash-10"), + }, + AccumulatedFeesInEpoch: big.NewInt(1000), + DevFeesInEpoch: big.NewInt(100), + ValidatorStatsRootHash: []byte("validator-stats-root-hash"), + }, + AccumulatedFees: big.NewInt(1000), + DeveloperFees: big.NewInt(100), + ReceiptsHash: []byte("receipts hash"), + }, + { + ExecutionResult: &dataBlock.BaseMetaExecutionResult{ + BaseExecutionResult: &dataBlock.BaseExecutionResult{ + HeaderHash: []byte("hdr-hash-11"), + HeaderNonce: 40, + HeaderRound: 11, + HeaderEpoch: 1, + RootHash: []byte("root-hash-11"), + }, + AccumulatedFeesInEpoch: big.NewInt(2000), + DevFeesInEpoch: big.NewInt(200), + ValidatorStatsRootHash: []byte("validator-stats-root-hash-1"), + }, + AccumulatedFees: big.NewInt(2000), + DeveloperFees: big.NewInt(200), + ReceiptsHash: []byte("receipts-hash-1"), + }, + { + ExecutionResult: &dataBlock.BaseMetaExecutionResult{ + BaseExecutionResult: &dataBlock.BaseExecutionResult{ + HeaderHash: []byte("hdr-hash-last"), + HeaderNonce: 41, + HeaderRound: 12, + HeaderEpoch: 2, + RootHash: []byte("root-hash-last"), + }, + AccumulatedFeesInEpoch: big.NewInt(3000), + DevFeesInEpoch: big.NewInt(300), + ValidatorStatsRootHash: []byte("validator-stats-root-hash-2"), + }, + AccumulatedFees: big.NewInt(3000), + DeveloperFees: big.NewInt(300), + ReceiptsHash: []byte("receipts-hash-2"), + }, + }, + + LastExecutionResult: &dataBlock.MetaExecutionResultInfo{ + NotarizedInRound: 14, + ExecutionResult: &dataBlock.BaseMetaExecutionResult{ + BaseExecutionResult: &dataBlock.BaseExecutionResult{ + HeaderHash: []byte("hdr-hash-last"), + HeaderNonce: 41, + HeaderRound: 12, + HeaderEpoch: 2, + RootHash: []byte("root-hash-last"), + }, + AccumulatedFeesInEpoch: big.NewInt(3000), + DevFeesInEpoch: big.NewInt(300), + ValidatorStatsRootHash: []byte("validator-stats-root-hash-2"), + }, + }, + } +} + // ------- TestNewInterceptedHeader func TestNewInterceptedMetaHeader_NilArgumentShouldErr(t *testing.T) { @@ -212,6 +347,7 @@ func TestInterceptedMetaHeader_Getters(t *testing.T) { assert.Equal(t, hash, inHdr.Hash()) assert.True(t, inHdr.IsForCurrentShard()) + require.False(t, inHdr.ShouldAllowDuplicates()) } func TestInterceptedMetaHeader_CheckValidityLeaderSignatureNotCorrectShouldErr(t *testing.T) { @@ -233,6 +369,33 @@ func TestInterceptedMetaHeader_CheckValidityLeaderSignatureNotCorrectShouldErr(t err := inHdr.CheckValidity() assert.Equal(t, expectedErr, err) } +func TestInterceptedMetaHeader_CheckValidityErrorInMiniBlockShouldErr(t *testing.T) { + t.Parallel() + + hdr := createMockMetaHeader() + badShardId := uint32(2) + hdr.MiniBlockHeaders = []dataBlock.MiniBlockHeader{ + { + Hash: make([]byte, 0), + SenderShardID: badShardId, + ReceiverShardID: 0, + TxCount: 0, + Type: 0, + }, + } + + arg := createDefaultMetaArgument() + marshaller := arg.Marshalizer + buff, _ := marshaller.Marshal(hdr) + arg.HdrBuff = buff + inHdr, err := interceptedBlocks.NewInterceptedMetaHeader(arg) + require.Nil(t, err) + require.NotNil(t, inHdr) + + err = inHdr.CheckValidity() + + assert.Equal(t, process.ErrInvalidShardId, err) +} func TestInterceptedMetaHeader_CheckValidityLeaderSignatureOkShouldWork(t *testing.T) { t.Parallel() @@ -250,6 +413,47 @@ func TestInterceptedMetaHeader_CheckValidityLeaderSignatureOkShouldWork(t *testi assert.Nil(t, err) } +func TestInterceptedMetaHeader_CheckValidityExecutionResultMiniblockErrorInMetaHeaderV3ShouldErr(t *testing.T) { + t.Parallel() + + arg := createDefaultMetaV3Argument() + inHdr, err := interceptedBlocks.NewInterceptedMetaHeader(arg) + assert.Nil(t, err) + assert.NotNil(t, inHdr) + + assert.True(t, inHdr.HeaderHandler().IsHeaderV3()) + badShardId := uint32(28) + mbs := []dataBlock.MiniBlockHeader{ + { + Hash: make([]byte, 0), + SenderShardID: badShardId, + ReceiverShardID: 0, + TxCount: 0, + Type: 0, + }, + } + mbHandlers := make([]data.MiniBlockHeaderHandler, len(mbs)) + for i := range mbs { + tmp := mbs[i] + mbHandlers[i] = &tmp + } + _ = inHdr.HeaderHandler().(*dataBlock.MetaBlockV3).ExecutionResults[1].SetMiniBlockHeadersHandlers(mbHandlers) + err = inHdr.CheckValidity() + + assert.Error(t, err) + assert.Equal(t, process.ErrInvalidShardId, err) +} + +func TestInterceptedMetaHeader_CheckValidityShouldWorkV3(t *testing.T) { + t.Parallel() + + arg := createDefaultMetaV3Argument() + inHdr, _ := interceptedBlocks.NewInterceptedMetaHeader(arg) + assert.True(t, inHdr.HeaderHandler().IsHeaderV3()) + err := inHdr.CheckValidity() + assert.Nil(t, err) +} + func TestInterceptedMetaHeader_isMetaHeaderEpochOutOfRange(t *testing.T) { epochStartTrigger := &mock.EpochStartTriggerStub{ EpochCalled: func() uint32 { diff --git a/process/block/interceptedBlocks/interceptedMiniblock.go b/process/block/interceptedBlocks/interceptedMiniblock.go index a6569697b0a..d62cc46673b 100644 --- a/process/block/interceptedBlocks/interceptedMiniblock.go +++ b/process/block/interceptedBlocks/interceptedMiniblock.go @@ -85,6 +85,11 @@ func (inMb *InterceptedMiniblock) CheckValidity() error { return inMb.integrity() } +// ShouldAllowDuplicates returns if this type of intercepted data should allow duplicates +func (inMb *InterceptedMiniblock) ShouldAllowDuplicates() bool { + return false +} + // IsForCurrentShard returns true if at least one contained miniblock is for current shard func (inMb *InterceptedMiniblock) IsForCurrentShard() bool { return inMb.isForCurrentShard diff --git a/process/block/interceptedBlocks/interceptedMiniblock_test.go b/process/block/interceptedBlocks/interceptedMiniblock_test.go index 46b489b259d..b592b742b96 100644 --- a/process/block/interceptedBlocks/interceptedMiniblock_test.go +++ b/process/block/interceptedBlocks/interceptedMiniblock_test.go @@ -6,6 +6,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/block/interceptedBlocks" @@ -36,7 +37,7 @@ func createMockMiniblock() *block.MiniBlock { } } -//------- NewInterceptedMiniblock +// ------- NewInterceptedMiniblock func TestNewInterceptedMiniblock_NilArgumentShouldErr(t *testing.T) { t.Parallel() @@ -70,7 +71,7 @@ func TestNewInterceptedMiniblock_ShouldWork(t *testing.T) { assert.Nil(t, err) } -//------- Verify +// ------- Verify func TestInterceptedMiniblock_InvalidReceiverShardIdShouldErr(t *testing.T) { t.Parallel() @@ -133,7 +134,7 @@ func TestInterceptedMiniblock_ShouldWork(t *testing.T) { assert.Nil(t, err) } -//------- getters +// ------- getters func TestInterceptedMiniblock_Getters(t *testing.T) { t.Parallel() @@ -146,9 +147,10 @@ func TestInterceptedMiniblock_Getters(t *testing.T) { assert.Equal(t, hash, inMb.Hash()) assert.Equal(t, createMockMiniblock(), inMb.Miniblock()) + require.False(t, inMb.ShouldAllowDuplicates()) } -//------- IsForCurrentShard +// ------- IsForCurrentShard func TestInterceptedMiniblock_IsForCurrentShardMiniblockForOtherShardsShouldRetFalse(t *testing.T) { t.Parallel() diff --git a/process/block/interface.go b/process/block/interface.go index 8e89b766593..53ed9422f9a 100644 --- a/process/block/interface.go +++ b/process/block/interface.go @@ -1,12 +1,21 @@ package block import ( + "time" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionTrack" + "github.com/multiversx/mx-chain-go/process/block/headerForBlock" ) type blockProcessor interface { removeStartOfEpochBlockDataFromPools(headerHandler data.HeaderHandler, bodyHandler data.BodyHandler) error + pruneTrieHeaderV3(header data.HeaderHandler) + resetPruning() + cancelPruneForDismissedExecutionResults(batches []executionTrack.DismissedBatch) } type gasConsumedProvider interface { @@ -23,5 +32,58 @@ type peerAccountsDBHandler interface { type receiptsRepository interface { SaveReceipts(holder common.ReceiptsHolder, header data.HeaderHandler, headerHash []byte) error + SaveReceiptsForExecResult(holder common.ReceiptsHolder, execResult data.BaseExecutionResultHandler) error + IsInterfaceNil() bool +} + +// HeadersForBlock defines a component able to hold headers for a block +type HeadersForBlock interface { + AddHeaderUsedInBlock(hash string, header data.HeaderHandler) + AddHeaderNotUsedInBlock(hash string, header data.HeaderHandler) + RequestShardHeaders(metaBlock data.MetaHeaderHandler) + RequestMetaHeaders(shardHeader data.ShardHeaderHandler) + WaitForHeadersIfNeeded(haveTime func() time.Duration) error + GetHeaderInfo(hash string) (headerForBlock.HeaderInfo, bool) + GetHeadersInfoMap() map[string]headerForBlock.HeaderInfo + GetHeadersMap() map[string]data.HeaderHandler + ComputeHeadersForCurrentBlock(usedInBlock bool) (map[uint32][]data.HeaderHandler, error) + ComputeHeadersForCurrentBlockInfo(usedInBlock bool) (map[uint32][]headerForBlock.NonceAndHashInfo, error) + GetMissingData() (uint32, uint32, uint32) + Reset() + IsInterfaceNil() bool +} + +// ExecutionResultsVerifier is the interface that defines the methods for verifying execution results +type ExecutionResultsVerifier interface { + VerifyHeaderExecutionResults(header data.HeaderHandler) error + IsInterfaceNil() bool +} + +// MiniBlocksSelectionSession defines a session for selecting mini blocks +type MiniBlocksSelectionSession interface { + ResetSelectionSession() + GetMiniBlockHeaderHandlers() []data.MiniBlockHeaderHandler + GetMiniBlocks() block.MiniBlockSlice + GetMiniBlockHashes() [][]byte + AddReferencedHeader(header data.HeaderHandler, headerHash []byte) + GetReferencedHeaderHashes() [][]byte + GetReferencedHeaders() []data.HeaderHandler + GetLastHeader() data.HeaderHandler + GetNumTxsAdded() uint32 + AddMiniBlocksAndHashes(miniBlocksAndHashes []block.MiniblockAndHash) error + CreateAndAddMiniBlockFromTransactions(txHashes [][]byte) error + IsInterfaceNil() bool +} + +// MissingDataResolver is the interface that defines the methods for resolving missing data +type MissingDataResolver interface { + RequestMissingMetaHeadersBlocking(shardHeader data.ShardHeaderHandler, timeout time.Duration) error + RequestMissingMetaHeaders(shardHeader data.ShardHeaderHandler) error + WaitForMissingData(timeout time.Duration) error + RequestBlockTransactions(body *block.Body) + RequestMiniBlocksAndTransactions(header data.HeaderHandler) + GetFinalCrossMiniBlockInfoAndRequestMissing(header data.HeaderHandler) []*data.MiniBlockInfo + RequestMissingShardHeaders(metaHeader data.MetaHeaderHandler) error + Reset() IsInterfaceNil() bool } diff --git a/process/block/mbsSelectionSession.go b/process/block/mbsSelectionSession.go new file mode 100644 index 00000000000..2af925d89cc --- /dev/null +++ b/process/block/mbsSelectionSession.go @@ -0,0 +1,288 @@ +package block + +import ( + "sync" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/hashing" + "github.com/multiversx/mx-chain-core-go/marshal" + + "github.com/multiversx/mx-chain-go/process" +) + +type miniBlocksSelectionConfig struct { + shardID uint32 + marshaller marshal.Marshalizer + hasher hashing.Hasher +} + +type miniBlocksSelectionResult struct { + miniBlockHeaderHandlers []data.MiniBlockHeaderHandler + miniBlocks block.MiniBlockSlice + miniBlockHashes [][]byte + miniBlockHashesUnique map[string]struct{} + referenceHeaderHashesUnique map[string]struct{} + referencedHeaderHashes [][]byte + referencedHeader []data.HeaderHandler + lastHeader data.HeaderHandler + numTxsAdded uint32 + mut sync.RWMutex +} + +type miniBlocksSelectionSession struct { + *miniBlocksSelectionConfig + *miniBlocksSelectionResult +} + +const defaultCapacity = 10 + +// NewMiniBlocksSelectionSession creates a new instance of miniBlocksSelectionSession +func NewMiniBlocksSelectionSession(shardID uint32, marshaller marshal.Marshalizer, hasher hashing.Hasher) (*miniBlocksSelectionSession, error) { + if check.IfNil(marshaller) { + return nil, process.ErrNilMarshalizer + } + if check.IfNil(hasher) { + return nil, process.ErrNilHasher + } + + return &miniBlocksSelectionSession{ + miniBlocksSelectionConfig: &miniBlocksSelectionConfig{ + shardID: shardID, + marshaller: marshaller, + hasher: hasher, + }, + miniBlocksSelectionResult: &miniBlocksSelectionResult{ + miniBlockHeaderHandlers: make([]data.MiniBlockHeaderHandler, 0, defaultCapacity), + miniBlocks: make(block.MiniBlockSlice, 0, defaultCapacity), + miniBlockHashes: make([][]byte, 0, defaultCapacity), + miniBlockHashesUnique: make(map[string]struct{}), + referenceHeaderHashesUnique: make(map[string]struct{}), + referencedHeaderHashes: make([][]byte, 0, defaultCapacity), + referencedHeader: make([]data.HeaderHandler, 0, defaultCapacity), + lastHeader: nil, + numTxsAdded: 0, + }, + }, nil +} + +// ResetSelectionSession resets the mini blocks selection session by clearing all the fields +func (s *miniBlocksSelectionSession) ResetSelectionSession() { + s.mut.Lock() + defer s.mut.Unlock() + + s.miniBlockHeaderHandlers = make([]data.MiniBlockHeaderHandler, 0, defaultCapacity) + s.miniBlocks = make(block.MiniBlockSlice, 0, defaultCapacity) + s.miniBlockHashes = make([][]byte, 0, defaultCapacity) + s.miniBlockHashesUnique = make(map[string]struct{}) + s.referenceHeaderHashesUnique = make(map[string]struct{}) + s.referencedHeaderHashes = make([][]byte, 0, defaultCapacity) + s.referencedHeader = make([]data.HeaderHandler, 0, defaultCapacity) + s.lastHeader = nil + s.numTxsAdded = 0 +} + +// GetMiniBlockHeaderHandlers returns the mini block header handlers +func (s *miniBlocksSelectionSession) GetMiniBlockHeaderHandlers() []data.MiniBlockHeaderHandler { + s.mut.RLock() + defer s.mut.RUnlock() + + if len(s.miniBlockHeaderHandlers) == 0 { + return nil + } + + miniBlockHeaderHandlers := make([]data.MiniBlockHeaderHandler, len(s.miniBlockHeaderHandlers)) + copy(miniBlockHeaderHandlers, s.miniBlockHeaderHandlers) + + return miniBlockHeaderHandlers +} + +// GetMiniBlocks returns the mini blocks +func (s *miniBlocksSelectionSession) GetMiniBlocks() block.MiniBlockSlice { + s.mut.RLock() + defer s.mut.RUnlock() + + if len(s.miniBlocks) == 0 { + return nil + } + + miniBlocks := make(block.MiniBlockSlice, len(s.miniBlocks)) + copy(miniBlocks, s.miniBlocks) + + return miniBlocks +} + +// GetMiniBlockHashes returns the hashes of the mini blocks +func (s *miniBlocksSelectionSession) GetMiniBlockHashes() [][]byte { + s.mut.RLock() + defer s.mut.RUnlock() + + if len(s.miniBlockHashes) == 0 { + return nil + } + + miniBlockHashes := make([][]byte, len(s.miniBlockHashes)) + copy(miniBlockHashes, s.miniBlockHashes) + + return miniBlockHashes +} + +// AddReferencedHeader adds a header and its hash to the session +func (s *miniBlocksSelectionSession) AddReferencedHeader(header data.HeaderHandler, headerHash []byte) { + if check.IfNil(header) || len(headerHash) == 0 { + return + } + + s.mut.Lock() + defer s.mut.Unlock() + + _, ok := s.referenceHeaderHashesUnique[string(headerHash)] + if !ok { + s.referenceHeaderHashesUnique[string(headerHash)] = struct{}{} + s.referencedHeader = append(s.referencedHeader, header) + s.referencedHeaderHashes = append(s.referencedHeaderHashes, headerHash) + s.lastHeader = header + } +} + +// GetReferencedHeaderHashes returns the hashes of the referenced headers +func (s *miniBlocksSelectionSession) GetReferencedHeaderHashes() [][]byte { + s.mut.RLock() + defer s.mut.RUnlock() + + if len(s.referencedHeaderHashes) == 0 { + return nil + } + + referencedHeaderHashes := make([][]byte, len(s.referencedHeaderHashes)) + copy(referencedHeaderHashes, s.referencedHeaderHashes) + + return referencedHeaderHashes +} + +// GetReferencedHeaders returns the referenced headers +func (s *miniBlocksSelectionSession) GetReferencedHeaders() []data.HeaderHandler { + s.mut.RLock() + defer s.mut.RUnlock() + + if len(s.referencedHeader) == 0 { + return nil + } + + referencedHeaders := make([]data.HeaderHandler, len(s.referencedHeader)) + copy(referencedHeaders, s.referencedHeader) + + return referencedHeaders +} + +// GetLastHeader returns the last header +func (s *miniBlocksSelectionSession) GetLastHeader() data.HeaderHandler { + s.mut.RLock() + defer s.mut.RUnlock() + + return s.lastHeader +} + +// GetNumTxsAdded returns the number of transactions added to the mini blocks +func (s *miniBlocksSelectionSession) GetNumTxsAdded() uint32 { + s.mut.RLock() + defer s.mut.RUnlock() + + return s.numTxsAdded +} + +// AddMiniBlocksAndHashes adds a slice of mini blocks and their hashes to the session +func (s *miniBlocksSelectionSession) AddMiniBlocksAndHashes(miniBlocksAndHashes []block.MiniblockAndHash) error { + var err error + miniBlocks := make([]*block.MiniBlock, 0, len(miniBlocksAndHashes)) + miniBlockHeaderHandlers := make([]data.MiniBlockHeaderHandler, 0, len(miniBlocksAndHashes)) + miniBlockHashes := make([][]byte, 0, len(miniBlocksAndHashes)) + numTxsAdded := 0 + + s.mut.Lock() + defer s.mut.Unlock() + + for _, miniBlockInfo := range miniBlocksAndHashes { + _, ok := s.miniBlockHashesUnique[string(miniBlockInfo.Hash)] + if ok { + continue + } + + s.miniBlockHashesUnique[string(miniBlockInfo.Hash)] = struct{}{} + + txCount := len(miniBlockInfo.Miniblock.GetTxHashes()) + + mbHeader := &block.MiniBlockHeader{ + Hash: miniBlockInfo.Hash, + SenderShardID: miniBlockInfo.Miniblock.SenderShardID, + ReceiverShardID: miniBlockInfo.Miniblock.ReceiverShardID, + TxCount: uint32(txCount), + Type: miniBlockInfo.Miniblock.GetType(), + } + err = setProcessingTypeAndConstructionStateForProposalMb(mbHeader) + if err != nil { + return err + } + + miniBlocks = append(miniBlocks, miniBlockInfo.Miniblock) + miniBlockHeaderHandlers = append(miniBlockHeaderHandlers, mbHeader) + miniBlockHashes = append(miniBlockHashes, miniBlockInfo.Hash) + numTxsAdded += txCount + } + + s.miniBlocks = append(s.miniBlocks, miniBlocks...) + s.miniBlockHeaderHandlers = append(s.miniBlockHeaderHandlers, miniBlockHeaderHandlers...) + s.miniBlockHashes = append(s.miniBlockHashes, miniBlockHashes...) + s.numTxsAdded += uint32(numTxsAdded) + + return nil +} + +// CreateAndAddMiniBlockFromTransactions creates a mini block from the provided transaction hashes +func (s *miniBlocksSelectionSession) CreateAndAddMiniBlockFromTransactions(txHashes [][]byte) error { + if len(txHashes) == 0 { + return nil + } + + // no need to create multiple miniblocks from the shard to itself or to other shards before processing + // the transactions, so create a single miniBlock + outgoingMiniBlock := &block.MiniBlock{ + TxHashes: txHashes, + ReceiverShardID: s.shardID, + SenderShardID: s.shardID, + Type: block.TxBlock, + Reserved: nil, + } + + outgoingMiniBlockHash, err := core.CalculateHash(s.marshaller, s.hasher, outgoingMiniBlock) + if err != nil { + return err + } + + return s.AddMiniBlocksAndHashes([]block.MiniblockAndHash{{ + Miniblock: outgoingMiniBlock, + Hash: outgoingMiniBlockHash, + }}) +} + +func setProcessingTypeAndConstructionStateForProposalMb( + miniBlockHeaderHandler data.MiniBlockHeaderHandler, +) error { + err := miniBlockHeaderHandler.SetProcessingType(int32(block.Normal)) + if err != nil { + return err + } + + err = miniBlockHeaderHandler.SetConstructionState(int32(block.Proposed)) + if err != nil { + return err + } + return nil +} + +// IsInterfaceNil checks if the interface is nil +func (s *miniBlocksSelectionSession) IsInterfaceNil() bool { + return s == nil +} diff --git a/process/block/mbsSelectionSession_test.go b/process/block/mbsSelectionSession_test.go new file mode 100644 index 00000000000..8192434e7f2 --- /dev/null +++ b/process/block/mbsSelectionSession_test.go @@ -0,0 +1,337 @@ +package block + +import ( + "fmt" + "testing" + + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/marshal/factory" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/testscommon" +) + +func TestNewMiniBlocksSelectionSession(t *testing.T) { + t.Parallel() + + marshaller := &testscommon.MarshallerStub{} + hasher := &testscommon.HasherStub{} + + t.Run("nil marshaller should return error", func(t *testing.T) { + t.Parallel() + + session, err := NewMiniBlocksSelectionSession(1, nil, hasher) + require.Nil(t, session) + require.Equal(t, process.ErrNilMarshalizer, err) + }) + + t.Run("nil hasher should return error", func(t *testing.T) { + t.Parallel() + + session, err := NewMiniBlocksSelectionSession(1, marshaller, nil) + require.Nil(t, session) + require.Equal(t, process.ErrNilHasher, err) + }) + + t.Run("should create session successfully", func(t *testing.T) { + t.Parallel() + + session, err := NewMiniBlocksSelectionSession(1, marshaller, hasher) + require.NotNil(t, session) + require.NoError(t, err) + }) +} + +func TestMiniBlockSelectionSession_ResetSelectionSession(t *testing.T) { + t.Parallel() + + session := createDummyFilledSession() + + require.NotEmpty(t, session.GetMiniBlocks()) + require.NotEmpty(t, session.GetMiniBlockHeaderHandlers()) + require.NotEmpty(t, session.GetMiniBlockHashes()) + require.NotEmpty(t, session.GetReferencedHeaderHashes()) + require.NotEmpty(t, session.GetReferencedHeaders()) + require.NotEmpty(t, session.referenceHeaderHashesUnique) + require.NotEmpty(t, session.miniBlockHashesUnique) + require.NotNil(t, session.GetLastHeader()) + + session.ResetSelectionSession() + + require.Empty(t, session.GetMiniBlocks()) + require.Empty(t, session.GetMiniBlockHeaderHandlers()) + require.Empty(t, session.GetMiniBlockHashes()) + require.Empty(t, session.GetReferencedHeaderHashes()) + require.Empty(t, session.GetReferencedHeaders()) + require.Empty(t, session.referenceHeaderHashesUnique) + require.Empty(t, session.miniBlockHashesUnique) + require.Nil(t, session.GetLastHeader()) + require.Equal(t, uint32(0), session.GetNumTxsAdded()) +} + +func TestMiniBlockSelectionSession_Getters(t *testing.T) { + t.Parallel() + + session := createDummyFilledSession() + + require.Len(t, session.GetMiniBlockHeaderHandlers(), 1) + require.Len(t, session.GetMiniBlocks(), 1) + require.Len(t, session.GetMiniBlockHashes(), 1) + require.Len(t, session.GetReferencedHeaderHashes(), 1) + require.Len(t, session.GetReferencedHeaders(), 1) + require.NotNil(t, session.GetLastHeader()) + require.Equal(t, uint32(2), session.GetNumTxsAdded()) +} + +func TestMiniBlockSelectionSession_AddMiniBlocksAndHashes(t *testing.T) { + t.Parallel() + + marshaller := &testscommon.MarshallerStub{} + hasher := &testscommon.HasherStub{} + t.Run("should not add empty mini blocks and hashes", func(t *testing.T) { + t.Parallel() + + session, _ := NewMiniBlocksSelectionSession(1, marshaller, hasher) + err := session.AddMiniBlocksAndHashes(nil) + + require.NoError(t, err) + require.Empty(t, session.GetMiniBlocks()) + require.Empty(t, session.GetMiniBlockHashes()) + }) + + t.Run("should not add same mini block twice", func(t *testing.T) { + t.Parallel() + + session, _ := NewMiniBlocksSelectionSession(1, marshaller, hasher) + err := session.AddMiniBlocksAndHashes([]block.MiniblockAndHash{ + {Miniblock: &block.MiniBlock{}, Hash: []byte("hash1")}, + {Miniblock: &block.MiniBlock{}, Hash: []byte("hash2")}, + {Miniblock: &block.MiniBlock{}, Hash: []byte("hash3")}, + }) + + require.NoError(t, err) + require.Equal(t, 3, len(session.GetMiniBlocks())) + require.Equal(t, 3, len(session.GetMiniBlockHashes())) + + err = session.AddMiniBlocksAndHashes([]block.MiniblockAndHash{{Miniblock: &block.MiniBlock{}, Hash: []byte("hash1")}}) + + require.NoError(t, err) + require.Equal(t, 3, len(session.GetMiniBlocks())) + }) + + t.Run("should add mini blocks and hashes successfully", func(t *testing.T) { + t.Parallel() + + session, _ := NewMiniBlocksSelectionSession(1, marshaller, hasher) + miniBlock := &block.MiniBlock{} + miniBlockHash := []byte("hash") + err := session.AddMiniBlocksAndHashes([]block.MiniblockAndHash{ + {Miniblock: miniBlock, Hash: miniBlockHash}, + }) + + require.NoError(t, err) + require.Len(t, session.GetMiniBlocks(), 1) + require.Len(t, session.GetMiniBlockHashes(), 1) + require.Equal(t, miniBlock, session.GetMiniBlocks()[0]) + require.Equal(t, miniBlockHash, session.GetMiniBlockHashes()[0]) + }) +} + +func Test_setProcessingTypeAndConstructionStateForProposalMb(t *testing.T) { + t.Parallel() + + t.Run("error on setProcessingType should error", func(t *testing.T) { + t.Parallel() + + mbHeaderHandler := &block.MiniBlockHeader{ + Reserved: []byte("invalid should error"), + } + err := setProcessingTypeAndConstructionStateForProposalMb(mbHeaderHandler) + require.Error(t, err) + }) + t.Run("should set processing type and construction state successfully", func(t *testing.T) { + t.Parallel() + + marshaller, err := factory.NewMarshalizer("gogo protobuf") + require.Nil(t, err) + + mbReserved := &block.MiniBlockHeaderReserved{} + marshalledReserved, err := marshaller.Marshal(mbReserved) + require.Nil(t, err) + + mbHeaderHandler := &block.MiniBlockHeader{ + Reserved: marshalledReserved, + } + + err = setProcessingTypeAndConstructionStateForProposalMb(mbHeaderHandler) + require.Nil(t, err) + require.Equal(t, block.TxBlock, mbHeaderHandler.GetType()) + require.Equal(t, int32(block.Proposed), mbHeaderHandler.GetConstructionState()) + require.Equal(t, int32(block.Normal), mbHeaderHandler.GetProcessingType()) + }) +} + +func TestMiniBlockSelectionSession_CreateAndAddMiniBlockFromTransactions(t *testing.T) { + t.Parallel() + + tx1Hash := []byte("tx1") + tx2Hash := []byte("tx2") + marshaller := &testscommon.MarshallerStub{} + hasher := &testscommon.HasherStub{} + t.Run("should create and add mini block from transactions successfully", func(t *testing.T) { + t.Parallel() + + session, _ := NewMiniBlocksSelectionSession(1, marshaller, hasher) + txHashes := [][]byte{tx1Hash, tx2Hash} + err := session.CreateAndAddMiniBlockFromTransactions(txHashes) + + require.NoError(t, err) + require.Len(t, session.GetMiniBlocks(), 1) + require.Len(t, session.GetMiniBlockHashes(), 1) + }) + + t.Run("should not add mini block for empty transactions", func(t *testing.T) { + t.Parallel() + + session, _ := NewMiniBlocksSelectionSession(1, marshaller, hasher) + err := session.CreateAndAddMiniBlockFromTransactions(nil) + + require.NoError(t, err) + require.Empty(t, session.GetMiniBlocks()) + require.Empty(t, session.GetMiniBlockHashes()) + }) + t.Run("should not add mini block for empty transactions slice", func(t *testing.T) { + t.Parallel() + + session, _ := NewMiniBlocksSelectionSession(1, marshaller, hasher) + err := session.CreateAndAddMiniBlockFromTransactions([][]byte{}) + + require.NoError(t, err) + require.Empty(t, session.GetMiniBlocks()) + require.Empty(t, session.GetMiniBlockHashes()) + }) + + t.Run("marshalling error should return error", func(t *testing.T) { + t.Parallel() + + expectedError := fmt.Errorf("marshalling error") + marshaller := &testscommon.MarshallerStub{ + MarshalCalled: func(_ interface{}) ([]byte, error) { + return nil, expectedError + }, + } + session, _ := NewMiniBlocksSelectionSession(1, marshaller, hasher) + err := session.CreateAndAddMiniBlockFromTransactions([][]byte{tx1Hash, tx2Hash}) + + require.Equal(t, expectedError, err) + require.Empty(t, session.GetMiniBlocks()) + require.Empty(t, session.GetMiniBlockHashes()) + }) +} + +func TestMiniBlocksSelectionSession_AddReferencedMetaBlock(t *testing.T) { + t.Parallel() + + marshaller := &testscommon.MarshallerStub{} + hasher := &testscommon.HasherStub{} + t.Run("should add referenced meta block successfully", func(t *testing.T) { + t.Parallel() + + session, _ := NewMiniBlocksSelectionSession(1, marshaller, hasher) + metaBlock := &block.MetaBlock{Epoch: 1, Round: 1} + metaBlockHash := []byte("metaHash") + session.AddReferencedHeader(metaBlock, metaBlockHash) + + require.Len(t, session.GetReferencedHeaders(), 1) + require.Len(t, session.GetReferencedHeaderHashes(), 1) + require.Equal(t, metaBlock, session.GetReferencedHeaders()[0]) + require.Equal(t, metaBlockHash, session.GetReferencedHeaderHashes()[0]) + }) + t.Run("should not add nil meta block", func(t *testing.T) { + t.Parallel() + + session, _ := NewMiniBlocksSelectionSession(1, marshaller, hasher) + session.AddReferencedHeader(nil, []byte("metaHash")) + + require.Empty(t, session.GetReferencedHeaders()) + require.Empty(t, session.GetReferencedHeaderHashes()) + }) + t.Run("should not add empty meta block hash", func(t *testing.T) { + t.Parallel() + + session, _ := NewMiniBlocksSelectionSession(1, marshaller, hasher) + metaBlock := &block.MetaBlock{Epoch: 1, Round: 1} + session.AddReferencedHeader(metaBlock, nil) + + require.Empty(t, session.GetReferencedHeaders()) + require.Empty(t, session.GetReferencedHeaderHashes()) + }) + t.Run("should not add same reference header twice", func(t *testing.T) { + session, _ := NewMiniBlocksSelectionSession(1, marshaller, hasher) + metaBlock := &block.MetaBlock{Epoch: 1, Round: 1} + metaBlockHash := []byte("metaHash") + session.AddReferencedHeader(metaBlock, metaBlockHash) + + require.Len(t, session.GetReferencedHeaders(), 1) + require.Len(t, session.GetReferencedHeaderHashes(), 1) + require.Equal(t, metaBlock, session.GetReferencedHeaders()[0]) + require.Equal(t, metaBlockHash, session.GetReferencedHeaderHashes()[0]) + + session.AddReferencedHeader(metaBlock, metaBlockHash) + + require.Len(t, session.GetReferencedHeaders(), 1) + require.Len(t, session.GetReferencedHeaderHashes(), 1) + require.Equal(t, metaBlock, session.GetReferencedHeaders()[0]) + require.Equal(t, metaBlockHash, session.GetReferencedHeaderHashes()[0]) + }) +} + +func TestMiniBlocksSelectionSession_IsInterfaceNil(t *testing.T) { + t.Parallel() + + t.Run("should return true if session is nil", func(t *testing.T) { + t.Parallel() + + var session *miniBlocksSelectionSession + require.True(t, session.IsInterfaceNil()) + }) + + t.Run("should return false if session is not nil", func(t *testing.T) { + t.Parallel() + + session := createDummyFilledSession() + require.False(t, session.IsInterfaceNil()) + }) +} + +func createDummyFilledSession() *miniBlocksSelectionSession { + marshaller := &testscommon.MarshallerStub{} + hasher := &testscommon.HasherStub{} + session, _ := NewMiniBlocksSelectionSession(1, marshaller, hasher) + + miniBlock := &block.MiniBlock{TxHashes: [][]byte{[]byte("tx1"), []byte("tx2")}} + miniBlockHash := []byte("dummyHash") + miniBlockHeader := &block.MiniBlockHeader{ + Hash: miniBlockHash, + SenderShardID: 1, + ReceiverShardID: 1, + TxCount: uint32(len(miniBlock.TxHashes)), + Type: block.TxBlock, + } + metaBlock := &block.MetaBlock{Epoch: 10, Round: 1000} + metaBlockHash := []byte("metaHash") + + // Add dummy mini blocks and hashes + session.miniBlocks = append(session.miniBlocks, miniBlock) + session.miniBlockHashes = append(session.miniBlockHashes, miniBlockHash) + session.miniBlockHashesUnique[string(miniBlockHash)] = struct{}{} + session.referenceHeaderHashesUnique[string(metaBlockHash)] = struct{}{} + session.miniBlockHeaderHandlers = append(session.miniBlockHeaderHandlers, miniBlockHeader) + session.referencedHeaderHashes = append(session.referencedHeaderHashes, metaBlockHash) + session.referencedHeader = append(session.referencedHeader, metaBlock) + session.lastHeader = metaBlock + session.numTxsAdded = 2 + + return session +} diff --git a/process/block/metablock.go b/process/block/metablock.go index 34aff7785fc..e8bc7290b7e 100644 --- a/process/block/metablock.go +++ b/process/block/metablock.go @@ -16,22 +16,41 @@ import ( "github.com/multiversx/mx-chain-core-go/data/headerVersionData" logger "github.com/multiversx/mx-chain-logger-go" + epochStartMetaCommmon "github.com/multiversx/mx-chain-go/epochStart/metachain" + "github.com/multiversx/mx-chain-go/trie" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/common/holders" "github.com/multiversx/mx-chain-go/dataRetriever" processOutport "github.com/multiversx/mx-chain-go/outport/process" "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionTrack" "github.com/multiversx/mx-chain-go/process/block/bootstrapStorage" "github.com/multiversx/mx-chain-go/process/block/helpers" "github.com/multiversx/mx-chain-go/process/block/processedMb" "github.com/multiversx/mx-chain-go/state" ) -const firstHeaderNonce = uint64(1) +const ( + firstHeaderNonce = uint64(1) + defaultMaxProposalNonceGap = 10 +) var _ process.BlockProcessor = (*metaProcessor)(nil) // metaProcessor implements metaProcessor interface, and actually it tries to execute block + +// ShardHeaderInfo holds info about a shard header +type ShardHeaderInfo struct { + Header data.HeaderHandler + Hash []byte + UsedInBlock bool +} + +type epochStartDataWrapper struct { + Epoch uint32 + EpochStartData *block.EpochStart +} type metaProcessor struct { *baseProcessor scToProtocol process.SmartContractToProtocolHandler @@ -45,20 +64,17 @@ type metaProcessor struct { shardsHeadersNonce *sync.Map shardBlockFinality uint32 headersCounter *headersCounter + epochStartDataWrapper *epochStartDataWrapper + mutEpochStartData sync.RWMutex + shardInfoCreateData process.ShardInfoCreator } // NewMetaProcessor creates a new metaProcessor object func NewMetaProcessor(arguments ArgMetaProcessor) (*metaProcessor, error) { - err := checkProcessorParameters(arguments.ArgBaseProcessor) + base, err := NewBaseProcessor(arguments.ArgBaseProcessor) if err != nil { return nil, err } - if check.IfNil(arguments.DataComponents.Datapool()) { - return nil, process.ErrNilDataPoolHolder - } - if check.IfNil(arguments.DataComponents.Datapool().Headers()) { - return nil, process.ErrNilHeadersDataPool - } if check.IfNil(arguments.SCToProtocol) { return nil, process.ErrNilSCToProtocol } @@ -86,63 +102,8 @@ func NewMetaProcessor(arguments ArgMetaProcessor) (*metaProcessor, error) { if check.IfNil(arguments.ReceiptsRepository) { return nil, process.ErrNilReceiptsRepository } - - processDebugger, err := createDisabledProcessDebugger() - if err != nil { - return nil, err - } - - genesisHdr := arguments.DataComponents.Blockchain().GetGenesisHeader() - base := &baseProcessor{ - accountsDB: arguments.AccountsDB, - blockSizeThrottler: arguments.BlockSizeThrottler, - forkDetector: arguments.ForkDetector, - hasher: arguments.CoreComponents.Hasher(), - marshalizer: arguments.CoreComponents.InternalMarshalizer(), - store: arguments.DataComponents.StorageService(), - shardCoordinator: arguments.BootstrapComponents.ShardCoordinator(), - feeHandler: arguments.FeeHandler, - nodesCoordinator: arguments.NodesCoordinator, - uint64Converter: arguments.CoreComponents.Uint64ByteSliceConverter(), - requestHandler: arguments.RequestHandler, - appStatusHandler: arguments.StatusCoreComponents.AppStatusHandler(), - blockChainHook: arguments.BlockChainHook, - txCoordinator: arguments.TxCoordinator, - epochStartTrigger: arguments.EpochStartTrigger, - headerValidator: arguments.HeaderValidator, - roundHandler: arguments.CoreComponents.RoundHandler(), - bootStorer: arguments.BootStorer, - blockTracker: arguments.BlockTracker, - dataPool: arguments.DataComponents.Datapool(), - blockChain: arguments.DataComponents.Blockchain(), - outportHandler: arguments.StatusComponents.OutportHandler(), - genesisNonce: genesisHdr.GetNonce(), - versionedHeaderFactory: arguments.BootstrapComponents.VersionedHeaderFactory(), - headerIntegrityVerifier: arguments.BootstrapComponents.HeaderIntegrityVerifier(), - historyRepo: arguments.HistoryRepository, - epochNotifier: arguments.CoreComponents.EpochNotifier(), - enableEpochsHandler: arguments.CoreComponents.EnableEpochsHandler(), - roundNotifier: arguments.CoreComponents.RoundNotifier(), - enableRoundsHandler: arguments.CoreComponents.EnableRoundsHandler(), - epochChangeGracePeriodHandler: arguments.CoreComponents.EpochChangeGracePeriodHandler(), - vmContainerFactory: arguments.VMContainersFactory, - vmContainer: arguments.VmContainer, - processDataTriesOnCommitEpoch: arguments.Config.Debug.EpochStart.ProcessDataTrieOnCommitEpoch, - gasConsumedProvider: arguments.GasHandler, - economicsData: arguments.CoreComponents.EconomicsData(), - scheduledTxsExecutionHandler: arguments.ScheduledTxsExecutionHandler, - pruningDelay: pruningDelay, - processedMiniBlocksTracker: arguments.ProcessedMiniBlocksTracker, - receiptsRepository: arguments.ReceiptsRepository, - processDebugger: processDebugger, - outportDataProvider: arguments.OutportDataProvider, - processStatusHandler: arguments.CoreComponents.ProcessStatusHandler(), - blockProcessingCutoffHandler: arguments.BlockProcessingCutoffHandler, - managedPeersHolder: arguments.ManagedPeersHolder, - sentSignaturesTracker: arguments.SentSignaturesTracker, - stateAccessesCollector: arguments.StateAccessesCollector, - extraDelayRequestBlockInfo: time.Duration(arguments.Config.EpochStartConfig.ExtraDelayForRequestBlockInfoInMilliseconds) * time.Millisecond, - proofsPool: arguments.DataComponents.Datapool().Proofs(), + if check.IfNil(arguments.ShardInfoCreator) { + return nil, process.ErrNilShardInfoCreator } mp := metaProcessor{ @@ -156,6 +117,11 @@ func NewMetaProcessor(arguments ArgMetaProcessor) (*metaProcessor, error) { validatorStatisticsProcessor: arguments.ValidatorStatisticsProcessor, validatorInfoCreator: arguments.EpochValidatorInfoCreator, epochSystemSCProcessor: arguments.EpochSystemSCProcessor, + shardInfoCreateData: arguments.ShardInfoCreator, + epochStartDataWrapper: &epochStartDataWrapper{ + Epoch: arguments.EpochStartTrigger.Epoch(), + EpochStartData: &block.EpochStart{}, + }, } argsTransactionCounter := ArgsTransactionCounter{ @@ -172,15 +138,6 @@ func NewMetaProcessor(arguments ArgMetaProcessor) (*metaProcessor, error) { mp.requestBlockBodyHandler = &mp mp.blockProcessor = &mp - mp.hdrsForCurrBlock = newHdrForBlock() - - headersPool := mp.dataPool.Headers() - headersPool.RegisterHandler(mp.receivedShardHeader) - - mp.proofsPool.RegisterHandler(mp.checkReceivedProofIfAttestingIsNeeded) - - mp.chRcvAllHdrs = make(chan bool) - mp.shardBlockFinality = process.BlockFinality mp.shardsHeadersNonce = &sync.Map{} @@ -202,7 +159,9 @@ func (mp *metaProcessor) ProcessBlock( return process.ErrNilHaveTimeHandler } - mp.processStatusHandler.SetBusy("metaProcessor.ProcessBlock") + if !mp.processStatusHandler.TrySetBusy("metaProcessor.ProcessBlock") { + return process.ErrBlockProcessorBusy + } defer mp.processStatusHandler.SetIdle() err := mp.checkBlockValidity(headerHandler, bodyHandler) @@ -249,24 +208,7 @@ func (mp *metaProcessor) ProcessBlock( return err } - headersPool := mp.dataPool.Headers() - numShardHeadersFromPool := 0 - for shardID := uint32(0); shardID < mp.shardCoordinator.NumberOfShards(); shardID++ { - numShardHeadersFromPool += headersPool.GetNumHeaders(shardID) - } - - txCounts, rewardCounts, unsignedCounts := mp.txCounter.getPoolCounts(mp.dataPool) - log.Debug("total txs in pool", "counts", txCounts.String()) - log.Debug("total txs in rewards pool", "counts", rewardCounts.String()) - log.Debug("total txs in unsigned pool", "counts", unsignedCounts.String()) - - go getMetricsFromMetaHeader( - header, - mp.marshalizer, - mp.appStatusHandler, - numShardHeadersFromPool, - mp.headersCounter.getNumShardMBHeadersTotalProcessed(), - ) + mp.updateMetrics(header) defer func() { if err != nil { @@ -280,7 +222,6 @@ func (mp *metaProcessor) ProcessBlock( } mp.epochStartTrigger.Update(header.GetRound(), header.GetNonce()) - err = mp.checkEpochCorrectness(header) if err != nil { return err @@ -307,7 +248,7 @@ func (mp *metaProcessor) ProcessBlock( } mp.txCoordinator.RequestBlockTransactions(body) - requestedShardHdrs, requestedFinalityAttestingShardHdrs, requestedProofs := mp.requestShardHeaders(header) + mp.hdrsForCurrBlock.RequestShardHeaders(header) if haveTime() < 0 { return process.ErrTimeIsOut @@ -318,47 +259,9 @@ func (mp *metaProcessor) ProcessBlock( return err } - haveMissingShardHeaders := requestedShardHdrs > 0 || requestedFinalityAttestingShardHdrs > 0 || requestedProofs > 0 - if haveMissingShardHeaders { - if requestedShardHdrs > 0 { - log.Debug("requested missing shard headers", - "num headers", requestedShardHdrs, - ) - } - if requestedFinalityAttestingShardHdrs > 0 { - log.Debug("requested missing finality attesting shard headers", - "num finality shard headers", requestedFinalityAttestingShardHdrs, - ) - } - if requestedProofs > 0 { - log.Debug("requested missing shard header proofs", - "num proofs", requestedProofs, - ) - } - - err = mp.waitForBlockHeaders(haveTime()) - - mp.hdrsForCurrBlock.mutHdrsForBlock.RLock() - missingShardHdrs := mp.hdrsForCurrBlock.missingHdrs - missingProofs := mp.hdrsForCurrBlock.missingProofs - mp.hdrsForCurrBlock.mutHdrsForBlock.RUnlock() - - mp.hdrsForCurrBlock.resetMissingHdrs() - - if requestedShardHdrs > 0 { - log.Debug("received missing shard headers", - "num headers", requestedShardHdrs-missingShardHdrs, - ) - } - if requestedProofs > 0 { - log.Debug("received missing shard header proofs", - "num proofs", requestedProofs-missingProofs, - ) - } - - if err != nil { - return err - } + err = mp.hdrsForCurrBlock.WaitForHeadersIfNeeded(haveTime) + if err != nil { + return err } defer func() { @@ -431,8 +334,29 @@ func (mp *metaProcessor) ProcessBlock( return nil } +func (mp *metaProcessor) updateMetrics(header data.MetaHeaderHandler) { + headersPool := mp.dataPool.Headers() + numShardHeadersFromPool := 0 + for shardID := uint32(0); shardID < mp.shardCoordinator.NumberOfShards(); shardID++ { + numShardHeadersFromPool += headersPool.GetNumHeaders(shardID) + } + + txCounts, rewardCounts, unsignedCounts := mp.txCounter.getPoolCounts(mp.dataPool) + log.Debug("total txs in pool", "counts", txCounts.String()) + log.Debug("total txs in rewards pool", "counts", rewardCounts.String()) + log.Debug("total txs in unsigned pool", "counts", unsignedCounts.String()) + + go getMetricsFromMetaHeader( + header, + mp.marshalizer, + mp.appStatusHandler, + numShardHeadersFromPool, + mp.headersCounter.getNumShardMBHeadersTotalProcessed(), + ) +} + func (mp *metaProcessor) processEpochStartMetaBlock( - header *block.MetaBlock, + header data.MetaHeaderHandler, body *block.Body, ) error { err := mp.epochStartDataCreator.VerifyEpochStartDataForMetablock(header) @@ -450,7 +374,7 @@ func (mp *metaProcessor) processEpochStartMetaBlock( return err } - err = mp.validatorStatisticsProcessor.ProcessRatingsEndOfEpoch(allValidatorsInfo, header.Epoch) + err = mp.validatorStatisticsProcessor.ProcessRatingsEndOfEpoch(allValidatorsInfo, header.GetEpoch()) if err != nil { return err } @@ -517,7 +441,7 @@ func (mp *metaProcessor) processEpochStartMetaBlock( return err } - saveEpochStartEconomicsMetrics(mp.appStatusHandler, header) + mp.saveEpochStartEconomicsMetrics(header) return nil } @@ -528,7 +452,7 @@ func (mp *metaProcessor) SetNumProcessedObj(numObj uint64) { } func (mp *metaProcessor) checkEpochCorrectness( - headerHandler data.HeaderHandler, + headerHandler data.MetaHeaderHandler, ) error { currentBlockHeader := mp.blockChain.GetCurrentBlockHeader() if check.IfNil(currentBlockHeader) { @@ -553,15 +477,15 @@ func (mp *metaProcessor) checkEpochCorrectness( return nil } -func (mp *metaProcessor) verifyCrossShardMiniBlockDstMe(metaBlock *block.MetaBlock) error { +func (mp *metaProcessor) verifyCrossShardMiniBlockDstMe(metaBlock data.MetaHeaderHandler) error { miniBlockShardsHashes, err := mp.getAllMiniBlockDstMeFromShards(metaBlock) if err != nil { return err } - mapMetaMiniBlockHeaders := make(map[string]struct{}, len(metaBlock.MiniBlockHeaders)) - for _, miniBlockHeader := range metaBlock.MiniBlockHeaders { - mapMetaMiniBlockHeaders[string(miniBlockHeader.Hash)] = struct{}{} + mapMetaMiniBlockHeaders := make(map[string]struct{}, len(metaBlock.GetMiniBlockHeaderHandlers())) + for _, miniBlockHeader := range metaBlock.GetMiniBlockHeaderHandlers() { + mapMetaMiniBlockHeaders[string(miniBlockHeader.GetHash())] = struct{}{} } for hash := range miniBlockShardsHashes { @@ -573,40 +497,57 @@ func (mp *metaProcessor) verifyCrossShardMiniBlockDstMe(metaBlock *block.MetaBlo return nil } -func (mp *metaProcessor) getAllMiniBlockDstMeFromShards(metaHdr *block.MetaBlock) (map[string][]byte, error) { +func (mp *metaProcessor) getAllMiniBlockDstMeFromShards(metaHdr data.MetaHeaderHandler) (map[string][]byte, error) { miniBlockShardsHashes := make(map[string][]byte) - mp.hdrsForCurrBlock.mutHdrsForBlock.RLock() - defer mp.hdrsForCurrBlock.mutHdrsForBlock.RUnlock() - - for _, shardInfo := range metaHdr.ShardInfo { - headerInfo, ok := mp.hdrsForCurrBlock.hdrHashAndInfo[string(shardInfo.HeaderHash)] - if !ok { - continue + var shardHeaderHandler data.HeaderHandler + var err error + for _, shardInfo := range getShardHeadersReferencedByMeta(metaHdr) { + shardHeaderHandler, err = mp.getHeaderFromHash(metaHdr.IsHeaderV3(), shardInfo.GetHeaderHash(), shardInfo.GetShardID()) + if err != nil { + return nil, fmt.Errorf("%w : for shardInfo.HeaderHash = %s", + process.ErrMissingHeader, hex.EncodeToString(shardInfo.GetHeaderHash())) } - shardHeader, ok := headerInfo.hdr.(data.ShardHeaderHandler) + + shardHeader, ok := shardHeaderHandler.(data.ShardHeaderHandler) if !ok { - continue + return nil, fmt.Errorf("%w : for shardInfo.HeaderHash = %s", + process.ErrWrongTypeAssertion, hex.EncodeToString(shardInfo.GetHeaderHash())) } - lastCrossNotarizedHeader, _, err := mp.blockTracker.GetLastCrossNotarizedHeader(shardInfo.ShardID) + lastCrossNotarizedHeader, _, err := mp.blockTracker.GetLastCrossNotarizedHeader(shardInfo.GetShardID()) if err != nil { return nil, err } - if shardHeader.GetRound() > metaHdr.Round { - continue + if shardHeader.GetRound() > metaHdr.GetRound() { + return nil, fmt.Errorf("%w : for shard info with hash = %s", + process.ErrHigherRoundInBlock, hex.EncodeToString(shardInfo.GetHeaderHash())) } - if shardHeader.GetRound() <= lastCrossNotarizedHeader.GetRound() { - continue + + isGenesisNotarization := metaHdr.GetNonce() == 1 + + if isGenesisNotarization && shardHeader.GetRound() != 0 { + return nil, fmt.Errorf("%w : for shard info with hash = %s", + process.ErrLowerRoundInBlock, hex.EncodeToString(shardInfo.GetHeaderHash())) } - if shardHeader.GetNonce() <= lastCrossNotarizedHeader.GetNonce() { - continue + if isGenesisNotarization && shardHeader.GetNonce() != 0 { + return nil, fmt.Errorf("%w : for shard info with hash = %s", + process.ErrLowerNonceInBlock, hex.EncodeToString(shardInfo.GetHeaderHash())) + } + + if !isGenesisNotarization && shardHeader.GetRound() <= lastCrossNotarizedHeader.GetRound() { + return nil, fmt.Errorf("%w : for shard info with hash = %s", + process.ErrLowerRoundInBlock, hex.EncodeToString(shardInfo.GetHeaderHash())) + } + if !isGenesisNotarization && shardHeader.GetNonce() <= lastCrossNotarizedHeader.GetNonce() { + return nil, fmt.Errorf("%w : for shard info with hash = %s", + process.ErrLowerNonceInBlock, hex.EncodeToString(shardInfo.GetHeaderHash())) } finalCrossMiniBlockHashes := mp.getFinalCrossMiniBlockHashes(shardHeader) for hash := range finalCrossMiniBlockHashes { - miniBlockShardsHashes[hash] = shardInfo.HeaderHash + miniBlockShardsHashes[hash] = shardInfo.GetHeaderHash() } } @@ -654,8 +595,8 @@ func (mp *metaProcessor) indexBlock( NotarizedHeadersHashes: notarizedHeadersHashes, HighestFinalBlockNonce: mp.forkDetector.GetHighestFinalBlockNonce(), HighestFinalBlockHash: mp.forkDetector.GetHighestFinalBlockHash(), + ScheduledRootHash: mp.scheduledTxsExecutionHandler.GetScheduledRootHash(), }) - if err != nil { log.Error("metaProcessor.indexBlock cannot prepare argSaveBlock", "error", err.Error(), "hash", headerHash, "nonce", metaBlock.GetNonce(), "round", metaBlock.GetRound()) @@ -785,10 +726,12 @@ func (mp *metaProcessor) CreateBlock( return nil, nil, process.ErrWrongTypeAssertion } - mp.processStatusHandler.SetBusy("metaProcessor.CreateBlock") + if !mp.processStatusHandler.TrySetBusy("metaProcessor.CreateBlock") { + return nil, nil, process.ErrBlockProcessorBusy + } defer mp.processStatusHandler.SetIdle() - metaHdr.SoftwareVersion = []byte(mp.headerIntegrityVerifier.GetVersion(metaHdr.Epoch)) + metaHdr.SoftwareVersion = []byte(mp.headerIntegrityVerifier.GetVersion(metaHdr.Epoch, metaHdr.Round)) mp.epochNotifier.CheckEpoch(metaHdr) var body data.BodyHandler @@ -892,7 +835,7 @@ func (mp *metaProcessor) updateEpochStartHeader(metaHdr *block.MetaBlock) error metaHdr.EpochStart.Economics = *economicsData - saveEpochStartEconomicsMetrics(mp.appStatusHandler, metaHdr) + mp.saveEpochStartEconomicsMetrics(metaHdr) return nil } @@ -909,6 +852,16 @@ func (mp *metaProcessor) createEpochStartBody(metaBlock *block.MetaBlock) (data. "nonce", metaBlock.GetNonce(), ) + return mp.processEpochStartMiniBlocks(metaBlock, &metaBlock.EpochStart.Economics) +} + +func (mp *metaProcessor) processEpochStartMiniBlocks( + metaBlock data.MetaHeaderHandler, + computedEconomics *block.Economics, +) (*block.Body, error) { + + epochToUse := epochStartMetaCommmon.GetEpochToUseEpochStartData(metaBlock) + currentRootHash, err := mp.validatorStatisticsProcessor.RootHash() if err != nil { return nil, err @@ -919,24 +872,60 @@ func (mp *metaProcessor) createEpochStartBody(metaBlock *block.MetaBlock) (data. return nil, err } - err = mp.validatorStatisticsProcessor.ProcessRatingsEndOfEpoch(allValidatorsInfo, metaBlock.Epoch) + err = mp.validatorStatisticsProcessor.ProcessRatingsEndOfEpoch(allValidatorsInfo, epochToUse) + if err != nil { + return nil, err + } + + rewardMiniBlocks, err := mp.createRewardsMiniBlocksAndProcessSystemSCs(metaBlock, allValidatorsInfo, computedEconomics) + if err != nil { + return nil, err + } + + computedEconomics.RewardsForProtocolSustainability.Set(mp.epochRewardsCreator.GetAcceleratorRewards()) + + err = mp.epochSystemSCProcessor.ProcessDelegationRewards(rewardMiniBlocks, mp.epochRewardsCreator.GetLocalTxCache()) + if err != nil { + return nil, err + } + + validatorMiniBlocks, err := mp.validatorInfoCreator.CreateValidatorInfoMiniBlocks(allValidatorsInfo) + if err != nil { + return nil, err + } + + err = mp.validatorStatisticsProcessor.ResetValidatorStatisticsAtNewEpoch(allValidatorsInfo) if err != nil { return nil, err } + finalMiniBlocks := make([]*block.MiniBlock, 0) + finalMiniBlocks = append(finalMiniBlocks, rewardMiniBlocks...) + finalMiniBlocks = append(finalMiniBlocks, validatorMiniBlocks...) + + return &block.Body{MiniBlocks: finalMiniBlocks}, nil +} + +func (mp *metaProcessor) createRewardsMiniBlocksAndProcessSystemSCs( + metaBlock data.MetaHeaderHandler, + allValidatorsInfo state.ShardValidatorsInfoMapHandler, + computedEconomics *block.Economics, +) (block.MiniBlockSlice, error) { + var rewardMiniBlocks block.MiniBlockSlice + var err error if mp.isRewardsV2Enabled(metaBlock) { err = mp.epochSystemSCProcessor.ProcessSystemSmartContract(allValidatorsInfo, metaBlock) if err != nil { return nil, err } - rewardMiniBlocks, err = mp.epochRewardsCreator.CreateRewardsMiniBlocks(metaBlock, allValidatorsInfo, &metaBlock.EpochStart.Economics) + rewardMiniBlocks, err = mp.createRewardsMiniBlocks(metaBlock, allValidatorsInfo, computedEconomics) if err != nil { return nil, err } } else { - rewardMiniBlocks, err = mp.epochRewardsCreator.CreateRewardsMiniBlocks(metaBlock, allValidatorsInfo, &metaBlock.EpochStart.Economics) + rewardMiniBlocks, err = mp.createRewardsMiniBlocks(metaBlock, allValidatorsInfo, computedEconomics) if err != nil { return nil, err } @@ -947,28 +936,29 @@ func (mp *metaProcessor) createEpochStartBody(metaBlock *block.MetaBlock) (data. } } - metaBlock.EpochStart.Economics.RewardsForProtocolSustainability.Set(mp.epochRewardsCreator.GetAcceleratorRewards()) + return rewardMiniBlocks, nil +} - err = mp.epochSystemSCProcessor.ProcessDelegationRewards(rewardMiniBlocks, mp.epochRewardsCreator.GetLocalTxCache()) - if err != nil { - return nil, err +func (mp *metaProcessor) createRewardsMiniBlocks( + metaHeader data.MetaHeaderHandler, + allValidatorsInfo state.ShardValidatorsInfoMapHandler, + computedEconomics *block.Economics, +) (block.MiniBlockSlice, error) { + if !metaHeader.IsHeaderV3() { + return mp.epochRewardsCreator.CreateRewardsMiniBlocks(metaHeader, allValidatorsInfo, computedEconomics) } - validatorMiniBlocks, err := mp.validatorInfoCreator.CreateValidatorInfoMiniBlocks(allValidatorsInfo) - if err != nil { - return nil, err + prevBlockExecutionResult := mp.blockChain.GetLastExecutionResult() + if check.IfNil(prevBlockExecutionResult) { + return nil, fmt.Errorf("%w for blockchain.GetLastExecutionResult", process.ErrNilBaseExecutionResult) } - err = mp.validatorStatisticsProcessor.ResetValidatorStatisticsAtNewEpoch(allValidatorsInfo) - if err != nil { - return nil, err + prevMetaExecResult, ok := prevBlockExecutionResult.(data.MetaExecutionResultHandler) + if !ok { + return nil, fmt.Errorf("%w to MetaExecutionResultHandler", process.ErrWrongTypeAssertion) } - finalMiniBlocks := make([]*block.MiniBlock, 0) - finalMiniBlocks = append(finalMiniBlocks, rewardMiniBlocks...) - finalMiniBlocks = append(finalMiniBlocks, validatorMiniBlocks...) - - return &block.Body{MiniBlocks: finalMiniBlocks}, nil + return mp.epochRewardsCreator.CreateRewardsMiniBlocksV3(metaHeader, allValidatorsInfo, computedEconomics, prevMetaExecResult) } // createBlockBody creates block body of metachain @@ -1039,6 +1029,11 @@ func (mp *metaProcessor) createMiniBlocks( ) } + err = mp.recreateTrieIfNeeded() + if err != nil { + return nil, err + } + mbsFromMe := mp.txCoordinator.CreateMbsAndProcessTransactionsFromMe(haveTime, randomness) if len(mbsFromMe) > 0 { miniBlocks = append(miniBlocks, mbsFromMe...) @@ -1087,7 +1082,7 @@ func (mp *metaProcessor) createAndProcessCrossMiniBlocksDstMe( "num shard headers", len(orderedHdrs), ) - lastShardHdr, err := mp.getLastCrossNotarizedShardHdrs() + lastShardHdr, err := mp.getLastCrossNotarizedShardHdrsAndAddToCurrentBlock() if err != nil { return nil, 0, 0, err } @@ -1102,7 +1097,6 @@ func (mp *metaProcessor) createAndProcessCrossMiniBlocksDstMe( return false } - mp.hdrsForCurrBlock.mutHdrsForBlock.Lock() for i := 0; i < len(orderedHdrs); i++ { if !haveTime() { log.Debug("time is up after putting cross txs with destination to current shard", @@ -1148,10 +1142,7 @@ func (mp *metaProcessor) createAndProcessCrossMiniBlocksDstMe( } if len(currShardHdr.GetMiniBlockHeadersWithDst(mp.shardCoordinator.SelfId())) == 0 { - mp.hdrsForCurrBlock.hdrHashAndInfo[string(orderedHdrsHashes[i])] = &hdrInfo{ - hdr: currShardHdr, - usedInBlock: true, - } + mp.hdrsForCurrBlock.AddHeaderUsedInBlock(string(orderedHdrsHashes[i]), currShardHdr) hdrsAdded++ hdrsAddedForShard[currShardHdr.GetShardID()]++ lastShardHdr[currShardHdr.GetShardID()] = currShardHdr @@ -1167,7 +1158,6 @@ func (mp *metaProcessor) createAndProcessCrossMiniBlocksDstMe( false) if createErr != nil { - mp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() return nil, 0, 0, createErr } @@ -1190,16 +1180,12 @@ func (mp *metaProcessor) createAndProcessCrossMiniBlocksDstMe( miniBlocks = append(miniBlocks, currMBProcessed...) txsAdded += currTxsAdded - mp.hdrsForCurrBlock.hdrHashAndInfo[string(orderedHdrsHashes[i])] = &hdrInfo{ - hdr: currShardHdr, - usedInBlock: true, - } + mp.hdrsForCurrBlock.AddHeaderUsedInBlock(string(orderedHdrsHashes[i]), currShardHdr) hdrsAdded++ hdrsAddedForShard[currShardHdr.GetShardID()]++ lastShardHdr[currShardHdr.GetShardID()] = currShardHdr } - mp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() go mp.requestShardHeadersIfNeeded(hdrsAddedForShard, lastShardHdr) @@ -1216,7 +1202,8 @@ func (mp *metaProcessor) requestShardHeadersIfNeeded( "num", hdrsAddedForShard[shardID], "highest nonce", lastShardHdr[shardID].GetNonce()) - roundTooOld := mp.roundHandler.Index() > int64(lastShardHdr[shardID].GetRound()+process.MaxRoundsWithoutNewBlockReceived) + lastShardHdrRound := lastShardHdr[shardID].GetRound() + roundTooOld := mp.roundHandler.Index() > int64(lastShardHdrRound+mp.getMaxRoundsWithoutBlockReceived(lastShardHdrRound)) shouldRequestCrossHeaders := hdrsAddedForShard[shardID] == 0 && roundTooOld if shouldRequestCrossHeaders { fromNonce := lastShardHdr[shardID].GetNonce() + 1 @@ -1224,6 +1211,7 @@ func (mp *metaProcessor) requestShardHeadersIfNeeded( for nonce := fromNonce; nonce <= toNonce; nonce++ { mp.addHeaderIntoTrackerPool(nonce, shardID) mp.requestHandler.RequestShardHeaderByNonce(shardID, nonce) + mp.requestProofIfNeeded(nonce, shardID, lastShardHdr[shardID].GetEpoch()) } } } @@ -1234,20 +1222,34 @@ func (mp *metaProcessor) CommitBlock( headerHandler data.HeaderHandler, bodyHandler data.BodyHandler, ) error { - mp.processStatusHandler.SetBusy("metaProcessor.CommitBlock") - var err error - defer func() { - if err != nil { - mp.RevertCurrentBlock() - } - mp.processStatusHandler.SetIdle() - }() - - err = checkForNils(headerHandler, bodyHandler) + err := checkForNils(headerHandler, bodyHandler) if err != nil { return err } + prevBlockHeader := mp.blockChain.GetCurrentBlockHeader() + prevBlockHeaderHash := mp.blockChain.GetCurrentBlockHeaderHash() + + if !headerHandler.IsHeaderV3() { + if !mp.processStatusHandler.TrySetBusy("metaProcessor.CommitBlock") { + return process.ErrBlockProcessorBusy + } + defer func() { + if err != nil { + mp.RevertCurrentBlock() + } + mp.processStatusHandler.SetIdle() + }() + } else { + defer func() { + if err != nil { + mp.RevertHeaderV3OnCommit(headerHandler) + _ = mp.blockChain.SetCurrentBlockHeader(prevBlockHeader) + mp.blockChain.SetCurrentBlockHeaderHash(prevBlockHeaderHash) + } + }() + } + log.Debug("started committing block", "epoch", headerHandler.GetEpoch(), "shard", headerHandler.GetShardID(), @@ -1262,7 +1264,7 @@ func (mp *metaProcessor) CommitBlock( mp.store.SetEpochForPutOperation(headerHandler.GetEpoch()) - header, ok := headerHandler.(*block.MetaBlock) + header, ok := headerHandler.(data.MetaHeaderHandler) if !ok { err = process.ErrWrongTypeAssertion return err @@ -1282,14 +1284,26 @@ func (mp *metaProcessor) CommitBlock( // must be called before commitEpochStart rewardsTxs := mp.getRewardsTxs(header, body) - mp.commitEpochStart(header, body) + err = mp.commitEpochStart(header, body) + if err != nil { + return err + } + if header.IsStartOfEpochBlock() { + mp.epochStartDataWrapper = &epochStartDataWrapper{ + Epoch: header.GetEpoch(), + EpochStartData: &block.EpochStart{}, + } + } headerHash := mp.hasher.Compute(string(marshalizedHeader)) mp.saveMetaHeader(header, headerHash, marshalizedHeader) mp.saveBody(body, header, headerHash) - err = mp.commitAll(headerHandler) - if err != nil { - return err + if !headerHandler.IsHeaderV3() { + // TODO commit state on ProcessBlockProposal for meta and header v3 + err = mp.commitState(headerHandler) + if err != nil { + return err + } } mp.validatorStatisticsProcessor.DisplayRatings(header.GetEpoch()) @@ -1313,6 +1327,13 @@ func (mp *metaProcessor) CommitBlock( mp.setNonceOfFirstCommittedBlock(headerHandler.GetNonce()) mp.updateLastCommittedInDebugger(headerHandler.GetRound()) + err = mp.computeOwnShardStuckIfNeeded(headerHandler) + if err != nil { + return err + } + + mp.updateGasConsumptionLimitsIfNeeded() + errNotCritical := mp.checkSentSignaturesAtCommitTime(headerHandler) if errNotCritical != nil { log.Debug("checkSentSignaturesBeforeCommitting", "error", errNotCritical.Error()) @@ -1345,27 +1366,38 @@ func (mp *metaProcessor) CommitBlock( finalMetaBlock, finalMetaBlockHash := mp.computeFinalMetaBlock(header, headerHash) mp.updateState(finalMetaBlock, finalMetaBlockHash) - committedRootHash, err := mp.accountsDB[state.UserAccountsState].RootHash() + rootHash := mp.getLastExecutedRootHash(header) + + err = mp.setCurrentBlockInfo(header, headerHash, rootHash) if err != nil { return err } - err = mp.blockChain.SetCurrentBlockHeaderAndRootHash(header, committedRootHash) + mp.blockChain.SetCurrentBlockHeaderHash(headerHash) + + lastExecutionResultHeader, err := mp.getLastExecutionResultHeader(header) if err != nil { return err } - mp.blockChain.SetCurrentBlockHeaderHash(headerHash) - if !check.IfNil(finalMetaBlock) && finalMetaBlock.IsStartOfEpochBlock() { - mp.blockTracker.CleanupInvalidCrossHeaders(header.Epoch, header.Round) + mp.blockTracker.CleanupInvalidCrossHeaders(header.GetEpoch(), header.GetRound()) + } + + err = mp.cleanExecutionResultsFromTracker(header) + if err != nil { + return err } // TODO: Should be sent also validatorInfoTxs alongside rewardsTxs -> mp.validatorInfoCreator.GetValidatorInfoTxs(body) ? mp.indexBlock(header, headerHash, body, finalMetaBlock, notarizedHeadersHashes, rewardsTxs) - mp.stateAccessesCollector.Reset() mp.recordBlockInHistory(headerHash, headerHandler, bodyHandler) + err = mp.OnExecutedBlock(lastExecutionResultHeader, rootHash) + if err != nil { + return err + } + highestFinalBlockNonce := mp.forkDetector.GetHighestFinalBlockNonce() saveMetricsForCommitMetachainBlock(mp.appStatusHandler, header, headerHash, mp.nodesCoordinator, highestFinalBlockNonce, mp.managedPeersHolder) @@ -1400,7 +1432,7 @@ func (mp *metaProcessor) CommitBlock( args := bootStorerDataArgs{ headerInfo: headerInfo, - round: header.Round, + round: header.GetRound(), lastSelfNotarizedHeaders: mp.getLastSelfNotarizedHeaders(), nodesCoordinatorConfigKey: nodesCoordinatorKey, epochStartTriggerConfigKey: epochStartKey, @@ -1408,25 +1440,40 @@ func (mp *metaProcessor) CommitBlock( highestFinalBlockNonce: highestFinalBlockNonce, } + // TODO adjust this method if needed for Supernova mp.prepareDataForBootStorer(args) - mp.blockSizeThrottler.Succeed(header.Round) + mp.blockSizeThrottler.Succeed(header.GetRound()) mp.displayPoolsInfo() - errNotCritical = mp.removeTxsFromPools(header, body) + err = mp.saveExecutedData(header) + if err != nil { + return err + } + + errNotCritical = mp.removeTxsFromPools(headerHash, header, body) if errNotCritical != nil { log.Debug("removeTxsFromPools", "error", errNotCritical.Error()) } + errNotCritical = mp.cleanPostProcessCache(header) + if errNotCritical != nil { + log.Debug("cleanPostProcessCache", "error", errNotCritical.Error()) + } + mp.cleanupPools(headerHandler) mp.blockProcessingCutoffHandler.HandlePauseCutoff(header) + if header.IsHeaderV3() && header.IsStartOfEpochBlock() { + mp.saveEpochStartEconomicsMetrics(header) + } + return nil } -func (mp *metaProcessor) computeFinalMetaBlock(metaBlock *block.MetaBlock, metaBlockHash []byte) (data.MetaHeaderHandler, []byte) { +func (mp *metaProcessor) computeFinalMetaBlock(metaBlock data.MetaHeaderHandler, metaBlockHash []byte) (data.MetaHeaderHandler, []byte) { lastHeader := mp.blockChain.GetCurrentBlockHeader() lastMetaBlock, ok := lastHeader.(data.MetaHeaderHandler) if !ok { @@ -1458,20 +1505,16 @@ func (mp *metaProcessor) computeFinalMetaBlock(metaBlock *block.MetaBlock, metaB return finalMetaBlock, finalMetaBlockHash } -func (mp *metaProcessor) updateCrossShardInfo(metaBlock *block.MetaBlock) ([]string, error) { - mp.hdrsForCurrBlock.mutHdrsForBlock.RLock() - defer mp.hdrsForCurrBlock.mutHdrsForBlock.RUnlock() - +func (mp *metaProcessor) updateCrossShardInfo(metaHeader data.MetaHeaderHandler) ([]string, error) { notarizedHeadersHashes := make([]string, 0) - for i := 0; i < len(metaBlock.ShardInfo); i++ { - shardHeaderHash := metaBlock.ShardInfo[i].HeaderHash - headerInfo, ok := mp.hdrsForCurrBlock.hdrHashAndInfo[string(shardHeaderHash)] - if !ok { + for _, shardData := range getShardHeadersReferencedByMeta(metaHeader) { + header, err := mp.getHeaderFromHash(metaHeader.IsHeaderV3(), shardData.GetHeaderHash(), shardData.GetShardID()) + if err != nil { return nil, fmt.Errorf("%w : updateCrossShardInfo shardHeaderHash = %s", - process.ErrMissingHeader, logger.DisplayByteSlice(shardHeaderHash)) + err, logger.DisplayByteSlice(shardData.GetHeaderHash())) } - shardHeader, ok := headerInfo.hdr.(data.ShardHeaderHandler) + shardHeader, ok := header.(data.ShardHeaderHandler) if !ok { return nil, process.ErrWrongTypeAssertion } @@ -1483,9 +1526,9 @@ func (mp *metaProcessor) updateCrossShardInfo(metaBlock *block.MetaBlock) ([]str return nil, err } - notarizedHeadersHashes = append(notarizedHeadersHashes, hex.EncodeToString(shardHeaderHash)) + notarizedHeadersHashes = append(notarizedHeadersHashes, hex.EncodeToString(shardData.GetHeaderHash())) - mp.saveShardHeader(shardHeader, shardHeaderHash, marshalizedShardHeader) + mp.saveShardHeader(shardHeader, shardData.GetHeaderHash(), marshalizedShardHeader) } mp.saveMetricCrossCheckBlockHeight() @@ -1521,13 +1564,34 @@ func (mp *metaProcessor) displayPoolsInfo() { mp.displayMiniBlocksPool() } +func (mp *metaProcessor) getRootHashAndValidatorRootHash(metaBlock data.MetaHeaderHandler) ([]byte, []byte, error) { + if !metaBlock.IsHeaderV3() { + return metaBlock.GetRootHash(), metaBlock.GetValidatorStatsRootHash(), nil + } + + execResults := metaBlock.GetLastExecutionResultHandler() + baseMetaExecRes, ok := execResults.(data.LastMetaExecutionResultHandler) + if !ok { + return nil, nil, process.ErrWrongTypeAssertion + } + execRes := baseMetaExecRes.GetExecutionResultHandler() + + return execRes.GetRootHash(), execRes.GetValidatorStatsRootHash(), nil +} + func (mp *metaProcessor) updateState(metaBlock data.MetaHeaderHandler, metaBlockHash []byte) { if check.IfNil(metaBlock) { log.Debug("updateState nil header") return } - mp.validatorStatisticsProcessor.SetLastFinalizedRootHash(metaBlock.GetValidatorStatsRootHash()) + rootHash, validatorRootHash, err := mp.getRootHashAndValidatorRootHash(metaBlock) + if err != nil { + log.Warn("could not get root hashes from meta header", "error", err.Error()) + return + } + + mp.validatorStatisticsProcessor.SetLastFinalizedRootHash(validatorRootHash) prevMetaBlockHash := metaBlock.GetPrevHash() prevMetaBlock, errNotCritical := process.GetMetaHeader( @@ -1542,38 +1606,43 @@ func (mp *metaProcessor) updateState(metaBlock data.MetaHeaderHandler, metaBlock } if metaBlock.IsStartOfEpochBlock() { + prevBlockRootHash, _, err := mp.getRootHashAndValidatorRootHash(prevMetaBlock) + if err != nil { + log.Warn("could not get root hashes from meta header", "error", err.Error()) + return + } + log.Debug("trie snapshot", - "rootHash", metaBlock.GetRootHash(), - "prevRootHash", prevMetaBlock.GetRootHash(), - "validatorStatsRootHash", metaBlock.GetValidatorStatsRootHash()) - mp.accountsDB[state.UserAccountsState].SnapshotState(metaBlock.GetRootHash(), metaBlock.GetEpoch()) - mp.accountsDB[state.PeerAccountsState].SnapshotState(metaBlock.GetValidatorStatsRootHash(), metaBlock.GetEpoch()) + "rootHash", rootHash, + "prevRootHash", prevBlockRootHash, + "validatorStatsRootHash", validatorRootHash) + mp.accountsDB[state.UserAccountsState].SnapshotState(rootHash, metaBlock.GetEpoch()) + mp.accountsDB[state.PeerAccountsState].SnapshotState(validatorRootHash, metaBlock.GetEpoch()) go func() { - metaBlock, ok := metaBlock.(*block.MetaBlock) - if !ok { - log.Warn("cannot commit Trie Epoch Root Hash: metaBlock is not *block.MetaBlock") - return - } - err := mp.commitTrieEpochRootHashIfNeeded(metaBlock, metaBlock.GetRootHash()) + err := mp.commitTrieEpochRootHashIfNeeded(metaBlock, rootHash) if err != nil { - log.Warn("couldn't commit trie checkpoint", "epoch", metaBlock.Epoch, "error", err) + log.Warn("couldn't commit trie checkpoint", "epoch", metaBlock.GetEpoch(), "error", err) } }() } - mp.updateStateStorage( - metaBlock, - metaBlock.GetRootHash(), - prevMetaBlock.GetRootHash(), - mp.accountsDB[state.UserAccountsState], - ) + if !metaBlock.IsHeaderV3() { + mp.updateStateStorage( + metaBlock.GetNonce(), + metaBlock.GetRootHash(), + prevMetaBlock.GetRootHash(), + mp.accountsDB[state.UserAccountsState], + ) - mp.updateStateStorage( - metaBlock, - metaBlock.GetValidatorStatsRootHash(), - prevMetaBlock.GetValidatorStatsRootHash(), - mp.accountsDB[state.PeerAccountsState], - ) + mp.updateStateStorage( + metaBlock.GetNonce(), + metaBlock.GetValidatorStatsRootHash(), + prevMetaBlock.GetValidatorStatsRootHash(), + mp.accountsDB[state.PeerAccountsState], + ) + } + + // for header v3, trie prnning is triggered in async mode from headers executor outportFinalizedHeaderHash := metaBlockHash if !common.IsFlagEnabledAfterEpochsStartBlock(metaBlock, mp.enableEpochsHandler, common.AndromedaFlag) { @@ -1581,43 +1650,215 @@ func (mp *metaProcessor) updateState(metaBlock data.MetaHeaderHandler, metaBlock } mp.setFinalizedHeaderHashInIndexer(outportFinalizedHeaderHash) - mp.blockChain.SetFinalBlockInfo(metaBlock.GetNonce(), metaBlockHash, metaBlock.GetRootHash()) + mp.blockChain.SetFinalBlockInfo(metaBlock.GetNonce(), metaBlockHash, rootHash) } -func (mp *metaProcessor) getLastSelfNotarizedHeaderByShard( - metaBlock *block.MetaBlock, - shardID uint32, -) (data.HeaderHandler, []byte) { - - lastNotarizedMetaHeader, lastNotarizedMetaHeaderHash, err := mp.blockTracker.GetLastSelfNotarizedHeader(shardID) - if err != nil { - log.Warn("getLastSelfNotarizedHeaderByShard.GetLastSelfNotarizedHeader", - "shard", shardID, - "error", err.Error()) - return nil, nil +func (mp *metaProcessor) pruneTrieHeaderV3( + metaBlock data.HeaderHandler, +) { + accountsDb := mp.accountsDB[state.UserAccountsState] + peerAccountsDb := mp.accountsDB[state.PeerAccountsState] + if !accountsDb.IsPruningEnabled() && !peerAccountsDb.IsPruningEnabled() { + return } - maxNotarizedNonce := lastNotarizedMetaHeader.GetNonce() - for _, shardData := range metaBlock.ShardInfo { - if shardData.ShardID != shardID { - continue - } - - mp.hdrsForCurrBlock.mutHdrsForBlock.RLock() - headerInfo, ok := mp.hdrsForCurrBlock.hdrHashAndInfo[string(shardData.HeaderHash)] - mp.hdrsForCurrBlock.mutHdrsForBlock.RUnlock() + prevMetaBlockHash := metaBlock.GetPrevHash() + execResults := metaBlock.GetExecutionResultsHandlers() + for i := range execResults { + currentExecRes, ok := execResults[i].(data.BaseMetaExecutionResultHandler) if !ok { - log.Debug("getLastSelfNotarizedHeaderByShard", - "error", process.ErrMissingHeader, - "hash", shardData.HeaderHash) + log.Warn("failed to assert current execution result for pruning", + "index", i, + "currentExecResType", fmt.Sprintf("%T", execResults[i])) + continue + } + prevExecRes, err := mp.getPreviousExecutionResult(i, execResults, prevMetaBlockHash) + if err != nil { + log.Warn("failed to get previous execution result for pruning", + "err", err, + "index", i, + "currentExecResHeaderHash", currentExecRes.GetHeaderHash()) + continue + } + + currentRootHash := currentExecRes.GetRootHash() + prevRootHash := prevExecRes.GetRootHash() + log.Trace("pruneUserTrieHeaderV3", + "currentRootHash", currentRootHash, + "prevRootHash", prevRootHash, + ) + + mp.updateStateStorage( + currentExecRes.GetHeaderNonce(), + currentRootHash, + prevRootHash, + accountsDb, + ) + + currentValidatorRootHash := currentExecRes.GetValidatorStatsRootHash() + prevValidatorRootHash := prevExecRes.GetValidatorStatsRootHash() + log.Trace("prunePeerTrieHeaderV3", + "currentValidatorRootHash", currentValidatorRootHash, + "prevValidatorRootHash", prevValidatorRootHash, + ) + + mp.updateStateStorage( + currentExecRes.GetHeaderNonce(), + currentValidatorRootHash, + prevValidatorRootHash, + peerAccountsDb, + ) + } +} + +func (mp *metaProcessor) resetPruning() { + accountsDb := mp.accountsDB[state.UserAccountsState] + if accountsDb.IsPruningEnabled() { + accountsDb.ResetPruning() + } + + peerAccountsDb := mp.accountsDB[state.PeerAccountsState] + if peerAccountsDb.IsPruningEnabled() { + peerAccountsDb.ResetPruning() + } +} + +func (mp *metaProcessor) cancelPruneForDismissedExecutionResults(batches []executionTrack.DismissedBatch) { + accountsDb := mp.accountsDB[state.UserAccountsState] + peerAccountsDb := mp.accountsDB[state.PeerAccountsState] + userPruningEnabled := accountsDb.IsPruningEnabled() + peerPruningEnabled := peerAccountsDb.IsPruningEnabled() + if !userPruningEnabled && !peerPruningEnabled { + return + } + + for _, batch := range batches { + mp.cancelPruneForDismissedBatch(accountsDb, peerAccountsDb, batch, userPruningEnabled, peerPruningEnabled) + } +} + +func (mp *metaProcessor) cancelPruneForDismissedBatch( + accountsDb state.AccountsAdapter, + peerAccountsDb state.AccountsAdapter, + batch executionTrack.DismissedBatch, + userPruningEnabled bool, + peerPruningEnabled bool, +) { + if batch.AnchorResult == nil { + return + } + + prevUserRootHash := batch.AnchorResult.GetRootHash() + prevValidatorRootHash := mp.extractValidatorStatsRootHash(batch.AnchorResult, peerPruningEnabled, "anchor") + + for _, result := range batch.Results { + currentUserRootHash := result.GetRootHash() + currentValidatorRootHash := mp.extractValidatorStatsRootHash(result, peerPruningEnabled, "result") + + if userPruningEnabled { + cancelPruneForRootHashTransition(accountsDb, prevUserRootHash, currentUserRootHash) + } + if peerPruningEnabled { + cancelPruneForRootHashTransition(peerAccountsDb, prevValidatorRootHash, currentValidatorRootHash) + } + + prevUserRootHash = currentUserRootHash + prevValidatorRootHash = currentValidatorRootHash + } +} + +func (mp *metaProcessor) extractValidatorStatsRootHash( + result data.BaseExecutionResultHandler, + peerPruningEnabled bool, + context string, +) []byte { + metaResult, ok := result.(data.BaseMetaExecutionResultHandler) + if ok { + return metaResult.GetValidatorStatsRootHash() + } + if peerPruningEnabled { + log.Warn("cancelPruneForDismissedExecutionResults: " + context + " does not implement BaseMetaExecutionResultHandler") + } + return nil +} + +func (mp *metaProcessor) getPreviousExecutionResult( + index int, + executionResultsHandlers []data.BaseExecutionResultHandler, + prevMetaBlockHash []byte, +) (data.BaseMetaExecutionResultHandler, error) { + if index > 0 { + metaExecRes, ok := executionResultsHandlers[index-1].(data.BaseMetaExecutionResultHandler) + if !ok { + return nil, process.ErrWrongTypeAssertion + } + return metaExecRes, nil + } + + prevMetaBlock, err := process.GetMetaHeader(prevMetaBlockHash, mp.dataPool.Headers(), mp.marshalizer, mp.store) + if err != nil { + return nil, err + } + + if prevMetaBlock.IsHeaderV3() { + lastExecRes := prevMetaBlock.GetLastExecutionResultHandler() + if check.IfNil(lastExecRes) { + return nil, fmt.Errorf("previous meta block has nil last execution result") + } + + lastMetaExecRes, ok := lastExecRes.(data.LastMetaExecutionResultHandler) + if !ok { + return nil, process.ErrWrongTypeAssertion + } + + return lastMetaExecRes.GetExecutionResultHandler(), nil + } + + lastExecRes, err := common.CreateLastExecutionResultFromPrevHeader(prevMetaBlock, prevMetaBlockHash) + if err != nil { + return nil, err + } + + lastMetaExecRes, ok := lastExecRes.(data.LastMetaExecutionResultHandler) + if !ok { + return nil, process.ErrWrongTypeAssertion + } + + return lastMetaExecRes.GetExecutionResultHandler(), nil +} + +func (mp *metaProcessor) getLastSelfNotarizedHeaderByShard( + metaHeader data.MetaHeaderHandler, + shardID uint32, +) (data.HeaderHandler, []byte) { + + lastNotarizedMetaHeader, lastNotarizedMetaHeaderHash, err := mp.blockTracker.GetLastSelfNotarizedHeader(shardID) + if err != nil { + log.Warn("getLastSelfNotarizedHeaderByShard.GetLastSelfNotarizedHeader", + "shard", shardID, + "error", err.Error()) + return nil, nil + } + + maxNotarizedNonce := lastNotarizedMetaHeader.GetNonce() + for _, shardData := range metaHeader.GetShardInfoHandlers() { + if shardData.GetShardID() != shardID { + continue + } + + header, err := mp.getHeaderFromHash(metaHeader.IsHeaderV3(), shardData.GetHeaderHash(), shardData.GetShardID()) + if err != nil { + log.Debug("getLastSelfNotarizedHeaderByShard", + "error", err.Error(), + "hash", shardData.GetHeaderHash()) continue } - shardHeader, ok := headerInfo.hdr.(data.ShardHeaderHandler) + shardHeader, ok := header.(data.ShardHeaderHandler) if !ok { log.Debug("getLastSelfNotarizedHeaderByShard", "error", process.ErrWrongTypeAssertion, - "hash", shardData.HeaderHash) + "hash", shardData.GetHeaderHash()) continue } @@ -1629,12 +1870,12 @@ func (mp *metaProcessor) getLastSelfNotarizedHeaderByShard( mp.store, ) if errGet != nil { - log.Trace("getLastSelfNotarizedHeaderByShard.GetMetaHeader", "error", errGet.Error()) + log.Debug("getLastSelfNotarizedHeaderByShard.GetMetaHeader", "error", errGet.Error()) continue } - if metaHeader.Nonce > maxNotarizedNonce { - maxNotarizedNonce = metaHeader.Nonce + if metaHeader.GetNonce() > maxNotarizedNonce { + maxNotarizedNonce = metaHeader.GetNonce() lastNotarizedMetaHeader = metaHeader lastNotarizedMetaHeaderHash = metaHash } @@ -1655,7 +1896,7 @@ func (mp *metaProcessor) getLastSelfNotarizedHeaderByShard( } // getRewardsTxs must be called before method commitEpoch start because when commit is done rewards txs are removed from pool and saved in storage -func (mp *metaProcessor) getRewardsTxs(header *block.MetaBlock, body *block.Body) (rewardsTx map[string]data.TransactionHandler) { +func (mp *metaProcessor) getRewardsTxs(header data.MetaHeaderHandler, body *block.Body) (rewardsTx map[string]data.TransactionHandler) { if !mp.outportHandler.HasDrivers() { return } @@ -1667,18 +1908,58 @@ func (mp *metaProcessor) getRewardsTxs(header *block.MetaBlock, body *block.Body return rewardsTx } -func (mp *metaProcessor) commitEpochStart(header *block.MetaBlock, body *block.Body) { +func (mp *metaProcessor) commitEpochStart(header data.MetaHeaderHandler, body *block.Body) error { if header.IsStartOfEpochBlock() { - mp.epochStartTrigger.SetProcessed(header, body) - go mp.epochRewardsCreator.SaveBlockDataToStorage(header, body) - go mp.validatorInfoCreator.SaveBlockDataToStorage(header, body) + processedBody, err := mp.prepareEpochStartBodyForTrigger(header, body) + if err != nil { + return err + } + + mp.epochStartTrigger.SetProcessed(header, processedBody) + mp.epochStartTrigger.SetEpochChangeProposed(false) + go mp.epochRewardsCreator.SaveBlockDataToStorage(header, processedBody) + go mp.validatorInfoCreator.SaveBlockDataToStorage(header, processedBody) } else { + if header.IsEpochChangeProposed() { + mp.epochStartTrigger.SetEpochChangeProposed(true) + } + currentHeader := mp.blockChain.GetCurrentBlockHeader() if !check.IfNil(currentHeader) && currentHeader.IsStartOfEpochBlock() { mp.epochStartTrigger.SetFinalityAttestingRound(header.GetRound()) mp.nodesCoordinator.ShuffleOutForEpoch(currentHeader.GetEpoch()) } } + + return nil +} + +func (mp *metaProcessor) prepareEpochStartBodyForTrigger(header data.MetaHeaderHandler, body *block.Body) (*block.Body, error) { + if !header.IsHeaderV3() { + return body, nil + } + + allMiniBlocks := make([]*block.MiniBlock, 0) + for _, execResult := range header.GetExecutionResultsHandlers() { + metaExecRes, castOk := execResult.(data.MetaExecutionResultHandler) + if !castOk { + return nil, fmt.Errorf("%w in prepareEpochStartBodyForTrigger for metaExecRes", process.ErrWrongTypeAssertion) + } + + retrievedObj, found := mp.dataPool.ExecutedMiniBlocks().Get(metaExecRes.GetHeaderHash()) + if !found { + return nil, fmt.Errorf("%w in prepareEpochStartBodyForTrigger for key: %s", trie.ErrKeyNotFound, hex.EncodeToString(metaExecRes.GetHeaderHash())) + } + + currMBs, castOk := retrievedObj.([]*block.MiniBlock) + if !castOk { + return nil, fmt.Errorf("%w in prepareEpochStartBodyForTrigger for marshalledMbs", process.ErrWrongTypeAssertion) + } + + allMiniBlocks = append(allMiniBlocks, currMBs...) + } + + return &block.Body{MiniBlocks: allMiniBlocks}, nil } // RevertStateToBlock recreates the state tries to the root hashes indicated by the provided root hash and header @@ -1764,7 +2045,7 @@ func (mp *metaProcessor) saveMetricCrossCheckBlockHeight() { mp.appStatusHandler.SetStringValue(common.MetricCrossCheckBlockHeight, crossCheckBlockHeight) } -func (mp *metaProcessor) saveLastNotarizedHeader(header *block.MetaBlock) error { +func (mp *metaProcessor) saveLastNotarizedHeader(metaHeader data.MetaHeaderHandler) error { lastCrossNotarizedHeaderForShard := make(map[uint32]*hashAndHdr, mp.shardCoordinator.NumberOfShards()) for shardID := uint32(0); shardID < mp.shardCoordinator.NumberOfShards(); shardID++ { lastCrossNotarizedHeader, lastCrossNotarizedHeaderHash, err := mp.blockTracker.GetLastCrossNotarizedHeader(shardID) @@ -1775,27 +2056,22 @@ func (mp *metaProcessor) saveLastNotarizedHeader(header *block.MetaBlock) error lastCrossNotarizedHeaderForShard[shardID] = &hashAndHdr{hdr: lastCrossNotarizedHeader, hash: lastCrossNotarizedHeaderHash} } - mp.hdrsForCurrBlock.mutHdrsForBlock.RLock() - for i := 0; i < len(header.ShardInfo); i++ { - shardHeaderHash := header.ShardInfo[i].HeaderHash - headerInfo, ok := mp.hdrsForCurrBlock.hdrHashAndInfo[string(shardHeaderHash)] - if !ok { - mp.hdrsForCurrBlock.mutHdrsForBlock.RUnlock() + for _, shardData := range getShardHeadersReferencedByMeta(metaHeader) { + header, err := mp.getHeaderFromHash(metaHeader.IsHeaderV3(), shardData.GetHeaderHash(), shardData.GetShardID()) + if err != nil { return fmt.Errorf("%w : saveLastNotarizedHeader shardHeaderHash = %s", - process.ErrMissingHeader, logger.DisplayByteSlice(shardHeaderHash)) + err, logger.DisplayByteSlice(shardData.GetHeaderHash())) } - shardHeader, ok := headerInfo.hdr.(data.ShardHeaderHandler) + shardHeader, ok := header.(data.ShardHeaderHandler) if !ok { - mp.hdrsForCurrBlock.mutHdrsForBlock.RUnlock() return process.ErrWrongTypeAssertion } if lastCrossNotarizedHeaderForShard[shardHeader.GetShardID()].hdr.GetNonce() < shardHeader.GetNonce() { - lastCrossNotarizedHeaderForShard[shardHeader.GetShardID()] = &hashAndHdr{hdr: shardHeader, hash: shardHeaderHash} + lastCrossNotarizedHeaderForShard[shardHeader.GetShardID()] = &hashAndHdr{hdr: shardHeader, hash: shardData.GetHeaderHash()} } } - mp.hdrsForCurrBlock.mutHdrsForBlock.RUnlock() for shardID := uint32(0); shardID < mp.shardCoordinator.NumberOfShards(); shardID++ { hdr := lastCrossNotarizedHeaderForShard[shardID].hdr @@ -1807,11 +2083,23 @@ func (mp *metaProcessor) saveLastNotarizedHeader(header *block.MetaBlock) error return nil } -func (mp *metaProcessor) getLastCrossNotarizedShardHdrs() (map[uint32]data.HeaderHandler, error) { - mp.hdrsForCurrBlock.mutHdrsForBlock.Lock() - defer mp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() +func (mp *metaProcessor) getLastCrossNotarizedShardHdrsAndAddToCurrentBlock() (map[uint32]data.HeaderHandler, error) { + lastCrossNotarizedHeader, err := mp.getLastCrossNotarizedShardHeaders() + if err != nil { + return nil, err + } + mp.addHeaders(lastCrossNotarizedHeader) + + headers := make(map[uint32]data.HeaderHandler, len(lastCrossNotarizedHeader)) + for k, v := range lastCrossNotarizedHeader { + headers[k] = v.Header + } + + return headers, nil +} - lastCrossNotarizedHeader := make(map[uint32]data.HeaderHandler, mp.shardCoordinator.NumberOfShards()) +func (mp *metaProcessor) getLastCrossNotarizedShardHeaders() (map[uint32]ShardHeaderInfo, error) { + lastCrossNotarizedHeader := make(map[uint32]ShardHeaderInfo, mp.shardCoordinator.NumberOfShards()) for shardID := uint32(0); shardID < mp.shardCoordinator.NumberOfShards(); shardID++ { lastCrossNotarizedHeaderForShard, hash, err := mp.blockTracker.GetLastCrossNotarizedHeader(shardID) if err != nil { @@ -1819,21 +2107,40 @@ func (mp *metaProcessor) getLastCrossNotarizedShardHdrs() (map[uint32]data.Heade } log.Debug("lastCrossNotarizedHeader for shard", "shardID", shardID, "hash", hash) - lastCrossNotarizedHeader[shardID] = lastCrossNotarizedHeaderForShard usedInBlock := mp.isGenesisShardBlockAndFirstMeta(lastCrossNotarizedHeaderForShard.GetNonce()) - mp.hdrsForCurrBlock.hdrHashAndInfo[string(hash)] = &hdrInfo{ - hdr: lastCrossNotarizedHeaderForShard, - usedInBlock: usedInBlock, + lastCrossNotarizedHeader[shardID] = ShardHeaderInfo{ + Header: lastCrossNotarizedHeaderForShard, + Hash: hash, + UsedInBlock: usedInBlock, } } return lastCrossNotarizedHeader, nil } +func (mp *metaProcessor) addHeaders(shardHeadersInfo map[uint32]ShardHeaderInfo) { + for shardID := uint32(0); shardID < mp.shardCoordinator.NumberOfShards(); shardID++ { + shardHdrInfo, ok := shardHeadersInfo[shardID] + if !ok { + continue + } + mp.addHeader(shardHdrInfo.Hash, shardHdrInfo.Header, shardHdrInfo.UsedInBlock) + } +} + +func (mp *metaProcessor) addHeader(hash []byte, header data.HeaderHandler, usedInBlock bool) { + if usedInBlock { + mp.hdrsForCurrBlock.AddHeaderUsedInBlock(string(hash), header) + return + } + + mp.hdrsForCurrBlock.AddHeaderNotUsedInBlock(string(hash), header) +} + // check if shard headers were signed and constructed correctly and returns headers which has to be // checked for finality -func (mp *metaProcessor) checkShardHeadersValidity(metaHdr *block.MetaBlock) (map[uint32]data.HeaderHandler, error) { - lastCrossNotarizedHeader, err := mp.getLastCrossNotarizedShardHdrs() +func (mp *metaProcessor) checkShardHeadersValidity(metaHdr data.MetaHeaderHandler) (map[uint32]data.HeaderHandler, error) { + lastCrossNotarizedHeader, err := mp.getLastCrossNotarizedShardHdrsAndAddToCurrentBlock() if err != nil { return nil, err } @@ -1862,16 +2169,13 @@ func (mp *metaProcessor) checkShardHeadersValidity(metaHdr *block.MetaBlock) (ma } } - mp.hdrsForCurrBlock.mutHdrsForBlock.Lock() - defer mp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() - - for _, shardData := range metaHdr.ShardInfo { - headerInfo, ok := mp.hdrsForCurrBlock.hdrHashAndInfo[string(shardData.HeaderHash)] + for _, shardData := range metaHdr.GetShardInfoHandlers() { + headerInfo, ok := mp.hdrsForCurrBlock.GetHeaderInfo(string(shardData.GetHeaderHash())) if !ok { return nil, fmt.Errorf("%w : checkShardHeadersValidity -> hash not found %s ", - process.ErrHeaderShardDataMismatch, hex.EncodeToString(shardData.HeaderHash)) + process.ErrHeaderShardDataMismatch, hex.EncodeToString(shardData.GetHeaderHash())) } - actualHdr := headerInfo.hdr + actualHdr := headerInfo.GetHeader() shardHdr, ok := actualHdr.(data.ShardHeaderHandler) if !ok { return nil, process.ErrWrongTypeAssertion @@ -1879,24 +2183,24 @@ func (mp *metaProcessor) checkShardHeadersValidity(metaHdr *block.MetaBlock) (ma finalMiniBlockHeaders := mp.getFinalMiniBlockHeaders(shardHdr.GetMiniBlockHeaderHandlers()) - if len(shardData.ShardMiniBlockHeaders) != len(finalMiniBlockHeaders) { + if len(shardData.GetShardMiniBlockHeaderHandlers()) != len(finalMiniBlockHeaders) { return nil, process.ErrHeaderShardDataMismatch } - if shardData.AccumulatedFees.Cmp(shardHdr.GetAccumulatedFees()) != 0 { + if shardData.GetAccumulatedFees().Cmp(shardHdr.GetAccumulatedFees()) != 0 { return nil, process.ErrAccumulatedFeesDoNotMatch } - if shardData.DeveloperFees.Cmp(shardHdr.GetDeveloperFees()) != 0 { + if shardData.GetDeveloperFees().Cmp(shardHdr.GetDeveloperFees()) != 0 { return nil, process.ErrDeveloperFeesDoNotMatch } if mp.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, shardHdr.GetEpoch()) { - if shardData.Epoch != shardHdr.GetEpoch() { + if shardData.GetEpoch() != shardHdr.GetEpoch() { return nil, process.ErrEpochMismatch } } mapMiniBlockHeadersInMetaBlock := make(map[string]struct{}) - for _, shardMiniBlockHdr := range shardData.ShardMiniBlockHeaders { - mapMiniBlockHeadersInMetaBlock[string(shardMiniBlockHdr.Hash)] = struct{}{} + for _, shardMiniBlockHdr := range shardData.GetShardMiniBlockHeaderHandlers() { + mapMiniBlockHeadersInMetaBlock[string(shardMiniBlockHdr.GetHash())] = struct{}{} } for _, actualMiniBlockHdr := range finalMiniBlockHeaders { @@ -2016,255 +2320,42 @@ func (mp *metaProcessor) checkShardHeaderFinalityBasedOnProofs(shardHdr data.Hea return true, nil } -// receivedShardHeader is a call back function which is called when a new header -// is added in the headers pool -func (mp *metaProcessor) receivedShardHeader(headerHandler data.HeaderHandler, shardHeaderHash []byte) { - shardHeader, ok := headerHandler.(data.ShardHeaderHandler) - if !ok { - return - } - - log.Trace("received shard header from network", - "shard", shardHeader.GetShardID(), - "round", shardHeader.GetRound(), - "nonce", shardHeader.GetNonce(), - "hash", shardHeaderHash, - ) - - mp.hdrsForCurrBlock.mutHdrsForBlock.Lock() - - haveMissingShardHeaders := mp.hdrsForCurrBlock.missingHdrs > 0 || mp.hdrsForCurrBlock.missingFinalityAttestingHdrs > 0 - if haveMissingShardHeaders { - hdrInfoForHash := mp.hdrsForCurrBlock.hdrHashAndInfo[string(shardHeaderHash)] - headerInfoIsNotNil := hdrInfoForHash != nil - headerIsMissing := headerInfoIsNotNil && check.IfNil(hdrInfoForHash.hdr) - hasProof := headerInfoIsNotNil && hdrInfoForHash.hasProof - hasProofRequested := headerInfoIsNotNil && hdrInfoForHash.hasProofRequested - if headerIsMissing { - hdrInfoForHash.hdr = shardHeader - mp.hdrsForCurrBlock.missingHdrs-- - - if shardHeader.GetNonce() > mp.hdrsForCurrBlock.highestHdrNonce[shardHeader.GetShardID()] { - mp.hdrsForCurrBlock.highestHdrNonce[shardHeader.GetShardID()] = shardHeader.GetNonce() - } - mp.updateLastNotarizedBlockForShard(shardHeader, shardHeaderHash) - - if !hasProof && !hasProofRequested { - mp.requestProofIfNeeded(shardHeaderHash, shardHeader) - } - } - - if mp.hdrsForCurrBlock.missingHdrs == 0 { - mp.hdrsForCurrBlock.missingFinalityAttestingHdrs = mp.requestMissingFinalityAttestingShardHeaders() - if mp.hdrsForCurrBlock.missingFinalityAttestingHdrs == 0 { - log.Debug("received all missing finality attesting shard headers") - } - } - - missingShardHdrs := mp.hdrsForCurrBlock.missingHdrs - missingFinalityAttestingShardHdrs := mp.hdrsForCurrBlock.missingFinalityAttestingHdrs - missingProofs := mp.hdrsForCurrBlock.missingProofs - mp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() - - allMissingShardHeadersReceived := missingShardHdrs == 0 && missingFinalityAttestingShardHdrs == 0 && missingProofs == 0 - if allMissingShardHeadersReceived { - mp.chRcvAllHdrs <- true - } - } else { - mp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() - } - - go mp.requestMiniBlocksIfNeeded(headerHandler) -} - -// requestMissingFinalityAttestingShardHeaders requests the headers needed to accept the current selected headers for -// processing the current block. It requests the shardBlockFinality headers greater than the highest shard header, -// for the given shard, related to the block which should be processed -// this method should be called only under the mutex protection: hdrsForCurrBlock.mutHdrsForBlock -func (mp *metaProcessor) requestMissingFinalityAttestingShardHeaders() uint32 { - missingFinalityAttestingShardHeaders := uint32(0) - - for shardId := uint32(0); shardId < mp.shardCoordinator.NumberOfShards(); shardId++ { - lastNotarizedShardHeader := mp.hdrsForCurrBlock.lastNotarizedShardHeaders[shardId] - missingFinalityAttestingHeaders := uint32(0) - if lastNotarizedShardHeader != nil && !lastNotarizedShardHeader.notarizedBasedOnProof { - missingFinalityAttestingHeaders = mp.requestMissingFinalityAttestingHeaders( - shardId, - mp.shardBlockFinality, - ) - } - missingFinalityAttestingShardHeaders += missingFinalityAttestingHeaders - } - - return missingFinalityAttestingShardHeaders -} - -func (mp *metaProcessor) requestShardHeaders(metaBlock *block.MetaBlock) (uint32, uint32, uint32) { - _ = core.EmptyChannel(mp.chRcvAllHdrs) - - if len(metaBlock.ShardInfo) == 0 { - return 0, 0, 0 - } - - return mp.computeExistingAndRequestMissingShardHeaders(metaBlock) -} - -func (mp *metaProcessor) updateLastNotarizedBlockForShard(hdr data.ShardHeaderHandler, headerHash []byte) { - lastNotarizedForShard := mp.hdrsForCurrBlock.lastNotarizedShardHeaders[hdr.GetShardID()] - if lastNotarizedForShard == nil { - lastNotarizedForShard = &lastNotarizedHeaderInfo{header: hdr} - mp.hdrsForCurrBlock.lastNotarizedShardHeaders[hdr.GetShardID()] = lastNotarizedForShard - } - - if hdr.GetNonce() >= lastNotarizedForShard.header.GetNonce() { - notarizedBasedOnProofs := mp.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, hdr.GetEpoch()) - hasProof := false - if notarizedBasedOnProofs { - hasProof = mp.proofsPool.HasProof(hdr.GetShardID(), headerHash) - } - - lastNotarizedForShard.header = hdr - lastNotarizedForShard.hash = headerHash - lastNotarizedForShard.notarizedBasedOnProof = notarizedBasedOnProofs - lastNotarizedForShard.hasProof = hasProof - } -} - -func (mp *metaProcessor) computeExistingAndRequestMissingShardHeaders(metaBlock *block.MetaBlock) (uint32, uint32, uint32) { - mp.hdrsForCurrBlock.mutHdrsForBlock.Lock() - defer mp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() - - for _, shardData := range metaBlock.ShardInfo { - if shardData.Nonce == mp.genesisNonce { - lastCrossNotarizedHeaderForShard, hash, err := mp.blockTracker.GetLastCrossNotarizedHeader(shardData.ShardID) - if err != nil { - log.Warn("computeExistingAndRequestMissingShardHeaders.GetLastCrossNotarizedHeader", "error", err.Error()) - continue - } - if !bytes.Equal(hash, shardData.HeaderHash) { - log.Warn("genesis hash mismatch", - "last notarized nonce", lastCrossNotarizedHeaderForShard.GetNonce(), - "last notarized hash", hash, - "genesis nonce", mp.genesisNonce, - "genesis hash", shardData.HeaderHash) - } - continue - } - - hdr, err := process.GetShardHeaderFromPool( - shardData.HeaderHash, - mp.dataPool.Headers()) - - if err != nil { - mp.hdrsForCurrBlock.missingHdrs++ - mp.hdrsForCurrBlock.hdrHashAndInfo[string(shardData.HeaderHash)] = &hdrInfo{ - hdr: nil, - usedInBlock: true, - hasProof: false, - hasProofRequested: false, - } - - go mp.requestHandler.RequestShardHeader(shardData.ShardID, shardData.HeaderHash) - continue - } - - mp.hdrsForCurrBlock.hdrHashAndInfo[string(shardData.HeaderHash)] = &hdrInfo{ - hdr: hdr, - usedInBlock: true, - } - - mp.requestProofIfNeeded(shardData.HeaderHash, hdr) - - if common.IsEpochChangeBlockForFlagActivation(hdr, mp.enableEpochsHandler, common.AndromedaFlag) { - continue - } - - if hdr.GetNonce() > mp.hdrsForCurrBlock.highestHdrNonce[shardData.ShardID] { - mp.hdrsForCurrBlock.highestHdrNonce[shardData.ShardID] = hdr.GetNonce() - } - - mp.updateLastNotarizedBlockForShard(hdr, shardData.HeaderHash) - } - - if mp.hdrsForCurrBlock.missingHdrs == 0 { - mp.hdrsForCurrBlock.missingFinalityAttestingHdrs = mp.requestMissingFinalityAttestingShardHeaders() - } - - return mp.hdrsForCurrBlock.missingHdrs, mp.hdrsForCurrBlock.missingFinalityAttestingHdrs, mp.hdrsForCurrBlock.missingProofs -} - -func (mp *metaProcessor) createShardInfo() ([]data.ShardDataHandler, error) { +func (mp *metaProcessor) createShardInfo(metaHdr data.MetaHeaderHandler) ([]data.ShardDataHandler, error) { var shardInfo []data.ShardDataHandler - if mp.epochStartTrigger.IsEpochStart() { + if metaHdr.IsStartOfEpochBlock() { return shardInfo, nil } - mp.hdrsForCurrBlock.mutHdrsForBlock.Lock() - defer mp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() - - for hdrHash, headerInfo := range mp.hdrsForCurrBlock.hdrHashAndInfo { - if !headerInfo.usedInBlock { + hdrHashAndInfo := mp.hdrsForCurrBlock.GetHeadersInfoMap() + headers := make([]data.ShardHeaderHandler, 0, len(hdrHashAndInfo)) + headerHashes := make([][]byte, 0, len(hdrHashAndInfo)) + for hdrHash, headerInfo := range hdrHashAndInfo { + if !headerInfo.UsedInBlock() { continue } - isBlockAfterAndromedaFlag := !check.IfNil(headerInfo.hdr) && - mp.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, headerInfo.hdr.GetEpoch()) && headerInfo.hdr.GetNonce() >= 1 - hasMissingShardHdrProof := isBlockAfterAndromedaFlag && !mp.proofsPool.HasProof(headerInfo.hdr.GetShardID(), []byte(hdrHash)) + isBlockAfterAndromedaFlag := !check.IfNil(headerInfo.GetHeader()) && + mp.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, headerInfo.GetHeader().GetEpoch()) && headerInfo.GetHeader().GetNonce() >= 1 + hasMissingShardHdrProof := isBlockAfterAndromedaFlag && !mp.proofsPool.HasProof(headerInfo.GetHeader().GetShardID(), []byte(hdrHash)) if hasMissingShardHdrProof { return nil, fmt.Errorf("%w for shard header with hash %s", process.ErrMissingHeaderProof, hex.EncodeToString([]byte(hdrHash))) } - shardHdr, ok := headerInfo.hdr.(data.ShardHeaderHandler) + shardHdr, ok := headerInfo.GetHeader().(data.ShardHeaderHandler) if !ok { return nil, process.ErrWrongTypeAssertion } - shardData := block.ShardData{} - shardData.TxCount = shardHdr.GetTxCount() - shardData.ShardID = shardHdr.GetShardID() - shardData.HeaderHash = []byte(hdrHash) - shardData.Round = shardHdr.GetRound() - shardData.PrevHash = shardHdr.GetPrevHash() - shardData.Nonce = shardHdr.GetNonce() - shardData.PrevRandSeed = shardHdr.GetPrevRandSeed() - shardData.PubKeysBitmap = shardHdr.GetPubKeysBitmap() - if mp.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, shardHdr.GetEpoch()) { - shardData.Epoch = shardHdr.GetEpoch() - } - shardData.NumPendingMiniBlocks = uint32(len(mp.pendingMiniBlocksHandler.GetPendingMiniBlocks(shardData.ShardID))) - header, _, err := mp.blockTracker.GetLastSelfNotarizedHeader(shardHdr.GetShardID()) - if err != nil { - return nil, err - } - shardData.LastIncludedMetaNonce = header.GetNonce() - shardData.AccumulatedFees = shardHdr.GetAccumulatedFees() - shardData.DeveloperFees = shardHdr.GetDeveloperFees() - - for i := 0; i < len(shardHdr.GetMiniBlockHeaderHandlers()); i++ { - if mp.enableEpochsHandler.IsFlagEnabled(common.ScheduledMiniBlocksFlag) { - miniBlockHeader := shardHdr.GetMiniBlockHeaderHandlers()[i] - if !miniBlockHeader.IsFinal() { - log.Debug("metaProcessor.createShardInfo: do not create shard data with mini block which is not final", "mb hash", miniBlockHeader.GetHash()) - continue - } - } - - shardMiniBlockHeader := block.MiniBlockHeader{} - shardMiniBlockHeader.SenderShardID = shardHdr.GetMiniBlockHeaderHandlers()[i].GetSenderShardID() - shardMiniBlockHeader.ReceiverShardID = shardHdr.GetMiniBlockHeaderHandlers()[i].GetReceiverShardID() - shardMiniBlockHeader.Hash = shardHdr.GetMiniBlockHeaderHandlers()[i].GetHash() - shardMiniBlockHeader.TxCount = shardHdr.GetMiniBlockHeaderHandlers()[i].GetTxCount() - shardMiniBlockHeader.Type = block.Type(shardHdr.GetMiniBlockHeaderHandlers()[i].GetTypeInt32()) - - shardData.ShardMiniBlockHeaders = append(shardData.ShardMiniBlockHeaders, shardMiniBlockHeader) - } + headers = append(headers, shardHdr) + headerHashes = append(headerHashes, []byte(hdrHash)) + } - shardInfo = append(shardInfo, &shardData) + shardInfo, err := mp.shardInfoCreateData.CreateShardInfoFromLegacyMeta(metaHdr, headers, headerHashes) + if err != nil { + return nil, err } - log.Debug("created shard data", - "size", len(shardInfo), - ) + log.Debug("created shard data", "size", len(shardInfo)) return shardInfo, nil } @@ -2285,30 +2376,71 @@ func (mp *metaProcessor) verifyTotalAccumulatedFeesInEpoch(metaHdr *block.MetaBl return nil } -func (mp *metaProcessor) computeAccumulatedFeesInEpoch(metaHdr data.MetaHeaderHandler) (*big.Int, *big.Int, error) { - currentlyAccumulatedFeesInEpoch := big.NewInt(0) - currentDevFeesInEpoch := big.NewInt(0) +func (mp *metaProcessor) getCurrentlyAccumulatedFees(metaHdr data.MetaHeaderHandler) (*big.Int, *big.Int, error) { + if metaHdr.IsHeaderV3() { + if metaHdr.IsEpochChangeProposed() { + return big.NewInt(0), big.NewInt(0), nil + } - lastHdr := mp.blockChain.GetCurrentBlockHeader() - if !check.IfNil(lastHdr) { - lastMeta, ok := lastHdr.(*block.MetaBlock) + lastExecResult, err := common.GetLastBaseExecutionResultHandler(metaHdr) + if err != nil { + return nil, nil, err + } + + lastMetaExecResult, ok := lastExecResult.(data.BaseMetaExecutionResultHandler) if !ok { return nil, nil, process.ErrWrongTypeAssertion } - if !lastHdr.IsStartOfEpochBlock() { - currentlyAccumulatedFeesInEpoch = big.NewInt(0).Set(lastMeta.AccumulatedFeesInEpoch) - currentDevFeesInEpoch = big.NewInt(0).Set(lastMeta.DevFeesInEpoch) - } + currentlyAccumulatedFeesInEpoch := big.NewInt(0).Set(lastMetaExecResult.GetAccumulatedFeesInEpoch()) + currentDevFeesInEpoch := big.NewInt(0).Set(lastMetaExecResult.GetDevFeesInEpoch()) + + return currentlyAccumulatedFeesInEpoch, currentDevFeesInEpoch, nil + } + + lastHdr := mp.blockChain.GetCurrentBlockHeader() + if check.IfNil(lastHdr) { + return big.NewInt(0), big.NewInt(0), nil + } + + lastMeta, ok := lastHdr.(*block.MetaBlock) + if !ok { + return nil, nil, process.ErrWrongTypeAssertion + } + + if lastHdr.IsStartOfEpochBlock() { + return big.NewInt(0), big.NewInt(0), nil + } + currentlyAccumulatedFeesInEpoch := big.NewInt(0).Set(lastMeta.AccumulatedFeesInEpoch) + currentDevFeesInEpoch := big.NewInt(0).Set(lastMeta.DevFeesInEpoch) + + return currentlyAccumulatedFeesInEpoch, currentDevFeesInEpoch, nil +} + +func (mp *metaProcessor) getHeaderAccumulatedFees(metaHdr data.MetaHeaderHandler) (*big.Int, *big.Int) { + if metaHdr.IsHeaderV3() { + gasAndFees := mp.getGasAndFees() + return gasAndFees.GetAccumulatedFees(), gasAndFees.GetDeveloperFees() } - currentlyAccumulatedFeesInEpoch.Add(currentlyAccumulatedFeesInEpoch, metaHdr.GetAccumulatedFees()) - currentDevFeesInEpoch.Add(currentDevFeesInEpoch, metaHdr.GetDeveloperFees()) + return metaHdr.GetAccumulatedFees(), metaHdr.GetDeveloperFees() +} + +func (mp *metaProcessor) computeAccumulatedFeesInEpoch(metaHdr data.MetaHeaderHandler) (*big.Int, *big.Int, error) { + currentlyAccumulatedFeesInEpoch, currentDevFeesInEpoch, err := mp.getCurrentlyAccumulatedFees(metaHdr) + if err != nil { + return nil, nil, err + } + + accumulatedFees, developerFees := mp.getHeaderAccumulatedFees(metaHdr) + + currentlyAccumulatedFeesInEpoch.Add(currentlyAccumulatedFeesInEpoch, accumulatedFees) + currentDevFeesInEpoch.Add(currentDevFeesInEpoch, developerFees) log.Debug("computeAccumulatedFeesInEpoch - meta block fees", "meta nonce", metaHdr.GetNonce(), - "accumulatedFees", metaHdr.GetAccumulatedFees().String(), - "devFees", metaHdr.GetDeveloperFees().String(), - "meta leader fees", core.GetIntTrimmedPercentageOfValue(big.NewInt(0).Sub(metaHdr.GetAccumulatedFees(), metaHdr.GetDeveloperFees()), mp.economicsData.LeaderPercentageInEpoch(metaHdr.GetEpoch())).String()) + "accumulatedFees", accumulatedFees.String(), + "devFees", developerFees.String(), + "meta leader fees", core.GetIntTrimmedPercentageOfValue(big.NewInt(0).Sub(accumulatedFees, developerFees), mp.economicsData.LeaderPercentageInEpoch(metaHdr.GetEpoch())).String()) for _, shardData := range metaHdr.GetShardInfoHandlers() { log.Debug("computeAccumulatedFeesInEpoch - adding shard data fees", @@ -2350,7 +2482,7 @@ func (mp *metaProcessor) applyBodyToHeader(metaHdr data.MetaHeaderHandler, bodyH }() sw.Start("createShardInfo") - shardInfo, err := mp.createShardInfo() + shardInfo, err := mp.createShardInfo(metaHdr) sw.Stop("createShardInfo") if err != nil { return nil, err @@ -2436,8 +2568,8 @@ func (mp *metaProcessor) applyBodyToHeader(metaHdr data.MetaHeaderHandler, bodyH } sw.Start("UpdatePeerState") - mp.prepareBlockHeaderInternalMapForValidatorProcessor() - valStatRootHash, err := mp.validatorStatisticsProcessor.UpdatePeerState(metaHdr, mp.hdrsForCurrBlock.getHdrHashMap()) + mp.prepareBlockHeaderInternalMapForValidatorProcessor(metaHdr) + valStatRootHash, err := mp.updatePeerState(metaHdr, mp.hdrsForCurrBlock.GetHeadersMap()) sw.Stop("UpdatePeerState") if err != nil { return nil, err @@ -2458,26 +2590,28 @@ func (mp *metaProcessor) applyBodyToHeader(metaHdr data.MetaHeaderHandler, bodyH return body, nil } -func (mp *metaProcessor) prepareBlockHeaderInternalMapForValidatorProcessor() { - currentBlockHeader := mp.blockChain.GetCurrentBlockHeader() - currentBlockHeaderHash := mp.blockChain.GetCurrentBlockHeaderHash() - - if check.IfNil(currentBlockHeader) { - currentBlockHeader = mp.blockChain.GetGenesisHeader() - currentBlockHeaderHash = mp.blockChain.GetGenesisHeaderHash() +func (mp *metaProcessor) prepareBlockHeaderInternalMapForValidatorProcessor(metaHeader data.MetaHeaderHandler) { + var blockHeader data.HeaderHandler + var blockHeaderHash []byte + if metaHeader.IsHeaderV3() { + blockHeader = mp.blockChain.GetLastExecutedBlockHeader() + _, blockHeaderHash, _ = mp.blockChain.GetLastExecutedBlockInfo() + } else { + blockHeader = mp.blockChain.GetCurrentBlockHeader() + blockHeaderHash = mp.blockChain.GetCurrentBlockHeaderHash() } - mp.hdrsForCurrBlock.mutHdrsForBlock.Lock() - mp.hdrsForCurrBlock.hdrHashAndInfo[string(currentBlockHeaderHash)] = &hdrInfo{ - usedInBlock: false, - hdr: currentBlockHeader, + if check.IfNil(blockHeader) { + blockHeader = mp.blockChain.GetGenesisHeader() + blockHeaderHash = mp.blockChain.GetGenesisHeaderHash() } - mp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() + + mp.hdrsForCurrBlock.AddHeaderNotUsedInBlock(string(blockHeaderHash), blockHeader) } -func (mp *metaProcessor) verifyValidatorStatisticsRootHash(header *block.MetaBlock) error { - mp.prepareBlockHeaderInternalMapForValidatorProcessor() - validatorStatsRH, err := mp.validatorStatisticsProcessor.UpdatePeerState(header, mp.hdrsForCurrBlock.getHdrHashMap()) +func (mp *metaProcessor) verifyValidatorStatisticsRootHash(header data.MetaHeaderHandler) error { + mp.prepareBlockHeaderInternalMapForValidatorProcessor(header) + validatorStatsRH, err := mp.updatePeerState(header, mp.hdrsForCurrBlock.GetHeadersMap()) if err != nil { return err } @@ -2491,20 +2625,69 @@ func (mp *metaProcessor) verifyValidatorStatisticsRootHash(header *block.MetaBlo process.ErrValidatorStatsRootHashDoesNotMatch, logger.DisplayByteSlice(validatorStatsRH), logger.DisplayByteSlice(header.GetValidatorStatsRootHash()), - header.Nonce, + header.GetNonce(), ) } return nil } -func (mp *metaProcessor) waitForBlockHeaders(waitTime time.Duration) error { - select { - case <-mp.chRcvAllHdrs: - return nil - case <-time.After(waitTime): - return process.ErrTimeIsOut +func (mp *metaProcessor) updatePeerState( + header data.MetaHeaderHandler, + cache map[string]data.HeaderHandler, +) ([]byte, error) { + if !header.IsHeaderV3() { + return mp.validatorStatisticsProcessor.UpdatePeerState(header, cache) + } + + lastExecutionResult, err := mp.getLastExecutionResult(header, cache) + if err != nil { + return nil, err + } + + return mp.validatorStatisticsProcessor.UpdatePeerStateV3(header, cache, lastExecutionResult) +} + +func (mp *metaProcessor) getLastExecutionResult( + header data.MetaHeaderHandler, + cache map[string]data.HeaderHandler, +) (data.MetaExecutionResultHandler, error) { + prevHdr, err := getPrevHdrFromCache(header, cache) + if err != nil { + return nil, err + } + + if !prevHdr.IsHeaderV3() { + // We only need these two fields for validatorStatisticsProcessor.UpdatePeerStateV3 + return &block.MetaExecutionResult{ + AccumulatedFees: prevHdr.GetAccumulatedFees(), + DeveloperFees: prevHdr.GetDeveloperFees(), + }, nil + } + + lastExecutionResult := mp.blockChain.GetLastExecutionResult() + if check.IfNil(lastExecutionResult) { + return nil, fmt.Errorf("missing last execution result in blockchain in metaProcessor.getLastExecutionResult") + } + + metaExecutionResult, castOk := lastExecutionResult.(data.MetaExecutionResultHandler) + if !castOk { + return nil, fmt.Errorf("%w in metaProcessor.getLastExecutionResult", process.ErrWrongTypeAssertion) + } + + return metaExecutionResult, nil +} + +func getPrevHdrFromCache( + header data.MetaHeaderHandler, + cache map[string]data.HeaderHandler, +) (data.HeaderHandler, error) { + prevHash := header.GetPrevHash() + prevHdr, found := cache[string(prevHash)] + if !found { + return nil, fmt.Errorf("%w in getPrevHdrFromCache", errNilPreviousHeader) } + return prevHdr, nil } // CreateNewHeader creates a new header @@ -2512,12 +2695,16 @@ func (mp *metaProcessor) CreateNewHeader(round uint64, nonce uint64) (data.Heade mp.epochStartTrigger.Update(round, nonce) epoch := mp.epochStartTrigger.Epoch() - header := mp.versionedHeaderFactory.Create(epoch) + header := mp.versionedHeaderFactory.Create(epoch, round) metaHeader, ok := header.(data.MetaHeaderHandler) if !ok { return nil, process.ErrWrongTypeAssertion } + if metaHeader.IsHeaderV3() { + return nil, process.ErrInvalidHeader + } + err := metaHeader.SetRound(round) if err != nil { return nil, err @@ -2531,32 +2718,45 @@ func (mp *metaProcessor) CreateNewHeader(round uint64, nonce uint64) (data.Heade return nil, err } - err = metaHeader.SetAccumulatedFees(big.NewInt(0)) + err = initializeFeesDataMetaHeaderIfNeeded(metaHeader) if err != nil { return nil, err } - err = metaHeader.SetAccumulatedFeesInEpoch(big.NewInt(0)) + err = mp.setHeaderVersionData(metaHeader) if err != nil { return nil, err } - err = metaHeader.SetDeveloperFees(big.NewInt(0)) + return metaHeader, nil +} + +func initializeFeesDataMetaHeaderIfNeeded(metaHeader data.MetaHeaderHandler) error { + if metaHeader.IsHeaderV3() { + return nil + } + + err := metaHeader.SetAccumulatedFees(big.NewInt(0)) if err != nil { - return nil, err + return err } - err = metaHeader.SetDevFeesInEpoch(big.NewInt(0)) + err = metaHeader.SetAccumulatedFeesInEpoch(big.NewInt(0)) if err != nil { - return nil, err + return err } - err = mp.setHeaderVersionData(metaHeader) + err = metaHeader.SetDeveloperFees(big.NewInt(0)) if err != nil { - return nil, err + return err } - return metaHeader, nil + err = metaHeader.SetDevFeesInEpoch(big.NewInt(0)) + if err != nil { + return err + } + + return nil } func (mp *metaProcessor) setHeaderVersionData(metaHeader data.MetaHeaderHandler) error { @@ -2584,10 +2784,11 @@ func (mp *metaProcessor) setHeaderVersionData(metaHeader data.MetaHeaderHandler) // MarshalizedDataToBroadcast prepares underlying data into a marshalized object according to destination func (mp *metaProcessor) MarshalizedDataToBroadcast( - hdr data.HeaderHandler, + headerHash []byte, + header data.HeaderHandler, bodyHandler data.BodyHandler, ) (map[uint32][]byte, map[string][][]byte, error) { - if check.IfNil(hdr) { + if check.IfNil(header) { return nil, nil, process.ErrNilMetaBlockHeader } if check.IfNil(bodyHandler) { @@ -2599,33 +2800,33 @@ func (mp *metaProcessor) MarshalizedDataToBroadcast( return nil, nil, process.ErrWrongTypeAssertion } + bodyToBroadcast, miniBlocksMapToBroadcast, err := mp.getFinalMiniBlocks(headerHash, header, body) + if err != nil { + return nil, nil, err + } + var mrsTxs map[string][][]byte - if hdr.IsStartOfEpochBlock() { - mrsTxs = mp.getAllMarshalledTxs(body) + if header.IsStartOfEpochBlock() { + mrsTxs = mp.getAllMarshalledTxs(bodyToBroadcast) } else { - mrsTxs = mp.txCoordinator.CreateMarshalizedData(body) + mrsTxs = mp.txCoordinator.CreateMarshalledDataForHeader(header, bodyToBroadcast, miniBlocksMapToBroadcast) } - bodies := make(map[uint32]block.MiniBlockSlice) - for _, miniBlock := range body.MiniBlocks { - if miniBlock.SenderShardID != mp.shardCoordinator.SelfId() || - miniBlock.ReceiverShardID == mp.shardCoordinator.SelfId() { - continue - } - bodies[miniBlock.ReceiverShardID] = append(bodies[miniBlock.ReceiverShardID], miniBlock) - } + mrsData := mp.marshalledBodyToBroadcast(bodyToBroadcast) - mrsData := make(map[uint32][]byte, len(bodies)) - for shardId, subsetBlockBody := range bodies { - buff, err := mp.marshalizer.Marshal(&block.Body{MiniBlocks: subsetBlockBody}) - if err != nil { - log.Error("metaProcessor.MarshalizedDataToBroadcast.Marshal", "error", err.Error()) - continue - } - mrsData[shardId] = buff + return mrsData, mrsTxs, nil +} + +func (mp *metaProcessor) getFinalMiniBlocks(headerHash []byte, header data.HeaderHandler, body *block.Body) (*block.Body, map[string]block.MiniBlockSlice, error) { + if header.IsHeaderV3() { + return mp.getFinalMiniBlocksFromExecutionResults(header) } - return mrsData, mrsTxs, nil + return body, + map[string]block.MiniBlockSlice{ + string(headerHash): body.MiniBlocks, + }, + nil } func (mp *metaProcessor) getAllMarshalledTxs(body *block.Body) map[string][][]byte { @@ -2666,29 +2867,32 @@ func (mp *metaProcessor) IsInterfaceNil() bool { // GetBlockBodyFromPool returns block body from pool for a given header func (mp *metaProcessor) GetBlockBodyFromPool(headerHandler data.HeaderHandler) (data.BodyHandler, error) { - metaBlock, ok := headerHandler.(*block.MetaBlock) + metaBlock, ok := headerHandler.(data.MetaHeaderHandler) if !ok { return nil, process.ErrWrongTypeAssertion } - miniBlocksPool := mp.dataPool.MiniBlocks() - var miniBlocks block.MiniBlockSlice + miniBlockHeaderHandlers, err := common.GetMiniBlockHeadersFromExecResult(metaBlock) + if err != nil { + return nil, err + } - for _, mbHeader := range metaBlock.MiniBlockHeaders { - obj, hashInPool := miniBlocksPool.Get(mbHeader.Hash) - if !hashInPool { - continue - } + return mp.getBlockBodyFromPool(metaBlock, miniBlockHeaderHandlers) +} - miniBlock, typeOk := obj.(*block.MiniBlock) - if !typeOk { - return nil, process.ErrWrongTypeAssertion - } +// GetProposedAndExecutedMiniBlockHeaders returns block body from pool with proposed and executed miniblocks +func (mp *metaProcessor) GetProposedAndExecutedMiniBlockHeaders(headerHandler data.HeaderHandler) (data.BodyHandler, error) { + metaBlock, ok := headerHandler.(data.MetaHeaderHandler) + if !ok { + return nil, process.ErrWrongTypeAssertion + } - miniBlocks = append(miniBlocks, miniBlock) + miniBlockHeaderHandlers, err := getProposedAndExecutedMiniBlockHeaders(metaBlock) + if err != nil { + return nil, err } - return &block.Body{MiniBlocks: miniBlocks}, nil + return mp.getBlockBodyFromPool(metaBlock, miniBlockHeaderHandlers) } func (mp *metaProcessor) getPendingMiniBlocks() []bootstrapStorage.PendingMiniBlocksInfo { @@ -2713,7 +2917,7 @@ func (mp *metaProcessor) removeStartOfEpochBlockDataFromPools( return nil } - metaBlock, ok := headerHandler.(*block.MetaBlock) + metaBlock, ok := headerHandler.(data.MetaHeaderHandler) if !ok { return process.ErrWrongTypeAssertion } @@ -2740,8 +2944,7 @@ func (mp *metaProcessor) DecodeBlockHeader(dta []byte) data.HeaderHandler { return nil } - metaBlock := &block.MetaBlock{} - err := mp.marshalizer.Unmarshal(metaBlock, dta) + metaBlock, err := process.UnmarshalMetaHeader(mp.marshalizer, dta) if err != nil { log.Debug("DecodeBlockHeader.Unmarshal", "error", err.Error()) return nil diff --git a/process/block/metablockProposal.go b/process/block/metablockProposal.go new file mode 100644 index 00000000000..eac58db3240 --- /dev/null +++ b/process/block/metablockProposal.go @@ -0,0 +1,1353 @@ +package block + +import ( + "bytes" + "errors" + "fmt" + "time" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + logger "github.com/multiversx/mx-chain-logger-go" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/state" +) + +// usedShardHeadersInfo holds the used shard headers information +type usedShardHeadersInfo struct { + headersPerShard map[uint32][]ShardHeaderInfo + orderedShardHeaders []data.HeaderHandler + orderedShardHeaderHashes [][]byte +} + +// CreateNewHeaderProposal creates a new header +func (mp *metaProcessor) CreateNewHeaderProposal(round uint64, nonce uint64) (data.HeaderHandler, error) { + // TODO: the trigger would need to be changed upon commit of a block with the epoch start results + epoch := mp.epochStartTrigger.Epoch() + + header := mp.versionedHeaderFactory.Create(epoch, round) + metaHeader, ok := header.(data.MetaHeaderHandler) + if !ok { + return nil, process.ErrWrongTypeAssertion + } + + if !metaHeader.IsHeaderV3() { + return nil, process.ErrInvalidHeader + } + + epochChangeProposed := mp.epochStartTrigger.ShouldProposeEpochChange(round, nonce) + metaHeader.SetEpochChangeProposed(epochChangeProposed) + err := metaHeader.SetRound(round) + if err != nil { + return nil, err + } + + err = metaHeader.SetNonce(nonce) + if err != nil { + return nil, err + } + + err = mp.addExecutionResultsOnHeader(metaHeader) + if err != nil { + return nil, err + } + + err = mp.checkNonceGaps(metaHeader) + if err != nil { + return nil, err + } + + err = metaHeader.SetEpochStartHandler(&block.EpochStart{}) + if err != nil { + return nil, err + } + hasEpochStartResults, err := mp.hasStartOfEpochExecutionResults(metaHeader) + if err != nil { + return nil, err + } + if !hasEpochStartResults { + return metaHeader, nil + } + + err = metaHeader.SetEpoch(epoch + 1) + if err != nil { + return nil, err + } + + epochStartData, err := mp.getComputedEpochStartData() + if err != nil { + return nil, err + } + + err = metaHeader.SetEpochStartHandler(epochStartData) + if err != nil { + return nil, err + } + + err = mp.checkEpochCorrectnessV3(metaHeader) + if err != nil { + return nil, fmt.Errorf("created meta header with invalid epoch start data: %w", err) + } + + return metaHeader, nil +} + +// CreateBlockProposal creates a block proposal without executing any of the transactions +func (mp *metaProcessor) CreateBlockProposal( + initialHdr data.HeaderHandler, + haveTime func() bool, +) (data.HeaderHandler, data.BodyHandler, error) { + if check.IfNil(initialHdr) { + return nil, nil, process.ErrNilBlockHeader + } + if !initialHdr.IsHeaderV3() { + return nil, nil, process.ErrInvalidHeader + } + + metaHdr, ok := initialHdr.(*block.MetaBlockV3) + if !ok { + return nil, nil, process.ErrWrongTypeAssertion + } + + metaHdr.SoftwareVersion = []byte(mp.headerIntegrityVerifier.GetVersion(metaHdr.Epoch, metaHdr.Round)) + + if metaHdr.IsStartOfEpochBlock() || metaHdr.GetEpochChangeProposed() || mp.epochStartTrigger.GetEpochChangeProposed() { + // no new transactions in start of epoch block + // to simplify bootstrapping + return metaHdr, &block.Body{}, nil + } + + mp.gasComputation.Reset() + mp.miniBlocksSelectionSession.ResetSelectionSession() + err := mp.createBlockBodyProposal(metaHdr, haveTime) + if err != nil { + return nil, nil, err + } + + mbsToMe := mp.miniBlocksSelectionSession.GetMiniBlocks() + miniBlocksHeadersToMe := mp.miniBlocksSelectionSession.GetMiniBlockHeaderHandlers() + numTxs := mp.miniBlocksSelectionSession.GetNumTxsAdded() + referencedShardHeaderHashes := mp.miniBlocksSelectionSession.GetReferencedHeaderHashes() + referencedShardHeaders := mp.miniBlocksSelectionSession.GetReferencedHeaders() + body := &block.Body{ + MiniBlocks: mbsToMe, + } + + if len(mbsToMe) > 0 { + log.Debug("created miniblocks with txs with destination in self shard", + "num miniblocks", len(mbsToMe), + "num txs proposed", numTxs, + "num shard headers", len(referencedShardHeaderHashes), + ) + + } + + defer func() { + go mp.checkAndRequestIfShardHeadersMissing() + }() + + shardDataProposalHandlers, shardDataHandlers, err := mp.shardInfoCreateData.CreateShardInfoV3(metaHdr, referencedShardHeaders, referencedShardHeaderHashes) + if err != nil { + return nil, nil, err + } + + err = metaHdr.SetShardInfoHandlers(shardDataHandlers) + if err != nil { + return nil, nil, err + } + + err = metaHdr.SetShardInfoProposalHandlers(shardDataProposalHandlers) + if err != nil { + return nil, nil, err + } + + err = metaHdr.SetMiniBlockHeaderHandlers(miniBlocksHeadersToMe) + if err != nil { + return nil, nil, err + } + + txsInExecutionResults, err := getTxCountExecutionResults(metaHdr) + if err != nil { + return nil, nil, err + } + + totalProcessedTxs := getTxCount(shardDataHandlers) + txsInExecutionResults + // TODO: consider if tx count per metablock header is still needed + // as we still have it in the execution results + err = metaHdr.SetTxCount(totalProcessedTxs) + if err != nil { + return nil, nil, err + } + + marshalledBody, err := mp.marshalizer.Marshal(body) + if err != nil { + return nil, nil, err + } + mp.blockSizeThrottler.Add(metaHdr.GetRound(), uint32(len(marshalledBody))) + + return metaHdr, body, nil +} + +// VerifyBlockProposal verifies the proposed block. It returns nil if all ok or the specific error +func (mp *metaProcessor) VerifyBlockProposal( + headerHandler data.HeaderHandler, + bodyHandler data.BodyHandler, + haveTime func() time.Duration, +) error { + err := mp.checkBlockValidity(headerHandler, bodyHandler) + if err != nil { + if errors.Is(err, process.ErrBlockHashDoesNotMatch) { + log.Debug("requested missing meta header", + "hash", headerHandler.GetPrevHash(), + "for shard", headerHandler.GetShardID(), + ) + + go mp.requestHandler.RequestMetaHeaderForEpoch(headerHandler.GetPrevHash(), headerHandler.GetEpoch()) + } + + return err + } + + log.Debug("started verifying proposed meta block", + "epoch", headerHandler.GetEpoch(), + "shard", headerHandler.GetShardID(), + "round", headerHandler.GetRound(), + "nonce", headerHandler.GetNonce()) + + header, ok := headerHandler.(*block.MetaBlockV3) + if !ok { + return process.ErrWrongTypeAssertion + } + + if !header.IsHeaderV3() { + return process.ErrInvalidHeader + } + + body, ok := bodyHandler.(*block.Body) + if !ok { + return process.ErrWrongTypeAssertion + } + + if header.IsEpochChangeProposed() && len(body.MiniBlocks) != 0 { + return process.ErrEpochStartProposeBlockHasMiniBlocks + } + + err = mp.checkHeaderBodyCorrelationProposal(header.GetMiniBlockHeaderHandlers(), body) + if err != nil { + return err + } + + err = mp.waitForExecutionResultsVerification(header, haveTime) + if err != nil { + return err + } + + err = mp.checkInclusionEstimationForExecutionResults(header) + if err != nil { + return err + } + + err = mp.checkNonceGaps(header) + if err != nil { + return err + } + + mp.updateMetrics(header) + + mp.missingDataResolver.Reset() + mp.missingDataResolver.RequestBlockTransactions(body) + err = mp.missingDataResolver.RequestMissingShardHeaders(header) + if err != nil { + return err + } + + err = mp.missingDataResolver.WaitForMissingData(haveTime()) + if err != nil { + return err + } + + defer func() { + go mp.checkAndRequestIfShardHeadersMissing() + }() + + err = mp.checkEpochCorrectnessV3(header) + if err != nil { + return err + } + + err = mp.checkShardHeadersValidityAndFinalityProposal(header) + if err != nil { + return err + } + + err = mp.verifyCrossShardMiniBlockDstMe(header) + if err != nil { + return err + } + + return mp.verifyGasLimit(header, body.MiniBlocks) +} + +// ProcessBlockProposal processes the proposed block. It returns nil if all ok or the specific error +func (mp *metaProcessor) ProcessBlockProposal( + headerHandler data.HeaderHandler, + headerHash []byte, + bodyHandler data.BodyHandler, +) (data.BaseExecutionResultHandler, error) { + if check.IfNil(headerHandler) { + return nil, process.ErrNilBlockHeader + } + if check.IfNil(bodyHandler) { + return nil, process.ErrNilBlockBody + } + if !headerHandler.IsHeaderV3() { + return nil, process.ErrInvalidHeader + } + + if !mp.processStatusHandler.TrySetBusy("metaProcessor.ProcessBlockProposal") { + return nil, process.ErrBlockProcessorBusy + } + defer mp.processStatusHandler.SetIdle() + + mp.roundNotifier.CheckRound(headerHandler) + mp.epochNotifier.CheckEpoch(headerHandler) + mp.requestHandler.SetEpoch(headerHandler.GetEpoch()) + + header, ok := headerHandler.(data.MetaHeaderHandler) + if !ok { + return nil, process.ErrWrongTypeAssertion + } + + body, ok := bodyHandler.(*block.Body) + if !ok { + return nil, process.ErrWrongTypeAssertion + } + + log.Debug("started processing block", + "epoch", headerHandler.GetEpoch(), + "shard", headerHandler.GetShardID(), + "round", headerHandler.GetRound(), + "nonce", headerHandler.GetNonce(), + ) + + if mp.accountsDB[state.UserAccountsState].JournalLen() != 0 { + log.Error("metaProcessor.ProcessBlockProposal first entry", "stack", string(mp.accountsDB[state.UserAccountsState].GetStackDebugFirstEntry())) + return nil, fmt.Errorf("%w for user accounts", process.ErrAccountStateDirty) + } + if mp.accountsDB[state.PeerAccountsState].JournalLen() != 0 { + log.Error("metaProcessor.ProcessBlockProposal peer accounts first entry", "stack", string(mp.accountsDB[state.PeerAccountsState].GetStackDebugFirstEntry())) + return nil, fmt.Errorf("%w for peer accounts", process.ErrAccountStateDirty) + } + + var err error + defer func() { + if err != nil { + mp.RevertCurrentBlock() + } + }() + + err = mp.checkAndUpdateContextBeforeExecution(header, headerHash) + if err != nil { + return nil, err + } + + err = mp.createBlockStarted() + if err != nil { + return nil, err + } + + err = mp.blockChainHook.SetCurrentHeader(header) + if err != nil { + return nil, err + } + + err = mp.processIfFirstBlockAfterEpochStartBlockV3() + if err != nil { + return nil, err + } + + var execResult data.BaseExecutionResultHandler + if header.IsEpochChangeProposed() { + // in case of error, will be picked up by the deferred revert + execResult, err = mp.processEpochStartProposeBlock(header, body) + return execResult, err + } + + mp.txCoordinator.RequestBlockTransactions(body) + mp.hdrsForCurrBlock.RequestShardHeaders(header) + + // although we can have a long time for processing, it being decoupled from consensus, + // we still give some reasonable timeout + proposalStartTime := time.Now() + haveTime := getHaveTimeForProposal(proposalStartTime, mp.processConfigsHandler.GetMaxBlockProcessingTime(headerHandler.GetRound())) + + err = mp.txCoordinator.IsDataPreparedForProcessing(haveTime) + if err != nil { + return nil, err + } + + err = mp.hdrsForCurrBlock.WaitForHeadersIfNeeded(haveTime) + if err != nil { + return nil, err + } + + startTime := time.Now() + err = mp.txCoordinator.ProcessBlockTransaction(header, body, haveTime) + elapsedTime := time.Since(startTime) + log.Debug("elapsed time to process block transaction", + "time [s]", elapsedTime, + ) + if err != nil { + return nil, err + } + + err = mp.txCoordinator.VerifyCreatedBlockTransactions(header, body) + if err != nil { + return nil, err + } + + constructedBody := mp.createBlockBodyAfterExecution(body) + err = mp.scToProtocol.UpdateProtocol(constructedBody, header.GetNonce()) + if err != nil { + return nil, err + } + + var valStatRootHash []byte + valStatRootHash, err = mp.updateValidatorStatistics(header) + if err != nil { + return nil, err + } + + // in case of error, will be picked up by the deferred revert + execResult, err = mp.collectExecutionResults(headerHash, header, body, valStatRootHash) + if err != nil { + return nil, err + } + + err = mp.blockProcessingCutoffHandler.HandleProcessErrorCutoff(header) + if err != nil { + return nil, err + } + + return execResult, nil +} + +// CommitBlockProposalState commits the accounts state after processing a block proposal +// and performs any post-commit operations (e.g. saving epoch start economics metrics). +func (mp *metaProcessor) CommitBlockProposalState(headerHandler data.HeaderHandler) error { + if check.IfNil(headerHandler) { + return process.ErrNilBlockHeader + } + + mp.cleanupDismissedEWLEntries() + + err := mp.commitState(headerHandler) + if err != nil { + return err + } + + metaHeader, ok := headerHandler.(data.MetaHeaderHandler) + if ok { + mp.saveEpochStartEconomicsMetricsV3IfNeeded(metaHeader) + } + + return nil +} + +// RevertBlockProposalState reverts the uncommitted accounts state after a block proposal processing failure +func (mp *metaProcessor) RevertBlockProposalState() { + mp.RevertCurrentBlock() +} + +func (mp *metaProcessor) checkNonceGaps(metaHeader data.MetaHeaderHandler) error { + err := mp.checkHeaderExecutionResultNonceGap(metaHeader) + if err != nil { + return err + } + + shardDataFinalizedNonces := make(map[uint32]uint64) + + // Initialize shardDataFinalizedNonces with data from block tracker + lastCrossNotarizedForAllShards, err := mp.blockTracker.GetLastCrossNotarizedHeadersForAllShards() + if err != nil { + return err + } + + // Get highest finalized nonce per shard from ShardInfoHandlers + for _, shardData := range metaHeader.GetShardInfoHandlers() { + shardID := shardData.GetShardID() + nonce := shardData.GetNonce() + + existing, found := shardDataFinalizedNonces[shardID] + if !found || nonce > existing { + lastCrossNotarizedInBlockTracker, foundInTracker := lastCrossNotarizedForAllShards[shardID] + if !foundInTracker { + log.Warn("missing cross notarized header for shard in block tracker", "shard", shardID) + return process.ErrMissingCrossNotarizedHeader + } + + lastExecResultNonceOfLastCrossNotarized := common.GetLastExecutionResultNonce(lastCrossNotarizedInBlockTracker) + if nonce < lastExecResultNonceOfLastCrossNotarized { + log.Warn("found proposed nonce lower than last exec result of cross notarized", + "shard", shardID, + "shardInfoNonce", nonce, + "lastExecResultNonceOfLastCrossNotarized", lastExecResultNonceOfLastCrossNotarized, + ) + return process.ErrInvalidShardInfo + } + + shardDataFinalizedNonces[shardID] = nonce + } + } + + // fill missing data from block tracker + for shardID, lastCrossNotarizedInBlockTracker := range lastCrossNotarizedForAllShards { + _, found := shardDataFinalizedNonces[shardID] + if found { + continue + } + + shardDataFinalizedNonces[shardID] = common.GetLastExecutionResultNonce(lastCrossNotarizedInBlockTracker) + } + + // Get highest proposed nonce per shard from ShardInfoProposalHandlers + shardDataProposedNonces := make(map[uint32]uint64) + for _, shardProposalData := range metaHeader.GetShardInfoProposalHandlers() { + shardID := shardProposalData.GetShardID() + nonce := shardProposalData.GetNonce() + + if existing, found := shardDataProposedNonces[shardID]; !found || nonce > existing { + shardDataProposedNonces[shardID] = nonce + } + } + + // Check nonce gaps for each shard + for shardID, maxProposedNonce := range shardDataProposedNonces { + lastFinalizedNonce, found := shardDataFinalizedNonces[shardID] + if !found { + log.Warn("missing last notarized header for shard", "shard", shardID) + return process.ErrMissingCrossNotarizedHeader + } + + if maxProposedNonce < lastFinalizedNonce { + return fmt.Errorf("%w: shard %d, last finalized nonce %d, proposed nonce %d", + process.ErrInvalidProposedNonce, + shardID, + lastFinalizedNonce, + maxProposedNonce) + } + + nonceGap := maxProposedNonce - lastFinalizedNonce + if nonceGap > mp.maxProposalNonceGap { + return fmt.Errorf("%w: shard %d has nonce gap of %d between finalized nonce %d and proposed nonce %d, max allowed gap is %d", + process.ErrNonceGapTooLarge, + shardID, + nonceGap, + lastFinalizedNonce, + maxProposedNonce, + mp.maxProposalNonceGap) + } + } + + return nil +} + +func (mp *metaProcessor) processEpochStartProposeBlock( + metaHeader data.MetaHeaderHandler, + body *block.Body, +) (data.BaseExecutionResultHandler, error) { + if check.IfNil(metaHeader) { + return nil, process.ErrNilBlockHeader + } + if body == nil { + return nil, process.ErrNilBlockBody + } + if len(body.MiniBlocks) != 0 { + return nil, process.ErrEpochStartProposeBlockHasMiniBlocks + } + + log.Debug("processing epoch start propose block", + "block epoch", metaHeader.GetEpoch(), + "for epoch", metaHeader.GetEpoch()+1, + "round", metaHeader.GetRound(), + "nonce", metaHeader.GetNonce(), + ) + + err := mp.processEconomicsDataForEpochStartProposeBlock(metaHeader) + if err != nil { + return nil, err + } + + var computedEconomics *block.Economics + computedEconomics, err = mp.getComputedEconomics(metaHeader.GetEpoch() + 1) + if err != nil { + return nil, err + } + + constructedBody, err := mp.processEpochStartMiniBlocks(metaHeader, computedEconomics) + if err != nil { + return nil, err + } + + valStatRootHash, err := mp.updateValidatorStatistics(metaHeader) + if err != nil { + return nil, err + } + + headerHash, err := core.CalculateHash(mp.marshalizer, mp.hasher, metaHeader) + if err != nil { + return nil, err + } + + execResult, err := mp.collectExecutionResultsEpochStartProposal(headerHash, metaHeader, constructedBody, valStatRootHash) + if err != nil { + return nil, err + } + + err = mp.blockProcessingCutoffHandler.HandleProcessErrorCutoff(metaHeader) + if err != nil { + return nil, err + } + + return execResult, nil +} + +func (mp *metaProcessor) saveEpochStartEconomicsMetricsV3IfNeeded(metaBlock data.MetaHeaderHandler) { + if !metaBlock.IsHeaderV3() { + // fee metrics for meta block will be handled on commit + return + } + + if !metaBlock.IsEpochChangeProposed() { + return + } + + lastExecutionResult := mp.blockChain.GetLastExecutionResult() + if !bytes.Equal(lastExecutionResult.GetHeaderHash(), metaBlock.GetPrevHash()) { + // should never happen, as this is called while processing proposeEpochChangeMetaBlock + return + } + + lastMetaExecutionResult, ok := lastExecutionResult.(data.BaseMetaExecutionResultHandler) + if !ok { + // should never happen + return + } + + mp.appStatusHandler.SetStringValue(common.MetricTotalFees, lastMetaExecutionResult.GetAccumulatedFeesInEpoch().String()) + mp.appStatusHandler.SetStringValue(common.MetricDevRewardsInEpoch, lastMetaExecutionResult.GetDevFeesInEpoch().String()) +} + +func (mp *metaProcessor) updateValidatorStatistics(header data.MetaHeaderHandler) ([]byte, error) { + sw := core.NewStopWatch() + sw.Start("UpdatePeerState") + mp.prepareBlockHeaderInternalMapForValidatorProcessor(header) + valStatRootHash, err := mp.updatePeerState(header, mp.hdrsForCurrBlock.GetHeadersMap()) + sw.Stop("UpdatePeerState") + return valStatRootHash, err +} + +func (mp *metaProcessor) collectExecutionResultsEpochStartProposal( + headerHash []byte, + header data.MetaHeaderHandler, + constructedBody *block.Body, + valStatRootHash []byte, +) (data.BaseExecutionResultHandler, error) { + totalTxCount, miniBlockHeaderHandlers, err := mp.createMiniBlockHeaderHandlersForExecutionResults(constructedBody) + if err != nil { + return nil, err + } + + receiptHash, err := mp.txCoordinator.CreateReceiptsHash() + if err != nil { + return nil, err + } + + // we consider the rewards and peer mini blocks as post process mbs (post execution of start of epoch proposed block) + err = mp.cacheIntraShardMiniBlocks(headerHash, constructedBody.MiniBlocks) + if err != nil { + return nil, err + } + + err = mp.cacheExecutedMiniBlocks(&block.Body{MiniBlocks: constructedBody.MiniBlocks}, miniBlockHeaderHandlers) + if err != nil { + return nil, err + } + + return mp.createExecutionResult(miniBlockHeaderHandlers, header, headerHash, receiptHash, valStatRootHash, totalTxCount) +} + +// collectExecutionResults collects the execution results after processing the block +func (mp *metaProcessor) collectExecutionResults( + headerHash []byte, + header data.MetaHeaderHandler, + body *block.Body, + valStatRootHash []byte, +) (data.BaseExecutionResultHandler, error) { + miniBlockHeaderHandlers, totalTxCount, receiptHash, err := mp.collectMiniBlocks(headerHash, body) + if err != nil { + return nil, err + } + + return mp.createExecutionResult(miniBlockHeaderHandlers, header, headerHash, receiptHash, valStatRootHash, totalTxCount) +} + +func (mp *metaProcessor) createExecutionResult( + miniBlockHeaderHandlers []data.MiniBlockHeaderHandler, + header data.MetaHeaderHandler, + headerHash []byte, + receiptHash []byte, + valStatRootHash []byte, + totalTxCount int, +) (data.BaseExecutionResultHandler, error) { + gasAndFees := mp.getGasAndFees() + gasNotUsedForProcessing := gasAndFees.GetGasPenalized() + gasAndFees.GetGasRefunded() + if gasAndFees.GetGasProvided() < gasNotUsedForProcessing { + return nil, process.ErrGasUsedExceedsGasProvided + } + + gasUsed := gasAndFees.GetGasProvided() - gasNotUsedForProcessing // needed for inclusion estimation + + accumulatedFeesInEpoch, devFeesInEpoch, err := mp.computeAccumulatedFeesInEpoch(header) + if err != nil { + return nil, err + } + + executionResult := &block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: headerHash, + HeaderNonce: header.GetNonce(), + HeaderRound: header.GetRound(), + HeaderEpoch: header.GetEpoch(), + RootHash: mp.getRootHash(), + GasUsed: gasUsed, + }, + ValidatorStatsRootHash: valStatRootHash, + AccumulatedFeesInEpoch: accumulatedFeesInEpoch, + DevFeesInEpoch: devFeesInEpoch, + }, + ReceiptsHash: receiptHash, + DeveloperFees: gasAndFees.GetDeveloperFees(), + AccumulatedFees: gasAndFees.GetAccumulatedFees(), + ExecutedTxCount: uint64(totalTxCount), + } + + err = executionResult.SetMiniBlockHeadersHandlers(miniBlockHeaderHandlers) + if err != nil { + return nil, err + } + + logs := mp.txCoordinator.GetAllCurrentLogs() + err = mp.cacheLogEvents(headerHash, logs) + if err != nil { + return nil, err + } + + err = mp.cacheIntermediateTxsForHeader(headerHash) + if err != nil { + return nil, err + } + + mp.cacheOrderedTxHashes(headerHash) + mp.cacheUnexecutableTxHashes(headerHash) + mp.cacheHeaderGasData(headerHash) + + return executionResult, nil +} + +func getTxCountExecutionResults(metaHeader data.MetaHeaderHandler) (uint32, error) { + if check.IfNil(metaHeader) { + return 0, nil + } + + totalTxs := uint64(0) + execResults := metaHeader.GetExecutionResultsHandlers() + for _, execResult := range execResults { + execResultsMeta, ok := execResult.(data.MetaExecutionResultHandler) + if !ok { + return 0, process.ErrWrongTypeAssertion + } + totalTxs += execResultsMeta.GetExecutedTxCount() + } + return uint32(totalTxs), nil +} + +func (mp *metaProcessor) hasStartOfEpochExecutionResults(metaHeader data.MetaHeaderHandler) (bool, error) { + if check.IfNil(metaHeader) { + return false, process.ErrNilHeaderHandler + } + execResults := metaHeader.GetExecutionResultsHandlers() + for _, execResult := range execResults { + ok, err := mp.hasRewardOrPeerMiniBlocksOnExecResult(execResult) + if err != nil { + return false, err + } + if ok { + return true, nil + } + } + return false, nil +} + +func (mp *metaProcessor) hasRewardOrPeerMiniBlocksOnExecResult(execResult data.BaseExecutionResultHandler) (bool, error) { + mbHeaders, err := common.GetMiniBlocksHeaderHandlersFromExecResult(execResult) + if err != nil { + return false, err + } + + return hasRewardOrPeerMiniBlocksFromMeta(mbHeaders), nil +} + +func hasRewardOrPeerMiniBlocksFromMeta(miniBlockHeaders []data.MiniBlockHeaderHandler) bool { + for _, mbHeader := range miniBlockHeaders { + if mbHeader.GetSenderShardID() != common.MetachainShardId { + continue + } + if mbHeader.GetTypeInt32() == int32(block.RewardsBlock) || + mbHeader.GetTypeInt32() == int32(block.PeerBlock) { + return true + } + } + return false +} + +func (mp *metaProcessor) createBlockBodyProposal( + metaHdr data.MetaHeaderHandler, + haveTime func() bool, +) error { + mp.blockSizeThrottler.ComputeCurrentMaxSize() + + log.Debug("started creating block body", + "epoch", metaHdr.GetEpoch(), + "round", metaHdr.GetRound(), + "nonce", metaHdr.GetNonce(), + ) + + return mp.createProposalMiniBlocks(haveTime) +} + +func (mp *metaProcessor) createProposalMiniBlocks(haveTime func() bool) error { + if !haveTime() { + log.Debug("metaProcessor.createProposalMiniBlocks", "error", process.ErrTimeIsOut) + return nil + } + + startTime := time.Now() + err := mp.selectIncomingMiniBlocksForProposal(haveTime) + if err != nil { + return err + } + elapsedTime := time.Since(startTime) + log.Debug("elapsed time to create mbs to me", "time", elapsedTime) + + return nil +} + +func (mp *metaProcessor) selectIncomingMiniBlocksForProposal( + haveTime func() bool, +) error { + sw := core.NewStopWatch() + sw.Start("ComputeLongestShardsChainsFromLastNotarized") + orderedHdrs, orderedHdrsHashes, _, err := mp.blockTracker.ComputeLongestShardsChainsFromLastNotarized() + sw.Stop("ComputeLongestShardsChainsFromLastNotarized") + log.Debug("measurements ComputeLongestShardsChainsFromLastNotarized", sw.GetMeasurements()...) + if err != nil { + return err + } + + log.Debug("shard headers ordered", + "num shard headers", len(orderedHdrs), + ) + + lastShardHdrs, err := mp.getLastCrossNotarizedShardHeaders() + if err != nil { + return err + } + + maxShardHeadersFromSameShard := core.MaxUint32( + process.MinShardHeadersFromSameShardInOneMetaBlock, + process.MaxShardHeadersAllowedInOneMetaBlock/mp.shardCoordinator.NumberOfShards(), + ) + err = mp.selectIncomingMiniBlocks(lastShardHdrs, orderedHdrs, orderedHdrsHashes, maxShardHeadersFromSameShard, haveTime) + if err != nil { + return err + } + + return nil +} + +func (mp *metaProcessor) selectIncomingMiniBlocks( + lastShardHdrs map[uint32]ShardHeaderInfo, + orderedHdrs []data.HeaderHandler, + orderedHdrsHashes [][]byte, + maxShardHeadersFromSameShard uint32, + haveTime func() bool, +) error { + hdrsAdded := uint32(0) + maxShardHeadersAllowedInOneMetaBlock := maxShardHeadersFromSameShard * mp.shardCoordinator.NumberOfShards() + hdrsAddedForShard := make(map[uint32]uint32) + var err error + + if len(orderedHdrs) != len(orderedHdrsHashes) { + return process.ErrInconsistentShardHeadersAndHashes + } + + for i := 0; i < len(orderedHdrs); i++ { + if !haveTime() { + log.Debug("time is up after putting cross txs with destination to metachain", + "num txs", mp.miniBlocksSelectionSession.GetNumTxsAdded(), + ) + break + } + + if hdrsAdded >= maxShardHeadersAllowedInOneMetaBlock { + log.Debug("maximum shard headers allowed to be included in one meta block has been reached", + "shard headers added", hdrsAdded, + ) + break + } + + currHdr := orderedHdrs[i] + currHdrHash := orderedHdrsHashes[i] + lastShardHeaderInfo, ok := lastShardHdrs[currHdr.GetShardID()] + if !ok { + return process.ErrMissingHeader + } + if currHdr.GetNonce() != lastShardHeaderInfo.Header.GetNonce()+1 { + log.Trace("skip searching", + "shard", currHdr.GetShardID(), + "last shard hdr nonce", lastShardHeaderInfo.Header.GetNonce(), + "curr shard hdr nonce", currHdr.GetNonce()) + continue + } + + if hdrsAddedForShard[currHdr.GetShardID()] >= maxShardHeadersFromSameShard { + log.Trace("maximum shard headers from same shard allowed to be included in one meta block has been reached", + "shard", currHdr.GetShardID(), + "shard headers added", hdrsAddedForShard[currHdr.GetShardID()], + ) + continue + } + + hasProofForHdr := mp.proofsPool.HasProof(currHdr.GetShardID(), currHdrHash) + if !hasProofForHdr { + log.Trace("no proof for shard header", + "shard", currHdr.GetShardID(), + "hash", logger.DisplayByteSlice(currHdrHash), + ) + continue + } + + if len(currHdr.GetMiniBlockHeadersWithDst(mp.shardCoordinator.SelfId())) == 0 { + mp.miniBlocksSelectionSession.AddReferencedHeader(currHdr, currHdrHash) + lastShardHdrs[currHdr.GetShardID()] = ShardHeaderInfo{ + Header: currHdr, + Hash: currHdrHash, + UsedInBlock: true, + } + hdrsAddedForShard[currHdr.GetShardID()]++ + hdrsAdded++ + continue + } + + createIncomingMbsResult, errCreated := mp.createMbsCrossShardDstMe(currHdrHash, currHdr, nil) + if errCreated != nil { + return errCreated + } + if !createIncomingMbsResult.HeaderFinished { + mp.revertGasForCrossShardDstMeMiniBlocks(createIncomingMbsResult.AddedMiniBlocks, createIncomingMbsResult.PendingMiniBlocks) + log.Debug("shard header cannot be fully added", + "round", currHdr.GetRound(), + "nonce", currHdr.GetNonce(), + "hash", currHdrHash) + break + } + + if len(createIncomingMbsResult.AddedMiniBlocks) > 0 { + err = mp.miniBlocksSelectionSession.AddMiniBlocksAndHashes(createIncomingMbsResult.AddedMiniBlocks) + if err != nil { + return err + } + } + + mp.miniBlocksSelectionSession.AddReferencedHeader(currHdr, currHdrHash) + lastShardHdrs[currHdr.GetShardID()] = ShardHeaderInfo{ + Header: currHdr, + Hash: currHdrHash, + UsedInBlock: true, + } + hdrsAddedForShard[currHdr.GetShardID()]++ + hdrsAdded++ + } + + go mp.requestShardHeadersInAdvanceIfNeeded(lastShardHdrs) + + return nil +} + +func (mp *metaProcessor) requestShardHeadersInAdvanceIfNeeded( + lastShardHdr map[uint32]ShardHeaderInfo, +) { + for shardID := uint32(0); shardID < mp.shardCoordinator.NumberOfShards(); shardID++ { + mp.requestHeadersFromHeaderIfNeeded(lastShardHdr[shardID].Header) + } +} + +func (mp *metaProcessor) verifyEpochStartData( + headerHandler data.MetaHeaderHandler, +) bool { + epochStartData, err := mp.getComputedEpochStartData() + if err != nil { + log.Error("verifyEpochStartData: failed to get epoch start data", "error", err) + return false + } + + return epochStartData.Equal(headerHandler.GetEpochStartHandler()) +} + +func (mp *metaProcessor) checkEpochCorrectnessV3( + headerHandler data.MetaHeaderHandler, +) error { + currentBlockHeader := mp.blockChain.GetCurrentBlockHeader() + if check.IfNil(currentBlockHeader) { + return nil + } + + hasEpochStartExecutionResults, err := mp.hasStartOfEpochExecutionResults(headerHandler) + if err != nil { + return err + } + + wasEpochStartProposed, err := mp.hasExecutionResultsForProposedEpochChange(headerHandler) + if err != nil { + return err + } + + isEpochStartBlock := headerHandler.IsStartOfEpochBlock() + + epochStartDataMatches := mp.verifyEpochStartData(headerHandler) + hasAllEpochStartData := hasEpochStartExecutionResults && isEpochStartBlock && wasEpochStartProposed && epochStartDataMatches + hasAnyEpochStartData := hasEpochStartExecutionResults || isEpochStartBlock || wasEpochStartProposed + hasIncompleteEpochStartData := hasAnyEpochStartData && !hasAllEpochStartData + + if hasIncompleteEpochStartData { + log.Warn("block has incomplete epoch start data", + "hasEpochStartExecutionResults", hasEpochStartExecutionResults, + "isEpochStartBlock", isEpochStartBlock, + "wasEpochStartProposed", wasEpochStartProposed, + "epochStartTrigger", mp.epochStartTrigger.Epoch()) + return process.ErrEpochDoesNotMatch + } + + isEpochIncorrect := headerHandler.GetEpoch() != currentBlockHeader.GetEpoch() && !hasAllEpochStartData + if isEpochIncorrect { + log.Warn("block does not have epoch start results but epoch has changed", + "currentHeaderEpoch", currentBlockHeader.GetEpoch(), + "receivedHeaderEpoch", headerHandler.GetEpoch(), + "epochStartTrigger", mp.epochStartTrigger.Epoch()) + return process.ErrEpochDoesNotMatch + } + + isEpochIncorrect = headerHandler.GetEpoch() == currentBlockHeader.GetEpoch() && hasAllEpochStartData + if isEpochIncorrect { + log.Warn("block has epoch start results but epoch did not change", + "currentHeaderEpoch", currentBlockHeader.GetEpoch(), + "receivedHeaderEpoch", headerHandler.GetEpoch(), + "epochStartTrigger", mp.epochStartTrigger.Epoch()) + return process.ErrEpochDoesNotMatch + } + + isEpochIncorrect = headerHandler.GetEpoch() != currentBlockHeader.GetEpoch()+1 && hasAllEpochStartData + if isEpochIncorrect { + log.Warn("block did not correctly change epoch, with proposed epoch change", + "currentHeaderEpoch", currentBlockHeader.GetEpoch(), + "receivedHeaderEpoch", headerHandler.GetEpoch(), + "epochStartTrigger", mp.epochStartTrigger.Epoch()) + return process.ErrEpochDoesNotMatch + } + + return nil +} + +func (mp *metaProcessor) hasExecutionResultsForProposedEpochChange(headerHandler data.MetaHeaderHandler) (bool, error) { + executionResults := headerHandler.GetExecutionResultsHandlers() + var header data.HeaderHandler + var err error + + for _, execResult := range executionResults { + header, err = process.GetHeader( + execResult.GetHeaderHash(), + mp.dataPool.Headers(), + mp.store, + mp.marshalizer, + headerHandler.GetShardID(), + ) + if err != nil { + log.Debug("hasExecutionResultsForProposedEpochChange: could not find header", + "hash", execResult.GetHeaderHash(), + ) + return false, err + } + metaHeaderHandler, ok := header.(data.MetaHeaderHandler) + if !ok { + return false, process.ErrWrongTypeAssertion + } + + isEpochChangeProposed := metaHeaderHandler.IsEpochChangeProposed() + hasStartOfEpochOnExecutionResult, err := mp.hasRewardOrPeerMiniBlocksOnExecResult(execResult) + if err != nil { + return false, err + } + + if isEpochChangeProposed && !hasStartOfEpochOnExecutionResult { + return false, process.ErrStartOfEpochExecutionResultsDoNotExist + } + + if isEpochChangeProposed { + return true, nil + } + } + + return false, nil +} + +func (mp *metaProcessor) checkShardHeadersValidityAndFinalityProposal( + metaHeaderHandler data.MetaHeaderHandler, +) error { + lastCrossNotarizedHeader, err := mp.getLastCrossNotarizedShardHeaders() + if err != nil { + return err + } + + usedShardHeaders, err := mp.getShardHeadersFromMetaHeader(metaHeaderHandler) + if err != nil { + return fmt.Errorf("%w : checkShardHeadersValidityAndFinalityProposal -> getShardHeadersFromMetaHeader", err) + } + + shouldNotHaveShardHeaders := metaHeaderHandler.IsStartOfEpochBlock() || metaHeaderHandler.IsEpochChangeProposed() || mp.epochStartTrigger.GetEpochChangeProposed() + if len(usedShardHeaders.orderedShardHeaders) > 0 && shouldNotHaveShardHeaders { + return fmt.Errorf("%w : between epoch change proposed and epoch start block", process.ErrShardHeadersShouldNotBeNotarized) + } + + ok := mp.hasProofsForHeaders(usedShardHeaders.headersPerShard) + if !ok { + return process.ErrMissingHeaderProof + } + + err = mp.verifyUsedShardHeadersValidity(usedShardHeaders.headersPerShard, lastCrossNotarizedHeader) + if err != nil { + return fmt.Errorf("%w : checkShardHeadersValidityAndFinalityProposal -> verifyUsedShardHeadersValidity", err) + } + + return mp.checkShardInfoValidity(metaHeaderHandler, usedShardHeaders) +} + +func (mp *metaProcessor) checkShardInfoValidity(metaHeaderHandler data.MetaHeaderHandler, usedShardHeadersInfo *usedShardHeadersInfo) error { + createdShardInfoProposal, createdShardInfo, err := mp.shardInfoCreateData.CreateShardInfoV3(metaHeaderHandler, usedShardHeadersInfo.orderedShardHeaders, usedShardHeadersInfo.orderedShardHeaderHashes) + if err != nil { + return fmt.Errorf("%w : checkShardInfoValidity -> CreateShardInfoV3", err) + } + + headerShardInfo := metaHeaderHandler.GetShardInfoHandlers() + headerShardInfoProposal := metaHeaderHandler.GetShardInfoProposalHandlers() + if len(createdShardInfo) != len(headerShardInfo) || len(createdShardInfoProposal) != len(headerShardInfoProposal) { + return process.ErrHeaderShardDataMismatch + } + + for i := 0; i < len(headerShardInfo); i++ { + if !headerShardInfo[i].Equal(createdShardInfo[i]) { + return fmt.Errorf("%w for shardInfo item %d", process.ErrHeaderShardDataMismatch, i) + } + } + for i := 0; i < len(headerShardInfoProposal); i++ { + if !headerShardInfoProposal[i].Equal(createdShardInfoProposal[i]) { + return fmt.Errorf("%w for shardInfoProposal item %d", process.ErrHeaderShardDataMismatch, i) + } + } + + return nil +} + +func (mp *metaProcessor) verifyUsedShardHeadersValidity( + usedShardHeaders map[uint32][]ShardHeaderInfo, + lastCrossNotarizedHeader map[uint32]ShardHeaderInfo, +) error { + var err error + for shardID, hdrsForShard := range usedShardHeaders { + err = mp.checkHeadersSequenceCorrectness(hdrsForShard, lastCrossNotarizedHeader[shardID]) + if err != nil { + return err + } + } + return nil +} + +func (mp *metaProcessor) checkHeadersSequenceCorrectness(hdrsForShard []ShardHeaderInfo, lastNotarizedHeaderInfoForShard ShardHeaderInfo) error { + var err error + for _, shardHdrInfo := range hdrsForShard { + if mp.isGenesisShardBlockAndFirstMeta(shardHdrInfo.Header.GetNonce()) { + continue + } + + err = mp.headerValidator.IsHeaderConstructionValid(shardHdrInfo.Header, lastNotarizedHeaderInfoForShard.Header) + if err != nil { + return err + } + + lastNotarizedHeaderInfoForShard = shardHdrInfo + } + + return nil +} + +func (mp *metaProcessor) hasProofsForHeaders(headersPerShard map[uint32][]ShardHeaderInfo) bool { + for _, headersForShard := range headersPerShard { + for _, headerInfo := range headersForShard { + if !mp.proofsPool.HasProof(headerInfo.Header.GetShardID(), headerInfo.Hash) { + log.Debug("missing proof for shard header", "shard", headerInfo.Header.GetShardID(), "headerHash", headerInfo.Hash) + return false + } + } + } + return true +} + +func (mp *metaProcessor) getShardHeadersFromMetaHeader( + metaHeaderHandler data.MetaHeaderHandler, +) (*usedShardHeadersInfo, error) { + shardInfoProposalHandlers := metaHeaderHandler.GetShardInfoProposalHandlers() + usedShardHeaders := make(map[uint32][]ShardHeaderInfo) + var err error + var header data.HeaderHandler + orderedShardHeaders := make([]data.HeaderHandler, 0, len(shardInfoProposalHandlers)) + orderedShardHeaderHashes := make([][]byte, 0, len(shardInfoProposalHandlers)) + for _, shardInfoHandler := range shardInfoProposalHandlers { + header, err = process.GetHeader( + shardInfoHandler.GetHeaderHash(), + mp.dataPool.Headers(), + mp.store, + mp.marshalizer, + shardInfoHandler.GetShardID(), + ) + if err != nil { + log.Debug("getShardHeadersFromMetaHeader: could not find header", + "hash", shardInfoHandler.GetHeaderHash(), + "error", err, + ) + return nil, process.ErrMissingHeader + } + + usedShardHeaders[header.GetShardID()] = append(usedShardHeaders[header.GetShardID()], ShardHeaderInfo{ + Header: header, + Hash: shardInfoHandler.GetHeaderHash(), + UsedInBlock: true, + }) + orderedShardHeaders = append(orderedShardHeaders, header) + orderedShardHeaderHashes = append(orderedShardHeaderHashes, shardInfoHandler.GetHeaderHash()) + } + + return &usedShardHeadersInfo{ + headersPerShard: usedShardHeaders, + orderedShardHeaders: orderedShardHeaders, + orderedShardHeaderHashes: orderedShardHeaderHashes, + }, nil +} + +func (mp *metaProcessor) processIfFirstBlockAfterEpochStartBlockV3() error { + prevExecutedBlock := mp.getPreviousExecutedBlock() + prevExecutedMetaHeader, ok := prevExecutedBlock.(data.MetaHeaderHandler) + if !ok { + return process.ErrWrongTypeAssertion + } + + if !prevExecutedMetaHeader.IsStartOfEpochBlock() { + return nil + } + + nodesForcedToStay, err := mp.validatorStatisticsProcessor.SaveNodesCoordinatorUpdates(prevExecutedMetaHeader.GetEpoch()) + if err != nil { + return err + } + + err = mp.epochSystemSCProcessor.ToggleUnStakeUnBond(nodesForcedToStay) + if err != nil { + return err + } + + return nil +} + +func (mp *metaProcessor) getPreviousExecutedBlock() data.HeaderHandler { + blockHeader := mp.blockChain.GetLastExecutedBlockHeader() + if check.IfNil(blockHeader) { + return mp.blockChain.GetGenesisHeader() + } + return blockHeader +} + +func (mp *metaProcessor) getComputedEpochStartData() (*block.EpochStart, error) { + mp.mutEpochStartData.RLock() + defer mp.mutEpochStartData.RUnlock() + + if mp.epochStartDataWrapper == nil || + mp.epochStartDataWrapper.EpochStartData == nil { + return nil, process.ErrNilEpochStartData + } + + epochStartData := *mp.epochStartDataWrapper.EpochStartData + + return &epochStartData, nil +} + +func (mp *metaProcessor) processEconomicsDataForEpochStartProposeBlock(metaHeader data.MetaHeaderHandler) error { + baseExecutionResult := mp.blockChain.GetLastExecutionResult() + if check.IfNil(baseExecutionResult) { + return fmt.Errorf("%w for blockchain.GetLastExecutionResult", process.ErrNilBaseExecutionResult) + } + prevExecutionResult, ok := baseExecutionResult.(data.MetaExecutionResultHandler) + if !ok { + return process.ErrWrongTypeAssertion + } + + // since there are no shard headers finalized between the epoch start proposal and the epoch start block, + // the last finalized data is the same as the one created at epoch start block proposal time + lastFinalizedData, err := mp.epochStartDataCreator.CreateEpochStartShardDataMetablockV3(metaHeader) + if err != nil { + return err + } + lastShardData := &block.EpochStart{ + LastFinalizedHeaders: lastFinalizedData, + } + + economicsData, err := mp.epochEconomics.ComputeEndOfEpochEconomicsV3(metaHeader, prevExecutionResult, lastShardData) + if err != nil { + return err + } + + lastShardData.Economics = *economicsData + + mp.mutEpochStartData.Lock() + defer mp.mutEpochStartData.Unlock() + mp.epochStartDataWrapper.Epoch = metaHeader.GetEpoch() + 1 + mp.epochStartDataWrapper.EpochStartData = lastShardData + + return nil +} + +func (mp *metaProcessor) getComputedEconomics(epoch uint32) (*block.Economics, error) { + mp.mutEpochStartData.RLock() + defer mp.mutEpochStartData.RUnlock() + if mp.epochStartDataWrapper == nil || + mp.epochStartDataWrapper.EpochStartData == nil || + mp.epochStartDataWrapper.Epoch != epoch { + return nil, process.ErrNilEpochStartData + } + computedEconomics := &mp.epochStartDataWrapper.EpochStartData.Economics + + return computedEconomics, nil +} diff --git a/process/block/metablockProposal_test.go b/process/block/metablockProposal_test.go new file mode 100644 index 00000000000..de7d9f42fd5 --- /dev/null +++ b/process/block/metablockProposal_test.go @@ -0,0 +1,5158 @@ +package block_test + +import ( + "bytes" + "errors" + "math/big" + "testing" + "time" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/dataRetriever" + "github.com/multiversx/mx-chain-go/storage" + "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" + statusHandlerMock "github.com/multiversx/mx-chain-go/testscommon/statusHandler" + storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" + + "github.com/multiversx/mx-chain-go/state" + "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" + testscommonState "github.com/multiversx/mx-chain-go/testscommon/state" + + "github.com/multiversx/mx-chain-go/common" + integrationTestsMock "github.com/multiversx/mx-chain-go/integrationTests/mock" + "github.com/multiversx/mx-chain-go/process" + blproc "github.com/multiversx/mx-chain-go/process/block" + "github.com/multiversx/mx-chain-go/process/block/processedMb" + "github.com/multiversx/mx-chain-go/process/mock" + "github.com/multiversx/mx-chain-go/testscommon" + dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" + "github.com/multiversx/mx-chain-go/testscommon/mbSelection" + "github.com/multiversx/mx-chain-go/testscommon/pool" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" +) + +func TestMetaProcessor_CreateNewHeaderProposal(t *testing.T) { + t.Parallel() + + defaultBootstrapComponents := &mock.BootstrapComponentsMock{ + Coordinator: mock.NewOneShardCoordinatorMock(), + HdrIntegrityVerifier: &mock.HeaderIntegrityVerifierStub{}, + VersionedHdrFactory: &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return &block.MetaBlock{} + }, + }, + } + + validMetaHeaderV3 := testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { + return true + }, + GetLastExecutionResultHandlerCalled: func() data.LastExecutionResultHandler { + return &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{}, + }, + } + }, + } + + prevValidMetaBlockV3 := testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { + return true + }, + GetLastExecutionResultHandlerCalled: func() data.LastExecutionResultHandler { + return &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{}, + } + }, + } + validMetaExecutionResultsWithEpochChange := []data.BaseExecutionResultHandler{ + &block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{}, + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("mb hash"), + SenderShardID: core.MetachainShardId, + Type: block.RewardsBlock, // this miniBlock marks the epoch start + }, + }, + }, + } + validMetaExecutionResultsWithoutEpochChange := []data.BaseExecutionResultHandler{ + &block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{}, + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("mb hash"), + ReceiverShardID: core.MetachainShardId, + SenderShardID: 0, + Type: block.TxBlock, + }, + }, + }, + } + + t.Run("versioned header factory creates an invalid meta header, should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.EpochStartTrigger = &testscommon.EpochStartTriggerStub{ + EpochCalled: func() uint32 { + return 1 + }, + } + bc := *defaultBootstrapComponents + bc.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return &block.Header{} + }, + } + + arguments.BootstrapComponents = &bc + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header, err := mp.CreateNewHeaderProposal(1, 1) + require.Nil(t, header) + require.Equal(t, process.ErrWrongTypeAssertion, err) + }) + t.Run("versioned header factory creates a metablock but with version < v3, should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.EpochStartTrigger = &testscommon.EpochStartTriggerStub{ + EpochCalled: func() uint32 { + return 1 + }, + } + bc := *defaultBootstrapComponents + bc.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return &block.MetaBlock{} + }, + } + + arguments.BootstrapComponents = &bc + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header, err := mp.CreateNewHeaderProposal(1, 1) + require.Nil(t, header) + require.Equal(t, process.ErrInvalidHeader, err) + }) + t.Run("correct meta header version, set round error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + bc := *defaultBootstrapComponents + bc.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return &testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { + return true + }, + SetRoundCalled: func(_ uint64) error { + return expectedErr + }, + } + }, + } + + arguments.BootstrapComponents = &bc + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header, err := mp.CreateNewHeaderProposal(1, 1) + require.Nil(t, header) + require.Equal(t, expectedErr, err) + }) + t.Run("correct meta header version, set nonce error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + bc := *defaultBootstrapComponents + versionedHeader := validMetaHeaderV3 + versionedHeader.SetNonceCalled = func(_ uint64) error { + return expectedErr + } + bc.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return &versionedHeader + }, + } + + arguments.BootstrapComponents = &bc + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header, err := mp.CreateNewHeaderProposal(1, 1) + require.Nil(t, header) + require.Equal(t, expectedErr, err) + }) + t.Run("correct meta header version, add execution result error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ExecutionManager = &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return nil, expectedErr + }, + } + bc := *defaultBootstrapComponents + bc.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return &validMetaHeaderV3 + }, + } + + arguments.BootstrapComponents = &bc + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header, err := mp.CreateNewHeaderProposal(1, 1) + require.Nil(t, header) + require.Equal(t, expectedErr, err) + }) + t.Run("error checking epoch start data in execution results, should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ExecutionManager = &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return nil, nil + }, + } + + metaBlockWithInvalidExecutionResult := validMetaHeaderV3 + metaBlockWithInvalidExecutionResult.GetExecutionResultsHandlersCalled = func() []data.BaseExecutionResultHandler { + return []data.BaseExecutionResultHandler{ + &block.BaseExecutionResult{}, // invalid for meta block + } + } + + bc := *defaultBootstrapComponents + bc.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return &metaBlockWithInvalidExecutionResult + }, + } + + arguments.BootstrapComponents = &bc + dataComponentsModified := *dataComponents + dataComponentsModified.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &prevValidMetaBlockV3 + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("prev header hash") + }, + } + arguments.DataComponents = &dataComponentsModified + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header, err := mp.CreateNewHeaderProposal(1, 1) + require.Nil(t, header) + require.Equal(t, process.ErrWrongTypeAssertion, err) + }) + t.Run("with epoch start data in execution results, but missing epoch start data in meta block processor", func(t *testing.T) { + t.Parallel() + + mapForMetaProcessor := createMetaProcessorMapForCreatingEpochStart() + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(mapForMetaProcessor) + require.Nil(t, err) + + header, err := mp.CreateNewHeaderProposal(1, 1) + require.Equal(t, process.ErrNilEpochStartData, err) + require.Nil(t, header) + }) + t.Run("with epoch start data in execution results and in meta block processor, error on set epoch", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ExecutionManager = &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return nil, nil + }, + } + + metaBlockWithValidExecutionResult := validMetaHeaderV3 + metaBlockWithValidExecutionResult.GetExecutionResultsHandlersCalled = func() []data.BaseExecutionResultHandler { + return validMetaExecutionResultsWithEpochChange + } + metaBlockWithValidExecutionResult.SetEpochCalled = func(epoch uint32) error { + require.Equal(t, uint32(1), epoch) + return expectedErr + } + + bc := *defaultBootstrapComponents + bc.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return &metaBlockWithValidExecutionResult + }, + } + + arguments.BootstrapComponents = &bc + dataComponentsModified := *dataComponents + dataComponentsModified.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &prevValidMetaBlockV3 + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("prev header hash") + }, + } + arguments.DataComponents = &dataComponentsModified + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + mp.SetEpochStartData(&blproc.EpochStartDataWrapper{ + EpochStartData: &block.EpochStart{ + LastFinalizedHeaders: make([]block.EpochStartShardData, 3), + Economics: block.Economics{}, + }, + }) + header, err := mp.CreateNewHeaderProposal(1, 1) + require.Equal(t, expectedErr, err) + require.Nil(t, header) + }) + t.Run("with epoch start data in execution results and in meta block processor, error on set epoch start data", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ExecutionManager = &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return nil, nil + }, + } + + metaBlockWithValidExecutionResult := validMetaHeaderV3 + metaBlockWithValidExecutionResult.GetExecutionResultsHandlersCalled = func() []data.BaseExecutionResultHandler { + return validMetaExecutionResultsWithEpochChange + } + metaBlockWithValidExecutionResult.SetEpochStartHandlerCalled = func(_ data.EpochStartHandler) error { + return expectedErr + } + + bc := *defaultBootstrapComponents + bc.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return &metaBlockWithValidExecutionResult + }, + } + + arguments.BootstrapComponents = &bc + dataComponentsModified := *dataComponents + dataComponentsModified.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &prevValidMetaBlockV3 + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("prev header hash") + }, + } + arguments.DataComponents = &dataComponentsModified + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + mp.SetEpochStartData(&blproc.EpochStartDataWrapper{ + EpochStartData: &block.EpochStart{ + LastFinalizedHeaders: make([]block.EpochStartShardData, 3), + Economics: block.Economics{}, + }, + }) + header, err := mp.CreateNewHeaderProposal(1, 1) + require.Equal(t, expectedErr, err) + require.Nil(t, header) + }) + t.Run("without epoch start data in execution results, should pass and not change epoch", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ExecutionManager = &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return nil, nil + }, + } + + metaBlockWithValidExecutionResult := validMetaHeaderV3 + metaBlockWithValidExecutionResult.GetExecutionResultsHandlersCalled = func() []data.BaseExecutionResultHandler { + return validMetaExecutionResultsWithoutEpochChange + } + metaBlockWithValidExecutionResult.SetEpochCalled = func(epoch uint32) error { + require.Fail(t, "should not have been called") + return nil + } + + bc := *defaultBootstrapComponents + bc.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return &metaBlockWithValidExecutionResult + }, + } + + arguments.BootstrapComponents = &bc + dataComponentsModified := *dataComponents + dataComponentsModified.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &prevValidMetaBlockV3 + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("prev header hash") + }, + } + arguments.DataComponents = &dataComponentsModified + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header, err := mp.CreateNewHeaderProposal(1, 1) + require.Nil(t, err) + require.NotNil(t, header) + }) + t.Run("with epoch start data in execution results and in meta block processor, should pass and change epoch", func(t *testing.T) { + t.Parallel() + + headersPoolMock := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.MetaBlockV3{ + EpochChangeProposed: true, + }, nil + }, + } + + dataPool := initDataPool() + dataPool.HeadersCalled = func() dataRetriever.HeadersPool { + return headersPoolMock + } + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents.DataPool = dataPool + + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{ + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{}, + }, + } + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash1") + }, + } + + bootstrapComponents.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return &block.MetaBlockV3{ + Epoch: 0, + } + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + mp.SetEpochStartData(&blproc.EpochStartDataWrapper{ + Epoch: 1, + EpochStartData: &block.EpochStart{ + LastFinalizedHeaders: make([]block.EpochStartShardData, 3), + Economics: block.Economics{}, + }, + }) + + header, err := mp.CreateNewHeaderProposal(1, 1) + require.Nil(t, err) + require.NotNil(t, header) + }) + + t.Run("higher nonce in last execution result should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &prevValidMetaBlockV3 + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash1") + }, + } + + metaHeader := &testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { + return true + }, + GetNonceCalled: func() uint64 { + return 5 + }, + GetLastExecutionResultHandlerCalled: func() data.LastExecutionResultHandler { + return &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 105, + }, + }, + } + }, + } + + bootstrapComponents.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return metaHeader + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header, err := mp.CreateNewHeaderProposal(1, 5) + require.Nil(t, header) + require.ErrorIs(t, err, process.ErrInvalidLastExecutionResult) + }) + + t.Run("nonce gap from last exec result exceeds maximum allowed, should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { + return true + }, + GetLastExecutionResultHandlerCalled: func() data.LastExecutionResultHandler { + return &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 5, + }, + }, + } + }, + } + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash1") + }, + } + + metaHeader := &block.MetaBlockV3{ + Nonce: 105, + } + + bootstrapComponents.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return metaHeader + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header, err := mp.CreateNewHeaderProposal(1, 105) + require.Nil(t, header) + require.ErrorIs(t, err, process.ErrNonceGapTooLarge) + require.Contains(t, err.Error(), "from last execution") + require.Contains(t, err.Error(), "gap of 100") + }) + + t.Run("nonce gap exceeds maximum allowed, should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &prevValidMetaBlockV3 + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash1") + }, + } + + metaHeader := &block.MetaBlockV3{ + ShardInfo: []block.ShardData{ + { + ShardID: 0, + Nonce: 100, + }, + }, + ShardInfoProposal: []block.ShardDataProposal{ + { + ShardID: 0, + Nonce: 250, // 150 gap + HeaderHash: []byte("hash"), + }, + }, + } + + bootstrapComponents.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return metaHeader + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header, err := mp.CreateNewHeaderProposal(1, 1) + require.Nil(t, header) + require.ErrorIs(t, err, process.ErrNonceGapTooLarge) + require.Contains(t, err.Error(), "shard 0") + require.Contains(t, err.Error(), "gap of 150") + }) + + t.Run("error on GetLastCrossNotarizedHeadersForAllShards should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &prevValidMetaBlockV3 + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash1") + }, + } + + metaHeader := &block.MetaBlockV3{ + ShardInfo: []block.ShardData{ + { + ShardID: 0, + Nonce: 100, + }, + }, + ShardInfoProposal: []block.ShardDataProposal{ + { + ShardID: 0, + Nonce: 101, + HeaderHash: []byte("hash"), + }, + }, + } + bootstrapComponents.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return metaHeader + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.BlockTracker = &integrationTestsMock.BlockTrackerStub{ + GetLastCrossNotarizedHeadersForAllShardsCalled: func() (map[uint32]data.HeaderHandler, error) { + return nil, expectedError + }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header, err := mp.CreateNewHeaderProposal(1, 1) + require.Equal(t, expectedError, err) + require.Nil(t, header) + }) + + t.Run("missing last notarized in block tracker for included shard should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &prevValidMetaBlockV3 + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash1") + }, + } + + metaHeader := &block.MetaBlockV3{ + ShardInfo: []block.ShardData{ + { + ShardID: 0, + Nonce: 100, + }, + { + // this shard does not exist in block tracker + ShardID: 1, + Nonce: 250, + HeaderHash: []byte("hash2"), + }, + }, + ShardInfoProposal: []block.ShardDataProposal{ + { + ShardID: 0, + Nonce: 101, + HeaderHash: []byte("hash"), + }, + }, + } + bootstrapComponents.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return metaHeader + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header, err := mp.CreateNewHeaderProposal(1, 1) + require.Equal(t, process.ErrMissingCrossNotarizedHeader, err) + require.Nil(t, header) + }) + + t.Run("higher last notarized in block tracker should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &prevValidMetaBlockV3 + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash1") + }, + } + + metaHeader := &block.MetaBlockV3{ + ShardInfo: []block.ShardData{ + { + ShardID: 0, + Nonce: 100, + }, + }, + ShardInfoProposal: []block.ShardDataProposal{ + { + ShardID: 0, + Nonce: 101, + HeaderHash: []byte("hash"), + }, + }, + } + bootstrapComponents.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return metaHeader + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + crossNotarizedWithHigherNonce := &block.HeaderV3{Nonce: 102} + arguments.BlockTracker.AddCrossNotarizedHeader(0, crossNotarizedWithHigherNonce, []byte("hash higher nonce")) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header, err := mp.CreateNewHeaderProposal(1, 1) + require.Equal(t, process.ErrInvalidShardInfo, err) + require.Nil(t, header) + }) + + t.Run("missing last notarized in block tracker for proposed shard should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &prevValidMetaBlockV3 + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash1") + }, + } + + metaHeader := &block.MetaBlockV3{ + ShardInfo: []block.ShardData{ + { + ShardID: 0, + Nonce: 100, + }, + }, + ShardInfoProposal: []block.ShardDataProposal{ + { + ShardID: 0, + Nonce: 101, + HeaderHash: []byte("hash"), + }, + { + // this shard does not exist in block tracker + ShardID: 1, + Nonce: 250, + HeaderHash: []byte("hash2"), + }, + }, + } + bootstrapComponents.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return metaHeader + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header, err := mp.CreateNewHeaderProposal(1, 1) + require.Equal(t, process.ErrMissingCrossNotarizedHeader, err) + require.Nil(t, header) + }) + + t.Run("lower proposed nonce should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &prevValidMetaBlockV3 + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash1") + }, + } + + metaHeader := &block.MetaBlockV3{ + ShardInfo: []block.ShardData{ + { + ShardID: 0, + Nonce: 100, + }, + }, + ShardInfoProposal: []block.ShardDataProposal{ + { + ShardID: 0, + Nonce: 90, // lower + HeaderHash: []byte("hash"), + }, + }, + } + bootstrapComponents.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return metaHeader + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header, err := mp.CreateNewHeaderProposal(1, 1) + require.True(t, errors.Is(err, process.ErrInvalidProposedNonce)) + require.Contains(t, err.Error(), "proposed nonce 90") + require.Nil(t, header) + }) + + t.Run("nonce gap within allowed limit, should succeed", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &prevValidMetaBlockV3 + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash1") + }, + } + + metaHeader := &block.MetaBlockV3{ + ShardInfo: []block.ShardData{ + { + ShardID: 0, + Nonce: 100, + }, + }, + ShardInfoProposal: []block.ShardDataProposal{ + { + ShardID: 0, + Nonce: 101, + HeaderHash: []byte("hash"), + }, + }, + } + bootstrapComponents.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return metaHeader + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header, err := mp.CreateNewHeaderProposal(1, 1) + require.Nil(t, err) + require.NotNil(t, header) + }) +} + +func TestMetaProcessor_CreateBlockProposal(t *testing.T) { + t.Parallel() + + t.Run("nil header", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + checkCreateBlockProposalResult(t, mp, nil, haveTimeTrue, process.ErrNilBlockHeader) + }) + t.Run("not header v3", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + notV3Header := &block.MetaBlock{} + checkCreateBlockProposalResult(t, mp, notV3Header, haveTimeTrue, process.ErrInvalidHeader) + }) + t.Run("shard header v3", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + shardHeaderV3 := &block.HeaderV3{} + checkCreateBlockProposalResult(t, mp, shardHeaderV3, haveTimeTrue, process.ErrWrongTypeAssertion) + }) + t.Run("createBlockBodyProposal error (ComputeLongestShardsChainsFromLastNotarized error)", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.BlockTracker = &mock.BlockTrackerMock{ + ComputeLongestShardsChainsFromLastNotarizedCalled: func() ([]data.HeaderHandler, [][]byte, map[uint32][]data.HeaderHandler, error) { + return nil, nil, nil, expectedErr + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + validMetaHeaderV3 := &block.MetaBlockV3{} + checkCreateBlockProposalResult(t, mp, validMetaHeaderV3, haveTimeTrue, expectedErr) + }) + t.Run("createShardInfoV3 error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ShardInfoCreator = &processMocks.ShardInfoCreatorMock{ + CreateShardInfoV3Called: func(metaHeader data.MetaHeaderHandler, shardHeaders []data.HeaderHandler, shardHeaderHashes [][]byte) ([]data.ShardDataProposalHandler, []data.ShardDataHandler, error) { + return nil, nil, expectedErr + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + validMetaHeaderV3 := &block.MetaBlockV3{} + checkCreateBlockProposalResult(t, mp, validMetaHeaderV3, haveTimeTrue, expectedErr) + }) + t.Run("set shard info error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + GetMiniBlocksCalled: func() block.MiniBlockSlice { + return make([]*block.MiniBlock, 5) // coverage + }, + } + var invalidShardData data.ShardDataHandler + arguments.ShardInfoCreator = &processMocks.ShardInfoCreatorMock{ + CreateShardInfoV3Called: func(metaHeader data.MetaHeaderHandler, shardHeaders []data.HeaderHandler, shardHeaderHashes [][]byte) ([]data.ShardDataProposalHandler, []data.ShardDataHandler, error) { + return nil, []data.ShardDataHandler{invalidShardData}, nil + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + validMetaHeaderV3 := &block.MetaBlockV3{} + checkCreateBlockProposalResult(t, mp, validMetaHeaderV3, haveTimeTrue, data.ErrInvalidTypeAssertion) + }) + t.Run("set shard info proposal error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + var invalidShardDataProposal data.ShardDataProposalHandler + arguments.ShardInfoCreator = &processMocks.ShardInfoCreatorMock{ + CreateShardInfoV3Called: func(metaHeader data.MetaHeaderHandler, shardHeaders []data.HeaderHandler, shardHeaderHashes [][]byte) ([]data.ShardDataProposalHandler, []data.ShardDataHandler, error) { + return []data.ShardDataProposalHandler{invalidShardDataProposal}, []data.ShardDataHandler{}, nil + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + validMetaHeaderV3 := &block.MetaBlockV3{} + checkCreateBlockProposalResult(t, mp, validMetaHeaderV3, haveTimeTrue, data.ErrInvalidTypeAssertion) + }) + t.Run("set mini block header handlers error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + var invalidMiniBlockHeader data.MiniBlockHeaderHandler + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + GetMiniBlockHeaderHandlersCalled: func() []data.MiniBlockHeaderHandler { + return []data.MiniBlockHeaderHandler{invalidMiniBlockHeader} + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + validMetaHeaderV3 := &block.MetaBlockV3{} + checkCreateBlockProposalResult(t, mp, validMetaHeaderV3, haveTimeTrue, data.ErrInvalidTypeAssertion) + }) + t.Run("marshall error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + GetMiniBlockHeaderHandlersCalled: func() []data.MiniBlockHeaderHandler { return nil }, + } + cc := coreComponents + cc.IntMarsh = &testscommon.MarshallerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + return nil, expectedErr + }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + validMetaHeaderV3 := &block.MetaBlockV3{} + checkCreateBlockProposalResult(t, mp, validMetaHeaderV3, haveTimeTrue, expectedErr) + }) + t.Run("successful creation, non start of epoch block", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + GetMiniBlockHeaderHandlersCalled: func() []data.MiniBlockHeaderHandler { return nil }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + validMetaHeaderV3 := &block.MetaBlockV3{} + header, body, err := mp.CreateBlockProposal(validMetaHeaderV3, haveTimeTrue) + require.Nil(t, err) + require.NotNil(t, header) + require.NotNil(t, body) + }) + t.Run("no mini blocks added if epoch change propose set", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + GetMiniBlockHeaderHandlersCalled: func() []data.MiniBlockHeaderHandler { + require.Fail(t, "should not be called") + return nil + }, + } + arguments.EpochStartTrigger = &testscommon.EpochStartTriggerStub{ + GetEpochChangeProposedCalled: func() bool { + return true + }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + validMetaHeaderV3 := &block.MetaBlockV3{} + header, body, err := mp.CreateBlockProposal(validMetaHeaderV3, haveTimeTrue) + require.Nil(t, err) + require.NotNil(t, header) + require.NotNil(t, body) + }) + t.Run("successful creation, start of epoch block with empy body", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + GetMiniBlockHeaderHandlersCalled: func() []data.MiniBlockHeaderHandler { return nil }, + } + arguments.GasComputation = &testscommon.GasComputationMock{ + ResetCalled: func() { + require.Fail(t, "should not be called") + }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + // add epoch start data to the meta processor so that IsEpochStartBlock returns true + validMetaHeaderV3 := &block.MetaBlockV3{ + EpochStart: block.EpochStart{ + LastFinalizedHeaders: make([]block.EpochStartShardData, 3), + }, + } + header, body, err := mp.CreateBlockProposal(validMetaHeaderV3, haveTimeTrue) + require.Nil(t, err) + require.NotNil(t, header) + require.NotNil(t, body) + b := body.(*block.Body) + // start of epoch block should have no mini blocks headers and no mini blocks in the body + require.Len(t, header.GetMiniBlockHeaderHandlers(), 0) + require.Len(t, b.MiniBlocks, 0) + }) + t.Run("successful creation, epoch change proposal block with empy body", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + GetMiniBlockHeaderHandlersCalled: func() []data.MiniBlockHeaderHandler { return nil }, + } + arguments.GasComputation = &testscommon.GasComputationMock{ + ResetCalled: func() { + require.Fail(t, "should not be called") + }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + // add epoch start data to the meta processor so that IsEpochStartBlock returns true + validMetaHeaderV3 := &block.MetaBlockV3{ + EpochStart: block.EpochStart{ + LastFinalizedHeaders: make([]block.EpochStartShardData, 3), + }, + } + header, body, err := mp.CreateBlockProposal(validMetaHeaderV3, haveTimeTrue) + require.Nil(t, err) + require.NotNil(t, header) + require.NotNil(t, body) + b := body.(*block.Body) + // epoch change proposal should have no mini blocks headers and no mini blocks in the body + require.Len(t, header.GetMiniBlockHeaderHandlers(), 0) + require.Len(t, b.MiniBlocks, 0) + }) +} + +func TestMetaProcessor_VerifyBlockProposal(t *testing.T) { + t.Run("invalid body handler, should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + err = mp.VerifyBlockProposal(&block.MetaBlockV3{}, nil, haveTime) + require.ErrorIs(t, err, process.ErrNilBlockBody) + }) + t.Run("block hash does not match, should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = createTestBlockchain() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header := &block.MetaBlockV3{ + PrevHash: []byte("prevHash"), + Nonce: 1, + } + body := &block.Body{} + err = mp.VerifyBlockProposal(header, body, haveTime) + require.ErrorIs(t, err, process.ErrBlockHashDoesNotMatch) + }) + t.Run("invalid header handler, should error", func(t *testing.T) { + t.Parallel() + + prevBlockHash := []byte("prev header hash") + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents = &mock.DataComponentsMock{ + Storage: dataComponents.Storage, + DataPool: dataComponents.DataPool, + BlockChain: &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderHashCalled: func() []byte { + return prevBlockHash + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{} + }, + }, + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header := &block.MetaBlock{ + PrevHash: prevBlockHash, + Nonce: 1, + Round: 1, + } + body := &block.Body{} + err = mp.VerifyBlockProposal(header, body, haveTime) + require.ErrorIs(t, err, process.ErrWrongTypeAssertion) + }) + t.Run("header handler of type MetaBlock, should error", func(t *testing.T) { + t.Parallel() + + prevBlockHash := []byte("prev header hash") + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents = &mock.DataComponentsMock{ + Storage: dataComponents.Storage, + DataPool: dataComponents.DataPool, + BlockChain: &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderHashCalled: func() []byte { + return prevBlockHash + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{} + }, + }, + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header := &block.MetaBlock{ + PrevHash: prevBlockHash, + Nonce: 1, + Round: 1, + } + body := &block.Body{} + err = mp.VerifyBlockProposal(header, body, haveTime) + require.ErrorIs(t, err, process.ErrWrongTypeAssertion) + }) + t.Run("body handler of type BodyV3, should error", func(t *testing.T) { + t.Parallel() + + prevBlockHash := []byte("prev header hash") + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents = &mock.DataComponentsMock{ + Storage: dataComponents.Storage, + DataPool: dataComponents.DataPool, + BlockChain: &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderHashCalled: func() []byte { + return prevBlockHash + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{} + }, + }, + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header := &block.MetaBlockV3{ + PrevHash: prevBlockHash, + Nonce: 1, + Round: 1, + } + body := &wrongBody{} + err = mp.VerifyBlockProposal(header, body, haveTime) + require.ErrorIs(t, err, process.ErrWrongTypeAssertion) + }) + t.Run("body mismatch, should error", func(t *testing.T) { + t.Parallel() + + prevBlockHash := []byte("prev header hash") + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents = &mock.DataComponentsMock{ + Storage: dataComponents.Storage, + DataPool: dataComponents.DataPool, + BlockChain: &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderHashCalled: func() []byte { + return prevBlockHash + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{} + }, + }, + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header := &block.MetaBlockV3{ + PrevHash: prevBlockHash, + Nonce: 1, + Round: 1, + } + body := &block.Body{MiniBlocks: []*block.MiniBlock{ + {SenderShardID: 0}, + }} + err = mp.VerifyBlockProposal(header, body, haveTime) + require.ErrorIs(t, err, process.ErrHeaderBodyMismatch) + }) + t.Run("invalid header execution results, should error", func(t *testing.T) { + t.Parallel() + + prevBlockHash := []byte("prev header hash") + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents = &mock.DataComponentsMock{ + Storage: dataComponents.Storage, + DataPool: dataComponents.DataPool, + BlockChain: &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderHashCalled: func() []byte { + return prevBlockHash + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{} + }, + }, + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ExecutionResultsVerifier = &processMocks.ExecutionResultsVerifierMock{ + VerifyHeaderExecutionResultsCalled: func(header data.HeaderHandler) error { + return expectedErr + }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header := &block.MetaBlockV3{ + PrevHash: prevBlockHash, + Nonce: 1, + Round: 1, + } + body := &block.Body{} + err = mp.VerifyBlockProposal(header, body, haveTime) + require.ErrorIs(t, err, expectedErr) + }) + t.Run("error on nonce gap verification", func(t *testing.T) { + t.Parallel() + + prevBlockHash := []byte("prev header hash") + prevLastMetaExecutionResult := &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{}, + }, + } + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents = &mock.DataComponentsMock{ + Storage: dataComponents.Storage, + DataPool: dataComponents.DataPool, + BlockChain: &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderHashCalled: func() []byte { + return prevBlockHash + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{ + LastExecutionResult: prevLastMetaExecutionResult, + } + }, + }, + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.MissingDataResolver = &processMocks.MissingDataResolverMock{ + RequestMissingShardHeadersCalled: func(_ data.MetaHeaderHandler) error { + require.Fail(t, "should have not been called") + return nil + }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header := &block.MetaBlockV3{ + PrevHash: prevBlockHash, + Nonce: 1, + Round: 1, + LastExecutionResult: prevLastMetaExecutionResult, + ShardInfo: []block.ShardData{ + { + ShardID: 0, + Nonce: 100, + }, + }, + ShardInfoProposal: []block.ShardDataProposal{ + { + ShardID: 0, + Nonce: 250, // 150 gap + HeaderHash: []byte("hash"), + }, + }, + } + body := &block.Body{} + err = mp.VerifyBlockProposal(header, body, haveTime) + require.ErrorIs(t, err, process.ErrNonceGapTooLarge) + }) + t.Run("error on request missing shard header", func(t *testing.T) { + t.Parallel() + + prevBlockHash := []byte("prev header hash") + prevLastMetaExecutionResult := &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{}, + }, + } + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents = &mock.DataComponentsMock{ + Storage: dataComponents.Storage, + DataPool: dataComponents.DataPool, + BlockChain: &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderHashCalled: func() []byte { + return prevBlockHash + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{ + LastExecutionResult: prevLastMetaExecutionResult, + } + }, + }, + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.MissingDataResolver = &processMocks.MissingDataResolverMock{ + RequestMissingShardHeadersCalled: func(_ data.MetaHeaderHandler) error { + return expectedErr + }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header := &block.MetaBlockV3{ + PrevHash: prevBlockHash, + Nonce: 1, + Round: 1, + LastExecutionResult: prevLastMetaExecutionResult, + } + body := &block.Body{} + err = mp.VerifyBlockProposal(header, body, haveTime) + require.ErrorIs(t, err, expectedErr) + }) + t.Run("error on wait for missing data", func(t *testing.T) { + t.Parallel() + + prevBlockHash := []byte("prev header hash") + prevLastMetaExecutionResult := &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{}, + }, + } + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents = &mock.DataComponentsMock{ + Storage: dataComponents.Storage, + DataPool: dataComponents.DataPool, + BlockChain: &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderHashCalled: func() []byte { + return prevBlockHash + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{ + LastExecutionResult: prevLastMetaExecutionResult, + } + }, + }, + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.MissingDataResolver = &processMocks.MissingDataResolverMock{ + WaitForMissingDataCalled: func(_ time.Duration) error { + return expectedErr + }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header := &block.MetaBlockV3{ + PrevHash: prevBlockHash, + Nonce: 1, + Round: 1, + LastExecutionResult: prevLastMetaExecutionResult, + } + body := &block.Body{} + err = mp.VerifyBlockProposal(header, body, haveTime) + require.ErrorIs(t, err, expectedErr) + }) + t.Run("error on check epoch correctness v3", func(t *testing.T) { + t.Parallel() + + prevBlockHash := []byte("prev header hash") + prevLastMetaExecutionResult := &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{}, + }, + } + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents = &mock.DataComponentsMock{ + Storage: dataComponents.Storage, + DataPool: dataComponents.DataPool, + BlockChain: &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderHashCalled: func() []byte { + return prevBlockHash + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{ + LastExecutionResult: prevLastMetaExecutionResult, + } + }, + }, + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.MissingDataResolver = &processMocks.MissingDataResolverMock{ + WaitForMissingDataCalled: func(_ time.Duration) error { + return nil + }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header := &block.MetaBlockV3{ + PrevHash: prevBlockHash, + Nonce: 1, + Round: 1, + LastExecutionResult: prevLastMetaExecutionResult, + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + {}, {}, + }, + }, + } + body := &block.Body{} + err = mp.VerifyBlockProposal(header, body, haveTime) + require.ErrorIs(t, err, process.ErrEpochDoesNotMatch) + }) + t.Run("error on check shard headers validity and finality proposal", func(t *testing.T) { + t.Parallel() + + prevBlockHash := []byte("prev header hash") + prevLastMetaExecutionResult := &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{}, + }, + } + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents = &mock.DataComponentsMock{ + Storage: dataComponents.Storage, + DataPool: dataComponents.DataPool, + BlockChain: &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderHashCalled: func() []byte { + return prevBlockHash + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{ + LastExecutionResult: prevLastMetaExecutionResult, + } + }, + }, + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.BlockTracker = &integrationTestsMock.BlockTrackerStub{ + GetLastCrossNotarizedHeaderCalled: func(_ uint32) (data.HeaderHandler, []byte, error) { + return nil, make([]byte, 0), expectedErr + }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header := &block.MetaBlockV3{ + PrevHash: prevBlockHash, + Nonce: 1, + Round: 1, + LastExecutionResult: prevLastMetaExecutionResult, + } + body := &block.Body{} + err = mp.VerifyBlockProposal(header, body, haveTime) + require.ErrorIs(t, err, expectedErr) + }) + t.Run("verify block proposal, should work", func(t *testing.T) { + t.Parallel() + + prevBlockHash := []byte("prev header hash") + prevLastMetaExecutionResult := &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{}, + }, + } + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents = &mock.DataComponentsMock{ + Storage: dataComponents.Storage, + DataPool: dataComponents.DataPool, + BlockChain: &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderHashCalled: func() []byte { + return prevBlockHash + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{ + LastExecutionResult: prevLastMetaExecutionResult, + } + }, + }, + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header := &block.MetaBlockV3{ + PrevHash: prevBlockHash, + Nonce: 1, + Round: 1, + LastExecutionResult: prevLastMetaExecutionResult, + } + body := &block.Body{} + + err = mp.VerifyBlockProposal(header, body, haveTime) + require.NoError(t, err) + }) + t.Run("epoch change proposed with miniblocks in body, should error", func(t *testing.T) { + t.Parallel() + + prevBlockHash := []byte("prev header hash") + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents = &mock.DataComponentsMock{ + Storage: dataComponents.Storage, + DataPool: dataComponents.DataPool, + BlockChain: &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderHashCalled: func() []byte { + return prevBlockHash + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{} + }, + }, + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + header := &block.MetaBlockV3{ + PrevHash: prevBlockHash, + Nonce: 1, + Round: 1, + EpochChangeProposed: true, + } + body := &block.Body{MiniBlocks: []*block.MiniBlock{ + {SenderShardID: 0}, + }} + err = mp.VerifyBlockProposal(header, body, haveTime) + require.ErrorIs(t, err, process.ErrEpochStartProposeBlockHasMiniBlocks) + }) +} + +func Test_checkShardHeadersValidityAndFinalityProposal(t *testing.T) { + t.Parallel() + + t.Run("error on getting last cross notarized header", func(t *testing.T) { + t.Parallel() + + metaHeader := &block.MetaBlockV3{} + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "shardCoordinator": mock.NewOneShardCoordinatorMock(), + "blockTracker": &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(_ uint32) (data.HeaderHandler, []byte, error) { + return nil, nil, expectedErr + }, + }, + "epochStartTrigger": &testscommon.EpochStartTriggerStub{ + GetEpochChangeProposedCalled: func() bool { + return false + }, + }, + }) + require.Nil(t, err) + + err = mp.CheckShardHeadersValidityAndFinalityProposal(metaHeader) + require.ErrorIs(t, err, expectedErr) + }) + t.Run("error on getting shard headers from meta header", func(t *testing.T) { + t.Parallel() + + metaHeader := &block.MetaBlockV3{ + ShardInfoProposal: []block.ShardDataProposal{ + { + HeaderHash: []byte("hash"), + }, + }, + } + + headersPoolMock := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(_ []byte) (data.HeaderHandler, error) { + return nil, expectedErr + }, + } + dataPoolMock := &dataRetrieverMock.PoolsHolderMock{} + dataPoolMock.SetHeadersPool(headersPoolMock) + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "shardCoordinator": mock.NewOneShardCoordinatorMock(), + "blockTracker": &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(_ uint32) (data.HeaderHandler, []byte, error) { + return &testscommon.HeaderHandlerStub{}, nil, nil + }, + }, + "epochStartTrigger": &testscommon.EpochStartTriggerStub{ + GetEpochChangeProposedCalled: func() bool { + return false + }, + }, + "dataPool": dataPoolMock, + }) + require.Nil(t, err) + + err = mp.CheckShardHeadersValidityAndFinalityProposal(metaHeader) + require.ErrorIs(t, err, process.ErrMissingHeader) + }) + t.Run("error on missing header proof", func(t *testing.T) { + t.Parallel() + + metaHeader := &block.MetaBlockV3{ + ShardInfoProposal: []block.ShardDataProposal{ + { + HeaderHash: []byte("hash"), + }, + }, + } + + headersPoolMock := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(_ []byte) (data.HeaderHandler, error) { + return &block.MetaBlockV3{}, nil + }, + } + dataPoolMock := &dataRetrieverMock.PoolsHolderMock{} + dataPoolMock.SetHeadersPool(headersPoolMock) + + proofsPool := &dataRetrieverMock.ProofsPoolMock{ + HasProofCalled: func(_ uint32, _ []byte) bool { + return false + }, + } + dataPoolMock.SetProofsPool(proofsPool) + + marshaller := &marshal.GogoProtoMarshalizer{} + storage := &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + blockBytes, _ := marshaller.Marshal(&block.HeaderV3{}) + return blockBytes, nil + }, + }, nil + }, + } + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "marshalizer": marshaller, + "shardCoordinator": mock.NewOneShardCoordinatorMock(), + "blockTracker": &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(_ uint32) (data.HeaderHandler, []byte, error) { + return &testscommon.HeaderHandlerStub{}, nil, nil + }, + }, + "epochStartTrigger": &testscommon.EpochStartTriggerStub{ + GetEpochChangeProposedCalled: func() bool { + return false + }, + }, + "dataPool": dataPoolMock, + "proofsPool": proofsPool, + "store": storage, + }) + require.Nil(t, err) + + err = mp.CheckShardHeadersValidityAndFinalityProposal(metaHeader) + require.ErrorIs(t, err, process.ErrMissingHeaderProof) + }) + t.Run("invalid used shard headers, should error", func(t *testing.T) { + t.Parallel() + + metaHeader := &block.MetaBlockV3{ + ShardInfoProposal: []block.ShardDataProposal{ + { + HeaderHash: []byte("hash"), + }, + }, + } + + headersPoolMock := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(_ []byte) (data.HeaderHandler, error) { + return &block.MetaBlockV3{}, nil + }, + } + dataPoolMock := &dataRetrieverMock.PoolsHolderMock{} + dataPoolMock.SetHeadersPool(headersPoolMock) + + proofsPool := &dataRetrieverMock.ProofsPoolMock{ + HasProofCalled: func(_ uint32, _ []byte) bool { + return true + }, + } + dataPoolMock.SetProofsPool(proofsPool) + + marshaller := &marshal.GogoProtoMarshalizer{} + storage := &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + blockBytes, _ := marshaller.Marshal(&block.HeaderV3{}) + return blockBytes, nil + }, + }, nil + }, + } + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "shardCoordinator": mock.NewOneShardCoordinatorMock(), + "blockTracker": &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(_ uint32) (data.HeaderHandler, []byte, error) { + return &testscommon.HeaderHandlerStub{}, nil, nil + }, + }, + "epochStartTrigger": &testscommon.EpochStartTriggerStub{ + GetEpochChangeProposedCalled: func() bool { + return false + }, + }, + "dataPool": dataPoolMock, + "marshalizer": marshaller, + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{} + }, + }, + "headerValidator": &integrationTestsMock.HeaderValidatorStub{ + IsHeaderConstructionValidCalled: func(_, _ data.HeaderHandler) error { + return expectedErr + }, + }, + "proofsPool": proofsPool, + "store": storage, + }) + require.Nil(t, err) + + err = mp.CheckShardHeadersValidityAndFinalityProposal(metaHeader) + require.ErrorIs(t, err, expectedErr) + }) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + metaHeader := &block.MetaBlockV3{} + + headersPoolMock := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(_ []byte) (data.HeaderHandler, error) { + return &block.MetaBlockV3{}, nil + }, + } + dataPoolMock := &dataRetrieverMock.PoolsHolderMock{} + dataPoolMock.SetHeadersPool(headersPoolMock) + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "shardCoordinator": mock.NewOneShardCoordinatorMock(), + "blockTracker": &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(_ uint32) (data.HeaderHandler, []byte, error) { + return &testscommon.HeaderHandlerStub{}, nil, nil + }, + }, + "epochStartTrigger": &testscommon.EpochStartTriggerStub{ + GetEpochChangeProposedCalled: func() bool { + return false + }, + }, + "dataPool": dataPoolMock, + "marshalizer": &marshal.GogoProtoMarshalizer{}, + "proofsPool": &dataRetrieverMock.ProofsPoolMock{ + HasProofCalled: func(_ uint32, _ []byte) bool { + return true + }, + }, + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{} + }, + }, + "headerValidator": &integrationTestsMock.HeaderValidatorStub{ + IsHeaderConstructionValidCalled: func(_, _ data.HeaderHandler) error { + return nil + }, + }, + "shardInfoCreateData": &processMocks.ShardInfoCreatorMock{ + CreateShardInfoV3Called: func(_ data.MetaHeaderHandler, _ []data.HeaderHandler, _ [][]byte) ([]data.ShardDataProposalHandler, []data.ShardDataHandler, error) { + return []data.ShardDataProposalHandler{}, []data.ShardDataHandler{}, nil + }, + }, + }) + require.Nil(t, err) + + err = mp.CheckShardHeadersValidityAndFinalityProposal(metaHeader) + require.Nil(t, err) + }) +} + +func Test_getTxCountExecutionResults(t *testing.T) { + t.Parallel() + + t.Run("nil meta block", func(t *testing.T) { + t.Parallel() + + txCount, err := blproc.GetTxCountExecutionResults(nil) + require.Nil(t, err) + require.Equal(t, uint32(0), txCount) + }) + t.Run("no execution results notarized", func(t *testing.T) { + t.Parallel() + + metaBlock := &block.MetaBlockV3{} + txCount, err := blproc.GetTxCountExecutionResults(metaBlock) + require.Nil(t, err) + require.Equal(t, uint32(0), txCount) + }) + t.Run("empty execution results notarized", func(t *testing.T) { + t.Parallel() + + metaBlock := &block.MetaBlockV3{ + ExecutionResults: []*block.MetaExecutionResult{{}, {}}, + } + txCount, err := blproc.GetTxCountExecutionResults(metaBlock) + require.Nil(t, err) + require.Equal(t, uint32(0), txCount) + }) + t.Run("invalid execution result in notarized list", func(t *testing.T) { + t.Parallel() + + var metaExecutionResult *block.BaseExecutionResult + metaBlock := &testscommon.HeaderHandlerStub{ + GetExecutionResultsHandlersCalled: func() []data.BaseExecutionResultHandler { + return []data.BaseExecutionResultHandler{ + metaExecutionResult, + } + }, + } + + txCount, err := blproc.GetTxCountExecutionResults(metaBlock) + require.Equal(t, process.ErrWrongTypeAssertion, err) + require.Equal(t, uint32(0), txCount) + }) + t.Run("execution results notarized", func(t *testing.T) { + t.Parallel() + + metaBlock := &block.MetaBlockV3{ + ExecutionResults: []*block.MetaExecutionResult{ + { + ExecutedTxCount: 5, + }, + { + ExecutedTxCount: 10, + }, + }, + } + txCount, err := blproc.GetTxCountExecutionResults(metaBlock) + require.Nil(t, err) + require.Equal(t, uint32(15), txCount) + }) +} + +func TestMetaProcessor_hasStartOfEpochExecutionResults(t *testing.T) { + t.Parallel() + + mbHeaderWithEpochStartData := block.MiniBlockHeader{ + Hash: []byte("mb hash"), + SenderShardID: core.MetachainShardId, + Type: block.RewardsBlock, + } + t.Run("nil meta block", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + hasEpochStartData, err := mp.HasStartOfEpochExecutionResults(nil) + require.Equal(t, process.ErrNilHeaderHandler, err) + require.False(t, hasEpochStartData) + }) + t.Run("no executionResults", func(t *testing.T) { + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + validMetaHeaderV3 := &block.MetaBlockV3{} + hasEpochStartData, err := mp.HasStartOfEpochExecutionResults(validMetaHeaderV3) + require.Nil(t, err) + require.False(t, hasEpochStartData) + }) + t.Run("executionResults with invalid data", func(t *testing.T) { + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + validMetaHeaderV3 := &testscommon.HeaderHandlerStub{ + GetExecutionResultsHandlersCalled: func() []data.BaseExecutionResultHandler { + return []data.BaseExecutionResultHandler{ + &block.BaseExecutionResult{}, // invalid for meta block + } + }, + } + hasEpochStartData, err := mp.HasStartOfEpochExecutionResults(validMetaHeaderV3) + require.Equal(t, process.ErrWrongTypeAssertion, err) + require.False(t, hasEpochStartData) + }) + t.Run("executionResults without epoch start data", func(t *testing.T) { + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + mbHeader := mbHeaderWithEpochStartData + mbHeader.Type = block.TxBlock + validMetaHeaderV3 := &testscommon.HeaderHandlerStub{ + GetExecutionResultsHandlersCalled: func() []data.BaseExecutionResultHandler { + return []data.BaseExecutionResultHandler{ + &block.MetaExecutionResult{MiniBlockHeaders: []block.MiniBlockHeader{mbHeader}}} + }, + } + + hasEpochStartData, err := mp.HasStartOfEpochExecutionResults(validMetaHeaderV3) + require.Nil(t, err) + require.False(t, hasEpochStartData) + }) + t.Run("executionResults with reward miniBlocks epoch start data not from meta", func(t *testing.T) { + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + mbHeader := mbHeaderWithEpochStartData + mbHeader.SenderShardID = 0 + validMetaHeaderV3 := &testscommon.HeaderHandlerStub{ + GetExecutionResultsHandlersCalled: func() []data.BaseExecutionResultHandler { + return []data.BaseExecutionResultHandler{ + &block.MetaExecutionResult{MiniBlockHeaders: []block.MiniBlockHeader{mbHeader}}} + }, + } + + hasEpochStartData, err := mp.HasStartOfEpochExecutionResults(validMetaHeaderV3) + require.Nil(t, err) + require.False(t, hasEpochStartData) + }) + t.Run("executionResults with peer miniBlocks epoch start data not from meta", func(t *testing.T) { + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + mbHeader := mbHeaderWithEpochStartData + mbHeader.SenderShardID = 0 + mbHeader.Type = block.PeerBlock + validMetaHeaderV3 := &testscommon.HeaderHandlerStub{ + GetExecutionResultsHandlersCalled: func() []data.BaseExecutionResultHandler { + return []data.BaseExecutionResultHandler{ + &block.MetaExecutionResult{MiniBlockHeaders: []block.MiniBlockHeader{mbHeader}}} + }, + } + + hasEpochStartData, err := mp.HasStartOfEpochExecutionResults(validMetaHeaderV3) + require.Nil(t, err) + require.False(t, hasEpochStartData) + }) + t.Run("executionResults with reward miniBlocks epoch start data from meta", func(t *testing.T) { + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + mbHeader := mbHeaderWithEpochStartData + mbHeader.Type = block.RewardsBlock + validMetaHeaderV3 := &testscommon.HeaderHandlerStub{ + GetExecutionResultsHandlersCalled: func() []data.BaseExecutionResultHandler { + return []data.BaseExecutionResultHandler{ + &block.MetaExecutionResult{MiniBlockHeaders: []block.MiniBlockHeader{mbHeader}}} + }, + } + + hasEpochStartData, err := mp.HasStartOfEpochExecutionResults(validMetaHeaderV3) + require.Nil(t, err) + require.True(t, hasEpochStartData) + }) + t.Run("executionResults with peer miniBlocks epoch start data from meta", func(t *testing.T) { + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + mbHeader := mbHeaderWithEpochStartData + mbHeader.Type = block.PeerBlock + validMetaHeaderV3 := &testscommon.HeaderHandlerStub{ + GetExecutionResultsHandlersCalled: func() []data.BaseExecutionResultHandler { + return []data.BaseExecutionResultHandler{ + &block.MetaExecutionResult{MiniBlockHeaders: []block.MiniBlockHeader{mbHeader}}} + }, + } + + hasEpochStartData, err := mp.HasStartOfEpochExecutionResults(validMetaHeaderV3) + require.Nil(t, err) + require.True(t, hasEpochStartData) + }) +} + +func Test_hasRewardOrPeerMiniBlocksFromSelf(t *testing.T) { + t.Parallel() + + t.Run("nil miniBlocks", func(t *testing.T) { + t.Parallel() + response := blproc.HasRewardOrPeerMiniBlocksFromMeta(nil) + require.False(t, response) + }) + t.Run("no miniBlocks", func(t *testing.T) { + t.Parallel() + response := blproc.HasRewardOrPeerMiniBlocksFromMeta([]data.MiniBlockHeaderHandler{}) + require.False(t, response) + }) + t.Run("with reward miniBlocks from different shard", func(t *testing.T) { + t.Parallel() + miniBlocks := []data.MiniBlockHeaderHandler{ + &block.MiniBlockHeader{ + SenderShardID: 1, + Type: block.RewardsBlock, + }, + } + response := blproc.HasRewardOrPeerMiniBlocksFromMeta(miniBlocks) + require.False(t, response) + }) + t.Run("only tx miniBlocks", func(t *testing.T) { + t.Parallel() + miniBlocks := []data.MiniBlockHeaderHandler{ + &block.MiniBlockHeader{ + SenderShardID: common.MetachainShardId, // although not possible in combination with txblock + Type: block.TxBlock, + }, + } + response := blproc.HasRewardOrPeerMiniBlocksFromMeta(miniBlocks) + require.False(t, response) + }) + t.Run("with reward miniBlocks from meta shard", func(t *testing.T) { + t.Parallel() + miniBlocks := []data.MiniBlockHeaderHandler{ + &block.MiniBlockHeader{ + SenderShardID: common.MetachainShardId, + Type: block.RewardsBlock, + }, + } + response := blproc.HasRewardOrPeerMiniBlocksFromMeta(miniBlocks) + require.True(t, response) + }) + t.Run("with peer miniBlocks from meta shard", func(t *testing.T) { + t.Parallel() + miniBlocks := []data.MiniBlockHeaderHandler{ + &block.MiniBlockHeader{ + SenderShardID: common.MetachainShardId, + Type: block.PeerBlock, + }, + } + response := blproc.HasRewardOrPeerMiniBlocksFromMeta(miniBlocks) + require.True(t, response) + }) +} + +func TestMetaProcessor_createProposalMiniBlocks(t *testing.T) { + t.Parallel() + miniblockSelectionSessionNoAdd := &mbSelection.MiniBlockSelectionSessionStub{ + AddMiniBlocksAndHashesCalled: func(miniBlocksAndHashes []block.MiniblockAndHash) error { + require.Fail(t, "miniBlocksAndHashes should not be called") + return nil + }, + AddReferencedHeaderCalled: func(metaBlock data.HeaderHandler, metaBlockHash []byte) { + require.Fail(t, "AddReferencedHeader should not be called") + }, + CreateAndAddMiniBlockFromTransactionsCalled: func(txHashes [][]byte) error { + require.Fail(t, "CreateAndAddMiniBlockFromTransactions should not be called") + return nil + }, + } + t.Run("no time", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.MiniBlocksSelectionSession = miniblockSelectionSessionNoAdd + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + err = mp.CreateProposalMiniBlocks(haveTimeFalse) + require.Nil(t, err) + }) + t.Run("with time and error returned by selectIncomingMiniBlocksForProposal", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.BlockTracker = &mock.BlockTrackerMock{ + ComputeLongestShardsChainsFromLastNotarizedCalled: func() ([]data.HeaderHandler, [][]byte, map[uint32][]data.HeaderHandler, error) { + return nil, nil, nil, expectedErr + }, + } + arguments.MiniBlocksSelectionSession = miniblockSelectionSessionNoAdd + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + err = mp.CreateProposalMiniBlocks(haveTimeTrue) + require.Equal(t, expectedErr, err) + }) + t.Run("with time and no error, no mini blocks/shard headers", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.MiniBlocksSelectionSession = miniblockSelectionSessionNoAdd + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + err = mp.CreateProposalMiniBlocks(haveTimeTrue) + require.Nil(t, err) + }) +} + +func TestMetaProcessor_selectIncomingMiniBlocksForProposal(t *testing.T) { + t.Parallel() + + t.Run("error from ComputeLongestShardsChainsFromLastNotarized", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.BlockTracker = &mock.BlockTrackerMock{ + ComputeLongestShardsChainsFromLastNotarizedCalled: func() ([]data.HeaderHandler, [][]byte, map[uint32][]data.HeaderHandler, error) { + return nil, nil, nil, expectedErr + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + err = mp.SelectIncomingMiniBlocksForProposal(haveTimeTrue) + require.Equal(t, expectedErr, err) + }) + t.Run("error from getLastCrossNotarizedShardHeaders", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.BlockTracker = &mock.BlockTrackerMock{ + ComputeLongestShardsChainsFromLastNotarizedCalled: func() ([]data.HeaderHandler, [][]byte, map[uint32][]data.HeaderHandler, error) { + return []data.HeaderHandler{}, [][]byte{}, nil, nil + }, + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return nil, nil, expectedErr + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + err = mp.SelectIncomingMiniBlocksForProposal(haveTimeTrue) + require.Equal(t, expectedErr, err) + }) + t.Run("selection ok", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + err = mp.SelectIncomingMiniBlocksForProposal(haveTimeTrue) + require.Nil(t, err) + }) +} + +func TestMetaProcessor_selectIncomingMiniBlocks(t *testing.T) { + t.Parallel() + + t.Run("no ordered headers", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + AddMiniBlocksAndHashesCalled: func(miniBlocksAndHashes []block.MiniblockAndHash) error { + require.Fail(t, "should not be called") + return nil + }, + AddReferencedHeaderCalled: func(metaBlock data.HeaderHandler, metaBlockHash []byte) { + require.Fail(t, "should not be called") + }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + lastShardHeaders := createLastShardHeadersNotGenesis() + var orderedHeaders []data.HeaderHandler + var orderedHeaderHashes [][]byte + + maxNumHeadersFromSameShard := uint32(2) + err = mp.SelectIncomingMiniBlocks(lastShardHeaders, orderedHeaders, orderedHeaderHashes, maxNumHeadersFromSameShard, haveTimeTrue) + require.Nil(t, err) + }) + + t.Run("time is up before processing any header", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + // ensure proofs exist but haveTime will stop immediately + pools := dataComponents.DataPool + if ph, ok := pools.(*dataRetrieverMock.PoolsHolderStub); ok { + ph.ProofsCalled = func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{HasProofCalled: func(shardID uint32, headerHash []byte) bool { return true }} + } + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + addRefCnt := 0 + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + AddReferencedHeaderCalled: func(metaBlock data.HeaderHandler, metaBlockHash []byte) { + addRefCnt++ + }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + lastShardHeaders := createLastShardHeadersNotGenesis() + h := &testscommon.HeaderHandlerStub{ + GetShardIDCalled: func() uint32 { return 0 }, + GetNonceCalled: func() uint64 { return 11 }, + GetMiniBlockHeadersWithDstCalled: func(destId uint32) map[string]uint32 { return map[string]uint32{"x": 1} }, + } + orderedHeaders := []data.HeaderHandler{h} + orderedHeaderHashes := [][]byte{[]byte("h1")} + + err = mp.SelectIncomingMiniBlocks(lastShardHeaders, orderedHeaders, orderedHeaderHashes, 2, haveTimeFalse) + require.Nil(t, err) + require.Equal(t, 0, addRefCnt) + }) + + t.Run("maximum shard headers allowed in one meta block reached (max=0)", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + pools := dataComponents.DataPool + if ph, ok := pools.(*dataRetrieverMock.PoolsHolderStub); ok { + ph.ProofsCalled = func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{HasProofCalled: func(shardID uint32, headerHash []byte) bool { return true }} + } + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + called := 0 + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + AddReferencedHeaderCalled: func(metaBlock data.HeaderHandler, metaBlockHash []byte) { called++ }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + lastShardHeaders := createLastShardHeadersNotGenesis() + h := &testscommon.HeaderHandlerStub{ + GetShardIDCalled: func() uint32 { return 0 }, + GetNonceCalled: func() uint64 { return 11 }, + GetMiniBlockHeadersWithDstCalled: func(destId uint32) map[string]uint32 { return map[string]uint32{"x": 1} }, + } + err = mp.SelectIncomingMiniBlocks(lastShardHeaders, []data.HeaderHandler{h}, [][]byte{[]byte("h1")}, 0, haveTimeTrue) + require.Nil(t, err) + require.Equal(t, 0, called) + }) + + t.Run("skip header due to nonce gap", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + pools := dataComponents.DataPool + if ph, ok := pools.(*dataRetrieverMock.PoolsHolderStub); ok { + ph.ProofsCalled = func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{HasProofCalled: func(shardID uint32, headerHash []byte) bool { return true }} + } + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + cntAddRef := 0 + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + AddReferencedHeaderCalled: func(metaBlock data.HeaderHandler, metaBlockHash []byte) { cntAddRef++ }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + lastShardHeaders := createLastShardHeadersNotGenesis() + // last nonce for shard 0 is 10 -> header has 12 so gap > 1 triggers continue + h := &testscommon.HeaderHandlerStub{ + GetShardIDCalled: func() uint32 { return 0 }, + GetNonceCalled: func() uint64 { return 12 }, + GetMiniBlockHeadersWithDstCalled: func(destId uint32) map[string]uint32 { return map[string]uint32{"x": 1} }, + } + err = mp.SelectIncomingMiniBlocks(lastShardHeaders, []data.HeaderHandler{h}, [][]byte{[]byte("h1")}, 2, haveTimeTrue) + require.Nil(t, err) + require.Equal(t, 0, cntAddRef) + }) + + t.Run("skip header due to per-shard limit", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + pools := dataComponents.DataPool + if ph, ok := pools.(*dataRetrieverMock.PoolsHolderStub); ok { + ph.ProofsCalled = func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{HasProofCalled: func(shardID uint32, headerHash []byte) bool { return true }} + } + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + cntAddRef := 0 + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + AddReferencedHeaderCalled: func(metaBlock data.HeaderHandler, metaBlockHash []byte) { cntAddRef++ }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + lastShardHeaders := createLastShardHeadersNotGenesis() + h1 := &testscommon.HeaderHandlerStub{GetShardIDCalled: func() uint32 { return 0 }, GetNonceCalled: func() uint64 { return 11 }, GetMiniBlockHeadersWithDstCalled: func(uint32) map[string]uint32 { return map[string]uint32{} }} + h2 := &testscommon.HeaderHandlerStub{GetShardIDCalled: func() uint32 { return 0 }, GetNonceCalled: func() uint64 { return 12 }, GetMiniBlockHeadersWithDstCalled: func(uint32) map[string]uint32 { return map[string]uint32{} }} + err = mp.SelectIncomingMiniBlocks(lastShardHeaders, []data.HeaderHandler{h1, h2}, [][]byte{[]byte("h1"), []byte("h2")}, 1, haveTimeTrue) + require.Nil(t, err) + // only first header should be referenced + require.Equal(t, 1, cntAddRef) + // last shard header nonce for shard 0 should remain 11 due to per-shard limit preventing second update + require.Equal(t, uint64(11), lastShardHeaders[0].Header.GetNonce()) + }) + + t.Run("skip header due to missing proof", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + pools := dataComponents.DataPool + if ph, ok := pools.(*dataRetrieverMock.PoolsHolderStub); ok { + ph.ProofsCalled = func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{HasProofCalled: func(shardID uint32, headerHash []byte) bool { return false }} + } + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + cntAddRef := 0 + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + AddReferencedHeaderCalled: func(metaBlock data.HeaderHandler, metaBlockHash []byte) { cntAddRef++ }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + lastShardHeaders := createLastShardHeadersNotGenesis() + h := &testscommon.HeaderHandlerStub{GetShardIDCalled: func() uint32 { return 0 }, GetNonceCalled: func() uint64 { return 11 }, GetMiniBlockHeadersWithDstCalled: func(uint32) map[string]uint32 { return map[string]uint32{} }} + err = mp.SelectIncomingMiniBlocks(lastShardHeaders, []data.HeaderHandler{h}, [][]byte{[]byte("h1")}, 2, haveTimeTrue) + require.Nil(t, err) + require.Equal(t, 0, cntAddRef) + }) + + t.Run("no cross mini blocks with dst me -> add referenced header only", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + pools := dataComponents.DataPool + if ph, ok := pools.(*dataRetrieverMock.PoolsHolderStub); ok { + ph.ProofsCalled = func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{HasProofCalled: func(shardID uint32, headerHash []byte) bool { return true }} + } + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + cntAddRef := 0 + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + AddReferencedHeaderCalled: func(metaBlock data.HeaderHandler, metaBlockHash []byte) { cntAddRef++ }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + lastShardHeaders := createLastShardHeadersNotGenesis() + h := &testscommon.HeaderHandlerStub{GetShardIDCalled: func() uint32 { return 0 }, GetNonceCalled: func() uint64 { return 11 }, GetMiniBlockHeadersWithDstCalled: func(uint32) map[string]uint32 { return map[string]uint32{} }} + err = mp.SelectIncomingMiniBlocks(lastShardHeaders, []data.HeaderHandler{h}, [][]byte{[]byte("h1")}, 2, haveTimeTrue) + require.Nil(t, err) + require.Equal(t, 1, cntAddRef) + // last shard header updated and marked used + require.True(t, lastShardHeaders[0].UsedInBlock) + require.Equal(t, []byte("h1"), lastShardHeaders[0].Hash) + }) + + t.Run("createMbsCrossShardDstMe returns error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + pools := dataComponents.DataPool + if ph, ok := pools.(*dataRetrieverMock.PoolsHolderStub); ok { + ph.ProofsCalled = func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{HasProofCalled: func(shardID uint32, headerHash []byte) bool { return true }} + } + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + CreateMbsCrossShardDstMeCalled: func(header data.HeaderHandler, processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo) ([]block.MiniblockAndHash, []block.MiniblockAndHash, uint32, bool, bool, error) { + return nil, nil, 0, false, false, expectedErr + }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + lastShardHeaders := createLastShardHeadersNotGenesis() + h := &testscommon.HeaderHandlerStub{ + GetShardIDCalled: func() uint32 { return 0 }, + GetNonceCalled: func() uint64 { return 11 }, + GetMiniBlockHeadersWithDstCalled: func(uint32) map[string]uint32 { return map[string]uint32{"mb": 1} }, + } + err = mp.SelectIncomingMiniBlocks(lastShardHeaders, []data.HeaderHandler{h}, [][]byte{[]byte("h1")}, 2, haveTimeTrue) + require.Equal(t, expectedErr, err) + }) + + t.Run("pending mini blocks returned -> break without adding header", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + pools := dataComponents.DataPool + if ph, ok := pools.(*dataRetrieverMock.PoolsHolderStub); ok { + ph.ProofsCalled = func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{HasProofCalled: func(shardID uint32, headerHash []byte) bool { return true }} + } + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + cntAddRef := 0 + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{AddReferencedHeaderCalled: func(metaBlock data.HeaderHandler, metaBlockHash []byte) { cntAddRef++ }} + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + CreateMbsCrossShardDstMeCalled: func(header data.HeaderHandler, processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo) ([]block.MiniblockAndHash, []block.MiniblockAndHash, uint32, bool, bool, error) { + return nil, []block.MiniblockAndHash{{}}, 0, false, false, nil + }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + lastShardHeaders := createLastShardHeadersNotGenesis() + h1 := &testscommon.HeaderHandlerStub{GetShardIDCalled: func() uint32 { return 0 }, GetNonceCalled: func() uint64 { return 11 }, GetMiniBlockHeadersWithDstCalled: func(uint32) map[string]uint32 { return map[string]uint32{"mb": 1} }} + h2 := &testscommon.HeaderHandlerStub{GetShardIDCalled: func() uint32 { return 0 }, GetNonceCalled: func() uint64 { return 12 }, GetMiniBlockHeadersWithDstCalled: func(uint32) map[string]uint32 { return map[string]uint32{"mb": 1} }} + err = mp.SelectIncomingMiniBlocks(lastShardHeaders, []data.HeaderHandler{h1, h2}, [][]byte{[]byte("h1"), []byte("h2")}, 2, haveTimeTrue) + require.Nil(t, err) + require.Equal(t, 0, cntAddRef) + // ensure second header was not processed due to break after first + require.Equal(t, uint64(10), lastShardHeaders[0].Header.GetNonce()) + }) + + t.Run("success: miniblocks added and header referenced", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + pools := dataComponents.DataPool + if ph, ok := pools.(*dataRetrieverMock.PoolsHolderStub); ok { + ph.ProofsCalled = func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{HasProofCalled: func(shardID uint32, headerHash []byte) bool { return true }} + } + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + cntAddRef := 0 + cntAddMbs := 0 + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + AddReferencedHeaderCalled: func(metaBlock data.HeaderHandler, metaBlockHash []byte) { cntAddRef++ }, + AddMiniBlocksAndHashesCalled: func(miniBlocksAndHashes []block.MiniblockAndHash) error { cntAddMbs++; return nil }, + } + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + CreateMbsCrossShardDstMeCalled: func(header data.HeaderHandler, processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo) ([]block.MiniblockAndHash, []block.MiniblockAndHash, uint32, bool, bool, error) { + return []block.MiniblockAndHash{{}}, nil, 3, true, false, nil + }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + lastShardHeaders := createLastShardHeadersNotGenesis() + h := &testscommon.HeaderHandlerStub{GetShardIDCalled: func() uint32 { return 0 }, GetNonceCalled: func() uint64 { return 11 }, GetMiniBlockHeadersWithDstCalled: func(uint32) map[string]uint32 { return map[string]uint32{"mb": 1} }} + err = mp.SelectIncomingMiniBlocks(lastShardHeaders, []data.HeaderHandler{h}, [][]byte{[]byte("h1")}, 2, haveTimeTrue) + require.Nil(t, err) + require.Equal(t, 1, cntAddMbs) + require.Equal(t, 1, cntAddRef) + // last shard header updated and marked used + require.True(t, lastShardHeaders[0].UsedInBlock) + require.Equal(t, []byte("h1"), lastShardHeaders[0].Hash) + }) +} + +func TestMetaProcessor_selectIncomingMiniBlocks_GapsAndDuplicates(t *testing.T) { + t.Parallel() + + // helper to build a MetaProcessor with proofs pool behavior + type metaSel interface { + SelectIncomingMiniBlocks(lastShardHdr map[uint32]blproc.ShardHeaderInfo, orderedHdrs []data.HeaderHandler, orderedHdrsHashes [][]byte, maxNumHeadersFromSameShard uint32, haveTime func() bool) error + } + buildMp := func(hasProofFn func(shardID uint32, headerHash []byte) bool) metaSel { + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + pools := dataComponents.DataPool + if ph, ok := pools.(*dataRetrieverMock.PoolsHolderStub); ok { + ph.ProofsCalled = func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{HasProofCalled: hasProofFn} + } + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + return mp + } + + t.Run("inconsistent ordered headers and hashes lengths -> error", func(t *testing.T) { + t.Parallel() + + mp := buildMp(func(uint32, []byte) bool { return true }) + lastShardHeaders := createLastShardHeadersNotGenesis() + h := &testscommon.HeaderHandlerStub{GetShardIDCalled: func() uint32 { return 0 }, GetNonceCalled: func() uint64 { return 11 }, GetMiniBlockHeadersWithDstCalled: func(uint32) map[string]uint32 { return map[string]uint32{} }} + err := mp.SelectIncomingMiniBlocks(lastShardHeaders, []data.HeaderHandler{h}, [][]byte{}, 2, haveTimeTrue) + require.Equal(t, process.ErrInconsistentShardHeadersAndHashes, err) + }) + + t.Run("missing last shard header for ordered header -> error", func(t *testing.T) { + t.Parallel() + + mp := buildMp(func(uint32, []byte) bool { return true }) + lastShardHeaders := createLastShardHeadersNotGenesis() + // header from shard 99, not present in lastShardHeaders map + h := &testscommon.HeaderHandlerStub{GetShardIDCalled: func() uint32 { return 99 }, GetNonceCalled: func() uint64 { return 1 }, GetMiniBlockHeadersWithDstCalled: func(uint32) map[string]uint32 { return map[string]uint32{} }} + err := mp.SelectIncomingMiniBlocks(lastShardHeaders, []data.HeaderHandler{h}, [][]byte{[]byte("h1")}, 2, haveTimeTrue) + require.Equal(t, process.ErrMissingHeader, err) + }) + + t.Run("duplicate nonce: first has proof accepted, second skipped", func(t *testing.T) { + t.Parallel() + + cntAddRef := 0 + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + pools := dataComponents.DataPool + if ph, ok := pools.(*dataRetrieverMock.PoolsHolderStub); ok { + ph.ProofsCalled = func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{HasProofCalled: func(uint32, []byte) bool { return true }} + } + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + AddReferencedHeaderCalled: func(metaBlock data.HeaderHandler, metaBlockHash []byte) { cntAddRef++ }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + lastShardHeaders := createLastShardHeadersNotGenesis() + h1 := &testscommon.HeaderHandlerStub{GetShardIDCalled: func() uint32 { return 0 }, GetNonceCalled: func() uint64 { return 11 }, GetMiniBlockHeadersWithDstCalled: func(uint32) map[string]uint32 { return map[string]uint32{} }} + h2 := &testscommon.HeaderHandlerStub{GetShardIDCalled: func() uint32 { return 0 }, GetNonceCalled: func() uint64 { return 11 }, GetMiniBlockHeadersWithDstCalled: func(uint32) map[string]uint32 { return map[string]uint32{} }} + err = mp.SelectIncomingMiniBlocks(lastShardHeaders, []data.HeaderHandler{h1, h2}, [][]byte{[]byte("h1"), []byte("h2")}, 2, haveTimeTrue) + require.Nil(t, err) + require.Equal(t, 1, cntAddRef) + // last shard header updated to first hash and used + require.True(t, lastShardHeaders[0].UsedInBlock) + require.Equal(t, []byte("h1"), lastShardHeaders[0].Hash) + }) + + t.Run("duplicate nonce: first missing proof skipped, second with proof accepted", func(t *testing.T) { + t.Parallel() + + cntAddRef := 0 + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + pools := dataComponents.DataPool + if ph, ok := pools.(*dataRetrieverMock.PoolsHolderStub); ok { + ph.ProofsCalled = func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{HasProofCalled: func(_ uint32, hash []byte) bool { return string(hash) == "h2" }} + } + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + AddReferencedHeaderCalled: func(metaBlock data.HeaderHandler, metaBlockHash []byte) { cntAddRef++ }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + lastShardHeaders := createLastShardHeadersNotGenesis() + h1 := &testscommon.HeaderHandlerStub{GetShardIDCalled: func() uint32 { return 0 }, GetNonceCalled: func() uint64 { return 11 }, GetMiniBlockHeadersWithDstCalled: func(uint32) map[string]uint32 { return map[string]uint32{} }} + h2 := &testscommon.HeaderHandlerStub{GetShardIDCalled: func() uint32 { return 0 }, GetNonceCalled: func() uint64 { return 11 }, GetMiniBlockHeadersWithDstCalled: func(uint32) map[string]uint32 { return map[string]uint32{} }} + err = mp.SelectIncomingMiniBlocks(lastShardHeaders, []data.HeaderHandler{h1, h2}, [][]byte{[]byte("h1"), []byte("h2")}, 2, haveTimeTrue) + require.Nil(t, err) + require.Equal(t, 1, cntAddRef) + // last shard header updated to second hash and used + require.True(t, lastShardHeaders[0].UsedInBlock) + require.Equal(t, []byte("h2"), lastShardHeaders[0].Hash) + }) +} + +func TestMetaProcessor_hasExecutionResultsForProposedEpochChange(t *testing.T) { + t.Parallel() + + t.Run("should error because of GetHeaderByHash", func(t *testing.T) { + t.Parallel() + + metaHeader := &block.MetaBlockV3{ + ExecutionResults: []*block.MetaExecutionResult{ + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash1"), + }, + }, + }, + }, + } + + headersPoolMock := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, expectedErr + }, + } + + dataPool := initDataPool() + dataPool.HeadersCalled = func() dataRetriever.HeadersPool { + return headersPoolMock + } + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents.DataPool = dataPool + + dataComponents.Storage = &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + return nil, expectedErr + }, + }, nil + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.HasExecutionResultsForProposedEpochChange(metaHeader) + require.ErrorIs(t, err, process.ErrMissingHeader) + }) + + t.Run("should return ErrStartOfEpochExecutionResultsDoNotExist", func(t *testing.T) { + t.Parallel() + + metaHeader := &block.MetaBlockV3{ + ExecutionResults: []*block.MetaExecutionResult{ + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash0"), + }, + }, + }, + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash1"), + }, + }, + }, + }, + } + + headersPoolMock := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, []byte("headerHash1")) { + return &block.MetaBlockV3{ + EpochChangeProposed: true, + }, nil + } + return &block.MetaBlockV3{ + EpochChangeProposed: false, + }, nil + }, + } + + dataPool := initDataPool() + dataPool.HeadersCalled = func() dataRetriever.HeadersPool { + return headersPoolMock + } + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents.DataPool = dataPool + + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.HasExecutionResultsForProposedEpochChange(metaHeader) + require.Equal(t, process.ErrStartOfEpochExecutionResultsDoNotExist, err) + }) + + t.Run("should find header with epoch change proposal", func(t *testing.T) { + t.Parallel() + + metaHeader := &block.MetaBlockV3{ + ExecutionResults: []*block.MetaExecutionResult{ + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash0"), + }, + }, + }, + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash1"), + }, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + { + SenderShardID: common.MetachainShardId, + Type: block.RewardsBlock, + }, + }, + }, + }, + } + + headersPoolMock := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, []byte("headerHash1")) { + return &block.MetaBlockV3{ + EpochChangeProposed: true, + }, nil + } + return &block.MetaBlockV3{ + EpochChangeProposed: false, + }, nil + }, + } + + dataPool := initDataPool() + dataPool.HeadersCalled = func() dataRetriever.HeadersPool { + return headersPoolMock + } + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents.DataPool = dataPool + + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + proposedChange, err := mp.HasExecutionResultsForProposedEpochChange(metaHeader) + require.Nil(t, err) + require.True(t, proposedChange) + }) +} + +func TestMetaProcessor_checkEpochCorrectnessV3(t *testing.T) { + t.Parallel() + + executionResults := []*block.MetaExecutionResult{ + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash0"), + }, + }, + }, + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash1"), + }, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + { + SenderShardID: common.MetachainShardId, + Type: block.RewardsBlock, + }, + }, + }, + } + + t.Run("should return nil current header", func(t *testing.T) { + t.Parallel() + + metaHeader := &block.MetaBlockV3{ + ExecutionResults: []*block.MetaExecutionResult{ + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash1"), + }, + }, + }, + }, + } + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return nil + }, + }, + }) + require.Nil(t, err) + + err = mp.CheckEpochCorrectnessV3(metaHeader) + require.Nil(t, err) + }) + + t.Run("should error ErrNilHeaderHandler", func(t *testing.T) { + t.Parallel() + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{} + }, + }, + }) + require.Nil(t, err) + + err = mp.CheckEpochCorrectnessV3(nil) + require.Equal(t, process.ErrNilHeaderHandler, err) + }) + + t.Run("should return error hasExecutionResultsForProposedEpochChange", func(t *testing.T) { + t.Parallel() + + metaHeader := &block.MetaBlockV3{ + Epoch: 2, + ExecutionResults: []*block.MetaExecutionResult{ + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash0"), + }, + }, + }, + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash1"), + }, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + { + SenderShardID: common.MetachainShardId, + Type: block.RewardsBlock, + }, + }, + }, + }, + } + + headersPoolMock := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, expectedErr + }, + } + dataPoolMock := &dataRetrieverMock.PoolsHolderMock{} + dataPoolMock.SetHeadersPool(headersPoolMock) + + marshaller := &marshal.GogoProtoMarshalizer{} + storage := &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + blockBytes, _ := marshaller.Marshal(&block.MetaBlockV3{}) + return blockBytes, expectedErr + }, + }, nil + }, + } + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "dataPool": dataPoolMock, + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{ + Epoch: 1, + } + }, + }, + "marshalizer": &marshal.GogoProtoMarshalizer{}, + "store": storage, + "epochStartTrigger": &testscommon.EpochStartTriggerStub{ + EpochCalled: func() uint32 { + return 1 + }, + ShouldProposeEpochChangeCalled: func(round uint64, nonce uint64) bool { + return false + }, + }, + }) + require.Nil(t, err) + + err = mp.CheckEpochCorrectnessV3(metaHeader) + require.ErrorIs(t, err, process.ErrMissingHeader) + }) + + t.Run("should return error ErrEpochDoesNotMatch because of incomplete data", func(t *testing.T) { + t.Parallel() + + metaHeader := &block.MetaBlockV3{ + ExecutionResults: executionResults, + } + + headersPoolMock := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, []byte("headerHash1")) { + return &block.MetaBlockV3{ + EpochChangeProposed: true, + }, nil + } + return &block.MetaBlockV3{ + EpochChangeProposed: false, + }, nil + }, + } + dataPoolMock := &dataRetrieverMock.PoolsHolderMock{} + dataPoolMock.SetHeadersPool(headersPoolMock) + + marshaller := &marshal.GogoProtoMarshalizer{} + storage := &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + blockBytes, _ := marshaller.Marshal(&block.MetaBlockV3{}) + return blockBytes, nil + }, + }, nil + }, + } + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "dataPool": dataPoolMock, + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{} + }, + }, + "epochStartTrigger": &testscommon.EpochStartTriggerStub{EpochCalled: func() uint32 { + return 1 + }}, + "marshalizer": marshaller, + "store": storage, + }) + require.Nil(t, err) + + err = mp.CheckEpochCorrectnessV3(metaHeader) + require.Equal(t, process.ErrEpochDoesNotMatch, err) + }) + + t.Run("should return error ErrEpochDoesNotMatch because of no epoch start results", func(t *testing.T) { + t.Parallel() + + metaHeader := &block.MetaBlockV3{ + Epoch: 2, + EpochStart: block.EpochStart{}, + ExecutionResults: []*block.MetaExecutionResult{ + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash0"), + }, + }, + }, + }, + } + + headersPoolMock := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, []byte("headerHash1")) { + return &block.MetaBlockV3{ + EpochChangeProposed: true, + }, nil + } + return &block.MetaBlockV3{ + EpochChangeProposed: false, + }, nil + }, + } + dataPoolMock := &dataRetrieverMock.PoolsHolderMock{} + dataPoolMock.SetHeadersPool(headersPoolMock) + + marshaller := &marshal.GogoProtoMarshalizer{} + storage := &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + blockBytes, _ := marshaller.Marshal(&block.MetaBlockV3{}) + return blockBytes, nil + }, + }, nil + }, + } + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "dataPool": dataPoolMock, + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{ + Epoch: 1, + } + }, + }, + "epochStartTrigger": &testscommon.EpochStartTriggerStub{EpochCalled: func() uint32 { + return 1 + }}, + "marshalizer": marshaller, + "store": storage, + }) + require.Nil(t, err) + + err = mp.CheckEpochCorrectnessV3(metaHeader) + require.Equal(t, process.ErrEpochDoesNotMatch, err) + }) + + t.Run("should return error ErrEpochDoesNotMatch because of epoch not changed", func(t *testing.T) { + t.Parallel() + + epochStartData := block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + {}, {}, + }, + } + + metaHeader := &block.MetaBlockV3{ + Epoch: 1, + EpochStart: epochStartData, + ExecutionResults: executionResults, + } + + headersPoolMock := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, []byte("headerHash1")) { + return &block.MetaBlockV3{ + EpochChangeProposed: true, + }, nil + } + return &block.MetaBlockV3{ + EpochChangeProposed: false, + }, nil + }, + } + dataPoolMock := &dataRetrieverMock.PoolsHolderMock{} + dataPoolMock.SetHeadersPool(headersPoolMock) + + marshaller := &marshal.GogoProtoMarshalizer{} + storage := &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + blockBytes, _ := marshaller.Marshal(&block.MetaBlockV3{}) + return blockBytes, nil + }, + }, nil + }, + } + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "dataPool": dataPoolMock, + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{ + Epoch: 1, + } + }, + }, + "epochStartTrigger": &testscommon.EpochStartTriggerStub{EpochCalled: func() uint32 { + return 1 + }}, + "store": storage, + "marshalizer": marshaller, + }) + require.Nil(t, err) + mp.SetEpochStartData(&blproc.EpochStartDataWrapper{ + EpochStartData: &epochStartData, + }) + + err = mp.CheckEpochCorrectnessV3(metaHeader) + require.Equal(t, process.ErrEpochDoesNotMatch, err) + }) + + t.Run("should return error ErrEpochDoesNotMatch because of epoch is discontinuous", func(t *testing.T) { + t.Parallel() + + epochStartData := block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + {}, {}, + }, + } + + metaHeader := &block.MetaBlockV3{ + Epoch: 3, + EpochStart: epochStartData, + ExecutionResults: executionResults, + } + + headersPoolMock := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, []byte("headerHash1")) { + return &block.MetaBlockV3{ + EpochChangeProposed: true, + }, nil + } + return &block.MetaBlockV3{ + EpochChangeProposed: false, + }, nil + }, + } + dataPoolMock := &dataRetrieverMock.PoolsHolderMock{} + dataPoolMock.SetHeadersPool(headersPoolMock) + + marshaller := &marshal.GogoProtoMarshalizer{} + storage := &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + blockBytes, _ := marshaller.Marshal(&block.MetaBlockV3{}) + return blockBytes, nil + }, + }, nil + }, + } + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "dataPool": dataPoolMock, + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{ + Epoch: 1, + } + }, + }, + "epochStartTrigger": &testscommon.EpochStartTriggerStub{EpochCalled: func() uint32 { + return 1 + }}, + "marshalizer": &marshal.GogoProtoMarshalizer{}, + "store": storage, + }) + require.Nil(t, err) + mp.SetEpochStartData(&blproc.EpochStartDataWrapper{ + EpochStartData: &epochStartData, + }) + + err = mp.CheckEpochCorrectnessV3(metaHeader) + require.Equal(t, process.ErrEpochDoesNotMatch, err) + }) + t.Run("not equal epoch start data should error", func(t *testing.T) { + t.Parallel() + + epochStartData := block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + {}, {}, + }, + } + epochStartDataFromMetaProcessor := block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + {}, {}, {}, + }, + } + + metaHeader := &block.MetaBlockV3{ + Epoch: 2, + EpochStart: epochStartData, + ExecutionResults: executionResults, + } + + headersPoolMock := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, []byte("headerHash1")) { + return &block.MetaBlockV3{ + EpochChangeProposed: true, + }, nil + } + return &block.MetaBlockV3{ + EpochChangeProposed: false, + }, nil + }, + } + dataPoolMock := &dataRetrieverMock.PoolsHolderMock{} + dataPoolMock.SetHeadersPool(headersPoolMock) + + marshaller := &marshal.GogoProtoMarshalizer{} + storage := &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + blockBytes, _ := marshaller.Marshal(&block.MetaBlockV3{}) + return blockBytes, nil + }, + }, nil + }, + } + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "dataPool": dataPoolMock, + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{ + Epoch: 1, + } + }, + }, + "epochStartTrigger": &testscommon.EpochStartTriggerStub{EpochCalled: func() uint32 { + return 1 + }}, + "store": storage, + "marshalizer": &marshal.GogoProtoMarshalizer{}, + }) + mp.SetEpochStartData(&blproc.EpochStartDataWrapper{ + EpochStartData: &epochStartDataFromMetaProcessor, + }) + require.Nil(t, err) + + err = mp.CheckEpochCorrectnessV3(metaHeader) + require.Equal(t, process.ErrEpochDoesNotMatch, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + epochStartData := block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + {}, {}, + }, + } + metaHeader := &block.MetaBlockV3{ + Epoch: 2, + EpochStart: epochStartData, + ExecutionResults: executionResults, + } + + headersPoolMock := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, []byte("headerHash1")) { + return &block.MetaBlockV3{ + EpochChangeProposed: true, + }, nil + } + return &block.MetaBlockV3{ + EpochChangeProposed: false, + }, nil + }, + } + dataPoolMock := &dataRetrieverMock.PoolsHolderMock{} + dataPoolMock.SetHeadersPool(headersPoolMock) + + marshaller := &marshal.GogoProtoMarshalizer{} + storage := &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + blockBytes, _ := marshaller.Marshal(&block.MetaBlockV3{}) + return blockBytes, nil + }, + }, nil + }, + } + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "dataPool": dataPoolMock, + "marshalizer": &marshal.GogoProtoMarshalizer{}, + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{ + Epoch: 1, + } + }, + }, + "epochStartTrigger": &testscommon.EpochStartTriggerStub{EpochCalled: func() uint32 { + return 1 + }}, + "store": storage, + }) + mp.SetEpochStartData(&blproc.EpochStartDataWrapper{ + Epoch: 2, + EpochStartData: &block.EpochStart{ + LastFinalizedHeaders: epochStartData.LastFinalizedHeaders, + }, + }) + require.Nil(t, err) + + err = mp.CheckEpochCorrectnessV3(metaHeader) + require.Nil(t, err) + }) +} + +func TestMetaProcessor_checkShardInfoValidity(t *testing.T) { + t.Parallel() + + t.Run("should return error from CreateShardInfoV3", func(t *testing.T) { + t.Parallel() + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "shardInfoCreateData": &processMocks.ShardInfoCreatorMock{ + CreateShardInfoV3Called: func(metaHeader data.MetaHeaderHandler, shardHeaders []data.HeaderHandler, shardHeaderHashes [][]byte) ([]data.ShardDataProposalHandler, []data.ShardDataHandler, error) { + return nil, nil, expectedErr + }, + }, + }) + require.Nil(t, err) + + err = mp.CheckShardInfoValidity(nil, &blproc.UsedShardHeadersInfo{}) + require.ErrorContains(t, err, expectedErr.Error()) + }) + + t.Run("should return ErrHeaderShardDataMismatch error", func(t *testing.T) { + t.Parallel() + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "shardInfoCreateData": &processMocks.ShardInfoCreatorMock{ + CreateShardInfoV3Called: func(metaHeader data.MetaHeaderHandler, shardHeaders []data.HeaderHandler, shardHeaderHashes [][]byte) ([]data.ShardDataProposalHandler, []data.ShardDataHandler, error) { + return nil, []data.ShardDataHandler{ + &block.ShardData{}, + }, nil + }, + }, + }) + require.Nil(t, err) + + err = mp.CheckShardInfoValidity(&block.MetaBlockV3{ + ShardInfo: []block.ShardData{}, + }, &blproc.UsedShardHeadersInfo{}) + + require.Equal(t, process.ErrHeaderShardDataMismatch, err) + }) + + t.Run("should return ErrHeaderShardDataMismatch error because of createdShardInfo", func(t *testing.T) { + t.Parallel() + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "shardInfoCreateData": &processMocks.ShardInfoCreatorMock{ + CreateShardInfoV3Called: func(metaHeader data.MetaHeaderHandler, shardHeaders []data.HeaderHandler, shardHeaderHashes [][]byte) ([]data.ShardDataProposalHandler, []data.ShardDataHandler, error) { + return nil, []data.ShardDataHandler{ + &block.ShardData{ + Nonce: 0, + }, + }, nil + }, + }, + }) + require.Nil(t, err) + + err = mp.CheckShardInfoValidity(&block.MetaBlockV3{ + ShardInfo: []block.ShardData{ + { + Nonce: 2, + }, + }, + }, &blproc.UsedShardHeadersInfo{}) + + require.ErrorContains(t, err, process.ErrHeaderShardDataMismatch.Error()) + }) + + t.Run("should return ErrHeaderShardDataMismatch error because of createdShardInfoProposal", func(t *testing.T) { + t.Parallel() + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "shardInfoCreateData": &processMocks.ShardInfoCreatorMock{ + CreateShardInfoV3Called: func(metaHeader data.MetaHeaderHandler, shardHeaders []data.HeaderHandler, shardHeaderHashes [][]byte) ([]data.ShardDataProposalHandler, []data.ShardDataHandler, error) { + return []data.ShardDataProposalHandler{ + &block.ShardDataProposal{ + Nonce: 0, + }, + }, []data.ShardDataHandler{ + &block.ShardData{ + Nonce: 0, + }, + }, nil + }, + }, + }) + require.Nil(t, err) + + err = mp.CheckShardInfoValidity(&block.MetaBlockV3{ + ShardInfo: []block.ShardData{ + { + Nonce: 0, + }, + }, + ShardInfoProposal: []block.ShardDataProposal{ + { + Nonce: 2, + }, + }, + }, &blproc.UsedShardHeadersInfo{}) + + require.ErrorContains(t, err, process.ErrHeaderShardDataMismatch.Error()) + }) +} + +func TestMetaProcessor_checkHeadersSequenceCorrectness(t *testing.T) { + t.Parallel() + + t.Run("should return error from IsHeaderConstructionValid", func(t *testing.T) { + t.Parallel() + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "headerValidator": &processMocks.HeaderValidatorMock{ + IsHeaderConstructionValidCalled: func(currHdr, prevHdr data.HeaderHandler) error { + return expectedErr + }, + }, + }) + require.Nil(t, err) + + err = mp.CheckHeadersSequenceCorrectness([]blproc.ShardHeaderInfo{ + { + Header: &block.Header{Nonce: 2}, + }, + }, blproc.ShardHeaderInfo{}) + require.Equal(t, expectedErr, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "headerValidator": &processMocks.HeaderValidatorMock{ + IsHeaderConstructionValidCalled: func(currHdr, prevHdr data.HeaderHandler) error { + return nil + }, + }, + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return nil + }, + }, + }) + require.Nil(t, err) + + err = mp.CheckHeadersSequenceCorrectness([]blproc.ShardHeaderInfo{ + { + Header: &block.Header{Nonce: 0}, + }, + { + Header: &block.Header{Nonce: 1}, + }, + }, blproc.ShardHeaderInfo{ + Header: &block.Header{Nonce: 0}, + }) + require.Nil(t, err) + }) +} + +func TestMetaProcessor_VerifyEpochStartData(t *testing.T) { + t.Parallel() + + t.Run("same epoch start data, should return true", func(t *testing.T) { + t.Parallel() + + lastFinalizedData := []block.EpochStartShardData{ + { + ShardID: 1, + Epoch: 1, + Nonce: 1, + HeaderHash: []byte("headerHash1"), + }, + } + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + arguments.EpochStartDataCreator = &mock.EpochStartDataCreatorStub{ + CreateEpochStartShardDataMetablockV3Called: func(metablock data.MetaHeaderHandler) ([]block.EpochStartShardData, error) { + return lastFinalizedData, nil + }, + } + + mp, _ := blproc.NewMetaProcessor(arguments) + mp.SetEpochStartData(&blproc.EpochStartDataWrapper{ + Epoch: 1, + EpochStartData: &block.EpochStart{ + LastFinalizedHeaders: lastFinalizedData, + Economics: block.Economics{}, + }, + }) + + epochStartData := &block.EpochStart{ + LastFinalizedHeaders: lastFinalizedData, + } + metaHeader := &block.MetaBlockV3{ + Epoch: 1, + EpochStart: *epochStartData, + } + + ok := mp.VerifyEpochStartData(metaHeader) + require.True(t, ok) + }) + + t.Run("different epoch start data, should return false", func(t *testing.T) { + t.Parallel() + + lastFinalizedData := []block.EpochStartShardData{ + { + ShardID: 1, + Epoch: 1, + Nonce: 1, + HeaderHash: []byte("headerHash1"), + }, + } + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + arguments.EpochStartDataCreator = &mock.EpochStartDataCreatorStub{ + CreateEpochStartShardDataMetablockV3Called: func(metablock data.MetaHeaderHandler) ([]block.EpochStartShardData, error) { + return lastFinalizedData, nil + }, + } + + mp, _ := blproc.NewMetaProcessor(arguments) + + lastFinalizedData2 := []block.EpochStartShardData{ + { + ShardID: 2, + Epoch: 2, + Nonce: 2, + HeaderHash: []byte("headerHash2"), + }, + } + epochStartData := &block.EpochStart{ + LastFinalizedHeaders: lastFinalizedData2, + } + metaHeader := &block.MetaBlockV3{ + Epoch: 3, + EpochStart: *epochStartData, + } + + ok := mp.VerifyEpochStartData(metaHeader) + require.False(t, ok) + }) +} + +func TestMetaProcessor_processIfFirstBlockAfterEpochStartBlockV3(t *testing.T) { + t.Parallel() + + t.Run("should return ErrWrongTypeAssertion error because of nil previous executed block", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain.SetLastExecutedBlockHeaderAndRootHash(&block.HeaderV3{}, nil, nil) + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + err = mp.ProcessIfFirstBlockAfterEpochStartBlockV3() + require.Equal(t, common.ErrWrongTypeAssertion, err) + }) + + t.Run("should return nil because it is not start of epoch block", func(t *testing.T) { + t.Parallel() + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "blockChain": &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{ + EpochStart: block.EpochStart{ + LastFinalizedHeaders: nil, + }, + } + }, + }, + }) + require.Nil(t, err) + + err = mp.ProcessIfFirstBlockAfterEpochStartBlockV3() + require.Nil(t, err) + }) + + t.Run("if SaveNodesCoordinatorUpdates fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "blockChain": &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{ + Epoch: 2, + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + {}, {}, {}, + }, + }, + } + }, + }, + "validatorStatisticsProcessor": &testscommon.ValidatorStatisticsProcessorStub{ + SaveNodesCoordinatorUpdatesCalled: func(epoch uint32) (bool, error) { + return false, expectedErr + }, + }, + }) + require.Nil(t, err) + + err = mp.ProcessIfFirstBlockAfterEpochStartBlockV3() + require.Equal(t, expectedErr, err) + }) + + t.Run("if ToggleUnStakeUnBondCalled fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "blockChain": &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{ + Epoch: 2, + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + {}, {}, {}, + }, + }, + } + }, + }, + "validatorStatisticsProcessor": &testscommon.ValidatorStatisticsProcessorStub{ + SaveNodesCoordinatorUpdatesCalled: func(epoch uint32) (bool, error) { + return true, nil + }, + }, + "epochSystemSCProcessor": &testscommon.EpochStartSystemSCStub{ + ToggleUnStakeUnBondCalled: func(value bool) error { + return expectedErr + }, + }, + }) + require.Nil(t, err) + + err = mp.ProcessIfFirstBlockAfterEpochStartBlockV3() + require.Equal(t, expectedErr, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "blockChain": &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{ + Epoch: 2, + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + {}, {}, {}, + }, + }, + } + }, + }, + "validatorStatisticsProcessor": &testscommon.ValidatorStatisticsProcessorStub{ + SaveNodesCoordinatorUpdatesCalled: func(epoch uint32) (bool, error) { + return true, nil + }, + }, + "epochSystemSCProcessor": &testscommon.EpochStartSystemSCStub{ + ToggleUnStakeUnBondCalled: func(value bool) error { + return nil + }, + }, + }) + require.Nil(t, err) + + err = mp.ProcessIfFirstBlockAfterEpochStartBlockV3() + require.Nil(t, err) + }) +} + +func TestMetaProcessor_processEpochStartProposeBlock(t *testing.T) { + t.Parallel() + + defaultMetaBlockV3 := block.MetaBlockV3{ + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + AccumulatedFeesInEpoch: big.NewInt(0), + DevFeesInEpoch: big.NewInt(0), + }, + }, + } + + t.Run("should return ErrNilBlockHeader because of nil metaHeader argument", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.ProcessEpochStartProposeBlock(nil, nil) + require.Equal(t, process.ErrNilBlockHeader, err) + }) + + t.Run("should return ErrNilBody because of nil body argument", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.ProcessEpochStartProposeBlock(&defaultMetaBlockV3, nil) + require.Equal(t, process.ErrNilBlockBody, err) + }) + + t.Run("should return ErrEpochStartProposeBlockHasMiniBlocks because the body has mini blocks", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.ProcessEpochStartProposeBlock(&defaultMetaBlockV3, &block.Body{ + MiniBlocks: []*block.MiniBlock{ + {}, + }, + }) + require.Equal(t, process.ErrEpochStartProposeBlockHasMiniBlocks, err) + }) + + t.Run("if processEconomicsDataForEpochStartProposeBlock fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "blockChain": &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.MetaExecutionResult{} + }, + }, + "epochStartDataCreator": &mock.EpochStartDataCreatorStub{ + CreateEpochStartShardDataMetablockV3Called: func(metaBlock data.MetaHeaderHandler) ([]block.EpochStartShardData, error) { + return nil, expectedErr + }, + }, + }) + require.Nil(t, err) + + _, err = mp.ProcessEpochStartProposeBlock(&defaultMetaBlockV3, &block.Body{}) + require.Equal(t, expectedErr, err) + }) + + t.Run("if processing epoch start mini blocks fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + blockchainMock := &testscommon.ChainHandlerMock{} + err := blockchainMock.SetGenesisHeader(&block.Header{}) + require.Nil(t, err) + blockchainMock.SetLastExecutionResult(&block.MetaExecutionResult{}) + dataComponents.BlockChain = blockchainMock + + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + arguments.ValidatorStatisticsProcessor = &testscommon.ValidatorStatisticsProcessorStub{ + RootHashCalled: func() ([]byte, error) { + return nil, expectedErr + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + mp.SetEpochStartData(&blproc.EpochStartDataWrapper{}) + _, err = mp.ProcessEpochStartProposeBlock(&defaultMetaBlockV3, &block.Body{}) + require.Equal(t, expectedErr, err) + }) + + t.Run("if updating validator statistics fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + blockchainMock := &testscommon.ChainHandlerMock{} + err := blockchainMock.SetGenesisHeader(&block.Header{}) + require.Nil(t, err) + blockchainMock.SetLastExecutionResult(&block.MetaExecutionResult{}) + dataComponents.BlockChain = blockchainMock + + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + arguments.EpochEconomics = &mock.EpochEconomicsStub{ + ComputeEndOfEpochEconomicsV3Called: func(metaBlock data.MetaHeaderHandler, prevBlockExecutionResults data.BaseMetaExecutionResultHandler, epochStartHandler data.EpochStartHandler) (*block.Economics, error) { + return &block.Economics{ + RewardsForProtocolSustainability: big.NewInt(0), + PrevEpochStartRound: 1, + }, nil + }, + } + + arguments.ValidatorStatisticsProcessor = &testscommon.ValidatorStatisticsProcessorStub{ + UpdatePeerStateV3Called: func(header data.MetaHeaderHandler, metaExecutionResult data.MetaExecutionResultHandler) ([]byte, error) { + return nil, expectedErr + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + mp.SetEpochStartData(&blproc.EpochStartDataWrapper{}) + + _, err = mp.ProcessEpochStartProposeBlock(&defaultMetaBlockV3, &block.Body{}) + require.Equal(t, expectedErr, err) + }) + + t.Run("commit state is not called by processEpochStartProposeBlock", func(t *testing.T) { + t.Parallel() + + commitCalled := false + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + blockchainMock := &testscommon.ChainHandlerMock{} + err := blockchainMock.SetGenesisHeader(&block.Header{}) + require.Nil(t, err) + blockchainMock.SetLastExecutionResult(&block.MetaExecutionResult{}) + dataComponents.BlockChain = blockchainMock + + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + arguments.EpochEconomics = &mock.EpochEconomicsStub{ + ComputeEndOfEpochEconomicsV3Called: func(metaBlock data.MetaHeaderHandler, prevBlockExecutionResults data.BaseMetaExecutionResultHandler, epochStartHandler data.EpochStartHandler) (*block.Economics, error) { + return &block.Economics{ + RewardsForProtocolSustainability: big.NewInt(0), + PrevEpochStartRound: 1, + }, nil + }, + } + + accountsDb := make(map[state.AccountsDbIdentifier]state.AccountsAdapter) + accounts := &testscommonState.AccountsStub{ + CommitCalled: func() ([]byte, error) { + commitCalled = true + return []byte("stateRoot"), nil + }, + } + accountsDb[state.UserAccountsState] = accounts + accountsDb[state.PeerAccountsState] = accounts + arguments.AccountsDB = accountsDb + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + mp.SetEpochStartData(&blproc.EpochStartDataWrapper{}) + + _, err = mp.ProcessEpochStartProposeBlock(&defaultMetaBlockV3, &block.Body{}) + require.Nil(t, err) + require.False(t, commitCalled) + }) + + t.Run("if HandleProcessErrorCutoff fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + blockchainMock := &testscommon.ChainHandlerMock{} + err := blockchainMock.SetGenesisHeader(&block.Header{}) + require.Nil(t, err) + blockchainMock.SetLastExecutionResult(&block.MetaExecutionResult{}) + dataComponents.BlockChain = blockchainMock + + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + arguments.EpochEconomics = &mock.EpochEconomicsStub{ + ComputeEndOfEpochEconomicsV3Called: func(metaBlock data.MetaHeaderHandler, prevBlockExecutionResults data.BaseMetaExecutionResultHandler, epochStartHandler data.EpochStartHandler) (*block.Economics, error) { + return &block.Economics{ + RewardsForProtocolSustainability: big.NewInt(0), + PrevEpochStartRound: 1, + }, nil + }, + } + + arguments.BlockProcessingCutoffHandler = &testscommon.BlockProcessingCutoffStub{ + HandleProcessErrorCutoffCalled: func(header data.HeaderHandler) error { + return expectedErr + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + mp.SetEpochStartData(&blproc.EpochStartDataWrapper{}) + + _, err = mp.ProcessEpochStartProposeBlock(&defaultMetaBlockV3, &block.Body{}) + require.Equal(t, expectedErr, err) + }) + + t.Run("if calculating the hash fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + err := coreComponents.SetInternalMarshalizer(&marshallerMock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + return nil, expectedErr + }, + }) + require.Nil(t, err) + + blockchainMock := &testscommon.ChainHandlerMock{} + err = blockchainMock.SetGenesisHeader(&block.Header{}) + require.Nil(t, err) + blockchainMock.SetLastExecutionResult(&block.MetaExecutionResult{}) + dataComponents.BlockChain = blockchainMock + + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + arguments.EpochEconomics = &mock.EpochEconomicsStub{ + ComputeEndOfEpochEconomicsV3Called: func(metaBlock data.MetaHeaderHandler, prevBlockExecutionResults data.BaseMetaExecutionResultHandler, epochStartHandler data.EpochStartHandler) (*block.Economics, error) { + return &block.Economics{ + RewardsForProtocolSustainability: big.NewInt(0), + PrevEpochStartRound: 1, + }, nil + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + mp.SetEpochStartData(&blproc.EpochStartDataWrapper{}) + + _, err = mp.ProcessEpochStartProposeBlock(&defaultMetaBlockV3, &block.Body{}) + require.Equal(t, expectedErr, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + blockchainMock := &testscommon.ChainHandlerMock{} + err := blockchainMock.SetGenesisHeader(&block.Header{}) + require.Nil(t, err) + blockchainMock.SetLastExecutionResult(&block.MetaExecutionResult{}) + dataComponents.BlockChain = blockchainMock + + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + arguments.EpochEconomics = &mock.EpochEconomicsStub{ + ComputeEndOfEpochEconomicsV3Called: func(metaBlock data.MetaHeaderHandler, prevBlockExecutionResults data.BaseMetaExecutionResultHandler, epochStartHandler data.EpochStartHandler) (*block.Economics, error) { + return &block.Economics{ + RewardsForProtocolSustainability: big.NewInt(0), + PrevEpochStartRound: 1, + }, nil + }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + mp.SetEpochStartData(&blproc.EpochStartDataWrapper{ + Epoch: 1, + EpochStartData: &block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{}, + Economics: block.Economics{}, + }, + }) + + _, err = mp.ProcessEpochStartProposeBlock(&defaultMetaBlockV3, &block.Body{}) + require.Nil(t, err) + }) +} + +func TestMetaProcessor_processEconomicsDataForEpochStartProposeBlock(t *testing.T) { + t.Parallel() + + defaultMetaBlockV3 := block.MetaBlockV3{ + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + AccumulatedFeesInEpoch: big.NewInt(0), + DevFeesInEpoch: big.NewInt(0), + }, + }, + } + + t.Run("should return ErrNilBaseExecutionResult error on nil last execution result", func(t *testing.T) { + t.Parallel() + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "blockChain": &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return nil + }, + }, + }) + require.Nil(t, err) + + err = mp.ProcessEconomicsDataForEpochStartProposeBlock(&defaultMetaBlockV3) + require.ErrorContains(t, err, process.ErrNilBaseExecutionResult.Error()) + }) + + t.Run("should return ErrWrongTypeAssertion error on wrong type of last execution result", func(t *testing.T) { + t.Parallel() + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "blockChain": &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.ExecutionResult{} + }, + }, + }) + require.Nil(t, err) + + err = mp.ProcessEconomicsDataForEpochStartProposeBlock(&defaultMetaBlockV3) + require.Equal(t, common.ErrWrongTypeAssertion, err) + }) + + t.Run("if CreateEpochStartShardDataMetablockV3 fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "blockChain": &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.MetaExecutionResult{} + }, + }, + "epochStartDataCreator": &mock.EpochStartDataCreatorStub{ + CreateEpochStartShardDataMetablockV3Called: func(metaBlock data.MetaHeaderHandler) ([]block.EpochStartShardData, error) { + return nil, expectedErr + }, + }, + }) + require.Nil(t, err) + + err = mp.ProcessEconomicsDataForEpochStartProposeBlock(&defaultMetaBlockV3) + require.Equal(t, expectedErr, err) + }) + + t.Run("if ComputeEndOfEpochEconomicsV3 fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "blockChain": &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.MetaExecutionResult{} + }, + }, + "epochStartDataCreator": &mock.EpochStartDataCreatorStub{ + CreateEpochStartShardDataMetablockV3Called: func(metaBlock data.MetaHeaderHandler) ([]block.EpochStartShardData, error) { + return []block.EpochStartShardData{ + {}, + }, nil + }, + }, + "epochEconomics": &mock.EpochEconomicsStub{ + ComputeEndOfEpochEconomicsV3Called: func(metaBlock data.MetaHeaderHandler, prevBlockExecutionResults data.BaseMetaExecutionResultHandler, epochStartHandler data.EpochStartHandler) (*block.Economics, error) { + return nil, expectedErr + }, + }, + }) + require.Nil(t, err) + + err = mp.ProcessEconomicsDataForEpochStartProposeBlock(&defaultMetaBlockV3) + require.Equal(t, expectedErr, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "blockChain": &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.MetaExecutionResult{} + }, + }, + "epochStartDataCreator": &mock.EpochStartDataCreatorStub{ + CreateEpochStartShardDataMetablockV3Called: func(metaBlock data.MetaHeaderHandler) ([]block.EpochStartShardData, error) { + return []block.EpochStartShardData{ + {}, + }, nil + }, + }, + "epochEconomics": &mock.EpochEconomicsStub{ + ComputeEndOfEpochEconomicsV3Called: func(metaBlock data.MetaHeaderHandler, prevBlockExecutionResults data.BaseMetaExecutionResultHandler, epochStartHandler data.EpochStartHandler) (*block.Economics, error) { + return &block.Economics{}, nil + }, + }, + }) + require.Nil(t, err) + + mp.SetEpochStartData(&blproc.EpochStartDataWrapper{}) + err = mp.ProcessEconomicsDataForEpochStartProposeBlock(&defaultMetaBlockV3) + require.Nil(t, err) + }) +} + +func TestMetaProcessor_createExecutionResult(t *testing.T) { + t.Parallel() + + t.Run("if computing the gas used fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "feeHandler": &mock.FeeAccumulatorStub{ + GetAccumulatedFeesCalled: func() *big.Int { + return big.NewInt(5) + }, + GetDeveloperFeesCalled: func() *big.Int { + return big.NewInt(5) + }, + }, + "gasConsumedProvider": &testscommon.GasHandlerStub{ + TotalGasPenalizedCalled: func() uint64 { + return 10 + }, + TotalGasRefundedCalled: func() uint64 { + return 10 + }, + }, + }) + require.Nil(t, err) + + mbh := []data.MiniBlockHeaderHandler{ + &block.MiniBlockHeader{}, + } + _, err = mp.CreateExecutionResult(mbh, &block.MetaBlockV3{ + EpochChangeProposed: true, + }, []byte("headerHash"), []byte("receiptHash"), []byte("valStatRootHash"), 5) + require.Equal(t, process.ErrGasUsedExceedsGasProvided, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + + arguments.FeeHandler = &mock.FeeAccumulatorStub{ + GetAccumulatedFeesCalled: func() *big.Int { + return big.NewInt(5) + }, + GetDeveloperFeesCalled: func() *big.Int { + return big.NewInt(5) + }, + } + arguments.GasHandler = &testscommon.GasHandlerStub{ + TotalGasProvidedCalled: func() uint64 { + return 10 + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + mbh := []data.MiniBlockHeaderHandler{ + &block.MiniBlockHeader{}, + } + execResult, err := mp.CreateExecutionResult(mbh, &block.MetaBlockV3{ + EpochChangeProposed: true, + }, []byte("headerHash"), []byte("receiptHash"), []byte("valStatRootHash"), 5) + require.Nil(t, err) + + metaExecResult, ok := execResult.(*block.MetaExecutionResult) + require.True(t, ok) + require.Equal(t, metaExecResult.ExecutedTxCount, uint64(5)) + require.Equal(t, metaExecResult.ReceiptsHash, []byte("receiptHash")) + require.Equal(t, metaExecResult.GetValidatorStatsRootHash(), []byte("valStatRootHash")) + }) +} + +func TestMetaProcessor_collectExecutionResults(t *testing.T) { + t.Parallel() + + t.Run("if CreateReceiptsHash fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + + txCoordinatorMock := createTxCoordinatorMock() + txCoordinatorMock.CreateReceiptsHashCalled = func() ([]byte, error) { + return nil, expectedErr + } + arguments.TxCoordinator = &txCoordinatorMock + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.CollectExecutionResults([]byte("headerHash"), &block.MetaBlockV3{ + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + AccumulatedFeesInEpoch: big.NewInt(0), + DevFeesInEpoch: big.NewInt(0), + }, + }, + }, &block.Body{}, []byte("valStatRootHash")) + require.Equal(t, expectedErr, err) + }) + + t.Run("if marshal fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + err := coreComponents.SetInternalMarshalizer(&marshallerMock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + return nil, expectedErr + }, + }) + require.Nil(t, err) + + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + + txCoordinatorMock := createTxCoordinatorMock() + arguments.TxCoordinator = &txCoordinatorMock + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.CollectExecutionResults([]byte("headerHash"), &block.MetaBlockV3{ + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + AccumulatedFeesInEpoch: big.NewInt(0), + DevFeesInEpoch: big.NewInt(0), + }, + }, + }, &block.Body{}, []byte("valStatRootHash")) + require.Equal(t, expectedErr, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + + txCoordinatorMock := createTxCoordinatorMock() + arguments.TxCoordinator = &txCoordinatorMock + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + execResult, err := mp.CollectExecutionResults([]byte("headerHash"), &block.MetaBlockV3{ + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + AccumulatedFeesInEpoch: big.NewInt(0), + DevFeesInEpoch: big.NewInt(0), + }, + }, + }, &block.Body{}, []byte("valStatRootHash")) + require.Nil(t, err) + + metaExecResult, ok := execResult.(*block.MetaExecutionResult) + require.True(t, ok) + require.Equal(t, metaExecResult.ExecutedTxCount, uint64(4)) + require.Equal(t, metaExecResult.ReceiptsHash, []byte("receiptHash")) + require.Equal(t, metaExecResult.GetValidatorStatsRootHash(), []byte("valStatRootHash")) + }) +} + +func TestMetaProcessor_collectExecutionResultsEpochStartProposal(t *testing.T) { + t.Parallel() + + t.Run("should fail because of error on CreateReceiptsHash", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + CreateReceiptsHashCalled: func() ([]byte, error) { + return nil, expectedErr + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.CollectExecutionResultsEpochStartProposal([]byte("headerHash"), &block.MetaBlockV3{ + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + AccumulatedFeesInEpoch: big.NewInt(0), + DevFeesInEpoch: big.NewInt(0), + }, + }, + }, &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("hash1"), + []byte("hash2"), + }, + }, + }, + }, []byte("valStatRootHash")) + require.Equal(t, expectedErr, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{} + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + execResult, err := mp.CollectExecutionResultsEpochStartProposal([]byte("headerHash"), &block.MetaBlockV3{ + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + AccumulatedFeesInEpoch: big.NewInt(0), + DevFeesInEpoch: big.NewInt(0), + }, + }, + }, &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("hash1"), + []byte("hash2"), + }, + }, + }, + }, []byte("valStatRootHash")) + require.Nil(t, err) + + metaExecResult, ok := execResult.(*block.MetaExecutionResult) + require.True(t, ok) + require.Equal(t, metaExecResult.ExecutedTxCount, uint64(2)) + require.Equal(t, metaExecResult.ReceiptsHash, []byte("receiptHash")) + require.Equal(t, metaExecResult.GetValidatorStatsRootHash(), []byte("valStatRootHash")) + }) +} + +func TestMetaProcessor_ProcessBlockProposal(t *testing.T) { + t.Parallel() + + defaultMetaBlockV3 := block.MetaBlockV3{ + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + AccumulatedFeesInEpoch: big.NewInt(0), + DevFeesInEpoch: big.NewInt(0), + }, + }, + } + t.Run("should return ErrNilBlockHeader because of nil argument", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.ProcessBlockProposal(nil, []byte("headerHash"), &block.Body{}) + require.Equal(t, process.ErrNilBlockHeader, err) + }) + + t.Run("should return ErrNilBlockBody because of nil argument", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.ProcessBlockProposal(&block.MetaBlockV3{}, []byte("headerHash"), nil) + require.Equal(t, process.ErrNilBlockBody, err) + }) + + t.Run("should return ErrInvalidHeader because of nil argument", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.ProcessBlockProposal(&block.MetaBlock{}, []byte("headerHash"), &block.Body{}) + require.Equal(t, process.ErrInvalidHeader, err) + }) + + t.Run("should return ErrWrongTypeAssertion in case of wrong header", func(t *testing.T) { + t.Parallel() + + checkEpochCounter := 0 + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + coreComponents.EpochNotifierField = &epochNotifier.EpochNotifierStub{ + CheckEpochCalled: func(header data.HeaderHandler) { + checkEpochCounter += 1 + }, + } + + checkRoundCounter := 0 + coreComponents.RoundNotifierField = &epochNotifier.RoundNotifierStub{ + CheckRoundCalled: func(header data.HeaderHandler) { + checkRoundCounter += 1 + }, + } + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.ProcessBlockProposal(&block.HeaderV3{ + Round: 2, + Epoch: 2, + }, []byte("headerHash"), &block.Body{}) + require.Equal(t, process.ErrWrongTypeAssertion, err) + require.Equal(t, 1, checkEpochCounter) + }) + + t.Run("should return ErrAccountStateDirty in case of dirty state", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + + accountsDb := make(map[state.AccountsDbIdentifier]state.AccountsAdapter) + accounts := &testscommonState.AccountsStub{ + CommitCalled: func() ([]byte, error) { + return nil, nil + }, + RootHashCalled: func() ([]byte, error) { + return nil, nil + }, + RecreateTrieIfNeededCalled: func(options common.RootHashHolder) error { + return nil + }, + JournalLenCalled: func() int { + return 1 + }, + } + + accountsDb[state.UserAccountsState] = accounts + accountsDb[state.PeerAccountsState] = accounts + + arguments.AccountsDB = accountsDb + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.ProcessBlockProposal(&block.MetaBlockV3{}, []byte("headerHash"), &block.Body{}) + require.True(t, errors.Is(err, process.ErrAccountStateDirty)) + }) + + t.Run("if checking context fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + previousHash := []byte("hash") + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return 0, previousHash, nil + }, + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{} + }, + } + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.ProcessBlockProposal(&block.MetaBlockV3{ + PrevHash: []byte("wrongHash"), + }, []byte("headerHash"), &block.Body{}) + require.Equal(t, process.ErrBlockHashDoesNotMatch, err) + }) + + t.Run("if creating block fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{ + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + {}, + }, + }, + } + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.BaseMetaExecutionResult{} + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + AddIntermediateTransactionsCalled: func(mapSCRs map[block.Type][]data.TransactionHandler, key []byte) error { + return expectedErr + }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.ProcessBlockProposal(&block.MetaBlockV3{ + Nonce: 1, + }, []byte("headerHash"), &block.Body{}) + require.Equal(t, expectedErr, err) + }) + + t.Run("if setting the current header fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + + arguments.BlockChainHook = &testscommon.BlockChainHookStub{ + SetCurrentHeaderCalled: func(hdr data.HeaderHandler) error { + return expectedErr + }, + } + + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{} + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.BaseMetaExecutionResult{} + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.ProcessBlockProposal(&block.MetaBlockV3{ + Nonce: 1, + }, []byte("headerHash"), &block.Body{}) + require.Equal(t, expectedErr, err) + }) + + t.Run("if processing first block after epoch start fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{ + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + {}, + }, + }, + } + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.BaseMetaExecutionResult{} + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + arguments.EpochSystemSCProcessor = &testscommon.EpochStartSystemSCStub{ + ToggleUnStakeUnBondCalled: func(value bool) error { + return expectedErr + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.ProcessBlockProposal(&block.MetaBlockV3{ + Nonce: 1, + }, []byte("headerHash"), &block.Body{}) + require.Equal(t, expectedErr, err) + }) + + t.Run("if processing epoch start proposal block fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{} + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.BaseMetaExecutionResult{} + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.ProcessBlockProposal(&block.MetaBlockV3{ + Nonce: 1, + EpochChangeProposed: true, + }, []byte("headerHash"), &block.Body{ + MiniBlocks: []*block.MiniBlock{ + {}, {}, {}, + }, + }) + require.Equal(t, process.ErrEpochStartProposeBlockHasMiniBlocks, err) + }) + + t.Run("if checking the data prepared for processing fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{} + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.BaseMetaExecutionResult{} + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + IsDataPreparedForProcessingCalled: func(haveTime func() time.Duration) error { + return expectedErr + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.ProcessBlockProposal(&block.MetaBlockV3{ + Nonce: 1, + }, []byte("headerHash"), &block.Body{}) + require.Equal(t, expectedErr, err) + }) + + t.Run("if waiting for failing headers fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{} + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.BaseMetaExecutionResult{} + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + arguments.HeadersForBlock = &testscommon.HeadersForBlockMock{ + WaitForHeadersIfNeededCalled: func(haveTime func() time.Duration) error { + return expectedErr + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.ProcessBlockProposal(&block.MetaBlockV3{ + Nonce: 1, + }, []byte("headerHash"), &block.Body{}) + require.Equal(t, expectedErr, err) + }) + + t.Run("if processing the transaction block fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{} + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.BaseMetaExecutionResult{} + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + ProcessBlockTransactionCalled: func(header data.HeaderHandler, body *block.Body, haveTime func() time.Duration) error { + return expectedErr + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.ProcessBlockProposal(&block.MetaBlockV3{ + Nonce: 1, + }, []byte("headerHash"), &block.Body{}) + require.Equal(t, expectedErr, err) + }) + + t.Run("if verifying created block fails, the error should be returned", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{} + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.BaseMetaExecutionResult{} + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + VerifyCreatedBlockTransactionsCalled: func(hdr data.HeaderHandler, body *block.Body) error { + return expectedErr + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.ProcessBlockProposal(&block.MetaBlockV3{ + Nonce: 1, + }, []byte("headerHash"), &block.Body{}) + require.Equal(t, expectedErr, err) + }) + + t.Run("if updating protocol fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{} + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.BaseMetaExecutionResult{} + }, + } + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + arguments.SCToProtocol = &mock.SCToProtocolStub{ + UpdateProtocolCalled: func(body *block.Body, nonce uint64) error { + return expectedErr + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.ProcessBlockProposal(&block.MetaBlockV3{ + Nonce: 1, + }, []byte("headerHash"), &block.Body{}) + require.Equal(t, expectedErr, err) + }) + + t.Run("if updating validator statistics fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.MetaExecutionResult{} + }, + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{} + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + arguments.ValidatorStatisticsProcessor = &testscommon.ValidatorStatisticsProcessorStub{ + UpdatePeerStateV3Called: func(header data.MetaHeaderHandler, metaExecutionResult data.MetaExecutionResultHandler) ([]byte, error) { + return nil, expectedErr + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.ProcessBlockProposal(&block.MetaBlockV3{ + Nonce: 1, + }, []byte("headerHash"), &block.Body{}) + require.Equal(t, expectedErr, err) + }) + + t.Run("commit state is not called by ProcessBlockProposal", func(t *testing.T) { + t.Parallel() + + commitCalled := false + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.MetaExecutionResult{} + }, + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &defaultMetaBlockV3 + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + + accountsDb := make(map[state.AccountsDbIdentifier]state.AccountsAdapter) + accounts := &testscommonState.AccountsStub{ + CommitCalled: func() ([]byte, error) { + commitCalled = true + return []byte("stateRoot"), nil + }, + RootHashCalled: func() ([]byte, error) { + return nil, nil + }, + RecreateTrieIfNeededCalled: func(options common.RootHashHolder) error { + return nil + }, + } + + accountsDb[state.UserAccountsState] = accounts + accountsDb[state.PeerAccountsState] = accounts + + arguments.AccountsDB = accountsDb + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + newBlock := defaultMetaBlockV3 + newBlock.Nonce = 1 + _, err = mp.ProcessBlockProposal(&newBlock, []byte("headerHash"), &block.Body{}) + require.Nil(t, err) + require.False(t, commitCalled) + + err = mp.CommitBlockProposalState(&newBlock) + require.Nil(t, err) + require.True(t, commitCalled) + }) + + t.Run("if HandleProcessErrorCutoff fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.MetaExecutionResult{} + }, + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &defaultMetaBlockV3 + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + arguments.BlockProcessingCutoffHandler = &testscommon.BlockProcessingCutoffStub{ + HandleProcessErrorCutoffCalled: func(header data.HeaderHandler) error { + return expectedErr + }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + newBlock := defaultMetaBlockV3 + newBlock.Nonce = 1 + _, err = mp.ProcessBlockProposal(&newBlock, []byte("headerHash"), &block.Body{}) + require.Equal(t, expectedErr, err) + }) + + t.Run("if creating the execution result fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.MetaExecutionResult{} + }, + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{} + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + CreateReceiptsHashCalled: func() ([]byte, error) { + return nil, expectedErr + }, + } + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + _, err = mp.ProcessBlockProposal(&block.MetaBlockV3{ + Nonce: 1, + }, []byte("headerHash"), &block.Body{}) + require.Equal(t, expectedErr, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{}, + } + }, + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{} + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + arguments.ValidatorStatisticsProcessor = &testscommon.ValidatorStatisticsProcessorStub{ + RootHashCalled: func() ([]byte, error) { + return nil, expectedErr + }, + } + + receiptHash := []byte("receiptHash") + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + CreateReceiptsHashCalled: func() ([]byte, error) { + return receiptHash, nil + }, + } + + mp, err := blproc.NewMetaProcessor(arguments) + require.Nil(t, err) + + executionResult, err := mp.ProcessBlockProposal(&block.MetaBlockV3{ + Nonce: 1, + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + DevFeesInEpoch: big.NewInt(1), + AccumulatedFeesInEpoch: big.NewInt(1), + }, + }, + }, []byte("headerHash"), &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + Type: block.ReceiptBlock, + TxHashes: [][]byte{ + []byte("txHash1"), + }, + }, + }, + }) + require.Nil(t, err) + + metaExecutionResult, ok := executionResult.(*block.MetaExecutionResult) + require.True(t, ok) + + require.Equal(t, receiptHash, metaExecutionResult.ReceiptsHash) + require.Equal(t, big.NewInt(1), metaExecutionResult.ExecutionResult.DevFeesInEpoch) + require.Equal(t, big.NewInt(1), metaExecutionResult.ExecutionResult.AccumulatedFeesInEpoch) + require.Equal(t, 0, len(metaExecutionResult.MiniBlockHeaders)) + require.Equal(t, uint64(0), metaExecutionResult.GetExecutedTxCount()) + }) +} + +func createTxCoordinatorMock() testscommon.TransactionCoordinatorMock { + return testscommon.TransactionCoordinatorMock{ + GetCreatedMiniBlocksFromMeCalled: func() block.MiniBlockSlice { + return []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("hash1"), + []byte("hash2"), + }, + }, + } + }, + CreatePostProcessMiniBlocksCalled: func() block.MiniBlockSlice { + return []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("hash3"), + []byte("hash4"), + }, + }, + } + }, + GetCreatedInShardMiniBlocksCalled: func() []*block.MiniBlock { + return []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("hash5"), + []byte("hash6"), + }, + }, + } + }, + } +} + +func createLastShardHeadersNotGenesis() map[uint32]blproc.ShardHeaderInfo { + shard0 := uint32(0) + shard1 := uint32(1) + shard2 := uint32(2) + + return map[uint32]blproc.ShardHeaderInfo{ + shard0: { + Header: &block.Header{ + ShardID: shard0, + Nonce: 10, + Round: 10, + }, + Hash: []byte("hash1"), + }, + shard1: { + Header: &block.Header{ + ShardID: shard1, + Nonce: 10, + Round: 10, + }, + Hash: []byte("hash2"), + }, + shard2: { + Header: &block.Header{ + ShardID: shard2, + Nonce: 10, + Round: 10, + }, + Hash: []byte("hash3"), + }, + } +} + +func createMetaProcessorMapForCreatingEpochStart() map[string]interface{} { + executionResultHeaderHash := []byte("exec result header hash") + executionResultsForEpochStart := block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: executionResultHeaderHash, + }, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("mb hash"), + SenderShardID: core.MetachainShardId, + Type: block.RewardsBlock, // this miniBlock marks the epoch start + }, + }, + } + prevValidMetaBlockV3 := testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { + return true + }, + GetLastExecutionResultHandlerCalled: func() data.LastExecutionResultHandler { + return &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{}, + } + }, + } + blockTracker := integrationTestsMock.BlockTrackerStub{ + GetLastCrossNotarizedHeadersForAllShardsCalled: func() (map[uint32]data.HeaderHandler, error) { + return map[uint32]data.HeaderHandler{ + 0: &block.HeaderV3{}, + 1: &block.HeaderV3{}, + common.MetachainShardId: &block.MetaBlockV3{}, + }, nil + }, + } + + return map[string]interface{}{ + "shardCoordinator": &mock.ShardCoordinatorStub{ + SelfIdCalled: func() uint32 { + return common.MetachainShardId + }, + }, + "epochStartTrigger": &testscommon.EpochStartTriggerStub{ + EpochCalled: func() uint32 { + return 0 + }, + ShouldProposeEpochChangeCalled: func(round uint64, nonce uint64) bool { + return false + }, + }, + "versionedHeaderFactory": &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return &block.MetaBlockV3{} + }, + }, + "executionManager": &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{&executionResultsForEpochStart}, nil + }, + }, + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &prevValidMetaBlockV3 + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("prev header hash") + }, + }, + "executionResultsInclusionEstimator": &processMocks.InclusionEstimatorMock{ + DecideCalled: func(lastNotarised *common.LastExecutionResultForInclusion, pending []data.BaseExecutionResultHandler, currentHdrTsMs uint64) (allowed int) { + return 1 // allow the inclusion of the first execution result + }, + }, + "appStatusHandler": &statusHandlerMock.AppStatusHandlerStub{}, + "maxProposalNonceGap": uint64(10), + "blockTracker": &blockTracker, + } +} diff --git a/process/block/metablockRequest_test.go b/process/block/metablockRequest_test.go index 2d9fdb5f89f..947863434e4 100644 --- a/process/block/metablockRequest_test.go +++ b/process/block/metablockRequest_test.go @@ -16,6 +16,7 @@ import ( "github.com/multiversx/mx-chain-go/dataRetriever" blockProcess "github.com/multiversx/mx-chain-go/process/block" + "github.com/multiversx/mx-chain-go/process/block/headerForBlock" "github.com/multiversx/mx-chain-go/process/mock" "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/testscommon" @@ -48,16 +49,14 @@ func TestMetaProcessor_computeExistingAndRequestMissingShardHeaders(t *testing.T require.Nil(t, err) require.NotNil(t, mp) - headersForBlock := mp.GetHdrForBlock() - numMissing, numAttestationMissing, missingProofs := mp.ComputeExistingAndRequestMissingShardHeaders(metaBlock) + arguments.HeadersForBlock.RequestShardHeaders(metaBlock) + numMissing, missingProofs, numAttestationMissing := arguments.HeadersForBlock.GetMissingData() time.Sleep(100 * time.Millisecond) require.Equal(t, uint32(2), numMissing) - require.Equal(t, uint32(2), headersForBlock.GetMissingHdrs()) // before receiving all missing headers referenced in metaBlock, the number of missing attestations is not updated require.Equal(t, uint32(0), numAttestationMissing) require.Equal(t, uint32(0), missingProofs) - require.Equal(t, uint32(0), headersForBlock.GetMissingFinalityAttestingHdrs()) - require.Len(t, headersForBlock.GetHdrHashAndInfo(), 2) + require.Len(t, arguments.HeadersForBlock.GetHeadersInfoMap(), 2) require.Equal(t, uint32(0), numCallsMissingAttestation.Load()) require.Equal(t, uint32(2), numCallsMissingHeaders.Load()) }) @@ -86,16 +85,16 @@ func TestMetaProcessor_computeExistingAndRequestMissingShardHeaders(t *testing.T headersPool := mp.GetDataPool().Headers() // adding the existing header headersPool.AddHeader(td[0].referencedHeaderData.headerHash, td[0].referencedHeaderData.header) - numMissing, numAttestationMissing, missingProofs := mp.ComputeExistingAndRequestMissingShardHeaders(metaBlock) + arguments.HeadersForBlock.RequestShardHeaders(metaBlock) + time.Sleep(100 * time.Millisecond) headersForBlock := mp.GetHdrForBlock() - require.Equal(t, uint32(1), numMissing) - require.Equal(t, uint32(1), headersForBlock.GetMissingHdrs()) + missingHdrs, missingProofs, missingAttesting := headersForBlock.GetMissingData() + require.Equal(t, uint32(1), missingHdrs) // before receiving all missing headers referenced in metaBlock, the number of missing attestations is not updated - require.Equal(t, uint32(0), numAttestationMissing) + require.Equal(t, uint32(0), missingAttesting) require.Equal(t, uint32(0), missingProofs) - require.Equal(t, uint32(0), headersForBlock.GetMissingFinalityAttestingHdrs()) - require.Len(t, headersForBlock.GetHdrHashAndInfo(), 2) + require.Len(t, headersForBlock.GetHeadersInfoMap(), 2) require.Equal(t, uint32(0), numCallsMissingAttestation.Load()) require.Equal(t, uint32(1), numCallsMissingHeaders.Load()) }) @@ -125,15 +124,14 @@ func TestMetaProcessor_computeExistingAndRequestMissingShardHeaders(t *testing.T // adding the existing headers headersPool.AddHeader(td[0].referencedHeaderData.headerHash, td[0].referencedHeaderData.header) headersPool.AddHeader(td[1].referencedHeaderData.headerHash, td[1].referencedHeaderData.header) - numMissing, numAttestationMissing, missingProofs := mp.ComputeExistingAndRequestMissingShardHeaders(metaBlock) + arguments.HeadersForBlock.RequestShardHeaders(metaBlock) time.Sleep(100 * time.Millisecond) headersForBlock := mp.GetHdrForBlock() - require.Equal(t, uint32(0), numMissing) - require.Equal(t, uint32(0), headersForBlock.GetMissingHdrs()) - require.Equal(t, uint32(2), numAttestationMissing) + missingHdrs, missingProofs, missingAttesting := headersForBlock.GetMissingData() + require.Equal(t, uint32(0), missingHdrs) require.Equal(t, uint32(0), missingProofs) - require.Equal(t, uint32(2), headersForBlock.GetMissingFinalityAttestingHdrs()) - require.Len(t, headersForBlock.GetHdrHashAndInfo(), 2) + require.Equal(t, uint32(2), missingAttesting) + require.Len(t, headersForBlock.GetHeadersInfoMap(), 2) require.Equal(t, uint32(2), numCallsMissingAttestation.Load()) require.Equal(t, uint32(0), numCallsMissingHeaders.Load()) }) @@ -164,15 +162,14 @@ func TestMetaProcessor_computeExistingAndRequestMissingShardHeaders(t *testing.T headersPool.AddHeader(td[0].referencedHeaderData.headerHash, td[0].referencedHeaderData.header) headersPool.AddHeader(td[1].referencedHeaderData.headerHash, td[1].referencedHeaderData.header) headersPool.AddHeader(td[0].attestationHeaderData.headerHash, td[0].attestationHeaderData.header) - numMissing, numAttestationMissing, missingProofs := mp.ComputeExistingAndRequestMissingShardHeaders(metaBlock) + arguments.HeadersForBlock.RequestShardHeaders(metaBlock) time.Sleep(100 * time.Millisecond) headersForBlock := mp.GetHdrForBlock() - require.Equal(t, uint32(0), numMissing) - require.Equal(t, uint32(0), headersForBlock.GetMissingHdrs()) - require.Equal(t, uint32(1), numAttestationMissing) + missingHdrs, missingProofs, missingAttesting := headersForBlock.GetMissingData() + require.Equal(t, uint32(0), missingHdrs) require.Equal(t, uint32(0), missingProofs) - require.Equal(t, uint32(1), headersForBlock.GetMissingFinalityAttestingHdrs()) - require.Len(t, headersForBlock.GetHdrHashAndInfo(), 3) + require.Equal(t, uint32(1), missingAttesting) + require.Len(t, headersForBlock.GetHeadersInfoMap(), 3) require.Equal(t, uint32(1), numCallsMissingAttestation.Load()) require.Equal(t, uint32(0), numCallsMissingHeaders.Load()) }) @@ -204,271 +201,19 @@ func TestMetaProcessor_computeExistingAndRequestMissingShardHeaders(t *testing.T headersPool.AddHeader(td[1].referencedHeaderData.headerHash, td[1].referencedHeaderData.header) headersPool.AddHeader(td[0].attestationHeaderData.headerHash, td[0].attestationHeaderData.header) headersPool.AddHeader(td[1].attestationHeaderData.headerHash, td[1].attestationHeaderData.header) - numMissing, numAttestationMissing, missingProofs := mp.ComputeExistingAndRequestMissingShardHeaders(metaBlock) + arguments.HeadersForBlock.RequestShardHeaders(metaBlock) time.Sleep(100 * time.Millisecond) headersForBlock := mp.GetHdrForBlock() - require.Equal(t, uint32(0), numMissing) - require.Equal(t, uint32(0), headersForBlock.GetMissingHdrs()) - require.Equal(t, uint32(0), numAttestationMissing) + missingHdrs, missingProofs, missingAttesting := headersForBlock.GetMissingData() + require.Equal(t, uint32(0), missingHdrs) require.Equal(t, uint32(0), missingProofs) - require.Equal(t, uint32(0), headersForBlock.GetMissingFinalityAttestingHdrs()) - require.Len(t, headersForBlock.GetHdrHashAndInfo(), 4) + require.Equal(t, uint32(0), missingAttesting) + require.Len(t, headersForBlock.GetHeadersInfoMap(), 4) require.Equal(t, uint32(0), numCallsMissingAttestation.Load()) require.Equal(t, uint32(0), numCallsMissingHeaders.Load()) }) } -func TestMetaProcessor_receivedShardHeader(t *testing.T) { - t.Parallel() - noOfShards := uint32(2) - td := createTestData() - - t.Run("receiving the last used in block shard header", func(t *testing.T) { - t.Parallel() - - numCalls := atomic.Uint32{} - arguments := createMetaProcessorArguments(t, noOfShards) - requestHandler, ok := arguments.ArgBaseProcessor.RequestHandler.(*testscommon.RequestHandlerStub) - require.True(t, ok) - - requestHandler.RequestShardHeaderByNonceCalled = func(shardID uint32, nonce uint64) { - attestationNonce := td[shardID].attestationHeaderData.header.GetNonce() - if nonce != attestationNonce { - require.Fail(t, fmt.Sprintf("nonce should have been %d", attestationNonce)) - } - numCalls.Add(1) - } - - mp, err := blockProcess.NewMetaProcessor(*arguments) - require.Nil(t, err) - require.NotNil(t, mp) - - hdrsForBlock := mp.GetHdrForBlock() - hdrsForBlock.SetNumMissingHdrs(1) - hdrsForBlock.SetNumMissingFinalityAttestingHdrs(0) - hdrsForBlock.SetHighestHdrNonce(0, td[0].referencedHeaderData.header.GetNonce()-1) - hdrsForBlock.SetHdrHashAndInfo(string(td[0].referencedHeaderData.headerHash), &blockProcess.HdrInfo{ - UsedInBlock: true, - Hdr: nil, - }) - - mp.ReceivedShardHeader(td[0].referencedHeaderData.header, td[0].referencedHeaderData.headerHash) - - time.Sleep(100 * time.Millisecond) - require.Nil(t, err) - require.NotNil(t, mp) - require.Equal(t, uint32(1), numCalls.Load()) - require.Equal(t, uint32(1), hdrsForBlock.GetMissingFinalityAttestingHdrs()) - }) - - t.Run("shard header used in block received, not latest", func(t *testing.T) { - t.Parallel() - - numCalls := atomic.Uint32{} - arguments := createMetaProcessorArguments(t, noOfShards) - requestHandler, ok := arguments.ArgBaseProcessor.RequestHandler.(*testscommon.RequestHandlerStub) - require.True(t, ok) - - // for requesting attestation header - requestHandler.RequestShardHeaderByNonceCalled = func(shardID uint32, nonce uint64) { - attestationNonce := td[shardID].attestationHeaderData.header.GetNonce() - require.Equal(t, nonce, attestationNonce, fmt.Sprintf("nonce should have been %d", attestationNonce)) - numCalls.Add(1) - } - - mp, err := blockProcess.NewMetaProcessor(*arguments) - require.Nil(t, err) - require.NotNil(t, mp) - - hdrsForBlock := mp.GetHdrForBlock() - hdrsForBlock.SetNumMissingHdrs(2) - hdrsForBlock.SetNumMissingFinalityAttestingHdrs(0) - referencedHeaderData := td[1].referencedHeaderData - hdrsForBlock.SetHighestHdrNonce(0, referencedHeaderData.header.GetNonce()-1) - hdrsForBlock.SetHdrHashAndInfo(string(referencedHeaderData.headerHash), &blockProcess.HdrInfo{ - UsedInBlock: true, - Hdr: nil, - }) - - mp.ReceivedShardHeader(referencedHeaderData.header, referencedHeaderData.headerHash) - - time.Sleep(100 * time.Millisecond) - require.Nil(t, err) - require.NotNil(t, mp) - // not yet requested attestation blocks as still missing one header - require.Equal(t, uint32(0), numCalls.Load()) - // not yet computed - require.Equal(t, uint32(0), hdrsForBlock.GetMissingFinalityAttestingHdrs()) - }) - t.Run("all needed shard attestation headers received", func(t *testing.T) { - t.Parallel() - - numCalls := atomic.Uint32{} - arguments := createMetaProcessorArguments(t, noOfShards) - - poolsHolder, ok := arguments.DataComponents.Datapool().(*dataRetrieverMock.PoolsHolderMock) - require.True(t, ok) - - headersPoolStub := createPoolsHolderForHeaderRequests() - poolsHolder.SetHeadersPool(headersPoolStub) - requestHandler, ok := arguments.ArgBaseProcessor.RequestHandler.(*testscommon.RequestHandlerStub) - require.True(t, ok) - - // for requesting attestation header - requestHandler.RequestShardHeaderByNonceCalled = func(shardID uint32, nonce uint64) { - attestationNonce := td[shardID].attestationHeaderData.header.GetNonce() - if nonce != attestationNonce { - require.Fail(t, "nonce should have been %d", attestationNonce) - } - numCalls.Add(1) - } - - mp, err := blockProcess.NewMetaProcessor(*arguments) - require.Nil(t, err) - require.NotNil(t, mp) - - hdrsForBlock := mp.GetHdrForBlock() - hdrsForBlock.SetNumMissingHdrs(1) - hdrsForBlock.SetNumMissingFinalityAttestingHdrs(0) - referencedHeaderData := td[0].referencedHeaderData - hdrsForBlock.SetHighestHdrNonce(0, referencedHeaderData.header.GetNonce()-1) - hdrsForBlock.SetHdrHashAndInfo(string(referencedHeaderData.headerHash), &blockProcess.HdrInfo{ - UsedInBlock: true, - Hdr: nil, - }) - - // receive the missing header - headersPool := mp.GetDataPool().Headers() - headersPool.AddHeader(referencedHeaderData.headerHash, referencedHeaderData.header) - mp.ReceivedShardHeader(td[0].referencedHeaderData.header, referencedHeaderData.headerHash) - - time.Sleep(100 * time.Millisecond) - require.Nil(t, err) - require.NotNil(t, mp) - require.Equal(t, uint32(1), numCalls.Load()) - require.Equal(t, uint32(1), hdrsForBlock.GetMissingFinalityAttestingHdrs()) - - // needs to be done before receiving the last header otherwise it will - // be blocked waiting on writing to the channel - wg := startWaitingForAllHeadersReceivedSignal(t, mp) - - // receive also the attestation header - attestationHeaderData := td[0].attestationHeaderData - headersPool.AddHeader(attestationHeaderData.headerHash, attestationHeaderData.header) - mp.ReceivedShardHeader(attestationHeaderData.header, attestationHeaderData.headerHash) - wg.Wait() - - require.Equal(t, uint32(1), numCalls.Load()) - require.Equal(t, uint32(0), hdrsForBlock.GetMissingFinalityAttestingHdrs()) - }) - t.Run("all needed shard attestation headers received, when multiple shards headers missing", func(t *testing.T) { - t.Parallel() - - numCalls := atomic.Uint32{} - arguments := createMetaProcessorArguments(t, noOfShards) - - poolsHolder, ok := arguments.DataComponents.Datapool().(*dataRetrieverMock.PoolsHolderMock) - require.True(t, ok) - - headersPoolStub := createPoolsHolderForHeaderRequests() - poolsHolder.SetHeadersPool(headersPoolStub) - requestHandler, ok := arguments.ArgBaseProcessor.RequestHandler.(*testscommon.RequestHandlerStub) - require.True(t, ok) - - // for requesting attestation header - requestHandler.RequestShardHeaderByNonceCalled = func(shardID uint32, nonce uint64) { - attestationNonce := td[shardID].attestationHeaderData.header.GetNonce() - if nonce != td[shardID].attestationHeaderData.header.GetNonce() { - require.Fail(t, fmt.Sprintf("requested nonce for shard %d should have been %d", shardID, attestationNonce)) - } - numCalls.Add(1) - } - - mp, err := blockProcess.NewMetaProcessor(*arguments) - require.Nil(t, err) - require.NotNil(t, mp) - - hdrsForBlock := mp.GetHdrForBlock() - hdrsForBlock.SetNumMissingHdrs(2) - hdrsForBlock.SetNumMissingFinalityAttestingHdrs(0) - hdrsForBlock.SetHighestHdrNonce(0, 99) - hdrsForBlock.SetHighestHdrNonce(1, 97) - hdrsForBlock.SetHdrHashAndInfo(string(td[0].referencedHeaderData.headerHash), &blockProcess.HdrInfo{ - UsedInBlock: true, - Hdr: nil, - }) - hdrsForBlock.SetHdrHashAndInfo(string(td[1].referencedHeaderData.headerHash), &blockProcess.HdrInfo{ - UsedInBlock: true, - Hdr: nil, - }) - - // receive the missing header for shard 0 - headersPool := mp.GetDataPool().Headers() - headersPool.AddHeader(td[0].referencedHeaderData.headerHash, td[0].referencedHeaderData.header) - mp.ReceivedShardHeader(td[0].referencedHeaderData.header, td[0].referencedHeaderData.headerHash) - - time.Sleep(100 * time.Millisecond) - require.Nil(t, err) - require.NotNil(t, mp) - // the attestation header for shard 0 is not requested as the attestation header for shard 1 is missing - // TODO: refactor request logic to request missing attestation headers as soon as possible - require.Equal(t, uint32(0), numCalls.Load()) - require.Equal(t, uint32(0), hdrsForBlock.GetMissingFinalityAttestingHdrs()) - - // receive the missing header for shard 1 - headersPool.AddHeader(td[1].referencedHeaderData.headerHash, td[1].referencedHeaderData.header) - mp.ReceivedShardHeader(td[1].referencedHeaderData.header, td[1].referencedHeaderData.headerHash) - - time.Sleep(100 * time.Millisecond) - require.Nil(t, err) - require.NotNil(t, mp) - require.Equal(t, uint32(2), numCalls.Load()) - require.Equal(t, uint32(2), hdrsForBlock.GetMissingFinalityAttestingHdrs()) - - // needs to be done before receiving the last header otherwise it will - // be blocked writing to a channel no one is reading from - wg := startWaitingForAllHeadersReceivedSignal(t, mp) - - // receive also the attestation header - headersPool.AddHeader(td[0].attestationHeaderData.headerHash, td[0].attestationHeaderData.header) - mp.ReceivedShardHeader(td[0].attestationHeaderData.header, td[0].attestationHeaderData.headerHash) - - headersPool.AddHeader(td[1].attestationHeaderData.headerHash, td[1].attestationHeaderData.header) - mp.ReceivedShardHeader(td[1].attestationHeaderData.header, td[1].attestationHeaderData.headerHash) - wg.Wait() - - time.Sleep(100 * time.Millisecond) - // the receive of an attestation header, if not the last one, will trigger a new request of missing attestation headers - // TODO: refactor request logic to not request recently already requested headers - require.Equal(t, uint32(3), numCalls.Load()) - require.Equal(t, uint32(0), hdrsForBlock.GetMissingFinalityAttestingHdrs()) - }) -} - -type receivedAllHeadersSignaler interface { - ChannelReceiveAllHeaders() chan bool -} - -func startWaitingForAllHeadersReceivedSignal(t *testing.T, mp receivedAllHeadersSignaler) *sync.WaitGroup { - wg := &sync.WaitGroup{} - wg.Add(1) - go func(w *sync.WaitGroup) { - receivedAllHeaders := checkReceivedAllHeaders(mp.ChannelReceiveAllHeaders()) - require.True(t, receivedAllHeaders) - wg.Done() - }(wg) - return wg -} - -func checkReceivedAllHeaders(channelReceiveAllHeaders chan bool) bool { - select { - case <-time.After(100 * time.Millisecond): - return false - case <-channelReceiveAllHeaders: - return true - } -} - func createPoolsHolderForHeaderRequests() dataRetriever.HeadersPool { headersInPool := make(map[string]data.HeaderHandler) mutHeadersInPool := sync.RWMutex{} @@ -537,6 +282,19 @@ func createMetaProcessorArguments(t *testing.T, noOfShards uint32) *blockProcess require.Fail(t, "should not have been called") }, } + headersForBlock, err := headerForBlock.NewHeadersForBlock(headerForBlock.ArgHeadersForBlock{ + DataPool: arguments.DataComponents.Datapool(), + RequestHandler: arguments.RequestHandler, + EnableEpochsHandler: arguments.CoreComponents.EnableEpochsHandler(), + ShardCoordinator: arguments.BootstrapComponents.ShardCoordinator(), + BlockTracker: arguments.BlockTracker, + TxCoordinator: arguments.TxCoordinator, + RoundHandler: arguments.CoreComponents.RoundHandler(), + ExtraDelayForRequestBlockInfoInMilliseconds: 100, + GenesisNonce: 0, + }) + require.Nil(t, err) + arguments.HeadersForBlock = headersForBlock return &arguments } diff --git a/process/block/metablock_test.go b/process/block/metablock_test.go index 23d89ae31f3..92519110f8f 100644 --- a/process/block/metablock_test.go +++ b/process/block/metablock_test.go @@ -6,11 +6,13 @@ import ( "math/big" "reflect" "sync" + atomic2 "sync/atomic" "testing" "time" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/atomic" + "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/stretchr/testify/assert" @@ -22,25 +24,40 @@ import ( "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/dataRetriever/blockchain" "github.com/multiversx/mx-chain-go/process" - blproc "github.com/multiversx/mx-chain-go/process/block" + "github.com/multiversx/mx-chain-go/process/aotSelection" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionManager" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionTrack" + processBlock "github.com/multiversx/mx-chain-go/process/block" "github.com/multiversx/mx-chain-go/process/block/bootstrapStorage" + "github.com/multiversx/mx-chain-go/process/block/headerForBlock" "github.com/multiversx/mx-chain-go/process/block/processedMb" + "github.com/multiversx/mx-chain-go/process/coordinator" + "github.com/multiversx/mx-chain-go/process/estimator" + "github.com/multiversx/mx-chain-go/process/factory/containers" + "github.com/multiversx/mx-chain-go/process/missingData" "github.com/multiversx/mx-chain-go/process/mock" "github.com/multiversx/mx-chain-go/sharding" "github.com/multiversx/mx-chain-go/state" + "github.com/multiversx/mx-chain-go/state/disabled" "github.com/multiversx/mx-chain-go/storage" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/cache" + commonMocks "github.com/multiversx/mx-chain-go/testscommon/common" dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/dblookupext" + "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" "github.com/multiversx/mx-chain-go/testscommon/factory" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" "github.com/multiversx/mx-chain-go/testscommon/outport" + poolMock "github.com/multiversx/mx-chain-go/testscommon/pool" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" stateMock "github.com/multiversx/mx-chain-go/testscommon/state" statusHandlerMock "github.com/multiversx/mx-chain-go/testscommon/statusHandler" storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" + "github.com/multiversx/mx-chain-go/trie" ) func createMockComponentHolders() ( @@ -49,7 +66,7 @@ func createMockComponentHolders() ( *mock.BootstrapComponentsMock, *mock.StatusComponentsMock, ) { - mdp := initDataPool([]byte("tx_hash")) + mdp := initDataPool() gracePeriod, _ := graceperiod.NewEpochChangeGracePeriod([]config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}) coreComponents := &mock.CoreComponentsMock{ IntMarsh: &mock.MarshalizerMock{}, @@ -63,6 +80,8 @@ func createMockComponentHolders() ( RoundNotifierField: &epochNotifier.RoundNotifierStub{}, EnableRoundsHandlerField: &testscommon.EnableRoundsHandlerStub{}, EpochChangeGracePeriodHandlerField: gracePeriod, + ProcessConfigsHandlerField: testscommon.GetDefaultProcessConfigsHandler(), + ClosingNodeStartedField: &atomic2.Bool{}, } dataComponents := &mock.DataComponentsMock{ @@ -74,7 +93,7 @@ func createMockComponentHolders() ( Coordinator: mock.NewOneShardCoordinatorMock(), HdrIntegrityVerifier: &mock.HeaderIntegrityVerifierStub{}, VersionedHdrFactory: &testscommon.VersionedHeaderFactoryStub{ - CreateCalled: func(epoch uint32) data.HeaderHandler { + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { return &block.MetaBlock{} }, }, @@ -92,46 +111,128 @@ func createMockMetaArguments( dataComponents *mock.DataComponentsMock, bootstrapComponents *mock.BootstrapComponentsMock, statusComponents *mock.StatusComponentsMock, -) blproc.ArgMetaProcessor { +) processBlock.ArgMetaProcessor { - argsHeaderValidator := blproc.ArgsHeaderValidator{ + argsHeaderValidator := processBlock.ArgsHeaderValidator{ Hasher: &mock.HasherStub{}, Marshalizer: &mock.MarshalizerMock{}, EnableEpochsHandler: coreComponents.EnableEpochsHandler(), } - headerValidator, _ := blproc.NewHeaderValidator(argsHeaderValidator) + headerValidator, _ := processBlock.NewHeaderValidator(argsHeaderValidator) startHeaders := createGenesisBlocks(bootstrapComponents.ShardCoordinator()) accountsDb := make(map[state.AccountsDbIdentifier]state.AccountsAdapter) - accountsDb[state.UserAccountsState] = &stateMock.AccountsStub{ + accounts := &stateMock.AccountsStub{ CommitCalled: func() ([]byte, error) { return nil, nil }, RootHashCalled: func() ([]byte, error) { return nil, nil }, - } - accountsDb[state.PeerAccountsState] = &stateMock.AccountsStub{ - CommitCalled: func() ([]byte, error) { - return nil, nil - }, - RootHashCalled: func() ([]byte, error) { - return nil, nil + RecreateTrieIfNeededCalled: func(options common.RootHashHolder) error { + return nil }, } - + accountsDb[state.UserAccountsState] = accounts + accountsDb[state.PeerAccountsState] = accounts statusCoreComponents := &factory.StatusCoreComponentsStub{ AppStatusHandlerField: &statusHandlerMock.AppStatusHandlerStub{}, } - arguments := blproc.ArgMetaProcessor{ - ArgBaseProcessor: blproc.ArgBaseProcessor{ + blockTracker := mock.NewBlockTrackerMock(bootstrapComponents.ShardCoordinator(), startHeaders) + var headersForBlock processBlock.HeadersForBlock = &testscommon.HeadersForBlockMock{} + if !check.IfNil(coreComponents) && !check.IfNil(bootstrapComponents) && !check.IfNil(dataComponents) { + headersForBlock, _ = headerForBlock.NewHeadersForBlock(headerForBlock.ArgHeadersForBlock{ + DataPool: dataComponents.DataPool, + RequestHandler: &testscommon.RequestHandlerStub{}, + EnableEpochsHandler: coreComponents.EnableEpochsHandler(), + ShardCoordinator: bootstrapComponents.ShardCoordinator(), + BlockTracker: blockTracker, + TxCoordinator: &testscommon.TransactionCoordinatorMock{}, + RoundHandler: coreComponents.RoundHandler(), + ExtraDelayForRequestBlockInfoInMilliseconds: 100, + GenesisNonce: 0, + }) + } + + preprocContainer := containers.NewPreProcessorsContainer() + blockDataRequesterArgs := coordinator.BlockDataRequestArgs{ + RequestHandler: &testscommon.RequestHandlerStub{}, + MiniBlockPool: dataComponents.Datapool().MiniBlocks(), + PreProcessors: preprocContainer, + ShardCoordinator: bootstrapComponents.ShardCoordinator(), + EnableEpochsHandler: coreComponents.EnableEpochsHandler(), + } + // second instance for proposal missing data fetching to avoid interferences + proposalBlockDataRequester, _ := coordinator.NewBlockDataRequester(blockDataRequesterArgs) + + mbSelectionSession, _ := processBlock.NewMiniBlocksSelectionSession( + bootstrapComponents.ShardCoordinator().SelfId(), + coreComponents.InternalMarshalizer(), + coreComponents.Hasher(), + ) + + executionResultsTracker := executionTrack.NewExecutionResultsTracker() + execManager, _ := executionManager.NewExecutionManager(executionManager.ArgsExecutionManager{ + BlocksCache: &processMocks.BlocksCacheMock{}, + ExecutionResultsTracker: executionResultsTracker, + BlockChain: dataComponents.BlockChain, + Headers: dataComponents.DataPool.Headers(), + PostProcessTransactions: dataComponents.DataPool.PostProcessTransactions(), + ExecutedMiniBlocks: dataComponents.DataPool.ExecutedMiniBlocks(), + StorageService: dataComponents.StorageService(), + Marshaller: coreComponents.InternalMarshalizer(), + ShardCoordinator: bootstrapComponents.ShardCoordinator(), + }) + execResultsVerifier, _ := processBlock.NewExecutionResultsVerifier(dataComponents.BlockChain, execManager) + _ = executionResultsTracker.SetLastNotarizedResult(&block.ExecutionResult{}) + inclusionEstimator, _ := estimator.NewExecutionResultInclusionEstimator( + config.ExecutionResultInclusionEstimatorConfig{ + SafetyMargin: 110, + MaxResultsPerBlock: 20, + }, + coreComponents.RoundHandler(), + &testscommon.ExecResSizeComputationStub{}, + ) + + missingDataArgs := missingData.ResolverArgs{ + HeadersPool: dataComponents.DataPool.Headers(), + ProofsPool: dataComponents.DataPool.Proofs(), + RequestHandler: &testscommon.RequestHandlerStub{}, + BlockDataRequester: proposalBlockDataRequester, + } + missingDataResolver, _ := missingData.NewMissingDataResolver(missingDataArgs) + + argsGasConsumption := processBlock.ArgsGasConsumption{ + EconomicsFee: &economicsmocks.EconomicsHandlerMock{}, + ShardCoordinator: bootstrapComponents.ShardCoordinator(), + GasHandler: &mock.GasHandlerMock{}, + BlockCapacityOverestimationFactor: 200, + PercentDecreaseLimitsStep: 10, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + } + gasComputation, _ := processBlock.NewGasConsumption(argsGasConsumption) + + shardInfoCreateDataArgs := processBlock.ShardInfoCreateDataArgs{ + EnableEpochsHandler: coreComponents.EnableEpochsHandler(), + HeadersPool: dataComponents.Datapool().Headers(), + ProofsPool: dataComponents.Datapool().Proofs(), + PendingMiniBlocksHandler: &mock.PendingMiniBlocksHandlerStub{}, + BlockTracker: blockTracker, + Storage: dataComponents.StorageService(), + Marshaller: coreComponents.InternalMarshalizer(), + } + shardInfoCreator, _ := processBlock.NewShardInfoCreateData(shardInfoCreateDataArgs) + + arguments := processBlock.ArgMetaProcessor{ + ArgBaseProcessor: processBlock.ArgBaseProcessor{ CoreComponents: coreComponents, DataComponents: dataComponents, BootstrapComponents: bootstrapComponents, StatusComponents: statusComponents, StatusCoreComponents: statusCoreComponents, AccountsDB: accountsDb, + AccountsProposal: accounts, ForkDetector: &mock.ForkDetectorMock{}, NodesCoordinator: shardingMocks.NewNodesCoordinatorMock(), FeeHandler: &mock.FeeAccumulatorStub{}, @@ -146,16 +247,26 @@ func createMockMetaArguments( return nil }, }, - BlockTracker: mock.NewBlockTrackerMock(bootstrapComponents.ShardCoordinator(), startHeaders), - BlockSizeThrottler: &mock.BlockSizeThrottlerStub{}, - HistoryRepository: &dblookupext.HistoryRepositoryStub{}, - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - ReceiptsRepository: &testscommon.ReceiptsRepositoryStub{}, - OutportDataProvider: &outport.OutportDataProviderStub{}, - BlockProcessingCutoffHandler: &testscommon.BlockProcessingCutoffStub{}, - ManagedPeersHolder: &testscommon.ManagedPeersHolderStub{}, - SentSignaturesTracker: &testscommon.SentSignatureTrackerStub{}, + BlockTracker: blockTracker, + BlockSizeThrottler: &mock.BlockSizeThrottlerStub{}, + HistoryRepository: &dblookupext.HistoryRepositoryStub{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + ReceiptsRepository: &testscommon.ReceiptsRepositoryStub{}, + OutportDataProvider: &outport.OutportDataProviderStub{}, + BlockProcessingCutoffHandler: &testscommon.BlockProcessingCutoffStub{}, + ManagedPeersHolder: &testscommon.ManagedPeersHolderStub{}, + SentSignaturesTracker: &testscommon.SentSignatureTrackerStub{}, + StateAccessesCollector: disabled.NewDisabledStateAccessesCollector(), + HeadersForBlock: headersForBlock, + MiniBlocksSelectionSession: mbSelectionSession, + ExecutionResultsVerifier: execResultsVerifier, + MissingDataResolver: missingDataResolver, + ExecutionResultsInclusionEstimator: inclusionEstimator, + GasComputation: gasComputation, + ExecutionManager: execManager, + TxExecutionOrderHandler: &commonMocks.TxExecutionOrderHandlerStub{}, + AOTSelector: aotSelection.NewDisabledAOTSelector(), }, SCToProtocol: &mock.SCToProtocolStub{}, PendingMiniBlocksHandler: &mock.PendingMiniBlocksHandlerStub{}, @@ -165,6 +276,7 @@ func createMockMetaArguments( EpochValidatorInfoCreator: &testscommon.EpochValidatorInfoCreatorStub{}, ValidatorStatisticsProcessor: &testscommon.ValidatorStatisticsProcessorStub{}, EpochSystemSCProcessor: &testscommon.EpochStartSystemSCStub{}, + ShardInfoCreator: shardInfoCreator, } return arguments } @@ -278,7 +390,7 @@ func TestNewMetaProcessor_NilAccountsAdapterShouldErr(t *testing.T) { arguments := createMockMetaArguments(createMockComponentHolders()) arguments.AccountsDB[state.UserAccountsState] = nil - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilAccountsAdapter, err) assert.Nil(t, be) } @@ -287,10 +399,10 @@ func TestNewMetaProcessor_NilDataPoolShouldErr(t *testing.T) { t.Parallel() coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() - dataComponents.DataPool = nil arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + dataComponents.DataPool = nil - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilDataPoolHolder, err) assert.Nil(t, be) } @@ -299,14 +411,14 @@ func TestNewMetaProcessor_NilHeadersDataPoolShouldErr(t *testing.T) { t.Parallel() coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) dataComponents.DataPool = &dataRetrieverMock.PoolsHolderStub{ HeadersCalled: func() dataRetriever.HeadersPool { return nil }, } - arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilHeadersDataPool, err) assert.Nil(t, be) } @@ -318,7 +430,7 @@ func TestNewMetaProcessor_NilSCToProtocolShouldErr(t *testing.T) { arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) arguments.SCToProtocol = nil - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilSCToProtocol, err) assert.Nil(t, be) } @@ -329,7 +441,7 @@ func TestNewMetaProcessor_NilForkDetectorShouldErr(t *testing.T) { arguments := createMockMetaArguments(createMockComponentHolders()) arguments.ForkDetector = nil - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilForkDetector, err) assert.Nil(t, be) } @@ -341,7 +453,7 @@ func TestNewMetaProcessor_NilEpochStartDataCreatorShouldErr(t *testing.T) { arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) arguments.EpochStartDataCreator = nil - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilEpochStartDataCreator, err) assert.Nil(t, be) } @@ -353,7 +465,7 @@ func TestNewMetaProcessor_NilEpochEconomicsShouldErr(t *testing.T) { arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) arguments.EpochEconomics = nil - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilEpochEconomics, err) assert.Nil(t, be) } @@ -365,7 +477,7 @@ func TestNewMetaProcessor_NilEpochRewardsCreatorShouldErr(t *testing.T) { arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) arguments.EpochRewardsCreator = nil - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilRewardsCreator, err) assert.Nil(t, be) } @@ -377,7 +489,7 @@ func TestNewMetaProcessor_NilEpochValidatorInfoCreatorShouldErr(t *testing.T) { arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) arguments.EpochValidatorInfoCreator = nil - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilEpochStartValidatorInfoCreator, err) assert.Nil(t, be) } @@ -389,7 +501,7 @@ func TestNewMetaProcessor_NilValidatorStatisticsProcessorShouldErr(t *testing.T) arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) arguments.ValidatorStatisticsProcessor = nil - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilValidatorStatistics, err) assert.Nil(t, be) } @@ -401,7 +513,7 @@ func TestNewMetaProcessor_NilEpochSystemSCProcessorShouldErr(t *testing.T) { arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) arguments.EpochSystemSCProcessor = nil - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilEpochStartSystemSCProcessor, err) assert.Nil(t, be) } @@ -413,7 +525,7 @@ func TestNewMetaProcessor_NilShardCoordinatorShouldErr(t *testing.T) { arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) bootstrapComponents.Coordinator = nil - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilShardCoordinator, err) assert.Nil(t, be) } @@ -425,7 +537,7 @@ func TestNewMetaProcessor_NilHasherShouldErr(t *testing.T) { coreComponents.Hash = nil arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilHasher, err) assert.Nil(t, be) } @@ -437,7 +549,7 @@ func TestNewMetaProcessor_NilMarshalizerShouldErr(t *testing.T) { coreComponents.IntMarsh = nil arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilMarshalizer, err) assert.Nil(t, be) } @@ -449,7 +561,7 @@ func TestNewMetaProcessor_NilChainStorerShouldErr(t *testing.T) { dataComponents.Storage = nil arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilStorage, err) assert.Nil(t, be) } @@ -460,7 +572,7 @@ func TestNewMetaProcessor_NilRequestHeaderHandlerShouldErr(t *testing.T) { arguments := createMockMetaArguments(createMockComponentHolders()) arguments.RequestHandler = nil - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilRequestHandler, err) assert.Nil(t, be) } @@ -471,7 +583,7 @@ func TestNewMetaProcessor_NilTxCoordinatorShouldErr(t *testing.T) { arguments := createMockMetaArguments(createMockComponentHolders()) arguments.TxCoordinator = nil - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilTransactionCoordinator, err) assert.Nil(t, be) } @@ -482,7 +594,7 @@ func TestNewMetaProcessor_NilEpochStartShouldErr(t *testing.T) { arguments := createMockMetaArguments(createMockComponentHolders()) arguments.EpochStartTrigger = nil - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilEpochStartTrigger, err) assert.Nil(t, be) } @@ -494,7 +606,7 @@ func TestNewMetaProcessor_NilRoundNotifierShouldErr(t *testing.T) { coreComponents.RoundNotifierField = nil arguments := createMockMetaArguments(coreComponents, dataComponentsMock, bootstrapComponentsMock, statusComponentsMock) - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilRoundNotifier, err) assert.Nil(t, be) } @@ -505,7 +617,7 @@ func TestNewMetaProcessor_NilPendingMiniBlocksShouldErr(t *testing.T) { arguments := createMockMetaArguments(createMockComponentHolders()) arguments.PendingMiniBlocksHandler = nil - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilPendingMiniBlocksHandler, err) assert.Nil(t, be) } @@ -516,7 +628,7 @@ func TestNewMetaProcessor_NilBlockSizeThrottlerShouldErr(t *testing.T) { arguments := createMockMetaArguments(createMockComponentHolders()) arguments.BlockSizeThrottler = nil - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilBlockSizeThrottler, err) assert.Nil(t, be) } @@ -527,7 +639,7 @@ func TestNewMetaProcessor_NilScheduledTxsExecutionHandlerShouldErr(t *testing.T) arguments := createMockMetaArguments(createMockComponentHolders()) arguments.ScheduledTxsExecutionHandler = nil - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilScheduledTxsExecutionHandler, err) assert.Nil(t, be) } @@ -538,7 +650,7 @@ func TestNewMetaProcessor_NilBlockProcessingCutoffHandlerShouldErr(t *testing.T) arguments := createMockMetaArguments(createMockComponentHolders()) arguments.BlockProcessingCutoffHandler = nil - be, err := blproc.NewMetaProcessor(arguments) + be, err := processBlock.NewMetaProcessor(arguments) assert.Equal(t, process.ErrNilBlockProcessingCutoffHandler, err) assert.Nil(t, be) } @@ -548,7 +660,7 @@ func TestNewMetaProcessor_OkValsShouldWork(t *testing.T) { arguments := createMockMetaArguments(createMockComponentHolders()) - mp, err := blproc.NewMetaProcessor(arguments) + mp, err := processBlock.NewMetaProcessor(arguments) assert.Nil(t, err) assert.NotNil(t, mp) } @@ -560,11 +672,11 @@ func TestMetaProcessor_CheckHeaderBodyCorrelationReceiverMissmatch(t *testing.T) hdr, body := createOneHeaderOneBody() arguments := createMockMetaArguments(createMockComponentHolders()) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) hdr.MiniBlockHeaders[0].ReceiverShardID = body.MiniBlocks[0].ReceiverShardID + 1 err := mp.CheckHeaderBodyCorrelation(hdr, body) - assert.Equal(t, process.ErrHeaderBodyMismatch, err) + assert.ErrorIs(t, err, process.ErrHeaderBodyMismatch) } func TestMetaProcessor_CheckHeaderBodyCorrelationSenderMissmatch(t *testing.T) { @@ -572,11 +684,11 @@ func TestMetaProcessor_CheckHeaderBodyCorrelationSenderMissmatch(t *testing.T) { hdr, body := createOneHeaderOneBody() arguments := createMockMetaArguments(createMockComponentHolders()) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) hdr.MiniBlockHeaders[0].SenderShardID = body.MiniBlocks[0].SenderShardID + 1 err := mp.CheckHeaderBodyCorrelation(hdr, body) - assert.Equal(t, process.ErrHeaderBodyMismatch, err) + assert.ErrorIs(t, err, process.ErrHeaderBodyMismatch) } func TestMetaProcessor_CheckHeaderBodyCorrelationTxCountMissmatch(t *testing.T) { @@ -584,7 +696,7 @@ func TestMetaProcessor_CheckHeaderBodyCorrelationTxCountMissmatch(t *testing.T) hdr, body := createOneHeaderOneBody() arguments := createMockMetaArguments(createMockComponentHolders()) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) hdr.MiniBlockHeaders[0].TxCount = uint32(len(body.MiniBlocks[0].TxHashes) + 1) err := mp.CheckHeaderBodyCorrelation(hdr, body) @@ -596,7 +708,7 @@ func TestMetaProcessor_CheckHeaderBodyCorrelationHashMissmatch(t *testing.T) { hdr, body := createOneHeaderOneBody() arguments := createMockMetaArguments(createMockComponentHolders()) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) hdr.MiniBlockHeaders[0].Hash = []byte("wrongHash") err := mp.CheckHeaderBodyCorrelation(hdr, body) @@ -608,18 +720,46 @@ func TestMetaProcessor_CheckHeaderBodyCorrelationShouldPass(t *testing.T) { hdr, body := createOneHeaderOneBody() arguments := createMockMetaArguments(createMockComponentHolders()) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) err := mp.CheckHeaderBodyCorrelation(hdr, body) assert.Nil(t, err) } +func TestMetaProcessor_CheckHeaderBodyCorrelationWrongProcessingIndexes(t *testing.T) { + t.Parallel() + + hdr, body := createOneHeaderOneBody() + arguments := createMockMetaArguments(createMockComponentHolders()) + mp, _ := processBlock.NewMetaProcessor(arguments) + + _ = hdr.MiniBlockHeaders[0].SetIndexOfFirstTxProcessed(0) + _ = hdr.MiniBlockHeaders[0].SetIndexOfLastTxProcessed(-1) + + err := mp.CheckHeaderBodyCorrelation(hdr, body) + require.NotNil(t, err) + require.ErrorContains(t, err, "index is out of bound") +} + +func TestMetaProcessor_CheckHeaderBodyCorrelationWrongConstructionState(t *testing.T) { + t.Parallel() + + hdr, body := createOneHeaderOneBody() + arguments := createMockMetaArguments(createMockComponentHolders()) + mp, _ := processBlock.NewMetaProcessor(arguments) + + _ = hdr.MiniBlockHeaders[0].SetConstructionState(int32(block.PartialExecuted)) + + err := mp.CheckHeaderBodyCorrelation(hdr, body) + require.NotNil(t, err) +} + func TestMetaProcessor_CheckHeaderBodyCorrelationNilMiniBlock(t *testing.T) { t.Parallel() hdr, body := createOneHeaderOneBody() arguments := createMockMetaArguments(createMockComponentHolders()) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) body.MiniBlocks[0] = nil @@ -635,7 +775,7 @@ func TestMetaProcessor_ProcessBlockWithNilHeaderShouldErr(t *testing.T) { arguments := createMockMetaArguments(createMockComponentHolders()) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) blk := &block.Body{} err := mp.ProcessBlock(nil, blk, haveTime) @@ -646,7 +786,7 @@ func TestMetaProcessor_ProcessBlockWithNilBlockBodyShouldErr(t *testing.T) { t.Parallel() arguments := createMockMetaArguments(createMockComponentHolders()) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) err := mp.ProcessBlock(&block.MetaBlock{}, nil, haveTime) assert.Equal(t, process.ErrNilBlockBody, err) @@ -656,13 +796,56 @@ func TestMetaProcessor_ProcessBlockWithNilHaveTimeFuncShouldErr(t *testing.T) { t.Parallel() arguments := createMockMetaArguments(createMockComponentHolders()) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) blk := &block.Body{} err := mp.ProcessBlock(&block.MetaBlock{}, blk, nil) assert.Equal(t, process.ErrNilHaveTimeHandler, err) } +func TestMetaProcessor_ProcessBlockShouldErrWhenProcessorBusy(t *testing.T) { + t.Parallel() + + arguments := createMockMetaArguments(createMockComponentHolders()) + + processHandler := arguments.CoreComponents.ProcessStatusHandler() + mockProcessHandler := processHandler.(*testscommon.ProcessStatusHandlerStub) + mockProcessHandler.TrySetBusyCalled = func(reason string) bool { + return false + } + + mp, _ := processBlock.NewMetaProcessor(arguments) + blk := &block.Body{} + + err := mp.ProcessBlock(&block.MetaBlock{ + Nonce: 1, + PubKeysBitmap: []byte("0100101"), + PrevHash: []byte(""), + Signature: []byte("signature"), + RootHash: []byte("roothash"), + }, blk, haveTime) + assert.Equal(t, process.ErrBlockProcessorBusy, err) +} + +func TestMetaProcessor_CreateBlockShouldErrWhenProcessorBusy(t *testing.T) { + t.Parallel() + + arguments := createMockMetaArguments(createMockComponentHolders()) + + processHandler := arguments.CoreComponents.ProcessStatusHandler() + mockProcessHandler := processHandler.(*testscommon.ProcessStatusHandlerStub) + mockProcessHandler.TrySetBusyCalled = func(reason string) bool { + return false + } + + mp, _ := processBlock.NewMetaProcessor(arguments) + + hdr, body, err := mp.CreateBlock(&block.MetaBlock{Round: 1}, func() bool { return true }) + assert.Equal(t, process.ErrBlockProcessorBusy, err) + assert.Nil(t, hdr) + assert.Nil(t, body) +} + func TestMetaProcessor_ProcessWithDirtyAccountShouldErr(t *testing.T) { t.Parallel() @@ -682,7 +865,7 @@ func TestMetaProcessor_ProcessWithDirtyAccountShouldErr(t *testing.T) { JournalLenCalled: journalLen, RevertToSnapshotCalled: revToSnapshot, } - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) // should return err err := mp.ProcessBlock(&hdr, body, haveTime) @@ -694,7 +877,7 @@ func TestMetaProcessor_ProcessWithHeaderNotFirstShouldErr(t *testing.T) { t.Parallel() arguments := createMockMetaArguments(createMockComponentHolders()) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) hdr := &block.MetaBlock{ Nonce: 2, @@ -719,7 +902,7 @@ func TestMetaProcessor_ProcessWithHeaderNotCorrectNonceShouldErr(t *testing.T) { dataComponents.BlockChain = blkc arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) hdr := &block.MetaBlock{ Round: 3, Nonce: 3, @@ -745,7 +928,7 @@ func TestMetaProcessor_ProcessWithHeaderNotCorrectPrevHashShouldErr(t *testing.T dataComponents.BlockChain = blkc arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) hdr := &block.MetaBlock{ Round: 2, Nonce: 2, @@ -791,11 +974,12 @@ func TestMetaProcessor_ProcessBlockWithErrOnVerifyStateRootCallShouldRevertState RevertToSnapshotCalled: revertToSnapshot, RootHashCalled: rootHashCalled, } - mp, _ := blproc.NewMetaProcessor(arguments) - - go func() { - mp.ChRcvAllHdrs() <- true - }() + arguments.HeadersForBlock = &testscommon.HeadersForBlockMock{ + WaitForHeadersIfNeededCalled: func(haveTime func() time.Duration) error { + return nil + }, + } + mp, _ := processBlock.NewMetaProcessor(arguments) // should return err mp.SetShardBlockFinality(0) @@ -806,47 +990,6 @@ func TestMetaProcessor_ProcessBlockWithErrOnVerifyStateRootCallShouldRevertState assert.True(t, wasCalled) } -// ------- requestFinalMissingHeader -func TestMetaProcessor_RequestFinalMissingHeaderShouldPass(t *testing.T) { - t.Parallel() - - mdp := initDataPool([]byte("tx_hash")) - accounts := &stateMock.AccountsStub{} - accounts.RevertToSnapshotCalled = func(snapshot int) error { - return nil - } - - coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() - dataComponents.DataPool = mdp - bootstrapComponents.Coordinator = mock.NewMultiShardsCoordinatorMock(3) - arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - mp, _ := blproc.NewMetaProcessor(arguments) - mp.AddHdrHashToRequestedList(&block.Header{}, []byte("header_hash")) - mp.SetHighestHdrNonceForCurrentBlock(0, 1) - mp.SetLastNotarizedHeaderForShard(0, &blproc.LastNotarizedHeaderInfo{ - Header: &block.Header{Nonce: 0, ShardID: 0}, - Hash: []byte("header hash"), - NotarizedBasedOnProof: false, - HasProof: false, - }) - mp.SetHighestHdrNonceForCurrentBlock(1, 2) - mp.SetLastNotarizedHeaderForShard(1, &blproc.LastNotarizedHeaderInfo{ - Header: &block.Header{Nonce: 2, ShardID: 1}, - Hash: []byte("header hash"), - NotarizedBasedOnProof: false, - HasProof: false, - }) - mp.SetHighestHdrNonceForCurrentBlock(2, 3) - mp.SetLastNotarizedHeaderForShard(2, &blproc.LastNotarizedHeaderInfo{ - Header: &block.Header{Nonce: 3, ShardID: 2}, - Hash: []byte("header hash"), - NotarizedBasedOnProof: false, - HasProof: false, - }) - res := mp.RequestMissingFinalityAttestingShardHeaders() - assert.Equal(t, uint32(3), res) -} - // ------- CommitBlock func TestMetaProcessor_CommitBlockMarshalizerFailForHeaderShouldErr(t *testing.T) { @@ -877,7 +1020,7 @@ func TestMetaProcessor_CommitBlockMarshalizerFailForHeaderShouldErr(t *testing.T coreComponents.IntMarsh = marshalizer arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) arguments.AccountsDB[state.UserAccountsState] = accounts - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) expectedFirstNonce := core.OptionalUint64{ HasValue: false, } @@ -924,6 +1067,8 @@ func TestMetaProcessor_CommitBlockStorageFailsForHeaderShouldNotReturnError(t *t _ = blkc.SetGenesisHeader(genesisHeader) coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + coreComponents.Hash = &hashingMocks.HasherMock{} + dataComponents.Storage = store dataComponents.BlockChain = blkc arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) @@ -943,7 +1088,7 @@ func TestMetaProcessor_CommitBlockStorageFailsForHeaderShouldNotReturnError(t *t } arguments.BlockTracker = blockTrackerMock arguments.StateAccessesCollector = &stateMock.StateAccessesCollectorStub{} - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) processHandler := arguments.CoreComponents.ProcessStatusHandler() mockProcessHandler := processHandler.(*testscommon.ProcessStatusHandlerStub) @@ -951,11 +1096,12 @@ func TestMetaProcessor_CommitBlockStorageFailsForHeaderShouldNotReturnError(t *t mockProcessHandler.SetIdleCalled = func() { busyIdleCalled = append(busyIdleCalled, idleIdentifier) } - mockProcessHandler.SetBusyCalled = func(reason string) { + mockProcessHandler.TrySetBusyCalled = func(reason string) bool { busyIdleCalled = append(busyIdleCalled, busyIdentifier) + return true } - mp.SetHdrForCurrentBlock([]byte("hdr_hash1"), &block.Header{}, true) + arguments.HeadersForBlock.AddHeaderUsedInBlock("hdr_hash1", &block.Header{}) expectedFirstNonce := core.OptionalUint64{ HasValue: false, } @@ -974,7 +1120,7 @@ func TestMetaProcessor_CommitBlockStorageFailsForHeaderShouldNotReturnError(t *t func TestMetaProcessor_CommitBlockNoTxInPoolShouldErr(t *testing.T) { t.Parallel() - mdp := initDataPool([]byte("tx_hash")) + mdp := initDataPool() hdr := createMetaBlockHeader() body := &block.Body{} accounts := &stateMock.AccountsStub{ @@ -996,7 +1142,7 @@ func TestMetaProcessor_CommitBlockNoTxInPoolShouldErr(t *testing.T) { arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) arguments.AccountsDB[state.UserAccountsState] = accounts arguments.ForkDetector = fd - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) mdp.HeadersCalled = func() dataRetriever.HeadersPool { return &mock.HeadersCacherStub{ @@ -1013,13 +1159,15 @@ func TestMetaProcessor_CommitBlockNoTxInPoolShouldErr(t *testing.T) { func TestMetaProcessor_CommitBlockOkValsShouldWork(t *testing.T) { t.Parallel() - mdp := initDataPool([]byte("tx_hash")) + commitCalled := false + mdp := initDataPool() rootHash := []byte("rootHash") hdr := createMetaBlockHeader() hdr.PubKeysBitmap = []byte{0b11111111} body := &block.Body{} accounts := &stateMock.AccountsStub{ CommitCalled: func() (i []byte, e error) { + commitCalled = true return rootHash, nil }, RootHashCalled: func() ([]byte, error) { @@ -1040,7 +1188,6 @@ func TestMetaProcessor_CommitBlockOkValsShouldWork(t *testing.T) { return 0 }, } - hasher := &mock.HasherStub{} blockHeaderUnit := &storageStubs.StorerStub{ PutCalled: func(key, data []byte) error { return nil @@ -1058,7 +1205,7 @@ func TestMetaProcessor_CommitBlockOkValsShouldWork(t *testing.T) { }, } - coreComponents.Hash = hasher + coreComponents.Hash = &hashingMocks.HasherMock{} arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) arguments.AccountsDB[state.UserAccountsState] = accounts arguments.AccountsDB[state.PeerAccountsState] = accounts @@ -1076,7 +1223,7 @@ func TestMetaProcessor_CommitBlockOkValsShouldWork(t *testing.T) { } arguments.StateAccessesCollector = &stateMock.StateAccessesCollectorStub{} - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) debuggerMethodWasCalled := false debugger := &testscommon.ProcessDebuggerStub{ @@ -1111,50 +1258,22 @@ func TestMetaProcessor_CommitBlockOkValsShouldWork(t *testing.T) { return cs } - mp.SetHdrForCurrentBlock([]byte("hdr_hash1"), &block.Header{}, true) + arguments.HeadersForBlock.AddHeaderUsedInBlock("hdr_hash1", &block.Header{}) err = mp.CommitBlock(hdr, body) assert.Nil(t, err) assert.True(t, forkDetectorAddCalled) assert.True(t, debuggerMethodWasCalled) assert.True(t, resetCountersForManagedBlockSignerCalled) + assert.True(t, commitCalled) // this should sleep as there is an async call to display current header and block in CommitBlock time.Sleep(time.Second) } -func TestBlockProc_RequestTransactionFromNetwork(t *testing.T) { - t.Parallel() - - mdp := initDataPool([]byte("tx_hash")) - - coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() - dataComponents.DataPool = mdp - dataComponents.Storage = initStore() - arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - mp, _ := blproc.NewMetaProcessor(arguments) - - mdp.HeadersCalled = func() dataRetriever.HeadersPool { - cs := &mock.HeadersCacherStub{} - cs.RegisterHandlerCalled = func(i func(header data.HeaderHandler, key []byte)) { - } - cs.GetHeaderByHashCalled = func(hash []byte) (handler data.HeaderHandler, e error) { - return nil, errors.New("err") - } - cs.MaxSizeCalled = func() int { - return 1000 - } - return cs - } - - header := createMetaBlockHeader() - hdrsRequested, _, _ := mp.RequestBlockHeaders(header) - assert.Equal(t, uint32(1), hdrsRequested) -} - func TestMetaProcessor_ApplyBodyToHeaderShouldWork(t *testing.T) { t.Parallel() coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() - dataComponents.DataPool = initDataPool([]byte("tx_hash")) + dataComponents.DataPool = initDataPool() dataComponents.Storage = initStore() arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ @@ -1166,7 +1285,7 @@ func TestMetaProcessor_ApplyBodyToHeaderShouldWork(t *testing.T) { }, } - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) hdr := &block.MetaBlock{} _, err := mp.ApplyBodyToHeader(hdr, &block.Body{}) @@ -1177,7 +1296,7 @@ func TestMetaProcessor_ApplyBodyToHeaderShouldSetEpochStart(t *testing.T) { t.Parallel() coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() - dataComponents.DataPool = initDataPool([]byte("tx_hash")) + dataComponents.DataPool = initDataPool() dataComponents.Storage = initStore() arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ @@ -1189,7 +1308,7 @@ func TestMetaProcessor_ApplyBodyToHeaderShouldSetEpochStart(t *testing.T) { }, } - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) metaBlk := &block.MetaBlock{TimeStamp: 12345} body := &block.Body{MiniBlocks: []*block.MiniBlock{{Type: 0}}} @@ -1208,15 +1327,17 @@ func TestMetaProcessor_CommitBlockShouldRevertCurrentBlockWhenErr(t *testing.T) } coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() - dataComponents.DataPool = initDataPool([]byte("tx_hash")) + dataComponents.DataPool = initDataPool() dataComponents.Storage = initStore() arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ RevertToSnapshotCalled: revToSnapshot, } - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) - err := mp.CommitBlock(nil, nil) + err := mp.CommitBlock(&block.MetaBlock{ + Nonce: 100, + }, &block.Body{}) assert.NotNil(t, err) assert.Equal(t, 0, journalEntries) } @@ -1224,7 +1345,7 @@ func TestMetaProcessor_CommitBlockShouldRevertCurrentBlockWhenErr(t *testing.T) func TestMetaProcessor_RevertStateRevertPeerStateFailsShouldErr(t *testing.T) { expectedErr := errors.New("err") coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() - dataComponents.DataPool = initDataPool([]byte("tx_hash")) + dataComponents.DataPool = initDataPool() dataComponents.Storage = initStore() arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{} @@ -1238,7 +1359,7 @@ func TestMetaProcessor_RevertStateRevertPeerStateFailsShouldErr(t *testing.T) { return expectedErr }, } - mp, err := blproc.NewMetaProcessor(arguments) + mp, err := processBlock.NewMetaProcessor(arguments) require.Nil(t, err) require.NotNil(t, mp) @@ -1252,7 +1373,7 @@ func TestMetaProcessor_RevertStateShouldWork(t *testing.T) { revertePeerStateWasCalled := false coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() - dataComponents.DataPool = initDataPool([]byte("tx_hash")) + dataComponents.DataPool = initDataPool() dataComponents.Storage = initStore() arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) @@ -1268,7 +1389,7 @@ func TestMetaProcessor_RevertStateShouldWork(t *testing.T) { return nil }, } - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) hdr := block.MetaBlock{Nonce: 37} err := mp.RevertStateToBlock(&hdr, hdr.RootHash) @@ -1280,48 +1401,112 @@ func TestMetaProcessor_RevertStateShouldWork(t *testing.T) { func TestMetaProcessor_MarshalizedDataToBroadcastShouldWork(t *testing.T) { t.Parallel() - coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() - dataComponents.Storage = initStore() - arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - mp, _ := blproc.NewMetaProcessor(arguments) + marshaller := &mock.MarshalizerMock{ + Fail: false, + } - msh, mstx, err := mp.MarshalizedDataToBroadcast(&block.MetaBlock{}, &block.Body{}) - assert.Nil(t, err) - assert.NotNil(t, msh) - assert.NotNil(t, mstx) -} + selfShardID := uint32(1) + otherShardID := uint32(2) -// ------- receivedHeader + mb1 := &block.MiniBlock{ + TxHashes: [][]byte{[]byte("txHash1")}, + ReceiverShardID: otherShardID, + SenderShardID: selfShardID, + } + marshalledMb, _ := marshaller.Marshal(mb1) -func TestMetaProcessor_ReceivedHeaderShouldDecreaseMissing(t *testing.T) { - t.Parallel() + t.Run("should work before header v3", func(t *testing.T) { + t.Parallel() - pool := dataRetrieverMock.NewPoolsHolderMock() - coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() - dataComponents.DataPool = pool - dataComponents.Storage = initStore() - arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - mp, _ := blproc.NewMetaProcessor(arguments) + shardCoordinator := testscommon.NewMultiShardsCoordinatorMock(3) + shardCoordinator.CurrentShard = selfShardID - // add 3 tx hashes on requested list - hdrHash1 := []byte("hdr hash 1") - hdrHash2 := []byte("hdr hash 2") - hdrHash3 := []byte("hdr hash 3") + mb1 := &block.MiniBlock{ + TxHashes: [][]byte{[]byte("txHash1")}, + ReceiverShardID: otherShardID, + SenderShardID: selfShardID, + } - hdr2 := &block.Header{Nonce: 2} + expectedBody := &block.Body{MiniBlocks: []*block.MiniBlock{mb1}} + expectedBodyMarshalled, _ := marshaller.Marshal(expectedBody) - mp.AddHdrHashToRequestedList(nil, hdrHash1) - mp.AddHdrHashToRequestedList(nil, hdrHash2) - mp.AddHdrHashToRequestedList(nil, hdrHash3) + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + bootstrapComponents.Coordinator = shardCoordinator + coreComponents.IntMarsh = marshaller - // received txHash2 - pool.Headers().AddHeader(hdrHash2, hdr2) + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + mp, _ := processBlock.NewMetaProcessor(arguments) + + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{[]byte("txHash1")}, + ReceiverShardID: otherShardID, + SenderShardID: selfShardID, + }, + }, + } + header := &block.MetaBlock{} + + msh, mstx, err := mp.MarshalizedDataToBroadcast(nil, header, body) + require.Nil(t, err) + + require.Nil(t, msh[selfShardID]) + require.Equal(t, expectedBodyMarshalled, msh[otherShardID]) + require.NotNil(t, mstx) + }) + + t.Run("should work with header v3", func(t *testing.T) { + t.Parallel() + + shardCoordinator := testscommon.NewMultiShardsCoordinatorMock(3) + shardCoordinator.CurrentShard = selfShardID + + expectedBody := &block.Body{MiniBlocks: []*block.MiniBlock{mb1}} + expectedBodyMarshalled, _ := marshaller.Marshal(expectedBody) + + executedMBs := &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + return marshalledMb, true + }, + } + dataPool := initDataPool() + dataPool.ExecutedMiniBlocksCalled = func() storage.Cacher { + return executedMBs + } + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + bootstrapComponents.Coordinator = shardCoordinator + dataComponents.DataPool = dataPool + coreComponents.IntMarsh = marshaller + + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + mp, _ := processBlock.NewMetaProcessor(arguments) - time.Sleep(100 * time.Millisecond) + executionResults := []*block.MetaExecutionResult{ + { + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("mbHash1"), + ReceiverShardID: otherShardID, + SenderShardID: selfShardID, + }, + }, + }, + } + header := &block.MetaBlockV3{ + ExecutionResults: executionResults, + } + + msh, mstx, err := mp.MarshalizedDataToBroadcast([]byte("hash"), header, &block.Body{}) + require.Nil(t, err) - assert.True(t, mp.IsHdrMissing(hdrHash1)) - assert.False(t, mp.IsHdrMissing(hdrHash2)) - assert.True(t, mp.IsHdrMissing(hdrHash3)) + require.Nil(t, msh[selfShardID]) + require.Equal(t, expectedBodyMarshalled, msh[otherShardID]) + require.NotNil(t, mstx) + }) } // ------- createShardInfo @@ -1390,19 +1575,19 @@ func TestMetaProcessor_CreateShardInfoShouldWorkNoHdrAddataRetrieverMockdedNotVa startHeaders := createGenesisBlocks(bootstrapComponents.ShardCoordinator()) arguments.BlockTracker = mock.NewBlockTrackerMock(bootstrapComponents.ShardCoordinator(), startHeaders) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) round := uint64(10) - shardInfo, err := mp.CreateShardInfo() + metaHdr := &block.MetaBlock{Round: round} + shardInfo, err := mp.CreateShardInfo(metaHdr) assert.Nil(t, err) assert.Equal(t, 0, len(shardInfo)) - metaHdr := &block.MetaBlock{Round: round} _, err = mp.CreateBlockBody(metaHdr, func() bool { return true }) assert.Nil(t, err) - shardInfo, err = mp.CreateShardInfo() + shardInfo, err = mp.CreateShardInfo(metaHdr) assert.Nil(t, err) assert.Equal(t, 1, len(shardInfo)) } @@ -1454,7 +1639,7 @@ func TestMetaProcessor_CreateShardInfoShouldWorkNoHdrAddedNotFinal(t *testing.T) startHeaders := createGenesisBlocks(bootstrapComponents.ShardCoordinator()) arguments.BlockTracker = mock.NewBlockTrackerMock(bootstrapComponents.ShardCoordinator(), startHeaders) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) haveTimeHandler := func() bool { return true } @@ -1498,14 +1683,14 @@ func TestMetaProcessor_CreateShardInfoShouldWorkNoHdrAddedNotFinal(t *testing.T) mp.SetShardBlockFinality(0) round := uint64(40) - shardInfo, err := mp.CreateShardInfo() + metaHdr := &block.MetaBlock{Round: round} + shardInfo, err := mp.CreateShardInfo(metaHdr) assert.Nil(t, err) assert.Equal(t, 0, len(shardInfo)) - metaHdr := &block.MetaBlock{Round: round} _, err = mp.CreateBlockBody(metaHdr, haveTimeHandler) assert.Nil(t, err) - shardInfo, err = mp.CreateShardInfo() + shardInfo, err = mp.CreateShardInfo(metaHdr) assert.Nil(t, err) assert.Equal(t, 3, len(shardInfo)) } @@ -1560,7 +1745,7 @@ func TestMetaProcessor_CreateShardInfoShouldWorkHdrsAdded(t *testing.T) { } startHeaders := createGenesisBlocks(bootstrapComponents.ShardCoordinator()) arguments.BlockTracker = mock.NewBlockTrackerMock(bootstrapComponents.ShardCoordinator(), startHeaders) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) haveTimeHandler := func() bool { return true } @@ -1650,14 +1835,14 @@ func TestMetaProcessor_CreateShardInfoShouldWorkHdrsAdded(t *testing.T) { mp.SetShardBlockFinality(1) round := uint64(15) - shardInfo, err := mp.CreateShardInfo() + metaHdr := &block.MetaBlock{Round: round} + shardInfo, err := mp.CreateShardInfo(metaHdr) assert.Nil(t, err) assert.Equal(t, 0, len(shardInfo)) - metaHdr := &block.MetaBlock{Round: round} _, err = mp.CreateBlockBody(metaHdr, haveTimeHandler) assert.Nil(t, err) - shardInfo, err = mp.CreateShardInfo() + shardInfo, err = mp.CreateShardInfo(metaHdr) assert.Nil(t, err) assert.Equal(t, 3, len(shardInfo)) } @@ -1713,7 +1898,7 @@ func TestMetaProcessor_CreateShardInfoEmptyBlockHDRRoundTooHigh(t *testing.T) { startHeaders := createGenesisBlocks(bootstrapComponents.ShardCoordinator()) arguments.BlockTracker = mock.NewBlockTrackerMock(bootstrapComponents.ShardCoordinator(), startHeaders) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) haveTimeHandler := func() bool { return true } @@ -1803,14 +1988,14 @@ func TestMetaProcessor_CreateShardInfoEmptyBlockHDRRoundTooHigh(t *testing.T) { mp.SetShardBlockFinality(1) round := uint64(20) - shardInfo, err := mp.CreateShardInfo() + metaHdr := &block.MetaBlock{Round: round} + shardInfo, err := mp.CreateShardInfo(metaHdr) assert.Nil(t, err) assert.Equal(t, 0, len(shardInfo)) - metaHdr := &block.MetaBlock{Round: round} _, err = mp.CreateBlockBody(metaHdr, haveTimeHandler) assert.Nil(t, err) - shardInfo, err = mp.CreateShardInfo() + shardInfo, err = mp.CreateShardInfo(metaHdr) assert.Nil(t, err) assert.Equal(t, 3, len(shardInfo)) } @@ -1821,7 +2006,7 @@ func TestMetaProcessor_RestoreBlockIntoPoolsShouldErrNilMetaBlockHeader(t *testi coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() dataComponents.Storage = initStore() arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) err := mp.RestoreBlockIntoPools(nil, nil) assert.NotNil(t, err) @@ -1855,7 +2040,7 @@ func TestMetaProcessor_RestoreBlockIntoPoolsShouldWork(t *testing.T) { dataComponents.DataPool = pool dataComponents.Storage = store arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) mhdr := createMetaBlockHeader() @@ -1889,7 +2074,7 @@ func TestMetaProcessor_CreateLastNotarizedHdrs(t *testing.T) { startHeaders := createGenesisBlocks(bootstrapComponents.ShardCoordinator()) arguments.BlockTracker = mock.NewBlockTrackerMock(bootstrapComponents.ShardCoordinator(), startHeaders) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) prevRandSeed := []byte("prevrand") currRandSeed := []byte("currrand") @@ -1937,8 +2122,8 @@ func TestMetaProcessor_CreateLastNotarizedHdrs(t *testing.T) { // wrong header type in pool and defer called pool.Headers().AddHeader(currHash, metaHdr) pool.Headers().AddHeader(prevHash, prevHdr) - mp.SetHdrForCurrentBlock(currHash, metaHdr, true) - mp.SetHdrForCurrentBlock(prevHash, prevHdr, true) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(currHash), metaHdr) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(prevHash), prevHdr) err = mp.SaveLastNotarizedHeader(metaHdr) assert.Equal(t, process.ErrWrongTypeAssertion, err) @@ -1948,14 +2133,84 @@ func TestMetaProcessor_CreateLastNotarizedHdrs(t *testing.T) { pool.Headers().AddHeader(currHash, currHdr) pool.Headers().AddHeader(prevHash, prevHdr) _ = mp.CreateBlockStarted() - mp.SetHdrForCurrentBlock(currHash, currHdr, true) - mp.SetHdrForCurrentBlock(prevHash, prevHdr, true) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(currHash), currHdr) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(prevHash), prevHdr) err = mp.SaveLastNotarizedHeader(metaHdr) assert.Nil(t, err) assert.Equal(t, currHdr, mp.LastNotarizedHdrForShard(currHdr.ShardID)) } +func TestMetaProcessor_saveLastNotarizedHeader(t *testing.T) { + t.Parallel() + + t.Run("get headerV3 by hash error case", func(t *testing.T) { + t.Parallel() + + headersPool := &poolMock.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, expectedErr + }, + } + pool := dataRetrieverMock.NewPoolsHolderMock() + pool.SetHeadersPool(headersPool) + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents.DataPool = pool + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, _ := processBlock.NewMetaProcessor(arguments) + + metaHdr := &block.MetaBlockV3{} + shDataCurr := block.ShardDataProposal{HeaderHash: []byte("hash")} + metaHdr.ShardInfoProposal = make([]block.ShardDataProposal, 0) + metaHdr.ShardInfoProposal = append(metaHdr.ShardInfoProposal, shDataCurr) + + err := mp.SaveLastNotarizedHeader(metaHdr) + require.Error(t, err, expectedErr) + }) + t.Run("get headerV3 by hash from headers pool, should work", func(t *testing.T) { + t.Parallel() + + hdr := &block.Header{} + hdrHash := []byte("hash") + + wasCalledGetHeaderFromPool := false + headersPool := &poolMock.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + wasCalledGetHeaderFromPool = true + require.Equal(t, hdrHash, hash) + return hdr, nil + }, + } + pool := dataRetrieverMock.NewPoolsHolderMock() + pool.SetHeadersPool(headersPool) + + hdrsForCurrBlock := &testscommon.HeadersForBlockMock{ + GetHeaderInfoCalled: func(_ string) (headerForBlock.HeaderInfo, bool) { + require.Fail(t, "should not be called for headerV3") + return nil, false + }, + } + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents.DataPool = pool + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.HeadersForBlock = hdrsForCurrBlock + mp, _ := processBlock.NewMetaProcessor(arguments) + + pool.Headers().AddHeader(hdrHash, hdr) + + metaHdr := &block.MetaBlockV3{} + shDataCurr := block.ShardDataProposal{HeaderHash: hdrHash} + metaHdr.ShardInfoProposal = make([]block.ShardDataProposal, 0) + metaHdr.ShardInfoProposal = append(metaHdr.ShardInfoProposal, shDataCurr) + + err := mp.SaveLastNotarizedHeader(metaHdr) + require.NoError(t, err) + require.True(t, wasCalledGetHeaderFromPool) + }) +} + func TestMetaProcessor_CheckShardHeadersValidity(t *testing.T) { t.Parallel() @@ -1980,14 +2235,14 @@ func TestMetaProcessor_CheckShardHeadersValidity(t *testing.T) { startHeaders := createGenesisBlocks(bootstrapComponents.ShardCoordinator()) arguments.BlockTracker = mock.NewBlockTrackerMock(bootstrapComponents.ShardCoordinator(), startHeaders) - argsHeaderValidator := blproc.ArgsHeaderValidator{ + argsHeaderValidator := processBlock.ArgsHeaderValidator{ Hasher: coreComponents.Hash, Marshalizer: coreComponents.InternalMarshalizer(), EnableEpochsHandler: coreComponents.EnableEpochsHandler(), } - arguments.HeaderValidator, _ = blproc.NewHeaderValidator(argsHeaderValidator) + arguments.HeaderValidator, _ = processBlock.NewHeaderValidator(argsHeaderValidator) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) prevRandSeed := []byte("prevrand") currRandSeed := []byte("currrand") @@ -2047,8 +2302,8 @@ func TestMetaProcessor_CheckShardHeadersValidity(t *testing.T) { shDataPrev := block.ShardData{ShardID: 0, HeaderHash: prevHash} metaHdr.ShardInfo = append(metaHdr.ShardInfo, shDataPrev) - mp.SetHdrForCurrentBlock(wrongCurrHash, wrongCurrHdr, true) - mp.SetHdrForCurrentBlock(prevHash, prevHdr, true) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(wrongCurrHash), wrongCurrHdr) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(prevHash), prevHdr) _, err := mp.CheckShardHeadersValidity(metaHdr) assert.True(t, errors.Is(err, process.ErrWrongNonceInBlock)) @@ -2070,8 +2325,8 @@ func TestMetaProcessor_CheckShardHeadersValidity(t *testing.T) { metaHdr.ShardInfo = append(metaHdr.ShardInfo, shDataPrev) _ = mp.CreateBlockStarted() - mp.SetHdrForCurrentBlock(currHash, currHdr, true) - mp.SetHdrForCurrentBlock(prevHash, prevHdr, true) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(currHash), currHdr) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(prevHash), prevHdr) highestNonceHdrs, err := mp.CheckShardHeadersValidity(metaHdr) assert.Nil(t, err) @@ -2101,7 +2356,7 @@ func TestMetaProcessor_CheckShardHeadersValidityWrongNonceFromLastNoted(t *testi startHeaders := createGenesisBlocks(bootstrapComponents.ShardCoordinator()) arguments.BlockTracker = mock.NewBlockTrackerMock(bootstrapComponents.ShardCoordinator(), startHeaders) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) prevRandSeed := []byte("prevrand") currRandSeed := []byte("currrand") @@ -2125,7 +2380,7 @@ func TestMetaProcessor_CheckShardHeadersValidityWrongNonceFromLastNoted(t *testi metaHdr.ShardInfo = make([]block.ShardData, 0) metaHdr.ShardInfo = append(metaHdr.ShardInfo, shDataCurr) - mp.SetHdrForCurrentBlock(currHash, currHdr, true) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(currHash), currHdr) highestNonceHdrs, err := mp.CheckShardHeadersValidity(metaHdr) assert.Nil(t, highestNonceHdrs) @@ -2158,7 +2413,7 @@ func TestMetaProcessor_CheckShardHeadersValidityRoundZeroLastNoted(t *testing.T) startHeaders := createGenesisBlocks(bootstrapComponents.ShardCoordinator()) arguments.BlockTracker = mock.NewBlockTrackerMock(bootstrapComponents.ShardCoordinator(), startHeaders) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) prevRandSeed := startHeaders[0].GetRandSeed() currRandSeed := []byte("currrand") @@ -2196,7 +2451,7 @@ func TestMetaProcessor_CheckShardHeadersValidityRoundZeroLastNoted(t *testing.T) assert.Nil(t, err) pool.Headers().AddHeader(currHash, currHdr) - mp.SetHdrForCurrentBlock(currHash, currHdr, true) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(currHash), currHdr) highestNonceHdrs, err = mp.CheckShardHeadersValidity(metaHdr) assert.NotNil(t, highestNonceHdrs) assert.Nil(t, err) @@ -2225,7 +2480,7 @@ func TestMetaProcessor_CheckShardHeadersFinality(t *testing.T) { startHeaders := createGenesisBlocks(bootstrapComponents.ShardCoordinator()) arguments.BlockTracker = mock.NewBlockTrackerMock(bootstrapComponents.ShardCoordinator(), startHeaders) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) prevRandSeed := []byte("prevrand") currRandSeed := []byte("currrand") @@ -2307,7 +2562,7 @@ func TestMetaProcessor_CheckShardHeadersFinality(t *testing.T) { nextHash, _ := mp.ComputeHeaderHash(nextHdr) pool.Headers().AddHeader(nextHash, nextHdr) - mp.SetHdrForCurrentBlock(nextHash, nextHdr, false) + arguments.HeadersForBlock.AddHeaderNotUsedInBlock(string(nextHash), nextHdr) metaHdr.Round = 20 err = mp.CheckShardHeadersFinality(highestNonceHdrs) @@ -2336,7 +2591,7 @@ func TestMetaProcessor_IsHdrConstructionValid(t *testing.T) { startHeaders := createGenesisBlocks(bootstrapComponents.ShardCoordinator()) arguments.BlockTracker = mock.NewBlockTrackerMock(bootstrapComponents.ShardCoordinator(), startHeaders) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) prevRandSeed := []byte("prevrand") currRandSeed := []byte("currrand") @@ -2409,7 +2664,7 @@ func TestMetaProcessor_DecodeBlockBody(t *testing.T) { marshalizerMock := &mock.MarshalizerMock{} arguments := createMockMetaArguments(createMockComponentHolders()) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) b := &block.Body{} message, err := marshalizerMock.Marshal(b) assert.Nil(t, err) @@ -2433,7 +2688,7 @@ func TestMetaProcessor_DecodeBlockHeader(t *testing.T) { }, } arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) hdr := &block.MetaBlock{} hdr.Nonce = 1 hdr.TimeStamp = uint64(0) @@ -2458,7 +2713,7 @@ func TestMetaProcessor_UpdateShardsHeadersNonce_ShouldWork(t *testing.T) { t.Parallel() arguments := createMockMetaArguments(createMockComponentHolders()) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) numberOfShards := uint32(4) type DataForMap struct { @@ -2505,7 +2760,7 @@ func TestMetaProcessor_CreateMiniBlocksJournalLenNotZeroShouldReturnEmptyBody(t } arguments := createMockMetaArguments(createMockComponentHolders()) arguments.AccountsDB[state.UserAccountsState] = accntAdapter - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) round := uint64(10) metaHdr := &block.MetaBlock{Round: round} @@ -2518,7 +2773,7 @@ func TestMetaProcessor_CreateMiniBlocksNoTimeShouldReturnEmptyBody(t *testing.T) t.Parallel() arguments := createMockMetaArguments(createMockComponentHolders()) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) round := uint64(10) metaHdr := &block.MetaBlock{Round: round} @@ -2542,7 +2797,7 @@ func TestMetaProcessor_CreateMiniBlocksDestMe(t *testing.T) { hdrHash2Bytes := []byte("hdr_hash2") expectedMiniBlock1 := &block.MiniBlock{TxHashes: [][]byte{hash1}} expectedMiniBlock2 := &block.MiniBlock{TxHashes: [][]byte{[]byte("hash2")}} - dPool := initDataPool([]byte("tx_hash")) + dPool := initDataPool() dPool.TransactionsCalled = func() dataRetriever.ShardedDataCacherNotifier { return testscommon.NewShardedDataStub() } @@ -2586,7 +2841,7 @@ func TestMetaProcessor_CreateMiniBlocksDestMe(t *testing.T) { arguments.TxCoordinator = txCoordinator arguments.BlockTracker.AddTrackedHeader(hdr1, hdrHash1Bytes) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) round := uint64(10) metaHdr := &block.MetaBlock{Round: round} @@ -2634,7 +2889,7 @@ func TestMetaProcessor_ProcessBlockWrongHeaderShouldErr(t *testing.T) { JournalLenCalled: journalLen, RevertToSnapshotCalled: revToSnapshot, } - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) // should return err err := mp.ProcessBlock(&hdr, body, haveTime) @@ -2689,10 +2944,15 @@ func TestMetaProcessor_ProcessBlockNoShardHeadersReceivedShouldErr(t *testing.T) JournalLenCalled: journalLen, RevertToSnapshotCalled: revToSnapshot, } - mp, _ := blproc.NewMetaProcessor(arguments) + arguments.HeadersForBlock = &testscommon.HeadersForBlockMock{ + WaitForHeadersIfNeededCalled: func(haveTime func() time.Duration) error { + return expectedErr + }, + } + mp, _ := processBlock.NewMetaProcessor(arguments) err := mp.ProcessBlock(&hdr, body, haveTime) - assert.Equal(t, process.ErrTimeIsOut, err) + assert.Equal(t, expectedErr, err) } func TestMetaProcessor_VerifyCrossShardMiniBlocksDstMe(t *testing.T) { @@ -2704,7 +2964,7 @@ func TestMetaProcessor_VerifyCrossShardMiniBlocksDstMe(t *testing.T) { hdrHash2Bytes := []byte("hdr_hash2") miniBlock1 := &block.MiniBlock{TxHashes: [][]byte{hash1}} miniBlock2 := &block.MiniBlock{TxHashes: [][]byte{hash2}} - dPool := initDataPool([]byte("tx_hash")) + dPool := initDataPool() dPool.TransactionsCalled = func() dataRetriever.ShardedDataCacherNotifier { return testscommon.NewShardedDataStub() } @@ -2775,7 +3035,7 @@ func TestMetaProcessor_VerifyCrossShardMiniBlocksDstMe(t *testing.T) { arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) arguments.TxCoordinator = txCoordinator - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) round := uint64(10) metaHdr := &block.MetaBlock{Round: round} @@ -2783,7 +3043,8 @@ func TestMetaProcessor_VerifyCrossShardMiniBlocksDstMe(t *testing.T) { assert.Nil(t, err) err = mp.VerifyCrossShardMiniBlockDstMe(hdr) - assert.Nil(t, err) + assert.Error(t, err) + assert.ErrorIs(t, err, process.ErrMissingHeader) } func TestMetaProcess_CreateNewBlockHeaderProcessHeaderExpectCheckRoundCalled(t *testing.T) { @@ -2803,7 +3064,7 @@ func TestMetaProcess_CreateNewBlockHeaderProcessHeaderExpectCheckRoundCalled(t * coreComponents.RoundNotifierField = roundsNotifier arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - metaProcessor, _ := blproc.NewMetaProcessor(arguments) + metaProcessor, _ := processBlock.NewMetaProcessor(arguments) metaHeader := &block.MetaBlock{Round: round} bodyHandler, _ := metaProcessor.CreateBlockBody(metaHeader, func() bool { return true }) @@ -2825,7 +3086,7 @@ func TestMetaProcessor_CreateBlockCreateHeaderProcessBlock(t *testing.T) { return hash } miniBlock1 := &block.MiniBlock{TxHashes: [][]byte{hash}} - dPool := initDataPool([]byte("tx_hash")) + dPool := initDataPool() dPool.TransactionsCalled = func() dataRetriever.ShardedDataCacherNotifier { return testscommon.NewShardedDataStub() } @@ -2883,7 +3144,7 @@ func TestMetaProcessor_CreateBlockCreateHeaderProcessBlock(t *testing.T) { dataComponents.DataPool = dPool dataComponents.BlockChain = blkc bootstrapComponents.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ - CreateCalled: func(epoch uint32) data.HeaderHandler { + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { return &block.MetaBlock{ Epoch: 0, } @@ -2892,7 +3153,7 @@ func TestMetaProcessor_CreateBlockCreateHeaderProcessBlock(t *testing.T) { arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) arguments.TxCoordinator = txCoordinator - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) round := uint64(10) nonce := uint64(5) @@ -2924,7 +3185,7 @@ func TestMetaProcessor_RequestShardHeadersIfNeededShouldAddHeaderIntoTrackerPool t.Parallel() var addedNonces []uint64 - poolsHolderStub := initDataPool([]byte("")) + poolsHolderStub := initDataPool() poolsHolderStub.HeadersCalled = func() dataRetriever.HeadersPool { return &mock.HeadersCacherStub{ GetHeaderByNonceAndShardIdCalled: func(hdrNonce uint64, shardId uint32) ([]data.HeaderHandler, [][]byte, error) { @@ -2939,7 +3200,7 @@ func TestMetaProcessor_RequestShardHeadersIfNeededShouldAddHeaderIntoTrackerPool roundHandlerMock := &mock.RoundHandlerMock{} coreComponents.RoundField = roundHandlerMock arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) roundHandlerMock.RoundIndex = 20 header := &block.Header{ @@ -2971,7 +3232,7 @@ func TestMetaProcessor_CreateAndProcessBlockCallsProcessAfterFirstEpoch(t *testi coreComponents.TxSignHasherField = hasher miniBlock1 := &block.MiniBlock{TxHashes: [][]byte{hash}} - dPool := initDataPool([]byte("tx_hash")) + dPool := initDataPool() dPool.TransactionsCalled = func() dataRetriever.ShardedDataCacherNotifier { return testscommon.NewShardedDataStub() } @@ -3058,11 +3319,12 @@ func TestMetaProcessor_CreateAndProcessBlockCallsProcessAfterFirstEpoch(t *testi mockProcessHandler.SetIdleCalled = func() { busyIdleCalled = append(busyIdleCalled, idleIdentifier) } - mockProcessHandler.SetBusyCalled = func(reason string) { + mockProcessHandler.TrySetBusyCalled = func(reason string) bool { busyIdleCalled = append(busyIdleCalled, busyIdentifier) + return true } - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) metaHdr := &block.MetaBlock{} headerHandler, bodyHandler, err := mp.CreateBlock(metaHdr, func() bool { return true }) assert.Nil(t, err) @@ -3103,7 +3365,7 @@ func TestMetaProcessor_CreateNewHeaderErrWrongTypeAssertion(t *testing.T) { Coordinator: mock.NewOneShardCoordinatorMock(), HdrIntegrityVerifier: &mock.HeaderIntegrityVerifierStub{}, VersionedHdrFactory: &testscommon.VersionedHeaderFactoryStub{ - CreateCalled: func(epoch uint32) data.HeaderHandler { + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { return &block.Header{} }, }, @@ -3111,7 +3373,7 @@ func TestMetaProcessor_CreateNewHeaderErrWrongTypeAssertion(t *testing.T) { arguments := createMockMetaArguments(cc, dc, boostrapComponents, sc) - mp, err := blproc.NewMetaProcessor(arguments) + mp, err := processBlock.NewMetaProcessor(arguments) assert.Nil(t, err) h, err := mp.CreateNewHeader(1, 1) @@ -3134,14 +3396,14 @@ func TestMetaProcessor_CreateNewHeaderValsOK(t *testing.T) { Coordinator: mock.NewOneShardCoordinatorMock(), HdrIntegrityVerifier: &mock.HeaderIntegrityVerifierStub{}, VersionedHdrFactory: &testscommon.VersionedHeaderFactoryStub{ - CreateCalled: func(epoch uint32) data.HeaderHandler { + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { return &block.Header{} }, }, } boostrapComponents.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ - CreateCalled: func(epoch uint32) data.HeaderHandler { + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { return &block.MetaBlock{ Epoch: epoch, } @@ -3160,7 +3422,7 @@ func TestMetaProcessor_CreateNewHeaderValsOK(t *testing.T) { }, } - mp, err := blproc.NewMetaProcessor(arguments) + mp, err := processBlock.NewMetaProcessor(arguments) assert.Nil(t, err) h, err := mp.CreateNewHeader(round, nonce) @@ -3178,6 +3440,93 @@ func TestMetaProcessor_CreateNewHeaderValsOK(t *testing.T) { assert.Equal(t, zeroInt, metaHeader.DevFeesInEpoch) } +func TestCreateNewHeaderProposal(t *testing.T) { + t.Parallel() + + rootHash := []byte("root") + round := uint64(7) + nonce := uint64(10) + epoch := uint32(5) + + coreComponents, dataComponents, _, statusComponents := createMockComponentHolders() + coreComponents.EnableRoundsHandlerField = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledCalled: func(flag common.EnableRoundFlag) bool { + return true + }, + } + // supernova epoch flag enabled + coreComponents.EnableEpochsHandlerField = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return true + }, + } + + boostrapComponents := &mock.BootstrapComponentsMock{ + Coordinator: mock.NewOneShardCoordinatorMock(), + HdrIntegrityVerifier: &mock.HeaderIntegrityVerifierStub{}, + VersionedHdrFactory: &testscommon.VersionedHeaderFactoryStub{ + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { + return &block.MetaBlockV3{ + Epoch: epoch, + } + }, + }, + } + + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + dataComponents = &mock.DataComponentsMock{ + BlockChain: &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlockV3{ + Epoch: epoch, + LastExecutionResult: &block.MetaExecutionResultInfo{ + NotarizedInRound: 10, + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash2"), + HeaderNonce: 9, + HeaderRound: 9, + HeaderEpoch: epoch, + RootHash: []byte("root hash"), + GasUsed: 1000, + }, + }, + }, + } + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash") + }, + }, + DataPool: arguments.DataComponents.Datapool(), + Storage: arguments.DataComponents.StorageService(), + } + + arguments.DataComponents = dataComponents + arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return rootHash, nil + }, + } + + arguments.EpochStartTrigger = &mock.EpochStartTriggerStub{ + EpochCalled: func() uint32 { + return epoch + }, + ShouldProposeEpochChangeCalled: func(round uint64, nonce uint64) bool { + return true + }, + } + + mp, err := processBlock.NewMetaProcessor(arguments) + assert.Nil(t, err) + + newHeader, err := mp.CreateNewHeaderProposal(round, nonce) + require.Nil(t, err) + require.IsType(t, &block.MetaBlockV3{}, newHeader) + require.Equal(t, epoch, newHeader.GetEpoch()) +} + func TestMetaProcessor_ProcessEpochStartMetaBlock(t *testing.T) { t.Parallel() @@ -3217,7 +3566,7 @@ func TestMetaProcessor_ProcessEpochStartMetaBlock(t *testing.T) { }, } - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) err := mp.ProcessEpochStartMetaBlock(headerMeta, &block.Body{}) assert.Nil(t, err) @@ -3256,7 +3605,7 @@ func TestMetaProcessor_ProcessEpochStartMetaBlock(t *testing.T) { }, } - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) err := mp.ProcessEpochStartMetaBlock(headerMeta, &block.Body{}) assert.Nil(t, err) @@ -3284,14 +3633,13 @@ func TestMetaProcessor_UpdateEpochStartHeader(t *testing.T) { t.Run("fail to compute end of epoch economics", func(t *testing.T) { arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - expectedErr := errors.New("expected error") arguments.EpochEconomics = &mock.EpochEconomicsStub{ - ComputeEndOfEpochEconomicsCalled: func(metaBlock *block.MetaBlock) (*block.Economics, error) { + ComputeEndOfEpochEconomicsCalled: func(metaBlock data.MetaHeaderHandler) (*block.Economics, error) { return nil, expectedErr }, } - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) header := &block.MetaBlock{ AccumulatedFeesInEpoch: big.NewInt(0), @@ -3316,12 +3664,12 @@ func TestMetaProcessor_UpdateEpochStartHeader(t *testing.T) { PrevEpochStartHash: []byte("prevEpochStartHash"), } arguments.EpochEconomics = &mock.EpochEconomicsStub{ - ComputeEndOfEpochEconomicsCalled: func(metaBlock *block.MetaBlock) (*block.Economics, error) { + ComputeEndOfEpochEconomicsCalled: func(metaBlock data.MetaHeaderHandler) (*block.Economics, error) { return expectedEconomics, nil }, } - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) header := &block.MetaBlock{ AccumulatedFeesInEpoch: big.NewInt(0), @@ -3353,7 +3701,7 @@ func TestMetaProcessor_CreateEpochStartBodyShouldFail(t *testing.T) { }, } - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) body, err := mp.CreateEpochStartBody(&block.MetaBlock{}) assert.Equal(t, expectedErr, err) @@ -3371,7 +3719,7 @@ func TestMetaProcessor_CreateEpochStartBodyShouldFail(t *testing.T) { }, } - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) body, err := mp.CreateEpochStartBody(&block.MetaBlock{}) assert.Equal(t, expectedErr, err) @@ -3389,7 +3737,7 @@ func TestMetaProcessor_CreateEpochStartBodyShouldFail(t *testing.T) { }, } - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) body, err := mp.CreateEpochStartBody(&block.MetaBlock{}) assert.Equal(t, expectedErr, err) @@ -3489,7 +3837,7 @@ func TestMetaProcessor_CreateEpochStartBodyShouldWork(t *testing.T) { }, } - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) miniBlocks := make([]*block.MiniBlock, 0) miniBlocks = append(miniBlocks, rewardMiniBlocks...) @@ -3567,7 +3915,7 @@ func TestMetaProcessor_CreateEpochStartBodyShouldWork(t *testing.T) { }, } - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) miniBlocks := make([]*block.MiniBlock, 0) miniBlocks = append(miniBlocks, rewardMiniBlocks...) @@ -3590,7 +3938,7 @@ func TestMetaProcessor_getFinalMiniBlockHashes(t *testing.T) { coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) expectedMbHeaders := make([]data.MiniBlockHeaderHandler, 1) @@ -3605,7 +3953,7 @@ func TestMetaProcessor_getFinalMiniBlockHashes(t *testing.T) { coreComponents.EnableEpochsHandlerField = enableEpochsHandlerMock.NewEnableEpochsHandlerStub(common.ScheduledMiniBlocksFlag) arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) mbh1 := &block.MiniBlockHeader{ Hash: []byte("hash1"), @@ -3663,7 +4011,7 @@ func TestMetaProcessor_getAllMarshalledTxs(t *testing.T) { }, } - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) body := &block.Body{ MiniBlocks: []*block.MiniBlock{ @@ -3737,13 +4085,13 @@ func TestMetaProcessor_CrossChecksBlockHeightsMetrics(t *testing.T) { Coordinator: mock.NewMultiShardsCoordinatorMock(3), HdrIntegrityVerifier: &mock.HeaderIntegrityVerifierStub{}, VersionedHdrFactory: &testscommon.VersionedHeaderFactoryStub{ - CreateCalled: func(epoch uint32) data.HeaderHandler { + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { return &block.MetaBlock{} }, }, } - mp, _ := blproc.NewMetaProcessor(arguments) + mp, _ := processBlock.NewMetaProcessor(arguments) mp.UpdateShardsHeadersNonce(0, 37) mp.UpdateShardsHeadersNonce(1, 38) @@ -3757,3 +4105,1209 @@ func TestMetaProcessor_CrossChecksBlockHeightsMetrics(t *testing.T) { requireInstance.Equal(uint64(38), savedMetrics["erd_cross_check_block_height_1"]) requireInstance.Equal(uint64(39), savedMetrics["erd_cross_check_block_height_2"]) } + +func TestMetaProcessor_PruneTrieAsyncHeader(t *testing.T) { + t.Parallel() + + t.Run("prune trie for headerV3, prev header is headerV2", func(t *testing.T) { + t.Parallel() + + rootHash1 := []byte("state root hash 1") + validatorStatsRootHash1 := []byte("validator stats root hash 1") + + prevHeader := &block.MetaBlock{ + RootHash: rootHash1, + ValidatorStatsRootHash: validatorStatsRootHash1, + } + pruneTrieForHeaderV3Test(t, prevHeader, rootHash1, validatorStatsRootHash1) + }) + + t.Run("prune trie for headerV3, prev header is headerV3", func(t *testing.T) { + t.Parallel() + + rootHash1 := []byte("state root hash 1") + validatorStatsRootHash1 := []byte("validator stats root hash 1") + prevHeader := &block.MetaBlockV3{ + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + RootHash: rootHash1, + }, + ValidatorStatsRootHash: validatorStatsRootHash1, + }, + }, + } + pruneTrieForHeaderV3Test(t, prevHeader, rootHash1, validatorStatsRootHash1) + }) +} + +func TestMetaProcessor_UpdateState(t *testing.T) { + t.Parallel() + + t.Run("trigger updateStateStorage for metaBlock", func(t *testing.T) { + t.Parallel() + + pruneCalledForUserAccounts := false + pruneCalledForPeerAccounts := false + cancelPruneCalledForUserAccounts := false + cancelPruneCalledForPeerAccounts := false + + prevHeader := &block.MetaBlock{ + RootHash: []byte("prev state root hash"), + ValidatorStatsRootHash: []byte("prev validator stats root hash"), + } + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + dataPool := initDataPool() + dataPool.HeadersCalled = func() dataRetriever.HeadersPool { + return &mock.HeadersCacherStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return prevHeader, nil + }, + } + } + dataComponents.DataPool = dataPool + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + arguments.AccountsDB = map[state.AccountsDbIdentifier]state.AccountsAdapter{ + state.UserAccountsState: &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { + return true + }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + cancelPruneCalledForUserAccounts = true + }, + PruneTrieCalled: func(rootHash []byte, identifier state.TriePruningIdentifier, handler state.PruningHandler) { + pruneCalledForUserAccounts = true + }, + }, + state.PeerAccountsState: &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { + return true + }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + cancelPruneCalledForPeerAccounts = true + }, + PruneTrieCalled: func(rootHash []byte, identifier state.TriePruningIdentifier, handler state.PruningHandler) { + pruneCalledForPeerAccounts = true + }, + }, + } + + mp, _ := processBlock.NewMetaProcessor(arguments) + + metaBlockHash := []byte("meta block hash") + metaBlock := &block.MetaBlock{ + RootHash: []byte("state root hash"), + ValidatorStatsRootHash: []byte("validator stats root hash"), + } + + mp.UpdateState(metaBlock, metaBlockHash) + + assert.True(t, pruneCalledForUserAccounts) + assert.True(t, pruneCalledForPeerAccounts) + assert.True(t, cancelPruneCalledForUserAccounts) + assert.True(t, cancelPruneCalledForPeerAccounts) + }) +} + +func pruneTrieForHeaderV3Test(t *testing.T, prevHeader data.HeaderHandler, rootHash1 []byte, validatorStatsRootHash1 []byte) { + pruneCalledForUserAccounts := 0 + pruneCalledForPeerAccounts := 0 + cancelPruneCalledForUserAccounts := 0 + cancelPruneCalledForPeerAccounts := 0 + + rootHash2 := []byte("state root hash 2") + validatorStatsRootHash2 := []byte("validator stats root hash 2") + + coreComponents, dataComponents, boostrapComponents, statusComponents := createMockComponentHolders() + dataPool := initDataPool() + dataPool.HeadersCalled = func() dataRetriever.HeadersPool { + return &mock.HeadersCacherStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return prevHeader, nil + }, + } + } + dataComponents.DataPool = dataPool + arguments := createMockMetaArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + arguments.AccountsDB = map[state.AccountsDbIdentifier]state.AccountsAdapter{ + state.UserAccountsState: &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { + return true + }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + if bytes.Equal(rootHash, rootHash1) { + cancelPruneCalledForUserAccounts++ + return + } + if bytes.Equal(rootHash, rootHash2) { + cancelPruneCalledForUserAccounts++ + return + } + assert.Fail(t, "unexpected root hash in CancelPruneCalled for user accounts") + }, + PruneTrieCalled: func(rootHash []byte, identifier state.TriePruningIdentifier, handler state.PruningHandler) { + if bytes.Equal(rootHash, rootHash1) { + pruneCalledForUserAccounts++ + return + } + if bytes.Equal(rootHash, rootHash2) { + pruneCalledForUserAccounts++ + return + } + assert.Fail(t, "unexpected root hash in PruneTrieCalled for user accounts") + }, + }, + state.PeerAccountsState: &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { + return true + }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + if bytes.Equal(rootHash, validatorStatsRootHash1) { + cancelPruneCalledForPeerAccounts++ + return + } + if bytes.Equal(rootHash, validatorStatsRootHash2) { + cancelPruneCalledForPeerAccounts++ + return + } + assert.Fail(t, "unexpected root hash in CancelPruneCalled for peer accounts") + }, + PruneTrieCalled: func(rootHash []byte, identifier state.TriePruningIdentifier, handler state.PruningHandler) { + if bytes.Equal(rootHash, validatorStatsRootHash1) { + pruneCalledForPeerAccounts++ + return + } + if bytes.Equal(rootHash, validatorStatsRootHash2) { + pruneCalledForPeerAccounts++ + return + } + assert.Fail(t, "unexpected root hash in PruneTrieCalled for peer accounts") + }, + }, + } + + metaBlock := &block.MetaBlockV3{ + PrevHash: []byte("hash"), + ExecutionResults: []*block.MetaExecutionResult{ + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + RootHash: rootHash2, + }, + ValidatorStatsRootHash: validatorStatsRootHash2, + }, + }, + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + RootHash: []byte("state root hash 3"), + }, + ValidatorStatsRootHash: []byte("validator stats root hash 3"), + }, + }, + }, + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + RootHash: []byte("state root hash 3"), + }, + ValidatorStatsRootHash: []byte("validator stats root hash 3"), + }, + }, + } + + blkc := createTestBlockchain() + blkc.GetCurrentBlockHeaderCalled = func() data.HeaderHandler { + return metaBlock + } + blkc.GetCurrentBlockHeaderHashCalled = func() []byte { + return []byte("metaHash") + } + dataComponents.BlockChain = blkc + + mp, _ := processBlock.NewMetaProcessor(arguments) + + mp.PruneTrieAsyncHeader() + + assert.Equal(t, 2, pruneCalledForUserAccounts) + assert.Equal(t, 2, pruneCalledForPeerAccounts) + assert.Equal(t, 2, cancelPruneCalledForUserAccounts) + assert.Equal(t, 2, cancelPruneCalledForPeerAccounts) +} + +func TestMetaProcessor_prepareEpochStartBodyForTrigger(t *testing.T) { + t.Parallel() + + metaBlockV3 := &block.MetaBlockV3{ + ExecutionResults: []*block.MetaExecutionResult{ + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hdrHash1"), + }, + }, + }, + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hdrHash2"), + }, + }, + }, + }, + } + + t.Run("meta header v1", func(t *testing.T) { + t.Parallel() + + args := createMockMetaArguments(createMockComponentHolders()) + mp, _ := processBlock.NewMetaProcessor(args) + + body := &block.Body{MiniBlocks: []*block.MiniBlock{{ReceiverShardID: 1}}} + res, err := mp.PrepareEpochStartBodyForTrigger(&block.MetaBlock{}, body) + require.Nil(t, err) + require.Equal(t, body, res) + }) + + t.Run("cannot get executed mbs from data pool", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataPoolMock := initDataPool() + dataPoolMock.ExecutedMiniBlocksCalled = func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + return nil, false + }, + } + } + + dataComponents.DataPool = dataPoolMock + args := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, _ := processBlock.NewMetaProcessor(args) + res, err := mp.PrepareEpochStartBodyForTrigger(metaBlockV3, nil) + require.Nil(t, res) + require.ErrorIs(t, err, trie.ErrKeyNotFound) + }) + + t.Run("retrieved data from pool is not byte array", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataPoolMock := initDataPool() + dataPoolMock.ExecutedMiniBlocksCalled = func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + return "data as string", true + }, + } + } + + dataComponents.DataPool = dataPoolMock + args := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, _ := processBlock.NewMetaProcessor(args) + res, err := mp.PrepareEpochStartBodyForTrigger(metaBlockV3, nil) + require.Nil(t, res) + require.ErrorIs(t, err, process.ErrWrongTypeAssertion) + }) + + t.Run("cannot assert to miniblocks, should fail", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataPoolMock := initDataPool() + dataPoolMock.ExecutedMiniBlocksCalled = func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + return []*block.HeaderV2{}, true + }, + } + } + + dataComponents.DataPool = dataPoolMock + args := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, _ := processBlock.NewMetaProcessor(args) + + res, err := mp.PrepareEpochStartBodyForTrigger(metaBlockV3, nil) + require.Nil(t, res) + require.ErrorIs(t, err, process.ErrWrongTypeAssertion) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + hdrHash1 := []byte("hdrHash1") + hdrHash2 := []byte("hdrHash2") + + metaBlock := &block.MetaBlockV3{ + ExecutionResults: []*block.MetaExecutionResult{ + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: hdrHash1, + }, + }, + }, + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: hdrHash2, + }, + }, + }, + }, + } + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataPoolMock := initDataPool() + + mbs := []*block.MiniBlock{ + { + Type: block.PeerBlock, + }, + { + Type: block.RewardsBlock, + }, + } + + dataPoolMock.ExecutedMiniBlocksCalled = func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + if bytes.Equal(key, hdrHash1) { + return mbs, true + } + + return []*block.MiniBlock{}, true + }, + } + } + + dataComponents.DataPool = dataPoolMock + args := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + mp, _ := processBlock.NewMetaProcessor(args) + + res, err := mp.PrepareEpochStartBodyForTrigger(metaBlock, nil) + require.Nil(t, err) + require.Equal(t, &block.Body{MiniBlocks: mbs}, res) + }) +} + +func TestMetaProcessor_commitEpochStartMetaBlockV3(t *testing.T) { + t.Parallel() + + metaBlockV3 := &block.MetaBlockV3{ + ExecutionResults: []*block.MetaExecutionResult{ + { + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hdrHash1"), + }, + }, + }, + }, + EpochStart: block.EpochStart{LastFinalizedHeaders: make([]block.EpochStartShardData, 1)}, + } + + mbs := []*block.MiniBlock{ + { + Type: block.PeerBlock, + }, + } + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataPoolMock := initDataPool() + dataPoolMock.ExecutedMiniBlocksCalled = func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + return mbs, true + }, + } + } + dataComponents.DataPool = dataPoolMock + + expectedBody := &block.Body{MiniBlocks: mbs} + + args := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + args.EpochStartTrigger = &mock.EpochStartTriggerStub{ + SetProcessedCalled: func(header data.HeaderHandler, body data.BodyHandler) { + require.Equal(t, metaBlockV3, header) + require.Equal(t, expectedBody, body) + }, + } + + wg := sync.WaitGroup{} + wg.Add(2) + args.EpochRewardsCreator = &testscommon.RewardsCreatorStub{ + SaveBlockDataToStorageCalled: func(metaBlock data.MetaHeaderHandler, body *block.Body) { + wg.Done() + require.Equal(t, metaBlockV3, metaBlock) + require.Equal(t, expectedBody, body) + }, + } + args.EpochValidatorInfoCreator = &testscommon.EpochValidatorInfoCreatorStub{ + SaveBlockDataToStorageCalled: func(metaBlock data.HeaderHandler, body *block.Body) { + wg.Done() + require.Equal(t, metaBlockV3, metaBlock) + require.Equal(t, expectedBody, body) + }, + } + + mp, _ := processBlock.NewMetaProcessor(args) + + // Call this with empty block body + err := mp.CommitEpochStart(metaBlockV3, &block.Body{}) + require.Nil(t, err) + wg.Wait() +} + +func TestPrepareBlockHeaderInternalMapForValidatorProcessor(t *testing.T) { + t.Parallel() + + t.Run("get current block header should work", func(t *testing.T) { + t.Parallel() + + metaHdr := &block.MetaBlock{} + metaHdrHash := []byte("hash") + wasAddHeaderNotUsedInBlockCalled := false + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return metaHdr + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return metaHdrHash + }, + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + hdrsForCurrBlock := &testscommon.HeadersForBlockMock{ + AddHeaderNotUsedInBlockCalled: func(hash string, header data.HeaderHandler) { + require.Equal(t, string(metaHdrHash), hash) + require.Equal(t, metaHdr, header) + wasAddHeaderNotUsedInBlockCalled = true + }, + } + arguments.HeadersForBlock = hdrsForCurrBlock + + mp, _ := processBlock.NewMetaProcessor(arguments) + mp.PrepareBlockHeaderInternalMapForValidatorProcessor(&block.MetaBlock{}) + require.True(t, wasAddHeaderNotUsedInBlockCalled) + }) + t.Run("get last executed block for headerV3 should work", func(t *testing.T) { + t.Parallel() + + metaHdr := &block.MetaBlockV3{} + metaHdrHash := []byte("hash") + wasAddHeaderNotUsedInBlockCalled := false + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return metaHdr + }, + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return 0, metaHdrHash, nil + }, + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + hdrsForCurrBlock := &testscommon.HeadersForBlockMock{ + AddHeaderNotUsedInBlockCalled: func(hash string, header data.HeaderHandler) { + require.Equal(t, string(metaHdrHash), hash) + require.Equal(t, metaHdr, header) + wasAddHeaderNotUsedInBlockCalled = true + }, + } + arguments.HeadersForBlock = hdrsForCurrBlock + + mp, _ := processBlock.NewMetaProcessor(arguments) + mp.PrepareBlockHeaderInternalMapForValidatorProcessor(&block.MetaBlockV3{}) + require.True(t, wasAddHeaderNotUsedInBlockCalled) + }) +} + +func TestUpdatePeerState(t *testing.T) { + t.Parallel() + + t.Run("update peer state should work", func(t *testing.T) { + t.Parallel() + + hdr := &block.MetaBlock{} + expectedRootHash := []byte("rootHash") + wasUpdatePeerStateCalled := false + + arguments := createMockMetaArguments(createMockComponentHolders()) + arguments.ValidatorStatisticsProcessor = &testscommon.ValidatorStatisticsProcessorStub{ + UpdatePeerStateCalled: func(header data.MetaHeaderHandler) ([]byte, error) { + require.Equal(t, hdr, header) + wasUpdatePeerStateCalled = true + return expectedRootHash, nil + }, + } + + mp, _ := processBlock.NewMetaProcessor(arguments) + rootHash, err := mp.UpdatePeerState(hdr, map[string]data.HeaderHandler{"": &block.MetaBlock{}}) + require.NoError(t, err) + require.Equal(t, expectedRootHash, rootHash) + require.True(t, wasUpdatePeerStateCalled) + }) + t.Run("update peer state v3 missing last execution result, should error", func(t *testing.T) { + t.Parallel() + + hdr := &block.MetaBlockV3{} + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return nil + }, + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + mp, _ := processBlock.NewMetaProcessor(arguments) + rootHash, err := mp.UpdatePeerState(hdr, map[string]data.HeaderHandler{"": &block.MetaBlockV3{}}) + require.ErrorContains(t, err, "missing last execution result") + require.Nil(t, rootHash) + }) + t.Run("update peer state v3 invalid last execution result, should error", func(t *testing.T) { + t.Parallel() + + hdr := &block.MetaBlockV3{} + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.ExecutionResult{} + }, + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + mp, _ := processBlock.NewMetaProcessor(arguments) + rootHash, err := mp.UpdatePeerState(hdr, map[string]data.HeaderHandler{"": &block.MetaBlockV3{}}) + require.ErrorIs(t, err, process.ErrWrongTypeAssertion) + require.Nil(t, rootHash) + }) + t.Run("update peer state v3 should work", func(t *testing.T) { + t.Parallel() + + hdr := &block.MetaBlockV3{} + metaExecResult := &block.MetaExecutionResult{} + wasUpdatePeerStateCalled := false + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return metaExecResult + }, + } + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ValidatorStatisticsProcessor = &testscommon.ValidatorStatisticsProcessorStub{ + UpdatePeerStateV3Called: func(header data.MetaHeaderHandler, metaExecutionResult data.MetaExecutionResultHandler) ([]byte, error) { + require.Equal(t, hdr, header) + require.Equal(t, metaExecResult, metaExecutionResult) + wasUpdatePeerStateCalled = true + + return make([]byte, 0), nil + }, + } + + mp, _ := processBlock.NewMetaProcessor(arguments) + _, err := mp.UpdatePeerState(hdr, map[string]data.HeaderHandler{"": &block.MetaBlockV3{}}) + require.NoError(t, err) + require.True(t, wasUpdatePeerStateCalled) + }) + t.Run("update peer state v3 in supernova enable round", func(t *testing.T) { + t.Parallel() + + prevHash := []byte("prevHdr") + hdr := &block.MetaBlockV3{PrevHash: prevHash} + + accFees := big.NewInt(1) + devFees := big.NewInt(2) + + expectedExecRes := &block.MetaExecutionResult{ + AccumulatedFees: accFees, + DeveloperFees: devFees, + } + prevHdr := &block.MetaBlock{ + AccumulatedFees: accFees, + DeveloperFees: devFees, + } + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + coreComponents.EnableRoundsHandlerField = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return flag == common.SupernovaRoundFlag + }, + } + + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + require.Fail(t, "should not get last execution result from blockchain hook") + return nil + }, + } + + wasUpdatePeerStateCalled := false + expectedRootHash := []byte("rootHash") + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ValidatorStatisticsProcessor = &testscommon.ValidatorStatisticsProcessorStub{ + UpdatePeerStateV3Called: func(header data.MetaHeaderHandler, metaExecutionResult data.MetaExecutionResultHandler) ([]byte, error) { + require.Equal(t, hdr, header) + require.Equal(t, expectedExecRes, metaExecutionResult) + wasUpdatePeerStateCalled = true + + return expectedRootHash, nil + }, + } + + mp, _ := processBlock.NewMetaProcessor(arguments) + + // No previous header found in cache, should return error + mapCache := make(map[string]data.HeaderHandler) + rootHash, err := mp.UpdatePeerState(hdr, mapCache) + require.Nil(t, rootHash) + require.ErrorIs(t, err, processBlock.ErrNilPreviousHdr) + require.False(t, wasUpdatePeerStateCalled) + + mapCache[string(prevHash)] = prevHdr + rootHash, err = mp.UpdatePeerState(hdr, mapCache) + require.Nil(t, err) + require.Equal(t, expectedRootHash, rootHash) + require.True(t, wasUpdatePeerStateCalled) + }) +} + +func Test_getCurrentlyAccumulatedFees(t *testing.T) { + t.Parallel() + + t.Run("should return reset fees for epoch start v3", func(t *testing.T) { + t.Parallel() + + mp, err := processBlock.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{}) + require.NoError(t, err) + + metaBlock := &block.MetaBlockV3{ + EpochChangeProposed: true, + } + currentlyAccumulatedFeesInEpoch, currentDevFeesInEpoch, err := mp.GetCurrentlyAccumulatedFees(metaBlock) + require.Nil(t, err) + require.Equal(t, big.NewInt(0), currentDevFeesInEpoch) + require.Equal(t, big.NewInt(0), currentlyAccumulatedFeesInEpoch) + }) + + t.Run("should propagate the error in case of nil last execution result", func(t *testing.T) { + t.Parallel() + + mp, err := processBlock.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{}) + require.NoError(t, err) + + metaBlock := &block.MetaBlockV3{} + currentlyAccumulatedFeesInEpoch, currentDevFeesInEpoch, err := mp.GetCurrentlyAccumulatedFees(metaBlock) + require.Nil(t, currentlyAccumulatedFeesInEpoch) + require.Nil(t, currentDevFeesInEpoch) + require.Equal(t, common.ErrNilLastExecutionResultHandler, err) + }) + + t.Run("should propagate the error in case of nil base execution result", func(t *testing.T) { + t.Parallel() + + mp, err := processBlock.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{}) + require.NoError(t, err) + + metaBlock := &block.MetaBlockV3{ + LastExecutionResult: &block.MetaExecutionResultInfo{}, + } + currentlyAccumulatedFeesInEpoch, currentDevFeesInEpoch, err := mp.GetCurrentlyAccumulatedFees(metaBlock) + require.Nil(t, currentlyAccumulatedFeesInEpoch) + require.Nil(t, currentDevFeesInEpoch) + require.Equal(t, common.ErrNilBaseExecutionResult, err) + }) + + t.Run("should return accumulated fees for v3", func(t *testing.T) { + t.Parallel() + + mp, err := processBlock.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{}) + require.NoError(t, err) + + metaBlock := &block.MetaBlockV3{ + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + AccumulatedFeesInEpoch: big.NewInt(10), + DevFeesInEpoch: big.NewInt(10), + }, + }, + } + currentlyAccumulatedFeesInEpoch, currentDevFeesInEpoch, err := mp.GetCurrentlyAccumulatedFees(metaBlock) + require.Nil(t, err) + require.Equal(t, big.NewInt(10), currentDevFeesInEpoch) + require.Equal(t, big.NewInt(10), currentlyAccumulatedFeesInEpoch) + }) + + t.Run("should return reset values in case of not v3 and nil current block header", func(t *testing.T) { + t.Parallel() + + mp, err := processBlock.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return nil + }, + }, + }) + require.NoError(t, err) + + metaBlock := &block.MetaBlock{} + currentlyAccumulatedFeesInEpoch, currentDevFeesInEpoch, err := mp.GetCurrentlyAccumulatedFees(metaBlock) + require.Nil(t, err) + require.Equal(t, big.NewInt(0), currentDevFeesInEpoch) + require.Equal(t, big.NewInt(0), currentlyAccumulatedFeesInEpoch) + }) + + t.Run("should return ErrWrongTypeAssertion because of wrong type of last current block header", func(t *testing.T) { + t.Parallel() + + mp, err := processBlock.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.Header{} + }, + }, + }) + require.NoError(t, err) + + metaBlock := &block.MetaBlock{} + _, _, err = mp.GetCurrentlyAccumulatedFees(metaBlock) + require.Equal(t, common.ErrWrongTypeAssertion, err) + }) + + t.Run("should return reset values in case of not v3 and epoch start block", func(t *testing.T) { + t.Parallel() + + mp, err := processBlock.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlock{ + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + {}, {}, {}, + }, + }, + } + }, + }, + }) + require.NoError(t, err) + + metaBlock := &block.MetaBlock{} + currentlyAccumulatedFeesInEpoch, currentDevFeesInEpoch, err := mp.GetCurrentlyAccumulatedFees(metaBlock) + require.Nil(t, err) + require.Equal(t, big.NewInt(0), currentDevFeesInEpoch) + require.Equal(t, big.NewInt(0), currentlyAccumulatedFeesInEpoch) + }) + + t.Run("should accumulate fees", func(t *testing.T) { + t.Parallel() + + mp, err := processBlock.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{ + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlock{ + AccumulatedFeesInEpoch: big.NewInt(5), + DevFeesInEpoch: big.NewInt(5), + } + }, + }, + }) + require.NoError(t, err) + + metaBlock := &block.MetaBlock{} + currentlyAccumulatedFeesInEpoch, currentDevFeesInEpoch, err := mp.GetCurrentlyAccumulatedFees(metaBlock) + require.Nil(t, err) + require.Equal(t, big.NewInt(5), currentDevFeesInEpoch) + require.Equal(t, big.NewInt(5), currentlyAccumulatedFeesInEpoch) + }) +} + +func TestMetaProcessor_CommitBlockV3FailAfterHeadMutationShouldRestoreChainHead(t *testing.T) { + t.Parallel() + + t.Run("should restore nil head after failed V3 commit", func(t *testing.T) { + t.Parallel() + + genesisHeaderHash := []byte("genesis_hash") + computedHeaderHash := []byte("computed_header_hash") + + var currentHeader data.HeaderHandler + var currentHeaderHash []byte + + testBlockchain := &testscommon.ChainHandlerStub{ + GetGenesisHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlock{Nonce: 0} + }, + GetGenesisHeaderHashCalled: func() []byte { + return genesisHeaderHash + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return currentHeader + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return currentHeaderHash + }, + SetCurrentBlockHeaderCalled: func(header data.HeaderHandler) error { + currentHeader = header + return nil + }, + SetCurrentBlockHeaderHashCalled: func(hash []byte) { + currentHeaderHash = hash + }, + SetFinalBlockInfoCalled: func(nonce uint64, headerHash []byte, rootHash []byte) {}, + } + + v3Header := &block.MetaBlockV3{ + Nonce: 1, + Round: 1, + PrevHash: genesisHeaderHash, + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("nonexistent_exec_result_hash"), + HeaderNonce: 1, + RootHash: []byte("exec_root_hash"), + }, + }, + }, + } + body := &block.Body{} + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + coreComponents.Hash = &mock.HasherStub{ + ComputeCalled: func(s string) []byte { + return computedHeaderHash + }, + } + + dataComponents.BlockChain = testBlockchain + dataComponents.Storage = initStore() + + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ExecutionManager = &processMocks.ExecutionManagerMock{} + arguments.ForkDetector = &mock.ForkDetectorMock{} + arguments.BlockTracker = mock.NewBlockTrackerMock(mock.NewOneShardCoordinatorMock(), createGenesisBlocks(mock.NewOneShardCoordinatorMock())) + + mp, err := processBlock.NewMetaProcessor(arguments) + require.Nil(t, err) + + require.Nil(t, testBlockchain.GetCurrentBlockHeader()) + require.Nil(t, testBlockchain.GetCurrentBlockHeaderHash()) + + err = mp.CommitBlock(v3Header, body) + require.NotNil(t, err) + + assert.Nil(t, testBlockchain.GetCurrentBlockHeader(), + "currentBlockHeader should be restored to nil after failed V3 commit") + assert.Nil(t, testBlockchain.GetCurrentBlockHeaderHash(), + "currentBlockHeaderHash should be restored to nil after failed V3 commit") + }) + + t.Run("should restore non-nil head after failed V3 commit", func(t *testing.T) { + t.Parallel() + + prevHeaderHash := []byte("prev_header_hash") + computedHeaderHash := []byte("computed_header_hash") + + prevHeader := &block.MetaBlockV3{ + Nonce: 5, + Round: 10, + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("prev_exec_hash"), + HeaderNonce: 5, + RootHash: []byte("prev_root_hash"), + }, + }, + }, + } + + var currentHeader data.HeaderHandler = prevHeader + currentHeaderHash := make([]byte, len(prevHeaderHash)) + copy(currentHeaderHash, prevHeaderHash) + + testBlockchain := &testscommon.ChainHandlerStub{ + GetGenesisHeaderCalled: func() data.HeaderHandler { + return &block.MetaBlock{Nonce: 0} + }, + GetGenesisHeaderHashCalled: func() []byte { + return []byte("genesis_hash") + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return currentHeader + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return currentHeaderHash + }, + SetCurrentBlockHeaderCalled: func(header data.HeaderHandler) error { + currentHeader = header + return nil + }, + SetCurrentBlockHeaderHashCalled: func(hash []byte) { + currentHeaderHash = hash + }, + SetFinalBlockInfoCalled: func(nonce uint64, headerHash []byte, rootHash []byte) {}, + } + + v3Header := &block.MetaBlockV3{ + Nonce: 6, + Round: 12, + PrevHash: prevHeaderHash, + LastExecutionResult: &block.MetaExecutionResultInfo{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("nonexistent_exec_result_hash"), + HeaderNonce: 6, + RootHash: []byte("exec_root_hash"), + }, + }, + }, + } + body := &block.Body{} + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + coreComponents.Hash = &mock.HasherStub{ + ComputeCalled: func(s string) []byte { + return computedHeaderHash + }, + } + + dataComponents.BlockChain = testBlockchain + dataComponents.Storage = initStore() + + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ExecutionManager = &processMocks.ExecutionManagerMock{} + arguments.ForkDetector = &mock.ForkDetectorMock{} + arguments.BlockTracker = mock.NewBlockTrackerMock(mock.NewOneShardCoordinatorMock(), createGenesisBlocks(mock.NewOneShardCoordinatorMock())) + + mp, err := processBlock.NewMetaProcessor(arguments) + require.Nil(t, err) + + require.Equal(t, prevHeader, testBlockchain.GetCurrentBlockHeader()) + require.Equal(t, prevHeaderHash, testBlockchain.GetCurrentBlockHeaderHash()) + + err = mp.CommitBlock(v3Header, body) + require.NotNil(t, err) + + assert.Equal(t, prevHeader, testBlockchain.GetCurrentBlockHeader(), + "currentBlockHeader should be restored to previous header after failed V3 commit") + assert.Equal(t, prevHeaderHash, testBlockchain.GetCurrentBlockHeaderHash(), + "currentBlockHeaderHash should be restored to previous hash after failed V3 commit") + }) +} + +func TestMetaProcessor_CancelPruneForDismissedExecutionResults(t *testing.T) { + t.Parallel() + + t.Run("both pruning disabled should not call CancelPrune", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { return false }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + require.Fail(t, "CancelPrune should not be called on user accounts") + }, + } + arguments.AccountsDB[state.PeerAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { return false }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + require.Fail(t, "CancelPrune should not be called on peer accounts") + }, + } + + mp, err := processBlock.NewMetaProcessor(arguments) + require.Nil(t, err) + + batches := []executionTrack.DismissedBatch{ + { + AnchorResult: &block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R0")}, + ValidatorStatsRootHash: []byte("V0"), + }, + }, + Results: []data.BaseExecutionResultHandler{ + &block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R1")}, + ValidatorStatsRootHash: []byte("V1"), + }, + }, + }, + }, + } + mp.CancelPruneForDismissedExecutionResults(batches) + }) + t.Run("user and peer pruning enabled should cancel prune for both", func(t *testing.T) { + t.Parallel() + + type cancelPruneCall struct { + rootHash []byte + identifier state.TriePruningIdentifier + } + userCalls := make([]cancelPruneCall, 0) + peerCalls := make([]cancelPruneCall, 0) + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { return true }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + userCalls = append(userCalls, cancelPruneCall{rootHash: rootHash, identifier: identifier}) + }, + } + arguments.AccountsDB[state.PeerAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { return true }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + peerCalls = append(peerCalls, cancelPruneCall{rootHash: rootHash, identifier: identifier}) + }, + } + + mp, err := processBlock.NewMetaProcessor(arguments) + require.Nil(t, err) + + // Dismissed chain: anchor(R0,V0) -> (R1,V1) -> (R2,V2) + batches := []executionTrack.DismissedBatch{ + { + AnchorResult: &block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R0")}, + ValidatorStatsRootHash: []byte("V0"), + }, + }, + Results: []data.BaseExecutionResultHandler{ + &block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R1")}, + ValidatorStatsRootHash: []byte("V1"), + }, + }, + &block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R2")}, + ValidatorStatsRootHash: []byte("V2"), + }, + }, + }, + }, + } + mp.CancelPruneForDismissedExecutionResults(batches) + + // User accounts: 2 transitions, 2 CancelPrune calls each = 4 + require.Len(t, userCalls, 4) + require.Equal(t, []byte("R1"), userCalls[0].rootHash) + require.Equal(t, state.NewRoot, userCalls[0].identifier) + require.Equal(t, []byte("R0"), userCalls[1].rootHash) + require.Equal(t, state.OldRoot, userCalls[1].identifier) + require.Equal(t, []byte("R2"), userCalls[2].rootHash) + require.Equal(t, state.NewRoot, userCalls[2].identifier) + require.Equal(t, []byte("R1"), userCalls[3].rootHash) + require.Equal(t, state.OldRoot, userCalls[3].identifier) + + // Peer accounts: 2 transitions, 2 CancelPrune calls each = 4 + require.Len(t, peerCalls, 4) + require.Equal(t, []byte("V1"), peerCalls[0].rootHash) + require.Equal(t, state.NewRoot, peerCalls[0].identifier) + require.Equal(t, []byte("V0"), peerCalls[1].rootHash) + require.Equal(t, state.OldRoot, peerCalls[1].identifier) + require.Equal(t, []byte("V2"), peerCalls[2].rootHash) + require.Equal(t, state.NewRoot, peerCalls[2].identifier) + require.Equal(t, []byte("V1"), peerCalls[3].rootHash) + require.Equal(t, state.OldRoot, peerCalls[3].identifier) + }) + t.Run("result not implementing BaseMetaExecutionResultHandler should skip peer cancel prune", func(t *testing.T) { + t.Parallel() + + userCancelCalls := 0 + peerCancelCalls := 0 + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { return true }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + userCancelCalls++ + }, + } + arguments.AccountsDB[state.PeerAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { return true }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + peerCancelCalls++ + }, + } + + mp, err := processBlock.NewMetaProcessor(arguments) + require.Nil(t, err) + + // Use ExecutionResult (not MetaExecutionResult) - does NOT implement BaseMetaExecutionResultHandler + batches := []executionTrack.DismissedBatch{ + { + AnchorResult: &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R0")}, + }, + Results: []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R1")}, + }, + }, + }, + } + mp.CancelPruneForDismissedExecutionResults(batches) + + // User accounts should still get CancelPrune (R0->R1 = 2 calls) + require.Equal(t, 2, userCancelCalls) + // Peer accounts should get 0 calls (validator root hashes are nil from non-meta results) + require.Equal(t, 0, peerCancelCalls) + }) + t.Run("only user pruning enabled should skip peer operations", func(t *testing.T) { + t.Parallel() + + userCancelCalls := 0 + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createMockComponentHolders() + arguments := createMockMetaArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { return true }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + userCancelCalls++ + }, + } + arguments.AccountsDB[state.PeerAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { return false }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + require.Fail(t, "CancelPrune should not be called on peer accounts when pruning disabled") + }, + } + + mp, err := processBlock.NewMetaProcessor(arguments) + require.Nil(t, err) + + batches := []executionTrack.DismissedBatch{ + { + AnchorResult: &block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R0")}, + ValidatorStatsRootHash: []byte("V0"), + }, + }, + Results: []data.BaseExecutionResultHandler{ + &block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R1")}, + ValidatorStatsRootHash: []byte("V1"), + }, + }, + }, + }, + } + mp.CancelPruneForDismissedExecutionResults(batches) + + require.Equal(t, 2, userCancelCalls) + }) +} diff --git a/process/block/metrics.go b/process/block/metrics.go index f55a5bfe43b..e6c8f3061fb 100644 --- a/process/block/metrics.go +++ b/process/block/metrics.go @@ -23,7 +23,7 @@ import ( const leaderIndex = 0 func getMetricsFromMetaHeader( - header *block.MetaBlock, + header data.MetaHeaderHandler, marshalizer marshal.Marshalizer, appStatusHandler core.AppStatusHandler, numShardHeadersFromPool int, @@ -32,8 +32,8 @@ func getMetricsFromMetaHeader( numMiniBlocksMetaBlock := uint64(0) headerSize := uint64(0) - for _, shardInfo := range header.ShardInfo { - numMiniBlocksMetaBlock += uint64(len(shardInfo.ShardMiniBlockHeaders)) + for _, shardInfo := range header.GetShardInfoHandlers() { + numMiniBlocksMetaBlock += uint64(len(shardInfo.GetShardMiniBlockHeaderHandlers())) } marshalizedHeader, err := marshalizer.Marshal(header) @@ -42,7 +42,7 @@ func getMetricsFromMetaHeader( } appStatusHandler.SetUInt64Value(common.MetricHeaderSize, headerSize) - appStatusHandler.SetUInt64Value(common.MetricNumTxInBlock, uint64(header.TxCount)) + appStatusHandler.SetUInt64Value(common.MetricNumTxInBlock, uint64(header.GetTxCount())) appStatusHandler.SetUInt64Value(common.MetricNumMiniBlocks, numMiniBlocksMetaBlock) appStatusHandler.SetUInt64Value(common.MetricNumShardHeadersProcessed, numShardHeadersProcessed) appStatusHandler.SetUInt64Value(common.MetricNumShardHeadersFromPool, uint64(numShardHeadersFromPool)) @@ -104,14 +104,14 @@ func saveMetricsForCommittedShardBlock( func saveMetricsForCommitMetachainBlock( appStatusHandler core.AppStatusHandler, - header *block.MetaBlock, + header data.MetaHeaderHandler, headerHash []byte, nodesCoordinator nodesCoordinator.NodesCoordinator, highestFinalBlockNonce uint64, managedPeersHolder common.ManagedPeersHolder, ) { appStatusHandler.SetStringValue(common.MetricCurrentBlockHash, logger.DisplayByteSlice(headerHash)) - appStatusHandler.SetUInt64Value(common.MetricEpochNumber, uint64(header.Epoch)) + appStatusHandler.SetUInt64Value(common.MetricEpochNumber, uint64(header.GetEpoch())) appStatusHandler.SetUInt64Value(common.MetricHighestFinalBlock, highestFinalBlockNonce) // TODO: remove if epoch start block needs to be validated by the new epoch nodes @@ -165,14 +165,20 @@ func indexRoundInfo( signersIndexes []uint64, enableEpochsHandler common.EnableEpochsHandler, ) { + timestampSec, timestampMs, err := common.GetHeaderTimestamps(header, enableEpochsHandler) + if err != nil { + log.Warn("failed to get header timestamps", "error", err) + return + } + roundInfo := &outportcore.RoundInfo{ Round: header.GetRound(), SignersIndexes: signersIndexes, BlockWasProposed: true, ShardId: shardId, Epoch: header.GetEpoch(), - Timestamp: uint64(time.Duration(header.GetTimeStamp())), - TimestampMs: uint64(time.Duration(common.ConvertTimeStampSecToMs(header.GetTimeStamp()))), + Timestamp: timestampSec, + TimestampMs: timestampMs, } if check.IfNil(lastHeader) { @@ -182,10 +188,13 @@ func indexRoundInfo( lastBlockRound := lastHeader.GetRound() currentBlockRound := header.GetRound() + + // TODO: evaluate more if this handling (based on current header and last header) is needed with one-short finality from andromeda roundDuration := calculateRoundDuration(lastHeader.GetTimeStamp(), header.GetTimeStamp(), lastBlockRound, currentBlockRound) roundsInfo := make([]*outportcore.RoundInfo, 0) roundsInfo = append(roundsInfo, roundInfo) + epoch := header.GetEpoch() for i := lastBlockRound + 1; i < currentBlockRound; i++ { var ok bool signersIndexes, ok = getSignersIndices(header, enableEpochsHandler, lastHeader, i, nodesCoordinator) @@ -194,15 +203,18 @@ func indexRoundInfo( } roundTimestamp := uint64(time.Duration(header.GetTimeStamp() - ((currentBlockRound - i) * roundDuration))) - roundTimestampMs := common.ConvertTimeStampSecToMs(roundTimestamp) + roundTimestampSec, roundTimestampMs, errP := common.PrepareTimestampBasedOnHeaderData(roundTimestamp, epoch, enableEpochsHandler) + if errP != nil { + continue + } roundInfo = &outportcore.RoundInfo{ Round: i, SignersIndexes: signersIndexes, BlockWasProposed: false, ShardId: shardId, - Epoch: header.GetEpoch(), - Timestamp: roundTimestamp, + Epoch: epoch, + Timestamp: roundTimestampSec, TimestampMs: roundTimestampMs, } @@ -292,13 +304,3 @@ func calculateRoundDuration( return diffTimeStamp / diffRounds } - -func saveEpochStartEconomicsMetrics(statusHandler core.AppStatusHandler, epochStartMetaBlock *block.MetaBlock) { - economics := epochStartMetaBlock.EpochStart.Economics - - statusHandler.SetStringValue(common.MetricTotalSupply, economics.TotalSupply.String()) - statusHandler.SetStringValue(common.MetricInflation, economics.TotalNewlyMinted.String()) - statusHandler.SetStringValue(common.MetricTotalFees, epochStartMetaBlock.AccumulatedFeesInEpoch.String()) - statusHandler.SetStringValue(common.MetricDevRewardsInEpoch, epochStartMetaBlock.DevFeesInEpoch.String()) - statusHandler.SetUInt64Value(common.MetricEpochForEconomicsData, uint64(epochStartMetaBlock.Epoch)) -} diff --git a/process/block/metrics_test.go b/process/block/metrics_test.go index eff2950f371..2cfd0155dbc 100644 --- a/process/block/metrics_test.go +++ b/process/block/metrics_test.go @@ -4,10 +4,16 @@ import ( "errors" "testing" + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data/block" + outportcore "github.com/multiversx/mx-chain-core-go/data/outport" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" + outportStub "github.com/multiversx/mx-chain-go/testscommon/outport" "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" statusHandlerMock "github.com/multiversx/mx-chain-go/testscommon/statusHandler" ) @@ -80,3 +86,54 @@ func TestMetrics_IncrementMetricCountConsensusAcceptedBlocks(t *testing.T) { assert.Equal(t, 2, cntIncrement) // main key + managed key }) } + +func TestMetrics_IndexRoundInfoShouldKeepSyntheticRoundTimestampsSplitByUnitsAfterSupernova(t *testing.T) { + t.Parallel() + + var savedRoundsInfo *outportcore.RoundsInfo + outportHandler := &outportStub.OutportStub{ + SaveRoundsInfoCalled: func(roundsInfo *outportcore.RoundsInfo) { + savedRoundsInfo = roundsInfo + }, + } + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag && epoch == 7 + }, + } + header := &testscommon.HeaderHandlerStub{ + EpochField: 7, + RoundField: 10, + TimestampField: 1774864086000, + GetRandSeedCalled: func() []byte { return []byte("rand-seed") }, + } + lastHeader := &testscommon.HeaderHandlerStub{ + EpochField: 7, + RoundField: 8, + TimestampField: 1774864080000, + GetRandSeedCalled: func() []byte { return []byte("rand-seed") }, + } + nodesCoordinator := &shardingMocks.NodesCoordinatorStub{ + GetValidatorsPublicKeysCalled: func(_ []byte, _ uint64, _ uint32, _ uint32) (string, []string, error) { + return "leader", []string{"pk1"}, nil + }, + GetValidatorsIndexesCalled: func(_ []string, _ uint32) ([]uint64, error) { + return []uint64{11}, nil + }, + } + + indexRoundInfo(outportHandler, nodesCoordinator, 1, header, lastHeader, []uint64{22}, enableEpochsHandler) + + require.NotNil(t, savedRoundsInfo) + require.Len(t, savedRoundsInfo.RoundsInfo, 2) + + currentRoundInfo := savedRoundsInfo.RoundsInfo[0] + assert.Equal(t, uint64(1774864086), currentRoundInfo.Timestamp) + assert.Equal(t, uint64(1774864086000), currentRoundInfo.TimestampMs) + + syntheticRoundInfo := savedRoundsInfo.RoundsInfo[1] + assert.Equal(t, uint64(9), syntheticRoundInfo.Round) + assert.Equal(t, uint64(1774864083), syntheticRoundInfo.Timestamp) + assert.Equal(t, uint64(1774864083000), syntheticRoundInfo.TimestampMs) + assert.Equal(t, []uint64{11}, syntheticRoundInfo.SignersIndexes) +} diff --git a/process/block/pendingMb/interface.go b/process/block/pendingMb/interface.go new file mode 100644 index 00000000000..1ec3ae3132a --- /dev/null +++ b/process/block/pendingMb/interface.go @@ -0,0 +1,9 @@ +package pendingMb + +import "github.com/multiversx/mx-chain-core-go/data" + +// HeadersPool defines what a headers pool structure can perform +type HeadersPool interface { + GetHeaderByHash(hash []byte) (data.HeaderHandler, error) + IsInterfaceNil() bool +} diff --git a/process/block/pendingMb/pendingMiniBlocks.go b/process/block/pendingMb/pendingMiniBlocks.go index ee3625bfca3..3db1514cbe2 100644 --- a/process/block/pendingMb/pendingMiniBlocks.go +++ b/process/block/pendingMb/pendingMiniBlocks.go @@ -7,8 +7,10 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" + logger "github.com/multiversx/mx-chain-logger-go" + + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" - "github.com/multiversx/mx-chain-logger-go" ) var _ process.PendingMiniBlocksHandler = (*pendingMiniBlocks)(nil) @@ -16,23 +18,34 @@ var _ process.PendingMiniBlocksHandler = (*pendingMiniBlocks)(nil) var log = logger.GetOrCreate("process/block/pendingMb") type pendingMiniBlocks struct { + headersPool HeadersPool mutPendingMbShard sync.RWMutex mapPendingMbShard map[string]uint32 beforeRevertPendingMbShard map[string]uint32 } // NewPendingMiniBlocks will create a new pendingMiniBlocks object -func NewPendingMiniBlocks() (*pendingMiniBlocks, error) { +func NewPendingMiniBlocks(headersPool HeadersPool) (*pendingMiniBlocks, error) { + if check.IfNil(headersPool) { + return nil, process.ErrNilHeadersDataPool + } + return &pendingMiniBlocks{ + headersPool: headersPool, mapPendingMbShard: make(map[string]uint32), beforeRevertPendingMbShard: make(map[string]uint32), }, nil } -func (p *pendingMiniBlocks) getAllCrossShardMiniBlocksHashes(metaBlock data.MetaHeaderHandler) map[string]uint32 { +func (p *pendingMiniBlocks) getMiniBlocksHashesReadyForCrossShardExecution(metaBlock data.MetaHeaderHandler) (map[string]uint32, error) { crossShardMiniBlocks := make(map[string]uint32) - for _, mbHeader := range metaBlock.GetMiniBlockHeaderHandlers() { + miniBlocksFromShards, err := p.getMiniBlockHandlersFromShardData(metaBlock) + if err != nil { + return nil, err + } + + for _, mbHeader := range miniBlocksFromShards { if !shouldConsiderCrossShardMiniBlock(mbHeader.GetSenderShardID(), mbHeader.GetReceiverShardID()) { continue } @@ -40,19 +53,31 @@ func (p *pendingMiniBlocks) getAllCrossShardMiniBlocksHashes(metaBlock data.Meta crossShardMiniBlocks[string(mbHeader.GetHash())] = mbHeader.GetReceiverShardID() } - shardInfoHandlers := metaBlock.GetShardInfoHandlers() - for _, shardData := range shardInfoHandlers { - miniblockHandlers := shardData.GetShardMiniBlockHeaderHandlers() - for _, mbHeader := range miniblockHandlers { - if !shouldConsiderCrossShardMiniBlock(mbHeader.GetSenderShardID(), mbHeader.GetReceiverShardID()) { - continue - } + metaMiniBlockHeaders, err := common.GetMiniBlockHeadersFromExecResult(metaBlock) + if err != nil { + return nil, err + } - crossShardMiniBlocks[string(mbHeader.GetHash())] = mbHeader.GetReceiverShardID() + for _, mbHeader := range metaMiniBlockHeaders { + if !shouldConsiderCrossShardMiniBlock(mbHeader.GetSenderShardID(), mbHeader.GetReceiverShardID()) { + continue } + + crossShardMiniBlocks[string(mbHeader.GetHash())] = mbHeader.GetReceiverShardID() + } + + return crossShardMiniBlocks, nil +} + +func (p *pendingMiniBlocks) getMiniBlockHandlersFromShardData(metaBlock data.MetaHeaderHandler) ([]data.MiniBlockHeaderHandler, error) { + miniBlocks := make([]data.MiniBlockHeaderHandler, 0) + + for _, shardData := range metaBlock.GetShardInfoHandlers() { + miniblockHandlers := shardData.GetShardMiniBlockHeaderHandlers() + miniBlocks = append(miniBlocks, miniblockHandlers...) } - return crossShardMiniBlocks + return miniBlocks, nil } func shouldConsiderCrossShardMiniBlock(senderShardID uint32, receiverShardID uint32) bool { @@ -121,7 +146,10 @@ func (p *pendingMiniBlocks) RevertHeader(headerHandler data.HeaderHandler) error } func (p *pendingMiniBlocks) processHeader(metaHandler data.MetaHeaderHandler) error { - crossShardMiniBlocksHashes := p.getAllCrossShardMiniBlocksHashes(metaHandler) + crossShardMiniBlocksHashes, err := p.getMiniBlocksHashesReadyForCrossShardExecution(metaHandler) + if err != nil { + return err + } p.mutPendingMbShard.Lock() defer p.mutPendingMbShard.Unlock() diff --git a/process/block/pendingMb/pendingMiniBlocks_test.go b/process/block/pendingMb/pendingMiniBlocks_test.go index 7d01250eaf0..4ee9446bdb2 100644 --- a/process/block/pendingMb/pendingMiniBlocks_test.go +++ b/process/block/pendingMb/pendingMiniBlocks_test.go @@ -6,18 +6,25 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" - "github.com/multiversx/mx-chain-go/process" - "github.com/multiversx/mx-chain-go/process/block/pendingMb" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/block/pendingMb" + "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/pool" ) -func TestNewPendingMiniBlocks_ShouldWork(t *testing.T) { +func TestNewPendingMiniBlocks(t *testing.T) { t.Parallel() - pmb, err := pendingMb.NewPendingMiniBlocks() + pmb, err := pendingMb.NewPendingMiniBlocks(nil) + require.Nil(t, pmb) + require.Equal(t, process.ErrNilHeadersDataPool, err) + pmb, err = pendingMb.NewPendingMiniBlocks(&pool.HeadersPoolStub{}) assert.False(t, check.IfNil(pmb)) assert.Nil(t, err) } @@ -25,7 +32,7 @@ func TestNewPendingMiniBlocks_ShouldWork(t *testing.T) { func TestPendingMiniBlockHeaders_AddProcessedHeaderNilHeaderShouldErr(t *testing.T) { t.Parallel() - pmb, _ := pendingMb.NewPendingMiniBlocks() + pmb, _ := pendingMb.NewPendingMiniBlocks(&pool.HeadersPoolStub{}) err := pmb.AddProcessedHeader(nil) assert.Equal(t, process.ErrNilHeaderHandler, err) @@ -34,7 +41,7 @@ func TestPendingMiniBlockHeaders_AddProcessedHeaderNilHeaderShouldErr(t *testing func TestPendingMiniBlockHeaders_AddProcessedHeaderWrongHeaderShouldErr(t *testing.T) { t.Parallel() - pmb, _ := pendingMb.NewPendingMiniBlocks() + pmb, _ := pendingMb.NewPendingMiniBlocks(&pool.HeadersPoolStub{}) header := &block.Header{} err := pmb.AddProcessedHeader(header) @@ -56,11 +63,14 @@ func createMockHeader( } } -func TestPendingMiniBlockHeaders_AddProcessedHeaderShouldWork(t *testing.T) { +func TestPendingMiniBlockHeaders_AddProcessedHeader(t *testing.T) { t.Parallel() - pmb, _ := pendingMb.NewPendingMiniBlocks() t.Run("same sender and receiver shard, empty pendingMb", func(t *testing.T) { + t.Parallel() + + pmb, _ := pendingMb.NewPendingMiniBlocks(&pool.HeadersPoolStub{}) + miniBlockHeaders := []block.MiniBlockHeader{ { Hash: []byte("mb header hash"), @@ -84,6 +94,9 @@ func TestPendingMiniBlockHeaders_AddProcessedHeaderShouldWork(t *testing.T) { assert.Equal(t, 0, len(pendingMiniblocks)) }) t.Run("metachain receiver shard, empty pendingMb", func(t *testing.T) { + t.Parallel() + + pmb, _ := pendingMb.NewPendingMiniBlocks(&pool.HeadersPoolStub{}) receivedShardId := core.MetachainShardId miniBlockHeaders := []block.MiniBlockHeader{ { @@ -107,6 +120,9 @@ func TestPendingMiniBlockHeaders_AddProcessedHeaderShouldWork(t *testing.T) { assert.Equal(t, 0, len(pendingMiniblocks)) }) t.Run("metachain sender shard, empty pendingMb", func(t *testing.T) { + t.Parallel() + + pmb, _ := pendingMb.NewPendingMiniBlocks(&pool.HeadersPoolStub{}) miniBlockHeaders := []block.MiniBlockHeader{ { Hash: []byte("mb header hash"), @@ -129,6 +145,9 @@ func TestPendingMiniBlockHeaders_AddProcessedHeaderShouldWork(t *testing.T) { assert.Equal(t, 0, len(pendingMiniblocks)) }) t.Run("different sender and receiver shard, non empty pendingMb", func(t *testing.T) { + t.Parallel() + + pmb, _ := pendingMb.NewPendingMiniBlocks(&pool.HeadersPoolStub{}) receivedShardId := uint32(1) miniBlockHeaders := []block.MiniBlockHeader{ { @@ -151,7 +170,11 @@ func TestPendingMiniBlockHeaders_AddProcessedHeaderShouldWork(t *testing.T) { pendingMiniblocks := pmb.GetPendingMiniBlocks(receivedShardId) assert.Equal(t, 2, len(pendingMiniblocks)) }) + t.Run("different sender and receiver shard, delete already added, non empty pendingMb", func(t *testing.T) { + t.Parallel() + + pmb, _ := pendingMb.NewPendingMiniBlocks(&pool.HeadersPoolStub{}) receivedShardId := uint32(1) miniBlockHeaders := []block.MiniBlockHeader{ { @@ -179,12 +202,19 @@ func TestPendingMiniBlockHeaders_AddProcessedHeaderShouldWork(t *testing.T) { assert.Nil(t, err) pendingMiniblocks := pmb.GetPendingMiniBlocks(receivedShardId) - assert.Equal(t, 2, len(pendingMiniblocks)) + assert.Equal(t, 4, len(pendingMiniblocks)) + + err = pmb.AddProcessedHeader(header) + assert.Nil(t, err) + + pendingMiniblocks = pmb.GetPendingMiniBlocks(receivedShardId) + assert.Equal(t, 2, len(pendingMiniblocks)) // removed duplicated mbs }) + t.Run("epoch start should reprocess everything", func(t *testing.T) { t.Parallel() - pendingMiniblocks, _ := pendingMb.NewPendingMiniBlocks() + pendingMiniblocks, _ := pendingMb.NewPendingMiniBlocks(&pool.HeadersPoolStub{}) pendingMiniblocks.SetInMapPendingMbShard("hash 1", 1) pendingMiniblocks.SetInMapPendingMbShard("hash 2", 2) pendingMiniblocks.SetInMapPendingMbShard("hash 3", 4) @@ -261,6 +291,146 @@ func TestPendingMiniBlockHeaders_AddProcessedHeaderShouldWork(t *testing.T) { assert.Equal(t, expectedMap, pendingMiniblocks.GetMapPendingMbShard()) assert.Equal(t, expectedBefore, pendingMiniblocks.GetBeforeRevertPendingMbShard()) }) + + t.Run("MetaBlockV3 with shard data proposals", func(t *testing.T) { + t.Parallel() + + dataPool := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &testscommon.HeaderHandlerStub{ + GetMiniBlockHeaderHandlersCalled: func() []data.MiniBlockHeaderHandler { + return []data.MiniBlockHeaderHandler{ + &block.MiniBlockHeader{ + Hash: []byte("same shard"), + SenderShardID: 1, + ReceiverShardID: 1, + }, + &block.MiniBlockHeader{ + Hash: []byte("shard to meta"), + SenderShardID: 1, + ReceiverShardID: core.MetachainShardId, + }, + &block.MiniBlockHeader{ + Hash: []byte("meta to all"), + SenderShardID: core.MetachainShardId, + ReceiverShardID: core.AllShardId, + }, + &block.MiniBlockHeader{ + Hash: []byte("cross shard 0-2"), + SenderShardID: 0, + ReceiverShardID: 2, + }, + } + }, + }, nil + }, + } + pmbLocal, _ := pendingMb.NewPendingMiniBlocks(dataPool) + header := &block.MetaBlockV3{ + Nonce: 1, + ShardInfo: []block.ShardData{ + { + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("cross shard 0-1"), + SenderShardID: 0, + ReceiverShardID: 1, + }, + }, + }, + }, + ShardInfoProposal: []block.ShardDataProposal{ + { + HeaderHash: []byte("proposed header"), + }, + }, + } + + err := pmbLocal.AddProcessedHeader(header) + require.Nil(t, err) + + // Only valid cross-shard miniblocks should be added + pendingMiniblocks := pmbLocal.GetPendingMiniBlocks(1) + assert.Equal(t, 1, len(pendingMiniblocks)) // shard data mb header is included here + assert.Equal(t, []byte("cross shard 0-1"), pendingMiniblocks[0]) + + pendingMiniblocks = pmbLocal.GetPendingMiniBlocks(2) + assert.Equal(t, 0, len(pendingMiniblocks)) // do not consider proposed shard data info + + // Invalid miniblocks should not be present + pendingMiniblocks = pmbLocal.GetPendingMiniBlocks(core.MetachainShardId) + assert.Nil(t, pendingMiniblocks) + }) + + t.Run("MetaBlockV3 with execution result miniblocks", func(t *testing.T) { + t.Parallel() + + pmbLocal, _ := pendingMb.NewPendingMiniBlocks(&pool.HeadersPoolStub{}) + + header := &block.MetaBlockV3{ + Nonce: 1, + ShardInfo: []block.ShardData{ + { + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("cross shard 0-1"), + SenderShardID: 0, + ReceiverShardID: 1, + }, + }, + }, + }, + ShardInfoProposal: []block.ShardDataProposal{ + { + HeaderHash: []byte("proposed header"), + }, + }, + ExecutionResults: []*block.MetaExecutionResult{ + { + ExecutionResult: &block.BaseMetaExecutionResult{}, + MiniBlockHeaders: []block.MiniBlockHeader{ + { + Hash: []byte("same shard"), + SenderShardID: 1, + ReceiverShardID: 1, + }, + { + Hash: []byte("shard to meta"), + SenderShardID: 1, + ReceiverShardID: core.MetachainShardId, + }, + { + Hash: []byte("meta to all"), + SenderShardID: core.MetachainShardId, + ReceiverShardID: core.AllShardId, + }, + { + Hash: []byte("cross shard 0-2"), + SenderShardID: 0, + ReceiverShardID: 2, + }, + }, + }, + }, + } + + err := pmbLocal.AddProcessedHeader(header) + require.Nil(t, err) + + // Only valid cross-shard miniblocks should be added + // added from execution results data from meta + pendingMiniblocks := pmbLocal.GetPendingMiniBlocks(1) + assert.Equal(t, 1, len(pendingMiniblocks)) + assert.Equal(t, []byte("cross shard 0-1"), pendingMiniblocks[0]) + + pendingMiniblocks = pmbLocal.GetPendingMiniBlocks(2) + assert.Equal(t, 1, len(pendingMiniblocks)) + assert.Equal(t, []byte("cross shard 0-2"), pendingMiniblocks[0]) + + // Invalid miniblocks should not be present + pendingMiniblocks = pmbLocal.GetPendingMiniBlocks(core.MetachainShardId) + assert.Nil(t, pendingMiniblocks) + }) } func TestPendingMiniBlockHeaders_Revert(t *testing.T) { @@ -269,7 +439,7 @@ func TestPendingMiniBlockHeaders_Revert(t *testing.T) { t.Run("nil header should error", func(t *testing.T) { t.Parallel() - pmb, _ := pendingMb.NewPendingMiniBlocks() + pmb, _ := pendingMb.NewPendingMiniBlocks(&pool.HeadersPoolStub{}) err := pmb.RevertHeader(nil) assert.Equal(t, process.ErrNilHeaderHandler, err) @@ -277,7 +447,7 @@ func TestPendingMiniBlockHeaders_Revert(t *testing.T) { t.Run("wrong header should error", func(t *testing.T) { t.Parallel() - pmb, _ := pendingMb.NewPendingMiniBlocks() + pmb, _ := pendingMb.NewPendingMiniBlocks(&pool.HeadersPoolStub{}) header := &block.Header{} err := pmb.RevertHeader(header) @@ -286,7 +456,7 @@ func TestPendingMiniBlockHeaders_Revert(t *testing.T) { t.Run("not an epoch start should revert", func(t *testing.T) { t.Parallel() - pmb, _ := pendingMb.NewPendingMiniBlocks() + pmb, _ := pendingMb.NewPendingMiniBlocks(&pool.HeadersPoolStub{}) pmb.SetInMapPendingMbShard("hash 1", 1) pmb.SetInMapPendingMbShard("hash 3", 1) pmb.SetInMapPendingMbShard("hash 4", 2) @@ -321,7 +491,7 @@ func TestPendingMiniBlockHeaders_Revert(t *testing.T) { t.Run("epoch start should revert completely", func(t *testing.T) { t.Parallel() - pmb, _ := pendingMb.NewPendingMiniBlocks() + pmb, _ := pendingMb.NewPendingMiniBlocks(&pool.HeadersPoolStub{}) pmb.SetInMapPendingMbShard("hash 1", 1) pmb.SetInMapPendingMbShard("hash 2", 2) pmb.SetInMapPendingMbShard("hash 3", 4) @@ -367,7 +537,7 @@ func TestPendingMiniBlockHeaders_Revert(t *testing.T) { func TestPendingMiniBlockHeaders_SetPendingMiniBlocks(t *testing.T) { t.Parallel() - pmb, _ := pendingMb.NewPendingMiniBlocks() + pmb, _ := pendingMb.NewPendingMiniBlocks(&pool.HeadersPoolStub{}) mbHashes := make([][]byte, 0) mbHashes = append(mbHashes, []byte("mbHash1")) diff --git a/process/block/poolsCleaner/basePoolsCleaner.go b/process/block/poolsCleaner/basePoolsCleaner.go index e3a5e93e132..b030af165a5 100644 --- a/process/block/poolsCleaner/basePoolsCleaner.go +++ b/process/block/poolsCleaner/basePoolsCleaner.go @@ -1,37 +1,35 @@ package poolsCleaner import ( - "fmt" "sync" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/sharding" ) -const minRoundsToKeepUnprocessedData = int64(1) - // ArgBasePoolsCleaner is the base argument structure used to create pools cleaners type ArgBasePoolsCleaner struct { - RoundHandler process.RoundHandler - ShardCoordinator sharding.Coordinator - MaxRoundsToKeepUnprocessedData int64 + RoundHandler process.RoundHandler + ShardCoordinator sharding.Coordinator + ProcessConfigsHandler common.ProcessConfigsHandler } type basePoolsCleaner struct { - roundHandler process.RoundHandler - shardCoordinator sharding.Coordinator - maxRoundsToKeepUnprocessedData int64 - cancelFunc func() - isCleaningRoutineRunning bool - mut sync.Mutex + roundHandler process.RoundHandler + shardCoordinator sharding.Coordinator + processConfigsHandler common.ProcessConfigsHandler + cancelFunc func() + isCleaningRoutineRunning bool + mut sync.Mutex } func newBasePoolsCleaner(args ArgBasePoolsCleaner) basePoolsCleaner { return basePoolsCleaner{ - roundHandler: args.RoundHandler, - shardCoordinator: args.ShardCoordinator, - maxRoundsToKeepUnprocessedData: args.MaxRoundsToKeepUnprocessedData, + roundHandler: args.RoundHandler, + shardCoordinator: args.ShardCoordinator, + processConfigsHandler: args.ProcessConfigsHandler, } } @@ -42,9 +40,8 @@ func checkBaseArgs(args ArgBasePoolsCleaner) error { if check.IfNil(args.ShardCoordinator) { return process.ErrNilShardCoordinator } - if args.MaxRoundsToKeepUnprocessedData < minRoundsToKeepUnprocessedData { - return fmt.Errorf("%w for MaxRoundsToKeepUnprocessedData, received %d, min expected %d", - process.ErrInvalidValue, args.MaxRoundsToKeepUnprocessedData, minRoundsToKeepUnprocessedData) + if check.IfNil(args.ProcessConfigsHandler) { + return process.ErrNilProcessConfigsHandler } return nil diff --git a/process/block/poolsCleaner/miniBlocksPoolsCleaner.go b/process/block/poolsCleaner/miniBlocksPoolsCleaner.go index 1c58c3fb8e6..bbf65c377ef 100644 --- a/process/block/poolsCleaner/miniBlocksPoolsCleaner.go +++ b/process/block/poolsCleaner/miniBlocksPoolsCleaner.go @@ -161,8 +161,9 @@ func (mbpc *miniBlocksPoolsCleaner) cleanMiniblocksPoolsIfNeeded() int { continue } - roundDif := mbpc.roundHandler.Index() - mbi.round - if roundDif <= mbpc.maxRoundsToKeepUnprocessedData { + round := mbpc.roundHandler.Index() + roundDif := round - mbi.round + if roundDif <= int64(mbpc.processConfigsHandler.GetMaxRoundsToKeepUnprocessedMiniBlocks(uint64(round))) { log.Trace("cleaning miniblock not yet allowed", "hash", []byte(hash), "round", mbi.round, diff --git a/process/block/poolsCleaner/miniBlocksPoolsCleaner_test.go b/process/block/poolsCleaner/miniBlocksPoolsCleaner_test.go index ba16c9dadbb..126926c9226 100644 --- a/process/block/poolsCleaner/miniBlocksPoolsCleaner_test.go +++ b/process/block/poolsCleaner/miniBlocksPoolsCleaner_test.go @@ -1,11 +1,10 @@ package poolsCleaner import ( - "errors" - "strings" "testing" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/mock" @@ -17,9 +16,13 @@ import ( func createMockArgMiniBlocksPoolsCleaner() ArgMiniBlocksPoolsCleaner { return ArgMiniBlocksPoolsCleaner{ ArgBasePoolsCleaner: ArgBasePoolsCleaner{ - RoundHandler: &mock.RoundHandlerMock{}, - ShardCoordinator: &mock.CoordinatorStub{}, - MaxRoundsToKeepUnprocessedData: 1, + RoundHandler: &mock.RoundHandlerMock{}, + ShardCoordinator: &mock.CoordinatorStub{}, + ProcessConfigsHandler: &testscommon.ProcessConfigsHandlerStub{ + GetMaxRoundsToKeepUnprocessedMiniBlocksCalled: func(round uint64) uint64 { + return 1 + }, + }, }, MiniblocksPool: cache.NewCacherStub(), } @@ -58,18 +61,6 @@ func TestNewMiniBlocksPoolsCleaner_NilShardCoordinatorShouldErr(t *testing.T) { assert.Nil(t, miniblockCleaner) } -func TestNewMiniBlocksPoolsCleaner_InvalidMaxRoundsToKeepUnprocessedDataShouldErr(t *testing.T) { - t.Parallel() - - args := createMockArgMiniBlocksPoolsCleaner() - args.MaxRoundsToKeepUnprocessedData = 0 - miniblockCleaner, err := NewMiniBlocksPoolsCleaner(args) - - assert.True(t, errors.Is(err, process.ErrInvalidValue)) - assert.True(t, strings.Contains(err.Error(), "MaxRoundsToKeepUnprocessedData")) - assert.Nil(t, miniblockCleaner) -} - func TestNewMiniBlocksPoolsCleaner_ShouldWork(t *testing.T) { t.Parallel() @@ -165,7 +156,7 @@ func TestCleanMiniblocksPoolsIfNeeded_MbShouldBeRemovedFromPoolAndMap(t *testing miniblockCleaner.receivedMiniBlock(key, miniblock) roundHandler.IndexCalled = func() int64 { - return args.MaxRoundsToKeepUnprocessedData + 1 + return int64(args.ProcessConfigsHandler.GetMaxRoundsToKeepUnprocessedMiniBlocks(0) + 1) } result := miniblockCleaner.cleanMiniblocksPoolsIfNeeded() assert.Equal(t, 0, result) diff --git a/process/block/poolsCleaner/txsPoolsCleaner.go b/process/block/poolsCleaner/txsPoolsCleaner.go index c35c2e24b4e..e080df9394b 100644 --- a/process/block/poolsCleaner/txsPoolsCleaner.go +++ b/process/block/poolsCleaner/txsPoolsCleaner.go @@ -14,7 +14,7 @@ import ( "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/storage" - "github.com/multiversx/mx-chain-go/storage/txcache" + "github.com/multiversx/mx-chain-go/txcache" ) var _ closing.Closer = (*txsPoolsCleaner)(nil) @@ -270,8 +270,9 @@ func (tpc *txsPoolsCleaner) cleanTxsPoolsIfNeeded() int { continue } + round := tpc.roundHandler.Index() roundDif := tpc.roundHandler.Index() - currTxInfo.round - if roundDif <= tpc.maxRoundsToKeepUnprocessedData { + if roundDif <= int64(tpc.processConfigsHandler.GetMaxRoundsToKeepUnprocessedTransactions(uint64(round))) { log.Trace("cleaning transaction not yet allowed", "hash", []byte(hash), "round", currTxInfo.round, diff --git a/process/block/poolsCleaner/txsPoolsCleaner_test.go b/process/block/poolsCleaner/txsPoolsCleaner_test.go index cbcab2aae85..1e3e2c49b23 100644 --- a/process/block/poolsCleaner/txsPoolsCleaner_test.go +++ b/process/block/poolsCleaner/txsPoolsCleaner_test.go @@ -1,8 +1,6 @@ package poolsCleaner import ( - "errors" - "strings" "testing" "github.com/multiversx/mx-chain-core-go/data/transaction" @@ -12,18 +10,22 @@ import ( "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/mock" "github.com/multiversx/mx-chain-go/storage" - "github.com/multiversx/mx-chain-go/storage/txcache" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/cache" dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" + "github.com/multiversx/mx-chain-go/txcache" ) func createMockArgTxsPoolsCleaner() ArgTxsPoolsCleaner { return ArgTxsPoolsCleaner{ ArgBasePoolsCleaner: ArgBasePoolsCleaner{ - RoundHandler: &mock.RoundHandlerMock{}, - ShardCoordinator: mock.NewMultipleShardsCoordinatorMock(), - MaxRoundsToKeepUnprocessedData: 1, + RoundHandler: &mock.RoundHandlerMock{}, + ShardCoordinator: mock.NewMultipleShardsCoordinatorMock(), + ProcessConfigsHandler: &testscommon.ProcessConfigsHandlerStub{ + GetMaxRoundsToKeepUnprocessedTransactionsCalled: func(round uint64) uint64 { + return 1 + }, + }, }, AddressPubkeyConverter: &testscommon.PubkeyConverterStub{}, DataPool: dataRetrieverMock.NewPoolsHolderMock(), @@ -121,17 +123,6 @@ func TestNewTxsPoolsCleaner_NilShardCoordinatorErr(t *testing.T) { assert.Equal(t, process.ErrNilShardCoordinator, err) } -func TestNewTxsPoolsCleaner_InvalidMaxRoundsToKeepUnprocessedDataShouldErr(t *testing.T) { - t.Parallel() - - args := createMockArgTxsPoolsCleaner() - args.MaxRoundsToKeepUnprocessedData = 0 - txsPoolsCleaner, err := NewTxsPoolsCleaner(args) - assert.True(t, errors.Is(err, process.ErrInvalidValue)) - assert.True(t, strings.Contains(err.Error(), "MaxRoundsToKeepUnprocessedData")) - assert.Nil(t, txsPoolsCleaner) -} - func TestNewTxsPoolsCleaner_ShouldWork(t *testing.T) { t.Parallel() @@ -351,7 +342,7 @@ func TestCleanTxsPoolsIfNeeded_RoundDiffTooBigShouldBeRemoved(t *testing.T) { txsPoolsCleaner.receivedUnsignedTx(txKey, tx) roundHandler.IndexCalled = func() int64 { - return args.MaxRoundsToKeepUnprocessedData + 1 + return int64(args.ProcessConfigsHandler.GetMaxRoundsToKeepUnprocessedTransactions(0) + 1) } numTxsInMap := txsPoolsCleaner.cleanTxsPoolsIfNeeded() assert.Equal(t, 0, numTxsInMap) diff --git a/process/block/postprocess/oneMBPostProcessor.go b/process/block/postprocess/oneMBPostProcessor.go index 6a87e32d6f4..66a69f93a35 100644 --- a/process/block/postprocess/oneMBPostProcessor.go +++ b/process/block/postprocess/oneMBPostProcessor.go @@ -75,7 +75,7 @@ func (opp *oneMBPostProcessor) GetNumOfCrossInterMbsAndTxs() (int, int) { return 0, 0 } -// CreateAllInterMiniBlocks returns the miniblock for the current round created from the receipts/bad transactions +// CreateAllInterMiniBlocks returns the miniBlock for the current round created from the receipts/bad transactions func (opp *oneMBPostProcessor) CreateAllInterMiniBlocks() []*block.MiniBlock { selfId := opp.shardCoordinator.SelfId() diff --git a/process/block/preprocess/basePreProcess.go b/process/block/preprocess/basePreProcess.go index 56ea615559e..800073b9708 100644 --- a/process/block/preprocess/basePreProcess.go +++ b/process/block/preprocess/basePreProcess.go @@ -2,8 +2,8 @@ package preprocess import ( "bytes" + "fmt" "math/big" - "sync" "time" "github.com/multiversx/mx-chain-core-go/core" @@ -16,6 +16,7 @@ import ( "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/sharding" "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/storage" ) @@ -91,27 +92,34 @@ type createScheduledMiniBlocksInfo struct { senderAddressToSkip []byte } -type txShardInfo struct { - senderShardID uint32 - receiverShardID uint32 -} - -type txInfo struct { - tx data.TransactionHandler - *txShardInfo -} - -type txsForBlock struct { - missingTxs int - mutTxsForBlock sync.RWMutex - txHashAndInfo map[string]*txInfo -} - type processedIndexes struct { indexOfLastTxProcessed int32 indexOfLastTxProcessedByProposer int32 } +// BasePreProcessorArgs holds the arguments for creating a base pre-processor +type BasePreProcessorArgs struct { + DataPool dataRetriever.ShardedDataCacherNotifier + Store dataRetriever.StorageService + Hasher hashing.Hasher + Marshalizer marshal.Marshalizer + ShardCoordinator sharding.Coordinator + Accounts state.AccountsAdapter + AccountsProposal state.AccountsAdapter + OnRequestTransaction func(shardID uint32, txHashes [][]byte) + GasHandler process.GasHandler + PubkeyConverter core.PubkeyConverter + BlockSizeComputation BlockSizeComputationHandler + BalanceComputation BalanceComputationHandler + ProcessedMiniBlocksTracker process.ProcessedMiniBlocksTracker + TxExecutionOrderHandler common.TxExecutionOrderHandler + EconomicsFee process.FeeHandler + EnableEpochsHandler common.EnableEpochsHandler + EpochNotifier process.EpochNotifier + EnableRoundsHandler common.EnableRoundsHandler + RoundNotifier process.RoundNotifier +} + // basePreProcess is the base struct for all pre-processors type basePreProcess struct { gasTracker @@ -120,10 +128,75 @@ type basePreProcess struct { blockSizeComputation BlockSizeComputationHandler balanceComputation BalanceComputationHandler accounts state.AccountsAdapter + accountsProposal state.AccountsAdapter pubkeyConverter core.PubkeyConverter processedMiniBlocksTracker process.ProcessedMiniBlocksTracker enableEpochsHandler common.EnableEpochsHandler txExecutionOrderHandler common.TxExecutionOrderHandler + enableRoundsHandler common.EnableRoundsHandler + feeHandler process.FeeHandler +} + +func checkBasePreProcessArgs(args BasePreProcessorArgs) error { + if check.IfNil(args.Hasher) { + return process.ErrNilHasher + } + if check.IfNil(args.Marshalizer) { + return process.ErrNilMarshalizer + } + if check.IfNil(args.DataPool) { + return process.ErrNilTransactionPool + } + if check.IfNil(args.Store) { + return process.ErrNilStorage + } + if check.IfNil(args.ShardCoordinator) { + return process.ErrNilShardCoordinator + } + if check.IfNil(args.Accounts) { + return process.ErrNilAccountsAdapter + } + if check.IfNil(args.AccountsProposal) { + return fmt.Errorf("%w for proposal", process.ErrNilAccountsAdapter) + } + if args.OnRequestTransaction == nil { + return process.ErrNilRequestHandler + } + if check.IfNil(args.GasHandler) { + return process.ErrNilGasHandler + } + if check.IfNil(args.PubkeyConverter) { + return process.ErrNilPubkeyConverter + } + if check.IfNil(args.BlockSizeComputation) { + return process.ErrNilBlockSizeComputationHandler + } + if check.IfNil(args.BalanceComputation) { + return process.ErrNilBalanceComputationHandler + } + if check.IfNil(args.ProcessedMiniBlocksTracker) { + return process.ErrNilProcessedMiniBlocksTracker + } + if check.IfNil(args.TxExecutionOrderHandler) { + return process.ErrNilTxExecutionOrderHandler + } + if check.IfNil(args.EconomicsFee) { + return process.ErrNilEconomicsFeeHandler + } + if check.IfNil(args.EnableEpochsHandler) { + return process.ErrNilEnableEpochsHandler + } + if check.IfNil(args.EpochNotifier) { + return process.ErrNilEpochNotifier + } + if check.IfNil(args.EnableRoundsHandler) { + return process.ErrNilEnableRoundsHandler + } + if check.IfNil(args.RoundNotifier) { + return process.ErrNilRoundNotifier + } + + return nil } func (bpp *basePreProcess) removeBlockDataFromPools( @@ -203,19 +276,20 @@ func (bpp *basePreProcess) removeMiniBlocksFromPools( return nil } -func (bpp *basePreProcess) createMarshalledData(txHashes [][]byte, forBlock *txsForBlock) ([][]byte, error) { +func (bpp *basePreProcess) createMarshalledData(txHashes [][]byte, forBlock TxsForBlockHandler) ([][]byte, error) { + if check.IfNil(forBlock) { + return nil, process.ErrNilTxsForBlockHandler + } + mrsTxs := make([][]byte, 0, len(txHashes)) for _, txHash := range txHashes { - forBlock.mutTxsForBlock.RLock() - txInfoFromMap := forBlock.txHashAndInfo[string(txHash)] - forBlock.mutTxsForBlock.RUnlock() - - if txInfoFromMap == nil || check.IfNil(txInfoFromMap.tx) { + txInfoFromMap, _ := forBlock.GetTxInfoByHash(txHash) + if txInfoFromMap == nil || check.IfNil(txInfoFromMap.Tx) { log.Warn("basePreProcess.createMarshalledData: tx not found", "hash", txHash) continue } - txMrs, err := bpp.marshalizer.Marshal(txInfoFromMap.tx) + txMrs, err := bpp.marshalizer.Marshal(txInfoFromMap.Tx) if err != nil { return nil, process.ErrMarshalWithoutSuccess } @@ -231,10 +305,15 @@ func (bpp *basePreProcess) createMarshalledData(txHashes [][]byte, forBlock *txs func (bpp *basePreProcess) saveTxsToStorage( txHashes [][]byte, - forBlock *txsForBlock, + forBlock TxsForBlockHandler, store dataRetriever.StorageService, dataUnit dataRetriever.UnitType, ) { + if check.IfNil(forBlock) { + log.Error("basePreProcess.saveTxsToStorage: nil TxsForBlockHandler") + return + } + for i := 0; i < len(txHashes); i++ { txHash := txHashes[i] bpp.saveTransactionToStorage(txHash, forBlock, store, dataUnit) @@ -243,20 +322,22 @@ func (bpp *basePreProcess) saveTxsToStorage( func (bpp *basePreProcess) saveTransactionToStorage( txHash []byte, - forBlock *txsForBlock, + forBlock TxsForBlockHandler, store dataRetriever.StorageService, dataUnit dataRetriever.UnitType, ) { - forBlock.mutTxsForBlock.RLock() - txInfoFromMap := forBlock.txHashAndInfo[string(txHash)] - forBlock.mutTxsForBlock.RUnlock() + if check.IfNil(forBlock) { + log.Error("basePreProcess.saveTransactionToStorage: nil TxsForBlockHandler") + return + } - if txInfoFromMap == nil || txInfoFromMap.tx == nil { + txInfoFromMap, _ := forBlock.GetTxInfoByHash(txHash) + if txInfoFromMap == nil || txInfoFromMap.Tx == nil { log.Warn("basePreProcess.saveTransactionToStorage", "txHash", txHash, "dataUnit", dataUnit, "error", process.ErrMissingTransaction) return } - buff, err := bpp.marshalizer.Marshal(txInfoFromMap.tx) + buff, err := bpp.marshalizer.Marshal(txInfoFromMap.Tx) if err != nil { log.Warn("basePreProcess.saveTransactionToStorage: Marshal", "txHash", txHash, "error", err) return @@ -275,137 +356,31 @@ func (bpp *basePreProcess) saveTransactionToStorage( func (bpp *basePreProcess) baseReceivedTransaction( txHash []byte, tx data.TransactionHandler, - forBlock *txsForBlock, -) bool { - - forBlock.mutTxsForBlock.Lock() - defer forBlock.mutTxsForBlock.Unlock() - - if forBlock.missingTxs > 0 { - txInfoForHash := forBlock.txHashAndInfo[string(txHash)] - if txInfoForHash != nil && txInfoForHash.txShardInfo != nil && - (txInfoForHash.tx == nil || txInfoForHash.tx.IsInterfaceNil()) { - forBlock.txHashAndInfo[string(txHash)].tx = tx - forBlock.missingTxs-- - } - - return forBlock.missingTxs == 0 + forBlock TxsForBlockHandler, +) { + if check.IfNil(forBlock) { + log.Error("basePreProcess.baseReceivedTransaction: nil TxsForBlockHandler") + return } - return false + if forBlock.HasMissingTransactions() { + forBlock.ReceivedTransaction(txHash, tx) + } } func (bpp *basePreProcess) computeExistingAndRequestMissing( body *block.Body, - forBlock *txsForBlock, - _ chan bool, + forBlock TxsForBlockHandler, isMiniBlockCorrect func(block.Type) bool, txPool dataRetriever.ShardedDataCacherNotifier, onRequestTxs func(shardID uint32, txHashes [][]byte), ) int { - - if check.IfNil(body) { + if check.IfNil(forBlock) { + log.Error("basePreProcess.computeExistingAndRequestMissing: nil TxsForBlockHandler") return 0 } - forBlock.mutTxsForBlock.Lock() - defer forBlock.mutTxsForBlock.Unlock() - - missingTxsForShard := make(map[uint32][][]byte, bpp.shardCoordinator.NumberOfShards()) - txHashes := make([][]byte, 0) - uniqueTxHashes := make(map[string]struct{}) - for i := 0; i < len(body.MiniBlocks); i++ { - miniBlock := body.MiniBlocks[i] - if !isMiniBlockCorrect(miniBlock.Type) { - continue - } - - txShardInfoObject := &txShardInfo{senderShardID: miniBlock.SenderShardID, receiverShardID: miniBlock.ReceiverShardID} - // TODO refactor this section - method := process.SearchMethodJustPeek - if miniBlock.Type == block.InvalidBlock { - method = process.SearchMethodSearchFirst - } - if miniBlock.Type == block.SmartContractResultBlock { - method = process.SearchMethodPeekWithFallbackSearchFirst - } - - for j := 0; j < len(miniBlock.TxHashes); j++ { - txHash := miniBlock.TxHashes[j] - - _, isAlreadyEvaluated := uniqueTxHashes[string(txHash)] - if isAlreadyEvaluated { - continue - } - uniqueTxHashes[string(txHash)] = struct{}{} - - tx, err := process.GetTransactionHandlerFromPool( - miniBlock.SenderShardID, - miniBlock.ReceiverShardID, - txHash, - txPool, - method) - - if err != nil { - txHashes = append(txHashes, txHash) - forBlock.missingTxs++ - log.Trace("missing tx", - "miniblock type", miniBlock.Type, - "sender", miniBlock.SenderShardID, - "receiver", miniBlock.ReceiverShardID, - "hash", txHash, - ) - continue - } - - forBlock.txHashAndInfo[string(txHash)] = &txInfo{tx: tx, txShardInfo: txShardInfoObject} - } - - if len(txHashes) > 0 { - bpp.setMissingTxsForShard(miniBlock.SenderShardID, miniBlock.ReceiverShardID, txHashes, forBlock) - missingTxsForShard[miniBlock.SenderShardID] = append(missingTxsForShard[miniBlock.SenderShardID], txHashes...) - } - - txHashes = make([][]byte, 0) - } - - return bpp.requestMissingTxsForShard(missingTxsForShard, onRequestTxs) -} - -// this method should be called only under the mutex protection: forBlock.mutTxsForBlock -func (bpp *basePreProcess) setMissingTxsForShard( - senderShardID uint32, - receiverShardID uint32, - txHashes [][]byte, - forBlock *txsForBlock, -) { - txShardInfoToSet := &txShardInfo{ - senderShardID: senderShardID, - receiverShardID: receiverShardID, - } - - for _, txHash := range txHashes { - forBlock.txHashAndInfo[string(txHash)] = &txInfo{ - tx: nil, - txShardInfo: txShardInfoToSet, - } - } -} - -// this method should be called only under the mutex protection: forBlock.mutTxsForBlock -func (bpp *basePreProcess) requestMissingTxsForShard( - missingTxsForShard map[uint32][][]byte, - onRequestTxs func(shardID uint32, txHashes [][]byte), -) int { - requestedTxs := 0 - for shardID, txHashes := range missingTxsForShard { - requestedTxs += len(txHashes) - go func(providedsShardID uint32, providedTxHashes [][]byte) { - onRequestTxs(providedsShardID, providedTxHashes) - }(shardID, txHashes) - } - - return requestedTxs + return forBlock.ComputeExistingAndRequestMissing(body, isMiniBlockCorrect, txPool, onRequestTxs) } func (bpp *basePreProcess) saveAccountBalanceForAddress(address []byte) error { @@ -439,9 +414,8 @@ func (bpp *basePreProcess) getBalanceForAddress(address []byte) (*big.Int, error return account.GetBalance(), nil } -func getTxMaxTotalCost(txHandler data.TransactionHandler) *big.Int { - cost := big.NewInt(0) - cost.Mul(big.NewInt(0).SetUint64(txHandler.GetGasPrice()), big.NewInt(0).SetUint64(txHandler.GetGasLimit())) +func (bpp *basePreProcess) getTxMaxTotalCost(txHandler data.TransactionHandler) *big.Int { + cost := bpp.getTxFee(txHandler) if txHandler.GetValue() != nil { cost.Add(cost, txHandler.GetValue()) @@ -450,6 +424,15 @@ func getTxMaxTotalCost(txHandler data.TransactionHandler) *big.Int { return cost } +func (bpp *basePreProcess) getTxFee(txHandler data.TransactionHandler) *big.Int { + isAsyncExecEnabled := common.IsAsyncExecutionEnabled(bpp.enableEpochsHandler, bpp.enableRoundsHandler) + if !isAsyncExecEnabled { + return big.NewInt(0).Mul(big.NewInt(0).SetUint64(txHandler.GetGasPrice()), big.NewInt(0).SetUint64(txHandler.GetGasLimit())) + } + + return bpp.feeHandler.ComputeTxFee(txHandler) +} + func (bpp *basePreProcess) getTotalGasConsumed() uint64 { if !bpp.enableEpochsHandler.IsFlagEnabled(common.OptimizeGasUsedInCrossMiniBlocksFlag) { return bpp.gasHandler.TotalGasProvided() @@ -526,6 +509,23 @@ func getMiniBlockHeaderOfMiniBlock(headerHandler data.HeaderHandler, miniBlockHa return nil, process.ErrMissingMiniBlockHeader } +func (bpp *basePreProcess) getIndexesOfLastTxProcessedOnExecution( + miniBlock *block.MiniBlock, + headerHandler data.HeaderHandler, +) (*processedIndexes, error) { + if !headerHandler.IsHeaderV3() { + return bpp.getIndexesOfLastTxProcessed(miniBlock, headerHandler) + } + + // for header v3, mini blocks need to be processed in their entirety, there are no longer partially processed mini blocks + pi := &processedIndexes{ + indexOfLastTxProcessed: -1, + indexOfLastTxProcessedByProposer: int32(len(miniBlock.GetTxHashes())) - 1, + } + + return pi, nil +} + func (bpp *basePreProcess) getIndexesOfLastTxProcessed( miniBlock *block.MiniBlock, headerHandler data.HeaderHandler, @@ -550,3 +550,35 @@ func (bpp *basePreProcess) getIndexesOfLastTxProcessed( return pi, nil } + +func (bpp *basePreProcess) addNumTxs(numTxs int) { + if common.IsAsyncExecutionEnabled(bpp.enableEpochsHandler, bpp.enableRoundsHandler) { + return + } + + bpp.blockSizeComputation.AddNumTxs(numTxs) +} + +func (bpp *basePreProcess) addNumMiniBlocks(numMiniBlocks int) { + if common.IsAsyncExecutionEnabled(bpp.enableEpochsHandler, bpp.enableRoundsHandler) { + return + } + + bpp.blockSizeComputation.AddNumMiniBlocks(numMiniBlocks) +} + +func (bpp *basePreProcess) isMaxBlockSizeReached(numNewMiniBlocks int, numNewTxs int) bool { + if common.IsAsyncExecutionEnabled(bpp.enableEpochsHandler, bpp.enableRoundsHandler) { + return false + } + + return bpp.blockSizeComputation.IsMaxBlockSizeReached(numNewMiniBlocks, numNewTxs) +} + +func (bpp *basePreProcess) isMaxBlockSizeWithoutThrottleReached(numNewMiniBlocks int, numNewTxs int) bool { + if common.IsAsyncExecutionEnabled(bpp.enableEpochsHandler, bpp.enableRoundsHandler) { + return false + } + + return bpp.blockSizeComputation.IsMaxBlockSizeWithoutThrottleReached(numNewMiniBlocks, numNewTxs) +} diff --git a/process/block/preprocess/basePreProcess_test.go b/process/block/preprocess/basePreProcess_test.go index 221f69c28db..76f75df2508 100644 --- a/process/block/preprocess/basePreProcess_test.go +++ b/process/block/preprocess/basePreProcess_test.go @@ -4,6 +4,8 @@ import ( "bytes" "testing" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -49,3 +51,32 @@ func TestBasePreProcess_handleProcessTransactionInit(t *testing.T) { assert.Equal(t, journalLen, recoveredJournalLen) assert.True(t, initProcessedTxsCalled) } + +func TestBasePreProcess_getIndexesOfLastTxProcessedOnExecution(t *testing.T) { + t.Parallel() + + mb := &block.MiniBlock{ + TxHashes: [][]byte{ + []byte("tx1"), + []byte("tx2"), + []byte("tx3"), + }, + } + + t.Run("for v3 header", func(t *testing.T) { + var headerHandler data.HeaderHandler = &testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { + return true + }, + } + + bp := &basePreProcess{} + + pi, err := bp.getIndexesOfLastTxProcessedOnExecution(mb, headerHandler) + require.NoError(t, err) + + require.Equal(t, int32(-1), pi.indexOfLastTxProcessed) + require.Equal(t, int32(len(mb.GetTxHashes())-1), pi.indexOfLastTxProcessedByProposer) + + }) +} diff --git a/process/block/preprocess/blockSizeComputation.go b/process/block/preprocess/blockSizeComputation.go index 1d1ce08cdeb..868538e6d0a 100644 --- a/process/block/preprocess/blockSizeComputation.go +++ b/process/block/preprocess/blockSizeComputation.go @@ -18,8 +18,8 @@ type blockSizeComputation struct { miniblockSize uint32 txSize uint32 - numMiniBlocks uint32 - numTxs uint32 + numMiniBlocks int32 + numTxs int32 blockSizeThrottler BlockSizeThrottler maxSize uint32 } @@ -30,7 +30,6 @@ func NewBlockSizeComputation( blockSizeThrottler BlockSizeThrottler, maxSize uint32, ) (*blockSizeComputation, error) { - if check.IfNil(marshalizer) { return nil, process.ErrNilMarshalizer } @@ -121,30 +120,61 @@ func (bsc *blockSizeComputation) generateDummyMiniblock(numTxHashes int) *block. // Init reset the stored values of accumulated numTxs and numMiniBlocks func (bsc *blockSizeComputation) Init() { - atomic.StoreUint32(&bsc.numTxs, 0) - atomic.StoreUint32(&bsc.numMiniBlocks, 0) + atomic.StoreInt32(&bsc.numTxs, 0) + atomic.StoreInt32(&bsc.numMiniBlocks, 0) } // AddNumMiniBlocks adds the provided value to numMiniBlocks in a concurrent safe manner func (bsc *blockSizeComputation) AddNumMiniBlocks(numMiniBlocks int) { - atomic.AddUint32(&bsc.numMiniBlocks, uint32(numMiniBlocks)) + atomic.AddInt32(&bsc.numMiniBlocks, int32(numMiniBlocks)) +} + +// DecNumMiniBlocks decrements the provided value to numMiniBlocks in a concurrent safe manner +func (bsc *blockSizeComputation) DecNumMiniBlocks(numMiniBlocks int) { + bsc.decCounter(&bsc.numMiniBlocks, numMiniBlocks) +} + +func (bsc *blockSizeComputation) decCounter(cntAddr *int32, n int) { + if n <= 0 { + return + } + + currentCnt := atomic.LoadInt32(cntAddr) + if currentCnt <= 0 { + return + } + + newCnt := currentCnt - int32(n) + if newCnt < 0 { + newCnt = 0 + } + + atomic.CompareAndSwapInt32(cntAddr, currentCnt, newCnt) } // AddNumTxs adds the provided value to numTxs in a concurrent safe manner func (bsc *blockSizeComputation) AddNumTxs(numTxs int) { - atomic.AddUint32(&bsc.numTxs, uint32(numTxs)) + atomic.AddInt32(&bsc.numTxs, int32(numTxs)) +} + +// DecNumTxs decrements the provided value to numTxs in a concurrent safe manner +func (bsc *blockSizeComputation) DecNumTxs(numTxs int) { + bsc.decCounter(&bsc.numTxs, numTxs) } // IsMaxBlockSizeReached returns true if the provided number of new miniblocks and txs go over // the maximum allowed throttled block size func (bsc *blockSizeComputation) IsMaxBlockSizeReached(numNewMiniBlocks int, numNewTxs int) bool { - totalMiniBlocks := atomic.LoadUint32(&bsc.numMiniBlocks) + uint32(numNewMiniBlocks) - totalTxs := atomic.LoadUint32(&bsc.numTxs) + uint32(numNewTxs) + totalMiniBlocks := uint32(atomic.LoadInt32(&bsc.numMiniBlocks)) + uint32(numNewMiniBlocks) + totalTxs := uint32(atomic.LoadInt32(&bsc.numTxs)) + uint32(numNewTxs) return bsc.isMaxBlockSizeReached(totalMiniBlocks, totalTxs) } -func (bsc *blockSizeComputation) isMaxBlockSizeReached(totalMiniBlocks uint32, totalTxs uint32) bool { +func (bsc *blockSizeComputation) isMaxBlockSizeReached( + totalMiniBlocks uint32, + totalTxs uint32, +) bool { miniblocksSize := bsc.miniblockSize * totalMiniBlocks txsSize := bsc.txSize * totalTxs @@ -154,8 +184,8 @@ func (bsc *blockSizeComputation) isMaxBlockSizeReached(totalMiniBlocks uint32, t // IsMaxBlockSizeWithoutThrottleReached returns true if the provided number of new miniblocks and txs go over // the maximum allowed not throttled block size func (bsc *blockSizeComputation) IsMaxBlockSizeWithoutThrottleReached(numNewMiniBlocks int, numNewTxs int) bool { - totalMiniBlocks := atomic.LoadUint32(&bsc.numMiniBlocks) + uint32(numNewMiniBlocks) - totalTxs := atomic.LoadUint32(&bsc.numTxs) + uint32(numNewTxs) + totalMiniBlocks := uint32(atomic.LoadInt32(&bsc.numMiniBlocks)) + uint32(numNewMiniBlocks) + totalTxs := uint32(atomic.LoadInt32(&bsc.numTxs)) + uint32(numNewTxs) return bsc.isMaxBlockSizeWithoutThrottleReached(totalMiniBlocks, totalTxs) } diff --git a/process/block/preprocess/blockSizeComputation_test.go b/process/block/preprocess/blockSizeComputation_test.go index 2da87ec911d..e7ca69d1af8 100644 --- a/process/block/preprocess/blockSizeComputation_test.go +++ b/process/block/preprocess/blockSizeComputation_test.go @@ -2,6 +2,7 @@ package preprocess_test import ( "errors" + "sync" "testing" "github.com/multiversx/mx-chain-core-go/core" @@ -10,6 +11,7 @@ import ( "github.com/multiversx/mx-chain-go/process/block/preprocess" "github.com/multiversx/mx-chain-go/process/mock" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const maxSizeInBytes = uint32(core.MegabyteSize * 90 / 100) @@ -190,3 +192,68 @@ func TestBlockSizeComputation_MaxTransactionsInOneMiniblock(t *testing.T) { assert.Equal(t, 27756, maxTxs) } + +func TestBlockSizeComputation_DecrementValues(t *testing.T) { + t.Parallel() + + bsc, _ := preprocess.NewBlockSizeComputation(&mock.ProtobufMarshalizerMock{}, &mock.BlockSizeThrottlerStub{}, maxSizeInBytes) + + bsc.Init() + + bsc.AddNumMiniBlocks(10) + require.Equal(t, uint32(10), bsc.NumMiniBlocks()) + + bsc.DecNumMiniBlocks(5) + require.Equal(t, uint32(5), bsc.NumMiniBlocks()) + + bsc.AddNumTxs(20) + require.Equal(t, uint32(20), bsc.NumTxs()) + + bsc.DecNumTxs(10) + require.Equal(t, uint32(10), bsc.NumTxs()) + + // should decrement down to zero + bsc.DecNumTxs(30) + require.Equal(t, uint32(0), bsc.NumTxs()) +} + +func TestBlockSizeComputation_Concurrency(t *testing.T) { + require.NotPanics(t, func() { + t.Parallel() + + bsc, _ := preprocess.NewBlockSizeComputation(&mock.ProtobufMarshalizerMock{}, &mock.BlockSizeThrottlerStub{}, maxSizeInBytes) + + bsc.Init() + + const numCalls = 1000 + wg := sync.WaitGroup{} + wg.Add(numCalls) + + for i := 0; i < numCalls; i++ { + go func(idx int) { + defer wg.Done() + + switch idx % 8 { + case 0: + bsc.AddNumMiniBlocks(1) + case 1: + bsc.DecNumMiniBlocks(1) + case 2: + bsc.AddNumTxs(10) + case 3: + bsc.DecNumTxs(10) + case 4: + bsc.Init() + case 5: + bsc.IsMaxBlockSizeReached(1, 10) + case 6: + bsc.IsMaxBlockSizeWithoutThrottleReached(1, 10) + case 7: + _ = bsc.MaxTransactionsInOneMiniblock() + } + }(i) + } + + wg.Wait() + }) +} diff --git a/process/block/preprocess/export_test.go b/process/block/preprocess/export_test.go index fb9cff5a429..e7c171c7213 100644 --- a/process/block/preprocess/export_test.go +++ b/process/block/preprocess/export_test.go @@ -7,94 +7,118 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + + "github.com/multiversx/mx-chain-go/process" ) +// ReceivedTransaction - func (txs *transactions) ReceivedTransaction(txHash []byte, value interface{}) { txs.receivedTransaction(txHash, value) } +// AddTxHashToRequestedList - func (txs *transactions) AddTxHashToRequestedList(txHash []byte) { - txs.txsForCurrBlock.mutTxsForBlock.Lock() - defer txs.txsForCurrBlock.mutTxsForBlock.Unlock() + txsForCurrentBlock := txs.txsForCurrBlock.(*txsForBlock) + txsForCurrentBlock.mutTxsForBlock.Lock() + defer txsForCurrentBlock.mutTxsForBlock.Unlock() - if txs.txsForCurrBlock.txHashAndInfo == nil { - txs.txsForCurrBlock.txHashAndInfo = make(map[string]*txInfo) + if txsForCurrentBlock.txHashAndInfo == nil { + txsForCurrentBlock.txHashAndInfo = make(map[string]*process.TxInfo) } - txs.txsForCurrBlock.txHashAndInfo[string(txHash)] = &txInfo{txShardInfo: &txShardInfo{}} + txsForCurrentBlock.txHashAndInfo[string(txHash)] = &process.TxInfo{TxShardInfo: &process.TxShardInfo{}} } +// IsTxHashRequested - func (txs *transactions) IsTxHashRequested(txHash []byte) bool { - txs.txsForCurrBlock.mutTxsForBlock.Lock() - defer txs.txsForCurrBlock.mutTxsForBlock.Unlock() + txsForCurrentBlock := txs.txsForCurrBlock.(*txsForBlock) + txsForCurrentBlock.mutTxsForBlock.Lock() + defer txsForCurrentBlock.mutTxsForBlock.Unlock() - return txs.txsForCurrBlock.txHashAndInfo[string(txHash)].tx == nil || - txs.txsForCurrBlock.txHashAndInfo[string(txHash)].tx.IsInterfaceNil() + return txsForCurrentBlock.txHashAndInfo[string(txHash)].Tx == nil || + txsForCurrentBlock.txHashAndInfo[string(txHash)].Tx.IsInterfaceNil() } +// SetMissingTxs - func (txs *transactions) SetMissingTxs(missingTxs int) { - txs.txsForCurrBlock.mutTxsForBlock.Lock() - txs.txsForCurrBlock.missingTxs = missingTxs - txs.txsForCurrBlock.mutTxsForBlock.Unlock() + txsForCurrentBlock := txs.txsForCurrBlock.(*txsForBlock) + + txsForCurrentBlock.mutTxsForBlock.Lock() + txsForCurrentBlock.numMissingTxs = missingTxs + txsForCurrentBlock.mutTxsForBlock.Unlock() } +// SetRcvdTxChan - func (txs *transactions) SetRcvdTxChan() { - txs.chRcvAllTxs <- true + tfb := txs.txsForCurrBlock.(*txsForBlock) + tfb.chRcvAllTxs <- true } +// AddScrHashToRequestedList - func (scr *smartContractResults) AddScrHashToRequestedList(txHash []byte) { - scr.scrForBlock.mutTxsForBlock.Lock() - defer scr.scrForBlock.mutTxsForBlock.Unlock() + scrForBlock := scr.scrForBlock.(*txsForBlock) + scrForBlock.mutTxsForBlock.Lock() + defer scrForBlock.mutTxsForBlock.Unlock() - if scr.scrForBlock.txHashAndInfo == nil { - scr.scrForBlock.txHashAndInfo = make(map[string]*txInfo) + if scrForBlock.txHashAndInfo == nil { + scrForBlock.txHashAndInfo = make(map[string]*process.TxInfo) } - scr.scrForBlock.txHashAndInfo[string(txHash)] = &txInfo{txShardInfo: &txShardInfo{}} + scrForBlock.txHashAndInfo[string(txHash)] = &process.TxInfo{TxShardInfo: &process.TxShardInfo{}} } +// IsScrHashRequested - func (scr *smartContractResults) IsScrHashRequested(txHash []byte) bool { - scr.scrForBlock.mutTxsForBlock.Lock() - defer scr.scrForBlock.mutTxsForBlock.Unlock() + scrForBlock := scr.scrForBlock.(*txsForBlock) + scrForBlock.mutTxsForBlock.Lock() + defer scrForBlock.mutTxsForBlock.Unlock() - return scr.scrForBlock.txHashAndInfo[string(txHash)].tx == nil || - scr.scrForBlock.txHashAndInfo[string(txHash)].tx.IsInterfaceNil() + return scrForBlock.txHashAndInfo[string(txHash)].Tx == nil || + scrForBlock.txHashAndInfo[string(txHash)].Tx.IsInterfaceNil() } +// SetMissingScr - func (scr *smartContractResults) SetMissingScr(missingTxs int) { - scr.scrForBlock.mutTxsForBlock.Lock() - scr.scrForBlock.missingTxs = missingTxs - scr.scrForBlock.mutTxsForBlock.Unlock() + missingScrsForBlock, _ := scr.scrForBlock.(*txsForBlock) + missingScrsForBlock.mutTxsForBlock.Lock() + missingScrsForBlock.numMissingTxs = missingTxs + missingScrsForBlock.mutTxsForBlock.Unlock() } +// AddTxs - func (rtp *rewardTxPreprocessor) AddTxs(txHashes [][]byte, txs []data.TransactionHandler) { - rtp.rewardTxsForBlock.mutTxsForBlock.Lock() - for i := 0; i < len(txHashes); i++ { - hash := txHashes[i] - tx := txs[i] - rtp.rewardTxsForBlock.txHashAndInfo[string(hash)] = &txInfo{ - tx: tx, - txShardInfo: &txShardInfo{receiverShardID: core.MetachainShardId, senderShardID: 0}, - } + rtp.rewardTxsForBlock.AddTransaction(txHashes[i], txs[i], core.MetachainShardId, 0) } - rtp.rewardTxsForBlock.mutTxsForBlock.Unlock() } +// SetMissingRewardTxs - +func (rtp *rewardTxPreprocessor) SetMissingRewardTxs(missingTxs int) { + missingRewards, _ := rtp.rewardTxsForBlock.(*txsForBlock) + missingRewards.mutTxsForBlock.Lock() + missingRewards.numMissingTxs = missingTxs + missingRewards.mutTxsForBlock.Unlock() +} + +// MiniblockSize - func (bsc *blockSizeComputation) MiniblockSize() uint32 { return bsc.miniblockSize } +// TxSize - func (bsc *blockSizeComputation) TxSize() uint32 { return bsc.txSize } +// NumMiniBlocks - func (bsc *blockSizeComputation) NumMiniBlocks() uint32 { - return atomic.LoadUint32(&bsc.numMiniBlocks) + return uint32(atomic.LoadInt32(&bsc.numMiniBlocks)) } +// NumTxs - func (bsc *blockSizeComputation) NumTxs() uint32 { - return atomic.LoadUint32(&bsc.numTxs) + return uint32(atomic.LoadInt32(&bsc.numTxs)) } +// ProcessTxsToMe - func (txs *transactions) ProcessTxsToMe( header data.HeaderHandler, body *block.Body, @@ -103,40 +127,27 @@ func (txs *transactions) ProcessTxsToMe( return txs.processTxsToMe(header, body, haveTime) } +// AddTxForCurrentBlock - func (txs *transactions) AddTxForCurrentBlock( txHash []byte, txHandler data.TransactionHandler, senderShardID uint32, receiverShardID uint32, ) { - txs.txsForCurrBlock.mutTxsForBlock.Lock() - defer txs.txsForCurrBlock.mutTxsForBlock.Unlock() - - if txs.txsForCurrBlock.txHashAndInfo == nil { - txs.txsForCurrBlock.txHashAndInfo = make(map[string]*txInfo) - } - - txs.txsForCurrBlock.txHashAndInfo[string(txHash)] = &txInfo{ - tx: txHandler, - txShardInfo: &txShardInfo{ - senderShardID: senderShardID, - receiverShardID: receiverShardID, - }, - } + txs.txsForCurrBlock.AddTransaction(txHash, txHandler, senderShardID, receiverShardID) } +// GetTxInfoForCurrentBlock - func (txs *transactions) GetTxInfoForCurrentBlock(txHash []byte) (data.TransactionHandler, uint32, uint32) { - txs.txsForCurrBlock.mutTxsForBlock.RLock() - defer txs.txsForCurrBlock.mutTxsForBlock.RUnlock() - - txInfo, ok := txs.txsForCurrBlock.txHashAndInfo[string(txHash)] + txInfo, ok := txs.txsForCurrBlock.GetTxInfoByHash(txHash) if !ok { return nil, 0, 0 } - return txInfo.tx, txInfo.senderShardID, txInfo.receiverShardID + return txInfo.Tx, txInfo.SenderShardID, txInfo.ReceiverShardID } +// GetBalanceOfAddress - func (bc *balanceComputation) GetBalanceOfAddress(address []byte) *big.Int { bc.mutAddressBalance.RLock() defer bc.mutAddressBalance.RUnlock() @@ -149,22 +160,27 @@ func (bc *balanceComputation) GetBalanceOfAddress(address []byte) *big.Int { return big.NewInt(0).Set(currValue) } +// GetTxHashesWithGasProvidedSinceLastReset - func (gc *gasComputation) GetTxHashesWithGasProvidedSinceLastReset(key []byte) [][]byte { return gc.getTxHashesWithGasProvidedSinceLastReset(key) } +// GetTxHashesWithGasProvidedAsScheduledSinceLastReset - func (gc *gasComputation) GetTxHashesWithGasProvidedAsScheduledSinceLastReset(key []byte) [][]byte { return gc.getTxHashesWithGasProvidedAsScheduledSinceLastReset(key) } +// GetTxHashesWithGasRefundedSinceLastReset - func (gc *gasComputation) GetTxHashesWithGasRefundedSinceLastReset(key []byte) [][]byte { return gc.getTxHashesWithGasRefundedSinceLastReset(key) } +// GetTxHashesWithGasPenalizedSinceLastReset - func (gc *gasComputation) GetTxHashesWithGasPenalizedSinceLastReset(key []byte) [][]byte { return gc.getTxHashesWithGasPenalizedSinceLastReset(key) } +// ComputeScheduledIntermediateTxs - func (ste *scheduledTxsExecution) ComputeScheduledIntermediateTxs( mapAllIntermediateTxsBeforeScheduledExecution map[block.Type]map[string]data.TransactionHandler, mapAllIntermediateTxsAfterScheduledExecution map[block.Type]map[string]data.TransactionHandler, @@ -174,6 +190,7 @@ func (ste *scheduledTxsExecution) ComputeScheduledIntermediateTxs( ste.mutScheduledTxs.Unlock() } +// GetMapScheduledIntermediateTxs - func (ste *scheduledTxsExecution) GetMapScheduledIntermediateTxs() map[block.Type][]data.TransactionHandler { ste.mutScheduledTxs.RLock() defer ste.mutScheduledTxs.RUnlock() @@ -185,3 +202,7 @@ func (ste *scheduledTxsExecution) GetMapScheduledIntermediateTxs() map[block.Typ return newMap } + +func (gt *gasTracker) getEpochAndOverestimationFactorForGasLimits() (uint32, uint64) { + return gt.gasEpochState.GetEpochForLimitsAndOverEstimationFactor() +} diff --git a/process/block/preprocess/gasComputation.go b/process/block/preprocess/gasComputation.go index f4e6a82b4a9..63eb382d323 100644 --- a/process/block/preprocess/gasComputation.go +++ b/process/block/preprocess/gasComputation.go @@ -7,6 +7,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" ) diff --git a/process/block/preprocess/gasEpochState.go b/process/block/preprocess/gasEpochState.go new file mode 100644 index 00000000000..8196be2bd5c --- /dev/null +++ b/process/block/preprocess/gasEpochState.go @@ -0,0 +1,101 @@ +package preprocess + +import ( + "sync" + + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/process" +) + +type gasEpochState struct { + economicsFee process.FeeHandler + enableEpochsHandler common.EnableEpochsHandler + enableRoundsHandler common.EnableRoundsHandler + mut sync.RWMutex + overEstimationFactor uint64 + epochForLimits uint32 + roundForLimits uint64 +} + +func newGasEpochState( + economicsFee process.FeeHandler, + enableEpochsHandler common.EnableEpochsHandler, + enableRoundsHandler common.EnableRoundsHandler, +) (*gasEpochState, error) { + if check.IfNil(economicsFee) { + return nil, process.ErrNilEconomicsFeeHandler + } + if check.IfNil(enableEpochsHandler) { + return nil, process.ErrNilEnableEpochsHandler + } + if check.IfNil(enableRoundsHandler) { + return nil, process.ErrNilEnableRoundsHandler + } + return &gasEpochState{ + economicsFee: economicsFee, + enableEpochsHandler: enableEpochsHandler, + enableRoundsHandler: enableRoundsHandler, + overEstimationFactor: noOverestimationFactor, + }, nil +} + +// EpochConfirmed is called whenever a new epoch is confirmed +func (ges *gasEpochState) EpochConfirmed(epoch uint32) { + ges.mut.Lock() + defer ges.mut.Unlock() + + ges.epochForLimits = epoch + + // if already computed, only store the new epoch + if ges.overEstimationFactor != noOverestimationFactor { + return + } + + isEpochFlagEnabled := ges.enableEpochsHandler.IsFlagEnabledInEpoch(common.SupernovaFlag, ges.epochForLimits) + if !isEpochFlagEnabled { + return + } + + isRoundFlagEnabled := ges.enableRoundsHandler.IsFlagEnabledInRound(common.SupernovaRoundFlag, ges.roundForLimits) + if !isRoundFlagEnabled && epoch > 0 { + ges.epochForLimits = epoch - 1 // use the previous epoch until activation round + } +} + +// RoundConfirmed is called whenever a new round is confirmed +func (ges *gasEpochState) RoundConfirmed(round uint64) { + ges.mut.Lock() + defer ges.mut.Unlock() + + ges.roundForLimits = round + + // if already computed, only store the new round + if ges.overEstimationFactor != noOverestimationFactor { + return + } + + isRoundFlagEnabled := ges.enableRoundsHandler.IsFlagEnabledInRound(common.SupernovaRoundFlag, ges.roundForLimits) + if !isRoundFlagEnabled { + return + } + + // new limits and overestimation should be enabled once the Supernova round is active + ges.overEstimationFactor = ges.economicsFee.BlockCapacityOverestimationFactor() + // epoch was previously held at (currentEpoch - 1) until the Supernova round activated. + // now that the round is active, we advance epochForLimits to the real epoch. + ges.epochForLimits = ges.epochForLimits + 1 +} + +// GetEpochForLimitsAndOverEstimationFactor returns the epoch for limits and the overestimation factor +func (ges *gasEpochState) GetEpochForLimitsAndOverEstimationFactor() (uint32, uint64) { + ges.mut.RLock() + defer ges.mut.RUnlock() + + return ges.epochForLimits, ges.overEstimationFactor +} + +// IsInterfaceNil returns true if there is no value under the interface +func (ges *gasEpochState) IsInterfaceNil() bool { + return ges == nil +} diff --git a/process/block/preprocess/gasEpochState_test.go b/process/block/preprocess/gasEpochState_test.go new file mode 100644 index 00000000000..f5e0e25f940 --- /dev/null +++ b/process/block/preprocess/gasEpochState_test.go @@ -0,0 +1,121 @@ +package preprocess + +import ( + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" + "github.com/stretchr/testify/assert" +) + +func TestNewGasEpochState(t *testing.T) { + t.Parallel() + + t.Run("nil economics fee handler", func(t *testing.T) { + t.Parallel() + + ges, err := newGasEpochState(nil, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) + assert.Equal(t, process.ErrNilEconomicsFeeHandler, err) + assert.True(t, check.IfNil(ges)) + }) + t.Run("nil enable epochs handler", func(t *testing.T) { + t.Parallel() + + ges, err := newGasEpochState(feeHandlerMock(), nil, &testscommon.EnableRoundsHandlerStub{}) + assert.Equal(t, process.ErrNilEnableEpochsHandler, err) + assert.True(t, check.IfNil(ges)) + }) + t.Run("nil enable rounds handler", func(t *testing.T) { + t.Parallel() + + ges, err := newGasEpochState(feeHandlerMock(), &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, nil) + assert.Equal(t, process.ErrNilEnableRoundsHandler, err) + assert.True(t, check.IfNil(ges)) + }) + t.Run("should create gas epoch state", func(t *testing.T) { + t.Parallel() + + ges, err := newGasEpochState(feeHandlerMock(), &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) + assert.Nil(t, err) + assert.False(t, check.IfNil(ges)) + }) +} + +func TestGasEpochState_EpochConfirmed(t *testing.T) { + t.Parallel() + + t.Run("should set epoch for limits", func(t *testing.T) { + t.Parallel() + + ges, err := newGasEpochState( + feeHandlerMock(), + &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + }, + &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return true + }, + }) + assert.Nil(t, err) + + assert.Equal(t, uint32(0), ges.epochForLimits) + ges.EpochConfirmed(10) + assert.Equal(t, uint32(10), ges.epochForLimits) + }) + t.Run("should set previous epoch for limits if round flag not enabled", func(t *testing.T) { + t.Parallel() + + ges, err := newGasEpochState( + feeHandlerMock(), + &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + }, + &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return false + }, + }) + assert.Nil(t, err) + + assert.Equal(t, uint32(0), ges.epochForLimits) + ges.EpochConfirmed(10) + assert.Equal(t, uint32(9), ges.epochForLimits) + }) +} + +func TestGasEpochState_RoundConfirmed(t *testing.T) { + t.Parallel() + + feeHandler := feeHandlerMock() + feeHandler.BlockCapacityOverestimationFactorCalled = func() uint64 { + return 3 + } + ges, err := newGasEpochState( + feeHandler, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return true + }, + }) + assert.Nil(t, err) + + assert.Equal(t, uint64(0), ges.roundForLimits) + ges.RoundConfirmed(20) + assert.Equal(t, uint64(20), ges.roundForLimits) + assert.Equal(t, uint32(1), ges.epochForLimits) + assert.Equal(t, uint64(3), ges.overEstimationFactor) + + epoch, overEstimationFactor := ges.GetEpochForLimitsAndOverEstimationFactor() + assert.Equal(t, uint32(1), epoch) + assert.Equal(t, uint64(3), overEstimationFactor) +} diff --git a/process/block/preprocess/gasTracker.go b/process/block/preprocess/gasTracker.go index 6cebea3426d..a4e024a0968 100644 --- a/process/block/preprocess/gasTracker.go +++ b/process/block/preprocess/gasTracker.go @@ -3,14 +3,47 @@ package preprocess import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/sharding" ) +const noOverestimationFactor = uint64(100) + type gasTracker struct { shardCoordinator sharding.Coordinator economicsFee process.FeeHandler gasHandler process.GasHandler + gasEpochState GasEpochStateHandler +} + +func newGasTracker( + shardCoordinator sharding.Coordinator, + gasHandler process.GasHandler, + economicsFee process.FeeHandler, + gasEpochState GasEpochStateHandler, +) gasTracker { + return gasTracker{ + shardCoordinator: shardCoordinator, + economicsFee: economicsFee, + gasHandler: gasHandler, + gasEpochState: gasEpochState, + } +} + +// EpochConfirmed is called whenever a new epoch is confirmed +func (gt *gasTracker) EpochConfirmed(epoch uint32, _ uint64) { + gt.gasEpochState.EpochConfirmed(epoch) +} + +// RoundConfirmed is called whenever a new round is confirmed +func (gt *gasTracker) RoundConfirmed(round uint64, _ uint64) { + gt.gasEpochState.RoundConfirmed(round) +} + +// IsInterfaceNil returns true if there is no value under the interface +func (gt *gasTracker) IsInterfaceNil() bool { + return gt == nil } func (gt *gasTracker) computeGasProvided( @@ -19,6 +52,7 @@ func (gt *gasTracker) computeGasProvided( tx data.TransactionHandler, txHash []byte, gasInfo *gasConsumedInfo, + skipBlockLimitCheck bool, ) (uint64, error) { gasProvidedByTxInSenderShard, gasProvidedByTxInReceiverShard, err := gt.computeGasProvidedByTx( senderShardId, @@ -29,22 +63,23 @@ func (gt *gasTracker) computeGasProvided( return 0, err } + epoch, overEstimationFactor := gt.gasEpochState.GetEpochForLimitsAndOverEstimationFactor() gasProvidedByTxInSelfShard := uint64(0) if gt.shardCoordinator.SelfId() == senderShardId { gasProvidedByTxInSelfShard = gasProvidedByTxInSenderShard - if gasProvidedByTxInReceiverShard > gt.economicsFee.MaxGasLimitPerTx() { + if gasProvidedByTxInReceiverShard > gt.getMaxGasLimitPerTx(epoch) { return 0, process.ErrMaxGasLimitPerOneTxInReceiverShardIsReached } - if gasInfo.gasConsumedByMiniBlockInReceiverShard+gasProvidedByTxInReceiverShard > gt.economicsFee.MaxGasLimitPerBlockForSafeCrossShard() { + if !skipBlockLimitCheck && gasInfo.gasConsumedByMiniBlockInReceiverShard+gasProvidedByTxInReceiverShard > gt.getMaxGasLimitPerBlockForSafeCrossShard(epoch, overEstimationFactor) { return 0, process.ErrMaxGasLimitPerMiniBlockInReceiverShardIsReached } } else { gasProvidedByTxInSelfShard = gasProvidedByTxInReceiverShard } - if gasInfo.totalGasConsumedInSelfShard+gasProvidedByTxInSelfShard > gt.economicsFee.MaxGasLimitPerBlock(gt.shardCoordinator.SelfId()) { + if !skipBlockLimitCheck && gasInfo.totalGasConsumedInSelfShard+gasProvidedByTxInSelfShard > gt.getMaxGasLimitPerBlock(epoch, overEstimationFactor) { return 0, process.ErrMaxGasLimitPerBlockInSelfShardIsReached } @@ -55,6 +90,18 @@ func (gt *gasTracker) computeGasProvided( return gasProvidedByTxInSelfShard, nil } +func (gt *gasTracker) getMaxGasLimitPerTx(epoch uint32) uint64 { + return gt.economicsFee.MaxGasLimitPerTxInEpoch(epoch) +} + +func (gt *gasTracker) getMaxGasLimitPerBlockForSafeCrossShard(epoch uint32, overEstimationFactor uint64) uint64 { + return gt.economicsFee.MaxGasLimitPerBlockForSafeCrossShardInEpoch(epoch) * overEstimationFactor / 100 +} + +func (gt *gasTracker) getMaxGasLimitPerBlock(epoch uint32, overEstimationFactor uint64) uint64 { + return gt.economicsFee.MaxGasLimitPerBlockInEpoch(gt.shardCoordinator.SelfId(), epoch) * overEstimationFactor / 100 +} + func (gt *gasTracker) computeGasProvidedByTx( senderShardId uint32, receiverShardId uint32, diff --git a/process/block/preprocess/gasTracker_test.go b/process/block/preprocess/gasTracker_test.go index d02d28ce5a4..06ead78c6d7 100644 --- a/process/block/preprocess/gasTracker_test.go +++ b/process/block/preprocess/gasTracker_test.go @@ -6,14 +6,18 @@ import ( "math/big" "testing" + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" - "github.com/stretchr/testify/require" ) func moveBalanceGas(data []byte) uint64 { @@ -60,6 +64,7 @@ func createDefaultGasTracker( selfShardID uint32, gcr *gasConsumedResult, gasRefunded uint64, + afterSupernova bool, ) *gasTracker { shardCoordinator := &testscommon.ShardsCoordinatorMock{ CurrentShard: selfShardID, @@ -68,15 +73,21 @@ func createDefaultGasTracker( MaxGasLimitPerBlockCalled: func(shardID uint32) uint64 { return 1500000000 }, - ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { + MaxGasLimitPerBlockInEpochCalled: func(shardID uint32, epoch uint32) uint64 { + return 1500000000 + }, + ComputeGasLimitInEpochCalled: func(tx data.TransactionWithFeeHandler, epoch uint32) uint64 { return moveBalanceGas(tx.GetData()) }, - MaxGasLimitPerTxCalled: func() uint64 { + MaxGasLimitPerTxInEpochCalled: func(epoch uint32) uint64 { return 1000000 }, - MaxGasLimitPerBlockForSafeCrossShardCalled: func() uint64 { + MaxGasLimitPerBlockForSafeCrossShardInEpochCalled: func(epoch uint32) uint64 { return 1000000 }, + BlockCapacityOverestimationFactorCalled: func() uint64 { + return 200 + }, } gasHandler := &testscommon.GasHandlerStub{ @@ -88,13 +99,32 @@ func createDefaultGasTracker( }, } - gt := &gasTracker{ - shardCoordinator: shardCoordinator, - economicsFee: economicsFee, - gasHandler: gasHandler, + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return afterSupernova + }, } - return gt + enableRoundsHandler := &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledCalled: func(flag common.EnableRoundFlag) bool { + return afterSupernova + }, + } + + ges, _ := newGasEpochState( + economicsFee, + enableEpochsHandler, + enableRoundsHandler, + ) + + gt := newGasTracker( + shardCoordinator, + gasHandler, + economicsFee, + ges, + ) + + return > } func Test_computeGasProvidedSelfSenderMoveBalanceIntra(t *testing.T) { @@ -113,7 +143,7 @@ func Test_computeGasProvidedSelfSenderMoveBalanceIntra(t *testing.T) { } gasRefund := uint64(25000) - gt := createDefaultGasTracker(senderShardID, gcr, gasRefund) + gt := createDefaultGasTracker(senderShardID, gcr, gasRefund, false) gasLimit := computeGasLimitFromResultAndRefund(gcr, gasRefund) tx := createDefaultTx(sndAddr, rcvAddr, gasLimit) @@ -149,7 +179,7 @@ func Test_computeGasProvidedSelfSenderSCCallIntra(t *testing.T) { gasRefund := uint64(25000) gasLimit := computeGasLimitFromResultAndRefund(gcr, gasRefund) - gt := createDefaultGasTracker(senderShardID, gcr, gasRefund) + gt := createDefaultGasTracker(senderShardID, gcr, gasRefund, false) tx := createDefaultTx(sndAddr, rcvAddr, gasLimit) tx.Data = []byte("sc invoking data") @@ -185,7 +215,7 @@ func Test_computeGasProvidedByTxSelfSenderMoveBalanceCross(t *testing.T) { gasRefund := uint64(25000) gasLimit := computeGasLimitFromResultAndRefund(gcr, gasRefund) - gt := createDefaultGasTracker(senderShardID, gcr, gasRefund) + gt := createDefaultGasTracker(senderShardID, gcr, gasRefund, false) tx := createDefaultTx(sndAddr, rcvAddr, gasLimit) txm, _ := marshaller.Marshal(tx) @@ -220,7 +250,7 @@ func Test_computeGasProvidedByTxSelfSenderScCallCross(t *testing.T) { gasRefund := uint64(25000) gasLimit := computeGasLimitFromResultAndRefund(gcr, gasRefund) - gt := createDefaultGasTracker(senderShardID, gcr, gasRefund) + gt := createDefaultGasTracker(senderShardID, gcr, gasRefund, false) tx := createDefaultTx(sndAddr, rcvAddr, gasLimit) tx.Data = []byte("tx invoking data") @@ -256,7 +286,7 @@ func Test_computeGasProvidedByTxGasHandlerComputeGasErrors(t *testing.T) { gasRefund := uint64(25000) gasLimit := computeGasLimitFromResultAndRefund(gcr, gasRefund) - gt := createDefaultGasTracker(senderShardID, gcr, gasRefund) + gt := createDefaultGasTracker(senderShardID, gcr, gasRefund, false) tx := createDefaultTx(sndAddr, rcvAddr, gasLimit) tx.Data = []byte("tx invoking data") @@ -299,7 +329,7 @@ func Test_computeGasProvidedByTxGasHandlerRefundGasLargerThanLimit(t *testing.T) gasRefund := uint64(25000) gasLimit := computeGasLimitFromResultAndRefund(gcr, gasRefund) - gt := createDefaultGasTracker(senderShardID, gcr, gasRefund) + gt := createDefaultGasTracker(senderShardID, gcr, gasRefund, false) tx := createDefaultTx(sndAddr, rcvAddr, gasLimit) tx.Data = []byte("tx invoking data") @@ -345,7 +375,7 @@ func Test_computeGasProvidedWithErrorForGasConsumedForTx(t *testing.T) { gasRefund := uint64(25000) gasLimit := computeGasLimitFromResultAndRefund(gcr, gasRefund) - gt := createDefaultGasTracker(senderShardID, gcr, gasRefund) + gt := createDefaultGasTracker(senderShardID, gcr, gasRefund, false) tx := createDefaultTx(sndAddr, rcvAddr, gasLimit) tx.Data = []byte("tx invoking data") @@ -366,6 +396,7 @@ func Test_computeGasProvidedWithErrorForGasConsumedForTx(t *testing.T) { tx, txHash, gci, + false, ) require.Equal(t, expectedError, err) } @@ -387,7 +418,7 @@ func Test_computeGasProvidedMaxGasLimitInSenderShardReached(t *testing.T) { gasRefund := uint64(25000) gasLimit := computeGasLimitFromResultAndRefund(gcr, gasRefund) - gt := createDefaultGasTracker(senderShardID, gcr, gasRefund) + gt := createDefaultGasTracker(senderShardID, gcr, gasRefund, false) tx := createDefaultTx(sndAddr, rcvAddr, gasLimit) tx.Data = []byte("tx invoking data") @@ -403,6 +434,7 @@ func Test_computeGasProvidedMaxGasLimitInSenderShardReached(t *testing.T) { tx, txHash, gci, + false, ) require.Equal(t, process.ErrMaxGasLimitPerMiniBlockInReceiverShardIsReached, err) } @@ -424,7 +456,7 @@ func Test_computeGasProvidedMaxGasLimitInReceiverShardReached(t *testing.T) { gasRefund := uint64(25000) gasLimit := computeGasLimitFromResultAndRefund(gcr, gasRefund) - gt := createDefaultGasTracker(receiverShardID, gcr, gasRefund) + gt := createDefaultGasTracker(receiverShardID, gcr, gasRefund, false) tx := createDefaultTx(sndAddr, rcvAddr, gasLimit) tx.Data = []byte("tx invoking data") @@ -440,10 +472,49 @@ func Test_computeGasProvidedMaxGasLimitInReceiverShardReached(t *testing.T) { tx, txHash, gci, + false, ) require.Equal(t, nil, err) } +func Test_computeGasProvidedMaxGasLimitInReceiverShardReachedIntra(t *testing.T) { + t.Parallel() + + senderShardID := uint32(0) + sndAddr, _ := hex.DecodeString("addrSender" + suffixShard0) + receiverShardID := uint32(0) + rcvAddr, _ := hex.DecodeString(smartContractAddressStart + suffixShard1) + hasher := &hashingMocks.HasherMock{} + marshaller := &marshallerMock.MarshalizerMock{} + gcr := &gasConsumedResult{ + consumedSenderShard: 75000, + consumedReceiverShard: 2000000, + err: nil, + } + + gasRefund := uint64(25000) + gasLimit := computeGasLimitFromResultAndRefund(gcr, gasRefund) + gt := createDefaultGasTracker(receiverShardID, gcr, gasRefund, false) + tx := createDefaultTx(sndAddr, rcvAddr, gasLimit) + tx.Data = []byte("tx invoking data") + + txm, _ := marshaller.Marshal(tx) + txHash := hasher.Compute(string(txm)) + + gci := &gasConsumedInfo{ + gasConsumedByMiniBlocksInSenderShard: gt.economicsFee.MaxGasLimitPerBlock(senderShardID) - gcr.consumedSenderShard/2, + } + _, err := gt.computeGasProvided( + senderShardID, + receiverShardID, + tx, + txHash, + gci, + false, + ) + require.Equal(t, process.ErrMaxGasLimitPerOneTxInReceiverShardIsReached, err) +} + func Test_computeGasProvidedMaxGasLimitPerBlockReached(t *testing.T) { t.Parallel() @@ -461,7 +532,7 @@ func Test_computeGasProvidedMaxGasLimitPerBlockReached(t *testing.T) { gasRefund := uint64(25000) gasLimit := computeGasLimitFromResultAndRefund(gcr, gasRefund) - gt := createDefaultGasTracker(senderShardID, gcr, gasRefund) + gt := createDefaultGasTracker(senderShardID, gcr, gasRefund, false) tx := createDefaultTx(sndAddr, rcvAddr, gasLimit) tx.Data = []byte("tx invoking data") @@ -477,6 +548,7 @@ func Test_computeGasProvidedMaxGasLimitPerBlockReached(t *testing.T) { tx, txHash, gci, + false, ) require.Equal(t, process.ErrMaxGasLimitPerBlockInSelfShardIsReached, err) } @@ -498,7 +570,7 @@ func Test_computeGasProvidedOK(t *testing.T) { gasRefund := uint64(25000) gasLimit := computeGasLimitFromResultAndRefund(gcr, gasRefund) - gt := createDefaultGasTracker(senderShardID, gcr, gasRefund) + gt := createDefaultGasTracker(senderShardID, gcr, gasRefund, true) tx := createDefaultTx(sndAddr, rcvAddr, gasLimit) tx.Data = []byte("tx invoking data") @@ -512,9 +584,67 @@ func Test_computeGasProvidedOK(t *testing.T) { tx, txHash, gci, + false, ) require.Nil(t, err) require.Equal(t, gcr.consumedSenderShard, gci.gasConsumedByMiniBlocksInSenderShard) require.Equal(t, gcr.consumedReceiverShard, gci.gasConsumedByMiniBlockInReceiverShard) require.Equal(t, gcr.consumedSenderShard, gci.totalGasConsumedInSelfShard) } + +func Test_getEpochAndOverestimationFactorForGasLimits(t *testing.T) { + t.Parallel() + + providedCurrentEpoch := uint32(10) + providedCurrentRound := uint64(150) + providedOverestimationFactor := uint64(200) + + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return epoch >= providedCurrentEpoch + }, + } + enableRoundsHandler := &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return round >= providedCurrentRound + }, + } + economicsFee := &economicsmocks.EconomicsHandlerMock{ + BlockCapacityOverestimationFactorCalled: func() uint64 { + return providedOverestimationFactor + }, + } + + ges, _ := newGasEpochState(economicsFee, enableEpochsHandler, enableRoundsHandler) + + gt := &gasTracker{ + shardCoordinator: &testscommon.ShardsCoordinatorMock{}, + economicsFee: &economicsmocks.EconomicsHandlerMock{ + BlockCapacityOverestimationFactorCalled: func() uint64 { + return providedOverestimationFactor + }, + }, + gasHandler: &testscommon.GasHandlerStub{}, + gasEpochState: ges, + } + + // before supernova + gt.EpochConfirmed(providedCurrentEpoch-1, 0) + gt.RoundConfirmed(0, 0) + epoch, overestimationFactor := gt.getEpochAndOverestimationFactorForGasLimits() + require.Equal(t, providedCurrentEpoch-1, epoch) + require.Equal(t, noOverestimationFactor, overestimationFactor) + + // supernova epoch active + gt.EpochConfirmed(providedCurrentEpoch, 0) + gt.RoundConfirmed(providedCurrentRound-1, 0) + epoch, overestimationFactor = gt.getEpochAndOverestimationFactorForGasLimits() + require.Equal(t, providedCurrentEpoch-1, epoch) + require.Equal(t, noOverestimationFactor, overestimationFactor) + + // supernova activation completed + gt.RoundConfirmed(providedCurrentRound, 0) + epoch, overestimationFactor = gt.getEpochAndOverestimationFactorForGasLimits() + require.Equal(t, providedCurrentEpoch, epoch) + require.Equal(t, providedOverestimationFactor, overestimationFactor) +} diff --git a/process/block/preprocess/interfaces.go b/process/block/preprocess/interfaces.go index b98f2271308..aa74d7b6e32 100644 --- a/process/block/preprocess/interfaces.go +++ b/process/block/preprocess/interfaces.go @@ -4,18 +4,22 @@ import ( "math/big" "time" - "github.com/multiversx/mx-chain-go/storage/txcache" -) + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" -// SortedTransactionsProvider defines the public API of the transactions cache -type SortedTransactionsProvider interface { - GetSortedTransactions(session txcache.SelectionSession) []*txcache.WrappedTransaction - IsInterfaceNil() bool -} + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/txcache" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/dataRetriever" +) // TxCache defines the functionality for the transactions cache type TxCache interface { - SelectTransactions(session txcache.SelectionSession, gasRequested uint64, maxNum int, selectionLoopMaximumDuration time.Duration) ([]*txcache.WrappedTransaction, uint64) + SelectTransactions(session txcache.SelectionSession, options common.TxSelectionOptions, currentBlockNonce uint64) ([]*txcache.WrappedTransaction, uint64, error) + SimulateSelectTransactions(session txcache.SelectionSession, options common.TxSelectionOptions, currentNonce uint64) ([]*txcache.WrappedTransaction, uint64, error) + GetVirtualNonceAndRootHash(sender []byte) (uint64, []byte, error) + SetAOTSelectionPreempter(preempter common.AOTSelectionPreempter) IsInterfaceNil() bool } @@ -30,7 +34,9 @@ type BlockTracker interface { type BlockSizeComputationHandler interface { Init() AddNumMiniBlocks(numMiniBlocks int) + DecNumMiniBlocks(numMiniBlocks int) AddNumTxs(numTxs int) + DecNumTxs(numTxs int) IsMaxBlockSizeReached(numNewMiniBlocks int, numNewTxs int) bool IsMaxBlockSizeWithoutThrottleReached(numNewMiniBlocks int, numNewTxs int) bool IsInterfaceNil() bool @@ -54,3 +60,35 @@ type BalanceComputationHandler interface { AddressHasEnoughBalance(address []byte, value *big.Int) bool IsInterfaceNil() bool } + +// TxsForBlockHandler defines the functionality for handling transactions for a block +type TxsForBlockHandler interface { + Reset() + AddTransaction( + txHash []byte, + tx data.TransactionHandler, + senderShardID uint32, + receiverShardID uint32, + ) + WaitForRequestedData(waitTime time.Duration) error + GetTxInfoByHash(hash []byte) (*process.TxInfo, bool) + GetAllCurrentUsedTxs() map[string]data.TransactionHandler + GetMissingTxsCount() int + ReceivedTransaction(txHash []byte, tx data.TransactionHandler) + HasMissingTransactions() bool + ComputeExistingAndRequestMissing( + body *block.Body, + isMiniBlockCorrect func(block.Type) bool, + txPool dataRetriever.ShardedDataCacherNotifier, + onRequestTxs func(shardID uint32, txHashes [][]byte), + ) int + IsInterfaceNil() bool +} + +// GasEpochStateHandler defines the functionality for handling gas limits and overestimation factor based on epochs and rounds +type GasEpochStateHandler interface { + EpochConfirmed(epoch uint32) + RoundConfirmed(round uint64) + GetEpochForLimitsAndOverEstimationFactor() (uint32, uint64) + IsInterfaceNil() bool +} diff --git a/process/block/preprocess/miniBlockBuilder.go b/process/block/preprocess/miniBlockBuilder.go index d10e6ba6ee5..4cf6ad83285 100644 --- a/process/block/preprocess/miniBlockBuilder.go +++ b/process/block/preprocess/miniBlockBuilder.go @@ -11,12 +11,13 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/sharding" "github.com/multiversx/mx-chain-go/state" - "github.com/multiversx/mx-chain-go/storage/txcache" + "github.com/multiversx/mx-chain-go/txcache" ) type miniBlocksBuilderArgs struct { @@ -30,6 +31,8 @@ type miniBlocksBuilderArgs struct { getTxMaxTotalCost func(txHandler data.TransactionHandler) *big.Int getTotalGasConsumed func() uint64 txPool dataRetriever.ShardedDataCacherNotifier + enableEpochsHandler common.EnableEpochsHandler + enableRoundsHandler common.EnableRoundsHandler } type miniBlockBuilderStats struct { @@ -62,6 +65,8 @@ type miniBlocksBuilder struct { getTxMaxTotalCost func(txHandler data.TransactionHandler) *big.Int stats miniBlockBuilderStats txPool dataRetriever.ShardedDataCacherNotifier + enableEpochsHandler common.EnableEpochsHandler + enableRoundsHandler common.EnableRoundsHandler } func newMiniBlockBuilder(args miniBlocksBuilderArgs) (*miniBlocksBuilder, error) { @@ -86,9 +91,11 @@ func newMiniBlockBuilder(args miniBlocksBuilderArgs) (*miniBlocksBuilder, error) gasConsumedByMiniBlockInReceiverShard: 0, totalGasConsumedInSelfShard: args.getTotalGasConsumed(), }, - stats: miniBlockBuilderStats{}, - senderToSkip: []byte(""), - txPool: args.txPool, + stats: miniBlockBuilderStats{}, + senderToSkip: []byte(""), + txPool: args.txPool, + enableEpochsHandler: args.enableEpochsHandler, + enableRoundsHandler: args.enableRoundsHandler, }, nil } @@ -300,7 +307,8 @@ func (mbb *miniBlocksBuilder) accountGasForTx(tx *transaction.Transaction, wtx * wtx.ReceiverShardID, tx, wtx.TxHash, - &mbb.gasInfo) + &mbb.gasInfo, + false) elapsedTime := time.Since(startTime) mbb.stats.totalGasComputeTime += elapsedTime if err != nil { @@ -351,10 +359,10 @@ func (mbb *miniBlocksBuilder) handleGasRefund(wtx *txcache.WrappedTransaction, g func (mbb *miniBlocksBuilder) handleFailedTransaction() { if !mbb.stats.firstInvalidTxFound { mbb.stats.firstInvalidTxFound = true - mbb.blockSizeComputation.AddNumMiniBlocks(1) + mbb.addNumMiniBlocks(1) } - mbb.blockSizeComputation.AddNumTxs(1) + mbb.addNumTxs(1) mbb.stats.numTxsFailed++ } @@ -362,11 +370,11 @@ func (mbb *miniBlocksBuilder) addTxAndUpdateBlockSize(tx *transaction.Transactio miniBlock := mbb.miniBlocks[wtx.ReceiverShardID] if len(miniBlock.TxHashes) == 0 { - mbb.blockSizeComputation.AddNumMiniBlocks(1) + mbb.addNumMiniBlocks(1) } miniBlock.TxHashes = append(miniBlock.TxHashes, wtx.TxHash) - mbb.blockSizeComputation.AddNumTxs(1) + mbb.addNumTxs(1) if isCrossShardScCallOrSpecialTx(wtx.ReceiverShardID, mbb.shardCoordinator.SelfId(), tx) { mbb.handleCrossShardScCallOrSpecialTx() } @@ -376,9 +384,25 @@ func (mbb *miniBlocksBuilder) addTxAndUpdateBlockSize(tx *transaction.Transactio func (mbb *miniBlocksBuilder) handleCrossShardScCallOrSpecialTx() { if !mbb.stats.firstCrossShardScCallOrSpecialTxFound { mbb.stats.firstCrossShardScCallOrSpecialTxFound = true - mbb.blockSizeComputation.AddNumMiniBlocks(1) + mbb.addNumMiniBlocks(1) } // we need to increment this as to account for the corresponding SCR hash - mbb.blockSizeComputation.AddNumTxs(common.AdditionalScrForEachScCallOrSpecialTx) + mbb.addNumTxs(common.AdditionalScrForEachScCallOrSpecialTx) mbb.stats.numCrossShardSCCallsOrSpecialTxs++ } + +func (mbb *miniBlocksBuilder) addNumTxs(numTxs int) { + if common.IsAsyncExecutionEnabled(mbb.enableEpochsHandler, mbb.enableRoundsHandler) { + return + } + + mbb.blockSizeComputation.AddNumTxs(numTxs) +} + +func (mbb *miniBlocksBuilder) addNumMiniBlocks(numMiniBlocks int) { + if common.IsAsyncExecutionEnabled(mbb.enableEpochsHandler, mbb.enableRoundsHandler) { + return + } + + mbb.blockSizeComputation.AddNumMiniBlocks(numMiniBlocks) +} diff --git a/process/block/preprocess/miniBlockBuilder_test.go b/process/block/preprocess/miniBlockBuilder_test.go index 7b73c77fead..1b48a328266 100644 --- a/process/block/preprocess/miniBlockBuilder_test.go +++ b/process/block/preprocess/miniBlockBuilder_test.go @@ -10,12 +10,13 @@ import ( "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-go/process" - "github.com/multiversx/mx-chain-go/storage/txcache" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" stateMock "github.com/multiversx/mx-chain-go/testscommon/state" + "github.com/multiversx/mx-chain-go/txcache" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -496,6 +497,7 @@ func Test_MiniBlocksBuilderAccountGasForTxComputeGasProvidedWithErr(t *testing.T return 0, 0, expectedErr }, }, + gasEpochState: &testscommon.GasEpochStateHandlerStub{}, } mbb, _ := newMiniBlockBuilder(args) sender, _ := hex.DecodeString("aaaaaaaaaa" + suffixShard0) @@ -518,6 +520,7 @@ func Test_MiniBlocksBuilderAccountGasForTxComputeGasProvidedOK(t *testing.T) { args := createDefaultMiniBlockBuilderArgs() gasProvidedByTxInReceiverShard := uint64(20) gasProvidedByTxInSenderShard := uint64(10) + ges, _ := newGasEpochState(args.gasTracker.economicsFee, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) args.gasTracker = gasTracker{ shardCoordinator: args.gasTracker.shardCoordinator, economicsFee: args.gasTracker.economicsFee, @@ -528,6 +531,7 @@ func Test_MiniBlocksBuilderAccountGasForTxComputeGasProvidedOK(t *testing.T) { return gasProvidedByTxInSenderShard, gasProvidedByTxInReceiverShard, nil }, }, + gasEpochState: ges, } mbb, _ := newMiniBlockBuilder(args) sender, _ := hex.DecodeString("aaaaaaaaaa" + suffixShard0) @@ -796,6 +800,7 @@ func Test_MiniBlocksBuilderCheckAddTransactionGasAccountingError(t *testing.T) { return 0, 0, expectedErr }, }, + gasEpochState: &testscommon.GasEpochStateHandlerStub{}, } mbb, _ := newMiniBlockBuilder(args) @@ -838,13 +843,13 @@ func createDefaultMiniBlockBuilderArgs() miniBlocksBuilderArgs { }, }, economicsFee: &economicsmocks.EconomicsHandlerMock{ - MaxGasLimitPerTxCalled: func() uint64 { + MaxGasLimitPerTxInEpochCalled: func(_ uint32) uint64 { return 1000000 }, - MaxGasLimitPerBlockForSafeCrossShardCalled: func() uint64 { + MaxGasLimitPerBlockForSafeCrossShardInEpochCalled: func(_ uint32) uint64 { return 1000000 }, - MaxGasLimitPerBlockCalled: func(shardID uint32) uint64 { + MaxGasLimitPerBlockInEpochCalled: func(shardID uint32, _ uint32) uint64 { return 1000000 }, }, @@ -856,6 +861,7 @@ func createDefaultMiniBlockBuilderArgs() miniBlocksBuilderArgs { RemoveGasPenalizedCalled: func(hashes [][]byte) { }, }, + gasEpochState: &testscommon.GasEpochStateHandlerStub{}, }, accounts: &stateMock.AccountsStub{}, blockSizeComputation: &testscommon.BlockSizeComputationStub{}, @@ -869,6 +875,8 @@ func createDefaultMiniBlockBuilderArgs() miniBlocksBuilderArgs { }, getTotalGasConsumed: getTotalGasConsumedZero, txPool: shardedDataCacherNotifier(), + enableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + enableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, } } diff --git a/process/block/preprocess/rewardTxPreProcessor.go b/process/block/preprocess/rewardTxPreProcessor.go index e695d51e498..e188de2841f 100644 --- a/process/block/preprocess/rewardTxPreProcessor.go +++ b/process/block/preprocess/rewardTxPreProcessor.go @@ -8,142 +8,100 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/rewardTx" - "github.com/multiversx/mx-chain-core-go/hashing" - "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" - "github.com/multiversx/mx-chain-go/sharding" - "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/storage" ) var _ process.DataMarshalizer = (*rewardTxPreprocessor)(nil) var _ process.PreProcessor = (*rewardTxPreprocessor)(nil) +// RewardsPreProcessorArgs is the struct that contains all the dependencies needed for creating a reward transaction preprocessor +type RewardsPreProcessorArgs struct { + BasePreProcessorArgs + RewardProcessor process.RewardTransactionProcessor +} + type rewardTxPreprocessor struct { *basePreProcess - chReceivedAllRewardTxs chan bool - onRequestRewardTx func(shardID uint32, txHashes [][]byte) - rewardTxsForBlock txsForBlock - rewardTxPool dataRetriever.ShardedDataCacherNotifier - storage dataRetriever.StorageService - rewardsProcessor process.RewardTransactionProcessor + onRequestRewardTx func(shardID uint32, txHashes [][]byte) + rewardTxsForBlock TxsForBlockHandler + rewardTxPool dataRetriever.ShardedDataCacherNotifier + storage dataRetriever.StorageService + rewardsProcessor process.RewardTransactionProcessor } // NewRewardTxPreprocessor creates a new reward transaction preprocessor object -func NewRewardTxPreprocessor( - rewardTxDataPool dataRetriever.ShardedDataCacherNotifier, - store dataRetriever.StorageService, - hasher hashing.Hasher, - marshalizer marshal.Marshalizer, - rewardProcessor process.RewardTransactionProcessor, - shardCoordinator sharding.Coordinator, - accounts state.AccountsAdapter, - onRequestRewardTransaction func(shardID uint32, txHashes [][]byte), - gasHandler process.GasHandler, - pubkeyConverter core.PubkeyConverter, - blockSizeComputation BlockSizeComputationHandler, - balanceComputation BalanceComputationHandler, - processedMiniBlocksTracker process.ProcessedMiniBlocksTracker, - txExecutionOrderHandler common.TxExecutionOrderHandler, -) (*rewardTxPreprocessor, error) { - - if check.IfNil(hasher) { - return nil, process.ErrNilHasher - } - if check.IfNil(marshalizer) { - return nil, process.ErrNilMarshalizer - } - if check.IfNil(rewardTxDataPool) { - return nil, process.ErrNilRewardTxDataPool - } - if check.IfNil(store) { - return nil, process.ErrNilStorage - } - if check.IfNil(rewardProcessor) { - return nil, process.ErrNilRewardsTxProcessor - } - if check.IfNil(shardCoordinator) { - return nil, process.ErrNilShardCoordinator - } - if check.IfNil(accounts) { - return nil, process.ErrNilAccountsAdapter - } - if onRequestRewardTransaction == nil { - return nil, process.ErrNilRequestHandler - } - if check.IfNil(gasHandler) { - return nil, process.ErrNilGasHandler - } - if check.IfNil(pubkeyConverter) { - return nil, process.ErrNilPubkeyConverter - } - if check.IfNil(blockSizeComputation) { - return nil, process.ErrNilBlockSizeComputationHandler - } - if check.IfNil(balanceComputation) { - return nil, process.ErrNilBalanceComputationHandler +func NewRewardTxPreprocessor(args RewardsPreProcessorArgs) (*rewardTxPreprocessor, error) { + err := checkBasePreProcessArgs(args.BasePreProcessorArgs) + if err != nil { + return nil, err } - if check.IfNil(processedMiniBlocksTracker) { - return nil, process.ErrNilProcessedMiniBlocksTracker + if check.IfNil(args.RewardProcessor) { + return nil, process.ErrNilRewardsTxProcessor } - if check.IfNil(txExecutionOrderHandler) { - return nil, process.ErrNilTxExecutionOrderHandler + + ges, err := newGasEpochState( + args.EconomicsFee, + args.EnableEpochsHandler, + args.EnableRoundsHandler, + ) + if err != nil { + return nil, err } bpp := &basePreProcess{ - hasher: hasher, - marshalizer: marshalizer, - gasTracker: gasTracker{ - shardCoordinator: shardCoordinator, - gasHandler: gasHandler, - economicsFee: nil, - }, - blockSizeComputation: blockSizeComputation, - balanceComputation: balanceComputation, - accounts: accounts, - pubkeyConverter: pubkeyConverter, - processedMiniBlocksTracker: processedMiniBlocksTracker, - txExecutionOrderHandler: txExecutionOrderHandler, - } + hasher: args.Hasher, + marshalizer: args.Marshalizer, + gasTracker: newGasTracker( + args.ShardCoordinator, + args.GasHandler, + args.EconomicsFee, + ges, + ), + blockSizeComputation: args.BlockSizeComputation, + balanceComputation: args.BalanceComputation, + accounts: args.Accounts, + accountsProposal: args.AccountsProposal, + pubkeyConverter: args.PubkeyConverter, + processedMiniBlocksTracker: args.ProcessedMiniBlocksTracker, + txExecutionOrderHandler: args.TxExecutionOrderHandler, + enableEpochsHandler: args.EnableEpochsHandler, + enableRoundsHandler: args.EnableRoundsHandler, + feeHandler: args.EconomicsFee, + } + + args.EpochNotifier.RegisterNotifyHandler(bpp) + args.RoundNotifier.RegisterNotifyHandler(bpp) rtp := &rewardTxPreprocessor{ basePreProcess: bpp, - storage: store, - rewardTxPool: rewardTxDataPool, - onRequestRewardTx: onRequestRewardTransaction, - rewardsProcessor: rewardProcessor, + storage: args.Store, + rewardTxPool: args.DataPool, + onRequestRewardTx: args.OnRequestTransaction, + rewardsProcessor: args.RewardProcessor, } - rtp.chReceivedAllRewardTxs = make(chan bool) rtp.rewardTxPool.RegisterOnAdded(rtp.receivedRewardTransaction) - rtp.rewardTxsForBlock.txHashAndInfo = make(map[string]*txInfo) + rtp.rewardTxsForBlock, err = NewTxsForBlock(args.ShardCoordinator) + if err != nil { + return nil, err + } return rtp, nil } -// waitForRewardTxHashes waits for a call whether all the requested smartContractResults appeared -func (rtp *rewardTxPreprocessor) waitForRewardTxHashes(waitTime time.Duration) error { - select { - case <-rtp.chReceivedAllRewardTxs: - return nil - case <-time.After(waitTime): - return process.ErrTimeIsOut - } -} - // IsDataPrepared returns non error if all the requested reward transactions arrived and were saved into the pool func (rtp *rewardTxPreprocessor) IsDataPrepared(requestedRewardTxs int, haveTime func() time.Duration) error { if requestedRewardTxs > 0 { log.Debug("requested missing reward txs", "num reward txs", requestedRewardTxs) - err := rtp.waitForRewardTxHashes(haveTime()) - rtp.rewardTxsForBlock.mutTxsForBlock.Lock() - missingRewardTxs := rtp.rewardTxsForBlock.missingTxs - rtp.rewardTxsForBlock.missingTxs = 0 - rtp.rewardTxsForBlock.mutTxsForBlock.Unlock() + err := rtp.rewardTxsForBlock.WaitForRequestedData(haveTime()) + missingRewardTxs := rtp.rewardTxsForBlock.GetMissingTxsCount() + // TODO: previously the number of missing reward txs was cleared in rewardTxsForBlock - check if this is still needed log.Debug("received reward txs", "num reward txs", requestedRewardTxs-missingRewardTxs) if err != nil { @@ -159,7 +117,7 @@ func (rtp *rewardTxPreprocessor) RemoveBlockDataFromPools(body *block.Body, mini } // RemoveTxsFromPools removes reward transactions from associated pools -func (rtp *rewardTxPreprocessor) RemoveTxsFromPools(body *block.Body) error { +func (rtp *rewardTxPreprocessor) RemoveTxsFromPools(body *block.Body, _ common.RootHashHolder) error { return rtp.removeTxsFromPools(body, rtp.rewardTxPool, rtp.isMiniBlockCorrect) } @@ -242,7 +200,7 @@ func (rtp *rewardTxPreprocessor) ProcessBlockTransactions( continue } - pi, err := rtp.getIndexesOfLastTxProcessed(miniBlock, headerHandler) + pi, err := rtp.getIndexesOfLastTxProcessedOnExecution(miniBlock, headerHandler) if err != nil { return err } @@ -259,15 +217,14 @@ func (rtp *rewardTxPreprocessor) ProcessBlockTransactions( } txHash := miniBlock.TxHashes[j] - rtp.rewardTxsForBlock.mutTxsForBlock.RLock() - txData, ok := rtp.rewardTxsForBlock.txHashAndInfo[string(txHash)] - rtp.rewardTxsForBlock.mutTxsForBlock.RUnlock() - if !ok || check.IfNil(txData.tx) { + + txData, ok := rtp.rewardTxsForBlock.GetTxInfoByHash(txHash) + if !ok || check.IfNil(txData.Tx) { log.Warn("missing rewardsTransaction in ProcessBlockTransactions ", "type", miniBlock.Type, "hash", txHash) return process.ErrMissingTransaction } - rTx, ok := txData.tx.(*rewardTx.RewardTx) + rTx, ok := txData.Tx.(*rewardTx.RewardTx) if !ok { return process.ErrWrongTypeAssertion } @@ -288,6 +245,16 @@ func (rtp *rewardTxPreprocessor) ProcessBlockTransactions( return nil } +// GetCreatedMiniBlocksFromMe returns nil as this preprocessor does not create any mini blocks +func (rtp *rewardTxPreprocessor) GetCreatedMiniBlocksFromMe() block.MiniBlockSlice { + return make(block.MiniBlockSlice, 0) +} + +// GetUnExecutableTransactions returns an empty map as reward transactions are always executable +func (rtp *rewardTxPreprocessor) GetUnExecutableTransactions() map[string]struct{} { + return make(map[string]struct{}) +} + // SaveTxsToStorage saves the reward transactions from body into storage func (rtp *rewardTxPreprocessor) SaveTxsToStorage(body *block.Body) error { if check.IfNil(body) { @@ -302,7 +269,7 @@ func (rtp *rewardTxPreprocessor) SaveTxsToStorage(body *block.Body) error { rtp.saveTxsToStorage( miniBlock.TxHashes, - &rtp.rewardTxsForBlock, + rtp.rewardTxsForBlock, rtp.storage, dataRetriever.RewardTransactionUnit, ) @@ -320,21 +287,12 @@ func (rtp *rewardTxPreprocessor) receivedRewardTransaction(key []byte, value int return } - receivedAllMissing := rtp.baseReceivedTransaction(key, tx, &rtp.rewardTxsForBlock) - - if receivedAllMissing { - rtp.chReceivedAllRewardTxs <- true - } + rtp.baseReceivedTransaction(key, tx, rtp.rewardTxsForBlock) } // CreateBlockStarted cleans the local cache map for processed/created reward transactions at this round func (rtp *rewardTxPreprocessor) CreateBlockStarted() { - _ = core.EmptyChannel(rtp.chReceivedAllRewardTxs) - - rtp.rewardTxsForBlock.mutTxsForBlock.Lock() - rtp.rewardTxsForBlock.missingTxs = 0 - rtp.rewardTxsForBlock.txHashAndInfo = make(map[string]*txInfo) - rtp.rewardTxsForBlock.mutTxsForBlock.Unlock() + rtp.rewardTxsForBlock.Reset() } // RequestBlockTransactions request for reward transactions if missing from a block.Body @@ -363,8 +321,7 @@ func (rtp *rewardTxPreprocessor) computeExistingAndRequestMissingRewardTxsForSha numMissingTxsForShards := rtp.computeExistingAndRequestMissing( &rewardTxsBody, - &rtp.rewardTxsForBlock, - rtp.chReceivedAllRewardTxs, + rtp.rewardTxsForBlock, rtp.isMiniBlockCorrect, rtp.rewardTxPool, rtp.onRequestRewardTx, @@ -373,26 +330,27 @@ func (rtp *rewardTxPreprocessor) computeExistingAndRequestMissingRewardTxsForSha return numMissingTxsForShards } -// RequestTransactionsForMiniBlock requests missing reward transactions for a certain miniblock -func (rtp *rewardTxPreprocessor) RequestTransactionsForMiniBlock(miniBlock *block.MiniBlock) int { +// GetTransactionsAndRequestMissingForMiniBlock returns the reward transactions from pool and requests missing for a certain miniblock +func (rtp *rewardTxPreprocessor) GetTransactionsAndRequestMissingForMiniBlock(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { if miniBlock == nil { - return 0 + return nil, 0 } - missingRewardTxsHashesForMiniBlock := rtp.computeMissingRewardTxsHashesForMiniBlock(miniBlock) + existingTxs, missingRewardTxsHashesForMiniBlock := rtp.computeMissingRewardTxsHashesForMiniBlock(miniBlock) if len(missingRewardTxsHashesForMiniBlock) > 0 { rtp.onRequestRewardTx(miniBlock.SenderShardID, missingRewardTxsHashesForMiniBlock) } - return len(missingRewardTxsHashesForMiniBlock) + return existingTxs, len(missingRewardTxsHashesForMiniBlock) } // computeMissingRewardTxsHashesForMiniBlock computes missing reward transactions hashes for a certain miniblock -func (rtp *rewardTxPreprocessor) computeMissingRewardTxsHashesForMiniBlock(miniBlock *block.MiniBlock) [][]byte { +func (rtp *rewardTxPreprocessor) computeMissingRewardTxsHashesForMiniBlock(miniBlock *block.MiniBlock) ([]data.TransactionHandler, [][]byte) { missingRewardTxsHashes := make([][]byte, 0) + existingTxs := make([]data.TransactionHandler, 0) if miniBlock.Type != block.RewardsBlock { - return missingRewardTxsHashes + return existingTxs, missingRewardTxsHashes } for _, txHash := range miniBlock.TxHashes { @@ -406,10 +364,13 @@ func (rtp *rewardTxPreprocessor) computeMissingRewardTxsHashesForMiniBlock(miniB if check.IfNil(tx) { missingRewardTxsHashes = append(missingRewardTxsHashes, txHash) + continue } + + existingTxs = append(existingTxs, tx) } - return missingRewardTxsHashes + return existingTxs, missingRewardTxsHashes } // getAllRewardTxsFromMiniBlock gets all the reward transactions from a miniblock into a new structure @@ -449,6 +410,11 @@ func (rtp *rewardTxPreprocessor) getAllRewardTxsFromMiniBlock( return rewardTxs, txHashes, nil } +// SelectOutgoingTransactions does nothing as rewards transactions are created by meta chain +func (rtp *rewardTxPreprocessor) SelectOutgoingTransactions(_ uint64, _ uint64, _ func() bool) ([][]byte, []data.TransactionHandler, error) { + return make([][]byte, 0), make([]data.TransactionHandler, 0), nil +} + // CreateAndProcessMiniBlocks creates miniblocks from storage and processes the reward transactions added into the miniblocks // as long as it has time func (rtp *rewardTxPreprocessor) CreateAndProcessMiniBlocks( @@ -491,7 +457,7 @@ func (rtp *rewardTxPreprocessor) ProcessMiniBlock( return nil, indexOfLastTxProcessed, false, err } - if rtp.blockSizeComputation.IsMaxBlockSizeWithoutThrottleReached(1, len(miniBlock.TxHashes)) { + if rtp.isMaxBlockSizeWithoutThrottleReached(1, len(miniBlock.TxHashes)) { return nil, indexOfLastTxProcessed, false, process.ErrMaxBlockSizeReached } @@ -528,23 +494,19 @@ func (rtp *rewardTxPreprocessor) ProcessMiniBlock( return processedTxHashes, txIndex - 1, true, err } - txShardData := &txShardInfo{senderShardID: miniBlock.SenderShardID, receiverShardID: miniBlock.ReceiverShardID} - - rtp.rewardTxsForBlock.mutTxsForBlock.Lock() for index, txHash := range miniBlockTxHashes { - rtp.rewardTxsForBlock.txHashAndInfo[string(txHash)] = &txInfo{tx: miniBlockRewardTxs[index], txShardInfo: txShardData} + rtp.rewardTxsForBlock.AddTransaction(txHash, miniBlockRewardTxs[index], miniBlock.SenderShardID, miniBlock.ReceiverShardID) } - rtp.rewardTxsForBlock.mutTxsForBlock.Unlock() - rtp.blockSizeComputation.AddNumMiniBlocks(1) - rtp.blockSizeComputation.AddNumTxs(len(miniBlock.TxHashes)) + rtp.addNumMiniBlocks(1) + rtp.addNumTxs(len(miniBlock.TxHashes)) return nil, txIndex - 1, false, err } // CreateMarshalledData marshals reward transactions hashes and saves them into a new structure func (rtp *rewardTxPreprocessor) CreateMarshalledData(txHashes [][]byte) ([][]byte, error) { - marshalledRewardTxs, err := rtp.createMarshalledData(txHashes, &rtp.rewardTxsForBlock) + marshalledRewardTxs, err := rtp.createMarshalledData(txHashes, rtp.rewardTxsForBlock) if err != nil { return nil, err } @@ -554,14 +516,7 @@ func (rtp *rewardTxPreprocessor) CreateMarshalledData(txHashes [][]byte) ([][]by // GetAllCurrentUsedTxs returns all the reward transactions used at current creation / processing func (rtp *rewardTxPreprocessor) GetAllCurrentUsedTxs() map[string]data.TransactionHandler { - rtp.rewardTxsForBlock.mutTxsForBlock.RLock() - rewardTxsPool := make(map[string]data.TransactionHandler, len(rtp.rewardTxsForBlock.txHashAndInfo)) - for txHash, txData := range rtp.rewardTxsForBlock.txHashAndInfo { - rewardTxsPool[txHash] = txData.tx - } - rtp.rewardTxsForBlock.mutTxsForBlock.RUnlock() - - return rewardTxsPool + return rtp.rewardTxsForBlock.GetAllCurrentUsedTxs() } // AddTxsFromMiniBlocks does nothing diff --git a/process/block/preprocess/rewardTxPreProcessor_test.go b/process/block/preprocess/rewardTxPreProcessor_test.go index 836a85d8652..1b7e0e41aba 100644 --- a/process/block/preprocess/rewardTxPreProcessor_test.go +++ b/process/block/preprocess/rewardTxPreProcessor_test.go @@ -9,6 +9,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/rewardTx" + "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" vmcommon "github.com/multiversx/mx-chain-vm-common-go" "github.com/stretchr/testify/assert" @@ -19,6 +20,7 @@ import ( "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/cache" "github.com/multiversx/mx-chain-go/testscommon/common" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" stateMock "github.com/multiversx/mx-chain-go/testscommon/state" storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" @@ -29,47 +31,22 @@ const testTxHash = "tx1_hash" func TestNewRewardTxPreprocessor_NilRewardTxDataPoolShouldErr(t *testing.T) { t.Parallel() - rtp, err := NewRewardTxPreprocessor( - nil, - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + tdp := initDataPool() + args := createDefaultRewardsProcessorArgs(tdp) + args.DataPool = nil + rtp, err := NewRewardTxPreprocessor(args) assert.Nil(t, rtp) - assert.Equal(t, process.ErrNilRewardTxDataPool, err) + assert.Equal(t, process.ErrNilTransactionPool, err) } func TestNewRewardTxPreprocessor_NilStoreShouldErr(t *testing.T) { t.Parallel() tdp := initDataPool() - rtp, err := NewRewardTxPreprocessor( - tdp.Transactions(), - nil, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + args.Store = nil + rtp, err := NewRewardTxPreprocessor(args) assert.Nil(t, rtp) assert.Equal(t, process.ErrNilStorage, err) @@ -79,22 +56,9 @@ func TestNewRewardTxPreprocessor_NilHasherShouldErr(t *testing.T) { t.Parallel() tdp := initDataPool() - rtp, err := NewRewardTxPreprocessor( - tdp.Transactions(), - &storageStubs.ChainStorerStub{}, - nil, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + args.Hasher = nil + rtp, err := NewRewardTxPreprocessor(args) assert.Nil(t, rtp) assert.Equal(t, process.ErrNilHasher, err) @@ -104,22 +68,9 @@ func TestNewRewardTxPreprocessor_NilMarshalizerShouldErr(t *testing.T) { t.Parallel() tdp := initDataPool() - rtp, err := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - nil, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + args.Marshalizer = nil + rtp, err := NewRewardTxPreprocessor(args) assert.Nil(t, rtp) assert.Equal(t, process.ErrNilMarshalizer, err) @@ -129,22 +80,9 @@ func TestNewRewardTxPreprocessor_NilRewardTxProcessorShouldErr(t *testing.T) { t.Parallel() tdp := initDataPool() - rtp, err := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - nil, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + args.RewardProcessor = nil + rtp, err := NewRewardTxPreprocessor(args) assert.Nil(t, rtp) assert.Equal(t, process.ErrNilRewardsTxProcessor, err) @@ -154,22 +92,9 @@ func TestNewRewardTxPreprocessor_NilShardCoordinatorShouldErr(t *testing.T) { t.Parallel() tdp := initDataPool() - rtp, err := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - nil, - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + args.ShardCoordinator = nil + rtp, err := NewRewardTxPreprocessor(args) assert.Nil(t, rtp) assert.Equal(t, process.ErrNilShardCoordinator, err) @@ -179,47 +104,33 @@ func TestNewRewardTxPreprocessor_NilAccountsShouldErr(t *testing.T) { t.Parallel() tdp := initDataPool() - rtp, err := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - nil, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + args.Accounts = nil + rtp, err := NewRewardTxPreprocessor(args) assert.Nil(t, rtp) assert.Equal(t, process.ErrNilAccountsAdapter, err) } +func TestNewRewardTxPreprocessor_NilAccountsProposalShouldErr(t *testing.T) { + t.Parallel() + + tdp := initDataPool() + args := createDefaultRewardsProcessorArgs(tdp) + args.AccountsProposal = nil + rtp, err := NewRewardTxPreprocessor(args) + + assert.Nil(t, rtp) + assert.ErrorIs(t, err, process.ErrNilAccountsAdapter) +} + func TestNewRewardTxPreprocessor_NilRequestHandlerShouldErr(t *testing.T) { t.Parallel() tdp := initDataPool() - rtp, err := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - nil, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + args.OnRequestTransaction = nil + rtp, err := NewRewardTxPreprocessor(args) assert.Nil(t, rtp) assert.Equal(t, process.ErrNilRequestHandler, err) @@ -229,47 +140,33 @@ func TestNewRewardTxPreprocessor_NilGasHandlerShouldErr(t *testing.T) { t.Parallel() tdp := initDataPool() - rtp, err := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - nil, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + args.GasHandler = nil + rtp, err := NewRewardTxPreprocessor(args) assert.Nil(t, rtp) assert.Equal(t, process.ErrNilGasHandler, err) } +func TestNewRewardTxPreprocessor_NilEnableRoundsHandlerShouldErr(t *testing.T) { + t.Parallel() + + tdp := initDataPool() + args := createDefaultRewardsProcessorArgs(tdp) + args.EnableRoundsHandler = nil + rtp, err := NewRewardTxPreprocessor(args) + + assert.Nil(t, rtp) + assert.Equal(t, process.ErrNilEnableRoundsHandler, err) +} + func TestNewRewardTxPreprocessor_NilPubkeyConverterShouldErr(t *testing.T) { t.Parallel() tdp := initDataPool() - rtp, err := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - nil, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + args.PubkeyConverter = nil + rtp, err := NewRewardTxPreprocessor(args) assert.Nil(t, rtp) assert.Equal(t, process.ErrNilPubkeyConverter, err) @@ -279,22 +176,9 @@ func TestNewRewardTxPreprocessor_NilBlockSizeComputationHandlerShouldErr(t *test t.Parallel() tdp := initDataPool() - rtp, err := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - nil, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + args.BlockSizeComputation = nil + rtp, err := NewRewardTxPreprocessor(args) assert.Nil(t, rtp) assert.Equal(t, process.ErrNilBlockSizeComputationHandler, err) @@ -304,22 +188,9 @@ func TestNewRewardTxPreprocessor_NilBalanceComputationHandlerShouldErr(t *testin t.Parallel() tdp := initDataPool() - rtp, err := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - nil, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + args.BalanceComputation = nil + rtp, err := NewRewardTxPreprocessor(args) assert.Nil(t, rtp) assert.Equal(t, process.ErrNilBalanceComputationHandler, err) @@ -329,22 +200,9 @@ func TestNewRewardTxPreprocessor_NilProcessedMiniBlocksTrackerShouldErr(t *testi t.Parallel() tdp := initDataPool() - rtp, err := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - nil, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + args.ProcessedMiniBlocksTracker = nil + rtp, err := NewRewardTxPreprocessor(args) assert.Nil(t, rtp) assert.Equal(t, process.ErrNilProcessedMiniBlocksTracker, err) @@ -354,22 +212,9 @@ func TestNewRewardTxPreprocessor_NilTxExecutionOrderHandlerShouldErr(t *testing. t.Parallel() tdp := initDataPool() - rtp, err := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - nil, - ) + args := createDefaultRewardsProcessorArgs(tdp) + args.TxExecutionOrderHandler = nil + rtp, err := NewRewardTxPreprocessor(args) assert.Nil(t, rtp) assert.Equal(t, process.ErrNilTxExecutionOrderHandler, err) @@ -379,22 +224,8 @@ func TestNewRewardTxPreprocessor_OkValsShouldWork(t *testing.T) { t.Parallel() tdp := initDataPool() - rtp, err := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + rtp, err := NewRewardTxPreprocessor(args) assert.Nil(t, err) assert.NotNil(t, rtp) } @@ -404,22 +235,8 @@ func TestRewardTxPreprocessor_CreateMarshalizedDataShouldWork(t *testing.T) { txHash := testTxHash tdp := initDataPool() - rtp, _ := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + rtp, _ := NewRewardTxPreprocessor(args) txHashes := [][]byte{[]byte(txHash)} txs := []data.TransactionHandler{&rewardTx.RewardTx{}} @@ -436,22 +253,8 @@ func TestRewardTxPreprocessor_ProcessMiniBlockInvalidMiniBlockTypeShouldErr(t *t txHash := testTxHash tdp := initDataPool() - rtp, _ := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + rtp, _ := NewRewardTxPreprocessor(args) txHashes := [][]byte{[]byte(txHash)} mb1 := block.MiniBlock{ @@ -475,26 +278,13 @@ func TestRewardTxPreprocessor_ProcessMiniBlockShouldWork(t *testing.T) { calledCount := 0 txHash := testTxHash tdp := initDataPool() - rtp, _ := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{ - AddCalled: func(txHash []byte) { - calledCount++ - }, + args := createDefaultRewardsProcessorArgs(tdp) + args.TxExecutionOrderHandler = &common.TxExecutionOrderHandlerStub{ + AddCalled: func(txHash []byte) { + calledCount++ }, - ) + } + rtp, _ := NewRewardTxPreprocessor(args) txHashes := [][]byte{[]byte(txHash)} mb1 := block.MiniBlock{ @@ -527,26 +317,13 @@ func TestRewardTxPreprocessor_ProcessMiniBlockNotFromMeta(t *testing.T) { calledCount := 0 txHash := testTxHash tdp := initDataPool() - rtp, _ := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{ - AddCalled: func(txHash []byte) { - calledCount++ - }, + args := createDefaultRewardsProcessorArgs(tdp) + args.TxExecutionOrderHandler = &common.TxExecutionOrderHandlerStub{ + AddCalled: func(txHash []byte) { + calledCount++ }, - ) + } + rtp, _ := NewRewardTxPreprocessor(args) txHashes := [][]byte{[]byte(txHash)} mb1 := block.MiniBlock{ @@ -573,22 +350,8 @@ func TestRewardTxPreprocessor_SaveTxsToStorageShouldWork(t *testing.T) { txHash := testTxHash tdp := initDataPool() - rtp, _ := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + rtp, _ := NewRewardTxPreprocessor(args) txHashes := [][]byte{[]byte(txHash)} txs := []data.TransactionHandler{&rewardTx.RewardTx{}} @@ -619,22 +382,8 @@ func TestRewardTxPreprocessor_RequestBlockTransactionsNoMissingTxsShouldWork(t * txHash := testTxHash tdp := initDataPool() - rtp, _ := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + rtp, _ := NewRewardTxPreprocessor(args) txHashes := [][]byte{[]byte(txHash)} mb1 := block.MiniBlock{ @@ -664,22 +413,8 @@ func TestRewardTxPreprocessor_RequestTransactionsForMiniBlockShouldWork(t *testi txHash := testTxHash tdp := initDataPool() - rtp, _ := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + rtp, _ := NewRewardTxPreprocessor(args) txHashes := [][]byte{[]byte(txHash)} mb1 := &block.MiniBlock{ @@ -689,8 +424,9 @@ func TestRewardTxPreprocessor_RequestTransactionsForMiniBlockShouldWork(t *testi Type: block.RewardsBlock, } - res := rtp.RequestTransactionsForMiniBlock(mb1) + txs, res := rtp.GetTransactionsAndRequestMissingForMiniBlock(mb1) assert.Equal(t, 0, res) + assert.Len(t, txs, 1) } func TestRewardTxPreprocessor_ProcessBlockTransactions(t *testing.T) { @@ -699,26 +435,13 @@ func TestRewardTxPreprocessor_ProcessBlockTransactions(t *testing.T) { txHash := testTxHash tdp := initDataPool() calledCount := 0 - rtp, _ := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{ - AddCalled: func(txHash []byte) { - calledCount++ - }, + args := createDefaultRewardsProcessorArgs(tdp) + args.TxExecutionOrderHandler = &common.TxExecutionOrderHandlerStub{ + AddCalled: func(txHash []byte) { + calledCount++ }, - ) + } + rtp, _ := NewRewardTxPreprocessor(args) txHashes := [][]byte{[]byte(txHash)} txs := []data.TransactionHandler{&rewardTx.RewardTx{}} @@ -754,26 +477,13 @@ func TestRewardTxPreprocessor_ProcessBlockTransactionsMissingTrieNode(t *testing missingNodeErr := fmt.Errorf(core.GetNodeFromDBErrorString) txHash := testTxHash tdp := initDataPool() - rtp, _ := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{ - GetExistingAccountCalled: func(_ []byte) (vmcommon.AccountHandler, error) { - return nil, missingNodeErr - }, + args := createDefaultRewardsProcessorArgs(tdp) + args.Accounts = &stateMock.AccountsStub{ + GetExistingAccountCalled: func(_ []byte) (vmcommon.AccountHandler, error) { + return nil, missingNodeErr }, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + } + rtp, _ := NewRewardTxPreprocessor(args) txHashes := [][]byte{[]byte(txHash)} txs := []data.TransactionHandler{&rewardTx.RewardTx{}} @@ -806,24 +516,13 @@ func TestRewardTxPreprocessor_IsDataPreparedShouldErr(t *testing.T) { t.Parallel() tdp := initDataPool() - rtp, _ := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + rtp, _ := NewRewardTxPreprocessor(args) - err := rtp.IsDataPrepared(1, haveTime) + txHashesMissing := [][]byte{[]byte("missing_tx_hash")} + + rtp.SetMissingRewardTxs(len(txHashesMissing)) + err := rtp.IsDataPrepared(len(txHashesMissing), haveTime) assert.Equal(t, process.ErrTimeIsOut, err) } @@ -832,26 +531,12 @@ func TestRewardTxPreprocessor_IsDataPrepared(t *testing.T) { t.Parallel() tdp := initDataPool() - rtp, _ := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + rtp, _ := NewRewardTxPreprocessor(args) go func() { time.Sleep(50 * time.Millisecond) - rtp.chReceivedAllRewardTxs <- true + rtp.SetMissingRewardTxs(0) }() err := rtp.IsDataPrepared(1, haveTime) @@ -879,22 +564,9 @@ func TestRewardTxPreprocessor_RestoreBlockDataIntoPools(t *testing.T) { }, nil }, } - rtp, _ := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storer, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + args.Store = &storer + rtp, _ := NewRewardTxPreprocessor(args) txHashes := [][]byte{[]byte("tx_hash1")} mb1 := block.MiniBlock{ @@ -918,29 +590,16 @@ func TestRewardTxPreprocessor_CreateAndProcessMiniBlocksShouldWork(t *testing.T) totalGasProvided := uint64(0) tdp := initDataPool() - rtp, _ := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{ - InitCalled: func() { - totalGasProvided = 0 - }, - TotalGasProvidedCalled: func() uint64 { - return totalGasProvided - }, + args := createDefaultRewardsProcessorArgs(tdp) + args.GasHandler = &testscommon.GasHandlerStub{ + InitCalled: func() { + totalGasProvided = 0 + }, + TotalGasProvidedCalled: func() uint64 { + return totalGasProvided }, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + } + rtp, _ := NewRewardTxPreprocessor(args) mBlocksSlice, err := rtp.CreateAndProcessMiniBlocks(haveTimeTrue, []byte("randomness")) assert.NotNil(t, mBlocksSlice) @@ -951,23 +610,62 @@ func TestRewardTxPreprocessor_CreateBlockStartedShouldCleanMap(t *testing.T) { t.Parallel() tdp := initDataPool() - rtp, _ := NewRewardTxPreprocessor( - tdp.RewardTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.RewardTxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - func(shardID uint32, txHashes [][]byte) {}, - &testscommon.GasHandlerStub{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &common.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultRewardsProcessorArgs(tdp) + rtp, _ := NewRewardTxPreprocessor(args) rtp.CreateBlockStarted() - assert.Equal(t, 0, len(rtp.rewardTxsForBlock.txHashAndInfo)) + rewardsForBlock := rtp.rewardTxsForBlock.(*txsForBlock) + assert.Equal(t, 0, len(rewardsForBlock.txHashAndInfo)) +} + +func createDefaultRewardsProcessorArgs(tdp dataRetriever.PoolsHolder) RewardsPreProcessorArgs { + requestTransaction := func(shardID uint32, txHashes [][]byte) {} + return RewardsPreProcessorArgs{ + BasePreProcessorArgs: BasePreProcessorArgs{ + DataPool: tdp.RewardTransactions(), + Store: &storageStubs.ChainStorerStub{}, + Hasher: &hashingMocks.HasherMock{}, + Marshalizer: &mock.MarshalizerMock{}, + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), + Accounts: &stateMock.AccountsStub{}, + AccountsProposal: &stateMock.AccountsStub{}, + OnRequestTransaction: requestTransaction, + GasHandler: &testscommon.GasHandlerStub{}, + PubkeyConverter: createMockPubkeyConverter(), + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &common.TxExecutionOrderHandlerStub{}, + EconomicsFee: feeHandlerMock(), + EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + }, + RewardProcessor: &testscommon.RewardTxProcessorMock{}, + } +} + +func TestRewardTxPreprocessor_GetCreatedMiniBlocksFromMe(t *testing.T) { + t.Parallel() + + tdp := initDataPool() + args := createDefaultRewardsProcessorArgs(tdp) + rtp, _ := NewRewardTxPreprocessor(args) + + // always returns empty + createdMbs := rtp.GetCreatedMiniBlocksFromMe() + assert.Len(t, createdMbs, 0) +} + +func TestRewardTxPreprocessor_GetUnExecutableTransactions(t *testing.T) { + t.Parallel() + + tdp := initDataPool() + args := createDefaultRewardsProcessorArgs(tdp) + rtp, _ := NewRewardTxPreprocessor(args) + + // always returns empty + unexecTxs := rtp.GetUnExecutableTransactions() + assert.Len(t, unexecTxs, 0) } diff --git a/process/block/preprocess/scheduledTxsExecution.go b/process/block/preprocess/scheduledTxsExecution.go index c52129c355b..0b7fdfda76b 100644 --- a/process/block/preprocess/scheduledTxsExecution.go +++ b/process/block/preprocess/scheduledTxsExecution.go @@ -16,6 +16,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/sharding" diff --git a/process/block/preprocess/selectionSession.go b/process/block/preprocess/selectionSession.go index 7e3b35687a1..987dea3c95f 100644 --- a/process/block/preprocess/selectionSession.go +++ b/process/block/preprocess/selectionSession.go @@ -2,103 +2,89 @@ package preprocess import ( "errors" + "math/big" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/state" - "github.com/multiversx/mx-chain-go/storage/txcache" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" ) type selectionSession struct { - accountsAdapter state.AccountsAdapter - transactionsProcessor process.TransactionProcessor - - // Cache of accounts, held in the scope of a single selection session. - // Not concurrency-safe, but never accessed concurrently. - ephemeralAccountsCache map[string]vmcommon.AccountHandler + transactionsProcessor process.TransactionProcessor + accountsProvider *state.AccountsEphemeralProvider + txVersionCheckerHandler process.TxVersionCheckerHandler } // ArgsSelectionSession holds the arguments for creating a new selection session. type ArgsSelectionSession struct { - AccountsAdapter state.AccountsAdapter - TransactionsProcessor process.TransactionProcessor + AccountsAdapter state.AccountsAdapter + TransactionsProcessor process.TransactionProcessor + TxVersionCheckerHandler process.TxVersionCheckerHandler } // NewSelectionSession creates a new selection session. +// TODO minimalize the interface used for AccountsAdapter func NewSelectionSession(args ArgsSelectionSession) (*selectionSession, error) { - if check.IfNil(args.AccountsAdapter) { - return nil, process.ErrNilAccountsAdapter - } if check.IfNil(args.TransactionsProcessor) { return nil, process.ErrNilTxProcessor } + if check.IfNil(args.TxVersionCheckerHandler) { + return nil, process.ErrNilTransactionVersionChecker + } - return &selectionSession{ - accountsAdapter: args.AccountsAdapter, - transactionsProcessor: args.TransactionsProcessor, - ephemeralAccountsCache: make(map[string]vmcommon.AccountHandler), - }, nil -} - -// GetAccountState returns the state of an account. -// Will be called by mempool during transaction selection. -func (session *selectionSession) GetAccountState(address []byte) (*txcache.AccountState, error) { - account, err := session.getExistingAccount(address) + // Provider is not concurrency-safe, but it's never accessed concurrently. + accountsProvider, err := state.NewAccountsEphemeralProvider(args.AccountsAdapter) if err != nil { return nil, err } - userAccount, ok := account.(state.UserAccountHandler) - if !ok { - return nil, process.ErrWrongTypeAssertion - } - - return &txcache.AccountState{ - Nonce: userAccount.GetNonce(), - Balance: userAccount.GetBalance(), + return &selectionSession{ + transactionsProcessor: args.TransactionsProcessor, + accountsProvider: accountsProvider, + txVersionCheckerHandler: args.TxVersionCheckerHandler, }, nil } -func (session *selectionSession) getExistingAccount(address []byte) (vmcommon.AccountHandler, error) { - account, ok := session.ephemeralAccountsCache[string(address)] - if ok { - return account, nil - } - - account, err := session.accountsAdapter.GetExistingAccount(address) - if err != nil { - return nil, err - } - - session.ephemeralAccountsCache[string(address)] = account - return account, nil +// GetAccountNonceAndBalance returns the nonce of the account, the balance of the account, and whether it's currently existing on-chain. +// Will be called by the transactions pool, during transactions selection. +func (session *selectionSession) GetAccountNonceAndBalance(address []byte) (uint64, *big.Int, bool, error) { + return session.accountsProvider.GetAccountNonceAndBalance(address) } // IsIncorrectlyGuarded checks if a transaction is incorrectly guarded (not executable). // Will be called by mempool during transaction selection. func (session *selectionSession) IsIncorrectlyGuarded(tx data.TransactionHandler) bool { address := tx.GetSndAddr() - account, err := session.getExistingAccount(address) - if err != nil { + account, err := session.accountsProvider.GetUserAccount(address) + if err != nil || check.IfNil(account) { + // Unexpected failure. In case of any error (or missing account), we assume the transaction is "correctly guarded". return false } - userAccount, ok := account.(state.UserAccountHandler) + txTyped, ok := tx.(*transaction.Transaction) if !ok { - // On this branch, we are (approximately) mirroring the behavior of "transactionsProcessor.VerifyGuardian()". - return true + return false } - txTyped, ok := tx.(*transaction.Transaction) + err = session.transactionsProcessor.VerifyGuardian(txTyped, account) + return errors.Is(err, process.ErrTransactionNotExecutable) +} + +// IsGuarded checks if the transaction is guarded +func (session *selectionSession) IsGuarded(tx data.TransactionHandler) bool { + txPtr, ok := tx.(*transaction.Transaction) if !ok { return false } - err = session.transactionsProcessor.VerifyGuardian(txTyped, userAccount) - return errors.Is(err, process.ErrTransactionNotExecutable) + return session.txVersionCheckerHandler.IsGuardedTransaction(txPtr) +} + +// GetRootHash returns the current root hash +func (session *selectionSession) GetRootHash() ([]byte, error) { + return session.accountsProvider.GetRootHash() } // IsInterfaceNil returns true if there is no value under the interface diff --git a/process/block/preprocess/selectionSession_test.go b/process/block/preprocess/selectionSession_test.go index 939da698cd1..65e98e2b806 100644 --- a/process/block/preprocess/selectionSession_test.go +++ b/process/block/preprocess/selectionSession_test.go @@ -3,6 +3,7 @@ package preprocess import ( "bytes" "fmt" + "math/big" "testing" "github.com/multiversx/mx-chain-core-go/data/transaction" @@ -18,28 +19,39 @@ func TestNewSelectionSession(t *testing.T) { t.Parallel() session, err := NewSelectionSession(ArgsSelectionSession{ - AccountsAdapter: nil, - TransactionsProcessor: &testscommon.TxProcessorStub{}, + AccountsAdapter: nil, + TransactionsProcessor: &testscommon.TxProcessorStub{}, + TxVersionCheckerHandler: &testscommon.TxVersionCheckerStub{}, }) require.Nil(t, session) - require.ErrorIs(t, err, process.ErrNilAccountsAdapter) + require.ErrorIs(t, err, state.ErrNilAccountsAdapter) session, err = NewSelectionSession(ArgsSelectionSession{ - AccountsAdapter: &stateMock.AccountsStub{}, - TransactionsProcessor: nil, + AccountsAdapter: &stateMock.AccountsStub{}, + TransactionsProcessor: nil, + TxVersionCheckerHandler: &testscommon.TxVersionCheckerStub{}, }) require.Nil(t, session) require.ErrorIs(t, err, process.ErrNilTxProcessor) session, err = NewSelectionSession(ArgsSelectionSession{ - AccountsAdapter: &stateMock.AccountsStub{}, - TransactionsProcessor: &testscommon.TxProcessorStub{}, + AccountsAdapter: &stateMock.AccountsStub{}, + TransactionsProcessor: &testscommon.TxProcessorStub{}, + TxVersionCheckerHandler: nil, + }) + require.Nil(t, session) + require.ErrorIs(t, err, process.ErrNilTransactionVersionChecker) + + session, err = NewSelectionSession(ArgsSelectionSession{ + AccountsAdapter: &stateMock.AccountsStub{}, + TransactionsProcessor: &testscommon.TxProcessorStub{}, + TxVersionCheckerHandler: &testscommon.TxVersionCheckerStub{}, }) require.NoError(t, err) require.NotNil(t, session) } -func TestSelectionSession_GetAccountState(t *testing.T) { +func TestSelectionSession_GetAccountNonceAndBalance(t *testing.T) { t.Parallel() accounts := &stateMock.AccountsStub{} @@ -50,6 +62,7 @@ func TestSelectionSession_GetAccountState(t *testing.T) { return &stateMock.UserAccountStub{ Address: []byte("alice"), Nonce: 42, + Balance: big.NewInt(3000000000000000000), }, nil } @@ -57,33 +70,61 @@ func TestSelectionSession_GetAccountState(t *testing.T) { return &stateMock.UserAccountStub{ Address: []byte("bob"), Nonce: 7, - IsGuardedCalled: func() bool { - return true - }, + Balance: big.NewInt(1000000000000000000), }, nil } - return nil, fmt.Errorf("account not found: %s", address) + return nil, state.ErrAccNotFound } session, err := NewSelectionSession(ArgsSelectionSession{ - AccountsAdapter: accounts, - TransactionsProcessor: processor, + AccountsAdapter: accounts, + TransactionsProcessor: processor, + TxVersionCheckerHandler: &testscommon.TxVersionCheckerStub{}, }) require.NoError(t, err) require.NotNil(t, session) - state, err := session.GetAccountState([]byte("alice")) + nonce, balance, existing, err := session.GetAccountNonceAndBalance([]byte("alice")) + require.NoError(t, err) + require.Equal(t, uint64(42), nonce) + require.Equal(t, "3000000000000000000", balance.String()) + require.True(t, existing) + + nonce, balance, existing, err = session.GetAccountNonceAndBalance([]byte("bob")) require.NoError(t, err) - require.Equal(t, uint64(42), state.Nonce) + require.Equal(t, uint64(7), nonce) + require.Equal(t, "1000000000000000000", balance.String()) + require.True(t, existing) + + nonce, balance, existing, err = session.GetAccountNonceAndBalance([]byte("carol")) + require.NoError(t, err) + require.Equal(t, uint64(0), nonce) + require.Equal(t, "0", balance.String()) + require.False(t, existing) +} + +func TestSelectionSession_GetRootHash(t *testing.T) { + t.Parallel() - state, err = session.GetAccountState([]byte("bob")) + processor := &testscommon.TxProcessorStub{} + accounts := &stateMock.AccountsStub{} + + accounts.RootHashCalled = func() ([]byte, error) { + return []byte("rootHash1"), nil + } + + session, err := NewSelectionSession(ArgsSelectionSession{ + AccountsAdapter: accounts, + TransactionsProcessor: processor, + TxVersionCheckerHandler: &testscommon.TxVersionCheckerStub{}, + }) require.NoError(t, err) - require.Equal(t, uint64(7), state.Nonce) + require.NotNil(t, session) - state, err = session.GetAccountState([]byte("carol")) - require.ErrorContains(t, err, "account not found: carol") - require.Nil(t, state) + rootHash, err := session.GetRootHash() + require.NoError(t, err) + require.Equal(t, []byte("rootHash1"), rootHash) } func TestSelectionSession_IsIncorrectlyGuarded(t *testing.T) { @@ -94,6 +135,7 @@ func TestSelectionSession_IsIncorrectlyGuarded(t *testing.T) { accounts.GetExistingAccountCalled = func(address []byte) (vmcommon.AccountHandler, error) { if bytes.Equal(address, []byte("bob")) { + // Bad account type (programming error). return &stateMock.BaseAccountMock{}, nil } @@ -112,8 +154,9 @@ func TestSelectionSession_IsIncorrectlyGuarded(t *testing.T) { } session, err := NewSelectionSession(ArgsSelectionSession{ - AccountsAdapter: accounts, - TransactionsProcessor: processor, + AccountsAdapter: accounts, + TransactionsProcessor: processor, + TxVersionCheckerHandler: &testscommon.TxVersionCheckerStub{}, }) require.NoError(t, err) require.NotNil(t, session) @@ -127,45 +170,7 @@ func TestSelectionSession_IsIncorrectlyGuarded(t *testing.T) { isIncorrectlyGuarded = session.IsIncorrectlyGuarded(&transaction.Transaction{Nonce: 44, SndAddr: []byte("alice")}) require.False(t, isIncorrectlyGuarded) + // Bad account type (programming error). isIncorrectlyGuarded = session.IsIncorrectlyGuarded(&transaction.Transaction{Nonce: 45, SndAddr: []byte("bob")}) - require.True(t, isIncorrectlyGuarded) -} - -func TestSelectionSession_ephemeralAccountsCache_IsSharedAmongCalls(t *testing.T) { - t.Parallel() - - accounts := &stateMock.AccountsStub{} - processor := &testscommon.TxProcessorStub{} - - numCallsGetExistingAccount := 0 - - accounts.GetExistingAccountCalled = func(_ []byte) (vmcommon.AccountHandler, error) { - numCallsGetExistingAccount++ - return &stateMock.UserAccountStub{}, nil - } - - session, err := NewSelectionSession(ArgsSelectionSession{ - AccountsAdapter: accounts, - TransactionsProcessor: processor, - }) - require.NoError(t, err) - require.NotNil(t, session) - - _, _ = session.GetAccountState([]byte("alice")) - require.Equal(t, 1, numCallsGetExistingAccount) - - _, _ = session.GetAccountState([]byte("alice")) - require.Equal(t, 1, numCallsGetExistingAccount) - - _ = session.IsIncorrectlyGuarded(&transaction.Transaction{Nonce: 42, SndAddr: []byte("alice")}) - require.Equal(t, 1, numCallsGetExistingAccount) - - _, _ = session.GetAccountState([]byte("bob")) - require.Equal(t, 2, numCallsGetExistingAccount) - - _, _ = session.GetAccountState([]byte("bob")) - require.Equal(t, 2, numCallsGetExistingAccount) - - _ = session.IsIncorrectlyGuarded(&transaction.Transaction{Nonce: 42, SndAddr: []byte("bob")}) - require.Equal(t, 2, numCallsGetExistingAccount) + require.False(t, isIncorrectlyGuarded) } diff --git a/process/block/preprocess/smartContractResults.go b/process/block/preprocess/smartContractResults.go index 3ac910a1834..e27107f9446 100644 --- a/process/block/preprocess/smartContractResults.go +++ b/process/block/preprocess/smartContractResults.go @@ -9,96 +9,45 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/smartContractResult" - "github.com/multiversx/mx-chain-core-go/hashing" - "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" - "github.com/multiversx/mx-chain-go/sharding" - "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/storage" ) var _ process.DataMarshalizer = (*smartContractResults)(nil) var _ process.PreProcessor = (*smartContractResults)(nil) +// SmartContractResultsArgs is the struct which contains all the arguments needed for creating a smartContractResult preprocessor +type SmartContractResultsArgs struct { + BasePreProcessorArgs + ScrProcessor process.SmartContractResultProcessor +} + type smartContractResults struct { *basePreProcess - chRcvAllScrs chan bool onRequestSmartContractResult func(shardID uint32, txHashes [][]byte) - scrForBlock txsForBlock + scrForBlock TxsForBlockHandler scrPool dataRetriever.ShardedDataCacherNotifier storage dataRetriever.StorageService scrProcessor process.SmartContractResultProcessor } // NewSmartContractResultPreprocessor creates a new smartContractResult preprocessor object -func NewSmartContractResultPreprocessor( - scrDataPool dataRetriever.ShardedDataCacherNotifier, - store dataRetriever.StorageService, - hasher hashing.Hasher, - marshalizer marshal.Marshalizer, - scrProcessor process.SmartContractResultProcessor, - shardCoordinator sharding.Coordinator, - accounts state.AccountsAdapter, - onRequestSmartContractResult func(shardID uint32, txHashes [][]byte), - gasHandler process.GasHandler, - economicsFee process.FeeHandler, - pubkeyConverter core.PubkeyConverter, - blockSizeComputation BlockSizeComputationHandler, - balanceComputation BalanceComputationHandler, - enableEpochsHandler common.EnableEpochsHandler, - processedMiniBlocksTracker process.ProcessedMiniBlocksTracker, - txExecutionOrderHandler common.TxExecutionOrderHandler, -) (*smartContractResults, error) { - - if check.IfNil(hasher) { - return nil, process.ErrNilHasher - } - if check.IfNil(marshalizer) { - return nil, process.ErrNilMarshalizer - } - if check.IfNil(scrDataPool) { - return nil, process.ErrNilUTxDataPool - } - if check.IfNil(store) { - return nil, process.ErrNilUTxStorage - } - if check.IfNil(scrProcessor) { - return nil, process.ErrNilTxProcessor - } - if check.IfNil(shardCoordinator) { - return nil, process.ErrNilShardCoordinator - } - if check.IfNil(accounts) { - return nil, process.ErrNilAccountsAdapter - } - if onRequestSmartContractResult == nil { - return nil, process.ErrNilRequestHandler - } - if check.IfNil(gasHandler) { - return nil, process.ErrNilGasHandler - } - if check.IfNil(economicsFee) { - return nil, process.ErrNilEconomicsFeeHandler - } - if check.IfNil(pubkeyConverter) { - return nil, process.ErrNilPubkeyConverter - } - if check.IfNil(blockSizeComputation) { - return nil, process.ErrNilBlockSizeComputationHandler - } - if check.IfNil(balanceComputation) { - return nil, process.ErrNilBalanceComputationHandler +func NewSmartContractResultPreprocessor(args SmartContractResultsArgs) (*smartContractResults, error) { + err := checkBasePreProcessArgs(args.BasePreProcessorArgs) + if err != nil { + return nil, err } - if check.IfNil(enableEpochsHandler) { - return nil, process.ErrNilEnableEpochsHandler + if check.IfNil(args.ScrProcessor) { + return nil, process.ErrNilTxProcessor } - if check.IfNil(processedMiniBlocksTracker) { + + if check.IfNil(args.ProcessedMiniBlocksTracker) { return nil, process.ErrNilProcessedMiniBlocksTracker } - err := core.CheckHandlerCompatibility(enableEpochsHandler, []core.EnableEpochFlag{ + err = core.CheckHandlerCompatibility(args.EnableEpochsHandler, []core.EnableEpochFlag{ common.OptimizeGasUsedInCrossMiniBlocksFlag, common.ScheduledMiniBlocksFlag, common.FrontRunningProtectionFlag, @@ -106,62 +55,65 @@ func NewSmartContractResultPreprocessor( if err != nil { return nil, err } - if check.IfNil(txExecutionOrderHandler) { - return nil, process.ErrNilTxExecutionOrderHandler + + ges, err := newGasEpochState( + args.EconomicsFee, + args.EnableEpochsHandler, + args.EnableRoundsHandler, + ) + if err != nil { + return nil, err } bpp := &basePreProcess{ - hasher: hasher, - marshalizer: marshalizer, - gasTracker: gasTracker{ - shardCoordinator: shardCoordinator, - gasHandler: gasHandler, - economicsFee: economicsFee, - }, - blockSizeComputation: blockSizeComputation, - balanceComputation: balanceComputation, - accounts: accounts, - pubkeyConverter: pubkeyConverter, - enableEpochsHandler: enableEpochsHandler, - processedMiniBlocksTracker: processedMiniBlocksTracker, - txExecutionOrderHandler: txExecutionOrderHandler, - } + hasher: args.Hasher, + marshalizer: args.Marshalizer, + gasTracker: newGasTracker( + args.ShardCoordinator, + args.GasHandler, + args.EconomicsFee, + ges, + ), + blockSizeComputation: args.BlockSizeComputation, + balanceComputation: args.BalanceComputation, + accounts: args.Accounts, + accountsProposal: args.AccountsProposal, + pubkeyConverter: args.PubkeyConverter, + enableEpochsHandler: args.EnableEpochsHandler, + enableRoundsHandler: args.EnableRoundsHandler, + processedMiniBlocksTracker: args.ProcessedMiniBlocksTracker, + txExecutionOrderHandler: args.TxExecutionOrderHandler, + feeHandler: args.EconomicsFee, + } + + args.EpochNotifier.RegisterNotifyHandler(bpp) + args.RoundNotifier.RegisterNotifyHandler(bpp) scr := &smartContractResults{ basePreProcess: bpp, - storage: store, - scrPool: scrDataPool, - onRequestSmartContractResult: onRequestSmartContractResult, - scrProcessor: scrProcessor, + storage: args.Store, + scrPool: args.DataPool, + onRequestSmartContractResult: args.OnRequestTransaction, + scrProcessor: args.ScrProcessor, } - scr.chRcvAllScrs = make(chan bool) scr.scrPool.RegisterOnAdded(scr.receivedSmartContractResult) - scr.scrForBlock.txHashAndInfo = make(map[string]*txInfo) + scr.scrForBlock, err = NewTxsForBlock(args.ShardCoordinator) + if err != nil { + return nil, err + } return scr, nil } -// waitForScrHashes waits for a call whether all the requested smartContractResults appeared -func (scr *smartContractResults) waitForScrHashes(waitTime time.Duration) error { - select { - case <-scr.chRcvAllScrs: - return nil - case <-time.After(waitTime): - return process.ErrTimeIsOut - } -} - // IsDataPrepared returns non error if all the requested smartContractResults arrived and were saved into the pool func (scr *smartContractResults) IsDataPrepared(requestedScrs int, haveTime func() time.Duration) error { if requestedScrs > 0 { log.Debug("requested missing scrs", "num scrs", requestedScrs) - err := scr.waitForScrHashes(haveTime()) - scr.scrForBlock.mutTxsForBlock.Lock() - missingScrs := scr.scrForBlock.missingTxs - scr.scrForBlock.missingTxs = 0 - scr.scrForBlock.mutTxsForBlock.Unlock() + err := scr.scrForBlock.WaitForRequestedData(haveTime()) + missingScrs := scr.scrForBlock.GetMissingTxsCount() + // TODO: previously the number of missing txs was cleared in scrForBlock - check if this is still needed log.Debug("received missing scrs", "num scrs", requestedScrs-missingScrs) if err != nil { @@ -177,7 +129,7 @@ func (scr *smartContractResults) RemoveBlockDataFromPools(body *block.Body, mini } // RemoveTxsFromPools removes smart contract results from associated pools -func (scr *smartContractResults) RemoveTxsFromPools(body *block.Body) error { +func (scr *smartContractResults) RemoveTxsFromPools(body *block.Body, _ common.RootHashHolder) error { return scr.removeTxsFromPools(body, scr.scrPool, scr.isMiniBlockCorrect) } @@ -284,6 +236,8 @@ func (scr *smartContractResults) ProcessBlockTransactions( ) }() + skipBlockLimitChecks := common.IsAsyncExecutionEnabled(scr.enableEpochsHandler, scr.enableRoundsHandler) + // basic validation already done in interceptors for i := 0; i < len(body.MiniBlocks); i++ { miniBlock := body.MiniBlocks[i] @@ -298,7 +252,7 @@ func (scr *smartContractResults) ProcessBlockTransactions( continue } - pi, err := scr.getIndexesOfLastTxProcessed(miniBlock, headerHandler) + pi, err := scr.getIndexesOfLastTxProcessedOnExecution(miniBlock, headerHandler) if err != nil { return err } @@ -309,32 +263,33 @@ func (scr *smartContractResults) ProcessBlockTransactions( return err } + var gasProvidedByTxInSelfShard uint64 for j := indexOfFirstTxToBeProcessed; j <= pi.indexOfLastTxProcessedByProposer; j++ { if !haveTime() { return process.ErrTimeIsOut } txHash := miniBlock.TxHashes[j] - scr.scrForBlock.mutTxsForBlock.RLock() - txInfoFromMap, ok := scr.scrForBlock.txHashAndInfo[string(txHash)] - scr.scrForBlock.mutTxsForBlock.RUnlock() - if !ok || check.IfNil(txInfoFromMap.tx) { + + txInfoFromMap, ok := scr.scrForBlock.GetTxInfoByHash(txHash) + if !ok || check.IfNil(txInfoFromMap.Tx) { log.Warn("missing transaction in ProcessBlockTransactions ", "type", miniBlock.Type, "txHash", txHash) return process.ErrMissingTransaction } - currScr, ok := txInfoFromMap.tx.(*smartContractResult.SmartContractResult) + currScr, ok := txInfoFromMap.Tx.(*smartContractResult.SmartContractResult) if !ok { return process.ErrWrongTypeAssertion } if scr.enableEpochsHandler.IsFlagEnabled(common.OptimizeGasUsedInCrossMiniBlocksFlag) { - gasProvidedByTxInSelfShard, err := scr.computeGasProvided( + gasProvidedByTxInSelfShard, err = scr.computeGasProvided( miniBlock.SenderShardID, miniBlock.ReceiverShardID, currScr, txHash, - &gasInfo) + &gasInfo, + skipBlockLimitChecks) if err != nil { return err @@ -362,6 +317,16 @@ func (scr *smartContractResults) ProcessBlockTransactions( return nil } +// GetCreatedMiniBlocksFromMe returns nil as this preprocessor does not create any mini blocks +func (scr *smartContractResults) GetCreatedMiniBlocksFromMe() block.MiniBlockSlice { + return make(block.MiniBlockSlice, 0) +} + +// GetUnExecutableTransactions returns an empty map as this preprocessor does not handle un-executable transactions +func (scr *smartContractResults) GetUnExecutableTransactions() map[string]struct{} { + return make(map[string]struct{}) +} + // SaveTxsToStorage saves smart contract results from body into storage func (scr *smartContractResults) SaveTxsToStorage(body *block.Body) error { if check.IfNil(body) { @@ -380,7 +345,7 @@ func (scr *smartContractResults) SaveTxsToStorage(body *block.Body) error { continue } - scr.saveTxsToStorage(miniBlock.TxHashes, &scr.scrForBlock, scr.storage, dataRetriever.UnsignedTransactionUnit) + scr.saveTxsToStorage(miniBlock.TxHashes, scr.scrForBlock, scr.storage, dataRetriever.UnsignedTransactionUnit) } return nil @@ -395,21 +360,12 @@ func (scr *smartContractResults) receivedSmartContractResult(key []byte, value i return } - receivedAllMissing := scr.baseReceivedTransaction(key, tx, &scr.scrForBlock) - - if receivedAllMissing { - scr.chRcvAllScrs <- true - } + scr.baseReceivedTransaction(key, tx, scr.scrForBlock) } // CreateBlockStarted cleans the local cache map for processed/created smartContractResults at this round func (scr *smartContractResults) CreateBlockStarted() { - _ = core.EmptyChannel(scr.chRcvAllScrs) - - scr.scrForBlock.mutTxsForBlock.Lock() - scr.scrForBlock.missingTxs = 0 - scr.scrForBlock.txHashAndInfo = make(map[string]*txInfo) - scr.scrForBlock.mutTxsForBlock.Unlock() + scr.scrForBlock.Reset() } // RequestBlockTransactions request for smartContractResults if missing from a block.Body @@ -438,8 +394,7 @@ func (scr *smartContractResults) computeExistingAndRequestMissingSCResultsForSha numMissingTxsForShard := scr.computeExistingAndRequestMissing( &scrTxs, - &scr.scrForBlock, - scr.chRcvAllScrs, + scr.scrForBlock, scr.isMiniBlockCorrect, scr.scrPool, scr.onRequestSmartContractResult, @@ -448,26 +403,27 @@ func (scr *smartContractResults) computeExistingAndRequestMissingSCResultsForSha return numMissingTxsForShard } -// RequestTransactionsForMiniBlock requests missing smartContractResults for a certain miniblock -func (scr *smartContractResults) RequestTransactionsForMiniBlock(miniBlock *block.MiniBlock) int { +// GetTransactionsAndRequestMissingForMiniBlock requests missing smartContractResults for a certain miniblock +func (scr *smartContractResults) GetTransactionsAndRequestMissingForMiniBlock(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { if miniBlock == nil { - return 0 + return nil, 0 } - missingScrsHashesForMiniBlock := scr.computeMissingScrsHashesForMiniBlock(miniBlock) + existingTxs, missingScrsHashesForMiniBlock := scr.computeMissingScrsHashesForMiniBlock(miniBlock) if len(missingScrsHashesForMiniBlock) > 0 { scr.onRequestSmartContractResult(miniBlock.SenderShardID, missingScrsHashesForMiniBlock) } - return len(missingScrsHashesForMiniBlock) + return existingTxs, len(missingScrsHashesForMiniBlock) } // computeMissingScrsHashesForMiniBlock computes missing smart contract results hashes for a certain miniblock -func (scr *smartContractResults) computeMissingScrsHashesForMiniBlock(miniBlock *block.MiniBlock) [][]byte { +func (scr *smartContractResults) computeMissingScrsHashesForMiniBlock(miniBlock *block.MiniBlock) ([]data.TransactionHandler, [][]byte) { missingSmartContractResultsHashes := make([][]byte, 0) + existingTxs := make([]data.TransactionHandler, 0) if miniBlock.Type != block.SmartContractResultBlock { - return missingSmartContractResultsHashes + return existingTxs, missingSmartContractResultsHashes } for _, txHash := range miniBlock.TxHashes { @@ -480,10 +436,13 @@ func (scr *smartContractResults) computeMissingScrsHashesForMiniBlock(miniBlock if check.IfNil(tx) { missingSmartContractResultsHashes = append(missingSmartContractResultsHashes, txHash) + continue } + + existingTxs = append(existingTxs, tx) } - return missingSmartContractResultsHashes + return existingTxs, missingSmartContractResultsHashes } // getAllScrsFromMiniBlock gets all the smartContractResults from a miniblock into a new structure @@ -530,6 +489,11 @@ func (scr *smartContractResults) getAllScrsFromMiniBlock( return smartContractResult.TrimSlicePtr(scResSlice), sliceUtil.TrimSliceSliceByte(txHashes), nil } +// SelectOutgoingTransactions returns an empty slice of byte slices, as this preprocessor does not handle outgoing transactions +func (scr *smartContractResults) SelectOutgoingTransactions(_ uint64, _ uint64, _ func() bool) ([][]byte, []data.TransactionHandler, error) { + return make([][]byte, 0), make([]data.TransactionHandler, 0), nil +} + // CreateAndProcessMiniBlocks creates miniblocks from storage and processes the reward transactions added into the miniblocks // as long as it has time func (scr *smartContractResults) CreateAndProcessMiniBlocks(_ func() bool, _ []byte) (block.MiniBlockSlice, error) { @@ -568,7 +532,7 @@ func (scr *smartContractResults) ProcessMiniBlock( return nil, indexOfLastTxProcessed, false, err } - if scr.blockSizeComputation.IsMaxBlockSizeWithoutThrottleReached(1, len(miniBlock.TxHashes)) { + if scr.isMaxBlockSizeWithoutThrottleReached(1, len(miniBlock.TxHashes)) { return nil, indexOfLastTxProcessed, false, process.ErrMaxBlockSizeReached } @@ -610,6 +574,7 @@ func (scr *smartContractResults) ProcessMiniBlock( ) }() + skipBlockLimitChecks := common.IsAsyncExecutionEnabled(scr.enableEpochsHandler, scr.enableRoundsHandler) for txIndex = indexOfFirstTxToBeProcessed; txIndex < len(miniBlockScrs); txIndex++ { if !haveTime() { err = process.ErrTimeIsOut @@ -621,7 +586,8 @@ func (scr *smartContractResults) ProcessMiniBlock( miniBlock.ReceiverShardID, miniBlockScrs[txIndex], miniBlockTxHashes[txIndex], - &gasInfo) + &gasInfo, + skipBlockLimitChecks) if err != nil { break @@ -658,23 +624,19 @@ func (scr *smartContractResults) ProcessMiniBlock( return processedTxHashes, txIndex - 1, true, err } - txShardInfoToSet := &txShardInfo{senderShardID: miniBlock.SenderShardID, receiverShardID: miniBlock.ReceiverShardID} - - scr.scrForBlock.mutTxsForBlock.Lock() for index, txHash := range miniBlockTxHashes { - scr.scrForBlock.txHashAndInfo[string(txHash)] = &txInfo{tx: miniBlockScrs[index], txShardInfo: txShardInfoToSet} + scr.scrForBlock.AddTransaction(txHash, miniBlockScrs[index], miniBlock.SenderShardID, miniBlock.ReceiverShardID) } - scr.scrForBlock.mutTxsForBlock.Unlock() - scr.blockSizeComputation.AddNumMiniBlocks(1) - scr.blockSizeComputation.AddNumTxs(len(miniBlock.TxHashes)) + scr.addNumMiniBlocks(1) + scr.addNumTxs(len(miniBlock.TxHashes)) return nil, txIndex - 1, false, err } // CreateMarshalledData marshals smart contract results hashes and saves them into a new structure func (scr *smartContractResults) CreateMarshalledData(txHashes [][]byte) ([][]byte, error) { - marshalledScrs, err := scr.createMarshalledData(txHashes, &scr.scrForBlock) + marshalledScrs, err := scr.createMarshalledData(txHashes, scr.scrForBlock) if err != nil { return nil, err } @@ -684,14 +646,7 @@ func (scr *smartContractResults) CreateMarshalledData(txHashes [][]byte) ([][]by // GetAllCurrentUsedTxs returns all the smartContractResults used at current creation / processing func (scr *smartContractResults) GetAllCurrentUsedTxs() map[string]data.TransactionHandler { - scr.scrForBlock.mutTxsForBlock.RLock() - scrsPool := make(map[string]data.TransactionHandler, len(scr.scrForBlock.txHashAndInfo)) - for txHash, txInfoFromMap := range scr.scrForBlock.txHashAndInfo { - scrsPool[txHash] = txInfoFromMap.tx - } - scr.scrForBlock.mutTxsForBlock.RUnlock() - - return scrsPool + return scr.scrForBlock.GetAllCurrentUsedTxs() } // AddTxsFromMiniBlocks does nothing diff --git a/process/block/preprocess/smartContractResults_test.go b/process/block/preprocess/smartContractResults_test.go index 37a03255c66..e16a2152c9f 100644 --- a/process/block/preprocess/smartContractResults_test.go +++ b/process/block/preprocess/smartContractResults_test.go @@ -13,8 +13,10 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/smartContractResult" + "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" vmcommon "github.com/multiversx/mx-chain-vm-common-go" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" @@ -61,81 +63,36 @@ func getTotalGasConsumedZero() uint64 { func TestScrsPreprocessor_NewSmartContractResultPreprocessorNilPool(t *testing.T) { t.Parallel() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, err := NewSmartContractResultPreprocessor( - nil, - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + tdp := initDataPool() + args := createDefaultSmartContractProcessorArgs(tdp) + args.DataPool = nil + + txs, err := NewSmartContractResultPreprocessor(args) assert.Nil(t, txs) - assert.Equal(t, process.ErrNilUTxDataPool, err) + assert.Equal(t, process.ErrNilTransactionPool, err) } func TestScrsPreprocessor_NewSmartContractResultPreprocessorNilStore(t *testing.T) { t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, err := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - nil, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + args.Store = nil + txs, err := NewSmartContractResultPreprocessor(args) assert.Nil(t, txs) - assert.Equal(t, process.ErrNilUTxStorage, err) + assert.Equal(t, process.ErrNilStorage, err) } func TestScrsPreprocessor_NewSmartContractResultPreprocessorNilHasher(t *testing.T) { t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, err := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - nil, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + args.Hasher = nil + + txs, err := NewSmartContractResultPreprocessor(args) assert.Nil(t, txs) assert.Equal(t, process.ErrNilHasher, err) @@ -145,54 +102,23 @@ func TestScrsPreprocessor_NewSmartContractResultPreprocessorNilMarsalizer(t *tes t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, err := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - nil, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + args.Marshalizer = nil + + txs, err := NewSmartContractResultPreprocessor(args) assert.Nil(t, txs) assert.Equal(t, process.ErrNilMarshalizer, err) } -func TestScrsPreprocessor_NewSmartContractResultPreprocessorNilTxProce(t *testing.T) { +func TestScrsPreprocessor_NewSmartContractResultPreprocessorNilScrProcessor(t *testing.T) { t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, err := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - nil, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + args.ScrProcessor = nil + txs, err := NewSmartContractResultPreprocessor(args) assert.Nil(t, txs) assert.Equal(t, process.ErrNilTxProcessor, err) } @@ -201,25 +127,10 @@ func TestScrsPreprocessor_NewSmartContractResultPreprocessorNilShardCoord(t *tes t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, err := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - nil, - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + args.ShardCoordinator = nil + + txs, err := NewSmartContractResultPreprocessor(args) assert.Nil(t, txs) assert.Equal(t, process.ErrNilShardCoordinator, err) @@ -229,52 +140,34 @@ func TestScrsPreprocessor_NewSmartContractResultPreprocessorNilAccounts(t *testi t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, err := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - nil, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + args.Accounts = nil + txs, err := NewSmartContractResultPreprocessor(args) assert.Nil(t, txs) assert.Equal(t, process.ErrNilAccountsAdapter, err) } +func TestScrsPreprocessor_NewSmartContractResultPreprocessorNilAccountsProposal(t *testing.T) { + t.Parallel() + + tdp := initDataPool() + args := createDefaultSmartContractProcessorArgs(tdp) + args.AccountsProposal = nil + + txs, err := NewSmartContractResultPreprocessor(args) + assert.Nil(t, txs) + require.ErrorIs(t, err, process.ErrNilAccountsAdapter) +} + func TestScrsPreprocessor_NewSmartContractResultPreprocessorNilRequestFunc(t *testing.T) { t.Parallel() tdp := initDataPool() - txs, err := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - nil, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + args.OnRequestTransaction = nil + + txs, err := NewSmartContractResultPreprocessor(args) assert.Nil(t, txs) assert.Equal(t, process.ErrNilRequestHandler, err) @@ -284,54 +177,35 @@ func TestScrsPreprocessor_NewSmartContractResultPreprocessorNilGasHandler(t *tes t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, err := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - nil, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + args.GasHandler = nil + + txs, err := NewSmartContractResultPreprocessor(args) assert.Nil(t, txs) assert.Equal(t, process.ErrNilGasHandler, err) } +func TestScrsPreprocessor_NewSmartContractResultPreprocessorNilEnableRoundsHandler(t *testing.T) { + t.Parallel() + + tdp := initDataPool() + args := createDefaultSmartContractProcessorArgs(tdp) + args.EnableRoundsHandler = nil + + txs, err := NewSmartContractResultPreprocessor(args) + + assert.Nil(t, txs) + assert.Equal(t, process.ErrNilEnableRoundsHandler, err) +} + func TestScrsPreprocessor_NewSmartContractResultPreprocessorShouldWork(t *testing.T) { t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, err := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + txs, err := NewSmartContractResultPreprocessor(args) assert.Nil(t, err) assert.False(t, check.IfNil(txs)) } @@ -340,26 +214,10 @@ func TestScrsPreprocessor_NewSmartContractResultPreprocessorNilPubkeyConverter(t t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, err := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - nil, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + args.PubkeyConverter = nil + txs, err := NewSmartContractResultPreprocessor(args) assert.Nil(t, txs) assert.Equal(t, process.ErrNilPubkeyConverter, err) } @@ -368,26 +226,10 @@ func TestScrsPreprocessor_NewSmartContractResultPreprocessorNilBlockSizeComputat t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, err := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - nil, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + args.BlockSizeComputation = nil + txs, err := NewSmartContractResultPreprocessor(args) assert.Nil(t, txs) assert.Equal(t, process.ErrNilBlockSizeComputationHandler, err) } @@ -396,25 +238,10 @@ func TestScrsPreprocessor_NewSmartContractResultPreprocessorNilBalanceComputatio t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, err := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - nil, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + args.BalanceComputation = nil + + txs, err := NewSmartContractResultPreprocessor(args) assert.Nil(t, txs) assert.Equal(t, process.ErrNilBalanceComputationHandler, err) @@ -424,26 +251,10 @@ func TestScrsPreprocessor_NewSmartContractResultPreprocessorNilEnableEpochsHandl t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, err := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &mock.GasHandlerMock{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - nil, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + args.EnableEpochsHandler = nil + txs, err := NewSmartContractResultPreprocessor(args) assert.Nil(t, txs) assert.Equal(t, process.ErrNilEnableEpochsHandler, err) } @@ -452,25 +263,10 @@ func TestScrsPreprocessor_NewSmartContractResultPreprocessorInvalidEnableEpochsH t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, err := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &mock.GasHandlerMock{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStubWithNoFlagsDefined(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + args.EnableEpochsHandler = enableEpochsHandlerMock.NewEnableEpochsHandlerStubWithNoFlagsDefined() + + txs, err := NewSmartContractResultPreprocessor(args) assert.Nil(t, txs) assert.True(t, errors.Is(err, core.ErrInvalidEnableEpochsHandler)) @@ -480,25 +276,10 @@ func TestScrsPreprocessor_NewSmartContractResultPreprocessorNilProcessedMiniBloc t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, err := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &mock.GasHandlerMock{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - nil, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + args.ProcessedMiniBlocksTracker = nil + + txs, err := NewSmartContractResultPreprocessor(args) assert.Nil(t, txs) assert.Equal(t, process.ErrNilProcessedMiniBlocksTracker, err) @@ -508,26 +289,10 @@ func TestNewSmartContractResult_NilTxExecutionOrderHandlerShouldErr(t *testing.T t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, err := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &mock.GasHandlerMock{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - nil, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + args.TxExecutionOrderHandler = nil + txs, err := NewSmartContractResultPreprocessor(args) assert.Nil(t, txs) assert.Equal(t, process.ErrNilTxExecutionOrderHandler, err) } @@ -536,25 +301,8 @@ func TestScrsPreProcessor_GetTransactionFromPool(t *testing.T) { t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, _ := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + txs, _ := NewSmartContractResultPreprocessor(args) txHash := []byte("tx1_hash") tx, _ := process.GetTransactionHandlerFromPool( @@ -573,26 +321,8 @@ func TestScrsPreprocessor_RequestTransactionNothingToRequestAsGeneratedAtProcess t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - shardCoord := mock.NewMultiShardsCoordinatorMock(3) - txs, _ := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - shardCoord, - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + txs, _ := NewSmartContractResultPreprocessor(args) shardId := uint32(1) txHash1 := []byte("tx_hash1") @@ -601,7 +331,7 @@ func TestScrsPreprocessor_RequestTransactionNothingToRequestAsGeneratedAtProcess txHashes := make([][]byte, 0) txHashes = append(txHashes, txHash1) txHashes = append(txHashes, txHash2) - mBlk := block.MiniBlock{SenderShardID: shardCoord.SelfId(), ReceiverShardID: shardId, TxHashes: txHashes, Type: block.SmartContractResultBlock} + mBlk := block.MiniBlock{SenderShardID: args.ShardCoordinator.SelfId(), ReceiverShardID: shardId, TxHashes: txHashes, Type: block.SmartContractResultBlock} body.MiniBlocks = append(body.MiniBlocks, &mBlk) txsRequested := txs.RequestBlockTransactions(body) @@ -613,26 +343,8 @@ func TestScrsPreprocessor_RequestTransactionFromNetwork(t *testing.T) { t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - shardCoord := mock.NewMultiShardsCoordinatorMock(3) - txs, _ := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - shardCoord, - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + txs, _ := NewSmartContractResultPreprocessor(args) shardId := uint32(1) txHash1 := []byte("tx_hash1") @@ -641,7 +353,7 @@ func TestScrsPreprocessor_RequestTransactionFromNetwork(t *testing.T) { txHashes := make([][]byte, 0) txHashes = append(txHashes, txHash1) txHashes = append(txHashes, txHash2) - mBlk := block.MiniBlock{SenderShardID: shardCoord.SelfId() + 1, ReceiverShardID: shardId, TxHashes: txHashes, Type: block.SmartContractResultBlock} + mBlk := block.MiniBlock{SenderShardID: args.ShardCoordinator.SelfId() + 1, ReceiverShardID: shardId, TxHashes: txHashes, Type: block.SmartContractResultBlock} body.MiniBlocks = append(body.MiniBlocks, &mBlk) txsRequested := txs.RequestBlockTransactions(body) @@ -653,25 +365,8 @@ func TestScrsPreprocessor_RequestBlockTransactionFromMiniBlockFromNetwork(t *tes t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, _ := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + txs, _ := NewSmartContractResultPreprocessor(args) shardId := uint32(1) txHash1 := []byte("tx_hash1") @@ -681,9 +376,9 @@ func TestScrsPreprocessor_RequestBlockTransactionFromMiniBlockFromNetwork(t *tes txHashes = append(txHashes, txHash2) mb := &block.MiniBlock{ReceiverShardID: shardId, TxHashes: txHashes, Type: block.SmartContractResultBlock} - txsRequested := txs.RequestTransactionsForMiniBlock(mb) - + txsInstances, txsRequested := txs.GetTransactionsAndRequestMissingForMiniBlock(mb) assert.Equal(t, 2, txsRequested) + assert.Len(t, txsInstances, 0) } func TestScrsPreprocessor_ReceivedTransactionShouldEraseRequested(t *testing.T) { @@ -703,25 +398,8 @@ func TestScrsPreprocessor_ReceivedTransactionShouldEraseRequested(t *testing.T) dataPool.SetUnsignedTransactions(shardedDataStub) - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, _ := NewSmartContractResultPreprocessor( - dataPool.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(dataPool) + txs, _ := NewSmartContractResultPreprocessor(args) // add 3 tx hashes on requested list txHash1 := []byte("tx hash 1") @@ -779,25 +457,8 @@ func TestScrsPreprocessor_GetAllTxsFromMiniBlockShouldWork(t *testing.T) { process.ShardCacherIdentifier(3, 4), ) - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, _ := NewSmartContractResultPreprocessor( - dataPool.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(dataPool) + txs, _ := NewSmartContractResultPreprocessor(args) mb := &block.MiniBlock{ SenderShardID: senderShardId, @@ -868,25 +529,8 @@ func TestScrsPreprocessor_GetAllTxsFromMiniBlockShouldWorkEvenIfScrIsMisplaced(t process.ShardCacherIdentifier(3, 4), ) - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, _ := NewSmartContractResultPreprocessor( - dataPool.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(dataPool) + txs, _ := NewSmartContractResultPreprocessor(args) mb := &block.MiniBlock{ SenderShardID: senderShardId, @@ -913,25 +557,8 @@ func TestScrsPreprocessor_RemoveBlockDataFromPoolsNilBlockShouldErr(t *testing.T t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, _ := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + txs, _ := NewSmartContractResultPreprocessor(args) err := txs.RemoveBlockDataFromPools(nil, tdp.MiniBlocks()) @@ -943,25 +570,8 @@ func TestScrsPreprocessor_RemoveBlockDataFromPoolsOK(t *testing.T) { t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, _ := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + txs, _ := NewSmartContractResultPreprocessor(args) body := &block.Body{} txHash := []byte("txHash") @@ -985,28 +595,12 @@ func TestScrsPreprocessor_IsDataPreparedErr(t *testing.T) { t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - - txs, _ := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + txs, _ := NewSmartContractResultPreprocessor(args) - err := txs.IsDataPrepared(1, haveTime) + scrHashesMissing := [][]byte{[]byte("missing_scr_hash")} + txs.SetMissingScr(len(scrHashesMissing)) + err := txs.IsDataPrepared(len(scrHashesMissing), haveTime) assert.Equal(t, process.ErrTimeIsOut, err) } @@ -1015,30 +609,13 @@ func TestScrsPreprocessor_IsDataPrepared(t *testing.T) { t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - - txs, _ := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + txs, _ := NewSmartContractResultPreprocessor(args) go func() { time.Sleep(50 * time.Millisecond) - txs.chRcvAllScrs <- true + tfb := txs.scrForBlock.(*txsForBlock) + tfb.chRcvAllTxs <- true }() err := txs.IsDataPrepared(1, haveTime) @@ -1050,26 +627,8 @@ func TestScrsPreprocessor_SaveTxsToStorage(t *testing.T) { t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - - txs, _ := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + txs, _ := NewSmartContractResultPreprocessor(args) body := &block.Body{} @@ -1093,7 +652,6 @@ func TestScrsPreprocessor_SaveTxsToStorageShouldSaveCorrectly(t *testing.T) { t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} txHashes := [][]byte{[]byte("txHash1"), []byte("txHash2"), []byte("txHash3"), []byte("txHash4"), []byte("txHash5")} storedTxs := make(map[string]*smartContractResult.SmartContractResult) @@ -1110,35 +668,23 @@ func TestScrsPreprocessor_SaveTxsToStorageShouldSaveCorrectly(t *testing.T) { }, } - txs, _ := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - chainStorer, - &hashingMocks.HasherMock{}, - marshaller, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + args.Store = chainStorer + txs, _ := NewSmartContractResultPreprocessor(args) body := &block.Body{} - txs.scrForBlock.mutTxsForBlock.Lock() + + scrForBlock := txs.scrForBlock.(*txsForBlock) + + scrForBlock.mutTxsForBlock.Lock() for _, hash := range txHashes { - txs.scrForBlock.txHashAndInfo[string(hash)] = &txInfo{ - tx: &smartContractResult.SmartContractResult{ + scrForBlock.txHashAndInfo[string(hash)] = &process.TxInfo{ + Tx: &smartContractResult.SmartContractResult{ Data: hash, }, } } - txs.scrForBlock.mutTxsForBlock.Unlock() + scrForBlock.mutTxsForBlock.Unlock() mb1 := &block.MiniBlock{ ReceiverShardID: 0, @@ -1189,25 +735,8 @@ func TestScrsPreprocessor_SaveTxsToStorageMissingTransactionsShouldNotErr(t *tes t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txs, _ := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + txs, _ := NewSmartContractResultPreprocessor(args) body := &block.Body{} @@ -1233,30 +762,14 @@ func TestScrsPreprocessor_ProcessBlockTransactionsShouldWork(t *testing.T) { t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - scrPreproc, _ := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{ - ProcessSmartContractResultCalled: func(scr *smartContractResult.SmartContractResult) (vmcommon.ReturnCode, error) { - return 0, nil - }, - }, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + args.ScrProcessor = &testscommon.TxProcessorMock{ + ProcessSmartContractResultCalled: func(scr *smartContractResult.SmartContractResult) (vmcommon.ReturnCode, error) { + return 0, nil + }, + } + scrPreproc, _ := NewSmartContractResultPreprocessor(args) body := &block.Body{} txHash := []byte("txHash") @@ -1270,18 +783,16 @@ func TestScrsPreprocessor_ProcessBlockTransactionsShouldWork(t *testing.T) { Type: block.SmartContractResultBlock, } - miniblockHash, _ := core.CalculateHash(scrPreproc.marshalizer, scrPreproc.hasher, &miniblock) + miniblockHash, _ := core.CalculateHash(args.Marshalizer, args.Hasher, &miniblock) body.MiniBlocks = append(body.MiniBlocks, &miniblock) scrPreproc.AddScrHashToRequestedList([]byte("txHash")) - txshardInfo := txShardInfo{0, 0} scr := smartContractResult.SmartContractResult{ Nonce: 1, Data: []byte("tx"), } - - scrPreproc.scrForBlock.txHashAndInfo["txHash"] = &txInfo{&scr, &txshardInfo} + scrPreproc.scrForBlock.AddTransaction([]byte("txHash"), &scr, 0, 0) err := scrPreproc.ProcessBlockTransactions(&block.Header{MiniBlockHeaders: []block.MiniBlockHeader{{TxCount: 1, Hash: miniblockHash}}}, body, haveTimeTrue) @@ -1293,34 +804,20 @@ func TestScrsPreprocessor_ProcessBlockTransactionsMissingTrieNode(t *testing.T) missingNodeErr := fmt.Errorf(core.GetNodeFromDBErrorString) tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - scrPreproc, _ := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{ - ProcessSmartContractResultCalled: func(_ *smartContractResult.SmartContractResult) (vmcommon.ReturnCode, error) { - return 0, nil - }, - }, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{ - GetExistingAccountCalled: func(_ []byte) (vmcommon.AccountHandler, error) { - return nil, missingNodeErr - }, + args := createDefaultSmartContractProcessorArgs(tdp) + + args.ScrProcessor = &testscommon.TxProcessorMock{ + ProcessSmartContractResultCalled: func(_ *smartContractResult.SmartContractResult) (vmcommon.ReturnCode, error) { + return 0, nil }, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + } + args.Accounts = &stateMock.AccountsStub{ + GetExistingAccountCalled: func(_ []byte) (vmcommon.AccountHandler, error) { + return nil, missingNodeErr + }, + } + scrPreproc, _ := NewSmartContractResultPreprocessor(args) body := &block.Body{} txHash := []byte("txHash") @@ -1339,13 +836,11 @@ func TestScrsPreprocessor_ProcessBlockTransactionsMissingTrieNode(t *testing.T) body.MiniBlocks = append(body.MiniBlocks, &miniblock) scrPreproc.AddScrHashToRequestedList([]byte("txHash")) - txshardInfo := txShardInfo{0, 0} scr := smartContractResult.SmartContractResult{ Nonce: 1, Data: []byte("tx"), } - - scrPreproc.scrForBlock.txHashAndInfo["txHash"] = &txInfo{&scr, &txshardInfo} + scrPreproc.scrForBlock.AddTransaction([]byte("txHash"), &scr, 0, 0) err := scrPreproc.ProcessBlockTransactions(&block.Header{MiniBlockHeaders: []block.MiniBlockHeader{{TxCount: 1, Hash: miniblockHash}}}, body, haveTimeTrue) assert.Equal(t, missingNodeErr, err) @@ -1356,39 +851,26 @@ func TestScrsPreprocessor_ProcessBlockTransactionsShouldErrMaxGasLimitPerBlockIn calledCount := 0 enableEpochsHandlerStub := enableEpochsHandlerMock.NewEnableEpochsHandlerStub() - enableEpochsHandler := enableEpochsHandlerStub tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - scrPreproc, _ := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{ - ProcessSmartContractResultCalled: func(scr *smartContractResult.SmartContractResult) (vmcommon.ReturnCode, error) { - return 0, nil - }, + + args := createDefaultSmartContractProcessorArgs(tdp) + args.ScrProcessor = &testscommon.TxProcessorMock{ + ProcessSmartContractResultCalled: func(scr *smartContractResult.SmartContractResult) (vmcommon.ReturnCode, error) { + return 0, nil }, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &mock.GasHandlerMock{ - ComputeGasProvidedByTxCalled: func(txSenderShardId uint32, txReceiverSharedId uint32, txHandler data.TransactionHandler) (uint64, uint64, error) { - return 0, MaxGasLimitPerBlock + 1, nil - }, + } + args.GasHandler = &mock.GasHandlerMock{ + ComputeGasProvidedByTxCalled: func(txSenderShardId uint32, txReceiverSharedId uint32, txHandler data.TransactionHandler) (uint64, uint64, error) { + return 0, MaxGasLimitPerBlock + 1, nil }, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandler, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{ - AddCalled: func(txHash []byte) { - calledCount++ - }, + } + args.TxExecutionOrderHandler = &commonTests.TxExecutionOrderHandlerStub{ + AddCalled: func(txHash []byte) { + calledCount++ }, - ) + } + args.EnableEpochsHandler = enableEpochsHandlerStub + scrPreproc, _ := NewSmartContractResultPreprocessor(args) body := &block.Body{} @@ -1407,13 +889,12 @@ func TestScrsPreprocessor_ProcessBlockTransactionsShouldErrMaxGasLimitPerBlockIn body.MiniBlocks = append(body.MiniBlocks, &miniblock) scrPreproc.AddScrHashToRequestedList([]byte("txHash")) - txshardInfo := txShardInfo{0, 0} scr := smartContractResult.SmartContractResult{ Nonce: 1, Data: []byte("tx"), } - scrPreproc.scrForBlock.txHashAndInfo["txHash"] = &txInfo{&scr, &txshardInfo} + scrPreproc.scrForBlock.AddTransaction([]byte("txHash"), &scr, 0, 0) err := scrPreproc.ProcessBlockTransactions(&block.Header{MiniBlockHeaders: []block.MiniBlockHeader{{Hash: miniblockHash, TxCount: 1}}}, body, haveTimeTrue) assert.Nil(t, err) @@ -1444,30 +925,13 @@ func TestScrsPreprocessor_ProcessMiniBlock(t *testing.T) { } } - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - - scr, _ := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{ - ProcessSmartContractResultCalled: func(scr *smartContractResult.SmartContractResult) (vmcommon.ReturnCode, error) { - return 0, nil - }, + args := createDefaultSmartContractProcessorArgs(tdp) + args.ScrProcessor = &testscommon.TxProcessorMock{ + ProcessSmartContractResultCalled: func(scr *smartContractResult.SmartContractResult) (vmcommon.ReturnCode, error) { + return 0, nil }, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + } + scr, _ := NewSmartContractResultPreprocessor(args) txHash := []byte("tx1_hash") txHashes := make([][]byte, 0) @@ -1493,26 +957,8 @@ func TestScrsPreprocessor_ProcessMiniBlockWrongTypeMiniblockShouldErr(t *testing t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - - scr, _ := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + scr, _ := NewSmartContractResultPreprocessor(args) miniblock := block.MiniBlock{ ReceiverShardID: 0, @@ -1557,26 +1003,10 @@ func TestScrsPreprocessor_RestoreBlockDataIntoPools(t *testing.T) { shardedDataStub := &testscommon.ShardedDataStub{} dataPool.SetUnsignedTransactions(shardedDataStub) - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - scr, _ := NewSmartContractResultPreprocessor( - dataPool.UnsignedTransactions(), - &scrstorage, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(dataPool) + args.Store = &scrstorage + scr, _ := NewSmartContractResultPreprocessor(args) body := &block.Body{} @@ -1602,26 +1032,8 @@ func TestScrsPreprocessor_RestoreBlockDataIntoPoolsNilMiniblockPoolShouldErr(t * t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - - scr, _ := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + scr, _ := NewSmartContractResultPreprocessor(args) body := &block.Body{} @@ -1637,63 +1049,79 @@ func TestSmartContractResults_CreateBlockStartedShouldEmptyTxHashAndInfo(t *test t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - - scr, _ := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + scr, _ := NewSmartContractResultPreprocessor(args) scr.CreateBlockStarted() - assert.Equal(t, 0, len(scr.scrForBlock.txHashAndInfo)) + scrForBlock := scr.scrForBlock.(*txsForBlock) + assert.Equal(t, 0, len(scrForBlock.txHashAndInfo)) } func TestSmartContractResults_GetAllCurrentUsedTxs(t *testing.T) { t.Parallel() tdp := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - - scrPreproc, _ := NewSmartContractResultPreprocessor( - tdp.UnsignedTransactions(), - &storageStubs.ChainStorerStub{}, - &hashingMocks.HasherMock{}, - &mock.MarshalizerMock{}, - &testscommon.TxProcessorMock{}, - mock.NewMultiShardsCoordinatorMock(3), - &stateMock.AccountsStub{}, - requestTransaction, - &testscommon.GasHandlerStub{}, - feeHandlerMock(), - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonTests.TxExecutionOrderHandlerStub{}, - ) + args := createDefaultSmartContractProcessorArgs(tdp) + scrPreproc, _ := NewSmartContractResultPreprocessor(args) - txshardInfo := txShardInfo{0, 3} scr := smartContractResult.SmartContractResult{ Nonce: 1, Data: []byte("tx"), } - scrPreproc.scrForBlock.txHashAndInfo["txHash"] = &txInfo{&scr, &txshardInfo} + scrPreproc.scrForBlock.AddTransaction([]byte("txHash"), &scr, 0, 3) retMap := scrPreproc.GetAllCurrentUsedTxs() assert.NotNil(t, retMap) } + +func createDefaultSmartContractProcessorArgs(tdp dataRetriever.PoolsHolder) SmartContractResultsArgs { + requestTransaction := func(shardID uint32, txHashes [][]byte) {} + return SmartContractResultsArgs{ + BasePreProcessorArgs: BasePreProcessorArgs{ + DataPool: tdp.UnsignedTransactions(), + Store: &storageStubs.ChainStorerStub{}, + Hasher: &hashingMocks.HasherMock{}, + Marshalizer: &mock.MarshalizerMock{}, + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), + Accounts: &stateMock.AccountsStub{}, + AccountsProposal: &stateMock.AccountsStub{}, + OnRequestTransaction: requestTransaction, + GasHandler: &testscommon.GasHandlerStub{}, + PubkeyConverter: createMockPubkeyConverter(), + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &commonTests.TxExecutionOrderHandlerStub{}, + EconomicsFee: feeHandlerMock(), + EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + }, + ScrProcessor: &testscommon.TxProcessorMock{}, + } +} + +func TestSmartContractResults_GetCreatedMiniBlocksFromMe(t *testing.T) { + t.Parallel() + + tdp := initDataPool() + args := createDefaultSmartContractProcessorArgs(tdp) + scrPreproc, _ := NewSmartContractResultPreprocessor(args) + + // always returns empty + createdMbs := scrPreproc.GetCreatedMiniBlocksFromMe() + assert.Len(t, createdMbs, 0) +} + +func TestSmartContractResult_GetUnExecutableTransactions(t *testing.T) { + t.Parallel() + + tdp := initDataPool() + args := createDefaultSmartContractProcessorArgs(tdp) + scrPreproc, _ := NewSmartContractResultPreprocessor(args) + + // always returns empty + unexecTxs := scrPreproc.GetUnExecutableTransactions() + assert.Len(t, unexecTxs, 0) +} diff --git a/process/block/preprocess/sortedTransactionsProvider.go b/process/block/preprocess/sortedTransactionsProvider.go deleted file mode 100644 index f30cb912892..00000000000 --- a/process/block/preprocess/sortedTransactionsProvider.go +++ /dev/null @@ -1,57 +0,0 @@ -package preprocess - -import ( - "github.com/multiversx/mx-chain-go/process" - "github.com/multiversx/mx-chain-go/storage" - "github.com/multiversx/mx-chain-go/storage/txcache" -) - -// TODO: Refactor "transactions.go" to not require the components in this file anymore -// createSortedTransactionsProvider is a "simple factory" for "SortedTransactionsProvider" objects -func createSortedTransactionsProvider(cache storage.Cacher) SortedTransactionsProvider { - txCache, isTxCache := cache.(TxCache) - if isTxCache { - return newAdapterTxCacheToSortedTransactionsProvider(txCache) - } - - log.Error("Could not create a real [SortedTransactionsProvider], will create a disabled one") - return &disabledSortedTransactionsProvider{} -} - -// adapterTxCacheToSortedTransactionsProvider adapts a "TxCache" to the "SortedTransactionsProvider" interface -type adapterTxCacheToSortedTransactionsProvider struct { - txCache TxCache -} - -func newAdapterTxCacheToSortedTransactionsProvider(txCache TxCache) *adapterTxCacheToSortedTransactionsProvider { - adapter := &adapterTxCacheToSortedTransactionsProvider{ - txCache: txCache, - } - - return adapter -} - -// GetSortedTransactions gets the transactions from the cache -func (adapter *adapterTxCacheToSortedTransactionsProvider) GetSortedTransactions(session txcache.SelectionSession) []*txcache.WrappedTransaction { - txs, _ := adapter.txCache.SelectTransactions(session, process.TxCacheSelectionGasRequested, process.TxCacheSelectionMaxNumTxs, process.TxCacheSelectionLoopMaximumDuration) - return txs -} - -// IsInterfaceNil returns true if there is no value under the interface -func (adapter *adapterTxCacheToSortedTransactionsProvider) IsInterfaceNil() bool { - return adapter == nil -} - -// disabledSortedTransactionsProvider is a disabled "SortedTransactionsProvider" (should never be used in practice) -type disabledSortedTransactionsProvider struct { -} - -// GetSortedTransactions returns an empty slice -func (adapter *disabledSortedTransactionsProvider) GetSortedTransactions(_ txcache.SelectionSession) []*txcache.WrappedTransaction { - return make([]*txcache.WrappedTransaction, 0) -} - -// IsInterfaceNil returns true if there is no value under the interface -func (adapter *disabledSortedTransactionsProvider) IsInterfaceNil() bool { - return adapter == nil -} diff --git a/process/block/preprocess/transactions.go b/process/block/preprocess/transactions.go index 2f3fc290ef6..b13a2bd62ae 100644 --- a/process/block/preprocess/transactions.go +++ b/process/block/preprocess/transactions.go @@ -13,18 +13,18 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/transaction" - "github.com/multiversx/mx-chain-core-go/hashing" - "github.com/multiversx/mx-chain-core-go/marshal" logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/common/holders" + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/txcache" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/block/helpers" - "github.com/multiversx/mx-chain-go/sharding" "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/storage" - "github.com/multiversx/mx-chain-go/storage/txcache" ) var _ process.DataMarshalizer = (*transactions)(nil) @@ -32,19 +32,24 @@ var _ process.PreProcessor = (*transactions)(nil) var log = logger.GetOrCreate("process/block/preprocess") -// 200% bandwidth to allow 100% overshooting estimations -const selectionGasBandwidthIncreasePercent = 400 - -// 130% to allow 30% overshooting estimations for scheduled SC calls -const selectionGasBandwidthIncreaseScheduledPercent = 260 - // TODO: increase code coverage with unit test +// ArgsTransactionPreProcessor holds the arguments to create a txs pre processor +type ArgsTransactionPreProcessor struct { + BasePreProcessorArgs + TxProcessor process.TransactionProcessor + BlockTracker BlockTracker + BlockType block.Type + TxTypeHandler process.TxTypeHandler + ScheduledTxsExecutionHandler process.ScheduledTxsExecutionHandler + TxCacheSelectionConfig config.TxCacheSelectionConfig + TxVersionCheckerHandler process.TxVersionCheckerHandler +} + type transactions struct { *basePreProcess - chRcvAllTxs chan bool onRequestTransaction func(shardID uint32, txHashes [][]byte) - txsForCurrBlock txsForBlock + txsForCurrBlock TxsForBlockHandler txPool dataRetriever.ShardedDataCacherNotifier storage dataRetriever.StorageService txProcessor process.TransactionProcessor @@ -56,82 +61,32 @@ type transactions struct { emptyAddress []byte txTypeHandler process.TxTypeHandler scheduledTxsExecutionHandler process.ScheduledTxsExecutionHandler -} + txCacheSelectionConfig config.TxCacheSelectionConfig + txVersionCheckerHandler process.TxVersionCheckerHandler -// ArgsTransactionPreProcessor holds the arguments to create a txs pre processor -type ArgsTransactionPreProcessor struct { - TxDataPool dataRetriever.ShardedDataCacherNotifier - Store dataRetriever.StorageService - Hasher hashing.Hasher - Marshalizer marshal.Marshalizer - TxProcessor process.TransactionProcessor - ShardCoordinator sharding.Coordinator - Accounts state.AccountsAdapter - OnRequestTransaction func(shardID uint32, txHashes [][]byte) - EconomicsFee process.FeeHandler - GasHandler process.GasHandler - BlockTracker BlockTracker - BlockType block.Type - PubkeyConverter core.PubkeyConverter - BlockSizeComputation BlockSizeComputationHandler - BalanceComputation BalanceComputationHandler - EnableEpochsHandler common.EnableEpochsHandler - TxTypeHandler process.TxTypeHandler - ScheduledTxsExecutionHandler process.ScheduledTxsExecutionHandler - ProcessedMiniBlocksTracker process.ProcessedMiniBlocksTracker - TxExecutionOrderHandler common.TxExecutionOrderHandler + unExecutableTransactions map[string]struct{} + mutUnExecutableTxs sync.RWMutex + + createdMiniBlocks block.MiniBlockSlice + mutCreatedMiniBlocks sync.RWMutex } // NewTransactionPreprocessor creates a new transaction preprocessor object func NewTransactionPreprocessor( args ArgsTransactionPreProcessor, ) (*transactions, error) { - if check.IfNil(args.Hasher) { - return nil, process.ErrNilHasher - } - if check.IfNil(args.Marshalizer) { - return nil, process.ErrNilMarshalizer - } - if check.IfNil(args.TxDataPool) { - return nil, process.ErrNilTransactionPool - } - if check.IfNil(args.Store) { - return nil, process.ErrNilTxStorage + err := checkBasePreProcessArgs(args.BasePreProcessorArgs) + if err != nil { + return nil, err } if check.IfNil(args.TxProcessor) { return nil, process.ErrNilTxProcessor } - if check.IfNil(args.ShardCoordinator) { - return nil, process.ErrNilShardCoordinator - } - if check.IfNil(args.Accounts) { - return nil, process.ErrNilAccountsAdapter - } - if args.OnRequestTransaction == nil { - return nil, process.ErrNilRequestHandler - } - if check.IfNil(args.EconomicsFee) { - return nil, process.ErrNilEconomicsFeeHandler - } - if check.IfNil(args.GasHandler) { - return nil, process.ErrNilGasHandler - } if check.IfNil(args.BlockTracker) { return nil, process.ErrNilBlockTracker } - if check.IfNil(args.PubkeyConverter) { - return nil, process.ErrNilPubkeyConverter - } - if check.IfNil(args.BlockSizeComputation) { - return nil, process.ErrNilBlockSizeComputationHandler - } - if check.IfNil(args.BalanceComputation) { - return nil, process.ErrNilBalanceComputationHandler - } - if check.IfNil(args.EnableEpochsHandler) { - return nil, process.ErrNilEnableEpochsHandler - } - err := core.CheckHandlerCompatibility(args.EnableEpochsHandler, []core.EnableEpochFlag{ + + err = core.CheckHandlerCompatibility(args.EnableEpochsHandler, []core.EnableEpochFlag{ common.OptimizeGasUsedInCrossMiniBlocksFlag, common.ScheduledMiniBlocksFlag, common.FrontRunningProtectionFlag, @@ -147,73 +102,96 @@ func NewTransactionPreprocessor( if check.IfNil(args.ScheduledTxsExecutionHandler) { return nil, process.ErrNilScheduledTxsExecutionHandler } - if check.IfNil(args.ProcessedMiniBlocksTracker) { - return nil, process.ErrNilProcessedMiniBlocksTracker + if args.TxCacheSelectionConfig.SelectionGasBandwidthIncreasePercent == 0 { + return nil, process.ErrBadSelectionGasBandwidthIncreasePercent + } + if args.TxCacheSelectionConfig.SelectionGasBandwidthIncreaseScheduledPercent == 0 { + return nil, process.ErrBadSelectionGasBandwidthIncreaseScheduledPercent + } + if args.TxCacheSelectionConfig.SelectionMaxNumTxs == 0 { + return nil, process.ErrBadTxCacheSelectionMaxNumTxs } - if check.IfNil(args.TxExecutionOrderHandler) { - return nil, process.ErrNilTxExecutionOrderHandler + if args.TxCacheSelectionConfig.SelectionGasRequested == 0 { + return nil, process.ErrBadTxCacheSelectionGasRequested + } + if args.TxCacheSelectionConfig.SelectionLoopDurationCheckInterval == 0 { + return nil, process.ErrBadTxCacheSelectionLoopDurationCheckInterval + } + if check.IfNil(args.TxVersionCheckerHandler) { + return nil, process.ErrNilTransactionVersionChecker + } + + ges, err := newGasEpochState( + args.EconomicsFee, + args.EnableEpochsHandler, + args.EnableRoundsHandler, + ) + if err != nil { + return nil, err } - bpp := basePreProcess{ + bpp := &basePreProcess{ hasher: args.Hasher, marshalizer: args.Marshalizer, - gasTracker: gasTracker{ - shardCoordinator: args.ShardCoordinator, - gasHandler: args.GasHandler, - economicsFee: args.EconomicsFee, - }, + gasTracker: newGasTracker( + args.ShardCoordinator, + args.GasHandler, + args.EconomicsFee, + ges, + ), blockSizeComputation: args.BlockSizeComputation, balanceComputation: args.BalanceComputation, accounts: args.Accounts, + accountsProposal: args.AccountsProposal, pubkeyConverter: args.PubkeyConverter, enableEpochsHandler: args.EnableEpochsHandler, + enableRoundsHandler: args.EnableRoundsHandler, processedMiniBlocksTracker: args.ProcessedMiniBlocksTracker, txExecutionOrderHandler: args.TxExecutionOrderHandler, + feeHandler: args.EconomicsFee, } + args.EpochNotifier.RegisterNotifyHandler(bpp) + args.RoundNotifier.RegisterNotifyHandler(bpp) + txs := &transactions{ - basePreProcess: &bpp, + basePreProcess: bpp, storage: args.Store, - txPool: args.TxDataPool, + txPool: args.DataPool, onRequestTransaction: args.OnRequestTransaction, txProcessor: args.TxProcessor, blockTracker: args.BlockTracker, blockType: args.BlockType, txTypeHandler: args.TxTypeHandler, scheduledTxsExecutionHandler: args.ScheduledTxsExecutionHandler, + txCacheSelectionConfig: args.TxCacheSelectionConfig, + txVersionCheckerHandler: args.TxVersionCheckerHandler, } - txs.chRcvAllTxs = make(chan bool) txs.txPool.RegisterOnAdded(txs.receivedTransaction) - txs.txsForCurrBlock.txHashAndInfo = make(map[string]*txInfo) + txs.txsForCurrBlock, err = NewTxsForBlock(args.ShardCoordinator) + if err != nil { + return nil, err + } txs.orderedTxs = make(map[string][]data.TransactionHandler) txs.orderedTxHashes = make(map[string][][]byte) txs.emptyAddress = make([]byte, txs.pubkeyConverter.Len()) - return txs, nil -} + txs.createdMiniBlocks = make(block.MiniBlockSlice, 0) + txs.unExecutableTransactions = make(map[string]struct{}) -// waitForTxHashes waits for a call whether all the requested transactions appeared -func (txs *transactions) waitForTxHashes(waitTime time.Duration) error { - select { - case <-txs.chRcvAllTxs: - return nil - case <-time.After(waitTime): - return process.ErrTimeIsOut - } + return txs, nil } // IsDataPrepared returns non error if all the requested transactions arrived and were saved into the pool func (txs *transactions) IsDataPrepared(requestedTxs int, haveTime func() time.Duration) error { if requestedTxs > 0 { log.Debug("requested missing txs", "num txs", requestedTxs) - err := txs.waitForTxHashes(haveTime()) - txs.txsForCurrBlock.mutTxsForBlock.Lock() - missingTxs := txs.txsForCurrBlock.missingTxs - txs.txsForCurrBlock.missingTxs = 0 - txs.txsForCurrBlock.mutTxsForBlock.Unlock() + err := txs.txsForCurrBlock.WaitForRequestedData(haveTime()) + missingTxs := txs.txsForCurrBlock.GetMissingTxsCount() + // TODO: previously the number of missing txs was cleared in txsForCurrentBlock - check if this is still needed log.Debug("received missing txs", "num txs", requestedTxs-missingTxs, "requested", requestedTxs, "missing", missingTxs) if err != nil { return err @@ -229,8 +207,27 @@ func (txs *transactions) RemoveBlockDataFromPools(body *block.Body, miniBlockPoo } // RemoveTxsFromPools removes transactions from associated pools -func (txs *transactions) RemoveTxsFromPools(body *block.Body) error { - return txs.removeTxsFromPools(body, txs.txPool, txs.isMiniBlockCorrect) +// TODO CleanupSelfShardTxCache - Maybe find a solution to use block nonce instead of randomness +func (txs *transactions) RemoveTxsFromPools(body *block.Body, rootHashHolder common.RootHashHolder) error { + err := txs.accountsProposal.RecreateTrieIfNeeded(rootHashHolder) + if err != nil { + return err + } + + accountsProvider, err := state.NewAccountsEphemeralProvider(txs.accountsProposal) + if err != nil { + return err + } + + err = txs.removeTxsFromPools(body, txs.txPool, txs.isMiniBlockCorrect) + if err != nil { + return err + } + + randomness := helpers.ComputeRandomnessForCleanup(body) + txs.txPool.CleanupSelfShardTxCache(accountsProvider, randomness, process.TxCacheCleanupMaxNumTxs, process.TxCacheCleanupLoopMaximumDuration) + + return err } // RestoreBlockDataIntoPools restores the transactions and miniblocks to associated pools @@ -334,6 +331,28 @@ func (txs *transactions) ProcessBlockTransactions( return process.ErrInvalidBody } +// GetCreatedMiniBlocksFromMe returns the created mini blocks from me +func (txs *transactions) GetCreatedMiniBlocksFromMe() block.MiniBlockSlice { + txs.mutCreatedMiniBlocks.RLock() + createdMiniBlocks := make(block.MiniBlockSlice, len(txs.createdMiniBlocks)) + copy(createdMiniBlocks, txs.createdMiniBlocks) + txs.mutCreatedMiniBlocks.RUnlock() + + return createdMiniBlocks +} + +// GetUnExecutableTransactions returns the un-executable transactions +func (txs *transactions) GetUnExecutableTransactions() map[string]struct{} { + txs.mutUnExecutableTxs.RLock() + unExecutableTransactions := make(map[string]struct{}, len(txs.unExecutableTransactions)) + for k, v := range txs.unExecutableTransactions { + unExecutableTransactions[k] = v + } + txs.mutUnExecutableTxs.RUnlock() + + return unExecutableTransactions +} + func (txs *transactions) computeTxsToMe( headerHandler data.HeaderHandler, body *block.Body, @@ -356,7 +375,7 @@ func (txs *transactions) computeTxsToMe( miniBlock.ReceiverShardID) } - pi, err := txs.getIndexesOfLastTxProcessed(miniBlock, headerHandler) + pi, err := txs.getIndexesOfLastTxProcessedOnExecution(miniBlock, headerHandler) if err != nil { return nil, err } @@ -447,16 +466,13 @@ func (txs *transactions) computeTxsFromMiniBlock( for i := indexOfFirstTxToBeProcessed; i <= pi.indexOfLastTxProcessedByProposer; i++ { txHash := miniBlock.TxHashes[i] - txs.txsForCurrBlock.mutTxsForBlock.RLock() - txInfoFromMap, ok := txs.txsForCurrBlock.txHashAndInfo[string(txHash)] - txs.txsForCurrBlock.mutTxsForBlock.RUnlock() - - if !ok || check.IfNil(txInfoFromMap.tx) { + txInfoFromMap, ok := txs.txsForCurrBlock.GetTxInfoByHash(txHash) + if !ok || check.IfNil(txInfoFromMap.Tx) { log.Warn("missing transaction in computeTxsFromMiniBlock", "type", miniBlock.Type, "txHash", txHash) return nil, process.ErrMissingTransaction } - tx, ok := txInfoFromMap.tx.(*transaction.Transaction) + tx, ok := txInfoFromMap.Tx.(*transaction.Transaction) if !ok { return nil, process.ErrWrongTypeAssertion } @@ -500,7 +516,8 @@ func (txs *transactions) processTxsToMe( var err error scheduledMode := false - if txs.enableEpochsHandler.IsFlagEnabled(common.ScheduledMiniBlocksFlag) { + isAsyncExecEnabled := header.IsHeaderV3() + if txs.enableEpochsHandler.IsFlagEnabled(common.ScheduledMiniBlocksFlag) && !isAsyncExecEnabled { scheduledMode, err = process.IsScheduledMode(header, body, txs.hasher, txs.marshalizer) if err != nil { return err @@ -572,7 +589,8 @@ func (txs *transactions) processTxsToMe( receiverShardID, tx, txHash, - &gasInfo) + &gasInfo, + isAsyncExecEnabled) if errComputeGas != nil { return errComputeGas @@ -646,6 +664,15 @@ func (txs *transactions) processTxsFromMe( return err } + if common.IsAsyncExecutionEnabled(txs.enableEpochsHandler, txs.enableRoundsHandler) { + // save the calculatedMiniBlocks for later comparison + txs.mutCreatedMiniBlocks.Lock() + txs.createdMiniBlocks = calculatedMiniBlocks + txs.mutCreatedMiniBlocks.Unlock() + + return nil + } + if !haveTime() { return process.ErrTimeIsOut } @@ -665,8 +692,12 @@ func (txs *transactions) processTxsFromMe( calculatedMiniBlocks = append(calculatedMiniBlocks, scheduledMiniBlocks...) + return txs.checkMiniBlocks(calculatedMiniBlocks, body.MiniBlocks) +} + +func (txs *transactions) checkMiniBlocks(calculatedMiniBlocks, bodyMiniBlocks block.MiniBlockSlice) error { receivedMiniBlocks := make(block.MiniBlockSlice, 0) - for _, miniBlock := range body.MiniBlocks { + for _, miniBlock := range bodyMiniBlocks { if miniBlock.Type == block.InvalidBlock { continue } @@ -753,7 +784,7 @@ func (txs *transactions) SaveTxsToStorage(body *block.Body) error { continue } - txs.saveTxsToStorage(miniBlock.TxHashes, &txs.txsForCurrBlock, txs.storage, dataRetriever.TransactionUnit) + txs.saveTxsToStorage(miniBlock.TxHashes, txs.txsForCurrBlock, txs.storage, dataRetriever.TransactionUnit) } return nil @@ -768,27 +799,25 @@ func (txs *transactions) receivedTransaction(key []byte, value interface{}) { return } - receivedAllMissing := txs.baseReceivedTransaction(key, wrappedTx.Tx, &txs.txsForCurrBlock) - - if receivedAllMissing { - txs.chRcvAllTxs <- true - } + txs.baseReceivedTransaction(key, wrappedTx.Tx, txs.txsForCurrBlock) } // CreateBlockStarted cleans the local cache map for processed/created transactions at this round func (txs *transactions) CreateBlockStarted() { - _ = core.EmptyChannel(txs.chRcvAllTxs) - - txs.txsForCurrBlock.mutTxsForBlock.Lock() - txs.txsForCurrBlock.missingTxs = 0 - txs.txsForCurrBlock.txHashAndInfo = make(map[string]*txInfo) - txs.txsForCurrBlock.mutTxsForBlock.Unlock() - + txs.txsForCurrBlock.Reset() txs.mutOrderedTxs.Lock() txs.orderedTxs = make(map[string][]data.TransactionHandler) txs.orderedTxHashes = make(map[string][][]byte) txs.mutOrderedTxs.Unlock() + txs.mutCreatedMiniBlocks.Lock() + txs.createdMiniBlocks = make(block.MiniBlockSlice, 0) + txs.mutCreatedMiniBlocks.Unlock() + + txs.mutUnExecutableTxs.Lock() + txs.unExecutableTransactions = make(map[string]struct{}) + txs.mutUnExecutableTxs.Unlock() + txs.scheduledTxsExecutionHandler.Init() } @@ -804,7 +833,6 @@ func (txs *transactions) AddTxsFromMiniBlocks(miniBlocks block.MiniBlockSlice) { continue } - txShardInfoToSet := &txShardInfo{senderShardID: mb.SenderShardID, receiverShardID: mb.ReceiverShardID} method := process.SearchMethodJustPeek if mb.Type == block.InvalidBlock { method = process.SearchMethodSearchFirst @@ -825,9 +853,7 @@ func (txs *transactions) AddTxsFromMiniBlocks(miniBlocks block.MiniBlockSlice) { continue } - txs.txsForCurrBlock.mutTxsForBlock.Lock() - txs.txsForCurrBlock.txHashAndInfo[string(txHash)] = &txInfo{tx: tx, txShardInfo: txShardInfoToSet} - txs.txsForCurrBlock.mutTxsForBlock.Unlock() + txs.txsForCurrBlock.AddTransaction(txHash, tx, mb.SenderShardID, mb.ReceiverShardID) } } } @@ -837,15 +863,13 @@ func (txs *transactions) AddTransactions(txHandlers []data.TransactionHandler) { for i, tx := range txHandlers { senderShardID := txs.getShardFromAddress(tx.GetSndAddr()) receiverShardID := txs.getShardFromAddress(tx.GetRcvAddr()) - txShardInfoToSet := &txShardInfo{senderShardID: senderShardID, receiverShardID: receiverShardID} txHash, err := core.CalculateHash(txs.marshalizer, txs.hasher, tx) if err != nil { log.Warn("transactions.AddTransactions CalculateHash", "error", err.Error()) continue } - txs.txsForCurrBlock.mutTxsForBlock.Lock() - txs.txsForCurrBlock.txHashAndInfo[string(txHash)] = &txInfo{tx: txHandlers[i], txShardInfo: txShardInfoToSet} - txs.txsForCurrBlock.mutTxsForBlock.Unlock() + + txs.txsForCurrBlock.AddTransaction(txHash, txHandlers[i], senderShardID, receiverShardID) } } @@ -863,8 +887,7 @@ func (txs *transactions) RequestBlockTransactions(body *block.Body) int { func (txs *transactions) computeExistingAndRequestMissingTxsForShards(body *block.Body) int { numMissingTxsForShard := txs.computeExistingAndRequestMissing( body, - &txs.txsForCurrBlock, - txs.chRcvAllTxs, + txs.txsForCurrBlock, txs.isMiniBlockCorrect, txs.txPool, txs.onRequestTransaction, @@ -873,21 +896,29 @@ func (txs *transactions) computeExistingAndRequestMissingTxsForShards(body *bloc return numMissingTxsForShard } -// processAndRemoveBadTransactions processed transactions, if txs are with error it removes them from pool +// processAndRemoveBadTransaction processed transactions, if txs are with error it removes them from pool func (txs *transactions) processAndRemoveBadTransaction( txHash []byte, tx *transaction.Transaction, sndShardId uint32, dstShardId uint32, ) error { - txs.txExecutionOrderHandler.Add(txHash) _, err := txs.txProcessor.ProcessTransaction(tx) - isTxTargetedForDeletion := errors.Is(err, process.ErrLowerNonceInTransaction) || errors.Is(err, process.ErrInsufficientFee) || errors.Is(err, process.ErrTransactionNotExecutable) - if isTxTargetedForDeletion { + + isNotExecutable := errors.Is(err, process.ErrLowerNonceInTransaction) || errors.Is(err, process.ErrInsufficientFee) || errors.Is(err, process.ErrTransactionNotExecutable) + isAsyncExecEnabled := common.IsAsyncExecutionEnabled(txs.enableEpochsHandler, txs.enableRoundsHandler) + if err != nil && isAsyncExecEnabled { + isNotExecutable = process.IsNotExecutableTransactionError(err) + } + if isNotExecutable { txs.txExecutionOrderHandler.Remove(txHash) - strCache := process.ShardCacherIdentifier(sndShardId, dstShardId) - txs.txPool.RemoveData(txHash, strCache) + // TODO: remove log if no longer needed for validation + log.Debug("processAndRemoveBadTransaction - found not executable transaction", "txHash", txHash) + if !isAsyncExecEnabled { + strCache := process.ShardCacherIdentifier(sndShardId, dstShardId) + txs.txPool.RemoveData(txHash, strCache) + } } if err != nil && !errors.Is(err, process.ErrFailedTransaction) { @@ -896,34 +927,32 @@ func (txs *transactions) processAndRemoveBadTransaction( return err } - txShardInfoToSet := &txShardInfo{senderShardID: sndShardId, receiverShardID: dstShardId} - txs.txsForCurrBlock.mutTxsForBlock.Lock() - txs.txsForCurrBlock.txHashAndInfo[string(txHash)] = &txInfo{tx: tx, txShardInfo: txShardInfoToSet} - txs.txsForCurrBlock.mutTxsForBlock.Unlock() + txs.txsForCurrBlock.AddTransaction(txHash, tx, sndShardId, dstShardId) return err } -// RequestTransactionsForMiniBlock requests missing transactions for a certain miniblock -func (txs *transactions) RequestTransactionsForMiniBlock(miniBlock *block.MiniBlock) int { +// GetTransactionsAndRequestMissingForMiniBlock requests missing transactions for a certain miniblock +func (txs *transactions) GetTransactionsAndRequestMissingForMiniBlock(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { if miniBlock == nil { - return 0 + return nil, 0 } - missingTxsHashesForMiniBlock := txs.computeMissingTxsHashesForMiniBlock(miniBlock) + existingTxs, missingTxsHashesForMiniBlock := txs.computeMissingTxsHashesForMiniBlock(miniBlock) if len(missingTxsHashesForMiniBlock) > 0 { txs.onRequestTransaction(miniBlock.SenderShardID, missingTxsHashesForMiniBlock) } - return len(missingTxsHashesForMiniBlock) + return existingTxs, len(missingTxsHashesForMiniBlock) } // computeMissingTxsHashesForMiniBlock computes missing transactions hashes for a certain miniblock -func (txs *transactions) computeMissingTxsHashesForMiniBlock(miniBlock *block.MiniBlock) [][]byte { +func (txs *transactions) computeMissingTxsHashesForMiniBlock(miniBlock *block.MiniBlock) ([]data.TransactionHandler, [][]byte) { missingTransactionsHashes := make([][]byte, 0) + existingTxs := make([]data.TransactionHandler, 0) if miniBlock.Type != txs.blockType { - return missingTransactionsHashes + return existingTxs, missingTransactionsHashes } method := process.SearchMethodJustPeek @@ -941,10 +970,13 @@ func (txs *transactions) computeMissingTxsHashesForMiniBlock(miniBlock *block.Mi if check.IfNil(tx) { missingTransactionsHashes = append(missingTransactionsHashes, txHash) + continue } + + existingTxs = append(existingTxs, tx) } - return missingTransactionsHashes + return existingTxs, missingTransactionsHashes } // getAllTxsFromMiniBlock gets all the transactions from a miniblock into a new structure @@ -1004,20 +1036,57 @@ func (txs *transactions) getRemainingGasPerBlockAsScheduled() uint64 { return gasBandwidth } +// SelectOutgoingTransactions selects outgoing transactions from the transaction pool +func (txs *transactions) SelectOutgoingTransactions( + bandwidth uint64, + nonce uint64, + haveTimeForSelection func() bool, +) ([][]byte, []data.TransactionHandler, error) { + wrappedTxs, err := txs.selectTransactionsFromTxPoolForProposal( + txs.shardCoordinator.SelfId(), + txs.shardCoordinator.SelfId(), + bandwidth, + nonce, + haveTimeForSelection, + ) + if err != nil { + return nil, nil, err + } + + txHashes, txInstances := unwrapTxs(wrappedTxs) + return txHashes, txInstances, nil +} + +func unwrapTxs(wrappedTxs []*txcache.WrappedTransaction) ([][]byte, []data.TransactionHandler) { + txHashes := make([][]byte, 0, len(wrappedTxs)) + txs := make([]data.TransactionHandler, 0, len(wrappedTxs)) + for _, wrappedTx := range wrappedTxs { + txHashes = append(txHashes, wrappedTx.TxHash) + txs = append(txs, wrappedTx.Tx) + } + return txHashes, txs +} + // CreateAndProcessMiniBlocks creates miniBlocks from storage and processes the transactions added into the miniblocks // as long as it has time // TODO: check if possible for transaction pre processor to receive a blockChainHook and use it to get the randomness instead func (txs *transactions) CreateAndProcessMiniBlocks(haveTime func() bool, randomness []byte) (block.MiniBlockSlice, error) { startTime := time.Now() - gasBandwidth := txs.getRemainingGasPerBlock() * selectionGasBandwidthIncreasePercent / 100 + gasBandwidth := txs.getRemainingGasPerBlock() * uint64(txs.txCacheSelectionConfig.SelectionGasBandwidthIncreasePercent) / 100 gasBandwidthForScheduled := uint64(0) if txs.enableEpochsHandler.IsFlagEnabled(common.ScheduledMiniBlocksFlag) { - gasBandwidthForScheduled = txs.getRemainingGasPerBlockAsScheduled() * selectionGasBandwidthIncreaseScheduledPercent / 100 + gasBandwidthForScheduled = txs.getRemainingGasPerBlockAsScheduled() * uint64(txs.txCacheSelectionConfig.SelectionGasBandwidthIncreaseScheduledPercent) / 100 gasBandwidth += gasBandwidthForScheduled } - sortedTxs, remainingTxsForScheduled, err := txs.computeSortedTxs(txs.shardCoordinator.SelfId(), txs.shardCoordinator.SelfId(), gasBandwidth, randomness) + sortedTxs, remainingTxsForScheduled, err := txs.computeSortedTxs( + txs.shardCoordinator.SelfId(), + txs.shardCoordinator.SelfId(), + gasBandwidth, + randomness, + haveTime, + ) elapsedTime := time.Since(startTime) if err != nil { log.Debug("computeSortedTxs", "error", err.Error()) @@ -1053,7 +1122,7 @@ func (txs *transactions) CreateAndProcessMiniBlocks(haveTime func() bool, random miniBlocks, remainingTxs, mapSCTxs, err := txs.createAndProcessMiniBlocksFromMe( haveTime, txs.blockTracker.IsShardStuck, - txs.blockSizeComputation.IsMaxBlockSizeReached, + txs.isMaxBlockSizeReached, sortedTxs, ) elapsedTime = time.Since(startTime) @@ -1103,7 +1172,7 @@ func (txs *transactions) createAndProcessScheduledMiniBlocksFromMeAsProposer( haveTime, haveAdditionalTime, txs.blockTracker.IsShardStuck, - txs.blockSizeComputation.IsMaxBlockSizeReached, + txs.isMaxBlockSizeReached, sortedTxs, mapSCTxs, ) @@ -1136,13 +1205,15 @@ func (txs *transactions) createAndProcessMiniBlocksFromMeV1( gasTracker: txs.gasTracker, accounts: txs.accounts, balanceComputationHandler: txs.balanceComputation, - blockSizeComputation: txs.blockSizeComputation, + blockSizeComputation: txs.blockSizeComputation, // this will use size calculation using methods from base preprocessor haveTime: haveTime, isShardStuck: isShardStuck, isMaxBlockSizeReached: isMaxBlockSizeReached, - getTxMaxTotalCost: getTxMaxTotalCost, + getTxMaxTotalCost: txs.getTxMaxTotalCost, getTotalGasConsumed: txs.getTotalGasConsumed, txPool: txs.txPool, + enableEpochsHandler: txs.enableEpochsHandler, + enableRoundsHandler: txs.enableRoundsHandler, } mbBuilder, err := newMiniBlockBuilder(args) @@ -1338,7 +1409,7 @@ func (txs *transactions) splitMiniBlockBasedOnMaxGasLimitIfNeeded(miniBlock *blo gasLimitInReceiverShard := uint64(0) for _, txHash := range miniBlock.TxHashes { - txInfoInstance, ok := txs.txsForCurrBlock.txHashAndInfo[string(txHash)] + txInfoInstance, ok := txs.txsForCurrBlock.GetTxInfoByHash(txHash) if !ok { log.Warn("transactions.splitMiniBlockIfNeeded: missing tx", "hash", txHash) currentMiniBlock.TxHashes = append(currentMiniBlock.TxHashes, txHash) @@ -1348,7 +1419,7 @@ func (txs *transactions) splitMiniBlockBasedOnMaxGasLimitIfNeeded(miniBlock *blo _, gasProvidedByTxInReceiverShard, err := txs.computeGasProvidedByTx( miniBlock.SenderShardID, miniBlock.ReceiverShardID, - txInfoInstance.tx, + txInfoInstance.Tx, txHash) if err != nil { log.Warn("transactions.splitMiniBlockIfNeeded: failed to compute gas consumed by tx", "hash", txHash, "error", err.Error()) @@ -1395,33 +1466,114 @@ func createEmptyMiniBlockFromMiniBlock(miniBlock *block.MiniBlock) *block.MiniBl } } -func (txs *transactions) computeSortedTxs( +func (txs *transactions) selectTransactionsFromTxPoolForProposal( sndShardId uint32, dstShardId uint32, gasBandwidth uint64, - randomness []byte, -) ([]*txcache.WrappedTransaction, []*txcache.WrappedTransaction, error) { + nonce uint64, + haveTimeForSelection func() bool, +) ([]*txcache.WrappedTransaction, error) { strCache := process.ShardCacherIdentifier(sndShardId, dstShardId) txShardPool := txs.txPool.ShardDataStore(strCache) if check.IfNil(txShardPool) { - return nil, nil, process.ErrNilTxDataPool + return nil, process.ErrNilTxDataPool + } + txCache, isTxCache := txShardPool.(TxCache) + if !isTxCache { + return nil, fmt.Errorf("%w: 'txShardPool' should be of type 'TxCache'", process.ErrWrongTypeAssertion) + } + + session, err := NewSelectionSession(ArgsSelectionSession{ + AccountsAdapter: txs.accountsProposal, + TransactionsProcessor: txs.txProcessor, + TxVersionCheckerHandler: txs.txVersionCheckerHandler, + }) + if err != nil { + return nil, err + } + + selectionOptions, err := holders.NewTxSelectionOptions( + gasBandwidth, + txs.txCacheSelectionConfig.SelectionMaxNumTxs, + txs.txCacheSelectionConfig.SelectionLoopDurationCheckInterval, + haveTimeForSelection, + ) + if err != nil { + return nil, err + } + + selectedTransactions, _, err := txCache.SelectTransactions(session, selectionOptions, nonce) + if err != nil { + return nil, err } - sortedTransactionsProvider := createSortedTransactionsProvider(txShardPool) - log.Debug("computeSortedTxs.GetSortedTransactions") + return selectedTransactions, nil +} + +func (txs *transactions) selectTransactionsFromTxPool( + sndShardId uint32, + dstShardId uint32, + gasBandwidth uint64, + haveTimeForSelection func() bool, +) ([]*txcache.WrappedTransaction, error) { + strCache := process.ShardCacherIdentifier(sndShardId, dstShardId) + txShardPool := txs.txPool.ShardDataStore(strCache) + + if check.IfNil(txShardPool) { + return nil, process.ErrNilTxDataPool + } + txCache, isTxCache := txShardPool.(TxCache) + if !isTxCache { + return nil, fmt.Errorf("%w: 'txShardPool' should be of type 'TxCache'", process.ErrWrongTypeAssertion) + } session, err := NewSelectionSession(ArgsSelectionSession{ - AccountsAdapter: txs.accounts, - TransactionsProcessor: txs.txProcessor, + AccountsAdapter: txs.accountsProposal, + TransactionsProcessor: txs.txProcessor, + TxVersionCheckerHandler: txs.txVersionCheckerHandler, }) if err != nil { - return nil, nil, err + return nil, err } - sortedTxs := sortedTransactionsProvider.GetSortedTransactions(session) + selectionOptions, err := holders.NewTxSelectionOptions( + gasBandwidth, + txs.txCacheSelectionConfig.SelectionMaxNumTxs, + txs.txCacheSelectionConfig.SelectionLoopDurationCheckInterval, + haveTimeForSelection, + ) + if err != nil { + return nil, err + } + + selectedTxs, _, err := txCache.SelectTransactions(session, selectionOptions, 0) + if err != nil { + return nil, err + } + + return selectedTxs, nil +} + +func (txs *transactions) computeSortedTxs( + sndShardId uint32, + dstShardId uint32, + gasBandwidth uint64, + randomness []byte, + haveTimeForSelection func() bool, +) ([]*txcache.WrappedTransaction, []*txcache.WrappedTransaction, error) { + log.Debug("computeSortedTxs", + "sndShardId", sndShardId, + "dstShardId", dstShardId, + "gasBandwidth", gasBandwidth, + "randomness", randomness, + ) + + sortedTxs, err := txs.selectTransactionsFromTxPool(sndShardId, dstShardId, gasBandwidth, haveTimeForSelection) + if err != nil { + return nil, nil, err + } - // TODO: this could be moved to SortedTransactionsProvider selectedTxs, remainingTxs := txs.preFilterTransactionsWithMoveBalancePriority(sortedTxs, gasBandwidth) txs.sortTransactionsBySenderAndNonce(selectedTxs, randomness) @@ -1460,7 +1612,7 @@ func (txs *transactions) ProcessMiniBlock( return nil, indexOfLastTxProcessed, false, err } - if txs.blockSizeComputation.IsMaxBlockSizeWithoutThrottleReached(1, len(miniBlock.TxHashes)) { + if txs.isMaxBlockSizeWithoutThrottleReached(1, len(miniBlock.TxHashes)) { return nil, indexOfLastTxProcessed, false, process.ErrMaxBlockSizeReached } @@ -1512,7 +1664,7 @@ func (txs *transactions) ProcessMiniBlock( }() numOfOldCrossInterMbs, numOfOldCrossInterTxs := preProcessorExecutionInfoHandler.GetNumOfCrossInterMbsAndTxs() - + skipBlockLimitChecks := common.IsAsyncExecutionEnabled(txs.enableEpochsHandler, txs.enableRoundsHandler) for txIndex = indexOfFirstTxToBeProcessed; txIndex < len(miniBlockTxs); txIndex++ { if !haveTime() && !haveAdditionalTime() { err = process.ErrTimeIsOut @@ -1524,7 +1676,8 @@ func (txs *transactions) ProcessMiniBlock( miniBlock.ReceiverShardID, miniBlockTxs[txIndex], miniBlockTxHashes[txIndex], - &gasInfo) + &gasInfo, + skipBlockLimitChecks) if err != nil { break @@ -1578,20 +1731,16 @@ func (txs *transactions) ProcessMiniBlock( numMiniBlocks := 1 + numOfNewCrossInterMbs numTxs := len(miniBlock.TxHashes) + numOfNewCrossInterTxs - if txs.blockSizeComputation.IsMaxBlockSizeWithoutThrottleReached(numMiniBlocks, numTxs) { + if txs.isMaxBlockSizeWithoutThrottleReached(numMiniBlocks, numTxs) { return processedTxHashes, txIndex - 1, true, process.ErrMaxBlockSizeReached } - txShardInfoToSet := &txShardInfo{senderShardID: miniBlock.SenderShardID, receiverShardID: miniBlock.ReceiverShardID} - - txs.txsForCurrBlock.mutTxsForBlock.Lock() for index, txHash := range miniBlockTxHashes { - txs.txsForCurrBlock.txHashAndInfo[string(txHash)] = &txInfo{tx: miniBlockTxs[index], txShardInfo: txShardInfoToSet} + txs.txsForCurrBlock.AddTransaction(txHash, miniBlockTxs[index], miniBlock.SenderShardID, miniBlock.ReceiverShardID) } - txs.txsForCurrBlock.mutTxsForBlock.Unlock() - txs.blockSizeComputation.AddNumMiniBlocks(numMiniBlocks) - txs.blockSizeComputation.AddNumTxs(numTxs) + txs.addNumMiniBlocks(numMiniBlocks) + txs.addNumTxs(numTxs) if scheduledMode { for index := indexOfFirstTxToBeProcessed; index <= txIndex-1; index++ { @@ -1628,7 +1777,7 @@ func (txs *transactions) processInNormalMode( // CreateMarshalledData marshals transactions hashes and saves them into a new structure func (txs *transactions) CreateMarshalledData(txHashes [][]byte) ([][]byte, error) { - marshalledTxs, err := txs.createMarshalledData(txHashes, &txs.txsForCurrBlock) + marshalledTxs, err := txs.createMarshalledData(txHashes, txs.txsForCurrBlock) if err != nil { return nil, err } @@ -1638,14 +1787,7 @@ func (txs *transactions) CreateMarshalledData(txHashes [][]byte) ([][]byte, erro // GetAllCurrentUsedTxs returns all the transactions used at current creation / processing func (txs *transactions) GetAllCurrentUsedTxs() map[string]data.TransactionHandler { - txs.txsForCurrBlock.mutTxsForBlock.RLock() - txsPool := make(map[string]data.TransactionHandler, len(txs.txsForCurrBlock.txHashAndInfo)) - for txHash, txInfoFromMap := range txs.txsForCurrBlock.txHashAndInfo { - txsPool[txHash] = txInfoFromMap.tx - } - txs.txsForCurrBlock.mutTxsForBlock.RUnlock() - - return txsPool + return txs.txsForCurrBlock.GetAllCurrentUsedTxs() } // IsInterfaceNil returns true if there is no value under the interface @@ -1762,7 +1904,7 @@ func (txs *transactions) prefilterTransactions( log.Debug("preFilterTransactions estimation", "initialTxs", len(initialTxs), - "gasCost initialTxs", initialTxsGasEstimation, + "gasCostOfInitialTxs", initialTxsGasEstimation, "additionalTxs", len(additionalTxs), "gasCostEstimation", gasEstimation, "selected", len(selectedTxs), diff --git a/process/block/preprocess/transactionsV2.go b/process/block/preprocess/transactionsV2.go index 8d5bbbc320a..443e45c4ac1 100644 --- a/process/block/preprocess/transactionsV2.go +++ b/process/block/preprocess/transactionsV2.go @@ -3,14 +3,16 @@ package preprocess import ( "bytes" "errors" + "math/big" "time" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" - "github.com/multiversx/mx-chain-go/storage/txcache" + "github.com/multiversx/mx-chain-go/txcache" ) func (txs *transactions) createAndProcessMiniBlocksFromMeV2( @@ -38,6 +40,7 @@ func (txs *transactions) createAndProcessMiniBlocksFromMeV2( sortedTxs[index], mbInfo) if !shouldContinue { + txs.addUnExecutableTransaction(sortedTxs[index].TxHash) continue } @@ -52,7 +55,8 @@ func (txs *transactions) createAndProcessMiniBlocksFromMeV2( receiverShardID, mbInfo) - if isMaxBlockSizeReached(txMbInfo.numNewMiniBlocks, txMbInfo.numNewTxs) { + if isMaxBlockSizeReached(txMbInfo.numNewMiniBlocks, txMbInfo.numNewTxs) && + !common.IsAsyncExecutionEnabled(txs.enableEpochsHandler, txs.enableRoundsHandler) { log.Debug("max txs accepted in one block is reached", "num txs added", mbInfo.processingInfo.numTxsAdded, "total txs", len(sortedTxs)) @@ -113,13 +117,35 @@ func (txs *transactions) createEmptyMiniBlocks(blockType block.Type, reserved [] } func (txs *transactions) hasAddressEnoughInitialBalance(tx *transaction.Transaction) bool { - addressHasEnoughBalance := true - isAddressSet := txs.balanceComputation.IsAddressSet(tx.GetSndAddr()) - if isAddressSet { - addressHasEnoughBalance = txs.balanceComputation.AddressHasEnoughBalance(tx.GetSndAddr(), getTxMaxTotalCost(tx)) + sender := tx.GetSndAddr() + feePayer := sender + if common.IsAsyncExecutionEnabled(txs.enableEpochsHandler, txs.enableRoundsHandler) { + feePayer = common.GetFeePayer(tx) + } + + if bytes.Equal(feePayer, sender) { + if txs.balanceComputation.IsAddressSet(sender) { + return txs.balanceComputation.AddressHasEnoughBalance(sender, txs.getTxMaxTotalCost(tx)) + } + return true + } + + // fee payer pays only the fee, sender pays only the value + if txs.balanceComputation.IsAddressSet(feePayer) { + if !txs.balanceComputation.AddressHasEnoughBalance(feePayer, txs.getTxFee(tx)) { + return false + } + } + + if tx.GetValue() != nil && tx.GetValue().Sign() > 0 { + if txs.balanceComputation.IsAddressSet(sender) { + if !txs.balanceComputation.AddressHasEnoughBalance(sender, tx.GetValue()) { + return false + } + } } - return addressHasEnoughBalance + return true } func (txs *transactions) processTransaction( @@ -135,6 +161,7 @@ func (txs *transactions) processTransaction( oldGasConsumedByMiniBlocksInSenderShard := mbInfo.gasInfo.gasConsumedByMiniBlocksInSenderShard oldGasConsumedByMiniBlockInReceiverShard := mbInfo.gasInfo.gasConsumedByMiniBlockInReceiverShard oldTotalGasConsumedInSelfShard := mbInfo.gasInfo.totalGasConsumedInSelfShard + isAsyncExecEnabled := common.IsAsyncExecutionEnabled(txs.enableEpochsHandler, txs.enableRoundsHandler) startTime := time.Now() gasProvidedByTxInSelfShard, err := txs.computeGasProvided( @@ -142,7 +169,8 @@ func (txs *transactions) processTransaction( receiverShardID, tx, txHash, - &mbInfo.gasInfo) + &mbInfo.gasInfo, + isAsyncExecEnabled) elapsedTime := time.Since(startTime) mbInfo.processingInfo.totalTimeUsedForComputeGasProvided += elapsedTime if err != nil { @@ -150,8 +178,10 @@ func (txs *transactions) processTransaction( isTxTargetedForDeletion := errors.Is(err, process.ErrMaxGasLimitPerOneTxInReceiverShardIsReached) if isTxTargetedForDeletion { mbInfo.processingInfo.numCrossShardTxsWithTooMuchGas++ - strCache := process.ShardCacherIdentifier(senderShardID, receiverShardID) - txs.txPool.RemoveData(txHash, strCache) + if !isAsyncExecEnabled { + strCache := process.ShardCacherIdentifier(senderShardID, receiverShardID) + txs.txPool.RemoveData(txHash, strCache) + } return false, err } return true, err @@ -191,6 +221,7 @@ func (txs *transactions) processTransaction( mbInfo.gasInfo.gasConsumedByMiniBlocksInSenderShard = oldGasConsumedByMiniBlocksInSenderShard mbInfo.mapGasConsumedByMiniBlockInReceiverShard[receiverShardID] = oldGasConsumedByMiniBlockInReceiverShard mbInfo.gasInfo.totalGasConsumedInSelfShard = oldTotalGasConsumedInSelfShard + txs.addUnExecutableTransaction(txHash) return false, err } @@ -227,16 +258,25 @@ func (txs *transactions) processTransaction( ) if !mbInfo.firstInvalidTxFound { mbInfo.firstInvalidTxFound = true - txs.blockSizeComputation.AddNumMiniBlocks(1) + txs.addNumMiniBlocks(1) } - txs.blockSizeComputation.AddNumTxs(1) + txs.addNumTxs(1) mbInfo.processingInfo.numTxsFailed++ } return false, err } +func (txs *transactions) addUnExecutableTransaction(txHash []byte) { + if common.IsAsyncExecutionEnabled(txs.enableEpochsHandler, txs.enableRoundsHandler) { + log.Warn("createAndProcessMiniBlocksFromMeV2 found unexecutable tx", "txHash", txHash) + txs.mutUnExecutableTxs.Lock() + txs.unExecutableTransactions[string(txHash)] = struct{}{} + txs.mutUnExecutableTxs.Unlock() + } +} + func (txs *transactions) getMiniBlockSliceFromMapV2(mapMiniBlocks map[uint32]*block.MiniBlock) block.MiniBlockSlice { miniBlocks := make(block.MiniBlockSlice, 0) @@ -342,14 +382,14 @@ func (txs *transactions) verifyTransaction( oldGasConsumedByMiniBlocksInSenderShard := mbInfo.gasInfo.gasConsumedByMiniBlocksInSenderShard oldGasConsumedByMiniBlockInReceiverShard := mbInfo.gasInfo.gasConsumedByMiniBlockInReceiverShard oldTotalGasConsumedInSelfShard := mbInfo.gasInfo.totalGasConsumedInSelfShard - startTime := time.Now() gasProvidedByTxInSelfShard, err := txs.computeGasProvided( senderShardID, receiverShardID, tx, txHash, - &mbInfo.gasInfo) + &mbInfo.gasInfo, + false) elapsedTime := time.Since(startTime) mbInfo.schedulingInfo.totalTimeUsedForScheduledComputeGasProvided += elapsedTime if err != nil { @@ -390,11 +430,7 @@ func (txs *transactions) verifyTransaction( return err } - txShardInfoToSet := &txShardInfo{senderShardID: senderShardID, receiverShardID: receiverShardID} - txs.txsForCurrBlock.mutTxsForBlock.Lock() - txs.txsForCurrBlock.txHashAndInfo[string(txHash)] = &txInfo{tx: tx, txShardInfo: txShardInfoToSet} - txs.txsForCurrBlock.mutTxsForBlock.Unlock() - + txs.txsForCurrBlock.AddTransaction(txHash, tx, senderShardID, receiverShardID) return nil } @@ -500,8 +536,9 @@ func (txs *transactions) shouldContinueProcessingTx( txHash := wrappedTx.TxHash senderShardID := wrappedTx.SenderShardID receiverShardID := wrappedTx.ReceiverShardID + asyncExecEnabled := common.IsAsyncExecutionEnabled(txs.enableEpochsHandler, txs.enableRoundsHandler) - if senderShardID != receiverShardID && isShardStuck != nil && isShardStuck(receiverShardID) { + if senderShardID != receiverShardID && isShardStuck != nil && isShardStuck(receiverShardID) && !asyncExecEnabled { log.Trace("shard is stuck", "shard", receiverShardID) return nil, nil, false } @@ -579,35 +616,31 @@ func (txs *transactions) applyExecutedTransaction( mbInfo *createAndProcessMiniBlocksInfo, ) { mbInfo.senderAddressToSkip = []byte("") - - if txs.balanceComputation.IsAddressSet(tx.GetSndAddr()) { - txMaxTotalCost := getTxMaxTotalCost(tx) - ok := txs.balanceComputation.SubBalanceFromAddress(tx.GetSndAddr(), txMaxTotalCost) - if !ok { - log.Error("applyExecutedTransaction.SubBalanceFromAddress", - "sender address", tx.GetSndAddr(), - "tx max total cost", txMaxTotalCost, - "err", process.ErrInsufficientFunds) - } + sender := tx.GetSndAddr() + feePayer := sender + if common.IsAsyncExecutionEnabled(txs.enableEpochsHandler, txs.enableRoundsHandler) { + feePayer = common.GetFeePayer(tx) } + txs.subtractTxCostFromBalances(tx, sender, feePayer) + if len(miniBlock.TxHashes) == 0 { - txs.blockSizeComputation.AddNumMiniBlocks(1) + txs.addNumMiniBlocks(1) } miniBlock.TxHashes = append(miniBlock.TxHashes, txHash) - txs.blockSizeComputation.AddNumTxs(1) + txs.addNumTxs(1) if txMbInfo.isCrossShardScCallOrSpecialTx { if !mbInfo.firstCrossShardScCallOrSpecialTxFound { mbInfo.firstCrossShardScCallOrSpecialTxFound = true - txs.blockSizeComputation.AddNumMiniBlocks(1) + txs.addNumMiniBlocks(1) } mbInfo.mapCrossShardScCallsOrSpecialTxs[receiverShardID]++ crossShardScCallsOrSpecialTxs := mbInfo.mapCrossShardScCallsOrSpecialTxs[receiverShardID] if crossShardScCallsOrSpecialTxs > mbInfo.maxCrossShardScCallsOrSpecialTxsPerShard { mbInfo.maxCrossShardScCallsOrSpecialTxsPerShard = crossShardScCallsOrSpecialTxs - //we need to increment this as to account for the corresponding SCR hash - txs.blockSizeComputation.AddNumTxs(common.AdditionalScrForEachScCallOrSpecialTx) + // we need to increment this as to account for the corresponding SCR hash + txs.addNumTxs(common.AdditionalScrForEachScCallOrSpecialTx) } mbInfo.processingInfo.numCrossShardScCallsOrSpecialTxs++ } @@ -622,6 +655,34 @@ func (txs *transactions) applyExecutedTransaction( mbInfo.processingInfo.numTxsAdded++ } +func (txs *transactions) subtractTxCostFromBalances(tx *transaction.Transaction, sender []byte, feePayer []byte) { + if bytes.Equal(feePayer, sender) { + txs.subtractBalanceIfSet(sender, txs.getTxMaxTotalCost(tx), "sender address") + return + } + + // fee payer pays only the fee, sender pays only the value + txs.subtractBalanceIfSet(feePayer, txs.getTxFee(tx), "fee payer address") + + if tx.GetValue() != nil && tx.GetValue().Sign() > 0 { + txs.subtractBalanceIfSet(sender, tx.GetValue(), "sender address") + } +} + +func (txs *transactions) subtractBalanceIfSet(address []byte, amount *big.Int, addressLabel string) { + if !txs.balanceComputation.IsAddressSet(address) { + return + } + + ok := txs.balanceComputation.SubBalanceFromAddress(address, amount) + if !ok { + log.Error("applyExecutedTransaction.SubBalanceFromAddress", + addressLabel, address, + "amount", amount, + "err", process.ErrInsufficientFunds) + } +} + func (txs *transactions) initCreateScheduledMiniBlocks() *createScheduledMiniBlocksInfo { reserved, _ := (&block.MiniBlockReserved{ ExecutionType: block.Scheduled, @@ -742,7 +803,7 @@ func (txs *transactions) applyVerifiedTransaction( mbInfo *createScheduledMiniBlocksInfo, ) { if txs.balanceComputation.IsAddressSet(tx.GetSndAddr()) { - txMaxTotalCost := getTxMaxTotalCost(tx) + txMaxTotalCost := txs.getTxMaxTotalCost(tx) ok := txs.balanceComputation.SubBalanceFromAddress(tx.GetSndAddr(), txMaxTotalCost) if !ok { log.Error("applyVerifiedTransaction.SubBalanceFromAddress", @@ -753,22 +814,22 @@ func (txs *transactions) applyVerifiedTransaction( } if len(miniBlock.TxHashes) == 0 { - txs.blockSizeComputation.AddNumMiniBlocks(1) + txs.addNumMiniBlocks(1) } miniBlock.TxHashes = append(miniBlock.TxHashes, txHash) - txs.blockSizeComputation.AddNumTxs(1) + txs.addNumTxs(1) if scheduledTxMbInfo.isCrossShardScCallTx { if !mbInfo.firstCrossShardScCallTxFound { mbInfo.firstCrossShardScCallTxFound = true - txs.blockSizeComputation.AddNumMiniBlocks(1) + txs.addNumMiniBlocks(1) } mbInfo.mapCrossShardScCallTxs[receiverShardID]++ crossShardScCallTxs := mbInfo.mapCrossShardScCallTxs[receiverShardID] if crossShardScCallTxs > mbInfo.maxCrossShardScCallTxsPerShard { mbInfo.maxCrossShardScCallTxsPerShard = crossShardScCallTxs - //we need to increment this as to account for the corresponding SCR hash - txs.blockSizeComputation.AddNumTxs(common.AdditionalScrForEachScCallOrSpecialTx) + // we need to increment this as to account for the corresponding SCR hash + txs.addNumTxs(common.AdditionalScrForEachScCallOrSpecialTx) } mbInfo.schedulingInfo.numScheduledCrossShardScCalls++ } diff --git a/process/block/preprocess/transactionsV2_test.go b/process/block/preprocess/transactionsV2_test.go index 40ec747963d..51b66df2501 100644 --- a/process/block/preprocess/transactionsV2_test.go +++ b/process/block/preprocess/transactionsV2_test.go @@ -11,74 +11,54 @@ import ( "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/smartContractResult" "github.com/multiversx/mx-chain-core-go/data/transaction" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/stretchr/testify/assert" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" + + "github.com/multiversx/mx-chain-go/testscommon/preprocMocks" + "github.com/multiversx/mx-chain-go/txcache" + "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/mock" - "github.com/multiversx/mx-chain-go/storage/txcache" "github.com/multiversx/mx-chain-go/testscommon" - commonMocks "github.com/multiversx/mx-chain-go/testscommon/common" "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" - "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" - "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" - stateMock "github.com/multiversx/mx-chain-go/testscommon/state" - storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" - "github.com/stretchr/testify/assert" ) func createTransactionPreprocessor() *transactions { - dataPool := initDataPool() - requestTransaction := func(shardID uint32, txHashes [][]byte) {} - txPreProcArgs := ArgsTransactionPreProcessor{ - TxDataPool: dataPool.Transactions(), - Store: &storageStubs.ChainStorerStub{}, - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - TxProcessor: &testscommon.TxProcessorMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: &stateMock.AccountsStub{}, - OnRequestTransaction: requestTransaction, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{ - MaxGasLimitPerMiniBlockForSafeCrossShardCalled: func() uint64 { - return MaxGasLimitPerBlock - }, - MaxGasLimitPerBlockForSafeCrossShardCalled: func() uint64 { - return MaxGasLimitPerBlock - }, - MaxGasLimitPerBlockCalled: func(_ uint32) uint64 { - return MaxGasLimitPerBlock - }, - MaxGasLimitPerTxCalled: func() uint64 { - return MaxGasLimitPerBlock - }, - }, - GasHandler: &mock.GasHandlerMock{}, - BlockTracker: &mock.BlockTrackerMock{}, - BlockType: block.TxBlock, - PubkeyConverter: createMockPubkeyConverter(), - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{ - IsAddressSetCalled: func(address []byte) bool { - return true - }, - SubBalanceFromAddressCalled: func(address []byte, value *big.Int) bool { - return false - }, + args := createDefaultTransactionsProcessorArgs() + args.EconomicsFee = &economicsmocks.EconomicsHandlerMock{ + MaxGasLimitPerMiniBlockForSafeCrossShardCalled: func() uint64 { + return MaxGasLimitPerBlock }, - EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - TxTypeHandler: &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { - if bytes.Equal(tx.GetRcvAddr(), []byte("smart contract address")) { - return process.MoveBalance, process.SCInvoking, false - } - return process.MoveBalance, process.MoveBalance, false - }, + MaxGasLimitPerBlockForSafeCrossShardInEpochCalled: func(_ uint32) uint64 { + return MaxGasLimitPerBlock + }, + MaxGasLimitPerBlockInEpochCalled: func(_ uint32, _ uint32) uint64 { + return MaxGasLimitPerBlock + }, + MaxGasLimitPerTxInEpochCalled: func(_ uint32) uint64 { + return MaxGasLimitPerBlock }, - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMocks.TxExecutionOrderHandlerStub{}, } - - preprocessor, _ := NewTransactionPreprocessor(txPreProcArgs) + args.BalanceComputation = &testscommon.BalanceComputationStub{ + IsAddressSetCalled: func(address []byte) bool { + return true + }, + SubBalanceFromAddressCalled: func(address []byte, value *big.Int) bool { + return false + }, + } + args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + if bytes.Equal(tx.GetRcvAddr(), []byte("smart contract address")) { + return process.MoveBalance, process.SCInvoking, false + } + return process.MoveBalance, process.MoveBalance, false + }, + } + preprocessor, _ := NewTransactionPreprocessor(args) return preprocessor } @@ -523,7 +503,9 @@ func TestTransactions_VerifyTransactionShouldWork(t *testing.T) { mbInfo.mapGasConsumedByMiniBlockInReceiverShard[receiverShardID] = 0 err = preprocessor.verifyTransaction(tx, txHash, senderShardID, receiverShardID, mbInfo) assert.Nil(t, err) - _, ok := preprocessor.txsForCurrBlock.txHashAndInfo[string(txHash)] + + txsForCurrBlock := preprocessor.txsForCurrBlock.(*txsForBlock) + _, ok := txsForCurrBlock.txHashAndInfo[string(txHash)] assert.True(t, ok) } @@ -823,6 +805,16 @@ func TestTransactions_ProcessTransactionShouldWork(t *testing.T) { return 0, gasProvidedByTx, nil }, } + preprocessor.enableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return true + }, + } + preprocessor.enableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledCalled: func(flag common.EnableRoundFlag) bool { + return true + }, + } tx := &transaction.Transaction{} txHash := []byte("txHash") @@ -866,3 +858,484 @@ func TestTransactions_ProcessTransactionShouldWork(t *testing.T) { _, err = preprocessor.processTransaction(tx, txHash, senderShardID, receiverShardID, mbInfo) assert.Nil(t, err) } + +func TestTransactions_GetCreatedMiniBlocksFromMe(t *testing.T) { + t.Parallel() + + preprocessor := createTransactionPreprocessor() + txHashes := [][]byte{[]byte("txHash1"), []byte("txHash2"), []byte("txHash3"), []byte("txHash4")} + + mb1 := &block.MiniBlock{ + TxHashes: [][]byte{txHashes[0]}, + ReceiverShardID: 0} + mb2 := &block.MiniBlock{ + TxHashes: [][]byte{txHashes[0], txHashes[1]}, + ReceiverShardID: 1} + mb3 := &block.MiniBlock{ + TxHashes: [][]byte{txHashes[0], txHashes[1], txHashes[2]}, + ReceiverShardID: 2} + mb4 := &block.MiniBlock{ + TxHashes: [][]byte{txHashes[0], txHashes[1], txHashes[2], txHashes[3]}, + ReceiverShardID: core.MetachainShardId} + + mbs := block.MiniBlockSlice{mb1, mb2, mb3, mb4} + preprocessor.createdMiniBlocks = mbs + + resultingMbs := preprocessor.GetCreatedMiniBlocksFromMe() + assert.Equal(t, 4, len(resultingMbs)) + assert.Equal(t, mbs, resultingMbs) +} + +func TestTransactions_GetUnExecutableTransactions(t *testing.T) { + t.Parallel() + + preprocessor := createTransactionPreprocessor() + + txs := make(map[string]struct{}) + txs["txHash1"] = struct{}{} + txs["txHash2"] = struct{}{} + txs["txHash3"] = struct{}{} + txs["txHash4"] = struct{}{} + + preprocessor.unExecutableTransactions = txs + + resultingTxs := preprocessor.GetUnExecutableTransactions() + assert.Len(t, resultingTxs, len(txs)) + assert.Equal(t, txs, resultingTxs) +} + +func TestTransactions_CreateBlockStarted(t *testing.T) { + t.Parallel() + + preprocessor := createTransactionPreprocessor() + preprocessor.createdMiniBlocks = make(block.MiniBlockSlice, 10) + preprocessor.unExecutableTransactions = map[string]struct{}{"txHash": {}} + preprocessor.orderedTxs = map[string][]data.TransactionHandler{"txHash": {}} + preprocessor.orderedTxHashes = map[string][][]byte{"shard0": {[]byte("txHash")}} + called := false + preprocessor.txsForCurrBlock = &preprocMocks.TxsForBlockStub{ + ResetCalled: func() { + called = true + }, + } + + preprocessor.CreateBlockStarted() + assert.Empty(t, preprocessor.createdMiniBlocks) + assert.Empty(t, preprocessor.unExecutableTransactions) + assert.Empty(t, preprocessor.orderedTxs) + assert.Empty(t, preprocessor.orderedTxHashes) + assert.True(t, called) +} + +func TestTransactions_HasAddressEnoughInitialBalance(t *testing.T) { + t.Parallel() + + t.Run("sender == fee payer, checks full cost against sender", func(t *testing.T) { + t.Parallel() + + var checkedAddress []byte + var checkedValue *big.Int + preprocessor := createTransactionPreprocessor() + preprocessor.balanceComputation = &testscommon.BalanceComputationStub{ + IsAddressSetCalled: func(address []byte) bool { + return true + }, + AddressHasEnoughBalanceCalled: func(address []byte, value *big.Int) bool { + checkedAddress = address + checkedValue = value + return true + }, + } + + tx := &transaction.Transaction{ + SndAddr: []byte("sender"), + GasPrice: 10, + GasLimit: 100, + Value: big.NewInt(500), + } + + result := preprocessor.hasAddressEnoughInitialBalance(tx) + assert.True(t, result) + assert.Equal(t, []byte("sender"), checkedAddress) + // should check full cost: gasPrice*gasLimit + value = 10*100 + 500 = 1500 + assert.Equal(t, big.NewInt(1500), checkedValue) + }) + + t.Run("sender == fee payer, not enough balance", func(t *testing.T) { + t.Parallel() + + preprocessor := createTransactionPreprocessor() + preprocessor.balanceComputation = &testscommon.BalanceComputationStub{ + IsAddressSetCalled: func(address []byte) bool { + return true + }, + AddressHasEnoughBalanceCalled: func(address []byte, value *big.Int) bool { + return false + }, + } + + tx := &transaction.Transaction{ + SndAddr: []byte("sender"), + GasPrice: 10, + GasLimit: 100, + Value: big.NewInt(500), + } + + result := preprocessor.hasAddressEnoughInitialBalance(tx) + assert.False(t, result) + }) + + t.Run("relayed v3: fee payer pays fee only, sender pays value only", func(t *testing.T) { + t.Parallel() + + checkedBalances := make(map[string]*big.Int) + preprocessor := createTransactionPreprocessor() + preprocessor.enableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return true + }, + } + preprocessor.enableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledCalled: func(flag common.EnableRoundFlag) bool { + return true + }, + } + txFee := big.NewInt(50) + feeHandlerMock := &economicsmocks.EconomicsHandlerMock{ + ComputeTxFeeCalled: func(tx data.TransactionWithFeeHandler) *big.Int { + return big.NewInt(0).Set(txFee) + }, + } + preprocessor.economicsFee = feeHandlerMock + preprocessor.feeHandler = feeHandlerMock + preprocessor.balanceComputation = &testscommon.BalanceComputationStub{ + IsAddressSetCalled: func(address []byte) bool { + return true + }, + AddressHasEnoughBalanceCalled: func(address []byte, value *big.Int) bool { + checkedBalances[string(address)] = big.NewInt(0).Set(value) + return true + }, + } + + tx := &transaction.Transaction{ + SndAddr: []byte("sender"), + RelayerAddr: []byte("relayer"), + GasPrice: 10, + GasLimit: 100, + Value: big.NewInt(500), + } + + result := preprocessor.hasAddressEnoughInitialBalance(tx) + assert.True(t, result) + // fee payer (relayer) should be checked for fee only + assert.Equal(t, txFee, checkedBalances["relayer"]) + // sender should be checked for value only + assert.Equal(t, big.NewInt(500), checkedBalances["sender"]) + }) + + t.Run("relayed v3: fee payer has not enough for fee", func(t *testing.T) { + t.Parallel() + + preprocessor := createTransactionPreprocessor() + preprocessor.enableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return true + }, + } + preprocessor.enableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledCalled: func(flag common.EnableRoundFlag) bool { + return true + }, + } + feeHandlerMock := &economicsmocks.EconomicsHandlerMock{ + ComputeTxFeeCalled: func(tx data.TransactionWithFeeHandler) *big.Int { + return big.NewInt(50) + }, + } + preprocessor.economicsFee = feeHandlerMock + preprocessor.feeHandler = feeHandlerMock + preprocessor.balanceComputation = &testscommon.BalanceComputationStub{ + IsAddressSetCalled: func(address []byte) bool { + return true + }, + AddressHasEnoughBalanceCalled: func(address []byte, value *big.Int) bool { + if bytes.Equal(address, []byte("relayer")) { + return false // relayer doesn't have enough for fee + } + return true + }, + } + + tx := &transaction.Transaction{ + SndAddr: []byte("sender"), + RelayerAddr: []byte("relayer"), + GasPrice: 10, + GasLimit: 100, + Value: big.NewInt(500), + } + + result := preprocessor.hasAddressEnoughInitialBalance(tx) + assert.False(t, result) + }) + + t.Run("relayed v3: sender has not enough for value", func(t *testing.T) { + t.Parallel() + + preprocessor := createTransactionPreprocessor() + preprocessor.enableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return true + }, + } + preprocessor.enableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledCalled: func(flag common.EnableRoundFlag) bool { + return true + }, + } + feeHandlerMock := &economicsmocks.EconomicsHandlerMock{ + ComputeTxFeeCalled: func(tx data.TransactionWithFeeHandler) *big.Int { + return big.NewInt(50) + }, + } + preprocessor.economicsFee = feeHandlerMock + preprocessor.feeHandler = feeHandlerMock + preprocessor.balanceComputation = &testscommon.BalanceComputationStub{ + IsAddressSetCalled: func(address []byte) bool { + return true + }, + AddressHasEnoughBalanceCalled: func(address []byte, value *big.Int) bool { + if bytes.Equal(address, []byte("sender")) { + return false // sender doesn't have enough for value + } + return true + }, + } + + tx := &transaction.Transaction{ + SndAddr: []byte("sender"), + RelayerAddr: []byte("relayer"), + GasPrice: 10, + GasLimit: 100, + Value: big.NewInt(500), + } + + result := preprocessor.hasAddressEnoughInitialBalance(tx) + assert.False(t, result) + }) + + t.Run("relayed v3: zero value tx, only fee payer checked", func(t *testing.T) { + t.Parallel() + + checkedAddresses := make([]string, 0) + preprocessor := createTransactionPreprocessor() + preprocessor.enableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return true + }, + } + preprocessor.enableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledCalled: func(flag common.EnableRoundFlag) bool { + return true + }, + } + feeHandlerMock := &economicsmocks.EconomicsHandlerMock{ + ComputeTxFeeCalled: func(tx data.TransactionWithFeeHandler) *big.Int { + return big.NewInt(50) + }, + } + preprocessor.economicsFee = feeHandlerMock + preprocessor.feeHandler = feeHandlerMock + preprocessor.balanceComputation = &testscommon.BalanceComputationStub{ + IsAddressSetCalled: func(address []byte) bool { + return true + }, + AddressHasEnoughBalanceCalled: func(address []byte, value *big.Int) bool { + checkedAddresses = append(checkedAddresses, string(address)) + return true + }, + } + + tx := &transaction.Transaction{ + SndAddr: []byte("sender"), + RelayerAddr: []byte("relayer"), + GasPrice: 10, + GasLimit: 100, + Value: big.NewInt(0), + } + + result := preprocessor.hasAddressEnoughInitialBalance(tx) + assert.True(t, result) + // only fee payer should be checked (sender value is 0, no check needed) + assert.Equal(t, []string{"relayer"}, checkedAddresses) + }) +} + +func TestTransactions_ApplyExecutedTransactionFeePayerSplit(t *testing.T) { + t.Parallel() + + t.Run("sender == fee payer, subtracts full cost from sender", func(t *testing.T) { + t.Parallel() + + var subtractedAddress []byte + var subtractedValue *big.Int + preprocessor := createTransactionPreprocessor() + preprocessor.blockSizeComputation = &testscommon.BlockSizeComputationStub{ + AddNumTxsCalled: func(i int) {}, + AddNumMiniBlocksCalled: func(i int) {}, + } + preprocessor.balanceComputation = &testscommon.BalanceComputationStub{ + IsAddressSetCalled: func(address []byte) bool { + return true + }, + SubBalanceFromAddressCalled: func(address []byte, value *big.Int) bool { + subtractedAddress = address + subtractedValue = value + return true + }, + } + + tx := &transaction.Transaction{ + SndAddr: []byte("sender"), + GasPrice: 10, + GasLimit: 100, + Value: big.NewInt(500), + } + txHash := []byte("hash") + mb := &block.MiniBlock{} + receiverShardID := uint32(1) + txMbInfo := &txAndMbInfo{} + mbInfo := &createAndProcessMiniBlocksInfo{ + mapCrossShardScCallsOrSpecialTxs: make(map[uint32]int), + mapScsForShard: make(map[uint32]int), + mapSCTxs: make(map[string]struct{}), + mapTxsForShard: make(map[uint32]int), + } + + preprocessor.applyExecutedTransaction(tx, txHash, mb, receiverShardID, txMbInfo, mbInfo) + assert.Equal(t, []byte("sender"), subtractedAddress) + // full cost: gasPrice*gasLimit + value = 10*100 + 500 = 1500 + assert.Equal(t, big.NewInt(1500), subtractedValue) + }) + + t.Run("relayed v3: fee from relayer, value from sender", func(t *testing.T) { + t.Parallel() + + subtractedAmounts := make(map[string]*big.Int) + preprocessor := createTransactionPreprocessor() + preprocessor.blockSizeComputation = &testscommon.BlockSizeComputationStub{ + AddNumTxsCalled: func(i int) {}, + AddNumMiniBlocksCalled: func(i int) {}, + } + preprocessor.enableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return true + }, + } + preprocessor.enableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledCalled: func(flag common.EnableRoundFlag) bool { + return true + }, + } + txFee := big.NewInt(50) + feeHandlerMock := &economicsmocks.EconomicsHandlerMock{ + ComputeTxFeeCalled: func(tx data.TransactionWithFeeHandler) *big.Int { + return big.NewInt(0).Set(txFee) + }, + } + preprocessor.economicsFee = feeHandlerMock + preprocessor.feeHandler = feeHandlerMock + preprocessor.balanceComputation = &testscommon.BalanceComputationStub{ + IsAddressSetCalled: func(address []byte) bool { + return true + }, + SubBalanceFromAddressCalled: func(address []byte, value *big.Int) bool { + subtractedAmounts[string(address)] = big.NewInt(0).Set(value) + return true + }, + } + + tx := &transaction.Transaction{ + SndAddr: []byte("sender"), + RelayerAddr: []byte("relayer"), + GasPrice: 10, + GasLimit: 100, + Value: big.NewInt(500), + } + txHash := []byte("hash") + mb := &block.MiniBlock{} + receiverShardID := uint32(1) + txMbInfo := &txAndMbInfo{} + mbInfo := &createAndProcessMiniBlocksInfo{ + mapCrossShardScCallsOrSpecialTxs: make(map[uint32]int), + mapScsForShard: make(map[uint32]int), + mapSCTxs: make(map[string]struct{}), + mapTxsForShard: make(map[uint32]int), + } + + preprocessor.applyExecutedTransaction(tx, txHash, mb, receiverShardID, txMbInfo, mbInfo) + // relayer pays fee only + assert.Equal(t, txFee, subtractedAmounts["relayer"]) + // sender pays value only + assert.Equal(t, big.NewInt(500), subtractedAmounts["sender"]) + }) + + t.Run("relayed v3: zero value tx, only fee subtracted from relayer", func(t *testing.T) { + t.Parallel() + + subtractedAddresses := make([]string, 0) + preprocessor := createTransactionPreprocessor() + preprocessor.blockSizeComputation = &testscommon.BlockSizeComputationStub{ + AddNumTxsCalled: func(i int) {}, + AddNumMiniBlocksCalled: func(i int) {}, + } + preprocessor.enableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return true + }, + } + preprocessor.enableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledCalled: func(flag common.EnableRoundFlag) bool { + return true + }, + } + feeHandlerMock := &economicsmocks.EconomicsHandlerMock{ + ComputeTxFeeCalled: func(tx data.TransactionWithFeeHandler) *big.Int { + return big.NewInt(50) + }, + } + preprocessor.economicsFee = feeHandlerMock + preprocessor.feeHandler = feeHandlerMock + preprocessor.balanceComputation = &testscommon.BalanceComputationStub{ + IsAddressSetCalled: func(address []byte) bool { + return true + }, + SubBalanceFromAddressCalled: func(address []byte, value *big.Int) bool { + subtractedAddresses = append(subtractedAddresses, string(address)) + return true + }, + } + + tx := &transaction.Transaction{ + SndAddr: []byte("sender"), + RelayerAddr: []byte("relayer"), + GasPrice: 10, + GasLimit: 100, + Value: big.NewInt(0), + } + txHash := []byte("hash") + mb := &block.MiniBlock{} + receiverShardID := uint32(1) + txMbInfo := &txAndMbInfo{} + mbInfo := &createAndProcessMiniBlocksInfo{ + mapCrossShardScCallsOrSpecialTxs: make(map[uint32]int), + mapScsForShard: make(map[uint32]int), + mapSCTxs: make(map[string]struct{}), + mapTxsForShard: make(map[uint32]int), + } + + preprocessor.applyExecutedTransaction(tx, txHash, mb, receiverShardID, txMbInfo, mbInfo) + // only relayer should have balance subtracted (no value to subtract from sender) + assert.Equal(t, []string{"relayer"}, subtractedAddresses) + }) +} diff --git a/process/block/preprocess/transactions_test.go b/process/block/preprocess/transactions_test.go index 68dd2d7e709..390f9584019 100644 --- a/process/block/preprocess/transactions_test.go +++ b/process/block/preprocess/transactions_test.go @@ -25,13 +25,17 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common/holders" + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" + "github.com/multiversx/mx-chain-go/txcache" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/mock" "github.com/multiversx/mx-chain-go/sharding" "github.com/multiversx/mx-chain-go/storage" - "github.com/multiversx/mx-chain-go/storage/txcache" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/cache" commonMocks "github.com/multiversx/mx-chain-go/testscommon/common" @@ -74,6 +78,15 @@ func feeHandlerMock() *economicsmocks.EconomicsHandlerMock { MaxGasLimitPerTxCalled: func() uint64 { return MaxGasLimitPerBlock }, + MaxGasLimitPerTxInEpochCalled: func(_ uint32) uint64 { + return MaxGasLimitPerBlock + }, + MaxGasLimitPerBlockForSafeCrossShardInEpochCalled: func(_ uint32) uint64 { + return MaxGasLimitPerBlock + }, + MaxGasLimitPerBlockInEpochCalled: func(shardID uint32, _ uint32) uint64 { + return MaxGasLimitPerBlock + }, } } @@ -220,26 +233,40 @@ func createDefaultTransactionsProcessorArgs() ArgsTransactionPreProcessor { requestTransaction := func(shardID uint32, txHashes [][]byte) {} return ArgsTransactionPreProcessor{ - TxDataPool: tdp.Transactions(), - Store: &storageStubs.ChainStorerStub{}, - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, + BasePreProcessorArgs: BasePreProcessorArgs{ + DataPool: tdp.Transactions(), + Store: &storageStubs.ChainStorerStub{}, + Hasher: &hashingMocks.HasherMock{}, + Marshalizer: &mock.MarshalizerMock{}, + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), + Accounts: &stateMock.AccountsStub{}, + AccountsProposal: &stateMock.AccountsStub{}, + OnRequestTransaction: requestTransaction, + GasHandler: &mock.GasHandlerMock{}, + PubkeyConverter: createMockPubkeyConverter(), + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &commonMocks.TxExecutionOrderHandlerStub{}, + EconomicsFee: feeHandlerMock(), + EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + }, TxProcessor: &testscommon.TxProcessorMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: &stateMock.AccountsStub{}, - OnRequestTransaction: requestTransaction, - EconomicsFee: feeHandlerMock(), - GasHandler: &mock.GasHandlerMock{}, BlockTracker: &mock.BlockTrackerMock{}, BlockType: block.TxBlock, - PubkeyConverter: createMockPubkeyConverter(), - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), TxTypeHandler: &testscommon.TxTypeHandlerMock{}, ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMocks.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: config.TxCacheSelectionConfig{ + SelectionGasBandwidthIncreasePercent: 400, + SelectionGasBandwidthIncreaseScheduledPercent: 260, + SelectionGasRequested: 10_000_000_000, + SelectionMaxNumTxs: 30000, + SelectionLoopDurationCheckInterval: 10, + }, + TxVersionCheckerHandler: &testscommon.TxVersionCheckerStub{}, } } @@ -247,7 +274,7 @@ func TestTxsPreprocessor_NewTransactionPreprocessorNilPool(t *testing.T) { t.Parallel() args := createDefaultTransactionsProcessorArgs() - args.TxDataPool = nil + args.DataPool = nil txs, err := NewTransactionPreprocessor(args) assert.Nil(t, txs) assert.Equal(t, process.ErrNilTransactionPool, err) @@ -261,7 +288,7 @@ func TestTxsPreprocessor_NewTransactionPreprocessorNilStore(t *testing.T) { txs, err := NewTransactionPreprocessor(args) assert.Nil(t, txs) - assert.Equal(t, process.ErrNilTxStorage, err) + assert.Equal(t, process.ErrNilStorage, err) } func TestTxsPreprocessor_NewTransactionPreprocessorNilHasher(t *testing.T) { @@ -407,6 +434,28 @@ func TestTxsPreprocessor_NewTransactionPreprocessorNilEnableEpochsHandler(t *tes assert.Equal(t, process.ErrNilEnableEpochsHandler, err) } +func TestTxsPreprocessor_NewTransactionPreprocessorNilEpochNotifier(t *testing.T) { + t.Parallel() + + args := createDefaultTransactionsProcessorArgs() + args.EpochNotifier = nil + + txs, err := NewTransactionPreprocessor(args) + assert.Nil(t, txs) + assert.Equal(t, process.ErrNilEpochNotifier, err) +} + +func TestTxsPreprocessor_NewTransactionPreprocessorNilRoundNotifier(t *testing.T) { + t.Parallel() + + args := createDefaultTransactionsProcessorArgs() + args.RoundNotifier = nil + + txs, err := NewTransactionPreprocessor(args) + assert.Nil(t, txs) + assert.Equal(t, process.ErrNilRoundNotifier, err) +} + func TestTxsPreprocessor_NewTransactionPreprocessorInvalidEnableEpochsHandler(t *testing.T) { t.Parallel() @@ -418,6 +467,17 @@ func TestTxsPreprocessor_NewTransactionPreprocessorInvalidEnableEpochsHandler(t assert.True(t, errors.Is(err, core.ErrInvalidEnableEpochsHandler)) } +func TestTxsPreprocessor_NewTransactionPreprocessorNilEnableRoundsHandler(t *testing.T) { + t.Parallel() + + args := createDefaultTransactionsProcessorArgs() + args.EnableRoundsHandler = nil + + txs, err := NewTransactionPreprocessor(args) + assert.Nil(t, txs) + assert.True(t, errors.Is(err, process.ErrNilEnableRoundsHandler)) +} + func TestTxsPreprocessor_NewTransactionPreprocessorNilTxTypeHandler(t *testing.T) { t.Parallel() @@ -458,6 +518,65 @@ func TestTxsPreprocessor_NewTransactionPreprocessorNilTxExecutionOrderHandler(t assert.Equal(t, process.ErrNilTxExecutionOrderHandler, err) } +func TestTxsPreprocessor_NewTransactionPreprocessorBadTxCacheSelectionConfig(t *testing.T) { + t.Parallel() + + t.Run("should err ErrBadSelectionGasBandwidthIncreasePercent", func(t *testing.T) { + t.Parallel() + + args := createDefaultTransactionsProcessorArgs() + args.TxCacheSelectionConfig.SelectionGasBandwidthIncreasePercent = 0 + + txs, err := NewTransactionPreprocessor(args) + assert.Nil(t, txs) + assert.Equal(t, process.ErrBadSelectionGasBandwidthIncreasePercent, err) + }) + + t.Run("should err ErrBadSelectionGasBandwidthIncreaseScheduledPercent", func(t *testing.T) { + t.Parallel() + + args := createDefaultTransactionsProcessorArgs() + args.TxCacheSelectionConfig.SelectionGasBandwidthIncreaseScheduledPercent = 0 + + txs, err := NewTransactionPreprocessor(args) + assert.Nil(t, txs) + assert.Equal(t, process.ErrBadSelectionGasBandwidthIncreaseScheduledPercent, err) + }) + + t.Run("should err ErrBadTxCacheSelectionGasRequested", func(t *testing.T) { + t.Parallel() + + args := createDefaultTransactionsProcessorArgs() + args.TxCacheSelectionConfig.SelectionGasRequested = 0 + + txs, err := NewTransactionPreprocessor(args) + assert.Nil(t, txs) + assert.Equal(t, process.ErrBadTxCacheSelectionGasRequested, err) + }) + + t.Run("should err ErrBadTxCacheSelectionMaxNumTxs", func(t *testing.T) { + t.Parallel() + + args := createDefaultTransactionsProcessorArgs() + args.TxCacheSelectionConfig.SelectionMaxNumTxs = 0 + + txs, err := NewTransactionPreprocessor(args) + assert.Nil(t, txs) + assert.Equal(t, process.ErrBadTxCacheSelectionMaxNumTxs, err) + }) + + t.Run("should err ErrBadTxCacheSelectionLoopDurationCheckInterval", func(t *testing.T) { + t.Parallel() + + args := createDefaultTransactionsProcessorArgs() + args.TxCacheSelectionConfig.SelectionLoopDurationCheckInterval = 0 + + txs, err := NewTransactionPreprocessor(args) + assert.Nil(t, txs) + assert.Equal(t, process.ErrBadTxCacheSelectionLoopDurationCheckInterval, err) + }) +} + func TestTxsPreprocessor_NewTransactionPreprocessorOkValsShouldWork(t *testing.T) { t.Parallel() @@ -513,8 +632,9 @@ func TestTransactionPreprocessor_RequestBlockTransactionFromMiniBlockFromNetwork txHashes = append(txHashes, txHash1) txHashes = append(txHashes, txHash2) mb := &block.MiniBlock{ReceiverShardID: shardID, TxHashes: txHashes} - txsRequested := txs.RequestTransactionsForMiniBlock(mb) + txsInstances, txsRequested := txs.GetTransactionsAndRequestMissingForMiniBlock(mb) assert.Equal(t, 2, txsRequested) + assert.Len(t, txsInstances, 0) } func TestTransactionPreprocessor_ReceivedTransactionShouldEraseRequested(t *testing.T) { @@ -647,12 +767,330 @@ func TestTransactionPreprocessor_RemoveBlockDataFromPoolsOK(t *testing.T) { assert.Nil(t, err) } +func TestCleanupSelfShardTxCacheTriggered(t *testing.T) { + t.Parallel() + + var gotNonce uint64 + var gotMaxNum int + mockCalled := false + stub := &testscommon.ShardedDataStub{ + CleanupSelfShardTxCacheCalled: func(_ interface{}, nonce uint64, maxNum int, _ time.Duration) { + gotNonce = nonce + gotMaxNum = maxNum + mockCalled = true + }, + } + + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + SenderShardID: 0, + ReceiverShardID: 0, + TxHashes: [][]byte{[]byte("dummy")}, + }, + }, + } + + args := createDefaultTransactionsProcessorArgs() + args.DataPool = stub + args.TxProcessor = &testscommon.TxProcessorMock{ + ProcessTransactionCalled: func(tx *transaction.Transaction) (vmcommon.ReturnCode, error) { + return vmcommon.Ok, nil + }, + } + args.BlockSizeComputation = &testscommon.BlockSizeComputationStub{} + + txs, err := NewTransactionPreprocessor(args) + require.NoError(t, err) + txs.accountsProposal = &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return []byte("rootHash"), nil + }, + RecreateTrieIfNeededCalled: func(options common.RootHashHolder) error { + return nil + }, + } + + rootHash, err := txs.accountsProposal.RootHash() + require.NoError(t, err) + + rootHashHolder := holders.NewDefaultRootHashesHolder(rootHash) + _ = txs.RemoveTxsFromPools(body, rootHashHolder) + + assert.True(t, mockCalled) + assert.Equal(t, uint64(0), gotNonce) + assert.Equal(t, 30000, gotMaxNum) +} + +func Test_RemoveTxsFromPools(t *testing.T) { + t.Parallel() + + t.Run("if recreating the trie fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + errExpected := errors.New("expected error") + args := createDefaultTransactionsProcessorArgs() + args.AccountsProposal = &stateMock.AccountsStub{ + RecreateTrieIfNeededCalled: func(options common.RootHashHolder) error { + return errExpected + }, + } + txs, err := NewTransactionPreprocessor(args) + require.Nil(t, err) + + err = txs.RemoveTxsFromPools(&block.Body{}, holders.NewDefaultRootHashesHolder([]byte("rootHash"))) + require.Equal(t, errExpected, err) + }) + + t.Run("if removing txs from pool fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + args := createDefaultTransactionsProcessorArgs() + args.AccountsProposal = &stateMock.AccountsStub{ + RecreateTrieIfNeededCalled: func(options common.RootHashHolder) error { + return nil + }, + } + txs, err := NewTransactionPreprocessor(args) + require.Nil(t, err) + + err = txs.RemoveTxsFromPools(nil, holders.NewDefaultRootHashesHolder([]byte("rootHash"))) + require.Equal(t, process.ErrNilTxBlockBody, err) + }) +} + +func createArgsForCleanupSelfShardTxCachePreprocessor() ArgsTransactionPreProcessor { + totalGasProvided := uint64(0) + args := createDefaultTransactionsProcessorArgs() + args.DataPool, _ = dataRetrieverMock.CreateTxPool(2, 0) + args.TxProcessor = &testscommon.TxProcessorMock{ + ProcessTransactionCalled: func(transaction *transaction.Transaction) (vmcommon.ReturnCode, error) { + return 0, nil + }} + args.GasHandler = &mock.GasHandlerMock{ + SetGasProvidedCalled: func(gasProvided uint64, hash []byte) { + totalGasProvided += gasProvided + }, + TotalGasProvidedCalled: func() uint64 { + return totalGasProvided + }, + ComputeGasProvidedByTxCalled: func(txSenderShardId uint32, txReceiverShardId uint32, txHandler data.TransactionHandler) (uint64, uint64, error) { + return 0, 0, nil + }, + SetGasRefundedCalled: func(gasRefunded uint64, hash []byte) {}, + TotalGasRefundedCalled: func() uint64 { + return 0 + }, + } + + accounts := &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return []byte("rootHash"), nil + }, + GetExistingAccountCalled: func(sender []byte) (vmcommon.AccountHandler, error) { + var nonce uint64 + switch { + case bytes.Equal(sender, []byte("alice")): + nonce = 2 + case bytes.Equal(sender, []byte("bob")): + nonce = 42 + case bytes.Equal(sender, []byte("carol")): + nonce = 7 + case bytes.Equal(sender, []byte("dave")): + nonce = 110 + case bytes.Equal(sender, []byte("eve")): + nonce = 780 + default: + nonce = 0 + } + + return &stateMock.UserAccountStub{ + Nonce: nonce, + Balance: big.NewInt(1_000_000_000_000_000_000), + }, nil + }, + RecreateTrieIfNeededCalled: func(options common.RootHashHolder) error { + return nil + }, + } + args.Accounts = accounts + args.AccountsProposal = accounts + + return args +} + +func TestCleanupSelfShardTxCache_NoTransactionToSelect(t *testing.T) { + t.Parallel() + + createTx := func(sender string, nonce uint64) *transaction.Transaction { + return &transaction.Transaction{ + SndAddr: []byte(sender), + Nonce: nonce, + GasLimit: 1000, + } + } + + args := createArgsForCleanupSelfShardTxCachePreprocessor() + txs, _ := NewTransactionPreprocessor(args) + assert.NotNil(t, txs) + + err := txs.txPool.OnExecutedBlock(&block.Header{}, []byte("rootHash")) + require.NoError(t, err) + + sndShardId := uint32(0) + dstShardId := uint32(0) + strCache := process.ShardCacherIdentifier(sndShardId, dstShardId) + + sortedTxsAndHashes, _, _ := txs.computeSortedTxs(sndShardId, dstShardId, MaxGasLimitPerBlock, []byte("randomness"), haveTimeTrue) + + miniBlocks, _, err := txs.createAndProcessMiniBlocksFromMeV1(haveTimeTrue, isShardStuckFalse, isMaxBlockSizeReachedFalse, sortedTxsAndHashes) + t.Logf("createAndProcessMiniBlocksFromMeV1 returned with err = %v", err) + assert.Nil(t, err) + + txHashes := 0 + for _, miniBlock := range miniBlocks { + txHashes += len(miniBlock.TxHashes) + } + + txsToAdd := []*transaction.Transaction{ + createTx("alice", 1), + createTx("alice", 2), + createTx("alice", 3), + createTx("bob", 40), + createTx("bob", 41), + createTx("bob", 42), + createTx("carol", 7), + createTx("carol", 8), + createTx("carol", 8), + } + + for i, tx := range txsToAdd { + hash := fmt.Appendf(nil, "hash-%d", i) + args.DataPool.AddData(hash, tx, 0, strCache) + } + + assert.Equal(t, len(txsToAdd), 9) + assert.Equal(t, 9, int(txs.txPool.GetCounts().GetTotal())) + + body := &block.Body{ + MiniBlocks: miniBlocks, + } + + rootHash, err := txs.accountsProposal.RootHash() + require.NoError(t, err) + rootHashHolder := holders.NewDefaultRootHashesHolder(rootHash) + + err = txs.RemoveTxsFromPools(body, rootHashHolder) + require.NoError(t, err) + expectEvicted := 3 + actual := int(txs.txPool.GetCounts().GetTotal()) + t.Logf("expected %d evicted, remaining pool size: %d", expectEvicted, actual) + assert.Equal(t, 9-expectEvicted, actual) +} + +func TestCleanupSelfShardTxCache(t *testing.T) { + t.Parallel() + + createTx := func(sender string, nonce uint64) *transaction.Transaction { + return &transaction.Transaction{ + SndAddr: []byte(sender), + Value: big.NewInt(0), + Nonce: nonce, + GasLimit: 1000, + GasPrice: 500, + } + } + createMoreValuableTx := func(sender string, nonce uint64) *transaction.Transaction { + return &transaction.Transaction{ + SndAddr: []byte(sender), + Value: big.NewInt(0), + Nonce: nonce, + GasLimit: 1000, + GasPrice: 1000, + } + } + args := createArgsForCleanupSelfShardTxCachePreprocessor() + txs, _ := NewTransactionPreprocessor(args) + assert.NotNil(t, txs) + + err := txs.txPool.OnExecutedBlock(&block.Header{}, []byte("rootHash")) + require.NoError(t, err) + + sndShardId := uint32(0) + dstShardId := uint32(0) + strCache := process.ShardCacherIdentifier(sndShardId, dstShardId) + + txsToAdd := []*transaction.Transaction{ + createTx("alice", 1), + createTx("alice", 2), + createTx("alice", 3), + createTx("bob", 42), + createTx("bob", 43), + createTx("carol", 7), + } + + for i, tx := range txsToAdd { + hash := fmt.Appendf(nil, "hash-%d", i) + args.DataPool.AddData(hash, tx, 0, strCache) + } + + sortedTxsAndHashes, _, _ := txs.computeSortedTxs(sndShardId, dstShardId, MaxGasLimitPerBlock, []byte("randomness"), haveTimeTrue) + miniBlocks, _, err := txs.createAndProcessMiniBlocksFromMeV1(haveTimeTrue, isShardStuckFalse, isMaxBlockSizeReachedFalse, sortedTxsAndHashes) + assert.Nil(t, err) + + txHashes := 0 + for _, miniBlock := range miniBlocks { + txHashes += len(miniBlock.TxHashes) + } + + txsToAddAfterMiniblockCreation := []*transaction.Transaction{ + createTx("dave", 120), + createMoreValuableTx("dave", 100), + createTx("dave", 101), + createTx("eve", 779), + createMoreValuableTx("eve", 780), + createTx("eve", 779), + createTx("carol", 6), + createTx("carol", 6), + createMoreValuableTx("carol", 8), + } + + for i, tx := range txsToAddAfterMiniblockCreation { + hash := fmt.Appendf(nil, "hash-%d", i+len(txsToAdd)) + args.DataPool.AddData(hash, tx, 0, strCache) + } + + body := &block.Body{ + MiniBlocks: miniBlocks, + } + + assert.Equal(t, 15, int(txs.txPool.GetCounts().GetTotal())) + + rootHash, err := txs.accountsProposal.RootHash() + require.NoError(t, err) + + rootHashHolder := holders.NewDefaultRootHashesHolder(rootHash) + _ = txs.RemoveTxsFromPools(body, rootHashHolder) + for _, hash := range txs.txPool.ShardDataStore(strCache).Keys() { + txRemained, _ := txs.txPool.ShardDataStore(strCache).Peek(hash) + log.Debug("txs left in pool after RemoveTxsFromPools", + "hash", hash, + "nonce", txRemained.(*transaction.Transaction).Nonce, + "sender", string(txRemained.(*transaction.Transaction).SndAddr), + "gasPrice", txRemained.(*transaction.Transaction).GasPrice) + } + + expectEvictedByRemoveTxsFromPool := 8 // 5 selected (2,3 for alice, 42,43 for bob, 7 for carol) + lower nonces: 1 for alice, 6 *2 for carol + expectedEvictedByCleanup := 4 // nonce 779 * 2 for dave, nonce 100, 101 for eve + assert.Equal(t, 15-expectedEvictedByCleanup-expectEvictedByRemoveTxsFromPool, int(txs.txPool.GetCounts().GetTotal())) +} + func TestTransactions_CreateAndProcessMiniBlockCrossShardGasLimitAddAll(t *testing.T) { t.Parallel() totalGasProvided := uint64(0) args := createDefaultTransactionsProcessorArgs() - args.TxDataPool, _ = dataRetrieverMock.CreateTxPool(2, 0) + args.DataPool, _ = dataRetrieverMock.CreateTxPool(2, 0) args.TxProcessor = &testscommon.TxProcessorMock{ ProcessTransactionCalled: func(transaction *transaction.Transaction) (vmcommon.ReturnCode, error) { return 0, nil @@ -672,7 +1110,10 @@ func TestTransactions_CreateAndProcessMiniBlockCrossShardGasLimitAddAll(t *testi return 0 }, } - args.Accounts = &stateMock.AccountsStub{ + accounts := &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return []byte("rootHash"), nil + }, GetExistingAccountCalled: func(_ []byte) (vmcommon.AccountHandler, error) { return &stateMock.UserAccountStub{ Nonce: 42, @@ -680,25 +1121,30 @@ func TestTransactions_CreateAndProcessMiniBlockCrossShardGasLimitAddAll(t *testi }, nil }, } + args.Accounts = accounts + args.AccountsProposal = accounts txs, _ := NewTransactionPreprocessor(args) assert.NotNil(t, txs) + err := txs.txPool.OnExecutedBlock(&block.Header{}, []byte("rootHash")) + require.NoError(t, err) + sndShardId := uint32(0) - dstShardId := uint32(1) + dstShardId := uint32(0) strCache := process.ShardCacherIdentifier(sndShardId, dstShardId) addedTxs := make([]*transaction.Transaction, 0) for i := 0; i < 10; i++ { - newTx := &transaction.Transaction{GasLimit: uint64(i), Nonce: 42 + uint64(i)} + newTx := &transaction.Transaction{Value: big.NewInt(0), GasLimit: uint64(i), Nonce: 42 + uint64(i)} txHash, _ := core.CalculateHash(args.Marshalizer, args.Hasher, newTx) - args.TxDataPool.AddData(txHash, newTx, newTx.Size(), strCache) + args.DataPool.AddData(txHash, newTx, newTx.Size(), strCache) addedTxs = append(addedTxs, newTx) } - sortedTxsAndHashes, _, _ := txs.computeSortedTxs(sndShardId, dstShardId, MaxGasLimitPerBlock, []byte("randomness")) + sortedTxsAndHashes, _, _ := txs.computeSortedTxs(sndShardId, dstShardId, MaxGasLimitPerBlock, []byte("randomness"), haveTimeTrue) miniBlocks, _, err := txs.createAndProcessMiniBlocksFromMeV1(haveTimeTrue, isShardStuckFalse, isMaxBlockSizeReachedFalse, sortedTxsAndHashes) assert.Nil(t, err) @@ -734,35 +1180,47 @@ func TestTransactions_CreateAndProcessMiniBlockCrossShardGasLimitAddAllAsNoSCCal return 0 }, } - args.Accounts = &stateMock.AccountsStub{ + accounts := &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return []byte("rootHash"), nil + }, GetExistingAccountCalled: func(_ []byte) (vmcommon.AccountHandler, error) { return &stateMock.UserAccountStub{ Nonce: 42, Balance: big.NewInt(1000000000000000000), }, nil }, + RecreateTrieIfNeededCalled: func(options common.RootHashHolder) error { + return nil + }, } - args.TxDataPool, _ = dataRetrieverMock.CreateTxPool(2, 0) + args.Accounts = accounts + args.AccountsProposal = accounts + + args.DataPool, _ = dataRetrieverMock.CreateTxPool(2, 0) txs, _ := NewTransactionPreprocessor(args) assert.NotNil(t, txs) + err := txs.txPool.OnExecutedBlock(&block.Header{}, []byte("rootHash")) + require.NoError(t, err) + sndShardId := uint32(0) dstShardId := uint32(1) strCache := process.ShardCacherIdentifier(sndShardId, dstShardId) - gasLimit := MaxGasLimitPerBlock / uint64(5) + gasLimit := MaxGasLimitPerBlock / uint64(10) addedTxs := make([]*transaction.Transaction, 0) for i := 0; i < 10; i++ { - newTx := &transaction.Transaction{GasLimit: gasLimit, GasPrice: uint64(i), RcvAddr: []byte("012345678910"), Nonce: 42 + uint64(i)} + newTx := &transaction.Transaction{Value: big.NewInt(0), GasLimit: gasLimit, GasPrice: uint64(i), RcvAddr: []byte("012345678910"), Nonce: 42 + uint64(i)} txHash, _ := core.CalculateHash(args.Marshalizer, args.Hasher, newTx) - args.TxDataPool.AddData(txHash, newTx, newTx.Size(), strCache) + args.DataPool.AddData(txHash, newTx, newTx.Size(), strCache) addedTxs = append(addedTxs, newTx) } - sortedTxsAndHashes, _, _ := txs.computeSortedTxs(sndShardId, dstShardId, MaxGasLimitPerBlock, []byte("randomness")) + sortedTxsAndHashes, _, _ := txs.computeSortedTxs(sndShardId, dstShardId, MaxGasLimitPerBlock, []byte("randomness"), haveTimeTrue) miniBlocks, _, err := txs.createAndProcessMiniBlocksFromMeV1(haveTimeTrue, isShardStuckFalse, isMaxBlockSizeReachedFalse, sortedTxsAndHashes) assert.Nil(t, err) @@ -782,7 +1240,7 @@ func TestTransactions_CreateAndProcessMiniBlockCrossShardGasLimitAddOnly5asSCCal totalGasProvided := uint64(0) args := createDefaultTransactionsProcessorArgs() - args.TxDataPool, _ = dataRetrieverMock.CreateTxPool(2, 0) + args.DataPool, _ = dataRetrieverMock.CreateTxPool(2, 0) args.TxProcessor = &testscommon.TxProcessorMock{ ProcessTransactionCalled: func(transaction *transaction.Transaction) (vmcommon.ReturnCode, error) { return 0, nil @@ -807,7 +1265,10 @@ func TestTransactions_CreateAndProcessMiniBlockCrossShardGasLimitAddOnly5asSCCal RemoveGasRefundedCalled: func(hashes [][]byte) { }, } - args.Accounts = &stateMock.AccountsStub{ + accounts := &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return []byte("rootHash"), nil + }, GetExistingAccountCalled: func(_ []byte) (vmcommon.AccountHandler, error) { return &stateMock.UserAccountStub{ Nonce: 42, @@ -815,24 +1276,29 @@ func TestTransactions_CreateAndProcessMiniBlockCrossShardGasLimitAddOnly5asSCCal }, nil }, } + args.Accounts = accounts + args.AccountsProposal = accounts txs, _ := NewTransactionPreprocessor(args) assert.NotNil(t, txs) + err := txs.txPool.OnExecutedBlock(&block.Header{}, []byte("rootHash")) + require.NoError(t, err) + sndShardId := uint32(0) dstShardId := uint32(1) strCache := process.ShardCacherIdentifier(sndShardId, dstShardId) scAddress, _ := hex.DecodeString("000000000000000000005fed9c659422cd8429ce92f8973bba2a9fb51e0eb3a1") for i := 0; i < 10; i++ { - newTx := &transaction.Transaction{GasLimit: gasLimit, GasPrice: uint64(i), RcvAddr: scAddress, Nonce: 42 + uint64(i)} + newTx := &transaction.Transaction{Value: big.NewInt(0), GasLimit: gasLimit, GasPrice: uint64(i), RcvAddr: scAddress, Nonce: 42 + uint64(i)} txHash, _ := core.CalculateHash(args.Marshalizer, args.Hasher, newTx) - args.TxDataPool.AddData(txHash, newTx, newTx.Size(), strCache) + args.DataPool.AddData(txHash, newTx, newTx.Size(), strCache) } - sortedTxsAndHashes, _, _ := txs.computeSortedTxs(sndShardId, dstShardId, MaxGasLimitPerBlock, []byte("randomness")) + sortedTxsAndHashes, _, _ := txs.computeSortedTxs(sndShardId, dstShardId, MaxGasLimitPerBlock, []byte("randomness"), haveTimeTrue) miniBlocks, _, err := txs.createAndProcessMiniBlocksFromMeV1(haveTimeTrue, isShardStuckFalse, isMaxBlockSizeReachedFalse, sortedTxsAndHashes) assert.Nil(t, err) @@ -863,7 +1329,10 @@ func TestTransactions_IsDataPrepared_NumMissingTxsGreaterThanZeroTxNotReceivedSh haveTimeShorter := func() time.Duration { return time.Millisecond } - err := txs.IsDataPrepared(2, haveTimeShorter) + + txHashesMissing := [][]byte{[]byte("missing_tx_hash"), []byte("missing_tx_hash2")} + txs.SetMissingTxs(len(txHashesMissing)) + err := txs.IsDataPrepared(len(txHashesMissing), haveTimeShorter) assert.Equal(t, process.ErrTimeIsOut, err) } @@ -985,7 +1454,6 @@ func Example_sortTransactionsBySenderAndNonce() { } sortTransactionsBySenderAndNonceLegacy(txs) - for _, item := range txs { fmt.Println(item.Tx.GetNonce(), string(item.Tx.GetSndAddr()), string(item.TxHash)) } @@ -1072,7 +1540,6 @@ func Example_sortTransactionsBySenderAndNonceWithFrontRunningProtection() { } txPreproc.sortTransactionsBySenderAndNonceWithFrontRunningProtection(txs, []byte(randomness)) - for _, item := range txs { fmt.Println(item.Tx.GetNonce(), hex.EncodeToString(item.Tx.GetSndAddr()), string(item.TxHash)) } @@ -1145,7 +1612,7 @@ func BenchmarkSortTransactionsByNonceAndSender_WhenReversedNoncesWithFrontRunnin func createGoodPreprocessor(dataPool dataRetriever.PoolsHolder) *transactions { args := createDefaultTransactionsProcessorArgs() - args.TxDataPool = dataPool.Transactions() + args.DataPool = dataPool.Transactions() preprocessor, _ := NewTransactionPreprocessor(args) return preprocessor @@ -1268,65 +1735,141 @@ func TestTransactionsPreprocessor_ProcessMiniBlockShouldWork(t *testing.T) { } }, } - nbTxsProcessed := 0 - maxBlockSize := 16 - args := createDefaultTransactionsProcessorArgs() - args.TxProcessor = &testscommon.TxProcessorMock{ - ProcessTransactionCalled: func(transaction *transaction.Transaction) (vmcommon.ReturnCode, error) { - nbTxsProcessed++ - return vmcommon.Ok, nil - }, - } - args.BlockSizeComputation = &testscommon.BlockSizeComputationStub{ - IsMaxBlockSizeWithoutThrottleReachedCalled: func(mbs int, txs int) bool { - return mbs+txs > maxBlockSize - }, - } - args.TxDataPool = tdp.Transactions() - txs, err := NewTransactionPreprocessor(args) - assert.NotNil(t, txs) - assert.Nil(t, err) + t.Run("no async execution", func(t *testing.T) { + nbTxsProcessed := 0 + maxBlockSize := 16 + args := createDefaultTransactionsProcessorArgs() + args.TxProcessor = &testscommon.TxProcessorMock{ + ProcessTransactionCalled: func(transaction *transaction.Transaction) (vmcommon.ReturnCode, error) { + nbTxsProcessed++ + return vmcommon.Ok, nil + }, + } + args.BlockSizeComputation = &testscommon.BlockSizeComputationStub{ + IsMaxBlockSizeWithoutThrottleReachedCalled: func(mbs int, txs int) bool { + return mbs+txs > maxBlockSize + }, + } + args.DataPool = tdp.Transactions() + txs, err := NewTransactionPreprocessor(args) - txHashes := make([][]byte, 0) - txHashes = append(txHashes, []byte("tx_hash1"), []byte("tx_hash2"), []byte("tx_hash3")) + assert.NotNil(t, txs) + assert.Nil(t, err) - miniBlock := &block.MiniBlock{ - ReceiverShardID: 0, - SenderShardID: 1, - TxHashes: txHashes, - Type: block.TxBlock, - } + txHashes := make([][]byte, 0) + txHashes = append(txHashes, []byte("tx_hash1"), []byte("tx_hash2"), []byte("tx_hash3")) - f := func() (int, int) { - if nbTxsProcessed == 0 { - return 0, 0 + miniBlock := &block.MiniBlock{ + ReceiverShardID: 0, + SenderShardID: 1, + TxHashes: txHashes, + Type: block.TxBlock, } - return nbTxsProcessed + 1, nbTxsProcessed * common.AdditionalScrForEachScCallOrSpecialTx - } - preProcessorExecutionInfoHandlerMock := &testscommon.PreProcessorExecutionInfoHandlerMock{ - GetNumOfCrossInterMbsAndTxsCalled: f, - } - txsToBeReverted, indexOfLastTxProcessed, _, err := txs.ProcessMiniBlock(miniBlock, haveTimeTrue, haveAdditionalTimeFalse, false, false, -1, preProcessorExecutionInfoHandlerMock) - assert.Equal(t, process.ErrMaxBlockSizeReached, err) - assert.Equal(t, 3, len(txsToBeReverted)) - assert.Equal(t, 2, indexOfLastTxProcessed) + f := func() (int, int) { + if nbTxsProcessed == 0 { + return 0, 0 + } + return nbTxsProcessed + 1, nbTxsProcessed * common.AdditionalScrForEachScCallOrSpecialTx + } + preProcessorExecutionInfoHandlerMock := &testscommon.PreProcessorExecutionInfoHandlerMock{ + GetNumOfCrossInterMbsAndTxsCalled: f, + } + txsToBeReverted, indexOfLastTxProcessed, _, err := txs.ProcessMiniBlock(miniBlock, haveTimeTrue, haveAdditionalTimeFalse, false, false, -1, preProcessorExecutionInfoHandlerMock) + + assert.Equal(t, process.ErrMaxBlockSizeReached, err) + assert.Equal(t, 3, len(txsToBeReverted)) + assert.Equal(t, 2, indexOfLastTxProcessed) - f = func() (int, int) { - if nbTxsProcessed == 0 { - return 0, 0 + f = func() (int, int) { + if nbTxsProcessed == 0 { + return 0, 0 + } + return nbTxsProcessed, nbTxsProcessed * common.AdditionalScrForEachScCallOrSpecialTx } - return nbTxsProcessed, nbTxsProcessed * common.AdditionalScrForEachScCallOrSpecialTx - } - preProcessorExecutionInfoHandlerMock = &testscommon.PreProcessorExecutionInfoHandlerMock{ - GetNumOfCrossInterMbsAndTxsCalled: f, - } - txsToBeReverted, indexOfLastTxProcessed, _, err = txs.ProcessMiniBlock(miniBlock, haveTimeTrue, haveAdditionalTimeFalse, false, false, -1, preProcessorExecutionInfoHandlerMock) + preProcessorExecutionInfoHandlerMock = &testscommon.PreProcessorExecutionInfoHandlerMock{ + GetNumOfCrossInterMbsAndTxsCalled: f, + } + txsToBeReverted, indexOfLastTxProcessed, _, err = txs.ProcessMiniBlock(miniBlock, haveTimeTrue, haveAdditionalTimeFalse, false, false, -1, preProcessorExecutionInfoHandlerMock) - assert.Nil(t, err) - assert.Equal(t, 0, len(txsToBeReverted)) - assert.Equal(t, 2, indexOfLastTxProcessed) + assert.Nil(t, err) + assert.Equal(t, 0, len(txsToBeReverted)) + assert.Equal(t, 2, indexOfLastTxProcessed) + }) + + t.Run("with async execution", func(t *testing.T) { + nbTxsProcessed := 0 + args := createDefaultTransactionsProcessorArgs() + args.TxProcessor = &testscommon.TxProcessorMock{ + ProcessTransactionCalled: func(transaction *transaction.Transaction) (vmcommon.ReturnCode, error) { + nbTxsProcessed++ + return vmcommon.Ok, nil + }, + } + args.BlockSizeComputation = &testscommon.BlockSizeComputationStub{ + IsMaxBlockSizeWithoutThrottleReachedCalled: func(mbs int, txs int) bool { + require.Fail(t, "should not have been called") + return false + }, + } + args.DataPool = tdp.Transactions() + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return true + }, + } + args.EnableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledCalled: func(flag common.EnableRoundFlag) bool { + return true + }, + } + + txs, err := NewTransactionPreprocessor(args) + + assert.NotNil(t, txs) + assert.Nil(t, err) + + txHashes := make([][]byte, 0) + txHashes = append(txHashes, []byte("tx_hash1"), []byte("tx_hash2"), []byte("tx_hash3")) + + miniBlock := &block.MiniBlock{ + ReceiverShardID: 0, + SenderShardID: 1, + TxHashes: txHashes, + Type: block.TxBlock, + } + + f := func() (int, int) { + if nbTxsProcessed == 0 { + return 0, 0 + } + return nbTxsProcessed + 1, nbTxsProcessed * common.AdditionalScrForEachScCallOrSpecialTx + } + preProcessorExecutionInfoHandlerMock := &testscommon.PreProcessorExecutionInfoHandlerMock{ + GetNumOfCrossInterMbsAndTxsCalled: f, + } + txsToBeReverted, indexOfLastTxProcessed, _, err := txs.ProcessMiniBlock(miniBlock, haveTimeTrue, haveAdditionalTimeFalse, false, false, -1, preProcessorExecutionInfoHandlerMock) + + assert.Nil(t, err) + assert.Equal(t, 0, len(txsToBeReverted)) + assert.Equal(t, 2, indexOfLastTxProcessed) + + f = func() (int, int) { + if nbTxsProcessed == 0 { + return 0, 0 + } + return nbTxsProcessed, nbTxsProcessed * common.AdditionalScrForEachScCallOrSpecialTx + } + preProcessorExecutionInfoHandlerMock = &testscommon.PreProcessorExecutionInfoHandlerMock{ + GetNumOfCrossInterMbsAndTxsCalled: f, + } + txsToBeReverted, indexOfLastTxProcessed, _, err = txs.ProcessMiniBlock(miniBlock, haveTimeTrue, haveAdditionalTimeFalse, false, false, -1, preProcessorExecutionInfoHandlerMock) + + assert.Nil(t, err) + assert.Equal(t, 0, len(txsToBeReverted)) + assert.Equal(t, 2, indexOfLastTxProcessed) + }) } func TestTransactionsPreprocessor_ProcessMiniBlockShouldErrMaxGasLimitUsedForDestMeTxsIsReached(t *testing.T) { @@ -1352,7 +1895,7 @@ func TestTransactionsPreprocessor_ProcessMiniBlockShouldErrMaxGasLimitUsedForDes args := createDefaultTransactionsProcessorArgs() enableEpochsHandlerStub := enableEpochsHandlerMock.NewEnableEpochsHandlerStub() args.EnableEpochsHandler = enableEpochsHandlerStub - args.TxDataPool = tdp.Transactions() + args.DataPool = tdp.Transactions() args.GasHandler = &mock.GasHandlerMock{ ComputeGasProvidedByTxCalled: func(txSenderShardId uint32, txReceiverSharedId uint32, txHandler data.TransactionHandler) (uint64, uint64, error) { return 0, MaxGasLimitPerBlock * maxGasLimitPercentUsedForDestMeTxs / 100, nil @@ -1401,7 +1944,7 @@ func TestTransactionsPreprocessor_ComputeGasProvidedShouldWork(t *testing.T) { txGasLimitInReceiver := maxGasLimit args := createDefaultTransactionsProcessorArgs() args.EconomicsFee = &economicsmocks.EconomicsHandlerMock{ - MaxGasLimitPerBlockCalled: func(_ uint32) uint64 { + MaxGasLimitPerBlockInEpochCalled: func(_ uint32, _ uint32) uint64 { return maxGasLimit }, } @@ -1428,6 +1971,7 @@ func TestTransactionsPreprocessor_ComputeGasProvidedShouldWork(t *testing.T) { &tx, txHash, &gasInfo, + false, ) assert.Nil(t, err) @@ -1467,12 +2011,13 @@ func TestTransactionsPreprocessor_SplitMiniBlocksIfNeededShouldWork(t *testing.T tx4 := transaction.Transaction{Nonce: 3, GasLimit: txGasLimit} tx5 := transaction.Transaction{Nonce: 4, GasLimit: txGasLimit} tx6 := transaction.Transaction{Nonce: 5, GasLimit: txGasLimit} - preprocessor.txsForCurrBlock.txHashAndInfo["hash1"] = &txInfo{tx: &tx1} - preprocessor.txsForCurrBlock.txHashAndInfo["hash2"] = &txInfo{tx: &tx2} - preprocessor.txsForCurrBlock.txHashAndInfo["hash3"] = &txInfo{tx: &tx3} - preprocessor.txsForCurrBlock.txHashAndInfo["hash4"] = &txInfo{tx: &tx4} - preprocessor.txsForCurrBlock.txHashAndInfo["hash5"] = &txInfo{tx: &tx5} - preprocessor.txsForCurrBlock.txHashAndInfo["hash6"] = &txInfo{tx: &tx6} + tfb := preprocessor.txsForCurrBlock.(*txsForBlock) + tfb.txHashAndInfo["hash1"] = &process.TxInfo{Tx: &tx1} + tfb.txHashAndInfo["hash2"] = &process.TxInfo{Tx: &tx2} + tfb.txHashAndInfo["hash3"] = &process.TxInfo{Tx: &tx3} + tfb.txHashAndInfo["hash4"] = &process.TxInfo{Tx: &tx4} + tfb.txHashAndInfo["hash5"] = &process.TxInfo{Tx: &tx5} + tfb.txHashAndInfo["hash6"] = &process.TxInfo{Tx: &tx6} miniBlocks := make([]*block.MiniBlock, 0) @@ -1526,6 +2071,7 @@ func TestTransactionsPreProcessor_preFilterTransactionsNoBandwidth(t *testing.T) shardCoordinator: mock.NewMultiShardsCoordinatorMock(3), economicsFee: economicsFee, gasHandler: gasHandler, + gasEpochState: &testscommon.GasEpochStateHandlerStub{}, }, }, } @@ -1575,6 +2121,7 @@ func TestTransactionsPreProcessor_preFilterTransactionsLimitedBandwidthMultipleT shardCoordinator: mock.NewMultiShardsCoordinatorMock(3), economicsFee: economicsFee, gasHandler: gasHandler, + gasEpochState: &testscommon.GasEpochStateHandlerStub{}, }, }, } @@ -1636,6 +2183,7 @@ func TestTransactionsPreProcessor_preFilterTransactionsLimitedBandwidthMultipleT shardCoordinator: mock.NewMultiShardsCoordinatorMock(3), economicsFee: economicsFee, gasHandler: gasHandler, + gasEpochState: &testscommon.GasEpochStateHandlerStub{}, }, }, } @@ -1705,6 +2253,7 @@ func TestTransactionsPreProcessor_preFilterTransactionsHighBandwidth(t *testing. shardCoordinator: mock.NewMultiShardsCoordinatorMock(3), economicsFee: economicsFee, gasHandler: gasHandler, + gasEpochState: &testscommon.GasEpochStateHandlerStub{}, }, }, } @@ -1766,6 +2315,7 @@ func TestTransactionsPreProcessor_getRemainingGasPerBlock(t *testing.T) { shardCoordinator: mock.NewMultiShardsCoordinatorMock(3), economicsFee: economicsFee, gasHandler: gasHandler, + gasEpochState: &testscommon.GasEpochStateHandlerStub{}, }, enableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), }, @@ -1796,6 +2346,7 @@ func TestTransactionsPreProcessor_getRemainingGasPerBlockAsScheduled(t *testing. shardCoordinator: mock.NewMultiShardsCoordinatorMock(3), economicsFee: economicsFee, gasHandler: gasHandler, + gasEpochState: &testscommon.GasEpochStateHandlerStub{}, }, }, } @@ -1867,7 +2418,8 @@ func TestTxsPreprocessor_AddTxsFromMiniBlocksShouldWork(t *testing.T) { } txs.AddTxsFromMiniBlocks(mbs) - assert.Equal(t, 2, len(txs.txsForCurrBlock.txHashAndInfo)) + tfb := txs.txsForCurrBlock.(*txsForBlock) + assert.Equal(t, 2, len(tfb.txHashAndInfo)) } func TestTransactions_AddTransactions(t *testing.T) { @@ -1887,7 +2439,8 @@ func TestTransactions_AddTransactions(t *testing.T) { } txPreproc, _ := NewTransactionPreprocessor(args) txPreproc.AddTransactions(txs) - require.Empty(t, &txPreproc.txsForCurrBlock.txHashAndInfo) + tfb := txPreproc.txsForCurrBlock.(*txsForBlock) + require.Empty(t, &tfb.txHashAndInfo) }) t.Run("should add txs", func(t *testing.T) { @@ -1897,7 +2450,8 @@ func TestTransactions_AddTransactions(t *testing.T) { txs := []data.TransactionHandler{tx1, tx2} txPreproc, _ := NewTransactionPreprocessor(args) txPreproc.AddTransactions(txs) - numTxsSaved := len(txPreproc.txsForCurrBlock.txHashAndInfo) + tfb := txPreproc.txsForCurrBlock.(*txsForBlock) + numTxsSaved := len(tfb.txHashAndInfo) require.Equal(t, 2, numTxsSaved) }) } @@ -2000,6 +2554,7 @@ func TestTransactions_ComputeCacheIdentifier(t *testing.T) { basePreProcess: &basePreProcess{ gasTracker: gasTracker{ shardCoordinator: coordinator, + gasEpochState: &testscommon.GasEpochStateHandlerStub{}, }, enableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(common.ScheduledMiniBlocksFlag), }, @@ -2043,7 +2598,7 @@ func TestTransactions_RestoreBlockDataIntoPools(t *testing.T) { t.Parallel() args := createDefaultTransactionsProcessorArgs() - args.TxDataPool = testscommon.NewShardedDataCacheNotifierMock() + args.DataPool = testscommon.NewShardedDataCacheNotifierMock() args.ShardCoordinator, _ = sharding.NewMultiShardCoordinator(3, 1) args.Store = genericMocks.NewChainStorerMock(0) txs, _ := NewTransactionPreprocessor(args) @@ -2087,11 +2642,11 @@ func TestTransactions_RestoreBlockDataIntoPools(t *testing.T) { assert.Equal(t, 6, numRestored) assert.Equal(t, 1, len(mbPool.Keys())) // only 1 mb is cross shard where destination is me - assert.Equal(t, 4, len(args.TxDataPool.ShardDataStore("1").Keys())) // intrashard + invalid - assert.Equal(t, 2, len(args.TxDataPool.ShardDataStore("2_1").Keys())) + assert.Equal(t, 4, len(args.DataPool.ShardDataStore("1").Keys())) // intrashard + invalid + assert.Equal(t, 2, len(args.DataPool.ShardDataStore("2_1").Keys())) }) - args.TxDataPool.Clear() + args.DataPool.Clear() mbPool.Clear() t.Run("feat scheduled activated", func(t *testing.T) { @@ -2102,9 +2657,9 @@ func TestTransactions_RestoreBlockDataIntoPools(t *testing.T) { assert.Equal(t, 6, numRestored) assert.Equal(t, 1, len(mbPool.Keys())) // the cross miniblock - assert.Equal(t, 2, len(args.TxDataPool.ShardDataStore("1").Keys())) // intrashard - assert.Equal(t, 2, len(args.TxDataPool.ShardDataStore("1_0").Keys())) // invalid - assert.Equal(t, 2, len(args.TxDataPool.ShardDataStore("2_1").Keys())) + assert.Equal(t, 2, len(args.DataPool.ShardDataStore("1").Keys())) // intrashard + assert.Equal(t, 2, len(args.DataPool.ShardDataStore("1_0").Keys())) // invalid + assert.Equal(t, 2, len(args.DataPool.ShardDataStore("2_1").Keys())) }) } @@ -2269,3 +2824,63 @@ func TestTransactions_getIndexesOfLastTxProcessed(t *testing.T) { assert.Equal(t, mbh.GetIndexOfLastTxProcessed(), pi.indexOfLastTxProcessedByProposer) }) } + +func Test_SelectOutgoingTransactions(t *testing.T) { + t.Parallel() + + t.Run("if selectTransactionsFromTxPoolForProposal fails the error should be propagated", func(t *testing.T) { + t.Parallel() + + args := createDefaultTransactionsProcessorArgs() + txs, _ := NewTransactionPreprocessor(args) + txs.txPool = &testscommon.ShardedDataStub{ + ShardDataStoreCalled: func(cacheID string) storage.Cacher { + return nil + }, + } + + _, _, err := txs.SelectOutgoingTransactions(0, 0, haveTimeTrue) + require.Equal(t, process.ErrNilTxDataPool, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + args := createArgsForCleanupSelfShardTxCachePreprocessor() + txs, _ := NewTransactionPreprocessor(args) + + err := txs.txPool.OnExecutedBlock(&block.Header{}, []byte("rootHash")) + require.NoError(t, err) + + sndShardId := uint32(0) + dstShardId := uint32(0) + strCache := process.ShardCacherIdentifier(sndShardId, dstShardId) + + txsToAdd := []*transaction.Transaction{ + { + SndAddr: []byte("alice"), + Value: big.NewInt(0), + Nonce: 2, + GasLimit: 1000, + GasPrice: 500, + }, + { + SndAddr: []byte("bob"), + Value: big.NewInt(0), + Nonce: 42, + GasLimit: 1000, + GasPrice: 500, + }, + } + + for i, tx := range txsToAdd { + hash := fmt.Appendf(nil, "hash-%d", i) + args.DataPool.AddData(hash, tx, 0, strCache) + } + + txHashes, txInstances, err := txs.SelectOutgoingTransactions(MaxGasLimitPerBlock, 0, haveTimeTrue) + require.NoError(t, err) + require.Equal(t, 2, len(txHashes)) + require.Equal(t, 2, len(txInstances)) + }) +} diff --git a/process/block/preprocess/txsForBlock.go b/process/block/preprocess/txsForBlock.go new file mode 100644 index 00000000000..5df33f1098b --- /dev/null +++ b/process/block/preprocess/txsForBlock.go @@ -0,0 +1,270 @@ +package preprocess + +import ( + "sync" + "time" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + + "github.com/multiversx/mx-chain-go/dataRetriever" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/sharding" +) + +// txsForBlock holds the information about the missing and existing transactions required for a block. +type txsForBlock struct { + shardCoordinator sharding.Coordinator + numMissingTxs int + mutTxsForBlock sync.RWMutex + txHashAndInfo map[string]*process.TxInfo + chRcvAllTxs chan bool +} + +// NewTxsForBlock creates a new instance of txsForBlock +func NewTxsForBlock(shardCoordinator sharding.Coordinator) (*txsForBlock, error) { + if check.IfNil(shardCoordinator) { + return nil, process.ErrNilShardCoordinator + } + return &txsForBlock{ + shardCoordinator: shardCoordinator, + numMissingTxs: 0, + mutTxsForBlock: sync.RWMutex{}, + txHashAndInfo: make(map[string]*process.TxInfo), + chRcvAllTxs: make(chan bool), + }, nil +} + +// Reset resets the state of txsForBlock, clearing the missing transactions count and the transaction hash map. +func (tfb *txsForBlock) Reset() { + _ = core.EmptyChannel(tfb.chRcvAllTxs) + + tfb.mutTxsForBlock.Lock() + defer tfb.mutTxsForBlock.Unlock() + tfb.numMissingTxs = 0 + tfb.txHashAndInfo = make(map[string]*process.TxInfo) +} + +// GetTxInfoByHash retrieves the transaction information by its hash. +func (tfb *txsForBlock) GetTxInfoByHash(hash []byte) (*process.TxInfo, bool) { + tfb.mutTxsForBlock.RLock() + defer tfb.mutTxsForBlock.RUnlock() + + value, ok := tfb.txHashAndInfo[string(hash)] + return value, ok +} + +// GetAllCurrentUsedTxs returns all the transactions used at current creation / processing +func (tfb *txsForBlock) GetAllCurrentUsedTxs() map[string]data.TransactionHandler { + tfb.mutTxsForBlock.RLock() + txsPool := make(map[string]data.TransactionHandler, len(tfb.txHashAndInfo)) + for txHash, txInfoFromMap := range tfb.txHashAndInfo { + txsPool[txHash] = txInfoFromMap.Tx + } + tfb.mutTxsForBlock.RUnlock() + + return txsPool +} + +// ReceivedTransaction updates the transaction information in the txsForBlock instance when a transaction is received. +func (tfb *txsForBlock) ReceivedTransaction( + txHash []byte, + tx data.TransactionHandler, +) { + tfb.mutTxsForBlock.Lock() + defer tfb.mutTxsForBlock.Unlock() + + if tfb.numMissingTxs <= 0 { + return + } + + txInfoForHash := tfb.txHashAndInfo[string(txHash)] + if txInfoForHash != nil && txInfoForHash.TxShardInfo != nil && + (txInfoForHash.Tx == nil || txInfoForHash.Tx.IsInterfaceNil()) { + tfb.txHashAndInfo[string(txHash)].Tx = tx + tfb.numMissingTxs-- + } + + if tfb.numMissingTxs == 0 { + go func() { + tfb.chRcvAllTxs <- true + }() + } +} + +// AddTransaction adds a transaction to the txsForBlock instance with its associated sender and receiver shard IDs. +func (tfb *txsForBlock) AddTransaction( + txHash []byte, + tx data.TransactionHandler, + senderShardID uint32, + receiverShardID uint32, +) { + if check.IfNil(tx) { + return + } + txShardInfoToSet := &process.TxShardInfo{SenderShardID: senderShardID, ReceiverShardID: receiverShardID} + tfb.mutTxsForBlock.Lock() + tfb.txHashAndInfo[string(txHash)] = &process.TxInfo{Tx: tx, TxShardInfo: txShardInfoToSet} + tfb.mutTxsForBlock.Unlock() +} + +// HasMissingTransactions checks if there are any missing transactions in the txsForBlock instance. +func (tfb *txsForBlock) HasMissingTransactions() bool { + tfb.mutTxsForBlock.RLock() + defer tfb.mutTxsForBlock.RUnlock() + + return tfb.numMissingTxs > 0 +} + +// GetMissingTxsCount returns the count of missing transactions in the txsForBlock instance. +func (tfb *txsForBlock) GetMissingTxsCount() int { + tfb.mutTxsForBlock.RLock() + defer tfb.mutTxsForBlock.RUnlock() + + return tfb.numMissingTxs +} + +// ComputeExistingAndRequestMissing processes the block body to compute existing transactions (in the node tx pool) and request missing ones. +func (tfb *txsForBlock) ComputeExistingAndRequestMissing( + body *block.Body, + isMiniBlockCorrect func(block.Type) bool, + txPool dataRetriever.ShardedDataCacherNotifier, + onRequestTxs func(shardID uint32, txHashes [][]byte), +) int { + if check.IfNil(body) { + return 0 + } + + tfb.mutTxsForBlock.Lock() + defer tfb.mutTxsForBlock.Unlock() + + missingTxsForShard := make(map[uint32][][]byte, tfb.shardCoordinator.NumberOfShards()) + missingTxHashes := make([][]byte, 0) + uniqueTxHashes := make(map[string]struct{}) + for i := 0; i < len(body.MiniBlocks); i++ { + miniBlock := body.MiniBlocks[i] + if !isMiniBlockCorrect(miniBlock.Type) { + continue + } + + txShardInfoObject := &process.TxShardInfo{SenderShardID: miniBlock.SenderShardID, ReceiverShardID: miniBlock.ReceiverShardID} + // TODO refactor this section + method := process.SearchMethodJustPeek + if miniBlock.Type == block.InvalidBlock { + method = process.SearchMethodSearchFirst + } + if miniBlock.Type == block.SmartContractResultBlock { + method = process.SearchMethodPeekWithFallbackSearchFirst + } + + missingTxHashesInMiniBlock := tfb.updateExistingAndComputeMissingTxsInMiniBlockNoLock(miniBlock, uniqueTxHashes, txPool, method, txShardInfoObject) + missingTxHashes = append(missingTxHashes, missingTxHashesInMiniBlock...) + if len(missingTxHashes) > 0 { + tfb.setMissingTxsForShardNoLock(miniBlock.SenderShardID, miniBlock.ReceiverShardID, missingTxHashes) + missingTxsForShard[miniBlock.SenderShardID] = append(missingTxsForShard[miniBlock.SenderShardID], missingTxHashes...) + } + + missingTxHashes = make([][]byte, 0) + } + + return tfb.requestMissingTxsForShardNoLock(missingTxsForShard, onRequestTxs) +} + +func (tfb *txsForBlock) updateExistingAndComputeMissingTxsInMiniBlockNoLock( + miniBlock *block.MiniBlock, + uniqueTxHashes map[string]struct{}, + txPool dataRetriever.ShardedDataCacherNotifier, + method process.ShardedCacheSearchMethod, + txShardInfoObject *process.TxShardInfo, +) [][]byte { + missingTxHashes := make([][]byte, 0) + for j := 0; j < len(miniBlock.TxHashes); j++ { + txHash := miniBlock.TxHashes[j] + + _, isAlreadyEvaluated := uniqueTxHashes[string(txHash)] + if isAlreadyEvaluated { + continue + } + uniqueTxHashes[string(txHash)] = struct{}{} + + tx, err := process.GetTransactionHandlerFromPool( + miniBlock.SenderShardID, + miniBlock.ReceiverShardID, + txHash, + txPool, + method) + + if err != nil { + missingTxHashes = append(missingTxHashes, txHash) + tfb.numMissingTxs++ + log.Trace("missing tx", + "miniblock type", miniBlock.Type, + "sender", miniBlock.SenderShardID, + "receiver", miniBlock.ReceiverShardID, + "hash", txHash, + ) + continue + } + + tfb.txHashAndInfo[string(txHash)] = &process.TxInfo{Tx: tx, TxShardInfo: txShardInfoObject} + } + + return missingTxHashes +} + +// this method should be called only under the mutex protection: forBlock.mutTxsForBlock +func (tfb *txsForBlock) setMissingTxsForShardNoLock( + senderShardID uint32, + receiverShardID uint32, + txHashes [][]byte, +) { + txShardInfoToSet := &process.TxShardInfo{ + SenderShardID: senderShardID, + ReceiverShardID: receiverShardID, + } + + for _, txHash := range txHashes { + tfb.txHashAndInfo[string(txHash)] = &process.TxInfo{ + Tx: nil, + TxShardInfo: txShardInfoToSet, + } + } +} + +// this method should be called only under the mutex protection: forBlock.mutTxsForBlock +func (tfb *txsForBlock) requestMissingTxsForShardNoLock( + missingTxsForShard map[uint32][][]byte, + onRequestTxs func(shardID uint32, txHashes [][]byte), +) int { + requestedTxs := 0 + for shardID, txHashes := range missingTxsForShard { + requestedTxs += len(txHashes) + go func(providedShardID uint32, providedTxHashes [][]byte) { + onRequestTxs(providedShardID, providedTxHashes) + }(shardID, txHashes) + } + + return requestedTxs +} + +// WaitForRequestedData waits for the requested data to be received within the specified wait time. +func (tfb *txsForBlock) WaitForRequestedData(waitTime time.Duration) error { + if !tfb.HasMissingTransactions() { + core.EmptyChannel(tfb.chRcvAllTxs) + return nil + } + + select { + case <-tfb.chRcvAllTxs: + return nil + case <-time.After(waitTime): + return process.ErrTimeIsOut + } +} + +// IsInterfaceNil checks if the txsForBlock instance is nil. +func (tfb *txsForBlock) IsInterfaceNil() bool { + return tfb == nil +} diff --git a/process/block/preprocess/txsForBlock_test.go b/process/block/preprocess/txsForBlock_test.go new file mode 100644 index 00000000000..cb3688b0624 --- /dev/null +++ b/process/block/preprocess/txsForBlock_test.go @@ -0,0 +1,358 @@ +package preprocess + +import ( + "strconv" + "testing" + "time" + + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/consensus/mock" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/testscommon" +) + +func Test_NewTxsForBlock(t *testing.T) { + t.Parallel() + + t.Run("nil shard coordinator should return error", func(t *testing.T) { + _, err := NewTxsForBlock(nil) + require.Equal(t, process.ErrNilShardCoordinator, err) + }) + t.Run("valid shard coordinator OK", func(t *testing.T) { + shardCoordinator := &mock.ShardCoordinatorMock{} + tfb, err := NewTxsForBlock(shardCoordinator) + require.NoError(t, err) + require.NotNil(t, tfb) + }) +} + +func TestTxsForBlock_Reset(t *testing.T) { + t.Parallel() + + shardCoordinator := &mock.ShardCoordinatorMock{} + tfb, _ := NewTxsForBlock(shardCoordinator) + + tfb.numMissingTxs = 5 + tfb.txHashAndInfo["hash1"] = &process.TxInfo{} + tfb.Reset() + + require.Equal(t, 0, tfb.numMissingTxs) + require.Empty(t, tfb.txHashAndInfo) +} + +func TestTxsForBlock_GetTxInfoByHash(t *testing.T) { + t.Parallel() + + shardCoordinator := &mock.ShardCoordinatorMock{} + tfb, _ := NewTxsForBlock(shardCoordinator) + + txHash := []byte("hash1") + tInfo := &process.TxInfo{} + tfb.txHashAndInfo[string(txHash)] = tInfo + + result, ok := tfb.GetTxInfoByHash(txHash) + require.True(t, ok) + require.Equal(t, tInfo, result) + + result, ok = tfb.GetTxInfoByHash([]byte("nonexistent")) + require.False(t, ok) + require.Nil(t, result) +} + +func TestTxsForBlock_ReceivedTransaction(t *testing.T) { + t.Parallel() + + shardCoordinator := &mock.ShardCoordinatorMock{} + + t.Run("receive last missing transaction", func(t *testing.T) { + t.Parallel() + + tfb, _ := NewTxsForBlock(shardCoordinator) + txHash := []byte("hash1") + tInfo := &process.TxInfo{TxShardInfo: &process.TxShardInfo{}} + tfb.txHashAndInfo[string(txHash)] = tInfo + tfb.numMissingTxs = 1 + + tx := &transaction.Transaction{ + Nonce: 1, + } + tfb.ReceivedTransaction(txHash, tx) + require.True(t, <-tfb.chRcvAllTxs) + require.Equal(t, tx, tfb.txHashAndInfo[string(txHash)].Tx) + require.Equal(t, 0, tfb.numMissingTxs) + }) + t.Run("receive transaction when nothing missing", func(t *testing.T) { + t.Parallel() + + tfb, _ := NewTxsForBlock(shardCoordinator) + txHash := []byte("hash1") + tInfo := &process.TxInfo{TxShardInfo: &process.TxShardInfo{}} + tfb.txHashAndInfo[string(txHash)] = tInfo + tfb.numMissingTxs = 0 + + tx := &transaction.Transaction{ + Nonce: 1, + } + tfb.ReceivedTransaction(txHash, tx) + + require.Equal(t, tInfo, tfb.txHashAndInfo[string(txHash)]) + require.Equal(t, 0, tfb.numMissingTxs) + }) + t.Run("receive one of multiple missing transactions", func(t *testing.T) { + t.Parallel() + + tfb, _ := NewTxsForBlock(shardCoordinator) + txHash1 := []byte("hash1") + txHash2 := []byte("hash2") + tInfo1 := &process.TxInfo{TxShardInfo: &process.TxShardInfo{}} + tInfo2 := &process.TxInfo{TxShardInfo: &process.TxShardInfo{}} + tfb.txHashAndInfo[string(txHash1)] = tInfo1 + tfb.txHashAndInfo[string(txHash2)] = tInfo2 + tfb.numMissingTxs = 2 + + tx := &transaction.Transaction{ + Nonce: 1, + } + tfb.ReceivedTransaction(txHash1, tx) + + require.Equal(t, tx, tfb.txHashAndInfo[string(txHash1)].Tx) + require.Equal(t, 1, tfb.numMissingTxs) + }) +} + +func TestTxsForBlock_AddTransaction(t *testing.T) { + t.Parallel() + + t.Run("nil transaction should not be added", func(t *testing.T) { + t.Parallel() + + shardCoordinator := &mock.ShardCoordinatorMock{} + tfb, _ := NewTxsForBlock(shardCoordinator) + + tfb.AddTransaction([]byte("hash1"), nil, 1, 2) + + require.Empty(t, tfb.txHashAndInfo) + }) + t.Run("valid transaction should be added", func(t *testing.T) { + t.Parallel() + + shardCoordinator := &mock.ShardCoordinatorMock{} + tfb, _ := NewTxsForBlock(shardCoordinator) + + txHash := []byte("hash1") + tx := &transaction.Transaction{ + Nonce: 1, + } + tfb.AddTransaction(txHash, tx, 1, 2) + + tInfo, ok := tfb.txHashAndInfo[string(txHash)] + require.True(t, ok) + require.Equal(t, tx, tInfo.Tx) + require.Equal(t, uint32(1), tInfo.SenderShardID) + require.Equal(t, uint32(2), tInfo.ReceiverShardID) + }) +} + +func TestTxsForBlock_HasMissingTransactions(t *testing.T) { + t.Parallel() + + shardCoordinator := &mock.ShardCoordinatorMock{} + tfb, _ := NewTxsForBlock(shardCoordinator) + + require.False(t, tfb.HasMissingTransactions()) + + tfb.numMissingTxs = 1 + require.True(t, tfb.HasMissingTransactions()) + + tfb.numMissingTxs = 10 + require.True(t, tfb.HasMissingTransactions()) +} + +func TestTxsForBlock_ComputeExistingAndRequestMissing(t *testing.T) { + t.Parallel() + + t.Run("nil body should return 0 missing transactions", func(t *testing.T) { + t.Parallel() + + shardCoordinator := &mock.ShardCoordinatorMock{} + tfb, _ := NewTxsForBlock(shardCoordinator) + + missingTxs := tfb.ComputeExistingAndRequestMissing(nil, func(block.Type) bool { return true }, nil, nil) + require.Equal(t, 0, missingTxs) + }) + t.Run("no missing transactions, as Smart contract result in pool", func(t *testing.T) { + t.Parallel() + + shardCoordinator := &mock.ShardCoordinatorMock{} + tfb, _ := NewTxsForBlock(shardCoordinator) + + body := createBlockBody(block.SmartContractResultBlock, 1) + + txPool := testscommon.NewShardedDataCacheNotifierMock() + txPool.AddData(body.MiniBlocks[0].TxHashes[0], &transaction.Transaction{Nonce: 1}, 100, "0") + + onRequestTxs := func(shardID uint32, txHashes [][]byte) { + require.Fail(t, "should not request transactions when none are missing") + } + + missingTxs := tfb.ComputeExistingAndRequestMissing(body, func(block.Type) bool { return true }, txPool, onRequestTxs) + require.Equal(t, 0, missingTxs) + }) + t.Run("no missing transactions, as invalid transaction in pool", func(t *testing.T) { + t.Parallel() + + shardCoordinator := &mock.ShardCoordinatorMock{} + tfb, _ := NewTxsForBlock(shardCoordinator) + + body := createBlockBody(block.InvalidBlock, 1) + + txPool := testscommon.NewShardedDataCacheNotifierMock() + txPool.AddData(body.MiniBlocks[0].TxHashes[0], &transaction.Transaction{Nonce: 1}, 100, "0") + + onRequestTxs := func(shardID uint32, txHashes [][]byte) { + require.Fail(t, "should not request transactions when none are missing") + } + + missingTxs := tfb.ComputeExistingAndRequestMissing(body, func(block.Type) bool { return true }, txPool, onRequestTxs) + require.Equal(t, 0, missingTxs) + }) + t.Run("no missing transactions, as no transactions in block", func(t *testing.T) { + t.Parallel() + + shardCoordinator := &mock.ShardCoordinatorMock{} + tfb, _ := NewTxsForBlock(shardCoordinator) + + body := createBlockBody(block.SmartContractResultBlock, 0) + + txPool := testscommon.NewShardedDataCacheNotifierMock() + onRequestTxs := func(shardID uint32, txHashes [][]byte) { + require.Fail(t, "should not request transactions when none are missing") + } + + missingTxs := tfb.ComputeExistingAndRequestMissing(body, func(block.Type) bool { return true }, txPool, onRequestTxs) + require.Equal(t, 0, missingTxs) + }) + t.Run("missing transactions should be requested", func(t *testing.T) { + t.Parallel() + + shardCoordinator := &mock.ShardCoordinatorMock{} + tfb, _ := NewTxsForBlock(shardCoordinator) + body := createBlockBody(block.TxBlock, 1) + bodyWithDuplicatedTxs := createBlockBody(block.TxBlock, 1) + bodyWithDuplicatedTxs.MiniBlocks[0].TxHashes = append(bodyWithDuplicatedTxs.MiniBlocks[0].TxHashes, body.MiniBlocks[0].TxHashes[0]) + + txPool := testscommon.NewShardedDataCacheNotifierMock() + onRequestTxs := func(shardID uint32, txHashes [][]byte) { + require.Equal(t, uint32(0), shardID) + require.Equal(t, body.MiniBlocks[0].TxHashes, txHashes) + } + + missingTxs := tfb.ComputeExistingAndRequestMissing(bodyWithDuplicatedTxs, func(block.Type) bool { return true }, txPool, onRequestTxs) + require.Equal(t, 1, missingTxs) + require.Equal(t, 1, tfb.numMissingTxs) + }) +} + +func TestTxsForBlock_WaitForRequestedData(t *testing.T) { + t.Parallel() + + t.Run("no missing transaction should immediately return", func(t *testing.T) { + t.Parallel() + + shardCoordinator := &mock.ShardCoordinatorMock{} + tfb, _ := NewTxsForBlock(shardCoordinator) + + tfb.numMissingTxs = 0 + err := tfb.WaitForRequestedData(100 * time.Millisecond) + require.NoError(t, err) + }) + + t.Run("wait for receiving a missing transaction", func(t *testing.T) { + shardCoordinator := &mock.ShardCoordinatorMock{} + tfb, _ := NewTxsForBlock(shardCoordinator) + + go func() { + time.Sleep(100 * time.Millisecond) + tfb.chRcvAllTxs <- true + }() + + tfb.numMissingTxs = 1 + err := tfb.WaitForRequestedData(200 * time.Millisecond) + require.NoError(t, err) + }) + t.Run("timeout while waiting for missing transaction", func(t *testing.T) { + t.Parallel() + + shardCoordinator := &mock.ShardCoordinatorMock{} + tfb, _ := NewTxsForBlock(shardCoordinator) + + tfb.numMissingTxs = 1 + err := tfb.WaitForRequestedData(100 * time.Millisecond) + require.Equal(t, process.ErrTimeIsOut, err) + }) +} + +func TestTxsForBlock_GetMissingTxsCount(t *testing.T) { + t.Parallel() + + shardCoordinator := &mock.ShardCoordinatorMock{} + tfb, _ := NewTxsForBlock(shardCoordinator) + + require.Equal(t, 0, tfb.GetMissingTxsCount()) + + tfb.numMissingTxs = 5 + require.Equal(t, 5, tfb.GetMissingTxsCount()) + + tfb.numMissingTxs = 10 + require.Equal(t, 10, tfb.GetMissingTxsCount()) +} + +func TestTxsForBlock_GetAllCurrentUsedTxs(t *testing.T) { + t.Parallel() + + shardCoordinator := &mock.ShardCoordinatorMock{} + tfb, _ := NewTxsForBlock(shardCoordinator) + + txHash1 := []byte("hash1") + txHash2 := []byte("hash2") + tfb.txHashAndInfo[string(txHash1)] = &process.TxInfo{Tx: &transaction.Transaction{Nonce: 1}} + tfb.txHashAndInfo[string(txHash2)] = &process.TxInfo{Tx: &transaction.Transaction{Nonce: 2}} + + allTxs := tfb.GetAllCurrentUsedTxs() + require.Len(t, allTxs, 2) + require.Equal(t, tfb.txHashAndInfo[string(txHash1)].Tx, allTxs[string(txHash1)]) + require.Equal(t, tfb.txHashAndInfo[string(txHash2)].Tx, allTxs[string(txHash2)]) +} + +func TestTxsForBlock_IsInterfaceNil(t *testing.T) { + t.Parallel() + + shardCoordinator := &mock.ShardCoordinatorMock{} + tfb, _ := NewTxsForBlock(shardCoordinator) + + require.False(t, tfb.IsInterfaceNil()) + + var nilTfb *txsForBlock + require.True(t, nilTfb.IsInterfaceNil()) +} + +func createBlockBody(blockType block.Type, numTxHashes uint16) *block.Body { + txHashes := make([][]byte, 0, numTxHashes) + for i := uint16(0); i < numTxHashes; i++ { + txHash := []byte("hash" + strconv.Itoa(int(i))) + txHashes = append(txHashes, txHash) + } + return &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + Type: blockType, + SenderShardID: 0, + ReceiverShardID: 1, + TxHashes: txHashes, + }, + }, + } +} diff --git a/process/block/preprocess/validatorInfoPreProcessor.go b/process/block/preprocess/validatorInfoPreProcessor.go index e7586f500e7..efa916c153d 100644 --- a/process/block/preprocess/validatorInfoPreProcessor.go +++ b/process/block/preprocess/validatorInfoPreProcessor.go @@ -7,11 +7,11 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" - "github.com/multiversx/mx-chain-core-go/hashing" - "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/sharding" "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/storage" ) @@ -19,44 +19,30 @@ import ( var _ process.DataMarshalizer = (*validatorInfoPreprocessor)(nil) var _ process.PreProcessor = (*validatorInfoPreprocessor)(nil) +// ValidatorInfoPreProcessorArgs is the struct that groups all arguments needed for creating a validatorInfo preprocessor +type ValidatorInfoPreProcessorArgs struct { + BasePreProcessorArgs +} + type validatorInfoPreprocessor struct { *basePreProcess chReceivedAllValidatorsInfo chan bool - validatorsInfoForBlock txsForBlock + validatorsInfoForBlock TxsForBlockHandler validatorsInfoPool dataRetriever.ShardedDataCacherNotifier storage dataRetriever.StorageService - enableEpochsHandler common.EnableEpochsHandler + shardCoordinator sharding.Coordinator } // NewValidatorInfoPreprocessor creates a new validatorInfo preprocessor object func NewValidatorInfoPreprocessor( - hasher hashing.Hasher, - marshalizer marshal.Marshalizer, - blockSizeComputation BlockSizeComputationHandler, - validatorsInfoPool dataRetriever.ShardedDataCacherNotifier, - store dataRetriever.StorageService, - enableEpochsHandler common.EnableEpochsHandler, + args ValidatorInfoPreProcessorArgs, ) (*validatorInfoPreprocessor, error) { - - if check.IfNil(hasher) { - return nil, process.ErrNilHasher - } - if check.IfNil(marshalizer) { - return nil, process.ErrNilMarshalizer - } - if check.IfNil(blockSizeComputation) { - return nil, process.ErrNilBlockSizeComputationHandler - } - if check.IfNil(validatorsInfoPool) { - return nil, process.ErrNilValidatorInfoPool - } - if check.IfNil(store) { - return nil, process.ErrNilStorage - } - if check.IfNil(enableEpochsHandler) { - return nil, process.ErrNilEnableEpochsHandler + err := checkBasePreProcessArgs(args.BasePreProcessorArgs) + if err != nil { + return nil, err } - err := core.CheckHandlerCompatibility(enableEpochsHandler, []core.EnableEpochFlag{ + + err = core.CheckHandlerCompatibility(args.EnableEpochsHandler, []core.EnableEpochFlag{ common.RefactorPeersMiniBlocksFlag, }) if err != nil { @@ -64,20 +50,27 @@ func NewValidatorInfoPreprocessor( } bpp := &basePreProcess{ - hasher: hasher, - marshalizer: marshalizer, - blockSizeComputation: blockSizeComputation, + hasher: args.Hasher, + marshalizer: args.Marshalizer, + blockSizeComputation: args.BlockSizeComputation, + enableEpochsHandler: args.EnableEpochsHandler, + enableRoundsHandler: args.EnableRoundsHandler, + feeHandler: args.EconomicsFee, } vip := &validatorInfoPreprocessor{ - basePreProcess: bpp, - storage: store, - validatorsInfoPool: validatorsInfoPool, - enableEpochsHandler: enableEpochsHandler, + basePreProcess: bpp, + storage: args.Store, + validatorsInfoPool: args.DataPool, + shardCoordinator: args.ShardCoordinator, } vip.chReceivedAllValidatorsInfo = make(chan bool) - vip.validatorsInfoForBlock.txHashAndInfo = make(map[string]*txInfo) + + vip.validatorsInfoForBlock, err = NewTxsForBlock(args.ShardCoordinator) + if err != nil { + return nil, err + } return vip, nil } @@ -93,7 +86,7 @@ func (vip *validatorInfoPreprocessor) RemoveBlockDataFromPools(body *block.Body, } // RemoveTxsFromPools removes validators info from associated pools -func (vip *validatorInfoPreprocessor) RemoveTxsFromPools(body *block.Body) error { +func (vip *validatorInfoPreprocessor) RemoveTxsFromPools(body *block.Body, _ common.RootHashHolder) error { return vip.removeTxsFromPools(body, vip.validatorsInfoPool, vip.isMiniBlockCorrect) } @@ -171,6 +164,16 @@ func (vip *validatorInfoPreprocessor) ProcessBlockTransactions( return nil } +// GetCreatedMiniBlocksFromMe returns nil as this preprocessor does not create any mini blocks +func (vip *validatorInfoPreprocessor) GetCreatedMiniBlocksFromMe() block.MiniBlockSlice { + return make(block.MiniBlockSlice, 0) +} + +// GetUnExecutableTransactions returns an empty map as validator info are always executable +func (vip *validatorInfoPreprocessor) GetUnExecutableTransactions() map[string]struct{} { + return make(map[string]struct{}) +} + // SaveTxsToStorage saves validator info from body into storage func (vip *validatorInfoPreprocessor) SaveTxsToStorage(body *block.Body) error { if check.IfNil(body) { @@ -222,12 +225,7 @@ func (vip *validatorInfoPreprocessor) saveValidatorInfoToStorage(miniBlock *bloc // CreateBlockStarted cleans the local cache map for processed/created validators info at this round func (vip *validatorInfoPreprocessor) CreateBlockStarted() { - _ = core.EmptyChannel(vip.chReceivedAllValidatorsInfo) - - vip.validatorsInfoForBlock.mutTxsForBlock.Lock() - vip.validatorsInfoForBlock.missingTxs = 0 - vip.validatorsInfoForBlock.txHashAndInfo = make(map[string]*txInfo) - vip.validatorsInfoForBlock.mutTxsForBlock.Unlock() + vip.validatorsInfoForBlock.Reset() } // RequestBlockTransactions does nothing @@ -235,9 +233,14 @@ func (vip *validatorInfoPreprocessor) RequestBlockTransactions(_ *block.Body) in return 0 } -// RequestTransactionsForMiniBlock does nothing -func (vip *validatorInfoPreprocessor) RequestTransactionsForMiniBlock(_ *block.MiniBlock) int { - return 0 +// GetTransactionsAndRequestMissingForMiniBlock does nothing +func (vip *validatorInfoPreprocessor) GetTransactionsAndRequestMissingForMiniBlock(_ *block.MiniBlock) ([]data.TransactionHandler, int) { + return nil, 0 +} + +// SelectOutgoingTransactions does nothing +func (vip *validatorInfoPreprocessor) SelectOutgoingTransactions(_ uint64, _ uint64, _ func() bool) ([][]byte, []data.TransactionHandler, error) { + return make([][]byte, 0), make([]data.TransactionHandler, 0), nil } // CreateAndProcessMiniBlocks does nothing @@ -263,12 +266,12 @@ func (vip *validatorInfoPreprocessor) ProcessMiniBlock( return nil, indexOfLastTxProcessed, false, process.ErrValidatorInfoMiniBlockNotFromMeta } - if vip.blockSizeComputation.IsMaxBlockSizeWithoutThrottleReached(1, len(miniBlock.TxHashes)) { + if vip.isMaxBlockSizeWithoutThrottleReached(1, len(miniBlock.TxHashes)) { return nil, indexOfLastTxProcessed, false, process.ErrMaxBlockSizeReached } - vip.blockSizeComputation.AddNumMiniBlocks(1) - vip.blockSizeComputation.AddNumTxs(len(miniBlock.TxHashes)) + vip.addNumMiniBlocks(1) + vip.addNumTxs(len(miniBlock.TxHashes)) return nil, len(miniBlock.TxHashes) - 1, false, nil } diff --git a/process/block/preprocess/validatorInfoPreProcessor_test.go b/process/block/preprocess/validatorInfoPreProcessor_test.go index 59cf03baa6c..fd516e8844e 100644 --- a/process/block/preprocess/validatorInfoPreProcessor_test.go +++ b/process/block/preprocess/validatorInfoPreProcessor_test.go @@ -8,18 +8,21 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/rewardTx" + "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/mock" "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/cache" + "github.com/multiversx/mx-chain-go/testscommon/common" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" - "github.com/multiversx/mx-chain-go/testscommon/genericMocks" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" + stateMock "github.com/multiversx/mx-chain-go/testscommon/state" "github.com/multiversx/mx-chain-go/testscommon/storage" ) @@ -27,14 +30,9 @@ func TestNewValidatorInfoPreprocessor_NilHasherShouldErr(t *testing.T) { t.Parallel() tdp := initDataPool() - rtp, err := NewValidatorInfoPreprocessor( - nil, - &marshallerMock.MarshalizerMock{}, - &testscommon.BlockSizeComputationStub{}, - tdp.ValidatorsInfo(), - genericMocks.NewChainStorerMock(0), - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - ) + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + args.Hasher = nil + rtp, err := NewValidatorInfoPreprocessor(args) assert.Nil(t, rtp) assert.Equal(t, process.ErrNilHasher, err) @@ -44,14 +42,9 @@ func TestNewValidatorInfoPreprocessor_NilMarshalizerShouldErr(t *testing.T) { t.Parallel() tdp := initDataPool() - rtp, err := NewValidatorInfoPreprocessor( - &hashingMocks.HasherMock{}, - nil, - &testscommon.BlockSizeComputationStub{}, - tdp.ValidatorsInfo(), - genericMocks.NewChainStorerMock(0), - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - ) + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + args.Marshalizer = nil + rtp, err := NewValidatorInfoPreprocessor(args) assert.Nil(t, rtp) assert.Equal(t, process.ErrNilMarshalizer, err) @@ -61,14 +54,9 @@ func TestNewValidatorInfoPreprocessor_NilBlockSizeComputationHandlerShouldErr(t t.Parallel() tdp := initDataPool() - rtp, err := NewValidatorInfoPreprocessor( - &hashingMocks.HasherMock{}, - &marshallerMock.MarshalizerMock{}, - nil, - tdp.ValidatorsInfo(), - genericMocks.NewChainStorerMock(0), - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - ) + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + args.BlockSizeComputation = nil + rtp, err := NewValidatorInfoPreprocessor(args) assert.Nil(t, rtp) assert.Equal(t, process.ErrNilBlockSizeComputationHandler, err) @@ -77,31 +65,22 @@ func TestNewValidatorInfoPreprocessor_NilBlockSizeComputationHandlerShouldErr(t func TestNewValidatorInfoPreprocessor_NilValidatorInfoPoolShouldErr(t *testing.T) { t.Parallel() - rtp, err := NewValidatorInfoPreprocessor( - &hashingMocks.HasherMock{}, - &marshallerMock.MarshalizerMock{}, - &testscommon.BlockSizeComputationStub{}, - nil, - genericMocks.NewChainStorerMock(0), - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - ) + tdp := initDataPool() + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + args.DataPool = nil + rtp, err := NewValidatorInfoPreprocessor(args) assert.Nil(t, rtp) - assert.Equal(t, process.ErrNilValidatorInfoPool, err) + assert.Equal(t, process.ErrNilTransactionPool, err) } func TestNewValidatorInfoPreprocessor_NilStoreShouldErr(t *testing.T) { t.Parallel() tdp := initDataPool() - rtp, err := NewValidatorInfoPreprocessor( - &hashingMocks.HasherMock{}, - &marshallerMock.MarshalizerMock{}, - &testscommon.BlockSizeComputationStub{}, - tdp.ValidatorsInfo(), - nil, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - ) + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + args.Store = nil + rtp, err := NewValidatorInfoPreprocessor(args) assert.Nil(t, rtp) assert.Equal(t, process.ErrNilStorage, err) @@ -111,31 +90,45 @@ func TestNewValidatorInfoPreprocessor_NilEnableEpochHandlerShouldErr(t *testing. t.Parallel() tdp := initDataPool() - rtp, err := NewValidatorInfoPreprocessor( - &hashingMocks.HasherMock{}, - &marshallerMock.MarshalizerMock{}, - &testscommon.BlockSizeComputationStub{}, - tdp.ValidatorsInfo(), - genericMocks.NewChainStorerMock(0), - nil, - ) + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + args.EnableEpochsHandler = nil + rtp, err := NewValidatorInfoPreprocessor(args) assert.Nil(t, rtp) assert.Equal(t, process.ErrNilEnableEpochsHandler, err) } +func TestNewValidatorInfoPreprocessor_NilEnableRoundsHandlerShouldErr(t *testing.T) { + t.Parallel() + + tdp := initDataPool() + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + args.EnableRoundsHandler = nil + rtp, err := NewValidatorInfoPreprocessor(args) + + assert.Nil(t, rtp) + assert.Equal(t, process.ErrNilEnableRoundsHandler, err) +} + +func TestNewValidatorInfoPreprocessor_NilShardsCoordinatorMock(t *testing.T) { + t.Parallel() + + tdp := initDataPool() + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + args.ShardCoordinator = nil + rtp, err := NewValidatorInfoPreprocessor(args) + + assert.Nil(t, rtp) + assert.Equal(t, process.ErrNilShardCoordinator, err) +} + func TestNewValidatorInfoPreprocessor_InvalidEnableEpochHandlerShouldErr(t *testing.T) { t.Parallel() tdp := initDataPool() - rtp, err := NewValidatorInfoPreprocessor( - &hashingMocks.HasherMock{}, - &marshallerMock.MarshalizerMock{}, - &testscommon.BlockSizeComputationStub{}, - tdp.ValidatorsInfo(), - genericMocks.NewChainStorerMock(0), - enableEpochsHandlerMock.NewEnableEpochsHandlerStubWithNoFlagsDefined(), - ) + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + args.EnableEpochsHandler = enableEpochsHandlerMock.NewEnableEpochsHandlerStubWithNoFlagsDefined() + rtp, err := NewValidatorInfoPreprocessor(args) assert.Nil(t, rtp) assert.True(t, errors.Is(err, core.ErrInvalidEnableEpochsHandler)) @@ -145,14 +138,8 @@ func TestNewValidatorInfoPreprocessor_OkValsShouldWork(t *testing.T) { t.Parallel() tdp := initDataPool() - rtp, err := NewValidatorInfoPreprocessor( - &hashingMocks.HasherMock{}, - &marshallerMock.MarshalizerMock{}, - &testscommon.BlockSizeComputationStub{}, - tdp.ValidatorsInfo(), - genericMocks.NewChainStorerMock(0), - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - ) + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + rtp, err := NewValidatorInfoPreprocessor(args) assert.Nil(t, err) assert.NotNil(t, rtp) } @@ -161,14 +148,8 @@ func TestNewValidatorInfoPreprocessor_CreateMarshalizedDataShouldWork(t *testing t.Parallel() tdp := initDataPool() - rtp, _ := NewValidatorInfoPreprocessor( - &hashingMocks.HasherMock{}, - &marshallerMock.MarshalizerMock{}, - &testscommon.BlockSizeComputationStub{}, - tdp.ValidatorsInfo(), - genericMocks.NewChainStorerMock(0), - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - ) + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + rtp, _ := NewValidatorInfoPreprocessor(args) hash := make([][]byte, 0) res, err := rtp.CreateMarshalledData(hash) @@ -181,14 +162,8 @@ func TestNewValidatorInfoPreprocessor_ProcessMiniBlockInvalidMiniBlockTypeShould t.Parallel() tdp := initDataPool() - rtp, _ := NewValidatorInfoPreprocessor( - &hashingMocks.HasherMock{}, - &marshallerMock.MarshalizerMock{}, - &testscommon.BlockSizeComputationStub{}, - tdp.ValidatorsInfo(), - genericMocks.NewChainStorerMock(0), - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - ) + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + rtp, _ := NewValidatorInfoPreprocessor(args) txHashes := make([][]byte, 0) mb1 := block.MiniBlock{ @@ -210,14 +185,8 @@ func TestNewValidatorInfoPreprocessor_ProcessMiniBlockShouldWork(t *testing.T) { t.Parallel() tdp := initDataPool() - rtp, _ := NewValidatorInfoPreprocessor( - &hashingMocks.HasherMock{}, - &marshallerMock.MarshalizerMock{}, - &testscommon.BlockSizeComputationStub{}, - tdp.ValidatorsInfo(), - genericMocks.NewChainStorerMock(0), - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - ) + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + rtp, _ := NewValidatorInfoPreprocessor(args) txHashes := make([][]byte, 0) mb1 := block.MiniBlock{ @@ -239,14 +208,8 @@ func TestNewValidatorInfoPreprocessor_ProcessMiniBlockNotFromMeta(t *testing.T) t.Parallel() tdp := initDataPool() - rtp, _ := NewValidatorInfoPreprocessor( - &hashingMocks.HasherMock{}, - &marshallerMock.MarshalizerMock{}, - &testscommon.BlockSizeComputationStub{}, - tdp.ValidatorsInfo(), - genericMocks.NewChainStorerMock(0), - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - ) + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + rtp, _ := NewValidatorInfoPreprocessor(args) txHashes := make([][]byte, 0) mb1 := block.MiniBlock{ @@ -267,19 +230,9 @@ func TestNewValidatorInfoPreprocessor_ProcessMiniBlockNotFromMeta(t *testing.T) func TestNewValidatorInfoPreprocessor_RestorePeerBlockIntoPools(t *testing.T) { t.Parallel() - hasher := &hashingMocks.HasherMock{} - marshalizer := &marshallerMock.MarshalizerMock{} - blockSizeComputation := &testscommon.BlockSizeComputationStub{} - tdp := initDataPool() - rtp, _ := NewValidatorInfoPreprocessor( - hasher, - marshalizer, - blockSizeComputation, - tdp.ValidatorsInfo(), - genericMocks.NewChainStorerMock(0), - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - ) + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + rtp, _ := NewValidatorInfoPreprocessor(args) txHashes := [][]byte{[]byte("tx_hash1")} mb1 := block.MiniBlock{ @@ -293,8 +246,8 @@ func TestNewValidatorInfoPreprocessor_RestorePeerBlockIntoPools(t *testing.T) { blockBody.MiniBlocks = append(blockBody.MiniBlocks, &mb1) miniBlockPool := cache.NewCacherMock() - marshalizedMb, _ := marshalizer.Marshal(mb1) - mbHash := hasher.Compute(string(marshalizedMb)) + marshalizedMb, _ := args.Marshalizer.Marshal(mb1) + mbHash := args.Hasher.Compute(string(marshalizedMb)) foundMb, ok := miniBlockPool.Get(mbHash) assert.Nil(t, foundMb) @@ -312,19 +265,9 @@ func TestNewValidatorInfoPreprocessor_RestorePeerBlockIntoPools(t *testing.T) { func TestNewValidatorInfoPreprocessor_RestoreOtherBlockTypeIntoPoolsShouldNotRestore(t *testing.T) { t.Parallel() - hasher := &hashingMocks.HasherMock{} - marshalizer := &marshallerMock.MarshalizerMock{} - blockSizeComputation := &testscommon.BlockSizeComputationStub{} - tdp := initDataPool() - rtp, _ := NewValidatorInfoPreprocessor( - hasher, - marshalizer, - blockSizeComputation, - tdp.ValidatorsInfo(), - genericMocks.NewChainStorerMock(0), - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - ) + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + rtp, _ := NewValidatorInfoPreprocessor(args) txHashes := [][]byte{[]byte("tx_hash1")} mb1 := block.MiniBlock{ @@ -338,8 +281,8 @@ func TestNewValidatorInfoPreprocessor_RestoreOtherBlockTypeIntoPoolsShouldNotRes blockBody.MiniBlocks = append(blockBody.MiniBlocks, &mb1) miniBlockPool := cache.NewCacherMock() - marshalizedMb, _ := marshalizer.Marshal(mb1) - mbHash := hasher.Compute(string(marshalizedMb)) + marshalizedMb, _ := args.Marshalizer.Marshal(mb1) + mbHash := args.Hasher.Compute(string(marshalizedMb)) foundMb, ok := miniBlockPool.Get(mbHash) assert.Nil(t, foundMb) @@ -357,19 +300,9 @@ func TestNewValidatorInfoPreprocessor_RestoreOtherBlockTypeIntoPoolsShouldNotRes func TestNewValidatorInfoPreprocessor_RemovePeerBlockFromPool(t *testing.T) { t.Parallel() - hasher := &hashingMocks.HasherMock{} - marshalizer := &marshallerMock.MarshalizerMock{} - blockSizeComputation := &testscommon.BlockSizeComputationStub{} - tdp := initDataPool() - rtp, _ := NewValidatorInfoPreprocessor( - hasher, - marshalizer, - blockSizeComputation, - tdp.ValidatorsInfo(), - genericMocks.NewChainStorerMock(0), - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - ) + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + rtp, _ := NewValidatorInfoPreprocessor(args) txHashes := [][]byte{[]byte("tx_hash1")} mb1 := block.MiniBlock{ @@ -379,8 +312,8 @@ func TestNewValidatorInfoPreprocessor_RemovePeerBlockFromPool(t *testing.T) { Type: block.PeerBlock, } - marshalizedMb, _ := marshalizer.Marshal(mb1) - mbHash := hasher.Compute(string(marshalizedMb)) + marshalizedMb, _ := args.Marshalizer.Marshal(mb1) + mbHash := args.Hasher.Compute(string(marshalizedMb)) blockBody := &block.Body{} blockBody.MiniBlocks = append(blockBody.MiniBlocks, &mb1) @@ -402,19 +335,9 @@ func TestNewValidatorInfoPreprocessor_RemovePeerBlockFromPool(t *testing.T) { func TestNewValidatorInfoPreprocessor_RemoveOtherBlockTypeFromPoolShouldNotRemove(t *testing.T) { t.Parallel() - hasher := &hashingMocks.HasherMock{} - marshalizer := &marshallerMock.MarshalizerMock{} - blockSizeComputation := &testscommon.BlockSizeComputationStub{} - tdp := initDataPool() - rtp, _ := NewValidatorInfoPreprocessor( - hasher, - marshalizer, - blockSizeComputation, - tdp.ValidatorsInfo(), - genericMocks.NewChainStorerMock(0), - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - ) + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + rtp, _ := NewValidatorInfoPreprocessor(args) txHashes := [][]byte{[]byte("tx_hash1")} mb1 := block.MiniBlock{ @@ -424,8 +347,8 @@ func TestNewValidatorInfoPreprocessor_RemoveOtherBlockTypeFromPoolShouldNotRemov Type: block.TxBlock, } - marshalizedMb, _ := marshalizer.Marshal(mb1) - mbHash := hasher.Compute(string(marshalizedMb)) + marshalizedMb, _ := args.Marshalizer.Marshal(mb1) + mbHash := args.Hasher.Compute(string(marshalizedMb)) blockBody := &block.Body{} blockBody.MiniBlocks = append(blockBody.MiniBlocks, &mb1) @@ -451,23 +374,15 @@ func TestNewValidatorInfoPreprocessor_RestoreValidatorsInfo(t *testing.T) { t.Parallel() expectedErr := errors.New("error") - hasher := &hashingMocks.HasherMock{} - marshalizer := &marshallerMock.MarshalizerMock{} - blockSizeComputation := &testscommon.BlockSizeComputationStub{} storer := &storage.ChainStorerStub{ GetAllCalled: func(unitType dataRetriever.UnitType, keys [][]byte) (map[string][]byte, error) { return nil, expectedErr }, } tdp := initDataPool() - rtp, _ := NewValidatorInfoPreprocessor( - hasher, - marshalizer, - blockSizeComputation, - tdp.ValidatorsInfo(), - storer, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - ) + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + args.Store = storer + rtp, _ := NewValidatorInfoPreprocessor(args) miniBlock := &block.MiniBlock{} err := rtp.restoreValidatorsInfo(miniBlock) @@ -477,9 +392,7 @@ func TestNewValidatorInfoPreprocessor_RestoreValidatorsInfo(t *testing.T) { t.Run("restore validators info with all txs found in storage", func(t *testing.T) { t.Parallel() - hasher := &hashingMocks.HasherMock{} marshalizer := &marshallerMock.MarshalizerMock{} - blockSizeComputation := &testscommon.BlockSizeComputationStub{} shardValidatorInfoHash := []byte("hash") shardValidatorInfo := &state.ShardValidatorInfo{ PublicKey: []byte("x"), @@ -503,14 +416,11 @@ func TestNewValidatorInfoPreprocessor_RestoreValidatorsInfo(t *testing.T) { }, } } - rtp, _ := NewValidatorInfoPreprocessor( - hasher, - marshalizer, - blockSizeComputation, - tdp.ValidatorsInfo(), - storer, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - ) + + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + args.Store = storer + args.Marshalizer = marshalizer + rtp, _ := NewValidatorInfoPreprocessor(args) miniBlock := &block.MiniBlock{} err := rtp.restoreValidatorsInfo(miniBlock) @@ -556,15 +466,10 @@ func TestValidatorInfoPreprocessor_SaveTxsToStorageShouldWork(t *testing.T) { return nil }, } + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + args.Store = storer - vip, _ := NewValidatorInfoPreprocessor( - &hashingMocks.HasherMock{}, - &marshallerMock.MarshalizerMock{}, - &testscommon.BlockSizeComputationStub{}, - tdp.ValidatorsInfo(), - storer, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - ) + vip, _ := NewValidatorInfoPreprocessor(args) err := vip.SaveTxsToStorage(nil) assert.Equal(t, process.ErrNilBlockBody, err) @@ -590,3 +495,54 @@ func TestValidatorInfoPreprocessor_SaveTxsToStorageShouldWork(t *testing.T) { require.Equal(t, 1, len(putHashes)) assert.Equal(t, txHash3, putHashes[0]) } + +func TestValidatorInfoPreprocessor_GetCreatedMiniBlocksFromMe(t *testing.T) { + t.Parallel() + + tdp := initDataPool() + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + vip, _ := NewValidatorInfoPreprocessor(args) + + // always returns empty + createdMbs := vip.GetCreatedMiniBlocksFromMe() + assert.Len(t, createdMbs, 0) +} + +func TestValidatorInfoPreprocessor_GetUnExecutableTransactions(t *testing.T) { + t.Parallel() + + tdp := initDataPool() + args := createDefaultValidatorInfoPreProcessorArgs(tdp) + vip, _ := NewValidatorInfoPreprocessor(args) + + // always returns empty + unexecTxs := vip.GetUnExecutableTransactions() + assert.Len(t, unexecTxs, 0) +} + +func createDefaultValidatorInfoPreProcessorArgs(tdp dataRetriever.PoolsHolder) ValidatorInfoPreProcessorArgs { + requestTransaction := func(shardID uint32, txHashes [][]byte) {} + return ValidatorInfoPreProcessorArgs{ + BasePreProcessorArgs: BasePreProcessorArgs{ + DataPool: tdp.ValidatorsInfo(), + Store: &storage.ChainStorerStub{}, + Hasher: &hashingMocks.HasherMock{}, + Marshalizer: &marshallerMock.MarshalizerMock{}, + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), + Accounts: &stateMock.AccountsStub{}, + AccountsProposal: &stateMock.AccountsStub{}, + OnRequestTransaction: requestTransaction, + GasHandler: &testscommon.GasHandlerStub{}, + PubkeyConverter: createMockPubkeyConverter(), + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &common.TxExecutionOrderHandlerStub{}, + EconomicsFee: feeHandlerMock(), + EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + }, + } +} diff --git a/process/block/shardInfo.go b/process/block/shardInfo.go new file mode 100644 index 00000000000..8be1cf2a05e --- /dev/null +++ b/process/block/shardInfo.go @@ -0,0 +1,331 @@ +package block + +import ( + "encoding/hex" + "fmt" + + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/marshal" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/dataRetriever" + "github.com/multiversx/mx-chain-go/process" +) + +// ShardInfoCreateDataArgs defines the arguments needed to create shard info creator +type ShardInfoCreateDataArgs struct { + EnableEpochsHandler common.EnableEpochsHandler + HeadersPool dataRetriever.HeadersPool + ProofsPool dataRetriever.ProofsPool + PendingMiniBlocksHandler process.PendingMiniBlocksHandler + BlockTracker process.BlockTracker + Storage dataRetriever.StorageService + Marshaller marshal.Marshalizer +} + +// ShardInfoCreateData is a component used to create shard info from shard headers +type ShardInfoCreateData struct { + enableEpochsHandler common.EnableEpochsHandler + headersPool dataRetriever.HeadersPool + proofsPool dataRetriever.ProofsPool + pendingMiniBlocksHandler process.PendingMiniBlocksHandler + blockTracker process.BlockTracker + storage dataRetriever.StorageService + marshaller marshal.Marshalizer +} + +// NewShardInfoCreateData creates a new ShardInfoCreateData instance +func NewShardInfoCreateData( + args ShardInfoCreateDataArgs, +) (*ShardInfoCreateData, error) { + if check.IfNil(args.EnableEpochsHandler) { + return nil, process.ErrNilEnableEpochsHandler + } + if check.IfNil(args.HeadersPool) { + return nil, process.ErrNilHeadersDataPool + } + if check.IfNil(args.ProofsPool) { + return nil, process.ErrNilProofsPool + } + if check.IfNil(args.PendingMiniBlocksHandler) { + return nil, process.ErrNilPendingMiniBlocksHandler + } + if check.IfNil(args.BlockTracker) { + return nil, process.ErrNilBlockTracker + } + if check.IfNil(args.Storage) { + return nil, process.ErrNilStorageService + } + if check.IfNil(args.Marshaller) { + return nil, process.ErrNilMarshalizer + } + + return &ShardInfoCreateData{ + enableEpochsHandler: args.EnableEpochsHandler, + headersPool: args.HeadersPool, + proofsPool: args.ProofsPool, + pendingMiniBlocksHandler: args.PendingMiniBlocksHandler, + blockTracker: args.BlockTracker, + storage: args.Storage, + marshaller: args.Marshaller, + }, nil +} + +// CreateShardInfoV3 creates shard info for V3 metablock headers +func (sic *ShardInfoCreateData) CreateShardInfoV3( + metaHeader data.MetaHeaderHandler, + shardHeaders []data.HeaderHandler, + shardHeaderHashes [][]byte, +) ([]data.ShardDataProposalHandler, []data.ShardDataHandler, error) { + if check.IfNil(metaHeader) { + return nil, nil, process.ErrNilHeaderHandler + } + if !metaHeader.IsHeaderV3() { + return nil, nil, process.ErrInvalidHeader + } + + var shardInfo []data.ShardDataHandler + var shardInfoProposal []data.ShardDataProposalHandler + if len(shardHeaders) != len(shardHeaderHashes) { + return nil, nil, process.ErrInconsistentShardHeadersAndHashes + } + + for i := 0; i < len(shardHeaders); i++ { + shardDataProposal, shardData, err := sic.createShardInfoFromHeader(shardHeaders[i], shardHeaderHashes[i]) + if err != nil { + return nil, nil, err + } + shardInfo = append(shardInfo, shardData...) + shardInfoProposal = append(shardInfoProposal, shardDataProposal) + } + + return shardInfoProposal, shardInfo, nil +} + +// CreateShardInfoFromLegacyMeta creates shard info for legacy meta header +func (sic *ShardInfoCreateData) CreateShardInfoFromLegacyMeta( + metaHeader data.MetaHeaderHandler, + shardHeaders []data.ShardHeaderHandler, + shardHeaderHashes [][]byte, +) ([]data.ShardDataHandler, error) { + if check.IfNil(metaHeader) { + return nil, process.ErrNilHeaderHandler + } + if metaHeader.IsHeaderV3() { + return nil, process.ErrInvalidHeader + } + + if len(shardHeaders) != len(shardHeaderHashes) { + return nil, process.ErrInconsistentShardHeadersAndHashes + } + + shardInfo := make([]data.ShardDataHandler, 0) + for i := 0; i < len(shardHeaders); i++ { + shardData, err := sic.createShardDataFromLegacyHeader(shardHeaders[i], shardHeaderHashes[i]) + if err != nil { + return nil, err + } + shardInfo = append(shardInfo, shardData...) + } + + return shardInfo, nil +} + +func (sic *ShardInfoCreateData) createShardInfoFromHeader( + shardHeader data.HeaderHandler, + hdrHash []byte, +) (data.ShardDataProposalHandler, []data.ShardDataHandler, error) { + if check.IfNil(shardHeader) { + return nil, nil, process.ErrNilHeaderHandler + } + if len(hdrHash) == 0 { + return nil, nil, process.ErrInvalidHash + } + + hasMissingShardHdrProof := shardHeader.GetNonce() >= 1 && !sic.proofsPool.HasProof(shardHeader.GetShardID(), hdrHash) + if hasMissingShardHdrProof { + return nil, nil, fmt.Errorf("%w for shard header with hash %s", process.ErrMissingHeaderProof, hex.EncodeToString(hdrHash)) + } + + if !shardHeader.IsHeaderV3() { + shardData, err := sic.createShardDataFromLegacyHeader(shardHeader, hdrHash) + return sic.createShardDataProposalFromHeader(shardHeader, hdrHash), shardData, err + } + + return sic.createShardDataFromV3Header(shardHeader, hdrHash) +} + +func (sic *ShardInfoCreateData) createShardDataFromLegacyHeader(shardHdr data.HeaderHandler, hdrHash []byte) ([]data.ShardDataHandler, error) { + shardData := &block.ShardData{} + shardData.TxCount = shardHdr.GetTxCount() + shardData.ShardID = shardHdr.GetShardID() + shardData.HeaderHash = hdrHash + shardData.Round = shardHdr.GetRound() + shardData.PrevHash = shardHdr.GetPrevHash() + shardData.Nonce = shardHdr.GetNonce() + shardData.PrevRandSeed = shardHdr.GetPrevRandSeed() + shardData.PubKeysBitmap = shardHdr.GetPubKeysBitmap() + if sic.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, shardHdr.GetEpoch()) { + shardData.Epoch = shardHdr.GetEpoch() + } + shardData.AccumulatedFees = shardHdr.GetAccumulatedFees() + shardData.DeveloperFees = shardHdr.GetDeveloperFees() + shardData.ShardMiniBlockHeaders = createShardMiniBlockHeaderFromHeader(shardHdr, sic.enableEpochsHandler) + err := sic.updateShardDataWithCrossShardInfo(shardData, shardHdr) + if err != nil { + return nil, err + } + + return []data.ShardDataHandler{shardData}, nil +} + +func (sic *ShardInfoCreateData) createShardDataFromV3Header( + shardHeader data.HeaderHandler, + hdrHash []byte, +) (data.ShardDataProposalHandler, []data.ShardDataHandler, error) { + if check.IfNil(shardHeader) { + return nil, nil, process.ErrNilHeaderHandler + } + + shardDataProposal := sic.createShardDataProposalFromHeader(shardHeader, hdrHash) + executionResults := shardHeader.GetExecutionResultsHandlers() + if len(executionResults) == 0 { + // return shard data proposal even though the shard header does not hold any execution result + return shardDataProposal, []data.ShardDataHandler{}, nil + } + + shardDataHandlers := make([]data.ShardDataHandler, len(executionResults)) + for i, execResult := range executionResults { + shardData, err := sic.createShardDataFromExecutionResult(execResult, shardHeader.GetShardID()) + if err != nil { + return nil, nil, err + } + shardDataHandlers[i] = shardData + } + + return shardDataProposal, shardDataHandlers, nil +} + +func (sic *ShardInfoCreateData) createShardDataProposalFromHeader( + header data.HeaderHandler, + hdrHash []byte, +) data.ShardDataProposalHandler { + return &block.ShardDataProposal{ + HeaderHash: hdrHash, + Round: header.GetRound(), + Nonce: header.GetNonce(), + ShardID: header.GetShardID(), + Epoch: header.GetEpoch(), + NumPendingMiniBlocks: uint32(len(sic.pendingMiniBlocksHandler.GetPendingMiniBlocks(header.GetShardID()))), + } +} + +func (sic *ShardInfoCreateData) createShardDataFromExecutionResult( + execResult data.BaseExecutionResultHandler, + shardID uint32, +) (data.ShardDataHandler, error) { + if check.IfNil(execResult) { + return nil, process.ErrNilExecutionResultHandler + } + + execResultHandler, ok := execResult.(data.ExecutionResultHandler) + if !ok { + return nil, process.ErrWrongTypeAssertion + } + + header, err := process.GetHeader( + execResultHandler.GetHeaderHash(), + sic.headersPool, + sic.storage, + sic.marshaller, + shardID, + ) + if err != nil { + log.Debug("createShardDataFromExecutionResult: failed to get header with hash", "hash", execResultHandler.GetHeaderHash(), "error", err) + return nil, err + } + + shardData := &block.ShardData{} + shardData.TxCount = uint32(execResultHandler.GetExecutedTxCount()) + shardData.ShardID = header.GetShardID() + shardData.HeaderHash = execResultHandler.GetHeaderHash() + shardData.Round = execResultHandler.GetHeaderRound() + shardData.PrevHash = header.GetPrevHash() + shardData.Nonce = execResultHandler.GetHeaderNonce() + shardData.PrevRandSeed = header.GetPrevRandSeed() + shardData.PubKeysBitmap = header.GetPubKeysBitmap() + shardData.Epoch = header.GetEpoch() + shardData.AccumulatedFees = execResultHandler.GetAccumulatedFees() + shardData.DeveloperFees = execResultHandler.GetDeveloperFees() + shardData.ShardMiniBlockHeaders = createShardMiniBlockHeaderFromExecutionResultHandler(execResultHandler) + err = sic.updateShardDataWithCrossShardInfo(shardData, header) + if err != nil { + return nil, err + } + + return shardData, nil +} + +func createShardMiniBlockHeaderFromHeader( + shardHdr data.HeaderHandler, + enableEpochsHandler common.EnableEpochsHandler, +) []block.MiniBlockHeader { + shardMiniBlockHeaders := make([]block.MiniBlockHeader, 0) + for i := 0; i < len(shardHdr.GetMiniBlockHeaderHandlers()); i++ { + if enableEpochsHandler.IsFlagEnabled(common.ScheduledMiniBlocksFlag) { + miniBlockHeader := shardHdr.GetMiniBlockHeaderHandlers()[i] + if !miniBlockHeader.IsFinal() { + log.Debug("metaProcessor.createShardInfo: do not create shard data with mini block which is not final", "mb hash", miniBlockHeader.GetHash()) + continue + } + } + + shardMiniBlockHeader := miniBlockHeaderFromMiniBlockHeaderHandler(shardHdr.GetMiniBlockHeaderHandlers()[i]) + shardMiniBlockHeaders = append(shardMiniBlockHeaders, shardMiniBlockHeader) + } + return shardMiniBlockHeaders +} + +func createShardMiniBlockHeaderFromExecutionResultHandler( + execResultHandler data.ExecutionResultHandler, +) []block.MiniBlockHeader { + mbHeaderHandlers := execResultHandler.GetMiniBlockHeadersHandlers() + shardMiniBlockHeaders := make([]block.MiniBlockHeader, 0, len(mbHeaderHandlers)) + for i := 0; i < len(mbHeaderHandlers); i++ { + shardMiniBlockHeader := miniBlockHeaderFromMiniBlockHeaderHandler(mbHeaderHandlers[i]) + shardMiniBlockHeaders = append(shardMiniBlockHeaders, shardMiniBlockHeader) + } + return shardMiniBlockHeaders +} + +func miniBlockHeaderFromMiniBlockHeaderHandler(miniBlockHeaderHandler data.MiniBlockHeaderHandler) block.MiniBlockHeader { + shardMiniBlockHeader := block.MiniBlockHeader{} + shardMiniBlockHeader.SenderShardID = miniBlockHeaderHandler.GetSenderShardID() + shardMiniBlockHeader.ReceiverShardID = miniBlockHeaderHandler.GetReceiverShardID() + shardMiniBlockHeader.Hash = miniBlockHeaderHandler.GetHash() + shardMiniBlockHeader.TxCount = miniBlockHeaderHandler.GetTxCount() + shardMiniBlockHeader.Type = block.Type(miniBlockHeaderHandler.GetTypeInt32()) + + return shardMiniBlockHeader +} + +func (sic *ShardInfoCreateData) updateShardDataWithCrossShardInfo(shardData *block.ShardData, header data.HeaderHandler) error { + if !header.IsHeaderV3() { + shardData.NumPendingMiniBlocks = uint32(len(sic.pendingMiniBlocksHandler.GetPendingMiniBlocks(header.GetShardID()))) + } + + metaHeader, _, err := sic.blockTracker.GetLastSelfNotarizedHeader(header.GetShardID()) + if err != nil { + return err + } + shardData.LastIncludedMetaNonce = metaHeader.GetNonce() + + return nil +} + +// IsInterfaceNil returns true if there is no value under the interface +func (sic *ShardInfoCreateData) IsInterfaceNil() bool { + return sic == nil +} diff --git a/process/block/shardblock.go b/process/block/shardblock.go index d15b62aa7b2..b2b04cb53b8 100644 --- a/process/block/shardblock.go +++ b/process/block/shardblock.go @@ -20,6 +20,7 @@ import ( "github.com/multiversx/mx-chain-go/dataRetriever" processOutport "github.com/multiversx/mx-chain-go/outport/process" "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionTrack" "github.com/multiversx/mx-chain-go/process/block/bootstrapStorage" "github.com/multiversx/mx-chain-go/process/block/helpers" "github.com/multiversx/mx-chain-go/process/block/processedMb" @@ -55,81 +56,14 @@ type shardProcessor struct { // NewShardProcessor creates a new shardProcessor object func NewShardProcessor(arguments ArgShardProcessor) (*shardProcessor, error) { - err := checkProcessorParameters(arguments.ArgBaseProcessor) + base, err := NewBaseProcessor(arguments.ArgBaseProcessor) if err != nil { return nil, err } - if check.IfNil(arguments.DataComponents.Datapool()) { - return nil, process.ErrNilDataPoolHolder - } - if check.IfNil(arguments.DataComponents.Datapool().Headers()) { - return nil, process.ErrNilHeadersDataPool - } if check.IfNil(arguments.DataComponents.Datapool().Transactions()) { return nil, process.ErrNilTransactionPool } - genesisHdr := arguments.DataComponents.Blockchain().GetGenesisHeader() - if check.IfNil(genesisHdr) { - return nil, fmt.Errorf("%w for genesis header in DataComponents.Blockchain", process.ErrNilHeaderHandler) - } - - processDebugger, err := createDisabledProcessDebugger() - if err != nil { - return nil, err - } - - base := &baseProcessor{ - accountsDB: arguments.AccountsDB, - blockSizeThrottler: arguments.BlockSizeThrottler, - forkDetector: arguments.ForkDetector, - hasher: arguments.CoreComponents.Hasher(), - marshalizer: arguments.CoreComponents.InternalMarshalizer(), - store: arguments.DataComponents.StorageService(), - shardCoordinator: arguments.BootstrapComponents.ShardCoordinator(), - nodesCoordinator: arguments.NodesCoordinator, - uint64Converter: arguments.CoreComponents.Uint64ByteSliceConverter(), - requestHandler: arguments.RequestHandler, - appStatusHandler: arguments.StatusCoreComponents.AppStatusHandler(), - blockChainHook: arguments.BlockChainHook, - txCoordinator: arguments.TxCoordinator, - roundHandler: arguments.CoreComponents.RoundHandler(), - epochStartTrigger: arguments.EpochStartTrigger, - headerValidator: arguments.HeaderValidator, - bootStorer: arguments.BootStorer, - blockTracker: arguments.BlockTracker, - dataPool: arguments.DataComponents.Datapool(), - blockChain: arguments.DataComponents.Blockchain(), - feeHandler: arguments.FeeHandler, - outportHandler: arguments.StatusComponents.OutportHandler(), - genesisNonce: genesisHdr.GetNonce(), - versionedHeaderFactory: arguments.BootstrapComponents.VersionedHeaderFactory(), - headerIntegrityVerifier: arguments.BootstrapComponents.HeaderIntegrityVerifier(), - historyRepo: arguments.HistoryRepository, - epochNotifier: arguments.CoreComponents.EpochNotifier(), - enableEpochsHandler: arguments.CoreComponents.EnableEpochsHandler(), - roundNotifier: arguments.CoreComponents.RoundNotifier(), - enableRoundsHandler: arguments.CoreComponents.EnableRoundsHandler(), - epochChangeGracePeriodHandler: arguments.CoreComponents.EpochChangeGracePeriodHandler(), - vmContainerFactory: arguments.VMContainersFactory, - vmContainer: arguments.VmContainer, - processDataTriesOnCommitEpoch: arguments.Config.Debug.EpochStart.ProcessDataTrieOnCommitEpoch, - gasConsumedProvider: arguments.GasHandler, - economicsData: arguments.CoreComponents.EconomicsData(), - scheduledTxsExecutionHandler: arguments.ScheduledTxsExecutionHandler, - pruningDelay: pruningDelay, - processedMiniBlocksTracker: arguments.ProcessedMiniBlocksTracker, - receiptsRepository: arguments.ReceiptsRepository, - processDebugger: processDebugger, - outportDataProvider: arguments.OutportDataProvider, - processStatusHandler: arguments.CoreComponents.ProcessStatusHandler(), - blockProcessingCutoffHandler: arguments.BlockProcessingCutoffHandler, - managedPeersHolder: arguments.ManagedPeersHolder, - sentSignaturesTracker: arguments.SentSignaturesTracker, - stateAccessesCollector: arguments.StateAccessesCollector, - extraDelayRequestBlockInfo: time.Duration(arguments.Config.EpochStartConfig.ExtraDelayForRequestBlockInfoInMilliseconds) * time.Millisecond, - proofsPool: arguments.DataComponents.Datapool().Proofs(), - } sp := shardProcessor{ baseProcessor: base, @@ -149,15 +83,6 @@ func NewShardProcessor(arguments ArgShardProcessor) (*shardProcessor, error) { sp.requestBlockBodyHandler = &sp sp.blockProcessor = &sp - sp.chRcvAllHdrs = make(chan bool) - - sp.hdrsForCurrBlock = newHdrForBlock() - - headersPool := sp.dataPool.Headers() - headersPool.RegisterHandler(sp.receivedMetaBlock) - - sp.proofsPool.RegisterHandler(sp.checkReceivedProofIfAttestingIsNeeded) - sp.metaBlockFinality = process.BlockFinality return &sp, nil @@ -173,7 +98,9 @@ func (sp *shardProcessor) ProcessBlock( return process.ErrNilHaveTimeHandler } - sp.processStatusHandler.SetBusy("shardProcessor.ProcessBlock") + if !sp.processStatusHandler.TrySetBusy("shardProcessor.ProcessBlock") { + return process.ErrBlockProcessorBusy + } defer sp.processStatusHandler.SetIdle() err := sp.checkBlockValidity(headerHandler, bodyHandler) @@ -241,7 +168,7 @@ func (sp *shardProcessor) ProcessBlock( } sp.txCoordinator.RequestBlockTransactions(body) - requestedMetaHdrs, requestedFinalityAttestingMetaHdrs, requestedProofs := sp.requestMetaHeaders(header) + sp.hdrsForCurrBlock.RequestMetaHeaders(header) if haveTime() < 0 { return process.ErrTimeIsOut @@ -252,48 +179,9 @@ func (sp *shardProcessor) ProcessBlock( return err } - haveMissingMetaHeaders := requestedMetaHdrs > 0 || requestedFinalityAttestingMetaHdrs > 0 || requestedProofs > 0 - if haveMissingMetaHeaders { - if requestedMetaHdrs > 0 { - log.Debug("requested missing meta headers", - "num headers", requestedMetaHdrs, - ) - } - if requestedFinalityAttestingMetaHdrs > 0 { - log.Debug("requested missing finality attesting meta headers", - "num finality meta headers", requestedFinalityAttestingMetaHdrs, - ) - } - if requestedProofs > 0 { - log.Debug("requested missing meta header proofs", - "num proofs", requestedProofs, - ) - } - - err = sp.waitForMetaHdrHashes(haveTime()) - - sp.hdrsForCurrBlock.mutHdrsForBlock.RLock() - missingMetaHdrs := sp.hdrsForCurrBlock.missingHdrs - missingProofs := sp.hdrsForCurrBlock.missingProofs - sp.hdrsForCurrBlock.mutHdrsForBlock.RUnlock() - - sp.hdrsForCurrBlock.resetMissingHdrs() - - if requestedMetaHdrs > 0 { - log.Debug("received missing meta headers", - "num headers", requestedMetaHdrs-missingMetaHdrs, - ) - } - - if requestedProofs > 0 { - log.Debug("received missing meta header proofs", - "num proofs", requestedProofs-missingProofs, - ) - } - - if err != nil { - return err - } + err = sp.hdrsForCurrBlock.WaitForHeadersIfNeeded(haveTime) + if err != nil { + return err } err = sp.requestEpochStartInfo(header, haveTime) @@ -382,14 +270,36 @@ func (sp *shardProcessor) ProcessBlock( return nil } -func (sp *shardProcessor) requestEpochStartInfo(header data.ShardHeaderHandler, haveTime func() time.Duration) error { +func (sp *shardProcessor) shouldEpochStartInfoBeAvailable(header data.ShardHeaderHandler) bool { if !header.IsStartOfEpochBlock() { - return nil + return false } if sp.epochStartTrigger.MetaEpoch() >= header.GetEpoch() { + return false + } + return !sp.epochStartTrigger.IsEpochStart() +} + +func (sp *shardProcessor) checkEpochStartInfoAvailableIfNeeded(header data.ShardHeaderHandler) error { + if !sp.shouldEpochStartInfoBeAvailable(header) { return nil } - if sp.epochStartTrigger.IsEpochStart() { + + headersPool := sp.dataPool.Headers() + _, err := headersPool.GetHeaderByHash(header.GetEpochStartMetaHash()) + if err != nil { + return fmt.Errorf("%w: missing epoch start meta header", process.ErrEpochStartInfoNotAvailable) + } + + if !sp.proofsPool.HasProof(core.MetachainShardId, header.GetEpochStartMetaHash()) { + return fmt.Errorf("%w: missing proof for epoch start meta header", process.ErrEpochStartInfoNotAvailable) + } + + return nil +} + +func (sp *shardProcessor) requestEpochStartInfo(header data.ShardHeaderHandler, haveTime func() time.Duration) error { + if !sp.shouldEpochStartInfoBeAvailable(header) { return nil } @@ -576,7 +486,7 @@ func (sp *shardProcessor) SetNumProcessedObj(numObj uint64) { sp.txCounter.totalTxs = numObj } -// checkMetaHeadersValidity - checks if listed metaheaders are valid as construction +// checkMetaHeadersValidityAndFinality - checks if listed metaheaders are valid as construction func (sp *shardProcessor) checkMetaHeadersValidityAndFinality() error { lastCrossNotarizedHeader, _, err := sp.blockTracker.GetLastCrossNotarizedHeader(core.MetachainShardId) if err != nil { @@ -692,6 +602,11 @@ func (sp *shardProcessor) indexBlockIfNeeded( return } + var scheduledRootHash []byte + if header.HasScheduledMiniBlocks() { + scheduledRootHash = sp.scheduledTxsExecutionHandler.GetScheduledRootHash() + } + log.Debug("preparing to index block", "hash", headerHash, "nonce", header.GetNonce(), "round", header.GetRound()) argSaveBlock, err := sp.outportDataProvider.PrepareOutportSaveBlockData(processOutport.ArgPrepareOutportSaveBlockData{ HeaderHash: headerHash, @@ -700,6 +615,7 @@ func (sp *shardProcessor) indexBlockIfNeeded( PreviousHeader: lastBlockHeader, HighestFinalBlockNonce: sp.forkDetector.GetHighestFinalBlockNonce(), HighestFinalBlockHash: sp.forkDetector.GetHighestFinalBlockHash(), + ScheduledRootHash: scheduledRootHash, }) if err != nil { log.Error("shardProcessor.indexBlockIfNeeded cannot prepare argSaveBlock", "error", err.Error(), @@ -758,7 +674,7 @@ func (sp *shardProcessor) restoreMetaBlockIntoPool( headersPool := sp.dataPool.Headers() mapMetaHashMiniBlockHashes := make(map[string][][]byte) - mapMetaHashMetaBlock := make(map[string]*block.MetaBlock) + mapMetaHashMetaBlock := make(map[string]data.MetaHeaderHandler) for _, metaBlockHash := range metaBlockHashes { metaBlock, errNotCritical := process.GetMetaHeaderFromStorage(metaBlockHash, sp.marshalizer, sp.store) @@ -806,8 +722,8 @@ func (sp *shardProcessor) restoreMetaBlockIntoPool( } log.Trace("meta block has been restored successfully", - "round", metaBlock.Round, - "nonce", metaBlock.Nonce, + "round", metaBlock.GetRound(), + "nonce", metaBlock.GetNonce(), "hash", metaBlockHash) } @@ -820,7 +736,7 @@ func (sp *shardProcessor) restoreMetaBlockIntoPool( return nil } -func (sp *shardProcessor) setProcessedMiniBlocksInfo(miniBlockHashes [][]byte, metaBlockHash string, metaBlock *block.MetaBlock) { +func (sp *shardProcessor) setProcessedMiniBlocksInfo(miniBlockHashes [][]byte, metaBlockHash string, metaBlock data.MetaHeaderHandler) { for _, miniBlockHash := range miniBlockHashes { indexOfLastTxProcessed := getIndexOfLastTxProcessedInMiniBlock(miniBlockHash, metaBlock) sp.processedMiniBlocksTracker.SetProcessedMiniBlockInfo([]byte(metaBlockHash), miniBlockHash, &processedMb.ProcessedMiniBlockInfo{ @@ -830,31 +746,32 @@ func (sp *shardProcessor) setProcessedMiniBlocksInfo(miniBlockHashes [][]byte, m } } -func getIndexOfLastTxProcessedInMiniBlock(miniBlockHash []byte, metaBlock *block.MetaBlock) int32 { - for _, mbh := range metaBlock.MiniBlockHeaders { - if bytes.Equal(mbh.Hash, miniBlockHash) { - return int32(mbh.TxCount) - 1 +func getIndexOfLastTxProcessedInMiniBlock(miniBlockHash []byte, metaBlock data.MetaHeaderHandler) int32 { + for _, mbh := range metaBlock.GetMiniBlockHeaderHandlers() { + if bytes.Equal(mbh.GetHash(), miniBlockHash) { + return int32(mbh.GetTxCount()) - 1 } } - for _, shardData := range metaBlock.ShardInfo { - for _, mbh := range shardData.ShardMiniBlockHeaders { - if bytes.Equal(mbh.Hash, miniBlockHash) { - return int32(mbh.TxCount) - 1 + for _, shardData := range metaBlock.GetShardInfoHandlers() { + for _, mbh := range shardData.GetShardMiniBlockHeaderHandlers() { + if bytes.Equal(mbh.GetHash(), miniBlockHash) { + return int32(mbh.GetTxCount()) - 1 } } } log.Warn("shardProcessor.getIndexOfLastTxProcessedInMiniBlock", "miniBlock hash", miniBlockHash, - "metaBlock round", metaBlock.Round, - "metaBlock nonce", metaBlock.Nonce, + "metaBlock round", metaBlock.GetRound(), + "metaBlock nonce", metaBlock.GetNonce(), "error", process.ErrMissingMiniBlock) return common.MaxIndexOfTxInMiniBlock } func (sp *shardProcessor) rollBackProcessedMiniBlocksInfo(headerHandler data.HeaderHandler, mapMiniBlockHashes map[string]uint32) { + for miniBlockHash := range mapMiniBlockHashes { miniBlockHeader := process.GetMiniBlockHeaderWithHash(headerHandler, []byte(miniBlockHash)) if miniBlockHeader == nil { @@ -868,8 +785,21 @@ func (sp *shardProcessor) rollBackProcessedMiniBlocksInfo(headerHandler data.Hea continue } - sp.rollBackProcessedMiniBlockInfo(miniBlockHeader, []byte(miniBlockHash)) + sp.rollBackProcessedCrossMiniBlockInfoForHeader(headerHandler, miniBlockHeader, []byte(miniBlockHash)) + } +} + +func (sp *shardProcessor) rollBackProcessedCrossMiniBlockInfoForHeader( + shardHeader data.HeaderHandler, + miniBlockHeader data.MiniBlockHeaderHandler, + miniBlockHash []byte) { + if !shardHeader.IsHeaderV3() { + sp.rollBackProcessedMiniBlockInfo(miniBlockHeader, miniBlockHash) + return } + + // for header V3 remove completely the mini block info from tracker + sp.processedMiniBlocksTracker.RemoveMiniBlockHash(miniBlockHash) } func (sp *shardProcessor) rollBackProcessedMiniBlockInfo(miniBlockHeader data.MiniBlockHeaderHandler, miniBlockHash []byte) { @@ -908,7 +838,9 @@ func (sp *shardProcessor) CreateBlock( return nil, nil, process.ErrWrongTypeAssertion } - sp.processStatusHandler.SetBusy("shardProcessor.CreateBlock") + if !sp.processStatusHandler.TrySetBusy("shardProcessor.CreateBlock") { + return nil, nil, process.ErrBlockProcessorBusy + } defer sp.processStatusHandler.SetIdle() err := sp.createBlockStarted() @@ -920,23 +852,9 @@ func (sp *shardProcessor) CreateBlock( // placeholder for shardProcessor.CreateBlock script 2 - if sp.epochStartTrigger.IsEpochStart() { - log.Debug("CreateBlock", "IsEpochStart", sp.epochStartTrigger.IsEpochStart(), - "epoch start meta header hash", sp.epochStartTrigger.EpochStartMetaHdrHash()) - err = shardHdr.SetEpochStartMetaHash(sp.epochStartTrigger.EpochStartMetaHdrHash()) - if err != nil { - return nil, nil, err - } - - epoch := sp.epochStartTrigger.MetaEpoch() - if initialHdr.GetEpoch() != epoch { - log.Debug("shardProcessor.CreateBlock: epoch from header is not the same as epoch from epoch start trigger, overwriting", - "epoch from header", initialHdr.GetEpoch(), "epoch from epoch start trigger", epoch) - err = shardHdr.SetEpoch(epoch) - if err != nil { - return nil, nil, err - } - } + err = sp.updateEpochIfNeeded(shardHdr) + if err != nil { + return nil, nil, err } sp.epochNotifier.CheckEpoch(shardHdr) @@ -967,6 +885,31 @@ func (sp *shardProcessor) CreateBlock( return shardHdr, finalBody, nil } +func (sp *shardProcessor) updateEpochIfNeeded(shardHeader data.ShardHeaderHandler) error { + if !sp.epochStartTrigger.IsEpochStart() { + return nil + } + + log.Debug("shardProcessor.updateEpochIfNeeded", "IsEpochStart", sp.epochStartTrigger.IsEpochStart(), + "epoch start meta header hash", sp.epochStartTrigger.EpochStartMetaHdrHash()) + err := shardHeader.SetEpochStartMetaHash(sp.epochStartTrigger.EpochStartMetaHdrHash()) + if err != nil { + return err + } + + epoch := sp.epochStartTrigger.MetaEpoch() + if shardHeader.GetEpoch() != epoch { + log.Debug("shardProcessor.updateEpochIfNeeded: epoch from header is not the same as epoch from epoch start trigger, overwriting", + "epoch from header", shardHeader.GetEpoch(), "epoch from epoch start trigger", epoch) + err = shardHeader.SetEpoch(epoch) + if err != nil { + return err + } + } + + return nil +} + // createBlockBody creates a list of miniblocks by filling them with transactions out of the transactions pools // as long as the transactions limit for the block has not been reached and there is still time to add transactions func (sp *shardProcessor) createBlockBody(shardHdr data.HeaderHandler, haveTime func() bool) (*block.Body, map[string]*processedMb.ProcessedMiniBlockInfo, error) { @@ -994,20 +937,34 @@ func (sp *shardProcessor) CommitBlock( headerHandler data.HeaderHandler, bodyHandler data.BodyHandler, ) error { - var err error - sp.processStatusHandler.SetBusy("shardProcessor.CommitBlock") - defer func() { - if err != nil { - sp.RevertCurrentBlock() - } - sp.processStatusHandler.SetIdle() - }() - - err = checkForNils(headerHandler, bodyHandler) + err := checkForNils(headerHandler, bodyHandler) if err != nil { return err } + prevBlockHeader := sp.blockChain.GetCurrentBlockHeader() + prevBlockHeaderHash := sp.blockChain.GetCurrentBlockHeaderHash() + + if !headerHandler.IsHeaderV3() { + if !sp.processStatusHandler.TrySetBusy("shardProcessor.CommitBlock") { + return process.ErrBlockProcessorBusy + } + defer func() { + if err != nil { + sp.RevertCurrentBlock() + } + sp.processStatusHandler.SetIdle() + }() + } else { + defer func() { + if err != nil { + sp.RevertHeaderV3OnCommit(headerHandler) + _ = sp.blockChain.SetCurrentBlockHeader(prevBlockHeader) + sp.blockChain.SetCurrentBlockHeaderHash(prevBlockHeaderHash) + } + }() + } + sp.store.SetEpochForPutOperation(headerHandler.GetEpoch()) log.Debug("started committing block", @@ -1054,7 +1011,7 @@ func (sp *shardProcessor) CommitBlock( sp.saveBody(body, header, headerHash) - processedMetaHdrs, err := sp.getOrderedProcessedMetaBlocksFromHeader(header) + processedMetaHdrs, partialProcessedMetaBlocks, err := sp.getOrderedProcessedMetaBlocksFromHeader(header) if err != nil { return err } @@ -1074,9 +1031,11 @@ func (sp *shardProcessor) CommitBlock( return err } - err = sp.commitAll(headerHandler) - if err != nil { - return err + if !headerHandler.IsHeaderV3() { + err = sp.commitState(headerHandler) + if err != nil { + return err + } } log.Info("shard block has been committed successfully", @@ -1090,12 +1049,19 @@ func (sp *shardProcessor) CommitBlock( sp.updateLastCommittedInDebugger(headerHandler.GetRound()) + err = sp.computeOwnShardStuckIfNeeded(headerHandler) + if err != nil { + return err + } + + sp.updateGasConsumptionLimitsIfNeeded() + errNotCritical := sp.checkSentSignaturesAtCommitTime(headerHandler) if errNotCritical != nil { log.Debug("checkSentSignaturesBeforeCommitting", "error", errNotCritical.Error()) } - errNotCritical = sp.updateCrossShardInfo(processedMetaHdrs) + errNotCritical = sp.updateCrossShardInfo(processedMetaHdrs, partialProcessedMetaBlocks) if errNotCritical != nil { log.Debug("updateCrossShardInfo", "error", errNotCritical.Error()) } @@ -1132,26 +1098,37 @@ func (sp *shardProcessor) CommitBlock( lastBlockHeader := sp.blockChain.GetCurrentBlockHeader() - committedRootHash, err := sp.accountsDB[state.UserAccountsState].RootHash() + rootHash := sp.getLastExecutedRootHash(header) + + err = sp.setCurrentBlockInfo(header, headerHash, rootHash) if err != nil { return err } + sp.blockChain.SetCurrentBlockHeaderHash(headerHash) - err = sp.blockChain.SetCurrentBlockHeaderAndRootHash(header, committedRootHash) + lastExecutionResultHeader, err := sp.getLastExecutionResultHeader(header) if err != nil { return err } - sp.blockChain.SetCurrentBlockHeaderHash(headerHash) sp.indexBlockIfNeeded(bodyHandler, headerHash, headerHandler, lastBlockHeader) - sp.stateAccessesCollector.Reset() sp.recordBlockInHistory(headerHash, headerHandler, bodyHandler) + err = sp.OnExecutedBlock(lastExecutionResultHeader, rootHash) + if err != nil { + return err + } + lastCrossNotarizedHeader, _, err := sp.blockTracker.GetLastCrossNotarizedHeader(core.MetachainShardId) if err != nil { return err } + err = sp.cleanExecutionResultsFromTracker(header) + if err != nil { + return err + } + saveMetricsForCommittedShardBlock( sp.nodesCoordinator, sp.appStatusHandler, @@ -1182,6 +1159,7 @@ func (sp *shardProcessor) CommitBlock( epochStartTriggerConfigKey: epochStartKey, } + // TODO adjust this method if needed for Supernova sp.prepareDataForBootStorer(args) // write data to log @@ -1202,11 +1180,21 @@ func (sp *shardProcessor) CommitBlock( sp.displayPoolsInfo() - errNotCritical = sp.removeTxsFromPools(header, body) + err = sp.saveExecutedData(header) + if err != nil { + return err + } + + errNotCritical = sp.removeTxsFromPools(headerHash, header, body) if errNotCritical != nil { log.Debug("removeTxsFromPools", "error", errNotCritical.Error()) } + errNotCritical = sp.cleanPostProcessCache(header) + if errNotCritical != nil { + log.Debug("cleanPostProcessCache", "error", errNotCritical.Error()) + } + sp.cleanupPools(headerHandler) sp.blockProcessingCutoffHandler.HandlePauseCutoff(header) @@ -1263,6 +1251,144 @@ func (sp *shardProcessor) displayPoolsInfo() { func (sp *shardProcessor) updateState(headers []data.HeaderHandler, currentHeader data.ShardHeaderHandler, currentHeaderHash []byte) { sp.snapShotEpochStartFromMeta(currentHeader) + if !currentHeader.IsHeaderV3() { + sp.pruneTrieLegacy(headers) + } else { + // for header v3, trie pruning is triggered in async mode from headers executor + + if currentHeader.IsStartOfEpochBlock() { + sp.nodesCoordinator.ShuffleOutForEpoch(currentHeader.GetEpoch()) + } + } + + if !sp.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, currentHeader.GetEpoch()) { + return + } + + // TODO: set proper finalized header in outport + sp.setFinalizedHeaderHashInIndexer(currentHeaderHash) + + scheduledHeaderRootHash, _ := sp.scheduledTxsExecutionHandler.GetScheduledRootHashForHeader(currentHeaderHash) + sp.setFinalBlockInfo(currentHeader, currentHeaderHash, scheduledHeaderRootHash) +} + +func (sp *shardProcessor) pruneTrieHeaderV3( + header data.HeaderHandler, +) { + executionResultsHandlers := header.GetExecutionResultsHandlers() + + accountsDb := sp.accountsDB[state.UserAccountsState] + if !accountsDb.IsPruningEnabled() { + return + } + + for i := range executionResultsHandlers { + currentExecRes := executionResultsHandlers[i] + prevExecRes, err := sp.getPreviousExecutionResult(i, executionResultsHandlers, header.GetPrevHash()) + if err != nil { + log.Warn("failed to get previous execution result for pruning", + "err", err, + "index", i, + "currentExecResHeaderHash", currentExecRes.GetHeaderHash()) + continue + } + + currentRootHash := currentExecRes.GetRootHash() + prevRootHash := prevExecRes.GetRootHash() + log.Trace("pruneTrieHeaderV3", + "currentRootHash", currentRootHash, + "prevRootHash", prevRootHash, + ) + if bytes.Equal(prevRootHash, currentRootHash) { + continue + } + + accountsDb.CancelPrune(prevRootHash, state.NewRoot) + accountsDb.PruneTrie(prevRootHash, state.OldRoot, sp.getPruningHandler(currentExecRes.GetHeaderNonce())) + + if sp.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, currentExecRes.GetHeaderEpoch()) { + continue + } + + sp.setFinalizedHeaderHashInIndexer(currentExecRes.GetHeaderHash()) + } +} + +func (sp *shardProcessor) resetPruning() { + accountsDb := sp.accountsDB[state.UserAccountsState] + if !accountsDb.IsPruningEnabled() { + return + } + + accountsDb.ResetPruning() +} + +func (sp *shardProcessor) cancelPruneForDismissedExecutionResults(batches []executionTrack.DismissedBatch) { + accountsDb := sp.accountsDB[state.UserAccountsState] + if !accountsDb.IsPruningEnabled() { + return + } + + for _, batch := range batches { + sp.cancelPruneForDismissedBatch(accountsDb, batch) + } +} + +func (sp *shardProcessor) cancelPruneForDismissedBatch(accountsDb state.AccountsAdapter, batch executionTrack.DismissedBatch) { + if batch.AnchorResult == nil { + return + } + + prevRootHash := batch.AnchorResult.GetRootHash() + for _, result := range batch.Results { + currentRootHash := result.GetRootHash() + cancelPruneForRootHashTransition(accountsDb, prevRootHash, currentRootHash) + prevRootHash = currentRootHash + } +} + +func (sp *shardProcessor) getPreviousExecutionResult( + index int, + executionResultsHandlers []data.BaseExecutionResultHandler, + prevHeaderHash []byte, +) (data.BaseExecutionResultHandler, error) { + if index > 0 { + return executionResultsHandlers[index-1], nil + } + + prevHeader, err := process.GetShardHeader(prevHeaderHash, sp.dataPool.Headers(), sp.marshalizer, sp.store) + if err != nil { + return nil, err + } + + if prevHeader.IsHeaderV3() { + lastExecRes := prevHeader.GetLastExecutionResultHandler() + if check.IfNil(lastExecRes) { + return nil, fmt.Errorf("previous header last execution result is nil") + } + + lastShardExecRes, ok := lastExecRes.(data.LastShardExecutionResultHandler) + if !ok { + return nil, process.ErrWrongTypeAssertion + } + + return lastShardExecRes.GetExecutionResultHandler(), nil + } + + lastExecRes, err := common.CreateLastExecutionResultFromPrevHeader(prevHeader, prevHeaderHash) + if err != nil { + return nil, err + } + + lastShardExecRes, ok := lastExecRes.(data.LastShardExecutionResultHandler) + if !ok { + return nil, process.ErrWrongTypeAssertion + } + + return lastShardExecRes.GetExecutionResultHandler(), nil +} + +func (sp *shardProcessor) pruneTrieLegacy(headers []data.HeaderHandler) { for _, header := range headers { if sp.forkDetector.GetHighestFinalBlockNonce() < header.GetNonce() { break @@ -1325,7 +1451,7 @@ func (sp *shardProcessor) updateState(headers []data.HeaderHandler, currentHeade ) sp.updateStateStorage( - header, + header.GetNonce(), headerRootHashForPruning, prevHeaderRootHashForPruning, sp.accountsDB[state.UserAccountsState], @@ -1339,15 +1465,6 @@ func (sp *shardProcessor) updateState(headers []data.HeaderHandler, currentHeade sp.setFinalBlockInfo(header, headerHash, scheduledHeaderRootHash) } - - if !sp.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, currentHeader.GetEpoch()) { - return - } - - sp.setFinalizedHeaderHashInIndexer(currentHeaderHash) - - scheduledHeaderRootHash, _ := sp.scheduledTxsExecutionHandler.GetScheduledRootHashForHeader(currentHeaderHash) - sp.setFinalBlockInfo(currentHeader, currentHeaderHash, scheduledHeaderRootHash) } func (sp *shardProcessor) setFinalBlockInfo( @@ -1355,26 +1472,29 @@ func (sp *shardProcessor) setFinalBlockInfo( headerHash []byte, scheduledHeaderRootHash []byte, ) { + if header.IsHeaderV3() { + // final block info is set in async mode on header executor + return + } + finalRootHash := scheduledHeaderRootHash if len(finalRootHash) == 0 { finalRootHash = header.GetRootHash() } + // TODO: maybe rename this to reflect last execution results sp.blockChain.SetFinalBlockInfo(header.GetNonce(), headerHash, finalRootHash) } func (sp *shardProcessor) snapShotEpochStartFromMeta(header data.ShardHeaderHandler) { accounts := sp.accountsDB[state.UserAccountsState] - sp.hdrsForCurrBlock.mutHdrsForBlock.RLock() - defer sp.hdrsForCurrBlock.mutHdrsForBlock.RUnlock() - for _, metaHash := range header.GetMetaBlockHashes() { - metaHdrInfo, ok := sp.hdrsForCurrBlock.hdrHashAndInfo[string(metaHash)] - if !ok { + hdr, err := sp.getHeaderFromHash(header.IsHeaderV3(), metaHash, core.MetachainShardId) + if err != nil { continue } - metaHdr, ok := metaHdrInfo.hdr.(*block.MetaBlock) + metaHdr, ok := hdr.(data.MetaHeaderHandler) if !ok { continue } @@ -1382,12 +1502,12 @@ func (sp *shardProcessor) snapShotEpochStartFromMeta(header data.ShardHeaderHand continue } - for _, epochStartShData := range metaHdr.EpochStart.LastFinalizedHeaders { - if epochStartShData.ShardID != header.GetShardID() { + for _, epochStartShData := range metaHdr.GetEpochStartHandler().GetLastFinalizedHeaderHandlers() { + if epochStartShData.GetShardID() != header.GetShardID() { continue } - rootHash := epochStartShData.RootHash + rootHash := epochStartShData.GetRootHash() schRootHash := epochStartShData.GetScheduledRootHash() if schRootHash != nil { log.Debug("using scheduled root hash for snapshotting", "schRootHash", schRootHash) @@ -1397,7 +1517,7 @@ func (sp *shardProcessor) snapShotEpochStartFromMeta(header data.ShardHeaderHand log.Debug("shard trie snapshot from epoch start shard data", "rootHash", rootHash, "epoch", epoch) accounts.SnapshotState(rootHash, epoch) sp.markSnapshotDoneInPeerAccounts() - saveEpochStartEconomicsMetrics(sp.appStatusHandler, metaHdr) + sp.saveEpochStartEconomicsMetrics(metaHdr) go func() { err := sp.commitTrieEpochRootHashIfNeeded(metaHdr, rootHash) if err != nil { @@ -1521,7 +1641,7 @@ func (sp *shardProcessor) saveLastNotarizedHeader(shardId uint32, processedHdrs // CreateNewHeader creates a new header func (sp *shardProcessor) CreateNewHeader(round uint64, nonce uint64) (data.HeaderHandler, error) { epoch := sp.epochStartTrigger.MetaEpoch() - header := sp.versionedHeaderFactory.Create(epoch) + header := sp.versionedHeaderFactory.Create(epoch, round) shardHeader, ok := header.(data.ShardHeaderHandler) if !ok { @@ -1541,28 +1661,44 @@ func (sp *shardProcessor) CreateNewHeader(round uint64, nonce uint64) (data.Head return nil, err } - err = shardHeader.SetAccumulatedFees(big.NewInt(0)) + err = initializeFeesDataShardHeaderIfNeeded(shardHeader) if err != nil { return nil, err } - err = shardHeader.SetDeveloperFees(big.NewInt(0)) + err = sp.setHeaderVersionData(shardHeader) if err != nil { return nil, err } - err = sp.setHeaderVersionData(shardHeader) + return header, nil +} + +func initializeFeesDataShardHeaderIfNeeded(shardHeader data.ShardHeaderHandler) error { + if shardHeader.IsHeaderV3() { + return nil + } + + err := shardHeader.SetAccumulatedFees(big.NewInt(0)) if err != nil { - return nil, err + return err } - return header, nil + err = shardHeader.SetDeveloperFees(big.NewInt(0)) + if err != nil { + return err + } + + return nil } func (sp *shardProcessor) setHeaderVersionData(shardHeader data.ShardHeaderHandler) error { if check.IfNil(shardHeader) { return process.ErrNilHeaderHandler } + if shardHeader.IsHeaderV3() { + return nil + } rootHash, err := sp.accountsDB[state.UserAccountsState].RootHash() if err != nil { @@ -1590,11 +1726,13 @@ func (sp *shardProcessor) getHighestHdrForOwnShardFromMetachain( ownShIdHdrs := make([]data.HeaderHandler, 0, len(processedHdrs)) for i := 0; i < len(processedHdrs); i++ { - hdr, ok := processedHdrs[i].(*block.MetaBlock) + hdr, ok := processedHdrs[i].(data.MetaHeaderHandler) if !ok { return nil, nil, process.ErrWrongTypeAssertion } + // since we are getting shard info data for own shard, it is safe to fetch it based on + // shard info proposed hdrs := sp.getHighestHdrForShardFromMetachain(sp.shardCoordinator.SelfId(), hdr) ownShIdHdrs = append(ownShIdHdrs, hdrs...) } @@ -1610,21 +1748,27 @@ func (sp *shardProcessor) getHighestHdrForOwnShardFromMetachain( return ownShIdHdrs, ownShIdHdrsHashes, nil } -func (sp *shardProcessor) getHighestHdrForShardFromMetachain(shardId uint32, hdr *block.MetaBlock) []data.HeaderHandler { - ownShIdHdr := make([]data.HeaderHandler, 0, len(hdr.ShardInfo)) +type shardInfoHandler interface { + GetHeaderHash() []byte + GetShardID() uint32 +} + +// it will fetch based on proposed shard info data +func (sp *shardProcessor) getHighestHdrForShardFromMetachain(shardId uint32, hdr data.MetaHeaderHandler) []data.HeaderHandler { + ownShIdHdr := make([]data.HeaderHandler, 0, len(hdr.GetShardInfoHandlers())) - for _, shardInfo := range hdr.ShardInfo { - if shardInfo.ShardID != shardId { + for _, shardInfo := range getShardHeadersReferencedByMeta(hdr) { + if shardInfo.GetShardID() != shardId { continue } - ownHdr, err := process.GetShardHeader(shardInfo.HeaderHash, sp.dataPool.Headers(), sp.marshalizer, sp.store) + ownHdr, err := process.GetShardHeader(shardInfo.GetHeaderHash(), sp.dataPool.Headers(), sp.marshalizer, sp.store) if err != nil { - go sp.requestHandler.RequestShardHeader(shardInfo.ShardID, shardInfo.HeaderHash) + go sp.requestHandler.RequestShardHeader(shardInfo.GetShardID(), shardInfo.GetHeaderHash()) log.Debug("requested missing shard header", - "hash", shardInfo.HeaderHash, - "shard", shardInfo.ShardID, + "hash", shardInfo.GetHeaderHash(), + "shard", shardInfo.GetShardID(), ) continue } @@ -1635,10 +1779,32 @@ func (sp *shardProcessor) getHighestHdrForShardFromMetachain(shardId uint32, hdr return data.TrimHeaderHandlerSlice(ownShIdHdr) } +func getShardHeadersReferencedByMeta( + header data.MetaHeaderHandler, +) []shardInfoHandler { + var shardInfoHandlers []shardInfoHandler + + if header.IsHeaderV3() { + for _, shardInfo := range header.GetShardInfoProposalHandlers() { + shardInfoHandlers = append(shardInfoHandlers, shardInfo) + } + + return shardInfoHandlers + } + + for _, shardInfo := range header.GetShardInfoHandlers() { + shardInfoHandlers = append(shardInfoHandlers, shardInfo) + } + + return shardInfoHandlers +} + // getOrderedProcessedMetaBlocksFromHeader returns all the meta blocks fully processed -func (sp *shardProcessor) getOrderedProcessedMetaBlocksFromHeader(header data.HeaderHandler) ([]data.HeaderHandler, error) { +func (sp *shardProcessor) getOrderedProcessedMetaBlocksFromHeader( + header data.HeaderHandler, +) ([]data.HeaderHandler, []*hashAndHdr, error) { if check.IfNil(header) { - return nil, process.ErrNilBlockHeader + return nil, nil, process.ErrNilBlockHeader } miniBlockHeaders := header.GetMiniBlockHeaderHandlers() @@ -1651,12 +1817,19 @@ func (sp *shardProcessor) getOrderedProcessedMetaBlocksFromHeader(header data.He "num miniblocks", len(miniBlockHashes), ) - processedMetaBlocks, err := sp.getOrderedProcessedMetaBlocksFromMiniBlockHashes(miniBlockHeaders, miniBlockHashes) + var processedMetaBlocks []data.HeaderHandler + partialProcessedMetaBlocks := make([]*hashAndHdr, 0) + var err error + if !header.IsHeaderV3() { + processedMetaBlocks, err = sp.getOrderedProcessedMetaBlocksFromMiniBlockHashes(miniBlockHeaders, miniBlockHashes) + } else { + processedMetaBlocks, partialProcessedMetaBlocks, err = sp.getOrderedProcessedMetaBlocksFromMiniBlockHashesV3(header, miniBlockHashes) + } if err != nil { - return nil, err + return nil, nil, err } - return processedMetaBlocks, nil + return processedMetaBlocks, partialProcessedMetaBlocks, nil } func (sp *shardProcessor) addProcessedCrossMiniBlocksFromHeader(headerHandler data.HeaderHandler) error { @@ -1673,18 +1846,14 @@ func (sp *shardProcessor) addProcessedCrossMiniBlocksFromHeader(headerHandler da miniBlockHashes[i] = headerHandler.GetMiniBlockHeaderHandlers()[i].GetHash() } - sp.hdrsForCurrBlock.mutHdrsForBlock.RLock() for _, metaBlockHash := range shardHeader.GetMetaBlockHashes() { - headerInfo, found := sp.hdrsForCurrBlock.hdrHashAndInfo[string(metaBlockHash)] - if !found { - sp.hdrsForCurrBlock.mutHdrsForBlock.RUnlock() + hdr, err := sp.getHeaderFromHash(shardHeader.IsHeaderV3(), metaBlockHash, core.MetachainShardId) + if err != nil { return fmt.Errorf("%w : addProcessedCrossMiniBlocksFromHeader metaBlockHash = %s", - process.ErrMissingHeader, logger.DisplayByteSlice(metaBlockHash)) + err, logger.DisplayByteSlice(metaBlockHash)) } - - metaBlock, isMetaBlock := headerInfo.hdr.(*block.MetaBlock) + metaBlock, isMetaBlock := hdr.(data.MetaHeaderHandler) if !isMetaBlock { - sp.hdrsForCurrBlock.mutHdrsForBlock.RUnlock() return process.ErrWrongTypeAssertion } @@ -1701,41 +1870,54 @@ func (sp *shardProcessor) addProcessedCrossMiniBlocksFromHeader(headerHandler da continue } - sp.processedMiniBlocksTracker.SetProcessedMiniBlockInfo(metaBlockHash, miniBlockHash, &processedMb.ProcessedMiniBlockInfo{ - FullyProcessed: miniBlockHeader.IsFinal(), - IndexOfLastTxProcessed: miniBlockHeader.GetIndexOfLastTxProcessed(), - }) - + sp.setProcessedCrossShardMiniBlockInfo(shardHeader, metaBlockHash, miniBlockHeader, miniBlockHash) delete(miniBlockHashes, key) } } - sp.hdrsForCurrBlock.mutHdrsForBlock.RUnlock() return nil } +func (sp *shardProcessor) setProcessedCrossShardMiniBlockInfo( + shardHeader data.ShardHeaderHandler, + metaBlockHash []byte, + miniBlockHeader data.MiniBlockHeaderHandler, + miniBlockHash []byte, +) { + if !shardHeader.IsHeaderV3() { + sp.processedMiniBlocksTracker.SetProcessedMiniBlockInfo(metaBlockHash, miniBlockHash, &processedMb.ProcessedMiniBlockInfo{ + FullyProcessed: miniBlockHeader.IsFinal(), + IndexOfLastTxProcessed: miniBlockHeader.GetIndexOfLastTxProcessed(), + }) + return + } + + sp.processedMiniBlocksTracker.SetProcessedMiniBlockInfo(metaBlockHash, miniBlockHash, &processedMb.ProcessedMiniBlockInfo{ + FullyProcessed: true, + IndexOfLastTxProcessed: int32(miniBlockHeader.GetTxCount()) - 1, + }) +} + func (sp *shardProcessor) getOrderedProcessedMetaBlocksFromMiniBlockHashes( miniBlockHeaders []data.MiniBlockHeaderHandler, miniBlockHashes map[int][]byte, ) ([]data.HeaderHandler, error) { + headersInfo := sp.hdrsForCurrBlock.GetHeadersInfoMap() + processedMetaHdrs := make([]data.HeaderHandler, 0, len(headersInfo)) + processedCrossMiniBlocksHashes := make(map[string]bool, len(headersInfo)) - processedMetaHdrs := make([]data.HeaderHandler, 0, len(sp.hdrsForCurrBlock.hdrHashAndInfo)) - processedCrossMiniBlocksHashes := make(map[string]bool, len(sp.hdrsForCurrBlock.hdrHashAndInfo)) - - sp.hdrsForCurrBlock.mutHdrsForBlock.RLock() - for metaBlockHash, headerInfo := range sp.hdrsForCurrBlock.hdrHashAndInfo { - if !headerInfo.usedInBlock { + for metaBlockHash, headerInfo := range headersInfo { + if !headerInfo.UsedInBlock() { continue } - metaBlock, ok := headerInfo.hdr.(*block.MetaBlock) + metaBlock, ok := headerInfo.GetHeader().(data.MetaHeaderHandler) if !ok { - sp.hdrsForCurrBlock.mutHdrsForBlock.RUnlock() return nil, process.ErrWrongTypeAssertion } log.Trace("meta header", - "nonce", metaBlock.Nonce, + "nonce", metaBlock.GetNonce(), ) crossMiniBlockHashes := metaBlock.GetMiniBlockHeadersWithDst(sp.shardCoordinator.SelfId()) @@ -1770,14 +1952,16 @@ func (sp *shardProcessor) getOrderedProcessedMetaBlocksFromMiniBlockHashes( processedMetaHdrs = append(processedMetaHdrs, metaBlock) } } - sp.hdrsForCurrBlock.mutHdrsForBlock.RUnlock() process.SortHeadersByNonce(processedMetaHdrs) return processedMetaHdrs, nil } -func (sp *shardProcessor) updateCrossShardInfo(processedMetaHdrs []data.HeaderHandler) error { +func (sp *shardProcessor) updateCrossShardInfo( + processedMetaHdrs []data.HeaderHandler, + partialProcessedMetaBlocks []*hashAndHdr, +) error { lastCrossNotarizedHeader, _, err := sp.blockTracker.GetLastCrossNotarizedHeader(core.MetachainShardId) if err != nil { return err @@ -1787,175 +1971,83 @@ func (sp *shardProcessor) updateCrossShardInfo(processedMetaHdrs []data.HeaderHa for i := 0; i < len(processedMetaHdrs); i++ { hdr := processedMetaHdrs[i] - // remove process finished - if hdr.GetNonce() > lastCrossNotarizedHeader.GetNonce() { - continue - } - - // metablock was processed and finalized - marshalizedHeader, errMarshal := sp.marshalizer.Marshal(hdr) - if errMarshal != nil { - log.Debug("updateCrossShardInfo.Marshal", "error", errMarshal.Error()) - continue - } - - headerHash := sp.hasher.Compute(string(marshalizedHeader)) - - sp.saveMetaHeader(hdr, headerHash, marshalizedHeader) + headerHash := sp.saveCrossShardMetaHeader(hdr, lastCrossNotarizedHeader) sp.processedMiniBlocksTracker.RemoveMetaBlockHash(headerHash) } - return nil -} - -// receivedMetaBlock is a callback function when a new metablock was received -// upon receiving, it parses the new metablock and requests miniblocks and transactions -// which destination is the current shard -func (sp *shardProcessor) receivedMetaBlock(headerHandler data.HeaderHandler, metaBlockHash []byte) { - metaBlock, ok := headerHandler.(*block.MetaBlock) - if !ok { - return - } - - log.Trace("received meta block from network", - "round", metaBlock.Round, - "nonce", metaBlock.Nonce, - "hash", metaBlockHash, - ) - - sp.hdrsForCurrBlock.mutHdrsForBlock.Lock() - - haveMissingMetaHeaders := sp.hdrsForCurrBlock.missingHdrs > 0 || sp.hdrsForCurrBlock.missingFinalityAttestingHdrs > 0 - if haveMissingMetaHeaders { - hdrInfoForHash := sp.hdrsForCurrBlock.hdrHashAndInfo[string(metaBlockHash)] - headerInfoIsNotNil := hdrInfoForHash != nil - headerIsMissing := headerInfoIsNotNil && check.IfNil(hdrInfoForHash.hdr) - hasProof := headerInfoIsNotNil && hdrInfoForHash.hasProof - hasProofRequested := headerInfoIsNotNil && hdrInfoForHash.hasProofRequested - if headerIsMissing { - hdrInfoForHash.hdr = metaBlock - sp.hdrsForCurrBlock.missingHdrs-- - - if metaBlock.Nonce > sp.hdrsForCurrBlock.highestHdrNonce[core.MetachainShardId] { - sp.hdrsForCurrBlock.highestHdrNonce[core.MetachainShardId] = metaBlock.Nonce - } - - if !hasProof && !hasProofRequested { - sp.requestProofIfNeeded(metaBlockHash, metaBlock) - } - } - - if sp.hdrsForCurrBlock.missingHdrs == 0 { - sp.checkFinalityRequestingMissing(metaBlock) - - if sp.hdrsForCurrBlock.missingFinalityAttestingHdrs == 0 { - log.Debug("received all missing finality attesting meta headers") - } - } - - missingMetaHdrs := sp.hdrsForCurrBlock.missingHdrs - missingFinalityAttestingMetaHdrs := sp.hdrsForCurrBlock.missingFinalityAttestingHdrs - missingProofs := sp.hdrsForCurrBlock.missingProofs - sp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() - - allMissingMetaHeadersReceived := missingMetaHdrs == 0 && missingFinalityAttestingMetaHdrs == 0 && missingProofs == 0 - if allMissingMetaHeadersReceived { - sp.chRcvAllHdrs <- true - } - } else { - sp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() + // if there are meta headers that were not included in the fully processed meta blocks + // and they are lower (by nonce) than last cross notarized meta header, + // that means there might have been a gap in the referenced meta header and these + // meta headers have to be saved into storage + for _, metaHdr := range partialProcessedMetaBlocks { + sp.saveCrossShardPartialMetaHeader(metaHdr, lastCrossNotarizedHeader) } - go sp.requestMiniBlocksIfNeeded(headerHandler) + return nil } -func (sp *shardProcessor) checkFinalityRequestingMissing(metaBlock *block.MetaBlock) { - shouldConsiderProofsForNotarization := sp.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, metaBlock.Epoch) - if !shouldConsiderProofsForNotarization { - sp.hdrsForCurrBlock.missingFinalityAttestingHdrs = sp.requestMissingFinalityAttestingHeaders( - core.MetachainShardId, - sp.metaBlockFinality, +func (sp *shardProcessor) saveCrossShardMetaHeader( + metaHdr data.HeaderHandler, + lastCrossNotarizedHeader data.HeaderHandler, +) []byte { + if metaHdr.GetNonce() > lastCrossNotarizedHeader.GetNonce() { + log.Debug("saveCrossShardMetaHeader: higher nonce", + "hdr nonce", metaHdr.GetNonce(), + "lastCrossNotarizedHeader nonce", lastCrossNotarizedHeader.GetNonce(), ) - - return // no proof needed + return nil } -} - -func (sp *shardProcessor) requestMetaHeaders(shardHeader data.ShardHeaderHandler) (uint32, uint32, uint32) { - _ = core.EmptyChannel(sp.chRcvAllHdrs) - if len(shardHeader.GetMetaBlockHashes()) == 0 { - return 0, 0, 0 + marshalizedHeader, errMarshal := sp.marshalizer.Marshal(metaHdr) + if errMarshal != nil { + log.Debug("saveCrossShardMetaHeader.Marshal", "error", errMarshal.Error()) + return nil } - return sp.computeExistingAndRequestMissingMetaHeaders(shardHeader) -} - -func (sp *shardProcessor) computeExistingAndRequestMissingMetaHeaders(header data.ShardHeaderHandler) (uint32, uint32, uint32) { - sp.hdrsForCurrBlock.mutHdrsForBlock.Lock() - defer sp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() - - metaBlockHashes := header.GetMetaBlockHashes() - lastMetablockNonceWithProof := uint64(0) - for i := 0; i < len(metaBlockHashes); i++ { - hdr, err := process.GetMetaHeaderFromPool( - metaBlockHashes[i], - sp.dataPool.Headers()) - - if err != nil { - sp.hdrsForCurrBlock.missingHdrs++ - sp.hdrsForCurrBlock.hdrHashAndInfo[string(metaBlockHashes[i])] = &hdrInfo{ - usedInBlock: true, - hdr: nil, - hasProof: false, - hasProofRequested: false, - } - - go sp.requestHandler.RequestMetaHeader(metaBlockHashes[i]) - continue - } - - sp.hdrsForCurrBlock.hdrHashAndInfo[string(metaBlockHashes[i])] = &hdrInfo{ - hdr: hdr, - usedInBlock: true, - } - - if hdr.GetNonce() > sp.hdrsForCurrBlock.highestHdrNonce[core.MetachainShardId] { - sp.hdrsForCurrBlock.highestHdrNonce[core.MetachainShardId] = hdr.GetNonce() - } - - shouldConsiderProofsForNotarization := sp.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, hdr.GetEpoch()) - if !shouldConsiderProofsForNotarization { - continue - } + headerHash := sp.hasher.Compute(string(marshalizedHeader)) - sp.requestProofIfNeeded(metaBlockHashes[i], hdr) + sp.saveMetaHeader(metaHdr, headerHash, marshalizedHeader) - sp.hdrsForCurrBlock.hdrHashAndInfo[string(metaBlockHashes[i])].hasProofRequested = true + return headerHash +} - if hdr.GetNonce() > lastMetablockNonceWithProof { - lastMetablockNonceWithProof = hdr.GetNonce() - } +func (sp *shardProcessor) saveCrossShardPartialMetaHeader( + hdrInfo *hashAndHdr, + lastCrossNotarizedHeader data.HeaderHandler, +) { + if hdrInfo.hdr.GetNonce() > lastCrossNotarizedHeader.GetNonce() { + log.Debug("saveCrossShardMetaHeader: higher nonce", + "hdr nonce", hdrInfo.hdr.GetNonce(), + "lastCrossNotarizedHeader nonce", lastCrossNotarizedHeader.GetNonce(), + ) + return } - if sp.hdrsForCurrBlock.missingHdrs == 0 && lastMetablockNonceWithProof == 0 { - sp.hdrsForCurrBlock.missingFinalityAttestingHdrs = sp.requestMissingFinalityAttestingHeaders( - core.MetachainShardId, - sp.metaBlockFinality, - ) + marshalizedHeader, errMarshal := sp.marshalizer.Marshal(hdrInfo.hdr) + if errMarshal != nil { + log.Debug("saveCrossShardMetaHeader.Marshal", "error", errMarshal.Error()) + return } - return sp.hdrsForCurrBlock.missingHdrs, sp.hdrsForCurrBlock.missingFinalityAttestingHdrs, sp.hdrsForCurrBlock.missingProofs + sp.saveMetaHeader(hdrInfo.hdr, hdrInfo.hash, marshalizedHeader) } func (sp *shardProcessor) verifyCrossShardMiniBlockDstMe(header data.ShardHeaderHandler) error { - miniBlockMetaHashes, err := sp.getAllMiniBlockDstMeFromMeta(header) + var miniBlockMetaHashes map[string][]byte + var crossMiniBlockHashes map[string]uint32 + var err error + if header.IsHeaderV3() { + miniBlockMetaHashes, err = sp.getAllMiniBlockDstMeFromMetaForProposal(header) + crossMiniBlockHashes = header.GetProposedMiniBlockHeadersWithDst(sp.shardCoordinator.SelfId()) + } else { + miniBlockMetaHashes, err = sp.getAllMiniBlockDstMeFromMeta(header) + crossMiniBlockHashes = header.GetMiniBlockHeadersWithDst(sp.shardCoordinator.SelfId()) + } if err != nil { return err } - crossMiniBlockHashes := header.GetMiniBlockHeadersWithDst(sp.shardCoordinator.SelfId()) for hash := range crossMiniBlockHashes { if _, ok := miniBlockMetaHashes[hash]; !ok { return process.ErrCrossShardMBWithoutConfirmationFromMeta @@ -1965,44 +2057,80 @@ func (sp *shardProcessor) verifyCrossShardMiniBlockDstMe(header data.ShardHeader return nil } -func (sp *shardProcessor) getAllMiniBlockDstMeFromMeta(header data.ShardHeaderHandler) (map[string][]byte, error) { +func (sp *shardProcessor) getAllMiniBlockDstMeFromMetaForProposal(header data.ShardHeaderHandler) (map[string][]byte, error) { lastCrossNotarizedHeader, _, err := sp.blockTracker.GetLastCrossNotarizedHeader(core.MetachainShardId) if err != nil { return nil, err } + var metaHeaderHandler data.HeaderHandler miniBlockMetaHashes := make(map[string][]byte) - - sp.hdrsForCurrBlock.mutHdrsForBlock.RLock() for _, metaBlockHash := range header.GetMetaBlockHashes() { - headerInfo, ok := sp.hdrsForCurrBlock.hdrHashAndInfo[string(metaBlockHash)] - if !ok { - continue - } - metaBlock, ok := headerInfo.hdr.(*block.MetaBlock) - if !ok { - continue - } - if metaBlock.GetRound() > header.GetRound() { - continue + metaHeaderHandler, err = sp.dataPool.Headers().GetHeaderByHash(metaBlockHash) + if err != nil { + return nil, err } - if metaBlock.GetRound() <= lastCrossNotarizedHeader.GetRound() { - continue + err = sp.addCrossShardMiniBlocksDstMeToMap(header, metaBlockHash, metaHeaderHandler, lastCrossNotarizedHeader, miniBlockMetaHashes) + if err != nil { + return nil, err } - if metaBlock.GetNonce() <= lastCrossNotarizedHeader.GetNonce() { - continue + } + + return miniBlockMetaHashes, nil +} + +func (sp *shardProcessor) getAllMiniBlockDstMeFromMeta(header data.ShardHeaderHandler) (map[string][]byte, error) { + lastCrossNotarizedHeader, _, err := sp.blockTracker.GetLastCrossNotarizedHeader(core.MetachainShardId) + if err != nil { + return nil, err + } + + miniBlockMetaHashes := make(map[string][]byte) + for _, metaBlockHash := range header.GetMetaBlockHashes() { + headerInfo, ok := sp.hdrsForCurrBlock.GetHeaderInfo(string(metaBlockHash)) + if !ok { + return nil, fmt.Errorf("%w for metaBlockHash %s", + process.ErrMissingHeader, hex.EncodeToString(metaBlockHash)) } - crossMiniBlockHashes := metaBlock.GetMiniBlockHeadersWithDst(sp.shardCoordinator.SelfId()) - for hash := range crossMiniBlockHashes { - miniBlockMetaHashes[hash] = metaBlockHash + err = sp.addCrossShardMiniBlocksDstMeToMap(header, metaBlockHash, headerInfo.GetHeader(), lastCrossNotarizedHeader, miniBlockMetaHashes) + if err != nil { + return nil, err } } - sp.hdrsForCurrBlock.mutHdrsForBlock.RUnlock() return miniBlockMetaHashes, nil } +func (sp *shardProcessor) addCrossShardMiniBlocksDstMeToMap( + header data.ShardHeaderHandler, + referencedMetaBlockHash []byte, + referencedMetaHeaderHandler data.HeaderHandler, + lastCrossNotarizedHeader data.HeaderHandler, + miniBlockMetaHashes map[string][]byte, +) error { + metaBlock, ok := referencedMetaHeaderHandler.(data.MetaHeaderHandler) + if !ok { + return process.ErrWrongTypeAssertion + } + if metaBlock.GetRound() > header.GetRound() { + return process.ErrHigherRoundInBlock + } + if metaBlock.GetRound() <= lastCrossNotarizedHeader.GetRound() { + return process.ErrLowerRoundInBlock + } + if metaBlock.GetNonce() <= lastCrossNotarizedHeader.GetNonce() { + return process.ErrLowerNonceInBlock + } + + crossMiniBlockHashes := metaBlock.GetMiniBlockHeadersWithDst(sp.shardCoordinator.SelfId()) + for hash := range crossMiniBlockHashes { + miniBlockMetaHashes[hash] = referencedMetaBlockHash + } + + return nil +} + // full verification through metachain header func (sp *shardProcessor) createAndProcessMiniBlocksDstMe(haveTime func() bool) (*createAndProcessMiniBlocksDestMeInfo, error) { log.Debug("createAndProcessMiniBlocksDstMe has been started") @@ -2040,7 +2168,6 @@ func (sp *shardProcessor) createAndProcessMiniBlocksDstMe(haveTime func() bool) } // do processing in order - sp.hdrsForCurrBlock.mutHdrsForBlock.Lock() for i := 0; i < len(orderedMetaBlocks); i++ { if !createAndProcessInfo.haveTime() && !createAndProcessInfo.haveAdditionalTime() { log.Debug("time is up after putting cross txs with destination to current shard", @@ -2078,10 +2205,7 @@ func (sp *shardProcessor) createAndProcessMiniBlocksDstMe(haveTime func() bool) createAndProcessInfo.currMetaHdrHash = orderedMetaBlocksHashes[i] if len(createAndProcessInfo.currMetaHdr.GetMiniBlockHeadersWithDst(sp.shardCoordinator.SelfId())) == 0 { - sp.hdrsForCurrBlock.hdrHashAndInfo[string(createAndProcessInfo.currMetaHdrHash)] = &hdrInfo{ - hdr: createAndProcessInfo.currMetaHdr, - usedInBlock: true, - } + sp.hdrsForCurrBlock.AddHeaderUsedInBlock(string(createAndProcessInfo.currMetaHdrHash), createAndProcessInfo.currMetaHdr) createAndProcessInfo.numHdrsAdded++ lastMetaHdr = createAndProcessInfo.currMetaHdr continue @@ -2092,7 +2216,6 @@ func (sp *shardProcessor) createAndProcessMiniBlocksDstMe(haveTime func() bool) shouldContinue, errCreated := sp.createMbsAndProcessCrossShardTransactionsDstMe(createAndProcessInfo) if errCreated != nil { - sp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() return nil, errCreated } if !shouldContinue { @@ -2101,7 +2224,6 @@ func (sp *shardProcessor) createAndProcessMiniBlocksDstMe(haveTime func() bool) lastMetaHdr = createAndProcessInfo.currMetaHdr } - sp.hdrsForCurrBlock.mutHdrsForBlock.Unlock() go sp.requestMetaHeadersIfNeeded(createAndProcessInfo.numHdrsAdded, lastMetaHdr) @@ -2145,10 +2267,7 @@ func (sp *shardProcessor) createMbsAndProcessCrossShardTransactionsDstMe( createAndProcessInfo.numTxsAdded += currNumTxsAdded if !createAndProcessInfo.hdrAdded && currNumTxsAdded > 0 { - sp.hdrsForCurrBlock.hdrHashAndInfo[string(createAndProcessInfo.currMetaHdrHash)] = &hdrInfo{ - hdr: createAndProcessInfo.currMetaHdr, - usedInBlock: true, - } + sp.hdrsForCurrBlock.AddHeaderUsedInBlock(string(createAndProcessInfo.currMetaHdrHash), createAndProcessInfo.currMetaHdr) createAndProcessInfo.numHdrsAdded++ createAndProcessInfo.hdrAdded = true } @@ -2180,7 +2299,7 @@ func (sp *shardProcessor) requestMetaHeadersIfNeeded(hdrsAdded uint32, lastMetaH "highest nonce", lastMetaHdr.GetNonce(), ) - roundTooOld := sp.roundHandler.Index() > int64(lastMetaHdr.GetRound()+process.MaxRoundsWithoutNewBlockReceived) + roundTooOld := sp.roundHandler.Index() > int64(lastMetaHdr.GetRound()+sp.getMaxRoundsWithoutBlockReceived(lastMetaHdr.GetRound())) shouldRequestCrossHeaders := hdrsAdded == 0 && roundTooOld if shouldRequestCrossHeaders { fromNonce := lastMetaHdr.GetNonce() + 1 @@ -2188,6 +2307,7 @@ func (sp *shardProcessor) requestMetaHeadersIfNeeded(hdrsAdded uint32, lastMetaH for nonce := fromNonce; nonce <= toNonce; nonce++ { sp.addHeaderIntoTrackerPool(nonce, core.MetachainShardId) sp.requestHandler.RequestMetaHeaderByNonce(nonce) + sp.requestProofIfNeeded(nonce, core.MetachainShardId, lastMetaHdr.GetEpoch()) } } } @@ -2265,7 +2385,23 @@ func (sp *shardProcessor) createMiniBlocks(haveTime func() bool, randomness []by return &block.Body{MiniBlocks: miniBlocks}, processedMiniBlocksDestMeInfo, nil } + if shouldDisableOutgoingTxs(sp.enableEpochsHandler, sp.enableRoundsHandler) { + interMBs := sp.txCoordinator.CreatePostProcessMiniBlocks() + miniBlocks = append(miniBlocks, interMBs...) + + log.Debug("outgoing transactions and mini blocks are disabled until Supernova round activation") + log.Debug("creating mini blocks has been finished", "num mini blocks", len(miniBlocks)) + + return &block.Body{MiniBlocks: miniBlocks}, processedMiniBlocksDestMeInfo, nil + } + + err = sp.recreateTrieIfNeeded() + if err != nil { + return nil, nil, err + } + startTime = time.Now() + mbsFromMe := sp.txCoordinator.CreateMbsAndProcessTransactionsFromMe(haveTime, randomness) elapsedTime = time.Since(startTime) log.Debug("elapsed time to create mbs from me", "time", elapsedTime) @@ -2287,6 +2423,12 @@ func (sp *shardProcessor) createMiniBlocks(haveTime func() bool, randomness []by return &block.Body{MiniBlocks: miniBlocks}, processedMiniBlocksDestMeInfo, nil } +func shouldDisableOutgoingTxs(enableEpochsHandler common.EnableEpochsHandler, enableRoundsHandler common.EnableRoundsHandler) bool { + isSupernovaEnabled := enableEpochsHandler.IsFlagEnabled(common.SupernovaFlag) + supernovaRoundEnabled := enableRoundsHandler.IsFlagEnabled(common.SupernovaRoundFlag) + return isSupernovaEnabled && !supernovaRoundEnabled +} + // applyBodyToHeader creates a miniblock header list given a block body func (sp *shardProcessor) applyBodyToHeader( shardHeader data.ShardHeaderHandler, @@ -2390,17 +2532,9 @@ func (sp *shardProcessor) applyBodyToHeader( return newBody, nil } -func (sp *shardProcessor) waitForMetaHdrHashes(waitTime time.Duration) error { - select { - case <-sp.chRcvAllHdrs: - return nil - case <-time.After(waitTime): - return process.ErrTimeIsOut - } -} - // MarshalizedDataToBroadcast prepares underlying data into a marshalized object according to destination func (sp *shardProcessor) MarshalizedDataToBroadcast( + headerHash []byte, header data.HeaderHandler, bodyHandler data.BodyHandler, ) (map[uint32][]byte, map[string][][]byte, error) { @@ -2415,32 +2549,14 @@ func (sp *shardProcessor) MarshalizedDataToBroadcast( } // Remove mini blocks which are not final from "body" to avoid sending them cross shard - newBodyToBroadcast, err := sp.getFinalMiniBlocks(header, body) + newBodyToBroadcast, miniBlocksMapToBroadcast, err := sp.getFinalMiniBlocks(headerHash, header, body) if err != nil { return nil, nil, err } - mrsTxs := sp.txCoordinator.CreateMarshalizedData(newBodyToBroadcast) + mrsTxs := sp.txCoordinator.CreateMarshalledDataForHeader(header, newBodyToBroadcast, miniBlocksMapToBroadcast) - bodies := make(map[uint32]block.MiniBlockSlice) - for _, miniBlock := range newBodyToBroadcast.MiniBlocks { - if miniBlock.SenderShardID != sp.shardCoordinator.SelfId() || - miniBlock.ReceiverShardID == sp.shardCoordinator.SelfId() { - continue - } - bodies[miniBlock.ReceiverShardID] = append(bodies[miniBlock.ReceiverShardID], miniBlock) - } - - mrsData := make(map[uint32][]byte, len(bodies)) - for shardId, subsetBlockBody := range bodies { - bodyForShard := block.Body{MiniBlocks: subsetBlockBody} - buff, errMarshal := sp.marshalizer.Marshal(&bodyForShard) - if errMarshal != nil { - log.Error("shardProcessor.MarshalizedDataToBroadcast.Marshal", "error", errMarshal.Error()) - continue - } - mrsData[shardId] = buff - } + mrsData := sp.marshalledBodyToBroadcast(newBodyToBroadcast) return mrsData, mrsTxs, nil } @@ -2457,24 +2573,27 @@ func (sp *shardProcessor) GetBlockBodyFromPool(headerHandler data.HeaderHandler) return nil, process.ErrWrongTypeAssertion } - miniBlocksPool := sp.dataPool.MiniBlocks() - var miniBlocks block.MiniBlockSlice + miniBlockHeaderHandlers, err := common.GetMiniBlockHeadersFromExecResult(header) + if err != nil { + return nil, err + } - for _, mbHeader := range header.GetMiniBlockHeaderHandlers() { - obj, hashInPool := miniBlocksPool.Get(mbHeader.GetHash()) - if !hashInPool { - continue - } + return sp.getBlockBodyFromPool(header, miniBlockHeaderHandlers) +} - miniBlock, typeOk := obj.(*block.MiniBlock) - if !typeOk { - return nil, process.ErrWrongTypeAssertion - } +// GetProposedAndExecutedMiniBlockHeaders returns block body from pool with proposed and executed miniblocks +func (sp *shardProcessor) GetProposedAndExecutedMiniBlockHeaders(headerHandler data.HeaderHandler) (data.BodyHandler, error) { + header, ok := headerHandler.(data.ShardHeaderHandler) + if !ok { + return nil, process.ErrWrongTypeAssertion + } - miniBlocks = append(miniBlocks, miniBlock) + miniBlockHeaderHandlers, err := getProposedAndExecutedMiniBlockHeaders(header) + if err != nil { + return nil, err } - return &block.Body{MiniBlocks: miniBlocks}, nil + return sp.getBlockBodyFromPool(header, miniBlockHeaderHandlers) } func (sp *shardProcessor) getBootstrapHeadersInfo( @@ -2530,11 +2649,6 @@ func (sp *shardProcessor) removeStartOfEpochBlockDataFromPools( return nil } -// Close - closes all underlying components -func (sp *shardProcessor) Close() error { - return sp.baseProcessor.Close() -} - // DecodeBlockHeader method decodes block header from a given byte array func (sp *shardProcessor) DecodeBlockHeader(dta []byte) data.HeaderHandler { if dta == nil { diff --git a/process/block/shardblockProposal.go b/process/block/shardblockProposal.go new file mode 100644 index 00000000000..ea6eea8b3b9 --- /dev/null +++ b/process/block/shardblockProposal.go @@ -0,0 +1,1062 @@ +package block + +import ( + "bytes" + "errors" + "fmt" + "sort" + "time" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + logger "github.com/multiversx/mx-chain-logger-go" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/state" +) + +type pendingBlocksAfterSelection struct { + headerHash []byte + header data.HeaderHandler + pendingMiniBlocks map[string]*block.MiniBlock +} + +// CreateNewHeaderProposal creates a new header proposal +func (sp *shardProcessor) CreateNewHeaderProposal(round uint64, nonce uint64) (data.HeaderHandler, error) { + epoch := sp.epochStartTrigger.MetaEpoch() + header := sp.versionedHeaderFactory.Create(epoch, round) + + shardHeader, ok := header.(data.ShardHeaderHandler) + if !ok { + return nil, process.ErrWrongTypeAssertion + } + if !shardHeader.IsHeaderV3() { + return nil, process.ErrInvalidHeader + } + + err := shardHeader.SetRound(round) + if err != nil { + return nil, err + } + + err = shardHeader.SetNonce(nonce) + if err != nil { + return nil, err + } + + return header, nil +} + +// CreateBlockProposal creates a block proposal without executing any of the transactions +func (sp *shardProcessor) CreateBlockProposal( + initialHdr data.HeaderHandler, + haveTime func() bool, +) (data.HeaderHandler, data.BodyHandler, error) { + if check.IfNil(initialHdr) { + return nil, nil, process.ErrNilBlockHeader + } + if !initialHdr.IsHeaderV3() { + return nil, nil, process.ErrInvalidHeader + } + shardHdr, ok := initialHdr.(data.ShardHeaderHandler) + if !ok { + return nil, nil, process.ErrWrongTypeAssertion + } + + err := sp.updateEpochIfNeeded(shardHdr) + if err != nil { + return nil, nil, err + } + + sp.gasComputation.Reset() + sp.miniBlocksSelectionSession.ResetSelectionSession() + err = sp.createBlockBodyProposal(shardHdr, haveTime) + if err != nil { + return nil, nil, err + } + + miniBlockHeaderHandlers := sp.miniBlocksSelectionSession.GetMiniBlockHeaderHandlers() + // todo: check empty mini blocks vs nil. Same for block.Body.MiniBlocks + err = shardHdr.SetMiniBlockHeaderHandlers(miniBlockHeaderHandlers) + if err != nil { + return nil, nil, err + } + + err = shardHdr.SetMetaBlockHashes(sp.miniBlocksSelectionSession.GetReferencedHeaderHashes()) + if err != nil { + return nil, nil, err + } + + totalTxCount := computeTxTotalTxCount(miniBlockHeaderHandlers) + err = shardHdr.SetTxCount(totalTxCount) + if err != nil { + return nil, nil, err + } + + miniBlocks := sp.miniBlocksSelectionSession.GetMiniBlocks() + err = checkMiniBlocksAndMiniBlockHeadersConsistency(miniBlocks, miniBlockHeaderHandlers) + if err != nil { + return nil, nil, err + } + + err = sp.addExecutionResultsOnHeader(shardHdr) + if err != nil { + return nil, nil, err + } + + err = sp.checkHeaderExecutionResultNonceGap(shardHdr) + if err != nil { + return nil, nil, err + } + + // TODO: sanity check use the verify execution results method + + body := &block.Body{MiniBlocks: miniBlocks} + + err = sp.verifyGasLimit(shardHdr, miniBlocks) + if err != nil { + return nil, nil, err + } + + sp.appStatusHandler.SetUInt64Value(common.MetricNumTxInBlock, uint64(totalTxCount)) + sp.appStatusHandler.SetUInt64Value(common.MetricNumMiniBlocks, uint64(len(body.MiniBlocks))) + + marshalledBody, err := sp.marshalizer.Marshal(body) + if err != nil { + return nil, nil, err + } + + sp.blockSizeThrottler.Add(shardHdr.GetRound(), uint32(len(marshalledBody))) + + defer func() { + go sp.checkAndRequestIfMetaHeadersMissing() + }() + + return shardHdr, body, nil +} + +// VerifyBlockProposal verifies the proposed block. It returns nil if all ok or the specific error +func (sp *shardProcessor) VerifyBlockProposal( + headerHandler data.HeaderHandler, + bodyHandler data.BodyHandler, + haveTime func() time.Duration, +) error { + err := sp.checkBlockValidity(headerHandler, bodyHandler) + if err != nil { + if errors.Is(err, process.ErrBlockHashDoesNotMatch) { + log.Debug("requested missing shard header", + "hash", headerHandler.GetPrevHash(), + "for shard", headerHandler.GetShardID(), + ) + + go sp.requestHandler.RequestShardHeaderForEpoch(headerHandler.GetShardID(), headerHandler.GetPrevHash(), headerHandler.GetEpoch()) + } + + return err + } + + log.Debug("started verifying proposed block", + "epoch", headerHandler.GetEpoch(), + "shard", headerHandler.GetShardID(), + "round", headerHandler.GetRound(), + "nonce", headerHandler.GetNonce(), + ) + + header, ok := headerHandler.(data.ShardHeaderHandler) + if !ok { + return process.ErrWrongTypeAssertion + } + + if !header.IsHeaderV3() { + return process.ErrInvalidHeader + } + + body, ok := bodyHandler.(*block.Body) + if !ok { + return process.ErrWrongTypeAssertion + } + + err = sp.checkHeaderBodyCorrelationProposal(header.GetMiniBlockHeaderHandlers(), body) + if err != nil { + return err + } + + err = sp.waitForExecutionResultsVerification(header, haveTime) + if err != nil { + return err + } + + err = sp.checkInclusionEstimationForExecutionResults(header) + if err != nil { + return err + } + + err = sp.checkHeaderExecutionResultNonceGap(header) + if err != nil { + return err + } + + sp.updateMetrics(header, body) + + sp.missingDataResolver.Reset() + sp.missingDataResolver.RequestBlockTransactions(body) + // the epoch start meta block and its proof is also requested here if missing + err = sp.missingDataResolver.RequestMissingMetaHeaders(header) + if err != nil { + return err + } + + err = sp.missingDataResolver.WaitForMissingData(haveTime()) + if err != nil { + return err + } + + defer func() { + go sp.checkAndRequestIfMetaHeadersMissing() + }() + + err = sp.checkEpochCorrectnessCrossChain() + if err != nil { + return err + } + + err = sp.checkEpochCorrectness(header) + if err != nil { + return err + } + + err = sp.checkMetaHeadersValidityAndFinalityProposal(header) + if err != nil { + return err + } + + err = sp.verifyCrossShardMiniBlockDstMe(header) + if err != nil { + return err + } + + err = sp.verifyGasLimit(header, body.MiniBlocks) + if err != nil { + return err + } + + hash, err := sp.getHeaderHash(header) + if err != nil { + return err + } + + return sp.OnProposedBlock(body, header, hash) +} + +func (sp *shardProcessor) updateMetrics(header data.HeaderHandler, body *block.Body) { + go getMetricsFromBlockBody(body, sp.marshalizer, sp.appStatusHandler) + + txCounts, rewardCounts, unsignedCounts := sp.txCounter.getPoolCounts(sp.dataPool) + log.Debug("total txs in pool", "counts", txCounts.String()) + log.Debug("total txs in rewards pool", "counts", rewardCounts.String()) + log.Debug("total txs in unsigned pool", "counts", unsignedCounts.String()) + + go getMetricsFromHeader(header, uint64(txCounts.GetTotal()), sp.marshalizer, sp.appStatusHandler) +} + +func getHaveTimeForProposal(startTime time.Time, maxDuration time.Duration) func() time.Duration { + timeOut := startTime.Add(maxDuration) + haveTime := func() time.Duration { + return time.Until(timeOut) + } + return haveTime +} + +// ProcessBlockProposal processes the proposed block. It returns nil if all ok or the specific error +func (sp *shardProcessor) ProcessBlockProposal( + headerHandler data.HeaderHandler, + headerHash []byte, + bodyHandler data.BodyHandler, +) (data.BaseExecutionResultHandler, error) { + if check.IfNil(headerHandler) { + return nil, process.ErrNilBlockHeader + } + if check.IfNil(bodyHandler) { + return nil, process.ErrNilBlockBody + } + if !headerHandler.IsHeaderV3() { + return nil, process.ErrInvalidHeader + } + + if !sp.processStatusHandler.TrySetBusy("shardProcessor.ProcessBlockProposal") { + return nil, process.ErrBlockProcessorBusy + } + defer sp.processStatusHandler.SetIdle() + + sp.roundNotifier.CheckRound(headerHandler) + sp.epochNotifier.CheckEpoch(headerHandler) + sp.requestHandler.SetEpoch(headerHandler.GetEpoch()) + + header, ok := headerHandler.(data.ShardHeaderHandler) + if !ok { + return nil, process.ErrWrongTypeAssertion + } + + body, ok := bodyHandler.(*block.Body) + if !ok { + return nil, process.ErrWrongTypeAssertion + } + + log.Debug("started processing block", + "epoch", headerHandler.GetEpoch(), + "shard", headerHandler.GetShardID(), + "round", headerHandler.GetRound(), + "nonce", headerHandler.GetNonce(), + ) + + if sp.accountsDB[state.UserAccountsState].JournalLen() != 0 { + log.Error("shardProcessor.ProcessBlockProposal first entry", "stack", string(sp.accountsDB[state.UserAccountsState].GetStackDebugFirstEntry())) + return nil, process.ErrAccountStateDirty + } + + // this is used now to reset the context for processing not creation of blocks + err := sp.createBlockStarted() + if err != nil { + return nil, err + } + + // should already be available in the pools since it passed the block proposal verification, + // but kept here to update the internal caches (txsForBlock, hdrsForCurrBlock) + sp.txCoordinator.RequestBlockTransactions(body) + sp.hdrsForCurrBlock.RequestMetaHeaders(header) + + // although we can have a long time for processing, it being decoupled from consensus, + // we still give some reasonable timeout + proposalStartTime := time.Now() + haveTime := getHaveTimeForProposal(proposalStartTime, sp.processConfigsHandler.GetMaxBlockProcessingTime(headerHandler.GetRound())) + + err = sp.txCoordinator.IsDataPreparedForProcessing(haveTime) + if err != nil { + return nil, err + } + + // TODO: improvement - add also a request if it is missing as a fallback, although it should not be missing at this point + err = sp.checkEpochStartInfoAvailableIfNeeded(header) + if err != nil { + return nil, err + } + + err = sp.hdrsForCurrBlock.WaitForHeadersIfNeeded(haveTime) + if err != nil { + return nil, err + } + + // TODO: check again before saving the last executed result + err = sp.blockChainHook.SetCurrentHeader(header) + if err != nil { + return nil, err + } + + defer func() { + if err != nil { + sp.RevertCurrentBlock() + } + }() + + err = sp.checkAndUpdateContextBeforeExecution(header, headerHash) + if err != nil { + return nil, err + } + + startTime := time.Now() + err = sp.txCoordinator.ProcessBlockTransaction(header, body, haveTime) + elapsedTime := time.Since(startTime) + log.Debug("elapsed time to process block transaction", + "time [s]", elapsedTime, + ) + if err != nil { + return nil, err + } + + err = sp.txCoordinator.VerifyCreatedBlockTransactions(header, body) + if err != nil { + return nil, err + } + + var executionResult data.BaseExecutionResultHandler + executionResult, err = sp.collectExecutionResults(headerHash, header, body) + if err != nil { + return nil, err + } + + err = sp.blockProcessingCutoffHandler.HandleProcessErrorCutoff(header) + if err != nil { + return nil, err + } + + return executionResult, nil +} + +// CommitBlockProposalState commits the accounts state after processing a block proposal +// and performs any post-commit operations (e.g. saving epoch start economics metrics). +func (sp *shardProcessor) CommitBlockProposalState(headerHandler data.HeaderHandler) error { + if check.IfNil(headerHandler) { + return process.ErrNilBlockHeader + } + + sp.cleanupDismissedEWLEntries() + + err := sp.commitState(headerHandler) + if err != nil { + return err + } + + header, ok := headerHandler.(data.ShardHeaderHandler) + if ok { + sp.saveEpochStartEconomicsIfNeeded(header) + } + + return nil +} + +// RevertBlockProposalState reverts the uncommitted accounts state after a block proposal processing failure +func (sp *shardProcessor) RevertBlockProposalState() { + sp.RevertCurrentBlock() +} + +func computeTxTotalTxCount(miniBlockHeaders []data.MiniBlockHeaderHandler) uint32 { + totalTxCount := uint32(0) + for i := range miniBlockHeaders { + totalTxCount += miniBlockHeaders[i].GetTxCount() + } + return totalTxCount +} + +func checkMiniBlocksAndMiniBlockHeadersConsistency(miniBlocks block.MiniBlockSlice, miniBlockHeaders []data.MiniBlockHeaderHandler) error { + if len(miniBlocks) != len(miniBlockHeaders) { + log.Warn("transactionCoordinator.verifyFees: num of mini blocks and mini blocks headers does not match", "num of mb", len(miniBlocks), "num of mbh", len(miniBlockHeaders)) + return process.ErrNumOfMiniBlocksAndMiniBlocksHeadersMismatch + } + + // TODO: check if the reserved field or other fields are consistent. + return nil +} + +func (sp *shardProcessor) createBlockBodyProposal( + shardHdr data.HeaderHandler, + haveTime func() bool, +) error { + sp.blockSizeThrottler.ComputeCurrentMaxSize() + + log.Debug("started creating block body", + "epoch", shardHdr.GetEpoch(), + "round", shardHdr.GetRound(), + "nonce", shardHdr.GetNonce(), + ) + + _, err := sp.prepareAccountsForProposal() + if err != nil { + return err + } + + return sp.createProposalMiniBlocks(haveTime, shardHdr.GetNonce()) +} + +func (sp *shardProcessor) selectIncomingMiniBlocksForProposal( + haveTime func() bool, +) ([]*pendingBlocksAfterSelection, error) { + log.Debug("selectIncomingMiniBlocksForProposal has been started") + + sw := core.NewStopWatch() + sw.Start("ComputeLongestMetaChainFromLastNotarized") + orderedMetaBlocks, orderedMetaBlocksHashes, err := sp.blockTracker.ComputeLongestMetaChainFromLastNotarized() + sw.Stop("ComputeLongestMetaChainFromLastNotarized") + log.Debug("measurements", sw.GetMeasurements()...) + if err != nil { + return nil, err + } + + log.Debug("meta blocks ordered", "num meta blocks", len(orderedMetaBlocks)) + + lastMetaHdr, _, err := sp.blockTracker.GetLastCrossNotarizedHeader(core.MetachainShardId) + if err != nil { + return nil, err + } + + pendingBlocks, err := sp.selectIncomingMiniBlocks(lastMetaHdr, orderedMetaBlocks, orderedMetaBlocksHashes, haveTime) + if err != nil { + return nil, err + } + + miniBlockHeaderHandlers := sp.miniBlocksSelectionSession.GetMiniBlockHeaderHandlers() + for _, miniBlockHeader := range miniBlockHeaderHandlers { + log.Debug("mini block info", + "type", miniBlockHeader.GetTypeInt32(), + "sender shard", miniBlockHeader.GetSenderShardID(), + "receiver shard", miniBlockHeader.GetReceiverShardID(), + "txs added", miniBlockHeader.GetTxCount()) + } + + log.Debug("selectIncomingMiniBlocksForProposal has been finished", + "num txs added", sp.miniBlocksSelectionSession.GetNumTxsAdded(), + "num referenced meta blocks", len(sp.miniBlocksSelectionSession.GetReferencedHeaders())) + + return pendingBlocks, nil +} + +func (sp *shardProcessor) selectIncomingMiniBlocks( + lastCrossNotarizedMetaHdr data.HeaderHandler, + orderedMetaBlocks []data.HeaderHandler, + orderedMetaBlocksHashes [][]byte, + haveTime func() bool, +) ([]*pendingBlocksAfterSelection, error) { + var currentMetaBlock data.HeaderHandler + var currentMetaBlockHash []byte + var pendingBlocks []*pendingBlocksAfterSelection + var errCreated error + var createIncomingMbsResult *CrossShardIncomingMbsCreationResult + lastMeta := lastCrossNotarizedMetaHdr + lastMetaAdded := lastCrossNotarizedMetaHdr + + for i := 0; i < len(orderedMetaBlocks); i++ { + if !haveTime() { + log.Debug("time is up after putting cross txs with destination to current shard", + "num txs added", sp.miniBlocksSelectionSession.GetNumTxsAdded(), + ) + break + } + + if len(sp.miniBlocksSelectionSession.GetReferencedHeaders()) >= process.MaxMetaHeadersAllowedInOneShardBlock { + log.Debug("maximum meta headers allowed to be included in one shard block has been reached", + "meta headers added", len(sp.miniBlocksSelectionSession.GetReferencedHeaders()), + ) + break + } + + currentMetaBlock = orderedMetaBlocks[i] + currentMetaBlockHash = orderedMetaBlocksHashes[i] + if currentMetaBlock.GetNonce() != lastMeta.GetNonce()+1 { + log.Debug("skip searching", + "last meta hdr nonce", lastMeta.GetNonce(), + "curr meta hdr nonce", currentMetaBlock.GetNonce()) + continue + } + + hasProofForHdr := sp.proofsPool.HasProof(core.MetachainShardId, currentMetaBlockHash) + if !hasProofForHdr { + log.Trace("no proof for meta header", + "hash", logger.DisplayByteSlice(currentMetaBlockHash), + ) + continue + } + + metaBlock, ok := currentMetaBlock.(data.MetaHeaderHandler) + if !ok { + log.Warn("selectIncomingMiniBlocks: wrong type assertion for meta block") + break + } + + if len(currentMetaBlock.GetMiniBlockHeadersWithDst(sp.shardCoordinator.SelfId())) == 0 { + // if all headers were completely included so far, include this one as well + if len(pendingBlocks) == 0 { + sp.miniBlocksSelectionSession.AddReferencedHeader(currentMetaBlock, currentMetaBlockHash) + lastMeta = currentMetaBlock + lastMetaAdded = currentMetaBlock + continue + } + + // if at least one previous header was not completely included, save the current one as pending but with no mini blocks + pendingBlocks = append(pendingBlocks, &pendingBlocksAfterSelection{ + headerHash: currentMetaBlockHash, + header: currentMetaBlock, + pendingMiniBlocks: nil, + }) + lastMeta = currentMetaBlock + continue + } + + currProcessedMiniBlocksInfo := sp.processedMiniBlocksTracker.GetProcessedMiniBlocksInfo(currentMetaBlockHash) + createIncomingMbsResult, errCreated = sp.createMbsCrossShardDstMe(currentMetaBlockHash, metaBlock, currProcessedMiniBlocksInfo) + if errCreated != nil { + return nil, errCreated + } + + if len(createIncomingMbsResult.AddedMiniBlocks) > 0 { + errAdd := sp.miniBlocksSelectionSession.AddMiniBlocksAndHashes(createIncomingMbsResult.AddedMiniBlocks) + if errAdd != nil { + return nil, errAdd + } + sp.miniBlocksSelectionSession.AddReferencedHeader(currentMetaBlock, currentMetaBlockHash) + lastMeta = currentMetaBlock + lastMetaAdded = currentMetaBlock + } + + if createIncomingMbsResult.HeaderFinished { + continue + } + + pendingBlocks = append(pendingBlocks, &pendingBlocksAfterSelection{ + headerHash: currentMetaBlockHash, + header: currentMetaBlock, + pendingMiniBlocks: miniBlocksSliceToMap(createIncomingMbsResult.PendingMiniBlocks), + }) + + // if missing data detected, break after saving pending as they are part of the same header already referenced + if createIncomingMbsResult.HasMissingData { + break + } + + canAddMorePendingMiniBlocks := sp.gasComputation.CanAddPendingIncomingMiniBlocks() + if !canAddMorePendingMiniBlocks { + break + } + + // continue saving pending mini blocks until they are done or there is no possible space left in the block + lastMeta = currentMetaBlock + } + + go sp.requestHeadersFromHeaderIfNeeded(lastMetaAdded) + + return pendingBlocks, nil +} + +func miniBlocksSliceToMap(miniBlocksAndHashes []block.MiniblockAndHash) map[string]*block.MiniBlock { + result := make(map[string]*block.MiniBlock, len(miniBlocksAndHashes)) + for _, miniBlockAndHash := range miniBlocksAndHashes { + result[string(miniBlockAndHash.Hash)] = miniBlockAndHash.Miniblock + } + return result +} + +func (sp *shardProcessor) createProposalMiniBlocks( + haveTime func() bool, + nonce uint64, +) error { + if !haveTime() { + log.Debug("shardProcessor.createProposalMiniBlocks", "error", process.ErrTimeIsOut) + return nil + } + startTime := time.Now() + pendingBlocks, err := sp.selectIncomingMiniBlocksForProposal(haveTime) + if err != nil { + return err + } + elapsedTime := time.Since(startTime) + log.Debug("elapsed time to create mbs to me", "time", elapsedTime) + + outgoingTransactions, pendingIncomingMiniBlocksAdded := sp.selectOutgoingTransactions(nonce, haveTime) + + err = sp.appendPendingMiniBlocksAfterSelectingOutgoingTransactions(pendingBlocks, pendingIncomingMiniBlocksAdded) + if err != nil { + return err + } + + err = sp.miniBlocksSelectionSession.CreateAndAddMiniBlockFromTransactions(outgoingTransactions) + if err != nil { + log.Debug("shardProcessor.createProposalMiniBlocks", "error", err.Error()) + return err + } + + // todo: maybe sanitize, removing empty miniBlocks + + return nil +} + +func (sp *shardProcessor) appendPendingMiniBlocksAfterSelectingOutgoingTransactions( + pendingBlocksLeft []*pendingBlocksAfterSelection, + pendingIncomingMiniBlocksAdded []data.MiniBlockHeaderHandler, +) error { + if len(pendingIncomingMiniBlocksAdded) == 0 { + // nothing else added, won't append more + return nil + } + + referencedHeaders := sp.miniBlocksSelectionSession.GetReferencedHeaders() + if len(referencedHeaders) == 0 { + log.Error("appendPendingMiniBlocksAfterSelectingOutgoingTransactions: no header referenced yet") + return process.ErrNoReferencedHeader + } + + extraMiniBlocksAdded := make([]block.MiniblockAndHash, len(pendingIncomingMiniBlocksAdded)) + lastNonceReferenced := referencedHeaders[len(referencedHeaders)-1].GetNonce() + lastHeaderReferencedFinished := false + for i, pendingMbAdded := range pendingIncomingMiniBlocksAdded { + miniBlockAndHash, headerHash, header, found, isHeaderFinished := findPendingMiniBlock(pendingBlocksLeft, pendingMbAdded) + if !found { + log.Error("pending mini block added does not exists in the remaining pending list") + return process.ErrInvalidHash + } + + extraMiniBlocksAdded[i] = miniBlockAndHash + + // if this is still the last one referenced, continue adding its mini blocks + // possible gaps should have been filled already and the current one already referenced + if header.GetNonce() == lastNonceReferenced { + continue + } + + // if the header is consecutive to the previous one, reference it + if header.GetNonce() == lastNonceReferenced+1 { + sp.miniBlocksSelectionSession.AddReferencedHeader(header, headerHash) + lastNonceReferenced = header.GetNonce() + lastHeaderReferencedFinished = isHeaderFinished + continue + } + + // if the header is not consecutive, check for all headers that should be in between + // return error if missing, all of them should be available and without mini blocks with dest me + for missingNonce := lastNonceReferenced + 1; missingNonce < header.GetNonce(); missingNonce++ { + hash, hdr, err := findPendingHeaderWithNonceAndNoMiniBlocksDstMe(missingNonce, pendingBlocksLeft) + if err != nil { + return err + } + + sp.miniBlocksSelectionSession.AddReferencedHeader(hdr, hash) + } + + sp.miniBlocksSelectionSession.AddReferencedHeader(header, headerHash) + lastNonceReferenced = header.GetNonce() + lastHeaderReferencedFinished = isHeaderFinished + } + + // if the last header referenced was finished, continue referencing headers that do not have mini blocks dest me + if lastHeaderReferencedFinished { + referencedHeaders = sp.miniBlocksSelectionSession.GetReferencedHeaders() + lastNonceReferenced = referencedHeaders[len(referencedHeaders)-1].GetNonce() + for { + hash, hdr, err := findPendingHeaderWithNonceAndNoMiniBlocksDstMe(lastNonceReferenced+1, pendingBlocksLeft) + if err != nil { + break + } + + sp.miniBlocksSelectionSession.AddReferencedHeader(hdr, hash) + lastNonceReferenced = hdr.GetNonce() + } + } + + return sp.miniBlocksSelectionSession.AddMiniBlocksAndHashes(extraMiniBlocksAdded) +} + +func findPendingMiniBlock( + pendingBlocksLeft []*pendingBlocksAfterSelection, + pendingMbAdded data.MiniBlockHeaderHandler, +) (block.MiniblockAndHash, []byte, data.HeaderHandler, bool, bool) { + for _, pendingMiniBlocksForHeader := range pendingBlocksLeft { + miniBlock, ok := pendingMiniBlocksForHeader.pendingMiniBlocks[string(pendingMbAdded.GetHash())] + if ok { + // update the map + delete(pendingMiniBlocksForHeader.pendingMiniBlocks, string(pendingMbAdded.GetHash())) + isHeaderFinished := len(pendingMiniBlocksForHeader.pendingMiniBlocks) == 0 + + return block.MiniblockAndHash{ + Miniblock: miniBlock, + Hash: pendingMbAdded.GetHash(), + }, + pendingMiniBlocksForHeader.headerHash, + pendingMiniBlocksForHeader.header, + ok, + isHeaderFinished + } + } + + return block.MiniblockAndHash{}, nil, nil, false, false +} + +func findPendingHeaderWithNonceAndNoMiniBlocksDstMe( + nonce uint64, + pendingBlocksLeft []*pendingBlocksAfterSelection, +) ([]byte, data.HeaderHandler, error) { + for _, pendingBlock := range pendingBlocksLeft { + if pendingBlock.header.GetNonce() == nonce { + hasPendingMiniBlocks := len(pendingBlock.pendingMiniBlocks) > 0 + if !hasPendingMiniBlocks { + return pendingBlock.headerHash, pendingBlock.header, nil + } + + log.Error("findPendingHeaderWithNonceAndNoMiniBlocksDstMe: pending block should not have pending mini blocks with destination me", "nonce", nonce, "pending mini blocks", len(pendingBlock.pendingMiniBlocks)) + + return nil, nil, process.ErrInvalidHeader + } + } + + return nil, nil, process.ErrMissingHeader +} + +func (sp *shardProcessor) selectOutgoingTransactions( + nonce uint64, + haveTimeForSelection func() bool, +) ([][]byte, []data.MiniBlockHeaderHandler) { + log.Debug("selectOutgoingTransactions has been started") + + sw := core.NewStopWatch() + sw.Start("selectOutgoingTransactions") + defer func() { + sw.Stop("selectOutgoingTransactions") + log.Debug("measurements", sw.GetMeasurements()...) + }() + + outgoingTransactions, pendingIncomingMiniBlocksAdded := sp.txCoordinator.SelectOutgoingTransactions(nonce, haveTimeForSelection) + log.Debug("selectOutgoingTransactions has been finished", + "num txs", len(outgoingTransactions), + "num pending mini blocks added", len(pendingIncomingMiniBlocksAdded)) + + return outgoingTransactions, pendingIncomingMiniBlocksAdded +} + +func (sp *shardProcessor) checkMetaHeadersValidityAndFinalityProposal(header data.ShardHeaderHandler) error { + lastCrossNotarizedHeader, _, err := sp.blockTracker.GetLastCrossNotarizedHeader(core.MetachainShardId) + if err != nil { + return err + } + _, usedMetaHeaders, err := sp.getReferencedMetaHeadersFromPool(header) + if err != nil { + return fmt.Errorf("%w : checkMetaHeadersValidityAndFinalityProposal -> getReferencedMetaHeadersFromPool", err) + } + + for _, metaHeader := range usedMetaHeaders { + err = sp.headerValidator.IsHeaderConstructionValid(metaHeader, lastCrossNotarizedHeader) + if err != nil { + return fmt.Errorf("%w : checkMetaHeadersValidityAndFinalityProposal -> isHdrConstructionValid", err) + } + + err = sp.checkHeaderHasProof(metaHeader) + if err != nil { + return fmt.Errorf("%w : checkMetaHeadersValidityAndFinalityProposal -> checkHeaderHasProof", err) + } + lastCrossNotarizedHeader = metaHeader + } + + return nil +} + +func (sp *shardProcessor) getReferencedMetaHeadersFromPool(header data.ShardHeaderHandler) ([][]byte, []data.HeaderHandler, error) { + usedMetaHdrHashes := header.GetMetaBlockHashes() + usedMetaHeaders := make([]data.HeaderHandler, 0, len(usedMetaHdrHashes)) + var metaHdr data.HeaderHandler + var err error + for _, metaHdrHash := range usedMetaHdrHashes { + metaHdr, err = sp.dataPool.Headers().GetHeaderByHash(metaHdrHash) + if err != nil { + return nil, nil, err + } + usedMetaHeaders = append(usedMetaHeaders, metaHdr) + } + + return usedMetaHdrHashes, usedMetaHeaders, nil +} + +// collectExecutionResults collects the execution results after processing the block +func (sp *shardProcessor) collectExecutionResults(headerHash []byte, header data.HeaderHandler, body *block.Body) (data.BaseExecutionResultHandler, error) { + miniBlockHeaderHandlers, totalTxCount, receiptHash, err := sp.collectMiniBlocks(headerHash, body) + if err != nil { + return nil, err + } + + gasAndFees := sp.getGasAndFees() + gasNotUsedForProcessing := gasAndFees.GetGasPenalized() + gasAndFees.GetGasRefunded() + if gasAndFees.GetGasProvided() < gasNotUsedForProcessing { + return nil, process.ErrGasUsedExceedsGasProvided + } + + gasUsed := gasAndFees.GetGasProvided() - gasNotUsedForProcessing // needed for inclusion estimation + + executionResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: headerHash, + HeaderNonce: header.GetNonce(), + HeaderRound: header.GetRound(), + HeaderEpoch: header.GetEpoch(), + RootHash: sp.getRootHash(), + GasUsed: gasUsed, + }, + ReceiptsHash: receiptHash, + DeveloperFees: gasAndFees.GetDeveloperFees(), + AccumulatedFees: gasAndFees.GetAccumulatedFees(), + ExecutedTxCount: uint64(totalTxCount), + } + + err = executionResult.SetMiniBlockHeadersHandlers(miniBlockHeaderHandlers) + if err != nil { + return nil, err + } + + logs := sp.txCoordinator.GetAllCurrentLogs() + err = sp.cacheLogEvents(headerHash, logs) + if err != nil { + return nil, err + } + + err = sp.cacheIntermediateTxsForHeader(headerHash) + if err != nil { + return nil, err + } + + sp.cacheOrderedTxHashes(headerHash) + sp.cacheUnexecutableTxHashes(headerHash) + sp.cacheHeaderGasData(headerHash) + + return executionResult, nil +} + +func (sp *shardProcessor) getOrderedProcessedMetaBlocksFromMiniBlockHashesV3( + header data.HeaderHandler, + miniBlockHashes map[int][]byte, +) ([]data.HeaderHandler, []*hashAndHdr, error) { + shardHeader, ok := header.(data.ShardHeaderHandler) + if !ok { + return nil, nil, process.ErrWrongTypeAssertion + } + metaHeaderHashes, metaHeaders, err := sp.getReferencedMetaHeadersFromPool(shardHeader) + if err != nil { + return nil, nil, err + } + + hashSet := make(map[string]struct{}, len(miniBlockHashes)) + for _, b := range miniBlockHashes { + hashSet[string(b)] = struct{}{} + } + + partialReferencedMetaBlocks := make([]*hashAndHdr, 0) + fullyReferencedMetaBlocks := make([]data.HeaderHandler, 0, len(metaHeaders)) + + var remaining int + var metaHeaderHash []byte + for i, metaHeader := range metaHeaders { + metaHeaderHash = metaHeaderHashes[i] + crossMiniBlockHashes := metaHeader.GetMiniBlockHeadersWithDst(sp.shardCoordinator.SelfId()) + if len(crossMiniBlockHashes) == 0 { + fullyReferencedMetaBlocks = append(fullyReferencedMetaBlocks, metaHeader) + continue + } + + for hash := range crossMiniBlockHashes { + if sp.processedMiniBlocksTracker.IsMiniBlockFullyProcessed(metaHeaderHash, []byte(hash)) { + hashSet[hash] = struct{}{} + } + } + + remaining = len(crossMiniBlockHashes) + for k := range crossMiniBlockHashes { + _, found := hashSet[k] + if found { + remaining-- + } + } + if remaining == 0 { + fullyReferencedMetaBlocks = append(fullyReferencedMetaBlocks, metaHeader) + } else { + partialReferencedMetaBlocks = append(partialReferencedMetaBlocks, &hashAndHdr{ + hdr: metaHeader, + hash: metaHeaderHash, + }) + } + } + + process.SortHeadersByNonce(fullyReferencedMetaBlocks) + sort.Slice(partialReferencedMetaBlocks, func(i, j int) bool { + return partialReferencedMetaBlocks[i].hdr.GetNonce() < partialReferencedMetaBlocks[j].hdr.GetNonce() + }) + + return fullyReferencedMetaBlocks, partialReferencedMetaBlocks, nil +} + +func (sp *shardProcessor) saveEpochStartEconomicsIfNeeded(header data.ShardHeaderHandler) { + metaEpochChangeHeaderHash, metaEpochChangeHeader, err := sp.extractEpochChangeHeader(header) + if err != nil { + return + } + + // if epoch change header found, extract epoch change proposed header + metaEpochChangeProposedHeader, err := sp.extractEpochChangeProposedHeader(metaEpochChangeHeader) + if err != nil { + return + } + + sp.saveEconomicsMetricFromHeaders(metaEpochChangeHeader, metaEpochChangeProposedHeader, metaEpochChangeHeaderHash) +} + +func (sp *shardProcessor) extractEpochChangeHeader(header data.ShardHeaderHandler) ([]byte, data.MetaHeaderHandler, error) { + for _, metaHash := range header.GetMetaBlockHashes() { + hdr, err := sp.getHeaderFromHash(header.IsHeaderV3(), metaHash, core.MetachainShardId) + if err != nil { + return nil, nil, err + } + metaHdr, ok := hdr.(data.MetaHeaderHandler) + if !ok { + continue + } + + // save epoch change header if found + if metaHdr.IsStartOfEpochBlock() { + return metaHash, metaHdr, nil + } + } + + return nil, nil, process.ErrMissingHeader +} + +func (sp *shardProcessor) extractEpochChangeProposedHeader(epochChangeHeader data.MetaHeaderHandler) (data.MetaHeaderHandler, error) { + for _, execResult := range epochChangeHeader.GetExecutionResultsHandlers() { + hdr, err := sp.getHeaderFromHash(epochChangeHeader.IsHeaderV3(), execResult.GetHeaderHash(), core.MetachainShardId) + if err != nil { + return nil, err + } + metaHdr, ok := hdr.(data.MetaHeaderHandler) + if !ok { + continue + } + + if !metaHdr.IsEpochChangeProposed() { + continue + } + + return metaHdr, nil + } + + return nil, process.ErrMissingHeader +} + +func (sp *shardProcessor) saveEconomicsMetricFromHeaders( + metaEpochChangeHeader data.MetaHeaderHandler, + metaEpochChangeProposedHeader data.MetaHeaderHandler, + metaEpochChangeHeaderHash []byte, +) { + // iterate through all headers starting from epoch change header up until epoch change proposed header + currentNonce := metaEpochChangeHeader.GetNonce() + currentHash := metaEpochChangeHeaderHash + for currentNonce > metaEpochChangeProposedHeader.GetNonce() { + intermHeader, err := sp.getHeaderFromHash(true, currentHash, core.MetachainShardId) + if err != nil { + // if a header is not found, return here to close the loop. should not be blocking + return + } + currentNonce = intermHeader.GetNonce() + + updatedMetrics := sp.saveEconomicsMetricFromExecutionResults(intermHeader.GetExecutionResultsHandlers(), metaEpochChangeProposedHeader.GetPrevHash()) + if updatedMetrics { + return + } + + currentHash = intermHeader.GetPrevHash() + } +} + +func (sp *shardProcessor) saveEconomicsMetricFromExecutionResults( + execResults []data.BaseExecutionResultHandler, + headerHashWithFees []byte, +) bool { + for _, execResult := range execResults { + if !bytes.Equal(headerHashWithFees, execResult.GetHeaderHash()) { + continue + } + + metaExecResultWithFees, okMetaExecResultCast := execResult.(data.BaseMetaExecutionResultHandler) + if !okMetaExecResultCast { + continue + } + + sp.appStatusHandler.SetStringValue(common.MetricTotalFees, metaExecResultWithFees.GetAccumulatedFeesInEpoch().String()) + sp.appStatusHandler.SetStringValue(common.MetricDevRewardsInEpoch, metaExecResultWithFees.GetDevFeesInEpoch().String()) + return true + } + + return false +} diff --git a/process/block/shardblockProposal_test.go b/process/block/shardblockProposal_test.go new file mode 100644 index 00000000000..d91f516f471 --- /dev/null +++ b/process/block/shardblockProposal_test.go @@ -0,0 +1,4850 @@ +package block_test + +import ( + "bytes" + "errors" + "fmt" + "math/big" + "sync" + "testing" + "time" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/common/graceperiod" + "github.com/multiversx/mx-chain-go/config" + retriever "github.com/multiversx/mx-chain-go/dataRetriever" + "github.com/multiversx/mx-chain-go/dataRetriever/blockchain" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionManager" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionTrack" + blproc "github.com/multiversx/mx-chain-go/process/block" + "github.com/multiversx/mx-chain-go/process/block/processedMb" + "github.com/multiversx/mx-chain-go/process/estimator" + "github.com/multiversx/mx-chain-go/process/mock" + "github.com/multiversx/mx-chain-go/state" + "github.com/multiversx/mx-chain-go/storage" + "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/cache" + commonMocks "github.com/multiversx/mx-chain-go/testscommon/common" + "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" + "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" + "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" + "github.com/multiversx/mx-chain-go/testscommon/mbSelection" + "github.com/multiversx/mx-chain-go/testscommon/pool" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" + "github.com/multiversx/mx-chain-go/testscommon/round" + stateMock "github.com/multiversx/mx-chain-go/testscommon/state" + statusHandlerMock "github.com/multiversx/mx-chain-go/testscommon/statusHandler" +) + +func getSimpleHeaderV3Mock() *testscommon.HeaderHandlerStub { + return &testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { + return true + }, + GetLastExecutionResultHandlerCalled: func() data.LastExecutionResultHandler { + return &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + } + }, + GetPrevHashCalled: func() []byte { + return []byte("prev hash") + }, + } +} + +func haveTimeTrue() bool { + return true +} + +func haveTimeFalse() bool { + return false +} + +type processorTest interface { + CreateBlockProposal( + initialHdr data.HeaderHandler, + haveTime func() bool, + ) (data.HeaderHandler, data.BodyHandler, error) +} + +func TestShardProcessor_CreateBlockProposal(t *testing.T) { + t.Parallel() + + t.Run("nil header", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + checkCreateBlockProposalResult(t, sp, nil, haveTimeTrue, process.ErrNilBlockHeader) + }) + t.Run("not header v3", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + metaHdr := &block.MetaBlock{ + Nonce: 1, + Round: 1, + } + + checkCreateBlockProposalResult(t, sp, metaHdr, haveTimeTrue, process.ErrInvalidHeader) + }) + t.Run("meta header v3", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + metaHdr := &block.MetaBlockV3{ + Nonce: 1, + Round: 1, + } + + checkCreateBlockProposalResult(t, sp, metaHdr, haveTimeTrue, process.ErrWrongTypeAssertion) + }) + t.Run("updateEpochIfNeeded fails due to error on SetEpochStartMetaHash", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + arguments.EpochStartTrigger = &mock.EpochStartTriggerStub{ + IsEpochStartCalled: func() bool { + return true + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + header := getSimpleHeaderV3Mock() + header.SetEpochStartMetaHashCalled = func(hash []byte) error { + return expectedErr + } + + checkCreateBlockProposalResult(t, sp, header, haveTimeTrue, expectedErr) + }) + t.Run("updateEpochIfNeeded fails due to error on SetEpoch", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + arguments.EpochStartTrigger = &mock.EpochStartTriggerStub{ + IsEpochStartCalled: func() bool { + return true + }, + MetaEpochCalled: func() uint32 { + return 1 + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + header := getSimpleHeaderV3Mock() + header.EpochField = 2 // different from the one from epochStartTrigger + header.SetEpochCalled = func(epoch uint32) error { + return expectedErr + } + + checkCreateBlockProposalResult(t, sp, header, haveTimeTrue, expectedErr) + }) + t.Run("selectIncomingMiniBlocksForProposal fails due to error on ComputeLongestMetaChainFromLastNotarized", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + updateBlockchainForOnProposed(dataComponents) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.BlockTracker = &mock.BlockTrackerMock{ + ComputeLongestMetaChainFromLastNotarizedCalled: func() ([]data.HeaderHandler, [][]byte, error) { + return nil, nil, expectedErr + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + checkCreateBlockProposalResult(t, sp, getSimpleHeaderV3Mock(), haveTimeTrue, expectedErr) + }) + t.Run("selectIncomingMiniBlocksForProposal fails due to error on GetLastCrossNotarizedHeader", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + updateBlockchainForOnProposed(dataComponents) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.BlockTracker = &mock.BlockTrackerMock{ + ComputeLongestMetaChainFromLastNotarizedCalled: func() ([]data.HeaderHandler, [][]byte, error) { + return []data.HeaderHandler{&block.MetaBlockV3{ + ShardInfo: []block.ShardData{ + { + ShardID: 1, + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + SenderShardID: 1, + ReceiverShardID: 0, + }, + }, + }, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + {}, + }, + }}, [][]byte{[]byte("hash")}, nil + }, + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return nil, nil, expectedErr + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + checkCreateBlockProposalResult(t, sp, getSimpleHeaderV3Mock(), haveTimeTrue, expectedErr) + }) + t.Run("selectIncomingMiniBlocksForProposal fails due to error on selectIncomingMiniBlocks", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + headers := dataComponents.DataPool.Headers() + updateBlockchainForOnProposed(dataComponents) + dataComponents.DataPool = &dataRetriever.PoolsHolderStub{ + ProofsCalled: func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + }, + HeadersCalled: func() retriever.HeadersPool { + return headers + }, + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, + } + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.BlockTracker = &mock.BlockTrackerMock{ + ComputeLongestMetaChainFromLastNotarizedCalled: func() ([]data.HeaderHandler, [][]byte, error) { + return []data.HeaderHandler{&block.MetaBlockV3{ + Nonce: 1, + ShardInfo: []block.ShardData{ + { + ShardID: 1, + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + SenderShardID: 1, + ReceiverShardID: 0, + }, + }, + }, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + {}, + }, + }}, [][]byte{[]byte("hash")}, nil + }, + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.MetaBlockV3{}, []byte("hash"), nil // dummy + }, + } + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + CreateMbsCrossShardDstMeCalled: func(header data.HeaderHandler, processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo) ([]block.MiniblockAndHash, []block.MiniblockAndHash, uint32, bool, bool, error) { + return nil, nil, 0, false, false, expectedError + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + checkCreateBlockProposalResult(t, sp, getSimpleHeaderV3Mock(), haveTimeTrue, expectedErr) + }) + t.Run("createProposalMiniBlocks fails due to error on CreateAndAddMiniBlockFromTransactions", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + updateBlockchainForOnProposed(dataComponents) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + CreateAndAddMiniBlockFromTransactionsCalled: func(txHashes [][]byte) error { + return expectedErr + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + checkCreateBlockProposalResult(t, sp, getSimpleHeaderV3Mock(), haveTimeTrue, expectedErr) + }) + t.Run("checkMiniBlocksAndMiniBlockHeadersConsistency fails due to different lengths", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + updateBlockchainForOnProposed(dataComponents) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + GetMiniBlockHeaderHandlersCalled: func() []data.MiniBlockHeaderHandler { + return []data.MiniBlockHeaderHandler{ + &block.MiniBlockHeader{ + Hash: []byte("mbHash1"), + SenderShardID: 0, + ReceiverShardID: 1, + TxCount: 1, + }, + } + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + checkCreateBlockProposalResult(t, sp, getSimpleHeaderV3Mock(), haveTimeTrue, process.ErrNumOfMiniBlocksAndMiniBlocksHeadersMismatch) + }) + t.Run("addExecutionResultsOnHeader fails due to error on GetPendingExecutionResults", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + updateBlockchainForOnProposed(dataComponents) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ExecutionManager = &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return nil, expectedErr + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + checkCreateBlockProposalResult(t, sp, getSimpleHeaderV3Mock(), haveTimeTrue, expectedErr) + }) + t.Run("addExecutionResultsOnHeader fails due to error on SetLastExecutionResultHandler", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV2{} // using V2 for simplicity + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash") + }, + } + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ExecutionResultsInclusionEstimator = &processMocks.InclusionEstimatorMock{ + DecideCalled: func(lastNotarised *common.LastExecutionResultForInclusion, pending []data.BaseExecutionResultHandler, currentHdrTsMs uint64) (allowed int) { + return 1 // coverage only + }, + } + arguments.ExecutionManager = &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + header := getSimpleHeaderV3Mock() + header.SetLastExecutionResultHandlerCalled = func(resultHandler data.LastExecutionResultHandler) error { + return expectedErr + } + checkCreateBlockProposalResult(t, sp, header, haveTimeTrue, expectedErr) + }) + t.Run("SetMiniBlockHeaderHandlers failure", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + updateBlockchainForOnProposed(dataComponents) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + header := getSimpleHeaderV3Mock() + header.SetMiniBlockHeaderHandlersCalled = func(mbsHandlers []data.MiniBlockHeaderHandler) error { + return expectedErr + } + checkCreateBlockProposalResult(t, sp, header, haveTimeFalse, expectedErr) // using haveTimeFalse for extra coverage + }) + t.Run("SetMetaBlockHashes failure", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + updateBlockchainForOnProposed(dataComponents) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + header := getSimpleHeaderV3Mock() + header.SetMetaBlockHashesCalled = func(hashes [][]byte) error { + return expectedErr + } + checkCreateBlockProposalResult(t, sp, header, haveTimeTrue, expectedErr) + }) + t.Run("SetTxCount failure", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + updateBlockchainForOnProposed(dataComponents) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + header := getSimpleHeaderV3Mock() + header.SetTxCountCalled = func(count uint32) error { + return expectedErr + } + checkCreateBlockProposalResult(t, sp, header, haveTimeTrue, expectedErr) + }) + t.Run("addExecutionResultsOnHeader fails due to error on CreateDataForInclusionEstimation", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + checkCreateBlockProposalResult(t, sp, getSimpleHeaderV3Mock(), haveTimeTrue, process.ErrNilHeaderHandler) + }) + t.Run("Marshal failure", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV2{} // using V2 for simplicity + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash") + }, + } + coreComponents.IntMarsh = &mock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + return nil, expectedErr + }, + } + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + checkCreateBlockProposalResult(t, sp, getSimpleHeaderV3Mock(), haveTimeTrue, expectedErr) + }) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + coreComponents.IntMarsh = &mock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + return []byte("marshalled"), nil + }, + } + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return getSimpleHeaderV3Mock() + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash") + }, + } + dataComponents.DataPool = &dataRetriever.PoolsHolderStub{ + ProofsCalled: func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + }, + HeadersCalled: func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + }, + } + }, + TransactionsCalled: func() retriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + SearchFirstDataCalled: func(key []byte) (value interface{}, ok bool) { + value = &transaction.Transaction{} + ok = true + return + }, + } + }, + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, + } + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.BlockTracker = &mock.BlockTrackerMock{ + ComputeLongestMetaChainFromLastNotarizedCalled: func() ([]data.HeaderHandler, [][]byte, error) { + return []data.HeaderHandler{ + &block.MetaBlockV3{ + Nonce: 1, + ShardInfo: []block.ShardData{ + { + Nonce: 0, + ShardID: 1, + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + SenderShardID: 1, + ReceiverShardID: 0, + }, + }, + }, + // for extra coverage, should be skipped as it is empty + { + Nonce: 1, + ShardID: 1, + ShardMiniBlockHeaders: []block.MiniBlockHeader{}, + }, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + {}, {}, + }, + }, + &block.MetaBlockV3{Nonce: 2}, + &block.MetaBlockV3{ + Nonce: 3, + ShardInfo: []block.ShardData{ + // should be saved as pending and added later + { + Nonce: 2, + ShardID: 1, + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + SenderShardID: 1, + ReceiverShardID: 0, + }, + }, + }, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + {}, + }, + }, + }, + [][]byte{ + []byte("hash_ok"), + []byte("hash_empty"), + []byte("hash_pending"), + }, + nil + }, + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.MetaBlockV3{ + Nonce: 0, + }, []byte("hash"), nil // dummy + }, + } + providedMb := &block.MiniBlock{ + TxHashes: [][]byte{[]byte("tx_hash")}, + } + providedPendingMb := &block.MiniBlock{ + TxHashes: [][]byte{[]byte("tx_hash2")}, + } + providedPendingMb2 := &block.MiniBlock{ + TxHashes: [][]byte{[]byte("tx_hash3")}, + } + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + CreateMbsCrossShardDstMeCalled: func(header data.HeaderHandler, processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo) ([]block.MiniblockAndHash, []block.MiniblockAndHash, uint32, bool, bool, error) { + switch header.GetNonce() { + case 1: + return []block.MiniblockAndHash{ + { + Miniblock: providedMb, + Hash: []byte("providedMB"), + }, + }, + []block.MiniblockAndHash{ + { + Miniblock: providedPendingMb, + Hash: []byte("providedPendingMB"), + }, + }, 0, false, false, nil + case 2: + return nil, nil, 0, true, false, nil // empty header + default: + return []block.MiniblockAndHash{}, + []block.MiniblockAndHash{ + { + Miniblock: providedPendingMb2, + Hash: []byte("providedPendingMB2"), + }, + }, 0, false, false, nil + } + }, + SelectOutgoingTransactionsCalled: func(nonce uint64, _ func() bool) ([][]byte, []data.MiniBlockHeaderHandler) { + pendingMbsAdded := []data.MiniBlockHeaderHandler{ + &block.MiniBlockHeader{ + Hash: []byte("providedPendingMB"), + }, + &block.MiniBlockHeader{ + Hash: []byte("providedPendingMB2"), + }, + } + return [][]byte{}, pendingMbsAdded + }, + } + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + initialHdr := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + PrevHash: []byte("prevHash"), + MiniBlockHeaders: []block.MiniBlockHeader{{}, {}, {}}, // 3 mb headers to match the provided ones + } + hdr, body, err := sp.CreateBlockProposal(initialHdr, haveTimeTrue) + require.NoError(t, err) + require.NotNil(t, hdr) + require.NotNil(t, body) + + rawBody, ok := body.(*block.Body) + require.True(t, ok) + require.Len(t, rawBody.MiniBlocks, 3) + require.Equal(t, providedMb, rawBody.MiniBlocks[0]) + require.Equal(t, providedPendingMb, rawBody.MiniBlocks[1]) + require.Equal(t, providedPendingMb2, rawBody.MiniBlocks[2]) + referencedHashes := arguments.MiniBlocksSelectionSession.GetReferencedHeaderHashes() + require.Len(t, referencedHashes, 3) + require.Equal(t, "hash_ok", string(referencedHashes[0])) + require.Equal(t, "hash_empty", string(referencedHashes[1])) + require.Equal(t, "hash_pending", string(referencedHashes[2])) + }) + t.Run("should work complex scenario", func(t *testing.T) { + // test scenario: + // - nonce 1: completely referenced (2 mini blocks dest me) + // - nonce 2: has 2 mini blocks dest me -> one is included one is saved as pending which will be included + // - nonce 3: has 2 mini blocks dest me -> both saved as pending and included + // - nonce 4: does not have mini blocks dest me -> saved as pending which will be included + // - nonce 5: has 2 mini blocks dest me -> both saved as pending, only one included + // - nonce 6: does not have mini blocks dest me -> saved as pending but not included as the last one is not finished + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + coreComponents.IntMarsh = &mock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + return []byte("marshalled"), nil + }, + } + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return getSimpleHeaderV3Mock() + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash") + }, + } + dataComponents.DataPool = &dataRetriever.PoolsHolderStub{ + ProofsCalled: func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + }, + HeadersCalled: func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + }, + } + }, + TransactionsCalled: func() retriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + SearchFirstDataCalled: func(key []byte) (value interface{}, ok bool) { + value = &transaction.Transaction{} + ok = true + return + }, + } + }, + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, + } + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.BlockTracker = &mock.BlockTrackerMock{ + ComputeLongestMetaChainFromLastNotarizedCalled: func() ([]data.HeaderHandler, [][]byte, error) { + return []data.HeaderHandler{ + &block.MetaBlockV3{ + Nonce: 1, + ShardInfo: []block.ShardData{ + { + Nonce: 0, + ShardID: 1, + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + SenderShardID: 1, + ReceiverShardID: 0, + }, + }, + }, + { + Nonce: 2, + ShardID: 1, + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + SenderShardID: 1, + ReceiverShardID: 0, + }, + }, + }, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + {}, {}, + }, + }, + &block.MetaBlockV3{ + Nonce: 2, + ShardInfo: []block.ShardData{ + { + Nonce: 0, + ShardID: 1, + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + SenderShardID: 1, + ReceiverShardID: 0, + }, + }, + }, + { + Nonce: 2, + ShardID: 1, + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + SenderShardID: 1, + ReceiverShardID: 0, + }, + }, + }, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + {}, {}, + }, + }, + &block.MetaBlockV3{ + Nonce: 3, + ShardInfo: []block.ShardData{ + { + Nonce: 0, + ShardID: 1, + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + SenderShardID: 1, + ReceiverShardID: 0, + }, + }, + }, + { + Nonce: 2, + ShardID: 1, + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + SenderShardID: 1, + ReceiverShardID: 0, + }, + }, + }, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + {}, {}, + }, + }, + &block.MetaBlockV3{ + Nonce: 4, + ShardInfo: []block.ShardData{}, + MiniBlockHeaders: []block.MiniBlockHeader{ + {}, + }, + }, + &block.MetaBlockV3{ + Nonce: 5, + ShardInfo: []block.ShardData{ + { + Nonce: 0, + ShardID: 1, + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + SenderShardID: 1, + ReceiverShardID: 0, + }, + }, + }, + { + Nonce: 2, + ShardID: 1, + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + SenderShardID: 1, + ReceiverShardID: 0, + }, + }, + }, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + {}, {}, + }, + }, + &block.MetaBlockV3{ + Nonce: 6, + ShardInfo: []block.ShardData{}, + MiniBlockHeaders: []block.MiniBlockHeader{ + {}, + }, + }, + }, + [][]byte{ + []byte("hash_1"), + []byte("hash_2"), + []byte("hash_3"), + []byte("hash_4"), + []byte("hash_5"), + []byte("hash_6"), + }, + nil + }, + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.MetaBlockV3{ + Nonce: 0, + }, []byte("hash"), nil // dummy + }, + } + providedMb := &block.MiniBlock{ + TxHashes: [][]byte{[]byte("tx_hash")}, + } + providedPendingMb := &block.MiniBlock{ + TxHashes: [][]byte{[]byte("tx_hash2")}, + } + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + CreateMbsCrossShardDstMeCalled: func(header data.HeaderHandler, processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo) ([]block.MiniblockAndHash, []block.MiniblockAndHash, uint32, bool, bool, error) { + switch header.GetNonce() { + case 1: + return []block.MiniblockAndHash{ + { + Miniblock: providedMb, + Hash: []byte("providedMB_1_0"), + }, + { + Miniblock: providedMb, + Hash: []byte("providedMB_1_1"), + }, + }, + []block.MiniblockAndHash{}, 0, true, false, nil + case 2: + return []block.MiniblockAndHash{ + { + Miniblock: providedMb, + Hash: []byte("providedMB_2_0"), + }, + }, + []block.MiniblockAndHash{ + { + Miniblock: providedPendingMb, + Hash: []byte("providedPendingMB_2_0"), + }, + }, 0, false, false, nil + case 3: + return []block.MiniblockAndHash{}, + []block.MiniblockAndHash{ + { + Miniblock: providedPendingMb, + Hash: []byte("providedPendingMB_3_0"), + }, + { + Miniblock: providedPendingMb, + Hash: []byte("providedPendingMB_3_1"), + }, + }, 0, false, false, nil + case 5: + return []block.MiniblockAndHash{}, + []block.MiniblockAndHash{ + { + Miniblock: providedPendingMb, + Hash: []byte("providedPendingMB_5_0"), + }, + { + Miniblock: providedPendingMb, + Hash: []byte("providedPendingMB_5_1"), + }, + }, 0, false, false, nil + case 4, 6: + return nil, nil, 0, true, false, nil // empty header + default: + require.Fail(t, "should not happen") + return nil, nil, 0, true, false, nil + } + }, + SelectOutgoingTransactionsCalled: func(nonce uint64, _ func() bool) ([][]byte, []data.MiniBlockHeaderHandler) { + pendingMbsAdded := []data.MiniBlockHeaderHandler{ + &block.MiniBlockHeader{ + Hash: []byte("providedPendingMB_2_0"), + }, + &block.MiniBlockHeader{ + Hash: []byte("providedPendingMB_3_0"), + }, + &block.MiniBlockHeader{ + Hash: []byte("providedPendingMB_3_1"), + }, + &block.MiniBlockHeader{ + Hash: []byte("providedPendingMB_5_0"), + }, + } + return [][]byte{}, pendingMbsAdded + }, + } + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + initialHdr := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + PrevHash: []byte("prevHash"), + MiniBlockHeaders: []block.MiniBlockHeader{{}, {}, {}, {}, {}}, // 5 mb headers to match the provided ones + } + hdr, body, err := sp.CreateBlockProposal(initialHdr, haveTimeTrue) + require.NoError(t, err) + require.NotNil(t, hdr) + require.NotNil(t, body) + + rawBody, ok := body.(*block.Body) + require.True(t, ok) + require.Len(t, rawBody.MiniBlocks, 7) + require.Equal(t, providedMb, rawBody.MiniBlocks[0]) + require.Equal(t, providedMb, rawBody.MiniBlocks[1]) + require.Equal(t, providedMb, rawBody.MiniBlocks[2]) + require.Equal(t, providedPendingMb, rawBody.MiniBlocks[3]) + require.Equal(t, providedPendingMb, rawBody.MiniBlocks[4]) + require.Equal(t, providedPendingMb, rawBody.MiniBlocks[5]) + require.Equal(t, providedPendingMb, rawBody.MiniBlocks[6]) + referencedHashes := arguments.MiniBlocksSelectionSession.GetReferencedHeaderHashes() + require.Len(t, referencedHashes, 5) + require.Equal(t, "hash_1", string(referencedHashes[0])) + require.Equal(t, "hash_2", string(referencedHashes[1])) + require.Equal(t, "hash_3", string(referencedHashes[2])) + require.Equal(t, "hash_4", string(referencedHashes[3])) + require.Equal(t, "hash_5", string(referencedHashes[4])) + }) + t.Run("only empty blocks should work", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + coreComponents.IntMarsh = &mock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + return []byte("marshalled"), nil + }, + } + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return getSimpleHeaderV3Mock() + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash") + }, + } + dataComponents.DataPool = &dataRetriever.PoolsHolderStub{ + ProofsCalled: func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + }, + HeadersCalled: func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + }, + } + }, + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, + } + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.BlockTracker = &mock.BlockTrackerMock{ + ComputeLongestMetaChainFromLastNotarizedCalled: func() ([]data.HeaderHandler, [][]byte, error) { + return []data.HeaderHandler{ + &block.MetaBlockV3{Nonce: 1}, // Empty - no mini blocks for shard + &block.MetaBlockV3{Nonce: 2}, // Empty + &block.MetaBlockV3{Nonce: 3}, // Empty + }, + [][]byte{ + []byte("hash_1"), + []byte("hash_2"), + []byte("hash_3"), + }, + nil + }, + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.MetaBlockV3{Nonce: 0}, []byte("hash_0"), nil + }, + } + + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + SelectOutgoingTransactionsCalled: func(nonce uint64, _ func() bool) ([][]byte, []data.MiniBlockHeaderHandler) { + return [][]byte{}, nil // no pending incoming added + }, + } + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + initialHdr := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + PrevHash: []byte("prevHash"), + } + hdr, body, err := sp.CreateBlockProposal(initialHdr, haveTimeTrue) + require.NoError(t, err) + require.NotNil(t, hdr) + require.NotNil(t, body) + + // all three empty meta blocks should be in referenced hashes (consecutive from nonce 1) + referencedHashes := arguments.MiniBlocksSelectionSession.GetReferencedHeaderHashes() + require.Len(t, referencedHashes, 3) + require.Equal(t, "hash_1", string(referencedHashes[0])) + require.Equal(t, "hash_2", string(referencedHashes[1])) + require.Equal(t, "hash_3", string(referencedHashes[2])) + }) + t.Run("no referenced headers with pending incoming should return error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + coreComponents.IntMarsh = &mock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + return []byte("marshalled"), nil + }, + } + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return getSimpleHeaderV3Mock() + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash") + }, + } + dataComponents.DataPool = &dataRetriever.PoolsHolderStub{ + ProofsCalled: func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + }, + HeadersCalled: func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + }, + } + }, + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, + } + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + arguments.BlockTracker = &mock.BlockTrackerMock{ + ComputeLongestMetaChainFromLastNotarizedCalled: func() ([]data.HeaderHandler, [][]byte, error) { + return []data.HeaderHandler{}, [][]byte{}, nil + }, + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.MetaBlockV3{Nonce: 0}, []byte("hash_0"), nil + }, + } + + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + SelectOutgoingTransactionsCalled: func(nonce uint64, _ func() bool) ([][]byte, []data.MiniBlockHeaderHandler) { + return [][]byte{}, []data.MiniBlockHeaderHandler{ + &block.MiniBlockHeader{Hash: []byte("pendingMB")}, + } + }, + } + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + initialHdr := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + PrevHash: []byte("prevHash"), + } + _, _, err = sp.CreateBlockProposal(initialHdr, haveTimeTrue) + require.Equal(t, process.ErrNoReferencedHeader, err) + }) + t.Run("pending mini block not found in pending list should return error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + coreComponents.IntMarsh = &mock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + return []byte("marshalled"), nil + }, + } + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return getSimpleHeaderV3Mock() + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash") + }, + } + dataComponents.DataPool = &dataRetriever.PoolsHolderStub{ + ProofsCalled: func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + }, + HeadersCalled: func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + }, + } + }, + TransactionsCalled: func() retriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + SearchFirstDataCalled: func(key []byte) (value interface{}, ok bool) { + return &transaction.Transaction{}, true + }, + } + }, + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, + } + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.BlockTracker = &mock.BlockTrackerMock{ + ComputeLongestMetaChainFromLastNotarizedCalled: func() ([]data.HeaderHandler, [][]byte, error) { + return []data.HeaderHandler{ + &testscommon.HeaderHandlerStub{ + GetNonceCalled: func() uint64 { return 1 }, + GetMiniBlockHeadersWithDstCalled: func(destId uint32) map[string]uint32 { + return map[string]uint32{"mb": 1} + }, + }, + }, + [][]byte{ + []byte("hash_1"), + }, + nil + }, + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.MetaBlockV3{Nonce: 0}, []byte("hash_0"), nil + }, + } + + providedMb := &block.MiniBlock{TxHashes: [][]byte{[]byte("tx_hash")}} + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + CreateMbsCrossShardDstMeCalled: func(header data.HeaderHandler, processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo) ([]block.MiniblockAndHash, []block.MiniblockAndHash, uint32, bool, bool, error) { + return []block.MiniblockAndHash{ + {Miniblock: providedMb, Hash: []byte("mb_hash_1")}, + }, nil, 0, true, false, nil + }, + SelectOutgoingTransactionsCalled: func(nonce uint64, _ func() bool) ([][]byte, []data.MiniBlockHeaderHandler) { + return [][]byte{}, []data.MiniBlockHeaderHandler{ + &block.MiniBlockHeader{Hash: []byte("nonExistentMB")}, // random pending mb added + } + }, + } + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + initialHdr := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + PrevHash: []byte("prevHash"), + MiniBlockHeaders: []block.MiniBlockHeader{{}}, + } + _, _, err = sp.CreateBlockProposal(initialHdr, haveTimeTrue) + require.Equal(t, process.ErrInvalidHash, err) + }) +} + +func Test_addExecutionResultsOnHeader(t *testing.T) { + t.Parallel() + t.Run("executionResultsTracker returns error should error", func(t *testing.T) { + t.Parallel() + + sp, _ := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "executionManager": &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return nil, expectedErr + }, + }, + "appStatusHandler": &statusHandlerMock.AppStatusHandlerStub{}, + }) + err := sp.AddExecutionResultsOnHeader(&block.HeaderV3{}) + require.Error(t, err) + require.Equal(t, expectedErr, err) + }) + t.Run("GetPrevBlockLastExecutionResult returns error should error", func(t *testing.T) { + t.Parallel() + sp, _ := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "executionManager": &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + }, + }, + "appStatusHandler": &statusHandlerMock.AppStatusHandlerStub{}, + }) + err := sp.AddExecutionResultsOnHeader(&block.HeaderV3{}) + require.Error(t, err) + require.Equal(t, process.ErrNilBlockChain, err) + }) + t.Run("CreateDataForInclusionEstimation returns error should error", func(t *testing.T) { + t.Parallel() + sp, _ := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "executionManager": &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + }, + }, + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash") + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV3{ + PrevHash: []byte("prev_hash"), + } + }, + }, + "appStatusHandler": &statusHandlerMock.AppStatusHandlerStub{}, + }) + err := sp.AddExecutionResultsOnHeader(&block.HeaderV3{}) + require.Error(t, err) + require.Equal(t, process.ErrNilLastExecutionResultHandler, err) + }) + + t.Run("CreateLastExecutionResultInfoFromExecutionResult returns error should error", func(t *testing.T) { + t.Parallel() + + baseExecutionResults := &block.BaseExecutionResult{ + HeaderHash: []byte("hash"), + HeaderNonce: 100, + HeaderRound: 1, + RootHash: []byte("rootHash"), + } + header := &block.HeaderV3{ + PrevHash: []byte("prev_hash"), + + LastExecutionResult: &block.ExecutionResultInfo{ + NotarizedInRound: 1, + ExecutionResult: baseExecutionResults, + }, + } + + prevHeaderHash := []byte("prev_hash") + sp, _ := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "executionManager": &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + // return one meta execution result (so Decide can include it) + meta := &block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: prevHeaderHash, + HeaderNonce: 1, + HeaderRound: 2, + RootHash: []byte("root"), + GasUsed: 100000, + }, + ValidatorStatsRootHash: []byte("vstats"), + AccumulatedFeesInEpoch: big.NewInt(123), + DevFeesInEpoch: big.NewInt(45), + }, + ReceiptsHash: []byte{}, + MiniBlockHeaders: nil, + ExecutedTxCount: 0, + } + return []data.BaseExecutionResultHandler{meta}, nil + }, + }, + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash") + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + }, + "executionResultsInclusionEstimator": &processMocks.InclusionEstimatorMock{ + DecideCalled: func(lastNotarised *common.LastExecutionResultForInclusion, pending []data.BaseExecutionResultHandler, currentHdrTsMs uint64) (allowed int) { + return 1 + }, + }, + "shardCoordinator": &mock.ShardCoordinatorStub{ + SelfIdCalled: func() uint32 { + return 0 + }, + }, + "appStatusHandler": &statusHandlerMock.AppStatusHandlerStub{}, + }) + err := sp.AddExecutionResultsOnHeader(&block.HeaderV3{ + Round: 3, + Nonce: 3, + PrevHash: prevHeaderHash, + }) + require.Error(t, err) + require.Equal(t, process.ErrWrongTypeAssertion, err) + }) + t.Run("will work with valid data", func(t *testing.T) { + t.Parallel() + + baseExecutionResults := &block.BaseExecutionResult{ + HeaderHash: []byte("hash"), + HeaderNonce: 3, + HeaderRound: 1, + RootHash: []byte("rootHash"), + } + header := &block.HeaderV3{ + PrevHash: []byte("prev_hash"), + + LastExecutionResult: &block.ExecutionResultInfo{ + NotarizedInRound: 1, + ExecutionResult: baseExecutionResults, + }, + } + + genesisTimeStampMs := uint64(1000) + roundTime := uint64(100) + roundHandler := &round.RoundHandlerMock{ + GetTimeStampForRoundCalled: func(round uint64) uint64 { + return genesisTimeStampMs + round*roundTime + }, + } + defaultCfg := config.ExecutionResultInclusionEstimatorConfig{ + SafetyMargin: 110, + MaxResultsPerBlock: 10, + } + + executionResultsInclusionEstimator, _ := estimator.NewExecutionResultInclusionEstimator(defaultCfg, roundHandler, &testscommon.ExecResSizeComputationStub{}) + + executionResult1 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{HeaderHash: []byte("hash1"), HeaderNonce: 1, HeaderRound: 2, GasUsed: 100_000_000}, + } + executionResult2 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{HeaderHash: []byte("hash2"), HeaderNonce: 2, HeaderRound: 2, GasUsed: 999_000_000}, + } + sp, _ := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "executionManager": &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{ + executionResult1, + executionResult2, + }, nil + }, + }, + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash") + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + }, + "executionResultsInclusionEstimator": executionResultsInclusionEstimator, + "shardCoordinator": &mock.ShardCoordinatorStub{ + SelfIdCalled: func() uint32 { + return 0 + }, + }, + "appStatusHandler": &statusHandlerMock.AppStatusHandlerStub{}, + }) + + proposalHeader := &block.HeaderV3{Round: 4} + err := sp.AddExecutionResultsOnHeader(proposalHeader) + + // expected only first pending execution result to be added + require.NoError(t, err) + actualLastExecutionResult := proposalHeader.GetLastExecutionResultHandler().(*block.ExecutionResultInfo) + assert.NotNil(t, actualLastExecutionResult) + assert.Equal(t, actualLastExecutionResult.ExecutionResult, executionResult1.BaseExecutionResult) + assert.NotNil(t, proposalHeader.ExecutionResults) + assert.Len(t, proposalHeader.ExecutionResults, 1) + assert.Equal(t, proposalHeader.ExecutionResults[0], executionResult1) + }) + + t.Run("should not add non canonical execution results", func(t *testing.T) { + t.Parallel() + + proposalHeaderNonce := uint64(5) + + baseExecutionResults := &block.BaseExecutionResult{ + HeaderHash: []byte("hash"), + HeaderNonce: 3, + HeaderRound: 1, + RootHash: []byte("rootHash"), + } + header := &block.HeaderV3{ + PrevHash: []byte("prev_hash"), + + LastExecutionResult: &block.ExecutionResultInfo{ + NotarizedInRound: 1, + ExecutionResult: baseExecutionResults, + }, + } + + genesisTimeStampMs := uint64(1000) + roundTime := uint64(100) + roundHandler := &round.RoundHandlerMock{ + GetTimeStampForRoundCalled: func(round uint64) uint64 { + return genesisTimeStampMs + round*roundTime + }, + } + defaultCfg := config.ExecutionResultInclusionEstimatorConfig{ + SafetyMargin: 110, + MaxResultsPerBlock: 10, + } + + executionResult1 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash1"), + HeaderNonce: proposalHeaderNonce, + }, + } + + execResEst, _ := estimator.NewExecutionResultInclusionEstimator(defaultCfg, roundHandler, &testscommon.ExecResSizeComputationStub{}) + + sp, _ := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "executionManager": &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{ + executionResult1, + }, nil + }, + }, + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash") + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + }, + "executionResultsInclusionEstimator": execResEst, + "shardCoordinator": &mock.ShardCoordinatorStub{ + SelfIdCalled: func() uint32 { + return 0 + }, + }, + "appStatusHandler": &statusHandlerMock.AppStatusHandlerStub{}, + }) + + proposalHeader := &block.HeaderV3{ + Nonce: proposalHeaderNonce, + Round: 3, + } + err := sp.AddExecutionResultsOnHeader(proposalHeader) + + // no execution result should be added + require.NoError(t, err) + assert.Len(t, proposalHeader.ExecutionResults, 0) + }) +} + +func TestShardProcessor_SelectIncomingMiniBlocks(t *testing.T) { + t.Parallel() + + providedLastCrossNotarizedMetaHdr := &block.MetaBlockV3{ + Nonce: 1, + } + providedOrderedMetaBlocks := []data.HeaderHandler{ + &block.MetaBlockV3{ + Nonce: 2, + }, + &block.MetaBlockV3{ + Nonce: 3, + }, + } + providedOrderedMetaBlocksHashes := [][]byte{ + []byte("hash2"), + []byte("hash3"), + } + t.Run("no time left should break and return nil", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + GetReferencedHeadersCalled: func() []data.HeaderHandler { + require.Fail(t, "should have not been called") + return nil + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + _, err = sp.SelectIncomingMiniBlocks(providedLastCrossNotarizedMetaHdr, providedOrderedMetaBlocks, providedOrderedMetaBlocksHashes, haveTimeFalse) + require.NoError(t, err) + }) + t.Run("too many referenced blocks should break and return nil", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + headers := dataComponents.DataPool.Headers() + dataComponents.DataPool = &dataRetriever.PoolsHolderStub{ + HeadersCalled: func() retriever.HeadersPool { + return headers + }, + ProofsCalled: func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + require.Fail(t, "should have not been called") + return true + }, + } + }, + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, + } + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + GetReferencedHeadersCalled: func() []data.HeaderHandler { + return make([]data.HeaderHandler, process.MaxMetaHeadersAllowedInOneShardBlock) + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + _, err = sp.SelectIncomingMiniBlocks(providedLastCrossNotarizedMetaHdr, providedOrderedMetaBlocks, providedOrderedMetaBlocksHashes, haveTimeTrue) + require.NoError(t, err) + }) + t.Run("nonce too high for one meta header should break", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + cntHasProof := 0 + headers := dataComponents.DataPool.Headers() + dataComponents.DataPool = &dataRetriever.PoolsHolderStub{ + HeadersCalled: func() retriever.HeadersPool { + return headers + }, + ProofsCalled: func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + cntHasProof++ + return true + }, + } + }, + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, + } + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + providedOrderedMetaBlocksCopy := make([]data.HeaderHandler, len(providedOrderedMetaBlocks)) + copy(providedOrderedMetaBlocksCopy, providedOrderedMetaBlocks) + _ = providedOrderedMetaBlocksCopy[1].SetNonce(10) + _, err = sp.SelectIncomingMiniBlocks(providedLastCrossNotarizedMetaHdr, providedOrderedMetaBlocksCopy, providedOrderedMetaBlocksHashes, haveTimeTrue) + require.NoError(t, err) + require.Equal(t, 1, cntHasProof) + }) + t.Run("missing proof for one meta header should break", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + headers := dataComponents.DataPool.Headers() + dataComponents.DataPool = &dataRetriever.PoolsHolderStub{ + HeadersCalled: func() retriever.HeadersPool { + return headers + }, + ProofsCalled: func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return false + }, + } + }, + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, + } + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + orderedMetaBlocks := []data.HeaderHandler{ + &testscommon.HeaderHandlerStub{ + GetMiniBlockHeadersWithDstCalled: func(destId uint32) map[string]uint32 { + require.Fail(t, "should have not been called") + return nil + }, + GetNonceCalled: func() uint64 { + return 2 + }, + }, + } + _, err = sp.SelectIncomingMiniBlocks(providedLastCrossNotarizedMetaHdr, orderedMetaBlocks, providedOrderedMetaBlocksHashes, haveTimeTrue) + require.NoError(t, err) + }) + t.Run("createMbsCrossShardDstMe fails due to error on AddMiniBlocksAndHashes", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + headers := dataComponents.DataPool.Headers() + dataComponents.DataPool = &dataRetriever.PoolsHolderStub{ + HeadersCalled: func() retriever.HeadersPool { + return headers + }, + ProofsCalled: func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + }, + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, + } + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + AddMiniBlocksAndHashesCalled: func(miniBlocksAndHashes []block.MiniblockAndHash) error { + return expectedErr + }, + } + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + CreateMbsCrossShardDstMeCalled: func(header data.HeaderHandler, processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo) ([]block.MiniblockAndHash, []block.MiniblockAndHash, uint32, bool, bool, error) { + return []block.MiniblockAndHash{ + { + Miniblock: &block.MiniBlock{}, + Hash: []byte("providedMB"), + }, + }, nil, 0, true, false, nil + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + orderedMetaBlocks := []data.HeaderHandler{ + &testscommon.HeaderHandlerStub{ + GetMiniBlockHeadersWithDstCalled: func(destId uint32) map[string]uint32 { + return map[string]uint32{ + "hash2_0": 1, + "hash2_1": 2, + } + }, + GetNonceCalled: func() uint64 { + return 2 + }, + }, + } + orderedMetaBlocksHashes := providedOrderedMetaBlocksHashes + orderedMetaBlocksHashes = orderedMetaBlocksHashes[:1] + _, err = sp.SelectIncomingMiniBlocks(providedLastCrossNotarizedMetaHdr, orderedMetaBlocks, orderedMetaBlocksHashes, haveTimeTrue) + require.Equal(t, expectedErr, err) + }) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + headers := dataComponents.DataPool.Headers() + dataComponents.DataPool = &dataRetriever.PoolsHolderStub{ + HeadersCalled: func() retriever.HeadersPool { + return headers + }, + ProofsCalled: func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + }, + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, + } + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + cntAddReferencedMetaBlockCalled := 0 + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + AddReferencedHeaderCalled: func(metaBlock data.HeaderHandler, metaBlockHash []byte) { + cntAddReferencedMetaBlockCalled++ + }, + } + cntCreateMbsCrossShardDstMeCalled := 0 + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + CreateMbsCrossShardDstMeCalled: func(header data.HeaderHandler, processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo) ([]block.MiniblockAndHash, []block.MiniblockAndHash, uint32, bool, bool, error) { + cntCreateMbsCrossShardDstMeCalled++ + if cntCreateMbsCrossShardDstMeCalled < 2 { + return []block.MiniblockAndHash{ + { + Miniblock: &block.MiniBlock{}, + Hash: []byte("providedMB"), + }, + }, nil, 0, true, false, nil + } + return nil, nil, 0, false, false, nil // shouldContinue = false -> only for coverage + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + orderedMetaBlocks := []data.HeaderHandler{ + &testscommon.HeaderHandlerStub{ + GetMiniBlockHeadersWithDstCalled: func(destId uint32) map[string]uint32 { + return map[string]uint32{ + "hash2_0": 1, + "hash2_1": 2, + } + }, + GetNonceCalled: func() uint64 { + return 2 + }, + }, + &testscommon.HeaderHandlerStub{ + GetMiniBlockHeadersWithDstCalled: func(destId uint32) map[string]uint32 { + return make(map[string]uint32) // empty + }, + GetNonceCalled: func() uint64 { + return 3 + }, + }, + &testscommon.HeaderHandlerStub{ + GetMiniBlockHeadersWithDstCalled: func(destId uint32) map[string]uint32 { + return map[string]uint32{ + "hash2_2": 1, + "hash2_3": 2, + } + }, + GetNonceCalled: func() uint64 { + return 4 // same nonce + }, + }, + } + orderedMetaBlocksHashes := providedOrderedMetaBlocksHashes + orderedMetaBlocksHashes = append(orderedMetaBlocksHashes, []byte("hash4")) + _, err = sp.SelectIncomingMiniBlocks(providedLastCrossNotarizedMetaHdr, orderedMetaBlocks, orderedMetaBlocksHashes, haveTimeTrue) + require.NoError(t, err) + // should be called for the first 2 meta blocks, the third one does not add any mini blocks, although it has some for the shard, so it is skipped + require.Equal(t, 2, cntAddReferencedMetaBlockCalled) + }) + t.Run("missing data should break and not reference further headers", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + headers := dataComponents.DataPool.Headers() + dataComponents.DataPool = &dataRetriever.PoolsHolderStub{ + HeadersCalled: func() retriever.HeadersPool { + return headers + }, + ProofsCalled: func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + }, + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, + } + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + cntAddReferencedHeader := 0 + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + AddReferencedHeaderCalled: func(metaBlock data.HeaderHandler, metaBlockHash []byte) { + cntAddReferencedHeader++ + }, + } + cntCreateMbs := 0 + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + CreateMbsCrossShardDstMeCalled: func(header data.HeaderHandler, processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo) ([]block.MiniblockAndHash, []block.MiniblockAndHash, uint32, bool, bool, error) { + cntCreateMbs++ + // first header has missing data -> all MBs skipped + return nil, nil, 0, false, true, nil + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + orderedMetaBlocks := []data.HeaderHandler{ + &testscommon.HeaderHandlerStub{ + GetMiniBlockHeadersWithDstCalled: func(destId uint32) map[string]uint32 { + return map[string]uint32{"mb1": 1} + }, + GetNonceCalled: func() uint64 { return 2 }, + }, + &testscommon.HeaderHandlerStub{ + GetMiniBlockHeadersWithDstCalled: func(destId uint32) map[string]uint32 { + return map[string]uint32{"mb2": 1} + }, + GetNonceCalled: func() uint64 { return 3 }, + }, + } + orderedMetaBlocksHashes := [][]byte{[]byte("hash2"), []byte("hash3")} + _, err = sp.SelectIncomingMiniBlocks(providedLastCrossNotarizedMetaHdr, orderedMetaBlocks, orderedMetaBlocksHashes, haveTimeTrue) + require.NoError(t, err) + // first header had missing data -> break, second header never processed + require.Equal(t, 1, cntCreateMbs) + require.Equal(t, 0, cntAddReferencedHeader) + }) + t.Run("missing data with some MBs added should break and not reference further headers", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + headers := dataComponents.DataPool.Headers() + dataComponents.DataPool = &dataRetriever.PoolsHolderStub{ + HeadersCalled: func() retriever.HeadersPool { + return headers + }, + ProofsCalled: func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + }, + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, + } + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + cntAddReferencedHeader := 0 + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + AddReferencedHeaderCalled: func(metaBlock data.HeaderHandler, metaBlockHash []byte) { + cntAddReferencedHeader++ + }, + } + cntCreateMbs := 0 + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + CreateMbsCrossShardDstMeCalled: func(header data.HeaderHandler, processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo) ([]block.MiniblockAndHash, []block.MiniblockAndHash, uint32, bool, bool, error) { + cntCreateMbs++ + // first header: some MBs added but has missing data + return []block.MiniblockAndHash{ + {Miniblock: &block.MiniBlock{}, Hash: []byte("mb")}, + }, nil, 1, false, true, nil + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + orderedMetaBlocks := []data.HeaderHandler{ + &testscommon.HeaderHandlerStub{ + GetMiniBlockHeadersWithDstCalled: func(destId uint32) map[string]uint32 { + return map[string]uint32{"mb1": 1} + }, + GetNonceCalled: func() uint64 { return 2 }, + }, + &testscommon.HeaderHandlerStub{ + GetMiniBlockHeadersWithDstCalled: func(destId uint32) map[string]uint32 { + return map[string]uint32{"mb2": 1} + }, + GetNonceCalled: func() uint64 { return 3 }, + }, + } + orderedMetaBlocksHashes := [][]byte{[]byte("hash2"), []byte("hash3")} + _, err = sp.SelectIncomingMiniBlocks(providedLastCrossNotarizedMetaHdr, orderedMetaBlocks, orderedMetaBlocksHashes, haveTimeTrue) + require.NoError(t, err) + // first header had missing data -> break, second header never processed + require.Equal(t, 1, cntCreateMbs) + // MBs were added before missing data check, so header was referenced + require.Equal(t, 1, cntAddReferencedHeader) + }) +} + +func TestShardProcessor_VerifyBlockProposal(t *testing.T) { + t.Parallel() + + t.Run("nil header should error", func(t *testing.T) { + t.Parallel() + + arguments := CreateMockArguments(createComponentHolderMocks()) + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + body := &block.Body{} + err = sp.VerifyBlockProposal(nil, body, haveTime) + require.Equal(t, process.ErrNilBlockHeader, err) + }) + + t.Run("block hash does not match should request prev header hash", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + + currentBlockHeader := &block.Header{} + _ = dataComponents.BlockChain.SetCurrentBlockHeaderAndRootHash(currentBlockHeader, []byte("root")) + dataComponents.BlockChain.SetCurrentBlockHeaderHash([]byte("wrong")) + + called := false + wg := &sync.WaitGroup{} + wg.Add(1) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.RequestHandler = &testscommon.RequestHandlerStub{ + RequestShardHeaderForEpochCalled: func(shardID uint32, hash []byte, epoch uint32) { + called = true + require.Equal(t, "prevHash", string(hash)) + wg.Done() + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + body := &block.Body{} + header := &block.Header{ + Nonce: 1, + Round: 2, + Epoch: 1, + PrevHash: []byte("prevHash"), + } + err = sp.VerifyBlockProposal(header, body, haveTime) + require.Equal(t, process.ErrBlockHashDoesNotMatch, err) + + wg.Wait() + require.True(t, called) + }) + + t.Run("wrong header type should error", func(t *testing.T) { + t.Parallel() + + arguments := CreateMockArguments(createComponentHolderMocks()) + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + body := &block.Body{} + header := &block.MetaBlock{ + Nonce: 1, + } + err = sp.VerifyBlockProposal(header, body, haveTime) + require.Equal(t, process.ErrWrongTypeAssertion, err) + }) + + t.Run("wrong header version should error", func(t *testing.T) { + t.Parallel() + + arguments := CreateMockArguments(createComponentHolderMocks()) + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + body := &block.Body{} + header := &block.Header{ + Nonce: 1, + } + err = sp.VerifyBlockProposal(header, body, haveTime) + require.Equal(t, process.ErrInvalidHeader, err) + }) + + t.Run("wrong body should error", func(t *testing.T) { + t.Parallel() + + arguments := CreateMockArguments(createComponentHolderMocks()) + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + body := &wrongBody{} + header := &block.HeaderV3{ + Nonce: 1, + } + err = sp.VerifyBlockProposal(header, body, haveTime) + require.Equal(t, process.ErrWrongTypeAssertion, err) + }) + t.Run("different mbs header from body vs from header should error", func(t *testing.T) { + t.Parallel() + + arguments := CreateMockArguments(createComponentHolderMocks()) + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{nil}, + } + + header := &block.HeaderV3{ + Nonce: 1, + MiniBlockHeaders: []block.MiniBlockHeader{ + {}, + }, + } + err = sp.VerifyBlockProposal(header, body, haveTime) + require.Equal(t, process.ErrNilMiniBlock, err) + }) + + t.Run("header execution results verification fails should error", func(t *testing.T) { + t.Parallel() + + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.ExecutionResultsVerifier = &processMocks.ExecutionResultsVerifierMock{ + VerifyHeaderExecutionResultsCalled: func(header data.HeaderHandler) error { + return expectedError + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + body := &block.Body{} + + header := &block.HeaderV3{ + Nonce: 1, + MiniBlockHeaders: []block.MiniBlockHeader{}, + } + err = sp.VerifyBlockProposal(header, body, haveTime) + require.Equal(t, expectedError, err) + }) + + t.Run("check inclusion estimation fails should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + currentBlockHeader := &block.HeaderV2{ + Header: &block.Header{}, + } + _ = dataComponents.BlockChain.SetCurrentBlockHeaderAndRootHash(currentBlockHeader, []byte("root")) + dataComponents.BlockChain.SetCurrentBlockHeaderHash([]byte("hash")) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ExecutionResultsVerifier = &processMocks.ExecutionResultsVerifierMock{ + VerifyHeaderExecutionResultsCalled: func(header data.HeaderHandler) error { + return nil + }, + } + arguments.ExecutionResultsInclusionEstimator = &processMocks.InclusionEstimatorMock{ + DecideCalled: func(lastNotarised *common.LastExecutionResultForInclusion, pending []data.BaseExecutionResultHandler, currentHdrTsMs uint64) (allowed int) { + return 10 + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + body := &block.Body{} + + header := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 1, + Round: 2, + MiniBlockHeaders: []block.MiniBlockHeader{}, + } + err = sp.VerifyBlockProposal(header, body, haveTime) + require.Equal(t, process.ErrInvalidNumberOfExecutionResultsInHeader, err) + }) + + t.Run("request missing meta headers fails should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + currentBlockHeader := &block.HeaderV2{ + Header: &block.Header{}, + } + _ = dataComponents.BlockChain.SetCurrentBlockHeaderAndRootHash(currentBlockHeader, []byte("root")) + dataComponents.BlockChain.SetCurrentBlockHeaderHash([]byte("hash")) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ExecutionResultsVerifier = &processMocks.ExecutionResultsVerifierMock{ + VerifyHeaderExecutionResultsCalled: func(header data.HeaderHandler) error { + return nil + }, + } + arguments.ExecutionResultsInclusionEstimator = &processMocks.InclusionEstimatorMock{ + DecideCalled: func(lastNotarised *common.LastExecutionResultForInclusion, pending []data.BaseExecutionResultHandler, currentHdrTsMs uint64) (allowed int) { + return 0 + }, + } + + arguments.MissingDataResolver = &processMocks.MissingDataResolverMock{ + RequestMissingMetaHeadersCalled: func(shardHeader data.ShardHeaderHandler) error { + return expectedError + }, + } + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + body := &block.Body{} + + header := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 1, + Round: 2, + MiniBlockHeaders: []block.MiniBlockHeader{}, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + } + err = sp.VerifyBlockProposal(header, body, haveTime) + require.Equal(t, expectedError, err) + }) + + t.Run("wait for missing data fails should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + currentBlockHeader := &block.HeaderV2{ + Header: &block.Header{}, + } + _ = dataComponents.BlockChain.SetCurrentBlockHeaderAndRootHash(currentBlockHeader, []byte("root")) + dataComponents.BlockChain.SetCurrentBlockHeaderHash([]byte("hash")) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ExecutionResultsVerifier = &processMocks.ExecutionResultsVerifierMock{ + VerifyHeaderExecutionResultsCalled: func(header data.HeaderHandler) error { + return nil + }, + } + arguments.ExecutionResultsInclusionEstimator = &processMocks.InclusionEstimatorMock{ + DecideCalled: func(lastNotarised *common.LastExecutionResultForInclusion, pending []data.BaseExecutionResultHandler, currentHdrTsMs uint64) (allowed int) { + return 0 + }, + } + + arguments.MissingDataResolver = &processMocks.MissingDataResolverMock{ + RequestMissingMetaHeadersCalled: func(shardHeader data.ShardHeaderHandler) error { + return nil + }, + WaitForMissingDataCalled: func(timeout time.Duration) error { + return expectedError + }, + } + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + body := &block.Body{} + + header := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 1, + Round: 2, + MiniBlockHeaders: []block.MiniBlockHeader{}, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + } + err = sp.VerifyBlockProposal(header, body, haveTime) + require.Equal(t, expectedError, err) + }) + + t.Run("checkEpochCorectnessCrossChain fails should error", func(t *testing.T) { + t.Parallel() + + subcomponents := createSubComponentsForVerifyProposalTest() + subcomponents["epochChangeGracePeriodHandler"] = processMocks.GracePeriodErrStub{} + subcomponents["epochStartTrigger"] = &mock.EpochStartTriggerStub{ + EpochStartRoundCalled: func() uint64 { + return 10 + }, + EpochFinalityAttestingRoundCalled: func() uint64 { + return 15 + }, + } + genesisNonce := uint64(0) + subcomponents["genesisNonce"] = genesisNonce + subcomponents["forkDetector"] = &mock.ForkDetectorMock{ + GetHighestFinalBlockNonceCalled: func() uint64 { + return genesisNonce + }, + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(subcomponents) + require.Nil(t, err) + + body := &block.Body{} + + header := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 1, + Round: 2, + MiniBlockHeaders: []block.MiniBlockHeader{}, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + } + + err = sp.VerifyBlockProposal(header, body, haveTime) + require.Error(t, err) + assert.Equal(t, "epochChangeGracePeriodHandler forced error", err.Error()) + }) + t.Run("checkEpochCorectness fails should error", func(t *testing.T) { + t.Parallel() + + subcomponents := createSubComponentsForVerifyProposalTest() + + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(subcomponents) + require.Nil(t, err) + + body := &block.Body{} + + header := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 1, + Round: 2, + Epoch: 5, + MiniBlockHeaders: []block.MiniBlockHeader{}, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + } + + err = sp.VerifyBlockProposal(header, body, haveTime) + require.Error(t, err) + assert.Contains(t, err.Error(), "epoch does not match") + }) + t.Run("checkMetaHeadersValidityAndFinalityProposal fails should error", func(t *testing.T) { + t.Parallel() + + subcomponents := createSubComponentsForVerifyProposalTest() + subcomponents["blockTracker"] = &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return nil, nil, expectedError + }, + } + + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(subcomponents) + require.Nil(t, err) + + body := &block.Body{} + + header := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 1, + Round: 2, + MiniBlockHeaders: []block.MiniBlockHeader{}, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + } + + err = sp.VerifyBlockProposal(header, body, haveTime) + require.Error(t, err) + assert.Equal(t, expectedError, err) + }) + t.Run("verifyCrossShardMiniBlockDstMe fails should error", func(t *testing.T) { + t.Parallel() + + headerValidator := &processMocks.HeaderValidatorMock{ + IsHeaderConstructionValidCalled: func(currHdr, prevHdr data.HeaderHandler) error { + return nil + }, + } + subcomponents := createSubComponentsForVerifyProposalTest() + subcomponents["headerValidator"] = headerValidator + subcomponents["hasher"] = &hashingMocks.HasherMock{} + subcomponents["proofsPool"] = &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(subcomponents) + require.Nil(t, err) + + body := &block.Body{} + + metablockHashes := [][]byte{ + []byte("hash1"), + []byte("hash2"), + } + header := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 1, + Round: 2, + MiniBlockHeaders: []block.MiniBlockHeader{}, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + MetaBlockHashes: metablockHashes, + } + + err = sp.VerifyBlockProposal(header, body, haveTime) + + require.Error(t, err) + // stub will not return meta headers, causing type assertion to fail + expError := errors.New("wrong type assertion") + assert.Equal(t, expError, err) + }) + t.Run("verifyGasLimit fails should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + + poolMock, ok := dataComponents.DataPool.(*dataRetriever.PoolsHolderStub) + require.True(t, ok) + poolMock.HeadersCalled = func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + }, + } + } + currentBlockHeader := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + } + _ = dataComponents.BlockChain.SetCurrentBlockHeaderAndRootHash(currentBlockHeader, []byte("root")) + dataComponents.BlockChain.SetCurrentBlockHeaderHash([]byte("hash")) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ExecutionResultsVerifier = &processMocks.ExecutionResultsVerifierMock{ + VerifyHeaderExecutionResultsCalled: func(header data.HeaderHandler) error { + return nil + }, + } + arguments.ExecutionResultsInclusionEstimator = &processMocks.InclusionEstimatorMock{ + DecideCalled: func(lastNotarised *common.LastExecutionResultForInclusion, pending []data.BaseExecutionResultHandler, currentHdrTsMs uint64) (allowed int) { + return 0 + }, + } + arguments.GasComputation = &testscommon.GasComputationMock{ + AddIncomingMiniBlocksCalled: func(miniBlocks []data.MiniBlockHeaderHandler, transactions map[string][]data.TransactionHandler) (int, int, error) { + return 0, 0, expectedError + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + body := &block.Body{} + + header := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 1, + Round: 2, + MiniBlockHeaders: []block.MiniBlockHeader{}, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + } + err = sp.VerifyBlockProposal(header, body, haveTime) + require.Error(t, err) + require.Equal(t, expectedErr, err) + }) + t.Run("getHeaderHash fails should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + + coreComponents.IntMarsh = &mock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + return nil, expectedErr + }, + } + + poolMock, ok := dataComponents.DataPool.(*dataRetriever.PoolsHolderStub) + require.True(t, ok) + poolMock.HeadersCalled = func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + }, + } + } + currentBlockHeader := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + } + _ = dataComponents.BlockChain.SetCurrentBlockHeaderAndRootHash(currentBlockHeader, []byte("root")) + dataComponents.BlockChain.SetCurrentBlockHeaderHash([]byte("hash")) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ExecutionResultsVerifier = &processMocks.ExecutionResultsVerifierMock{ + VerifyHeaderExecutionResultsCalled: func(header data.HeaderHandler) error { + return nil + }, + } + arguments.ExecutionResultsInclusionEstimator = &processMocks.InclusionEstimatorMock{ + DecideCalled: func(lastNotarised *common.LastExecutionResultForInclusion, pending []data.BaseExecutionResultHandler, currentHdrTsMs uint64) (allowed int) { + return 0 + }, + } + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + body := &block.Body{} + + header := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 1, + Round: 2, + MiniBlockHeaders: []block.MiniBlockHeader{}, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + } + err = sp.VerifyBlockProposal(header, body, haveTime) + require.Error(t, err) + require.Equal(t, expectedErr, err) + }) + + t.Run("nonce gap from last exec result exceeds maximum allowed, should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + poolMock, ok := dataComponents.DataPool.(*dataRetriever.PoolsHolderStub) + require.True(t, ok) + poolMock.HeadersCalled = func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + }, + } + } + currentBlockHeader := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + Nonce: 109, + } + _ = dataComponents.BlockChain.SetCurrentBlockHeaderAndRootHash(currentBlockHeader, []byte("root")) + dataComponents.BlockChain.SetCurrentBlockHeaderHash([]byte("hash")) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ExecutionResultsVerifier = &processMocks.ExecutionResultsVerifierMock{ + VerifyHeaderExecutionResultsCalled: func(header data.HeaderHandler) error { + return nil + }, + } + arguments.ExecutionResultsInclusionEstimator = &processMocks.InclusionEstimatorMock{ + DecideCalled: func(lastNotarised *common.LastExecutionResultForInclusion, pending []data.BaseExecutionResultHandler, currentHdrTsMs uint64) (allowed int) { + return 0 + }, + } + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + body := &block.Body{} + + header := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 110, + Round: 2, + MiniBlockHeaders: []block.MiniBlockHeader{}, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 5, + }, + }, + } + err = sp.VerifyBlockProposal(header, body, haveTime) + require.ErrorIs(t, err, process.ErrNonceGapTooLarge) + require.Contains(t, err.Error(), "from last execution") + require.Contains(t, err.Error(), "gap of 105") + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + poolMock, ok := dataComponents.DataPool.(*dataRetriever.PoolsHolderStub) + require.True(t, ok) + poolMock.HeadersCalled = func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + }, + } + } + currentBlockHeader := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + } + _ = dataComponents.BlockChain.SetCurrentBlockHeaderAndRootHash(currentBlockHeader, []byte("root")) + dataComponents.BlockChain.SetCurrentBlockHeaderHash([]byte("hash")) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ExecutionResultsVerifier = &processMocks.ExecutionResultsVerifierMock{ + VerifyHeaderExecutionResultsCalled: func(header data.HeaderHandler) error { + return nil + }, + } + arguments.ExecutionResultsInclusionEstimator = &processMocks.InclusionEstimatorMock{ + DecideCalled: func(lastNotarised *common.LastExecutionResultForInclusion, pending []data.BaseExecutionResultHandler, currentHdrTsMs uint64) (allowed int) { + return 0 + }, + } + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + body := &block.Body{} + + header := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 1, + Round: 2, + MiniBlockHeaders: []block.MiniBlockHeader{}, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + } + err = sp.VerifyBlockProposal(header, body, haveTime) + require.NoError(t, err) + }) +} + +func TestShardProcessor_CheckInclusionEstimationForExecutionResults(t *testing.T) { + t.Parallel() + + t.Run("cannot get prev block last execution results should error", func(t *testing.T) { + t.Parallel() + + arguments := CreateMockArguments(createComponentHolderMocks()) + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{} + err := sp.CheckInclusionEstimationForExecutionResults(header) + require.Equal(t, process.ErrNilHeaderHandler, err) + }) + + t.Run("invalid number of execution results", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + currentBlockHeader := &block.HeaderV2{ + Header: &block.Header{}, + } + _ = dataComponents.BlockChain.SetCurrentBlockHeaderAndRootHash(currentBlockHeader, []byte("root")) + dataComponents.BlockChain.SetCurrentBlockHeaderHash([]byte("hash")) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + arguments.ExecutionResultsInclusionEstimator = &processMocks.InclusionEstimatorMock{ + DecideCalled: func(lastNotarised *common.LastExecutionResultForInclusion, pending []data.BaseExecutionResultHandler, currentHdrTsMs uint64) (allowed int) { + return 1 + }, + } + + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{} + err := sp.CheckInclusionEstimationForExecutionResults(header) + require.Equal(t, process.ErrInvalidNumberOfExecutionResultsInHeader, err) + }) + + t.Run("should propagate the error in case of non canonical execution results", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + currentBlockHeader := &block.HeaderV2{ + Header: &block.Header{}, + } + _ = dataComponents.BlockChain.SetCurrentBlockHeaderAndRootHash(currentBlockHeader, []byte("root")) + dataComponents.BlockChain.SetCurrentBlockHeaderHash([]byte("hash")) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{ + Nonce: 2, + ExecutionResults: []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 2, + }, + }, + }, + } + + err := sp.CheckInclusionEstimationForExecutionResults(header) + require.Equal(t, process.ErrNonCanonicalExecutionResultIncluded, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + currentBlockHeader := &block.HeaderV2{ + Header: &block.Header{}, + } + _ = dataComponents.BlockChain.SetCurrentBlockHeaderAndRootHash(currentBlockHeader, []byte("root")) + dataComponents.BlockChain.SetCurrentBlockHeaderHash([]byte("hash")) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{} + err := sp.CheckInclusionEstimationForExecutionResults(header) + require.Equal(t, nil, err) + }) +} + +func TestShardProcessor_CheckMetaHeadersValidityAndFinalityProposal(t *testing.T) { + t.Parallel() + + t.Run("cannot get last notarized header should err", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataPool, ok := dataComponents.DataPool.(*dataRetriever.PoolsHolderStub) + require.True(t, ok) + dataPool.HeadersCalled = func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.HeaderV3{}, nil + }, + } + } + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + arguments.BlockTracker = &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return nil, nil, expectedError + }, + } + + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{} + err := sp.CheckMetaHeadersValidityAndFinalityProposal(header) + require.Equal(t, expectedError, err) + }) + + t.Run("invalid header should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + metaHeader := &block.MetaBlockV3{} + arguments.BlockTracker = &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return metaHeader, []byte("h"), nil + }, + } + arguments.HeaderValidator = &processMocks.HeaderValidatorMock{ + IsHeaderConstructionValidCalled: func(currHdr, prevHdr data.HeaderHandler) error { + return expectedError + }, + } + + dataPool, ok := dataComponents.Datapool().(*dataRetriever.PoolsHolderStub) + require.True(t, ok) + + dataPool.HeadersCalled = func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.Header{}, nil + }, + } + } + + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{ + MetaBlockHashes: [][]byte{[]byte("hh")}, + } + err := sp.CheckMetaHeadersValidityAndFinalityProposal(header) + require.NotNil(t, err) + require.ErrorContains(t, err, expectedError.Error()) + }) + + t.Run("missing proof should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + metaHeader := &block.MetaBlockV3{} + + arguments.HeaderValidator = &processMocks.HeaderValidatorMock{ + IsHeaderConstructionValidCalled: func(currHdr, prevHdr data.HeaderHandler) error { + return nil + }, + } + + arguments.BlockTracker = &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return metaHeader, []byte("h"), nil + }, + } + + dataPool, ok := dataComponents.Datapool().(*dataRetriever.PoolsHolderStub) + require.True(t, ok) + + dataPool.HeadersCalled = func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.Header{}, nil + }, + } + } + + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{ + MetaBlockHashes: [][]byte{[]byte("hh")}, + } + err := sp.CheckMetaHeadersValidityAndFinalityProposal(header) + require.NotNil(t, err) + require.ErrorContains(t, err, process.ErrHeaderNotFinal.Error()) + }) + + t.Run("GetHeaderByHash error should propagate", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + metaHeader := &block.MetaBlockV3{} + + arguments.HeaderValidator = &processMocks.HeaderValidatorMock{ + IsHeaderConstructionValidCalled: func(currHdr, prevHdr data.HeaderHandler) error { + return nil + }, + } + + arguments.BlockTracker = &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return metaHeader, []byte("h"), nil + }, + } + + dataPool, ok := dataComponents.Datapool().(*dataRetriever.PoolsHolderStub) + require.True(t, ok) + + expectedError := errors.New("expected error from GetHeaderByHash") + dataPool.HeadersCalled = func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, expectedError + }, + } + } + dataPool.ProofsCalled = func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + } + + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{ + MetaBlockHashes: [][]byte{[]byte("hh")}, + } + err := sp.CheckMetaHeadersValidityAndFinalityProposal(header) + require.Error(t, err) + require.ErrorIs(t, err, expectedError) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + metaHeader := &block.MetaBlockV3{} + + arguments.HeaderValidator = &processMocks.HeaderValidatorMock{ + IsHeaderConstructionValidCalled: func(currHdr, prevHdr data.HeaderHandler) error { + return nil + }, + } + + arguments.BlockTracker = &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return metaHeader, []byte("h"), nil + }, + } + + dataPool, ok := dataComponents.Datapool().(*dataRetriever.PoolsHolderStub) + require.True(t, ok) + + dataPool.HeadersCalled = func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.Header{}, nil + }, + } + } + dataPool.ProofsCalled = func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + } + + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{ + MetaBlockHashes: [][]byte{[]byte("hh")}, + } + err := sp.CheckMetaHeadersValidityAndFinalityProposal(header) + require.Nil(t, err) + }) +} + +func checkCreateBlockProposalResult( + t *testing.T, + processor processorTest, + header data.HeaderHandler, + haveTime func() bool, + expectedError error, +) { + hdr, body, err := processor.CreateBlockProposal(header, haveTime) + require.Equal(t, expectedError, err) + require.Nil(t, hdr) + require.Nil(t, body) +} + +func TestShardBlockProposal_CreateAndVerifyProposal(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + blkc, _ := blockchain.NewBlockChain(&statusHandlerMock.AppStatusHandlerStub{}) + _ = blkc.SetGenesisHeader(&block.Header{Nonce: 0}) + + currentHeader := &block.HeaderV3{ + Nonce: 10, + Round: 10, + LastExecutionResult: &block.ExecutionResultInfo{ + NotarizedInRound: 10, + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("prevHeaderHash"), + HeaderNonce: 9, + HeaderRound: 9, + RootHash: []byte("prevRootHash"), + GasUsed: 100000, + }, + }, + } + currentHeaderHash := []byte("currHdrHash") + blkc.SetCurrentBlockHeaderHash(currentHeaderHash) + err := blkc.SetCurrentBlockHeaderAndRootHash(currentHeader, []byte("currHdrRootHash")) + require.Nil(t, err) + dataComponents.DataPool = &dataRetriever.PoolsHolderStub{ + ProofsCalled: func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + }, + HeadersCalled: func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + }, + } + }, + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, + } + dataComponents.BlockChain = blkc + + executionResultsTracker := executionTrack.NewExecutionResultsTracker() + execManager, _ := executionManager.NewExecutionManager(executionManager.ArgsExecutionManager{ + BlocksCache: &processMocks.BlocksCacheMock{}, + ExecutionResultsTracker: executionResultsTracker, + BlockChain: blkc, + Headers: dataComponents.DataPool.Headers(), + PostProcessTransactions: dataComponents.DataPool.PostProcessTransactions(), + ExecutedMiniBlocks: dataComponents.DataPool.ExecutedMiniBlocks(), + StorageService: dataComponents.StorageService(), + Marshaller: coreComponents.InternalMarshalizer(), + ShardCoordinator: bootstrapComponents.ShardCoordinator(), + }) + execResultsVerifier, _ := blproc.NewExecutionResultsVerifier(dataComponents.BlockChain, execManager) + + arguments.ArgBaseProcessor.ExecutionManager = execManager + arguments.ArgBaseProcessor.ExecutionResultsVerifier = execResultsVerifier + + shardProcessor, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + t.Run("should work - without miniblocks and transactions", func(t *testing.T) { + header := &block.HeaderV3{ + Round: 11, + Nonce: 11, + PrevHash: currentHeaderHash, + } + headerProposed, bodyProposed, err := shardProcessor.CreateBlockProposal(header, haveTimeTrue) + require.Nil(t, err) + require.NotNil(t, headerProposed) + require.NotNil(t, bodyProposed) + + err = shardProcessor.VerifyBlockProposal(headerProposed, bodyProposed, func() time.Duration { return time.Second }) + require.Nil(t, err) + }) + + t.Run("nil proposed block should fail", func(t *testing.T) { + headerProposed, bodyProposed, err := shardProcessor.CreateBlockProposal(nil, haveTimeTrue) + require.Equal(t, process.ErrNilBlockHeader, err) + require.Nil(t, headerProposed) + require.Nil(t, bodyProposed) + }) + + t.Run("nil proposed body should fail", func(t *testing.T) { + header := &block.HeaderV3{ + Round: 11, + Nonce: 11, + PrevHash: currentHeaderHash, + } + headerProposed, bodyProposed, err := shardProcessor.CreateBlockProposal(header, haveTimeTrue) + require.Nil(t, err) + require.NotNil(t, headerProposed) + require.NotNil(t, bodyProposed) + + err = shardProcessor.VerifyBlockProposal(headerProposed, nil, func() time.Duration { return time.Second }) + require.Equal(t, process.ErrNilBlockBody, err) + }) +} + +func TestShardBlockProposal_CreateAndVerifyProposal_WithTransactions(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + blkc, _ := blockchain.NewBlockChain(&statusHandlerMock.AppStatusHandlerStub{}) + _ = blkc.SetGenesisHeader(&block.Header{Nonce: 0}) + + currentHeader := &block.HeaderV3{ + Nonce: 10, + Round: 10, + LastExecutionResult: &block.ExecutionResultInfo{ + NotarizedInRound: 10, + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("prevHeaderHash"), + HeaderNonce: 9, + HeaderRound: 9, + RootHash: []byte("prevRootHash"), + GasUsed: 100000, + }, + }, + } + currentHeaderHash := []byte("currHdrHash") + blkc.SetCurrentBlockHeaderHash(currentHeaderHash) + err := blkc.SetCurrentBlockHeaderAndRootHash(currentHeader, []byte("currHdrRootHash")) + require.Nil(t, err) + + epochStartMetaHash := []byte("epochStartMetaHash") + metaBlockHash1 := []byte("metaBlockHash1") + metaBlock1 := &block.MetaBlockV3{ + Round: 10, + } + lastCommitedMetaHash := []byte("lastCommitedMeta") + lastCommitedMeta := &block.MetaBlockV3{ + Round: 9, + Nonce: 9, + } + lastCrossNotarizedMetaHdrHash := []byte("lastCrossNotarizedMetaHdrHash") + lastCrossNotarizedMetaHdr := &block.MetaBlockV3{ + Round: 8, + Nonce: 8, + } + + providedMb := &block.MiniBlock{ + TxHashes: [][]byte{[]byte("tx_hash")}, + } + + headers := &mock.HeadersCacherStub{} + headers.GetHeaderByHashCalled = func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, epochStartMetaHash) { + return &block.MetaBlockV3{}, nil + } + if bytes.Equal(hash, metaBlockHash1) { + return metaBlock1, nil + } + if bytes.Equal(hash, lastCommitedMetaHash) { + return lastCommitedMeta, nil + } + if bytes.Equal(hash, lastCrossNotarizedMetaHdrHash) { + return lastCrossNotarizedMetaHdr, nil + } + + return &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + } + + headers.AddHeader(epochStartMetaHash, &block.MetaBlockV3{}) + + dataComponents.DataPool = &dataRetriever.PoolsHolderStub{ + ProofsCalled: func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + }, + HeadersCalled: func() retriever.HeadersPool { + return headers + }, + TransactionsCalled: func() retriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + SearchFirstDataCalled: func(key []byte) (value interface{}, ok bool) { + value = &transaction.Transaction{} + ok = true + return + }, + } + }, + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, + } + dataComponents.BlockChain = blkc + + executionResultsTracker := executionTrack.NewExecutionResultsTracker() + execManager, _ := executionManager.NewExecutionManager(executionManager.ArgsExecutionManager{ + BlocksCache: &processMocks.BlocksCacheMock{}, + ExecutionResultsTracker: executionResultsTracker, + BlockChain: blkc, + Headers: dataComponents.DataPool.Headers(), + PostProcessTransactions: dataComponents.DataPool.PostProcessTransactions(), + ExecutedMiniBlocks: dataComponents.DataPool.ExecutedMiniBlocks(), + StorageService: dataComponents.StorageService(), + Marshaller: coreComponents.InternalMarshalizer(), + ShardCoordinator: bootstrapComponents.ShardCoordinator(), + }) + execResultsVerifier, _ := blproc.NewExecutionResultsVerifier(dataComponents.BlockChain, execManager) + + arguments.ArgBaseProcessor.ExecutionManager = execManager + arguments.ArgBaseProcessor.ExecutionResultsVerifier = execResultsVerifier + + arguments.MissingDataResolver = &processMocks.MissingDataResolverMock{ + WaitForMissingDataCalled: func(timeout time.Duration) error { + return nil + }, + } + + arguments.BlockTracker = &mock.BlockTrackerMock{ + ComputeLongestMetaChainFromLastNotarizedCalled: func() ([]data.HeaderHandler, [][]byte, error) { + return []data.HeaderHandler{&block.MetaBlockV3{ + ShardInfo: []block.ShardData{ + { + ShardID: 1, + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + SenderShardID: 1, + ReceiverShardID: 0, + }, + }, + }, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + {}, + }, + }}, + [][]byte{lastCommitedMetaHash}, + nil + }, + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return lastCrossNotarizedMetaHdr, lastCrossNotarizedMetaHdrHash, nil + }, + } + mbHash, _ := core.CalculateHash(arguments.CoreComponents.InternalMarshalizer(), arguments.CoreComponents.Hasher(), providedMb) + arguments.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + CreateMbsCrossShardDstMeCalled: func(header data.HeaderHandler, processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo) ([]block.MiniblockAndHash, []block.MiniblockAndHash, uint32, bool, bool, error) { + return []block.MiniblockAndHash{ + { + Miniblock: providedMb, + Hash: mbHash, + }, + }, nil, 0, true, false, nil + }, + } + + shardProcessor, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + argsHeaderValidator := blproc.ArgsHeaderValidator{ + Hasher: coreComponents.Hasher(), + Marshalizer: coreComponents.InternalMarshalizer(), + EnableEpochsHandler: coreComponents.EnableEpochsHandler(), + } + headerValidator, _ := blproc.NewHeaderValidator(argsHeaderValidator) + shardProcessor.SetHeaderValidator(headerValidator) + + header := &block.HeaderV3{ + Round: 11, + Nonce: 11, + PrevHash: currentHeaderHash, + MetaBlockHashes: [][]byte{metaBlockHash1}, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + } + headerProposed, bodyProposed, err := shardProcessor.CreateBlockProposal(header, haveTimeTrue) + require.Nil(t, err) + require.NotNil(t, headerProposed) + require.NotNil(t, bodyProposed) + + err = shardProcessor.VerifyBlockProposal(headerProposed, bodyProposed, func() time.Duration { return time.Second }) + require.Nil(t, err) +} + +func TestShardProcessor_VerifyGasLimit(t *testing.T) { + t.Parallel() + + t.Run("splitTransactionsForHeader fails due to invalid mini blocks", func(t *testing.T) { + t.Parallel() + + outgoingMbh, outgoingMb, incomingMbh, _ := createMiniBlocks() + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataPool := adaptDataPoolForVerifyGas(dataComponents.DataPool) + dataComponents.DataPool = dataPool + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + sp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + err = sp.VerifyGasLimit(createHeaderFromMBs(outgoingMbh, incomingMbh), block.MiniBlockSlice{outgoingMb}) // less mbs in slice + require.Error(t, err) + }) + t.Run("getTransactionsForMiniBlock fails due to error on GetTransactionHandlerFromPool", func(t *testing.T) { + t.Parallel() + + outgoingMbh, outgoingMb, incomingMbh, incomingMb := createMiniBlocks() + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataPool := adaptDataPoolForVerifyGas(dataComponents.DataPool) + dataPool.TransactionsCalled = func() retriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + SearchFirstDataCalled: func(key []byte) (value interface{}, ok bool) { + return nil, false + }, + } + } + dataComponents.DataPool = dataPool + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + sp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + err = sp.VerifyGasLimit(createHeaderFromMBs(outgoingMbh, incomingMbh), block.MiniBlockSlice{outgoingMb, incomingMb}) + require.Error(t, err) + }) + t.Run("AddIncomingMiniBlocks error", func(t *testing.T) { + t.Parallel() + + outgoingMbh, outgoingMb, incomingMbh, incomingMb := createMiniBlocks() + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataPool := adaptDataPoolForVerifyGas(dataComponents.DataPool) + dataComponents.DataPool = dataPool + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.GasComputation = &testscommon.GasComputationMock{ + AddIncomingMiniBlocksCalled: func(miniBlocks []data.MiniBlockHeaderHandler, transactions map[string][]data.TransactionHandler) (int, int, error) { + return 0, 0, expectedError + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + err = sp.VerifyGasLimit(createHeaderFromMBs(outgoingMbh, incomingMbh), block.MiniBlockSlice{outgoingMb, incomingMb}) + require.Equal(t, expectedError, err) + }) + t.Run("AddOutgoingTransactions error", func(t *testing.T) { + t.Parallel() + + outgoingMbh, outgoingMb, incomingMbh, incomingMb := createMiniBlocks() + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataPool := adaptDataPoolForVerifyGas(dataComponents.DataPool) + dataComponents.DataPool = dataPool + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.GasComputation = &testscommon.GasComputationMock{ + AddIncomingMiniBlocksCalled: func(miniBlocks []data.MiniBlockHeaderHandler, transactions map[string][]data.TransactionHandler) (int, int, error) { + return len(miniBlocks), 0, nil + }, + AddOutgoingTransactionsCalled: func(txHashes [][]byte, transactions []data.TransactionHandler) ([][]byte, []data.MiniBlockHeaderHandler, error) { + return nil, nil, expectedError + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + err = sp.VerifyGasLimit(createHeaderFromMBs(outgoingMbh, incomingMbh), block.MiniBlockSlice{outgoingMb, incomingMb}) + require.Equal(t, expectedError, err) + }) + t.Run("AddOutgoingTransactions results in limit exceeded", func(t *testing.T) { + t.Parallel() + + outgoingMbh, outgoingMb, incomingMbh, incomingMb := createMiniBlocks() + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataPool := adaptDataPoolForVerifyGas(dataComponents.DataPool) + dataComponents.DataPool = dataPool + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.GasComputation = &testscommon.GasComputationMock{ + AddIncomingMiniBlocksCalled: func(miniBlocks []data.MiniBlockHeaderHandler, transactions map[string][]data.TransactionHandler) (int, int, error) { + return len(miniBlocks), 0, nil + }, + AddOutgoingTransactionsCalled: func(txHashes [][]byte, transactions []data.TransactionHandler) ([][]byte, []data.MiniBlockHeaderHandler, error) { + return txHashes[:len(txHashes)-1], nil, nil // one tx over the limit + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + err = sp.VerifyGasLimit(createHeaderFromMBs(outgoingMbh, incomingMbh), block.MiniBlockSlice{outgoingMb, incomingMb}) + require.ErrorIs(t, err, process.ErrInvalidMaxGasLimitPerMiniBlock) + require.Contains(t, err.Error(), "outgoing transactions exceeded") + }) + t.Run("AddOutgoingTransactions adds extra pending mini blocks on AddOutgoingTransactions", func(t *testing.T) { + t.Parallel() + + outgoingMbh, outgoingMb, incomingMbh, incomingMb := createMiniBlocks() + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataPool := adaptDataPoolForVerifyGas(dataComponents.DataPool) + dataComponents.DataPool = dataPool + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.GasComputation = &testscommon.GasComputationMock{ + AddIncomingMiniBlocksCalled: func(miniBlocks []data.MiniBlockHeaderHandler, transactions map[string][]data.TransactionHandler) (int, int, error) { + return len(miniBlocks), 0, nil // no pending mini blocks left + }, + AddOutgoingTransactionsCalled: func(txHashes [][]byte, transactions []data.TransactionHandler) ([][]byte, []data.MiniBlockHeaderHandler, error) { + return txHashes, []data.MiniBlockHeaderHandler{&block.MiniBlockHeader{}}, nil // one pending mini block added + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + err = sp.VerifyGasLimit(createHeaderFromMBs(outgoingMbh, incomingMbh), block.MiniBlockSlice{outgoingMb, incomingMb}) + require.ErrorIs(t, err, process.ErrInvalidMaxGasLimitPerMiniBlock) + require.Contains(t, err.Error(), "incoming mini blocks exceeded the limit") + }) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + outgoingMbh, outgoingMb, incomingMbh, incomingMb := createMiniBlocks() + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataPool := adaptDataPoolForVerifyGas(dataComponents.DataPool) + dataComponents.DataPool = dataPool + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + sp, err := blproc.NewShardProcessor(arguments) + require.NoError(t, err) + + header := createHeaderFromMBs(outgoingMbh, incomingMbh) + headerV3, ok := header.(*block.HeaderV3) + require.True(t, ok) + headerV3.LastExecutionResult = &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + } + err = sp.VerifyGasLimit(headerV3, block.MiniBlockSlice{outgoingMb, incomingMb}) + require.NoError(t, err) + }) +} + +func adaptDataPoolForVerifyGas( + initialPool retriever.PoolsHolder, +) *dataRetriever.PoolsHolderStub { + headers := initialPool.Headers() + proofs := initialPool.Proofs() + postProcessTxs := initialPool.PostProcessTransactions() + executedMbs := initialPool.ExecutedMiniBlocks() + dsTxs := initialPool.DirectSentTransactions() + return &dataRetriever.PoolsHolderStub{ + HeadersCalled: func() retriever.HeadersPool { + return headers + }, + ProofsCalled: func() retriever.ProofsPool { + return proofs + }, + TransactionsCalled: func() retriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + SearchFirstDataCalled: func(key []byte) (value interface{}, ok bool) { + value = &transaction.Transaction{} + ok = true + return + }, + } + }, + PostProcessTransactionsCalled: func() storage.Cacher { + return postProcessTxs + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return executedMbs + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return dsTxs + }, + } +} + +func createHeaderFromMBs(mbs ...block.MiniBlockHeader) data.ShardHeaderHandler { + return &block.HeaderV3{MiniBlockHeaders: mbs} +} + +func createMiniBlocks() ( + outgoingMbh block.MiniBlockHeader, + outgoingMb *block.MiniBlock, + incomingMbh block.MiniBlockHeader, + incomingMb *block.MiniBlock, +) { + outgoingMbh, outgoingMb = createMiniBlock("outgoingMBHash", 0, 0) + incomingMbh, incomingMb = createMiniBlock("incomingMBHash", 1, 0) + return +} + +func createMiniBlock(hash string, srcShard uint32, dstShard uint32) (block.MiniBlockHeader, *block.MiniBlock) { + mbh := block.MiniBlockHeader{ + Hash: []byte(hash), + SenderShardID: srcShard, + ReceiverShardID: dstShard, + } + mb := &block.MiniBlock{ + TxHashes: [][]byte{ + []byte("txHash"), + }, + SenderShardID: srcShard, + ReceiverShardID: dstShard, + } + return mbh, mb +} + +func TestShardProcessor_ProcessBlockProposal(t *testing.T) { + t.Parallel() + + arguments := CreateMockArguments(createComponentHolderMocks()) + headerHash := []byte("headerHash") + t.Run("nil header should error", func(t *testing.T) { + t.Parallel() + + sp, _ := blproc.NewShardProcessor(arguments) + body := &block.Body{} + _, err := sp.ProcessBlockProposal(nil, headerHash, body) + + require.Equal(t, process.ErrNilBlockHeader, err) + }) + t.Run("nil body should error", func(t *testing.T) { + t.Parallel() + + sp, _ := blproc.NewShardProcessor(arguments) + header := &block.HeaderV3{} + _, err := sp.ProcessBlockProposal(header, headerHash, nil) + + require.Equal(t, process.ErrNilBlockBody, err) + }) + t.Run("not headerV3 should error", func(t *testing.T) { + t.Parallel() + + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.Header{} // wrong type + body := &block.Body{} + _, err := sp.ProcessBlockProposal(header, headerHash, body) + + require.Equal(t, process.ErrInvalidHeader, err) + }) + t.Run("wrong header type (meta) should error", func(t *testing.T) { + t.Parallel() + + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.MetaBlockV3{} // wrong type + body := &block.Body{} + _, err := sp.ProcessBlockProposal(header, headerHash, body) + + require.Equal(t, process.ErrWrongTypeAssertion, err) + }) + t.Run("wrong body type should error", func(t *testing.T) { + t.Parallel() + + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{} + body := &wrongBody{} // wrong type + _, err := sp.ProcessBlockProposal(header, headerHash, body) + + require.Equal(t, process.ErrWrongTypeAssertion, err) + }) + t.Run("createBlockStarted fails should error", func(t *testing.T) { + t.Parallel() + + args := CreateMockArguments(createComponentHolderMocks()) + args.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + AddIntermediateTransactionsCalled: func(mapSCRs map[block.Type][]data.TransactionHandler, key []byte) error { + return expectedErr + }, + } + sp, _ := blproc.NewShardProcessor(args) + + header := &block.HeaderV3{ + Nonce: 1, + } + body := &block.Body{} + _, err := sp.ProcessBlockProposal(header, headerHash, body) + + require.Equal(t, expectedErr, err) + }) + t.Run("IsDataPreparedForProcessing fails should error", func(t *testing.T) { + t.Parallel() + + args := CreateMockArguments(createComponentHolderMocks()) + args.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + IsDataPreparedForProcessingCalled: func(haveTime func() time.Duration) error { + return expectedErr + }, + } + sp, _ := blproc.NewShardProcessor(args) + + header := &block.HeaderV3{ + Nonce: 1, + } + body := &block.Body{} + _, err := sp.ProcessBlockProposal(header, headerHash, body) + + require.Equal(t, expectedErr, err) + }) + t.Run("checkEpochStartInfoAvailableIfNeeded fails should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + args := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + args.EpochStartTrigger = &mock.EpochStartTriggerStub{ + MetaEpochCalled: func() uint32 { + return 5 + }, + IsEpochStartCalled: func() bool { + return false + }, + } + dataComponents.DataPool = &dataRetriever.PoolsHolderStub{ + HeadersCalled: func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, expectedErr + }, + } + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, + } + sp, _ := blproc.NewShardProcessor(args) + + header := &block.HeaderV3{ + Nonce: 1, + Epoch: 10, + EpochStartMetaHash: []byte("epochStartHash"), + } + body := &block.Body{} + _, err := sp.ProcessBlockProposal(header, headerHash, body) + + require.Error(t, err) + require.ErrorIs(t, err, process.ErrEpochStartInfoNotAvailable) + }) + t.Run("WaitForHeadersIfNeeded fails should error", func(t *testing.T) { + t.Parallel() + + args := CreateMockArguments(createComponentHolderMocks()) + args.HeadersForBlock = &testscommon.HeadersForBlockMock{ + WaitForHeadersIfNeededCalled: func(haveTime func() time.Duration) error { + return expectedErr + }, + } + sp, _ := blproc.NewShardProcessor(args) + + header := &block.HeaderV3{ + Nonce: 1, + } + body := &block.Body{} + _, err := sp.ProcessBlockProposal(header, headerHash, body) + require.Equal(t, expectedErr, err) + }) + t.Run("WaitForHeadersIfNeeded fails should error", func(t *testing.T) { + t.Parallel() + + args := CreateMockArguments(createComponentHolderMocks()) + args.BlockChainHook = &testscommon.BlockChainHookStub{ + SetCurrentHeaderCalled: func(hdr data.HeaderHandler) error { + return expectedErr + }, + } + sp, _ := blproc.NewShardProcessor(args) + + header := &block.HeaderV3{ + Nonce: 1, + } + body := &block.Body{} + _, err := sp.ProcessBlockProposal(header, headerHash, body) + require.Equal(t, expectedErr, err) + }) + t.Run("ProcessBlockTransaction fails should error", func(t *testing.T) { + t.Parallel() + + args := CreateMockArguments(createComponentHolderMocks()) + wasRemoveAtNonceAndHigherCalled := false + args.ExecutionManager = &processMocks.ExecutionManagerMock{ + RemoveAtNonceAndHigherCalled: func(nonce uint64) error { + wasRemoveAtNonceAndHigherCalled = true + return nil + }, + } + args.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + ProcessBlockTransactionCalled: func(header data.HeaderHandler, body *block.Body, haveTime func() time.Duration) error { + return expectedErr + }, + } + sp, _ := blproc.NewShardProcessor(args) + + header := &block.HeaderV3{ + Nonce: 1, + } + body := &block.Body{} + _, err := sp.ProcessBlockProposal(header, headerHash, body) + require.Equal(t, expectedErr, err) + require.False(t, wasRemoveAtNonceAndHigherCalled) + }) + t.Run("VerifyCreatedBlockTransactions fails should error", func(t *testing.T) { + t.Parallel() + + args := CreateMockArguments(createComponentHolderMocks()) + args.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + VerifyCreatedBlockTransactionsCalled: func(hdr data.HeaderHandler, body *block.Body) error { + return expectedErr + }, + } + sp, _ := blproc.NewShardProcessor(args) + + header := &block.HeaderV3{ + Nonce: 1, + } + body := &block.Body{} + _, err := sp.ProcessBlockProposal(header, headerHash, body) + require.Equal(t, expectedErr, err) + }) + t.Run("collectExecutionResults fails should error", func(t *testing.T) { + t.Parallel() + + args := CreateMockArguments(createComponentHolderMocks()) + args.TxCoordinator = &testscommon.TransactionCoordinatorMock{ + CreateReceiptsHashCalled: func() ([]byte, error) { + return nil, expectedErr + }, + } + sp, _ := blproc.NewShardProcessor(args) + + header := &block.HeaderV3{ + Nonce: 1, + } + body := &block.Body{} + _, err := sp.ProcessBlockProposal(header, headerHash, body) + require.Equal(t, expectedErr, err) + }) + t.Run("HandleProcessErrorCutoff fails should error", func(t *testing.T) { + t.Parallel() + + args := CreateMockArguments(createComponentHolderMocks()) + args.BlockProcessingCutoffHandler = &testscommon.BlockProcessingCutoffStub{ + HandleProcessErrorCutoffCalled: func(header data.HeaderHandler) error { + return expectedErr + }, + } + sp, _ := blproc.NewShardProcessor(args) + + header := &block.HeaderV3{ + Nonce: 1, + } + body := &block.Body{} + _, err := sp.ProcessBlockProposal(header, headerHash, body) + require.Equal(t, expectedErr, err) + }) + t.Run("commit state is not called by ProcessBlockProposal", func(t *testing.T) { + t.Parallel() + + commitCalled := false + + args := CreateMockArguments(createComponentHolderMocks()) + args.AccountsDB = map[state.AccountsDbIdentifier]state.AccountsAdapter{ + state.UserAccountsState: &stateMock.AccountsStub{ + CommitCalled: func() ([]byte, error) { + commitCalled = true + return []byte("stateRoot"), nil + }, + RootHashCalled: func() ([]byte, error) { + return nil, nil + }, + RecreateTrieIfNeededCalled: func(options common.RootHashHolder) error { + return nil + }, + }, + } + + sp, _ := blproc.NewShardProcessor(args) + + header := &block.HeaderV3{ + Nonce: 1, + } + body := &block.Body{} + _, err := sp.ProcessBlockProposal(header, headerHash, body) + require.Nil(t, err) + require.False(t, commitCalled) + + err = sp.CommitBlockProposalState(header) + require.Nil(t, err) + require.True(t, commitCalled) + }) + t.Run("should work, no transactions", func(t *testing.T) { + t.Parallel() + + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{ + Nonce: 1, + } + body := &block.Body{} + _, err := sp.ProcessBlockProposal(header, headerHash, body) + + require.Nil(t, err) + }) +} + +func TestShardProcessor_ShouldEpochStartInfoBeAvailable(t *testing.T) { + t.Parallel() + + t.Run("no epoch start meta hash should return false", func(t *testing.T) { + t.Parallel() + + arguments := CreateMockArguments(createComponentHolderMocks()) + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{} + result := sp.ShouldEpochStartInfoBeAvailable(header) + require.False(t, result) + }) + + t.Run("epoch start triggered should return false", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.EpochStartTrigger = &mock.EpochStartTriggerStub{ + IsEpochStartCalled: func() bool { + return true + }, + } + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{} + _ = header.SetEpochStartMetaHash([]byte("hash")) + result := sp.ShouldEpochStartInfoBeAvailable(header) + require.False(t, result) + }) + + t.Run("epoch less than or equal to meta epoch should return false", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.EpochStartTrigger = &mock.EpochStartTriggerStub{ + IsEpochStartCalled: func() bool { + return false + }, + MetaEpochCalled: func() uint32 { + return 5 + }, + } + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{} + _ = header.SetEpochStartMetaHash([]byte("hash")) + _ = header.SetEpoch(5) // equal + result := sp.ShouldEpochStartInfoBeAvailable(header) + require.False(t, result) + + _ = header.SetEpoch(4) // less than + result = sp.ShouldEpochStartInfoBeAvailable(header) + require.False(t, result) + }) + + t.Run("should return true when all conditions met", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.EpochStartTrigger = &mock.EpochStartTriggerStub{ + IsEpochStartCalled: func() bool { + return false + }, + MetaEpochCalled: func() uint32 { + return 5 + }, + } + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{} + _ = header.SetEpochStartMetaHash([]byte("hash")) + _ = header.SetEpoch(10) + result := sp.ShouldEpochStartInfoBeAvailable(header) + require.True(t, result) + }) +} + +func TestShardProcessor_GetCrossShardIncomingMiniBlocksFromBody(t *testing.T) { + t.Parallel() + + t.Run("empty body should return empty", func(t *testing.T) { + t.Parallel() + + arguments := CreateMockArguments(createComponentHolderMocks()) + sp, _ := blproc.NewShardProcessor(arguments) + + body := &block.Body{} + result := sp.GetCrossShardIncomingMiniBlocksFromBody(body) + require.Empty(t, result) + }) + + t.Run("should filter only cross shard incoming miniblocks", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + // Using default OneShardCoordinator from createComponentHolderMocks + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + sp, _ := blproc.NewShardProcessor(arguments) + + // Test with different shard IDs - one matching self shard, others cross-shard + // Default coordinator has selfId = 0 + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + SenderShardID: 0, + ReceiverShardID: 0, + TxHashes: [][]byte{[]byte("tx1")}, + }, + { + SenderShardID: 1, + ReceiverShardID: 0, + TxHashes: [][]byte{[]byte("tx2")}, + }, + { + SenderShardID: 0, + ReceiverShardID: 1, + TxHashes: [][]byte{[]byte("tx3")}, + }, + { + SenderShardID: 2, + ReceiverShardID: 0, + TxHashes: [][]byte{[]byte("tx4")}, + }, + }, + } + result := sp.GetCrossShardIncomingMiniBlocksFromBody(body) + // Should include miniblocks from shard 1 and 2 going to shard 0 + require.Len(t, result, 2) + require.Equal(t, uint32(1), result[0].SenderShardID) + require.Equal(t, uint32(2), result[1].SenderShardID) + }) +} + +func TestGetHaveTimeForProposal(t *testing.T) { + t.Parallel() + + startTime := time.Now() + maxDuration := 100 * time.Millisecond + + haveTimeLocal := blproc.GetHaveTimeForProposal(startTime, maxDuration) + remaining := haveTimeLocal() + + require.Greater(t, remaining, time.Duration(0)) + require.LessOrEqual(t, remaining, maxDuration) + +} + +func TestShouldDisableOutgoingTxs(t *testing.T) { + t.Parallel() + + t.Run("should return true when conditions met", func(t *testing.T) { + t.Parallel() + + coreComponents, _, _, _ := createComponentHolderMocks() + enableEpochsHandler := coreComponents.EnableEpochsHandler() + enableRoundsHandler := coreComponents.EnableRoundsHandler() + + result := blproc.ShouldDisableOutgoingTxs(enableEpochsHandler, enableRoundsHandler) + // This tests that the function executes without error + require.NotNil(t, result) // result can be true or false depending on configuration + }) +} + +func TestShardProcessor_OnProposedBlock(t *testing.T) { + t.Parallel() + + t.Run("wrong type assertion on body should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + wrongBodyInstance := &wrongBody{} + header := getSimpleHeaderV3Mock() + proposedHash := []byte("proposedHash") + + err = sp.OnProposedBlock(wrongBodyInstance, header, proposedHash) + require.Equal(t, process.ErrWrongTypeAssertion, err) + }) + + t.Run("GetCurrentHeaderHash empty should return error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + RootHash: []byte("rootHash"), + }, + }, + } + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return nil + }, + } + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + body := &block.Body{} + header := getSimpleHeaderV3Mock() + proposedHash := []byte("proposedHash") + + err = sp.OnProposedBlock(body, header, proposedHash) + require.Equal(t, process.ErrNilHeaderHandler, err) + }) + + t.Run("GetLastBaseExecutionResultHandler error should return error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataPool, ok := dataComponents.DataPool.(*dataRetriever.PoolsHolderStub) + require.True(t, ok) + dataPool.HeadersCalled = func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.HeaderV3{}, nil // nil last exec result + }, + } + } + + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + // without execution result should generate error + return &block.HeaderV3{} + }, + } + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + cntAddRef := 0 + arguments.MiniBlocksSelectionSession = &mbSelection.MiniBlockSelectionSessionStub{ + AddReferencedHeaderCalled: func(metaBlock data.HeaderHandler, metaBlockHash []byte) { cntAddRef++ }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + body := &block.Body{} + header := getSimpleHeaderV3Mock() + proposedHash := []byte("proposedHash") + + err = sp.OnProposedBlock(body, header, proposedHash) + require.Error(t, err) + }) + + t.Run("should work with previous V3 header", func(t *testing.T) { + t.Parallel() + + wasOnProposedBlockCalled := false + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataPool, ok := dataComponents.DataPool.(*dataRetriever.PoolsHolderStub) + require.True(t, ok) + dataPool.TransactionsCalled = func() retriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + OnProposedBlockCalled: func(blockHash []byte, blockBody *block.Body, blockHeader data.HeaderHandler, accountsProvider common.AccountNonceAndBalanceProvider, latestExecutedHash []byte) error { + wasOnProposedBlockCalled = true + return nil + }, + } + } + dataPool.HeadersCalled = func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return getSimpleHeaderV3Mock(), nil + }, + } + } + dataComponents.DataPool = dataPool + updateBlockchainForOnProposed(dataComponents) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + body := &block.Body{} + header := getSimpleHeaderV3Mock() + proposedHash := []byte("proposedHash") + + err = sp.OnProposedBlock(body, header, proposedHash) + require.NoError(t, err) + require.True(t, wasOnProposedBlockCalled) + }) + t.Run("should work with previous V2 header", func(t *testing.T) { + t.Parallel() + + wasOnProposedBlockCalled := false + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataPool, ok := dataComponents.DataPool.(*dataRetriever.PoolsHolderStub) + require.True(t, ok) + dataPool.TransactionsCalled = func() retriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + OnProposedBlockCalled: func(blockHash []byte, blockBody *block.Body, blockHeader data.HeaderHandler, accountsProvider common.AccountNonceAndBalanceProvider, latestExecutedHash []byte) error { + wasOnProposedBlockCalled = true + return nil + }, + } + } + dataPool.HeadersCalled = func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return getSimpleHeaderV3Mock(), nil + }, + } + } + dataComponents.DataPool = dataPool + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV2{ + Header: &block.Header{ + RootHash: []byte("rootHash"), + }, + } + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("prev header hash") + }, + } + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + body := &block.Body{} + header := getSimpleHeaderV3Mock() + proposedHash := []byte("proposedHash") + + err = sp.OnProposedBlock(body, header, proposedHash) + require.NoError(t, err) + require.True(t, wasOnProposedBlockCalled) + }) +} + +func TestShardProcessor_collectExecutionResults(t *testing.T) { + t.Parallel() + + t.Run("with CreateReceiptsHash error should return error", func(t *testing.T) { + t.Parallel() + + txCoordinator := &testscommon.TransactionCoordinatorMock{ + CreateReceiptsHashCalled: func() ([]byte, error) { + return nil, expectedErr + }, + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "txCoordinator": txCoordinator, + "shardCoordinator": &mock.ShardCoordinatorStub{ + SelfIdCalled: func() uint32 { + return 0 + }, + }, + }) + require.Nil(t, err) + + header := &block.HeaderV3{} + _, err = sp.CollectExecutionResults(make([]byte, 0), header, &block.Body{}) + require.Equal(t, expectedErr, err) + }) + + t.Run("with gas used exceeds gas provided should return error", func(t *testing.T) { + t.Parallel() + + subComponents, header, body := createSubComponentsForCollectExecutionResultsTest() + gasProvider := subComponents["gasConsumedProvider"].(*testscommon.GasHandlerStub) + gasProvider.TotalGasProvidedCalled = func() uint64 { + return 10 // less than gas used in test + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(subComponents) + require.Nil(t, err) + headerHash := []byte("header hash to be tested") + _, err = sp.CollectExecutionResults(headerHash, header, body) + assert.Equal(t, process.ErrGasUsedExceedsGasProvided, err) + }) + + t.Run("with cacheExecutedMiniBlocks error should return error", func(t *testing.T) { + t.Parallel() + + expectedErr := errors.New("MarshalizerMock generic error") + subComponents, header, body := createSubComponentsForCollectExecutionResultsTest() + + expected_blocks := 10 + marshallerCalled := 0 + marshallerMock := subComponents["marshalizer"].(*mock.MarshalizerMock) + marshaller := &mock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + // Marshaller is first called for mini block headers, then for executed mini blocks + // We want to fail on the executed mini blocks marshalling + + if marshallerCalled == expected_blocks { + marshallerMock.Fail = true + } + marshallerCalled++ + return marshallerMock.Marshal(obj) + }, + } + subComponents["marshalizer"] = marshaller + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(subComponents) + require.Nil(t, err) + headerHash := []byte("header hash to be tested") + _, err = sp.CollectExecutionResults(headerHash, header, body) + assert.Error(t, err) + assert.Equal(t, expectedErr, err) + }) + + t.Run("with createMiniBlockHeaderHandlers error should return error", func(t *testing.T) { + t.Parallel() + + expectedErr := errors.New("MarshalizerMock generic error") + subComponents, header, body := createSubComponentsForCollectExecutionResultsTest() + + marshaller := subComponents["marshalizer"].(*mock.MarshalizerMock) + marshaller.Fail = true + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(subComponents) + require.Nil(t, err) + headerHash := []byte("header hash to be tested") + _, err = sp.CollectExecutionResults(headerHash, header, body) + assert.Error(t, err) + assert.Equal(t, expectedErr, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + subComponents, header, body := createSubComponentsForCollectExecutionResultsTest() + + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(subComponents) + require.Nil(t, err) + + headerHash := []byte("header hash to be tested") + result, err := sp.CollectExecutionResults(headerHash, header, body) + + require.Nil(t, err) + require.NotNil(t, result) + assert.Equal(t, uint64(1350), result.GetGasUsed(), "gas used should be set correctly") + assert.Equal(t, uint32(10), result.GetHeaderEpoch(), "epoch should be 10 as per mock header") + assert.Equal(t, headerHash, result.GetHeaderHash(), "header hash should match input") + assert.Equal(t, uint64(155), result.GetHeaderNonce(), "nonce should be 155 as per mock header") + assert.Equal(t, uint64(2067), result.GetHeaderRound(), "round should be 2067 as per mock header") + assert.Equal(t, []byte("root hash to be tested"), result.GetRootHash(), "root hash should match mock") + + realResult := result.(*block.ExecutionResult) + realResult.GetMiniBlockHeaders() + actual_miniblocks := map[block.Type]int{} + expected_miniblocks := map[block.Type]int{ + block.TxBlock: 4, + block.SmartContractResultBlock: 2, + block.ReceiptBlock: 1, + block.RewardsBlock: 1, + block.InvalidBlock: 1, + block.PeerBlock: 1, + } + for i, mbResult := range realResult.GetMiniBlockHeaders() { + switch mbResult.GetType() { + case block.TxBlock: + actual_miniblocks[block.TxBlock]++ + if mbResult.GetSenderShardID() == uint32(0) && mbResult.GetReceiverShardID() == uint32(0) { + assert.Equal(t, uint32(2), mbResult.GetTxCount(), "same-shard tx miniblock should have 2 txs as per mock") + continue + } + if mbResult.GetSenderShardID() == uint32(0) && mbResult.GetReceiverShardID() == uint32(1) { + assert.Equal(t, uint32(2), mbResult.GetTxCount(), "cross-shard tx miniblock should have 2 txs as per mock") + continue + } + if mbResult.GetSenderShardID() == uint32(1) && mbResult.GetReceiverShardID() == uint32(0) { + assert.Equal(t, uint32(1), mbResult.GetTxCount(), "cross-shard tx miniblock should have 1 tx as per mock") + continue + } + if mbResult.GetSenderShardID() == uint32(2) && mbResult.GetReceiverShardID() == uint32(0) { + assert.Equal(t, uint32(1), mbResult.GetTxCount(), "cross-shard tx miniblock should have 1 tx as per mock") + continue + } + require.Fail(t, "unexpected tx miniblock shard IDs") + + case block.SmartContractResultBlock: + actual_miniblocks[block.SmartContractResultBlock]++ + if mbResult.GetSenderShardID() == uint32(0) && mbResult.GetReceiverShardID() == uint32(1) { + assert.Equal(t, uint32(1), mbResult.GetTxCount(), "outgoing SCR miniblock should have 1 tx as per mock") + continue + } + if mbResult.GetSenderShardID() == uint32(2) && mbResult.GetReceiverShardID() == uint32(0) { + assert.Equal(t, uint32(2), mbResult.GetTxCount(), "incoming SCR miniblock should have 2 txs as per mock") + continue + } + require.Fail(t, "unexpected SCR miniblock shard IDs") + + case block.ReceiptBlock: + actual_miniblocks[block.ReceiptBlock]++ + assert.NotEqual(t, uint32(0), mbResult.GetSenderShardID(), "receipt miniblock should not be self-shard") + assert.Equal(t, uint32(1), mbResult.GetTxCount(), "receipt miniblock should have 1 tx as per mock") + case block.RewardsBlock: + actual_miniblocks[block.RewardsBlock]++ + assert.Equal(t, uint32(1), mbResult.GetTxCount(), "rewards miniblock should have 1 tx as per mock") + case block.InvalidBlock: + actual_miniblocks[block.InvalidBlock]++ + assert.Equal(t, uint32(1), mbResult.GetTxCount(), "invalid miniblock should have 1 tx as per mock") + case block.PeerBlock: + actual_miniblocks[block.PeerBlock]++ + assert.Equal(t, uint32(1), mbResult.GetTxCount(), "peer miniblock should have 1 tx as per mock") + default: + require.Fail(t, "unexpected miniblock type") + } + + fmt.Println("MiniBlockHeader Result ", i, ": type ", mbResult.GetType(), ", sender shard ", mbResult.GetSenderShardID(), ", receiver shard ", mbResult.GetReceiverShardID(), ", tx count ", mbResult.GetTxCount()) + } + assert.Equal(t, expected_miniblocks, actual_miniblocks, "miniblock counts by type should match expected") + assert.Equal(t, big.NewInt(1000), realResult.GetAccumulatedFees(), "accumulated fees should match mock") + assert.Equal(t, big.NewInt(100), realResult.GetDeveloperFees(), "developer fees should match mock") + }) +} + +func Test_getOrderedProcessedMetaBlocksFromMiniBlockHashesV3(t *testing.T) { + t.Parallel() + + mbHash1 := []byte("miniBlockHeaderHash1") + mbHash2 := []byte("miniBlockHeaderHash2") + metaHeaderHash1 := []byte("metaHeaderHash1") + metaHeaderHash2 := []byte("metaHeaderHash2") + + t.Run("should return ErrWrongTypeAssertion in case of wrong header type", func(t *testing.T) { + t.Parallel() + + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{}) + require.Nil(t, err) + + _, _, err = sp.GetOrderedProcessedMetaBlocksFromMiniBlockHashesV3( + &block.MetaBlockV3{}, + nil, + ) + require.Equal(t, process.ErrWrongTypeAssertion, err) + }) + + t.Run("if GetHeaderByHash from dataPool fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "dataPool": &dataRetriever.PoolsHolderStub{ + HeadersCalled: func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, expectedErr + }, + } + }, + }, + "processedMiniBlocksTracker": &testscommon.ProcessedMiniBlocksTrackerStub{}, + }) + require.Nil(t, err) + + header := &block.HeaderV3{ + MetaBlockHashes: [][]byte{metaHeaderHash1}, + } + _, _, err = sp.GetOrderedProcessedMetaBlocksFromMiniBlockHashesV3( + header, + make(map[int][]byte), + ) + require.Equal(t, expectedErr, err) + }) + + t.Run("should mark a meta block as fully referenced in case of no cross mini blocks with dst", func(t *testing.T) { + t.Parallel() + + headersByHash := map[string]data.HeaderHandler{ + string(metaHeaderHash1): &block.MetaBlockV3{ + Nonce: 1, + ShardInfo: []block.ShardData{ + { + ShardID: 0, + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + ReceiverShardID: 0, + Hash: mbHash1, + }, + }, + }, + }, + }, + } + + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "dataPool": &dataRetriever.PoolsHolderStub{ + HeadersCalled: func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return headersByHash[string(hash)], nil + }, + } + }, + }, + "shardCoordinator": &mock.CoordinatorStub{SelfIdCalled: func() uint32 { + return 1 + }}, + "processedMiniBlocksTracker": &testscommon.ProcessedMiniBlocksTrackerStub{}, + }) + require.Nil(t, err) + + header := &block.HeaderV3{MetaBlockHashes: [][]byte{metaHeaderHash1}} + fullyReferencedMetaBlocks, _, err := sp.GetOrderedProcessedMetaBlocksFromMiniBlockHashesV3( + header, + make(map[int][]byte), + ) + require.Nil(t, err) + require.Equal(t, 1, len(fullyReferencedMetaBlocks)) + }) + + t.Run("should mark a meta block as fully referenced in case of all hashes referenced on block", func(t *testing.T) { + t.Parallel() + + metaHeader1 := &block.MetaBlockV3{ + Nonce: 1, + ShardInfo: []block.ShardData{ + { + ShardID: 0, + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + ReceiverShardID: 1, + Hash: mbHash1, + }, + }, + }, + }, + } + + metaHeader2 := &block.MetaBlockV3{ + Nonce: 2, + ShardInfo: []block.ShardData{ + { + ShardID: 0, + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + ReceiverShardID: 1, + Hash: mbHash2, + }, + }, + }, + }, + } + + headersByHash := map[string]data.HeaderHandler{ + string(metaHeaderHash1): metaHeader1, + string(metaHeaderHash2): metaHeader2, + } + + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "dataPool": &dataRetriever.PoolsHolderStub{ + HeadersCalled: func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return headersByHash[string(hash)], nil + }, + } + }, + }, + "shardCoordinator": &mock.CoordinatorStub{SelfIdCalled: func() uint32 { + return 1 + }}, + "processedMiniBlocksTracker": &testscommon.ProcessedMiniBlocksTrackerStub{}, + }) + require.Nil(t, err) + + header := &block.HeaderV3{ + MetaBlockHashes: [][]byte{metaHeaderHash1, metaHeaderHash2}, + } + fullyReferencedMetaBlocks, _, err := sp.GetOrderedProcessedMetaBlocksFromMiniBlockHashesV3( + header, + map[int][]byte{ + 0: mbHash1, + 1: mbHash2, + }, + ) + + expectedFullyReferencedMetaBlocks := []data.HeaderHandler{metaHeader1, metaHeader2} + require.Nil(t, err) + require.Equal(t, 2, len(fullyReferencedMetaBlocks)) + require.Equal(t, expectedFullyReferencedMetaBlocks, fullyReferencedMetaBlocks) + }) + + t.Run("shouldn't mark a meta block as fully referenced in case not all hashes are referenced on block", func(t *testing.T) { + t.Parallel() + + headersByHash := map[string]data.HeaderHandler{ + string(metaHeaderHash1): &block.MetaBlockV3{ + ShardInfo: []block.ShardData{ + { + ShardID: 0, + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + ReceiverShardID: 1, + Hash: []byte("miniBlockHeaderHash1"), + }, + }, + }, + }, + }, + string(metaHeaderHash2): &block.MetaBlockV3{ + ShardInfo: []block.ShardData{ + { + ShardID: 0, + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + ReceiverShardID: 1, + Hash: []byte("miniBlockHeaderHash2"), + }, + }, + }, + }, + }, + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "dataPool": &dataRetriever.PoolsHolderStub{ + HeadersCalled: func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return headersByHash[string(hash)], nil + }, + } + }, + }, + "shardCoordinator": &mock.CoordinatorStub{SelfIdCalled: func() uint32 { + return 1 + }}, + "processedMiniBlocksTracker": &testscommon.ProcessedMiniBlocksTrackerStub{}, + }) + require.Nil(t, err) + + header := &block.HeaderV3{ + MetaBlockHashes: [][]byte{metaHeaderHash1, metaHeaderHash2}, + } + fullyReferencedMetaBlocks, _, err := sp.GetOrderedProcessedMetaBlocksFromMiniBlockHashesV3( + header, + map[int][]byte{ + 0: mbHash1, + }, + ) + require.Nil(t, err) + require.Equal(t, 1, len(fullyReferencedMetaBlocks)) + }) + t.Run("metablock fully referenced across multiple shard headers", func(t *testing.T) { + t.Parallel() + + metaHeader1 := &block.MetaBlockV3{ + Nonce: 1, + ShardInfo: []block.ShardData{ + { + ShardID: 0, + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + ReceiverShardID: 1, + Hash: mbHash1, + }, + }, + }, + }, + } + + metaHeader2 := &block.MetaBlockV3{ + Nonce: 2, + ShardInfo: []block.ShardData{}, + } + + headersByHash := map[string]data.HeaderHandler{ + string(metaHeaderHash1): metaHeader1, + string(metaHeaderHash2): metaHeader2, + } + + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "dataPool": &dataRetriever.PoolsHolderStub{ + HeadersCalled: func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return headersByHash[string(hash)], nil + }, + } + }, + }, + "shardCoordinator": &mock.CoordinatorStub{SelfIdCalled: func() uint32 { + return 1 + }}, + "processedMiniBlocksTracker": &testscommon.ProcessedMiniBlocksTrackerStub{ + IsMiniBlockFullyProcessedCalled: func(metaBlockHash []byte, miniBlockHash []byte) bool { + return bytes.Equal(miniBlockHash, mbHash2) + }, + }, + }) + require.Nil(t, err) + + header := &block.HeaderV3{ + MetaBlockHashes: [][]byte{metaHeaderHash1, metaHeaderHash2}, + } + fullyReferencedMetaBlocks, _, err := sp.GetOrderedProcessedMetaBlocksFromMiniBlockHashesV3( + header, + map[int][]byte{ + 0: mbHash1, + 1: mbHash2, + }, + ) + + expectedFullyReferencedMetaBlocks := []data.HeaderHandler{metaHeader1, metaHeader2} + require.Nil(t, err) + require.Equal(t, 2, len(fullyReferencedMetaBlocks)) + require.Equal(t, expectedFullyReferencedMetaBlocks, fullyReferencedMetaBlocks) + }) +} + +func createSubComponentsForCollectExecutionResultsTest() (map[string]interface{}, data.HeaderHandler, *block.Body) { + + txCoordinator := &testscommon.TransactionCoordinatorMock{ + GetCreatedMiniBlocksFromMeCalled: func() block.MiniBlockSlice { + return block.MiniBlockSlice{ + // same-shard regular transactions + { + SenderShardID: 0, + ReceiverShardID: 0, + Type: block.TxBlock, + TxHashes: [][]byte{ + []byte("tx_self_3"), + []byte("tx_self_4"), + }, + }, + // cross-shard outgoing transactions + { + SenderShardID: 0, + ReceiverShardID: 1, + Type: block.TxBlock, + TxHashes: [][]byte{ + []byte("tx_cross_3"), + []byte("tx_cross_4"), + }, + }, + // invalid transactions + { + SenderShardID: 0, + ReceiverShardID: 0, + Type: block.InvalidBlock, + TxHashes: [][]byte{ + []byte("tx_invalid_1"), + }, + }, + } + }, + + CreatePostProcessMiniBlocksCalled: func() block.MiniBlockSlice { + return block.MiniBlockSlice{ + // self shard SCRs - should be sanitized out + { + SenderShardID: 0, + ReceiverShardID: 0, + Type: block.SmartContractResultBlock, + TxHashes: [][]byte{ + []byte("scr_self_1"), + []byte("scr_self_2"), + }, + }, + // outgoing SCRs + { + SenderShardID: 0, + ReceiverShardID: 1, + Type: block.SmartContractResultBlock, + TxHashes: [][]byte{ + []byte("scr_out_1"), + }, + }, + // self shard receipts - should be sanitized out + { + SenderShardID: 0, + ReceiverShardID: 0, + Type: block.ReceiptBlock, + TxHashes: [][]byte{ + []byte("rcpt_0"), + }, + }, + } + }, + + CreateReceiptsHashCalled: func() ([]byte, error) { + return []byte("mock_receipts_hash"), nil + }, + GetAllIntermediateTxsCalled: func() map[block.Type]map[string]data.TransactionHandler { + return map[block.Type]map[string]data.TransactionHandler{} + }, + } + + rootHash := []byte("root hash to be tested") + accounts := map[state.AccountsDbIdentifier]state.AccountsAdapter{ + 0: &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return rootHash, nil + }, + }, + } + feeHandler := &mock.FeeAccumulatorStub{ + GetAccumulatedFeesCalled: func() *big.Int { + return big.NewInt(1000) + }, + GetDeveloperFeesCalled: func() *big.Int { + return big.NewInt(100) + }, + } + gasConsumedProvider := &testscommon.GasHandlerStub{ + TotalGasProvidedCalled: func() uint64 { + return 1500 + }, + TotalGasRefundedCalled: func() uint64 { + return 100 + }, + TotalGasPenalizedCalled: func() uint64 { + return 50 + }, + } + subComponents := map[string]interface{}{ + "txCoordinator": txCoordinator, + "shardCoordinator": &mock.ShardCoordinatorStub{ + SelfIdCalled: func() uint32 { + return 0 + }, + }, + "feeHandler": feeHandler, + "gasConsumedProvider": gasConsumedProvider, + "marshalizer": &mock.MarshalizerMock{}, + "hasher": &hashingMocks.HasherMock{}, + "enableEpochsHandler": enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + "dataPool": initDataPool(), + "accountsDB": accounts, + "txExecutionOrderHandler": &commonMocks.TxExecutionOrderHandlerStub{}, + "economicsData": &economicsmocks.EconomicsHandlerMock{}, + } + + header, body := createHeaderAndBodyForTestingProcessBlockProposal() + + return subComponents, header, body +} + +func createHeaderAndBodyForTestingProcessBlockProposal() (data.HeaderHandler, *block.Body) { + header := &block.HeaderV3{ + PrevHash: []byte("previousHash"), + Epoch: 10, + Nonce: 155, + Round: 2067, + TxCount: 13, + } + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + // Outgoing miniblock from shard + { + SenderShardID: 0, + TxHashes: [][]byte{ + []byte("tx_self_1"), + []byte("tx_self_2"), + []byte("tx_cross_1"), + []byte("tx_cross_2"), + []byte("tx_invalid_1"), + []byte("tx_unexecutable_1"), + }, + }, + // Incoming miniblocks to shard + { + SenderShardID: 1, + ReceiverShardID: 0, + TxHashes: [][]byte{[]byte("tx_incoming_1")}, + }, + { + SenderShardID: 2, + ReceiverShardID: 0, + TxHashes: [][]byte{[]byte("tx_incoming_2")}, + }, + // Incoming SCR miniblock + { + SenderShardID: 2, + ReceiverShardID: 0, + TxHashes: [][]byte{[]byte("tx_scr_in_1"), []byte("tx_scr_in_2")}, + Type: block.SmartContractResultBlock, + }, + // Other types of miniblocks + { + Type: block.RewardsBlock, + SenderShardID: core.MetachainShardId, + ReceiverShardID: 0, + TxHashes: [][]byte{[]byte("reward1")}, + }, + { + Type: block.PeerBlock, + SenderShardID: core.MetachainShardId, + ReceiverShardID: 0, + TxHashes: [][]byte{[]byte("peer1")}, + }, + { + Type: block.ReceiptBlock, + SenderShardID: 2, + ReceiverShardID: 0, + TxHashes: [][]byte{[]byte("rcpt_1")}, + }, + }, + } + return header, body +} + +func createSubComponentsForVerifyProposalTest() map[string]interface{} { + poolMock := initDataPool() + poolMock.HeadersCalled = func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + }, + } + } + currentBlockHeader := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 0, + Round: 1, + ShardID: 0, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + } + + blkc, _ := blockchain.NewBlockChain(&statusHandlerMock.AppStatusHandlerStub{}) + _ = blkc.SetCurrentBlockHeaderAndRootHash(currentBlockHeader, []byte("root")) + blkc.SetCurrentBlockHeaderHash([]byte("hash")) + gracePeriod, _ := graceperiod.NewEpochChangeGracePeriod([]config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}) + + return map[string]interface{}{ + "blockChain": blkc, + "dataPool": poolMock, + "shardCoordinator": &mock.ShardCoordinatorStub{ + SelfIdCalled: func() uint32 { + return 0 + }, + }, + "executionResultsVerifier": &processMocks.ExecutionResultsVerifierMock{ + VerifyHeaderExecutionResultsCalled: func(header data.HeaderHandler) error { + return nil + }, + }, + "executionResultsInclusionEstimator": &processMocks.InclusionEstimatorMock{ + DecideCalled: func(lastNotarised *common.LastExecutionResultForInclusion, pending []data.BaseExecutionResultHandler, currentHdrTsMs uint64) (allowed int) { + return 0 + }, + }, + "missingDataResolver": &processMocks.MissingDataResolverMock{ + WaitForMissingDataCalled: func(timeout time.Duration) error { + return nil + }, + }, + "epochStartTrigger": &mock.EpochStartTriggerStub{ + EpochStartRoundCalled: func() uint64 { + return 10 + }, + EpochFinalityAttestingRoundCalled: func() uint64 { + return 5 + }, + }, + "enableEpochsHandler": enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + "epochChangeGracePeriodHandler": gracePeriod, + "blockTracker": &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.MetaBlockV3{}, []byte("h"), nil + }, + }, + "gasComputation": &testscommon.GasComputationMock{ + AddIncomingMiniBlocksCalled: func(miniBlocks []data.MiniBlockHeaderHandler, transactions map[string][]data.TransactionHandler) (int, int, error) { + return len(miniBlocks) - 1, 0, nil // no pending mini blocks left + }, + AddOutgoingTransactionsCalled: func(txHashes [][]byte, transactions []data.TransactionHandler) ([][]byte, []data.MiniBlockHeaderHandler, error) { + return txHashes, []data.MiniBlockHeaderHandler{&block.MiniBlockHeader{}}, nil // one pending mini block added + }, + }, + "appStatusHandler": &statusHandlerMock.AppStatusHandlerStub{}, + "marshalizer": &mock.MarshalizerMock{}, + "roundHandler": &mock.RoundHandlerMock{}, + "maxProposalNonceGap": uint64(10), + } +} + +func updateBlockchainForOnProposed(dataComponents *mock.DataComponentsMock) { + dataComponents.BlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + RootHash: []byte("rootHash"), + }, + }, + } + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("prev header hash") + }, + } +} diff --git a/process/block/shardblockRequest_test.go b/process/block/shardblockRequest_test.go index 3ab3a0f942f..e640e465c5c 100644 --- a/process/block/shardblockRequest_test.go +++ b/process/block/shardblockRequest_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" blproc "github.com/multiversx/mx-chain-go/process/block" + "github.com/multiversx/mx-chain-go/process/block/headerForBlock" "github.com/multiversx/mx-chain-go/testscommon" dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" ) @@ -26,62 +27,6 @@ type shardBlockTestData struct { headerData []*headerData } -func TestShardProcessor_RequestMissingFinalityAttestingHeaders(t *testing.T) { - t.Parallel() - - t.Run("missing attesting meta header", func(t *testing.T) { - t.Parallel() - - arguments, requestHandler := shardBlockRequestTestInit(t) - testData := createShardProcessorTestData() - metaChainData := testData[core.MetachainShardId] - numCalls := atomic.Uint32{} - requestHandler.RequestShardHeaderByNonceCalled = func(shardID uint32, nonce uint64) { - require.Fail(t, fmt.Sprintf("should not request shard header by nonce, shardID: %d, nonce: %d", shardID, nonce)) - } - requestHandler.RequestMetaHeaderByNonceCalled = func(nonce uint64) { - attestationNonce := metaChainData.headerData[1].header.GetNonce() - require.Equal(t, attestationNonce, nonce, fmt.Sprintf("nonce should have been %d", attestationNonce)) - numCalls.Add(1) - } - sp, _ := blproc.NewShardProcessor(arguments) - - metaBlockData := metaChainData.headerData[0] - // not adding the confirmation metaBlock to the headers pool means it will be missing and requested - sp.SetHighestHdrNonceForCurrentBlock(core.MetachainShardId, metaBlockData.header.GetNonce()) - res := sp.RequestMissingFinalityAttestingHeaders() - time.Sleep(100 * time.Millisecond) - - require.Equal(t, uint32(1), res) - require.Equal(t, uint32(1), numCalls.Load()) - }) - t.Run("no missing attesting meta header", func(t *testing.T) { - t.Parallel() - - arguments, requestHandler := shardBlockRequestTestInit(t) - testData := createShardProcessorTestData() - metaChainData := testData[core.MetachainShardId] - requestHandler.RequestShardHeaderByNonceCalled = func(shardID uint32, nonce uint64) { - require.Fail(t, fmt.Sprintf("should not request shard header by nonce, shardID: %d, nonce: %d", shardID, nonce)) - } - requestHandler.RequestMetaHeaderByNonceCalled = func(nonce uint64) { - require.Fail(t, "should not request meta header by nonce") - } - sp, _ := blproc.NewShardProcessor(arguments) - - headersDataPool := arguments.DataComponents.Datapool().Headers() - require.NotNil(t, headersDataPool) - metaBlockData := metaChainData.headerData[0] - confirmationMetaBlockData := metaChainData.headerData[1] - headersDataPool.AddHeader(confirmationMetaBlockData.hash, confirmationMetaBlockData.header) - sp.SetHighestHdrNonceForCurrentBlock(core.MetachainShardId, metaBlockData.header.GetNonce()) - res := sp.RequestMissingFinalityAttestingHeaders() - time.Sleep(100 * time.Millisecond) - - require.Equal(t, uint32(0), res) - }) -} - func TestShardProcessor_computeExistingAndRequestMissingMetaHeaders(t *testing.T) { t.Parallel() @@ -105,10 +50,8 @@ func TestShardProcessor_computeExistingAndRequestMissingMetaHeaders(t *testing.T require.Equal(t, metaChainData.headerData[1].hash, hash) numCalls.Add(1) } - sp, _ := blproc.NewShardProcessor(arguments) metaBlockData := metaChainData.headerData[0] - sp.SetHighestHdrNonceForCurrentBlock(core.MetachainShardId, metaBlockData.header.GetNonce()) // not adding the referenced metaBlock to the headers pool means it will be missing and requested // first of the 2 referenced headers is added, the other will be missing headersDataPool := arguments.DataComponents.Datapool().Headers() @@ -116,7 +59,8 @@ func TestShardProcessor_computeExistingAndRequestMissingMetaHeaders(t *testing.T blockBeingProcessed := shard1Data.headerData[1].header shardBlockBeingProcessed := blockBeingProcessed.(*block.Header) - missingHeaders, missingFinalityAttestingHeaders, missingProofs := sp.ComputeExistingAndRequestMissingMetaHeaders(shardBlockBeingProcessed) + arguments.HeadersForBlock.RequestMetaHeaders(shardBlockBeingProcessed) + missingHeaders, missingProofs, missingFinalityAttestingHeaders := arguments.HeadersForBlock.GetMissingData() time.Sleep(100 * time.Millisecond) require.Equal(t, uint32(1), missingHeaders) @@ -146,14 +90,13 @@ func TestShardProcessor_computeExistingAndRequestMissingMetaHeaders(t *testing.T numCalls.Add(1) } - sp, _ := blproc.NewShardProcessor(arguments) - metaBlockData := testData[core.MetachainShardId].headerData[0] + // not adding the referenced metaBlock to the headers pool means it will be missing and requested - sp.SetHighestHdrNonceForCurrentBlock(core.MetachainShardId, metaBlockData.header.GetNonce()) blockBeingProcessed := shard1Data.headerData[1].header shardBlockBeingProcessed := blockBeingProcessed.(*block.Header) - missingHeaders, missingFinalityAttestingHeaders, missingProofs := sp.ComputeExistingAndRequestMissingMetaHeaders(shardBlockBeingProcessed) + arguments.HeadersForBlock.RequestMetaHeaders(shardBlockBeingProcessed) + missingHeaders, missingProofs, missingFinalityAttestingHeaders := arguments.HeadersForBlock.GetMissingData() time.Sleep(100 * time.Millisecond) require.Equal(t, uint32(2), missingHeaders) @@ -185,7 +128,6 @@ func TestShardProcessor_computeExistingAndRequestMissingMetaHeaders(t *testing.T numCallsMissing.Add(1) } - sp, _ := blproc.NewShardProcessor(arguments) // not adding the referenced metaBlock to the headers pool means it will be missing and requested headersDataPool := arguments.DataComponents.Datapool().Headers() headersDataPool.AddHeader(metaChainData.headerData[0].hash, metaChainData.headerData[0].header) @@ -193,7 +135,8 @@ func TestShardProcessor_computeExistingAndRequestMissingMetaHeaders(t *testing.T blockBeingProcessed := shard1Data.headerData[1].header shardBlockBeingProcessed := blockBeingProcessed.(*block.Header) - missingHeaders, missingFinalityAttestingHeaders, missingProofs := sp.ComputeExistingAndRequestMissingMetaHeaders(shardBlockBeingProcessed) + arguments.HeadersForBlock.RequestMetaHeaders(shardBlockBeingProcessed) + missingHeaders, missingProofs, missingFinalityAttestingHeaders := arguments.HeadersForBlock.GetMissingData() time.Sleep(100 * time.Millisecond) require.Equal(t, uint32(0), missingHeaders) @@ -220,7 +163,6 @@ func TestShardProcessor_computeExistingAndRequestMissingMetaHeaders(t *testing.T requestHandler.RequestMetaHeaderCalled = func(hash []byte) { numCallsMissing.Add(1) } - sp, _ := blproc.NewShardProcessor(arguments) // not adding the referenced metaBlock to the headers pool means it will be missing and requested headersDataPool := arguments.DataComponents.Datapool().Headers() headersDataPool.AddHeader(metaChainData.headerData[0].hash, metaChainData.headerData[0].header) @@ -237,7 +179,8 @@ func TestShardProcessor_computeExistingAndRequestMissingMetaHeaders(t *testing.T blockBeingProcessed := shard1Data.headerData[1].header shardBlockBeingProcessed := blockBeingProcessed.(*block.Header) - missingHeaders, missingFinalityAttestingHeaders, missingProofs := sp.ComputeExistingAndRequestMissingMetaHeaders(shardBlockBeingProcessed) + arguments.HeadersForBlock.RequestMetaHeaders(shardBlockBeingProcessed) + missingHeaders, missingProofs, missingFinalityAttestingHeaders := arguments.HeadersForBlock.GetMissingData() time.Sleep(100 * time.Millisecond) require.Equal(t, uint32(0), missingHeaders) @@ -248,197 +191,6 @@ func TestShardProcessor_computeExistingAndRequestMissingMetaHeaders(t *testing.T }) } -func TestShardProcessor_receivedMetaBlock(t *testing.T) { - t.Parallel() - - t.Run("received non referenced metaBlock, while still having missing referenced metaBlocks", func(t *testing.T) { - t.Parallel() - - arguments, requestHandler := shardBlockRequestTestInit(t) - testData := createShardProcessorTestData() - sp, _ := blproc.NewShardProcessor(arguments) - hdrsForBlock := sp.GetHdrForBlock() - - firstMissingMetaBlockData := testData[core.MetachainShardId].headerData[0] - secondMissingMetaBlockData := testData[core.MetachainShardId].headerData[1] - - requestHandler.RequestMetaHeaderCalled = func(hash []byte) { - require.Fail(t, "no requests expected") - } - requestHandler.RequestMetaHeaderByNonceCalled = func(nonce uint64) { - require.Fail(t, "no requests expected") - } - - highestHeaderNonce := firstMissingMetaBlockData.header.GetNonce() - 1 - hdrsForBlock.SetNumMissingHdrs(2) - hdrsForBlock.SetNumMissingFinalityAttestingHdrs(0) - hdrsForBlock.SetHighestHdrNonce(core.MetachainShardId, highestHeaderNonce) - hdrsForBlock.SetHdrHashAndInfo(string(firstMissingMetaBlockData.hash), - &blproc.HdrInfo{ - UsedInBlock: true, - Hdr: nil, - }) - hdrsForBlock.SetHdrHashAndInfo(string(secondMissingMetaBlockData.hash), - &blproc.HdrInfo{ - UsedInBlock: true, - Hdr: nil, - }) - otherMetaBlock := &block.MetaBlock{ - Nonce: 102, - Round: 102, - PrevHash: []byte("other meta block prev hash"), - } - - otherMetaBlockHash := []byte("other meta block hash") - sp.ReceivedMetaBlock(otherMetaBlock, otherMetaBlockHash) - time.Sleep(100 * time.Millisecond) - - require.Equal(t, uint32(2), hdrsForBlock.GetMissingHdrs()) - require.Equal(t, uint32(0), hdrsForBlock.GetMissingFinalityAttestingHdrs()) - highestHeaderNonces := hdrsForBlock.GetHighestHdrNonce() - require.Equal(t, highestHeaderNonce, highestHeaderNonces[core.MetachainShardId]) - }) - t.Run("received missing referenced metaBlock, other referenced metaBlock still missing", func(t *testing.T) { - t.Parallel() - - arguments, requestHandler := shardBlockRequestTestInit(t) - testData := createShardProcessorTestData() - sp, _ := blproc.NewShardProcessor(arguments) - hdrsForBlock := sp.GetHdrForBlock() - - firstMissingMetaBlockData := testData[core.MetachainShardId].headerData[0] - secondMissingMetaBlockData := testData[core.MetachainShardId].headerData[1] - - requestHandler.RequestMetaHeaderCalled = func(hash []byte) { - require.Fail(t, "no requests expected") - } - requestHandler.RequestMetaHeaderByNonceCalled = func(nonce uint64) { - require.Fail(t, "no requests expected") - } - - highestHeaderNonce := firstMissingMetaBlockData.header.GetNonce() - 1 - hdrsForBlock.SetNumMissingHdrs(2) - hdrsForBlock.SetNumMissingFinalityAttestingHdrs(0) - hdrsForBlock.SetHighestHdrNonce(core.MetachainShardId, highestHeaderNonce) - hdrsForBlock.SetHdrHashAndInfo(string(firstMissingMetaBlockData.hash), - &blproc.HdrInfo{ - UsedInBlock: true, - Hdr: nil, - }) - hdrsForBlock.SetHdrHashAndInfo(string(secondMissingMetaBlockData.hash), - &blproc.HdrInfo{ - UsedInBlock: true, - Hdr: nil, - }) - - sp.ReceivedMetaBlock(firstMissingMetaBlockData.header, firstMissingMetaBlockData.hash) - time.Sleep(100 * time.Millisecond) - - require.Equal(t, uint32(1), hdrsForBlock.GetMissingHdrs()) - require.Equal(t, uint32(0), hdrsForBlock.GetMissingFinalityAttestingHdrs()) - highestHeaderNonces := hdrsForBlock.GetHighestHdrNonce() - require.Equal(t, firstMissingMetaBlockData.header.GetNonce(), highestHeaderNonces[core.MetachainShardId]) - }) - t.Run("received non missing referenced metaBlock", func(t *testing.T) { - t.Parallel() - - arguments, requestHandler := shardBlockRequestTestInit(t) - testData := createShardProcessorTestData() - sp, _ := blproc.NewShardProcessor(arguments) - hdrsForBlock := sp.GetHdrForBlock() - - notMissingReferencedMetaBlockData := testData[core.MetachainShardId].headerData[0] - missingMetaBlockData := testData[core.MetachainShardId].headerData[1] - - requestHandler.RequestMetaHeaderCalled = func(hash []byte) { - require.Fail(t, "no requests expected") - } - requestHandler.RequestMetaHeaderByNonceCalled = func(nonce uint64) { - require.Fail(t, "no requests expected") - } - - highestHeaderNonce := notMissingReferencedMetaBlockData.header.GetNonce() - 1 - hdrsForBlock.SetNumMissingHdrs(1) - hdrsForBlock.SetNumMissingFinalityAttestingHdrs(0) - hdrsForBlock.SetHighestHdrNonce(core.MetachainShardId, highestHeaderNonce) - hdrsForBlock.SetHdrHashAndInfo(string(notMissingReferencedMetaBlockData.hash), - &blproc.HdrInfo{ - UsedInBlock: true, - Hdr: notMissingReferencedMetaBlockData.header, - }) - hdrsForBlock.SetHdrHashAndInfo(string(missingMetaBlockData.hash), - &blproc.HdrInfo{ - UsedInBlock: true, - Hdr: nil, - }) - - headersDataPool := arguments.DataComponents.Datapool().Headers() - require.NotNil(t, headersDataPool) - headersDataPool.AddHeader(notMissingReferencedMetaBlockData.hash, notMissingReferencedMetaBlockData.header) - - sp.ReceivedMetaBlock(notMissingReferencedMetaBlockData.header, notMissingReferencedMetaBlockData.hash) - time.Sleep(100 * time.Millisecond) - - require.Equal(t, uint32(1), hdrsForBlock.GetMissingHdrs()) - require.Equal(t, uint32(0), hdrsForBlock.GetMissingFinalityAttestingHdrs()) - hdrsForBlockHighestNonces := hdrsForBlock.GetHighestHdrNonce() - require.Equal(t, highestHeaderNonce, hdrsForBlockHighestNonces[core.MetachainShardId]) - }) - t.Run("received missing attestation metaBlock", func(t *testing.T) { - t.Parallel() - - arguments, requestHandler := shardBlockRequestTestInit(t) - testData := createShardProcessorTestData() - sp, _ := blproc.NewShardProcessor(arguments) - hdrsForBlock := sp.GetHdrForBlock() - - referencedMetaBlock := testData[core.MetachainShardId].headerData[0] - lastReferencedMetaBlock := testData[core.MetachainShardId].headerData[1] - attestationMetaBlockHash := []byte("attestation meta block hash") - attestationMetaBlock := &block.MetaBlock{ - Nonce: lastReferencedMetaBlock.header.GetNonce() + 1, - Round: lastReferencedMetaBlock.header.GetRound() + 1, - PrevHash: lastReferencedMetaBlock.hash, - } - - requestHandler.RequestMetaHeaderCalled = func(hash []byte) { - require.Fail(t, "no requests expected") - } - requestHandler.RequestMetaHeaderByNonceCalled = func(nonce uint64) { - require.Fail(t, "no requests expected") - } - - hdrsForBlock.SetNumMissingHdrs(0) - hdrsForBlock.SetNumMissingFinalityAttestingHdrs(1) - hdrsForBlock.SetHighestHdrNonce(core.MetachainShardId, lastReferencedMetaBlock.header.GetNonce()) - hdrsForBlock.SetHdrHashAndInfo(string(referencedMetaBlock.hash), - &blproc.HdrInfo{ - UsedInBlock: true, - Hdr: referencedMetaBlock.header, - }) - hdrsForBlock.SetHdrHashAndInfo(string(lastReferencedMetaBlock.hash), - &blproc.HdrInfo{ - UsedInBlock: true, - Hdr: lastReferencedMetaBlock.header, - }) - - headersDataPool := arguments.DataComponents.Datapool().Headers() - require.NotNil(t, headersDataPool) - headersDataPool.AddHeader(referencedMetaBlock.hash, referencedMetaBlock.header) - headersDataPool.AddHeader(lastReferencedMetaBlock.hash, lastReferencedMetaBlock.header) - headersDataPool.AddHeader(attestationMetaBlockHash, attestationMetaBlock) - wg := startWaitingForAllHeadersReceivedSignal(t, sp) - - sp.ReceivedMetaBlock(attestationMetaBlock, attestationMetaBlockHash) - wg.Wait() - - require.Equal(t, uint32(0), hdrsForBlock.GetMissingHdrs()) - require.Equal(t, uint32(0), hdrsForBlock.GetMissingFinalityAttestingHdrs()) - hdrsForBlockHighestNonces := hdrsForBlock.GetHighestHdrNonce() - require.Equal(t, lastReferencedMetaBlock.header.GetNonce(), hdrsForBlockHighestNonces[core.MetachainShardId]) - }) -} - func shardBlockRequestTestInit(t *testing.T) (blproc.ArgShardProcessor, *testscommon.RequestHandlerStub) { coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() poolMock := dataRetrieverMock.NewPoolsHolderMock() @@ -453,6 +205,21 @@ func shardBlockRequestTestInit(t *testing.T) (blproc.ArgShardProcessor, *testsco requestHandler, ok := arguments.ArgBaseProcessor.RequestHandler.(*testscommon.RequestHandlerStub) require.True(t, ok) + + headersForBlock, err := headerForBlock.NewHeadersForBlock(headerForBlock.ArgHeadersForBlock{ + DataPool: arguments.DataComponents.Datapool(), + RequestHandler: arguments.RequestHandler, + EnableEpochsHandler: arguments.CoreComponents.EnableEpochsHandler(), + ShardCoordinator: arguments.BootstrapComponents.ShardCoordinator(), + BlockTracker: arguments.BlockTracker, + TxCoordinator: arguments.TxCoordinator, + RoundHandler: arguments.CoreComponents.RoundHandler(), + ExtraDelayForRequestBlockInfoInMilliseconds: 100, + GenesisNonce: 0, + }) + require.Nil(t, err) + arguments.HeadersForBlock = headersForBlock + return arguments, requestHandler } diff --git a/process/block/shardblock_test.go b/process/block/shardblock_test.go index 24051d6f7b1..e93ca3454e3 100644 --- a/process/block/shardblock_test.go +++ b/process/block/shardblock_test.go @@ -27,11 +27,15 @@ import ( "github.com/stretchr/testify/require" "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/common/graceperiod" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/dataRetriever/blockchain" processOutport "github.com/multiversx/mx-chain-go/outport/process" "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionTrack" blproc "github.com/multiversx/mx-chain-go/process/block" + "github.com/multiversx/mx-chain-go/process/block/headerForBlock" "github.com/multiversx/mx-chain-go/process/block/processedMb" "github.com/multiversx/mx-chain-go/process/coordinator" "github.com/multiversx/mx-chain-go/process/factory/shard" @@ -39,6 +43,7 @@ import ( "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/storage" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/cache" commonMock "github.com/multiversx/mx-chain-go/testscommon/common" dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" @@ -46,6 +51,8 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" "github.com/multiversx/mx-chain-go/testscommon/outport" + "github.com/multiversx/mx-chain-go/testscommon/pool" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" stateMock "github.com/multiversx/mx-chain-go/testscommon/state" statusHandlerMock "github.com/multiversx/mx-chain-go/testscommon/statusHandler" storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" @@ -57,8 +64,6 @@ func createMockPubkeyConverter() *testscommon.PubkeyConverterMock { return testscommon.NewPubkeyConverterMock(32) } -// ------- NewShardProcessor - func initAccountsMock() *stateMock.AccountsStub { rootHashCalled := func() ([]byte, error) { return []byte("rootHash"), nil @@ -68,7 +73,7 @@ func initAccountsMock() *stateMock.AccountsStub { } } -func initBasicTestData() (*dataRetrieverMock.PoolsHolderMock, data.ChainHandler, []byte, *block.Body, [][]byte, *hashingMocks.HasherMock, *mock.MarshalizerMock, error, []byte) { +func initBasicTestData() (*dataRetrieverMock.PoolsHolderMock, data.ChainHandler, []byte, *block.Body, [][]byte, *hashingMocks.HasherMock, *mock.MarshalizerMock, []byte) { tdp := dataRetrieverMock.NewPoolsHolderMock() txHash := []byte("tx_hash1") randSeed := []byte("rand seed") @@ -95,7 +100,7 @@ func initBasicTestData() (*dataRetrieverMock.PoolsHolderMock, data.ChainHandler, marshalizer := &mock.MarshalizerMock{} mbbytes, _ := marshalizer.Marshal(&miniblock) mbHash := hasher.Compute(string(mbbytes)) - return tdp, blkc, rootHash, body, txHashes, hasher, marshalizer, nil, mbHash + return tdp, blkc, rootHash, body, txHashes, hasher, marshalizer, mbHash } func initBlockHeader(prevHash []byte, prevRandSeed []byte, rootHash []byte, mbHdrs []block.MiniBlockHeader) block.Header { @@ -122,7 +127,7 @@ func CreateCoreComponentsMultiShard() ( coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() dataComponents.BlockChain, _ = blockchain.NewBlockChain(&statusHandlerMock.AppStatusHandlerStub{}) _ = dataComponents.BlockChain.SetGenesisHeader(&block.Header{Nonce: 0}) - dataComponents.DataPool = initDataPool([]byte("tx_hash1")) + dataComponents.DataPool = initDataPool() bootstrapComponents.Coordinator = mock.NewMultiShardsCoordinatorMock(3) return coreComponents, dataComponents, bootstrapComponents, statusComponents @@ -141,7 +146,15 @@ func CreateMockArgumentsMultiShard( return arguments } -// ------- NewBlockProcessor +func createMockTxCacheSelectionConfig() config.TxCacheSelectionConfig { + return config.TxCacheSelectionConfig{ + SelectionGasBandwidthIncreasePercent: 400, + SelectionGasBandwidthIncreaseScheduledPercent: 260, + SelectionGasRequested: 10_000_000_000, + SelectionMaxNumTxs: 30000, + SelectionLoopDurationCheckInterval: 10, + } +} func TestNewShardProcessor(t *testing.T) { t.Parallel() @@ -161,15 +174,17 @@ func TestNewShardProcessor(t *testing.T) { { args: func() blproc.ArgShardProcessor { dataCompCopy := *dataComponents + args := CreateMockArgumentsMultiShard(coreComponents, &dataCompCopy, bootstrapComponents, statusComponents) dataCompCopy.DataPool = nil - return CreateMockArgumentsMultiShard(coreComponents, &dataCompCopy, bootstrapComponents, statusComponents) + return args }, expectedErr: process.ErrNilDataPoolHolder, }, { args: func() blproc.ArgShardProcessor { dataCompCopy := *dataComponents + args := CreateMockArgumentsMultiShard(coreComponents, &dataCompCopy, bootstrapComponents, statusComponents) dataCompCopy.DataPool = &dataRetrieverMock.PoolsHolderStub{ HeadersCalled: func() dataRetriever.HeadersPool { return nil @@ -178,7 +193,7 @@ func TestNewShardProcessor(t *testing.T) { return &dataRetrieverMock.ProofsPoolMock{} }, } - return CreateMockArgumentsMultiShard(coreComponents, &dataCompCopy, bootstrapComponents, statusComponents) + return args }, expectedErr: process.ErrNilHeadersDataPool, }, @@ -189,6 +204,9 @@ func TestNewShardProcessor(t *testing.T) { HeadersCalled: func() dataRetriever.HeadersPool { return &mock.HeadersCacherStub{} }, + ProofsCalled: func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{} + }, TransactionsCalled: func() dataRetriever.ShardedDataCacherNotifier { return nil }, @@ -215,6 +233,14 @@ func TestNewShardProcessor(t *testing.T) { }, expectedErr: nil, }, + { + args: func() blproc.ArgShardProcessor { + args := CreateMockArgumentsMultiShard(coreComponents, dataComponents, bootstrapComponents, statusComponents) + args.AccountsProposal = nil + return args + }, + expectedErr: process.ErrNilAccountsAdapter, + }, } for _, test := range tests { @@ -298,8 +324,9 @@ func TestShardProcess_CreateNewBlockHeaderProcessHeaderExpectCheckRoundCalled(t mockProcessHandler.SetIdleCalled = func() { busyIdleCalled = append(busyIdleCalled, idleIdentifier) } - mockProcessHandler.SetBusyCalled = func(reason string) { + mockProcessHandler.TrySetBusyCalled = func(reason string) bool { busyIdleCalled = append(busyIdleCalled, busyIdentifier) + return true } err = shardProcessor.ProcessBlock(headerHandler, bodyHandler, func() time.Duration { return time.Second }) @@ -308,6 +335,54 @@ func TestShardProcess_CreateNewBlockHeaderProcessHeaderExpectCheckRoundCalled(t assert.Equal(t, []string{busyIdentifier, idleIdentifier}, busyIdleCalled) // the order is important } +func TestShardProcessor_ProcessBlockShouldErrWhenProcessorBusy(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + processHandler := arguments.CoreComponents.ProcessStatusHandler() + mockProcessHandler := processHandler.(*testscommon.ProcessStatusHandlerStub) + mockProcessHandler.TrySetBusyCalled = func(reason string) bool { + return false + } + + sp, _ := blproc.NewShardProcessor(arguments) + header := &block.Header{ + Nonce: 1, + PubKeysBitmap: []byte("0100101"), + PrevHash: []byte(""), + PrevRandSeed: []byte("rand seed"), + Signature: []byte("signature"), + RootHash: []byte("roothash"), + } + body := &block.Body{} + + err := sp.ProcessBlock(header, body, func() time.Duration { return time.Second }) + require.Equal(t, process.ErrBlockProcessorBusy, err) +} + +func TestShardProcessor_CreateBlockShouldErrWhenProcessorBusy(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + processHandler := arguments.CoreComponents.ProcessStatusHandler() + mockProcessHandler := processHandler.(*testscommon.ProcessStatusHandlerStub) + mockProcessHandler.TrySetBusyCalled = func(reason string) bool { + return false + } + + sp, _ := blproc.NewShardProcessor(arguments) + header := &block.Header{Round: 1} + + hdr, body, err := sp.CreateBlock(header, func() bool { return true }) + require.Equal(t, process.ErrBlockProcessorBusy, err) + require.Nil(t, hdr) + require.Nil(t, body) +} + func TestShardProcessor_ProcessWithDirtyAccountShouldErr(t *testing.T) { t.Parallel() // set accounts dirty @@ -389,8 +464,10 @@ func TestShardProcessor_ProcessBlockHeaderBodyMismatchShouldErr(t *testing.T) { func TestShardProcessor_ProcessBlockWithInvalidTransactionShouldErr(t *testing.T) { t.Parallel() - tdp := initDataPool([]byte("tx_hash1")) + + tdp := initDataPool() txHash := []byte("tx_hash1") + tdp.Transactions().AddData(txHash, &transaction.Transaction{}, 128, "0") body := &block.Body{} txHashes := make([][]byte, 0) @@ -437,24 +514,26 @@ func TestShardProcessor_ProcessBlockWithInvalidTransactionShouldErr(t *testing.T RevertToSnapshotCalled: revertToSnapshot, RootHashCalled: rootHashCalled, } - factory, _ := shard.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(5), - initStore(), - marshalizer, - hasher, - tdp, - createMockPubkeyConverter(), - accounts, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{ + + argsPreProcessorsContainerFactory := shard.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(5), + Store: initStore(), + Marshalizer: marshalizer, + Hasher: hasher, + DataPool: tdp, + PubkeyConverter: createMockPubkeyConverter(), + Accounts: accounts, + AccountsProposal: accounts, + RequestHandler: &testscommon.RequestHandlerStub{}, + TxProcessor: &testscommon.TxProcessorMock{ ProcessTransactionCalled: func(transaction *transaction.Transaction) (vmcommon.ReturnCode, error) { return 0, process.ErrHigherNonceInTransaction }, }, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{ + ScProcessor: &testscommon.SCProcessorMock{}, + ScResultProcessor: &testscommon.SmartContractResultsProcessorMock{}, + RewardsTxProcessor: &testscommon.RewardTxProcessorMock{}, + EconomicsFee: &economicsmocks.EconomicsHandlerMock{ ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { return 0 }, @@ -462,7 +541,7 @@ func TestShardProcessor_ProcessBlockWithInvalidTransactionShouldErr(t *testing.T return MaxGasLimitPerBlock }, }, - &testscommon.GasHandlerStub{ + GasHandler: &testscommon.GasHandlerStub{ ComputeGasProvidedByMiniBlockCalled: func(miniBlock *block.MiniBlock, mapHashTx map[string]data.TransactionHandler) (uint64, uint64, error) { return 0, 0, nil }, @@ -473,18 +552,30 @@ func TestShardProcessor_ProcessBlockWithInvalidTransactionShouldErr(t *testing.T RemoveGasRefundedCalled: func(hashes [][]byte) {}, RemoveGasProvidedCalled: func(hashes [][]byte) {}, }, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) - container, _ := factory.Create() - - argsTransactionCoordinator := createMockTransactionCoordinatorArguments(accounts, tdp, container) + BlockTracker: &mock.BlockTrackerMock{}, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + TxTypeHandler: &testscommon.TxTypeHandlerMock{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: createMockTxCacheSelectionConfig(), + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + } + + factory, err := shard.NewPreProcessorsContainerFactory(argsPreProcessorsContainerFactory) + require.NoError(t, err) + + container, err := factory.Create() + require.NoError(t, err) + containerPreProcProposal, err := factory.Create() + require.NoError(t, err) + + argsTransactionCoordinator := createMockTransactionCoordinatorArguments(accounts, tdp, container, containerPreProcProposal) argsTransactionCoordinator.GasHandler = &testscommon.GasHandlerStub{ InitCalled: func() { }, @@ -593,8 +684,11 @@ func TestShardProcessor_ProcessWithHeaderNotCorrectPrevHashShouldErr(t *testing. func TestShardProcessor_ProcessBlockWithErrOnProcessBlockTransactionsCallShouldRevertState(t *testing.T) { t.Parallel() - tdp := initDataPool([]byte("tx_hash1")) + + tdp := initDataPool() txHash := []byte("tx_hash1") + tdp.Transactions().AddData(txHash, &transaction.Transaction{}, 128, "0") + randSeed := []byte("rand seed") blkc, _ := blockchain.NewBlockChain(&statusHandlerMock.AppStatusHandlerStub{}) _ = blkc.SetCurrentBlockHeaderAndRootHash( @@ -663,20 +757,22 @@ func TestShardProcessor_ProcessBlockWithErrOnProcessBlockTransactionsCallShouldR RevertToSnapshotCalled: revertToSnapshot, RootHashCalled: rootHashCalled, } - factory, _ := shard.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - store, - marshalizer, - hasher, - tdp, - createMockPubkeyConverter(), - accounts, - &testscommon.RequestHandlerStub{}, - tpm, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{ + + argsPreProcessorsContainerFactory := shard.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), + Store: store, + Marshalizer: marshalizer, + Hasher: hasher, + DataPool: tdp, + PubkeyConverter: createMockPubkeyConverter(), + Accounts: accounts, + AccountsProposal: accounts, + RequestHandler: &testscommon.RequestHandlerStub{}, + TxProcessor: tpm, + ScProcessor: &testscommon.SCProcessorMock{}, + ScResultProcessor: &testscommon.SmartContractResultsProcessorMock{}, + RewardsTxProcessor: &testscommon.RewardTxProcessorMock{}, + EconomicsFee: &economicsmocks.EconomicsHandlerMock{ ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { return 0 }, @@ -684,7 +780,7 @@ func TestShardProcessor_ProcessBlockWithErrOnProcessBlockTransactionsCallShouldR return MaxGasLimitPerBlock }, }, - &testscommon.GasHandlerStub{ + GasHandler: &testscommon.GasHandlerStub{ ComputeGasProvidedByMiniBlockCalled: func(miniBlock *block.MiniBlock, mapHashTx map[string]data.TransactionHandler) (uint64, uint64, error) { return 0, 0, nil }, @@ -695,19 +791,30 @@ func TestShardProcessor_ProcessBlockWithErrOnProcessBlockTransactionsCallShouldR RemoveGasRefundedCalled: func(hashes [][]byte) {}, RemoveGasProvidedCalled: func(hashes [][]byte) {}, }, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) - container, _ := factory.Create() + BlockTracker: &mock.BlockTrackerMock{}, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + TxTypeHandler: &testscommon.TxTypeHandlerMock{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: createMockTxCacheSelectionConfig(), + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + } + factory, err := shard.NewPreProcessorsContainerFactory(argsPreProcessorsContainerFactory) + require.NoError(t, err) + + container, err := factory.Create() + require.NoError(t, err) + containerPreProcProposal, err := factory.Create() + require.NoError(t, err) totalGasProvided := uint64(0) - argsTransactionCoordinator := createMockTransactionCoordinatorArguments(accounts, tdp, container) + argsTransactionCoordinator := createMockTransactionCoordinatorArguments(accounts, tdp, container, containerPreProcProposal) argsTransactionCoordinator.GasHandler = &testscommon.GasHandlerStub{ InitCalled: func() { totalGasProvided = 0 @@ -748,7 +855,7 @@ func TestShardProcessor_ProcessBlockWithErrOnProcessBlockTransactionsCallShouldR func TestShardProcessor_ProcessBlockWithErrOnVerifyStateRootCallShouldRevertState(t *testing.T) { t.Parallel() - tdp := initDataPool([]byte("tx_hash1")) + tdp := initDataPool() randSeed := []byte("rand seed") txHash := []byte("tx_hash1") blkc, _ := blockchain.NewBlockChain(&statusHandlerMock.AppStatusHandlerStub{}) @@ -836,7 +943,7 @@ func TestShardProcessor_ProcessBlockWithErrOnVerifyStateRootCallShouldRevertStat func TestShardProcessor_ProcessBlockOnlyIntraShardShouldPass(t *testing.T) { t.Parallel() - tdp := initDataPool([]byte("tx_hash1")) + tdp := initDataPool() randSeed := []byte("rand seed") txHash := []byte("tx_hash1") blkc, _ := blockchain.NewBlockChain(&statusHandlerMock.AppStatusHandlerStub{}) @@ -917,7 +1024,7 @@ func TestShardProcessor_ProcessBlockCrossShardWithoutMetaShouldFail(t *testing.T t.Parallel() randSeed := []byte("rand seed") - tdp := initDataPool([]byte("tx_hash1")) + tdp := initDataPool() txHash := []byte("tx_hash1") blkc, _ := blockchain.NewBlockChain(&statusHandlerMock.AppStatusHandlerStub{}) _ = blkc.SetCurrentBlockHeaderAndRootHash( @@ -997,7 +1104,7 @@ func TestShardProcessor_ProcessBlockCrossShardWithoutMetaShouldFail(t *testing.T func TestShardProcessor_ProcessBlockCrossShardWithMetaShouldPass(t *testing.T) { t.Parallel() - tdp, blkc, rootHash, body, txHashes, hasher, marshalizer, _, mbHash := initBasicTestData() + tdp, blkc, rootHash, body, txHashes, hasher, marshalizer, mbHash := initBasicTestData() mbHdr := block.MiniBlockHeader{ ReceiverShardID: 0, SenderShardID: 1, @@ -1079,7 +1186,7 @@ func TestShardProcessor_ProcessBlockCrossShardWithMetaShouldPass(t *testing.T) { func TestShardProcessor_ProcessBlockHaveTimeLessThanZeroShouldErr(t *testing.T) { t.Parallel() txHash := []byte("tx_hash1") - tdp := initDataPool(txHash) + tdp := initDataPool() randSeed := []byte("rand seed") blkc, _ := blockchain.NewBlockChain(&statusHandlerMock.AppStatusHandlerStub{}) @@ -1142,7 +1249,7 @@ func TestShardProcessor_ProcessBlockHaveTimeLessThanZeroShouldErr(t *testing.T) func TestShardProcessor_ProcessBlockWithMissingMetaHdrShouldErr(t *testing.T) { t.Parallel() - tdp, blkc, rootHash, body, txHashes, hasher, marshalizer, _, mbHash := initBasicTestData() + tdp, blkc, rootHash, body, txHashes, hasher, marshalizer, mbHash := initBasicTestData() mbHdr := block.MiniBlockHeader{ ReceiverShardID: 0, SenderShardID: 1, @@ -1224,7 +1331,7 @@ func TestShardProcessor_ProcessBlockWithWrongMiniBlockHeaderShouldErr(t *testing t.Parallel() txHash := []byte("tx_hash1") - tdp := initDataPool(txHash) + tdp := initDataPool() randSeed := []byte("rand seed") blkc, _ := blockchain.NewBlockChain(&statusHandlerMock.AppStatusHandlerStub{}) _ = blkc.SetCurrentBlockHeaderAndRootHash( @@ -1278,7 +1385,7 @@ func TestShardProcessor_ProcessBlockWithWrongMiniBlockHeaderShouldErr(t *testing // should return err err := sp.ProcessBlock(&hdr, body, haveTime) - assert.Equal(t, process.ErrHeaderBodyMismatch, err) + assert.ErrorIs(t, err, process.ErrHeaderBodyMismatch) } // ------- requestEpochStartInfo @@ -1439,6 +1546,9 @@ func TestShardProcessor_RequestEpochStartInfo(t *testing.T) { ProofsCalled: func() dataRetriever.ProofsPool { return &dataRetrieverMock.ProofsPoolMock{} }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, } args := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) @@ -1494,6 +1604,9 @@ func TestShardProcessor_RequestEpochStartInfo(t *testing.T) { ProofsCalled: func() dataRetriever.ProofsPool { return &dataRetrieverMock.ProofsPoolMock{} }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, } args := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) @@ -1555,6 +1668,9 @@ func TestShardProcessor_RequestEpochStartInfo(t *testing.T) { ProofsCalled: func() dataRetriever.ProofsPool { return &dataRetrieverMock.ProofsPoolMock{} }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, } args := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) @@ -1600,7 +1716,7 @@ func TestShardProcessor_CheckAndRequestIfMetaHeadersMissingShouldErr(t *testing. t.Parallel() hdrNoncesRequestCalled := int32(0) - tdp, blkc, rootHash, body, txHashes, hasher, marshalizer, _, mbHash := initBasicTestData() + tdp, blkc, rootHash, body, txHashes, hasher, marshalizer, mbHash := initBasicTestData() mbHdr := block.MiniBlockHeader{ ReceiverShardID: 0, SenderShardID: 1, @@ -1664,6 +1780,17 @@ func TestShardProcessor_CheckAndRequestIfMetaHeadersMissingShouldErr(t *testing. atomic.AddInt32(&hdrNoncesRequestCalled, 1) }, } + arguments.HeadersForBlock, _ = headerForBlock.NewHeadersForBlock(headerForBlock.ArgHeadersForBlock{ + DataPool: dataComponents.DataPool, + RequestHandler: arguments.RequestHandler, + EnableEpochsHandler: coreComponents.EnableEpochsHandler(), + ShardCoordinator: bootstrapComponents.ShardCoordinator(), + BlockTracker: arguments.BlockTracker, + TxCoordinator: arguments.TxCoordinator, + RoundHandler: coreComponents.RoundHandler(), + ExtraDelayForRequestBlockInfoInMilliseconds: 100, + GenesisNonce: 0, + }) arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ JournalLenCalled: journalLen, RevertToSnapshotCalled: revertToSnapshot, @@ -1774,8 +1901,8 @@ func TestShardProcessor_CheckMetaHeadersValidityAndFinalityShouldPass(t *testing sp, _ := blproc.NewShardProcessor(arguments) hdr.Round = 4 - sp.SetHdrForCurrentBlock(metaHash1, meta1, true) - sp.SetHdrForCurrentBlock(metaHash2, meta2, false) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(metaHash1), meta1) + arguments.HeadersForBlock.AddHeaderNotUsedInBlock(string(metaHash2), meta2) err := sp.CheckMetaHeadersValidityAndFinality() assert.Nil(t, err) @@ -1804,7 +1931,7 @@ func TestShardProcessor_CheckMetaHeadersValidityAndFinalityShouldReturnNilWhenNo func TestShardProcessor_CommitBlockMarshalizerFailForHeaderShouldErr(t *testing.T) { t.Parallel() - tdp := initDataPool([]byte("tx_hash1")) + tdp := initDataPool() rootHash := []byte("root hash to be tested") accounts := &stateMock.AccountsStub{ RootHashCalled: func() ([]byte, error) { @@ -1851,7 +1978,7 @@ func TestShardProcessor_CommitBlockMarshalizerFailForHeaderShouldErr(t *testing. func TestShardProcessor_CommitBlockStorageFailsForHeaderShouldErr(t *testing.T) { t.Parallel() - tdp := initDataPool([]byte("tx_hash1")) + tdp := initDataPool() errPersister := errors.New("failure") putCalledNr := uint32(0) rootHash := []byte("root hash to be tested") @@ -1867,12 +1994,14 @@ func TestShardProcessor_CommitBlockStorageFailsForHeaderShouldErr(t *testing.T) return nil }, } - hdr := &block.Header{ - Nonce: 1, - Round: 1, - PubKeysBitmap: []byte("0100101"), - Signature: []byte("signature"), - RootHash: rootHash, + hdr := &block.HeaderV2{ + Header: &block.Header{ + Nonce: 1, + Round: 1, + PubKeysBitmap: []byte("0100101"), + Signature: []byte("signature"), + RootHash: rootHash, + }, } body := &block.Body{} wg := sync.WaitGroup{} @@ -1897,10 +2026,15 @@ func TestShardProcessor_CommitBlockStorageFailsForHeaderShouldErr(t *testing.T) SetUInt64ValueHandler: func(key string, value uint64) {}, }) _ = blkc.SetGenesisHeader(&block.Header{Nonce: 0}) + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + + coreComponents.Hash = &hashingMocks.HasherMock{} + dataComponents.DataPool = tdp dataComponents.Storage = store dataComponents.BlockChain = blkc + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) arguments.AccountsDB[state.UserAccountsState] = accounts arguments.ForkDetector = &mock.ForkDetectorMock{ @@ -1929,8 +2063,9 @@ func TestShardProcessor_CommitBlockStorageFailsForHeaderShouldErr(t *testing.T) mockProcessHandler.SetIdleCalled = func() { busyIdleCalled = append(busyIdleCalled, idleIdentifier) } - mockProcessHandler.SetBusyCalled = func(reason string) { + mockProcessHandler.TrySetBusyCalled = func(reason string) bool { busyIdleCalled = append(busyIdleCalled, busyIdentifier) + return true } expectedFirstNonce := core.OptionalUint64{ HasValue: false, @@ -1944,16 +2079,17 @@ func TestShardProcessor_CommitBlockStorageFailsForHeaderShouldErr(t *testing.T) assert.Equal(t, []string{busyIdentifier, idleIdentifier}, busyIdleCalled) // the order is important expectedFirstNonce.HasValue = true - expectedFirstNonce.Value = hdr.Nonce + expectedFirstNonce.Value = hdr.GetNonce() assert.Equal(t, expectedFirstNonce, sp.NonceOfFirstCommittedBlock()) } func TestShardProcessor_CommitBlockStorageFailsForBodyShouldWork(t *testing.T) { t.Parallel() - tdp := initDataPool([]byte("tx_hash1")) + putCalledNr := uint32(0) errPersister := errors.New("failure") rootHash := []byte("root hash to be tested") + accounts := &stateMock.AccountsStub{ RootHashCalled: func() ([]byte, error) { return rootHash, nil @@ -1965,12 +2101,18 @@ func TestShardProcessor_CommitBlockStorageFailsForBodyShouldWork(t *testing.T) { return nil }, } - hdr := &block.Header{ - Nonce: 1, - Round: 1, - PubKeysBitmap: []byte("0100101"), - Signature: []byte("signature"), - RootHash: rootHash, + + genesisHash := []byte("genesisHash1") + + hdr := &block.HeaderV2{ + Header: &block.Header{ + Nonce: 1, + Round: 1, + PubKeysBitmap: []byte("0100101"), + Signature: []byte("signature"), + RootHash: rootHash, + PrevHash: genesisHash, + }, } mb := block.MiniBlock{} body := &block.Body{} @@ -1992,10 +2134,16 @@ func TestShardProcessor_CommitBlockStorageFailsForBodyShouldWork(t *testing.T) { SetUInt64ValueHandler: func(key string, value uint64) {}, }) _ = blkc.SetGenesisHeader(&block.Header{Nonce: 0}) + blkc.SetGenesisHeaderHash(genesisHash) + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + + tdp := initDataPool() dataComponents.DataPool = tdp dataComponents.Storage = store dataComponents.BlockChain = blkc + coreComponents.Hash = &hashingMocks.HasherMock{} + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) arguments.AccountsDB[state.UserAccountsState] = accounts arguments.ForkDetector = &mock.ForkDetectorMock{ @@ -2019,53 +2167,58 @@ func TestShardProcessor_CommitBlockStorageFailsForBodyShouldWork(t *testing.T) { assert.Nil(t, err) err = sp.CommitBlock(hdr, body) + require.Nil(t, err) + wg.Wait() - assert.Nil(t, err) + assert.True(t, atomic.LoadUint32(&putCalledNr) > 0) } func TestShardProcessor_CommitBlockOkValsShouldWork(t *testing.T) { t.Parallel() - tdp := initDataPool([]byte("tx_hash1")) - txHash := []byte("tx_hash1") + txHash := []byte("tx_hash1") rootHash := []byte("root hash") hdrHash := []byte("header hash") randSeed := []byte("rand seed") - prevHdr := &block.Header{ - Nonce: 0, - Round: 0, - PubKeysBitmap: rootHash, - PrevHash: hdrHash, - Signature: rootHash, - RootHash: rootHash, - RandSeed: randSeed, + prevHdr := &block.HeaderV2{ + Header: &block.Header{ + Nonce: 0, + Round: 0, + PubKeysBitmap: rootHash, + PrevHash: hdrHash, + Signature: rootHash, + RootHash: rootHash, + RandSeed: randSeed, + }, } - hdr := &block.Header{ - Nonce: 1, - Round: 1, - PubKeysBitmap: []byte{0b11111111}, - PrevHash: hdrHash, - Signature: rootHash, - RootHash: rootHash, - PrevRandSeed: randSeed, - AccumulatedFees: big.NewInt(0), - DeveloperFees: big.NewInt(0), + hdr := &block.HeaderV2{ + Header: &block.Header{ + Nonce: 1, + Round: 1, + PubKeysBitmap: []byte{0b11111111}, + PrevHash: hdrHash, + Signature: rootHash, + RootHash: rootHash, + PrevRandSeed: randSeed, + AccumulatedFees: big.NewInt(0), + DeveloperFees: big.NewInt(0), + }, } mb := block.MiniBlock{ TxHashes: [][]byte{txHash}, } body := &block.Body{MiniBlocks: []*block.MiniBlock{&mb}} - mbHdr := block.MiniBlockHeader{ + mbHdr := &block.MiniBlockHeader{ TxCount: uint32(len(mb.TxHashes)), Hash: hdrHash, } - mbHdrs := make([]block.MiniBlockHeader, 0) + mbHdrs := make([]data.MiniBlockHeaderHandler, 0) mbHdrs = append(mbHdrs, mbHdr) - hdr.MiniBlockHeaders = mbHdrs + _ = hdr.SetMiniBlockHeaderHandlers(mbHdrs) accounts := &stateMock.AccountsStub{ CommitCalled: func() (i []byte, e error) { @@ -2108,9 +2261,17 @@ func TestShardProcessor_CommitBlockOkValsShouldWork(t *testing.T) { coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() coreComponents.Hash = hasher - dataComponents.DataPool = tdp + dataComponents.Storage = store dataComponents.BlockChain = blkc + + txPoolOnExecutedBlockCalled := false + dataComponents.DataPool = initDataPool() + dataComponents.DataPool.Transactions().(*testscommon.ShardedDataCacheNotifierMock).OnExecutedBlockCalled = func(blockHeader data.HeaderHandler, rootHash []byte) error { + txPoolOnExecutedBlockCalled = true + return nil + } + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) arguments.AccountsDB[state.UserAccountsState] = accounts arguments.ForkDetector = fd @@ -2130,7 +2291,7 @@ func TestShardProcessor_CommitBlockOkValsShouldWork(t *testing.T) { debuggerMethodWasCalled := false debugger := &testscommon.ProcessDebuggerStub{ SetLastCommittedBlockRoundCalled: func(round uint64) { - assert.Equal(t, hdr.Round, round) + assert.Equal(t, hdr.GetRound(), round) debuggerMethodWasCalled = true }, } @@ -2143,58 +2304,156 @@ func TestShardProcessor_CommitBlockOkValsShouldWork(t *testing.T) { err = sp.ProcessBlock(hdr, body, haveTime) assert.Nil(t, err) + err = sp.CommitBlock(hdr, body) assert.Nil(t, err) + assert.True(t, forkDetectorAddCalled) assert.Equal(t, hdrHash, blkc.GetCurrentBlockHeaderHash()) assert.True(t, debuggerMethodWasCalled) assert.True(t, resetCountersForManagedBlockSignerCalled) - // this should sleep as there is an async call to display current hdr and block in CommitBlock - time.Sleep(time.Second) + assert.True(t, txPoolOnExecutedBlockCalled) +} + +func TestShardProcessor_CommitBlockFailsWhenOnExecutedBlockFails(t *testing.T) { + t.Parallel() + + rootHash := []byte("root hash") + headerHash := []byte("header hash") + + prevHeader := &block.Header{ + Nonce: 0, + Round: 0, + PrevHash: headerHash, + RootHash: rootHash, + } + + mb := block.MiniBlock{ + TxHashes: [][]byte{[]byte("abba")}, + } + + header := &block.HeaderV2{ + Header: &block.Header{ + Nonce: 1, + Round: 1, + PrevHash: headerHash, + RootHash: rootHash, + AccumulatedFees: big.NewInt(0), + DeveloperFees: big.NewInt(0), + MiniBlockHeaders: []block.MiniBlockHeader{ + { + TxCount: uint32(len(mb.TxHashes)), + Hash: headerHash, + }, + }, + }, + } + + body := &block.Body{MiniBlocks: []*block.MiniBlock{&mb}} + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + coreComponents.Hash = &mock.HasherStub{ + ComputeCalled: func(s string) []byte { + return headerHash + }, + } + + testBlockchain := createTestBlockchain() + testBlockchain.GetCurrentBlockHeaderCalled = func() data.HeaderHandler { + return prevHeader + } + testBlockchain.GetCurrentBlockHeaderHashCalled = func() []byte { + return headerHash + } + testBlockchain.GetGenesisHeaderCalled = func() data.HeaderHandler { + return &block.Header{ + RootHash: []byte("genesisHeaderHash"), + } + } + + dataComponents.BlockChain = testBlockchain + dataComponents.Storage = initStore() + + txPoolOnExecutedBlockCalled := false + dataComponents.DataPool = initDataPool() + dataComponents.DataPool.Transactions().(*testscommon.ShardedDataCacheNotifierMock).OnExecutedBlockCalled = func(blockHeader data.HeaderHandler, rootHash []byte) error { + if bytes.Equal(rootHash, []byte("genesisHeaderHash")) { + return nil + } + + txPoolOnExecutedBlockCalled = true + return expectedErr + } + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ + CommitCalled: func() (i []byte, e error) { + return rootHash, nil + }, + RootHashCalled: func() ([]byte, error) { + return rootHash, nil + }, + } + arguments.ForkDetector = &mock.ForkDetectorMock{} + arguments.BlockTracker = mock.NewBlockTrackerMock(mock.NewOneShardCoordinatorMock(), createGenesisBlocks(mock.NewOneShardCoordinatorMock())) + + sp, err := blproc.NewShardProcessor(arguments) + assert.Nil(t, err) + + err = sp.ProcessBlock(header, body, haveTime) + assert.Nil(t, err) + + err = sp.CommitBlock(header, body) + assert.Equal(t, expectedErr, err) + assert.True(t, txPoolOnExecutedBlockCalled) } func TestShardProcessor_CommitBlockCallsIndexerMethods(t *testing.T) { t.Parallel() - tdp := initDataPool([]byte("tx_hash1")) + tdp := initDataPool() txHash := []byte("tx_hash1") rootHash := []byte("root hash") hdrHash := []byte("header hash") randSeed := []byte("rand seed") - prevHdr := &block.Header{ - Nonce: 0, - Round: 0, - PubKeysBitmap: rootHash, - PrevHash: hdrHash, - Signature: rootHash, - RootHash: rootHash, - RandSeed: randSeed, + prevHdr := &block.HeaderV2{ + Header: &block.Header{ + Nonce: 0, + Round: 0, + PubKeysBitmap: rootHash, + PrevHash: hdrHash, + Signature: rootHash, + RootHash: rootHash, + RandSeed: randSeed, + }, } - hdr := &block.Header{ - Nonce: 1, - Round: 1, - PubKeysBitmap: rootHash, - PrevHash: hdrHash, - Signature: rootHash, - RootHash: rootHash, - PrevRandSeed: randSeed, - AccumulatedFees: big.NewInt(0), - DeveloperFees: big.NewInt(0), + hdr := &block.HeaderV2{ + Header: &block.Header{ + Nonce: 1, + Round: 1, + PubKeysBitmap: rootHash, + PrevHash: hdrHash, + Signature: rootHash, + RootHash: rootHash, + PrevRandSeed: randSeed, + AccumulatedFees: big.NewInt(0), + DeveloperFees: big.NewInt(0), + }, } mb := block.MiniBlock{ TxHashes: [][]byte{txHash}, } body := &block.Body{MiniBlocks: []*block.MiniBlock{&mb}} - mbHdr := block.MiniBlockHeader{ + mbHdr := &block.MiniBlockHeader{ TxCount: uint32(len(mb.TxHashes)), Hash: hdrHash, } - mbHdrs := make([]block.MiniBlockHeader, 0) + mbHdrs := make([]data.MiniBlockHeaderHandler, 0) mbHdrs = append(mbHdrs, mbHdr) - hdr.MiniBlockHeaders = mbHdrs + _ = hdr.SetMiniBlockHeaderHandlers(mbHdrs) accounts := &stateMock.AccountsStub{ CommitCalled: func() (i []byte, e error) { @@ -2281,7 +2540,7 @@ func TestShardProcessor_CommitBlockCallsIndexerMethods(t *testing.T) { func TestShardProcessor_CreateTxBlockBodyWithDirtyAccStateShouldReturnEmptyBody(t *testing.T) { t.Parallel() - tdp := initDataPool([]byte("tx_hash1")) + tdp := initDataPool() journalLen := func() int { return 3 } revToSnapshot := func(snapshot int) error { return nil } @@ -2302,7 +2561,7 @@ func TestShardProcessor_CreateTxBlockBodyWithDirtyAccStateShouldReturnEmptyBody( func TestShardProcessor_CreateTxBlockBodyWithNoTimeShouldReturnEmptyBody(t *testing.T) { t.Parallel() - tdp := initDataPool([]byte("tx_hash1")) + tdp := initDataPool() journalLen := func() int { return 0 } rootHashfunc := func() ([]byte, error) { return []byte("roothash"), nil @@ -2328,7 +2587,7 @@ func TestShardProcessor_CreateTxBlockBodyWithNoTimeShouldReturnEmptyBody(t *test func TestShardProcessor_CreateTxBlockBodyOK(t *testing.T) { t.Parallel() - tdp := initDataPool([]byte("tx_hash1")) + tdp := initDataPool() journalLen := func() int { return 0 } rootHashfunc := func() ([]byte, error) { return []byte("roothash"), nil @@ -2354,7 +2613,7 @@ func TestShardProcessor_CreateTxBlockBodyOK(t *testing.T) { func TestNode_ComputeNewNoncePrevHashShouldWork(t *testing.T) { t.Parallel() - tdp := initDataPool([]byte("tx_hash1")) + tdp := initDataPool() marshalizer := &mock.MarshalizerStub{} hasher := &mock.HasherStub{} coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() @@ -2440,7 +2699,7 @@ func createTestHdrTxBlockBody() (*block.Header, *block.Body) { func TestShardProcessor_DisplayLogInfo(t *testing.T) { t.Parallel() - tdp := initDataPool([]byte("tx_hash1")) + tdp := initDataPool() hasher := hashingMocks.HasherMock{} hdr, txBlock := createTestHdrTxBlockBody() shardCoordinator := mock.NewMultiShardsCoordinatorMock(3) @@ -2556,14 +2815,194 @@ func TestShardProcessor_CommitBlockShouldRevertCurrentBlockWhenErr(t *testing.T) RevertToSnapshotCalled: revToSnapshot, } bp, _ := blproc.NewShardProcessor(arguments) - err := bp.CommitBlock(nil, nil) + err := bp.CommitBlock(&block.HeaderV2{ + Header: &block.Header{ + Nonce: 100, + }, + }, &block.Body{}) assert.NotNil(t, err) assert.Equal(t, 0, journalEntries) } +func TestShardProcessor_MarshalizedDataToBroadcast_WithSupernova(t *testing.T) { + t.Parallel() + + marshalizer := &mock.MarshalizerMock{ + Fail: false, + } + + mb1 := &block.MiniBlock{ + TxHashes: [][]byte{[]byte("txHash1")}, + ReceiverShardID: 1, + SenderShardID: 0, + } + mb2 := &block.MiniBlock{ + TxHashes: [][]byte{[]byte("txHash2")}, + ReceiverShardID: 1, + SenderShardID: 0, + } + mb3 := &block.MiniBlock{ + TxHashes: [][]byte{[]byte("txHash3")}, + ReceiverShardID: 1, + SenderShardID: 0, + } + mb4 := &block.MiniBlock{ + TxHashes: [][]byte{[]byte("txHash4")}, + ReceiverShardID: 1, + SenderShardID: 0, + } + + mbsMap := make(map[string]*block.MiniBlock) + mbsMap["mbHash1"] = mb1 + mbsMap["mbHash2"] = mb2 + mbsMap["mbHash3"] = mb3 + mbsMap["mbHash4"] = mb4 + + miniBlocksSlice := []*block.MiniBlock{ + mb1, mb2, mb3, mb4, + } + + executionResults := []*block.ExecutionResult{ + &block.ExecutionResult{ + MiniBlockHeaders: []block.MiniBlockHeader{ + block.MiniBlockHeader{ + Hash: []byte("mbHash1"), + ReceiverShardID: 1, + SenderShardID: 0, + }, + block.MiniBlockHeader{ + Hash: []byte("mbHash2"), + ReceiverShardID: 1, + SenderShardID: 0, + }, + }, + }, + &block.ExecutionResult{ + MiniBlockHeaders: []block.MiniBlockHeader{ + block.MiniBlockHeader{ + Hash: []byte("mbHash3"), + ReceiverShardID: 1, + SenderShardID: 0, + }, + block.MiniBlockHeader{ + Hash: []byte("mbHash4"), + ReceiverShardID: 1, + SenderShardID: 0, + }, + }, + }, + } + + executedMBs := &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + mb, ok := mbsMap[string(key)] + if ok { + mbBytes, _ := marshalizer.Marshal(mb) + return mbBytes, ok + } + + return nil, false + }, + } + dataPool := initDataPool() + dataPool.ExecutedMiniBlocksCalled = func() storage.Cacher { + return executedMBs + } + + // mini blocks related to current processing block + txHash00 := []byte("txHash00") + mb00 := block.MiniBlock{ + ReceiverShardID: 0, + SenderShardID: 0, + TxHashes: [][]byte{txHash00}, + } + txHash01 := []byte("txHash01") + mb01 := block.MiniBlock{ + ReceiverShardID: 1, + SenderShardID: 0, + TxHashes: [][]byte{txHash01}, + } + body := &block.Body{} + body.MiniBlocks = append(body.MiniBlocks, &mb00) + body.MiniBlocks = append(body.MiniBlocks, &mb01) + + argsPreProcessorsContainerFactory := shard.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), + Store: initStore(), + Marshalizer: marshalizer, + Hasher: &hashingMocks.HasherMock{}, + DataPool: dataPool, + PubkeyConverter: createMockPubkeyConverter(), + Accounts: initAccountsMock(), + AccountsProposal: initAccountsMock(), + RequestHandler: &testscommon.RequestHandlerStub{}, + TxProcessor: &testscommon.TxProcessorMock{}, + ScProcessor: &testscommon.SCProcessorMock{}, + ScResultProcessor: &testscommon.SmartContractResultsProcessorMock{}, + RewardsTxProcessor: &testscommon.RewardTxProcessorMock{}, + EconomicsFee: &economicsmocks.EconomicsHandlerMock{}, + GasHandler: &testscommon.GasHandlerStub{}, + BlockTracker: &mock.BlockTrackerMock{}, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + TxTypeHandler: &testscommon.TxTypeHandlerMock{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: createMockTxCacheSelectionConfig(), + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + } + factory, err := shard.NewPreProcessorsContainerFactory(argsPreProcessorsContainerFactory) + require.NoError(t, err) + + container, err := factory.Create() + require.NoError(t, err) + containerPreProcProposal, err := factory.Create() + require.NoError(t, err) + + argsTransactionCoordinator := createMockTransactionCoordinatorArguments(initAccountsMock(), dataPool, container, containerPreProcProposal) + tc, err := coordinator.NewTransactionCoordinator(argsTransactionCoordinator) + assert.Nil(t, err) + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataComponents.DataPool = dataPool + coreComponents.IntMarsh = marshalizer + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.TxCoordinator = tc + sp, _ := blproc.NewShardProcessor(arguments) + + tc.AddTxsFromMiniBlocks(miniBlocksSlice) + + marshalledBlockBody, marshalledTxs, err := sp.MarshalizedDataToBroadcast( + []byte("hash"), + &block.HeaderV3{ + ExecutionResults: executionResults, + }, body) + require.Nil(t, err) + require.NotNil(t, marshalledBlockBody) + require.NotNil(t, marshalledTxs) + + fmt.Println(marshalledBlockBody) + fmt.Println(marshalledTxs) + + blockBody := &block.Body{} + err = marshalizer.Unmarshal(blockBody, marshalledBlockBody[1]) + assert.Nil(t, err) + assert.Equal(t, len(blockBody.MiniBlocks), 4) + assert.Equal(t, mb1, blockBody.MiniBlocks[0]) + assert.Equal(t, mb2, blockBody.MiniBlocks[1]) + assert.Equal(t, mb3, blockBody.MiniBlocks[2]) + assert.Equal(t, mb4, blockBody.MiniBlocks[3]) +} + func TestShardProcessor_MarshalizedDataToBroadcastShouldWork(t *testing.T) { t.Parallel() - tdp := initDataPool([]byte("tx_hash1")) + tdp := initDataPool() txHash0 := []byte("txHash0") mb0 := block.MiniBlock{ ReceiverShardID: 0, @@ -2585,33 +3024,46 @@ func TestShardProcessor_MarshalizedDataToBroadcastShouldWork(t *testing.T) { Fail: false, } - factory, _ := shard.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - initStore(), - marshalizer, - &hashingMocks.HasherMock{}, - tdp, - createMockPubkeyConverter(), - initAccountsMock(), - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) - container, _ := factory.Create() - - argsTransactionCoordinator := createMockTransactionCoordinatorArguments(initAccountsMock(), tdp, container) + argsPreProcessorsContainerFactory := shard.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), + Store: initStore(), + Marshalizer: marshalizer, + Hasher: &hashingMocks.HasherMock{}, + DataPool: tdp, + PubkeyConverter: createMockPubkeyConverter(), + Accounts: initAccountsMock(), + AccountsProposal: initAccountsMock(), + RequestHandler: &testscommon.RequestHandlerStub{}, + TxProcessor: &testscommon.TxProcessorMock{}, + ScProcessor: &testscommon.SCProcessorMock{}, + ScResultProcessor: &testscommon.SmartContractResultsProcessorMock{}, + RewardsTxProcessor: &testscommon.RewardTxProcessorMock{}, + EconomicsFee: &economicsmocks.EconomicsHandlerMock{}, + GasHandler: &testscommon.GasHandlerStub{}, + BlockTracker: &mock.BlockTrackerMock{}, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + TxTypeHandler: &testscommon.TxTypeHandlerMock{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: createMockTxCacheSelectionConfig(), + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + } + + factory, err := shard.NewPreProcessorsContainerFactory(argsPreProcessorsContainerFactory) + require.NoError(t, err) + + container, err := factory.Create() + require.NoError(t, err) + containerPreProcProposal, err := factory.Create() + require.NoError(t, err) + + argsTransactionCoordinator := createMockTransactionCoordinatorArguments(initAccountsMock(), tdp, container, containerPreProcProposal) tc, err := coordinator.NewTransactionCoordinator(argsTransactionCoordinator) assert.Nil(t, err) @@ -2621,7 +3073,7 @@ func TestShardProcessor_MarshalizedDataToBroadcastShouldWork(t *testing.T) { arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) arguments.TxCoordinator = tc sp, _ := blproc.NewShardProcessor(arguments) - msh, mstx, err := sp.MarshalizedDataToBroadcast(&block.Header{}, body) + msh, mstx, err := sp.MarshalizedDataToBroadcast([]byte("hash"), &block.Header{}, body) assert.Nil(t, err) assert.NotNil(t, msh) assert.NotNil(t, mstx) @@ -2638,7 +3090,7 @@ func TestShardProcessor_MarshalizedDataToBroadcastShouldWork(t *testing.T) { func TestShardProcessor_MarshalizedDataWrongType(t *testing.T) { t.Parallel() - tdp := initDataPool([]byte("tx_hash1")) + tdp := initDataPool() marshalizer := &mock.MarshalizerMock{ Fail: false, } @@ -2650,7 +3102,7 @@ func TestShardProcessor_MarshalizedDataWrongType(t *testing.T) { sp, _ := blproc.NewShardProcessor(arguments) wr := &wrongBody{} - msh, mstx, err := sp.MarshalizedDataToBroadcast(&block.Header{}, wr) + msh, mstx, err := sp.MarshalizedDataToBroadcast([]byte("hash"), &block.Header{}, wr) assert.Equal(t, process.ErrWrongTypeAssertion, err) assert.Nil(t, msh) assert.Nil(t, mstx) @@ -2658,7 +3110,7 @@ func TestShardProcessor_MarshalizedDataWrongType(t *testing.T) { func TestShardProcessor_MarshalizedDataNilInput(t *testing.T) { t.Parallel() - tdp := initDataPool([]byte("tx_hash1")) + tdp := initDataPool() marshalizer := &mock.MarshalizerMock{ Fail: false, } @@ -2669,7 +3121,7 @@ func TestShardProcessor_MarshalizedDataNilInput(t *testing.T) { arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) sp, _ := blproc.NewShardProcessor(arguments) - msh, mstx, err := sp.MarshalizedDataToBroadcast(nil, nil) + msh, mstx, err := sp.MarshalizedDataToBroadcast(nil, nil, nil) assert.Equal(t, process.ErrNilMiniBlocks, err) assert.Nil(t, msh) assert.Nil(t, mstx) @@ -2678,7 +3130,7 @@ func TestShardProcessor_MarshalizedDataNilInput(t *testing.T) { func TestShardProcessor_MarshalizedDataMarshalWithoutSuccess(t *testing.T) { t.Parallel() wasCalled := false - tdp := initDataPool([]byte("tx_hash1")) + tdp := initDataPool() txHash0 := []byte("txHash0") mb0 := block.MiniBlock{ ReceiverShardID: 1, @@ -2694,33 +3146,45 @@ func TestShardProcessor_MarshalizedDataMarshalWithoutSuccess(t *testing.T) { }, } - factory, _ := shard.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - initStore(), - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - tdp, - createMockPubkeyConverter(), - initAccountsMock(), - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) - container, _ := factory.Create() - - argsTransactionCoordinator := createMockTransactionCoordinatorArguments(initAccountsMock(), tdp, container) + args := shard.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), + Store: initStore(), + Marshalizer: &mock.MarshalizerMock{}, + Hasher: &hashingMocks.HasherMock{}, + DataPool: tdp, + PubkeyConverter: createMockPubkeyConverter(), + Accounts: initAccountsMock(), + AccountsProposal: initAccountsMock(), + RequestHandler: &testscommon.RequestHandlerStub{}, + TxProcessor: &testscommon.TxProcessorMock{}, + ScProcessor: &testscommon.SCProcessorMock{}, + ScResultProcessor: &testscommon.SmartContractResultsProcessorMock{}, + RewardsTxProcessor: &testscommon.RewardTxProcessorMock{}, + EconomicsFee: &economicsmocks.EconomicsHandlerMock{}, + GasHandler: &testscommon.GasHandlerStub{}, + BlockTracker: &mock.BlockTrackerMock{}, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + TxTypeHandler: &testscommon.TxTypeHandlerMock{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: createMockTxCacheSelectionConfig(), + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + } + factory, err := shard.NewPreProcessorsContainerFactory(args) + require.NoError(t, err) + + container, err := factory.Create() + require.NoError(t, err) + containerPreProcProposal, err := factory.Create() + require.NoError(t, err) + + argsTransactionCoordinator := createMockTransactionCoordinatorArguments(initAccountsMock(), tdp, container, containerPreProcProposal) tc, err := coordinator.NewTransactionCoordinator(argsTransactionCoordinator) assert.Nil(t, err) @@ -2732,167 +3196,23 @@ func TestShardProcessor_MarshalizedDataMarshalWithoutSuccess(t *testing.T) { sp, _ := blproc.NewShardProcessor(arguments) - msh, mstx, err := sp.MarshalizedDataToBroadcast(&block.Header{}, body) + msh, mstx, err := sp.MarshalizedDataToBroadcast([]byte("hash"), &block.Header{}, body) assert.Nil(t, err) assert.True(t, wasCalled) assert.Equal(t, 0, len(msh)) assert.Equal(t, 0, len(mstx)) } -// ------- receivedMetaBlock - -func TestShardProcessor_ReceivedMetaBlockShouldRequestMissingMiniBlocks(t *testing.T) { +// --------- createAndProcessCrossMiniBlocksDstMe +func TestShardProcessor_CreateAndProcessCrossMiniBlocksDstMe(t *testing.T) { t.Parallel() - hasher := &hashingMocks.HasherMock{} - marshalizer := &mock.MarshalizerMock{} - datapool := dataRetrieverMock.NewPoolsHolderMock() - - // we will have a metablock that will return 3 miniblock hashes - // 1 miniblock hash will be in cache - // 2 will be requested on network + tdp := dataRetrieverMock.NewPoolsHolderMock() + txHash := []byte("tx_hash1") + tdp.Transactions().AddData(txHash, &transaction.Transaction{}, 0, process.ShardCacherIdentifier(1, 0)) - miniBlockHash1 := []byte("miniblock hash 1 found in cache") - miniBlockHash2 := []byte("miniblock hash 2") - miniBlockHash3 := []byte("miniblock hash 3") - - metaBlock := &block.MetaBlock{ - Nonce: 1, - Round: 1, - ShardInfo: []block.ShardData{ - { - ShardID: 1, - ShardMiniBlockHeaders: []block.MiniBlockHeader{ - {Hash: miniBlockHash1, SenderShardID: 1, ReceiverShardID: 0}, - {Hash: miniBlockHash2, SenderShardID: 1, ReceiverShardID: 0}, - {Hash: miniBlockHash3, SenderShardID: 1, ReceiverShardID: 0}, - }}, - }} - - // put this metaBlock inside datapool - metaBlockHash := []byte("metablock hash") - datapool.Headers().AddHeader(metaBlockHash, metaBlock) - // put the existing miniblock inside datapool - datapool.MiniBlocks().Put(miniBlockHash1, &block.MiniBlock{}, 0) - - miniBlockHash1Requested := int32(0) - miniBlockHash2Requested := int32(0) - miniBlockHash3Requested := int32(0) - - requestHandler := &testscommon.RequestHandlerStub{ - RequestMiniBlocksHandlerCalled: func(destShardID uint32, miniblocksHashes [][]byte) { - for _, mbHash := range miniblocksHashes { - if bytes.Equal(miniBlockHash1, mbHash) { - atomic.AddInt32(&miniBlockHash1Requested, 1) - } - if bytes.Equal(miniBlockHash2, mbHash) { - atomic.AddInt32(&miniBlockHash2Requested, 1) - } - if bytes.Equal(miniBlockHash3, mbHash) { - atomic.AddInt32(&miniBlockHash3Requested, 1) - } - } - }, - } - - argsTransactionCoordinator := createMockTransactionCoordinatorArguments(initAccountsMock(), datapool, &mock.PreProcessorContainerMock{}) - argsTransactionCoordinator.RequestHandler = requestHandler - tc, _ := coordinator.NewTransactionCoordinator(argsTransactionCoordinator) - - coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() - dataComponents.DataPool = datapool - coreComponents.Hash = hasher - coreComponents.IntMarsh = marshalizer - arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - arguments.RequestHandler = requestHandler - arguments.TxCoordinator = tc - - bp, _ := blproc.NewShardProcessor(arguments) - bp.ReceivedMetaBlock(metaBlock, metaBlockHash) - - // we have to wait to be sure txHash1Requested is not incremented by a late call - time.Sleep(common.ExtraDelayForRequestBlockInfo + time.Second) - - assert.Equal(t, int32(0), atomic.LoadInt32(&miniBlockHash1Requested)) - assert.Equal(t, int32(1), atomic.LoadInt32(&miniBlockHash2Requested)) - assert.Equal(t, int32(1), atomic.LoadInt32(&miniBlockHash2Requested)) -} - -// --------- receivedMetaBlockNoMissingMiniBlocks -func TestShardProcessor_ReceivedMetaBlockNoMissingMiniBlocksShouldPass(t *testing.T) { - t.Parallel() - - hasher := &hashingMocks.HasherMock{} - marshalizer := &mock.MarshalizerMock{} - datapool := dataRetrieverMock.NewPoolsHolderMock() - - // we will have a metablock that will return 3 miniblock hashes - // 1 miniblock hash will be in cache - // 2 will be requested on network - - miniBlockHash1 := []byte("miniblock hash 1 found in cache") - - metaBlock := &block.MetaBlock{ - Nonce: 1, - Round: 1, - ShardInfo: []block.ShardData{ - { - ShardID: 1, - ShardMiniBlockHeaders: []block.MiniBlockHeader{ - { - Hash: miniBlockHash1, - SenderShardID: 1, - ReceiverShardID: 0, - }, - }, - }, - }} - - // put this metaBlock inside datapool - metaBlockHash := []byte("metablock hash") - datapool.Headers().AddHeader(metaBlockHash, metaBlock) - // put the existing miniblock inside datapool - datapool.MiniBlocks().Put(miniBlockHash1, &block.MiniBlock{}, 0) - - noOfMissingMiniBlocks := int32(0) - - requestHandler := &testscommon.RequestHandlerStub{ - RequestMiniBlockHandlerCalled: func(destShardID uint32, miniblockHash []byte) { - atomic.AddInt32(&noOfMissingMiniBlocks, 1) - }, - } - - argsTransactionCoordinator := createMockTransactionCoordinatorArguments(initAccountsMock(), datapool, &mock.PreProcessorContainerMock{}) - argsTransactionCoordinator.RequestHandler = requestHandler - tc, _ := coordinator.NewTransactionCoordinator(argsTransactionCoordinator) - - coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() - dataComponents.DataPool = datapool - coreComponents.Hash = hasher - coreComponents.IntMarsh = marshalizer - arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) - arguments.RequestHandler = requestHandler - arguments.TxCoordinator = tc - - sp, _ := blproc.NewShardProcessor(arguments) - sp.ReceivedMetaBlock(metaBlock, metaBlockHash) - - // we have to wait to be sure txHash1Requested is not incremented by a late call - time.Sleep(common.ExtraDelayForRequestBlockInfo + time.Second) - - assert.Equal(t, int32(0), atomic.LoadInt32(&noOfMissingMiniBlocks)) -} - -// --------- createAndProcessCrossMiniBlocksDstMe -func TestShardProcessor_CreateAndProcessCrossMiniBlocksDstMe(t *testing.T) { - t.Parallel() - - tdp := dataRetrieverMock.NewPoolsHolderMock() - txHash := []byte("tx_hash1") - tdp.Transactions().AddData(txHash, &transaction.Transaction{}, 0, process.ShardCacherIdentifier(1, 0)) - - hasher := &mock.HasherStub{} - marshalizer := &mock.MarshalizerMock{} + hasher := &mock.HasherStub{} + marshalizer := &mock.MarshalizerMock{} meta := &block.MetaBlock{ Nonce: 1, @@ -3058,6 +3378,9 @@ func TestShardProcessor_CreateMiniBlocksShouldWorkWithIntraShardTxs(t *testing.T } shardCoordinator := mock.NewMultiShardsCoordinatorMock(3) accntAdapter := &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return []byte("rootHash1"), nil + }, RevertToSnapshotCalled: func(snapshot int) error { assert.Fail(t, "revert should have not been called") return nil @@ -3071,23 +3394,28 @@ func TestShardProcessor_CreateMiniBlocksShouldWorkWithIntraShardTxs(t *testing.T Balance: big.NewInt(1000000000000000000), }, nil }, + RecreateTrieIfNeededCalled: func(options common.RootHashHolder) error { + return nil + }, } totalGasProvided := uint64(0) - factory, _ := shard.NewPreProcessorsContainerFactory( - shardCoordinator, - initStore(), - marshalizer, - hasher, - datapool, - createMockPubkeyConverter(), - accntAdapter, - &testscommon.RequestHandlerStub{}, - txProcessorMock, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{ + + args := shard.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: shardCoordinator, + Store: initStore(), + Marshalizer: marshalizer, + Hasher: hasher, + DataPool: datapool, + PubkeyConverter: createMockPubkeyConverter(), + Accounts: accntAdapter, + AccountsProposal: accntAdapter, + RequestHandler: &testscommon.RequestHandlerStub{}, + TxProcessor: txProcessorMock, + ScProcessor: &testscommon.SCProcessorMock{}, + ScResultProcessor: &testscommon.SmartContractResultsProcessorMock{}, + RewardsTxProcessor: &testscommon.RewardTxProcessorMock{}, + EconomicsFee: &economicsmocks.EconomicsHandlerMock{ ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { return 0 }, @@ -3095,7 +3423,7 @@ func TestShardProcessor_CreateMiniBlocksShouldWorkWithIntraShardTxs(t *testing.T return MaxGasLimitPerBlock }, }, - &testscommon.GasHandlerStub{ + GasHandler: &testscommon.GasHandlerStub{ SetGasProvidedCalled: func(gasProvided uint64, hash []byte) { totalGasProvided += gasProvided }, @@ -3110,18 +3438,29 @@ func TestShardProcessor_CreateMiniBlocksShouldWorkWithIntraShardTxs(t *testing.T return 0 }, }, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) - container, _ := factory.Create() - - argsTransactionCoordinator := createMockTransactionCoordinatorArguments(accntAdapter, datapool, container) + BlockTracker: &mock.BlockTrackerMock{}, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + TxTypeHandler: &testscommon.TxTypeHandlerMock{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: createMockTxCacheSelectionConfig(), + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + } + factory, err := shard.NewPreProcessorsContainerFactory(args) + require.NoError(t, err) + + container, err := factory.Create() + require.NoError(t, err) + containerPreProcProposal, err := factory.Create() + require.NoError(t, err) + + argsTransactionCoordinator := createMockTransactionCoordinatorArguments(accntAdapter, datapool, container, containerPreProcProposal) tc, err := coordinator.NewTransactionCoordinator(argsTransactionCoordinator) require.Nil(t, err) @@ -3131,10 +3470,14 @@ func TestShardProcessor_CreateMiniBlocksShouldWorkWithIntraShardTxs(t *testing.T coreComponents.IntMarsh = marshalizer arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) arguments.AccountsDB[state.UserAccountsState] = accntAdapter + arguments.AccountsProposal = accntAdapter arguments.TxCoordinator = tc bp, err := blproc.NewShardProcessor(arguments) require.Nil(t, err) + err = bp.OnExecutedBlock(&block.Header{}, []byte("rootHash1")) + require.Nil(t, err) + blockBody, _, err := bp.CreateMiniBlocks(func() bool { return true }) assert.Nil(t, err) @@ -3203,9 +3546,9 @@ func TestShardProcessor_GetProcessedMetaBlockFromPoolShouldWork(t *testing.T) { } bp, _ := blproc.NewShardProcessor(arguments) - bp.SetHdrForCurrentBlock(metaBlockHash1, metaBlock1, true) - bp.SetHdrForCurrentBlock(metaBlockHash2, metaBlock2, true) - bp.SetHdrForCurrentBlock(metaBlockHash3, metaBlock3, true) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(metaBlockHash1), metaBlock1) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(metaBlockHash2), metaBlock2) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(metaBlockHash3), metaBlock3) // create mini block headers with first 3 miniblocks from miniblocks var mbHeaders := []block.MiniBlockHeader{ @@ -3229,7 +3572,7 @@ func TestShardProcessor_GetProcessedMetaBlockFromPoolShouldWork(t *testing.T) { func TestBlockProcessor_RestoreBlockIntoPoolsShouldErrNilBlockHeader(t *testing.T) { t.Parallel() - tdp := initDataPool([]byte("tx_hash1")) + tdp := initDataPool() coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() dataComponents.DataPool = tdp @@ -3242,7 +3585,7 @@ func TestBlockProcessor_RestoreBlockIntoPoolsShouldErrNilBlockHeader(t *testing. func TestBlockProcessor_RestoreBlockIntoPoolsShouldWorkNilTxBlockBody(t *testing.T) { t.Parallel() - tdp := initDataPool([]byte("tx_hash1")) + tdp := initDataPool() coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() dataComponents.DataPool = tdp @@ -3277,33 +3620,45 @@ func TestShardProcessor_RestoreBlockIntoPoolsShouldWork(t *testing.T) { }, } - factory, _ := shard.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - store, - marshalizerMock, - hasherMock, - datapool, - createMockPubkeyConverter(), - initAccountsMock(), - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) - container, _ := factory.Create() - - argsTransactionCoordinator := createMockTransactionCoordinatorArguments(initAccountsMock(), datapool, container) + args := shard.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), + Store: store, + Marshalizer: marshalizerMock, + Hasher: hasherMock, + DataPool: datapool, + PubkeyConverter: createMockPubkeyConverter(), + Accounts: initAccountsMock(), + AccountsProposal: initAccountsMock(), + RequestHandler: &testscommon.RequestHandlerStub{}, + TxProcessor: &testscommon.TxProcessorMock{}, + ScProcessor: &testscommon.SCProcessorMock{}, + ScResultProcessor: &testscommon.SmartContractResultsProcessorMock{}, + RewardsTxProcessor: &testscommon.RewardTxProcessorMock{}, + EconomicsFee: &economicsmocks.EconomicsHandlerMock{}, + GasHandler: &testscommon.GasHandlerStub{}, + BlockTracker: &mock.BlockTrackerMock{}, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + TxTypeHandler: &testscommon.TxTypeHandlerMock{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: createMockTxCacheSelectionConfig(), + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + } + factory, err := shard.NewPreProcessorsContainerFactory(args) + require.NoError(t, err) + + container, err := factory.Create() + require.NoError(t, err) + containerPreProcProposal, err := factory.Create() + require.NoError(t, err) + + argsTransactionCoordinator := createMockTransactionCoordinatorArguments(initAccountsMock(), datapool, container, containerPreProcProposal) tc, err := coordinator.NewTransactionCoordinator(argsTransactionCoordinator) assert.Nil(t, err) @@ -3364,7 +3719,7 @@ func TestShardProcessor_RestoreBlockIntoPoolsShouldWork(t *testing.T) { func TestShardProcessor_DecodeBlockBody(t *testing.T) { t.Parallel() - tdp := initDataPool([]byte("tx_hash1")) + tdp := initDataPool() marshalizerMock := &mock.MarshalizerMock{} coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() dataComponents.DataPool = tdp @@ -3387,7 +3742,7 @@ func TestShardProcessor_DecodeBlockBody(t *testing.T) { func TestShardProcessor_DecodeBlockHeader(t *testing.T) { t.Parallel() - tdp := initDataPool([]byte("tx_hash1")) + tdp := initDataPool() marshalizerMock := &mock.MarshalizerMock{} coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() @@ -3421,7 +3776,7 @@ func TestShardProcessor_IsHdrConstructionValid(t *testing.T) { hasher := &hashingMocks.HasherMock{} marshalizer := &mock.MarshalizerMock{} - datapool := initDataPool([]byte("tx_hash1")) + datapool := initDataPool() shardNr := uint32(5) coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() @@ -3571,13 +3926,13 @@ func TestShardProcessor_RemoveAndSaveLastNotarizedMetaHdrNoDstMB(t *testing.T) { blockHeader := &block.Header{} // test header not in pool and defer called - processedMetaHdrs, err := sp.GetOrderedProcessedMetaBlocksFromHeader(blockHeader) + processedMetaHdrs, _, err := sp.GetOrderedProcessedMetaBlocksFromHeader(blockHeader) assert.Nil(t, err) err = sp.SaveLastNotarizedHeader(core.MetachainShardId, processedMetaHdrs) assert.Nil(t, err) - err = sp.UpdateCrossShardInfo(processedMetaHdrs) + err = sp.UpdateCrossShardInfo(processedMetaHdrs, make([]*blproc.HashAndHdr, 0)) assert.Nil(t, err) assert.Equal(t, uint32(0), atomic.LoadUint32(&putCalledNr)) @@ -3586,19 +3941,19 @@ func TestShardProcessor_RemoveAndSaveLastNotarizedMetaHdrNoDstMB(t *testing.T) { // wrong header type in pool and defer called datapool.Headers().AddHeader(currHash, shardHdr) - sp.SetHdrForCurrentBlock(currHash, shardHdr, true) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(currHash), shardHdr) hashes := make([][]byte, 0) hashes = append(hashes, currHash) blockHeader = &block.Header{MetaBlockHashes: hashes, MiniBlockHeaders: mbHeaders} - processedMetaHdrs, err = sp.GetOrderedProcessedMetaBlocksFromHeader(blockHeader) + processedMetaHdrs, _, err = sp.GetOrderedProcessedMetaBlocksFromHeader(blockHeader) assert.Equal(t, process.ErrWrongTypeAssertion, err) err = sp.SaveLastNotarizedHeader(core.MetachainShardId, processedMetaHdrs) assert.Nil(t, err) - err = sp.UpdateCrossShardInfo(processedMetaHdrs) + err = sp.UpdateCrossShardInfo(processedMetaHdrs, make([]*blproc.HashAndHdr, 0)) assert.Nil(t, err) assert.Equal(t, uint32(0), atomic.LoadUint32(&putCalledNr)) @@ -3609,21 +3964,21 @@ func TestShardProcessor_RemoveAndSaveLastNotarizedMetaHdrNoDstMB(t *testing.T) { datapool.Headers().AddHeader(prevHash, prevHdr) _ = sp.CreateBlockStarted() - sp.SetHdrForCurrentBlock(currHash, currHdr, true) - sp.SetHdrForCurrentBlock(prevHash, prevHdr, true) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(currHash), currHdr) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(prevHash), prevHdr) hashes = make([][]byte, 0) hashes = append(hashes, currHash) hashes = append(hashes, prevHash) blockHeader = &block.Header{MetaBlockHashes: hashes, MiniBlockHeaders: mbHeaders} - processedMetaHdrs, err = sp.GetOrderedProcessedMetaBlocksFromHeader(blockHeader) + processedMetaHdrs, partialProcessedMetaBlocks, err := sp.GetOrderedProcessedMetaBlocksFromHeader(blockHeader) assert.Nil(t, err) err = sp.SaveLastNotarizedHeader(core.MetachainShardId, processedMetaHdrs) assert.Nil(t, err) - err = sp.UpdateCrossShardInfo(processedMetaHdrs) + err = sp.UpdateCrossShardInfo(processedMetaHdrs, partialProcessedMetaBlocks) wg.Wait() assert.Nil(t, err) assert.Equal(t, uint32(4), atomic.LoadUint32(&putCalledNr)) @@ -3774,21 +4129,21 @@ func TestShardProcessor_RemoveAndSaveLastNotarizedMetaHdrNotAllMBFinished(t *tes datapool.Headers().AddHeader(currHash, currHdr) datapool.Headers().AddHeader(prevHash, prevHdr) - sp.SetHdrForCurrentBlock(currHash, currHdr, true) - sp.SetHdrForCurrentBlock(prevHash, prevHdr, true) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(currHash), currHdr) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(prevHash), prevHdr) hashes := make([][]byte, 0) hashes = append(hashes, currHash) hashes = append(hashes, prevHash) blockHeader := &block.Header{MetaBlockHashes: hashes, MiniBlockHeaders: mbHeaders} - processedMetaHdrs, err := sp.GetOrderedProcessedMetaBlocksFromHeader(blockHeader) + processedMetaHdrs, partialProcessedMetaBlocks, err := sp.GetOrderedProcessedMetaBlocksFromHeader(blockHeader) assert.Nil(t, err) err = sp.SaveLastNotarizedHeader(core.MetachainShardId, processedMetaHdrs) assert.Nil(t, err) - err = sp.UpdateCrossShardInfo(processedMetaHdrs) + err = sp.UpdateCrossShardInfo(processedMetaHdrs, partialProcessedMetaBlocks) wg.Wait() assert.Nil(t, err) assert.Equal(t, uint32(2), atomic.LoadUint32(&putCalledNr)) @@ -3796,6 +4151,153 @@ func TestShardProcessor_RemoveAndSaveLastNotarizedMetaHdrNotAllMBFinished(t *tes assert.Equal(t, prevHdr, sp.LastNotarizedHdrForShard(core.MetachainShardId)) } +func TestShardProcessor_RemoveAndSaveLastNotarizedMetaHdrV3_NotAllMBFinished(t *testing.T) { + t.Parallel() + + hasher := &hashingMocks.HasherMock{} + marshalizer := &mock.MarshalizerMock{} + datapool := dataRetrieverMock.NewPoolsHolderMock() + forkDetector := &mock.ForkDetectorMock{} + highNonce := uint64(500) + forkDetector.GetHighestFinalBlockNonceCalled = func() uint64 { + return highNonce + } + + putCalledNr := uint32(0) + store := &storageStubs.ChainStorerStub{ + PutCalled: func(unitType dataRetriever.UnitType, key []byte, value []byte) error { + atomic.AddUint32(&putCalledNr, 1) + return nil + }, + } + + shardNr := uint32(5) + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + dataComponents.DataPool = datapool + dataComponents.Storage = store + coreComponents.Hash = hasher + coreComponents.IntMarsh = marshalizer + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + bootstrapComponents.Coordinator = mock.NewMultiShardsCoordinatorMock(shardNr) + arguments.ForkDetector = forkDetector + startHeaders := createGenesisBlocks(bootstrapComponents.ShardCoordinator()) + arguments.BlockTracker = mock.NewBlockTrackerMock(bootstrapComponents.ShardCoordinator(), startHeaders) + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + prevRandSeed := []byte("prevrand") + currRandSeed := []byte("currrand") + notarizedHdrs := sp.NotarizedHdrs() + firstNonce := uint64(44) + + lastHdr := &block.MetaBlockV3{Round: 9, + Nonce: firstNonce, + RandSeed: prevRandSeed} + notarizedHdrs[core.MetachainShardId] = append(notarizedHdrs[core.MetachainShardId], lastHdr) + + txHash := []byte("txhash") + txHashes := make([][]byte, 0) + txHashes = append(txHashes, txHash) + miniblock1 := block.MiniBlock{ + ReceiverShardID: 0, + SenderShardID: 1, + TxHashes: txHashes, + } + miniblock2 := block.MiniBlock{ + ReceiverShardID: 0, + SenderShardID: 2, + TxHashes: txHashes, + } + miniblock3 := block.MiniBlock{ + ReceiverShardID: 0, + SenderShardID: 3, + TxHashes: txHashes, + } + miniblock4 := block.MiniBlock{ + ReceiverShardID: 0, + SenderShardID: 4, + TxHashes: txHashes, + } + mbHeaders := make([]block.MiniBlockHeader, 0) + + hashed, err := core.CalculateHash(marshalizer, hasher, &miniblock1) + require.Nil(t, err) + mbHeaders = append(mbHeaders, block.MiniBlockHeader{Hash: hashed}) + + hashed, err = core.CalculateHash(marshalizer, hasher, &miniblock2) + require.Nil(t, err) + mbHeaders = append(mbHeaders, block.MiniBlockHeader{Hash: hashed}) + + hashed, err = core.CalculateHash(marshalizer, hasher, &miniblock3) + require.Nil(t, err) + mbHeaders = append(mbHeaders, block.MiniBlockHeader{Hash: hashed}) + + miniBlocks := make([]block.MiniBlock, 0) + miniBlocks = append(miniBlocks, miniblock1, miniblock2) + // header shard 0 + metaHdr1Hash, err := sp.ComputeHeaderHash(sp.LastNotarizedHdrForShard(core.MetachainShardId).(*block.MetaBlock)) + require.Nil(t, err) + metaHdr1 := &block.MetaBlockV3{ + Round: 10, + Nonce: 45, + PrevRandSeed: prevRandSeed, + RandSeed: currRandSeed, + PrevHash: metaHdr1Hash, + ShardInfo: createShardData(hasher, marshalizer, miniBlocks)} + + miniBlocks = make([]block.MiniBlock, 0) + miniBlocks = append(miniBlocks, miniblock3, miniblock4) + metaHdr1Hash, err = sp.ComputeHeaderHash(metaHdr1) + require.Nil(t, err) + metaHdr2 := &block.MetaBlockV3{ + Round: 11, + Nonce: 46, + PrevRandSeed: currRandSeed, + RandSeed: []byte("nextrand"), + PrevHash: metaHdr1Hash, + ShardInfo: createShardData(hasher, marshalizer, miniBlocks)} + + metaHdr0 := &block.MetaBlockV3{ + Round: 9, + Nonce: 44, + } + + metaHdr2Hash, err := sp.ComputeHeaderHash(metaHdr2) + require.Nil(t, err) + metaHdr1Hash, err = sp.ComputeHeaderHash(metaHdr1) + require.Nil(t, err) + metaHdr0Hash, err := sp.ComputeHeaderHash(metaHdr0) + require.Nil(t, err) + + // put headers in pool + datapool.Headers().AddHeader(metaHdr2Hash, metaHdr2) + datapool.Headers().AddHeader(metaHdr1Hash, metaHdr1) + datapool.Headers().AddHeader(metaHdr0Hash, metaHdr0) + + hashes := make([][]byte, 0) + hashes = append(hashes, metaHdr2Hash) + hashes = append(hashes, metaHdr1Hash) + hashes = append(hashes, metaHdr0Hash) + blockHeader := &block.HeaderV3{MetaBlockHashes: hashes, MiniBlockHeaders: mbHeaders} + + processedMetaHdrs, partialProcessedMetaBlocks, err := sp.GetOrderedProcessedMetaBlocksFromHeader(blockHeader) + assert.Nil(t, err) + + require.Equal(t, 1, len(partialProcessedMetaBlocks)) + + err = sp.SaveLastNotarizedHeader(core.MetachainShardId, processedMetaHdrs) + assert.Nil(t, err) + + err = sp.UpdateCrossShardInfo(processedMetaHdrs, partialProcessedMetaBlocks) + assert.Nil(t, err) + + // 2 sets of saves into storage: 1 fully processed + 1 not fully processed + assert.Equal(t, uint32(2*2), atomic.LoadUint32(&putCalledNr)) + + assert.Equal(t, metaHdr1, sp.LastNotarizedHdrForShard(core.MetachainShardId)) +} + func TestShardProcessor_RemoveAndSaveLastNotarizedMetaHdrAllMBFinished(t *testing.T) { t.Parallel() @@ -3917,22 +4419,22 @@ func TestShardProcessor_RemoveAndSaveLastNotarizedMetaHdrAllMBFinished(t *testin PrevHash: currHash, Nonce: 47}) - sp.SetHdrForCurrentBlock(currHash, currHdr, true) - sp.SetHdrForCurrentBlock(prevHash, prevHdr, true) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(currHash), currHdr) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(prevHash), prevHdr) hashes := make([][]byte, 0) hashes = append(hashes, currHash) hashes = append(hashes, prevHash) blockHeader := &block.Header{MetaBlockHashes: hashes, MiniBlockHeaders: mbHeaders} - processedMetaHdrs, err := sp.GetOrderedProcessedMetaBlocksFromHeader(blockHeader) + processedMetaHdrs, partialProcessedMetaBlocks, err := sp.GetOrderedProcessedMetaBlocksFromHeader(blockHeader) assert.Nil(t, err) assert.Equal(t, 2, len(processedMetaHdrs)) err = sp.SaveLastNotarizedHeader(core.MetachainShardId, processedMetaHdrs) assert.Nil(t, err) - err = sp.UpdateCrossShardInfo(processedMetaHdrs) + err = sp.UpdateCrossShardInfo(processedMetaHdrs, partialProcessedMetaBlocks) wg.Wait() assert.Nil(t, err) assert.Equal(t, uint32(4), atomic.LoadUint32(&putCalledNr)) @@ -3989,7 +4491,7 @@ func TestShardProcessor_CheckHeaderBodyCorrelationReceiverMissmatch(t *testing.T hdr.MiniBlockHeaders[0].ReceiverShardID = body.MiniBlocks[0].ReceiverShardID + 1 err := sp.CheckHeaderBodyCorrelation(hdr, body) - assert.Equal(t, process.ErrHeaderBodyMismatch, err) + assert.ErrorIs(t, err, process.ErrHeaderBodyMismatch) } func TestShardProcessor_CheckHeaderBodyCorrelationSenderMissmatch(t *testing.T) { @@ -4002,7 +4504,7 @@ func TestShardProcessor_CheckHeaderBodyCorrelationSenderMissmatch(t *testing.T) hdr.MiniBlockHeaders[0].SenderShardID = body.MiniBlocks[0].SenderShardID + 1 err := sp.CheckHeaderBodyCorrelation(hdr, body) - assert.Equal(t, process.ErrHeaderBodyMismatch, err) + assert.ErrorIs(t, err, process.ErrHeaderBodyMismatch) } func TestShardProcessor_CheckHeaderBodyCorrelationTxCountMissmatch(t *testing.T) { @@ -4192,6 +4694,99 @@ func TestShardProcessor_RestoreMetaBlockIntoPoolShouldPass(t *testing.T) { assert.Nil(t, err) } +func TestShardPreprocessor_getAllMiniBlockDstMeFromMetaGetLastCrossNotarizedHeaderErrorShouldError(t *testing.T) { + t.Parallel() + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "blockTracker": &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return nil, nil, process.ErrNilBlockHeader + }, + }, + }) + require.Nil(t, err) + + metablockHashes := [][]byte{ + []byte("hash1"), + []byte("hash2"), + } + header := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 1, + Round: 2, + MiniBlockHeaders: []block.MiniBlockHeader{}, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + MetaBlockHashes: metablockHashes, + } + _, err = sp.GetAllMiniBlockDstMeFromMeta(header) + assert.Error(t, err) + assert.ErrorIs(t, err, process.ErrNilBlockHeader) +} + +func TestShardPreprocessor_getAllMiniBlockDstMeFromMetaMissingMetaHeaderShouldError(t *testing.T) { + t.Parallel() + + headerValidator := &processMocks.HeaderValidatorMock{ + IsHeaderConstructionValidCalled: func(currHdr, prevHdr data.HeaderHandler) error { + return nil + }, + } + poolMock := initDataPool() + poolMock.HeadersCalled = func() dataRetriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + }, + } + } + + metablockHashes := [][]byte{ + []byte("hash1"), + []byte("hash2"), + } + header := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 1, + Round: 2, + MiniBlockHeaders: []block.MiniBlockHeader{}, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + MetaBlockHashes: metablockHashes, + } + + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "headerValidator": headerValidator, + "hasher": &hashingMocks.HasherMock{}, + "proofsPool": &dataRetrieverMock.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + }, + "dataPool": poolMock, + "blockTracker": &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.MetaBlockV3{}, []byte("h"), nil + }, + }, + "hdrsForCurrBlock": &testscommon.HeadersForBlockMock{ + GetHeaderInfoCalled: func(hash string) (headerForBlock.HeaderInfo, bool) { + return nil, false + }, + }, + }) + require.Nil(t, err) + + mblocks, err := sp.GetAllMiniBlockDstMeFromMeta(header) + assert.Error(t, err) + assert.ErrorIs(t, err, process.ErrMissingHeader) + assert.Nil(t, mblocks) +} func TestShardPreprocessor_getAllMiniBlockDstMeFromMetaShouldPass(t *testing.T) { t.Parallel() @@ -4226,10 +4821,8 @@ func TestShardPreprocessor_getAllMiniBlockDstMeFromMetaShouldPass(t *testing.T) shardHdrs = append(shardHdrs, shardHeader) metaBlock := &block.MetaBlock{Nonce: 1, Round: 1, ShardInfo: shardHdrs} - idp := initDataPool([]byte("tx_hash1")) - coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() - dataComponents.DataPool = idp + dataComponents.DataPool = initDataPool() arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) sp, _ := blproc.NewShardProcessor(arguments) @@ -4238,7 +4831,7 @@ func TestShardPreprocessor_getAllMiniBlockDstMeFromMetaShouldPass(t *testing.T) return []byte("cool") } metaHash := hasher.Compute(string(metaBytes)) - sp.SetHdrForCurrentBlock(metaHash, metaBlock, true) + arguments.HeadersForBlock.AddHeaderUsedInBlock(string(metaHash), metaBlock) metablockHashes := make([][]byte, 0) metablockHashes = append(metablockHashes, metaHash) @@ -4607,10 +5200,128 @@ func TestShardProcessor_updateStateStorage(t *testing.T) { assert.True(t, cancelPruneWasCalled) } -func TestShardProcessor_checkEpochCorrectnessCrossChainNilCurrentBlock(t *testing.T) { +func TestShardProcessor_checkEpochCorrectnessCrossChain_gracePeriodError(t *testing.T) { t.Parallel() - chain := &testscommon.ChainHandlerStub{ + genesisNonce := uint64(0) + gracePeriod := &processMocks.GracePeriodErrStub{} + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.Header{ + Nonce: 0, + Epoch: 1, + Round: 10, + } + }, + GetGenesisHeaderCalled: func() data.HeaderHandler { + return &block.Header{ + Nonce: 0, + Epoch: 0, + Round: 10, + } + }, + GetGenesisHeaderHashCalled: func() []byte { + return []byte("genesis-hash") + }, + } + + epochStartTrigger := &mock.EpochStartTriggerStub{ + EpochFinalityAttestingRoundCalled: func() uint64 { + return 5 + }, + EpochStartRoundCalled: func() uint64 { + return 1 + }, + MetaEpochCalled: func() uint32 { + return 2 + }, + EpochCalled: func() uint32 { + return 1 + }, + } + + forkDetector := &mock.ForkDetectorMock{ + GetHighestFinalBlockNonceCalled: func() uint64 { + return genesisNonce + }, + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "genesisNonce": genesisNonce, + "blockChain": blockchain, + "epochStartTrigger": epochStartTrigger, + "forkDetector": forkDetector, + "epochChangeGracePeriodHandler": gracePeriod, + }) + require.Nil(t, err) + + err = sp.CheckEpochCorrectnessCrossChain() + assert.NotNil(t, err) + assert.Equal(t, "epochChangeGracePeriodHandler forced error", err.Error()) + +} + +func TestShardProcessor_checkEpochCorrectnessCrossChain_FinalizedReached(t *testing.T) { + t.Parallel() + + genesisNonce := uint64(0) + gracePeriod, _ := graceperiod.NewEpochChangeGracePeriod([]config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}) + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.Header{ + Nonce: 0, + Epoch: 1, + Round: 10, + } + }, + GetGenesisHeaderCalled: func() data.HeaderHandler { + return &block.Header{ + Nonce: 0, + Epoch: 0, + Round: 10, + } + }, + GetGenesisHeaderHashCalled: func() []byte { + return []byte("genesis-hash") + }, + } + + epochStartTrigger := &mock.EpochStartTriggerStub{ + EpochFinalityAttestingRoundCalled: func() uint64 { + return 5 + }, + EpochStartRoundCalled: func() uint64 { + return 1 + }, + MetaEpochCalled: func() uint32 { + return 2 + }, + EpochCalled: func() uint32 { + return 1 + }, + } + + forkDetector := &mock.ForkDetectorMock{ + GetHighestFinalBlockNonceCalled: func() uint64 { + return genesisNonce + }, + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "genesisNonce": genesisNonce, + "blockChain": blockchain, + "epochStartTrigger": epochStartTrigger, + "forkDetector": forkDetector, + "epochChangeGracePeriodHandler": gracePeriod, + }) + require.Nil(t, err) + + err = sp.CheckEpochCorrectnessCrossChain() + assert.Nil(t, err) +} + +func TestShardProcessor_checkEpochCorrectnessCrossChainNilCurrentBlock(t *testing.T) { + t.Parallel() + + chain := &testscommon.ChainHandlerStub{ GetCurrentBlockHeaderCalled: func() data.HeaderHandler { return nil }, @@ -4950,7 +5661,7 @@ func TestShardProcessor_RequestMetaHeadersIfNeededShouldAddHeaderIntoTrackerPool t.Parallel() var addedNonces []uint64 - poolsHolderStub := initDataPool([]byte("")) + poolsHolderStub := initDataPool() poolsHolderStub.HeadersCalled = func() dataRetriever.HeadersPool { return &mock.HeadersCacherStub{ GetHeaderByNonceAndShardIdCalled: func(hdrNonce uint64, shardId uint32) ([]data.HeaderHandler, [][]byte, error) { @@ -4979,6 +5690,56 @@ func TestShardProcessor_RequestMetaHeadersIfNeededShouldAddHeaderIntoTrackerPool assert.Equal(t, expectedAddedNonces, addedNonces) } +func TestShardProcessor_CheckEpochCorrectnessShouldErrorWhenHeaderEpochBehindCurrentHeader(t *testing.T) { + t.Parallel() + + header := &block.Header{ + Epoch: 1, + } + currentHeader := &block.Header{ + Epoch: 3, + } + blockChain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return currentHeader + }, + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "blockChain": blockChain, + }) + require.Nil(t, err) + + err = sp.CheckEpochCorrectness(header) + assert.Error(t, err) + assert.Equal(t, "epoch does not match proposed header with older epoch 1 than blockchain epoch 3", err.Error()) +} + +func TestShardProcessor_CheckEpochCorrectnessShouldErrorWhenIsStartOfEpochButShouldNotBe(t *testing.T) { + t.Parallel() + + // set EpochStartMetaHash non-nil to emulate IsStartOfEpochBlock == true + header := &block.Header{ + Epoch: 3, + EpochStartMetaHash: []byte("start"), + } + currentHeader := &block.Header{ + Epoch: 3, + } + blockChain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return currentHeader + }, + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "blockChain": blockChain, + }) + require.Nil(t, err) + + err = sp.CheckEpochCorrectness(header) + assert.Error(t, err) + assert.Equal(t, "epoch does not match proposed header with same epoch 3 as blockchain and it is of epoch start", err.Error()) +} + func TestShardProcessor_CheckEpochCorrectnessShouldRemoveAndRequestStartOfEpochMetaBlockWhenEpochDoesNotMatch(t *testing.T) { t.Parallel() @@ -5001,6 +5762,13 @@ func TestShardProcessor_CheckEpochCorrectnessShouldRemoveAndRequestStartOfEpochM GetGenesisHeaderCalled: func() data.HeaderHandler { return &block.Header{} }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash"), + }, + } + }, } epochStartTriggerStub := &mock.EpochStartTriggerStub{ MetaEpochCalled: func() uint32 { @@ -5020,6 +5788,15 @@ func TestShardProcessor_CheckEpochCorrectnessShouldRemoveAndRequestStartOfEpochM ProofsCalled: func() dataRetriever.ProofsPool { return &dataRetrieverMock.ProofsPoolMock{} }, + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + ExecutedMiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, } ch := make(chan struct{}) @@ -5054,6 +5831,169 @@ func TestShardProcessor_CheckEpochCorrectnessShouldRemoveAndRequestStartOfEpochM assert.True(t, errors.Is(err, process.ErrEpochDoesNotMatch)) } +func TestShardProcessor_CheckEpochCorrectnessShouldErrorWhenIsHeaderOfInvalidEpoch(t *testing.T) { + t.Parallel() + + header := &block.Header{ + Epoch: 3, + } + currentHeader := &block.Header{ + Epoch: 3, + } + blockChain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return currentHeader + }, + } + epochStartTriggerStub := &mock.EpochStartTriggerStub{ + MetaEpochCalled: func() uint32 { + return 2 + }, + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "blockChain": blockChain, + "epochStartTrigger": epochStartTriggerStub, + }) + require.Nil(t, err) + + err = sp.CheckEpochCorrectness(header) + assert.Error(t, err) + assert.Equal(t, "epoch does not match proposed header with epoch too high 3 with trigger in epoch 2", err.Error()) +} + +func TestShardProcessor_CheckEpochCorrectnessShouldErrorWhenEpochChangeGracePeriodHandlerErrors(t *testing.T) { + t.Parallel() + + header := &block.Header{ + Epoch: 3, + } + currentHeader := &block.Header{ + Epoch: 3, + } + blockChain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return currentHeader + }, + } + epochStartTriggerStub := &mock.EpochStartTriggerStub{ + MetaEpochCalled: func() uint32 { + return 3 + }, + } + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return false + }, + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "blockChain": blockChain, + "epochStartTrigger": epochStartTriggerStub, + "enableEpochsHandler": enableEpochsHandler, + "epochChangeGracePeriodHandler": &processMocks.GracePeriodErrStub{}, + }) + require.Nil(t, err) + + err = sp.CheckEpochCorrectness(header) + assert.Error(t, err) + assert.Equal(t, "epochChangeGracePeriodHandler forced error could not get grace period for epoch 3", err.Error()) +} + +func TestShardProcessor_CheckEpochCorrectnessShouldErrorWhenIsOldEpochAndShouldBeNew(t *testing.T) { + t.Parallel() + + gracePeriod, _ := graceperiod.NewEpochChangeGracePeriod([]config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}) + + header := &block.Header{ + Epoch: 3, + Round: 7, + } + currentHeader := &block.Header{ + Epoch: 3, + } + blockChain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return currentHeader + }, + } + + epochStartTriggerStub := &mock.EpochStartTriggerStub{ + MetaEpochCalled: func() uint32 { + return 4 + }, + EpochStartRoundCalled: func() uint64 { + return 5 + }, + EpochFinalityAttestingRoundCalled: func() uint64 { + return 5 + }, + IsEpochStartCalled: func() bool { + return true + }, + } + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "blockChain": blockChain, + "epochStartTrigger": epochStartTriggerStub, + "enableEpochsHandler": enableEpochsHandler, + "epochChangeGracePeriodHandler": gracePeriod, + }) + require.Nil(t, err) + + err = sp.CheckEpochCorrectness(header) + assert.Error(t, err) + assert.Equal(t, "epoch does not match proposed header with epoch 3 should be in epoch 4", err.Error()) +} + +func TestShardProcessor_CheckEpochCorrectnessShouldErrorWhenIsNotEpochStartButShouldBe(t *testing.T) { + t.Parallel() + + gracePeriod, _ := graceperiod.NewEpochChangeGracePeriod([]config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}) + + header := &block.Header{ + Epoch: 4, + } + currentHeader := &block.Header{ + Epoch: 3, + } + blockChain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return currentHeader + }, + } + + epochStartTriggerStub := &mock.EpochStartTriggerStub{ + MetaEpochCalled: func() uint32 { + return 4 + }, + EpochStartRoundCalled: func() uint64 { + return 5 + }, + EpochFinalityAttestingRoundCalled: func() uint64 { + return 5 + }, + } + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return false + }, + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "blockChain": blockChain, + "epochStartTrigger": epochStartTriggerStub, + "enableEpochsHandler": enableEpochsHandler, + "epochChangeGracePeriodHandler": gracePeriod, + }) + require.Nil(t, err) + + err = sp.CheckEpochCorrectness(header) + assert.Error(t, err) + assert.Equal(t, "epoch does not match proposed header with new epoch 4 is not of type epoch start", err.Error()) +} + func TestShardProcessor_CreateNewHeaderErrWrongTypeAssertion(t *testing.T) { t.Parallel() @@ -5063,7 +6003,7 @@ func TestShardProcessor_CreateNewHeaderErrWrongTypeAssertion(t *testing.T) { Coordinator: mock.NewOneShardCoordinatorMock(), HdrIntegrityVerifier: &mock.HeaderIntegrityVerifierStub{}, VersionedHdrFactory: &testscommon.VersionedHeaderFactoryStub{ - CreateCalled: func(epoch uint32) data.HeaderHandler { + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { return &block.MetaBlock{} }, }, @@ -5092,14 +6032,14 @@ func TestShardProcessor_CreateNewHeaderValsOK(t *testing.T) { Coordinator: mock.NewOneShardCoordinatorMock(), HdrIntegrityVerifier: &mock.HeaderIntegrityVerifierStub{}, VersionedHdrFactory: &testscommon.VersionedHeaderFactoryStub{ - CreateCalled: func(epoch uint32) data.HeaderHandler { + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { return &block.HeaderV2{} }, }, } boostrapComponents.VersionedHdrFactory = &testscommon.VersionedHeaderFactoryStub{ - CreateCalled: func(epoch uint32) data.HeaderHandler { + CreateCalled: func(epoch uint32, _ uint64) data.HeaderHandler { return &block.HeaderV2{ Header: &block.Header{}, } @@ -5355,8 +6295,9 @@ func TestShardProcessor_CreateBlock(t *testing.T) { mockProcessHandler.SetIdleCalled = func() { busyIdleCalled = append(busyIdleCalled, idleIdentifier) } - mockProcessHandler.SetBusyCalled = func(reason string) { + mockProcessHandler.TrySetBusyCalled = func(reason string) bool { busyIdleCalled = append(busyIdleCalled, busyIdentifier) + return true } expectedBusyIdleSequencePerCall := []string{busyIdentifier, idleIdentifier} @@ -5488,3 +6429,1302 @@ func TestShardProcessor_CreateBlock(t *testing.T) { assert.Nil(t, err) }) } + +func TestVerifyCrossShardMiniBlockDstMe(t *testing.T) { + t.Parallel() + + t.Run("header v1 cannot get last notarized meta block", func(t *testing.T) { + localError := errors.New("local err") + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.BlockTracker = &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return nil, nil, localError + }, + } + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.Header{} + + err := sp.VerifyCrossShardMiniBlockDstMe(header) + require.Equal(t, localError, err) + }) + + t.Run("header v3 cannot get last notarized meta block", func(t *testing.T) { + localError := errors.New("local err") + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.BlockTracker = &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return nil, nil, localError + }, + } + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{} + + err := sp.VerifyCrossShardMiniBlockDstMe(header) + require.Equal(t, localError, err) + }) + + t.Run("header v1 wrong header from pool", func(t *testing.T) { + localError := errors.New("local err") + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.BlockTracker = &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.MetaBlockV3{}, nil, nil + }, + } + + blkc, _ := blockchain.NewBlockChain(&statusHandlerMock.AppStatusHandlerStub{}) + _ = blkc.SetGenesisHeader(&block.Header{Nonce: 0}) + dataComponents := &mock.DataComponentsMock{ + Storage: initStore(), + BlockChain: blkc, + } + + dataComponents.DataPool = &dataRetrieverMock.PoolsHolderStub{ + HeadersCalled: func() dataRetriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, []byte("m1")) { + return nil, localError + } + if bytes.Equal(hash, []byte("m2")) { + return &block.Header{}, nil + } + return &block.MetaBlockV3{}, nil + }, + } + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, + } + arguments.DataComponents = dataComponents + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.Header{ + MetaBlockHashes: [][]byte{[]byte("m1"), []byte("m2")}, + } + + err := sp.VerifyCrossShardMiniBlockDstMe(header) + require.NotNil(t, err) + }) + + t.Run("cannot find header in pool should error", func(t *testing.T) { + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.BlockTracker = &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.MetaBlockV3{}, nil, nil + }, + } + + blkc, _ := blockchain.NewBlockChain(&statusHandlerMock.AppStatusHandlerStub{}) + _ = blkc.SetGenesisHeader(&block.Header{Nonce: 0}) + dataComponents := &mock.DataComponentsMock{ + Storage: initStore(), + BlockChain: blkc, + } + + dataComponents.DataPool = &dataRetrieverMock.PoolsHolderStub{ + HeadersCalled: func() dataRetriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, []byte("m1")) { + return nil, expectedError + } + return nil, nil + }, + } + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, + } + arguments.DataComponents = dataComponents + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{ + MetaBlockHashes: [][]byte{[]byte("m1"), []byte("m2")}, + Round: 100, + } + + err := sp.VerifyCrossShardMiniBlockDstMe(header) + require.Equal(t, expectedError, err) + }) + + t.Run("wrong header type in pool", func(t *testing.T) { + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.BlockTracker = &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.MetaBlockV3{}, nil, nil + }, + } + + blkc, _ := blockchain.NewBlockChain(&statusHandlerMock.AppStatusHandlerStub{}) + _ = blkc.SetGenesisHeader(&block.Header{Nonce: 0}) + dataComponents := &mock.DataComponentsMock{ + Storage: initStore(), + BlockChain: blkc, + } + + dataComponents.DataPool = &dataRetrieverMock.PoolsHolderStub{ + HeadersCalled: func() dataRetriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, []byte("m1")) { + return &block.Header{}, nil + } + return nil, nil + }, + } + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, + } + arguments.DataComponents = dataComponents + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{ + MetaBlockHashes: [][]byte{[]byte("m1"), []byte("m2")}, + Round: 100, + } + + err := sp.VerifyCrossShardMiniBlockDstMe(header) + require.Equal(t, process.ErrWrongTypeAssertion, err) + }) + + t.Run("should work", func(t *testing.T) { + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.BlockTracker = &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.MetaBlockV3{}, nil, nil + }, + } + + blkc, _ := blockchain.NewBlockChain(&statusHandlerMock.AppStatusHandlerStub{}) + _ = blkc.SetGenesisHeader(&block.Header{Nonce: 0}) + dataComponents := &mock.DataComponentsMock{ + Storage: initStore(), + BlockChain: blkc, + } + + dataComponents.DataPool = &dataRetrieverMock.PoolsHolderStub{ + HeadersCalled: func() dataRetriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.MetaBlock{ + Round: 1, + Nonce: 1, + }, nil + }, + } + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, + } + arguments.DataComponents = dataComponents + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{ + MetaBlockHashes: [][]byte{[]byte("m1"), []byte("m2")}, + Round: 100, + } + + err := sp.VerifyCrossShardMiniBlockDstMe(header) + require.Nil(t, err) + }) +} + +func TestShardProcessor_AddCrossShardMiniBlocksDstMeToMap(t *testing.T) { + t.Parallel() + + t.Run("wrong type", func(t *testing.T) { + arguments := CreateMockArguments(createComponentHolderMocks()) + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{} + metaBlock := &block.Header{} + err := sp.AddCrossShardMiniBlocksDstMeToMap(header, []byte("h1"), metaBlock, metaBlock, nil) + require.Equal(t, process.ErrWrongTypeAssertion, err) + }) + + t.Run("higher round", func(t *testing.T) { + arguments := CreateMockArguments(createComponentHolderMocks()) + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{ + Round: 100, + } + metaBlock := &block.MetaBlockV3{ + Round: 200, + } + err := sp.AddCrossShardMiniBlocksDstMeToMap(header, []byte("h1"), metaBlock, metaBlock, nil) + require.Equal(t, process.ErrHigherRoundInBlock, err) + }) + + t.Run("lower round in last notarized", func(t *testing.T) { + arguments := CreateMockArguments(createComponentHolderMocks()) + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{ + Round: 100, + } + metaBlock := &block.MetaBlockV3{ + Round: 100, + } + lastCrossNotarized := &block.HeaderV3{ + Round: 150, + } + + err := sp.AddCrossShardMiniBlocksDstMeToMap(header, []byte("h1"), metaBlock, lastCrossNotarized, nil) + require.Equal(t, process.ErrLowerRoundInBlock, err) + }) + + t.Run("lower nonce in last notarized", func(t *testing.T) { + arguments := CreateMockArguments(createComponentHolderMocks()) + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{ + Round: 101, + } + metaBlock := &block.MetaBlockV3{ + Round: 101, + Nonce: 101, + } + lastCrossNotarized := &block.HeaderV3{ + Round: 100, + Nonce: 102, + } + + err := sp.AddCrossShardMiniBlocksDstMeToMap(header, []byte("h1"), metaBlock, lastCrossNotarized, nil) + require.Equal(t, process.ErrLowerNonceInBlock, err) + }) + + t.Run("should add miniblock hashes", func(t *testing.T) { + arguments := CreateMockArguments(createComponentHolderMocks()) + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{ + Round: 101, + } + metaBlock := &block.MetaBlockV3{ + Round: 101, + Nonce: 101, + ShardInfo: []block.ShardData{ + { + ShardID: 1, + ShardMiniBlockHeaders: []block.MiniBlockHeader{ + { + ReceiverShardID: 0, + SenderShardID: 1, + Hash: []byte("h2"), + }, + }, + }, + }, + } + lastCrossNotarized := &block.HeaderV3{ + Round: 100, + Nonce: 100, + } + + miniblocks := make(map[string][]byte) + err := sp.AddCrossShardMiniBlocksDstMeToMap(header, []byte("h1"), metaBlock, lastCrossNotarized, miniblocks) + require.Nil(t, err) + require.Equal(t, 1, len(miniblocks)) + require.Equal(t, []byte("h1"), miniblocks["h2"]) + }) +} + +func TestShardProcessor_checkEpochStartInfoAvailableIfNeeded(t *testing.T) { + t.Parallel() + t.Run("no epoch start block, should return nil", func(t *testing.T) { + t.Parallel() + + arguments := CreateMockArguments(createComponentHolderMocks()) + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.Header{ + Epoch: 10, + EpochStartMetaHash: nil, + } + err := sp.CheckEpochStartInfoAvailableIfNeeded(header) + require.Nil(t, err) + }) + t.Run("meta header has the same epoch as shard header, should return nil", func(t *testing.T) { + t.Parallel() + + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.EpochStartTrigger = &mock.EpochStartTriggerStub{ + MetaEpochCalled: func() uint32 { + return 10 // same epoch as for the header, no trigger + }, + } + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.Header{ + Epoch: 10, + EpochStartMetaHash: []byte("hash"), + } + err := sp.CheckEpochStartInfoAvailableIfNeeded(header) + require.Nil(t, err) + }) + t.Run("epoch start trigger, epoch start already in progress", func(t *testing.T) { + t.Parallel() + + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.EpochStartTrigger = &mock.EpochStartTriggerStub{ + MetaEpochCalled: func() uint32 { + return 9 + }, + IsEpochStartCalled: func() bool { + return true + }, + } + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.Header{ + Epoch: 10, + EpochStartMetaHash: []byte("hash"), + } + err := sp.CheckEpochStartInfoAvailableIfNeeded(header) + require.Nil(t, err) + }) + t.Run("epoch start trigger, epoch start not in progress, epoch start header not available, should return error", func(t *testing.T) { + t.Parallel() + + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.EpochStartTrigger = &mock.EpochStartTriggerStub{ + MetaEpochCalled: func() uint32 { + return 9 + }, + IsEpochStartCalled: func() bool { + return false + }, + } + + dataPool := initDataPool() + dataPool.HeadersCalled = func() dataRetriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, expectedError + }, + } + } + + arguments.DataComponents = &mock.DataComponentsMock{ + Storage: initStore(), + DataPool: dataPool, + BlockChain: arguments.DataComponents.Blockchain(), + } + + sp, _ := blproc.NewShardProcessor(arguments) + startOfEpochHeaderHash := []byte("start of epoch hash") + sp.ProofsPool().AddProof(&block.HeaderProof{ + HeaderHash: startOfEpochHeaderHash, + HeaderShardId: core.MetachainShardId, + }) + + header := &block.Header{ + Epoch: 10, + EpochStartMetaHash: startOfEpochHeaderHash, + } + err := sp.CheckEpochStartInfoAvailableIfNeeded(header) + require.ErrorIs(t, err, process.ErrEpochStartInfoNotAvailable) + }) + t.Run("epoch start trigger, epoch start not in progress, epoch start proof not available, should return error", func(t *testing.T) { + t.Parallel() + + arguments := CreateMockArguments(createComponentHolderMocks()) + startOfEpochHeaderHash := []byte("start of epoch hash") + arguments.EpochStartTrigger = &mock.EpochStartTriggerStub{ + MetaEpochCalled: func() uint32 { + return 9 + }, + IsEpochStartCalled: func() bool { + return false + }, + } + sp, _ := blproc.NewShardProcessor(arguments) + sp.HeadersPool().AddHeader(startOfEpochHeaderHash, &block.MetaBlock{}) + header := &block.Header{ + Epoch: 10, + EpochStartMetaHash: startOfEpochHeaderHash, + } + err := sp.CheckEpochStartInfoAvailableIfNeeded(header) + require.ErrorIs(t, err, process.ErrEpochStartInfoNotAvailable) + }) + t.Run("epoch start trigger, epoch start not in progress, epoch start header and proof available, should return nil", func(t *testing.T) { + t.Parallel() + + arguments := CreateMockArguments(createComponentHolderMocks()) + startOfEpochHeaderHash := []byte("start of epoch hash") + arguments.EpochStartTrigger = &mock.EpochStartTriggerStub{ + MetaEpochCalled: func() uint32 { + return 9 + }, + IsEpochStartCalled: func() bool { + return false + }, + } + sp, _ := blproc.NewShardProcessor(arguments) + sp.HeadersPool().AddHeader(startOfEpochHeaderHash, &block.MetaBlock{}) + sp.ProofsPool().AddProof(&block.HeaderProof{ + HeaderHash: startOfEpochHeaderHash, + HeaderShardId: core.MetachainShardId, + }) + + header := &block.HeaderV3{ + Epoch: 10, + EpochStartMetaHash: startOfEpochHeaderHash, + } + err := sp.CheckEpochStartInfoAvailableIfNeeded(header) + require.Nil(t, err) + }) +} + +func Test_ShouldDisableOutgoingTxs(t *testing.T) { + t.Parallel() + + t.Run("both flag not set, should return false", func(t *testing.T) { + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{} + enableRoundsHandler := &testscommon.EnableRoundsHandlerStub{} + require.False(t, blproc.ShouldDisableOutgoingTxs(enableEpochsHandler, enableRoundsHandler)) + }) + t.Run("epoch flag enabled, round flag disabled, should return true", func(t *testing.T) { + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return true + }, + } + enableRoundsHandler := &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledCalled: func(flag common.EnableRoundFlag) bool { + return false + }, + } + require.True(t, blproc.ShouldDisableOutgoingTxs(enableEpochsHandler, enableRoundsHandler)) + }) + t.Run("epoch flag disabled, round flag enabled, should return false", func(t *testing.T) { + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return false + }, + } + enableRoundsHandler := &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledCalled: func(flag common.EnableRoundFlag) bool { + return true + }, + } + require.False(t, blproc.ShouldDisableOutgoingTxs(enableEpochsHandler, enableRoundsHandler)) + }) + t.Run("both flag enabled, should return false", func(t *testing.T) { + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return true + }, + } + enableRoundsHandler := &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledCalled: func(flag common.EnableRoundFlag) bool { + return true + }, + } + require.False(t, blproc.ShouldDisableOutgoingTxs(enableEpochsHandler, enableRoundsHandler)) + }) +} + +func TestShardProcessor_GetLastExecutionResultHeader(t *testing.T) { + t.Parallel() + + t.Run("should return current header if not header v3", func(t *testing.T) { + t.Parallel() + + currHeader := &block.Header{} + + dataPool := initDataPool() + + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.DataComponents = &mock.DataComponentsMock{ + Storage: initStore(), + DataPool: dataPool, + BlockChain: arguments.DataComponents.Blockchain(), + } + + sp, _ := blproc.NewShardProcessor(arguments) + + retHeader, err := sp.GetLastExecutionResultHeader(currHeader) + require.Nil(t, err) + require.Equal(t, currHeader, retHeader) + }) + + t.Run("should error if failed to get header from storage", func(t *testing.T) { + t.Parallel() + + headerHash1 := []byte("hash1") + + currHeader := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: headerHash1, + }, + }, + } + + dataPool := initDataPool() + dataPool.HeadersCalled = func() dataRetriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, expectedError + }, + } + } + + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.DataComponents = &mock.DataComponentsMock{ + Storage: initStore(), + DataPool: dataPool, + BlockChain: arguments.DataComponents.Blockchain(), + } + + sp, _ := blproc.NewShardProcessor(arguments) + + retHeader, err := sp.GetLastExecutionResultHeader(currHeader) + require.ErrorIs(t, err, process.ErrMissingHeader) + require.Nil(t, retHeader) + }) + + t.Run("should try to get from storage if not found in pool", func(t *testing.T) { + t.Parallel() + + headerHash1 := []byte("hash1") + + currHeader := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: headerHash1, + }, + }, + } + + dataPool := initDataPool() + dataPool.HeadersCalled = func() dataRetriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, errors.New("other error") + }, + } + } + + marshaller := &marshal.GogoProtoMarshalizer{} + + execResHeader := &block.HeaderV3{ + Nonce: 1, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: headerHash1, + }, + }, + } + + storer := &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{ + RemoveCalled: func(key []byte) error { + return nil + }, + GetCalled: func(key []byte) ([]byte, error) { + return marshaller.Marshal(execResHeader) + }, + }, nil + }, + } + + coreComp, dataComp, bootComp, statusComp := createComponentHolderMocks() + coreComp.IntMarsh = marshaller + + dataComp.Storage = storer + dataComp.DataPool = dataPool + + arguments := CreateMockArguments(coreComp, dataComp, bootComp, statusComp) + + sp, _ := blproc.NewShardProcessor(arguments) + + retHeader, err := sp.GetLastExecutionResultHeader(currHeader) + require.Nil(t, err) + require.Equal(t, execResHeader, retHeader) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + headerHash1 := []byte("hash1") + + currHeader := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: headerHash1, + }, + }, + } + + execResHeader := &block.HeaderV3{} + + dataPool := initDataPool() + dataPool.HeadersCalled = func() dataRetriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, headerHash1) { + return execResHeader, nil + } + + return nil, expectedError + }, + } + } + + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.DataComponents = &mock.DataComponentsMock{ + Storage: initStore(), + DataPool: dataPool, + BlockChain: arguments.DataComponents.Blockchain(), + } + + sp, _ := blproc.NewShardProcessor(arguments) + + retHeader, err := sp.GetLastExecutionResultHeader(currHeader) + require.Nil(t, err) + require.Equal(t, execResHeader, retHeader) + }) +} + +func TestShardProcessor_GetLastExecutedRootHash(t *testing.T) { + t.Parallel() + + t.Run("before header v3, should return root hash from accounts db", func(t *testing.T) { + t.Parallel() + + rootHash := []byte("rootHash1") + + header := &block.HeaderV2{} + + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.DataComponents = &mock.DataComponentsMock{ + Storage: initStore(), + DataPool: initDataPool(), + BlockChain: arguments.DataComponents.Blockchain(), + } + + accountsDb := make(map[state.AccountsDbIdentifier]state.AccountsAdapter) + accounts := &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return rootHash, nil + }, + RecreateTrieIfNeededCalled: func(options common.RootHashHolder) error { + return nil + }, + } + accountsDb[state.UserAccountsState] = accounts + + arguments.AccountsDB = accountsDb + + sp, _ := blproc.NewShardProcessor(arguments) + retRootHash := sp.GetLastExecutedRootHash(header) + require.Equal(t, rootHash, retRootHash) + }) + + t.Run("with header v3, should return exec results root hash", func(t *testing.T) { + t.Parallel() + + rootHash1 := []byte("rootHash1") + rootHash2 := []byte("rootHash2") + + header := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + RootHash: rootHash2, + }, + }, + } + + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.DataComponents = &mock.DataComponentsMock{ + Storage: initStore(), + DataPool: initDataPool(), + BlockChain: arguments.DataComponents.Blockchain(), + } + + accountsDb := make(map[state.AccountsDbIdentifier]state.AccountsAdapter) + accounts := &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return rootHash1, nil + }, + RecreateTrieIfNeededCalled: func(options common.RootHashHolder) error { + return nil + }, + } + accountsDb[state.UserAccountsState] = accounts + + sp, _ := blproc.NewShardProcessor(arguments) + retRootHash := sp.GetLastExecutedRootHash(header) + require.Equal(t, rootHash2, retRootHash) + }) + + t.Run("with header v3 and nil last execution result, should fallback to blockchain last executed info", func(t *testing.T) { + t.Parallel() + + accountsRootHash := []byte("accountsRootHash") + blockchainRootHash := []byte("blockchainRootHash") + + header := &block.HeaderV3{ + LastExecutionResult: nil, + } + + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.DataComponents = &mock.DataComponentsMock{ + Storage: initStore(), + DataPool: initDataPool(), + BlockChain: &testscommon.ChainHandlerStub{ + GetGenesisHeaderCalled: func() data.HeaderHandler { + return &block.Header{} + }, + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return 0, nil, blockchainRootHash + }, + }, + } + + accountsDb := make(map[state.AccountsDbIdentifier]state.AccountsAdapter) + accounts := &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return accountsRootHash, nil + }, + RecreateTrieIfNeededCalled: func(options common.RootHashHolder) error { + return nil + }, + } + accountsDb[state.UserAccountsState] = accounts + arguments.AccountsDB = accountsDb + + sp, _ := blproc.NewShardProcessor(arguments) + retRootHash := sp.GetLastExecutedRootHash(header) + require.Equal(t, blockchainRootHash, retRootHash) + }) + + t.Run("with header v3, should not call getRootHash from accounts db", func(t *testing.T) { + t.Parallel() + + rootHashCalled := false + execRootHash := []byte("execRootHash") + + header := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + RootHash: execRootHash, + }, + }, + } + + arguments := CreateMockArguments(createComponentHolderMocks()) + arguments.DataComponents = &mock.DataComponentsMock{ + Storage: initStore(), + DataPool: initDataPool(), + BlockChain: arguments.DataComponents.Blockchain(), + } + + accountsDb := make(map[state.AccountsDbIdentifier]state.AccountsAdapter) + accounts := &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + rootHashCalled = true + return []byte("should-not-be-used"), nil + }, + RecreateTrieIfNeededCalled: func(options common.RootHashHolder) error { + return nil + }, + } + accountsDb[state.UserAccountsState] = accounts + arguments.AccountsDB = accountsDb + + sp, _ := blproc.NewShardProcessor(arguments) + retRootHash := sp.GetLastExecutedRootHash(header) + require.Equal(t, execRootHash, retRootHash) + require.False(t, rootHashCalled, "getRootHash should not be called for v3 headers") + }) +} + +func TestShardProcessor_PruneTrieHeaderV3(t *testing.T) { + t.Parallel() + + t.Run("pruneTrieHeaderV3 with headerV2 as previous header", func(t *testing.T) { + t.Parallel() + + rootHash1 := []byte("rootHash1") + prevHeader := &block.HeaderV2{ + Header: &block.Header{ + RootHash: rootHash1, + }, + } + + pruneTrieHeaderV3Test(t, prevHeader, rootHash1) + }) + t.Run("pruneTrieHeaderV3 with headerV3 as previous header", func(t *testing.T) { + t.Parallel() + + rootHash1 := []byte("rootHash1") + prevHeader := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + RootHash: rootHash1, + }, + }, + } + + pruneTrieHeaderV3Test(t, prevHeader, rootHash1) + }) +} + +func pruneTrieHeaderV3Test(t *testing.T, prevHeader data.HeaderHandler, rootHash1 []byte) { + pruneCalled := 0 + cancelPruneCalled := 0 + prevHeaderHash := []byte("prevHeaderHash") + rootHash2 := []byte("rootHash2") + + coreComponents, dataComponents, boostrapComponents, statusComponents := createComponentHolderMocks() + dataPool := initDataPool() + dataPool.HeadersCalled = func() dataRetriever.HeadersPool { + return &mock.HeadersCacherStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if !bytes.Equal(hash, prevHeaderHash) { + assert.Fail(t, "unexpected hash in GetHeaderByHashCalled") + return nil, expectedError + } + return prevHeader, nil + }, + } + } + dataComponents.DataPool = dataPool + + arguments := CreateMockArguments(coreComponents, dataComponents, boostrapComponents, statusComponents) + arguments.AccountsDB = map[state.AccountsDbIdentifier]state.AccountsAdapter{ + state.UserAccountsState: &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { + return true + }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + if bytes.Equal(rootHash, rootHash1) { + cancelPruneCalled++ + return + } + if bytes.Equal(rootHash, rootHash2) { + cancelPruneCalled++ + return + } + assert.Fail(t, "unexpected root hash in CancelPruneCalled for user accounts") + }, + PruneTrieCalled: func(rootHash []byte, identifier state.TriePruningIdentifier, handler state.PruningHandler) { + if bytes.Equal(rootHash, rootHash1) { + pruneCalled++ + return + } + if bytes.Equal(rootHash, rootHash2) { + pruneCalled++ + return + } + assert.Fail(t, "unexpected root hash in PruneTrieCalled for user accounts") + }, + }, + } + + sp, _ := blproc.NewShardProcessor(arguments) + + executionResultsHandlers := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + RootHash: rootHash2, + }, + }, + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + RootHash: []byte("some other root hash"), + }, + }, + } + header1 := &block.HeaderV3{ + PrevHash: prevHeaderHash, + } + _ = header1.SetExecutionResultsHandlers(executionResultsHandlers) + + sp.PruneTrieHeaderV3(header1) + + assert.Equal(t, 2, pruneCalled) + assert.Equal(t, 2, cancelPruneCalled) +} + +func TestShardProcessor_CommitBlockV3FailAfterHeadMutationShouldRestoreChainHead(t *testing.T) { + t.Parallel() + + t.Run("should restore nil head after failed V3 commit", func(t *testing.T) { + t.Parallel() + + genesisHeaderHash := []byte("genesis_hash") + computedHeaderHash := []byte("computed_header_hash") + + var currentHeader data.HeaderHandler + var currentHeaderHash []byte + + testBlockchain := &testscommon.ChainHandlerStub{ + GetGenesisHeaderCalled: func() data.HeaderHandler { + return &block.Header{Nonce: 0} + }, + GetGenesisHeaderHashCalled: func() []byte { + return genesisHeaderHash + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return currentHeader + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return currentHeaderHash + }, + SetCurrentBlockHeaderCalled: func(header data.HeaderHandler) error { + currentHeader = header + return nil + }, + SetCurrentBlockHeaderHashCalled: func(hash []byte) { + currentHeaderHash = hash + }, + SetFinalBlockInfoCalled: func(nonce uint64, headerHash []byte, rootHash []byte) {}, + } + + v3Header := &block.HeaderV3{ + Nonce: 1, + Round: 1, + PrevHash: genesisHeaderHash, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("nonexistent_exec_result_hash"), + HeaderNonce: 1, + RootHash: []byte("exec_root_hash"), + }, + }, + } + body := &block.Body{} + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + coreComponents.Hash = &mock.HasherStub{ + ComputeCalled: func(s string) []byte { + return computedHeaderHash + }, + } + + dataComponents.BlockChain = testBlockchain + dataComponents.Storage = initStore() + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ExecutionManager = &processMocks.ExecutionManagerMock{} + arguments.ForkDetector = &mock.ForkDetectorMock{} + arguments.BlockTracker = mock.NewBlockTrackerMock(mock.NewOneShardCoordinatorMock(), createGenesisBlocks(mock.NewOneShardCoordinatorMock())) + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + require.Nil(t, testBlockchain.GetCurrentBlockHeader()) + require.Nil(t, testBlockchain.GetCurrentBlockHeaderHash()) + + err = sp.CommitBlock(v3Header, body) + require.NotNil(t, err) + + assert.Nil(t, testBlockchain.GetCurrentBlockHeader(), + "currentBlockHeader should be restored to nil after failed V3 commit") + assert.Nil(t, testBlockchain.GetCurrentBlockHeaderHash(), + "currentBlockHeaderHash should be restored to nil after failed V3 commit") + }) + + t.Run("should restore non-nil head after failed V3 commit", func(t *testing.T) { + t.Parallel() + + prevHeaderHash := []byte("prev_header_hash") + computedHeaderHash := []byte("computed_header_hash") + + prevHeader := &block.HeaderV3{ + Nonce: 5, + Round: 10, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("prev_exec_hash"), + HeaderNonce: 5, + RootHash: []byte("prev_root_hash"), + }, + }, + } + + var currentHeader data.HeaderHandler = prevHeader + currentHeaderHash := make([]byte, len(prevHeaderHash)) + copy(currentHeaderHash, prevHeaderHash) + + testBlockchain := &testscommon.ChainHandlerStub{ + GetGenesisHeaderCalled: func() data.HeaderHandler { + return &block.Header{Nonce: 0} + }, + GetGenesisHeaderHashCalled: func() []byte { + return []byte("genesis_hash") + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return currentHeader + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return currentHeaderHash + }, + SetCurrentBlockHeaderCalled: func(header data.HeaderHandler) error { + currentHeader = header + return nil + }, + SetCurrentBlockHeaderHashCalled: func(hash []byte) { + currentHeaderHash = hash + }, + SetFinalBlockInfoCalled: func(nonce uint64, headerHash []byte, rootHash []byte) {}, + } + + v3Header := &block.HeaderV3{ + Nonce: 6, + Round: 12, + PrevHash: prevHeaderHash, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("nonexistent_exec_result_hash"), + HeaderNonce: 6, + RootHash: []byte("exec_root_hash"), + }, + }, + } + body := &block.Body{} + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + coreComponents.Hash = &mock.HasherStub{ + ComputeCalled: func(s string) []byte { + return computedHeaderHash + }, + } + + dataComponents.BlockChain = testBlockchain + dataComponents.Storage = initStore() + + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ExecutionManager = &processMocks.ExecutionManagerMock{} + arguments.ForkDetector = &mock.ForkDetectorMock{} + arguments.BlockTracker = mock.NewBlockTrackerMock(mock.NewOneShardCoordinatorMock(), createGenesisBlocks(mock.NewOneShardCoordinatorMock())) + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + require.Equal(t, prevHeader, testBlockchain.GetCurrentBlockHeader()) + require.Equal(t, prevHeaderHash, testBlockchain.GetCurrentBlockHeaderHash()) + + err = sp.CommitBlock(v3Header, body) + require.NotNil(t, err) + + assert.Equal(t, prevHeader, testBlockchain.GetCurrentBlockHeader(), + "currentBlockHeader should be restored to previous header after failed V3 commit") + assert.Equal(t, prevHeaderHash, testBlockchain.GetCurrentBlockHeaderHash(), + "currentBlockHeaderHash should be restored to previous hash after failed V3 commit") + }) +} + +func TestShardProcessor_CancelPruneForDismissedExecutionResults(t *testing.T) { + t.Parallel() + + t.Run("pruning disabled should not call CancelPrune", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { return false }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + require.Fail(t, "CancelPrune should not be called when pruning is disabled") + }, + } + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + batches := []executionTrack.DismissedBatch{ + { + AnchorResult: &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R0")}, + }, + Results: []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R1")}, + }, + }, + }, + } + sp.CancelPruneForDismissedExecutionResults(batches) + }) + t.Run("nil anchor should skip batch", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { return true }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + require.Fail(t, "CancelPrune should not be called for nil anchor batch") + }, + } + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + batches := []executionTrack.DismissedBatch{ + {AnchorResult: nil, Results: []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R1")}, + }, + }}, + } + sp.CancelPruneForDismissedExecutionResults(batches) + }) + t.Run("single batch with multiple results should cancel prune for each transition", func(t *testing.T) { + t.Parallel() + + type cancelPruneCall struct { + rootHash []byte + identifier state.TriePruningIdentifier + } + calls := make([]cancelPruneCall, 0) + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { return true }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + calls = append(calls, cancelPruneCall{rootHash: rootHash, identifier: identifier}) + }, + } + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + // Dismissed chain: anchor(R0) -> R1 -> R2 -> R3 + batches := []executionTrack.DismissedBatch{ + { + AnchorResult: &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R0")}, + }, + Results: []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R1")}, + }, + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R2")}, + }, + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R3")}, + }, + }, + }, + } + sp.CancelPruneForDismissedExecutionResults(batches) + + // 3 transitions: R0->R1, R1->R2, R2->R3, 2 CancelPrune calls each = 6 total + require.Len(t, calls, 6) + // Transition R0->R1 + require.Equal(t, []byte("R1"), calls[0].rootHash) + require.Equal(t, state.NewRoot, calls[0].identifier) + require.Equal(t, []byte("R0"), calls[1].rootHash) + require.Equal(t, state.OldRoot, calls[1].identifier) + // Transition R1->R2 + require.Equal(t, []byte("R2"), calls[2].rootHash) + require.Equal(t, state.NewRoot, calls[2].identifier) + require.Equal(t, []byte("R1"), calls[3].rootHash) + require.Equal(t, state.OldRoot, calls[3].identifier) + // Transition R2->R3 + require.Equal(t, []byte("R3"), calls[4].rootHash) + require.Equal(t, state.NewRoot, calls[4].identifier) + require.Equal(t, []byte("R2"), calls[5].rootHash) + require.Equal(t, state.OldRoot, calls[5].identifier) + }) + t.Run("equal consecutive root hashes should be skipped", func(t *testing.T) { + t.Parallel() + + cancelPruneCalls := 0 + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { return true }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + cancelPruneCalls++ + }, + } + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + // anchor(R0) -> R0 (same hash, no state change) -> R1 + batches := []executionTrack.DismissedBatch{ + { + AnchorResult: &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R0")}, + }, + Results: []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R0")}, + }, + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("R1")}, + }, + }, + }, + } + sp.CancelPruneForDismissedExecutionResults(batches) + + // R0->R0 is skipped (equal), R0->R1 produces 2 calls + require.Equal(t, 2, cancelPruneCalls) + }) + t.Run("multiple batches should all be processed", func(t *testing.T) { + t.Parallel() + + cancelPruneCalls := 0 + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.AccountsDB[state.UserAccountsState] = &stateMock.AccountsStub{ + IsPruningEnabledCalled: func() bool { return true }, + CancelPruneCalled: func(rootHash []byte, identifier state.TriePruningIdentifier) { + cancelPruneCalls++ + }, + } + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + batches := []executionTrack.DismissedBatch{ + { + AnchorResult: &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("A0")}, + }, + Results: []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("A1")}, + }, + }, + }, + { + AnchorResult: &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("B0")}, + }, + Results: []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{RootHash: []byte("B1")}, + }, + }, + }, + } + sp.CancelPruneForDismissedExecutionResults(batches) + + // 2 batches, 1 transition each, 2 CancelPrune calls = 4 total + require.Equal(t, 4, cancelPruneCalls) + }) +} diff --git a/process/block/shardinfo_test.go b/process/block/shardinfo_test.go new file mode 100644 index 00000000000..21f66f73328 --- /dev/null +++ b/process/block/shardinfo_test.go @@ -0,0 +1,1123 @@ +package block + +import ( + "fmt" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/dataRetriever" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/mock" + "github.com/multiversx/mx-chain-go/storage" + dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" + poolMock "github.com/multiversx/mx-chain-go/testscommon/pool" + mockStorage "github.com/multiversx/mx-chain-go/testscommon/storage" + + "github.com/stretchr/testify/require" +) + +func TestShardInfo_IsInterfaceNil(t *testing.T) { + t.Parallel() + var si *ShardInfoCreateData + require.True(t, si.IsInterfaceNil()) + si = &ShardInfoCreateData{} + require.False(t, si.IsInterfaceNil()) +} + +func TestShardInfo_NewShardInfoCreateData(t *testing.T) { + t.Parallel() + + t.Run("nil enableEpochsHandler", func(t *testing.T) { + t.Parallel() + + args := createDefaultShardInfoCreateDataArgs() + args.EnableEpochsHandler = nil + + sicd, err := NewShardInfoCreateData(args) + require.Nil(t, sicd) + require.Equal(t, process.ErrNilEnableEpochsHandler, err) + }) + + t.Run("nil headersPool", func(t *testing.T) { + t.Parallel() + + args := createDefaultShardInfoCreateDataArgs() + args.HeadersPool = nil + + sicd, err := NewShardInfoCreateData(args) + require.Nil(t, sicd) + require.Equal(t, process.ErrNilHeadersDataPool, err) + }) + + t.Run("nil proofsPool", func(t *testing.T) { + t.Parallel() + + args := createDefaultShardInfoCreateDataArgs() + args.ProofsPool = nil + + sicd, err := NewShardInfoCreateData(args) + require.Nil(t, sicd) + require.Equal(t, process.ErrNilProofsPool, err) + }) + + t.Run("nil pendingMiniBlocksHandler", func(t *testing.T) { + t.Parallel() + + args := createDefaultShardInfoCreateDataArgs() + args.PendingMiniBlocksHandler = nil + + sicd, err := NewShardInfoCreateData(args) + require.Nil(t, sicd) + require.Equal(t, process.ErrNilPendingMiniBlocksHandler, err) + }) + + t.Run("nil blockTracker", func(t *testing.T) { + t.Parallel() + + args := createDefaultShardInfoCreateDataArgs() + args.BlockTracker = nil + + sicd, err := NewShardInfoCreateData(args) + require.Nil(t, sicd) + require.Equal(t, process.ErrNilBlockTracker, err) + }) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + args := createDefaultShardInfoCreateDataArgs() + + sicd, err := NewShardInfoCreateData(args) + require.NotNil(t, sicd) + require.Nil(t, err) + }) +} + +func TestShardInfoCreateData_CreateShardInfoV3(t *testing.T) { + t.Parallel() + + hdrHash0 := []byte("header hash for shard 0") + hdrHash1 := []byte("header hash for shard 1") + hdrHash2 := []byte("header hash for shard 2") + hdrHash3 := []byte("header hash for shard 2 V3") + + header0 := getHeaderV3ForShard(uint32(0), hdrHash0) + header1 := getShardHeaderForShard(uint32(1)) + header2 := getShardHeaderForShard(uint32(2)) + header3 := getHeaderV3ForShard(uint32(2), hdrHash3) + headers := []data.HeaderHandler{header0, header1, header2, header3} + + headerHashes := [][]byte{hdrHash0, hdrHash1, hdrHash2, hdrHash3} + + pool := dataRetrieverMock.NewPoolsHolderMock() + pool.Headers().AddHeader(hdrHash0, header0) + pool.Headers().AddHeader(hdrHash1, header1) + pool.Headers().AddHeader(hdrHash2, header2) + pool.Headers().AddHeader(hdrHash3, header3) + + round := uint64(10) + metaHdrV3 := &block.MetaBlockV3{Round: round} + + args := createDefaultShardInfoCreateDataArgs() + args.HeadersPool = pool.Headers() + + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + + t.Run("should fail with nil meta header", func(t *testing.T) { + t.Parallel() + + shardDataProposalHandlers, shardDataHandlers, err := sic.CreateShardInfoV3(nil, headers, headerHashes) + require.ErrorIs(t, err, process.ErrNilHeaderHandler) + require.Nil(t, shardDataProposalHandlers) + require.Nil(t, shardDataHandlers) + }) + + t.Run("should fail with legacy meta header", func(t *testing.T) { + t.Parallel() + + metaHdr := &block.MetaBlock{Round: round} + + shardDataProposalHandlers, shardDataHandlers, err := sic.CreateShardInfoV3(metaHdr, headers, headerHashes) + require.Equal(t, process.ErrInvalidHeader, err) + require.Nil(t, shardDataProposalHandlers) + require.Nil(t, shardDataHandlers) + }) + t.Run("should fail with inconsistent headers and hashes", func(t *testing.T) { + t.Parallel() + + shardDataProposalHandlers, shardDataHandlers, err := sic.CreateShardInfoV3(metaHdrV3, headers, headerHashes[:2]) + require.Equal(t, process.ErrInconsistentShardHeadersAndHashes, err) + require.Nil(t, shardDataProposalHandlers) + require.Nil(t, shardDataHandlers) + }) + t.Run("should fail when createShardInfoFromHeader errors", func(t *testing.T) { + t.Parallel() + + shardDataProposalHandlers, shardDataHandlers, err := sic.CreateShardInfoV3(metaHdrV3, headers, headerHashes) + require.Contains(t, err.Error(), process.ErrMissingHeaderProof.Error()) + require.Nil(t, shardDataProposalHandlers) + require.Nil(t, shardDataHandlers) + }) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + args := createDefaultShardInfoCreateDataArgs() + args.HeadersPool = pool.Headers() + args.PendingMiniBlocksHandler = &mock.PendingMiniBlocksHandlerStub{ + GetPendingMiniBlocksCalled: func(shardID uint32) [][]byte { + return [][]byte{[]byte("hash1"), []byte("hash2")} + }, + } + args.BlockTracker = &mock.BlockTrackerMock{ + GetLastSelfNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.Header{Nonce: headers[shardID].GetNonce()}, []byte("selfNotarizedHash"), nil + }, + } + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + } + args.ProofsPool = &dataRetrieverMock.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + shardDataProposalHandlers, shardDataHandlers, err := sic.CreateShardInfoV3(metaHdrV3, headers, headerHashes) + require.Nil(t, err) + require.NotNil(t, shardDataHandlers) + require.NotNil(t, shardDataProposalHandlers) + require.Equal(t, 4, len(shardDataHandlers)) + require.Equal(t, 4, len(shardDataProposalHandlers)) + }) +} + +func TestShardInfoCreateData_CreateShardInfoFromLegacyMeta(t *testing.T) { + t.Parallel() + + pool := dataRetrieverMock.NewPoolsHolderMock() + // we will have a 3 hdrs in pool + hdrHash1 := []byte("hdr hash 1") + hdrHash2 := []byte("hdr hash 2") + hdrHash3 := []byte("hdr hash 3") + + mbHash1 := []byte("mb hash 1") + mbHash2 := []byte("mb hash 2") + mbHash3 := []byte("mb hash 3") + + miniBlockHeader1 := block.MiniBlockHeader{Hash: mbHash1} + miniBlockHeader2 := block.MiniBlockHeader{Hash: mbHash2} + miniBlockHeader3 := block.MiniBlockHeader{Hash: mbHash3} + + miniBlockHeaders1 := []block.MiniBlockHeader{miniBlockHeader1, miniBlockHeader2, miniBlockHeader3} + miniBlockHeaders2 := []block.MiniBlockHeader{miniBlockHeader1, miniBlockHeader2} + miniBlockHeaders3 := []block.MiniBlockHeader{miniBlockHeader1} + + header1 := &block.Header{ + Round: 1, + Nonce: 45, + ShardID: 0, + MiniBlockHeaders: miniBlockHeaders1} + header2 := &block.Header{ + Round: 2, + Nonce: 45, + ShardID: 1, + MiniBlockHeaders: miniBlockHeaders2} + header3 := &block.Header{ + Round: 3, + Nonce: 45, + ShardID: 2, + MiniBlockHeaders: miniBlockHeaders3} + // put the existing headers inside datapool + pool.Headers().AddHeader(hdrHash1, header1) + pool.Headers().AddHeader(hdrHash2, header2) + pool.Headers().AddHeader(hdrHash3, header3) + + headerHashes := [][]byte{hdrHash1, hdrHash2, hdrHash3} + headers := []data.ShardHeaderHandler{header1, header2, header3} + round := uint64(10) + metaHdr := &block.MetaBlock{Round: round} + + args := createDefaultShardInfoCreateDataArgs() + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + t.Run("should fail with nil meta header", func(t *testing.T) { + t.Parallel() + + shardInfo, err := sic.CreateShardInfoFromLegacyMeta(nil, headers, headerHashes) + require.Equal(t, process.ErrNilHeaderHandler, err) + require.Nil(t, shardInfo) + }) + t.Run("should fail with V3 meta header", func(t *testing.T) { + t.Parallel() + + metaHdrV3 := &block.MetaBlockV3{Round: round} + shardInfo, err := sic.CreateShardInfoFromLegacyMeta(metaHdrV3, headers, headerHashes) + require.Equal(t, process.ErrInvalidHeader, err) + require.Nil(t, shardInfo) + }) + t.Run("should fail with inconsistent headers and hashes", func(t *testing.T) { + t.Parallel() + + shardInfo, err := sic.CreateShardInfoFromLegacyMeta(metaHdr, headers, headerHashes[:1]) + require.Equal(t, process.ErrInconsistentShardHeadersAndHashes, err) + require.Nil(t, shardInfo) + + }) + t.Run("should fail when createShardDataFromLegacyHeader errors", func(t *testing.T) { + t.Parallel() + + args := createDefaultShardInfoCreateDataArgs() + args.HeadersPool = pool.Headers() + args.PendingMiniBlocksHandler = &mock.PendingMiniBlocksHandlerStub{ + GetPendingMiniBlocksCalled: func(shardID uint32) [][]byte { + return [][]byte{[]byte("hash1"), []byte("hash2")} + }, + } + args.BlockTracker = &mock.BlockTrackerMock{ + GetLastSelfNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return nil, nil, errExpected + }, + } + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + } + args.ProofsPool = &dataRetrieverMock.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + shardInfo, err := sic.CreateShardInfoFromLegacyMeta(metaHdr, headers, headerHashes) + require.NotNil(t, err) + require.Nil(t, shardInfo) + require.Equal(t, errExpected, err) + }) + t.Run("should work", func(t *testing.T) { + t.Parallel() + args := createDefaultShardInfoCreateDataArgs() + args.HeadersPool = pool.Headers() + args.PendingMiniBlocksHandler = &mock.PendingMiniBlocksHandlerStub{ + GetPendingMiniBlocksCalled: func(shardID uint32) [][]byte { + return [][]byte{[]byte("hash1"), []byte("hash2")} + }, + } + args.BlockTracker = &mock.BlockTrackerMock{ + GetLastSelfNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.Header{Nonce: headers[shardID].GetNonce()}, []byte("selfNotarizedHash"), nil + }, + } + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + } + args.ProofsPool = &dataRetrieverMock.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + shardInfo, err := sic.CreateShardInfoFromLegacyMeta(metaHdr, headers, headerHashes) + require.Nil(t, err) + require.NotNil(t, shardInfo) + require.Equal(t, 3, len(shardInfo)) + }) +} + +func TestShardInfoCreateData_createShardInfoFromHeader(t *testing.T) { + t.Parallel() + + t.Run("should fail with nil header", func(t *testing.T) { + t.Parallel() + args := createDefaultShardInfoCreateDataArgs() + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + + shardDataProposalHandlers, shardDataHandlers, err := sic.createShardInfoFromHeader(nil, nil) + require.Nil(t, shardDataProposalHandlers) + require.Nil(t, shardDataHandlers) + require.ErrorIs(t, err, process.ErrNilHeaderHandler) + }) + t.Run("should fail with invalid hash", func(t *testing.T) { + t.Parallel() + args := createDefaultShardInfoCreateDataArgs() + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + + shardDataProposalHandlers, shardDataHandlers, err := sic.createShardInfoFromHeader(&block.Header{}, []byte{}) + require.Nil(t, shardDataProposalHandlers) + require.Nil(t, shardDataHandlers) + require.ErrorIs(t, err, process.ErrInvalidHash) + }) + + t.Run("should fail with missing shard header proof", func(t *testing.T) { + t.Parallel() + + args := createDefaultShardInfoCreateDataArgs() + args.ProofsPool = &dataRetrieverMock.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return false + }, + } + + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + + shardDataProposalHandlers, shardDataHandlers, err := sic.createShardInfoFromHeader(&block.Header{Nonce: 1}, []byte("hash")) + require.Nil(t, shardDataProposalHandlers) + require.Nil(t, shardDataHandlers) + require.ErrorIs(t, err, process.ErrMissingHeaderProof) + }) + t.Run("should work with Legacy", func(t *testing.T) { + t.Parallel() + header := getShardHeaderForShard(uint32(1)) + args := createDefaultShardInfoCreateDataArgs() + args.PendingMiniBlocksHandler = &mock.PendingMiniBlocksHandlerStub{ + GetPendingMiniBlocksCalled: func(shardID uint32) [][]byte { + return [][]byte{[]byte("hash1"), []byte("hash2")} + }, + } + args.BlockTracker = &mock.BlockTrackerMock{ + GetLastSelfNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.Header{Nonce: header.GetNonce()}, []byte("selfNotarizedHash"), nil + }, + } + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + } + args.ProofsPool = &dataRetrieverMock.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + expectedProposalHandler := &block.ShardDataProposal{ + HeaderHash: []byte("hash"), + Round: 10, + Nonce: 45, + ShardID: 1, + Epoch: 0, + NumPendingMiniBlocks: 2, + } + + shardDataProposalHandler, shardDataHandlers, err := sic.createShardInfoFromHeader(header, []byte("hash")) + require.Nil(t, err) + require.NotNil(t, shardDataProposalHandler) + require.NotNil(t, shardDataHandlers) + require.Equal(t, expectedProposalHandler, shardDataProposalHandler) + }) + t.Run("should work with Legacy no proof for nonce < 1", func(t *testing.T) { + t.Parallel() + header := getShardHeaderForShard(uint32(1)) + _ = header.SetNonce(0) + args := createDefaultShardInfoCreateDataArgs() + args.PendingMiniBlocksHandler = &mock.PendingMiniBlocksHandlerStub{ + GetPendingMiniBlocksCalled: func(shardID uint32) [][]byte { + return [][]byte{[]byte("hash1"), []byte("hash2")} + }, + } + args.BlockTracker = &mock.BlockTrackerMock{ + GetLastSelfNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.Header{Nonce: header.GetNonce()}, []byte("selfNotarizedHash"), nil + }, + } + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + } + args.ProofsPool = &dataRetrieverMock.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + expectedProposalHandler := &block.ShardDataProposal{ + HeaderHash: []byte("hash"), + Round: 10, + Nonce: 0, + ShardID: 1, + Epoch: 0, + NumPendingMiniBlocks: 2, + } + + shardDataProposalHandler, shardDataHandlers, err := sic.createShardInfoFromHeader(header, []byte("hash")) + require.Nil(t, err) + require.NotNil(t, shardDataProposalHandler) + require.NotNil(t, shardDataHandlers) + require.Equal(t, expectedProposalHandler, shardDataProposalHandler) + }) + t.Run("should work with V3", func(t *testing.T) { + t.Parallel() + header := getHeaderV3ForShard(uint32(1), []byte("header hash for shard 1")) + + args := createDefaultShardInfoCreateDataArgs() + args.HeadersPool = &poolMock.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return header, nil + }, + } + args.PendingMiniBlocksHandler = &mock.PendingMiniBlocksHandlerStub{ + GetPendingMiniBlocksCalled: func(shardID uint32) [][]byte { + return [][]byte{[]byte("hash1"), []byte("hash2")} + }, + } + args.BlockTracker = &mock.BlockTrackerMock{ + GetLastSelfNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.Header{Nonce: header.GetNonce()}, []byte("selfNotarizedHash"), nil + }, + } + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + } + args.ProofsPool = &dataRetrieverMock.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + expectedProposalHandler := &block.ShardDataProposal{ + HeaderHash: []byte("hash"), + Round: 10, + Nonce: 45, + ShardID: 1, + Epoch: 7, + NumPendingMiniBlocks: 2, + } + + shardDataProposalHandler, shardDataHandlers, err := sic.createShardInfoFromHeader(header, []byte("hash")) + require.Nil(t, err) + require.NotNil(t, shardDataProposalHandler) + require.NotNil(t, shardDataHandlers) + require.Equal(t, expectedProposalHandler, shardDataProposalHandler) + }) + t.Run("should work with V3 no proof for nonce < 1", func(t *testing.T) { + t.Parallel() + + header := getHeaderV3ForShard(uint32(1), []byte("header hash for shard 1")) + expectedNonce := uint64(0) + _ = header.SetNonce(expectedNonce) + + args := createDefaultShardInfoCreateDataArgs() + args.HeadersPool = &poolMock.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return header, nil + }, + } + args.PendingMiniBlocksHandler = &mock.PendingMiniBlocksHandlerStub{ + GetPendingMiniBlocksCalled: func(shardID uint32) [][]byte { + return [][]byte{[]byte("hash1"), []byte("hash2")} + }, + } + args.BlockTracker = &mock.BlockTrackerMock{ + GetLastSelfNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.Header{Nonce: header.GetNonce()}, []byte("selfNotarizedHash"), nil + }, + } + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + } + args.ProofsPool = &dataRetrieverMock.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return false + }, + } + + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + expectedProposalHandler := &block.ShardDataProposal{ + HeaderHash: []byte("hash"), + Round: 10, + Nonce: 0, + ShardID: 1, + Epoch: 7, + NumPendingMiniBlocks: 2, + } + + shardDataProposalHandler, shardDataHandlers, err := sic.createShardInfoFromHeader(header, []byte("hash")) + require.Nil(t, err) + require.NotNil(t, shardDataProposalHandler) + require.NotNil(t, shardDataHandlers) + require.Equal(t, expectedProposalHandler, shardDataProposalHandler) + }) +} + +func TestShardInfoCreateData_createShardDataFromLegacyHeader(t *testing.T) { + t.Parallel() + + t.Run("should fail with updateShardDataWithCrossShardInfo error", func(t *testing.T) { + t.Parallel() + header := getShardHeaderForShard(uint32(1)) + args := createDefaultShardInfoCreateDataArgs() + args.PendingMiniBlocksHandler = &mock.PendingMiniBlocksHandlerStub{ + GetPendingMiniBlocksCalled: func(shardID uint32) [][]byte { + return [][]byte{[]byte("hash1"), []byte("hash2")} + }, + } + args.BlockTracker = &mock.BlockTrackerMock{ + GetLastSelfNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return nil, nil, fmt.Errorf("GetLastSelfNotarizedHeader error") + }, + } + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return false + }, + } + + sic, err := NewShardInfoCreateData(args) + + require.Nil(t, err) + shardDataList, err := sic.createShardDataFromLegacyHeader(header, []byte("headerHash")) + require.Contains(t, err.Error(), "GetLastSelfNotarizedHeader error") + require.Nil(t, shardDataList) + }) + + t.Run("should work with enable epoch flag disabled", func(t *testing.T) { + t.Parallel() + header := getShardHeaderForShard(uint32(1)) + + args := createDefaultShardInfoCreateDataArgs() + args.PendingMiniBlocksHandler = &mock.PendingMiniBlocksHandlerStub{ + GetPendingMiniBlocksCalled: func(shardID uint32) [][]byte { + return [][]byte{[]byte("hash1"), []byte("hash2")} + }, + } + args.BlockTracker = &mock.BlockTrackerMock{ + GetLastSelfNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.Header{Nonce: header.GetNonce()}, []byte("selfNotarizedHash"), nil + }, + } + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return false + }, + } + + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + + shardDataList, err := sic.createShardDataFromLegacyHeader(header, []byte("headerHash")) + require.Nil(t, err) + require.NotNil(t, shardDataList) + require.Equal(t, 1, len(shardDataList)) + require.Equal(t, header.GetNonce(), shardDataList[0].GetNonce()) + require.Equal(t, uint32(0), shardDataList[0].(*block.ShardData).GetEpoch()) + }) + t.Run("should work with enable epoch flag enabled", func(t *testing.T) { + t.Parallel() + + header := getShardHeaderForShard(uint32(1)) + + args := createDefaultShardInfoCreateDataArgs() + args.PendingMiniBlocksHandler = &mock.PendingMiniBlocksHandlerStub{ + GetPendingMiniBlocksCalled: func(shardID uint32) [][]byte { + return [][]byte{[]byte("hash1"), []byte("hash2")} + }, + } + args.BlockTracker = &mock.BlockTrackerMock{ + GetLastSelfNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.Header{Nonce: header.GetNonce()}, []byte("selfNotarizedHash"), nil + }, + } + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + } + + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + + shardDataList, err := sic.createShardDataFromLegacyHeader(header, []byte("headerHash")) + require.Nil(t, err) + require.NotNil(t, shardDataList) + require.Equal(t, 1, len(shardDataList)) + require.Equal(t, header.GetNonce(), shardDataList[0].GetNonce()) + require.Equal(t, header.GetEpoch(), shardDataList[0].(*block.ShardData).GetEpoch()) + }) +} +func TestShardInfoCreateData_createShardDataFromV3Header(t *testing.T) { + t.Parallel() + + t.Run("should fail with nil header", func(t *testing.T) { + t.Parallel() + args := createDefaultShardInfoCreateDataArgs() + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + shardDataProposalHandler, shardDataHandlers, err := sic.createShardDataFromV3Header(nil, nil) + require.Nil(t, shardDataHandlers) + require.Nil(t, shardDataProposalHandler) + require.ErrorIs(t, err, process.ErrNilHeaderHandler) + }) + t.Run("should return early if no execution results", func(t *testing.T) { + t.Parallel() + header := &block.HeaderV3{ + Nonce: 0, + } + args := createDefaultShardInfoCreateDataArgs() + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + shardDataProposalHandler, shardDataHandlers, err := sic.createShardDataFromV3Header(header, []byte("headerHash")) + require.Nil(t, err) + require.NotNil(t, shardDataHandlers) + require.NotNil(t, shardDataProposalHandler) + require.Equal(t, 0, len(shardDataHandlers)) + }) + t.Run("should fail with createShardDataFromExecutionResult error", func(t *testing.T) { + t.Parallel() + + expectedNonce := uint64(12345) + header := getHeaderV3ForShard(uint32(1), []byte("header hash for shard 1")) + // GetHeaderByHash error will fail createShardDataFromExecutionResult + args := createDefaultShardInfoCreateDataArgs() + args.HeadersPool = &poolMock.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, fmt.Errorf("GetHeaderByHash error") + }, + } + args.PendingMiniBlocksHandler = &mock.PendingMiniBlocksHandlerStub{ + GetPendingMiniBlocksCalled: func(shardID uint32) [][]byte { + return [][]byte{[]byte("hash1"), []byte("hash2")} + }, + } + args.BlockTracker = &mock.BlockTrackerMock{ + GetLastSelfNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.Header{Nonce: expectedNonce}, []byte("selfNotarizedHash"), nil + }, + } + args.Storage = &mockStorage.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &mockStorage.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + return nil, fmt.Errorf("GetHeaderByHash error from storage") + }, + }, nil + }, + } + + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + + shardDataProposalHandler, shardDataHandlers, err := sic.createShardDataFromV3Header(header, []byte("headerHash")) + require.Nil(t, shardDataHandlers) + require.Nil(t, shardDataProposalHandler) + require.ErrorIs(t, err, process.ErrMissingHeader) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + expectedNonce := uint64(12345) + header := getHeaderV3ForShard(uint32(1), []byte("header hash for shard 1")) + + args := createDefaultShardInfoCreateDataArgs() + args.HeadersPool = &poolMock.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return header, nil + }, + } + args.PendingMiniBlocksHandler = &mock.PendingMiniBlocksHandlerStub{ + GetPendingMiniBlocksCalled: func(shardID uint32) [][]byte { + return [][]byte{[]byte("hash1"), []byte("hash2")} + }, + } + args.BlockTracker = &mock.BlockTrackerMock{ + GetLastSelfNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.Header{Nonce: expectedNonce}, []byte("selfNotarizedHash"), nil + }, + } + + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + + shardDataProposalHandler, shardDataHandlers, err := sic.createShardDataFromV3Header(header, []byte("headerHash")) + require.Nil(t, err) + require.NotNil(t, shardDataHandlers) + require.Equal(t, 1, len(shardDataHandlers)) + require.Equal(t, expectedNonce, shardDataHandlers[0].GetNonce()) + require.NotNil(t, shardDataProposalHandler) + require.Equal(t, int(header.GetNonce()), int(shardDataProposalHandler.GetNonce())) + }) +} + +func TestShardInfoCreateData_createShardDataFromExecutionResult(t *testing.T) { + t.Parallel() + + t.Run("should fail with nil execution result", func(t *testing.T) { + t.Parallel() + + args := createDefaultShardInfoCreateDataArgs() + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + + shardData, err := sic.createShardDataFromExecutionResult(nil, 0) + require.ErrorIs(t, err, process.ErrNilExecutionResultHandler) + require.Nil(t, shardData) + }) + + t.Run("should fail with wrong type of execution result", func(t *testing.T) { + t.Parallel() + + args := createDefaultShardInfoCreateDataArgs() + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + + shardData, err := sic.createShardDataFromExecutionResult(&block.BaseExecutionResult{}, 0) + require.ErrorIs(t, err, process.ErrWrongTypeAssertion) + require.Nil(t, shardData) + }) + + t.Run("should fail when getting header from pool and storage fails", func(t *testing.T) { + t.Parallel() + args := createDefaultShardInfoCreateDataArgs() + args.HeadersPool = &poolMock.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, fmt.Errorf("GetHeaderByHash error") + }, + } + args.Storage = &mockStorage.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &mockStorage.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + return nil, fmt.Errorf("GetHeaderByHash error from storage") + }, + }, nil + }, + } + + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + + execResult := getExecutionResultForShard(uint32(1), []byte("header hash for shard 1")) + shardData, err := sic.createShardDataFromExecutionResult(execResult, uint32(1)) + require.ErrorIs(t, err, process.ErrMissingHeader) + require.Nil(t, shardData) + }) + + t.Run("should fail when updateShardDataWithCrossShardInfo fails", func(t *testing.T) { + t.Parallel() + + args := createDefaultShardInfoCreateDataArgs() + args.HeadersPool = &poolMock.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return getShardHeaderForShard(uint32(1)), nil + }, + } + + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + + execResult := getExecutionResultForShard(uint32(1), []byte("header hash for shard 1")) + shardData, err := sic.createShardDataFromExecutionResult(execResult, uint32(1)) + require.Equal(t, process.ErrNotarizedHeadersSliceIsNil, err) + require.Nil(t, shardData) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + expectedNonce := uint64(1) + header := getShardHeaderForShard(uint32(1)) + + args := createDefaultShardInfoCreateDataArgs() + args.HeadersPool = &poolMock.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return header, nil + }, + } + args.PendingMiniBlocksHandler = &mock.PendingMiniBlocksHandlerStub{ + GetPendingMiniBlocksCalled: func(shardID uint32) [][]byte { + return [][]byte{[]byte("hash1"), []byte("hash2")} + }, + } + args.BlockTracker = &mock.BlockTrackerMock{ + GetLastSelfNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.Header{Nonce: expectedNonce}, []byte("selfNotarizedHash"), nil + }, + } + + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + + execResult := getExecutionResultForShard(uint32(1), []byte("header hash for shard 1")) + shardData, err := sic.createShardDataFromExecutionResult(execResult, uint32(1)) + require.Nil(t, err) + require.NotNil(t, shardData) + require.Equal(t, uint32(2), shardData.GetNumPendingMiniBlocks()) + require.Equal(t, uint32(execResult.GetExecutedTxCount()), shardData.GetTxCount()) + require.Equal(t, uint32(1), shardData.GetShardID()) + require.Equal(t, execResult.GetAccumulatedFees(), shardData.GetAccumulatedFees()) + require.Equal(t, execResult.GetHeaderHash(), shardData.GetHeaderHash()) + require.Equal(t, execResult.GetHeaderRound(), shardData.GetRound()) + require.Equal(t, header.GetPrevHash(), shardData.GetPrevHash()) + require.Equal(t, execResult.GetHeaderNonce(), shardData.GetNonce()) + require.Equal(t, header.GetPrevRandSeed(), shardData.GetPrevRandSeed()) + require.Equal(t, header.GetPubKeysBitmap(), shardData.GetPubKeysBitmap()) + require.Equal(t, execResult.GetAccumulatedFees(), shardData.GetAccumulatedFees()) + require.Equal(t, execResult.GetDeveloperFees(), shardData.GetDeveloperFees()) + require.Equal(t, header.GetEpoch(), shardData.(*block.ShardData).GetEpoch()) + }) +} + +func TestShardInfoCreateData_miniBlockHeaderFromMiniBlockHeader(t *testing.T) { + t.Parallel() + + t.Run("ScheduledMiniBlocksFlag disabled", func(t *testing.T) { + t.Parallel() + headerHandler := getShardHeaderForShard(uint32(1)) + enableEpochsHandler := enableEpochsHandlerMock.NewEnableEpochsHandlerStub() + + miniblockHeaders := createShardMiniBlockHeaderFromHeader(headerHandler, enableEpochsHandler) + require.NotNil(t, miniblockHeaders) + require.Equal(t, 3, len(miniblockHeaders)) + checkMiniBlockHeadersComparable(t, headerHandler.GetMiniBlockHeaderHandlers()[0], miniblockHeaders[0]) + checkMiniBlockHeadersComparable(t, headerHandler.GetMiniBlockHeaderHandlers()[1], miniblockHeaders[1]) + checkMiniBlockHeadersComparable(t, headerHandler.GetMiniBlockHeaderHandlers()[2], miniblockHeaders[2]) + }) + t.Run("ScheduledMiniBlocksFlag enabled, all miniblocks final", func(t *testing.T) { + t.Parallel() + headerHandler := getShardHeaderForShard(uint32(1)) + enableEpochsHandler := enableEpochsHandlerMock.NewEnableEpochsHandlerStub() + enableEpochsHandler.IsFlagEnabledCalled = func(flag core.EnableEpochFlag) bool { + return flag == common.ScheduledMiniBlocksFlag + } + + miniblockHeaders := createShardMiniBlockHeaderFromHeader(headerHandler, enableEpochsHandler) + require.NotNil(t, miniblockHeaders) + require.Equal(t, 3, len(miniblockHeaders)) + checkMiniBlockHeadersComparable(t, headerHandler.GetMiniBlockHeaderHandlers()[0], miniblockHeaders[0]) + checkMiniBlockHeadersComparable(t, headerHandler.GetMiniBlockHeaderHandlers()[1], miniblockHeaders[1]) + checkMiniBlockHeadersComparable(t, headerHandler.GetMiniBlockHeaderHandlers()[2], miniblockHeaders[2]) + }) + t.Run("ScheduledMiniBlocksFlag enabled, not all miniblocks final", func(t *testing.T) { + t.Parallel() + headerHandler := getShardHeaderForShard(uint32(1)) + enableEpochsHandler := enableEpochsHandlerMock.NewEnableEpochsHandlerStub() + enableEpochsHandler.IsFlagEnabledCalled = func(flag core.EnableEpochFlag) bool { + return flag == common.ScheduledMiniBlocksFlag + } + _ = headerHandler.GetMiniBlockHeaderHandlers()[1].SetConstructionState(int32(block.Proposed)) + require.False(t, headerHandler.GetMiniBlockHeaderHandlers()[1].IsFinal()) + + miniblockHeaders := createShardMiniBlockHeaderFromHeader(headerHandler, enableEpochsHandler) + require.NotNil(t, miniblockHeaders) + require.Equal(t, 2, len(miniblockHeaders)) + checkMiniBlockHeadersComparable(t, headerHandler.GetMiniBlockHeaderHandlers()[0], miniblockHeaders[0]) + checkMiniBlockHeadersComparable(t, headerHandler.GetMiniBlockHeaderHandlers()[2], miniblockHeaders[1]) + }) + t.Run("ScheduledMiniBlocksFlag enabled, no final miniblocks", func(t *testing.T) { + t.Parallel() + headerHandler := getShardHeaderForShard(uint32(1)) + enableEpochsHandler := enableEpochsHandlerMock.NewEnableEpochsHandlerStub() + enableEpochsHandler.IsFlagEnabledCalled = func(flag core.EnableEpochFlag) bool { + return flag == common.ScheduledMiniBlocksFlag + } + for i := 0; i < len(headerHandler.GetMiniBlockHeaderHandlers()); i++ { + _ = headerHandler.GetMiniBlockHeaderHandlers()[i].SetConstructionState(int32(block.Proposed)) + require.False(t, headerHandler.GetMiniBlockHeaderHandlers()[i].IsFinal()) + } + + miniblockHeaders := createShardMiniBlockHeaderFromHeader(headerHandler, enableEpochsHandler) + require.NotNil(t, miniblockHeaders) + require.Equal(t, 0, len(miniblockHeaders)) + }) +} + +func TestShardInfoCreateData_createShardMiniBlockHeaderFromExecutionResultHandler(t *testing.T) { + t.Parallel() + + execResultHandler := getExecutionResultForShard(uint32(1), []byte("header hash for shard 1")) + shardMiniBlockHeaders := createShardMiniBlockHeaderFromExecutionResultHandler(execResultHandler) + require.NotNil(t, shardMiniBlockHeaders) + require.Equal(t, 3, len(shardMiniBlockHeaders)) + for i := 0; i < len(shardMiniBlockHeaders); i++ { + require.Equal(t, execResultHandler.MiniBlockHeaders[i].Hash, shardMiniBlockHeaders[i].Hash) + require.Equal(t, execResultHandler.MiniBlockHeaders[i].Type, shardMiniBlockHeaders[i].Type) + require.Equal(t, execResultHandler.MiniBlockHeaders[i].TxCount, shardMiniBlockHeaders[i].TxCount) + require.Equal(t, execResultHandler.MiniBlockHeaders[i].SenderShardID, shardMiniBlockHeaders[i].SenderShardID) + require.Equal(t, execResultHandler.MiniBlockHeaders[i].ReceiverShardID, shardMiniBlockHeaders[i].ReceiverShardID) + } +} + +// TODO modify when the function is updated +func TestShardInfoCreateData_updateShardDataWithCrossShardInfo(t *testing.T) { + t.Parallel() + + t.Run("should fail with GetLastSelfNotarizedHeader error", func(t *testing.T) { + t.Parallel() + + header := block.Header{ShardID: 1} + shardData := &block.ShardData{} + + args := createDefaultShardInfoCreateDataArgs() + args.BlockTracker = &mock.BlockTrackerMock{ + GetLastSelfNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return nil, nil, fmt.Errorf("GetLastSelfNotarizedHeader error") + }, + } + + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + + err = sic.updateShardDataWithCrossShardInfo(shardData, &header) + require.Contains(t, err.Error(), "GetLastSelfNotarizedHeader error") + }) + + t.Run("should work with no data", func(t *testing.T) { + t.Parallel() + + header := block.Header{ShardID: 1} + shardData := &block.ShardData{} + expectedNonce := uint64(12345) + + args := createDefaultShardInfoCreateDataArgs() + args.PendingMiniBlocksHandler = &mock.PendingMiniBlocksHandlerStub{ + GetPendingMiniBlocksCalled: func(shardID uint32) [][]byte { + return [][]byte{[]byte("hash1"), []byte("hash2")} + }, + } + args.BlockTracker = &mock.BlockTrackerMock{ + GetLastSelfNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.Header{Nonce: expectedNonce}, []byte("selfNotarizedHash"), nil + }, + } + + sic, err := NewShardInfoCreateData(args) + require.Nil(t, err) + + err = sic.updateShardDataWithCrossShardInfo(shardData, &header) + require.NotNil(t, shardData) + require.Nil(t, err) + require.Equal(t, uint32(2), shardData.NumPendingMiniBlocks) + require.Equal(t, expectedNonce, shardData.LastIncludedMetaNonce) + }) +} + +func createDefaultShardInfoCreateDataArgs() ShardInfoCreateDataArgs { + return ShardInfoCreateDataArgs{ + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + HeadersPool: &poolMock.HeadersPoolStub{}, + ProofsPool: &dataRetrieverMock.ProofsPoolMock{}, + PendingMiniBlocksHandler: &mock.PendingMiniBlocksHandlerStub{}, + BlockTracker: &mock.BlockTrackerMock{}, + Storage: &mockStorage.ChainStorerStub{}, + Marshaller: &mock.MarshalizerMock{}, + } +} + +func getMiniBlockHeadersForShard(shardID uint32) []block.MiniBlockHeader { + mbHash1 := []byte(fmt.Sprintf("mb hash 1 for shard %d", shardID)) + mbHash2 := []byte(fmt.Sprintf("mb hash 2 for shard %d", shardID)) + mbHash3 := []byte(fmt.Sprintf("mb hash 3 for shard %d", shardID)) + + miniBlockHeader1 := block.MiniBlockHeader{ + Hash: mbHash1, + Type: block.TxBlock, + TxCount: 10, + SenderShardID: shardID, + ReceiverShardID: 2, + } + miniBlockHeader2 := block.MiniBlockHeader{ + Hash: mbHash2, + Type: block.InvalidBlock, + TxCount: 1, + SenderShardID: shardID, + ReceiverShardID: 2, + } + miniBlockHeader3 := block.MiniBlockHeader{ + Hash: mbHash3, + Type: block.SmartContractResultBlock, + TxCount: 5, + SenderShardID: shardID, + ReceiverShardID: 0, + } + + miniBlockHeaders := []block.MiniBlockHeader{miniBlockHeader1, miniBlockHeader2, miniBlockHeader3} + return miniBlockHeaders +} + +func getShardHeaderForShard(shardID uint32) data.HeaderHandler { + prevHash := []byte(fmt.Sprintf("prevHash for shard %d", shardID)) + prevRandSeed := []byte(fmt.Sprintf("prevRandSeed for shard %d", shardID)) + currRandSeed := []byte(fmt.Sprintf("currRandSeed for shard %d", shardID)) + return &block.HeaderV2{ + Header: &block.Header{ + Round: 10, + Nonce: 45, + ShardID: shardID, + PrevRandSeed: prevRandSeed, + RandSeed: currRandSeed, + PrevHash: prevHash, + MiniBlockHeaders: getMiniBlockHeadersForShard(shardID), + }, + } +} + +func getHeaderV3ForShard(shardID uint32, hash []byte) data.HeaderHandler { + prevHash := []byte(fmt.Sprintf("prevHash for shard %d", shardID)) + prevRandSeed := []byte(fmt.Sprintf("prevRandSeed for shard %d", shardID)) + currRandSeed := []byte(fmt.Sprintf("currRandSeed for shard %d", shardID)) + return &block.HeaderV3{ + Epoch: 7, + Round: 10, + Nonce: 45, + ShardID: shardID, + PrevRandSeed: prevRandSeed, + RandSeed: currRandSeed, + PrevHash: prevHash, + MiniBlockHeaders: getMiniBlockHeadersForShard(shardID), + ExecutionResults: []*block.ExecutionResult{getExecutionResultForShard(shardID, hash)}, + } +} + +func getExecutionResultForShard(shardID uint32, hash []byte) *block.ExecutionResult { + return &block.ExecutionResult{ + ExecutedTxCount: 100, + MiniBlockHeaders: getMiniBlockHeadersForShard(shardID), + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: hash, + HeaderNonce: 12345, + HeaderEpoch: 7, + HeaderRound: 15, + RootHash: []byte(fmt.Sprintf("root hash for shard %d", shardID)), + GasUsed: 5000, + }, + } +} + +// compares only the fields that are set in createShardMiniBlockHeaderFromExecutionResultHandler +func checkMiniBlockHeadersComparable(t *testing.T, mbHeader1 data.MiniBlockHeaderHandler, mbHeader2 block.MiniBlockHeader) { + require.Equal(t, mbHeader1.GetHash(), mbHeader2.Hash) + require.Equal(t, block.Type(mbHeader1.GetTypeInt32()), mbHeader2.Type) + require.Equal(t, mbHeader1.GetTxCount(), mbHeader2.TxCount) + require.Equal(t, mbHeader1.GetSenderShardID(), mbHeader2.SenderShardID) + require.Equal(t, mbHeader1.GetReceiverShardID(), mbHeader2.ReceiverShardID) +} diff --git a/process/common.go b/process/common.go index 198645ea6ab..dddbde0e2ec 100644 --- a/process/common.go +++ b/process/common.go @@ -3,6 +3,7 @@ package process import ( "bytes" "encoding/hex" + "errors" "fmt" "math" "math/big" @@ -18,9 +19,11 @@ import ( "github.com/multiversx/mx-chain-core-go/data/typeConverters" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/storage" logger "github.com/multiversx/mx-chain-logger-go" vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/state" ) @@ -88,7 +91,7 @@ func GetMetaHeader( headersCacher dataRetriever.HeadersPool, marshalizer marshal.Marshalizer, storageService dataRetriever.StorageService, -) (*block.MetaBlock, error) { +) (data.MetaHeaderHandler, error) { err := checkGetHeaderParamsForNil(headersCacher, marshalizer, storageService) if err != nil { @@ -129,14 +132,14 @@ func GetShardHeaderFromPool( func GetMetaHeaderFromPool( hash []byte, headersCacher dataRetriever.HeadersPool, -) (*block.MetaBlock, error) { +) (data.MetaHeaderHandler, error) { obj, err := getHeaderFromPool(hash, headersCacher) if err != nil { return nil, err } - hdr, ok := obj.(*block.MetaBlock) + hdr, ok := obj.(data.MetaHeaderHandler) if !ok { return nil, ErrWrongTypeAssertion } @@ -182,15 +185,13 @@ func GetMetaHeaderFromStorage( hash []byte, marshalizer marshal.Marshalizer, storageService dataRetriever.StorageService, -) (*block.MetaBlock, error) { - +) (data.MetaHeaderHandler, error) { buffHdr, err := GetMarshalizedHeaderFromStorage(dataRetriever.MetaBlockUnit, hash, marshalizer, storageService) if err != nil { return nil, err } - hdr := &block.MetaBlock{} - err = marshalizer.Unmarshal(hdr, buffHdr) + hdr, err := UnmarshalMetaHeader(marshalizer, buffHdr) if err != nil { return nil, ErrUnmarshalWithoutSuccess } @@ -260,7 +261,7 @@ func GetMetaHeaderWithNonce( marshalizer marshal.Marshalizer, storageService dataRetriever.StorageService, uint64Converter typeConverters.Uint64ByteSliceConverter, -) (*block.MetaBlock, []byte, error) { +) (data.MetaHeaderHandler, []byte, error) { err := checkGetHeaderWithNonceParamsForNil(headersCacher, marshalizer, storageService, uint64Converter) if err != nil { @@ -302,14 +303,14 @@ func GetShardHeaderFromPoolWithNonce( func GetMetaHeaderFromPoolWithNonce( nonce uint64, headersCacher dataRetriever.HeadersPool, -) (*block.MetaBlock, []byte, error) { +) (data.MetaHeaderHandler, []byte, error) { obj, hash, err := getHeaderFromPoolWithNonce(nonce, core.MetachainShardId, headersCacher) if err != nil { return nil, nil, err } - hdr, ok := obj.(*block.MetaBlock) + hdr, ok := obj.(data.MetaHeaderHandler) if !ok { return nil, nil, ErrWrongTypeAssertion } @@ -365,8 +366,7 @@ func GetMetaHeaderFromStorageWithNonce( storageService dataRetriever.StorageService, uint64Converter typeConverters.Uint64ByteSliceConverter, marshalizer marshal.Marshalizer, -) (*block.MetaBlock, []byte, error) { - +) (data.MetaHeaderHandler, []byte, error) { hash, err := GetHeaderHashFromStorageWithNonce( nonce, storageService, @@ -566,7 +566,7 @@ func checkGetTransactionParamsForNil( func getHeaderFromPool( hash []byte, headersCacher dataRetriever.HeadersPool, -) (interface{}, error) { +) (data.HeaderHandler, error) { if check.IfNil(headersCacher) { return nil, ErrNilCacher @@ -790,19 +790,77 @@ func GetHeader( return GetShardHeader(headerHash, headersPool, marshaller, headersStorer) } +// UnmarshalExecutionResult unmarshalls an execution result +func UnmarshalExecutionResult(marshaller marshal.Marshalizer, executionResultsBytes []byte) (data.ExecutionResultHandler, error) { + executionResult, err := UnmarshalShardExecutionResult(marshaller, executionResultsBytes) + if err == nil { + return executionResult, nil + } + + return UnmarshallMetaExecutionResult(marshaller, executionResultsBytes) +} + +// UnmarshalShardExecutionResult unmarshalls a shard execution result +func UnmarshalShardExecutionResult(marshaller marshal.Marshalizer, executionResultsBytes []byte) (data.ExecutionResultHandler, error) { + executionResult := &block.ExecutionResult{} + err := marshaller.Unmarshal(executionResult, executionResultsBytes) + if err != nil { + return nil, err + } + + return executionResult, nil +} + +// UnmarshallMetaExecutionResult unmarshalls a meta execution result +func UnmarshallMetaExecutionResult(marshaller marshal.Marshalizer, executionResultsBytes []byte) (data.MetaExecutionResultHandler, error) { + executionResult := &block.MetaExecutionResult{} + err := marshaller.Unmarshal(executionResult, executionResultsBytes) + if err != nil { + return nil, err + } + + return executionResult, nil +} + // UnmarshalHeader unmarshalls a block header -func UnmarshalHeader(shardId uint32, marshalizer marshal.Marshalizer, headerBuffer []byte) (data.HeaderHandler, error) { +func UnmarshalHeader(shardId uint32, marshaller marshal.Marshalizer, headerBuffer []byte) (data.HeaderHandler, error) { if shardId == core.MetachainShardId { - return UnmarshalMetaHeader(marshalizer, headerBuffer) + return UnmarshalMetaHeader(marshaller, headerBuffer) } else { - return UnmarshalShardHeader(marshalizer, headerBuffer) + return UnmarshalShardHeader(marshaller, headerBuffer) } } // UnmarshalMetaHeader unmarshalls a meta header -func UnmarshalMetaHeader(marshalizer marshal.Marshalizer, headerBuffer []byte) (data.MetaHeaderHandler, error) { +func UnmarshalMetaHeader(marshaller marshal.Marshalizer, headerBuffer []byte) (data.MetaHeaderHandler, error) { + hdr, err := UnmarshalMetaHeaderV3(marshaller, headerBuffer) + if err == nil { + return hdr, nil + } + + return UnmarshalMetaHeaderV1(marshaller, headerBuffer) +} + +// UnmarshalMetaHeaderV3 unmarshalls a meta header v3 +func UnmarshalMetaHeaderV3(marshaller marshal.Marshalizer, headerBuffer []byte) (data.MetaHeaderHandler, error) { + header := &block.MetaBlockV3{} + err := marshaller.Unmarshal(header, headerBuffer) + if err != nil { + return nil, err + } + + // this should not be nil for meta header v3 + if header.GetLastExecutionResult() == nil { + return nil, ErrInvalidHeader + } + + return header, nil +} + +// UnmarshalMetaHeaderV1 unmarshalls a meta header v1 +func UnmarshalMetaHeaderV1(marshaller marshal.Marshalizer, headerBuffer []byte) (data.MetaHeaderHandler, error) { header := &block.MetaBlock{} - err := marshalizer.Unmarshal(header, headerBuffer) + err := marshaller.Unmarshal(header, headerBuffer) if err != nil { return nil, err } @@ -811,14 +869,34 @@ func UnmarshalMetaHeader(marshalizer marshal.Marshalizer, headerBuffer []byte) ( } // UnmarshalShardHeader unmarshalls a shard header -func UnmarshalShardHeader(marshalizer marshal.Marshalizer, hdrBuff []byte) (data.ShardHeaderHandler, error) { - hdr, err := UnmarshalShardHeaderV2(marshalizer, hdrBuff) +func UnmarshalShardHeader(marshaller marshal.Marshalizer, hdrBuff []byte) (data.ShardHeaderHandler, error) { + hdr, err := UnmarshalShardHeaderV3(marshaller, hdrBuff) + if err == nil { + return hdr, nil + } + + hdr, err = UnmarshalShardHeaderV2(marshaller, hdrBuff) if err == nil { return hdr, nil } - hdr, err = UnmarshalShardHeaderV1(marshalizer, hdrBuff) - return hdr, err + return UnmarshalShardHeaderV1(marshaller, hdrBuff) +} + +// UnmarshalShardHeaderV3 unmarshalls a header with version 3 +func UnmarshalShardHeaderV3(marshaller marshal.Marshalizer, hdrBuff []byte) (data.ShardHeaderHandler, error) { + hdrV3 := &block.HeaderV3{} + err := marshaller.Unmarshal(hdrV3, hdrBuff) + if err != nil { + return nil, err + } + + // this should not be nil for shard header v3 + if hdrV3.GetLastExecutionResult() == nil { + return nil, ErrInvalidHeader + } + + return hdrV3, nil } // UnmarshalShardHeaderV2 unmarshalls a header with version 2 @@ -925,6 +1003,21 @@ func GetMiniBlockHeaderWithHash(header data.HeaderHandler, miniBlockHash []byte) return miniBlockHeader } } + + for _, execResult := range header.GetExecutionResultsHandlers() { + mbHeaders, err := common.GetMiniBlocksHeaderHandlersFromExecResult(execResult) + if err != nil { + log.Warn("GetMiniBlockHeaderWithHash", "error", err.Error()) + continue + } + + for _, mbHeader := range mbHeaders { + if bytes.Equal(mbHeader.GetHash(), miniBlockHash) { + return mbHeader + } + } + } + return nil } @@ -963,3 +1056,405 @@ func CheckIfIndexesAreOutOfBound( return nil } + +// SetBaseExecutionResult sets the last notarized base execution result in the execution results tracker +func SetBaseExecutionResult(executionManager ExecutionManager, blockChain data.ChainHandler) error { + if check.IfNil(blockChain) { + return ErrNilBlockChain + } + if check.IfNil(executionManager) { + return ErrNilExecutionManager + } + + currentBlock := blockChain.GetCurrentBlockHeader() + if currentBlock == nil || !currentBlock.IsHeaderV3() { + return nil + } + + lastNotarizedResult := currentBlock.GetLastExecutionResultHandler() + if check.IfNil(lastNotarizedResult) { + return ErrNilLastExecutionResultHandler + } + + var lastBaseExecutionResult data.BaseExecutionResultHandler + switch lastNotarizedBaseResult := lastNotarizedResult.(type) { + case data.LastShardExecutionResultHandler: + lastBaseExecutionResult = lastNotarizedBaseResult.GetExecutionResultHandler() + case data.LastMetaExecutionResultHandler: + lastBaseExecutionResult = lastNotarizedBaseResult.GetExecutionResultHandler() + default: + return ErrWrongTypeAssertion + } + + if check.IfNil(lastBaseExecutionResult) { + return ErrNilBaseExecutionResult + } + + return executionManager.SetLastNotarizedResult(lastBaseExecutionResult) +} + +// SeparateBodyByType creates a map of bodies according to type +func SeparateBodyByType(body *block.Body) map[block.Type]*block.Body { + separatedBodies := make(map[block.Type]*block.Body) + for i := 0; i < len(body.MiniBlocks); i++ { + mb := body.MiniBlocks[i] + + separatedMbType := mb.Type + if mb.Type == block.InvalidBlock { + separatedMbType = block.TxBlock + } + + if _, ok := separatedBodies[separatedMbType]; !ok { + separatedBodies[separatedMbType] = &block.Body{} + } + + separatedBodies[separatedMbType].MiniBlocks = append(separatedBodies[separatedMbType].MiniBlocks, mb) + } + + return separatedBodies +} + +// GetPrevBlockLastExecutionResult gets the last execution result from the previous block +func GetPrevBlockLastExecutionResult(blockChain data.ChainHandler) (data.LastExecutionResultHandler, error) { + if check.IfNil(blockChain) { + return nil, ErrNilBlockChain + } + + prevHeader := blockChain.GetCurrentBlockHeader() + prevHeaderHash := blockChain.GetCurrentBlockHeaderHash() + if check.IfNil(prevHeader) || len(prevHeaderHash) == 0 { + prevHeader = blockChain.GetGenesisHeader() + prevHeaderHash = blockChain.GetGenesisHeaderHash() + + if check.IfNil(prevHeader) || len(prevHeaderHash) == 0 { + return nil, ErrNilHeaderHandler + } + } + + if prevHeader.IsHeaderV3() { + return prevHeader.GetLastExecutionResultHandler(), nil + } + + return common.CreateLastExecutionResultFromPrevHeader(prevHeader, prevHeaderHash) +} + +// CreateLastExecutionResultInfoFromExecutionResult creates a LastExecutionResultInfo object from the given execution result +func CreateLastExecutionResultInfoFromExecutionResult(notarizedInRound uint64, lastExecResult data.BaseExecutionResultHandler, shardID uint32) (data.LastExecutionResultHandler, error) { + if check.IfNil(lastExecResult) { + return nil, ErrNilExecutionResultHandler + } + + if shardID != core.MetachainShardId { + if _, ok := lastExecResult.(*block.ExecutionResult); !ok { + return nil, ErrWrongTypeAssertion + } + + return &block.ExecutionResultInfo{ + NotarizedInRound: notarizedInRound, + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: lastExecResult.GetHeaderHash(), + HeaderNonce: lastExecResult.GetHeaderNonce(), + HeaderRound: lastExecResult.GetHeaderRound(), + HeaderEpoch: lastExecResult.GetHeaderEpoch(), + RootHash: lastExecResult.GetRootHash(), + GasUsed: lastExecResult.GetGasUsed(), + }, + }, nil + } + + lastMetaExecResult, ok := lastExecResult.(*block.MetaExecutionResult) + if !ok { + return nil, ErrWrongTypeAssertion + } + + return &block.MetaExecutionResultInfo{ + NotarizedInRound: notarizedInRound, + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: lastMetaExecResult.GetHeaderHash(), + HeaderNonce: lastMetaExecResult.GetHeaderNonce(), + HeaderRound: lastMetaExecResult.GetHeaderRound(), + HeaderEpoch: lastMetaExecResult.GetHeaderEpoch(), + RootHash: lastMetaExecResult.GetRootHash(), + GasUsed: lastMetaExecResult.GetGasUsed(), + }, + ValidatorStatsRootHash: lastMetaExecResult.GetValidatorStatsRootHash(), + AccumulatedFeesInEpoch: lastMetaExecResult.GetAccumulatedFeesInEpoch(), + DevFeesInEpoch: lastMetaExecResult.GetDevFeesInEpoch(), + }, + }, nil +} + +// CreateDataForInclusionEstimation creates the metadata needed for inclusion time estimation +func CreateDataForInclusionEstimation( + handler data.LastExecutionResultHandler, +) (*common.LastExecutionResultForInclusion, error) { + if check.IfNil(handler) { + return nil, ErrNilLastExecutionResultHandler + } + + var proposedInRound uint64 + var notarizedInRound uint64 + switch lastExecutionResult := handler.(type) { + case *block.ExecutionResultInfo: + if check.IfNil(lastExecutionResult.GetExecutionResult()) { + return nil, ErrNilBaseExecutionResult + } + notarizedInRound = lastExecutionResult.GetNotarizedInRound() + proposedInRound = lastExecutionResult.GetExecutionResult().HeaderRound + case *block.MetaExecutionResultInfo: + if check.IfNil(lastExecutionResult.GetExecutionResult()) { + return nil, ErrNilBaseExecutionResult + } + notarizedInRound = lastExecutionResult.GetNotarizedInRound() + proposedInRound = lastExecutionResult.GetExecutionResult().GetHeaderRound() + default: + return nil, ErrWrongTypeAssertion + } + + return &common.LastExecutionResultForInclusion{ + NotarizedInRound: notarizedInRound, + ProposedInRound: proposedInRound, + }, nil +} + +// IsNotExecutableTransactionError checks if the given error is related to a transaction which cannot be executed +// TODO: needs to be called for Supernova processing +func IsNotExecutableTransactionError(err error) bool { + return errors.Is(err, ErrLowerNonceInTransaction) || + errors.Is(err, ErrHigherNonceInTransaction) || + errors.Is(err, ErrInsufficientFee) || + errors.Is(err, ErrTransactionNotExecutable) +} + +// GetMarshaledSliceSize will return marshalled slice size for any slice +func GetMarshaledSliceSize[T any](items []T, marshaller marshal.Marshalizer) (int, error) { + size := 0 + + for i := range items { + d, err := marshaller.Marshal(items[i]) + if err != nil { + return 0, err + } + + size += len(d) + } + + return size, nil +} + +// GetDataPoolByBlockType returns data pool by type +func GetDataPoolByBlockType( + blockType block.Type, + dataPool dataRetriever.PoolsHolder, +) (dataRetriever.ShardedDataCacherNotifier, error) { + switch blockType { + case block.TxBlock, block.InvalidBlock: + return dataPool.Transactions(), nil + case block.SmartContractResultBlock: + return dataPool.UnsignedTransactions(), nil + case block.RewardsBlock: + return dataPool.RewardTransactions(), nil + case block.PeerBlock: + return dataPool.ValidatorsInfo(), nil + default: + return nil, fmt.Errorf("unsupported block type for dataPool: %d", blockType) + } +} + +// GetStorageUnitByBlockType returns storage by type +func GetStorageUnitByBlockType(blockType block.Type) (dataRetriever.UnitType, error) { + switch blockType { + case block.TxBlock, block.InvalidBlock: + return dataRetriever.TransactionUnit, nil + case block.SmartContractResultBlock: + return dataRetriever.UnsignedTransactionUnit, nil + case block.ReceiptBlock: + return dataRetriever.ReceiptsUnit, nil + case block.RewardsBlock: + return dataRetriever.RewardTransactionUnit, nil + case block.PeerBlock: + return dataRetriever.UnsignedTransactionUnit, nil + } + return 0, ErrInvalidBlockType +} + +// IsReplacementBlockForExecution returns true if the provided header is a replacement for the last execution result +func IsReplacementBlockForExecution(header data.HeaderHandler, headerHash []byte, lastExecutionResult data.BaseExecutionResultHandler) bool { + if check.IfNil(header) || check.IfNil(lastExecutionResult) { + return false + } + + sameNonce := header.GetNonce() == lastExecutionResult.GetHeaderNonce() + differentHash := !bytes.Equal(headerHash, lastExecutionResult.GetHeaderHash()) + return sameNonce && differentHash +} + +// UpdateContextForReplacedHeader updates the blockchain context when a header should be replaced +func UpdateContextForReplacedHeader( + header data.HeaderHandler, + executionManager ExecutionManager, + blockChain data.ChainHandler, + headersPool dataRetriever.HeadersPool, + postProcessTransactions storage.Cacher, + executedMiniBlocks storage.Cacher, + storageService dataRetriever.StorageService, + marshaller marshal.Marshalizer, + shardID uint32, +) error { + err := checkForNils(header, executionManager, blockChain, headersPool, storageService, marshaller) + if err != nil { + return err + } + + pendingExecutionResults, err := executionManager.GetPendingExecutionResults() + if err != nil { + return err + } + + lastExecutionResult, err := executionManager.GetLastNotarizedExecutionResult() + if err != nil { + return err + } + + executionResultToSet, err := getExecutionResultToSetOnReplacedHeader( + header, + pendingExecutionResults, + lastExecutionResult, + ) + if err != nil { + return err + } + + // TODO: optimize to add into pool at bootstrap + headerToSet, err := GetHeader(executionResultToSet.GetHeaderHash(), headersPool, storageService, marshaller, shardID) + if err != nil { + return err + } + + err = CleanCachesForExecutionResult(blockChain.GetLastExecutionResult(), postProcessTransactions, executedMiniBlocks) + if err != nil { + return err + } + + log.Debug("UpdateContextForReplacedHeader last executed header", + "round", headerToSet.GetRound(), + "nonce", headerToSet.GetNonce(), + "hash", executionResultToSet.GetHeaderHash()) + + blockChain.SetLastExecutedBlockHeaderAndRootHash(headerToSet, executionResultToSet.GetHeaderHash(), executionResultToSet.GetRootHash()) + blockChain.SetLastExecutionResult(executionResultToSet) + + // need to remove all execution results after the one set + err = executionManager.RemovePendingExecutionResultsFromNonce(executionResultToSet.GetHeaderNonce() + 1) + if err != nil { + return err + } + + log.Debug("UpdateContextForReplacedHeader finished", + "nonce", header.GetNonce(), + "old round", header.GetRound(), + "new round", headerToSet.GetRound(), + "new hash", executionResultToSet.GetHeaderHash(), + ) + + return nil +} + +// CleanCachesForExecutionResult cleans post-process transactions and executed mini blocks caches +func CleanCachesForExecutionResult( + execResult data.BaseExecutionResultHandler, + postProcessTxsCache storage.Cacher, + executedMbs storage.Cacher, +) error { + if check.IfNil(execResult) { + return ErrNilExecutionResultHandler + } + if check.IfNil(postProcessTxsCache) { + return ErrNilPostProcessTransactionsCache + } + if check.IfNil(executedMbs) { + return ErrNilExecutedMiniBlocksCache + } + + headerHash := execResult.GetHeaderHash() + // all transactions moved, cleaning the cache + postProcessTxsCache.Remove(headerHash) + // remove execution order data + postProcessTxsCache.Remove(common.PrepareOrderedTxHashesKey(headerHash)) + // remove cached log events + postProcessTxsCache.Remove(common.PrepareLogEventsKey(headerHash)) + // remove header gas data + postProcessTxsCache.Remove(common.PrepareHeaderGasDataKey(headerHash)) + + // remove headerHash from executed mini blocks + executedMbs.Remove(headerHash) + + // remove mini block headers from executed mini blocks + mbHeaders, err := common.GetMiniBlocksHeaderHandlersFromExecResult(execResult) + if err != nil { + return err + } + for _, mbHeader := range mbHeaders { + executedMbs.Remove(mbHeader.GetHash()) + } + + return nil +} + +func checkForNils( + header data.HeaderHandler, + executionManager ExecutionManager, + blockChain data.ChainHandler, + headersPool common.HeadersPool, + storage dataRetriever.StorageService, + marshaller marshal.Marshalizer, +) error { + if check.IfNil(header) { + return ErrNilHeaderHandler + } + if check.IfNil(executionManager) { + return ErrNilExecutionManager + } + if check.IfNil(blockChain) { + return ErrNilBlockChain + } + if check.IfNil(headersPool) { + return ErrNilHeadersDataPool + } + if check.IfNil(storage) { + return ErrNilStorageService + } + if check.IfNil(marshaller) { + return ErrNilMarshalizer + } + return nil +} + +func getExecutionResultToSetOnReplacedHeader( + header data.HeaderHandler, + pendingExecutionResults []data.BaseExecutionResultHandler, + lastNotarizedResult data.BaseExecutionResultHandler, +) (data.BaseExecutionResultHandler, error) { + prevNonce := header.GetNonce() - 1 + prevHash := header.GetPrevHash() + + headerHashToSet := lastNotarizedResult.GetHeaderHash() + executionResultToSet := lastNotarizedResult + if bytes.Equal(prevHash, headerHashToSet) { + return executionResultToSet, nil + } + + for i := len(pendingExecutionResults) - 1; i >= 0; i-- { + if pendingExecutionResults[i].GetHeaderNonce() <= prevNonce { + headerHashToSet = pendingExecutionResults[i].GetHeaderHash() + executionResultToSet = pendingExecutionResults[i] + break + } + } + if !bytes.Equal(prevHash, headerHashToSet) { + return nil, ErrExecutionResultNotFound + } + + return executionResultToSet, nil +} diff --git a/process/common_test.go b/process/common_test.go index b6e308ec3ab..e076d43090e 100644 --- a/process/common_test.go +++ b/process/common_test.go @@ -3,6 +3,7 @@ package process_test import ( "bytes" "errors" + "fmt" "math/big" "strings" "testing" @@ -15,6 +16,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" + "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/mock" @@ -24,6 +28,8 @@ import ( storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" ) +var errExpected = errors.New("expected error") + func TestGetShardHeaderShouldErrNilCacher(t *testing.T) { t.Parallel() @@ -152,9 +158,12 @@ func TestGetMetaHeaderShouldErrNilStorage(t *testing.T) { func TestGetMetaHeaderShouldGetHeaderFromPool(t *testing.T) { t.Parallel() - hash := []byte("X") + testGetMetaHeader(t, &block.MetaBlock{Nonce: 1}) + testGetMetaHeader(t, &block.MetaBlockV3{Nonce: 2}) +} - hdr := &block.MetaBlock{Nonce: 1} +func testGetMetaHeader(t *testing.T, hdr data.MetaHeaderHandler) { + hash := []byte("X") cacher := &mock.HeadersCacherStub{ GetHeaderByHashCalled: func(hash []byte) (handler data.HeaderHandler, e error) { return hdr, nil @@ -299,9 +308,12 @@ func TestGetMetaHeaderFromPoolShouldErrWrongTypeAssertion(t *testing.T) { func TestGetMetaHeaderFromPoolShouldWork(t *testing.T) { t.Parallel() - hash := []byte("X") + testGetMetaHeaderFromPool(t, &block.MetaBlock{Nonce: 10}) + testGetMetaHeaderFromPool(t, &block.MetaBlockV3{Nonce: 11}) +} - hdr := &block.MetaBlock{Nonce: 10} +func testGetMetaHeaderFromPool(t *testing.T, hdr data.MetaHeaderHandler) { + hash := []byte("X") cacher := &mock.HeadersCacherStub{ GetHeaderByHashCalled: func(hash []byte) (handler data.HeaderHandler, e error) { return hdr, nil @@ -372,17 +384,16 @@ func TestGetShardHeaderFromStorageShouldErrGetHeadersStorageReturnsErr(t *testin t.Parallel() hash := []byte("X") - expectedErr := errors.New("expected error") marshalizer := &mock.MarshalizerMock{} storageService := &storageStubs.ChainStorerStub{ GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { - return nil, expectedErr + return nil, errExpected }, } header, err := process.GetShardHeaderFromStorage(hash, marshalizer, storageService) assert.Nil(t, header) - assert.Equal(t, expectedErr, err) + assert.Equal(t, errExpected, err) } func TestGetShardHeaderFromStorageShouldErrMissingHeader(t *testing.T) { @@ -480,17 +491,16 @@ func TestGetMetaHeaderFromStorageShouldErrGetHeadersStorageReturnsErr(t *testing t.Parallel() hash := []byte("X") - expectedErr := errors.New("expected error") marshalizer := &mock.MarshalizerMock{} storageService := &storageStubs.ChainStorerStub{ GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { - return nil, expectedErr + return nil, errExpected }, } header, err := process.GetMetaHeaderFromStorage(hash, marshalizer, storageService) assert.Nil(t, header) - assert.Equal(t, expectedErr, err) + assert.Equal(t, errExpected, err) } func TestGetMetaHeaderFromStorageShouldErrMissingHeader(t *testing.T) { @@ -538,9 +548,13 @@ func TestGetMetaHeaderFromStorageShouldErrUnmarshalWithoutSuccess(t *testing.T) func TestGetMetaHeaderFromStorageShouldWork(t *testing.T) { t.Parallel() + testGetMetaHeaderFromStorage(t, &block.MetaBlock{Nonce: 1}) + testGetMetaHeaderFromStorage(t, &block.MetaBlockV3{Nonce: 2, LastExecutionResult: &block.MetaExecutionResultInfo{}}) +} + +func testGetMetaHeaderFromStorage(t *testing.T, hdr data.MetaHeaderHandler) { hash := []byte("X") - hdr := &block.MetaBlock{} marshalizer := &mock.MarshalizerMock{} storageService := &storageStubs.ChainStorerStub{ GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { @@ -588,17 +602,16 @@ func TestGetMarshalizedHeaderFromStorageShouldErrGetHeadersStorageReturnsErr(t * t.Parallel() hash := []byte("X") - expectedErr := errors.New("expected error") marshalizer := &mock.MarshalizerMock{} storageService := &storageStubs.ChainStorerStub{ GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { - return nil, expectedErr + return nil, errExpected }, } headerMarsh, err := process.GetMarshalizedHeaderFromStorage(dataRetriever.MetaBlockUnit, hash, marshalizer, storageService) assert.Nil(t, headerMarsh) - assert.Equal(t, expectedErr, err) + assert.Equal(t, errExpected, err) } func TestGetMarshalizedHeaderFromStorageShouldErrMissingHeader(t *testing.T) { @@ -934,11 +947,14 @@ func TestGetMetaHeaderWithNonceShouldGetHeaderFromPool(t *testing.T) { func TestGetMetaHeaderWithNonceShouldGetHeaderFromStorage(t *testing.T) { t.Parallel() + testGetMetaHeaderWithNonceShouldGetHeaderFromStorage(t, &block.MetaBlock{Nonce: 1}) + testGetMetaHeaderWithNonceShouldGetHeaderFromStorage(t, &block.MetaBlockV3{Nonce: 2}) +} + +func testGetMetaHeaderWithNonceShouldGetHeaderFromStorage(t *testing.T, hdr data.MetaHeaderHandler) { hash := []byte("X") - nonce := uint64(1) nonceToByte := []byte("1") - hdr := &block.MetaBlock{Nonce: nonce} cacher := &mock.HeadersCacherStub{ GetHeaderByNonceAndShardIdCalled: func(hdrNonce uint64, shardId uint32) (handlers []data.HeaderHandler, i [][]byte, e error) { return []data.HeaderHandler{hdr}, [][]byte{hash}, nil @@ -963,7 +979,7 @@ func TestGetMetaHeaderWithNonceShouldGetHeaderFromStorage(t *testing.T) { } uint64Converter := &mock.Uint64ByteSliceConverterMock{ ToByteSliceCalled: func(n uint64) []byte { - if n == nonce { + if n == hdr.GetNonce() { return nonceToByte } @@ -972,7 +988,7 @@ func TestGetMetaHeaderWithNonceShouldGetHeaderFromStorage(t *testing.T) { } header, _, _ := process.GetMetaHeaderWithNonce( - nonce, + hdr.GetNonce(), cacher, marshalizer, storageService, @@ -1152,17 +1168,20 @@ func TestGetMetaHeaderFromPoolWithNonceShouldErrWrongTypeAssertion(t *testing.T) func TestGetMetaHeaderFromPoolWithNonceShouldWork(t *testing.T) { t.Parallel() + testGetMetaHeaderFromPoolWithNonce(t, &block.MetaBlock{Nonce: 1}) + testGetMetaHeaderFromPoolWithNonce(t, &block.MetaBlockV3{Nonce: 2}) +} + +func testGetMetaHeaderFromPoolWithNonce(t *testing.T, hdr data.MetaHeaderHandler) { hash := []byte("X") - nonce := uint64(1) - hdr := &block.MetaBlock{Nonce: nonce} cacher := &mock.HeadersCacherStub{ GetHeaderByNonceAndShardIdCalled: func(hdrNonce uint64, shardId uint32) (handlers []data.HeaderHandler, i [][]byte, e error) { return []data.HeaderHandler{hdr}, [][]byte{hash}, nil }, } - header, headerHash, err := process.GetMetaHeaderFromPoolWithNonce(nonce, cacher) + header, headerHash, err := process.GetMetaHeaderFromPoolWithNonce(hdr.GetNonce(), cacher) assert.Nil(t, err) assert.Equal(t, hash, headerHash) assert.Equal(t, hdr, header) @@ -1236,10 +1255,9 @@ func TestGetShardHeaderFromStorageWithNonceShouldErrGetHeadersStorageReturnsErr( nonce := uint64(1) shardId := uint32(0) - expectedErr := errors.New("expected error") storageService := &storageStubs.ChainStorerStub{ GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { - return nil, expectedErr + return nil, errExpected }, } uint64Converter := &mock.Uint64ByteSliceConverterMock{} @@ -1254,7 +1272,7 @@ func TestGetShardHeaderFromStorageWithNonceShouldErrGetHeadersStorageReturnsErr( assert.Nil(t, header) assert.Nil(t, hash) - assert.Equal(t, expectedErr, err) + assert.Equal(t, errExpected, err) } func TestGetShardHeaderFromStorageWithNonceShouldErrMissingHashForHeaderNonce(t *testing.T) { @@ -1515,10 +1533,9 @@ func TestGetMetaHeaderFromStorageWithNonceShouldErrGetHeadersStorageReturnsErr(t t.Parallel() nonce := uint64(1) - expectedErr := errors.New("expected error") storageService := &storageStubs.ChainStorerStub{ GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { - return nil, expectedErr + return nil, errExpected }, } uint64Converter := &mock.Uint64ByteSliceConverterMock{} @@ -1532,7 +1549,7 @@ func TestGetMetaHeaderFromStorageWithNonceShouldErrGetHeadersStorageReturnsErr(t assert.Nil(t, header) assert.Nil(t, hash) - assert.Equal(t, expectedErr, err) + assert.Equal(t, errExpected, err) } func TestGetMetaHeaderFromStorageWithNonceShouldErrMissingHashForHeaderNonce(t *testing.T) { @@ -1649,10 +1666,13 @@ func TestGetMetaHeaderFromStorageWithNonceShouldErrUnmarshalWithoutSuccess(t *te func TestGetMetaHeaderFromStorageWithNonceShouldWork(t *testing.T) { t.Parallel() - nonce := uint64(1) + testGetMetaHeaderFromStorageWithNonce(t, &block.MetaBlock{Nonce: 1}) + testGetMetaHeaderFromStorageWithNonce(t, &block.MetaBlockV3{Nonce: 2, LastExecutionResult: &block.MetaExecutionResultInfo{}}) +} + +func testGetMetaHeaderFromStorageWithNonce(t *testing.T, hdr data.MetaHeaderHandler) { hash := []byte("X") nonceToByte := []byte("1") - hdr := &block.MetaBlock{Nonce: nonce} marshalizer := &mock.MarshalizerMock{} marshHdr, _ := marshalizer.Marshal(hdr) storageService := &storageStubs.ChainStorerStub{ @@ -1672,7 +1692,7 @@ func TestGetMetaHeaderFromStorageWithNonceShouldWork(t *testing.T) { } uint64Converter := &mock.Uint64ByteSliceConverterMock{ ToByteSliceCalled: func(n uint64) []byte { - if n == nonce { + if n == hdr.GetNonce() { return nonceToByte } @@ -1681,7 +1701,7 @@ func TestGetMetaHeaderFromStorageWithNonceShouldWork(t *testing.T) { } header, headerHash, err := process.GetMetaHeaderFromStorageWithNonce( - nonce, + hdr.GetNonce(), storageService, uint64Converter, marshalizer) @@ -2243,6 +2263,55 @@ func TestGetMiniBlockHeaderWithHash(t *testing.T) { mbh := process.GetMiniBlockHeaderWithHash(header, []byte(hash1)) assert.Equal(t, expectedMbh, mbh) }) + + t.Run("if extracting from execution results fails, nil should be returned", func(t *testing.T) { + t.Parallel() + + header := &block.MetaBlockV3{ + ExecutionResults: []*block.MetaExecutionResult{ + nil, + }, + } + + mbh := process.GetMiniBlockHeaderWithHash(header, []byte(hash1)) + assert.Nil(t, mbh) + }) + + t.Run("should find the mini block header in the execution results", func(t *testing.T) { + t.Parallel() + + expectedMbh := block.MiniBlockHeader{ + Hash: []byte(hash1), + } + header := &block.MetaBlockV3{ + ExecutionResults: []*block.MetaExecutionResult{ + { + MiniBlockHeaders: []block.MiniBlockHeader{expectedMbh}, + }, + }, + } + + mbh := process.GetMiniBlockHeaderWithHash(header, []byte(hash1)) + assert.Equal(t, &expectedMbh, mbh) + }) + + t.Run("should return nil in case the execution result is not found", func(t *testing.T) { + t.Parallel() + + expectedMbh := block.MiniBlockHeader{ + Hash: []byte(hash1), + } + header := &block.MetaBlockV3{ + ExecutionResults: []*block.MetaExecutionResult{ + { + MiniBlockHeaders: []block.MiniBlockHeader{expectedMbh}, + }, + }, + } + + mbh := process.GetMiniBlockHeaderWithHash(header, []byte("hashX")) + assert.Nil(t, mbh) + }) } func Test_IsBuiltinFuncCallWithParam(t *testing.T) { @@ -2320,11 +2389,15 @@ func TestUnmarshalHeader(t *testing.T) { shardHeaderV1 := &block.Header{Nonce: 42, EpochStartMetaHash: []byte{0xaa, 0xbb}} shardHeaderV2 := &block.HeaderV2{Header: &block.Header{Nonce: 43, EpochStartMetaHash: []byte{0xaa, 0xbb}}} + shardHeaderV3 := &block.HeaderV3{Nonce: 44, LastExecutionResult: &block.ExecutionResultInfo{NotarizedInRound: 100}} metaHeader := &block.MetaBlock{Nonce: 7, ValidatorStatsRootHash: []byte{0xcc, 0xdd}} + metaHeaderV3 := &block.MetaBlockV3{Nonce: 8, LastExecutionResult: &block.MetaExecutionResultInfo{NotarizedInRound: 100}} shardHeaderV1Buffer, _ := marshalizer.Marshal(shardHeaderV1) shardHeaderV2Buffer, _ := marshalizer.Marshal(shardHeaderV2) + shardHeaderV3Buffer, _ := marshalizer.Marshal(shardHeaderV3) metaHeaderBuffer, _ := marshalizer.Marshal(metaHeader) + metaHeaderV3Buffer, _ := marshalizer.Marshal(metaHeaderV3) t.Run("should work", func(t *testing.T) { t.Parallel() @@ -2337,9 +2410,17 @@ func TestUnmarshalHeader(t *testing.T) { assert.Nil(t, err) assert.Equal(t, shardHeaderV2, header) + header, err = process.UnmarshalHeader(1, marshalizer, shardHeaderV3Buffer) + assert.Nil(t, err) + assert.Equal(t, shardHeaderV3, header) + header, err = process.UnmarshalHeader(core.MetachainShardId, marshalizer, metaHeaderBuffer) assert.Nil(t, err) assert.Equal(t, metaHeader, header) + + header, err = process.UnmarshalHeader(core.MetachainShardId, marshalizer, metaHeaderV3Buffer) + assert.Nil(t, err) + assert.Equal(t, metaHeaderV3, header) }) t.Run("should err", func(t *testing.T) { @@ -2364,3 +2445,1370 @@ func TestShardedCacheSearchMethod_ToString(t *testing.T) { str := process.ShardedCacheSearchMethod(166).ToString() assert.Equal(t, "unknown method 166", str) } + +func Test_SetBaseExecutionResult(t *testing.T) { + t.Parallel() + + t.Run("nil execution manager should error", func(t *testing.T) { + t.Parallel() + err := process.SetBaseExecutionResult(nil, &testscommon.ChainHandlerStub{}) + require.Equal(t, process.ErrNilExecutionManager, err) + }) + + t.Run("nil chain handler should error", func(t *testing.T) { + t.Parallel() + err := process.SetBaseExecutionResult(&processMocks.ExecutionManagerMock{}, nil) + require.Equal(t, process.ErrNilBlockChain, err) + }) + + t.Run("execution manager error should error", func(t *testing.T) { + t.Parallel() + + executionManager := &processMocks.ExecutionManagerMock{ + SetLastNotarizedResultCalled: func(data.BaseExecutionResultHandler) error { + return errExpected + }, + } + + executionResultsInfo := &block.ExecutionResultInfo{ + NotarizedInRound: 100, + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash"), + HeaderNonce: 100, + HeaderRound: 100, + RootHash: []byte("root hash"), + }, + } + + header := &block.HeaderV3{ + Round: 100, + LastExecutionResult: executionResultsInfo, + } + chainHandler := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + } + + err := process.SetBaseExecutionResult(executionManager, chainHandler) + require.Equal(t, errExpected, err) + }) + + t.Run("chain handler returning nil should not set", func(t *testing.T) { + t.Parallel() + + chainHandler := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return nil + }, + } + executionManager := &processMocks.ExecutionManagerMock{ + SetLastNotarizedResultCalled: func(data.BaseExecutionResultHandler) error { + require.Fail(t, "should not be called") + return nil + }, + } + + err := process.SetBaseExecutionResult(executionManager, chainHandler) + require.Nil(t, err) + }) + + t.Run("chain handler returning header of different version should not set", func(t *testing.T) { + t.Parallel() + + chainHandler := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.Header{} + }, + } + executionManager := &processMocks.ExecutionManagerMock{ + SetLastNotarizedResultCalled: func(data.BaseExecutionResultHandler) error { + require.Fail(t, "should not be called") + return nil + }, + } + + err := process.SetBaseExecutionResult(executionManager, chainHandler) + require.Nil(t, err) + }) + t.Run("chain handler returning header without execution result should not set", func(t *testing.T) { + t.Parallel() + + header := &block.HeaderV3{} + chainHandler := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + } + executionManager := &processMocks.ExecutionManagerMock{ + SetLastNotarizedResultCalled: func(data.BaseExecutionResultHandler) error { + require.Fail(t, "should not be called") + return nil + }, + } + + err := process.SetBaseExecutionResult(executionManager, chainHandler) + require.Equal(t, process.ErrNilLastExecutionResultHandler, err) + }) + + t.Run("chain handler returning header with execution result but nil based execution result should not set", func(t *testing.T) { + t.Parallel() + + header := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{}, + } + chainHandler := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + } + executionManager := &processMocks.ExecutionManagerMock{ + SetLastNotarizedResultCalled: func(data.BaseExecutionResultHandler) error { + require.Fail(t, "should not be called") + return nil + }, + } + + err := process.SetBaseExecutionResult(executionManager, chainHandler) + require.Equal(t, process.ErrNilBaseExecutionResult, err) + }) + + t.Run("ok with shard header", func(t *testing.T) { + t.Parallel() + + executionResultsInfo := &block.ExecutionResultInfo{ + NotarizedInRound: 101, + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash"), + HeaderNonce: 100, + HeaderRound: 100, + RootHash: []byte("root hash"), + }, + } + + header := &block.HeaderV3{ + Round: 101, + LastExecutionResult: executionResultsInfo, + } + + called := false + executionManager := &processMocks.ExecutionManagerMock{ + SetLastNotarizedResultCalled: func(result data.BaseExecutionResultHandler) error { + require.Equal(t, executionResultsInfo.ExecutionResult, result) + called = true + return nil + }, + } + chainHandler := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + } + + err := process.SetBaseExecutionResult(executionManager, chainHandler) + require.Nil(t, err) + require.True(t, called) + }) + + t.Run("ok with meta header", func(t *testing.T) { + t.Parallel() + + executionResultsInfo := &block.MetaExecutionResultInfo{ + NotarizedInRound: 101, + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash"), + HeaderNonce: 100, + HeaderRound: 100, + RootHash: []byte("root hash"), + }, + }, + } + + header := &block.MetaBlockV3{ + Round: 101, + LastExecutionResult: executionResultsInfo, + } + + called := false + executionManager := &processMocks.ExecutionManagerMock{ + SetLastNotarizedResultCalled: func(result data.BaseExecutionResultHandler) error { + require.Equal(t, executionResultsInfo.ExecutionResult, result) + called = true + return nil + }, + } + chainHandler := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + } + + err := process.SetBaseExecutionResult(executionManager, chainHandler) + require.Nil(t, err) + require.True(t, called) + }) +} + +func TestTransactionCoordinator_SeparateBody(t *testing.T) { + t.Parallel() + + body := &block.Body{} + body.MiniBlocks = append(body.MiniBlocks, &block.MiniBlock{Type: block.TxBlock}) + body.MiniBlocks = append(body.MiniBlocks, &block.MiniBlock{Type: block.TxBlock}) + body.MiniBlocks = append(body.MiniBlocks, &block.MiniBlock{Type: block.TxBlock}) + body.MiniBlocks = append(body.MiniBlocks, &block.MiniBlock{Type: block.SmartContractResultBlock}) + body.MiniBlocks = append(body.MiniBlocks, &block.MiniBlock{Type: block.SmartContractResultBlock}) + body.MiniBlocks = append(body.MiniBlocks, &block.MiniBlock{Type: block.SmartContractResultBlock}) + body.MiniBlocks = append(body.MiniBlocks, &block.MiniBlock{Type: block.SmartContractResultBlock}) + body.MiniBlocks = append(body.MiniBlocks, &block.MiniBlock{Type: block.InvalidBlock}) // invalid blocks will go into the TxBlock bucket + body.MiniBlocks = append(body.MiniBlocks, &block.MiniBlock{Type: block.PeerBlock}) + body.MiniBlocks = append(body.MiniBlocks, &block.MiniBlock{Type: block.PeerBlock}) + body.MiniBlocks = append(body.MiniBlocks, &block.MiniBlock{Type: block.RewardsBlock}) + body.MiniBlocks = append(body.MiniBlocks, &block.MiniBlock{Type: block.RewardsBlock}) + body.MiniBlocks = append(body.MiniBlocks, &block.MiniBlock{Type: block.RewardsBlock}) + + separated := process.SeparateBodyByType(body) + require.Equal(t, 4, len(separated)) + require.Equal(t, 4, len(separated[block.TxBlock].MiniBlocks)) + require.Equal(t, 4, len(separated[block.SmartContractResultBlock].MiniBlocks)) + require.Equal(t, 2, len(separated[block.PeerBlock].MiniBlocks)) + require.Equal(t, 3, len(separated[block.RewardsBlock].MiniBlocks)) +} + +func Test_CreateLastExecutionResultInfoFromExecutionResult(t *testing.T) { + t.Parallel() + + notarizedInRound := uint64(1) + t.Run("nil executionResult", func(t *testing.T) { + t.Parallel() + + lastExecutionResultInfo, err := process.CreateLastExecutionResultInfoFromExecutionResult(notarizedInRound, nil, 0) + require.Equal(t, process.ErrNilExecutionResultHandler, err) + require.Nil(t, lastExecutionResultInfo) + }) + t.Run("invalid shard executionResult type", func(t *testing.T) { + t.Parallel() + + executionResult := createDummyMetaExecutionResult() // This is a valid type, but we will use it incorrectly + lastExecutionResultInfo, err := process.CreateLastExecutionResultInfoFromExecutionResult(notarizedInRound, executionResult, 0) + require.Equal(t, process.ErrWrongTypeAssertion, err) + require.Nil(t, lastExecutionResultInfo) + }) + t.Run("valid executionResult for shard", func(t *testing.T) { + t.Parallel() + + executionResult := createDummyShardExecutionResult() + expectedLastExecutionResult := &block.ExecutionResultInfo{ + NotarizedInRound: notarizedInRound, + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: executionResult.GetHeaderHash(), + HeaderNonce: executionResult.GetHeaderNonce(), + HeaderRound: executionResult.GetHeaderRound(), + RootHash: executionResult.GetRootHash(), + GasUsed: executionResult.GetGasUsed(), + }, + } + + lastExecutionResultHandler, err := process.CreateLastExecutionResultInfoFromExecutionResult(notarizedInRound, executionResult, 0) + lastExecutionResultInfo := lastExecutionResultHandler.(*block.ExecutionResultInfo) + require.NoError(t, err) + require.NotNil(t, lastExecutionResultInfo) + require.Equal(t, expectedLastExecutionResult, lastExecutionResultInfo) + }) + t.Run("invalid metaChain executionResult", func(t *testing.T) { + t.Parallel() + + executionResult := createDummyShardExecutionResult() + lastExecutionResultHandler, err := process.CreateLastExecutionResultInfoFromExecutionResult(notarizedInRound, executionResult, core.MetachainShardId) + require.Equal(t, process.ErrWrongTypeAssertion, err) + require.Nil(t, lastExecutionResultHandler) + }) + t.Run("valid executionResult for metaChain", func(t *testing.T) { + t.Parallel() + + executionResult := createDummyMetaExecutionResult() + expectedLastExecutionResult := &block.MetaExecutionResultInfo{ + NotarizedInRound: notarizedInRound, + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: executionResult.GetHeaderHash(), + HeaderNonce: executionResult.GetHeaderNonce(), + HeaderRound: executionResult.GetHeaderRound(), + RootHash: executionResult.GetRootHash(), + GasUsed: executionResult.GetGasUsed(), + }, + ValidatorStatsRootHash: executionResult.GetValidatorStatsRootHash(), + AccumulatedFeesInEpoch: executionResult.GetAccumulatedFeesInEpoch(), + DevFeesInEpoch: executionResult.GetDevFeesInEpoch(), + }, + } + + lastExecutionResultHandler, err := process.CreateLastExecutionResultInfoFromExecutionResult(notarizedInRound, executionResult, core.MetachainShardId) + lastExecutionResultInfo, ok := lastExecutionResultHandler.(*block.MetaExecutionResultInfo) + require.True(t, ok) + require.NoError(t, err) + require.NotNil(t, lastExecutionResultInfo) + require.Equal(t, expectedLastExecutionResult, lastExecutionResultInfo) + }) +} + +func Test_GetPrevBlockLastExecutionResult(t *testing.T) { + t.Parallel() + + t.Run("nil blockchain", func(t *testing.T) { + t.Parallel() + + _, err := process.GetPrevBlockLastExecutionResult(nil) + require.Equal(t, process.ErrNilBlockChain, err) + }) + t.Run("nil current block header", func(t *testing.T) { + t.Parallel() + + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return nil + }, + } + _, err := process.GetPrevBlockLastExecutionResult(blockchain) + require.Equal(t, process.ErrNilHeaderHandler, err) + }) + t.Run("valid prev header - shard header v3", func(t *testing.T) { + t.Parallel() + + prevHeader := createShardHeaderV3WithExecutionResults() + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return prevHeader + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("prevHeaderHash") + }, + } + + lastExecutionResult, err := process.GetPrevBlockLastExecutionResult(blockchain) + require.NoError(t, err) + require.NotNil(t, lastExecutionResult) + require.Equal(t, prevHeader.LastExecutionResult, lastExecutionResult) + }) + t.Run("valid prev header - shard header v2", func(t *testing.T) { + t.Parallel() + + prevHeader := createDummyPrevShardHeaderV2() + prevHeaderHash := []byte("prevHeaderHash") + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return prevHeader + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return prevHeaderHash + }, + } + + lastExecutionResult, err := process.GetPrevBlockLastExecutionResult(blockchain) + require.NoError(t, err) + require.NotNil(t, lastExecutionResult) + expectedLastExecutionResult, _ := common.CreateLastExecutionResultFromPrevHeader(prevHeader, prevHeaderHash) + require.Equal(t, expectedLastExecutionResult, lastExecutionResult) + }) + t.Run("valid prev header - meta header v3", func(t *testing.T) { + t.Parallel() + + prevHeader := createMetaHeaderV3WithExecutionResults() + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return prevHeader + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("prevHeaderHash") + }, + } + + lastExecutionResult, err := process.GetPrevBlockLastExecutionResult(blockchain) + require.NoError(t, err) + require.NotNil(t, lastExecutionResult) + require.Equal(t, prevHeader.LastExecutionResult, lastExecutionResult) + }) + t.Run("valid prev header - meta header", func(t *testing.T) { + t.Parallel() + + prevHeader := createDummyPrevMetaHeader() + prevHeaderHash := []byte("prevHeaderHash") + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return prevHeader + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return prevHeaderHash + }, + } + + lastExecutionResult, err := process.GetPrevBlockLastExecutionResult(blockchain) + require.NoError(t, err) + require.NotNil(t, lastExecutionResult) + expectedLastExecutionResult, _ := common.CreateLastExecutionResultFromPrevHeader(prevHeader, prevHeaderHash) + require.Equal(t, expectedLastExecutionResult, lastExecutionResult) + }) +} + +func Test_CreateDataForInclusionEstimation(t *testing.T) { + t.Parallel() + + t.Run("nil last exec result handler", func(t *testing.T) { + t.Parallel() + + lastExecResForInclusion, err := process.CreateDataForInclusionEstimation(nil) + require.Equal(t, process.ErrNilLastExecutionResultHandler, err) + require.Nil(t, lastExecResForInclusion) + }) + t.Run("valid last execution result for shard", func(t *testing.T) { + t.Parallel() + + executionResult := createLastExecutionResultShard() + expectedLastExecResultForInclusion := &common.LastExecutionResultForInclusion{ + NotarizedInRound: executionResult.GetNotarizedInRound(), + ProposedInRound: executionResult.GetExecutionResult().GetHeaderRound(), + } + lastExecResForInclusion, err := process.CreateDataForInclusionEstimation(executionResult) + require.NoError(t, err) + require.NotNil(t, lastExecResForInclusion) + require.Equal(t, expectedLastExecResultForInclusion, lastExecResForInclusion) + }) + t.Run("valid last execution result for meta", func(t *testing.T) { + t.Parallel() + + executionResult := createLastExecutionResultMeta() + expectedLastExecResultForInclusion := &common.LastExecutionResultForInclusion{ + NotarizedInRound: executionResult.GetNotarizedInRound(), + ProposedInRound: executionResult.GetExecutionResult().GetHeaderRound(), + } + lastExecResForInclusion, err := process.CreateDataForInclusionEstimation(executionResult) + require.NoError(t, err) + require.NotNil(t, lastExecResForInclusion) + require.Equal(t, expectedLastExecResultForInclusion, lastExecResForInclusion) + }) + t.Run("invalid last execution result", func(t *testing.T) { + t.Parallel() + + executionResult := &block.ExecutionResult{} + lastExecResForInclusion, err := process.CreateDataForInclusionEstimation(executionResult) + require.Equal(t, process.ErrWrongTypeAssertion, err) + require.Nil(t, lastExecResForInclusion) + }) +} + +func Test_IsNotExecutableTransactionError(t *testing.T) { + t.Parallel() + + t.Run("nil error", func(t *testing.T) { + t.Parallel() + require.False(t, process.IsNotExecutableTransactionError(nil)) + }) + t.Run("lower nonce", func(t *testing.T) { + t.Parallel() + require.True(t, process.IsNotExecutableTransactionError(process.ErrLowerNonceInTransaction)) + }) + t.Run("higher nonce", func(t *testing.T) { + t.Parallel() + wrappedErr := fmt.Errorf("wrapping: %w", process.ErrHigherNonceInTransaction) + require.True(t, process.IsNotExecutableTransactionError(wrappedErr)) + }) + t.Run("insufficient fee", func(t *testing.T) { + t.Parallel() + require.True(t, process.IsNotExecutableTransactionError(process.ErrInsufficientFee)) + }) + t.Run("transaction is not executable", func(t *testing.T) { + t.Parallel() + require.True(t, process.IsNotExecutableTransactionError(process.ErrTransactionNotExecutable)) + }) + t.Run("different error", func(t *testing.T) { + t.Parallel() + require.False(t, process.IsNotExecutableTransactionError(errors.New("some other error"))) + }) +} + +func createDummyShardExecutionResult() *block.ExecutionResult { + return &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash"), + HeaderNonce: 1, + HeaderRound: 2, + GasUsed: 10000000, + RootHash: []byte("rootHash"), + }, + ReceiptsHash: []byte("receiptsHash"), + MiniBlockHeaders: nil, + DeveloperFees: big.NewInt(100), + AccumulatedFees: big.NewInt(200), + ExecutedTxCount: 100, + } +} + +func createDummyMetaExecutionResult() *block.MetaExecutionResult { + return &block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash"), + HeaderNonce: 1, + HeaderRound: 2, + GasUsed: 20000000, + RootHash: []byte("rootHash"), + }, + ValidatorStatsRootHash: []byte("validatorStatsRootHash"), + AccumulatedFeesInEpoch: big.NewInt(300), + DevFeesInEpoch: big.NewInt(400), + }, + ReceiptsHash: []byte("receiptsHash"), + DeveloperFees: big.NewInt(500), + AccumulatedFees: big.NewInt(600), + ExecutedTxCount: 200, + } +} + +func createDummyPrevShardHeaderV2() *block.HeaderV2 { + return &block.HeaderV2{ + Header: &block.Header{ + Nonce: 1, + Round: 2, + RootHash: []byte("prevRootHash"), + }, + } +} + +func createLastExecutionResultShard() *block.ExecutionResultInfo { + return &block.ExecutionResultInfo{ + NotarizedInRound: 3, + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash"), + HeaderNonce: 1, + HeaderRound: 2, + RootHash: []byte("rootHash"), + }, + } +} + +func createLastExecutionResultMeta() *block.MetaExecutionResultInfo { + return &block.MetaExecutionResultInfo{ + NotarizedInRound: 3, + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("headerHash"), + HeaderNonce: 1, + HeaderRound: 2, + RootHash: []byte("rootHash"), + }, + ValidatorStatsRootHash: []byte("validatorStatsRootHash"), + AccumulatedFeesInEpoch: big.NewInt(300), + DevFeesInEpoch: big.NewInt(400), + }, + } +} + +func createShardHeaderV3WithExecutionResults() *block.HeaderV3 { + executionResult := createDummyShardExecutionResult() + lastExecutionResult := createLastExecutionResultShard() + return &block.HeaderV3{ + PrevHash: []byte("prevHash"), + Nonce: 2, + Round: 3, + ShardID: 0, + ExecutionResults: []*block.ExecutionResult{executionResult}, + LastExecutionResult: lastExecutionResult, + } +} + +func createMetaHeaderV3WithExecutionResults() *block.MetaBlockV3 { + executionResults := createDummyMetaExecutionResult() + lastExecutionResult := createLastExecutionResultMeta() + return &block.MetaBlockV3{ + Nonce: 3, + Round: 4, + LastExecutionResult: lastExecutionResult, + ExecutionResults: []*block.MetaExecutionResult{executionResults}, + } +} + +func createDummyPrevMetaHeader() *block.MetaBlock { + return &block.MetaBlock{ + Nonce: 4, + Round: 5, + RootHash: []byte("prevRootHash"), + ValidatorStatsRootHash: []byte("prevValidatorStatsRootHash"), + DevFeesInEpoch: big.NewInt(300), + AccumulatedFeesInEpoch: big.NewInt(400), + } +} + +func Test_UpdateContextForReplacedHeader(t *testing.T) { + t.Parallel() + + t.Run("nil header should error", func(t *testing.T) { + t.Parallel() + + err := process.UpdateContextForReplacedHeader( + nil, + &processMocks.ExecutionManagerMock{}, + &testscommon.ChainHandlerStub{}, + &mock.HeadersCacherStub{}, + &cache.CacherStub{}, + &cache.CacherStub{}, + &storageStubs.ChainStorerStub{}, + &mock.MarshalizerMock{}, + 0, + ) + require.Equal(t, process.ErrNilHeaderHandler, err) + }) + + t.Run("nil executionManager should error", func(t *testing.T) { + t.Parallel() + + err := process.UpdateContextForReplacedHeader( + &block.HeaderV3{}, + nil, + &testscommon.ChainHandlerStub{}, + &mock.HeadersCacherStub{}, + &cache.CacherStub{}, + &cache.CacherStub{}, + &storageStubs.ChainStorerStub{}, + &mock.MarshalizerMock{}, + 0, + ) + require.Equal(t, process.ErrNilExecutionManager, err) + }) + + t.Run("nil blockChain should error", func(t *testing.T) { + t.Parallel() + + err := process.UpdateContextForReplacedHeader( + &block.HeaderV3{}, + &processMocks.ExecutionManagerMock{}, + nil, + &mock.HeadersCacherStub{}, + &cache.CacherStub{}, + &cache.CacherStub{}, + &storageStubs.ChainStorerStub{}, + &mock.MarshalizerMock{}, + 0, + ) + require.Equal(t, process.ErrNilBlockChain, err) + }) + + t.Run("nil headersPool should error", func(t *testing.T) { + t.Parallel() + + err := process.UpdateContextForReplacedHeader( + &block.HeaderV3{}, + &processMocks.ExecutionManagerMock{}, + &testscommon.ChainHandlerStub{}, + nil, + &cache.CacherStub{}, + &cache.CacherStub{}, + &storageStubs.ChainStorerStub{}, + &mock.MarshalizerMock{}, + 0, + ) + require.Equal(t, process.ErrNilHeadersDataPool, err) + }) + + t.Run("error on CleanCachesForExecutionResult due to postProcessTransaction should error", func(t *testing.T) { + t.Parallel() + + lastNotarizedResult := &block.BaseExecutionResult{ + HeaderHash: []byte("lastNotarizedHash"), + HeaderNonce: 5, + HeaderRound: 5, + RootHash: []byte("lastNotarizedRoot"), + } + + executionManager := &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{}, nil + }, + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return lastNotarizedResult, nil + }, + } + + header := &block.HeaderV3{ + Nonce: 6, + PrevHash: []byte("lastNotarizedHash"), + } + + headerToSet := &block.HeaderV3{ + Nonce: 5, + } + + headersPool := &mock.HeadersCacherStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, []byte("lastNotarizedHash")) { + return headerToSet, nil + } + return nil, errors.New("not found") + }, + } + + blockChain := &testscommon.ChainHandlerStub{ + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.ExecutionResult{ + BaseExecutionResult: lastNotarizedResult, + } + }, + } + + err := process.UpdateContextForReplacedHeader( + header, + executionManager, + blockChain, + headersPool, + nil, + &cache.CacherStub{}, + &storageStubs.ChainStorerStub{}, + &mock.MarshalizerMock{}, + 0, + ) + require.Equal(t, process.ErrNilPostProcessTransactionsCache, err) + }) + + t.Run("nil storage should error", func(t *testing.T) { + t.Parallel() + + err := process.UpdateContextForReplacedHeader( + &block.HeaderV3{}, + &processMocks.ExecutionManagerMock{}, + &testscommon.ChainHandlerStub{}, + &mock.HeadersCacherStub{}, + &cache.CacherStub{}, + &cache.CacherStub{}, + nil, + &mock.MarshalizerMock{}, + 0, + ) + require.Equal(t, process.ErrNilStorageService, err) + }) + + t.Run("nil marshaller should error", func(t *testing.T) { + t.Parallel() + + err := process.UpdateContextForReplacedHeader( + &block.HeaderV3{}, + &processMocks.ExecutionManagerMock{}, + &testscommon.ChainHandlerStub{}, + &mock.HeadersCacherStub{}, + &cache.CacherStub{}, + &cache.CacherStub{}, + &storageStubs.ChainStorerStub{}, + nil, + 0, + ) + require.Equal(t, process.ErrNilMarshalizer, err) + }) + + t.Run("error from GetPendingExecutionResults should error", func(t *testing.T) { + t.Parallel() + + executionManager := &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return nil, errExpected + }, + } + + err := process.UpdateContextForReplacedHeader( + &block.HeaderV3{Nonce: 10}, + executionManager, + &testscommon.ChainHandlerStub{}, + &mock.HeadersCacherStub{}, + &cache.CacherStub{}, + &cache.CacherStub{}, + &storageStubs.ChainStorerStub{}, + &mock.MarshalizerMock{}, + 0, + ) + require.Equal(t, errExpected, err) + }) + + t.Run("error from GetLastNotarizedExecutionResult should error", func(t *testing.T) { + t.Parallel() + + executionManager := &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{}, nil + }, + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return nil, errExpected + }, + } + + err := process.UpdateContextForReplacedHeader( + &block.HeaderV3{Nonce: 10}, + executionManager, + &testscommon.ChainHandlerStub{}, + &mock.HeadersCacherStub{}, + &cache.CacherStub{}, + &cache.CacherStub{}, + &storageStubs.ChainStorerStub{}, + &mock.MarshalizerMock{}, + 0, + ) + require.Equal(t, errExpected, err) + }) + + t.Run("error when execution result not found should error", func(t *testing.T) { + t.Parallel() + + lastNotarizedResult := &block.BaseExecutionResult{ + HeaderHash: []byte("lastNotarizedHash"), + HeaderNonce: 5, + HeaderRound: 5, + RootHash: []byte("lastNotarizedRoot"), + } + + executionManager := &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{}, nil + }, + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return lastNotarizedResult, nil + }, + } + + header := &block.HeaderV3{ + Nonce: 10, + PrevHash: []byte("differentPrevHash"), + } + + err := process.UpdateContextForReplacedHeader( + header, + executionManager, + &testscommon.ChainHandlerStub{}, + &mock.HeadersCacherStub{}, + &cache.CacherStub{}, + &cache.CacherStub{}, + &storageStubs.ChainStorerStub{}, + &mock.MarshalizerMock{}, + 0, + ) + require.Equal(t, process.ErrExecutionResultNotFound, err) + }) + + t.Run("error from GetHeader should error", func(t *testing.T) { + t.Parallel() + + lastNotarizedResult := &block.BaseExecutionResult{ + HeaderHash: []byte("lastNotarizedHash"), + HeaderNonce: 5, + HeaderRound: 5, + RootHash: []byte("lastNotarizedRoot"), + } + + executionManager := &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{}, nil + }, + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return lastNotarizedResult, nil + }, + } + + header := &block.HeaderV3{ + Nonce: 6, + PrevHash: []byte("lastNotarizedHash"), + } + + headersPool := &mock.HeadersCacherStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, errExpected + }, + } + + storageService := &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + return nil, errors.New("not found in storage") + }, + }, nil + }, + } + + err := process.UpdateContextForReplacedHeader( + header, + executionManager, + &testscommon.ChainHandlerStub{}, + headersPool, + &cache.CacherStub{}, + &cache.CacherStub{}, + storageService, + &mock.MarshalizerMock{}, + 0, + ) + require.NotNil(t, err) + }) + + t.Run("successful path with lastNotarizedResult", func(t *testing.T) { + t.Parallel() + + lastNotarizedResult := &block.BaseExecutionResult{ + HeaderHash: []byte("lastNotarizedHash"), + HeaderNonce: 5, + HeaderRound: 5, + RootHash: []byte("lastNotarizedRoot"), + } + + removePendingCalled := false + executionManager := &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{}, nil + }, + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return lastNotarizedResult, nil + }, + RemovePendingExecutionResultsFromNonceCalled: func(nonce uint64) error { + removePendingCalled = true + require.Equal(t, uint64(6), nonce) + return nil + }, + } + + header := &block.HeaderV3{ + Nonce: 6, + PrevHash: []byte("lastNotarizedHash"), + } + + headerToSet := &block.HeaderV3{ + Nonce: 5, + } + + setLastExecutedCalled := false + setLastExecutionResultCalled := false + blockChain := &testscommon.ChainHandlerStub{ + SetLastExecutedBlockHeaderAndRootHashCalled: func(hdr data.HeaderHandler, hash []byte, rootHash []byte) { + setLastExecutedCalled = true + require.Equal(t, headerToSet, hdr) + require.Equal(t, lastNotarizedResult.HeaderHash, hash) + require.Equal(t, lastNotarizedResult.RootHash, rootHash) + }, + SetLastExecutionResultCalled: func(executionResult data.BaseExecutionResultHandler) { + setLastExecutionResultCalled = true + require.Equal(t, lastNotarizedResult, executionResult) + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.ExecutionResult{ + BaseExecutionResult: lastNotarizedResult, + } + }, + } + + headersPool := &mock.HeadersCacherStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, []byte("lastNotarizedHash")) { + return headerToSet, nil + } + return nil, errors.New("not found") + }, + } + + err := process.UpdateContextForReplacedHeader( + header, + executionManager, + blockChain, + headersPool, + &cache.CacherStub{}, + &cache.CacherStub{}, + &storageStubs.ChainStorerStub{}, + &mock.MarshalizerMock{}, + 0, + ) + require.Nil(t, err) + require.True(t, setLastExecutedCalled) + require.True(t, setLastExecutionResultCalled) + require.True(t, removePendingCalled) + }) + + t.Run("successful path with pending execution result", func(t *testing.T) { + t.Parallel() + + lastNotarizedResult := &block.BaseExecutionResult{ + HeaderHash: []byte("lastNotarizedHash"), + HeaderNonce: 5, + HeaderRound: 5, + RootHash: []byte("lastNotarizedRoot"), + } + + pendingResult := &block.BaseExecutionResult{ + HeaderHash: []byte("pendingHash"), + HeaderNonce: 8, + HeaderRound: 8, + RootHash: []byte("pendingRoot"), + } + + removePendingCalled := false + executionManager := &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{pendingResult}, nil + }, + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return lastNotarizedResult, nil + }, + RemovePendingExecutionResultsFromNonceCalled: func(nonce uint64) error { + removePendingCalled = true + require.Equal(t, uint64(9), nonce) + return nil + }, + } + + header := &block.HeaderV3{ + Nonce: 9, + PrevHash: []byte("pendingHash"), + } + + headerToSet := &block.HeaderV3{ + Nonce: 8, + } + + setLastExecutedCalled := false + setLastExecutionResultCalled := false + blockChain := &testscommon.ChainHandlerStub{ + SetLastExecutedBlockHeaderAndRootHashCalled: func(hdr data.HeaderHandler, hash []byte, rootHash []byte) { + setLastExecutedCalled = true + require.Equal(t, headerToSet, hdr) + require.Equal(t, pendingResult.HeaderHash, hash) + require.Equal(t, pendingResult.RootHash, rootHash) + }, + SetLastExecutionResultCalled: func(executionResult data.BaseExecutionResultHandler) { + setLastExecutionResultCalled = true + require.Equal(t, pendingResult, executionResult) + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.ExecutionResult{ + BaseExecutionResult: lastNotarizedResult, + } + }, + } + + headersPool := &mock.HeadersCacherStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, []byte("pendingHash")) { + return headerToSet, nil + } + return nil, errors.New("not found") + }, + } + + err := process.UpdateContextForReplacedHeader( + header, + executionManager, + blockChain, + headersPool, + &cache.CacherStub{}, + &cache.CacherStub{}, + &storageStubs.ChainStorerStub{}, + &mock.MarshalizerMock{}, + 0, + ) + require.Nil(t, err) + require.True(t, setLastExecutedCalled) + require.True(t, setLastExecutionResultCalled) + require.True(t, removePendingCalled) + }) + + t.Run("error from RemovePendingExecutionResultsFromNonce should error", func(t *testing.T) { + t.Parallel() + + lastNotarizedResult := &block.BaseExecutionResult{ + HeaderHash: []byte("lastNotarizedHash"), + HeaderNonce: 5, + HeaderRound: 5, + RootHash: []byte("lastNotarizedRoot"), + } + + executionManager := &processMocks.ExecutionManagerMock{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{}, nil + }, + GetLastNotarizedExecutionResultCalled: func() (data.BaseExecutionResultHandler, error) { + return lastNotarizedResult, nil + }, + RemovePendingExecutionResultsFromNonceCalled: func(nonce uint64) error { + return errExpected + }, + } + + header := &block.HeaderV3{ + Nonce: 6, + PrevHash: []byte("lastNotarizedHash"), + } + + headerToSet := &block.HeaderV3{ + Nonce: 5, + } + + blockChain := &testscommon.ChainHandlerStub{ + SetLastExecutedBlockHeaderAndRootHashCalled: func(hdr data.HeaderHandler, hash []byte, rootHash []byte) { + }, + SetLastExecutionResultCalled: func(executionResult data.BaseExecutionResultHandler) { + }, + GetLastExecutionResultCalled: func() data.BaseExecutionResultHandler { + return &block.ExecutionResult{ + BaseExecutionResult: lastNotarizedResult, + } + }, + } + + headersPool := &mock.HeadersCacherStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if bytes.Equal(hash, []byte("lastNotarizedHash")) { + return headerToSet, nil + } + return nil, errors.New("not found") + }, + } + + err := process.UpdateContextForReplacedHeader( + header, + executionManager, + blockChain, + headersPool, + &cache.CacherStub{}, + &cache.CacherStub{}, + &storageStubs.ChainStorerStub{}, + &mock.MarshalizerMock{}, + 0, + ) + require.Equal(t, errExpected, err) + }) +} + +func TestCleanCachesForExecutionResult(t *testing.T) { + t.Parallel() + + t.Run("nil execution result should error", func(t *testing.T) { + t.Parallel() + + postProcessTxsCache := &cache.CacherStub{} + executedMbs := &cache.CacherStub{} + + err := process.CleanCachesForExecutionResult(nil, postProcessTxsCache, executedMbs) + require.Equal(t, process.ErrNilExecutionResultHandler, err) + }) + + t.Run("nil postProcessTxsCache should error", func(t *testing.T) { + t.Parallel() + + execResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash"), + }, + } + executedMbs := &cache.CacherStub{} + + err := process.CleanCachesForExecutionResult(execResult, nil, executedMbs) + require.Equal(t, process.ErrNilPostProcessTransactionsCache, err) + }) + + t.Run("nil executedMbs should error", func(t *testing.T) { + t.Parallel() + + execResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hash"), + }, + } + postProcessTxsCache := &cache.CacherStub{} + + err := process.CleanCachesForExecutionResult(execResult, postProcessTxsCache, nil) + require.Equal(t, process.ErrNilExecutedMiniBlocksCache, err) + }) + + t.Run("error from GetMiniBlocksHeaderHandlersFromExecResult should return error", func(t *testing.T) { + t.Parallel() + + // Create an execution result that doesn't implement the executionResultHandler interface properly + // This will cause GetMiniBlocksHeaderHandlersFromExecResult to return an error + execResult := &block.BaseExecutionResult{ + HeaderHash: []byte("hash"), + } + + postProcessTxsCache := &cache.CacherStub{} + executedMbs := &cache.CacherStub{} + + err := process.CleanCachesForExecutionResult(execResult, postProcessTxsCache, executedMbs) + require.NotNil(t, err) + require.ErrorIs(t, err, common.ErrWrongTypeAssertion) + }) + + t.Run("success with no mini block headers", func(t *testing.T) { + t.Parallel() + + headerHash := []byte("headerHash") + execResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: headerHash, + }, + MiniBlockHeaders: []block.MiniBlockHeader{}, + } + + postProcessRemovedKeys := make(map[string]struct{}) + postProcessTxsCache := &cache.CacherStub{ + RemoveCalled: func(key []byte) { + postProcessRemovedKeys[string(key)] = struct{}{} + }, + } + + executedMbsRemovedKeys := make(map[string]struct{}) + executedMbsCache := &cache.CacherStub{ + RemoveCalled: func(key []byte) { + executedMbsRemovedKeys[string(key)] = struct{}{} + }, + } + + err := process.CleanCachesForExecutionResult(execResult, postProcessTxsCache, executedMbsCache) + require.Nil(t, err) + + // Verify all expected keys were removed from postProcessTxsCache + expectedPostProcessKeys := []string{ + string(headerHash), + string(common.PrepareOrderedTxHashesKey(headerHash)), + string(common.PrepareLogEventsKey(headerHash)), + } + + for _, key := range expectedPostProcessKeys { + _, found := postProcessRemovedKeys[key] + require.True(t, found, fmt.Sprintf("key %s should have been removed from postProcessTxsCache", key)) + } + require.Equal(t, 4, len(postProcessRemovedKeys)) + + // Verify headerHash was removed from executedMbs + _, found := executedMbsRemovedKeys[string(headerHash)] + require.True(t, found, "headerHash should have been removed from executedMbs") + require.Equal(t, 1, len(executedMbsRemovedKeys)) + }) + + t.Run("success with mini block headers", func(t *testing.T) { + t.Parallel() + + headerHash := []byte("headerHash") + mbHash1 := []byte("mbHash1") + mbHash2 := []byte("mbHash2") + mbHash3 := []byte("mbHash3") + + execResult := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: headerHash, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + {Hash: mbHash1}, + {Hash: mbHash2}, + {Hash: mbHash3}, + }, + } + + postProcessRemovedKeys := make(map[string]struct{}) + postProcessTxsCache := &cache.CacherStub{ + RemoveCalled: func(key []byte) { + postProcessRemovedKeys[string(key)] = struct{}{} + }, + } + + executedMbsRemovedKeys := make(map[string]struct{}) + executedMbsCache := &cache.CacherStub{ + RemoveCalled: func(key []byte) { + executedMbsRemovedKeys[string(key)] = struct{}{} + }, + } + + err := process.CleanCachesForExecutionResult(execResult, postProcessTxsCache, executedMbsCache) + require.Nil(t, err) + + // Verify all expected keys were removed from postProcessTxsCache + expectedPostProcessKeys := []string{ + string(headerHash), + string(common.PrepareOrderedTxHashesKey(headerHash)), + string(common.PrepareLogEventsKey(headerHash)), + string(common.PrepareHeaderGasDataKey(headerHash)), + } + + for _, key := range expectedPostProcessKeys { + _, found := postProcessRemovedKeys[key] + require.True(t, found, fmt.Sprintf("key %s should have been removed from postProcessTxsCache", key)) + } + require.Equal(t, 4, len(postProcessRemovedKeys)) + + // Verify all miniblock headers and headerHash were removed from executedMbs + expectedExecutedMbsKeys := []string{ + string(headerHash), + string(mbHash1), + string(mbHash2), + string(mbHash3), + } + + for _, key := range expectedExecutedMbsKeys { + _, found := executedMbsRemovedKeys[key] + require.True(t, found, fmt.Sprintf("key %s should have been removed from executedMbs", key)) + } + require.Equal(t, 4, len(executedMbsRemovedKeys)) + }) + + t.Run("success with MetaExecutionResult and mini block headers", func(t *testing.T) { + t.Parallel() + + headerHash := []byte("metaHeaderHash") + mbHash1 := []byte("metaMbHash1") + mbHash2 := []byte("metaMbHash2") + + metaExecResult := &block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: headerHash, + }, + }, + MiniBlockHeaders: []block.MiniBlockHeader{ + {Hash: mbHash1}, + {Hash: mbHash2}, + }, + } + + postProcessRemovedKeys := make(map[string]struct{}) + postProcessTxsCache := &cache.CacherStub{ + RemoveCalled: func(key []byte) { + postProcessRemovedKeys[string(key)] = struct{}{} + }, + } + + executedMbsRemovedKeys := make(map[string]struct{}) + executedMbsCache := &cache.CacherStub{ + RemoveCalled: func(key []byte) { + executedMbsRemovedKeys[string(key)] = struct{}{} + }, + } + + err := process.CleanCachesForExecutionResult(metaExecResult, postProcessTxsCache, executedMbsCache) + require.Nil(t, err) + + // Verify all expected keys were removed from postProcessTxsCache + expectedPostProcessKeys := []string{ + string(headerHash), + string(common.PrepareOrderedTxHashesKey(headerHash)), + string(common.PrepareLogEventsKey(headerHash)), + string(common.PrepareHeaderGasDataKey(headerHash)), + } + + for _, key := range expectedPostProcessKeys { + _, found := postProcessRemovedKeys[key] + require.True(t, found, fmt.Sprintf("key %s should have been removed from postProcessTxsCache", key)) + } + require.Equal(t, 4, len(postProcessRemovedKeys)) + + // Verify headerHash and miniblock hashes were removed from executedMbs + expectedExecutedMbsKeys := []string{ + string(headerHash), + string(mbHash1), + string(mbHash2), + } + + for _, key := range expectedExecutedMbsKeys { + _, found := executedMbsRemovedKeys[key] + require.True(t, found, fmt.Sprintf("key %s should have been removed from executedMbs", key)) + } + require.Equal(t, 3, len(executedMbsRemovedKeys)) + }) +} diff --git a/process/constants.go b/process/constants.go index 8468feb302b..2740da14f5e 100644 --- a/process/constants.go +++ b/process/constants.go @@ -81,10 +81,6 @@ const MaxHeaderRequestsAllowed = 20 // committed block nonce, after which, node is considered himself not synced const NonceDifferenceWhenSynced = 0 -// MaxSyncWithErrorsAllowed defines the maximum allowed number of sync with errors, -// before a special action to be applied -const MaxSyncWithErrorsAllowed = 10 - // MaxHeadersToRequestInAdvance defines the maximum number of headers which will be requested in advance, // if they are missing const MaxHeadersToRequestInAdvance = 20 @@ -92,32 +88,9 @@ const MaxHeadersToRequestInAdvance = 20 // RoundModulusTrigger defines a round modulus on which a trigger for an action will be released const RoundModulusTrigger = 5 -// RoundModulusTriggerWhenSyncIsStuck defines a round modulus on which a trigger for an action when sync is stuck will be released -const RoundModulusTriggerWhenSyncIsStuck = 20 - -// MaxRoundsWithoutCommittedBlock defines the maximum rounds to wait for a new block to be committed, -// before a special action to be applied -const MaxRoundsWithoutCommittedBlock = 10 - // MinForkRound represents the minimum fork round set by a notarized header received const MinForkRound = uint64(0) -// MaxMetaNoncesBehind defines the maximum difference between the current meta block nonce and the processed meta block -// nonce before a shard is considered stuck -const MaxMetaNoncesBehind = 15 - -// MaxMetaNoncesBehindForGlobalStuck defines the maximum difference between the current meta block nonce and the processed -// meta block nonce for any shard, where the chain is considered stuck and enters recovery -const MaxMetaNoncesBehindForGlobalStuck = 30 - -// MaxShardNoncesBehind defines the maximum difference between the current shard block nonce and the last notarized -// shard block nonce by meta, before meta is considered stuck -const MaxShardNoncesBehind = 15 - -// MaxRoundsWithoutNewBlockReceived defines the maximum number of rounds to wait for a new block to be received, -// before a special action to be applied -const MaxRoundsWithoutNewBlockReceived = 10 - // MaxMetaHeadersAllowedInOneShardBlock defines the maximum number of meta headers allowed to be included in one shard block const MaxMetaHeadersAllowedInOneShardBlock = 50 @@ -136,11 +109,8 @@ const MaxHeadersToWhitelistInAdvance = 300 // nothing will be refunded to the sender const MaxGasFeeHigherFactorAccepted = 10 -// TxCacheSelectionGasRequested defines the maximum total gas for transactions that should be selected from the cache. -const TxCacheSelectionGasRequested = 10_000_000_000 - -// TxCacheSelectionMaxNumTxs defines the maximum number of transactions that should be selected from the cache. -const TxCacheSelectionMaxNumTxs = 30_000 +// TxCacheCleanupMaxNumTxs defines the maximum number of transactions that should be cleaned from the cache in one go. +const TxCacheCleanupMaxNumTxs = 30_000 -// TxCacheSelectionLoopMaximumDuration defines the maximum duration for the loop that selects transactions from the cache. -const TxCacheSelectionLoopMaximumDuration = 250 * time.Millisecond +// TxCacheCleanupLoopMaximumDuration defines the maximum duration for the loop that cleans transactions from the cache. +const TxCacheCleanupLoopMaximumDuration = 250 * time.Millisecond diff --git a/process/coordinator/blockDataRequest.go b/process/coordinator/blockDataRequest.go new file mode 100644 index 00000000000..5b61282f97c --- /dev/null +++ b/process/coordinator/blockDataRequest.go @@ -0,0 +1,299 @@ +package coordinator + +import ( + "fmt" + "sync" + "time" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/storage" + "github.com/multiversx/mx-chain-go/storage/cache" +) + +// BlockDataRequestArgs holds the arguments needed to create a BlockDataRequest +type BlockDataRequestArgs struct { + RequestHandler process.RequestHandler + MiniBlockPool storage.Cacher + PreProcessors process.PreProcessorsContainer + ShardCoordinator process.ShardCoordinator + EnableEpochsHandler common.EnableEpochsHandler +} + +// BlockDataRequest implements the BlockDataRequester interface +type BlockDataRequest struct { + mutRequestedTxs sync.RWMutex + requestedTxs map[block.Type]int + requestedItemsHandler process.TimeCacher + miniBlockPool storage.Cacher + preProcessors process.PreProcessorsContainer + shardCoordinator process.ShardCoordinator + enableEpochsHandler common.EnableEpochsHandler + requestHandler process.RequestHandler +} + +// NewBlockDataRequester creates a new instance of BlockDataRequest +func NewBlockDataRequester(args BlockDataRequestArgs) (*BlockDataRequest, error) { + if check.IfNil(args.RequestHandler) { + return nil, process.ErrNilRequestHandler + } + if check.IfNil(args.MiniBlockPool) { + return nil, process.ErrNilMiniBlockPool + } + if check.IfNil(args.PreProcessors) { + return nil, process.ErrNilPreProcessorsContainer + } + if check.IfNil(args.ShardCoordinator) { + return nil, process.ErrNilShardCoordinator + } + if check.IfNil(args.EnableEpochsHandler) { + return nil, process.ErrNilEnableEpochsHandler + } + + bdr := &BlockDataRequest{ + requestedTxs: make(map[block.Type]int), + preProcessors: args.PreProcessors, + miniBlockPool: args.MiniBlockPool, + shardCoordinator: args.ShardCoordinator, + enableEpochsHandler: args.EnableEpochsHandler, + requestHandler: args.RequestHandler, + } + + bdr.requestedItemsHandler = cache.NewTimeCache(common.MaxWaitingTimeToReceiveRequestedItem) + bdr.miniBlockPool.RegisterHandler(bdr.receivedMiniBlock, core.UniqueIdentifier()) + + return bdr, nil +} + +// Reset resets the internal state of BlockDataRequest +func (bdr *BlockDataRequest) Reset() { + bdr.initRequestedTxs() + bdr.requestedItemsHandler.Sweep() + + // clean up the preprocessors used only for missing data requests + preprocessors := bdr.preProcessors.Keys() + for _, preprocessorType := range preprocessors { + preprocessor, err := bdr.preProcessors.Get(preprocessorType) + if err != nil { + log.Warn("BlockDataRequest.Reset: GetPreProcessor", "type", preprocessorType, "error", err) + continue + } + preprocessor.CreateBlockStarted() + } +} + +// RequestBlockTransactions verifies missing transaction and requests them +func (bdr *BlockDataRequest) RequestBlockTransactions(body *block.Body) { + if check.IfNil(body) { + return + } + + separatedBodies := process.SeparateBodyByType(body) + + bdr.initRequestedTxs() + + wg := sync.WaitGroup{} + wg.Add(len(separatedBodies)) + + for key, value := range separatedBodies { + go func(blockType block.Type, blockBody *block.Body) { + preproc, err := bdr.preProcessors.Get(blockType) + if err != nil { + wg.Done() + return + } + requestedTxs := preproc.RequestBlockTransactions(blockBody) + + bdr.mutRequestedTxs.Lock() + bdr.requestedTxs[blockType] = requestedTxs + bdr.mutRequestedTxs.Unlock() + + wg.Done() + }(key, value) + } + + wg.Wait() +} + +// RequestMiniBlocksAndTransactions requests mini blocks and transactions if missing +func (bdr *BlockDataRequest) RequestMiniBlocksAndTransactions(header data.HeaderHandler) { + if check.IfNil(header) { + return + } + + finalCrossMiniBlockHashes := bdr.getFinalCrossMiniBlockHashes(header) + mbsInfo := make([]*data.MiniBlockInfo, 0, len(finalCrossMiniBlockHashes)) + for mbHash, senderShardID := range finalCrossMiniBlockHashes { + mbsInfo = append(mbsInfo, &data.MiniBlockInfo{ + Hash: []byte(mbHash), + SenderShardID: senderShardID, + Round: header.GetRound(), + }) + } + + bdr.requestMissingMiniBlocksAndTransactions(mbsInfo) +} + +// GetFinalCrossMiniBlockInfoAndRequestMissing returns the final cross mini block infos and requests missing mini blocks and transactions +func (bdr *BlockDataRequest) GetFinalCrossMiniBlockInfoAndRequestMissing(header data.HeaderHandler) []*data.MiniBlockInfo { + finalCrossMiniBlockInfos := bdr.getFinalCrossMiniBlockInfos(header.GetOrderedCrossMiniblocksWithDst(bdr.shardCoordinator.SelfId()), header) + bdr.requestMissingMiniBlocksAndTransactions(finalCrossMiniBlockInfos) + + return finalCrossMiniBlockInfos +} + +// IsDataPreparedForProcessing verifies if all the needed data is prepared +func (bdr *BlockDataRequest) IsDataPreparedForProcessing(haveTime func() time.Duration) error { + var errFound error + errMutex := sync.Mutex{} + + wg := sync.WaitGroup{} + + bdr.mutRequestedTxs.RLock() + wg.Add(len(bdr.requestedTxs)) + + for key, value := range bdr.requestedTxs { + go func(blockType block.Type, requestedTxs int) { + preproc, err := bdr.preProcessors.Get(blockType) + if err != nil { + wg.Done() + return + } + + err = preproc.IsDataPrepared(requestedTxs, haveTime) + if err != nil { + log.Trace("IsDataPrepared", "error", err.Error()) + + errMutex.Lock() + errFound = err + errMutex.Unlock() + } + wg.Done() + }(key, value) + } + + wg.Wait() + bdr.mutRequestedTxs.RUnlock() + + return errFound +} + +// IsInterfaceNil checks if there is no value under the interface +func (bdr *BlockDataRequest) IsInterfaceNil() bool { + return bdr == nil +} + +// initRequestedTxs init the requested txs number +func (bdr *BlockDataRequest) initRequestedTxs() { + bdr.mutRequestedTxs.Lock() + bdr.requestedTxs = make(map[block.Type]int) + bdr.mutRequestedTxs.Unlock() +} + +func (bdr *BlockDataRequest) requestMissingMiniBlocksAndTransactions(mbsInfo []*data.MiniBlockInfo) { + mapMissingMiniBlocksPerShard := make(map[uint32][][]byte) + + bdr.requestedItemsHandler.Sweep() + + for _, mbInfo := range mbsInfo { + object, isMiniBlockFound := bdr.miniBlockPool.Peek(mbInfo.Hash) + if !isMiniBlockFound { + log.Debug("BlockDataRequest.requestMissingMiniBlocksAndTransactions: mini block not found and was requested", + "sender shard", mbInfo.SenderShardID, + "hash", mbInfo.Hash, + "round", mbInfo.Round, + ) + mapMissingMiniBlocksPerShard[mbInfo.SenderShardID] = append(mapMissingMiniBlocksPerShard[mbInfo.SenderShardID], mbInfo.Hash) + _ = bdr.requestedItemsHandler.Add(string(mbInfo.Hash)) + continue + } + + miniBlock, isMiniBlock := object.(*block.MiniBlock) + if !isMiniBlock { + log.Warn("BlockDataRequest.requestMissingMiniBlocksAndTransactions", "mb hash", mbInfo.Hash, "error", process.ErrWrongTypeAssertion) + continue + } + + preproc, err := bdr.preProcessors.Get(miniBlock.Type) + if err != nil { + log.Warn("BlockDataRequest.requestMissingMiniBlocksAndTransactions: preProcessors.Get", "mb type", miniBlock.Type, "error", err) + continue + } + + _, numTxsRequested := preproc.GetTransactionsAndRequestMissingForMiniBlock(miniBlock) + if numTxsRequested > 0 { + log.Debug("BlockDataRequest.requestMissingMiniBlocksAndTransactions: GetTransactionsAndRequestMissingForMiniBlock", "mb hash", mbInfo.Hash, + "num txs requested", numTxsRequested) + } + } + + for senderShardID, mbsHashes := range mapMissingMiniBlocksPerShard { + go bdr.requestHandler.RequestMiniBlocks(senderShardID, mbsHashes) + } +} + +func (bdr *BlockDataRequest) receivedMiniBlock(key []byte, value interface{}) { + if key == nil { + return + } + + if !bdr.requestedItemsHandler.Has(string(key)) { + return + } + + miniBlock, ok := value.(*block.MiniBlock) + if !ok { + log.Warn("BlockDataRequest.receivedMiniBlock", "error", process.ErrWrongTypeAssertion) + return + } + + log.Trace("BlockDataRequest.receivedMiniBlock", "hash", key) + + preproc, err := bdr.preProcessors.Get(miniBlock.Type) + if err != nil { + log.Warn("BlockDataRequest.receivedMiniBlock", + "error", fmt.Errorf("%w unknown block type %d", err, miniBlock.Type)) + return + } + + _, numTxsRequested := preproc.GetTransactionsAndRequestMissingForMiniBlock(miniBlock) + if numTxsRequested > 0 { + log.Debug("BlockDataRequest.receivedMiniBlock", "hash", key, + "num txs requested", numTxsRequested) + } +} + +func (bdr *BlockDataRequest) getFinalCrossMiniBlockHashes(headerHandler data.HeaderHandler) map[string]uint32 { + if !bdr.enableEpochsHandler.IsFlagEnabled(common.ScheduledMiniBlocksFlag) { + return headerHandler.GetMiniBlockHeadersWithDst(bdr.shardCoordinator.SelfId()) + } + return process.GetFinalCrossMiniBlockHashes(headerHandler, bdr.shardCoordinator.SelfId()) +} + +func (bdr *BlockDataRequest) getFinalCrossMiniBlockInfos( + crossMiniBlockInfos []*data.MiniBlockInfo, + header data.HeaderHandler, +) []*data.MiniBlockInfo { + + if !bdr.enableEpochsHandler.IsFlagEnabled(common.ScheduledMiniBlocksFlag) { + return crossMiniBlockInfos + } + + miniBlockInfos := make([]*data.MiniBlockInfo, 0) + for _, crossMiniBlockInfo := range crossMiniBlockInfos { + miniBlockHeader := process.GetMiniBlockHeaderWithHash(header, crossMiniBlockInfo.Hash) + if miniBlockHeader != nil && !miniBlockHeader.IsFinal() { + log.Debug("BlockDataRequest.getFinalCrossMiniBlockInfos: do not execute mini block which is not final", "mb hash", miniBlockHeader.GetHash()) + continue + } + + miniBlockInfos = append(miniBlockInfos, crossMiniBlockInfo) + } + + return miniBlockInfos +} diff --git a/process/coordinator/blockDataRequest_test.go b/process/coordinator/blockDataRequest_test.go new file mode 100644 index 00000000000..628d0204d22 --- /dev/null +++ b/process/coordinator/blockDataRequest_test.go @@ -0,0 +1,1315 @@ +package coordinator + +import ( + "bytes" + "errors" + "fmt" + "sync" + "testing" + "time" + + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/process" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/process/factory/containers" + "github.com/multiversx/mx-chain-go/process/mock" + "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/cache" + dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" + "github.com/multiversx/mx-chain-go/testscommon/preprocMocks" +) + +func TestNewBlockDataRequester(t *testing.T) { + t.Parallel() + + t.Run("nil request handler should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.RequestHandler = nil + + blockDataRequester, err := NewBlockDataRequester(args) + require.Nil(t, blockDataRequester) + require.Equal(t, process.ErrNilRequestHandler, err) + }) + + t.Run("nil mini block pool should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.MiniBlockPool = nil + + blockDataRequester, err := NewBlockDataRequester(args) + require.Nil(t, blockDataRequester) + require.Equal(t, process.ErrNilMiniBlockPool, err) + }) + + t.Run("nil pre processors should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.PreProcessors = nil + + blockDataRequester, err := NewBlockDataRequester(args) + require.Nil(t, blockDataRequester) + require.Equal(t, process.ErrNilPreProcessorsContainer, err) + }) + + t.Run("nil shard coordinator should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.ShardCoordinator = nil + + blockDataRequester, err := NewBlockDataRequester(args) + require.Nil(t, blockDataRequester) + require.Equal(t, process.ErrNilShardCoordinator, err) + }) + + t.Run("nil enable epochs handler should error", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.EnableEpochsHandler = nil + + blockDataRequester, err := NewBlockDataRequester(args) + require.Nil(t, blockDataRequester) + require.Equal(t, process.ErrNilEnableEpochsHandler, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + + blockDataRequester, err := NewBlockDataRequester(args) + require.False(t, blockDataRequester.IsInterfaceNil()) + require.Nil(t, err) + require.NotNil(t, blockDataRequester.requestedTxs) + require.NotNil(t, blockDataRequester.preProcessors) + require.NotNil(t, blockDataRequester.miniBlockPool) + require.NotNil(t, blockDataRequester.shardCoordinator) + require.NotNil(t, blockDataRequester.enableEpochsHandler) + require.NotNil(t, blockDataRequester.requestHandler) + require.NotNil(t, blockDataRequester.requestedItemsHandler) + }) +} + +func TestBlockDataRequest_getFinalCrossMiniBlockInfos(t *testing.T) { + t.Parallel() + + hash1, hash2 := "hash1", "hash2" + + t.Run("scheduledMiniBlocks flag not set", func(t *testing.T) { + t.Parallel() + blockDataRequesterArgs := createMockArgs() + blockDataRequester, _ := NewBlockDataRequester(blockDataRequesterArgs) + + var crossMiniBlockInfos []*data.MiniBlockInfo + + mbInfos := blockDataRequester.getFinalCrossMiniBlockInfos(crossMiniBlockInfos, &block.Header{}) + require.Equal(t, crossMiniBlockInfos, mbInfos) + }) + + t.Run("should work, miniblocks info found for final miniBlock header", func(t *testing.T) { + t.Parallel() + + args := createMockArgs() + enableEpochsHandlerStub := enableEpochsHandlerMock.NewEnableEpochsHandlerStub() + args.EnableEpochsHandler = enableEpochsHandlerStub + blockDataRequester, _ := NewBlockDataRequester(args) + enableEpochsHandlerStub.AddActiveFlags(common.ScheduledMiniBlocksFlag) + + mbInfo1 := &data.MiniBlockInfo{Hash: []byte(hash1)} + mbInfo2 := &data.MiniBlockInfo{Hash: []byte(hash2)} + crossMiniBlockInfos := []*data.MiniBlockInfo{mbInfo1, mbInfo2} + + mbh1 := block.MiniBlockHeader{Hash: []byte(hash1)} + mbhReserved1 := block.MiniBlockHeaderReserved{State: block.Proposed} + mbh1.Reserved, _ = mbhReserved1.Marshal() + + mbh2 := block.MiniBlockHeader{Hash: []byte(hash2)} + mbhReserved2 := block.MiniBlockHeaderReserved{State: block.Final} + mbh2.Reserved, _ = mbhReserved2.Marshal() + + header := &block.MetaBlock{ + MiniBlockHeaders: []block.MiniBlockHeader{ + mbh1, + mbh2, + }, + } + + expectedMbInfos := []*data.MiniBlockInfo{mbInfo2} + + mbInfos := blockDataRequester.getFinalCrossMiniBlockInfos(crossMiniBlockInfos, header) + require.Equal(t, expectedMbInfos, mbInfos) + }) +} + +func TestTransactionCoordinator_requestMissingMiniBlocksAndTransactionsShouldWork(t *testing.T) { + t.Parallel() + + args := createMockArgs() + args.MiniBlockPool = &cache.CacherStub{ + PeekCalled: func(key []byte) (value interface{}, ok bool) { + if bytes.Equal(key, []byte("hash0")) || bytes.Equal(key, []byte("hash1")) || bytes.Equal(key, []byte("hash2")) { + if bytes.Equal(key, []byte("hash0")) { + return nil, true + } + + if bytes.Equal(key, []byte("hash1")) { + return &block.MiniBlock{ + Type: block.PeerBlock, + TxHashes: [][]byte{ + []byte("hash 1"), + []byte("hash 2"), + }, + }, true + } + + if bytes.Equal(key, []byte("hash2")) { + return &block.MiniBlock{ + Type: block.TxBlock, + TxHashes: [][]byte{ + []byte("hash 3"), + []byte("hash 4"), + }, + }, true + } + } + return nil, false + }, + } + + numTxsRequested := 0 + args.PreProcessors = containers.NewPreProcessorsContainer() + err := args.PreProcessors.Add(block.TxBlock, &preprocMocks.PreProcessorMock{ + GetTransactionsAndRequestMissingForMiniBlockCalled: func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + numTxsRequested += len(miniBlock.TxHashes) + return make([]data.TransactionHandler, 0), len(miniBlock.TxHashes) + }, + }) + require.Nil(t, err) + + wg := sync.WaitGroup{} + wg.Add(3) + mapRequestedMiniBlocksPerShard := make(map[uint32]int) + mutMap := sync.RWMutex{} + args.RequestHandler = &testscommon.RequestHandlerStub{ + RequestMiniBlocksHandlerCalled: func(destShardID uint32, miniblocksHashes [][]byte) { + mutMap.Lock() + mapRequestedMiniBlocksPerShard[destShardID] += len(miniblocksHashes) + mutMap.Unlock() + wg.Done() + }, + } + + blockDataRequester, _ := NewBlockDataRequester(args) + + mbsInfo := []*data.MiniBlockInfo{ + {SenderShardID: 0}, + {SenderShardID: 1}, + {SenderShardID: 2}, + {SenderShardID: 0, Hash: []byte("hash0")}, + {SenderShardID: 1, Hash: []byte("hash1")}, + {SenderShardID: 2, Hash: []byte("hash2")}, + {SenderShardID: 0}, + {SenderShardID: 1}, + {SenderShardID: 0}, + } + + blockDataRequester.requestMissingMiniBlocksAndTransactions(mbsInfo) + + wg.Wait() + + mutMap.RLock() + require.Equal(t, 3, mapRequestedMiniBlocksPerShard[0]) + require.Equal(t, 2, mapRequestedMiniBlocksPerShard[1]) + require.Equal(t, 1, mapRequestedMiniBlocksPerShard[2]) + require.Equal(t, 2, numTxsRequested) + mutMap.RUnlock() +} + +func TestBlockDataRequest_RequestBlockTransactions(t *testing.T) { + t.Parallel() + + t.Run("nil body should return early", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + blockDataRequester, _ := NewBlockDataRequester(args) + + // Should not panic + require.NotPanics(t, func() { + blockDataRequester.RequestBlockTransactions(nil) + }) + }) + + t.Run("empty body should work", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + blockDataRequester, _ := NewBlockDataRequester(args) + + emptyBody := &block.Body{} + blockDataRequester.RequestBlockTransactions(emptyBody) + + // Should initialize requestedTxs map + blockDataRequester.mutRequestedTxs.RLock() + require.Equal(t, 0, len(blockDataRequester.requestedTxs)) + blockDataRequester.mutRequestedTxs.RUnlock() + }) + + t.Run("should request transactions for different block types", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + + // Create preprocessors container with mocks + preprocContainer := containers.NewPreProcessorsContainer() + txPreproc := &preprocMocks.PreProcessorMock{ + RequestBlockTransactionsCalled: func(body *block.Body) int { + totalTxs := 0 + for _, mb := range body.MiniBlocks { + if mb.Type == block.TxBlock { + totalTxs += len(mb.TxHashes) + } + } + return totalTxs + }, + } + peerPreproc := &preprocMocks.PreProcessorMock{ + RequestBlockTransactionsCalled: func(body *block.Body) int { + totalTxs := 0 + for _, mb := range body.MiniBlocks { + if mb.Type == block.PeerBlock { + totalTxs += len(mb.TxHashes) + } + } + return totalTxs + }, + } + + _ = preprocContainer.Add(block.TxBlock, txPreproc) + _ = preprocContainer.Add(block.PeerBlock, peerPreproc) + args.PreProcessors = preprocContainer + + blockDataRequester, _ := NewBlockDataRequester(args) + + // Create body with different block types + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + Type: block.TxBlock, + TxHashes: [][]byte{[]byte("tx1"), []byte("tx2")}, + }, + { + Type: block.PeerBlock, + TxHashes: [][]byte{[]byte("peer1"), []byte("peer2"), []byte("peer3")}, + }, + }, + } + + blockDataRequester.RequestBlockTransactions(body) + + // Check that requestedTxs was updated + blockDataRequester.mutRequestedTxs.RLock() + require.Equal(t, 2, len(blockDataRequester.requestedTxs)) + require.Equal(t, 2, blockDataRequester.requestedTxs[block.TxBlock]) + require.Equal(t, 3, blockDataRequester.requestedTxs[block.PeerBlock]) + blockDataRequester.mutRequestedTxs.RUnlock() + }) + + t.Run("should handle preprocessor not found gracefully", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + blockDataRequester, _ := NewBlockDataRequester(args) + blockDataRequester.preProcessors = &preprocMocks.PreProcessorContainerMock{ + GetCalled: func(key block.Type) (process.PreProcessor, error) { + return nil, errors.New("not found") + }, + } + + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + Type: block.TxBlock, + TxHashes: [][]byte{[]byte("tx1")}, + }, + }, + } + + // Should not panic even if preprocessor is not found + blockDataRequester.RequestBlockTransactions(body) + + // Should still initialize the map + blockDataRequester.mutRequestedTxs.RLock() + require.Equal(t, 0, len(blockDataRequester.requestedTxs)) + blockDataRequester.mutRequestedTxs.RUnlock() + }) + + t.Run("should handle concurrent requests safely", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + + // Create a preprocessor that simulates some processing time + preprocContainer := containers.NewPreProcessorsContainer() + txPreproc := &preprocMocks.PreProcessorMock{ + RequestBlockTransactionsCalled: func(body *block.Body) int { + time.Sleep(10 * time.Millisecond) // Simulate processing time + if body == nil || len(body.MiniBlocks) == 0 || body.MiniBlocks[0] == nil { + return 0 + } + return len(body.MiniBlocks[0].TxHashes) + }, + } + _ = preprocContainer.Add(block.TxBlock, txPreproc) + args.PreProcessors = preprocContainer + + blockDataRequester, _ := NewBlockDataRequester(args) + + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + Type: block.TxBlock, + TxHashes: [][]byte{[]byte("tx1")}, + }, + }, + } + + require.NotPanics(t, func() { + blockDataRequester.RequestBlockTransactions(body) + }) + + // Should not have race conditions + blockDataRequester.mutRequestedTxs.RLock() + require.Equal(t, 1, len(blockDataRequester.requestedTxs)) + blockDataRequester.mutRequestedTxs.RUnlock() + }) +} + +func TestBlockDataRequest_RequestMiniBlocksAndTransactions(t *testing.T) { + t.Parallel() + + t.Run("nil header should return early", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + blockDataRequester, _ := NewBlockDataRequester(args) + + require.NotPanics(t, func() { + blockDataRequester.RequestBlockTransactions(nil) + }) + }) + + t.Run("should work with scheduled mini blocks flag disabled", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + enableEpochsHandlerStub := enableEpochsHandlerMock.NewEnableEpochsHandlerStub() + args.EnableEpochsHandler = enableEpochsHandlerStub + + calledMiniblock := 0 + wg := &sync.WaitGroup{} + wg.Add(1) + args.RequestHandler = &testscommon.RequestHandlerStub{ + RequestMiniBlocksHandlerCalled: func(destShardID uint32, miniblocksHashes [][]byte) { + calledMiniblock++ + wg.Done() + }, + } + blockDataRequester, _ := NewBlockDataRequester(args) + + // Mock the mini block pool to return some mini blocks + miniBlockPool := &cache.CacherStub{ + PeekCalled: func(key []byte) (value interface{}, ok bool) { + if bytes.Equal(key, []byte("hash1")) { + return &block.MiniBlock{ + Type: block.TxBlock, + TxHashes: [][]byte{[]byte("tx1"), []byte("tx2")}, + }, true + } + return nil, false + }, + } + blockDataRequester.miniBlockPool = miniBlockPool + + // Mock preprocessors + calledCount := 0 + preprocContainer := containers.NewPreProcessorsContainer() + txPreproc := &preprocMocks.PreProcessorMock{ + GetTransactionsAndRequestMissingForMiniBlockCalled: func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + calledCount++ + return make([]data.TransactionHandler, 0), len(miniBlock.TxHashes) + }, + } + _ = preprocContainer.Add(block.TxBlock, txPreproc) + blockDataRequester.preProcessors = preprocContainer + + // Mock the shard coordinator to return self ID + shardCoordinator := mock.NewMultiShardsCoordinatorMock(3) + shardCoordinator.CurrentShard = 1 + blockDataRequester.shardCoordinator = shardCoordinator + + // Mock the header to return cross mini blocks + headerWithCrossMbs := &testscommon.HeaderHandlerStub{ + RoundField: 100, + GetMiniBlockHeadersWithDstCalled: func(destShardID uint32) map[string]uint32 { + return map[string]uint32{ + "hash1": 0, // from shard 0 to shard 1 + "hash2": 2, // from shard 2 to shard 1 + } + }, + } + + blockDataRequester.RequestMiniBlocksAndTransactions(headerWithCrossMbs) + + wg.Wait() + + require.Equal(t, 1, calledCount) + require.Equal(t, 1, calledMiniblock) + }) + + t.Run("should work with scheduled mini blocks flag enabled", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + enableEpochsHandlerStub := enableEpochsHandlerMock.NewEnableEpochsHandlerStub() + enableEpochsHandlerStub.AddActiveFlags(common.ScheduledMiniBlocksFlag) + args.EnableEpochsHandler = enableEpochsHandlerStub + + blockDataRequester, _ := NewBlockDataRequester(args) + + // Mock the mini block pool to return mini blocks not found + miniBlockPool := &cache.CacherStub{ + PeekCalled: func(key []byte) (value interface{}, ok bool) { + return nil, false // Mini blocks not found, so they will be requested + }, + } + blockDataRequester.miniBlockPool = miniBlockPool + + // Mock the request handler to track requests + var requestedMiniBlocksCount int + mutRequested := sync.RWMutex{} + + wg := &sync.WaitGroup{} + wg.Add(2) + requestHandler := &testscommon.RequestHandlerStub{ + RequestMiniBlocksHandlerCalled: func(destShardID uint32, miniblocksHashes [][]byte) { + mutRequested.Lock() + requestedMiniBlocksCount += len(miniblocksHashes) + mutRequested.Unlock() + wg.Done() + }, + } + blockDataRequester.requestHandler = requestHandler + + // Mock the shard coordinator + shardCoordinator := mock.NewMultiShardsCoordinatorMock(3) + shardCoordinator.CurrentShard = 1 + blockDataRequester.shardCoordinator = shardCoordinator + + // Create header with cross mini blocks + header := &testscommon.HeaderHandlerStub{ + RoundField: 100, + GetMiniBlockHeadersWithDstCalled: func(destShardID uint32) map[string]uint32 { + return map[string]uint32{ + "hash1": 0, // from shard 0 to shard 1 + "hash2": 2, // from shard 2 to shard 1 + } + }, + GetMiniBlockHeaderHandlersCalled: func() []data.MiniBlockHeaderHandler { + // Create final mini block headers + mbh1 := &block.MiniBlockHeader{Hash: []byte("hash1")} + mbhReserved1 := block.MiniBlockHeaderReserved{State: block.Final} + mbh1.Reserved, _ = mbhReserved1.Marshal() + + mbh2 := &block.MiniBlockHeader{Hash: []byte("hash2")} + mbhReserved2 := block.MiniBlockHeaderReserved{State: block.Final} + mbh2.Reserved, _ = mbhReserved2.Marshal() + + return []data.MiniBlockHeaderHandler{mbh1, mbh2} + }, + } + + blockDataRequester.RequestMiniBlocksAndTransactions(header) + + wg.Wait() + + // Verify that mini blocks were requested + mutRequested.RLock() + require.Equal(t, 2, requestedMiniBlocksCount) + mutRequested.RUnlock() + }) + + t.Run("should handle empty cross mini blocks", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + blockDataRequester, _ := NewBlockDataRequester(args) + + // Mock the shard coordinator + shardCoordinator := mock.NewMultiShardsCoordinatorMock(3) + shardCoordinator.CurrentShard = 1 + blockDataRequester.shardCoordinator = shardCoordinator + + // Create header with no cross mini blocks + header := &testscommon.HeaderHandlerStub{ + RoundField: 100, + GetMiniBlockHeadersWithDstCalled: func(destShardID uint32) map[string]uint32 { + return map[string]uint32{} // Empty map + }, + } + + require.NotPanics(t, func() { + blockDataRequester.RequestMiniBlocksAndTransactions(header) + }) + }) +} + +func TestBlockDataRequest_IsDataPreparedForProcessing(t *testing.T) { + t.Parallel() + + t.Run("no requested transactions should return nil", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + blockDataRequester, _ := NewBlockDataRequester(args) + + // No transactions requested yet + blockDataRequester.mutRequestedTxs.Lock() + blockDataRequester.requestedTxs = make(map[block.Type]int) + blockDataRequester.mutRequestedTxs.Unlock() + + haveTime := func() time.Duration { return time.Second } + err := blockDataRequester.IsDataPreparedForProcessing(haveTime) + require.Nil(t, err) + }) + + t.Run("all preprocessors succeed should return nil", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + + // Create preprocessors container with mocks that always succeed + preprocContainer := containers.NewPreProcessorsContainer() + txPreproc := &preprocMocks.PreProcessorMock{ + IsDataPreparedCalled: func(requestedTxs int, haveTime func() time.Duration) error { + return nil + }, + } + peerPreproc := &preprocMocks.PreProcessorMock{ + IsDataPreparedCalled: func(requestedTxs int, haveTime func() time.Duration) error { + return nil + }, + } + + _ = preprocContainer.Add(block.TxBlock, txPreproc) + _ = preprocContainer.Add(block.PeerBlock, peerPreproc) + args.PreProcessors = preprocContainer + + blockDataRequester, _ := NewBlockDataRequester(args) + + // Set some requested transactions + blockDataRequester.mutRequestedTxs.Lock() + blockDataRequester.requestedTxs[block.TxBlock] = 5 + blockDataRequester.requestedTxs[block.PeerBlock] = 3 + blockDataRequester.mutRequestedTxs.Unlock() + + haveTime := func() time.Duration { return time.Second } + err := blockDataRequester.IsDataPreparedForProcessing(haveTime) + require.Nil(t, err) + }) + + t.Run("one preprocessor fails should return error", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + + expectedErr := errors.New("data not prepared") + preprocContainer := containers.NewPreProcessorsContainer() + txPreproc := &preprocMocks.PreProcessorMock{ + IsDataPreparedCalled: func(requestedTxs int, haveTime func() time.Duration) error { + return expectedErr + }, + } + peerPreproc := &preprocMocks.PreProcessorMock{ + IsDataPreparedCalled: func(requestedTxs int, haveTime func() time.Duration) error { + return nil + }, + } + + _ = preprocContainer.Add(block.TxBlock, txPreproc) + _ = preprocContainer.Add(block.PeerBlock, peerPreproc) + args.PreProcessors = preprocContainer + + blockDataRequester, _ := NewBlockDataRequester(args) + + // Set some requested transactions + blockDataRequester.mutRequestedTxs.Lock() + blockDataRequester.requestedTxs[block.TxBlock] = 5 + blockDataRequester.requestedTxs[block.PeerBlock] = 3 + blockDataRequester.mutRequestedTxs.Unlock() + + haveTime := func() time.Duration { return time.Second } + err := blockDataRequester.IsDataPreparedForProcessing(haveTime) + require.Equal(t, expectedErr, err) + }) + + t.Run("preprocessor not found should continue gracefully", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + + // Create preprocessors container with only one preprocessor + preprocContainer := containers.NewPreProcessorsContainer() + txPreproc := &preprocMocks.PreProcessorMock{ + IsDataPreparedCalled: func(requestedTxs int, haveTime func() time.Duration) error { + return nil + }, + } + _ = preprocContainer.Add(block.TxBlock, txPreproc) + args.PreProcessors = preprocContainer + + blockDataRequester, _ := NewBlockDataRequester(args) + + // Set requested transactions for both types, but only one preprocessor exists + blockDataRequester.mutRequestedTxs.Lock() + blockDataRequester.requestedTxs[block.TxBlock] = 5 + blockDataRequester.requestedTxs[block.PeerBlock] = 3 + blockDataRequester.mutRequestedTxs.Unlock() + + haveTime := func() time.Duration { return time.Second } + err := blockDataRequester.IsDataPreparedForProcessing(haveTime) + require.Nil(t, err) // Should not panic and should return nil + }) + + t.Run("concurrent processing should work correctly", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + + // Create preprocessors that simulate some processing time + preprocContainer := containers.NewPreProcessorsContainer() + txPreproc := &preprocMocks.PreProcessorMock{ + IsDataPreparedCalled: func(requestedTxs int, haveTime func() time.Duration) error { + time.Sleep(10 * time.Millisecond) // Simulate processing time + return nil + }, + } + peerPreproc := &preprocMocks.PreProcessorMock{ + IsDataPreparedCalled: func(requestedTxs int, haveTime func() time.Duration) error { + time.Sleep(15 * time.Millisecond) // Different processing time + return nil + }, + } + + _ = preprocContainer.Add(block.TxBlock, txPreproc) + _ = preprocContainer.Add(block.PeerBlock, peerPreproc) + args.PreProcessors = preprocContainer + + blockDataRequester, _ := NewBlockDataRequester(args) + + // Set multiple requested transactions + blockDataRequester.mutRequestedTxs.Lock() + blockDataRequester.requestedTxs[block.TxBlock] = 5 + blockDataRequester.requestedTxs[block.PeerBlock] = 3 + blockDataRequester.mutRequestedTxs.Unlock() + + haveTime := func() time.Duration { return time.Second } + err := blockDataRequester.IsDataPreparedForProcessing(haveTime) + require.Nil(t, err) + }) + + t.Run("haveTime function should be called by preprocessors", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + + timeFunctionCalled := false + haveTime := func() time.Duration { + timeFunctionCalled = true + return time.Second + } + + preprocContainer := containers.NewPreProcessorsContainer() + txPreproc := &preprocMocks.PreProcessorMock{ + IsDataPreparedCalled: func(requestedTxs int, haveTime func() time.Duration) error { + haveTime() // Call the function to verify it's passed correctly + return nil + }, + } + + _ = preprocContainer.Add(block.TxBlock, txPreproc) + args.PreProcessors = preprocContainer + + blockDataRequester, _ := NewBlockDataRequester(args) + + // Set requested transactions + blockDataRequester.mutRequestedTxs.Lock() + blockDataRequester.requestedTxs[block.TxBlock] = 5 + blockDataRequester.mutRequestedTxs.Unlock() + + err := blockDataRequester.IsDataPreparedForProcessing(haveTime) + require.Nil(t, err) + require.True(t, timeFunctionCalled) + }) +} + +func TestBlockDataRequest_receivedMiniBlock(t *testing.T) { + t.Parallel() + + t.Run("nil key should return early", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + blockDataRequester, _ := NewBlockDataRequester(args) + + require.NotPanics(t, func() { + blockDataRequester.receivedMiniBlock(nil, &block.MiniBlock{}) + }) + }) + + t.Run("unrequested mini block should return early", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + blockDataRequester, _ := NewBlockDataRequester(args) + + // Mini block was not requested + key := []byte("hash1") + miniBlock := &block.MiniBlock{ + Type: block.TxBlock, + TxHashes: [][]byte{[]byte("tx1"), []byte("tx2")}, + } + + require.NotPanics(t, func() { + blockDataRequester.receivedMiniBlock(key, miniBlock) + }) + }) + + t.Run("wrong type assertion should log warning and return", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + blockDataRequester, _ := NewBlockDataRequester(args) + + // Add the mini block to requested items + key := []byte("hash1") + _ = blockDataRequester.requestedItemsHandler.Add(string(key)) + + // Pass wrong type (not a mini block) + wrongValue := "not a mini block" + + require.NotPanics(t, func() { + blockDataRequester.receivedMiniBlock(key, wrongValue) + }) + }) + + t.Run("preprocessor not found should log warning and return", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + blockDataRequester, _ := NewBlockDataRequester(args) + + // Add the mini block to requested items + key := []byte("hash1") + _ = blockDataRequester.requestedItemsHandler.Add(string(key)) + + // Create mini block with type that has no preprocessor + miniBlock := &block.MiniBlock{ + Type: block.InvalidBlock, // This type won't have a preprocessor + TxHashes: [][]byte{[]byte("tx1")}, + } + + require.NotPanics(t, func() { + blockDataRequester.receivedMiniBlock(key, miniBlock) + }) + }) + + t.Run("should successfully request transactions for mini block", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + + // Create preprocessors container with mock + countCalled := 0 + preprocContainer := containers.NewPreProcessorsContainer() + txPreproc := &preprocMocks.PreProcessorMock{ + GetTransactionsAndRequestMissingForMiniBlockCalled: func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + countCalled++ + return make([]data.TransactionHandler, 0), len(miniBlock.TxHashes) + }, + } + _ = preprocContainer.Add(block.TxBlock, txPreproc) + args.PreProcessors = preprocContainer + + blockDataRequester, _ := NewBlockDataRequester(args) + + // Add the mini block to requested items + key := []byte("hash1") + _ = blockDataRequester.requestedItemsHandler.Add(string(key)) + + // Create mini block + miniBlock := &block.MiniBlock{ + Type: block.TxBlock, + TxHashes: [][]byte{[]byte("tx1"), []byte("tx2"), []byte("tx3")}, + } + + require.NotPanics(t, func() { + blockDataRequester.receivedMiniBlock(key, miniBlock) + }) + require.Equal(t, 1, countCalled) + }) + + t.Run("should handle mini block with no transactions", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + + // Create preprocessors container with mock + preprocContainer := containers.NewPreProcessorsContainer() + txPreproc := &preprocMocks.PreProcessorMock{ + GetTransactionsAndRequestMissingForMiniBlockCalled: func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + return make([]data.TransactionHandler, 0), len(miniBlock.TxHashes) + }, + } + _ = preprocContainer.Add(block.TxBlock, txPreproc) + args.PreProcessors = preprocContainer + + blockDataRequester, _ := NewBlockDataRequester(args) + + // Add the mini block to requested items + key := []byte("hash1") + _ = blockDataRequester.requestedItemsHandler.Add(string(key)) + + // Create mini block with no transactions + miniBlock := &block.MiniBlock{ + Type: block.TxBlock, + TxHashes: [][]byte{}, // Empty transactions + } + + require.NotPanics(t, func() { + blockDataRequester.receivedMiniBlock(key, miniBlock) + }) + }) + + t.Run("should handle multiple mini block types", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + + // Create preprocessors container with multiple mocks + numRequestedTxs := 0 + numRequestedPeer := 0 + preprocContainer := containers.NewPreProcessorsContainer() + txPreproc := &preprocMocks.PreProcessorMock{ + GetTransactionsAndRequestMissingForMiniBlockCalled: func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + require.Equal(t, block.TxBlock, miniBlock.Type) + numRequestedTxs += len(miniBlock.TxHashes) + return make([]data.TransactionHandler, 0), len(miniBlock.TxHashes) + }, + } + peerPreproc := &preprocMocks.PreProcessorMock{ + GetTransactionsAndRequestMissingForMiniBlockCalled: func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + require.Equal(t, block.PeerBlock, miniBlock.Type) + numRequestedPeer += len(miniBlock.TxHashes) + return make([]data.TransactionHandler, 0), len(miniBlock.TxHashes) + }, + } + _ = preprocContainer.Add(block.TxBlock, txPreproc) + _ = preprocContainer.Add(block.PeerBlock, peerPreproc) + args.PreProcessors = preprocContainer + + blockDataRequester, _ := NewBlockDataRequester(args) + + // Test TxBlock type + key1 := []byte("hash1") + _ = blockDataRequester.requestedItemsHandler.Add(string(key1)) + txMiniBlock := &block.MiniBlock{ + Type: block.TxBlock, + TxHashes: [][]byte{[]byte("tx1"), []byte("tx2")}, + } + blockDataRequester.receivedMiniBlock(key1, txMiniBlock) + + // Test PeerBlock type + key2 := []byte("hash2") + _ = blockDataRequester.requestedItemsHandler.Add(string(key2)) + peerMiniBlock := &block.MiniBlock{ + Type: block.PeerBlock, + TxHashes: [][]byte{[]byte("peer1"), []byte("peer2"), []byte("peer3")}, + } + blockDataRequester.receivedMiniBlock(key2, peerMiniBlock) + require.Equal(t, 2, numRequestedTxs) + require.Equal(t, 3, numRequestedPeer) + }) +} + +func TestBlockDataRequest_ConcurrentOperations(t *testing.T) { + t.Parallel() + + t.Run("concurrent RequestBlockTransactions and IsDataPreparedForProcessing", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + + // Create preprocessors that simulate processing time + preprocContainer := containers.NewPreProcessorsContainer() + txPreproc := &preprocMocks.PreProcessorMock{ + RequestBlockTransactionsCalled: func(body *block.Body) int { + time.Sleep(5 * time.Millisecond) // Simulate processing time + if body == nil || len(body.MiniBlocks) == 0 { + return 0 + } + return len(body.MiniBlocks[0].TxHashes) + }, + IsDataPreparedCalled: func(requestedTxs int, haveTime func() time.Duration) error { + time.Sleep(3 * time.Millisecond) // Simulate processing time + return nil + }, + } + peerPreproc := &preprocMocks.PreProcessorMock{ + RequestBlockTransactionsCalled: func(body *block.Body) int { + time.Sleep(7 * time.Millisecond) // Different processing time + if body == nil || len(body.MiniBlocks) == 0 { + return 0 + } + return len(body.MiniBlocks[0].TxHashes) + }, + IsDataPreparedCalled: func(requestedTxs int, haveTime func() time.Duration) error { + time.Sleep(4 * time.Millisecond) // Different processing time + return nil + }, + } + + _ = preprocContainer.Add(block.TxBlock, txPreproc) + _ = preprocContainer.Add(block.PeerBlock, peerPreproc) + args.PreProcessors = preprocContainer + + blockDataRequester, _ := NewBlockDataRequester(args) + + // Create test bodies + body1 := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + Type: block.TxBlock, + TxHashes: [][]byte{[]byte("tx1"), []byte("tx2")}, + }, + }, + } + body2 := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + Type: block.PeerBlock, + TxHashes: [][]byte{[]byte("peer1"), []byte("peer2"), []byte("peer3")}, + }, + }, + } + + haveTime := func() time.Duration { return time.Second } + + // Run concurrent operations + var wg sync.WaitGroup + numGoroutines := 10 + + // Concurrent RequestBlockTransactions calls + for i := 0; i < numGoroutines/2; i++ { + wg.Add(1) + go func() { + defer wg.Done() + blockDataRequester.RequestBlockTransactions(body1) + }() + } + + // Concurrent IsDataPreparedForProcessing calls + for i := 0; i < numGoroutines/2; i++ { + wg.Add(1) + go func() { + defer wg.Done() + // First request some transactions + blockDataRequester.RequestBlockTransactions(body2) + // Then check if data is prepared + _ = blockDataRequester.IsDataPreparedForProcessing(haveTime) + }() + } + + wg.Wait() + + // Verify no race conditions occurred + blockDataRequester.mutRequestedTxs.RLock() + require.NotNil(t, blockDataRequester.requestedTxs) + blockDataRequester.mutRequestedTxs.RUnlock() + }) + + t.Run("concurrent RequestMiniBlocksAndTransactions calls", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + + // Mock the mini block pool + miniBlockPool := &cache.CacherStub{ + PeekCalled: func(key []byte) (value interface{}, ok bool) { + // Simulate some mini blocks found, some not + if bytes.Equal(key, []byte("hash1")) || bytes.Equal(key, []byte("hash3")) { + return &block.MiniBlock{ + Type: block.TxBlock, + TxHashes: [][]byte{[]byte("tx1")}, + }, true + } + return nil, false + }, + } + args.MiniBlockPool = miniBlockPool + + // Mock preprocessors + numRequested := 0 + mutRequested := sync.RWMutex{} + preprocContainer := containers.NewPreProcessorsContainer() + txPreproc := &preprocMocks.PreProcessorMock{ + GetTransactionsAndRequestMissingForMiniBlockCalled: func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + mutRequested.Lock() + numRequested++ + mutRequested.Unlock() + return make([]data.TransactionHandler, 0), len(miniBlock.TxHashes) + }, + } + _ = preprocContainer.Add(block.TxBlock, txPreproc) + args.PreProcessors = preprocContainer + + // Mock request handler + var requestedMiniBlocks int + mutMiniBlocks := sync.RWMutex{} + requestHandler := &testscommon.RequestHandlerStub{ + RequestMiniBlocksHandlerCalled: func(destShardID uint32, miniblocksHashes [][]byte) { + mutMiniBlocks.Lock() + requestedMiniBlocks += len(miniblocksHashes) + mutMiniBlocks.Unlock() + }, + } + args.RequestHandler = requestHandler + + // Mock shard coordinator + shardCoordinator := mock.NewMultiShardsCoordinatorMock(3) + shardCoordinator.CurrentShard = 1 + args.ShardCoordinator = shardCoordinator + + blockDataRequester, _ := NewBlockDataRequester(args) + + // Create headers with cross mini blocks + header1 := &testscommon.HeaderHandlerStub{ + RoundField: 100, + GetMiniBlockHeadersWithDstCalled: func(destShardID uint32) map[string]uint32 { + return map[string]uint32{ + "hash1": 0, + "hash2": 2, + } + }, + } + + header2 := &testscommon.HeaderHandlerStub{ + RoundField: 101, + GetMiniBlockHeadersWithDstCalled: func(destShardID uint32) map[string]uint32 { + return map[string]uint32{ + "hash3": 0, + "hash4": 2, + } + }, + } + + // Run concurrent RequestMiniBlocksAndTransactions calls + var wg sync.WaitGroup + numGoroutines := 5 + + for i := 0; i < numGoroutines; i++ { + wg.Add(1) + go func(headerIndex int) { + defer wg.Done() + if headerIndex%2 == 0 { + blockDataRequester.RequestMiniBlocksAndTransactions(header1) + } else { + blockDataRequester.RequestMiniBlocksAndTransactions(header2) + } + }(i) + } + + wg.Wait() + + // Verify operations completed without race conditions + mutRequested.RLock() + require.GreaterOrEqual(t, numRequested, 0) + mutRequested.RUnlock() + + mutMiniBlocks.RLock() + require.GreaterOrEqual(t, requestedMiniBlocks, 0) + mutMiniBlocks.RUnlock() + }) + + t.Run("comprehensive concurrent operations", func(t *testing.T) { + t.Parallel() + args := createMockArgs() + + // Create comprehensive preprocessors + preprocContainer := containers.NewPreProcessorsContainer() + txPreproc := &preprocMocks.PreProcessorMock{ + RequestBlockTransactionsCalled: func(body *block.Body) int { + time.Sleep(time.Millisecond * 2) + if body == nil || len(body.MiniBlocks) == 0 { + return 0 + } + return len(body.MiniBlocks[0].TxHashes) + }, + IsDataPreparedCalled: func(requestedTxs int, haveTime func() time.Duration) error { + time.Sleep(time.Millisecond * 1) + return nil + }, + GetTransactionsAndRequestMissingForMiniBlockCalled: func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + time.Sleep(time.Millisecond * 1) + return make([]data.TransactionHandler, 0), len(miniBlock.TxHashes) + }, + } + peerPreproc := &preprocMocks.PreProcessorMock{ + RequestBlockTransactionsCalled: func(body *block.Body) int { + time.Sleep(time.Millisecond * 3) + if body == nil || len(body.MiniBlocks) == 0 { + return 0 + } + return len(body.MiniBlocks[0].TxHashes) + }, + IsDataPreparedCalled: func(requestedTxs int, haveTime func() time.Duration) error { + time.Sleep(time.Millisecond * 2) + return nil + }, + GetTransactionsAndRequestMissingForMiniBlockCalled: func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + time.Sleep(time.Millisecond * 2) + return make([]data.TransactionHandler, 0), len(miniBlock.TxHashes) + }, + } + scrPreproc := &preprocMocks.PreProcessorMock{ + RequestBlockTransactionsCalled: func(body *block.Body) int { + time.Sleep(time.Millisecond * 1) + if body == nil || len(body.MiniBlocks) == 0 { + return 0 + } + return len(body.MiniBlocks[0].TxHashes) + }, + IsDataPreparedCalled: func(requestedTxs int, haveTime func() time.Duration) error { + time.Sleep(time.Millisecond * 1) + return nil + }, + GetTransactionsAndRequestMissingForMiniBlockCalled: func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + time.Sleep(time.Millisecond * 1) + return make([]data.TransactionHandler, 0), len(miniBlock.TxHashes) + }, + } + + _ = preprocContainer.Add(block.TxBlock, txPreproc) + _ = preprocContainer.Add(block.PeerBlock, peerPreproc) + _ = preprocContainer.Add(block.SmartContractResultBlock, scrPreproc) + args.PreProcessors = preprocContainer + + // Mock mini block pool + miniBlockPool := &cache.CacherStub{ + PeekCalled: func(key []byte) (value interface{}, ok bool) { + time.Sleep(time.Microsecond * 100) + if bytes.Equal(key, []byte("hash1")) || bytes.Equal(key, []byte("hash3")) { + return &block.MiniBlock{ + Type: block.TxBlock, + TxHashes: [][]byte{[]byte("tx1"), []byte("tx2")}, + }, true + } + return nil, false + }, + } + args.MiniBlockPool = miniBlockPool + requestHandler := &testscommon.RequestHandlerStub{ + RequestMiniBlocksHandlerCalled: func(destShardID uint32, miniblocksHashes [][]byte) { + time.Sleep(time.Millisecond * 1) + }, + } + args.RequestHandler = requestHandler + + shardCoordinator := mock.NewMultiShardsCoordinatorMock(3) + shardCoordinator.CurrentShard = 1 + args.ShardCoordinator = shardCoordinator + + blockDataRequester, _ := NewBlockDataRequester(args) + + // Create test data + body1 := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + Type: block.TxBlock, + TxHashes: [][]byte{[]byte("tx1"), []byte("tx2")}, + }, + }, + } + + header1 := &testscommon.HeaderHandlerStub{ + RoundField: 100, + GetMiniBlockHeadersWithDstCalled: func(destShardID uint32) map[string]uint32 { + return map[string]uint32{ + "hash1": 0, + "hash2": 2, + } + }, + GetOrderedCrossMiniblocksWithDstCalled: func(destShardID uint32) []*data.MiniBlockInfo { + return []*data.MiniBlockInfo{ + {Hash: []byte("hash1"), SenderShardID: 0, Round: 100}, + {Hash: []byte("hash2"), SenderShardID: 2, Round: 100}, + } + }, + } + + haveTime := func() time.Duration { return time.Second } + + miniBlocks := make([]*block.MiniBlock, 10) + keys := make([][]byte, 10) + for i := 0; i < 10; i++ { + keys[i] = []byte(fmt.Sprintf("mbhash%d", i)) + miniBlocks[i] = &block.MiniBlock{ + Type: block.TxBlock, + TxHashes: [][]byte{[]byte(fmt.Sprintf("mbtx%d", i))}, + } + _ = blockDataRequester.requestedItemsHandler.Add(string(keys[i])) + } + + numOperations := 1000 + wg := sync.WaitGroup{} + wg.Add(numOperations) + numFunctions := 8 + + for i := 0; i < numOperations; i++ { + go func(idx int) { + operation := idx % numFunctions + switch operation { + case 0: + + blockDataRequester.RequestBlockTransactions(body1) + case 1: + _ = blockDataRequester.IsDataPreparedForProcessing(haveTime) + case 2: + blockDataRequester.RequestMiniBlocksAndTransactions(header1) + case 3: + mbIndex := idx % len(miniBlocks) + blockDataRequester.receivedMiniBlock(keys[mbIndex], miniBlocks[mbIndex]) + case 4: + _ = blockDataRequester.GetFinalCrossMiniBlockInfoAndRequestMissing(header1) + case 5: + _ = blockDataRequester.IsInterfaceNil() + case 6: + blockDataRequester.mutRequestedTxs.RLock() + _ = blockDataRequester.requestedTxs + blockDataRequester.mutRequestedTxs.RUnlock() + case 7: + blockDataRequester.RequestBlockTransactions(body1) + _ = blockDataRequester.IsDataPreparedForProcessing(haveTime) + blockDataRequester.RequestMiniBlocksAndTransactions(header1) + default: + require.Fail(t, fmt.Sprintf("invalid numFunctions value %d, operation: %d", numFunctions, operation)) + } + + wg.Done() + }(i) + } + + wg.Wait() + + // Verify final state is consistent + blockDataRequester.mutRequestedTxs.RLock() + require.NotNil(t, blockDataRequester.requestedTxs) + blockDataRequester.mutRequestedTxs.RUnlock() + }) + +} + +func createMockArgs() BlockDataRequestArgs { + return BlockDataRequestArgs{ + RequestHandler: &testscommon.RequestHandlerStub{}, + MiniBlockPool: dataRetrieverMock.NewPoolsHolderMock().MiniBlocks(), + PreProcessors: &preprocMocks.PreProcessorContainerMock{}, + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(5), + EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + } +} diff --git a/process/coordinator/preprocData.go b/process/coordinator/preprocData.go new file mode 100644 index 00000000000..78d2407f140 --- /dev/null +++ b/process/coordinator/preprocData.go @@ -0,0 +1,46 @@ +package coordinator + +import ( + "sort" + "sync" + + "github.com/multiversx/mx-chain-core-go/data/block" + + "github.com/multiversx/mx-chain-go/process" +) + +type preProcData struct { + mutPreProcessor sync.RWMutex + txPreProcessors map[block.Type]process.PreProcessor + keysTxPreProcs []block.Type +} + +func newPreProcData(container process.PreProcessorsContainer) (*preProcData, error) { + ppd := &preProcData{} + ppd.txPreProcessors = make(map[block.Type]process.PreProcessor) + ppd.keysTxPreProcs = container.Keys() + sort.Slice(ppd.keysTxPreProcs, func(i, j int) bool { + return ppd.keysTxPreProcs[i] < ppd.keysTxPreProcs[j] + }) + for _, value := range ppd.keysTxPreProcs { + preProc, errGet := container.Get(value) + if errGet != nil { + return nil, errGet + } + ppd.txPreProcessors[value] = preProc + } + + return ppd, nil +} + +func (ppd *preProcData) getPreProcessor(blockType block.Type) process.PreProcessor { + ppd.mutPreProcessor.RLock() + preprocessor, exists := ppd.txPreProcessors[blockType] + ppd.mutPreProcessor.RUnlock() + + if !exists { + return nil + } + + return preprocessor +} diff --git a/process/coordinator/process.go b/process/coordinator/process.go index dbc68612649..5784c767249 100644 --- a/process/coordinator/process.go +++ b/process/coordinator/process.go @@ -17,6 +17,8 @@ import ( "github.com/multiversx/mx-chain-core-go/marshal" logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/dataRetriever" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/block/preprocess" @@ -24,8 +26,6 @@ import ( "github.com/multiversx/mx-chain-go/process/factory" "github.com/multiversx/mx-chain-go/sharding" "github.com/multiversx/mx-chain-go/state" - "github.com/multiversx/mx-chain-go/storage" - "github.com/multiversx/mx-chain-go/storage/cache" ) var _ process.TransactionCoordinator = (*transactionCoordinator)(nil) @@ -51,9 +51,9 @@ type ArgTransactionCoordinator struct { Marshalizer marshal.Marshalizer ShardCoordinator sharding.Coordinator Accounts state.AccountsAdapter - MiniBlockPool storage.Cacher - RequestHandler process.RequestHandler + DataPool dataRetriever.PoolsHolder PreProcessors process.PreProcessorsContainer + PreProcessorsProposal process.PreProcessorsContainer InterProcessors process.IntermediateProcessorContainer GasHandler process.GasHandler FeeHandler process.TransactionFeeHandler @@ -63,36 +63,35 @@ type ArgTransactionCoordinator struct { TxTypeHandler process.TxTypeHandler TransactionsLogProcessor process.TransactionLogProcessor EnableEpochsHandler common.EnableEpochsHandler + EnableRoundsHandler common.EnableRoundsHandler ScheduledTxsExecutionHandler process.ScheduledTxsExecutionHandler DoubleTransactionsDetector process.DoubleTransactionDetector ProcessedMiniBlocksTracker process.ProcessedMiniBlocksTracker TxExecutionOrderHandler common.TxExecutionOrderHandler + BlockDataRequester process.BlockDataRequester + BlockDataRequesterProposal process.BlockDataRequester + GasComputation process.GasComputation + AOTSelector process.AOTTransactionSelector } type transactionCoordinator struct { shardCoordinator sharding.Coordinator accounts state.AccountsAdapter - miniBlockPool storage.Cacher + dataPool dataRetriever.PoolsHolder hasher hashing.Hasher marshalizer marshal.Marshalizer - mutPreProcessor sync.RWMutex - txPreProcessors map[block.Type]process.PreProcessor - keysTxPreProcs []block.Type + preProcExecution *preProcData + preProcProposal *preProcData mutInterimProcessors sync.RWMutex interimProcessors map[block.Type]process.IntermediateTransactionHandler keysInterimProcs []block.Type - mutRequestedTxs sync.RWMutex - requestedTxs map[block.Type]int - - onRequestMiniBlocks func(shardId uint32, mbHashes [][]byte) gasHandler process.GasHandler feeHandler process.TransactionFeeHandler blockSizeComputation preprocess.BlockSizeComputationHandler balanceComputation preprocess.BalanceComputationHandler - requestedItemsHandler process.TimeCacher economicsFee process.FeeHandler txTypeHandler process.TxTypeHandler transactionsLogProcessor process.TransactionLogProcessor @@ -100,7 +99,12 @@ type transactionCoordinator struct { doubleTransactionsDetector process.DoubleTransactionDetector processedMiniBlocksTracker process.ProcessedMiniBlocksTracker enableEpochsHandler common.EnableEpochsHandler + enableRoundsHandler common.EnableRoundsHandler txExecutionOrderHandler common.TxExecutionOrderHandler + blockDataRequester process.BlockDataRequester + blockDataRequesterProposal process.BlockDataRequester + gasComputation process.GasComputation + aotSelector process.AOTTransactionSelector } // NewTransactionCoordinator creates a transaction coordinator to run and coordinate preprocessors and processors @@ -113,6 +117,7 @@ func NewTransactionCoordinator(args ArgTransactionCoordinator) (*transactionCoor tc := &transactionCoordinator{ shardCoordinator: args.ShardCoordinator, accounts: args.Accounts, + dataPool: args.DataPool, gasHandler: args.GasHandler, hasher: args.Hasher, marshalizer: args.Marshalizer, @@ -126,25 +131,23 @@ func NewTransactionCoordinator(args ArgTransactionCoordinator) (*transactionCoor doubleTransactionsDetector: args.DoubleTransactionsDetector, processedMiniBlocksTracker: args.ProcessedMiniBlocksTracker, enableEpochsHandler: args.EnableEpochsHandler, + enableRoundsHandler: args.EnableRoundsHandler, txExecutionOrderHandler: args.TxExecutionOrderHandler, + blockDataRequester: args.BlockDataRequester, + blockDataRequesterProposal: args.BlockDataRequesterProposal, + gasComputation: args.GasComputation, + aotSelector: args.AOTSelector, } - tc.miniBlockPool = args.MiniBlockPool - tc.onRequestMiniBlocks = args.RequestHandler.RequestMiniBlocks - tc.requestedTxs = make(map[block.Type]int) - tc.txPreProcessors = make(map[block.Type]process.PreProcessor) tc.interimProcessors = make(map[block.Type]process.IntermediateTransactionHandler) - tc.keysTxPreProcs = args.PreProcessors.Keys() - sort.Slice(tc.keysTxPreProcs, func(i, j int) bool { - return tc.keysTxPreProcs[i] < tc.keysTxPreProcs[j] - }) - for _, value := range tc.keysTxPreProcs { - preProc, errGet := args.PreProcessors.Get(value) - if errGet != nil { - return nil, errGet - } - tc.txPreProcessors[value] = preProc + tc.preProcExecution, err = newPreProcData(args.PreProcessors) + if err != nil { + return nil, err + } + tc.preProcProposal, err = newPreProcData(args.PreProcessorsProposal) + if err != nil { + return nil, err } tc.keysInterimProcs = args.InterProcessors.Keys() @@ -159,116 +162,16 @@ func NewTransactionCoordinator(args ArgTransactionCoordinator) (*transactionCoor tc.interimProcessors[value] = interProc } - tc.requestedItemsHandler = cache.NewTimeCache(common.MaxWaitingTimeToReceiveRequestedItem) - tc.miniBlockPool.RegisterHandler(tc.receivedMiniBlock, core.UniqueIdentifier()) - return tc, nil } -// separateBodyByType creates a map of bodies according to type -func (tc *transactionCoordinator) separateBodyByType(body *block.Body) map[block.Type]*block.Body { - separatedBodies := make(map[block.Type]*block.Body) - for i := 0; i < len(body.MiniBlocks); i++ { - mb := body.MiniBlocks[i] - - separatedMbType := mb.Type - if mb.Type == block.InvalidBlock { - separatedMbType = block.TxBlock - } - - if _, ok := separatedBodies[separatedMbType]; !ok { - separatedBodies[separatedMbType] = &block.Body{} - } - - separatedBodies[separatedMbType].MiniBlocks = append(separatedBodies[separatedMbType].MiniBlocks, mb) - } - - return separatedBodies -} - -// initRequestedTxs init the requested txs number -func (tc *transactionCoordinator) initRequestedTxs() { - tc.mutRequestedTxs.Lock() - tc.requestedTxs = make(map[block.Type]int) - tc.mutRequestedTxs.Unlock() -} - -// RequestBlockTransactions verifies missing transaction and requests them -func (tc *transactionCoordinator) RequestBlockTransactions(body *block.Body) { - if check.IfNil(body) { - return - } - - separatedBodies := tc.separateBodyByType(body) - - tc.initRequestedTxs() - - wg := sync.WaitGroup{} - wg.Add(len(separatedBodies)) - - for key, value := range separatedBodies { - go func(blockType block.Type, blockBody *block.Body) { - preproc := tc.getPreProcessor(blockType) - if check.IfNil(preproc) { - wg.Done() - return - } - requestedTxs := preproc.RequestBlockTransactions(blockBody) - - tc.mutRequestedTxs.Lock() - tc.requestedTxs[blockType] = requestedTxs - tc.mutRequestedTxs.Unlock() - - wg.Done() - }(key, value) - } - - wg.Wait() -} - -// IsDataPreparedForProcessing verifies if all the needed data is prepared -func (tc *transactionCoordinator) IsDataPreparedForProcessing(haveTime func() time.Duration) error { - var errFound error - errMutex := sync.Mutex{} - - wg := sync.WaitGroup{} - - tc.mutRequestedTxs.RLock() - wg.Add(len(tc.requestedTxs)) - - for key, value := range tc.requestedTxs { - go func(blockType block.Type, requestedTxs int) { - preproc := tc.getPreProcessor(blockType) - if check.IfNil(preproc) { - wg.Done() - return - } - - err := preproc.IsDataPrepared(requestedTxs, haveTime) - if err != nil { - log.Trace("IsDataPrepared", "error", err.Error()) - - errMutex.Lock() - errFound = err - errMutex.Unlock() - } - wg.Done() - }(key, value) - } - - wg.Wait() - tc.mutRequestedTxs.RUnlock() - - return errFound -} - // SaveTxsToStorage saves transactions from block body into storage units func (tc *transactionCoordinator) SaveTxsToStorage(body *block.Body) { if check.IfNil(body) { return } - separatedBodies := tc.separateBodyByType(body) + separatedBodies := process.SeparateBodyByType(body) for key, value := range separatedBodies { tc.saveTxsToStorage(key, value) } @@ -279,7 +182,7 @@ func (tc *transactionCoordinator) SaveTxsToStorage(body *block.Body) { } func (tc *transactionCoordinator) saveTxsToStorage(blockType block.Type, blockBody *block.Body) { - preproc := tc.getPreProcessor(blockType) + preproc := tc.preProcExecution.getPreProcessor(blockType) if check.IfNil(preproc) { return } @@ -305,7 +208,7 @@ func (tc *transactionCoordinator) RestoreBlockDataFromStorage(body *block.Body) return 0, nil } - separatedBodies := tc.separateBodyByType(body) + separatedBodies := process.SeparateBodyByType(body) var errFound error localMutex := sync.Mutex{} @@ -316,13 +219,13 @@ func (tc *transactionCoordinator) RestoreBlockDataFromStorage(body *block.Body) for key, value := range separatedBodies { go func(blockType block.Type, blockBody *block.Body) { - preproc := tc.getPreProcessor(blockType) + preproc := tc.preProcExecution.getPreProcessor(blockType) if check.IfNil(preproc) { wg.Done() return } - restoredTxs, err := preproc.RestoreBlockDataIntoPools(blockBody, tc.miniBlockPool) + restoredTxs, err := preproc.RestoreBlockDataIntoPools(blockBody, tc.dataPool.MiniBlocks()) if err != nil { log.Trace("RestoreBlockDataIntoPools", "error", err.Error()) @@ -351,7 +254,7 @@ func (tc *transactionCoordinator) RemoveBlockDataFromPool(body *block.Body) erro return nil } - separatedBodies := tc.separateBodyByType(body) + separatedBodies := process.SeparateBodyByType(body) var errFound error errMutex := sync.Mutex{} @@ -361,13 +264,13 @@ func (tc *transactionCoordinator) RemoveBlockDataFromPool(body *block.Body) erro for key, value := range separatedBodies { go func(blockType block.Type, blockBody *block.Body) { - preproc := tc.getPreProcessor(blockType) + preproc := tc.preProcExecution.getPreProcessor(blockType) if check.IfNil(preproc) { wg.Done() return } - err := preproc.RemoveBlockDataFromPools(blockBody, tc.miniBlockPool) + err := preproc.RemoveBlockDataFromPools(blockBody, tc.dataPool.MiniBlocks()) if err != nil { log.Trace("RemoveBlockDataFromPools", "error", err.Error()) @@ -385,12 +288,12 @@ func (tc *transactionCoordinator) RemoveBlockDataFromPool(body *block.Body) erro } // RemoveTxsFromPool deletes txs from pools -func (tc *transactionCoordinator) RemoveTxsFromPool(body *block.Body) error { +func (tc *transactionCoordinator) RemoveTxsFromPool(body *block.Body, rootHashHolder common.RootHashHolder) error { if check.IfNil(body) { return nil } - separatedBodies := tc.separateBodyByType(body) + separatedBodies := process.SeparateBodyByType(body) var errFound error errMutex := sync.Mutex{} @@ -400,13 +303,13 @@ func (tc *transactionCoordinator) RemoveTxsFromPool(body *block.Body) error { for key, value := range separatedBodies { go func(blockType block.Type, blockBody *block.Body) { - preproc := tc.getPreProcessor(blockType) + preproc := tc.preProcExecution.getPreProcessor(blockType) if check.IfNil(preproc) { wg.Done() return } - err := preproc.RemoveTxsFromPools(blockBody) + err := preproc.RemoveTxsFromPools(blockBody, rootHashHolder) if err != nil { log.Trace("RemoveTxsFromPools", "error", err.Error()) @@ -429,11 +332,15 @@ func (tc *transactionCoordinator) ProcessBlockTransaction( body *block.Body, timeRemaining func() time.Duration, ) error { + if check.IfNil(header) { + return process.ErrNilHeaderHandler + } if check.IfNil(body) { return process.ErrNilBlockBody } - if tc.isMaxBlockSizeReached(body) { + isAsyncExecEnabled := header.IsHeaderV3() + if !isAsyncExecEnabled && tc.isMaxBlockSizeReached(body) { return process.ErrMaxBlockSizeReached } @@ -471,6 +378,59 @@ func (tc *transactionCoordinator) ProcessBlockTransaction( return nil } +// GetCreatedMiniBlocksFromMe returns the created mini blocks from me +func (tc *transactionCoordinator) GetCreatedMiniBlocksFromMe() block.MiniBlockSlice { + miniBlocks := make(block.MiniBlockSlice, 0) + for _, blockType := range tc.preProcExecution.keysTxPreProcs { + preProc := tc.preProcExecution.getPreProcessor(blockType) + if check.IfNil(preProc) { + log.Error("GetCreatedMiniBlocksFromMe", "error", process.ErrMissingPreProcessor) + continue + } + + miniBlocks = append(miniBlocks, preProc.GetCreatedMiniBlocksFromMe()...) + } + + return miniBlocks +} + +// GetUnExecutableTransactions will return a map with hashes of unexecutable transactions +func (tc *transactionCoordinator) GetUnExecutableTransactions() map[string]struct{} { + return tc.getUnExecutableTransactions() +} + +func (tc *transactionCoordinator) getUnExecutableTransactions() map[string]struct{} { + unExecutableTxs := make(map[string]struct{}) + for _, blockType := range tc.preProcExecution.keysTxPreProcs { + preProc := tc.preProcExecution.getPreProcessor(blockType) + if check.IfNil(preProc) { + log.Error("getUnExecutableTransactions", "error", process.ErrMissingPreProcessor) + continue + } + + for txHash := range preProc.GetUnExecutableTransactions() { + unExecutableTxs[txHash] = struct{}{} + } + } + + return unExecutableTxs +} + +// RequestMiniBlocksAndTransactions forwards the request to block data requester +func (tc *transactionCoordinator) RequestMiniBlocksAndTransactions(header data.HeaderHandler) { + tc.blockDataRequester.RequestMiniBlocksAndTransactions(header) +} + +// RequestBlockTransactions forwards the request to block data requester +func (tc *transactionCoordinator) RequestBlockTransactions(body *block.Body) { + tc.blockDataRequester.RequestBlockTransactions(body) +} + +// IsDataPreparedForProcessing checks if the data is prepared for processing +func (tc *transactionCoordinator) IsDataPreparedForProcessing(haveTime func() time.Duration) error { + return tc.blockDataRequester.IsDataPreparedForProcessing(haveTime) +} + func (tc *transactionCoordinator) processMiniBlocksFromMe( header data.HeaderHandler, body *block.Body, @@ -483,7 +443,7 @@ func (tc *transactionCoordinator) processMiniBlocksFromMe( } numMiniBlocksProcessed := 0 - separatedBodies := tc.separateBodyByType(body) + separatedBodies := process.SeparateBodyByType(body) defer func() { log.Debug("transactionCoordinator.processMiniBlocksFromMe: gas consumed, refunded and penalized info", @@ -495,12 +455,12 @@ func (tc *transactionCoordinator) processMiniBlocksFromMe( }() // processing has to be done in order, as the order of different type of transactions over the same account is strict - for _, blockType := range tc.keysTxPreProcs { + for _, blockType := range tc.preProcExecution.keysTxPreProcs { if separatedBodies[blockType] == nil { continue } - preProc := tc.getPreProcessor(blockType) + preProc := tc.preProcExecution.getPreProcessor(blockType) if check.IfNil(preProc) { return process.ErrMissingPreProcessor } @@ -541,7 +501,7 @@ func (tc *transactionCoordinator) processMiniBlocksToMe( return mbIndex, nil } - preProc := tc.getPreProcessor(miniBlock.Type) + preProc := tc.preProcExecution.getPreProcessor(miniBlock.Type) if check.IfNil(preProc) { return mbIndex, process.ErrMissingPreProcessor } @@ -593,7 +553,7 @@ func (tc *transactionCoordinator) CreateMbsAndProcessCrossShardTransactionsDstMe tc.handleCreateMiniBlocksDestMeInit(headerHash) - finalCrossMiniBlockInfos := tc.getFinalCrossMiniBlockInfos(hdr.GetOrderedCrossMiniblocksWithDst(tc.shardCoordinator.SelfId()), hdr) + finalCrossMiniBlockInfos := tc.blockDataRequester.GetFinalCrossMiniBlockInfoAndRequestMissing(hdr) defer func() { log.Debug("transactionCoordinator.CreateMbsAndProcessCrossShardTransactionsDstMe: gas provided, refunded and penalized info", @@ -608,8 +568,6 @@ func (tc *transactionCoordinator) CreateMbsAndProcessCrossShardTransactionsDstMe "total gas penalized", tc.gasHandler.TotalGasPenalized()) }() - tc.requestMissingMiniBlocksAndTransactions(finalCrossMiniBlockInfos) - for _, miniBlockInfo := range finalCrossMiniBlockInfos { if !haveTime() && !haveAdditionalTime() { log.Debug("CreateMbsAndProcessCrossShardTransactionsDstMe", @@ -647,7 +605,7 @@ func (tc *transactionCoordinator) CreateMbsAndProcessCrossShardTransactionsDstMe continue } - miniVal, _ := tc.miniBlockPool.Peek(miniBlockInfo.Hash) + miniVal, _ := tc.dataPool.MiniBlocks().Peek(miniBlockInfo.Hash) if miniVal == nil { shouldSkipShard[miniBlockInfo.SenderShardID] = true log.Trace("transactionCoordinator.CreateMbsAndProcessCrossShardTransactionsDstMe: mini block not found and was requested", @@ -684,12 +642,12 @@ func (tc *transactionCoordinator) CreateMbsAndProcessCrossShardTransactionsDstMe continue } - preproc := tc.getPreProcessor(miniBlock.Type) + preproc := tc.preProcExecution.getPreProcessor(miniBlock.Type) if check.IfNil(preproc) { return nil, 0, false, fmt.Errorf("%w unknown block type %d", process.ErrNilPreProcessor, miniBlock.Type) } - requestedTxs := preproc.RequestTransactionsForMiniBlock(miniBlock) + _, requestedTxs := preproc.GetTransactionsAndRequestMissingForMiniBlock(miniBlock) if requestedTxs > 0 { shouldSkipShard[miniBlockInfo.SenderShardID] = true log.Trace("transactionCoordinator.CreateMbsAndProcessCrossShardTransactionsDstMe: transactions not found and were requested", @@ -754,48 +712,6 @@ func (tc *transactionCoordinator) CreateMbsAndProcessCrossShardTransactionsDstMe return createMBDestMeExecutionInfo.miniBlocks, createMBDestMeExecutionInfo.numTxAdded, allMBsProcessed, nil } -func (tc *transactionCoordinator) requestMissingMiniBlocksAndTransactions(mbsInfo []*data.MiniBlockInfo) { - mapMissingMiniBlocksPerShard := make(map[uint32][][]byte) - - tc.requestedItemsHandler.Sweep() - - for _, mbInfo := range mbsInfo { - object, isMiniBlockFound := tc.miniBlockPool.Peek(mbInfo.Hash) - if !isMiniBlockFound { - log.Debug("transactionCoordinator.requestMissingMiniBlocksAndTransactions: mini block not found and was requested", - "sender shard", mbInfo.SenderShardID, - "hash", mbInfo.Hash, - "round", mbInfo.Round, - ) - mapMissingMiniBlocksPerShard[mbInfo.SenderShardID] = append(mapMissingMiniBlocksPerShard[mbInfo.SenderShardID], mbInfo.Hash) - _ = tc.requestedItemsHandler.Add(string(mbInfo.Hash)) - continue - } - - miniBlock, isMiniBlock := object.(*block.MiniBlock) - if !isMiniBlock { - log.Warn("transactionCoordinator.requestMissingMiniBlocksAndTransactions", "mb hash", mbInfo.Hash, "error", process.ErrWrongTypeAssertion) - continue - } - - preproc := tc.getPreProcessor(miniBlock.Type) - if check.IfNil(preproc) { - log.Warn("transactionCoordinator.requestMissingMiniBlocksAndTransactions: getPreProcessor", "mb type", miniBlock.Type, "error", process.ErrNilPreProcessor) - continue - } - - numTxsRequested := preproc.RequestTransactionsForMiniBlock(miniBlock) - if numTxsRequested > 0 { - log.Debug("transactionCoordinator.requestMissingMiniBlocksAndTransactions: RequestTransactionsForMiniBlock", "mb hash", mbInfo.Hash, - "num txs requested", numTxsRequested) - } - } - - for senderShardID, mbsHashes := range mapMissingMiniBlocksPerShard { - go tc.onRequestMiniBlocks(senderShardID, mbsHashes) - } -} - func initMiniBlockDestMeExecutionInfo() *createMiniBlockDestMeExecutionInfo { return &createMiniBlockDestMeExecutionInfo{ processedTxHashes: make([][]byte, 0), @@ -859,29 +775,6 @@ func getProcessedMiniBlockInfo( return processedMbInfo } -func (tc *transactionCoordinator) getFinalCrossMiniBlockInfos( - crossMiniBlockInfos []*data.MiniBlockInfo, - header data.HeaderHandler, -) []*data.MiniBlockInfo { - - if !tc.enableEpochsHandler.IsFlagEnabled(common.ScheduledMiniBlocksFlag) { - return crossMiniBlockInfos - } - - miniBlockInfos := make([]*data.MiniBlockInfo, 0) - for _, crossMiniBlockInfo := range crossMiniBlockInfos { - miniBlockHeader := process.GetMiniBlockHeaderWithHash(header, crossMiniBlockInfo.Hash) - if miniBlockHeader != nil && !miniBlockHeader.IsFinal() { - log.Debug("transactionCoordinator.getFinalCrossMiniBlockInfos: do not execute mini block which is not final", "mb hash", miniBlockHeader.GetHash()) - continue - } - - miniBlockInfos = append(miniBlockInfos, crossMiniBlockInfo) - } - - return miniBlockInfos -} - func (tc *transactionCoordinator) revertIfNeeded(createMBDestMeExecutionInfo *createMiniBlockDestMeExecutionInfo, key []byte) { shouldRevert := tc.shardCoordinator.SelfId() == core.MetachainShardId && len(createMBDestMeExecutionInfo.processedTxHashes) > 0 if !shouldRevert { @@ -913,8 +806,8 @@ func (tc *transactionCoordinator) CreateMbsAndProcessTransactionsFromMe( "total gas penalized", tc.gasHandler.TotalGasPenalized()) }() - for _, blockType := range tc.keysTxPreProcs { - txPreProc := tc.getPreProcessor(blockType) + for _, blockType := range tc.preProcExecution.keysTxPreProcs { + txPreProc := tc.preProcExecution.getPreProcessor(blockType) if check.IfNil(txPreProc) { return nil } @@ -965,12 +858,13 @@ func (tc *transactionCoordinator) CreateBlockStarted() { tc.blockSizeComputation.Init() tc.balanceComputation.Init() tc.txExecutionOrderHandler.Clear() + tc.blockDataRequester.Reset() - tc.mutPreProcessor.RLock() - for _, value := range tc.txPreProcessors { + tc.preProcExecution.mutPreProcessor.RLock() + for _, value := range tc.preProcExecution.txPreProcessors { value.CreateBlockStarted() } - tc.mutPreProcessor.RUnlock() + tc.preProcExecution.mutPreProcessor.RUnlock() tc.mutInterimProcessors.RLock() for _, value := range tc.interimProcessors { @@ -981,18 +875,6 @@ func (tc *transactionCoordinator) CreateBlockStarted() { tc.transactionsLogProcessor.Clean() } -func (tc *transactionCoordinator) getPreProcessor(blockType block.Type) process.PreProcessor { - tc.mutPreProcessor.RLock() - preprocessor, exists := tc.txPreProcessors[blockType] - tc.mutPreProcessor.RUnlock() - - if !exists { - return nil - } - - return preprocessor -} - func (tc *transactionCoordinator) getInterimProcessor(blockType block.Type) process.IntermediateTransactionHandler { tc.mutInterimProcessors.RLock() interProcessor, exists := tc.interimProcessors[blockType] @@ -1027,6 +909,185 @@ func createBroadcastTopic(shardC sharding.Coordinator, destShId uint32, mbType b return transactionTopic, nil } +// ProposedDirectSentTransactionsToBroadcast creates marshaled intra-shard transactions received via direct-send for broadcasting +func (tc *transactionCoordinator) ProposedDirectSentTransactionsToBroadcast(proposedBody data.BodyHandler) map[string][][]byte { + mrsTxs := make(map[string][][]byte) + + bodyPtr, ok := proposedBody.(*block.Body) + if !ok { + log.Warn("ProposedDirectSentTransactionsToBroadcast could not cast body") + return mrsTxs + } + + // should not be any intermediate transactions at this point and all data needed should be in pools + cachedIntermediateTxsMap := make(map[block.Type]map[string]data.TransactionHandler) + + for _, miniBlock := range bodyPtr.MiniBlocks { + isIntraShardMb := miniBlock.SenderShardID == miniBlock.ReceiverShardID && + miniBlock.SenderShardID == tc.shardCoordinator.SelfId() + if !isIntraShardMb { + continue + } + + tc.appendTransactionsForMiniBlock(miniBlock, cachedIntermediateTxsMap, mrsTxs, tc.shouldSkipTransaction) + } + + return mrsTxs +} + +func (tc *transactionCoordinator) shouldSkipTransaction(txHash []byte) bool { + directSentTransactionsCache := tc.dataPool.DirectSentTransactions() + _, found := directSentTransactionsCache.Get(txHash) + return !found +} + +// CreateMarshalledDataForHeader creates marshaled data for broadcasting based on header +func (tc *transactionCoordinator) CreateMarshalledDataForHeader(header data.HeaderHandler, body *block.Body, miniBlocksMap map[string]block.MiniBlockSlice) map[string][][]byte { + mrsTxs := make(map[string][][]byte) + + if check.IfNil(header) || check.IfNil(body) { + return mrsTxs + } + + if !header.IsHeaderV3() { + return tc.CreateMarshalizedData(body) + } + + return tc.createMarshalledDataV3(miniBlocksMap) +} + +func (tc *transactionCoordinator) createMarshalledDataV3(miniBlocksMap map[string]block.MiniBlockSlice) map[string][][]byte { + // for header v3, the mini blocks are from execution results + mrsTxs := make(map[string][][]byte) + + shouldNotSkipTransactionFunc := func(_ []byte) bool { return false } + + for headerHash, miniBlocks := range miniBlocksMap { + cachedIntermediateTxsMap, err := common.GetCachedIntermediateTxs(tc.dataPool.PostProcessTransactions(), []byte(headerHash)) + if err != nil { + log.Warn("createMarshalledDataV3.GetCachedIntermediateTxs", "error", err.Error()) + } + + for _, miniBlock := range miniBlocks { + if miniBlock.SenderShardID != tc.shardCoordinator.SelfId() || + miniBlock.ReceiverShardID == tc.shardCoordinator.SelfId() { + continue + } + + tc.appendTransactionsForMiniBlock(miniBlock, cachedIntermediateTxsMap, mrsTxs, shouldNotSkipTransactionFunc) + } + } + + return mrsTxs +} + +func (tc *transactionCoordinator) appendTransactionsForMiniBlock( + miniBlock *block.MiniBlock, + cachedIntermediateTxsMap map[block.Type]map[string]data.TransactionHandler, + mrsTxs map[string][][]byte, + shouldSkipTransactionFunc func(txHash []byte) bool, +) { + broadcastTopic, errCreate := createBroadcastTopic(tc.shardCoordinator, miniBlock.ReceiverShardID, miniBlock.Type) + if errCreate != nil { + log.Warn("appendTransactionsForMiniBlock.createBroadcastTopic", "error", errCreate.Error()) + return + } + + if miniBlock.Type == block.TxBlock || miniBlock.Type == block.InvalidBlock { + tc.appendTransactionsFromPoolForMiniBlock(tc.dataPool.Transactions(), miniBlock, broadcastTopic, mrsTxs, shouldSkipTransactionFunc) + return + } + + if miniBlock.Type == block.RewardsBlock { + tc.appendTransactionsFromPoolForMiniBlock(tc.dataPool.RewardTransactions(), miniBlock, broadcastTopic, mrsTxs, shouldSkipTransactionFunc) + return + } + + if miniBlock.Type == block.PeerBlock { + tc.appendTransactionsFromPoolForMiniBlock(tc.dataPool.ValidatorsInfo(), miniBlock, broadcastTopic, mrsTxs, shouldSkipTransactionFunc) + } + + tc.appendPostProcessTransactionsForMiniBlocks(cachedIntermediateTxsMap, miniBlock, broadcastTopic, mrsTxs, shouldSkipTransactionFunc) +} + +func (tc *transactionCoordinator) appendTransactionsFromPoolForMiniBlock( + pool dataRetriever.ShardedDataCacherNotifier, + miniBlock *block.MiniBlock, + broadcastTopic string, + mrsTxs map[string][][]byte, + shouldSkipTransactionFunc func(txHash []byte) bool, +) { + for _, txHash := range miniBlock.TxHashes { + if shouldSkipTransactionFunc(txHash) { + continue + } + + rawTx, ok := pool.SearchFirstData(txHash) + if !ok { + log.Warn("appendTransactionsFromPoolForMiniBlock could not find transaction for miniBlock in pool", "hash", txHash) + return + } + + tx, ok := rawTx.(data.TransactionHandler) + if !ok { + log.Warn("appendTransactionsFromPoolForMiniBlock could not cast transaction from pool", "hash", txHash) + return + } + + txBuff, errMarshal := tc.marshalizer.Marshal(tx) + if errMarshal != nil { + log.Warn("appendTransactionsFromPoolForMiniBlock.Marshal", "error", errMarshal.Error()) + return + } + + mrsTxs[broadcastTopic] = append(mrsTxs[broadcastTopic], txBuff) + } +} + +func (tc *transactionCoordinator) appendPostProcessTransactionsForMiniBlocks( + cachedIntermediateTxsMap map[block.Type]map[string]data.TransactionHandler, + miniBlock *block.MiniBlock, + broadcastTopic string, + mrsTxs map[string][][]byte, + shouldSkipTransactionFunc func(txHash []byte) bool, +) { + transactionsForMiniBlock, ok := cachedIntermediateTxsMap[miniBlock.Type] + if !ok { + log.Warn("appendPostProcessTransactionsForMiniBlocks could not find transactions for miniBlock", "type", miniBlock.Type) + return + } + + tc.appendMarshalledDataForTransactions(miniBlock.TxHashes, transactionsForMiniBlock, broadcastTopic, mrsTxs, shouldSkipTransactionFunc) +} + +func (tc *transactionCoordinator) appendMarshalledDataForTransactions( + txHashes [][]byte, + transactionsForMiniBlock map[string]data.TransactionHandler, + broadcastTopic string, + mrsTxs map[string][][]byte, + shouldSkipTransactionFunc func(txHash []byte) bool, +) { + for _, txHash := range txHashes { + if shouldSkipTransactionFunc(txHash) { + continue + } + + tx, ok := transactionsForMiniBlock[string(txHash)] + if !ok { + log.Warn("appendMarshalledDataForTransactions.createBroadcastTopic", "txHash", txHash) + continue + } + + txBuff, errMarshal := tc.marshalizer.Marshal(tx) + if errMarshal != nil { + log.Warn("appendMarshalledDataForTransactions.Marshal", "error", errMarshal.Error()) + continue + } + + mrsTxs[broadcastTopic] = append(mrsTxs[broadcastTopic], txBuff) + } +} + // CreateMarshalizedData creates marshalized data for broadcasting func (tc *transactionCoordinator) CreateMarshalizedData(body *block.Body) map[string][][]byte { mrsTxs := make(map[string][][]byte) @@ -1049,7 +1110,7 @@ func (tc *transactionCoordinator) CreateMarshalizedData(body *block.Body) map[st } isPreProcessMiniBlock := miniBlock.Type == block.TxBlock - preproc := tc.getPreProcessor(miniBlock.Type) + preproc := tc.preProcExecution.getPreProcessor(miniBlock.Type) if !check.IfNil(preproc) && isPreProcessMiniBlock { dataMarshalizer, ok := preproc.(process.DataMarshalizer) if ok { @@ -1103,7 +1164,7 @@ func (tc *transactionCoordinator) GetAllCurrentUsedTxs(blockType block.Type) map txPool := make(map[string]data.TransactionHandler) interTxPool := make(map[string]data.TransactionHandler) - preProc := tc.getPreProcessor(blockType) + preProc := tc.preProcExecution.getPreProcessor(blockType) if preProc != nil { txPool = preProc.GetAllCurrentUsedTxs() } @@ -1121,68 +1182,11 @@ func (tc *transactionCoordinator) GetAllCurrentUsedTxs(blockType block.Type) map } // GetAllCurrentLogs return the cached logs data from current round -func (tc *transactionCoordinator) GetAllCurrentLogs() []*data.LogData { +func (tc *transactionCoordinator) GetAllCurrentLogs() []data.LogDataHandler { return tc.transactionsLogProcessor.GetAllCurrentLogs() } -// RequestMiniBlocksAndTransactions requests mini blocks and transactions if missing -func (tc *transactionCoordinator) RequestMiniBlocksAndTransactions(header data.HeaderHandler) { - if check.IfNil(header) { - return - } - - finalCrossMiniBlockHashes := tc.getFinalCrossMiniBlockHashes(header) - mbsInfo := make([]*data.MiniBlockInfo, 0, len(finalCrossMiniBlockHashes)) - for mbHash, senderShardID := range finalCrossMiniBlockHashes { - mbsInfo = append(mbsInfo, &data.MiniBlockInfo{ - Hash: []byte(mbHash), - SenderShardID: senderShardID, - Round: header.GetRound(), - }) - } - - tc.requestMissingMiniBlocksAndTransactions(mbsInfo) -} - -func (tc *transactionCoordinator) getFinalCrossMiniBlockHashes(headerHandler data.HeaderHandler) map[string]uint32 { - if !tc.enableEpochsHandler.IsFlagEnabled(common.ScheduledMiniBlocksFlag) { - return headerHandler.GetMiniBlockHeadersWithDst(tc.shardCoordinator.SelfId()) - } - return process.GetFinalCrossMiniBlockHashes(headerHandler, tc.shardCoordinator.SelfId()) -} - -func (tc *transactionCoordinator) receivedMiniBlock(key []byte, value interface{}) { - if key == nil { - return - } - - if !tc.requestedItemsHandler.Has(string(key)) { - return - } - - miniBlock, ok := value.(*block.MiniBlock) - if !ok { - log.Warn("transactionCoordinator.receivedMiniBlock", "error", process.ErrWrongTypeAssertion) - return - } - - log.Trace("transactionCoordinator.receivedMiniBlock", "hash", key) - - preproc := tc.getPreProcessor(miniBlock.Type) - if check.IfNil(preproc) { - log.Warn("transactionCoordinator.receivedMiniBlock", - "error", fmt.Errorf("%w unknown block type %d", process.ErrNilPreProcessor, miniBlock.Type)) - return - } - - numTxsRequested := preproc.RequestTransactionsForMiniBlock(miniBlock) - if numTxsRequested > 0 { - log.Debug("transactionCoordinator.receivedMiniBlock", "hash", key, - "num txs requested", numTxsRequested) - } -} - -// processMiniBlockComplete - all transactions must be processed together, otherwise error +// processCompleteMiniBlock - all transactions must be processed together, otherwise error func (tc *transactionCoordinator) processCompleteMiniBlock( preproc process.PreProcessor, miniBlock *block.MiniBlock, @@ -1333,9 +1337,12 @@ func (tc *transactionCoordinator) RevertProcessedTxsResults(txHashes [][]byte, k // VerifyCreatedBlockTransactions checks whether the created transactions are the same as the one proposed func (tc *transactionCoordinator) VerifyCreatedBlockTransactions(hdr data.HeaderHandler, body *block.Body) error { + if hdr.IsHeaderV3() { + return tc.verifyCreatedMiniBlocksSanity(body) + } + errMutex := sync.Mutex{} var errFound error - wg := sync.WaitGroup{} tc.mutInterimProcessors.RLock() @@ -1382,29 +1389,24 @@ func (tc *transactionCoordinator) VerifyCreatedBlockTransactions(hdr data.Header // CreateReceiptsHash will return the hash for the receipts func (tc *transactionCoordinator) CreateReceiptsHash() ([]byte, error) { - tc.mutInterimProcessors.RLock() - defer tc.mutInterimProcessors.RUnlock() + createdIntermediateMbs := tc.GetCreatedInShardMiniBlocks() + return tc.createReceiptHash(createdIntermediateMbs) +} +func (tc *transactionCoordinator) createReceiptHash(miniBlocks []*block.MiniBlock) ([]byte, error) { allReceiptsHashes := make([][]byte, 0) - for _, value := range tc.keysInterimProcs { - interProc, ok := tc.interimProcessors[value] - if !ok { - continue - } - - mb := interProc.GetCreatedInShardMiniBlock() + for _, mb := range miniBlocks { if mb == nil { - log.Trace("CreateReceiptsHash nil inshard miniblock for type", "type", value) + log.Trace("CreateReceiptsHash nil inshard miniblock") continue } - log.Trace("CreateReceiptsHash.GetCreatedInShardMiniBlock", + log.Trace("CreateReceiptsHash", "type", mb.Type, "senderShardID", mb.SenderShardID, "receiverShardID", mb.ReceiverShardID, "numTxHashes", len(mb.TxHashes), - "interimProcType", value, ) for _, hash := range mb.TxHashes { @@ -1419,8 +1421,7 @@ func (tc *transactionCoordinator) CreateReceiptsHash() ([]byte, error) { allReceiptsHashes = append(allReceiptsHashes, currHash) } - finalReceiptHash, err := core.CalculateHash(tc.marshalizer, tc.hasher, &batch.Batch{Data: allReceiptsHashes}) - return finalReceiptHash, err + return core.CalculateHash(tc.marshalizer, tc.hasher, &batch.Batch{Data: allReceiptsHashes}) } // GetCreatedInShardMiniBlocks will return the intra-shard created miniblocks @@ -1472,7 +1473,7 @@ func (tc *transactionCoordinator) isMaxBlockSizeReached(body *block.Body) bool { allTxs := make(map[string]data.TransactionHandler) - preProc := tc.getPreProcessor(block.TxBlock) + preProc := tc.preProcExecution.getPreProcessor(block.TxBlock) if check.IfNil(preProc) { log.Warn("transactionCoordinator.isMaxBlockSizeReached: preProc is nil", "blockType", block.TxBlock) } else { @@ -1766,11 +1767,23 @@ func checkTransactionCoordinatorNilParameters(arguments ArgTransactionCoordinato if check.IfNil(arguments.Accounts) { return process.ErrNilAccountsAdapter } - if check.IfNil(arguments.MiniBlockPool) { + if check.IfNil(arguments.DataPool) { + return process.ErrNilPoolsHolder + } + if check.IfNil(arguments.DataPool.MiniBlocks()) { return process.ErrNilMiniBlockPool } - if check.IfNil(arguments.RequestHandler) { - return process.ErrNilRequestHandler + if check.IfNil(arguments.DataPool.PostProcessTransactions()) { + return process.ErrNilPostProcessTransactionsCache + } + if check.IfNil(arguments.DataPool.Transactions()) { + return process.ErrNilTxDataPool + } + if check.IfNil(arguments.DataPool.RewardTransactions()) { + return process.ErrNilRewardTxDataPool + } + if check.IfNil(arguments.DataPool.ValidatorsInfo()) { + return process.ErrNilValidatorInfoPool } if check.IfNil(arguments.InterProcessors) { return process.ErrNilIntermediateProcessorContainer @@ -1778,6 +1791,9 @@ func checkTransactionCoordinatorNilParameters(arguments ArgTransactionCoordinato if check.IfNil(arguments.PreProcessors) { return process.ErrNilPreProcessorsContainer } + if check.IfNil(arguments.PreProcessorsProposal) { + return fmt.Errorf("%w for proposal", process.ErrNilPreProcessorsContainer) + } if check.IfNil(arguments.GasHandler) { return process.ErrNilGasHandler } @@ -1808,6 +1824,9 @@ func checkTransactionCoordinatorNilParameters(arguments ArgTransactionCoordinato if check.IfNil(arguments.EnableEpochsHandler) { return process.ErrNilEnableEpochsHandler } + if check.IfNil(arguments.EnableRoundsHandler) { + return process.ErrNilEnableRoundsHandler + } err := core.CheckHandlerCompatibility(arguments.EnableEpochsHandler, []core.EnableEpochFlag{ common.ScheduledMiniBlocksFlag, common.MiniBlockPartialExecutionFlag, @@ -1828,6 +1847,18 @@ func checkTransactionCoordinatorNilParameters(arguments ArgTransactionCoordinato if check.IfNil(arguments.TxExecutionOrderHandler) { return process.ErrNilTxExecutionOrderHandler } + if check.IfNil(arguments.BlockDataRequester) { + return process.ErrNilBlockDataRequester + } + if check.IfNil(arguments.BlockDataRequesterProposal) { + return fmt.Errorf("%w for proposal", process.ErrNilBlockDataRequester) + } + if check.IfNil(arguments.GasComputation) { + return process.ErrNilGasComputation + } + if check.IfNil(arguments.AOTSelector) { + return process.ErrNilAOTSelector + } return nil } @@ -1867,7 +1898,7 @@ func (tc *transactionCoordinator) GetAllIntermediateTxs() map[block.Type]map[str // AddTxsFromMiniBlocks adds transactions from given mini blocks needed by the current block func (tc *transactionCoordinator) AddTxsFromMiniBlocks(miniBlocks block.MiniBlockSlice) { for _, mb := range miniBlocks { - preProc := tc.getPreProcessor(mb.Type) + preProc := tc.preProcExecution.getPreProcessor(mb.Type) if check.IfNil(preProc) { log.Warn("transactionCoordinator.AddTxsFromMiniBlocks: preProc is nil", "blockType", mb.Type) continue @@ -1879,7 +1910,7 @@ func (tc *transactionCoordinator) AddTxsFromMiniBlocks(miniBlocks block.MiniBloc // AddTransactions adds the given transactions to the preprocessor func (tc *transactionCoordinator) AddTransactions(txs []data.TransactionHandler, blockType block.Type) { - preProc := tc.getPreProcessor(blockType) + preProc := tc.preProcExecution.getPreProcessor(blockType) if check.IfNil(preProc) { log.Warn("transactionCoordinator.AddTransactions preProc is nil", "blockType", blockType) return diff --git a/process/coordinator/processProposal.go b/process/coordinator/processProposal.go new file mode 100644 index 00000000000..3110c69ba39 --- /dev/null +++ b/process/coordinator/processProposal.go @@ -0,0 +1,306 @@ +package coordinator + +import ( + "fmt" + + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/block/processedMb" +) + +// CreateMbsCrossShardDstMe creates cross-shard miniblocks for the current shard +func (tc *transactionCoordinator) CreateMbsCrossShardDstMe( + hdr data.HeaderHandler, + processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo, +) (addedMiniBlocksAndHashes []block.MiniblockAndHash, pendingMiniBlocksAndHashes []block.MiniblockAndHash, numTransactions uint32, allMiniBlocksAdded bool, hasMissingData bool, err error) { + if check.IfNil(hdr) { + return nil, nil, 0, false, false, process.ErrNilHeaderHandler + } + + numMiniBlocksAlreadyProcessed := 0 + miniBlocksAndHashes := make([]block.MiniblockAndHash, 0) + numTransactions = uint32(0) + shouldSkipShard := make(map[uint32]bool) + + finalCrossMiniBlockInfos := tc.blockDataRequesterProposal.GetFinalCrossMiniBlockInfoAndRequestMissing(hdr) + defer func() { + log.Debug("transactionCoordinator.CreateMbsCrossShardDstMe", + "header round", hdr.GetRound(), + "header nonce", hdr.GetNonce(), + "num mini blocks to be processed", len(finalCrossMiniBlockInfos), + "total gas consumed in self shard", tc.gasComputation.TotalGasConsumedInSelfShard()) + }() + + txsForMbs := make(map[string][]data.TransactionHandler, 0) + mbsSlice := make([]data.MiniBlockHeaderHandler, 0) + for _, miniBlockInfo := range finalCrossMiniBlockInfos { + isAsyncExecutionEnabled := hdr.IsHeaderV3() + if !isAsyncExecutionEnabled && tc.blockSizeComputation.IsMaxBlockSizeReached(0, 0) { + log.Debug("transactionCoordinator.CreateMbsCrossShardDstMe", + "stop creating", "max block size has been reached") + break + } + + if shouldSkipShard[miniBlockInfo.SenderShardID] { + log.Trace("transactionCoordinator.CreateMbsCrossShardDstMe: should skip shard", + "sender shard", miniBlockInfo.SenderShardID, + "hash", miniBlockInfo.Hash, + "round", miniBlockInfo.Round, + ) + continue + } + + processedMbInfo := getProcessedMiniBlockInfo(processedMiniBlocksInfo, miniBlockInfo.Hash) + if processedMbInfo.FullyProcessed { + numMiniBlocksAlreadyProcessed++ + log.Trace("transactionCoordinator.CreateMbsCrossShardDstMe: mini block already processed", + "sender shard", miniBlockInfo.SenderShardID, + "hash", miniBlockInfo.Hash, + "round", miniBlockInfo.Round, + ) + continue + } + + miniVal, _ := tc.dataPool.MiniBlocks().Peek(miniBlockInfo.Hash) + if miniVal == nil { + shouldSkipShard[miniBlockInfo.SenderShardID] = true + log.Trace("transactionCoordinator.CreateMbsCrossShardDstMe: mini block not found and was skipped", + "sender shard", miniBlockInfo.SenderShardID, + "hash", miniBlockInfo.Hash, + "round", miniBlockInfo.Round, + ) + continue + } + + miniBlock, ok := miniVal.(*block.MiniBlock) + if !ok { + shouldSkipShard[miniBlockInfo.SenderShardID] = true + log.Error("transactionCoordinator.CreateMbsCrossShardDstMe: mini block assertion type failed", + "sender shard", miniBlockInfo.SenderShardID, + "hash", miniBlockInfo.Hash, + "round", miniBlockInfo.Round, + ) + continue + } + + preproc := tc.preProcProposal.getPreProcessor(miniBlock.Type) + if check.IfNil(preproc) { + return nil, nil, 0, false, false, fmt.Errorf("%w unknown block type %d", process.ErrNilPreProcessor, miniBlock.Type) + } + + existingTxsForMb, missingTxs := preproc.GetTransactionsAndRequestMissingForMiniBlock(miniBlock) + if missingTxs > 0 { + shouldSkipShard[miniBlockInfo.SenderShardID] = true + log.Trace("transactionCoordinator.CreateMbsCrossShardDstMe: transactions not found", + "sender shard", miniBlockInfo.SenderShardID, + "hash", miniBlockInfo.Hash, + "round", miniBlockInfo.Round, + "missing txs", missingTxs, + ) + continue + } + log.Debug("transactionCoordinator.CreateMbsCrossShardDstMe: selected mini block", + "sender shard", miniBlockInfo.SenderShardID, + "hash", miniBlockInfo.Hash, + "type", miniBlock.Type, + "round", miniBlockInfo.Round, + "num txs", len(miniBlock.TxHashes), + ) + + miniBlocksAndHashes = append(miniBlocksAndHashes, block.MiniblockAndHash{ + Miniblock: miniBlock, + Hash: miniBlockInfo.Hash, + }) + numTransactions += uint32(len(miniBlock.TxHashes)) + + txsForMbs[string(miniBlockInfo.Hash)] = existingTxsForMb + mbsSlice = append(mbsSlice, &block.MiniBlockHeader{ + Hash: miniBlockInfo.Hash, + SenderShardID: miniBlock.SenderShardID, + ReceiverShardID: miniBlock.ReceiverShardID, + TxCount: uint32(len(miniBlock.TxHashes)), + Type: miniBlock.Type, + Reserved: miniBlock.Reserved, + }) + } + + lastMBIndex, _, err := tc.gasComputation.AddIncomingMiniBlocks(mbsSlice, txsForMbs) + if err != nil { + return nil, nil, 0, false, false, err + } + + // if not all mini blocks were included, remove them from the miniBlocksAndHashes slice + // but add them into pendingMiniBlocksAndHashes + if lastMBIndex < len(mbsSlice)-1 { + log.Debug("transactionCoordinator.CreateMbsCrossShardDstMe: could not select all mini blocks, saving them as pending", "lastMBIndex", lastMBIndex) + + for _, mbAndHash := range miniBlocksAndHashes[lastMBIndex+1:] { + numTransactions -= uint32(len(mbAndHash.Miniblock.TxHashes)) + } + + pendingMiniBlocksAndHashes = miniBlocksAndHashes[lastMBIndex+1:] + miniBlocksAndHashes = miniBlocksAndHashes[:lastMBIndex+1] + } + + allMiniBlocksAdded = len(miniBlocksAndHashes)+numMiniBlocksAlreadyProcessed == len(finalCrossMiniBlockInfos) + hasMissingData = !allMiniBlocksAdded && len(shouldSkipShard) > 0 + + return miniBlocksAndHashes, pendingMiniBlocksAndHashes, numTransactions, allMiniBlocksAdded, hasMissingData, nil +} + +func (tc *transactionCoordinator) getAOTSelection(nonce uint64) ([][]byte, []data.TransactionHandler) { + if check.IfNil(tc.aotSelector) { + return [][]byte{}, []data.TransactionHandler{} + } + + // cancel any ongoing AOT selection + tc.aotSelector.CancelOngoingSelection() + aotResult, found := tc.aotSelector.GetPreSelectedTransactions(nonce) + if !found || aotResult == nil { + log.Trace("GetAOTSelection: no AOT pre-selected transactions found for nonce", "nonce", nonce) + return [][]byte{}, []data.TransactionHandler{} + } + + if len(aotResult.TxHashes) == 0 { + return [][]byte{}, []data.TransactionHandler{} + } + + // Retrieve transaction handlers from the data pool using cached hashes + selectedTxHashes, selectedTxs := tc.getTxHandlersFromHashes(aotResult.TxHashes) + if len(selectedTxs) == 0 { + log.Warn("AOT selection abandoned, some pre-selected txs unavailable in pool, will re-select", "nonce", nonce, "numHashes", len(aotResult.TxHashes)) + return [][]byte{}, []data.TransactionHandler{} + } + + log.Info("SelectOutgoingTransactions: using AOT pre-selected transactions", + "nonce", nonce, + "numTxs", len(aotResult.TxHashes)) + + return selectedTxHashes, selectedTxs +} + +// SelectOutgoingTransactions returns transactions originating in the shard, for a block proposal +func (tc *transactionCoordinator) SelectOutgoingTransactions( + nonce uint64, + haveTimeForSelection func() bool, +) ([][]byte, []data.MiniBlockHeaderHandler) { + selectedTxHashes, selectedTxs := tc.getAOTSelection(nonce) + // if no tx returned from AOT selection, fallback to regular selection from pre-processors + if len(selectedTxs) == 0 { + for _, blockType := range tc.preProcProposal.keysTxPreProcs { + txPreProc := tc.preProcProposal.getPreProcessor(blockType) + if check.IfNil(txPreProc) { + log.Warn("transactionCoordinator.SelectOutgoingTransactions: getPreProcessor returned nil for block type", "blockType", blockType) + continue + } + + gasBandwidth := tc.gasComputation.GetBandwidthForTransactions() + txHashes, txs, err := txPreProc.SelectOutgoingTransactions(gasBandwidth, nonce, haveTimeForSelection) + if err != nil { + log.Warn("transactionCoordinator.SelectOutgoingTransactions: SelectOutgoingTransactions returned error", "error", err) + continue + } + selectedTxHashes = append(selectedTxHashes, txHashes...) + selectedTxs = append(selectedTxs, txs...) + } + } + + selectedTxHashes, pendingMiniBlocksAdded, err := tc.gasComputation.AddOutgoingTransactions(selectedTxHashes, selectedTxs) + if err != nil { + log.Warn("transactionCoordinator.AddOutgoingTransactions: AddOutgoingTransactions returned error", "error", err) + } + + return selectedTxHashes, pendingMiniBlocksAdded +} + +// getTxHandlersFromHashes retrieves transaction handlers from the data pool using the provided hashes +func (tc *transactionCoordinator) getTxHandlersFromHashes(txHashes [][]byte) ([][]byte, []data.TransactionHandler) { + validHashes := make([][]byte, 0, len(txHashes)) + txs := make([]data.TransactionHandler, 0, len(txHashes)) + + txPool := tc.dataPool.Transactions() + for _, txHash := range txHashes { + val, ok := txPool.SearchFirstData(txHash) + if !ok { + log.Trace("getTxHandlersFromHashes: transaction not found in pool", "hash", txHash) + return [][]byte{}, []data.TransactionHandler{} + } + + tx, isTx := val.(data.TransactionHandler) + if !isTx { + log.Warn("getTxHandlersFromHashes: value is not a TransactionHandler", "hash", txHash) + return [][]byte{}, []data.TransactionHandler{} + } + + validHashes = append(validHashes, txHash) + txs = append(txs, tx) + } + + return validHashes, txs +} + +func (tc *transactionCoordinator) verifyCreatedMiniBlocksSanity(body *block.Body) error { + miniblocksFromSelf := make([]*block.MiniBlock, 0) + for _, mb := range body.MiniBlocks { + if mb.SenderShardID == tc.shardCoordinator.SelfId() { + miniblocksFromSelf = append(miniblocksFromSelf, mb) + } + } + + collectedMbsAfterExecution := tc.GetCreatedMiniBlocksFromMe() + unExecutableTransactions := tc.getUnExecutableTransactions() + invalidTxsInterimProc := tc.getInterimProcessor(block.InvalidBlock) + invalidTransactions := invalidTxsInterimProc.GetAllCurrentFinishedTxs() + + allProposedOutgoingTxsInBody, err := collectTransactionsFromMiniBlocks(miniblocksFromSelf) + if err != nil { + return fmt.Errorf("%w: for body miniBlocks", err) + } + + allCollectedTxs, err := collectTransactionsFromMiniBlocks(collectedMbsAfterExecution) + if err != nil { + return fmt.Errorf("%w: for created miniBlocks", err) + } + + // add the un-executable transactions to the collected transactions + for txHash := range unExecutableTransactions { + if _, exists := allCollectedTxs[txHash]; exists { + return fmt.Errorf("%w: for collected unexecutable transactions", process.ErrDuplicatedTransaction) + } + allCollectedTxs[txHash] = struct{}{} + } + + // check that invalid transactions are not part of the collected transactions + for txHash := range invalidTransactions { + if _, exists := allCollectedTxs[txHash]; exists { + return fmt.Errorf("%w: for collected invalid transactions", process.ErrDuplicatedTransaction) + } + allCollectedTxs[txHash] = struct{}{} + } + + // check that allProposedOutgoingTxsInBody are part of the collected transactions + // the collected transactions may contain also extra items (rewards/peer changes/scrs) + for txHash := range allProposedOutgoingTxsInBody { + if _, exists := allCollectedTxs[txHash]; !exists { + return process.ErrTransactionsMismatch + } + } + + return nil +} + +func collectTransactionsFromMiniBlocks(intraShardMbs []*block.MiniBlock) (map[string]struct{}, error) { + allTxsInBody := make(map[string]struct{}) + for _, mb := range intraShardMbs { + for _, txHash := range mb.TxHashes { + if _, exists := allTxsInBody[string(txHash)]; exists { + return nil, process.ErrDuplicatedTransaction + } + allTxsInBody[string(txHash)] = struct{}{} + } + } + return allTxsInBody, nil +} diff --git a/process/coordinator/processProposal_test.go b/process/coordinator/processProposal_test.go new file mode 100644 index 00000000000..8ad82c661b6 --- /dev/null +++ b/process/coordinator/processProposal_test.go @@ -0,0 +1,1292 @@ +package coordinator + +import ( + "errors" + "math/big" + "reflect" + "testing" + "time" + + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/data/transaction" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/dataRetriever" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/block/processedMb" + "github.com/multiversx/mx-chain-go/process/factory/shard" + "github.com/multiversx/mx-chain-go/process/mock" + "github.com/multiversx/mx-chain-go/state" + "github.com/multiversx/mx-chain-go/storage" + "github.com/multiversx/mx-chain-go/testscommon" + aotStubs "github.com/multiversx/mx-chain-go/testscommon/aotStubs" + "github.com/multiversx/mx-chain-go/testscommon/cache" + commonMock "github.com/multiversx/mx-chain-go/testscommon/common" + dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" + "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" + "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" + "github.com/multiversx/mx-chain-go/testscommon/preprocMocks" + stateMock "github.com/multiversx/mx-chain-go/testscommon/state" +) + +func haveTimeTrue() bool { + return true +} + +type testData struct { + hdr *block.Header + mbHashes [][]byte + miniBlockInfos []*data.MiniBlockInfo + tx1Hash []byte + tx2Hash []byte + tx3Hash []byte + mb1Info block.MiniblockAndHash + mb2Info block.MiniblockAndHash +} + +func TestTransactionCoordinator_CreateMbsCrossShardDstMe_NilHeader(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + miniBlocks, pendingMiniBlocks, numTxs, allAdded, hasMissingData, err := tc.CreateMbsCrossShardDstMe(nil, nil) + + require.Equal(t, process.ErrNilHeaderHandler, err) + require.Equal(t, 0, len(miniBlocks)) + require.Equal(t, 0, len(pendingMiniBlocks)) + require.Equal(t, uint32(0), numTxs) + require.False(t, allAdded) + require.False(t, hasMissingData) +} + +func TestTransactionCoordinator_CreateMbsCrossShardDstMe_UsesProposalContext(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + // Verify that proposal and execution contexts are separate instances + require.False(t, reflect.DeepEqual(tc.preProcProposal, tc.preProcExecution)) + require.False(t, reflect.DeepEqual(tc.blockDataRequesterProposal, tc.blockDataRequester)) + + td := createHeaderWithMiniBlocksAndTransactions() + expectedMiniBlocks := []block.MiniblockAndHash{td.mb1Info, td.mb2Info} + + // Mock the block data requester proposal to return empty slice + proposalBlockDataRequesterCalled := false + tc.blockDataRequesterProposal = &preprocMocks.BlockDataRequesterStub{ + GetFinalCrossMiniBlockInfoAndRequestMissingCalled: func(header data.HeaderHandler) []*data.MiniBlockInfo { + proposalBlockDataRequesterCalled = true + return td.miniBlockInfos + }, + } + + // Mock the execution block data requester to ensure it's not called + executionBlockDataRequesterCalled := false + tc.blockDataRequester = &preprocMocks.BlockDataRequesterStub{ + GetFinalCrossMiniBlockInfoAndRequestMissingCalled: func(header data.HeaderHandler) []*data.MiniBlockInfo { + executionBlockDataRequesterCalled = true + return []*data.MiniBlockInfo{} + }, + } + + // make sure mini blocks are available in the pool + _ = tc.dataPool.MiniBlocks().Put(td.mbHashes[0], td.mb1Info.Miniblock, 100) + _ = tc.dataPool.MiniBlocks().Put(td.mbHashes[1], td.mb2Info.Miniblock, 100) + + // make sure transactions are available in the pool + cacheId := process.ShardCacherIdentifier(td.mb1Info.Miniblock.SenderShardID, td.mb1Info.Miniblock.ReceiverShardID) + ph.Transactions().AddData(td.tx1Hash, &transaction.Transaction{}, 100, cacheId) + ph.Transactions().AddData(td.tx2Hash, &transaction.Transaction{}, 100, cacheId) + cacheId = process.ShardCacherIdentifier(td.mb2Info.Miniblock.SenderShardID, td.mb2Info.Miniblock.ReceiverShardID) + ph.Transactions().AddData(td.tx3Hash, &transaction.Transaction{}, 100, cacheId) + + miniBlocks, _, numTxs, allAdded, hasMissingData, err := tc.CreateMbsCrossShardDstMe(td.hdr, nil) + + require.Nil(t, err) + require.Equal(t, expectedMiniBlocks, miniBlocks) + require.Equal(t, uint32(3), numTxs) + require.True(t, allAdded) + require.False(t, hasMissingData) + + // Verify proposal context was used, not execution context + require.True(t, proposalBlockDataRequesterCalled) + require.False(t, executionBlockDataRequesterCalled) +} + +func TestTransactionCoordinator_CreateMbsCrossShardDstMe_MaxBlockSizeReached(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + td := createHeaderWithMiniBlocksAndTransactions() + + // Mock block size computation to return max block size reached + tc.blockSizeComputation = &testscommon.BlockSizeComputationStub{ + IsMaxBlockSizeReachedCalled: func(numNewMiniBlocks, numNewTxs int) bool { + return true + }, + } + + // Mock the block data requester to return some mini blocks + tc.blockDataRequesterProposal = &preprocMocks.BlockDataRequesterStub{ + GetFinalCrossMiniBlockInfoAndRequestMissingCalled: func(header data.HeaderHandler) []*data.MiniBlockInfo { + return td.miniBlockInfos + }, + } + + miniBlocks, _, numTxs, allAdded, hasMissingData, err := tc.CreateMbsCrossShardDstMe(td.hdr, nil) + + require.Nil(t, err) + require.Equal(t, 0, len(miniBlocks)) + require.Equal(t, uint32(0), numTxs) + require.False(t, allAdded) // Not all mini blocks were processed due to size limit + require.False(t, hasMissingData) +} + +func TestTransactionCoordinator_CreateMbsCrossShardDstMe_MiniBlockProcessing(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + td := createHeaderWithMiniBlocksAndTransactions() + + // Mock mini block pool to return the test mini block + tc.dataPool = &dataRetrieverMock.PoolsHolderStub{ + TransactionsCalled: ph.Transactions, + PostProcessTransactionsCalled: ph.PostProcessTransactions, + MiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{ + PeekCalled: func(key []byte) (value interface{}, ok bool) { + if reflect.DeepEqual(key, td.mb1Info.Hash) { + return td.mb1Info.Miniblock, true + } + return nil, false + }, + } + }, + } + + // Mock the block data requester to return the mini block info + tc.blockDataRequesterProposal = &preprocMocks.BlockDataRequesterStub{ + GetFinalCrossMiniBlockInfoAndRequestMissingCalled: func(header data.HeaderHandler) []*data.MiniBlockInfo { + return td.miniBlockInfos[:1] // Only first mini block + }, + } + + // Mock preprocessor to simulate no missing transactions + proposalPreprocessorCalled := false + tc.preProcProposal.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + GetTransactionsAndRequestMissingForMiniBlockCalled: func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + proposalPreprocessorCalled = true + require.Equal(t, td.mb1Info.Miniblock, miniBlock) + return nil, 0 // No missing transactions + }, + } + + // Mock execution preprocessor to ensure it's not called + executionPreprocessorCalled := false + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + GetTransactionsAndRequestMissingForMiniBlockCalled: func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + executionPreprocessorCalled = true + return nil, 0 + }, + } + + miniBlocks, _, numTxs, allAdded, hasMissingData, err := tc.CreateMbsCrossShardDstMe(td.hdr, nil) + + require.Nil(t, err) + require.Equal(t, 1, len(miniBlocks)) + require.Equal(t, td.mb1Info.Miniblock, miniBlocks[0].Miniblock) + require.Equal(t, td.mb1Info.Hash, miniBlocks[0].Hash) + require.Equal(t, uint32(2), numTxs) // Two transactions in the mini block + require.True(t, allAdded) + require.False(t, hasMissingData) + + // Verify proposal preprocessor was used, not execution + require.True(t, proposalPreprocessorCalled) + require.False(t, executionPreprocessorCalled) +} + +func TestTransactionCoordinator_CreateMbsCrossShardDstMe_MiniBlockProcessing_WithGasComputationError(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + td := createHeaderWithMiniBlocksAndTransactions() + + // Mock mini block pool to return the test mini block + tc.dataPool = &dataRetrieverMock.PoolsHolderStub{ + TransactionsCalled: ph.Transactions, + PostProcessTransactionsCalled: ph.PostProcessTransactions, + MiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{ + PeekCalled: func(key []byte) (value interface{}, ok bool) { + if reflect.DeepEqual(key, td.mb1Info.Hash) { + return td.mb1Info.Miniblock, true + } + return nil, false + }, + } + }, + } + + // Mock the block data requester to return the mini block info + tc.blockDataRequesterProposal = &preprocMocks.BlockDataRequesterStub{ + GetFinalCrossMiniBlockInfoAndRequestMissingCalled: func(header data.HeaderHandler) []*data.MiniBlockInfo { + return td.miniBlockInfos[:1] // Only first mini block + }, + } + + // Mock preprocessor to simulate no missing transactions + proposalPreprocessorCalled := false + tc.preProcProposal.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + GetTransactionsAndRequestMissingForMiniBlockCalled: func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + proposalPreprocessorCalled = true + require.Equal(t, td.mb1Info.Miniblock, miniBlock) + return nil, 0 // No missing transactions + }, + } + + // Mock execution preprocessor to ensure it's not called + executionPreprocessorCalled := false + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + GetTransactionsAndRequestMissingForMiniBlockCalled: func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + executionPreprocessorCalled = true + return nil, 0 + }, + } + + tc.gasComputation = &testscommon.GasComputationMock{ + AddIncomingMiniBlocksCalled: func(miniBlocks []data.MiniBlockHeaderHandler, transactions map[string][]data.TransactionHandler) (int, int, error) { + return 0, 0, errors.New("gas computation error") + }, + } + + miniBlocks, _, numTxs, allAdded, _, err := tc.CreateMbsCrossShardDstMe(td.hdr, nil) + + require.Error(t, err) + require.Contains(t, err.Error(), "gas computation error") + require.Nil(t, miniBlocks) + require.Equal(t, uint32(0), numTxs) + require.False(t, allAdded) // No mini blocks were processed + + // Verify proposal preprocessor was used, not execution + require.True(t, proposalPreprocessorCalled) + require.False(t, executionPreprocessorCalled) +} + +func TestTransactionCoordinator_CreateMbsCrossShardDstMe_MiniBlockProcessing_WithPendingMiniBlocks(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + td := createHeaderWithMiniBlocksAndTransactions() + + // Mock mini block pool to return the test mini block + tc.dataPool = &dataRetrieverMock.PoolsHolderStub{ + TransactionsCalled: ph.Transactions, + PostProcessTransactionsCalled: ph.PostProcessTransactions, + MiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{ + PeekCalled: func(key []byte) (value interface{}, ok bool) { + if reflect.DeepEqual(key, td.mb1Info.Hash) { + return td.mb1Info.Miniblock, true + } + return td.mb2Info.Miniblock, true + }, + } + }, + } + + // Mock the block data requester to return the mini block info + tc.blockDataRequesterProposal = &preprocMocks.BlockDataRequesterStub{ + GetFinalCrossMiniBlockInfoAndRequestMissingCalled: func(header data.HeaderHandler) []*data.MiniBlockInfo { + return td.miniBlockInfos + }, + } + + // Mock preprocessor to simulate no missing transactions + proposalPreprocessorCalled := false + tc.preProcProposal.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + GetTransactionsAndRequestMissingForMiniBlockCalled: func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + proposalPreprocessorCalled = true + return nil, 0 // No missing transactions + }, + } + + // Mock execution preprocessor to ensure it's not called + executionPreprocessorCalled := false + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + GetTransactionsAndRequestMissingForMiniBlockCalled: func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + executionPreprocessorCalled = true + return nil, 0 + }, + } + + tc.gasComputation = &testscommon.GasComputationMock{ + AddIncomingMiniBlocksCalled: func(miniBlocks []data.MiniBlockHeaderHandler, transactions map[string][]data.TransactionHandler) (int, int, error) { + return 0, 1, nil // last mb added index is 0, so only first mini block is added, num pending miniblocks is 1, so the second is pending + }, + } + + miniBlocks, pendingMiniBlocks, numTxs, allAdded, hasMissingData, err := tc.CreateMbsCrossShardDstMe(td.hdr, nil) + + require.Nil(t, err) + + require.Equal(t, 1, len(miniBlocks)) + require.Equal(t, td.mb1Info.Miniblock, miniBlocks[0].Miniblock) + require.Equal(t, td.mb1Info.Hash, miniBlocks[0].Hash) + + require.Equal(t, 1, len(pendingMiniBlocks)) + require.Equal(t, td.mb2Info.Miniblock, pendingMiniBlocks[0].Miniblock) + require.Equal(t, td.mb2Info.Hash, pendingMiniBlocks[0].Hash) + + require.Equal(t, uint32(2), numTxs) + require.False(t, allAdded) + require.False(t, hasMissingData) + + // Verify proposal preprocessor was used, not execution + require.True(t, proposalPreprocessorCalled) + require.False(t, executionPreprocessorCalled) +} + +func TestTransactionCoordinator_CreateMbsCrossShardDstMe_SkipShardOnMissingMiniBlock(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + td := createHeaderWithMiniBlocksAndTransactions() + // this should be the first mini block info, which will be missing from shard 2, then the next one will be skipped + missingMiniBlockInfo := &data.MiniBlockInfo{ + Hash: []byte("missing_mb_hash"), + SenderShardID: 2, + Round: 0, + } + + // Mock mini block pool to return nil (mini block not found) + tc.dataPool = &dataRetrieverMock.PoolsHolderStub{ + TransactionsCalled: ph.Transactions, + PostProcessTransactionsCalled: ph.PostProcessTransactions, + MiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{ + PeekCalled: func(key []byte) (value interface{}, ok bool) { + if reflect.DeepEqual(key, missingMiniBlockInfo.Hash) { + return nil, false + } + if reflect.DeepEqual(key, td.mb1Info.Hash) { + return td.mb1Info.Miniblock, true // although the second mini block is available, it should be skipped + } + return nil, false // other mini blocks not found as well + }, + } + }, + } + + // Mock the block data requester to return multiple mini blocks from same shard + tc.blockDataRequesterProposal = &preprocMocks.BlockDataRequesterStub{ + GetFinalCrossMiniBlockInfoAndRequestMissingCalled: func(header data.HeaderHandler) []*data.MiniBlockInfo { + return []*data.MiniBlockInfo{missingMiniBlockInfo, td.miniBlockInfos[0], td.miniBlockInfos[1]} + }, + } + + miniBlocks, _, numTxs, allAdded, hasMissingData, err := tc.CreateMbsCrossShardDstMe(td.hdr, nil) + + require.Nil(t, err) + require.Equal(t, 0, len(miniBlocks)) + require.Equal(t, uint32(0), numTxs) + require.False(t, allAdded) // No mini blocks were processed + require.True(t, hasMissingData) +} + +func TestTransactionCoordinator_CreateMbsCrossShardDstMe_SkipShardOnMissingTransactions(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + td := createHeaderWithMiniBlocksAndTransactions() + + tc.dataPool.MiniBlocks().Put(td.mbHashes[0], td.mb1Info.Miniblock, 100) + tc.dataPool.MiniBlocks().Put(td.mbHashes[1], td.mb2Info.Miniblock, 100) + + // Mock the block data requester + tc.blockDataRequesterProposal = &preprocMocks.BlockDataRequesterStub{ + GetFinalCrossMiniBlockInfoAndRequestMissingCalled: func(header data.HeaderHandler) []*data.MiniBlockInfo { + return td.miniBlockInfos + }, + } + + // Make only the second mini block transactions available + cacheId := process.ShardCacherIdentifier(td.mb2Info.Miniblock.SenderShardID, td.mb2Info.Miniblock.ReceiverShardID) + ph.Transactions().AddData(td.tx3Hash, &transaction.Transaction{}, 100, cacheId) + + miniBlocks, _, numTxs, allAdded, hasMissingData, err := tc.CreateMbsCrossShardDstMe(td.hdr, nil) + + require.Nil(t, err) + require.Equal(t, 1, len(miniBlocks)) + require.Equal(t, td.mb2Info.Miniblock, miniBlocks[0].Miniblock) + require.Equal(t, uint32(len(td.mb2Info.Miniblock.TxHashes)), numTxs) + require.False(t, allAdded) + require.True(t, hasMissingData) +} + +func TestTransactionCoordinator_CreateMbsCrossShardDstMe_ErrorOnUnknownBlockType(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + hdr := createTestHeaderForProposal() + mbHash := []byte("mb_hash_1") + + // Create test mini block with unknown type + testMiniBlock := &block.MiniBlock{ + SenderShardID: 1, + ReceiverShardID: 0, + Type: block.Type(99), // Unknown block type + TxHashes: [][]byte{[]byte("tx_hash_1")}, + } + + // Mock mini block pool to return the test mini block + tc.dataPool = &dataRetrieverMock.PoolsHolderStub{ + TransactionsCalled: ph.Transactions, + PostProcessTransactionsCalled: ph.PostProcessTransactions, + MiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{ + PeekCalled: func(key []byte) (value interface{}, ok bool) { + if reflect.DeepEqual(key, mbHash) { + return testMiniBlock, true + } + return nil, false + }, + } + }, + } + + // Mock the block data requester + tc.blockDataRequesterProposal = &preprocMocks.BlockDataRequesterStub{ + GetFinalCrossMiniBlockInfoAndRequestMissingCalled: func(header data.HeaderHandler) []*data.MiniBlockInfo { + return []*data.MiniBlockInfo{ + { + Hash: mbHash, + SenderShardID: 1, + Round: 1, + }, + } + }, + } + + miniBlocks, _, numTxs, allAdded, _, err := tc.CreateMbsCrossShardDstMe(hdr, nil) + + require.NotNil(t, err) + require.Contains(t, err.Error(), "unknown block type") + require.Nil(t, miniBlocks) + require.Equal(t, uint32(0), numTxs) + require.False(t, allAdded) +} + +func TestTransactionCoordinator_SelectOutgoingTransactions_EmptyResult(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + // Mock proposal preprocessors to return empty transactions + proposalPreprocessorCalled := false + tc.preProcProposal.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + SelectOutgoingTransactionsCalled: func(_ uint64, _ uint64, _ func() bool) ([][]byte, []data.TransactionHandler, error) { + proposalPreprocessorCalled = true + return [][]byte{}, []data.TransactionHandler{}, nil + }, + } + + // Mock execution preprocessor to ensure it's not called + executionPreprocessorCalled := false + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + SelectOutgoingTransactionsCalled: func(_ uint64, _ uint64, _ func() bool) ([][]byte, []data.TransactionHandler, error) { + executionPreprocessorCalled = true + return [][]byte{}, []data.TransactionHandler{}, nil + }, + } + + txHashes, _ := tc.SelectOutgoingTransactions(0, haveTimeTrue) + + require.Equal(t, 0, len(txHashes)) + + // Verify proposal preprocessor was used, not execution + require.True(t, proposalPreprocessorCalled) + require.False(t, executionPreprocessorCalled) +} + +func TestTransactionCoordinator_SelectOutgoingTransactions_ReturnsTransactions(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + expectedTxHashes := [][]byte{[]byte("tx_hash_1"), []byte("tx_hash_2")} + expectedTxs := []data.TransactionHandler{&transaction.Transaction{}, &transaction.Transaction{}} + + // Mock proposal preprocessor to return transactions + proposalPreprocessorCalled := false + tc.preProcProposal.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + SelectOutgoingTransactionsCalled: func(_ uint64, _ uint64, _ func() bool) ([][]byte, []data.TransactionHandler, error) { + proposalPreprocessorCalled = true + return expectedTxHashes, expectedTxs, nil + }, + } + + // Mock execution preprocessor to ensure it's not called + executionPreprocessorCalled := false + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + SelectOutgoingTransactionsCalled: func(_ uint64, _ uint64, _ func() bool) ([][]byte, []data.TransactionHandler, error) { + executionPreprocessorCalled = true + return [][]byte{}, []data.TransactionHandler{}, nil + }, + } + + txHashes, _ := tc.SelectOutgoingTransactions(0, haveTimeTrue) + + require.Equal(t, len(expectedTxHashes), len(txHashes)) + for i, expectedHash := range expectedTxHashes { + require.Equal(t, expectedHash, txHashes[i]) + } + + // Verify proposal preprocessor was used, not execution + require.True(t, proposalPreprocessorCalled) + require.False(t, executionPreprocessorCalled) +} + +func TestTransactionCoordinator_SelectOutgoingTransactions_MultipleBlockTypes(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + txHashesType1 := [][]byte{[]byte("tx_hash_1"), []byte("tx_hash_2")} + txHashesType2 := [][]byte{[]byte("tx_hash_3"), []byte("tx_hash_4")} + + // add transactions to the transactions pool + cacheId := process.ShardCacherIdentifier(0, 0) + ph.Transactions().AddData(txHashesType1[0], &transaction.Transaction{SndAddr: []byte("sender1"), Value: big.NewInt(0), Nonce: 0}, 100, cacheId) + ph.Transactions().AddData(txHashesType1[1], &transaction.Transaction{SndAddr: []byte("sender1"), Value: big.NewInt(0), Nonce: 1}, 100, cacheId) + + // add transactions to the unsigned transactions pool + ph.UnsignedTransactions().AddData(txHashesType2[0], &transaction.Transaction{SndAddr: []byte("sender2"), Value: big.NewInt(0), Nonce: 0}, 100, cacheId) + ph.UnsignedTransactions().AddData(txHashesType2[1], &transaction.Transaction{SndAddr: []byte("sender3"), Value: big.NewInt(0), Nonce: 0}, 100, cacheId) + + // Add both block types to the keys + tc.preProcProposal.keysTxPreProcs = []block.Type{block.TxBlock, block.SmartContractResultBlock} + + txHashes, _ := tc.SelectOutgoingTransactions(0, haveTimeTrue) + + // Should contain hashes from TxBlock type, for SmartContractsResultsBlock type the selection returns empty + expectedTotal := len(txHashesType1) + require.Equal(t, expectedTotal, len(txHashes)) + + // Verify all expected hashes are present + for _, expectedHash := range txHashesType1 { + found := false + for _, actualHash := range txHashes { + if reflect.DeepEqual(expectedHash, actualHash) { + found = true + break + } + } + require.True(t, found, "Expected hash %s not found in result", string(expectedHash)) + } +} + +func TestTransactionCoordinator_SelectOutgoingTransactions_HandlesErrors(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + // Mock proposal preprocessor to return error + tc.preProcProposal.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + SelectOutgoingTransactionsCalled: func(_ uint64, _ uint64, _ func() bool) ([][]byte, []data.TransactionHandler, error) { + return nil, nil, errors.New("test error") + }, + } + + txHashes, _ := tc.SelectOutgoingTransactions(0, haveTimeTrue) + + // Function should continue and return empty slice despite error + require.Equal(t, 0, len(txHashes)) +} + +func TestTransactionCoordinator_SelectOutgoingTransactions_HandlesNilPreprocessor(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + // Set proposal preprocessor to nil + tc.preProcProposal.txPreProcessors[block.TxBlock] = nil + + txHashes, _ := tc.SelectOutgoingTransactions(0, haveTimeTrue) + + // Function should handle nil preprocessor gracefully + require.Equal(t, 0, len(txHashes)) +} + +func TestTransactionCoordinator_SelectOutgoingTransactions_AddOutgoingTransactionsError(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + txHashesType1 := [][]byte{[]byte("tx_hash_1"), []byte("tx_hash_2")} + txHashesType2 := [][]byte{[]byte("tx_hash_3"), []byte("tx_hash_4")} + + // add transactions to the transactions pool + cacheId := process.ShardCacherIdentifier(0, 0) + ph.Transactions().AddData(txHashesType1[0], &transaction.Transaction{SndAddr: []byte("sender1"), Nonce: 0}, 100, cacheId) + ph.Transactions().AddData(txHashesType1[1], &transaction.Transaction{SndAddr: []byte("sender1"), Nonce: 1}, 100, cacheId) + + // add transactions to the unsigned transactions pool + ph.UnsignedTransactions().AddData(txHashesType2[0], &transaction.Transaction{SndAddr: []byte("sender2"), Nonce: 0}, 100, cacheId) + ph.UnsignedTransactions().AddData(txHashesType2[1], &transaction.Transaction{SndAddr: []byte("sender3"), Nonce: 0}, 100, cacheId) + + // Add both block types to the keys + tc.preProcProposal.keysTxPreProcs = []block.Type{block.TxBlock, block.SmartContractResultBlock} + tc.gasComputation = &testscommon.GasComputationMock{ + AddOutgoingTransactionsCalled: func(txHashes [][]byte, transactions []data.TransactionHandler) ([][]byte, []data.MiniBlockHeaderHandler, error) { + return nil, nil, errors.New("test error in AddOutgoingTransactions") + }, + } + + require.NotPanics(t, func() { + selectedTxHashes, selectedPendingIncomingMiniBlocks := tc.SelectOutgoingTransactions(0, haveTimeTrue) + // Function should continue and return empty slice despite error + require.Nil(t, selectedTxHashes) + require.Nil(t, selectedPendingIncomingMiniBlocks) + }) +} + +func TestTransactionCoordinator_CreateMbsCrossShardDstMe_ProcessedMiniBlocksInfo(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + hdr := createTestHeaderForProposal() + mbHash := []byte("mb_hash_1") + + // Create processed mini blocks info with already processed mini block + processedMiniBlocksInfo := map[string]*processedMb.ProcessedMiniBlockInfo{ + string(mbHash): { + FullyProcessed: true, + IndexOfLastTxProcessed: 1, + }, + } + + // Mock the block data requester + tc.blockDataRequesterProposal = &preprocMocks.BlockDataRequesterStub{ + GetFinalCrossMiniBlockInfoAndRequestMissingCalled: func(header data.HeaderHandler) []*data.MiniBlockInfo { + return []*data.MiniBlockInfo{ + { + Hash: mbHash, + SenderShardID: 1, + Round: 1, + }, + } + }, + } + + miniBlocks, _, numTxs, allAdded, _, err := tc.CreateMbsCrossShardDstMe(hdr, processedMiniBlocksInfo) + + require.Nil(t, err) + require.Equal(t, 0, len(miniBlocks)) // Should skip already processed mini block + require.Equal(t, uint32(0), numTxs) + require.True(t, allAdded) +} + +func TestTransactionCoordinator_CreateMbsCrossShardDstMe_TypeAssertion(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + hdr := createTestHeaderForProposal() + mbHash := []byte("mb_hash_1") + + // Mock mini block pool to return wrong type + tc.dataPool = &dataRetrieverMock.PoolsHolderStub{ + TransactionsCalled: ph.Transactions, + PostProcessTransactionsCalled: ph.PostProcessTransactions, + MiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{ + PeekCalled: func(key []byte) (value interface{}, ok bool) { + if reflect.DeepEqual(key, mbHash) { + return "invalid_type", true // Wrong type + } + return nil, false + }, + } + }, + } + + // Mock the block data requester + tc.blockDataRequesterProposal = &preprocMocks.BlockDataRequesterStub{ + GetFinalCrossMiniBlockInfoAndRequestMissingCalled: func(header data.HeaderHandler) []*data.MiniBlockInfo { + return []*data.MiniBlockInfo{ + { + Hash: mbHash, + SenderShardID: 1, + Round: 1, + }, + } + }, + } + + miniBlocks, _, numTxs, allAdded, hasMissingData, err := tc.CreateMbsCrossShardDstMe(hdr, nil) + + require.Nil(t, err) + require.Equal(t, 0, len(miniBlocks)) // Should skip due to type assertion failure + require.Equal(t, uint32(0), numTxs) + require.True(t, hasMissingData) + require.False(t, allAdded) // Not all mini blocks were processed +} + +func TestTransactionCoordinator_verifyCreatedMiniBlocksSanity(t *testing.T) { + t.Parallel() + + t.Run("duplicated transactions should error", func(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("hash"), + }, + }, + { + TxHashes: [][]byte{ + []byte("hash"), + }, + }, + }, + } + err = tc.verifyCreatedMiniBlocksSanity(body) + require.True(t, errors.Is(err, process.ErrDuplicatedTransaction)) + }) + + t.Run("error adding un-executable transactions to the collected transactions - should error", func(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + GetUnExecutableTransactionsCalled: func() map[string]struct{} { + return map[string]struct{}{ + string([]byte("tx_unexec_hash")): {}, + } + }, + // make the same preprocessor also report the created miniblocks from me + GetCreatedMiniBlocksFromMeCalled: func() block.MiniBlockSlice { + return block.MiniBlockSlice{ + &block.MiniBlock{ + SenderShardID: 0, + ReceiverShardID: 0, + TxHashes: [][]byte{ + []byte("tx_unexec_hash"), + }, + }, + } + }, + } + require.Nil(t, err) + require.NotNil(t, tc) + + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + SenderShardID: 0, + ReceiverShardID: 0, + TxHashes: [][]byte{ + []byte("tx_unexec_hash"), + }, + }, + }, + } + + err = tc.verifyCreatedMiniBlocksSanity(body) + require.True(t, errors.Is(err, process.ErrDuplicatedTransaction)) + }) + + t.Run("transactions mismatch should error", func(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + // preprocessor says we created tx 'a' + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + GetUnExecutableTransactionsCalled: func() map[string]struct{} { + return map[string]struct{}{} // no duplicates this time + }, + GetCreatedMiniBlocksFromMeCalled: func() block.MiniBlockSlice { + return block.MiniBlockSlice{ + &block.MiniBlock{ + SenderShardID: 0, + ReceiverShardID: 0, + TxHashes: [][]byte{ + []byte("tx_a"), + }, + }, + } + }, + } + + // but the block body includes tx 'b' + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + SenderShardID: 0, + ReceiverShardID: 0, + TxHashes: [][]byte{ + []byte("tx_b"), + }, + }, + }, + } + + err = tc.verifyCreatedMiniBlocksSanity(body) + require.True(t, errors.Is(err, process.ErrTransactionsMismatch)) + }) + + t.Run("verifyCreatedMiniBlocksSanity should error on collectTransactionsFromMiniBlocks", func(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.NoError(t, err) + require.NotNil(t, tc) + + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + GetCreatedMiniBlocksFromMeCalled: func() block.MiniBlockSlice { + return block.MiniBlockSlice{ + { + SenderShardID: 0, + ReceiverShardID: 0, + TxHashes: [][]byte{ + []byte("tx_dup"), + []byte("tx_dup"), // duplicate triggers collectTransactionsFromMiniBlocks error + }, + }, + } + }, + GetUnExecutableTransactionsCalled: func() map[string]struct{} { + return nil + }, + } + + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + SenderShardID: 0, + ReceiverShardID: 0, + TxHashes: [][]byte{ + []byte("tx_dup"), + }, + }, + }, + } + + err = tc.verifyCreatedMiniBlocksSanity(body) + require.Error(t, err) + require.Contains(t, err.Error(), "for created miniBlocks") + require.True(t, errors.Is(err, process.ErrDuplicatedTransaction)) + }) + + t.Run("should work empty body", func(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + {}, + }, + } + err = tc.verifyCreatedMiniBlocksSanity(body) + require.NoError(t, err) + }) + t.Run("should work with unexecutable transactions", func(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.NoError(t, err) + require.NotNil(t, tc) + + // first mini-block tx3 is un-executable + mb1 := &block.MiniBlock{ + SenderShardID: 0, + Type: block.TxBlock, + TxHashes: [][]byte{[]byte("tx1"), []byte("tx2"), []byte("tx_unexec")}, + } + // second mini-block incomming - will be ignored for created txs verification + mb2 := &block.MiniBlock{ + SenderShardID: 1, + ReceiverShardID: 0, + Type: block.TxBlock, + TxHashes: [][]byte{[]byte("tx4")}, + } + + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{mb1, mb2}, + } + + // Preprocessor still returns all created mini-blocks + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + GetCreatedMiniBlocksFromMeCalled: func() block.MiniBlockSlice { + return block.MiniBlockSlice{ + { + SenderShardID: 0, + ReceiverShardID: 0, + Type: block.TxBlock, + TxHashes: [][]byte{[]byte("tx1"), []byte("tx2")}, + }, + } + }, + GetUnExecutableTransactionsCalled: func() map[string]struct{} { + return map[string]struct{}{ + string([]byte("tx_unexec")): {}, + } + }, + } + + err = tc.verifyCreatedMiniBlocksSanity(body) + require.NoError(t, err) + }) + +} + +func createPreProcessorContainerWithPoolsHolder(poolsHolder dataRetriever.PoolsHolder) process.PreProcessorsContainer { + preProcessorsFactoryArgs := shard.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(5), + Store: initStore(), + Marshalizer: &mock.MarshalizerMock{}, + Hasher: &hashingMocks.HasherMock{}, + DataPool: poolsHolder, + PubkeyConverter: createMockPubkeyConverter(), + Accounts: &stateMock.AccountsStub{}, + AccountsProposal: &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return nil, nil + }, + GetExistingAccountCalled: func(addressContainer []byte) (vmcommon.AccountHandler, error) { + return nil, state.ErrAccNotFound // only new accounts + }, + }, + RequestHandler: &testscommon.RequestHandlerStub{}, + TxProcessor: &testscommon.TxProcessorMock{}, + ScProcessor: &testscommon.SCProcessorMock{}, + ScResultProcessor: &testscommon.SmartContractResultsProcessorMock{}, + RewardsTxProcessor: &testscommon.RewardTxProcessorMock{}, + EconomicsFee: FeeHandlerMock(), + GasHandler: &testscommon.GasHandlerStub{}, + BlockTracker: &mock.BlockTrackerMock{}, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + TxTypeHandler: &testscommon.TxTypeHandlerMock{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: createMockTxCacheSelectionConfig(), + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + } + + preFactory, _ := shard.NewPreProcessorsContainerFactory(preProcessorsFactoryArgs) + container, _ := preFactory.Create() + + return container +} + +func createMockTransactionCoordinatorForProposalTests(poolsHolder dataRetriever.PoolsHolder) (*transactionCoordinator, error) { + args := createMockTransactionCoordinatorArguments() + + // Create separate preprocessor containers for execution and proposal to ensure context isolation + args.PreProcessors = createPreProcessorContainerWithPoolsHolder(poolsHolder) + args.PreProcessorsProposal = createPreProcessorContainerWithPoolsHolder(poolsHolder) + args.InterProcessors = createInterimProcessorContainer() + + createAndAddBlockDataRequesters( + &args, + 5, + poolsHolder, + &testscommon.RequestHandlerStub{}, + args.PreProcessors, + args.PreProcessorsProposal, + ) + + return NewTransactionCoordinator(args) +} + +func createTestHeaderForProposal() *block.Header { + return &block.Header{ + Round: 1, + Nonce: 2, + ShardID: 0, + TimeStamp: uint64(time.Now().Unix()), + } +} + +func createHeaderWithMiniBlocksAndTransactions() testData { + hdr := createTestHeaderForProposal() + mbHashes := [][]byte{[]byte("hash1"), []byte("hash2")} + miniBlockInfos := []*data.MiniBlockInfo{ + { + Hash: mbHashes[0], + SenderShardID: 2, + Round: 0, + }, + { + Hash: mbHashes[1], + SenderShardID: 1, + Round: 0, + }, + } + + tx1Hash := []byte("tx1") + tx2Hash := []byte("tx2") + tx3Hash := []byte("tx3") + mb1Info := block.MiniblockAndHash{ + Miniblock: &block.MiniBlock{ + SenderShardID: 2, + ReceiverShardID: 0, + Type: block.TxBlock, + TxHashes: [][]byte{tx1Hash, tx2Hash}, + }, + Hash: mbHashes[0], + } + + mb2Info := block.MiniblockAndHash{ + Miniblock: &block.MiniBlock{ + SenderShardID: 1, + ReceiverShardID: 0, + Type: block.TxBlock, + TxHashes: [][]byte{tx3Hash}, + }, + Hash: mbHashes[1], + } + hdr.MiniBlockHeaders = []block.MiniBlockHeader{ + { + ReceiverShardID: 0, + Hash: mbHashes[0], + Type: block.TxBlock, + }, + { + ReceiverShardID: 0, + Hash: mbHashes[1], + Type: block.TxBlock, + }, + } + + return testData{ + hdr: hdr, + mbHashes: mbHashes, + miniBlockInfos: miniBlockInfos, + tx1Hash: tx1Hash, + tx2Hash: tx2Hash, + tx3Hash: tx3Hash, + mb1Info: mb1Info, + mb2Info: mb2Info, + } +} + +func TestTransactionCoordinator_SelectOutgoingTransactionsAOTCacheHit(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + txHash1 := []byte("aot_tx_hash_1") + txHash2 := []byte("aot_tx_hash_2") + tx1 := &transaction.Transaction{SndAddr: []byte("sender1"), Nonce: 0, Value: big.NewInt(0)} + tx2 := &transaction.Transaction{SndAddr: []byte("sender2"), Nonce: 0, Value: big.NewInt(0)} + + // Add transactions to tc.dataPool (used by getTxHandlersFromHashes) + cacheId := process.ShardCacherIdentifier(0, 0) + tc.dataPool.Transactions().AddData(txHash1, tx1, 100, cacheId) + tc.dataPool.Transactions().AddData(txHash2, tx2, 100, cacheId) + + // Set up AOT selector that returns a cache hit + tc.aotSelector = &aotStubs.AOTSelectorStub{ + GetPreSelectedTransactionsCalled: func(blockNonce uint64) (*process.AOTSelectionResult, bool) { + return &process.AOTSelectionResult{ + TxHashes: [][]byte{txHash1, txHash2}, + GasProvided: 50000, + PredictedBlockNonce: blockNonce, + }, true + }, + } + + // Track whether the preprocessor was called (it shouldn't be with AOT hit) + preprocessorCalled := false + tc.preProcProposal.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + SelectOutgoingTransactionsCalled: func(_ uint64, _ uint64, _ func() bool) ([][]byte, []data.TransactionHandler, error) { + preprocessorCalled = true + return nil, nil, nil + }, + } + + txHashes, _ := tc.SelectOutgoingTransactions(42, haveTimeTrue) + + require.Equal(t, 2, len(txHashes)) + require.Equal(t, txHash1, txHashes[0]) + require.Equal(t, txHash2, txHashes[1]) + require.False(t, preprocessorCalled, "preprocessor should not be called when AOT cache hit") +} + +func TestTransactionCoordinator_SelectOutgoingTransactionsAOTCacheMiss(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + // Set up AOT selector that returns a cache miss + tc.aotSelector = &aotStubs.AOTSelectorStub{ + GetPreSelectedTransactionsCalled: func(blockNonce uint64) (*process.AOTSelectionResult, bool) { + return nil, false + }, + } + + // Set up preprocessor to return transactions (fallback path) + expectedTxHashes := [][]byte{[]byte("fallback_tx_1"), []byte("fallback_tx_2")} + expectedTxs := []data.TransactionHandler{&transaction.Transaction{}, &transaction.Transaction{}} + preprocessorCalled := false + tc.preProcProposal.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + SelectOutgoingTransactionsCalled: func(_ uint64, _ uint64, _ func() bool) ([][]byte, []data.TransactionHandler, error) { + preprocessorCalled = true + return expectedTxHashes, expectedTxs, nil + }, + } + + txHashes, _ := tc.SelectOutgoingTransactions(42, haveTimeTrue) + + require.Equal(t, 2, len(txHashes)) + require.Equal(t, expectedTxHashes[0], txHashes[0]) + require.Equal(t, expectedTxHashes[1], txHashes[1]) + require.True(t, preprocessorCalled, "preprocessor should be called when AOT cache miss") +} + +func TestTransactionCoordinator_SelectOutgoingTransactionsNilAOTSelector(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + // Ensure AOT selector is nil (default from createMockTransactionCoordinatorArguments) + tc.aotSelector = nil + + // Set up preprocessor (should be called as fallback) + expectedTxHashes := [][]byte{[]byte("normal_tx_1")} + expectedTxs := []data.TransactionHandler{&transaction.Transaction{}} + preprocessorCalled := false + tc.preProcProposal.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + SelectOutgoingTransactionsCalled: func(_ uint64, _ uint64, _ func() bool) ([][]byte, []data.TransactionHandler, error) { + preprocessorCalled = true + return expectedTxHashes, expectedTxs, nil + }, + } + + txHashes, _ := tc.SelectOutgoingTransactions(42, haveTimeTrue) + + require.Equal(t, 1, len(txHashes)) + require.Equal(t, expectedTxHashes[0], txHashes[0]) + require.True(t, preprocessorCalled, "preprocessor should be called when AOT selector is nil") +} + +func TestTransactionCoordinator_GetTxHandlersFromHashesSomeMissing(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + txHash1 := []byte("present_tx") + txHash2 := []byte("missing_tx") + txHash3 := []byte("also_present_tx") + tx1 := &transaction.Transaction{SndAddr: []byte("sender1"), Nonce: 0, Value: big.NewInt(0)} + tx3 := &transaction.Transaction{SndAddr: []byte("sender3"), Nonce: 0, Value: big.NewInt(0)} + + // Only add tx1 and tx3 to tc.dataPool, tx2 is missing + cacheId := process.ShardCacherIdentifier(0, 0) + tc.dataPool.Transactions().AddData(txHash1, tx1, 100, cacheId) + tc.dataPool.Transactions().AddData(txHash3, tx3, 100, cacheId) + + validHashes, txs := tc.getTxHandlersFromHashes([][]byte{txHash1, txHash2, txHash3}) + + require.Equal(t, 0, len(validHashes)) + require.Equal(t, 0, len(txs)) +} diff --git a/process/coordinator/process_test.go b/process/coordinator/process_test.go index be4150f01b9..15ea2ffaa79 100644 --- a/process/coordinator/process_test.go +++ b/process/coordinator/process_test.go @@ -25,6 +25,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/process/aotSelection" + "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" @@ -44,6 +48,7 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" + "github.com/multiversx/mx-chain-go/testscommon/preprocMocks" stateMock "github.com/multiversx/mx-chain-go/testscommon/state" storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" ) @@ -52,6 +57,16 @@ const MaxGasLimitPerBlock = uint64(100000) var txHash = []byte("tx_hash1") +func createMockTxCacheSelectionConfig() config.TxCacheSelectionConfig { + return config.TxCacheSelectionConfig{ + SelectionGasBandwidthIncreasePercent: 400, + SelectionGasBandwidthIncreaseScheduledPercent: 260, + SelectionGasRequested: 10_000_000_000, + SelectionMaxNumTxs: 30000, + SelectionLoopDurationCheckInterval: 10, + } +} + func FeeHandlerMock() *economicsmocks.EconomicsHandlerMock { return &economicsmocks.EconomicsHandlerMock{ ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { @@ -72,6 +87,15 @@ func FeeHandlerMock() *economicsmocks.EconomicsHandlerMock { MaxGasLimitPerTxCalled: func() uint64 { return MaxGasLimitPerBlock }, + MaxGasLimitPerTxInEpochCalled: func(_ uint32) uint64 { + return MaxGasLimitPerBlock + }, + MaxGasLimitPerBlockForSafeCrossShardInEpochCalled: func(_ uint32) uint64 { + return MaxGasLimitPerBlock + }, + MaxGasLimitPerBlockInEpochCalled: func(shardID uint32, _ uint32) uint64 { + return MaxGasLimitPerBlock + }, } } @@ -175,6 +199,9 @@ func initDataPool(testHash []byte) *dataRetrieverMock.PoolsHolderStub { } return cs }, + PostProcessTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, HeadersCalled: func() dataRetriever.HeadersPool { cs := &mock.HeadersCacherStub{} cs.RegisterHandlerCalled = func(i func(header data.HeaderHandler, key []byte)) { @@ -204,8 +231,8 @@ func initStore() *dataRetriever.ChainStorer { } func generateTestCache() storage.Cacher { - cache, _ := storageunit.NewCache(storageunit.CacheConfig{Type: storageunit.LRUCache, Capacity: 1000, Shards: 1, SizeInBytes: 0}) - return cache + testCache, _ := storageunit.NewCache(storageunit.CacheConfig{Type: storageunit.LRUCache, Capacity: 1000, Shards: 1, SizeInBytes: 0}) + return testCache } func generateTestUnit() storage.Storer { @@ -227,14 +254,22 @@ func initAccountsMock() *stateMock.AccountsStub { } func createMockTransactionCoordinatorArguments() ArgTransactionCoordinator { + accounts := &stateMock.AccountsStub{} + accounts.RecreateTrieIfNeededCalled = func(options common.RootHashHolder) error { + return nil + } + accounts.RootHashCalled = func() ([]byte, error) { + return nil, nil + } + poolsHolder := dataRetrieverMock.NewPoolsHolderMock() argsTransactionCoordinator := ArgTransactionCoordinator{ Hasher: &hashingMocks.HasherMock{}, Marshalizer: &mock.MarshalizerMock{}, ShardCoordinator: mock.NewMultiShardsCoordinatorMock(5), - Accounts: &stateMock.AccountsStub{}, - MiniBlockPool: dataRetrieverMock.NewPoolsHolderMock().MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: &mock.PreProcessorContainerMock{}, + Accounts: accounts, + DataPool: poolsHolder, + PreProcessors: &preprocMocks.PreProcessorContainerMock{}, + PreProcessorsProposal: &preprocMocks.PreProcessorContainerMock{}, InterProcessors: &mock.InterimProcessorContainerMock{}, GasHandler: &testscommon.GasHandlerStub{}, FeeHandler: &mock.FeeAccumulatorStub{}, @@ -244,15 +279,72 @@ func createMockTransactionCoordinatorArguments() ArgTransactionCoordinator { TxTypeHandler: &testscommon.TxTypeHandlerMock{}, TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, + BlockDataRequester: &preprocMocks.BlockDataRequesterStub{}, + BlockDataRequesterProposal: &preprocMocks.BlockDataRequesterStub{}, + GasComputation: &testscommon.GasComputationMock{ + AddOutgoingTransactionsCalled: func(txHashes [][]byte, transactions []data.TransactionHandler) ([][]byte, []data.MiniBlockHeaderHandler, error) { + return txHashes, nil, nil + }, + AddIncomingMiniBlocksCalled: func(miniBlocks []data.MiniBlockHeaderHandler, transactions map[string][]data.TransactionHandler) (int, int, error) { + return len(miniBlocks) - 1, 0, nil + }, + }, + AOTSelector: aotSelection.NewDisabledAOTSelector(), + } + + blockDataRequesterArgs := BlockDataRequestArgs{ + RequestHandler: &testscommon.RequestHandlerStub{}, + MiniBlockPool: poolsHolder.MiniBlocks(), + PreProcessors: argsTransactionCoordinator.PreProcessors, + ShardCoordinator: argsTransactionCoordinator.ShardCoordinator, + EnableEpochsHandler: argsTransactionCoordinator.EnableEpochsHandler, } + blockDataRequester, _ := NewBlockDataRequester(blockDataRequesterArgs) + argsTransactionCoordinator.BlockDataRequester = blockDataRequester + return argsTransactionCoordinator } +func createAndAddBlockDataRequesters( + argsTransactionCoordinator *ArgTransactionCoordinator, + nrShards uint32, + poolsHolder dataRetriever.PoolsHolder, + requestHandler process.RequestHandler, + preprocContainer process.PreProcessorsContainer, + preprocContainerProposal process.PreProcessorsContainer, +) *BlockDataRequest { + argsTransactionCoordinator.PreProcessors = preprocContainer + argsTransactionCoordinator.ShardCoordinator = mock.NewMultiShardsCoordinatorMock(nrShards) + blockDataRequestArgs := BlockDataRequestArgs{ + RequestHandler: requestHandler, + MiniBlockPool: poolsHolder.MiniBlocks(), + PreProcessors: preprocContainer, + ShardCoordinator: argsTransactionCoordinator.ShardCoordinator, + EnableEpochsHandler: argsTransactionCoordinator.EnableEpochsHandler, + } + blockDataRequest, _ := NewBlockDataRequester(blockDataRequestArgs) + argsTransactionCoordinator.BlockDataRequester = blockDataRequest + + blockDataRequesterArgsProposal := BlockDataRequestArgs{ + RequestHandler: requestHandler, + MiniBlockPool: poolsHolder.MiniBlocks(), + PreProcessors: preprocContainerProposal, + ShardCoordinator: argsTransactionCoordinator.ShardCoordinator, + EnableEpochsHandler: argsTransactionCoordinator.EnableEpochsHandler, + } + + blockDataRequesterProposal, _ := NewBlockDataRequester(blockDataRequesterArgsProposal) + argsTransactionCoordinator.BlockDataRequesterProposal = blockDataRequesterProposal + + return blockDataRequest +} + func TestNewTransactionCoordinator_NilHasher(t *testing.T) { t.Parallel() @@ -312,22 +404,44 @@ func TestNewTransactionCoordinator_NilDataPool(t *testing.T) { t.Parallel() argsTransactionCoordinator := createMockTransactionCoordinatorArguments() - argsTransactionCoordinator.MiniBlockPool = nil + argsTransactionCoordinator.DataPool = nil + tc, err := NewTransactionCoordinator(argsTransactionCoordinator) + + assert.Nil(t, tc) + assert.Equal(t, process.ErrNilPoolsHolder, err) +} + +func TestNewTransactionCoordinator_NilMiniBlockPool(t *testing.T) { + t.Parallel() + + argsTransactionCoordinator := createMockTransactionCoordinatorArguments() + argsTransactionCoordinator.DataPool = &dataRetrieverMock.PoolsHolderStub{ + MiniBlocksCalled: func() storage.Cacher { + return nil + }, + } tc, err := NewTransactionCoordinator(argsTransactionCoordinator) assert.Nil(t, tc) assert.Equal(t, process.ErrNilMiniBlockPool, err) } -func TestNewTransactionCoordinator_NilRequestHandler(t *testing.T) { +func TestNewTransactionCoordinator_NilPostProcessTransactions(t *testing.T) { t.Parallel() argsTransactionCoordinator := createMockTransactionCoordinatorArguments() - argsTransactionCoordinator.RequestHandler = nil + argsTransactionCoordinator.DataPool = &dataRetrieverMock.PoolsHolderStub{ + MiniBlocksCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + PostProcessTransactionsCalled: func() storage.Cacher { + return nil + }, + } tc, err := NewTransactionCoordinator(argsTransactionCoordinator) assert.Nil(t, tc) - assert.Equal(t, process.ErrNilRequestHandler, err) + assert.Equal(t, process.ErrNilPostProcessTransactionsCache, err) } func TestNewTransactionCoordinator_NilPreProcessor(t *testing.T) { @@ -440,6 +554,28 @@ func TestNewTransactionCoordinator_NilEnableEpochsHandler(t *testing.T) { assert.Equal(t, process.ErrNilEnableEpochsHandler, err) } +func TestNewTransactionCoordinator_NilBlockDataRequester(t *testing.T) { + t.Parallel() + + argsTransactionCoordinator := createMockTransactionCoordinatorArguments() + argsTransactionCoordinator.BlockDataRequester = nil + tc, err := NewTransactionCoordinator(argsTransactionCoordinator) + + assert.Nil(t, tc) + assert.Equal(t, process.ErrNilBlockDataRequester, err) +} + +func TestNewTransactionCoordinator_NilGasComputation(t *testing.T) { + t.Parallel() + + argsTransactionCoordinator := createMockTransactionCoordinatorArguments() + argsTransactionCoordinator.GasComputation = nil + tc, err := NewTransactionCoordinator(argsTransactionCoordinator) + + assert.Nil(t, tc) + assert.Equal(t, process.ErrNilGasComputation, err) +} + func TestNewTransactionCoordinator_InvalidEnableEpochsHandler(t *testing.T) { t.Parallel() @@ -499,8 +635,8 @@ func TestTransactionCoordinator_GetAllCurrentLogs(t *testing.T) { argsTransactionCoordinator := createMockTransactionCoordinatorArguments() argsTransactionCoordinator.TransactionsLogProcessor = &mock.TxLogsProcessorStub{ - GetAllCurrentLogsCalled: func() []*data.LogData { - return []*data.LogData{} + GetAllCurrentLogsCalled: func() []data.LogDataHandler { + return []data.LogDataHandler{} }, } @@ -510,58 +646,42 @@ func TestTransactionCoordinator_GetAllCurrentLogs(t *testing.T) { require.NotNil(t, logs) } -func TestTransactionCoordinator_SeparateBody(t *testing.T) { - t.Parallel() - - argsTransactionCoordinator := createMockTransactionCoordinatorArguments() - tc, err := NewTransactionCoordinator(argsTransactionCoordinator) - assert.Nil(t, err) - assert.NotNil(t, tc) - - body := &block.Body{} - body.MiniBlocks = append(body.MiniBlocks, &block.MiniBlock{Type: block.TxBlock}) - body.MiniBlocks = append(body.MiniBlocks, &block.MiniBlock{Type: block.TxBlock}) - body.MiniBlocks = append(body.MiniBlocks, &block.MiniBlock{Type: block.TxBlock}) - body.MiniBlocks = append(body.MiniBlocks, &block.MiniBlock{Type: block.SmartContractResultBlock}) - body.MiniBlocks = append(body.MiniBlocks, &block.MiniBlock{Type: block.SmartContractResultBlock}) - body.MiniBlocks = append(body.MiniBlocks, &block.MiniBlock{Type: block.SmartContractResultBlock}) - body.MiniBlocks = append(body.MiniBlocks, &block.MiniBlock{Type: block.SmartContractResultBlock}) - - separated := tc.separateBodyByType(body) - assert.Equal(t, 2, len(separated)) - assert.Equal(t, 3, len(separated[block.TxBlock].MiniBlocks)) - assert.Equal(t, 4, len(separated[block.SmartContractResultBlock].MiniBlocks)) -} - func createPreProcessorContainer() process.PreProcessorsContainer { - preFactory, _ := shard.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(5), - initStore(), - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - initDataPool([]byte("tx_hash0")), - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{ + args := shard.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(5), + Store: initStore(), + Marshalizer: &mock.MarshalizerMock{}, + Hasher: &hashingMocks.HasherMock{}, + DataPool: initDataPool([]byte("tx_hash0")), + PubkeyConverter: createMockPubkeyConverter(), + Accounts: &stateMock.AccountsStub{}, + AccountsProposal: &stateMock.AccountsStub{}, + RequestHandler: &testscommon.RequestHandlerStub{}, + TxProcessor: &testscommon.TxProcessorMock{ ProcessTransactionCalled: func(transaction *transaction.Transaction) (vmcommon.ReturnCode, error) { return 0, nil }, }, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - FeeHandlerMock(), - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + ScProcessor: &testscommon.SCProcessorMock{}, + ScResultProcessor: &testscommon.SmartContractResultsProcessorMock{}, + RewardsTxProcessor: &testscommon.RewardTxProcessorMock{}, + EconomicsFee: FeeHandlerMock(), + GasHandler: &testscommon.GasHandlerStub{}, + BlockTracker: &mock.BlockTrackerMock{}, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + TxTypeHandler: &testscommon.TxTypeHandlerMock{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: createMockTxCacheSelectionConfig(), + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + } + preFactory, _ := shard.NewPreProcessorsContainerFactory(args) container, _ := preFactory.Create() return container @@ -592,25 +712,26 @@ func createPreProcessorContainerWithDataPool( ) process.PreProcessorsContainer { totalGasProvided := uint64(0) - preFactory, _ := shard.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(5), - initStore(), - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataPool, - createMockPubkeyConverter(), - accounts, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{ + args := shard.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(5), + Store: initStore(), + Marshalizer: &mock.MarshalizerMock{}, + Hasher: &hashingMocks.HasherMock{}, + DataPool: dataPool, + PubkeyConverter: createMockPubkeyConverter(), + Accounts: accounts, + AccountsProposal: accounts, + RequestHandler: &testscommon.RequestHandlerStub{}, + TxProcessor: &testscommon.TxProcessorMock{ ProcessTransactionCalled: func(transaction *transaction.Transaction) (vmcommon.ReturnCode, error) { return 0, nil }, }, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - FeeHandlerMock(), - &testscommon.GasHandlerStub{ + ScProcessor: &testscommon.SCProcessorMock{}, + ScResultProcessor: &testscommon.SmartContractResultsProcessorMock{}, + RewardsTxProcessor: &testscommon.RewardTxProcessorMock{}, + EconomicsFee: FeeHandlerMock(), + GasHandler: &testscommon.GasHandlerStub{ SetGasProvidedCalled: func(gasProvided uint64, hash []byte) { totalGasProvided += gasProvided }, @@ -653,15 +774,21 @@ func createPreProcessorContainerWithDataPool( RemoveGasRefundedCalled: func(hashes [][]byte) { }, }, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + BlockTracker: &mock.BlockTrackerMock{}, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + TxTypeHandler: &testscommon.TxTypeHandlerMock{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: createMockTxCacheSelectionConfig(), + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + } + preFactory, _ := shard.NewPreProcessorsContainerFactory(args) container, _ := preFactory.Create() return container @@ -687,12 +814,12 @@ func TestTransactionCoordinator_CreateBlockStarted(t *testing.T) { tc.CreateBlockStarted() - tc.mutPreProcessor.Lock() - for _, value := range tc.txPreProcessors { + tc.preProcExecution.mutPreProcessor.Lock() + for _, value := range tc.preProcExecution.txPreProcessors { txs := value.GetAllCurrentUsedTxs() assert.Equal(t, 0, len(txs)) } - tc.mutPreProcessor.Unlock() + tc.preProcExecution.mutPreProcessor.Unlock() } func TestTransactionCoordinator_CreateMarshalizedDataNilBody(t *testing.T) { @@ -890,25 +1017,27 @@ func TestTransactionCoordinator_CreateMbsAndProcessCrossShardTransactions(t *tes } totalGasProvided := uint64(0) - preFactory, _ := shard.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(5), - initStore(), - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - tdp, - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{ + + args := shard.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(5), + Store: initStore(), + Marshalizer: &mock.MarshalizerMock{}, + Hasher: &hashingMocks.HasherMock{}, + DataPool: tdp, + PubkeyConverter: createMockPubkeyConverter(), + Accounts: &stateMock.AccountsStub{}, + AccountsProposal: &stateMock.AccountsStub{}, + RequestHandler: &testscommon.RequestHandlerStub{}, + TxProcessor: &testscommon.TxProcessorMock{ ProcessTransactionCalled: func(transaction *transaction.Transaction) (vmcommon.ReturnCode, error) { return 0, nil }, }, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - FeeHandlerMock(), - &testscommon.GasHandlerStub{ + ScProcessor: &testscommon.SCProcessorMock{}, + ScResultProcessor: &testscommon.SmartContractResultsProcessorMock{}, + RewardsTxProcessor: &testscommon.RewardTxProcessorMock{}, + EconomicsFee: FeeHandlerMock(), + GasHandler: &testscommon.GasHandlerStub{ SetGasProvidedCalled: func(gasProvided uint64, hash []byte) { totalGasProvided += gasProvided }, @@ -923,19 +1052,26 @@ func TestTransactionCoordinator_CreateMbsAndProcessCrossShardTransactions(t *tes return 0 }, }, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + BlockTracker: &mock.BlockTrackerMock{}, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + TxTypeHandler: &testscommon.TxTypeHandlerMock{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: createMockTxCacheSelectionConfig(), + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + } + + preFactory, _ := shard.NewPreProcessorsContainerFactory(args) container, _ := preFactory.Create() argsTransactionCoordinator := createMockTransactionCoordinatorArguments() - argsTransactionCoordinator.MiniBlockPool = tdp.MiniBlocks() + argsTransactionCoordinator.DataPool = tdp argsTransactionCoordinator.PreProcessors = container argsTransactionCoordinator.GasHandler = &testscommon.GasHandlerStub{ TotalGasProvidedCalled: func() uint64 { @@ -973,14 +1109,12 @@ func TestTransactionCoordinator_CreateMbsAndProcessCrossShardTransactions(t *tes func TestTransactionCoordinator_CreateMbsAndProcessCrossShardTransactionsWithSkippedShard(t *testing.T) { t.Parallel() - mbPool := dataRetrieverMock.NewPoolsHolderMock().MiniBlocks() argsTransactionCoordinator := createMockTransactionCoordinatorArguments() - argsTransactionCoordinator.MiniBlockPool = mbPool tc, _ := NewTransactionCoordinator(argsTransactionCoordinator) - tc.txPreProcessors[block.TxBlock] = &mock.PreProcessorMock{ - RequestTransactionsForMiniBlockCalled: func(miniBlock *block.MiniBlock) int { - return 0 + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + GetTransactionsAndRequestMissingForMiniBlockCalled: func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + return nil, 0 }, } @@ -1010,7 +1144,7 @@ func TestTransactionCoordinator_CreateMbsAndProcessCrossShardTransactionsWithSki hash := fmt.Sprintf("tx_hash_from_%s", mbHdr.Hash) mb := block.MiniBlock{SenderShardID: mbHdr.SenderShardID, ReceiverShardID: mbHdr.ReceiverShardID, Type: block.TxBlock, TxHashes: [][]byte{[]byte(hash)}} - mbPool.Put(mbHdr.Hash, &mb, mb.Size()) + argsTransactionCoordinator.DataPool.MiniBlocks().Put(mbHdr.Hash, &mb, mb.Size()) } } @@ -1081,21 +1215,22 @@ func TestTransactionCoordinator_CreateMbsAndProcessCrossShardTransactionsNilPreP } totalGasProvided := uint64(0) - preFactory, _ := shard.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(5), - initStore(), - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - tdp, - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - FeeHandlerMock(), - &testscommon.GasHandlerStub{ + args := shard.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(5), + Store: initStore(), + Marshalizer: &mock.MarshalizerMock{}, + Hasher: &hashingMocks.HasherMock{}, + DataPool: tdp, + PubkeyConverter: createMockPubkeyConverter(), + Accounts: &stateMock.AccountsStub{}, + AccountsProposal: &stateMock.AccountsStub{}, + RequestHandler: &testscommon.RequestHandlerStub{}, + TxProcessor: &testscommon.TxProcessorMock{}, + ScProcessor: &testscommon.SCProcessorMock{}, + ScResultProcessor: &testscommon.SmartContractResultsProcessorMock{}, + RewardsTxProcessor: &testscommon.RewardTxProcessorMock{}, + EconomicsFee: FeeHandlerMock(), + GasHandler: &testscommon.GasHandlerStub{ SetGasProvidedCalled: func(gasProvided uint64, hash []byte) { totalGasProvided += gasProvided }, @@ -1110,19 +1245,25 @@ func TestTransactionCoordinator_CreateMbsAndProcessCrossShardTransactionsNilPreP return 0 }, }, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + BlockTracker: &mock.BlockTrackerMock{}, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + TxTypeHandler: &testscommon.TxTypeHandlerMock{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: createMockTxCacheSelectionConfig(), + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + } + preFactory, _ := shard.NewPreProcessorsContainerFactory(args) container, _ := preFactory.Create() argsTransactionCoordinator := createMockTransactionCoordinatorArguments() - argsTransactionCoordinator.MiniBlockPool = tdp.MiniBlocks() + argsTransactionCoordinator.DataPool = tdp argsTransactionCoordinator.PreProcessors = container argsTransactionCoordinator.GasHandler = &testscommon.GasHandlerStub{ TotalGasProvidedCalled: func() uint64 { @@ -1186,12 +1327,12 @@ func TestTransactionCoordinator_CreateMbsAndProcessTransactionsFromMeNothingToPr } totalGasProvided := uint64(0) - preFactory, _ := shard.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(5), - initStore(), - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - &dataRetrieverMock.PoolsHolderStub{ + args := shard.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(5), + Store: initStore(), + Marshalizer: &mock.MarshalizerMock{}, + Hasher: &hashingMocks.HasherMock{}, + DataPool: &dataRetrieverMock.PoolsHolderStub{ TransactionsCalled: func() dataRetriever.ShardedDataCacherNotifier { return shardedCacheMock }, @@ -1202,36 +1343,43 @@ func TestTransactionCoordinator_CreateMbsAndProcessTransactionsFromMeNothingToPr return shardedCacheMock }, }, - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{ + PubkeyConverter: createMockPubkeyConverter(), + Accounts: &stateMock.AccountsStub{}, + AccountsProposal: &stateMock.AccountsStub{}, + RequestHandler: &testscommon.RequestHandlerStub{}, + TxProcessor: &testscommon.TxProcessorMock{ ProcessTransactionCalled: func(transaction *transaction.Transaction) (vmcommon.ReturnCode, error) { return 0, nil }, }, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - FeeHandlerMock(), - &testscommon.GasHandlerStub{ + ScProcessor: &testscommon.SCProcessorMock{}, + ScResultProcessor: &testscommon.SmartContractResultsProcessorMock{}, + RewardsTxProcessor: &testscommon.RewardTxProcessorMock{}, + EconomicsFee: FeeHandlerMock(), + GasHandler: &testscommon.GasHandlerStub{ TotalGasProvidedCalled: func() uint64 { return totalGasProvided }, }, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + BlockTracker: &mock.BlockTrackerMock{}, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + TxTypeHandler: &testscommon.TxTypeHandlerMock{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: createMockTxCacheSelectionConfig(), + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + } + preFactory, _ := shard.NewPreProcessorsContainerFactory(args) container, _ := preFactory.Create() argsTransactionCoordinator := createMockTransactionCoordinatorArguments() - argsTransactionCoordinator.MiniBlockPool = dataRetrieverMock.NewPoolsHolderMock().MiniBlocks() + argsTransactionCoordinator.DataPool = dataRetrieverMock.NewPoolsHolderMock() argsTransactionCoordinator.PreProcessors = container tc, err := NewTransactionCoordinator(argsTransactionCoordinator) assert.Nil(t, err) @@ -1249,7 +1397,7 @@ func TestTransactionCoordinator_CreateMbsAndProcessTransactionsFromMeNoTime(t *t t.Parallel() tdp := initDataPool(txHash) argsTransactionCoordinator := createMockTransactionCoordinatorArguments() - argsTransactionCoordinator.MiniBlockPool = tdp.MiniBlocks() + argsTransactionCoordinator.DataPool = tdp argsTransactionCoordinator.PreProcessors = createPreProcessorContainerWithDataPool(tdp, FeeHandlerMock(), argsTransactionCoordinator.Accounts) tc, err := NewTransactionCoordinator(argsTransactionCoordinator) assert.Nil(t, err) @@ -1268,7 +1416,7 @@ func TestTransactionCoordinator_CreateMbsAndProcessTransactionsFromMeNoSpace(t * totalGasProvided := uint64(0) tdp := initDataPool(txHash) argsTransactionCoordinator := createMockTransactionCoordinatorArguments() - argsTransactionCoordinator.MiniBlockPool = tdp.MiniBlocks() + argsTransactionCoordinator.DataPool = tdp argsTransactionCoordinator.PreProcessors = createPreProcessorContainerWithDataPool(tdp, FeeHandlerMock(), argsTransactionCoordinator.Accounts) argsTransactionCoordinator.GasHandler = &testscommon.GasHandlerStub{ TotalGasProvidedCalled: func() uint64 { @@ -1299,6 +1447,9 @@ func TestTransactionCoordinator_CreateMbsAndProcessTransactionsFromMe(t *testing argsTransactionCoordinator := createMockTransactionCoordinatorArguments() argsTransactionCoordinator.Accounts = &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return []byte("rootHash"), nil + }, GetExistingAccountCalled: func(_ []byte) (vmcommon.AccountHandler, error) { return &stateMock.UserAccountStub{ Nonce: 42, @@ -1307,7 +1458,7 @@ func TestTransactionCoordinator_CreateMbsAndProcessTransactionsFromMe(t *testing }, } argsTransactionCoordinator.ShardCoordinator = mock.NewMultiShardsCoordinatorMock(nrShards) - argsTransactionCoordinator.MiniBlockPool = tdp.MiniBlocks() + argsTransactionCoordinator.DataPool = tdp argsTransactionCoordinator.PreProcessors = createPreProcessorContainerWithDataPool(tdp, FeeHandlerMock(), argsTransactionCoordinator.Accounts) tc, err := NewTransactionCoordinator(argsTransactionCoordinator) @@ -1322,12 +1473,15 @@ func TestTransactionCoordinator_CreateMbsAndProcessTransactionsFromMe(t *testing hasher := &hashingMocks.HasherMock{} for shId := uint32(0); shId < nrShards; shId++ { strCache := process.ShardCacherIdentifier(0, shId) - newTx := &transaction.Transaction{GasLimit: uint64(shId), Nonce: 42 + uint64(shId)} + newTx := &transaction.Transaction{Value: big.NewInt(0), GasLimit: uint64(shId), Nonce: 42 + uint64(shId)} computedTxHash, _ := core.CalculateHash(marshalizer, hasher, newTx) txPool.AddData(computedTxHash, newTx, newTx.Size(), strCache) } + err = txPool.OnExecutedBlock(&block.Header{}, []byte("rootHash")) + require.Nil(t, err) + // we have one tx per shard. mbs := tc.CreateMbsAndProcessTransactionsFromMe(haveTime, []byte("randomness")) @@ -1346,6 +1500,9 @@ func TestTransactionCoordinator_CreateMbsAndProcessTransactionsFromMeMultipleMin argsTransactionCoordinator := createMockTransactionCoordinatorArguments() argsTransactionCoordinator.Accounts = &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return []byte("rootHash"), nil + }, GetExistingAccountCalled: func(_ []byte) (vmcommon.AccountHandler, error) { return &stateMock.UserAccountStub{ Nonce: 0, @@ -1354,7 +1511,7 @@ func TestTransactionCoordinator_CreateMbsAndProcessTransactionsFromMeMultipleMin }, } argsTransactionCoordinator.ShardCoordinator = mock.NewMultiShardsCoordinatorMock(nrShards) - argsTransactionCoordinator.MiniBlockPool = tdp.MiniBlocks() + argsTransactionCoordinator.DataPool = tdp argsTransactionCoordinator.PreProcessors = createPreProcessorContainerWithDataPool(tdp, FeeHandlerMock(), argsTransactionCoordinator.Accounts) tc, err := NewTransactionCoordinator(argsTransactionCoordinator) assert.Nil(t, err) @@ -1378,12 +1535,14 @@ func TestTransactionCoordinator_CreateMbsAndProcessTransactionsFromMeMultipleMin allTxs := 100 for i := 0; i < allTxs; i++ { - newTx := &transaction.Transaction{GasLimit: gasLimit, GasPrice: uint64(i), RcvAddr: scAddress} + newTx := &transaction.Transaction{Value: big.NewInt(0), GasLimit: gasLimit, GasPrice: uint64(i), RcvAddr: scAddress} computedTxHash, _ := core.CalculateHash(marshalizer, hasher, newTx) txPool.AddData(computedTxHash, newTx, newTx.Size(), strCache) } + err = txPool.OnExecutedBlock(&block.Header{}, []byte("rootHash")) + require.Nil(t, err) // we have one tx per shard. mbs := tc.CreateMbsAndProcessTransactionsFromMe(haveTime, []byte("randomness")) @@ -1407,6 +1566,9 @@ func TestTransactionCoordinator_CreateMbsAndProcessTransactionsFromMeMultipleMin argsTransactionCoordinator := createMockTransactionCoordinatorArguments() argsTransactionCoordinator.Accounts = &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return []byte("rootHash"), nil + }, GetExistingAccountCalled: func(_ []byte) (vmcommon.AccountHandler, error) { return &stateMock.UserAccountStub{ Nonce: 0, @@ -1415,7 +1577,7 @@ func TestTransactionCoordinator_CreateMbsAndProcessTransactionsFromMeMultipleMin }, } argsTransactionCoordinator.ShardCoordinator = mock.NewMultiShardsCoordinatorMock(nrShards) - argsTransactionCoordinator.MiniBlockPool = tdp.MiniBlocks() + argsTransactionCoordinator.DataPool = tdp argsTransactionCoordinator.PreProcessors = createPreProcessorContainerWithDataPool( tdp, &economicsmocks.EconomicsHandlerMock{ @@ -1450,12 +1612,15 @@ func TestTransactionCoordinator_CreateMbsAndProcessTransactionsFromMeMultipleMin scAddress, _ := hex.DecodeString("000000000000000000005fed9c659422cd8429ce92f8973bba2a9fb51e0eb3a1") for i := 0; i < allTxs; i++ { - newTx := &transaction.Transaction{GasLimit: gasLimit + gasLimit/uint64(numMiniBlocks), GasPrice: uint64(i), RcvAddr: scAddress} + newTx := &transaction.Transaction{Value: big.NewInt(0), GasLimit: gasLimit + gasLimit/uint64(numMiniBlocks), GasPrice: uint64(i), RcvAddr: scAddress} computedTxHash, _ := core.CalculateHash(marshalizer, hasher, newTx) txPool.AddData(computedTxHash, newTx, newTx.Size(), strCache) } + err = txPool.OnExecutedBlock(&block.Header{}, []byte("rootHash")) + require.Nil(t, err) + // we have one tx per shard. mbs := tc.CreateMbsAndProcessTransactionsFromMe(haveTime, []byte("randomness")) @@ -1478,6 +1643,9 @@ func TestTransactionCoordinator_CompactAndExpandMiniblocksShouldWork(t *testing. argsTransactionCoordinator := createMockTransactionCoordinatorArguments() argsTransactionCoordinator.Accounts = &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return []byte("rootHash"), nil + }, GetExistingAccountCalled: func(_ []byte) (vmcommon.AccountHandler, error) { return &stateMock.UserAccountStub{ Nonce: 0, @@ -1486,7 +1654,7 @@ func TestTransactionCoordinator_CompactAndExpandMiniblocksShouldWork(t *testing. }, } argsTransactionCoordinator.ShardCoordinator = mock.NewMultiShardsCoordinatorMock(nrShards) - argsTransactionCoordinator.MiniBlockPool = tdp.MiniBlocks() + argsTransactionCoordinator.DataPool = tdp argsTransactionCoordinator.PreProcessors = createPreProcessorContainerWithDataPool( tdp, &economicsmocks.EconomicsHandlerMock{ @@ -1526,13 +1694,16 @@ func TestTransactionCoordinator_CompactAndExpandMiniblocksShouldWork(t *testing. for _, shardCacher := range shardCacherIdentifiers { for i := 0; i < numTxsPerBulk; i++ { - newTx := &transaction.Transaction{GasLimit: gasLimit, GasPrice: uint64(i), RcvAddr: scAddress} + newTx := &transaction.Transaction{Value: big.NewInt(0), GasLimit: gasLimit, GasPrice: uint64(i), RcvAddr: scAddress} computedTxHash, _ := core.CalculateHash(marshalizer, hasher, newTx) txPool.AddData(computedTxHash, newTx, newTx.Size(), shardCacher) } } + err = txPool.OnExecutedBlock(&block.Header{}, []byte("rootHash")) + require.Nil(t, err) + mbs := tc.CreateMbsAndProcessTransactionsFromMe(haveTime, []byte("randomness")) assert.Equal(t, 1, len(mbs)) @@ -1550,6 +1721,9 @@ func TestTransactionCoordinator_GetAllCurrentUsedTxs(t *testing.T) { argsTransactionCoordinator := createMockTransactionCoordinatorArguments() argsTransactionCoordinator.Accounts = &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return []byte("rootHash"), nil + }, GetExistingAccountCalled: func(_ []byte) (vmcommon.AccountHandler, error) { return &stateMock.UserAccountStub{ Nonce: 42, @@ -1557,9 +1731,9 @@ func TestTransactionCoordinator_GetAllCurrentUsedTxs(t *testing.T) { }, nil }, } - argsTransactionCoordinator.ShardCoordinator = mock.NewMultiShardsCoordinatorMock(nrShards) - argsTransactionCoordinator.MiniBlockPool = tdp.MiniBlocks() - argsTransactionCoordinator.PreProcessors = createPreProcessorContainerWithDataPool(tdp, FeeHandlerMock(), argsTransactionCoordinator.Accounts) + preprocContainer := createPreProcessorContainerWithDataPool(tdp, FeeHandlerMock(), argsTransactionCoordinator.Accounts) + _ = createAndAddBlockDataRequesters(&argsTransactionCoordinator, nrShards, tdp, &testscommon.RequestHandlerStub{}, preprocContainer, preprocContainer) + argsTransactionCoordinator.GasHandler = &testscommon.GasHandlerStub{ ComputeGasProvidedByTxCalled: func(txSndShId uint32, txRcvShId uint32, txHandler data.TransactionHandler) (uint64, uint64, error) { return 0, 0, nil @@ -1581,12 +1755,15 @@ func TestTransactionCoordinator_GetAllCurrentUsedTxs(t *testing.T) { hasher := &hashingMocks.HasherMock{} for i := uint32(0); i < nrShards; i++ { strCache := process.ShardCacherIdentifier(0, i) - newTx := &transaction.Transaction{GasLimit: uint64(i), Nonce: 42 + uint64(i)} + newTx := &transaction.Transaction{Value: big.NewInt(0), GasLimit: uint64(i), Nonce: 42 + uint64(i)} computedTxHash, _ := core.CalculateHash(marshalizer, hasher, newTx) txPool.AddData(computedTxHash, newTx, newTx.Size(), strCache) } + err = txPool.OnExecutedBlock(&block.Header{}, []byte("rootHash")) + require.Nil(t, err) + mbs := tc.CreateMbsAndProcessTransactionsFromMe(haveTime, []byte("randomness")) require.Equal(t, 5, len(mbs)) @@ -1597,23 +1774,23 @@ func TestTransactionCoordinator_GetAllCurrentUsedTxs(t *testing.T) { func TestTransactionCoordinator_RequestBlockTransactionsNilBody(t *testing.T) { t.Parallel() + argsTransactionCoordinator := createMockTransactionCoordinatorArguments() tdp := initDataPool(txHash) nrShards := uint32(5) - argsTransactionCoordinator := createMockTransactionCoordinatorArguments() - argsTransactionCoordinator.ShardCoordinator = mock.NewMultiShardsCoordinatorMock(nrShards) - argsTransactionCoordinator.MiniBlockPool = tdp.MiniBlocks() - argsTransactionCoordinator.PreProcessors = createPreProcessorContainerWithDataPool(tdp, FeeHandlerMock(), argsTransactionCoordinator.Accounts) + preprocContainer := createPreProcessorContainerWithDataPool(tdp, FeeHandlerMock(), argsTransactionCoordinator.Accounts) + blockDataRequester := createAndAddBlockDataRequesters(&argsTransactionCoordinator, nrShards, tdp, &testscommon.RequestHandlerStub{}, preprocContainer, preprocContainer) + tc, err := NewTransactionCoordinator(argsTransactionCoordinator) assert.Nil(t, err) assert.NotNil(t, tc) tc.RequestBlockTransactions(nil) - tc.mutRequestedTxs.Lock() - for _, value := range tc.requestedTxs { + blockDataRequester.mutRequestedTxs.Lock() + for _, value := range blockDataRequester.requestedTxs { assert.Equal(t, 0, value) } - tc.mutRequestedTxs.Unlock() + blockDataRequester.mutRequestedTxs.Unlock() } func TestTransactionCoordinator_RequestBlockTransactionsRequestOne(t *testing.T) { @@ -1622,9 +1799,8 @@ func TestTransactionCoordinator_RequestBlockTransactionsRequestOne(t *testing.T) tdp := initDataPool(txHash) nrShards := uint32(5) argsTransactionCoordinator := createMockTransactionCoordinatorArguments() - argsTransactionCoordinator.ShardCoordinator = mock.NewMultiShardsCoordinatorMock(nrShards) - argsTransactionCoordinator.MiniBlockPool = tdp.MiniBlocks() - argsTransactionCoordinator.PreProcessors = createPreProcessorContainerWithDataPool(tdp, FeeHandlerMock(), argsTransactionCoordinator.Accounts) + preprocContainer := createPreProcessorContainerWithDataPool(tdp, FeeHandlerMock(), argsTransactionCoordinator.Accounts) + blockDataRequester := createAndAddBlockDataRequesters(&argsTransactionCoordinator, nrShards, tdp, &testscommon.RequestHandlerStub{}, preprocContainer, preprocContainer) tc, err := NewTransactionCoordinator(argsTransactionCoordinator) assert.Nil(t, err) assert.NotNil(t, tc) @@ -1635,9 +1811,9 @@ func TestTransactionCoordinator_RequestBlockTransactionsRequestOne(t *testing.T) body.MiniBlocks = append(body.MiniBlocks, miniBlock) tc.RequestBlockTransactions(body) - tc.mutRequestedTxs.Lock() - assert.Equal(t, 1, tc.requestedTxs[block.TxBlock]) - tc.mutRequestedTxs.Unlock() + blockDataRequester.mutRequestedTxs.Lock() + assert.Equal(t, 1, blockDataRequester.requestedTxs[block.TxBlock]) + blockDataRequester.mutRequestedTxs.Unlock() haveTime := func() time.Duration { return time.Second @@ -1649,12 +1825,7 @@ func TestTransactionCoordinator_RequestBlockTransactionsRequestOne(t *testing.T) func TestTransactionCoordinator_IsDataPreparedForProcessing(t *testing.T) { t.Parallel() - tdp := initDataPool(txHash) - nrShards := uint32(5) - argsTransactionCoordinator := createMockTransactionCoordinatorArguments() - argsTransactionCoordinator.ShardCoordinator = mock.NewMultiShardsCoordinatorMock(nrShards) - argsTransactionCoordinator.MiniBlockPool = tdp.MiniBlocks() - argsTransactionCoordinator.PreProcessors = createPreProcessorContainerWithDataPool(tdp, FeeHandlerMock(), argsTransactionCoordinator.Accounts) + argsTransactionCoordinator := createDefaultTxCoordinatorArgs() tc, err := NewTransactionCoordinator(argsTransactionCoordinator) assert.Nil(t, err) assert.NotNil(t, tc) @@ -1669,12 +1840,7 @@ func TestTransactionCoordinator_IsDataPreparedForProcessing(t *testing.T) { func TestTransactionCoordinator_SaveTxsToStorage(t *testing.T) { t.Parallel() - tdp := initDataPool(txHash) - argsTransactionCoordinator := createMockTransactionCoordinatorArguments() - argsTransactionCoordinator.ShardCoordinator = mock.NewMultiShardsCoordinatorMock(3) - argsTransactionCoordinator.Accounts = initAccountsMock() - argsTransactionCoordinator.MiniBlockPool = tdp.MiniBlocks() - argsTransactionCoordinator.PreProcessors = createPreProcessorContainerWithDataPool(tdp, FeeHandlerMock(), argsTransactionCoordinator.Accounts) + argsTransactionCoordinator := createDefaultTxCoordinatorArgs() tc, err := NewTransactionCoordinator(argsTransactionCoordinator) assert.Nil(t, err) assert.NotNil(t, tc) @@ -1705,12 +1871,7 @@ func TestTransactionCoordinator_SaveTxsToStorage(t *testing.T) { func TestTransactionCoordinator_RestoreBlockDataFromStorage(t *testing.T) { t.Parallel() - tdp := initDataPool(txHash) - argsTransactionCoordinator := createMockTransactionCoordinatorArguments() - argsTransactionCoordinator.ShardCoordinator = mock.NewMultiShardsCoordinatorMock(3) - argsTransactionCoordinator.Accounts = initAccountsMock() - argsTransactionCoordinator.MiniBlockPool = tdp.MiniBlocks() - argsTransactionCoordinator.PreProcessors = createPreProcessorContainerWithDataPool(tdp, FeeHandlerMock(), argsTransactionCoordinator.Accounts) + argsTransactionCoordinator := createDefaultTxCoordinatorArgs() tc, err := NewTransactionCoordinator(argsTransactionCoordinator) assert.Nil(t, err) assert.NotNil(t, tc) @@ -1743,12 +1904,7 @@ func TestTransactionCoordinator_RestoreBlockDataFromStorage(t *testing.T) { func TestTransactionCoordinator_RemoveBlockDataFromPool(t *testing.T) { t.Parallel() - dataPool := initDataPool(txHash) - argsTransactionCoordinator := createMockTransactionCoordinatorArguments() - argsTransactionCoordinator.ShardCoordinator = mock.NewMultiShardsCoordinatorMock(3) - argsTransactionCoordinator.Accounts = initAccountsMock() - argsTransactionCoordinator.MiniBlockPool = dataPool.MiniBlocks() - argsTransactionCoordinator.PreProcessors = createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), argsTransactionCoordinator.Accounts) + argsTransactionCoordinator := createDefaultTxCoordinatorArgs() tc, err := NewTransactionCoordinator(argsTransactionCoordinator) assert.Nil(t, err) assert.NotNil(t, tc) @@ -1771,25 +1927,26 @@ func TestTransactionCoordinator_ProcessBlockTransactionProcessTxError(t *testing dataPool := initDataPool(txHash) accounts := initAccountsMock() - preFactory, _ := shard.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(5), - initStore(), - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataPool, - createMockPubkeyConverter(), - accounts, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{ + args := shard.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(5), + Store: initStore(), + Marshalizer: &mock.MarshalizerMock{}, + Hasher: &hashingMocks.HasherMock{}, + DataPool: dataPool, + PubkeyConverter: createMockPubkeyConverter(), + Accounts: accounts, + AccountsProposal: accounts, + RequestHandler: &testscommon.RequestHandlerStub{}, + TxProcessor: &testscommon.TxProcessorMock{ ProcessTransactionCalled: func(transaction *transaction.Transaction) (vmcommon.ReturnCode, error) { return 0, process.ErrHigherNonceInTransaction }, }, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - FeeHandlerMock(), - &testscommon.GasHandlerStub{ + ScProcessor: &testscommon.SCProcessorMock{}, + ScResultProcessor: &testscommon.SmartContractResultsProcessorMock{}, + RewardsTxProcessor: &testscommon.RewardTxProcessorMock{}, + EconomicsFee: FeeHandlerMock(), + GasHandler: &testscommon.GasHandlerStub{ ComputeGasProvidedByMiniBlockCalled: func(miniBlock *block.MiniBlock, mapHashTx map[string]data.TransactionHandler) (uint64, uint64, error) { return 0, 0, nil }, @@ -1798,22 +1955,27 @@ func TestTransactionCoordinator_ProcessBlockTransactionProcessTxError(t *testing }, SetGasRefundedCalled: func(gasRefunded uint64, hash []byte) {}, }, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + BlockTracker: &mock.BlockTrackerMock{}, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + TxTypeHandler: &testscommon.TxTypeHandlerMock{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: createMockTxCacheSelectionConfig(), + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + } + preFactory, _ := shard.NewPreProcessorsContainerFactory(args) container, _ := preFactory.Create() argsTransactionCoordinator := createMockTransactionCoordinatorArguments() - argsTransactionCoordinator.ShardCoordinator = mock.NewMultiShardsCoordinatorMock(3) argsTransactionCoordinator.Accounts = initAccountsMock() - argsTransactionCoordinator.MiniBlockPool = dataPool.MiniBlocks() argsTransactionCoordinator.PreProcessors = container + _ = createAndAddBlockDataRequesters(&argsTransactionCoordinator, 3, dataPool, &testscommon.RequestHandlerStub{}, container, container) tc, err := NewTransactionCoordinator(argsTransactionCoordinator) assert.Nil(t, err) assert.NotNil(t, tc) @@ -1850,12 +2012,7 @@ func TestTransactionCoordinator_ProcessBlockTransactionProcessTxError(t *testing func TestTransactionCoordinator_ProcessBlockTransaction(t *testing.T) { t.Parallel() - dataPool := initDataPool(txHash) - argsTransactionCoordinator := createMockTransactionCoordinatorArguments() - argsTransactionCoordinator.ShardCoordinator = mock.NewMultiShardsCoordinatorMock(3) - argsTransactionCoordinator.Accounts = initAccountsMock() - argsTransactionCoordinator.MiniBlockPool = dataPool.MiniBlocks() - argsTransactionCoordinator.PreProcessors = createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), argsTransactionCoordinator.Accounts) + argsTransactionCoordinator := createDefaultTxCoordinatorArgs() tc, err := NewTransactionCoordinator(argsTransactionCoordinator) assert.Nil(t, err) assert.NotNil(t, tc) @@ -1893,7 +2050,6 @@ func TestTransactionCoordinator_RequestMiniblocks(t *testing.T) { t.Parallel() dataPool := initDataPool(txHash) - shardCoordinator := mock.NewMultiShardsCoordinatorMock(3) nrCalled := 0 mutex := sync.Mutex{} @@ -1906,42 +2062,47 @@ func TestTransactionCoordinator_RequestMiniblocks(t *testing.T) { } accounts := initAccountsMock() - preFactory, _ := shard.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(5), - initStore(), - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataPool, - createMockPubkeyConverter(), - accounts, - requestHandler, - &testscommon.TxProcessorMock{ + args := shard.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(5), + Store: initStore(), + Marshalizer: &mock.MarshalizerMock{}, + Hasher: &hashingMocks.HasherMock{}, + DataPool: dataPool, + PubkeyConverter: createMockPubkeyConverter(), + Accounts: accounts, + AccountsProposal: accounts, + RequestHandler: requestHandler, + TxProcessor: &testscommon.TxProcessorMock{ ProcessTransactionCalled: func(transaction *transaction.Transaction) (vmcommon.ReturnCode, error) { return 0, nil }, }, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - FeeHandlerMock(), - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + ScProcessor: &testscommon.SCProcessorMock{}, + ScResultProcessor: &testscommon.SmartContractResultsProcessorMock{}, + RewardsTxProcessor: &testscommon.RewardTxProcessorMock{}, + EconomicsFee: FeeHandlerMock(), + GasHandler: &testscommon.GasHandlerStub{}, + BlockTracker: &mock.BlockTrackerMock{}, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + TxTypeHandler: &testscommon.TxTypeHandlerMock{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: createMockTxCacheSelectionConfig(), + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + } + preFactory, _ := shard.NewPreProcessorsContainerFactory(args) container, _ := preFactory.Create() argsTransactionCoordinator := createMockTransactionCoordinatorArguments() - argsTransactionCoordinator.ShardCoordinator = shardCoordinator argsTransactionCoordinator.Accounts = accounts - argsTransactionCoordinator.MiniBlockPool = dataPool.MiniBlocks() - argsTransactionCoordinator.RequestHandler = requestHandler argsTransactionCoordinator.PreProcessors = container + _ = createAndAddBlockDataRequesters(&argsTransactionCoordinator, 3, dataPool, requestHandler, container, container) tc, err := NewTransactionCoordinator(argsTransactionCoordinator) assert.Nil(t, err) assert.NotNil(t, tc) @@ -1955,7 +2116,7 @@ func TestTransactionCoordinator_RequestMiniblocks(t *testing.T) { header := createTestMetablock() tc.RequestMiniBlocksAndTransactions(header) - crossMbs := header.GetMiniBlockHeadersWithDst(shardCoordinator.SelfId()) + crossMbs := header.GetMiniBlockHeadersWithDst(argsTransactionCoordinator.ShardCoordinator.SelfId()) time.Sleep(time.Second) mutex.Lock() assert.Equal(t, len(crossMbs), nrCalled) @@ -2011,6 +2172,9 @@ func TestShardProcessor_ProcessMiniBlockCompleteWithOkTxsShouldExecuteThemAndNot tx3ExecutionResult := uint64(0) accounts := &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return []byte("rootHash"), nil + }, RevertToSnapshotCalled: func(snapshot int) error { assert.Fail(t, "revert should have not been called") return nil @@ -2021,17 +2185,17 @@ func TestShardProcessor_ProcessMiniBlockCompleteWithOkTxsShouldExecuteThemAndNot } totalGasProvided := uint64(0) - - preFactory, _ := shard.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(5), - initStore(), - marshalizer, - hasher, - dataPool, - createMockPubkeyConverter(), - accounts, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{ + args := shard.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(5), + Store: initStore(), + Marshalizer: marshalizer, + Hasher: hasher, + DataPool: dataPool, + PubkeyConverter: createMockPubkeyConverter(), + Accounts: accounts, + AccountsProposal: accounts, + RequestHandler: &testscommon.RequestHandlerStub{}, + TxProcessor: &testscommon.TxProcessorMock{ ProcessTransactionCalled: func(transaction *transaction.Transaction) (vmcommon.ReturnCode, error) { // execution, in this context, means moving the tx nonce to itx corresponding execution result variable if bytes.Equal(transaction.Data, txHash1) { @@ -2047,11 +2211,11 @@ func TestShardProcessor_ProcessMiniBlockCompleteWithOkTxsShouldExecuteThemAndNot return 0, nil }, }, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - FeeHandlerMock(), - &testscommon.GasHandlerStub{ + ScProcessor: &testscommon.SCProcessorMock{}, + ScResultProcessor: &testscommon.SmartContractResultsProcessorMock{}, + RewardsTxProcessor: &testscommon.RewardTxProcessorMock{}, + EconomicsFee: FeeHandlerMock(), + GasHandler: &testscommon.GasHandlerStub{ SetGasProvidedCalled: func(gasProvided uint64, hash []byte) { totalGasProvided += gasProvided }, @@ -2066,21 +2230,27 @@ func TestShardProcessor_ProcessMiniBlockCompleteWithOkTxsShouldExecuteThemAndNot return 0 }, }, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + BlockTracker: &mock.BlockTrackerMock{}, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + TxTypeHandler: &testscommon.TxTypeHandlerMock{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: createMockTxCacheSelectionConfig(), + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + } + preFactory, _ := shard.NewPreProcessorsContainerFactory(args) container, _ := preFactory.Create() argsTransactionCoordinator := createMockTransactionCoordinatorArguments() argsTransactionCoordinator.ShardCoordinator = mock.NewMultiShardsCoordinatorMock(3) argsTransactionCoordinator.Accounts = accounts - argsTransactionCoordinator.MiniBlockPool = dataPool.MiniBlocks() + argsTransactionCoordinator.DataPool = dataPool argsTransactionCoordinator.PreProcessors = container argsTransactionCoordinator.GasHandler = &testscommon.GasHandlerStub{ TotalGasProvidedCalled: func() uint64 { @@ -2097,7 +2267,7 @@ func TestShardProcessor_ProcessMiniBlockCompleteWithOkTxsShouldExecuteThemAndNot haveAdditionalTime := func() bool { return false } - preproc := tc.getPreProcessor(block.TxBlock) + preproc := tc.preProcExecution.getPreProcessor(block.TxBlock) processedMbInfo := &processedMb.ProcessedMiniBlockInfo{ IndexOfLastTxProcessed: -1, FullyProcessed: false, @@ -2158,6 +2328,9 @@ func TestShardProcessor_ProcessMiniBlockCompleteWithErrorWhileProcessShouldCallR revertAccntStateCalled := false accounts := &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return []byte("rootHash"), nil + }, RevertToSnapshotCalled: func(snapshot int) error { if snapshot == currentJournalLen { revertAccntStateCalled = true @@ -2170,16 +2343,17 @@ func TestShardProcessor_ProcessMiniBlockCompleteWithErrorWhileProcessShouldCallR }, } - preFactory, _ := shard.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(5), - initStore(), - marshalizer, - hasher, - dataPool, - createMockPubkeyConverter(), - accounts, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{ + args := shard.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(5), + Store: initStore(), + Marshalizer: marshalizer, + Hasher: hasher, + DataPool: dataPool, + PubkeyConverter: createMockPubkeyConverter(), + Accounts: accounts, + AccountsProposal: accounts, + RequestHandler: &testscommon.RequestHandlerStub{}, + TxProcessor: &testscommon.TxProcessorMock{ ProcessTransactionCalled: func(transaction *transaction.Transaction) (vmcommon.ReturnCode, error) { if bytes.Equal(transaction.Data, txHash2) { return 0, process.ErrHigherNonceInTransaction @@ -2187,11 +2361,11 @@ func TestShardProcessor_ProcessMiniBlockCompleteWithErrorWhileProcessShouldCallR return 0, nil }, }, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - FeeHandlerMock(), - &testscommon.GasHandlerStub{ + ScProcessor: &testscommon.SCProcessorMock{}, + ScResultProcessor: &testscommon.SmartContractResultsProcessorMock{}, + RewardsTxProcessor: &testscommon.RewardTxProcessorMock{}, + EconomicsFee: FeeHandlerMock(), + GasHandler: &testscommon.GasHandlerStub{ ComputeGasProvidedByTxCalled: func(txSenderShardId uint32, txReceiverSharedId uint32, txHandler data.TransactionHandler) (uint64, uint64, error) { return 0, 0, nil }, @@ -2208,22 +2382,28 @@ func TestShardProcessor_ProcessMiniBlockCompleteWithErrorWhileProcessShouldCallR RemoveGasProvidedCalled: func(hashes [][]byte) { }, }, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + BlockTracker: &mock.BlockTrackerMock{}, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + TxTypeHandler: &testscommon.TxTypeHandlerMock{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: createMockTxCacheSelectionConfig(), + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + } + preFactory, _ := shard.NewPreProcessorsContainerFactory(args) container, _ := preFactory.Create() totalGasProvided := uint64(0) argsTransactionCoordinator := createMockTransactionCoordinatorArguments() argsTransactionCoordinator.ShardCoordinator = mock.NewMultiShardsCoordinatorMock(3) argsTransactionCoordinator.Accounts = accounts - argsTransactionCoordinator.MiniBlockPool = dataPool.MiniBlocks() + argsTransactionCoordinator.DataPool = dataPool argsTransactionCoordinator.PreProcessors = container argsTransactionCoordinator.GasHandler = &testscommon.GasHandlerStub{ TotalGasProvidedCalled: func() uint64 { @@ -2243,7 +2423,7 @@ func TestShardProcessor_ProcessMiniBlockCompleteWithErrorWhileProcessShouldCallR haveAdditionalTime := func() bool { return false } - preproc := tc.getPreProcessor(block.TxBlock) + preproc := tc.preProcExecution.getPreProcessor(block.TxBlock) processedMbInfo := &processedMb.ProcessedMiniBlockInfo{ IndexOfLastTxProcessed: -1, FullyProcessed: false, @@ -2275,7 +2455,7 @@ func TestTransactionCoordinator_VerifyCreatedBlockTransactionsNilOrMiss(t *testi argsTransactionCoordinator := createMockTransactionCoordinatorArguments() argsTransactionCoordinator.ShardCoordinator = shardCoordinator - argsTransactionCoordinator.MiniBlockPool = tdp.MiniBlocks() + argsTransactionCoordinator.DataPool = tdp argsTransactionCoordinator.InterProcessors = container tc, err := NewTransactionCoordinator(argsTransactionCoordinator) assert.Nil(t, err) @@ -2336,7 +2516,7 @@ func TestTransactionCoordinator_VerifyCreatedBlockTransactionsOk(t *testing.T) { argsTransactionCoordinator := createMockTransactionCoordinatorArguments() argsTransactionCoordinator.ShardCoordinator = shardCoordinator - argsTransactionCoordinator.MiniBlockPool = tdp.MiniBlocks() + argsTransactionCoordinator.DataPool = tdp argsTransactionCoordinator.InterProcessors = container tc, err := NewTransactionCoordinator(argsTransactionCoordinator) assert.Nil(t, err) @@ -2409,7 +2589,7 @@ func TestTransactionCoordinator_SaveTxsToStorageCallsSaveIntermediate(t *testing argsTransactionCoordinator := createMockTransactionCoordinatorArguments() argsTransactionCoordinator.ShardCoordinator = mock.NewMultiShardsCoordinatorMock(3) argsTransactionCoordinator.Accounts = initAccountsMock() - argsTransactionCoordinator.MiniBlockPool = tdp.MiniBlocks() + argsTransactionCoordinator.DataPool = tdp argsTransactionCoordinator.PreProcessors = createPreProcessorContainerWithDataPool(tdp, FeeHandlerMock(), argsTransactionCoordinator.Accounts) argsTransactionCoordinator.InterProcessors = &mock.InterimProcessorContainerMock{ KeysCalled: func() []block.Type { @@ -2447,15 +2627,15 @@ func TestTransactionCoordinator_PreprocessorsHasToBeOrderedRewardsAreLast(t *tes argsTransactionCoordinator := createMockTransactionCoordinatorArguments() argsTransactionCoordinator.ShardCoordinator = mock.NewMultiShardsCoordinatorMock(3) argsTransactionCoordinator.Accounts = initAccountsMock() - argsTransactionCoordinator.MiniBlockPool = dataPool.MiniBlocks() + argsTransactionCoordinator.DataPool = dataPool argsTransactionCoordinator.PreProcessors = createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), argsTransactionCoordinator.Accounts) argsTransactionCoordinator.InterProcessors = createInterimProcessorContainer() tc, err := NewTransactionCoordinator(argsTransactionCoordinator) assert.Nil(t, err) assert.NotNil(t, tc) - preProcLen := len(tc.keysTxPreProcs) - lastKey := tc.keysTxPreProcs[preProcLen-1] + preProcLen := len(tc.preProcExecution.keysTxPreProcs) + lastKey := tc.preProcExecution.keysTxPreProcs[preProcLen-1] assert.Equal(t, block.RewardsBlock, lastKey) } @@ -2528,7 +2708,7 @@ func TestTransactionCoordinator_IsMaxBlockSizeReachedShouldWork(t *testing.T) { } tc, _ := NewTransactionCoordinator(argsTransactionCoordinator) - tc.keysTxPreProcs = append(tc.keysTxPreProcs, block.TxBlock) + tc.preProcExecution.keysTxPreProcs = append(tc.preProcExecution.keysTxPreProcs, block.TxBlock) body := &block.Body{ MiniBlocks: make([]*block.MiniBlock, 0), @@ -2547,7 +2727,7 @@ func TestTransactionCoordinator_IsMaxBlockSizeReachedShouldWork(t *testing.T) { body.MiniBlocks = append(body.MiniBlocks, mb1) body.MiniBlocks = append(body.MiniBlocks, mb2) - tc.txPreProcessors[block.TxBlock] = &mock.PreProcessorMock{ + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ GetAllCurrentUsedTxsCalled: func() map[string]data.TransactionHandler { allTxs := make(map[string]data.TransactionHandler) allTxs["txHash2"] = &transaction.Transaction{ @@ -2558,7 +2738,7 @@ func TestTransactionCoordinator_IsMaxBlockSizeReachedShouldWork(t *testing.T) { } assert.False(t, tc.isMaxBlockSizeReached(body)) - tc.txPreProcessors[block.TxBlock] = &mock.PreProcessorMock{ + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ GetAllCurrentUsedTxsCalled: func() map[string]data.TransactionHandler { allTxs := make(map[string]data.TransactionHandler) allTxs["txHash2"] = &transaction.Transaction{ @@ -2631,37 +2811,14 @@ func TestTransactionCoordinator_GetNumOfCrossShardSpecialTxsShouldWork(t *testin func TestTransactionCoordinator_VerifyCreatedMiniBlocksShouldReturnWhenEpochIsNotEnabled(t *testing.T) { t.Parallel() - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &testscommon.GasHandlerStub{}, - FeeHandler: &mock.FeeAccumulatorStub{}, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{}, - TxTypeHandler: &testscommon.TxTypeHandlerMock{}, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{ - GetActivationEpochCalled: func(flag core.EnableEpochFlag) uint32 { - if flag == common.BlockGasAndFeesReCheckFlag { - return 1 - } - return 0 - }, + txCoordinatorArgs := createDefaultTxCoordinatorArgs() + txCoordinatorArgs.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + GetActivationEpochCalled: func(flag core.EnableEpochFlag) uint32 { + if flag == common.BlockGasAndFeesReCheckFlag { + return 1 + } + return 0 }, - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, } tc, err := NewTransactionCoordinator(txCoordinatorArgs) assert.Nil(t, err) @@ -2678,47 +2835,24 @@ func TestTransactionCoordinator_VerifyCreatedMiniBlocksShouldErrMaxGasLimitPerMi t.Parallel() maxGasLimitPerBlock := uint64(1500000000) - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &testscommon.GasHandlerStub{}, - FeeHandler: &mock.FeeAccumulatorStub{}, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{ - ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { - return maxGasLimitPerBlock + 1 - }, - MaxGasLimitPerBlockCalled: func(_ uint32) uint64 { - return maxGasLimitPerBlock - }, - MaxGasLimitPerMiniBlockCalled: func(shardID uint32) uint64 { - return maxGasLimitPerBlock - }, + txCoordinatorArgs := createDefaultTxCoordinatorArgs() + txCoordinatorArgs.EconomicsFee = &economicsmocks.EconomicsHandlerMock{ + ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { + return maxGasLimitPerBlock + 1 + }, + MaxGasLimitPerBlockCalled: func(_ uint32) uint64 { + return maxGasLimitPerBlock + }, + MaxGasLimitPerMiniBlockCalled: func(shardID uint32) uint64 { + return maxGasLimitPerBlock }, - TxTypeHandler: &testscommon.TxTypeHandlerMock{}, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, } tc, err := NewTransactionCoordinator(txCoordinatorArgs) assert.Nil(t, err) assert.NotNil(t, tc) - tc.txPreProcessors[block.TxBlock] = &mock.PreProcessorMock{ + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ GetAllCurrentUsedTxsCalled: func() map[string]data.TransactionHandler { allTxs := make(map[string]data.TransactionHandler) allTxs[string(txHash)] = &transaction.Transaction{} @@ -2746,53 +2880,30 @@ func TestTransactionCoordinator_VerifyCreatedMiniBlocksShouldErrMaxAccumulatedFe t.Parallel() maxGasLimitPerBlock := uint64(1500000000) - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &testscommon.GasHandlerStub{}, - FeeHandler: &mock.FeeAccumulatorStub{}, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{ - ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { - return maxGasLimitPerBlock - }, - MaxGasLimitPerBlockCalled: func(_ uint32) uint64 { - return maxGasLimitPerBlock - }, - MaxGasLimitPerMiniBlockForSafeCrossShardCalled: func() uint64 { - return maxGasLimitPerBlock - }, - MaxGasLimitPerTxCalled: func() uint64 { - return maxGasLimitPerBlock - }, - DeveloperPercentageCalled: func() float64 { - return 0.1 - }, + txCoordinatorArgs := createDefaultTxCoordinatorArgs() + txCoordinatorArgs.EconomicsFee = &economicsmocks.EconomicsHandlerMock{ + ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { + return maxGasLimitPerBlock + }, + MaxGasLimitPerBlockCalled: func(_ uint32) uint64 { + return maxGasLimitPerBlock + }, + MaxGasLimitPerMiniBlockForSafeCrossShardCalled: func() uint64 { + return maxGasLimitPerBlock + }, + MaxGasLimitPerTxCalled: func() uint64 { + return maxGasLimitPerBlock + }, + DeveloperPercentageCalled: func() float64 { + return 0.1 }, - TxTypeHandler: &testscommon.TxTypeHandlerMock{}, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, } tc, err := NewTransactionCoordinator(txCoordinatorArgs) assert.Nil(t, err) assert.NotNil(t, tc) - tc.txPreProcessors[block.TxBlock] = &mock.PreProcessorMock{ + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ GetAllCurrentUsedTxsCalled: func() map[string]data.TransactionHandler { allTxs := make(map[string]data.TransactionHandler) allTxs[string(txHash)] = &transaction.Transaction{ @@ -2825,53 +2936,30 @@ func TestTransactionCoordinator_VerifyCreatedMiniBlocksShouldErrMaxDeveloperFees t.Parallel() maxGasLimitPerBlock := uint64(1500000000) - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &testscommon.GasHandlerStub{}, - FeeHandler: &mock.FeeAccumulatorStub{}, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{ - ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { - return maxGasLimitPerBlock - }, - MaxGasLimitPerBlockCalled: func(_ uint32) uint64 { - return maxGasLimitPerBlock - }, - MaxGasLimitPerMiniBlockForSafeCrossShardCalled: func() uint64 { - return maxGasLimitPerBlock - }, - MaxGasLimitPerTxCalled: func() uint64 { - return maxGasLimitPerBlock - }, - DeveloperPercentageCalled: func() float64 { - return 0.1 - }, + txCoordinatorArgs := createDefaultTxCoordinatorArgs() + txCoordinatorArgs.EconomicsFee = &economicsmocks.EconomicsHandlerMock{ + ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { + return maxGasLimitPerBlock + }, + MaxGasLimitPerBlockCalled: func(_ uint32) uint64 { + return maxGasLimitPerBlock + }, + MaxGasLimitPerMiniBlockForSafeCrossShardCalled: func() uint64 { + return maxGasLimitPerBlock + }, + MaxGasLimitPerTxCalled: func() uint64 { + return maxGasLimitPerBlock + }, + DeveloperPercentageCalled: func() float64 { + return 0.1 }, - TxTypeHandler: &testscommon.TxTypeHandlerMock{}, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, } tc, err := NewTransactionCoordinator(txCoordinatorArgs) assert.Nil(t, err) assert.NotNil(t, tc) - tc.txPreProcessors[block.TxBlock] = &mock.PreProcessorMock{ + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ GetAllCurrentUsedTxsCalled: func() map[string]data.TransactionHandler { allTxs := make(map[string]data.TransactionHandler) allTxs[string(txHash)] = &transaction.Transaction{ @@ -2904,53 +2992,30 @@ func TestTransactionCoordinator_VerifyCreatedMiniBlocksShouldWork(t *testing.T) t.Parallel() maxGasLimitPerBlock := uint64(1500000000) - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &testscommon.GasHandlerStub{}, - FeeHandler: &mock.FeeAccumulatorStub{}, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{ - ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { - return maxGasLimitPerBlock - }, - MaxGasLimitPerBlockCalled: func(_ uint32) uint64 { - return maxGasLimitPerBlock - }, - MaxGasLimitPerMiniBlockForSafeCrossShardCalled: func() uint64 { - return maxGasLimitPerBlock - }, - MaxGasLimitPerTxCalled: func() uint64 { - return maxGasLimitPerBlock - }, - DeveloperPercentageCalled: func() float64 { - return 0.1 - }, + txCoordinatorArgs := createDefaultTxCoordinatorArgs() + txCoordinatorArgs.EconomicsFee = &economicsmocks.EconomicsHandlerMock{ + ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { + return maxGasLimitPerBlock + }, + MaxGasLimitPerBlockCalled: func(_ uint32) uint64 { + return maxGasLimitPerBlock + }, + MaxGasLimitPerMiniBlockForSafeCrossShardCalled: func() uint64 { + return maxGasLimitPerBlock + }, + MaxGasLimitPerTxCalled: func() uint64 { + return maxGasLimitPerBlock + }, + DeveloperPercentageCalled: func() float64 { + return 0.1 }, - TxTypeHandler: &testscommon.TxTypeHandlerMock{}, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, } tc, err := NewTransactionCoordinator(txCoordinatorArgs) assert.Nil(t, err) assert.NotNil(t, tc) - tc.txPreProcessors[block.TxBlock] = &mock.PreProcessorMock{ + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ GetAllCurrentUsedTxsCalled: func() map[string]data.TransactionHandler { allTxs := make(map[string]data.TransactionHandler) allTxs[string(txHash)] = &transaction.Transaction{ @@ -2982,31 +3047,7 @@ func TestTransactionCoordinator_VerifyCreatedMiniBlocksShouldWork(t *testing.T) func TestTransactionCoordinator_GetAllTransactionsShouldWork(t *testing.T) { t.Parallel() - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &testscommon.GasHandlerStub{}, - FeeHandler: &mock.FeeAccumulatorStub{}, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{}, - TxTypeHandler: &testscommon.TxTypeHandlerMock{}, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, - } + txCoordinatorArgs := createDefaultTxCoordinatorArgs() tc, err := NewTransactionCoordinator(txCoordinatorArgs) assert.Nil(t, err) assert.NotNil(t, tc) @@ -3019,7 +3060,7 @@ func TestTransactionCoordinator_GetAllTransactionsShouldWork(t *testing.T) { txHash2 := "hash2" txHash3 := "hash3" - tc.txPreProcessors[block.TxBlock] = &mock.PreProcessorMock{ + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ GetAllCurrentUsedTxsCalled: func() map[string]data.TransactionHandler { allTxs := make(map[string]data.TransactionHandler) allTxs[txHash1] = tx1 @@ -3057,40 +3098,17 @@ func TestTransactionCoordinator_VerifyGasLimitShouldErrMaxGasLimitPerMiniBlockIn tx2GasLimit := uint64(200000000) tx3GasLimit := uint64(300000001) - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &testscommon.GasHandlerStub{}, - FeeHandler: &mock.FeeAccumulatorStub{}, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{ - MaxGasLimitPerBlockCalled: func(_ uint32) uint64 { - return tx1GasLimit + tx2GasLimit + tx3GasLimit - 1 - }, - MaxGasLimitPerMiniBlockCalled: func(shardID uint32) uint64 { - return tx1GasLimit + tx2GasLimit + tx3GasLimit - 1 - }, - ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { - return tx.GetGasLimit() - }, + txCoordinatorArgs := createDefaultTxCoordinatorArgs() + txCoordinatorArgs.EconomicsFee = &economicsmocks.EconomicsHandlerMock{ + MaxGasLimitPerBlockCalled: func(_ uint32) uint64 { + return tx1GasLimit + tx2GasLimit + tx3GasLimit - 1 + }, + MaxGasLimitPerMiniBlockCalled: func(shardID uint32) uint64 { + return tx1GasLimit + tx2GasLimit + tx3GasLimit - 1 + }, + ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { + return tx.GetGasLimit() }, - TxTypeHandler: &testscommon.TxTypeHandlerMock{}, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, } tc, err := NewTransactionCoordinator(txCoordinatorArgs) assert.Nil(t, err) @@ -3104,7 +3122,7 @@ func TestTransactionCoordinator_VerifyGasLimitShouldErrMaxGasLimitPerMiniBlockIn txHash2 := "hash2" txHash3 := "hash3" - tc.txPreProcessors[block.TxBlock] = &mock.PreProcessorMock{ + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ GetAllCurrentUsedTxsCalled: func() map[string]data.TransactionHandler { allTxs := make(map[string]data.TransactionHandler) allTxs[txHash1] = tx1 @@ -3152,40 +3170,17 @@ func TestTransactionCoordinator_VerifyGasLimitShouldWork(t *testing.T) { tx2GasLimit := uint64(200) tx3GasLimit := uint64(300) - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &testscommon.GasHandlerStub{}, - FeeHandler: &mock.FeeAccumulatorStub{}, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{ - MaxGasLimitPerBlockCalled: func(_ uint32) uint64 { - return tx1GasLimit + tx2GasLimit + tx3GasLimit - }, - MaxGasLimitPerMiniBlockForSafeCrossShardCalled: func() uint64 { - return tx1GasLimit + tx2GasLimit + tx3GasLimit - }, - ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { - return tx.GetGasLimit() - }, + txCoordinatorArgs := createDefaultTxCoordinatorArgs() + txCoordinatorArgs.EconomicsFee = &economicsmocks.EconomicsHandlerMock{ + MaxGasLimitPerBlockCalled: func(_ uint32) uint64 { + return tx1GasLimit + tx2GasLimit + tx3GasLimit + }, + MaxGasLimitPerMiniBlockForSafeCrossShardCalled: func() uint64 { + return tx1GasLimit + tx2GasLimit + tx3GasLimit + }, + ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { + return tx.GetGasLimit() }, - TxTypeHandler: &testscommon.TxTypeHandlerMock{}, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, } tc, err := NewTransactionCoordinator(txCoordinatorArgs) assert.Nil(t, err) @@ -3199,7 +3194,7 @@ func TestTransactionCoordinator_VerifyGasLimitShouldWork(t *testing.T) { txHash2 := "hash2" txHash3 := "hash3" - tc.txPreProcessors[block.TxBlock] = &mock.PreProcessorMock{ + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ GetAllCurrentUsedTxsCalled: func() map[string]data.TransactionHandler { allTxs := make(map[string]data.TransactionHandler) allTxs[txHash1] = tx1 @@ -3243,31 +3238,7 @@ func TestTransactionCoordinator_VerifyGasLimitShouldWork(t *testing.T) { func TestTransactionCoordinator_CheckGasProvidedByMiniBlockInReceiverShardShouldErrMissingTransaction(t *testing.T) { t.Parallel() - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &testscommon.GasHandlerStub{}, - FeeHandler: &mock.FeeAccumulatorStub{}, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{}, - TxTypeHandler: &testscommon.TxTypeHandlerMock{}, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, - } + txCoordinatorArgs := createDefaultTxCoordinatorArgs() tc, err := NewTransactionCoordinator(txCoordinatorArgs) assert.Nil(t, err) assert.NotNil(t, tc) @@ -3287,38 +3258,16 @@ func TestTransactionCoordinator_CheckGasProvidedByMiniBlockInReceiverShardShould tx1GasLimit := uint64(100) - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &testscommon.GasHandlerStub{}, - FeeHandler: &mock.FeeAccumulatorStub{}, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{ - ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { - return tx.GetGasLimit() + 1 - }, + txCoordinatorArgs := createDefaultTxCoordinatorArgs() + txCoordinatorArgs.EconomicsFee = &economicsmocks.EconomicsHandlerMock{ + ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { + return tx.GetGasLimit() + 1 }, - TxTypeHandler: &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { - return process.MoveBalance, process.SCInvoking, false - }, + } + txCoordinatorArgs.TxTypeHandler = &testscommon.TxTypeHandlerMock{ + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.MoveBalance, process.SCInvoking, false }, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, } tc, err := NewTransactionCoordinator(txCoordinatorArgs) assert.Nil(t, err) @@ -3346,39 +3295,18 @@ func TestTransactionCoordinator_CheckGasProvidedByMiniBlockInReceiverShardShould tx1GasLimit := uint64(math.MaxUint64) tx2GasLimit := uint64(1) - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &testscommon.GasHandlerStub{}, - FeeHandler: &mock.FeeAccumulatorStub{}, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{ - ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { - return 0 - }, + txCoordinatorArgs := createDefaultTxCoordinatorArgs() + txCoordinatorArgs.EconomicsFee = &economicsmocks.EconomicsHandlerMock{ + ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { + return 0 }, - TxTypeHandler: &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { - return process.MoveBalance, process.SCInvoking, false - }, + } + txCoordinatorArgs.TxTypeHandler = &testscommon.TxTypeHandlerMock{ + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.MoveBalance, process.SCInvoking, false }, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, } + tc, err := NewTransactionCoordinator(txCoordinatorArgs) assert.Nil(t, err) assert.NotNil(t, tc) @@ -3410,40 +3338,17 @@ func TestTransactionCoordinator_CheckGasProvidedByMiniBlockInReceiverShardShould tx2GasLimit := uint64(200000000) tx3GasLimit := uint64(300000001) - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &testscommon.GasHandlerStub{}, - FeeHandler: &mock.FeeAccumulatorStub{}, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{ - MaxGasLimitPerBlockCalled: func(_ uint32) uint64 { - return tx1GasLimit + tx2GasLimit + tx3GasLimit - 1 - }, - MaxGasLimitPerMiniBlockCalled: func(shardID uint32) uint64 { - return tx1GasLimit + tx2GasLimit + tx3GasLimit - 1 - }, - ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { - return tx.GetGasLimit() - }, + txCoordinatorArgs := createDefaultTxCoordinatorArgs() + txCoordinatorArgs.EconomicsFee = &economicsmocks.EconomicsHandlerMock{ + MaxGasLimitPerBlockCalled: func(_ uint32) uint64 { + return tx1GasLimit + tx2GasLimit + tx3GasLimit - 1 + }, + MaxGasLimitPerMiniBlockCalled: func(shardID uint32) uint64 { + return tx1GasLimit + tx2GasLimit + tx3GasLimit - 1 + }, + ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { + return tx.GetGasLimit() }, - TxTypeHandler: &testscommon.TxTypeHandlerMock{}, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, } tc, err := NewTransactionCoordinator(txCoordinatorArgs) assert.Nil(t, err) @@ -3479,40 +3384,17 @@ func TestTransactionCoordinator_CheckGasProvidedByMiniBlockInReceiverShardShould tx2GasLimit := uint64(200) tx3GasLimit := uint64(300) - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &testscommon.GasHandlerStub{}, - FeeHandler: &mock.FeeAccumulatorStub{}, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{ - MaxGasLimitPerBlockCalled: func(_ uint32) uint64 { - return tx1GasLimit + tx2GasLimit + tx3GasLimit - }, - MaxGasLimitPerMiniBlockForSafeCrossShardCalled: func() uint64 { - return tx1GasLimit + tx2GasLimit + tx3GasLimit - }, - ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { - return tx.GetGasLimit() - }, + txCoordinatorArgs := createDefaultTxCoordinatorArgs() + txCoordinatorArgs.EconomicsFee = &economicsmocks.EconomicsHandlerMock{ + MaxGasLimitPerBlockCalled: func(_ uint32) uint64 { + return tx1GasLimit + tx2GasLimit + tx3GasLimit + }, + MaxGasLimitPerMiniBlockForSafeCrossShardCalled: func() uint64 { + return tx1GasLimit + tx2GasLimit + tx3GasLimit + }, + ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { + return tx.GetGasLimit() }, - TxTypeHandler: &testscommon.TxTypeHandlerMock{}, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, } tc, err := NewTransactionCoordinator(txCoordinatorArgs) @@ -3545,32 +3427,7 @@ func TestTransactionCoordinator_CheckGasProvidedByMiniBlockInReceiverShardShould func TestTransactionCoordinator_VerifyFeesShouldErrMissingTransaction(t *testing.T) { t.Parallel() - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &testscommon.GasHandlerStub{}, - FeeHandler: &mock.FeeAccumulatorStub{}, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{}, - TxTypeHandler: &testscommon.TxTypeHandlerMock{}, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, - } - + txCoordinatorArgs := createDefaultTxCoordinatorArgs() tc, err := NewTransactionCoordinator(txCoordinatorArgs) assert.Nil(t, err) assert.NotNil(t, tc) @@ -3602,34 +3459,11 @@ func TestTransactionCoordinator_VerifyFeesShouldErrMaxAccumulatedFeesExceeded(t tx1GasLimit := uint64(100) - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &testscommon.GasHandlerStub{}, - FeeHandler: &mock.FeeAccumulatorStub{}, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{ - DeveloperPercentageCalled: func() float64 { - return 0.1 - }, + txCoordinatorArgs := createDefaultTxCoordinatorArgs() + txCoordinatorArgs.EconomicsFee = &economicsmocks.EconomicsHandlerMock{ + DeveloperPercentageCalled: func() float64 { + return 0.1 }, - TxTypeHandler: &testscommon.TxTypeHandlerMock{}, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, } tc, err := NewTransactionCoordinator(txCoordinatorArgs) @@ -3673,34 +3507,11 @@ func TestTransactionCoordinator_VerifyFeesShouldErrMaxDeveloperFeesExceeded(t *t tx1GasLimit := uint64(100) - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &testscommon.GasHandlerStub{}, - FeeHandler: &mock.FeeAccumulatorStub{}, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{ - DeveloperPercentageCalled: func() float64 { - return 0.1 - }, + txCoordinatorArgs := createDefaultTxCoordinatorArgs() + txCoordinatorArgs.EconomicsFee = &economicsmocks.EconomicsHandlerMock{ + DeveloperPercentageCalled: func() float64 { + return 0.1 }, - TxTypeHandler: &testscommon.TxTypeHandlerMock{}, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, } tc, err := NewTransactionCoordinator(txCoordinatorArgs) assert.Nil(t, err) @@ -3743,43 +3554,19 @@ func TestTransactionCoordinator_VerifyFeesShouldErrMaxAccumulatedFeesExceededWhe tx1GasLimit := uint64(100) - enableEpochsHandlerStub := enableEpochsHandlerMock.NewEnableEpochsHandlerStub() - - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &testscommon.GasHandlerStub{}, - FeeHandler: &mock.FeeAccumulatorStub{}, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{ - DeveloperPercentageCalled: func() float64 { - return 0.1 - }, + txCoordinatorArgs := createDefaultTxCoordinatorArgs() + txCoordinatorArgs.EconomicsFee = &economicsmocks.EconomicsHandlerMock{ + DeveloperPercentageCalled: func() float64 { + return 0.1 }, - TxTypeHandler: &testscommon.TxTypeHandlerMock{}, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerStub, - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{ - GetScheduledGasAndFeesCalled: func() scheduled.GasAndFees { - return scheduled.GasAndFees{ - AccumulatedFees: big.NewInt(1), - DeveloperFees: big.NewInt(0), - } - }, + } + txCoordinatorArgs.ScheduledTxsExecutionHandler = &testscommon.ScheduledTxsExecutionStub{ + GetScheduledGasAndFeesCalled: func() scheduled.GasAndFees { + return scheduled.GasAndFees{ + AccumulatedFees: big.NewInt(1), + DeveloperFees: big.NewInt(0), + } }, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, } tc, err := NewTransactionCoordinator(txCoordinatorArgs) assert.Nil(t, err) @@ -3819,7 +3606,8 @@ func TestTransactionCoordinator_VerifyFeesShouldErrMaxAccumulatedFeesExceededWhe err = tc.verifyFees(header, body, mapMiniBlockTypeAllTxs) assert.Equal(t, process.ErrMaxAccumulatedFeesExceeded, err) - enableEpochsHandlerStub.AddActiveFlags(common.ScheduledMiniBlocksFlag, common.MiniBlockPartialExecutionFlag) + epochsEnableStub := txCoordinatorArgs.EnableEpochsHandler.(*enableEpochsHandlerMock.EnableEpochsHandlerStub) + epochsEnableStub.AddActiveFlags(common.ScheduledMiniBlocksFlag, common.MiniBlockPartialExecutionFlag) err = tc.verifyFees(header, body, mapMiniBlockTypeAllTxs) assert.Nil(t, err) @@ -3830,43 +3618,22 @@ func TestTransactionCoordinator_VerifyFeesShouldErrMaxDeveloperFeesExceededWhenS tx1GasLimit := uint64(100) - enableEpochsHandlerStub := enableEpochsHandlerMock.NewEnableEpochsHandlerStub() - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &testscommon.GasHandlerStub{}, - FeeHandler: &mock.FeeAccumulatorStub{}, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{ - DeveloperPercentageCalled: func() float64 { - return 0.1 - }, + txCoordinatorArgs := createDefaultTxCoordinatorArgs() + txCoordinatorArgs.EconomicsFee = &economicsmocks.EconomicsHandlerMock{ + DeveloperPercentageCalled: func() float64 { + return 0.1 }, - TxTypeHandler: &testscommon.TxTypeHandlerMock{}, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerStub, - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{ - GetScheduledGasAndFeesCalled: func() scheduled.GasAndFees { - return scheduled.GasAndFees{ - AccumulatedFees: big.NewInt(0), - DeveloperFees: big.NewInt(1), - } - }, + } + + txCoordinatorArgs.ScheduledTxsExecutionHandler = &testscommon.ScheduledTxsExecutionStub{ + GetScheduledGasAndFeesCalled: func() scheduled.GasAndFees { + return scheduled.GasAndFees{ + AccumulatedFees: big.NewInt(0), + DeveloperFees: big.NewInt(1), + } }, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, } + tc, err := NewTransactionCoordinator(txCoordinatorArgs) assert.Nil(t, err) assert.NotNil(t, tc) @@ -3905,7 +3672,8 @@ func TestTransactionCoordinator_VerifyFeesShouldErrMaxDeveloperFeesExceededWhenS err = tc.verifyFees(header, body, mapMiniBlockTypeAllTxs) assert.Equal(t, process.ErrMaxDeveloperFeesExceeded, err) - enableEpochsHandlerStub.AddActiveFlags(common.ScheduledMiniBlocksFlag, common.MiniBlockPartialExecutionFlag) + epochsEnableStub := txCoordinatorArgs.EnableEpochsHandler.(*enableEpochsHandlerMock.EnableEpochsHandlerStub) + epochsEnableStub.AddActiveFlags(common.ScheduledMiniBlocksFlag, common.MiniBlockPartialExecutionFlag) err = tc.verifyFees(header, body, mapMiniBlockTypeAllTxs) assert.Nil(t, err) @@ -3915,44 +3683,22 @@ func TestTransactionCoordinator_VerifyFeesShouldWork(t *testing.T) { t.Parallel() tx1GasLimit := uint64(100) - - enableEpochsHandlerStub := enableEpochsHandlerMock.NewEnableEpochsHandlerStub() - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &testscommon.GasHandlerStub{}, - FeeHandler: &mock.FeeAccumulatorStub{}, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{ - DeveloperPercentageCalled: func() float64 { - return 0.1 - }, + txCoordinatorArgs := createDefaultTxCoordinatorArgs() + txCoordinatorArgs.EconomicsFee = &economicsmocks.EconomicsHandlerMock{ + DeveloperPercentageCalled: func() float64 { + return 0.1 }, - TxTypeHandler: &testscommon.TxTypeHandlerMock{}, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerStub, - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{ - GetScheduledGasAndFeesCalled: func() scheduled.GasAndFees { - return scheduled.GasAndFees{ - AccumulatedFees: big.NewInt(1), - DeveloperFees: big.NewInt(1), - } - }, + } + + txCoordinatorArgs.ScheduledTxsExecutionHandler = &testscommon.ScheduledTxsExecutionStub{ + GetScheduledGasAndFeesCalled: func() scheduled.GasAndFees { + return scheduled.GasAndFees{ + AccumulatedFees: big.NewInt(1), + DeveloperFees: big.NewInt(1), + } }, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, } + tc, err := NewTransactionCoordinator(txCoordinatorArgs) assert.Nil(t, err) assert.NotNil(t, tc) @@ -3988,7 +3734,8 @@ func TestTransactionCoordinator_VerifyFeesShouldWork(t *testing.T) { err = tc.verifyFees(header, body, mapMiniBlockTypeAllTxs) assert.Nil(t, err) - enableEpochsHandlerStub.AddActiveFlags(common.ScheduledMiniBlocksFlag, common.MiniBlockPartialExecutionFlag) + epochsEnableStub := txCoordinatorArgs.EnableEpochsHandler.(*enableEpochsHandlerMock.EnableEpochsHandlerStub) + epochsEnableStub.AddActiveFlags(common.ScheduledMiniBlocksFlag, common.MiniBlockPartialExecutionFlag) header = &block.Header{ AccumulatedFees: big.NewInt(101), @@ -4006,31 +3753,7 @@ func TestTransactionCoordinator_VerifyFeesShouldWork(t *testing.T) { func TestTransactionCoordinator_GetMaxAccumulatedAndDeveloperFeesShouldErr(t *testing.T) { t.Parallel() - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &testscommon.GasHandlerStub{}, - FeeHandler: &mock.FeeAccumulatorStub{}, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{}, - TxTypeHandler: &testscommon.TxTypeHandlerMock{}, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, - } + txCoordinatorArgs := createDefaultTxCoordinatorArgs() tc, err := NewTransactionCoordinator(txCoordinatorArgs) assert.Nil(t, err) assert.NotNil(t, tc) @@ -4060,35 +3783,13 @@ func TestTransactionCoordinator_GetMaxAccumulatedAndDeveloperFeesShouldWork(t *t tx2GasLimit := uint64(200) tx3GasLimit := uint64(300) - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &testscommon.GasHandlerStub{}, - FeeHandler: &mock.FeeAccumulatorStub{}, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{ - DeveloperPercentageCalled: func() float64 { - return 0.1 - }, + txCoordinatorArgs := createDefaultTxCoordinatorArgs() + txCoordinatorArgs.EconomicsFee = &economicsmocks.EconomicsHandlerMock{ + DeveloperPercentageCalled: func() float64 { + return 0.1 }, - TxTypeHandler: &testscommon.TxTypeHandlerMock{}, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, } + tc, err := NewTransactionCoordinator(txCoordinatorArgs) assert.Nil(t, err) assert.NotNil(t, tc) @@ -4128,40 +3829,7 @@ func TestTransactionCoordinator_RevertIfNeededShouldWork(t *testing.T) { restoreGasSinceLastResetCalled := false numTxsFeesReverted := 0 - dataPool := initDataPool(txHash) - accounts := initAccountsMock() - - txCoordinatorArgs := ArgTransactionCoordinator{ - Hasher: &hashingMocks.HasherMock{}, - Marshalizer: &mock.MarshalizerMock{}, - ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), - Accounts: accounts, - MiniBlockPool: dataPool.MiniBlocks(), - RequestHandler: &testscommon.RequestHandlerStub{}, - PreProcessors: createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts), - InterProcessors: createInterimProcessorContainer(), - GasHandler: &mock.GasHandlerMock{ - RestoreGasSinceLastResetCalled: func(key []byte) { - restoreGasSinceLastResetCalled = true - }, - }, - FeeHandler: &mock.FeeAccumulatorStub{ - RevertFeesCalled: func(txHashes [][]byte) { - numTxsFeesReverted += len(txHashes) - }, - }, - BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, - BalanceComputation: &testscommon.BalanceComputationStub{}, - EconomicsFee: &economicsmocks.EconomicsHandlerMock{}, - TxTypeHandler: &testscommon.TxTypeHandlerMock{}, - TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, - EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), - ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, - DoubleTransactionsDetector: &testscommon.PanicDoubleTransactionsDetector{}, - ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, - TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, - } - + txCoordinatorArgs := createDefaultTxCoordinatorArgs() txHashes := make([][]byte, 0) txCoordinatorArgs.ShardCoordinator = &mock.CoordinatorStub{ @@ -4169,6 +3837,17 @@ func TestTransactionCoordinator_RevertIfNeededShouldWork(t *testing.T) { return 0 }, } + txCoordinatorArgs.GasHandler = &mock.GasHandlerMock{ + RestoreGasSinceLastResetCalled: func(key []byte) { + restoreGasSinceLastResetCalled = true + }, + } + txCoordinatorArgs.FeeHandler = &mock.FeeAccumulatorStub{ + RevertFeesCalled: func(txHashes [][]byte) { + numTxsFeesReverted += len(txHashes) + }, + } + tc, err := NewTransactionCoordinator(txCoordinatorArgs) require.Nil(t, err) @@ -4206,57 +3885,6 @@ func TestTransactionCoordinator_RevertIfNeededShouldWork(t *testing.T) { assert.Equal(t, len(txHashes), numTxsFeesReverted) } -func TestTransactionCoordinator_getFinalCrossMiniBlockInfos(t *testing.T) { - t.Parallel() - - hash1, hash2 := "hash1", "hash2" - - t.Run("scheduledMiniBlocks flag not set", func(t *testing.T) { - t.Parallel() - - tc, _ := NewTransactionCoordinator(createMockTransactionCoordinatorArguments()) - - var crossMiniBlockInfos []*data.MiniBlockInfo - - mbInfos := tc.getFinalCrossMiniBlockInfos(crossMiniBlockInfos, &block.Header{}) - assert.Equal(t, crossMiniBlockInfos, mbInfos) - }) - - t.Run("should work, miniblocks info found for final miniBlock header", func(t *testing.T) { - t.Parallel() - - args := createMockTransactionCoordinatorArguments() - enableEpochsHandlerStub := enableEpochsHandlerMock.NewEnableEpochsHandlerStub() - args.EnableEpochsHandler = enableEpochsHandlerStub - tc, _ := NewTransactionCoordinator(args) - enableEpochsHandlerStub.AddActiveFlags(common.ScheduledMiniBlocksFlag) - - mbInfo1 := &data.MiniBlockInfo{Hash: []byte(hash1)} - mbInfo2 := &data.MiniBlockInfo{Hash: []byte(hash2)} - crossMiniBlockInfos := []*data.MiniBlockInfo{mbInfo1, mbInfo2} - - mbh1 := block.MiniBlockHeader{Hash: []byte(hash1)} - mbhReserved1 := block.MiniBlockHeaderReserved{State: block.Proposed} - mbh1.Reserved, _ = mbhReserved1.Marshal() - - mbh2 := block.MiniBlockHeader{Hash: []byte(hash2)} - mbhReserved2 := block.MiniBlockHeaderReserved{State: block.Final} - mbh2.Reserved, _ = mbhReserved2.Marshal() - - header := &block.MetaBlock{ - MiniBlockHeaders: []block.MiniBlockHeader{ - mbh1, - mbh2, - }, - } - - expectedMbInfos := []*data.MiniBlockInfo{mbInfo2} - - mbInfos := tc.getFinalCrossMiniBlockInfos(crossMiniBlockInfos, header) - assert.Equal(t, expectedMbInfos, mbInfos) - }) -} - func TestTransactionCoordinator_AddIntermediateTransactions(t *testing.T) { t.Parallel() @@ -4401,13 +4029,13 @@ func TestTransactionCoordinator_AddTxsFromMiniBlocks(t *testing.T) { tc, _ := NewTransactionCoordinator(args) tc.keysInterimProcs = []block.Type{block.TxBlock} - tc.txPreProcessors[block.TxBlock] = &mock.PreProcessorMock{ + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ AddTxsFromMiniBlocksCalled: func(miniBlocks block.MiniBlockSlice) { require.Equal(t, miniBlocks, block.MiniBlockSlice{mb1}) }, } - tc.txPreProcessors[block.SmartContractResultBlock] = &mock.PreProcessorMock{ + tc.preProcExecution.txPreProcessors[block.SmartContractResultBlock] = &preprocMocks.PreProcessorMock{ AddTxsFromMiniBlocksCalled: func(miniBlocks block.MiniBlockSlice) { require.Equal(t, miniBlocks, block.MiniBlockSlice{mb2}) }, @@ -4451,8 +4079,8 @@ func TestTransactionCoordinator_AddTransactions(t *testing.T) { t.Run("valid preprocessor should add", func(t *testing.T) { tc, _ := NewTransactionCoordinator(args) addTransactionsCalled := &atomic.Flag{} - tc.keysTxPreProcs = append(tc.keysTxPreProcs, block.TxBlock) - tc.txPreProcessors[block.TxBlock] = &mock.PreProcessorMock{ + tc.preProcExecution.keysTxPreProcs = append(tc.preProcExecution.keysTxPreProcs, block.TxBlock) + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ AddTransactionsCalled: func(txHandlers []data.TransactionHandler) { require.Equal(t, txs, txHandlers) addTransactionsCalled.SetValue(true) @@ -4539,82 +4167,310 @@ func TestTransactionCoordinator_getIndexesOfLastTxProcessed(t *testing.T) { }) } -func TestTransactionCoordinator_requestMissingMiniBlocksAndTransactionsShouldWork(t *testing.T) { +func TestTransactionCoordinator_GetCreatedMiniBlocksFromMe(t *testing.T) { t.Parallel() - args := createMockTransactionCoordinatorArguments() - args.MiniBlockPool = &cache.CacherStub{ - PeekCalled: func(key []byte) (value interface{}, ok bool) { - if bytes.Equal(key, []byte("hash0")) || bytes.Equal(key, []byte("hash1")) || bytes.Equal(key, []byte("hash2")) { - if bytes.Equal(key, []byte("hash0")) { - return nil, true - } + txCoordinatorArgs := createDefaultTxCoordinatorArgs() + tc, err := NewTransactionCoordinator(txCoordinatorArgs) + assert.Nil(t, err) + assert.NotNil(t, tc) - if bytes.Equal(key, []byte("hash1")) { - return &block.MiniBlock{ - Type: block.PeerBlock, - TxHashes: [][]byte{ - []byte("hash 1"), - []byte("hash 2"), - }, - }, true - } + mb1 := &block.MiniBlock{ + Type: block.TxBlock, + TxHashes: [][]byte{[]byte("tx1"), []byte("tx2")}, + ReceiverShardID: 0, + SenderShardID: 1, + } - if bytes.Equal(key, []byte("hash2")) { - return &block.MiniBlock{ - Type: block.TxBlock, - TxHashes: [][]byte{ - []byte("hash 3"), - []byte("hash 4"), - }, - }, true - } - } - return nil, false - }, + mb2 := &block.MiniBlock{ + Type: block.SmartContractResultBlock, + TxHashes: [][]byte{[]byte("tx3"), []byte("tx4")}, + ReceiverShardID: 1, + SenderShardID: 0, } - tc, _ := NewTransactionCoordinator(args) + mb3 := &block.MiniBlock{ + Type: block.RewardsBlock, + TxHashes: [][]byte{[]byte("tx5"), []byte("tx6")}, + ReceiverShardID: 1, + SenderShardID: 0, + } + + mb4 := &block.MiniBlock{ + Type: block.PeerBlock, + TxHashes: [][]byte{[]byte("tx7"), []byte("tx8")}, + ReceiverShardID: 1, + SenderShardID: 0, + } - numTxsRequested := 0 - tc.txPreProcessors[block.TxBlock] = &mock.PreProcessorMock{ - RequestTransactionsForMiniBlockCalled: func(miniBlock *block.MiniBlock) int { - numTxsRequested += len(miniBlock.TxHashes) - return len(miniBlock.TxHashes) + miniBlocks := block.MiniBlockSlice{mb1, mb2, mb3, mb4} + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + GetCreatedMiniBlocksFromMeCalled: func() block.MiniBlockSlice { + return block.MiniBlockSlice{mb1} }, } + tc.preProcExecution.txPreProcessors[block.SmartContractResultBlock] = &preprocMocks.PreProcessorMock{ + GetCreatedMiniBlocksFromMeCalled: func() block.MiniBlockSlice { + return block.MiniBlockSlice{mb2} + }, + } + tc.preProcExecution.txPreProcessors[block.RewardsBlock] = &preprocMocks.PreProcessorMock{ + GetCreatedMiniBlocksFromMeCalled: func() block.MiniBlockSlice { + return block.MiniBlockSlice{mb3} + }, + } + + tc.preProcExecution.txPreProcessors[block.PeerBlock] = &preprocMocks.PreProcessorMock{ + GetCreatedMiniBlocksFromMeCalled: func() block.MiniBlockSlice { + return block.MiniBlockSlice{mb4} + }, + } + + createdMBs := tc.GetCreatedMiniBlocksFromMe() + require.Equal(t, 4, len(createdMBs)) + for _, mb := range miniBlocks { + require.Contains(t, createdMBs, mb) + } +} + +func TestTransactionCoordinator_getUnExecutableTransactions(t *testing.T) { + t.Parallel() + + txCoordinatorArgs := createDefaultTxCoordinatorArgs() + tc, err := NewTransactionCoordinator(txCoordinatorArgs) + assert.Nil(t, err) + assert.NotNil(t, tc) - wg := sync.WaitGroup{} - wg.Add(3) - mapRequestedMiniBlocksPerShard := make(map[uint32]int) - mutMap := sync.RWMutex{} - tc.onRequestMiniBlocks = func(shardId uint32, mbHashes [][]byte) { - mutMap.Lock() - mapRequestedMiniBlocksPerShard[shardId] += len(mbHashes) - mutMap.Unlock() - wg.Done() + txHashes := []string{"tx1", "tx2", "tx3", "tx4"} + + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + GetUnExecutableTransactionsCalled: func() map[string]struct{} { + return map[string]struct{}{ + txHashes[0]: {}, + } + }, + } + tc.preProcExecution.txPreProcessors[block.SmartContractResultBlock] = &preprocMocks.PreProcessorMock{ + GetUnExecutableTransactionsCalled: func() map[string]struct{} { + return map[string]struct{}{ + txHashes[1]: {}, + } + }, + } + tc.preProcExecution.txPreProcessors[block.RewardsBlock] = &preprocMocks.PreProcessorMock{ + GetUnExecutableTransactionsCalled: func() map[string]struct{} { + return map[string]struct{}{ + txHashes[2]: {}, + } + }, + } + tc.preProcExecution.txPreProcessors[block.PeerBlock] = &preprocMocks.PreProcessorMock{ + GetUnExecutableTransactionsCalled: func() map[string]struct{} { + return map[string]struct{}{ + txHashes[3]: {}, + } + }, } - mbsInfo := []*data.MiniBlockInfo{ - {SenderShardID: 0}, - {SenderShardID: 1}, - {SenderShardID: 2}, - {SenderShardID: 0, Hash: []byte("hash0")}, - {SenderShardID: 1, Hash: []byte("hash1")}, - {SenderShardID: 2, Hash: []byte("hash2")}, - {SenderShardID: 0}, - {SenderShardID: 1}, - {SenderShardID: 0}, + unExecutableTxs := tc.GetUnExecutableTransactions() + require.Equal(t, 4, len(unExecutableTxs)) + for _, th := range txHashes { + require.Contains(t, unExecutableTxs, th) } +} + +func TestTransactionCoordinator_ProposedDirectSentTransactionsToBroadcast(t *testing.T) { + t.Parallel() + + t.Run("nil body should return empty map", func(t *testing.T) { + t.Parallel() + + txCoordinatorArgs := createMockTransactionCoordinatorArguments() + tc, err := NewTransactionCoordinator(txCoordinatorArgs) + require.Nil(t, err) + require.NotNil(t, tc) + + result := tc.ProposedDirectSentTransactionsToBroadcast(nil) + require.NotNil(t, result) + require.Equal(t, 0, len(result)) + }) + + t.Run("error getting cached intermediate txs should return empty map", func(t *testing.T) { + t.Parallel() + + txCoordinatorArgs := createMockTransactionCoordinatorArguments() + txCoordinatorArgs.DataPool = &dataRetrieverMock.PoolsHolderStub{ + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{ + PeekCalled: func(key []byte) (interface{}, bool) { + return nil, false + }, + } + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{} + }, + } + tc, err := NewTransactionCoordinator(txCoordinatorArgs) + require.Nil(t, err) + require.NotNil(t, tc) + + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + Type: block.TxBlock, + SenderShardID: 0, + ReceiverShardID: 0, + TxHashes: [][]byte{[]byte("txHash1")}, + }, + }, + } + + result := tc.ProposedDirectSentTransactionsToBroadcast(body) + require.NotNil(t, result) + require.Equal(t, 0, len(result)) + }) + + t.Run("cross-shard miniblocks should be skipped", func(t *testing.T) { + t.Parallel() + + txCoordinatorArgs := createMockTransactionCoordinatorArguments() + txCoordinatorArgs.ShardCoordinator = mock.NewMultiShardsCoordinatorMock(3) + + tc, err := NewTransactionCoordinator(txCoordinatorArgs) + require.Nil(t, err) + require.NotNil(t, tc) + + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + Type: block.TxBlock, + SenderShardID: 0, + ReceiverShardID: 1, // cross-shard + TxHashes: [][]byte{[]byte("txHash1")}, + }, + { + Type: block.TxBlock, + SenderShardID: 1, + ReceiverShardID: 0, // cross-shard + TxHashes: [][]byte{[]byte("txHash2")}, + }, + }, + } + + result := tc.ProposedDirectSentTransactionsToBroadcast(body) + require.NotNil(t, result) + require.Equal(t, 0, len(result)) + }) + + t.Run("mixed scenario with some txs in cache and some not", func(t *testing.T) { + t.Parallel() - tc.requestMissingMiniBlocksAndTransactions(mbsInfo) + tx1 := &transaction.Transaction{Nonce: 1, Value: big.NewInt(100)} + tx2 := &transaction.Transaction{Nonce: 2, Value: big.NewInt(200)} + txHash1 := []byte("txHash1") + txHash2 := []byte("txHash2") - wg.Wait() + directSentCache := &cache.CacherStub{ + GetCalled: func(key []byte) (interface{}, bool) { + // txHash1 is in cache, txHash2 is not + if bytes.Equal(key, txHash1) { + return struct{}{}, true + } + return nil, false + }, + } + + transactionsCache := &testscommon.ShardedDataStub{ + SearchFirstDataCalled: func(key []byte) (value interface{}, ok bool) { + switch string(key) { + case string(txHash1): + return tx1, true + case string(txHash2): + return tx2, true + default: + require.Fail(t, "should not happen") + } + + return nil, false + }, + } + txCoordinatorArgs := createMockTransactionCoordinatorArguments() + txCoordinatorArgs.ShardCoordinator = mock.NewMultiShardsCoordinatorMock(3) + txCoordinatorArgs.DataPool = &dataRetrieverMock.PoolsHolderStub{ + PostProcessTransactionsCalled: func() storage.Cacher { + return &cache.CacherStub{ + GetCalled: func(key []byte) (value interface{}, ok bool) { + return map[block.Type]map[string]data.TransactionHandler{}, true + }, + } + }, + DirectSentTransactionsCalled: func() storage.Cacher { + return directSentCache + }, + TransactionsCalled: func() dataRetriever.ShardedDataCacherNotifier { + return transactionsCache + }, + } + txCoordinatorArgs.Marshalizer = &marshallerMock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + return []byte("marshalledTx"), nil + }, + } + + tc, err := NewTransactionCoordinator(txCoordinatorArgs) + require.Nil(t, err) + require.NotNil(t, tc) + + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + Type: block.TxBlock, + SenderShardID: 0, + ReceiverShardID: 0, // intra-shard + TxHashes: [][]byte{txHash1, txHash2}, + }, + { + Type: block.TxBlock, + SenderShardID: 0, + ReceiverShardID: 1, // cross-shard + }, + }, + } + + result := tc.ProposedDirectSentTransactionsToBroadcast(body) + require.NotNil(t, result) + require.Equal(t, 1, len(result)) + txs, ok := result[factory.TransactionTopic+"_0"] + require.True(t, ok) + require.Equal(t, 1, len(txs)) + }) +} - mutMap.RLock() - assert.Equal(t, 3, mapRequestedMiniBlocksPerShard[0]) - assert.Equal(t, 2, mapRequestedMiniBlocksPerShard[1]) - assert.Equal(t, 1, mapRequestedMiniBlocksPerShard[2]) - assert.Equal(t, 2, numTxsRequested) - mutMap.RUnlock() +func createDefaultTxCoordinatorArgs() ArgTransactionCoordinator { + dataPool := initDataPool(txHash) + accounts := initAccountsMock() + preProcessors := createPreProcessorContainerWithDataPool(dataPool, FeeHandlerMock(), accounts) + shardCoordinator := mock.NewMultiShardsCoordinatorMock(3) + enableEpochsHandler := enableEpochsHandlerMock.NewEnableEpochsHandlerStub() + args := BlockDataRequestArgs{ + RequestHandler: &testscommon.RequestHandlerStub{}, + MiniBlockPool: dataPool.MiniBlocks(), + PreProcessors: preProcessors, + ShardCoordinator: shardCoordinator, + EnableEpochsHandler: enableEpochsHandler, + } + blockDataRequester, _ := NewBlockDataRequester(args) + + txCoordinatorArgs := createMockTransactionCoordinatorArguments() + + txCoordinatorArgs.ShardCoordinator = shardCoordinator + txCoordinatorArgs.Accounts = accounts + txCoordinatorArgs.DataPool = dataPool + txCoordinatorArgs.PreProcessors = preProcessors + txCoordinatorArgs.InterProcessors = createInterimProcessorContainer() + txCoordinatorArgs.EnableEpochsHandler = enableEpochsHandler + txCoordinatorArgs.BlockDataRequester = blockDataRequester + + return txCoordinatorArgs } diff --git a/process/dataValidators/txValidator.go b/process/dataValidators/txValidator.go index d043f207ac0..f5c826db14d 100644 --- a/process/dataValidators/txValidator.go +++ b/process/dataValidators/txValidator.go @@ -6,6 +6,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/sharding" @@ -25,6 +26,7 @@ type txValidator struct { whiteListHandler process.WhiteListHandler pubKeyConverter core.PubkeyConverter txVersionChecker process.TxVersionCheckerHandler + enableEpochsHandler core.EnableEpochsHandler maxNonceDeltaAllowed int } @@ -35,6 +37,7 @@ func NewTxValidator( whiteListHandler process.WhiteListHandler, pubKeyConverter core.PubkeyConverter, txVersionChecker process.TxVersionCheckerHandler, + enableEpochsHandler core.EnableEpochsHandler, maxNonceDeltaAllowed int, ) (*txValidator, error) { if check.IfNil(accounts) { @@ -52,6 +55,9 @@ func NewTxValidator( if check.IfNil(txVersionChecker) { return nil, process.ErrNilTransactionVersionChecker } + if check.IfNil(enableEpochsHandler) { + return nil, process.ErrNilEnableEpochsHandler + } return &txValidator{ accounts: accounts, @@ -60,6 +66,7 @@ func NewTxValidator( maxNonceDeltaAllowed: maxNonceDeltaAllowed, pubKeyConverter: pubKeyConverter, txVersionChecker: txVersionChecker, + enableEpochsHandler: enableEpochsHandler, }, nil } @@ -77,9 +84,11 @@ func (txv *txValidator) CheckTxValidity(interceptedTx process.InterceptedTransac } // for relayed v3, we allow sender accounts that do not exist + // only if the transaction is not guarded isRelayedV3 := common.IsRelayedTxV3(interceptedTx.Transaction()) hasValue := hasTxValue(interceptedTx) - shouldAllowMissingSenderAccount := isRelayedV3 && !hasValue + isGuardedTx := txv.isGuardedTxAfterSupernova(interceptedTx.Transaction()) + shouldAllowMissingSenderAccount := isRelayedV3 && !hasValue && !isGuardedTx accountHandler, err := txv.getSenderAccount(interceptedTx) if err != nil && !shouldAllowMissingSenderAccount { return err @@ -88,6 +97,19 @@ func (txv *txValidator) CheckTxValidity(interceptedTx process.InterceptedTransac return txv.checkAccount(interceptedTx, accountHandler) } +func (txv *txValidator) isGuardedTxAfterSupernova(tx data.TransactionHandler) bool { + if !txv.enableEpochsHandler.IsFlagEnabled(common.SupernovaFlag) { + return false + } + + txPtr, ok := tx.(*transaction.Transaction) + if !ok { + return false + } + + return txv.txVersionChecker.IsGuardedTransaction(txPtr) +} + func hasTxValue(interceptedTx process.InterceptedTransactionHandler) bool { txValue := interceptedTx.Transaction().GetValue() if check.IfNilReflect(txValue) { @@ -111,6 +133,10 @@ func (txv *txValidator) checkAccount( return err } + if common.IsRelayedTxV3(interceptedTx.Transaction()) && feePayerAccount.IsGuarded() { + return process.ErrGuardedRelayerNotAllowed + } + return txv.checkBalance(interceptedTx, feePayerAccount) } diff --git a/process/dataValidators/txValidator_test.go b/process/dataValidators/txValidator_test.go index 31fa230ec39..1f738549566 100644 --- a/process/dataValidators/txValidator_test.go +++ b/process/dataValidators/txValidator_test.go @@ -7,14 +7,17 @@ import ( "strconv" "testing" + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/dataValidators" "github.com/multiversx/mx-chain-go/process/mock" "github.com/multiversx/mx-chain-go/state/accounts" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" stateMock "github.com/multiversx/mx-chain-go/testscommon/state" "github.com/multiversx/mx-chain-go/testscommon/trie" vmcommon "github.com/multiversx/mx-chain-vm-common-go" @@ -86,6 +89,7 @@ func TestNewTxValidator_NilAccountsShouldErr(t *testing.T) { &testscommon.WhiteListHandlerStub{}, testscommon.NewPubkeyConverterMock(32), &testscommon.TxVersionCheckerStub{}, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, maxNonceDeltaAllowed, ) @@ -104,6 +108,7 @@ func TestNewTxValidator_NilShardCoordinatorShouldErr(t *testing.T) { &testscommon.WhiteListHandlerStub{}, testscommon.NewPubkeyConverterMock(32), &testscommon.TxVersionCheckerStub{}, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, maxNonceDeltaAllowed, ) @@ -123,6 +128,7 @@ func TestTxValidator_NewValidatorNilWhiteListHandlerShouldErr(t *testing.T) { nil, testscommon.NewPubkeyConverterMock(32), &testscommon.TxVersionCheckerStub{}, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, maxNonceDeltaAllowed, ) @@ -142,6 +148,7 @@ func TestNewTxValidator_NilPubkeyConverterShouldErr(t *testing.T) { &testscommon.WhiteListHandlerStub{}, nil, &testscommon.TxVersionCheckerStub{}, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, maxNonceDeltaAllowed, ) @@ -161,12 +168,32 @@ func TestNewTxValidator_NilTxVersionCheckerShouldErr(t *testing.T) { &testscommon.WhiteListHandlerStub{}, testscommon.NewPubkeyConverterMock(32), nil, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, maxNonceDeltaAllowed, ) assert.Nil(t, txValidator) assert.True(t, errors.Is(err, process.ErrNilTransactionVersionChecker)) } +func TestNewTxValidator_NilEnableEpochsHandlerShouldErr(t *testing.T) { + t.Parallel() + + adb := getAccAdapter(0, big.NewInt(0)) + shardCoordinator := createMockCoordinator("_", 0) + maxNonceDeltaAllowed := 100 + txValidator, err := dataValidators.NewTxValidator( + adb, + shardCoordinator, + &testscommon.WhiteListHandlerStub{}, + testscommon.NewPubkeyConverterMock(32), + &testscommon.TxVersionCheckerStub{}, + nil, + maxNonceDeltaAllowed, + ) + assert.Nil(t, txValidator) + assert.True(t, errors.Is(err, process.ErrNilEnableEpochsHandler)) +} + func TestNewTxValidator_ShouldWork(t *testing.T) { t.Parallel() @@ -179,6 +206,7 @@ func TestNewTxValidator_ShouldWork(t *testing.T) { &testscommon.WhiteListHandlerStub{}, testscommon.NewPubkeyConverterMock(32), &testscommon.TxVersionCheckerStub{}, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, maxNonceDeltaAllowed, ) @@ -202,6 +230,7 @@ func TestTxValidator_CheckTxValidityTxCrossShardShouldWork(t *testing.T) { &testscommon.WhiteListHandlerStub{}, testscommon.NewPubkeyConverterMock(32), &testscommon.TxVersionCheckerStub{}, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, maxNonceDeltaAllowed, ) assert.Nil(t, err) @@ -228,6 +257,7 @@ func TestTxValidator_CheckTxValidityAccountNonceIsGreaterThanTxNonceShouldReturn &testscommon.WhiteListHandlerStub{}, testscommon.NewPubkeyConverterMock(32), &testscommon.TxVersionCheckerStub{}, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, maxNonceDeltaAllowed, ) assert.Nil(t, err) @@ -255,6 +285,7 @@ func TestTxValidator_CheckTxValidityTxNonceIsTooHigh(t *testing.T) { &testscommon.WhiteListHandlerStub{}, testscommon.NewPubkeyConverterMock(32), &testscommon.TxVersionCheckerStub{}, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, maxNonceDeltaAllowed, ) assert.Nil(t, err) @@ -298,6 +329,7 @@ func TestTxValidator_CheckTxValidityAccountBalanceIsLessThanTxTotalValueShouldRe &testscommon.WhiteListHandlerStub{}, testscommon.NewPubkeyConverterMock(32), &testscommon.TxVersionCheckerStub{}, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, maxNonceDeltaAllowed, ) assert.Nil(t, err) @@ -344,6 +376,7 @@ func TestTxValidator_CheckTxValidityAccountBalanceIsLessThanTxTotalValueShouldRe &testscommon.WhiteListHandlerStub{}, testscommon.NewPubkeyConverterMock(32), &testscommon.TxVersionCheckerStub{}, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, maxNonceDeltaAllowed, ) assert.Nil(t, err) @@ -365,6 +398,181 @@ func TestTxValidator_CheckTxValidityAccountBalanceIsLessThanTxTotalValueShouldRe assert.NotNil(t, result) assert.True(t, errors.Is(result, process.ErrInsufficientFunds)) }) + t.Run("relayed tx v3 with guarded relayer should fail", func(t *testing.T) { + t.Parallel() + + txNonce := uint64(1) + fee := big.NewInt(1000) + + providedRelayerAddress := []byte("relayer") + providedSenderAddress := []byte("address") + adb := &stateMock.AccountsStub{} + adb.GetExistingAccountCalled = func(address []byte) (handler vmcommon.AccountHandler, e error) { + return &stateMock.UserAccountStub{ + IsGuardedCalled: func() bool { + return bytes.Equal(providedRelayerAddress, address) + }, + }, nil + } + + shardCoordinator := createMockCoordinator("_", 0) + maxNonceDeltaAllowed := 100 + txValidator, err := dataValidators.NewTxValidator( + adb, + shardCoordinator, + &testscommon.WhiteListHandlerStub{}, + testscommon.NewPubkeyConverterMock(32), + &testscommon.TxVersionCheckerStub{}, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + maxNonceDeltaAllowed, + ) + assert.Nil(t, err) + + currentShard := uint32(0) + txValidatorHandler := getInterceptedTxHandler(currentShard, currentShard, txNonce, providedSenderAddress, fee) + txValidatorHandlerStub, ok := txValidatorHandler.(*mock.InterceptedTxHandlerStub) + require.True(t, ok) + txValidatorHandlerStub.TransactionCalled = func() data.TransactionHandler { + return &transaction.Transaction{ + SndAddr: providedSenderAddress, + Signature: []byte("address sig"), + RelayerAddr: providedRelayerAddress, + RelayerSignature: []byte("relayer sig"), + Value: big.NewInt(0), + } + } + result := txValidator.CheckTxValidity(txValidatorHandler) + assert.NotNil(t, result) + assert.True(t, errors.Is(result, process.ErrGuardedRelayerNotAllowed)) + }) +} + +func TestTxValidator_CheckTxValidityRelayedV3(t *testing.T) { + t.Parallel() + + t.Run("missing sender should work without value", func(t *testing.T) { + t.Parallel() + + accountNonce := uint64(0) + txNonce := uint64(1) + fee := big.NewInt(1000) + accountBalance := big.NewInt(1000) + + providedRelayerAddress := []byte("relayer") + providedSenderAddress := []byte("address") + adb := &stateMock.AccountsStub{} + cnt := 0 + adb.GetExistingAccountCalled = func(address []byte) (handler vmcommon.AccountHandler, e error) { + cnt++ + if cnt == 1 { + return nil, errors.New("sender not found") + } + + require.True(t, bytes.Equal(providedRelayerAddress, address)) + + acc, _ := accounts.NewUserAccount(address, &trie.DataTrieTrackerStub{}, &trie.TrieLeafParserStub{}) + acc.Nonce = accountNonce + acc.Balance = accountBalance + + return acc, nil + } + + shardCoordinator := createMockCoordinator("_", 0) + maxNonceDeltaAllowed := 100 + txValidator, err := dataValidators.NewTxValidator( + adb, + shardCoordinator, + &testscommon.WhiteListHandlerStub{}, + testscommon.NewPubkeyConverterMock(32), + &testscommon.TxVersionCheckerStub{}, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + maxNonceDeltaAllowed, + ) + assert.Nil(t, err) + + currentShard := uint32(0) + txValidatorHandler := getInterceptedTxHandler(currentShard, currentShard, txNonce, providedSenderAddress, fee) + txValidatorHandlerStub, ok := txValidatorHandler.(*mock.InterceptedTxHandlerStub) + require.True(t, ok) + txValidatorHandlerStub.TransactionCalled = func() data.TransactionHandler { + return &transaction.Transaction{ + SndAddr: providedSenderAddress, + Signature: []byte("address sig"), + RelayerAddr: providedRelayerAddress, + RelayerSignature: []byte("relayer sig"), + Value: big.NewInt(0), + } + } + err = txValidator.CheckTxValidity(txValidatorHandler) + assert.NoError(t, err) + }) + t.Run("missing sender but guarded tx should error", func(t *testing.T) { + t.Parallel() + + accountNonce := uint64(0) + txNonce := uint64(1) + fee := big.NewInt(1000) + accountBalance := big.NewInt(1000) + + providedRelayerAddress := []byte("relayer") + providedSenderAddress := []byte("address") + adb := &stateMock.AccountsStub{} + cnt := 0 + adb.GetExistingAccountCalled = func(address []byte) (handler vmcommon.AccountHandler, e error) { + cnt++ + if cnt == 1 { + return nil, errors.New("sender not found") + } + + require.True(t, bytes.Equal(providedRelayerAddress, address)) + + acc, _ := accounts.NewUserAccount(address, &trie.DataTrieTrackerStub{}, &trie.TrieLeafParserStub{}) + acc.Nonce = accountNonce + acc.Balance = accountBalance + + return acc, nil + } + + shardCoordinator := createMockCoordinator("_", 0) + maxNonceDeltaAllowed := 100 + txValidator, err := dataValidators.NewTxValidator( + adb, + shardCoordinator, + &testscommon.WhiteListHandlerStub{}, + testscommon.NewPubkeyConverterMock(32), + &testscommon.TxVersionCheckerStub{ + IsGuardedTransactionCalled: func(tx *transaction.Transaction) bool { + return true + }, + }, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return flag == common.SupernovaFlag + }, + }, + maxNonceDeltaAllowed, + ) + assert.Nil(t, err) + + currentShard := uint32(0) + txValidatorHandler := getInterceptedTxHandler(currentShard, currentShard, txNonce, providedSenderAddress, fee) + txValidatorHandlerStub, ok := txValidatorHandler.(*mock.InterceptedTxHandlerStub) + require.True(t, ok) + txValidatorHandlerStub.TransactionCalled = func() data.TransactionHandler { + return &transaction.Transaction{ + SndAddr: providedSenderAddress, + Signature: []byte("address sig"), + RelayerAddr: providedRelayerAddress, + RelayerSignature: []byte("relayer sig"), + Value: big.NewInt(0), + GuardianAddr: []byte("guardian"), + GuardianSignature: []byte("guardian sig"), + Options: 2, + } + } + err = txValidator.CheckTxValidity(txValidatorHandler) + assert.True(t, errors.Is(err, process.ErrAccountNotFound)) + }) } func TestTxValidator_CheckTxValidityAccountNotExitsShouldReturnFalse(t *testing.T) { @@ -382,6 +590,7 @@ func TestTxValidator_CheckTxValidityAccountNotExitsShouldReturnFalse(t *testing. &testscommon.WhiteListHandlerStub{}, testscommon.NewPubkeyConverterMock(32), &testscommon.TxVersionCheckerStub{}, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, maxNonceDeltaAllowed, ) @@ -412,6 +621,7 @@ func TestTxValidator_CheckTxValidityAccountNotExitsButWhiteListedShouldReturnTru }, testscommon.NewPubkeyConverterMock(32), &testscommon.TxVersionCheckerStub{}, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, maxNonceDeltaAllowed, ) @@ -447,6 +657,7 @@ func TestTxValidator_CheckTxValidityWrongAccountTypeShouldReturnFalse(t *testing &testscommon.WhiteListHandlerStub{}, testscommon.NewPubkeyConverterMock(32), &testscommon.TxVersionCheckerStub{}, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, maxNonceDeltaAllowed, ) @@ -472,6 +683,7 @@ func TestTxValidator_CheckTxValidityTxIsOkShouldReturnTrue(t *testing.T) { &testscommon.WhiteListHandlerStub{}, testscommon.NewPubkeyConverterMock(32), &testscommon.TxVersionCheckerStub{}, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, maxNonceDeltaAllowed, ) @@ -517,7 +729,7 @@ func Test_getTxData(t *testing.T) { }) } -//------- IsInterfaceNil +// ------- IsInterfaceNil func TestTxValidator_IsInterfaceNil(t *testing.T) { t.Parallel() @@ -530,6 +742,7 @@ func TestTxValidator_IsInterfaceNil(t *testing.T) { &testscommon.WhiteListHandlerStub{}, testscommon.NewPubkeyConverterMock(32), &testscommon.TxVersionCheckerStub{}, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, 100, ) _ = txValidator diff --git a/process/economics/economicsData.go b/process/economics/economicsData.go index 91b26fbe006..1dfddd92491 100644 --- a/process/economics/economicsData.go +++ b/process/economics/economicsData.go @@ -40,7 +40,7 @@ type economicsData struct { type ArgsNewEconomicsData struct { TxVersionChecker process.TxVersionCheckerHandler Economics *config.EconomicsConfig - GeneralConfig *config.Config + ChainParamsHandler process.ChainParametersHandler EpochNotifier process.EpochNotifier EnableEpochsHandler common.EnableEpochsHandler PubkeyConverter core.PubkeyConverter @@ -94,7 +94,7 @@ func NewEconomicsData(args ArgsNewEconomicsData) (*economicsData, error) { return nil, err } - ed.globalSettingsHandler, err = newGlobalSettingsHandler(args.Economics, args.GeneralConfig) + ed.globalSettingsHandler, err = newGlobalSettingsHandler(args.Economics, args.ChainParamsHandler) if err != nil { return nil, err } @@ -220,6 +220,11 @@ func (ed *economicsData) MaxGasPriceSetGuardian() uint64 { return ed.maxGasPriceSetGuardian } +// BlockCapacityOverestimationFactor returns the block capacity overestimation factor +func (ed *economicsData) BlockCapacityOverestimationFactor() uint64 { + return ed.blockCapacityOverestimationFactor +} + // GasPerDataByte returns the gas required for a economicsData byte func (ed *economicsData) GasPerDataByte() uint64 { return ed.gasPerDataByte @@ -346,7 +351,7 @@ func (ed *economicsData) CheckValidityTxValuesInEpoch(tx data.TransactionWithFee } // The following check should be kept as it is in order to avoid backwards compatibility issues - if tx.GetGasLimit() >= ed.getMaxGasLimitPerBlock(epoch) { + if tx.GetGasLimit() > ed.getMaxGasLimitPerBlock(epoch) { return process.ErrMoreGasThanGasLimitPerBlock } diff --git a/process/economics/economicsData_test.go b/process/economics/economicsData_test.go index e27b763a8b9..9c42f276357 100644 --- a/process/economics/economicsData_test.go +++ b/process/economics/economicsData_test.go @@ -19,6 +19,7 @@ import ( "github.com/multiversx/mx-chain-go/process/economics" "github.com/multiversx/mx-chain-go/sharding" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" "github.com/multiversx/mx-chain-go/testscommon/statusHandler" @@ -105,12 +106,11 @@ func createArgsForEconomicsData(gasModifier float64) economics.ArgsNewEconomicsD feeSettings := feeSettingsDummy(gasModifier) pkConv, _ := pubkeyConverter.NewBech32PubkeyConverter(32, "erd") shardC, _ := sharding.NewMultiShardCoordinator(2, 0) - cfg := &config.Config{EpochStartConfig: config.EpochStartConfig{RoundsPerEpoch: 14400}} - cfg.GeneralSettings.ChainParametersByEpoch = []config.ChainParametersByEpochConfig{{RoundDuration: 6000}} + args := economics.ArgsNewEconomicsData{ - GeneralConfig: cfg, - Economics: createDummyEconomicsConfig(feeSettings), - EpochNotifier: &epochNotifier.EpochNotifierStub{}, + ChainParamsHandler: &chainParameters.ChainParametersHolderMock{}, + Economics: createDummyEconomicsConfig(feeSettings), + EpochNotifier: &epochNotifier.EpochNotifierStub{}, EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{ IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { return flag == common.GasPriceModifierFlag @@ -127,12 +127,10 @@ func createArgsForEconomicsDataRealFees() economics.ArgsNewEconomicsData { feeSettings := feeSettingsReal() pkConv, _ := pubkeyConverter.NewBech32PubkeyConverter(32, "erd") shardC, _ := sharding.NewMultiShardCoordinator(2, 0) - cfg := &config.Config{EpochStartConfig: config.EpochStartConfig{RoundsPerEpoch: 14400}} - cfg.GeneralSettings.ChainParametersByEpoch = []config.ChainParametersByEpochConfig{{RoundDuration: 6000}} args := economics.ArgsNewEconomicsData{ - GeneralConfig: cfg, - Economics: createDummyEconomicsConfig(feeSettings), - EpochNotifier: &epochNotifier.EpochNotifierStub{}, + ChainParamsHandler: &chainParameters.ChainParametersHolderMock{}, + Economics: createDummyEconomicsConfig(feeSettings), + EpochNotifier: &epochNotifier.EpochNotifierStub{}, EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{ IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { return flag == common.GasPriceModifierFlag @@ -1056,16 +1054,25 @@ func TestEconomicsData_TxWithWithMoreGasLimitThanMaximumPerMiniBlockForSafeCross args.Economics.FeeSettings.GasLimitSettings[0].MinGasLimit = fmt.Sprintf("%d", minGasLimit) economicsData, _ := economics.NewEconomicsData(args) - t.Run("maximum gas limit as defined should work", func(t *testing.T) { + t.Run("maximum gas limit as defined should return error", func(t *testing.T) { // do not change this behavior: backwards compatibility reasons tx := &transaction.Transaction{ GasPrice: minGasPrice + 1, - GasLimit: maxGasLimitPerBlock, + GasLimit: maxGasLimitPerBlock + 1, Value: big.NewInt(0), } err := economicsData.CheckValidityTxValues(tx) require.Equal(t, process.ErrMoreGasThanGasLimitPerBlock, err) }) + t.Run("maximum gas limit as defined should work", func(t *testing.T) { + tx := &transaction.Transaction{ + GasPrice: minGasPrice + 1, + GasLimit: maxGasLimitPerBlock, + Value: big.NewInt(0), + } + err := economicsData.CheckValidityTxValues(tx) + require.NoError(t, err) + }) t.Run("maximum gas limit + 1 as defined should error", func(t *testing.T) { tx := &transaction.Transaction{ GasPrice: minGasPrice + 1, @@ -1458,6 +1465,16 @@ func TestEconomicsData_MaxGasPriceSetGuardian(t *testing.T) { require.Equal(t, expectedMaxGasPriceSetGuardian, economicData.MaxGasPriceSetGuardian()) } +func TestEconomicsData_BlockCapacityOverestimationFactor(t *testing.T) { + t.Parallel() + + args := createArgsForEconomicsDataRealFees() + args.Economics.FeeSettings.BlockCapacityOverestimationFactor = 100 + economicData, _ := economics.NewEconomicsData(args) + + require.Equal(t, uint64(100), economicData.BlockCapacityOverestimationFactor()) +} + func TestEconomicsData_SetStatusHandler(t *testing.T) { t.Parallel() diff --git a/process/economics/gasConfigHandler.go b/process/economics/gasConfigHandler.go index 2955160b1c8..23ade15d78d 100644 --- a/process/economics/gasConfigHandler.go +++ b/process/economics/gasConfigHandler.go @@ -29,14 +29,23 @@ type gasConfig struct { maxGasHigherFactorAccepted uint64 } +type convertedGenericEconomicValues struct { + minGasPrice uint64 + gasPerDataByte uint64 + genesisTotalSupply *big.Int + maxGasPriceSetGuardian uint64 + blockCapacityOverestimationFactor uint64 +} + type gasConfigHandler struct { - statusHandler core.AppStatusHandler - gasLimitSettings []*gasConfig - minGasPrice uint64 - gasPerDataByte uint64 - genesisTotalSupply *big.Int - maxGasPriceSetGuardian uint64 - mut sync.RWMutex + statusHandler core.AppStatusHandler + gasLimitSettings []*gasConfig + minGasPrice uint64 + gasPerDataByte uint64 + genesisTotalSupply *big.Int + maxGasPriceSetGuardian uint64 + blockCapacityOverestimationFactor uint64 + mut sync.RWMutex } // newGasConfigHandler returns a new instance of gasConfigHandler @@ -50,18 +59,19 @@ func newGasConfigHandler(economics *config.EconomicsConfig) (*gasConfigHandler, return gasConfigSlice[i].gasLimitSettingEpoch < gasConfigSlice[j].gasLimitSettingEpoch }) - minGasPrice, gasPerDataByte, genesisTotalSupply, maxGasPriceSetGuardian, err := convertGenericValues(economics) + result, err := convertGenericValues(economics) if err != nil { return nil, err } return &gasConfigHandler{ - statusHandler: statusHandler.NewNilStatusHandler(), - gasLimitSettings: gasConfigSlice, - minGasPrice: minGasPrice, - gasPerDataByte: gasPerDataByte, - genesisTotalSupply: genesisTotalSupply, - maxGasPriceSetGuardian: maxGasPriceSetGuardian, + statusHandler: statusHandler.NewNilStatusHandler(), + gasLimitSettings: gasConfigSlice, + minGasPrice: result.minGasPrice, + gasPerDataByte: result.gasPerDataByte, + genesisTotalSupply: result.genesisTotalSupply, + maxGasPriceSetGuardian: result.maxGasPriceSetGuardian, + blockCapacityOverestimationFactor: result.blockCapacityOverestimationFactor, }, nil } @@ -114,7 +124,7 @@ func (handler *gasConfigHandler) getMaxGasLimitPerMiniBlock(epoch uint32) uint64 return gc.maxGasLimitPerMiniBlock } -// getMaxGasLimitPerMiniBlock returns max gas limit per mini block in a specific epoch +// getGasHigherFactorAccepted returns the maximum gas higher factor accepted for the given epoch func (handler *gasConfigHandler) getGasHigherFactorAccepted(epoch uint32) uint64 { gc := handler.getGasConfigForEpoch(epoch) return gc.maxGasHigherFactorAccepted @@ -259,29 +269,37 @@ func checkAndParseGasLimitSettings(gasLimitSetting config.GasLimitSetting) (*gas return gc, nil } -func convertGenericValues(economics *config.EconomicsConfig) (uint64, uint64, *big.Int, uint64, error) { +func convertGenericValues(economics *config.EconomicsConfig) (*convertedGenericEconomicValues, error) { conversionBase := 10 bitConversionSize := 64 minGasPrice, err := strconv.ParseUint(economics.FeeSettings.MinGasPrice, conversionBase, bitConversionSize) if err != nil { - return 0, 0, nil, 0, process.ErrInvalidMinimumGasPrice + return nil, process.ErrInvalidMinimumGasPrice } gasPerDataByte, err := strconv.ParseUint(economics.FeeSettings.GasPerDataByte, conversionBase, bitConversionSize) if err != nil { - return 0, 0, nil, 0, process.ErrInvalidGasPerDataByte + return nil, process.ErrInvalidGasPerDataByte } genesisTotalSupply, ok := big.NewInt(0).SetString(economics.GlobalSettings.GenesisTotalSupply, conversionBase) if !ok { - return 0, 0, nil, 0, process.ErrInvalidGenesisTotalSupply + return nil, process.ErrInvalidGenesisTotalSupply } maxGasPriceSetGuardian, err := strconv.ParseUint(economics.FeeSettings.MaxGasPriceSetGuardian, conversionBase, bitConversionSize) if err != nil { - return 0, 0, nil, 0, process.ErrInvalidMaxGasPriceSetGuardian + return nil, process.ErrInvalidMaxGasPriceSetGuardian } - return minGasPrice, gasPerDataByte, genesisTotalSupply, maxGasPriceSetGuardian, nil + blockCapacityOverestimationFactor := economics.FeeSettings.BlockCapacityOverestimationFactor + + return &convertedGenericEconomicValues{ + minGasPrice: minGasPrice, + gasPerDataByte: gasPerDataByte, + genesisTotalSupply: genesisTotalSupply, + maxGasPriceSetGuardian: maxGasPriceSetGuardian, + blockCapacityOverestimationFactor: blockCapacityOverestimationFactor, + }, nil } diff --git a/process/economics/globalSettings.go b/process/economics/globalSettings.go index 7b4036d7abf..a8148df598b 100644 --- a/process/economics/globalSettings.go +++ b/process/economics/globalSettings.go @@ -2,18 +2,26 @@ package economics import ( "math" + "sort" "sync" + "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/process" ) +type inflationForEpochCompound struct { + enableEpoch uint32 + maxInflation float64 +} + type globalSettingsHandler struct { minInflation float64 yearSettings map[uint32]*config.YearSetting + chainParameters process.ChainParametersHandler tailInflationActivationEpoch uint32 startYearInflation float64 - inflationForEpochCompound float64 + inflationForEpoch []inflationForEpochCompound decayPercentage float64 mutYearSettings sync.RWMutex } @@ -23,12 +31,17 @@ var numSecondsPerMinute = uint64(60) var numMinutesPerHour = uint64(60) var numHoursPerDay = uint64(24) var numDaysInYear = uint64(365) +var numberOfMillisecondsInYear = numMillisecondsPerSeconds * numSecondsPerMinute * numMinutesPerHour * numHoursPerDay * numDaysInYear // newGlobalSettingsHandler creates a new global settings provider func newGlobalSettingsHandler( economics *config.EconomicsConfig, - generalConfig *config.Config, + chainParameters process.ChainParametersHandler, ) (*globalSettingsHandler, error) { + if check.IfNil(chainParameters) { + return nil, process.ErrNilChainParametersHandler + } + g := &globalSettingsHandler{ minInflation: economics.GlobalSettings.MinimumInflation, yearSettings: make(map[uint32]*config.YearSetting), @@ -36,9 +49,9 @@ func newGlobalSettingsHandler( startYearInflation: economics.GlobalSettings.TailInflation.StartYearInflation, decayPercentage: economics.GlobalSettings.TailInflation.DecayPercentage, mutYearSettings: sync.RWMutex{}, + chainParameters: chainParameters, } - g.yearSettings = make(map[uint32]*config.YearSetting) for _, yearSetting := range economics.GlobalSettings.YearSettings { g.yearSettings[yearSetting.Year] = &config.YearSetting{ Year: yearSetting.Year, @@ -46,46 +59,66 @@ func newGlobalSettingsHandler( } } - err := g.calculateInflationForEpochCompound(generalConfig) + err := g.calculateInflationForEpochCompound() if err != nil { return nil, err } if isPercentageInvalid(g.minInflation) || isPercentageInvalid(g.startYearInflation) || - isPercentageInvalid(g.decayPercentage) || - isPercentageInvalid(g.inflationForEpochCompound) { + isPercentageInvalid(g.decayPercentage) { return nil, process.ErrInvalidInflationPercentages } return g, nil } -// TODO integrate supernova components here to calculate correctly after the transition as the config changed -func (g *globalSettingsHandler) calculateInflationForEpochCompound(generalConfig *config.Config) error { - roundsPerEpoch := uint64(generalConfig.EpochStartConfig.RoundsPerEpoch) - if len(generalConfig.GeneralSettings.ChainParametersByEpoch) == 0 { - return process.ErrInvalidChainParameters +func (g *globalSettingsHandler) calculateInflationForEpochCompound() error { + allChainParams := g.chainParameters.AllChainParameters() + if len(allChainParams) == 0 { + return process.ErrEmptyChainParametersConfiguration } - roundDuration := generalConfig.GeneralSettings.ChainParametersByEpoch[0].RoundDuration - numberOfMillisecondsInYear := numMillisecondsPerSeconds * numSecondsPerMinute * numMinutesPerHour * numHoursPerDay * numDaysInYear - epochDurationInMilliseconds := roundDuration * roundsPerEpoch + g.inflationForEpoch = make([]inflationForEpochCompound, len(allChainParams)) + + for index, chainParams := range allChainParams { + roundsPerEpoch := chainParams.RoundsPerEpoch + roundDuration := chainParams.RoundDuration + + epochDurationInMilliseconds := roundDuration * uint64(roundsPerEpoch) + if epochDurationInMilliseconds == 0 { + return process.ErrZeroDurationForEpoch + } + + numberOfEpochsPerYear := float64(numberOfMillisecondsInYear) / float64(epochDurationInMilliseconds) + + inflationForCompound := numberOfEpochsPerYear * (math.Pow(1.0+g.startYearInflation, 1.0/numberOfEpochsPerYear) - 1) + g.inflationForEpoch[index] = inflationForEpochCompound{ + enableEpoch: chainParams.EnableEpoch, + maxInflation: inflationForCompound, + } - if epochDurationInMilliseconds == 0 { - return process.ErrZeroDurationForEpoch + if isPercentageInvalid(inflationForCompound) { + return process.ErrInvalidInflationPercentages + } } - numberOfEpochsPerYear := float64(numberOfMillisecondsInYear) / float64(epochDurationInMilliseconds) + sort.SliceStable(g.inflationForEpoch, func(i, j int) bool { + return g.inflationForEpoch[i].enableEpoch > g.inflationForEpoch[j].enableEpoch + }) - g.inflationForEpochCompound = numberOfEpochsPerYear * (math.Pow(1.0+g.startYearInflation, 1.0/numberOfEpochsPerYear) - 1) return nil } // TODO: implement decay, implement growth, calculations will change after supernova func (g *globalSettingsHandler) maxInflationRate(year uint32, epoch uint32) float64 { if g.isTailInflationActive(epoch) { - return g.inflationForEpochCompound + for _, inflationForEpoch := range g.inflationForEpoch { + if inflationForEpoch.enableEpoch <= epoch { + return inflationForEpoch.maxInflation + } + } + return 0.0 } return g.yearSettingsInflation(year) diff --git a/process/economics/globalSettings_test.go b/process/economics/globalSettings_test.go index b0da45d5fb6..bb911265b65 100644 --- a/process/economics/globalSettings_test.go +++ b/process/economics/globalSettings_test.go @@ -2,6 +2,7 @@ package economics import ( "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" "github.com/stretchr/testify/require" "math" "testing" @@ -48,13 +49,20 @@ func createGlobalSettingsHandler() *globalSettingsHandler { FeeSettings: config.FeeSettings{}, } - cfg := &config.Config{EpochStartConfig: config.EpochStartConfig{RoundsPerEpoch: 14400}} - cfg.GeneralSettings.ChainParametersByEpoch = []config.ChainParametersByEpochConfig{ - { - RoundDuration: 6000, + chainParams := config.ChainParametersByEpochConfig{RoundsPerEpoch: 14400, RoundDuration: 6000} + chainParamsHolder := &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(epoch uint32) (config.ChainParametersByEpochConfig, error) { + return chainParams, nil + }, + CurrentChainParametersCalled: func() config.ChainParametersByEpochConfig { + return chainParams + }, + AllChainParametersCalled: func() []config.ChainParametersByEpochConfig { + return []config.ChainParametersByEpochConfig{chainParams} }, } - gsh, _ := newGlobalSettingsHandler(&economics, cfg) + + gsh, _ := newGlobalSettingsHandler(&economics, chainParamsHolder) return gsh } @@ -114,3 +122,61 @@ func TestGlobalSettingsMaxInflationRate_withSupply(t *testing.T) { require.True(t, totalSupplyDay365Yearly >= totalSupplyDay365Daily) require.True(t, totalSupplyDay365Yearly-totalSupplyDay365Daily < oneToken) } + +func TestNewGlobalSettingsHandler_ZeroEpochDuration(t *testing.T) { + t.Parallel() + + economics := &config.EconomicsConfig{} + chainParams := config.ChainParametersByEpochConfig{RoundsPerEpoch: 100, RoundDuration: 0} + chainParamsHolder := &chainParameters.ChainParametersHandlerStub{ + AllChainParametersCalled: func() []config.ChainParametersByEpochConfig { + return []config.ChainParametersByEpochConfig{chainParams} + }, + } + + _, err := newGlobalSettingsHandler(economics, chainParamsHolder) + require.Error(t, err) +} + +func TestNewGlobalSettingsHandler_Sorting(t *testing.T) { + t.Parallel() + + economics := &config.EconomicsConfig{} + chainParams1 := config.ChainParametersByEpochConfig{RoundsPerEpoch: 100, RoundDuration: 1000, EnableEpoch: 10} + chainParams2 := config.ChainParametersByEpochConfig{RoundsPerEpoch: 200, RoundDuration: 2000, EnableEpoch: 20} + chainParamsHolder := &chainParameters.ChainParametersHandlerStub{ + AllChainParametersCalled: func() []config.ChainParametersByEpochConfig { + return []config.ChainParametersByEpochConfig{chainParams1, chainParams2} + }, + } + + gsh, err := newGlobalSettingsHandler(economics, chainParamsHolder) + require.NoError(t, err) + + require.Equal(t, uint32(20), gsh.inflationForEpoch[0].enableEpoch) + require.Equal(t, uint32(10), gsh.inflationForEpoch[1].enableEpoch) +} + +func TestNewGlobalSettingsHandler_MultipleChainParameters(t *testing.T) { + t.Parallel() + + economics := &config.EconomicsConfig{} + economics.GlobalSettings.TailInflation.StartYearInflation = 0.1 + + chainParams1 := config.ChainParametersByEpochConfig{RoundsPerEpoch: 14400, RoundDuration: 6000, EnableEpoch: 10} + chainParams2 := config.ChainParametersByEpochConfig{RoundsPerEpoch: 7200, RoundDuration: 3000, EnableEpoch: 20} + chainParamsHolder := &chainParameters.ChainParametersHandlerStub{ + AllChainParametersCalled: func() []config.ChainParametersByEpochConfig { + return []config.ChainParametersByEpochConfig{chainParams1, chainParams2} + }, + } + + gsh, err := newGlobalSettingsHandler(economics, chainParamsHolder) + require.NoError(t, err) + + require.Equal(t, uint32(20), gsh.inflationForEpoch[0].enableEpoch) + require.InDelta(t, 0.0953137, gsh.inflationForEpoch[0].maxInflation, 0.000001) + + require.Equal(t, uint32(10), gsh.inflationForEpoch[1].enableEpoch) + require.InDelta(t, 0.0953227, gsh.inflationForEpoch[1].maxInflation, 0.000001) +} diff --git a/process/economics/rewardsConfigHandler.go b/process/economics/rewardsConfigHandler.go index 9c98af62735..770904a5a09 100644 --- a/process/economics/rewardsConfigHandler.go +++ b/process/economics/rewardsConfigHandler.go @@ -223,8 +223,8 @@ func decodeAddressAndVerifyShard( return nil, err } - protocolSustainabilityShardID := shardCoordinator.ComputeId(decodedAddress) - if protocolSustainabilityShardID == core.MetachainShardId { + addressShardID := shardCoordinator.ComputeId(decodedAddress) + if addressShardID == core.MetachainShardId { return nil, process.ErrProtocolSustainabilityAddressInMetachain } diff --git a/process/errors.go b/process/errors.go index 8086d53b22e..ad6a0a49495 100644 --- a/process/errors.go +++ b/process/errors.go @@ -104,6 +104,9 @@ var ErrNilMiniBlocks = errors.New("nil mini blocks") // ErrNilMiniBlock signals that an operation has been attempted with a nil miniblock var ErrNilMiniBlock = errors.New("nil mini block") +// ErrNilMiniBlockHeader signals that an operation has been attempted with a nil mini block header +var ErrNilMiniBlockHeader = errors.New("nil mini block header") + // ErrNilRootHash signals that an operation has been attempted with a nil root hash var ErrNilRootHash = errors.New("root hash is nil") @@ -131,6 +134,9 @@ var ErrValidatorStatsRootHashDoesNotMatch = errors.New("root hash for validator // ErrAccountStateDirty signals that the accounts were modified before starting the current modification var ErrAccountStateDirty = errors.New("accountState was dirty before starting to change") +// ErrBlockProcessorBusy signals that the block processor is already busy processing another block +var ErrBlockProcessorBusy = errors.New("block processor is busy") + // ErrInvalidShardId signals that the shard id is invalid var ErrInvalidShardId = errors.New("invalid shard id") @@ -146,6 +152,9 @@ var ErrMissingBody = errors.New("missing body") // ErrNilBlockProcessor signals that an operation has been attempted to or with a nil BlockProcessor implementation var ErrNilBlockProcessor = errors.New("nil block processor") +// ErrNilExecutionManager signals that a nil execution manager has been provided +var ErrNilExecutionManager = errors.New("nil execution manager") + // ErrNilMarshalizer signals that an operation has been attempted to or with a nil Marshalizer implementation var ErrNilMarshalizer = errors.New("nil Marshalizer") @@ -215,9 +224,6 @@ var ErrNilDataToProcess = errors.New("nil data to process") // ErrNilPoolsHolder signals that an operation has been attempted to or with a nil pools holder object var ErrNilPoolsHolder = errors.New("nil pools holder") -// ErrNilTxStorage signals that a nil transaction storage has been provided -var ErrNilTxStorage = errors.New("nil transaction storage") - // ErrNilStorage signals that a nil storage has been provided var ErrNilStorage = errors.New("nil storage") @@ -236,6 +242,12 @@ var ErrNilTransactionPool = errors.New("nil transaction pool") // ErrNilMiniBlockPool signals that a nil mini blocks pool was used var ErrNilMiniBlockPool = errors.New("nil mini block pool") +// ErrNilPostProcessTransactionsCache signals that a nil post-process transactions cache has been provided +var ErrNilPostProcessTransactionsCache = errors.New("nil post process transactions cache") + +// ErrNilExecutedMiniBlocksCache signals that a nil executed mini blocks cache has been provided +var ErrNilExecutedMiniBlocksCache = errors.New("nil executed mini blocks cache") + // ErrNilMetaBlocksPool signals that a nil meta blocks pool was used var ErrNilMetaBlocksPool = errors.New("nil meta blocks pool") @@ -375,9 +387,6 @@ var ErrNilRewardTxDataPool = errors.New("reward transactions pool is nil") // ErrNilUnsignedTxDataPool signals that the unsigned transactions pool is nil var ErrNilUnsignedTxDataPool = errors.New("unsigned transactions pool is nil") -// ErrNilUTxStorage signals that unsigned transaction storage is nil -var ErrNilUTxStorage = errors.New("unsigned transactions storage is nil") - // ErrNilScAddress signals that a nil smart contract address has been provided var ErrNilScAddress = errors.New("nil SC address") @@ -477,6 +486,12 @@ var ErrInvalidCacheRefreshIntervalInSec = errors.New("invalid cacheRefreshInterv // ErrEpochDoesNotMatch signals that epoch does not match between headers var ErrEpochDoesNotMatch = errors.New("epoch does not match") +// ErrInvalidTimestamp signals that the header timestamp does not match the expected value for the round +var ErrInvalidTimestamp = errors.New("invalid timestamp") + +// ErrStartOfEpochExecutionResultsDoNotExist signals that the start of epoch execution results do not exist +var ErrStartOfEpochExecutionResultsDoNotExist = errors.New("start of epoch execution results do not exist") + // ErrOverallBalanceChangeFromSC signals that all sumed balance changes are not zero var ErrOverallBalanceChangeFromSC = errors.New("SC output balance updates are wrong") @@ -531,6 +546,12 @@ var ErrMaxGasLimitPerBlockInSelfShardIsReached = errors.New("max gas limit per b // ErrMaxGasLimitUsedForDestMeTxsIsReached signals that max gas limit used for dest me txs has been reached var ErrMaxGasLimitUsedForDestMeTxsIsReached = errors.New("max gas limit used for dest me txs is reached") +// ErrMaxGasLimitPerTransactionIsReached signals that max gas limit per transactions has been reached +var ErrMaxGasLimitPerTransactionIsReached = errors.New("max gas limit per transaction is reached") + +// ErrMaxGasLimitPerMiniBlockIsReached signals that max gas limit per mini block has been reached +var ErrMaxGasLimitPerMiniBlockIsReached = errors.New("max gas limit per mini block is reached") + // ErrInvalidMinimumGasPrice signals that an invalid gas price has been read from config file var ErrInvalidMinimumGasPrice = errors.New("invalid minimum gas price") @@ -540,6 +561,9 @@ var ErrInvalidExtraGasLimitGuardedTx = errors.New("invalid extra gas limit for g // ErrInvalidMaxGasPriceSetGuardian signals that an invalid maximum gas price has been provided in the config file var ErrInvalidMaxGasPriceSetGuardian = errors.New("invalid maximum gas price for set guardian") +// ErrInvalidBlockCapacityOverestimationFactor signals that an invalid block capacity overestimation factor has been provided in the config file +var ErrInvalidBlockCapacityOverestimationFactor = errors.New("invalid block capacity overestimation factor") + // ErrInvalidMaxGasHigherFactorAccepted signals that an invalid gas factor has been provided in the config file var ErrInvalidMaxGasHigherFactorAccepted = errors.New("invalid gas higher factor accepted") @@ -594,7 +618,7 @@ var ErrNilPeerShardMapper = errors.New("nil peer shard mapper") // ErrNilBlockTracker signals that a nil block tracker was provided var ErrNilBlockTracker = errors.New("nil block tracker") -// ErrHeaderIsBlackListed signals that the header provided is black listed +// ErrHeaderIsBlackListed signals that the header provided is blacklisted var ErrHeaderIsBlackListed = errors.New("header is black listed") // ErrNilEconomicsData signals that nil economics data has been provided @@ -1158,6 +1182,12 @@ var ErrNilEnableEpochsHandler = errors.New("nil enable epochs handler") // ErrNilEpochChangeGracePeriodHandler signals that a nil epoch change grace period handler has been provided var ErrNilEpochChangeGracePeriodHandler = errors.New("nil epoch change grace period handler") +// ErrNilProcessConfigsHandler signals that a nil process configs by epoch handler has been provided +var ErrNilProcessConfigsHandler = errors.New("nil process configs by epoch handler") + +// ErrNilAntifloodConfigsHandler signals that a nil antiflood configs handler has been provided +var ErrNilAntifloodConfigsHandler = errors.New("nil antiflood configs handler") + // ErrNilMultiSignerContainer signals that the given multisigner container is nil var ErrNilMultiSignerContainer = errors.New("nil multiSigner container") @@ -1191,6 +1221,9 @@ var ErrMaxCallsReached = errors.New("max calls reached") // ErrNilTxExecutionOrderHandler signals that a nil transaction execution order handler was provided var ErrNilTxExecutionOrderHandler = errors.New("nil transaction execution order handler") +// ErrNilBlockDataRequester signals that a nil processor requester was provided +var ErrNilBlockDataRequester = errors.New("nil block data requester") + // ErrWrongTransactionType signals that transaction is invalid var ErrWrongTransactionType = errors.New("invalid transaction type") @@ -1242,12 +1275,18 @@ var ErrNilManagedPeersHolder = errors.New("nil managed peers holder") // ErrNilStorageService signals that a nil storage service has been provided var ErrNilStorageService = errors.New("nil storage service") +// ErrNilPubKeysHandler signals that a nil public keys handler has been provided +var ErrNilPubKeysHandler = errors.New("nil public keys handler") + // ErrInvalidAsyncArguments signals that invalid arguments were given for async/callBack processing var ErrInvalidAsyncArguments = errors.New("invalid arguments to process async/callback function") // ErrNilSentSignatureTracker defines the error for setting a nil SentSignatureTracker var ErrNilSentSignatureTracker = errors.New("nil sent signature tracker") +// ErrNilStateAccessesCollector signals that a nil state access collector has been given +var ErrNilStateAccessesCollector = errors.New("nil state accesses collector") + // ErrTransferAndExecuteByUserAddressesAreNil signals that transfer and execute by user addresses are nil var ErrTransferAndExecuteByUserAddressesAreNil = errors.New("transfer and execute by user addresses are nil") @@ -1281,6 +1320,9 @@ var ErrNilHeaderProof = errors.New("nil header proof") // ErrNilInterceptedDataCache signals that a nil cacher was provided for intercepted data verifier var ErrNilInterceptedDataCache = errors.New("nil cache for intercepted data") +// ErrNilDirectSentCache signals that a nil cacher was provided for direct-sent data +var ErrNilDirectSentCache = errors.New("nil cache for direct sent") + // ErrFlagNotActive signals that a flag is not active var ErrFlagNotActive = errors.New("flag not active") @@ -1290,8 +1332,8 @@ var ErrInvalidInterceptedData = errors.New("invalid intercepted data") // ErrMissingHeaderProof signals that the proof for the header is missing var ErrMissingHeaderProof = errors.New("missing header proof") -// ErrInvalidHeaderProof signals that an invalid equivalent proof has been provided -var ErrInvalidHeaderProof = errors.New("invalid equivalent proof") +// ErrInvalidHeader signals that an invalid header has been provided +var ErrInvalidHeader = errors.New("invalid header") // ErrUnexpectedHeaderProof signals that a header proof has been provided unexpectedly var ErrUnexpectedHeaderProof = errors.New("unexpected header proof") @@ -1313,3 +1355,144 @@ var ErrInvalidChainParameters = errors.New("invalid chain parameters") // ErrDuplicatedHashInBlock signals that the same hash appears more than once where uniqueness is expected var ErrDuplicatedHashInBlock = errors.New("duplicated hash in block") + +// ErrBadSelectionGasBandwidthIncreasePercent signals a bad txcache config +var ErrBadSelectionGasBandwidthIncreasePercent = errors.New("bad selection gas bandwidth increase percent") + +// ErrBadSelectionGasBandwidthIncreaseScheduledPercent signals a bad txcache config +var ErrBadSelectionGasBandwidthIncreaseScheduledPercent = errors.New("bad selection gas bandwidth increase scheduled percent") + +// ErrBadTxCacheSelectionGasRequested signals a bad txcache config +var ErrBadTxCacheSelectionGasRequested = errors.New("bad tx cache selection gas requested") + +// ErrBadTxCacheSelectionMaxNumTxs signals a bad txcache config +var ErrBadTxCacheSelectionMaxNumTxs = errors.New("bad tx cache selection max num txs") + +// ErrBadTxCacheSelectionLoopDurationCheckInterval signals a bad txcache config +var ErrBadTxCacheSelectionLoopDurationCheckInterval = errors.New("bad selection loop duration check interval") + +// ErrDuplicatedInterceptedDataNotAllowed signals that duplicated intercepted data is not allowed +var ErrDuplicatedInterceptedDataNotAllowed = errors.New("duplicated intercepted data not allowed") + +// ErrNilTxsForBlockHandler signals that a nil transactions for block handler has been provided +var ErrNilTxsForBlockHandler = errors.New("nil txs for block handler") + +// ErrNilHeadersForBlock signals that a nil headers for block has been provided +var ErrNilHeadersForBlock = errors.New("nil headers for block") + +// ErrNilLastExecutionResultHandler signals that a nil last execution result handler has been provided +var ErrNilLastExecutionResultHandler = errors.New("nil last execution result handler") + +// ErrNilExecutionResultHandler signals that a nil execution result handler has been provided +var ErrNilExecutionResultHandler = errors.New("nil execution result handler") + +// ErrExecutionResultDoesNotMatch signals that the execution result does not match the expected one +var ErrExecutionResultDoesNotMatch = errors.New("execution result does not match") + +// ErrExecutionResultsNumberMismatch signals that the number of execution results does not match the expected one +var ErrExecutionResultsNumberMismatch = errors.New("execution results number mismatch") + +// ErrInvalidHash signals that an invalid hash has been provided +var ErrInvalidHash = errors.New("invalid hash") + +// ErrExecutionResultsNonConsecutive signals that execution results are not consecutive +var ErrExecutionResultsNonConsecutive = errors.New("execution results non consecutive") + +// ErrInvalidMaxNonceDifference signals that an invalid max nonce difference has been provided +var ErrInvalidMaxNonceDifference = errors.New("invalid max nonce difference") + +// ErrNilBaseExecutionResult signals that a nil base execution result has been provided +var ErrNilBaseExecutionResult = errors.New("nil base execution result") + +// ErrNilGasComputation signals that a nil gas computation has been provided +var ErrNilGasComputation = errors.New("nil gas computation") + +// ErrNilExecutionResultsInclusionEstimator signals that a nil execution results inclusion estimator has been provided +var ErrNilExecutionResultsInclusionEstimator = errors.New("nil execution results inclusion estimator") + +// ErrNilMiniBlocksSelectionSession signals that a nil miniblocks selection session has been provided +var ErrNilMiniBlocksSelectionSession = errors.New("nil miniblocks selection session") + +// ErrNilExecutionResultsVerifier signals that a nil execution results verifier has been provided +var ErrNilExecutionResultsVerifier = errors.New("nil execution results verifier") + +// ErrNilMissingDataResolver signals that a nil missing data resolver has been provided +var ErrNilMissingDataResolver = errors.New("nil missing data resolver") + +// ErrWrongMiniBlockConstructionState signals that the mini block construction state is not as expected +var ErrWrongMiniBlockConstructionState = errors.New("wrong mini block construction state") + +// ErrWrongMiniBlockProcessingType signals that the mini block processing type is not as expected +var ErrWrongMiniBlockProcessingType = errors.New("wrong mini block processing type") + +// ErrInvalidNumberOfExecutionResultsInHeader signals that the number of execution results in header is invalid +var ErrInvalidNumberOfExecutionResultsInHeader = errors.New("invalid number of execution results in header") + +// ErrEpochStartInfoNotAvailable signals that epoch start info is not available +var ErrEpochStartInfoNotAvailable = errors.New("epoch start info not available") + +// ErrDuplicatedTransaction signals that a duplicated transaction was found +var ErrDuplicatedTransaction = errors.New("duplicated transaction") + +// ErrTransactionsMismatch signals that transactions do not match +var ErrTransactionsMismatch = errors.New("transactions mismatch") + +// ErrGasUsedExceedsGasProvided signals that gas used exceeds gas provided +var ErrGasUsedExceedsGasProvided = errors.New("gas used exceeds gas provided") + +// ErrInvalidBlockType signals that an invalid block type has been provided +var ErrInvalidBlockType = errors.New("invalid block type") + +// ErrNilEpochStartData signals that nil epoch start data has been provided +var ErrNilEpochStartData = errors.New("nil epoch start data") + +// ErrInconsistentShardHeadersAndHashes signals that shard headers are inconsistent with their hashes +var ErrInconsistentShardHeadersAndHashes = errors.New("inconsistent shard headers and hashes") + +// ErrNilShardInfoCreator signals that a nil shard info creator has been provided +var ErrNilShardInfoCreator = errors.New("nil shard info creator") + +// ErrEpochStartProposeBlockHasMiniBlocks signals that epoch start propose block has mini blocks +var ErrEpochStartProposeBlockHasMiniBlocks = errors.New("epoch start propose block has mini blocks") + +// ErrNonCanonicalExecutionResultIncluded signals that a non-canonical execution result has been included +var ErrNonCanonicalExecutionResultIncluded = errors.New("non-canonical execution result included") + +// ErrNoReferencedHeader signals that no header was referenced +var ErrNoReferencedHeader = errors.New("no referenced header") + +// ErrExecutionResultNotFound signals that the execution result was not found +var ErrExecutionResultNotFound = errors.New("execution result not found") + +// ErrShardHeadersShouldNotBeNotarized signals that shard headers should not be notarized +var ErrShardHeadersShouldNotBeNotarized = errors.New("shard headers should not be notarized") + +// ErrNilKeysHandler signals that a nil keys handler has been provided +var ErrNilKeysHandler = errors.New("nil keys handler") + +// ErrNilNodeRedundancyHandler signals that a nil node redundancy handler has been provided +var ErrNilNodeRedundancyHandler = errors.New("nil node redundancy handler") + +// ErrNilTxCache signals that a nil transaction cache has been provided +var ErrNilTxCache = errors.New("nil tx cache") + +// ErrNilAOTSelector signals that a nil AOT selector has been provided +var ErrNilAOTSelector = errors.New("nil AOT selector") + +// ErrNonceGapTooLarge signals that nonce gap between finalized and proposed shard headers is too large +var ErrNonceGapTooLarge = errors.New("nonce gap between finalized and proposed shard headers is too large") + +// ErrInvalidLastExecutionResult signals that an invalid last execution result has been included +var ErrInvalidLastExecutionResult = errors.New("invalid last execution result") + +// ErrInvalidProposedNonce signals that an invalid nonce has been proposed +var ErrInvalidProposedNonce = errors.New("invalid proposed nonce") + +// ErrMissingCrossNotarizedHeader signals that there is a missing cross notarized header +var ErrMissingCrossNotarizedHeader = errors.New("missing cross notarized header") + +// ErrInvalidShardInfo signals that an invalid shard info has been provided +var ErrInvalidShardInfo = errors.New("invalid shard info") + +// ErrNilClosingNodeStartedFlag signals that the closing node started flag is nil +var ErrNilClosingNodeStartedFlag = errors.New("closing node started flag is nil") diff --git a/process/estimator/executionResultInclusionEstimator.go b/process/estimator/executionResultInclusionEstimator.go new file mode 100644 index 00000000000..33f0e1e625e --- /dev/null +++ b/process/estimator/executionResultInclusionEstimator.go @@ -0,0 +1,248 @@ +package estimator + +import ( + "errors" + "math/bits" + + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + logger "github.com/multiversx/mx-chain-logger-go" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/process" +) + +// ErrInvalidMaxResultsPerBlock signals that invalid max results per block config has been provided +var ErrInvalidMaxResultsPerBlock = errors.New("invalid max results per block config") + +var log = logger.GetOrCreate("process/executionResultInclusionEstimator") + +// Time per gas unit on minimum‑spec hardware (1 gas = 1ns) +var tGas = uint64(1) + +// RoundHandler provides the current round index and timestamp. +type RoundHandler interface { + GetTimeStampForRound(round uint64) uint64 + IsInterfaceNil() bool +} + +// ExecutionResultInclusionEstimator (EIE) is a deterministic component shipped with the MultiversX *Supernova* +// node. It determines, at proposal‑time and at validation‑time, whether one or more pending execution results can be +// safely embedded in the block that is being produced / verified. +type ExecutionResultInclusionEstimator struct { + cfg config.ExecutionResultInclusionEstimatorConfig // immutable after construction + tGas uint64 // time per gas unit on minimum‑spec hardware - 1 ns per gas unit + roundHandler RoundHandler + // TODO add also max estimated block gas capacity - used gas must be lower than this + execResSizeComputation ExecResSizeComputationHandler +} + +// NewExecutionResultInclusionEstimator returns a new instance of EIE +func NewExecutionResultInclusionEstimator( + cfg config.ExecutionResultInclusionEstimatorConfig, + roundHandler RoundHandler, + execResultComputationHandler ExecResSizeComputationHandler, +) (*ExecutionResultInclusionEstimator, error) { + err := checkConfig(cfg) + if err != nil { + return nil, err + } + if check.IfNil(roundHandler) { + return nil, process.ErrNilRoundHandler + } + + return &ExecutionResultInclusionEstimator{ + cfg: cfg, + tGas: tGas, + roundHandler: roundHandler, + execResSizeComputation: execResultComputationHandler, + }, nil +} + +func checkConfig(cfg config.ExecutionResultInclusionEstimatorConfig) error { + if cfg.MaxResultsPerBlock == 0 { + return ErrInvalidMaxResultsPerBlock + } + + return nil +} + +// Decide returns the prefix of `pending` that may be inserted into the block currently being built / verified. +// Return value: `allowed` is the count of leading entries in `pending` deemed safe. The caller slices `pending[:allowed]` and embeds them. +func (erie *ExecutionResultInclusionEstimator) Decide( + lastNotarised *common.LastExecutionResultForInclusion, + pending []data.BaseExecutionResultHandler, + currentRound uint64, +) (allowed int) { + allowed = 0 + + if len(pending) == 0 { + return allowed + } + + nominalSafetyMargin := uint64(100) + safetyMargin := erie.cfg.SafetyMargin + if erie.cfg.SafetyMargin > nominalSafetyMargin { + safetyMargin -= nominalSafetyMargin + } + + var roundForTBaseCalculation uint64 + var previousExecutionResultMeta data.BaseExecutionResultHandler + + // lastNotarised is nil if genesis. + if lastNotarised == nil { + roundForTBaseCalculation = 0 + } else { + roundForTBaseCalculation = lastNotarised.NotarizedInRound + } + + tBase := convertMsToNs(erie.roundHandler.GetTimeStampForRound(roundForTBaseCalculation)) + + currentHdrTsMs := erie.roundHandler.GetTimeStampForRound(currentRound) + currentHdrTsNs := convertMsToNs(currentHdrTsMs) + + execResSizeLimitChecker := erie.execResSizeComputation.NewComputation() + + // accumulated execution tBase for each pending execution result in ns (1 gas = 1ns) + estimatedTBase := tBase + for pendingExecutionIndex, executionResultMeta := range pending { + if pendingExecutionIndex > 0 { + previousExecutionResultMeta = pending[pendingExecutionIndex-1] + } + ok := erie.checkSanity(executionResultMeta, previousExecutionResultMeta, lastNotarised, currentRound) + if !ok { + return pendingExecutionIndex + } + + overflow, currentEstimatedTime := bits.Mul64(executionResultMeta.GetGasUsed(), erie.tGas) + if overflow != 0 { + log.Debug("ExecutionResultInclusionEstimator: overflow detected in currentEstimatedTime", + "currentEstimatedTime", currentEstimatedTime, + "gasUsed", executionResultMeta.GetGasUsed(), + "tGas", erie.tGas, + ) + return pendingExecutionIndex + } + + // Round timestamp for current execution result, since it is the time when the execution result becomes available + tRoundNs := convertMsToNs(erie.roundHandler.GetTimeStampForRound(executionResultMeta.GetHeaderRound())) + + // Align previously estimated tBase with start of round if needed + estimatedTBase = max(estimatedTBase, tRoundNs) + estimatedTBase, overflow = bits.Add64(estimatedTBase, currentEstimatedTime, 0) + if overflow != 0 { + log.Debug("ExecutionResultInclusionEstimator: overflow detected in block transactions time estimation", + "estimatedTBase", estimatedTBase, + "currentEstimatedTime", currentEstimatedTime) + return pendingExecutionIndex + } + + // Apply safety margin to current estimated time + overflow, currentEstimatedTimeMargin := bits.Mul64(currentEstimatedTime, safetyMargin) + if overflow != 0 { + log.Debug("ExecutionResultInclusionEstimator: overflow detected in estimated time with margin", + "currentEstimatedTime", currentEstimatedTime, + "safetyMargin", safetyMargin) + return pendingExecutionIndex + } + currentEstimatedTimeMargin /= nominalSafetyMargin + + tDone, overflow := bits.Add64(estimatedTBase, currentEstimatedTimeMargin, 0) + if overflow != 0 { + log.Debug("ExecutionResultInclusionEstimator: overflow detected in total estimated time", + "estimatedTBase", estimatedTBase, + "estimatedTimeWithMargin", currentEstimatedTimeMargin) + return pendingExecutionIndex + } + + // check for time cap reached, cannot include current pending item or anything after + if tDone > currentHdrTsNs { + log.Debug("ExecutionResultInclusionEstimator: estimated time exceeds current header timestamp", + "tDone", tDone, + "currentHdrTsNs", currentHdrTsNs) + return pendingExecutionIndex + } + + if execResSizeLimitChecker.IsMaxExecResSizeReached(1) { + log.Debug("ExecutionResultInclusionEstimator: estimated max size reached", + "currentIndex", pendingExecutionIndex) + return pendingExecutionIndex + } + execResSizeLimitChecker.AddNumExecRes(1) + + // check for number of results cap reached, including current pending item. MaxResultsPerBlock = 0 means no cap. + if uint64(pendingExecutionIndex+1) >= erie.cfg.MaxResultsPerBlock { + log.Debug("ExecutionResultInclusionEstimator: reached MaxResultsPerBlock cap", + "maxResultsPerBlock", erie.cfg.MaxResultsPerBlock, + "currentIndex", pendingExecutionIndex+1) + return pendingExecutionIndex + 1 + } + } + + // If we reach here, all pending items are safe to include + return len(pending) +} + +// IsInterfaceNil returns true if there is no value under the interface +func (erie *ExecutionResultInclusionEstimator) IsInterfaceNil() bool { + return erie == nil +} + +func (erie *ExecutionResultInclusionEstimator) checkSanity( + currentExecutionResult data.BaseExecutionResultHandler, + previousExecutionResult data.BaseExecutionResultHandler, + lastNotarised *common.LastExecutionResultForInclusion, + currentRound uint64, +) bool { + // Check for genesis round + if currentExecutionResult.GetHeaderRound() == 0 { + log.Debug("ExecutionResultInclusionEstimator: ExecutionResultHeaderRound on genesis detected", + "headerNonce", currentExecutionResult.GetHeaderNonce(), + ) + return false + } + // Check for strict nonce monotonicity + if previousExecutionResult != nil && currentExecutionResult.GetHeaderNonce() != previousExecutionResult.GetHeaderNonce()+1 { + log.Debug("ExecutionResultInclusionEstimator: non-monotonic HeaderNonce detected", + "currentHeaderNonce", currentExecutionResult.GetHeaderNonce(), + "previousHeaderNonce", previousExecutionResult.GetHeaderNonce(), + "currentRound", currentExecutionResult.GetHeaderRound(), + "previousRound", previousExecutionResult.GetHeaderRound(), + ) + return false + } + // Check for monotonicity of rounds + if previousExecutionResult != nil && currentExecutionResult.GetHeaderRound() <= previousExecutionResult.GetHeaderRound() { + log.Debug("ExecutionResultInclusionEstimator: non-monotonic rounds detected", + "currentRound", currentExecutionResult.GetHeaderRound(), + "previousERound", previousExecutionResult.GetHeaderRound(), + ) + return false + } + + // Check for round before last notarised + if lastNotarised != nil && currentExecutionResult.GetHeaderRound() <= lastNotarised.ProposedInRound { + log.Debug("ExecutionResultInclusionEstimator: Round before last notarised detected", + "headerNonce", currentExecutionResult.GetHeaderNonce(), + "lastNotarisedResultProposalRound", lastNotarised.ProposedInRound, + "currentExecutionResultRound", currentExecutionResult.GetHeaderRound()) + return false + } + // Check for results in the future + if currentExecutionResult.GetHeaderRound() >= currentRound { + log.Debug("ExecutionResultInclusionEstimator: Execution result round in the future detected", + "currentExecutionResultNonce", currentExecutionResult.GetHeaderNonce(), + "currentExecutionResultRound", currentExecutionResult.GetHeaderRound(), + "currentHeaderRound", currentRound, + ) + return false + } + return true +} + +// TODO check for overflow? +func convertMsToNs(ms uint64) uint64 { + // Convert milliseconds to nanoseconds + return ms * 1_000_000 +} diff --git a/process/estimator/executionResultInclusionEstimator_test.go b/process/estimator/executionResultInclusionEstimator_test.go new file mode 100644 index 00000000000..eeb049f6edb --- /dev/null +++ b/process/estimator/executionResultInclusionEstimator_test.go @@ -0,0 +1,547 @@ +package estimator_test + +import ( + "fmt" + "math" + "testing" + + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/estimator" + "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" + "github.com/multiversx/mx-chain-go/testscommon/round" +) + +func newExecResultSizeComputationHandler() estimator.ExecResSizeComputationHandler { + execResSizeComputationHandler, _ := estimator.NewExecResultSizeComputationHandler(&marshallerMock.MarshalizerMock{}, maxSizeInBytes) + return execResSizeComputationHandler +} + +func TestEstimatorCreation(t *testing.T) { + t.Parallel() + genesisTimeStampMs := uint64(1000) + roundHandler := &round.RoundHandlerMock{ + GetTimeStampForRoundCalled: func(round uint64) uint64 { + return genesisTimeStampMs + round*1000 + }, + } + + t.Run("Nil interface", func(t *testing.T) { + t.Parallel() + + var erie *estimator.ExecutionResultInclusionEstimator + require.True(t, erie.IsInterfaceNil(), "IsInterfaceNil() should return true for nil interface") + + erie = &estimator.ExecutionResultInclusionEstimator{} + require.False(t, erie.IsInterfaceNil(), "IsInterfaceNil() should return false for non-nil interface") + }) + + t.Run("Nil RoundHandler", func(t *testing.T) { + t.Parallel() + + var erie *estimator.ExecutionResultInclusionEstimator + cfg := config.ExecutionResultInclusionEstimatorConfig{MaxResultsPerBlock: 10} + erie, err := estimator.NewExecutionResultInclusionEstimator(cfg, nil, newExecResultSizeComputationHandler()) + require.Equal(t, process.ErrNilRoundHandler, err) + require.True(t, erie.IsInterfaceNil(), "IsInterfaceNil() should return true if RoundHandler is nil") + }) + + t.Run("Default config", func(t *testing.T) { + t.Parallel() + cfg := config.ExecutionResultInclusionEstimatorConfig{SafetyMargin: 0, MaxResultsPerBlock: 1} + erie, err := estimator.NewExecutionResultInclusionEstimator(cfg, roundHandler, newExecResultSizeComputationHandler()) + require.Nil(t, err) + require.NotNil(t, erie, "NewExecutionResultInclusionEstimator should not return nil") + }) + + t.Run("Custom config", func(t *testing.T) { + t.Parallel() + cfg := config.ExecutionResultInclusionEstimatorConfig{SafetyMargin: 120, MaxResultsPerBlock: 10} + erie, err := estimator.NewExecutionResultInclusionEstimator(cfg, roundHandler, newExecResultSizeComputationHandler()) + require.Nil(t, err) + require.NotNil(t, erie, "NewExecutionResultInclusionEstimator should not return nil") + }) +} + +func TestDecide(t *testing.T) { + t.Parallel() + + genesisTimeStampMs := uint64(1000) + roundTime := uint64(100) + roundHandler := &round.RoundHandlerMock{ + GetTimeStampForRoundCalled: func(round uint64) uint64 { + return genesisTimeStampMs + round*roundTime + }, + } + defaultCfg := config.ExecutionResultInclusionEstimatorConfig{ + SafetyMargin: 110, + MaxResultsPerBlock: 10, + } + defaultErie, _ := estimator.NewExecutionResultInclusionEstimator(defaultCfg, roundHandler, newExecResultSizeComputationHandler()) + roundNow := uint64(3) + + t.Run("Empty pending", func(t *testing.T) { + t.Parallel() + cfg := config.ExecutionResultInclusionEstimatorConfig{SafetyMargin: 110, MaxResultsPerBlock: 10} + erie, _ := estimator.NewExecutionResultInclusionEstimator(cfg, roundHandler, newExecResultSizeComputationHandler()) + lastNotarised := &common.LastExecutionResultForInclusion{ + NotarizedInRound: 1, + ProposedInRound: 0, + } + var pending []data.BaseExecutionResultHandler + wantAllowed := 0 + got := erie.Decide(lastNotarised, pending, roundNow) + require.Equal(t, wantAllowed, got, fmt.Sprintf("Decide() = %d, want %d", got, wantAllowed)) + }) + + t.Run("Accept all items", func(t *testing.T) { + t.Parallel() + lastNotarised := &common.LastExecutionResultForInclusion{ + NotarizedInRound: 1, + ProposedInRound: 0, + } + + pending := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 1, HeaderRound: 1, GasUsed: 100}}, + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 2, HeaderRound: 2, GasUsed: 200}}, + } + wantAllowed := 2 + + got := defaultErie.Decide(lastNotarised, pending, roundNow) + if got != wantAllowed { + t.Errorf("Decide() = %d, want %d", got, wantAllowed) + } + }) + + t.Run("Reject second item", func(t *testing.T) { + t.Parallel() + cfg := config.ExecutionResultInclusionEstimatorConfig{SafetyMargin: 110, MaxResultsPerBlock: 10} + erie, _ := estimator.NewExecutionResultInclusionEstimator(cfg, roundHandler, newExecResultSizeComputationHandler()) + lastNotarised := &common.LastExecutionResultForInclusion{ + NotarizedInRound: 1, + ProposedInRound: 0, + } + pending := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 1, HeaderRound: 1, GasUsed: 100_000_000}}, + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 2, HeaderRound: 2, GasUsed: 999_000_000}}, + } + wantAllowed := 1 + + got := erie.Decide(lastNotarised, pending, roundNow) + if got != wantAllowed { + t.Errorf("Decide() = %d, want %d", got, wantAllowed) + } + }) + + t.Run("Allow only up to boundary (t_done == t_now)", func(t *testing.T) { + t.Parallel() + cfg := config.ExecutionResultInclusionEstimatorConfig{SafetyMargin: 110, MaxResultsPerBlock: 10} + erie, _ := estimator.NewExecutionResultInclusionEstimator(cfg, roundHandler, newExecResultSizeComputationHandler()) + lastNotarised := &common.LastExecutionResultForInclusion{ + NotarizedInRound: 1, + ProposedInRound: 0, + } + pending := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 1, HeaderRound: 1, GasUsed: 100_000_000}}, + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 2, HeaderRound: 2, GasUsed: 100_000_000}}, + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 3, HeaderRound: 3, GasUsed: 100_000_000}}, + } + wantAllowed := 1 + + got := erie.Decide(lastNotarised, pending, roundNow) + require.Equal(t, wantAllowed, got, fmt.Sprintf("Decide() = %d, want %d", got, wantAllowed)) + }) + + t.Run("Hit MaxResultsPerBlock cap", func(t *testing.T) { + t.Parallel() + cfg := config.ExecutionResultInclusionEstimatorConfig{SafetyMargin: 110, MaxResultsPerBlock: 1} + erie, _ := estimator.NewExecutionResultInclusionEstimator(cfg, roundHandler, newExecResultSizeComputationHandler()) + lastNotarised := &common.LastExecutionResultForInclusion{ + NotarizedInRound: 1, + ProposedInRound: 0, + } + pending := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 1, HeaderRound: 1, GasUsed: 100}}, + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 2, HeaderRound: 2, GasUsed: 100}}, + } + currentHdrTsMs := 2000 + genesisTimeStampMs + wantAllowed := 1 + + got := erie.Decide(lastNotarised, pending, currentHdrTsMs) + require.Equal(t, wantAllowed, got, fmt.Sprintf("Decide() = %d, want %d", got, wantAllowed)) + }) + + t.Run("Genesis fallback", func(t *testing.T) { + t.Parallel() + + genesisTimeStampMs := uint64(1_700_000_000_000) + roundHandler := &round.RoundHandlerMock{ + GetTimeStampForRoundCalled: func(round uint64) uint64 { + return genesisTimeStampMs + round*roundTime + }, + } + cfg := config.ExecutionResultInclusionEstimatorConfig{SafetyMargin: 110, MaxResultsPerBlock: 10} + erie, _ := estimator.NewExecutionResultInclusionEstimator(cfg, roundHandler, newExecResultSizeComputationHandler()) + var lastNotarised *common.LastExecutionResultForInclusion = nil + pending := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 1, HeaderRound: 1, GasUsed: 100}}, // after genesis + } + currentHdrTsMs := genesisTimeStampMs + 2*roundTime + wantAllowed := 1 + + got := erie.Decide(lastNotarised, pending, currentHdrTsMs) + require.Equal(t, wantAllowed, got, fmt.Sprintf("Decide() = %d, want %d", got, wantAllowed)) + }) + + t.Run("Overflow protection", func(t *testing.T) { + t.Parallel() + cfg := config.ExecutionResultInclusionEstimatorConfig{SafetyMargin: 110, MaxResultsPerBlock: 10} + erie, _ := estimator.NewExecutionResultInclusionEstimator(cfg, roundHandler, newExecResultSizeComputationHandler()) + lastNotarised := &common.LastExecutionResultForInclusion{ + NotarizedInRound: 1, + ProposedInRound: 0, + } + pending := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 1, HeaderRound: 1, GasUsed: 1000}}, + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 2, HeaderRound: 2, GasUsed: math.MaxUint64}}, + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 1, HeaderRound: 1, GasUsed: 100}}, + } + currentHdrTsMs := uint64(1_700_000_000_000+1000) * 1_000_000 + wantAllowed := 1 + + got := erie.Decide(lastNotarised, pending, currentHdrTsMs) + require.Equal(t, wantAllowed, got, fmt.Sprintf("Decide() = %d, want %d", got, wantAllowed)) + }) +} + +func TestOverflowProtection(t *testing.T) { + t.Parallel() + roundHandler := &round.RoundHandlerMock{ + GetTimeStampForRoundCalled: func(round uint64) uint64 { + return round * 1000 + }, + } + + t.Run("overflow detected in block transactions time estimation - gasUsed * t_gas overflows", func(t *testing.T) { + t.Parallel() + cfg := config.ExecutionResultInclusionEstimatorConfig{ + SafetyMargin: 110, + MaxResultsPerBlock: 10, + } + erie, _ := estimator.NewExecutionResultInclusionEstimator(cfg, roundHandler, newExecResultSizeComputationHandler()) + + pending := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 1, HeaderRound: 1, GasUsed: 1000}}, + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 2, HeaderRound: 2, GasUsed: math.MaxUint64}}, // This will cause overflow + } + + currentRound := uint64(1<<63 - 1) + + numAccepted := erie.Decide(nil, pending, currentRound) + t.Log("num_accepted:", numAccepted) + require.Equal(t, 1, numAccepted, "should only accept first result, then overflow") + }) + + t.Run("overflow detected in estimated time with margin", func(t *testing.T) { + t.Parallel() + cfg := config.ExecutionResultInclusionEstimatorConfig{ + SafetyMargin: 110, // => delta = 10 + MaxResultsPerBlock: 10, + } + + erie, _ := estimator.NewExecutionResultInclusionEstimator(cfg, roundHandler, newExecResultSizeComputationHandler()) + + const nominalSafetyMargin = 100 + safetyMargin := cfg.SafetyMargin - nominalSafetyMargin // 10 + + gasUsed := (math.MaxUint64 / safetyMargin) + 1 + + pending := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 1, HeaderRound: 1, GasUsed: gasUsed}}, + } + + currentRound := uint64(3) + numAccepted := erie.Decide(nil, pending, currentRound) + + require.Equal(t, 0, numAccepted, "should overflow at margin calculation") + }) + + t.Run("overflow detected in total estimated time - accumulated estimatedTime overflows", func(t *testing.T) { + t.Parallel() + cfg := config.ExecutionResultInclusionEstimatorConfig{ + SafetyMargin: 110, + MaxResultsPerBlock: 10, + } + lastNotarised := &common.LastExecutionResultForInclusion{ + NotarizedInRound: uint64(math.MaxUint64 - 3), + ProposedInRound: 0, + } + erie, _ := estimator.NewExecutionResultInclusionEstimator(cfg, roundHandler, newExecResultSizeComputationHandler()) + pending := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 1, HeaderRound: uint64(math.MaxUint64-110) / 1_000_000, GasUsed: 509000000}}, // This will bring estimatedTime close to max in margin calculation + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 2, HeaderRound: 2, GasUsed: 1}}, + } + currentRound := uint64(math.MaxUint64 - 1) + numAccepted := erie.Decide(lastNotarised, pending, currentRound) + t.Log("num_accepted:", numAccepted) + require.Equal(t, 0, numAccepted, "should overflow from the first result") + }) +} + +func TestDecide_EdgeCases(t *testing.T) { + t.Parallel() + + genesisTimeStampMs := uint64(1000) + roundTime := uint64(1000) + roundHandler := &round.RoundHandlerMock{ + GetTimeStampForRoundCalled: func(round uint64) uint64 { + return genesisTimeStampMs + round*roundTime + }, + } + cfg := config.ExecutionResultInclusionEstimatorConfig{ + SafetyMargin: 10, + MaxResultsPerBlock: 10, + } + erie, _ := estimator.NewExecutionResultInclusionEstimator(cfg, roundHandler, newExecResultSizeComputationHandler()) + roundNow := uint64(3) + + t.Run("zero GasUsed", func(t *testing.T) { + t.Parallel() + pending := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 1, HeaderRound: 1, GasUsed: 0}}, + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 2, HeaderRound: 2, GasUsed: 0}}, + } + + got := erie.Decide(nil, pending, roundNow) + require.Equal(t, 2, got) + }) + + t.Run("HeaderTime on genesis time", func(t *testing.T) { + t.Parallel() + + cfg := config.ExecutionResultInclusionEstimatorConfig{SafetyMargin: 110, MaxResultsPerBlock: 10} + erie, _ := estimator.NewExecutionResultInclusionEstimator(cfg, roundHandler, newExecResultSizeComputationHandler()) + pending := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 1, HeaderRound: 0, GasUsed: 100}}, + } + + got := erie.Decide(nil, pending, roundNow) + require.Equal(t, 0, got) + }) + + t.Run("HeaderRound before last notarised", func(t *testing.T) { + t.Parallel() + cfg := config.ExecutionResultInclusionEstimatorConfig{SafetyMargin: 110, MaxResultsPerBlock: 10} + erie, _ := estimator.NewExecutionResultInclusionEstimator(cfg, roundHandler, newExecResultSizeComputationHandler()) + lastNotarised := &common.LastExecutionResultForInclusion{ + NotarizedInRound: 3, + ProposedInRound: 2, + } + pending := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 1, HeaderRound: 1, GasUsed: 100}}, + } + round := uint64(4) + got := erie.Decide(lastNotarised, pending, round) + require.Equal(t, 0, got) + }) + + t.Run("second execution result HeaderTimeMs after current", func(t *testing.T) { + t.Parallel() + pending := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 1, HeaderRound: 1, GasUsed: 100}}, + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 2, HeaderRound: roundNow + 1, GasUsed: 200}}, + } + got := erie.Decide(nil, pending, roundNow) + require.Equal(t, 1, got) // should stop at 2nd + }) + + t.Run("all results after t_now", func(t *testing.T) { + t.Parallel() + pending := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 1, HeaderRound: roundNow + 1, GasUsed: 10_000}}, + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 2, HeaderRound: roundNow + 2, GasUsed: 10_000}}, + } + got := erie.Decide(nil, pending, roundNow) + require.Equal(t, 0, got) + }) + + t.Run("non-monotonic in round", func(t *testing.T) { + t.Parallel() + pending := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 1, HeaderRound: 2, GasUsed: 50}}, + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 2, HeaderRound: 1, GasUsed: 20}}, + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 3, HeaderRound: 3, GasUsed: 70}}, + } + got := erie.Decide(nil, pending, roundNow) + require.Equal(t, 1, got) + }) + + t.Run("non-monotonic in nonce", func(t *testing.T) { + t.Parallel() + pending := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 1, HeaderRound: 1, GasUsed: 50}}, + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 2, HeaderRound: 2, GasUsed: 20}}, + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 4, HeaderRound: 3, GasUsed: 70}}, + } + got := erie.Decide(nil, pending, roundNow) + require.Equal(t, 2, got) + + pending = []data.BaseExecutionResultHandler{ + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 2, HeaderRound: 1, GasUsed: 20}}, + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 1, HeaderRound: 2, GasUsed: 70}}, + } + got = erie.Decide(nil, pending, roundNow) + require.Equal(t, got, 1) + }) +} + +func TestDecide_RoundStartAlignment(t *testing.T) { + t.Parallel() + roundTime := uint64(100) + + t.Run("accept all with start of round alignment", func(t *testing.T) { + t.Parallel() + genesisTimeStampMs := uint64(0) + roundHandler := &round.RoundHandlerMock{ + GetTimeStampForRoundCalled: func(round uint64) uint64 { + return genesisTimeStampMs + round*roundTime + }, + } + roundNow := uint64(5) + cfg := config.ExecutionResultInclusionEstimatorConfig{SafetyMargin: 110, MaxResultsPerBlock: 10} + erie, _ := estimator.NewExecutionResultInclusionEstimator(cfg, roundHandler, newExecResultSizeComputationHandler()) + lastNotarised := &common.LastExecutionResultForInclusion{ + NotarizedInRound: 1, + ProposedInRound: 0, + } + //second execution result finishes in round 4, allignment will take its finish time as base. third small enough to fit so all accepted + pending := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 2, HeaderRound: 2, GasUsed: 50000000}}, + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 3, HeaderRound: 3, GasUsed: 120000000}}, + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 4, HeaderRound: 4, GasUsed: 10000000}}, + } + got := erie.Decide(lastNotarised, pending, roundNow) + require.Equal(t, 3, got) + }) + + t.Run("reject last with start of round alignment", func(t *testing.T) { + t.Parallel() + + genesisTimeStampMs := uint64(0) + roundHandler := &round.RoundHandlerMock{ + GetTimeStampForRoundCalled: func(round uint64) uint64 { + return genesisTimeStampMs + round*roundTime + }, + } + roundNow := uint64(5) + cfg := config.ExecutionResultInclusionEstimatorConfig{SafetyMargin: 110, MaxResultsPerBlock: 10} + erie, _ := estimator.NewExecutionResultInclusionEstimator(cfg, roundHandler, newExecResultSizeComputationHandler()) + lastNotarised := &common.LastExecutionResultForInclusion{ + NotarizedInRound: 1, + ProposedInRound: 0, + } + // third execution result doesn't finish in time since it is available only in round 4 and with margin goes beyond round 5 start + pending := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 2, HeaderRound: 2, GasUsed: 50000000}}, + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 3, HeaderRound: 3, GasUsed: 20000000}}, + &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 4, HeaderRound: 4, GasUsed: 100000000}}, + } + got := erie.Decide(lastNotarised, pending, roundNow) + require.Equal(t, 2, got) + }) +} + +func TestSafetyMargin(t *testing.T) { + roundTime := uint64(100) + genesisTimeStampMs := uint64(0) + roundHandler := &round.RoundHandlerMock{ + GetTimeStampForRoundCalled: func(round uint64) uint64 { + return genesisTimeStampMs + round*roundTime + }, + } + + t.Run("rejected due to safety margin", func(t *testing.T) { + cfg := config.ExecutionResultInclusionEstimatorConfig{ + SafetyMargin: 110, + MaxResultsPerBlock: 10, + } + erie, _ := estimator.NewExecutionResultInclusionEstimator(cfg, roundHandler, newExecResultSizeComputationHandler()) + + pending := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 1, + HeaderRound: 1, + GasUsed: 100_000_000, // 100 ms + }, + }, + } + + numAccepted := erie.Decide(nil, pending, 2) + require.Equal(t, 0, numAccepted, "should reject because safety margin pushes over") + }) + + t.Run("within safety margin", func(t *testing.T) { + cfg := config.ExecutionResultInclusionEstimatorConfig{ + SafetyMargin: 110, // delta = 10 + MaxResultsPerBlock: 10, + } + erie, _ := estimator.NewExecutionResultInclusionEstimator(cfg, roundHandler, newExecResultSizeComputationHandler()) + + gasUsed := uint64(100_000_000 * 100 / 110) // such that with margin it fits exactly 100ms + pending := []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 1, + HeaderRound: 1, + GasUsed: gasUsed, + }, + }, + } + + numAccepted := erie.Decide(nil, pending, 2) + require.Equal(t, 1, numAccepted, "should accept because within safety margin") + }) +} + +func BenchmarkDecideScaling_10(b *testing.B) { + cfg := config.ExecutionResultInclusionEstimatorConfig{SafetyMargin: 110, MaxResultsPerBlock: 10} + roundHandler := &round.RoundHandlerMock{ + GetTimeStampForRoundCalled: func(round uint64) uint64 { + return round * 1000 + }, + } + erie, _ := estimator.NewExecutionResultInclusionEstimator(cfg, roundHandler, newExecResultSizeComputationHandler()) + last := &common.LastExecutionResultForInclusion{ + NotarizedInRound: 0, + ProposedInRound: 0, + } + + b.ReportAllocs() + + for n := 0; n < 10; n++ { + pendingSize := 12 * (1 << n) // 12, 24, 48, ... + pending := make([]data.BaseExecutionResultHandler, pendingSize) + for i := range pending { + pending[i] = &block.ExecutionResult{BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: uint64(i + 1), + HeaderRound: uint64(i + 1), + GasUsed: 1, + }} + } + now := uint64(100) + + b.Run(fmt.Sprintf("%d_results", pendingSize), func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + erie.Decide(last, pending, now) + } + }) + } +} diff --git a/process/estimator/executionResultSizeChecker.go b/process/estimator/executionResultSizeChecker.go new file mode 100644 index 00000000000..d2cba701e29 --- /dev/null +++ b/process/estimator/executionResultSizeChecker.go @@ -0,0 +1,36 @@ +package estimator + +import "sync/atomic" + +type execResSizeLimitChecker struct { + execResSize uint32 + numExecRes uint32 + maxExecResSize uint32 +} + +func newExecResultSizeLimitChecker(execResSize uint32, maxExecResSize uint32) *execResSizeLimitChecker { + return &execResSizeLimitChecker{ + execResSize: execResSize, + maxExecResSize: maxExecResSize, + } +} + +// AddNumExecRes adds the provided value to numExecRes in a concurrent safe manner +func (ers *execResSizeLimitChecker) AddNumExecRes(numExecRes int) { + atomic.AddUint32(&ers.numExecRes, uint32(numExecRes)) +} + +// IsMaxExecResSizeReached returns true if the provided number of execution results exceeds maximum allowed size +func (ers *execResSizeLimitChecker) IsMaxExecResSizeReached(numNewExecRes int) bool { + totalExecRes := atomic.LoadUint32(&ers.numExecRes) + uint32(numNewExecRes) + execResSize := ers.execResSize * totalExecRes + + // TODO: evaluate adding an execution results size throttler as for blocks + + return execResSize > ers.maxExecResSize +} + +// IsInterfaceNil returns true if there is no value under the interface +func (ers *execResSizeLimitChecker) IsInterfaceNil() bool { + return ers == nil +} diff --git a/process/estimator/executionResultSizeComputation.go b/process/estimator/executionResultSizeComputation.go new file mode 100644 index 00000000000..97a4bdd42c8 --- /dev/null +++ b/process/estimator/executionResultSizeComputation.go @@ -0,0 +1,96 @@ +package estimator + +import ( + "crypto/rand" + "math/big" + + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/process" +) + +type execResSizeComputation struct { + execResSize uint32 + maxExecResSize uint32 +} + +// NewExecResultSizeComputationHandler creates a execResSizeComputation instance +func NewExecResultSizeComputationHandler( + marshalizer marshal.Marshalizer, + maxExecResSize uint32, +) (*execResSizeComputation, error) { + if check.IfNil(marshalizer) { + return nil, process.ErrNilMarshalizer + } + + ers := &execResSizeComputation{ + maxExecResSize: maxExecResSize, + } + + var err error + ers.execResSize, err = ers.generateDummyExecutionResultSize(marshalizer, 10) + if err != nil { + return nil, err + } + + return ers, nil +} + +func (ers *execResSizeComputation) generateDummyExecutionResultSize( + marshaller marshal.Marshalizer, + numMbs int, +) (uint32, error) { + dummyHash := make([]byte, 32) + _, _ = rand.Reader.Read(dummyHash) + + bigIntValue, _ := big.NewInt(0).SetString("10000000000000000000", 10) + + executionResult := &block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: dummyHash, + HeaderNonce: 1, + HeaderRound: 2, + HeaderEpoch: 3, + RootHash: dummyHash, + GasUsed: 1234, + }, + ValidatorStatsRootHash: dummyHash, + AccumulatedFeesInEpoch: bigIntValue, + DevFeesInEpoch: bigIntValue, + }, + ReceiptsHash: dummyHash, + ExecutedTxCount: 10, + AccumulatedFees: bigIntValue, + DeveloperFees: bigIntValue, + } + + executionResult.MiniBlockHeaders = make([]block.MiniBlockHeader, numMbs) + for i := 0; i < numMbs; i++ { + executionResult.MiniBlockHeaders[i] = block.MiniBlockHeader{ + Hash: dummyHash, + SenderShardID: 1, + ReceiverShardID: 2, + TxCount: 10, + Type: 1, + } + } + + buff, err := marshaller.Marshal(executionResult) + if err != nil { + return 0, err + } + + return uint32(len(buff)), nil +} + +// NewComputation will create a new size limit checker based on the precalculated execution result size +func (ers *execResSizeComputation) NewComputation() ExecResSizeLimitCheckerHandler { + return newExecResultSizeLimitChecker(ers.execResSize, ers.maxExecResSize) +} + +// IsInterfaceNil returns true if there is no value under the interface +func (ers *execResSizeComputation) IsInterfaceNil() bool { + return ers == nil +} diff --git a/process/estimator/executionResultSizeComputation_test.go b/process/estimator/executionResultSizeComputation_test.go new file mode 100644 index 00000000000..c0c292c97e7 --- /dev/null +++ b/process/estimator/executionResultSizeComputation_test.go @@ -0,0 +1,64 @@ +package estimator_test + +import ( + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/estimator" + "github.com/multiversx/mx-chain-go/process/mock" + "github.com/stretchr/testify/require" +) + +const maxSizeInBytes = uint32(core.MegabyteSize * 10 / 100) + +func TestNewExecResultSizeComputation(t *testing.T) { + t.Parallel() + + t.Run("nil marshaller", func(t *testing.T) { + t.Parallel() + + ers, err := estimator.NewExecResultSizeComputationHandler(nil, maxSizeInBytes) + require.Nil(t, ers) + require.Equal(t, process.ErrNilMarshalizer, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + ers, err := estimator.NewExecResultSizeComputationHandler(&mock.ProtobufMarshalizerMock{}, maxSizeInBytes) + require.Nil(t, err) + require.NotNil(t, ers) + }) +} + +func TestExecResultSizeComputation_IsMaxExecResSizeReached(t *testing.T) { + t.Parallel() + + ers, _ := estimator.NewExecResultSizeComputationHandler( + &mock.ProtobufMarshalizerMock{}, + maxSizeInBytes, + ) + + ersl := ers.NewComputation() + + testData := []struct { + numNewExecRes int + expected bool + name string + }{ + {numNewExecRes: 0, expected: false, name: "with numExecRes 0"}, + {numNewExecRes: 10, expected: false, name: "with numExecRes 10"}, + {numNewExecRes: 30, expected: false, name: "with numExecRes 30"}, + {numNewExecRes: 150, expected: false, name: "with numExecRes 150"}, + {numNewExecRes: 200, expected: true, name: "with numExecRes 200"}, + {numNewExecRes: 1000, expected: true, name: "with numExecRes 1000"}, + {numNewExecRes: 10000, expected: true, name: "with numExecRes 10000"}, + } + + for _, td := range testData { + t.Run(td.name, func(t *testing.T) { + require.Equal(t, td.expected, ersl.IsMaxExecResSizeReached(td.numNewExecRes)) + }) + } +} diff --git a/process/estimator/interface.go b/process/estimator/interface.go new file mode 100644 index 00000000000..6964bd686a3 --- /dev/null +++ b/process/estimator/interface.go @@ -0,0 +1,14 @@ +package estimator + +// ExecResSizeComputationHandler defines the behaviour of a component that is able to spin new encapsulated execution results size limit checker +type ExecResSizeComputationHandler interface { + NewComputation() ExecResSizeLimitCheckerHandler + IsInterfaceNil() bool +} + +// ExecResSizeLimitCheckerHandler defines the behaviour of a size limit checker +type ExecResSizeLimitCheckerHandler interface { + AddNumExecRes(numExecRes int) + IsMaxExecResSizeReached(numNewExecRes int) bool + IsInterfaceNil() bool +} diff --git a/process/factory/containers/preProcessorsContainer_test.go b/process/factory/containers/preProcessorsContainer_test.go index d0228bafb74..3d3589f6e4a 100644 --- a/process/factory/containers/preProcessorsContainer_test.go +++ b/process/factory/containers/preProcessorsContainer_test.go @@ -5,10 +5,12 @@ import ( "testing" "github.com/multiversx/mx-chain-core-go/data/block" + + "github.com/stretchr/testify/assert" + "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/factory/containers" - "github.com/multiversx/mx-chain-go/process/mock" - "github.com/stretchr/testify/assert" + "github.com/multiversx/mx-chain-go/testscommon/preprocMocks" ) func TestNewPreProcessorsContainer_ShouldWork(t *testing.T) { @@ -20,15 +22,15 @@ func TestNewPreProcessorsContainer_ShouldWork(t *testing.T) { assert.False(t, c.IsInterfaceNil()) } -//------- Add +// ------- Add func TestPreProcessorsContainer_AddAlreadyExistingShouldErr(t *testing.T) { t.Parallel() c := containers.NewPreProcessorsContainer() - _ = c.Add(block.TxBlock, &mock.PreProcessorMock{}) - err := c.Add(block.TxBlock, &mock.PreProcessorMock{}) + _ = c.Add(block.TxBlock, &preprocMocks.PreProcessorMock{}) + err := c.Add(block.TxBlock, &preprocMocks.PreProcessorMock{}) assert.Equal(t, process.ErrContainerKeyAlreadyExists, err) } @@ -48,13 +50,13 @@ func TestPreProcessorsContainer_AddShouldWork(t *testing.T) { c := containers.NewPreProcessorsContainer() - err := c.Add(block.TxBlock, &mock.PreProcessorMock{}) + err := c.Add(block.TxBlock, &preprocMocks.PreProcessorMock{}) assert.Nil(t, err) assert.Equal(t, 1, c.Len()) } -//------- AddMultiple +// ------- AddMultiple func TestPreProcessorsContainer_AddMultipleAlreadyExistingShouldErr(t *testing.T) { t.Parallel() @@ -62,7 +64,7 @@ func TestPreProcessorsContainer_AddMultipleAlreadyExistingShouldErr(t *testing.T c := containers.NewPreProcessorsContainer() keys := []block.Type{block.TxBlock, block.TxBlock} - preprocessors := []process.PreProcessor{&mock.PreProcessorMock{}, &mock.PreProcessorMock{}} + preprocessors := []process.PreProcessor{&preprocMocks.PreProcessorMock{}, &preprocMocks.PreProcessorMock{}} err := c.AddMultiple(keys, preprocessors) @@ -75,7 +77,7 @@ func TestPreProcessorsContainer_AddMultipleLenMismatchShouldErr(t *testing.T) { c := containers.NewPreProcessorsContainer() keys := []block.Type{block.TxBlock} - preprocessors := []process.PreProcessor{&mock.PreProcessorMock{}, &mock.PreProcessorMock{}} + preprocessors := []process.PreProcessor{&preprocMocks.PreProcessorMock{}, &preprocMocks.PreProcessorMock{}} err := c.AddMultiple(keys, preprocessors) @@ -88,7 +90,7 @@ func TestPreProcessorsContainer_AddMultipleShouldWork(t *testing.T) { c := containers.NewPreProcessorsContainer() keys := []block.Type{block.TxBlock, block.SmartContractResultBlock} - preprocessors := []process.PreProcessor{&mock.PreProcessorMock{}, &mock.PreProcessorMock{}} + preprocessors := []process.PreProcessor{&preprocMocks.PreProcessorMock{}, &preprocMocks.PreProcessorMock{}} err := c.AddMultiple(keys, preprocessors) @@ -96,7 +98,7 @@ func TestPreProcessorsContainer_AddMultipleShouldWork(t *testing.T) { assert.Equal(t, 2, c.Len()) } -//------- Get +// ------- Get func TestPreProcessorsContainer_GetNotFoundShouldErr(t *testing.T) { t.Parallel() @@ -105,7 +107,7 @@ func TestPreProcessorsContainer_GetNotFoundShouldErr(t *testing.T) { key := block.TxBlock keyNotFound := block.SmartContractResultBlock - val := &mock.PreProcessorMock{} + val := &preprocMocks.PreProcessorMock{} _ = c.Add(key, val) valRecovered, err := c.Get(keyNotFound) @@ -134,7 +136,7 @@ func TestPreProcessorsContainer_GetShouldWork(t *testing.T) { c := containers.NewPreProcessorsContainer() key := block.TxBlock - val := &mock.PreProcessorMock{} + val := &preprocMocks.PreProcessorMock{} _ = c.Add(key, val) valRecovered, err := c.Get(key) @@ -143,7 +145,7 @@ func TestPreProcessorsContainer_GetShouldWork(t *testing.T) { assert.Nil(t, err) } -//------- Replace +// ------- Replace func TestPreProcessorsContainer_ReplaceNilValueShouldErrAndNotModify(t *testing.T) { t.Parallel() @@ -151,7 +153,7 @@ func TestPreProcessorsContainer_ReplaceNilValueShouldErrAndNotModify(t *testing. c := containers.NewPreProcessorsContainer() key := block.TxBlock - val := &mock.PreProcessorMock{} + val := &preprocMocks.PreProcessorMock{} _ = c.Add(key, val) err := c.Replace(key, nil) @@ -168,8 +170,8 @@ func TestPreProcessorsContainer_ReplaceShouldWork(t *testing.T) { c := containers.NewPreProcessorsContainer() key := block.TxBlock - val := &mock.PreProcessorMock{} - val2 := &mock.PreProcessorMock{} + val := &preprocMocks.PreProcessorMock{} + val2 := &preprocMocks.PreProcessorMock{} _ = c.Add(key, val) err := c.Replace(key, val2) @@ -180,7 +182,7 @@ func TestPreProcessorsContainer_ReplaceShouldWork(t *testing.T) { assert.Nil(t, err) } -//------- Remove +// ------- Remove func TestPreProcessorsContainer_RemoveShouldWork(t *testing.T) { t.Parallel() @@ -188,7 +190,7 @@ func TestPreProcessorsContainer_RemoveShouldWork(t *testing.T) { c := containers.NewPreProcessorsContainer() key := block.TxBlock - val := &mock.PreProcessorMock{} + val := &preprocMocks.PreProcessorMock{} _ = c.Add(key, val) c.Remove(key) @@ -199,21 +201,21 @@ func TestPreProcessorsContainer_RemoveShouldWork(t *testing.T) { assert.True(t, errors.Is(err, process.ErrInvalidContainerKey)) } -//------- Len +// ------- Len func TestPreProcessorsContainer_LenShouldWork(t *testing.T) { t.Parallel() c := containers.NewPreProcessorsContainer() - _ = c.Add(block.TxBlock, &mock.PreProcessorMock{}) + _ = c.Add(block.TxBlock, &preprocMocks.PreProcessorMock{}) assert.Equal(t, 1, c.Len()) keys := c.Keys() assert.Equal(t, 1, len(keys)) assert.Equal(t, block.TxBlock, keys[0]) - _ = c.Add(block.PeerBlock, &mock.PreProcessorMock{}) + _ = c.Add(block.PeerBlock, &preprocMocks.PreProcessorMock{}) assert.Equal(t, 2, c.Len()) keys = c.Keys() diff --git a/process/factory/interceptorscontainer/args.go b/process/factory/interceptorscontainer/args.go index 8e98c7c18ab..40a9910cca8 100644 --- a/process/factory/interceptorscontainer/args.go +++ b/process/factory/interceptorscontainer/args.go @@ -2,6 +2,7 @@ package interceptorscontainer import ( crypto "github.com/multiversx/mx-chain-crypto-go" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" @@ -45,4 +46,5 @@ type CommonInterceptorsContainerFactoryArgs struct { HardforkTrigger heartbeat.HardforkTrigger NodeOperationMode common.NodeOperation InterceptedDataVerifierFactory process.InterceptedDataVerifierFactory + Config config.Config } diff --git a/process/factory/interceptorscontainer/baseInterceptorsContainerFactory.go b/process/factory/interceptorscontainer/baseInterceptorsContainerFactory.go index bdd6ea118e1..3a4748bc58e 100644 --- a/process/factory/interceptorscontainer/baseInterceptorsContainerFactory.go +++ b/process/factory/interceptorscontainer/baseInterceptorsContainerFactory.go @@ -7,8 +7,8 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/hashing" - "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/heartbeat" "github.com/multiversx/mx-chain-go/process" @@ -21,6 +21,8 @@ import ( "github.com/multiversx/mx-chain-go/sharding/nodesCoordinator" "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/storage" + "github.com/multiversx/mx-chain-go/storage/cache" + "github.com/multiversx/mx-chain-go/storage/disabled" ) const ( @@ -57,6 +59,7 @@ type baseInterceptorsContainerFactory struct { nodeOperationMode common.NodeOperation interceptedDataVerifierFactory process.InterceptedDataVerifierFactory enableEpochsHandler common.EnableEpochsHandler + config config.Config } func checkBaseParams( @@ -216,7 +219,7 @@ func createTopicAndAssignHandlerOnMessenger( return messenger.RegisterMessageProcessor(topic, common.DefaultInterceptorsIdentifier, interceptor) } -// ------- Tx interceptors +// ------- tx interceptors func (bicf *baseInterceptorsContainerFactory) generateTxInterceptors() error { shardC := bicf.shardCoordinator @@ -229,7 +232,8 @@ func (bicf *baseInterceptorsContainerFactory) generateTxInterceptors() error { for idx := uint32(0); idx < noOfShards; idx++ { identifierTx := factory.TransactionTopic + shardC.CommunicationIdentifier(idx) - interceptor, err := bicf.createOneTxInterceptor(identifierTx) + isCrossShard := idx != shardC.SelfId() + interceptor, err := bicf.createOneTxInterceptor(identifierTx, isCrossShard) if err != nil { return err } @@ -241,7 +245,8 @@ func (bicf *baseInterceptorsContainerFactory) generateTxInterceptors() error { // tx interceptor for metachain topic identifierTx := factory.TransactionTopic + shardC.CommunicationIdentifier(core.MetachainShardId) - interceptor, err := bicf.createOneTxInterceptor(identifierTx) + isCrossShard := core.MetachainShardId != shardC.SelfId() + interceptor, err := bicf.createOneTxInterceptor(identifierTx, isCrossShard) if err != nil { return err } @@ -252,7 +257,7 @@ func (bicf *baseInterceptorsContainerFactory) generateTxInterceptors() error { return bicf.addInterceptorsToContainers(keys, interceptorSlice) } -func (bicf *baseInterceptorsContainerFactory) createOneTxInterceptor(topic string) (process.Interceptor, error) { +func (bicf *baseInterceptorsContainerFactory) createOneTxInterceptor(topic string, isCrossShard bool) (process.Interceptor, error) { if bicf.argInterceptorFactory == nil { return nil, process.ErrNilArgumentStruct } @@ -268,6 +273,7 @@ func (bicf *baseInterceptorsContainerFactory) createOneTxInterceptor(topic strin bicf.whiteListHandler, addrPubKeyConverter, bicf.argInterceptorFactory.CoreComponents.TxVersionChecker(), + bicf.enableEpochsHandler, bicf.maxTxNonceDeltaAllowed, ) if err != nil { @@ -275,8 +281,9 @@ func (bicf *baseInterceptorsContainerFactory) createOneTxInterceptor(topic strin } argProcessor := &processor.ArgTxInterceptorProcessor{ - ShardedDataCache: bicf.dataPool.Transactions(), - TxValidator: txValidator, + ShardedDataCache: bicf.dataPool.Transactions(), + TxValidator: txValidator, + DirectSentTransactionsCache: bicf.dataPool.DirectSentTransactions(), } txProcessor, err := processor.NewTxInterceptorProcessor(argProcessor) if err != nil { @@ -307,16 +314,24 @@ func (bicf *baseInterceptorsContainerFactory) createOneTxInterceptor(topic strin CurrentPeerId: bicf.mainMessenger.ID(), PreferredPeersHolder: bicf.preferredPeersHolder, InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: bicf.argInterceptorFactory.CryptoComponents.ManagedPeersHolder(), }, ) if err != nil { return nil, err } + if isCrossShard { + err = bicf.setUniqueChunksProcessor(interceptor) + if err != nil { + return nil, err + } + } + return bicf.createTopicAndAssignHandler(topic, interceptor, true) } -func (bicf *baseInterceptorsContainerFactory) createOneUnsignedTxInterceptor(topic string) (process.Interceptor, error) { +func (bicf *baseInterceptorsContainerFactory) createOneUnsignedTxInterceptor(topic string, isCrossShard bool) (process.Interceptor, error) { if bicf.argInterceptorFactory == nil { return nil, process.ErrNilArgumentStruct } @@ -325,8 +340,9 @@ func (bicf *baseInterceptorsContainerFactory) createOneUnsignedTxInterceptor(top } argProcessor := &processor.ArgTxInterceptorProcessor{ - ShardedDataCache: bicf.dataPool.UnsignedTransactions(), - TxValidator: dataValidators.NewDisabledTxValidator(), + ShardedDataCache: bicf.dataPool.UnsignedTransactions(), + TxValidator: dataValidators.NewDisabledTxValidator(), + DirectSentTransactionsCache: bicf.dataPool.DirectSentTransactions(), } txProcessor, err := processor.NewTxInterceptorProcessor(argProcessor) if err != nil { @@ -357,16 +373,24 @@ func (bicf *baseInterceptorsContainerFactory) createOneUnsignedTxInterceptor(top CurrentPeerId: bicf.mainMessenger.ID(), PreferredPeersHolder: bicf.preferredPeersHolder, InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: bicf.argInterceptorFactory.CryptoComponents.ManagedPeersHolder(), }, ) if err != nil { return nil, err } + if isCrossShard { + err = bicf.setUniqueChunksProcessor(interceptor) + if err != nil { + return nil, err + } + } + return bicf.createTopicAndAssignHandler(topic, interceptor, true) } -func (bicf *baseInterceptorsContainerFactory) createOneRewardTxInterceptor(topic string) (process.Interceptor, error) { +func (bicf *baseInterceptorsContainerFactory) createOneRewardTxInterceptor(topic string, isCrossShard bool) (process.Interceptor, error) { if bicf.argInterceptorFactory == nil { return nil, process.ErrNilArgumentStruct } @@ -375,8 +399,9 @@ func (bicf *baseInterceptorsContainerFactory) createOneRewardTxInterceptor(topic } argProcessor := &processor.ArgTxInterceptorProcessor{ - ShardedDataCache: bicf.dataPool.RewardTransactions(), - TxValidator: dataValidators.NewDisabledTxValidator(), + ShardedDataCache: bicf.dataPool.RewardTransactions(), + TxValidator: dataValidators.NewDisabledTxValidator(), + DirectSentTransactionsCache: bicf.dataPool.DirectSentTransactions(), } txProcessor, err := processor.NewTxInterceptorProcessor(argProcessor) if err != nil { @@ -407,12 +432,20 @@ func (bicf *baseInterceptorsContainerFactory) createOneRewardTxInterceptor(topic CurrentPeerId: bicf.mainMessenger.ID(), PreferredPeersHolder: bicf.preferredPeersHolder, InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: bicf.argInterceptorFactory.CryptoComponents.ManagedPeersHolder(), }, ) if err != nil { return nil, err } + if isCrossShard { + err = bicf.setUniqueChunksProcessor(interceptor) + if err != nil { + return nil, err + } + } + return bicf.createTopicAndAssignHandler(topic, interceptor, true) } @@ -457,6 +490,7 @@ func (bicf *baseInterceptorsContainerFactory) generateHeaderInterceptors() error CurrentPeerId: bicf.mainMessenger.ID(), PreferredPeersHolder: bicf.preferredPeersHolder, InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: bicf.argInterceptorFactory.CryptoComponents.ManagedPeersHolder(), }, ) if err != nil { @@ -482,7 +516,8 @@ func (bicf *baseInterceptorsContainerFactory) generateMiniBlocksInterceptors() e for idx := uint32(0); idx < noOfShards; idx++ { identifierMiniBlocks := factory.MiniBlocksTopic + shardC.CommunicationIdentifier(idx) - interceptor, err := bicf.createOneMiniBlocksInterceptor(identifierMiniBlocks) + isCrossShard := idx != shardC.SelfId() + interceptor, err := bicf.createOneMiniBlocksInterceptor(identifierMiniBlocks, isCrossShard) if err != nil { return err } @@ -493,7 +528,8 @@ func (bicf *baseInterceptorsContainerFactory) generateMiniBlocksInterceptors() e identifierMiniBlocks := factory.MiniBlocksTopic + shardC.CommunicationIdentifier(core.MetachainShardId) - interceptor, err := bicf.createOneMiniBlocksInterceptor(identifierMiniBlocks) + isCrossShard := core.MetachainShardId != shardC.SelfId() + interceptor, err := bicf.createOneMiniBlocksInterceptor(identifierMiniBlocks, isCrossShard) if err != nil { return err } @@ -503,7 +539,7 @@ func (bicf *baseInterceptorsContainerFactory) generateMiniBlocksInterceptors() e identifierAllShardsMiniBlocks := factory.MiniBlocksTopic + shardC.CommunicationIdentifier(core.AllShardId) - allShardsMiniBlocksInterceptor, err := bicf.createOneMiniBlocksInterceptor(identifierAllShardsMiniBlocks) + allShardsMiniBlocksInterceptor, err := bicf.createOneMiniBlocksInterceptor(identifierAllShardsMiniBlocks, true) if err != nil { return err } @@ -514,7 +550,7 @@ func (bicf *baseInterceptorsContainerFactory) generateMiniBlocksInterceptors() e return bicf.addInterceptorsToContainers(keys, interceptorsSlice) } -func (bicf *baseInterceptorsContainerFactory) createOneMiniBlocksInterceptor(topic string) (process.Interceptor, error) { +func (bicf *baseInterceptorsContainerFactory) createOneMiniBlocksInterceptor(topic string, isCrossShard bool) (process.Interceptor, error) { internalMarshaller := bicf.argInterceptorFactory.CoreComponents.InternalMarshalizer() hasher := bicf.argInterceptorFactory.CoreComponents.Hasher() argProcessor := &processor.ArgMiniblockInterceptorProcessor{ @@ -552,15 +588,50 @@ func (bicf *baseInterceptorsContainerFactory) createOneMiniBlocksInterceptor(top CurrentPeerId: bicf.mainMessenger.ID(), PreferredPeersHolder: bicf.preferredPeersHolder, InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: bicf.argInterceptorFactory.CryptoComponents.ManagedPeersHolder(), }, ) if err != nil { return nil, err } + if isCrossShard { + err = bicf.setUniqueChunksProcessor(interceptor) + if err != nil { + return nil, err + } + } + return bicf.createTopicAndAssignHandler(topic, interceptor, true) } +func (bicf *baseInterceptorsContainerFactory) setUniqueChunksProcessor(interceptor *interceptors.MultiDataInterceptor) error { + internalMarshaller := bicf.argInterceptorFactory.CoreComponents.InternalMarshalizer() + + chunksCache, err := bicf.createCache() + if err != nil { + return err + } + + chunkProcessor, err := processor.NewUniqueChunksProcessor(chunksCache, internalMarshaller, bicf.hasher) + if err != nil { + return err + } + + return interceptor.SetChunkProcessor(chunkProcessor) +} + +func (bicf *baseInterceptorsContainerFactory) createCache() (storage.Cacher, error) { + if !bicf.config.InterceptedDataVerifier.EnableCaching { + return disabled.NewCache(), nil + } + + return cache.NewTimeCacher(cache.ArgTimeCacher{ + DefaultSpan: time.Duration(bicf.config.InterceptedDataVerifier.CacheSpanInSec) * time.Second, + CacheExpiry: time.Duration(bicf.config.InterceptedDataVerifier.CacheExpiryInSec) * time.Second, + }) +} + // ------- MetachainHeader interceptors func (bicf *baseInterceptorsContainerFactory) generateMetachainHeaderInterceptors() error { @@ -602,6 +673,7 @@ func (bicf *baseInterceptorsContainerFactory) generateMetachainHeaderInterceptor CurrentPeerId: bicf.mainMessenger.ID(), PreferredPeersHolder: bicf.preferredPeersHolder, InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: bicf.argInterceptorFactory.CryptoComponents.ManagedPeersHolder(), }, ) if err != nil { @@ -646,6 +718,7 @@ func (bicf *baseInterceptorsContainerFactory) createOneTrieNodesInterceptor(topi CurrentPeerId: bicf.mainMessenger.ID(), PreferredPeersHolder: bicf.preferredPeersHolder, InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: bicf.argInterceptorFactory.CryptoComponents.ManagedPeersHolder(), }, ) if err != nil { @@ -683,7 +756,8 @@ func (bicf *baseInterceptorsContainerFactory) generateUnsignedTxsInterceptors() for idx := uint32(0); idx < noOfShards; idx++ { identifierScr := factory.UnsignedTransactionTopic + shardC.CommunicationIdentifier(idx) - interceptor, err := bicf.createOneUnsignedTxInterceptor(identifierScr) + isCrossShard := idx != shardC.SelfId() + interceptor, err := bicf.createOneUnsignedTxInterceptor(identifierScr, isCrossShard) if err != nil { return err } @@ -693,7 +767,8 @@ func (bicf *baseInterceptorsContainerFactory) generateUnsignedTxsInterceptors() } identifierScr := factory.UnsignedTransactionTopic + shardC.CommunicationIdentifier(core.MetachainShardId) - interceptor, err := bicf.createOneUnsignedTxInterceptor(identifierScr) + isCrossShard := core.MetachainShardId != shardC.SelfId() + interceptor, err := bicf.createOneUnsignedTxInterceptor(identifierScr, isCrossShard) if err != nil { return err } @@ -744,6 +819,7 @@ func (bicf *baseInterceptorsContainerFactory) generatePeerAuthenticationIntercep PreferredPeersHolder: bicf.preferredPeersHolder, CurrentPeerId: bicf.mainMessenger.ID(), InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: bicf.argInterceptorFactory.CryptoComponents.ManagedPeersHolder(), }, ) if err != nil { @@ -808,6 +884,7 @@ func (bicf *baseInterceptorsContainerFactory) createHeartbeatV2Interceptor( PreferredPeersHolder: bicf.preferredPeersHolder, CurrentPeerId: bicf.mainMessenger.ID(), InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: bicf.argInterceptorFactory.CryptoComponents.ManagedPeersHolder(), }, ) if err != nil { @@ -863,6 +940,7 @@ func (bicf *baseInterceptorsContainerFactory) createPeerShardInterceptor( CurrentPeerId: bicf.mainMessenger.ID(), PreferredPeersHolder: bicf.preferredPeersHolder, InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: bicf.argInterceptorFactory.CryptoComponents.ManagedPeersHolder(), }, ) if err != nil { @@ -908,12 +986,18 @@ func (bicf *baseInterceptorsContainerFactory) generateValidatorInfoInterceptor() PreferredPeersHolder: bicf.preferredPeersHolder, CurrentPeerId: bicf.mainMessenger.ID(), InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: bicf.argInterceptorFactory.CryptoComponents.ManagedPeersHolder(), }, ) if err != nil { return err } + err = bicf.setUniqueChunksProcessor(mdInterceptor) + if err != nil { + return err + } + interceptor, err := bicf.createTopicAndAssignHandler(identifier, mdInterceptor, true) if err != nil { return err @@ -945,6 +1029,7 @@ func (bicf *baseInterceptorsContainerFactory) createOneShardEquivalentProofsInte CurrentPeerId: bicf.mainMessenger.ID(), PreferredPeersHolder: bicf.preferredPeersHolder, InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: bicf.argInterceptorFactory.CryptoComponents.ManagedPeersHolder(), }, ) if err != nil { diff --git a/process/factory/interceptorscontainer/metaInterceptorsContainerFactory.go b/process/factory/interceptorscontainer/metaInterceptorsContainerFactory.go index 8f6b8fc6b0a..174e16e0a5f 100644 --- a/process/factory/interceptorscontainer/metaInterceptorsContainerFactory.go +++ b/process/factory/interceptorscontainer/metaInterceptorsContainerFactory.go @@ -130,6 +130,7 @@ func NewMetaInterceptorsContainerFactory( nodeOperationMode: args.NodeOperationMode, interceptedDataVerifierFactory: args.InterceptedDataVerifierFactory, enableEpochsHandler: args.CoreComponents.EnableEpochsHandler(), + config: args.Config, } icf := &metaInterceptorsContainerFactory{ @@ -234,7 +235,7 @@ func (micf *metaInterceptorsContainerFactory) AddShardTrieNodeInterceptors(conta return container.AddMultiple(keys, trieInterceptors) } -//------- Shard header interceptors +// ------- Shard header interceptors func (micf *metaInterceptorsContainerFactory) generateShardHeaderInterceptors() error { shardC := micf.shardCoordinator @@ -242,7 +243,7 @@ func (micf *metaInterceptorsContainerFactory) generateShardHeaderInterceptors() keys := make([]string, noOfShards) interceptorsSlice := make([]process.Interceptor, noOfShards) - //wire up to topics: shardBlocks_0_META, shardBlocks_1_META ... + // wire up to topics: shardBlocks_0_META, shardBlocks_1_META ... for idx := uint32(0); idx < noOfShards; idx++ { identifierHeader := factory.ShardBlocksTopic + shardC.CommunicationIdentifier(idx) interceptor, err := micf.createOneShardHeaderInterceptor(identifierHeader) @@ -290,6 +291,7 @@ func (micf *metaInterceptorsContainerFactory) createOneShardHeaderInterceptor(to CurrentPeerId: micf.mainMessenger.ID(), PreferredPeersHolder: micf.preferredPeersHolder, InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: micf.argInterceptorFactory.CryptoComponents.ManagedPeersHolder(), }, ) if err != nil { @@ -324,7 +326,7 @@ func (micf *metaInterceptorsContainerFactory) generateTrieNodesInterceptors() er return micf.addInterceptorsToContainers(keys, trieInterceptors) } -//------- Reward transactions interceptors +// ------- Reward transactions interceptors func (micf *metaInterceptorsContainerFactory) generateRewardTxInterceptors() error { shardC := micf.shardCoordinator @@ -336,7 +338,7 @@ func (micf *metaInterceptorsContainerFactory) generateRewardTxInterceptors() err for idx := uint32(0); idx < noOfShards; idx++ { identifierScr := factory.RewardsTransactionTopic + shardC.CommunicationIdentifier(idx) - interceptor, err := micf.createOneRewardTxInterceptor(identifierScr) + interceptor, err := micf.createOneRewardTxInterceptor(identifierScr, true) if err != nil { return err } diff --git a/process/factory/interceptorscontainer/metaInterceptorsContainerFactory_test.go b/process/factory/interceptorscontainer/metaInterceptorsContainerFactory_test.go index eafb147747a..7e0af6f9bb8 100644 --- a/process/factory/interceptorscontainer/metaInterceptorsContainerFactory_test.go +++ b/process/factory/interceptorscontainer/metaInterceptorsContainerFactory_test.go @@ -5,6 +5,7 @@ import ( "strings" "testing" + "github.com/multiversx/mx-chain-go/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -83,6 +84,9 @@ func createMetaDataPools() dataRetriever.PoolsHolder { ProofsCalled: func() dataRetriever.ProofsPool { return &dataRetrieverMock.ProofsPoolMock{} }, + DirectSentTransactionsCalled: func() storage.Cacher { + return cache.NewCacherStub() + }, } return pools @@ -736,5 +740,11 @@ func getArgumentsMeta( HardforkTrigger: &testscommon.HardforkTriggerStub{}, NodeOperationMode: common.NormalOperation, InterceptedDataVerifierFactory: &mock.InterceptedDataVerifierFactoryMock{}, + Config: config.Config{ + InterceptedDataVerifier: config.InterceptedDataVerifierConfig{ + CacheSpanInSec: 1, + CacheExpiryInSec: 1, + }, + }, } } diff --git a/process/factory/interceptorscontainer/shardInterceptorsContainerFactory.go b/process/factory/interceptorscontainer/shardInterceptorsContainerFactory.go index d144113d30f..1922c3619db 100644 --- a/process/factory/interceptorscontainer/shardInterceptorsContainerFactory.go +++ b/process/factory/interceptorscontainer/shardInterceptorsContainerFactory.go @@ -131,6 +131,7 @@ func NewShardInterceptorsContainerFactory( nodeOperationMode: args.NodeOperationMode, interceptedDataVerifierFactory: args.InterceptedDataVerifierFactory, enableEpochsHandler: args.CoreComponents.EnableEpochsHandler(), + config: args.Config, } icf := &shardInterceptorsContainerFactory{ @@ -237,7 +238,7 @@ func (sicf *shardInterceptorsContainerFactory) generateRewardTxInterceptor() err interceptorSlice := make([]process.Interceptor, 0) identifierTx := factory.RewardsTransactionTopic + shardC.CommunicationIdentifier(core.MetachainShardId) - interceptor, err := sicf.createOneRewardTxInterceptor(identifierTx) + interceptor, err := sicf.createOneRewardTxInterceptor(identifierTx, true) if err != nil { return err } diff --git a/process/factory/interceptorscontainer/shardInterceptorsContainerFactory_test.go b/process/factory/interceptorscontainer/shardInterceptorsContainerFactory_test.go index b72d32ad037..2d80286da1f 100644 --- a/process/factory/interceptorscontainer/shardInterceptorsContainerFactory_test.go +++ b/process/factory/interceptorscontainer/shardInterceptorsContainerFactory_test.go @@ -96,6 +96,9 @@ func createShardDataPools() dataRetriever.PoolsHolder { pools.ProofsCalled = func() dataRetriever.ProofsPool { return &dataRetrieverMock.ProofsPoolMock{} } + pools.DirectSentTransactionsCalled = func() storage.Cacher { + return cache.NewCacherStub() + } return pools } @@ -719,6 +722,7 @@ func createMockComponentHolders() (*mock.CoreComponentsMock, *mock.CryptoCompone HardforkTriggerPubKeyField: providedHardforkPubKey, EnableEpochsHandlerField: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, EpochChangeGracePeriodHandlerField: gracePeriod, + ProcessConfigsHandlerField: testscommon.GetDefaultProcessConfigsHandler(), } multiSigner := cryptoMocks.NewMultiSigner() cryptoComponents := &mock.CryptoComponentsMock{ @@ -727,6 +731,7 @@ func createMockComponentHolders() (*mock.CoreComponentsMock, *mock.CryptoCompone MultiSigContainer: cryptoMocks.NewMultiSignerContainerMock(multiSigner), BlKeyGen: &mock.SingleSignKeyGenMock{}, TxKeyGen: &mock.SingleSignKeyGenMock{}, + ManagedPeers: &testscommon.ManagedPeersHolderStub{}, } return coreComponents, cryptoComponents @@ -767,5 +772,11 @@ func getArgumentsShard( FullArchivePeerShardMapper: &p2pmocks.NetworkShardingCollectorStub{}, HardforkTrigger: &testscommon.HardforkTriggerStub{}, InterceptedDataVerifierFactory: &mock.InterceptedDataVerifierFactoryMock{}, + Config: config.Config{ + InterceptedDataVerifier: config.InterceptedDataVerifierConfig{ + CacheSpanInSec: 1, + CacheExpiryInSec: 1, + }, + }, } } diff --git a/process/factory/metachain/intermediateProcessorsContainerFactory.go b/process/factory/metachain/intermediateProcessorsContainerFactory.go index c9584623c91..8a7b503981c 100644 --- a/process/factory/metachain/intermediateProcessorsContainerFactory.go +++ b/process/factory/metachain/intermediateProcessorsContainerFactory.go @@ -6,6 +6,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" diff --git a/process/factory/metachain/intermediateProcessorsContainerFactory_test.go b/process/factory/metachain/intermediateProcessorsContainerFactory_test.go index d99f4361905..c1c4c3507cf 100644 --- a/process/factory/metachain/intermediateProcessorsContainerFactory_test.go +++ b/process/factory/metachain/intermediateProcessorsContainerFactory_test.go @@ -3,6 +3,8 @@ package metachain_test import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/factory/metachain" @@ -14,7 +16,6 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" - "github.com/stretchr/testify/assert" ) func createMockPubkeyConverter() *testscommon.PubkeyConverterMock { diff --git a/process/factory/metachain/preProcessorsContainerFactory.go b/process/factory/metachain/preProcessorsContainerFactory.go index 4354a80ab1e..277b96187e5 100644 --- a/process/factory/metachain/preProcessorsContainerFactory.go +++ b/process/factory/metachain/preProcessorsContainerFactory.go @@ -1,12 +1,15 @@ package metachain import ( + "fmt" + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/block/preprocess" @@ -17,6 +20,36 @@ import ( var _ process.PreProcessorsContainerFactory = (*preProcessorsContainerFactory)(nil) +// ArgsPreProcessorsContainerFactory holds the arguments needed for creating a new preProcessorsContainerFactory instance +type ArgsPreProcessorsContainerFactory struct { + ShardCoordinator sharding.Coordinator + Store dataRetriever.StorageService + Marshalizer marshal.Marshalizer + Hasher hashing.Hasher + DataPool dataRetriever.PoolsHolder + Accounts state.AccountsAdapter + AccountsProposal state.AccountsAdapter + RequestHandler process.RequestHandler + TxProcessor process.TransactionProcessor + ScResultProcessor process.SmartContractResultProcessor + EconomicsFee process.FeeHandler + GasHandler process.GasHandler + BlockTracker preprocess.BlockTracker + PubkeyConverter core.PubkeyConverter + BlockSizeComputation preprocess.BlockSizeComputationHandler + BalanceComputation preprocess.BalanceComputationHandler + EnableEpochsHandler common.EnableEpochsHandler + EpochNotifier process.EpochNotifier + EnableRoundsHandler common.EnableRoundsHandler + RoundNotifier process.RoundNotifier + TxTypeHandler process.TxTypeHandler + ScheduledTxsExecutionHandler process.ScheduledTxsExecutionHandler + ProcessedMiniBlocksTracker process.ProcessedMiniBlocksTracker + TxExecutionOrderHandler common.TxExecutionOrderHandler + TxCacheSelectionConfig config.TxCacheSelectionConfig + TxVersionCheckerHandler process.TxVersionCheckerHandler +} + type preProcessorsContainerFactory struct { shardCoordinator sharding.Coordinator store dataRetriever.StorageService @@ -26,6 +59,7 @@ type preProcessorsContainerFactory struct { txProcessor process.TransactionProcessor scResultProcessor process.SmartContractResultProcessor accounts state.AccountsAdapter + accountsProposal state.AccountsAdapter requestHandler process.RequestHandler economicsFee process.FeeHandler gasHandler process.GasHandler @@ -34,118 +68,117 @@ type preProcessorsContainerFactory struct { blockSizeComputation preprocess.BlockSizeComputationHandler balanceComputation preprocess.BalanceComputationHandler enableEpochsHandler common.EnableEpochsHandler + epochNotifier process.EpochNotifier + enableRoundsHandler common.EnableRoundsHandler + roundNotifier process.RoundNotifier txTypeHandler process.TxTypeHandler scheduledTxsExecutionHandler process.ScheduledTxsExecutionHandler processedMiniBlocksTracker process.ProcessedMiniBlocksTracker txExecutionOrderHandler common.TxExecutionOrderHandler + txCacheSelectionConfig config.TxCacheSelectionConfig + txVersionChecker process.TxVersionCheckerHandler } // NewPreProcessorsContainerFactory is responsible for creating a new preProcessors factory object -func NewPreProcessorsContainerFactory( - shardCoordinator sharding.Coordinator, - store dataRetriever.StorageService, - marshalizer marshal.Marshalizer, - hasher hashing.Hasher, - dataPool dataRetriever.PoolsHolder, - accounts state.AccountsAdapter, - requestHandler process.RequestHandler, - txProcessor process.TransactionProcessor, - scResultProcessor process.SmartContractResultProcessor, - economicsFee process.FeeHandler, - gasHandler process.GasHandler, - blockTracker preprocess.BlockTracker, - pubkeyConverter core.PubkeyConverter, - blockSizeComputation preprocess.BlockSizeComputationHandler, - balanceComputation preprocess.BalanceComputationHandler, - enableEpochsHandler common.EnableEpochsHandler, - txTypeHandler process.TxTypeHandler, - scheduledTxsExecutionHandler process.ScheduledTxsExecutionHandler, - processedMiniBlocksTracker process.ProcessedMiniBlocksTracker, - txExecutionOrderHandler common.TxExecutionOrderHandler, -) (*preProcessorsContainerFactory, error) { - - if check.IfNil(shardCoordinator) { +func NewPreProcessorsContainerFactory(args ArgsPreProcessorsContainerFactory) (*preProcessorsContainerFactory, error) { + + if check.IfNil(args.ShardCoordinator) { return nil, process.ErrNilShardCoordinator } - if check.IfNil(store) { + if check.IfNil(args.Store) { return nil, process.ErrNilStore } - if check.IfNil(marshalizer) { + if check.IfNil(args.Marshalizer) { return nil, process.ErrNilMarshalizer } - if check.IfNil(hasher) { + if check.IfNil(args.Hasher) { return nil, process.ErrNilHasher } - if check.IfNil(dataPool) { + if check.IfNil(args.DataPool) { return nil, process.ErrNilDataPoolHolder } - if check.IfNil(txProcessor) { + if check.IfNil(args.TxProcessor) { return nil, process.ErrNilTxProcessor } - if check.IfNil(accounts) { + if check.IfNil(args.Accounts) { return nil, process.ErrNilAccountsAdapter } - if check.IfNil(requestHandler) { + if check.IfNil(args.AccountsProposal) { + return nil, fmt.Errorf("%w for proposal", process.ErrNilAccountsAdapter) + } + if check.IfNil(args.RequestHandler) { return nil, process.ErrNilRequestHandler } - if check.IfNil(economicsFee) { + if check.IfNil(args.EconomicsFee) { return nil, process.ErrNilEconomicsFeeHandler } - if check.IfNil(scResultProcessor) { + if check.IfNil(args.ScResultProcessor) { return nil, process.ErrNilSmartContractResultProcessor } - if check.IfNil(gasHandler) { + if check.IfNil(args.GasHandler) { return nil, process.ErrNilGasHandler } - if check.IfNil(blockTracker) { + if check.IfNil(args.BlockTracker) { return nil, process.ErrNilBlockTracker } - if check.IfNil(pubkeyConverter) { + if check.IfNil(args.PubkeyConverter) { return nil, process.ErrNilPubkeyConverter } - if check.IfNil(blockSizeComputation) { + if check.IfNil(args.BlockSizeComputation) { return nil, process.ErrNilBlockSizeComputationHandler } - if check.IfNil(balanceComputation) { + if check.IfNil(args.BalanceComputation) { return nil, process.ErrNilBalanceComputationHandler } - if check.IfNil(enableEpochsHandler) { + if check.IfNil(args.EnableEpochsHandler) { return nil, process.ErrNilEnableEpochsHandler } - if check.IfNil(txTypeHandler) { + if check.IfNil(args.TxTypeHandler) { return nil, process.ErrNilTxTypeHandler } - if check.IfNil(scheduledTxsExecutionHandler) { + if check.IfNil(args.ScheduledTxsExecutionHandler) { return nil, process.ErrNilScheduledTxsExecutionHandler } - if check.IfNil(processedMiniBlocksTracker) { + if check.IfNil(args.ProcessedMiniBlocksTracker) { return nil, process.ErrNilProcessedMiniBlocksTracker } - if check.IfNil(txExecutionOrderHandler) { + if check.IfNil(args.TxExecutionOrderHandler) { return nil, process.ErrNilTxExecutionOrderHandler } + if check.IfNil(args.EnableRoundsHandler) { + return nil, process.ErrNilEnableRoundsHandler + } + if check.IfNil(args.TxVersionCheckerHandler) { + return nil, process.ErrNilTransactionVersionChecker + } return &preProcessorsContainerFactory{ - shardCoordinator: shardCoordinator, - store: store, - marshalizer: marshalizer, - hasher: hasher, - dataPool: dataPool, - txProcessor: txProcessor, - accounts: accounts, - requestHandler: requestHandler, - economicsFee: economicsFee, - scResultProcessor: scResultProcessor, - gasHandler: gasHandler, - blockTracker: blockTracker, - pubkeyConverter: pubkeyConverter, - blockSizeComputation: blockSizeComputation, - balanceComputation: balanceComputation, - enableEpochsHandler: enableEpochsHandler, - txTypeHandler: txTypeHandler, - scheduledTxsExecutionHandler: scheduledTxsExecutionHandler, - processedMiniBlocksTracker: processedMiniBlocksTracker, - txExecutionOrderHandler: txExecutionOrderHandler, + shardCoordinator: args.ShardCoordinator, + store: args.Store, + marshalizer: args.Marshalizer, + hasher: args.Hasher, + dataPool: args.DataPool, + txProcessor: args.TxProcessor, + accounts: args.Accounts, + accountsProposal: args.AccountsProposal, + requestHandler: args.RequestHandler, + economicsFee: args.EconomicsFee, + scResultProcessor: args.ScResultProcessor, + gasHandler: args.GasHandler, + blockTracker: args.BlockTracker, + pubkeyConverter: args.PubkeyConverter, + blockSizeComputation: args.BlockSizeComputation, + balanceComputation: args.BalanceComputation, + enableEpochsHandler: args.EnableEpochsHandler, + epochNotifier: args.EpochNotifier, + enableRoundsHandler: args.EnableRoundsHandler, + roundNotifier: args.RoundNotifier, + txTypeHandler: args.TxTypeHandler, + scheduledTxsExecutionHandler: args.ScheduledTxsExecutionHandler, + processedMiniBlocksTracker: args.ProcessedMiniBlocksTracker, + txExecutionOrderHandler: args.TxExecutionOrderHandler, + txCacheSelectionConfig: args.TxCacheSelectionConfig, + txVersionChecker: args.TxVersionCheckerHandler, }, nil } @@ -178,54 +211,65 @@ func (ppcm *preProcessorsContainerFactory) Create() (process.PreProcessorsContai func (ppcm *preProcessorsContainerFactory) createTxPreProcessor() (process.PreProcessor, error) { args := preprocess.ArgsTransactionPreProcessor{ - TxDataPool: ppcm.dataPool.Transactions(), - Store: ppcm.store, - Hasher: ppcm.hasher, - Marshalizer: ppcm.marshalizer, + BasePreProcessorArgs: preprocess.BasePreProcessorArgs{ + DataPool: ppcm.dataPool.Transactions(), + Store: ppcm.store, + Hasher: ppcm.hasher, + Marshalizer: ppcm.marshalizer, + ShardCoordinator: ppcm.shardCoordinator, + Accounts: ppcm.accounts, + AccountsProposal: ppcm.accountsProposal, + OnRequestTransaction: ppcm.requestHandler.RequestTransactions, + GasHandler: ppcm.gasHandler, + PubkeyConverter: ppcm.pubkeyConverter, + BlockSizeComputation: ppcm.blockSizeComputation, + BalanceComputation: ppcm.balanceComputation, + ProcessedMiniBlocksTracker: ppcm.processedMiniBlocksTracker, + TxExecutionOrderHandler: ppcm.txExecutionOrderHandler, + EconomicsFee: ppcm.economicsFee, + EnableEpochsHandler: ppcm.enableEpochsHandler, + EpochNotifier: ppcm.epochNotifier, + EnableRoundsHandler: ppcm.enableRoundsHandler, + RoundNotifier: ppcm.roundNotifier, + }, TxProcessor: ppcm.txProcessor, - ShardCoordinator: ppcm.shardCoordinator, - Accounts: ppcm.accounts, - OnRequestTransaction: ppcm.requestHandler.RequestTransaction, - EconomicsFee: ppcm.economicsFee, - GasHandler: ppcm.gasHandler, BlockTracker: ppcm.blockTracker, BlockType: block.TxBlock, - PubkeyConverter: ppcm.pubkeyConverter, - BlockSizeComputation: ppcm.blockSizeComputation, - BalanceComputation: ppcm.balanceComputation, - EnableEpochsHandler: ppcm.enableEpochsHandler, TxTypeHandler: ppcm.txTypeHandler, ScheduledTxsExecutionHandler: ppcm.scheduledTxsExecutionHandler, - ProcessedMiniBlocksTracker: ppcm.processedMiniBlocksTracker, - TxExecutionOrderHandler: ppcm.txExecutionOrderHandler, + TxCacheSelectionConfig: ppcm.txCacheSelectionConfig, + TxVersionCheckerHandler: ppcm.txVersionChecker, } - txPreprocessor, err := preprocess.NewTransactionPreprocessor(args) - - return txPreprocessor, err + return preprocess.NewTransactionPreprocessor(args) } - func (ppcm *preProcessorsContainerFactory) createSmartContractResultPreProcessor() (process.PreProcessor, error) { - scrPreprocessor, err := preprocess.NewSmartContractResultPreprocessor( - ppcm.dataPool.UnsignedTransactions(), - ppcm.store, - ppcm.hasher, - ppcm.marshalizer, - ppcm.scResultProcessor, - ppcm.shardCoordinator, - ppcm.accounts, - ppcm.requestHandler.RequestUnsignedTransactions, - ppcm.gasHandler, - ppcm.economicsFee, - ppcm.pubkeyConverter, - ppcm.blockSizeComputation, - ppcm.balanceComputation, - ppcm.enableEpochsHandler, - ppcm.processedMiniBlocksTracker, - ppcm.txExecutionOrderHandler, - ) - - return scrPreprocessor, err + args := preprocess.SmartContractResultsArgs{ + BasePreProcessorArgs: preprocess.BasePreProcessorArgs{ + DataPool: ppcm.dataPool.UnsignedTransactions(), + Store: ppcm.store, + Hasher: ppcm.hasher, + Marshalizer: ppcm.marshalizer, + ShardCoordinator: ppcm.shardCoordinator, + Accounts: ppcm.accounts, + AccountsProposal: ppcm.accountsProposal, + OnRequestTransaction: ppcm.requestHandler.RequestUnsignedTransactions, + GasHandler: ppcm.gasHandler, + PubkeyConverter: ppcm.pubkeyConverter, + BlockSizeComputation: ppcm.blockSizeComputation, + BalanceComputation: ppcm.balanceComputation, + ProcessedMiniBlocksTracker: ppcm.processedMiniBlocksTracker, + TxExecutionOrderHandler: ppcm.txExecutionOrderHandler, + EconomicsFee: ppcm.economicsFee, + EnableEpochsHandler: ppcm.enableEpochsHandler, + EpochNotifier: ppcm.epochNotifier, + EnableRoundsHandler: ppcm.enableRoundsHandler, + RoundNotifier: ppcm.roundNotifier, + }, + ScrProcessor: ppcm.scResultProcessor, + } + + return preprocess.NewSmartContractResultPreprocessor(args) } // IsInterfaceNil returns true if there is no value under the interface diff --git a/process/factory/metachain/preProcessorsContainerFactory_test.go b/process/factory/metachain/preProcessorsContainerFactory_test.go index 325fdc9d76e..55502d67710 100644 --- a/process/factory/metachain/preProcessorsContainerFactory_test.go +++ b/process/factory/metachain/preProcessorsContainerFactory_test.go @@ -3,6 +3,7 @@ package metachain_test import ( "testing" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/factory/metachain" @@ -12,37 +13,28 @@ import ( dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" + "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" stateMock "github.com/multiversx/mx-chain-go/testscommon/state" storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" "github.com/stretchr/testify/assert" ) +func createMockTxCacheSelectionConfig() config.TxCacheSelectionConfig { + return config.TxCacheSelectionConfig{ + SelectionGasBandwidthIncreasePercent: 400, + SelectionGasBandwidthIncreaseScheduledPercent: 260, + SelectionGasRequested: 10_000_000_000, + SelectionMaxNumTxs: 30000, + SelectionLoopDurationCheckInterval: 10, + } +} + func TestNewPreProcessorsContainerFactory_NilShardCoordinator(t *testing.T) { t.Parallel() - - ppcm, err := metachain.NewPreProcessorsContainerFactory( - nil, - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorContainerFactoryArgs() + args.ShardCoordinator = nil + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilShardCoordinator, err) assert.Nil(t, ppcm) @@ -51,28 +43,9 @@ func TestNewPreProcessorsContainerFactory_NilShardCoordinator(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilStore(t *testing.T) { t.Parallel() - ppcm, err := metachain.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - nil, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorContainerFactoryArgs() + args.Store = nil + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilStore, err) assert.Nil(t, ppcm) @@ -81,28 +54,9 @@ func TestNewPreProcessorsContainerFactory_NilStore(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilMarshalizer(t *testing.T) { t.Parallel() - ppcm, err := metachain.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - nil, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorContainerFactoryArgs() + args.Marshalizer = nil + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilMarshalizer, err) assert.Nil(t, ppcm) @@ -111,28 +65,9 @@ func TestNewPreProcessorsContainerFactory_NilMarshalizer(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilHasher(t *testing.T) { t.Parallel() - ppcm, err := metachain.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - nil, - dataRetrieverMock.NewPoolsHolderMock(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorContainerFactoryArgs() + args.Hasher = nil + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilHasher, err) assert.Nil(t, ppcm) @@ -141,28 +76,9 @@ func TestNewPreProcessorsContainerFactory_NilHasher(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilDataPool(t *testing.T) { t.Parallel() - ppcm, err := metachain.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - nil, - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorContainerFactoryArgs() + args.DataPool = nil + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilDataPoolHolder, err) assert.Nil(t, ppcm) @@ -171,58 +87,31 @@ func TestNewPreProcessorsContainerFactory_NilDataPool(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilAccounts(t *testing.T) { t.Parallel() - ppcm, err := metachain.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - nil, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorContainerFactoryArgs() + args.Accounts = nil + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilAccountsAdapter, err) assert.Nil(t, ppcm) } +func TestNewPreProcessorsContainerFactory_NilAccountsProposal(t *testing.T) { + t.Parallel() + + args := createPreProcessorContainerFactoryArgs() + args.AccountsProposal = nil + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) + + assert.ErrorIs(t, err, process.ErrNilAccountsAdapter) + assert.Nil(t, ppcm) +} + func TestNewPreProcessorsContainerFactory_NilFeeHandler(t *testing.T) { t.Parallel() - ppcm, err := metachain.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - nil, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorContainerFactoryArgs() + args.EconomicsFee = nil + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilEconomicsFeeHandler, err) assert.Nil(t, ppcm) @@ -231,28 +120,9 @@ func TestNewPreProcessorsContainerFactory_NilFeeHandler(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilTxProcessor(t *testing.T) { t.Parallel() - ppcm, err := metachain.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - nil, - &testscommon.SmartContractResultsProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorContainerFactoryArgs() + args.TxProcessor = nil + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilTxProcessor, err) assert.Nil(t, ppcm) @@ -261,28 +131,9 @@ func TestNewPreProcessorsContainerFactory_NilTxProcessor(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilRequestHandler(t *testing.T) { t.Parallel() - ppcm, err := metachain.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - &stateMock.AccountsStub{}, - nil, - &testscommon.TxProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorContainerFactoryArgs() + args.RequestHandler = nil + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilRequestHandler, err) assert.Nil(t, ppcm) } @@ -290,28 +141,9 @@ func TestNewPreProcessorsContainerFactory_NilRequestHandler(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilGasHandler(t *testing.T) { t.Parallel() - ppcm, err := metachain.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - nil, - &mock.BlockTrackerMock{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorContainerFactoryArgs() + args.GasHandler = nil + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilGasHandler, err) assert.Nil(t, ppcm) } @@ -319,28 +151,9 @@ func TestNewPreProcessorsContainerFactory_NilGasHandler(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilBlockTracker(t *testing.T) { t.Parallel() - ppcm, err := metachain.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - nil, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorContainerFactoryArgs() + args.BlockTracker = nil + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilBlockTracker, err) assert.Nil(t, ppcm) } @@ -348,28 +161,9 @@ func TestNewPreProcessorsContainerFactory_NilBlockTracker(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilPubkeyConverter(t *testing.T) { t.Parallel() - ppcm, err := metachain.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - nil, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorContainerFactoryArgs() + args.PubkeyConverter = nil + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilPubkeyConverter, err) assert.Nil(t, ppcm) } @@ -377,28 +171,9 @@ func TestNewPreProcessorsContainerFactory_NilPubkeyConverter(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilBlockSizeComputationHandler(t *testing.T) { t.Parallel() - ppcm, err := metachain.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - createMockPubkeyConverter(), - nil, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorContainerFactoryArgs() + args.BlockSizeComputation = nil + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilBlockSizeComputationHandler, err) assert.Nil(t, ppcm) } @@ -406,28 +181,9 @@ func TestNewPreProcessorsContainerFactory_NilBlockSizeComputationHandler(t *test func TestNewPreProcessorsContainerFactory_NilBalanceComputationHandler(t *testing.T) { t.Parallel() - ppcm, err := metachain.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - nil, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorContainerFactoryArgs() + args.BalanceComputation = nil + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilBalanceComputationHandler, err) assert.Nil(t, ppcm) } @@ -435,57 +191,29 @@ func TestNewPreProcessorsContainerFactory_NilBalanceComputationHandler(t *testin func TestNewPreProcessorsContainerFactory_NilEnableEpochsHandler(t *testing.T) { t.Parallel() - ppcm, err := metachain.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - nil, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorContainerFactoryArgs() + args.EnableEpochsHandler = nil + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilEnableEpochsHandler, err) assert.Nil(t, ppcm) } +func TestNewPreProcessorsContainerFactory_NilEnableRoundsHandler(t *testing.T) { + t.Parallel() + + args := createPreProcessorContainerFactoryArgs() + args.EnableRoundsHandler = nil + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) + assert.Equal(t, process.ErrNilEnableRoundsHandler, err) + assert.Nil(t, ppcm) +} + func TestNewPreProcessorsContainerFactory_NilTxTypeHandler(t *testing.T) { t.Parallel() - ppcm, err := metachain.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - nil, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorContainerFactoryArgs() + args.TxTypeHandler = nil + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilTxTypeHandler, err) assert.Nil(t, ppcm) } @@ -493,28 +221,9 @@ func TestNewPreProcessorsContainerFactory_NilTxTypeHandler(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilScheduledTxsExecutionHandler(t *testing.T) { t.Parallel() - ppcm, err := metachain.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - nil, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorContainerFactoryArgs() + args.ScheduledTxsExecutionHandler = nil + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilScheduledTxsExecutionHandler, err) assert.Nil(t, ppcm) } @@ -522,28 +231,9 @@ func TestNewPreProcessorsContainerFactory_NilScheduledTxsExecutionHandler(t *tes func TestNewPreProcessorsContainerFactory_NilProcessedMiniBlocksTracker(t *testing.T) { t.Parallel() - ppcm, err := metachain.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - nil, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorContainerFactoryArgs() + args.ProcessedMiniBlocksTracker = nil + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilProcessedMiniBlocksTracker, err) assert.Nil(t, ppcm) } @@ -551,28 +241,9 @@ func TestNewPreProcessorsContainerFactory_NilProcessedMiniBlocksTracker(t *testi func TestNewPreProcessorsContainerFactory_NilTxExecutionOrderHandler(t *testing.T) { t.Parallel() - ppcm, err := metachain.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - nil, - ) + args := createPreProcessorContainerFactoryArgs() + args.TxExecutionOrderHandler = nil + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilTxExecutionOrderHandler, err) assert.Nil(t, ppcm) @@ -581,28 +252,8 @@ func TestNewPreProcessorsContainerFactory_NilTxExecutionOrderHandler(t *testing. func TestNewPreProcessorsContainerFactory(t *testing.T) { t.Parallel() - ppcm, err := metachain.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorContainerFactoryArgs() + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Nil(t, err) assert.NotNil(t, ppcm) @@ -616,29 +267,10 @@ func TestPreProcessorsContainerFactory_CreateErrTxPreproc(t *testing.T) { dataPool.TransactionsCalled = func() dataRetriever.ShardedDataCacherNotifier { return nil } + args := createPreProcessorContainerFactoryArgs() + args.DataPool = dataPool - ppcm, err := metachain.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataPool, - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Nil(t, err) assert.NotNil(t, ppcm) @@ -651,28 +283,8 @@ func TestPreProcessorsContainerFactory_CreateErrTxPreproc(t *testing.T) { func TestPreProcessorsContainerFactory_Create(t *testing.T) { t.Parallel() - ppcm, err := metachain.NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - createMockPubkeyConverter(), - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorContainerFactoryArgs() + ppcm, err := metachain.NewPreProcessorsContainerFactory(args) assert.Nil(t, err) assert.NotNil(t, ppcm) @@ -681,3 +293,34 @@ func TestPreProcessorsContainerFactory_Create(t *testing.T) { assert.Nil(t, err) assert.Equal(t, 2, container.Len()) } + +func createPreProcessorContainerFactoryArgs() metachain.ArgsPreProcessorsContainerFactory { + return metachain.ArgsPreProcessorsContainerFactory{ + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), + Store: &storageStubs.ChainStorerStub{}, + Marshalizer: &mock.MarshalizerMock{}, + Hasher: &hashingMocks.HasherMock{}, + DataPool: dataRetrieverMock.NewPoolsHolderMock(), + Accounts: &stateMock.AccountsStub{}, + AccountsProposal: &stateMock.AccountsStub{}, + RequestHandler: &testscommon.RequestHandlerStub{}, + TxProcessor: &testscommon.TxProcessorMock{}, + ScResultProcessor: &testscommon.SmartContractResultsProcessorMock{}, + EconomicsFee: &economicsmocks.EconomicsHandlerMock{}, + GasHandler: &testscommon.GasHandlerStub{}, + BlockTracker: &mock.BlockTrackerMock{}, + PubkeyConverter: createMockPubkeyConverter(), + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + TxTypeHandler: &testscommon.TxTypeHandlerMock{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: createMockTxCacheSelectionConfig(), + TxVersionCheckerHandler: &testscommon.TxVersionCheckerStub{}, + } +} diff --git a/process/factory/metachain/vmContainerFactory.go b/process/factory/metachain/vmContainerFactory.go index 8f8fd90bbc9..5c75aebb92f 100644 --- a/process/factory/metachain/vmContainerFactory.go +++ b/process/factory/metachain/vmContainerFactory.go @@ -44,6 +44,7 @@ type vmContainerFactory struct { scFactory vm.SystemSCContainerFactory shardCoordinator sharding.Coordinator enableEpochsHandler common.EnableEpochsHandler + enableRoundsHandler common.EnableRoundsHandler nodesCoordinator vm.NodesCoordinator } @@ -64,6 +65,7 @@ type ArgsNewVMContainerFactory struct { PubkeyConv core.PubkeyConverter BlockChainHook process.BlockChainHookWithAccountsAdapter EnableEpochsHandler common.EnableEpochsHandler + EnableRoundsHandler common.EnableRoundsHandler NodesCoordinator vm.NodesCoordinator } @@ -111,6 +113,9 @@ func NewVMContainerFactory(args ArgsNewVMContainerFactory) (*vmContainerFactory, if check.IfNil(args.EnableEpochsHandler) { return nil, vm.ErrNilEnableEpochsHandler } + if check.IfNil(args.EnableRoundsHandler) { + return nil, vm.ErrNilEnableRoundsHandler + } if check.IfNil(args.NodesCoordinator) { return nil, fmt.Errorf("%w in NewVMContainerFactory", process.ErrNilNodesCoordinator) } @@ -133,6 +138,7 @@ func NewVMContainerFactory(args ArgsNewVMContainerFactory) (*vmContainerFactory, addressPubKeyConverter: args.PubkeyConv, shardCoordinator: args.ShardCoordinator, enableEpochsHandler: args.EnableEpochsHandler, + enableRoundsHandler: args.EnableRoundsHandler, nodesCoordinator: args.NodesCoordinator, }, nil } @@ -207,6 +213,7 @@ func (vmf *vmContainerFactory) createSystemVMFactoryAndEEI() (vm.SystemSCContain AddressPubKeyConverter: vmf.addressPubKeyConverter, ShardCoordinator: vmf.shardCoordinator, EnableEpochsHandler: vmf.enableEpochsHandler, + EnableRoundsHandler: vmf.enableRoundsHandler, NodesCoordinator: vmf.nodesCoordinator, } scFactory, err := systemVMFactory.NewSystemSCFactory(argsNewSystemScFactory) diff --git a/process/factory/metachain/vmContainerFactory_test.go b/process/factory/metachain/vmContainerFactory_test.go index 58a36249763..992ee16c9ba 100644 --- a/process/factory/metachain/vmContainerFactory_test.go +++ b/process/factory/metachain/vmContainerFactory_test.go @@ -13,6 +13,7 @@ import ( "github.com/multiversx/mx-chain-go/process/factory" "github.com/multiversx/mx-chain-go/process/mock" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" @@ -58,6 +59,7 @@ func createVmContainerMockArgument(gasSchedule core.GasScheduleNotifier) ArgsNew MinStepValue: "10", MinStakeValue: "1", UnBondPeriod: 1, + UnBondPeriodSupernova: 2, NumRoundsWithoutBleed: 1, MaximumPercentageToBleed: 1, BleedPercentagePerRound: 1, @@ -78,6 +80,7 @@ func createVmContainerMockArgument(gasSchedule core.GasScheduleNotifier) ArgsNew ChanceComputer: &mock.RaterMock{}, ShardCoordinator: &mock.ShardCoordinatorStub{}, EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(common.StakeFlag), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, NodesCoordinator: &shardingMocks.NodesCoordinatorMock{GetNumTotalEligibleCalled: func() uint64 { return 1000 }}, @@ -278,10 +281,8 @@ func TestNewVMContainerFactory_OkValues(t *testing.T) { func TestVmContainerFactory_Create(t *testing.T) { t.Parallel() - cfg := &config.Config{EpochStartConfig: config.EpochStartConfig{RoundsPerEpoch: 14400}} - cfg.GeneralSettings.ChainParametersByEpoch = []config.ChainParametersByEpochConfig{{RoundDuration: 6000}} argsNewEconomicsData := economics.ArgsNewEconomicsData{ - GeneralConfig: cfg, + ChainParamsHandler: &chainParameters.ChainParametersHolderMock{}, Economics: &config.EconomicsConfig{ GlobalSettings: config.GlobalSettings{ GenesisTotalSupply: "2000000000000000000000", @@ -370,6 +371,7 @@ func TestVmContainerFactory_Create(t *testing.T) { MinStepValue: "100", MinStakeValue: "1", UnBondPeriod: 1, + UnBondPeriodSupernova: 2, NumRoundsWithoutBleed: 1, MaximumPercentageToBleed: 1, BleedPercentagePerRound: 1, @@ -400,6 +402,7 @@ func TestVmContainerFactory_Create(t *testing.T) { ChanceComputer: &mock.RaterMock{}, ShardCoordinator: mock.NewMultiShardsCoordinatorMock(1), EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, NodesCoordinator: &shardingMocks.NodesCoordinatorMock{GetNumTotalEligibleCalled: func() uint64 { return 1000 }}, diff --git a/process/factory/shard/intermediateProcessorsContainerFactory.go b/process/factory/shard/intermediateProcessorsContainerFactory.go index 007e6e2c991..d235fa51c02 100644 --- a/process/factory/shard/intermediateProcessorsContainerFactory.go +++ b/process/factory/shard/intermediateProcessorsContainerFactory.go @@ -6,6 +6,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" diff --git a/process/factory/shard/preProcessorsContainerFactory.go b/process/factory/shard/preProcessorsContainerFactory.go index a561412737b..7b4fa5744ff 100644 --- a/process/factory/shard/preProcessorsContainerFactory.go +++ b/process/factory/shard/preProcessorsContainerFactory.go @@ -1,12 +1,15 @@ package shard import ( + "fmt" + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/block/preprocess" @@ -17,6 +20,38 @@ import ( var _ process.PreProcessorsContainerFactory = (*preProcessorsContainerFactory)(nil) +// ArgsPreProcessorsContainerFactory holds the arguments needed for creating a preProcessorsContainerFactory +type ArgsPreProcessorsContainerFactory struct { + ShardCoordinator sharding.Coordinator + Store dataRetriever.StorageService + Marshalizer marshal.Marshalizer + Hasher hashing.Hasher + DataPool dataRetriever.PoolsHolder + PubkeyConverter core.PubkeyConverter + Accounts state.AccountsAdapter + AccountsProposal state.AccountsAdapter + RequestHandler process.RequestHandler + TxProcessor process.TransactionProcessor + ScProcessor process.SmartContractProcessor + ScResultProcessor process.SmartContractResultProcessor + RewardsTxProcessor process.RewardTransactionProcessor + EconomicsFee process.FeeHandler + GasHandler process.GasHandler + BlockTracker preprocess.BlockTracker + BlockSizeComputation preprocess.BlockSizeComputationHandler + BalanceComputation preprocess.BalanceComputationHandler + EnableEpochsHandler common.EnableEpochsHandler + EpochNotifier process.EpochNotifier + EnableRoundsHandler common.EnableRoundsHandler + RoundNotifier process.RoundNotifier + TxTypeHandler process.TxTypeHandler + ScheduledTxsExecutionHandler process.ScheduledTxsExecutionHandler + ProcessedMiniBlocksTracker process.ProcessedMiniBlocksTracker + TxExecutionOrderHandler common.TxExecutionOrderHandler + TxCacheSelectionConfig config.TxCacheSelectionConfig + TxVersionChecker process.TxVersionCheckerHandler +} + type preProcessorsContainerFactory struct { shardCoordinator sharding.Coordinator store dataRetriever.StorageService @@ -29,6 +64,7 @@ type preProcessorsContainerFactory struct { scResultProcessor process.SmartContractResultProcessor rewardsTxProcessor process.RewardTransactionProcessor accounts state.AccountsAdapter + accountsProposal state.AccountsAdapter requestHandler process.RequestHandler economicsFee process.FeeHandler gasHandler process.GasHandler @@ -36,128 +72,125 @@ type preProcessorsContainerFactory struct { blockSizeComputation preprocess.BlockSizeComputationHandler balanceComputation preprocess.BalanceComputationHandler enableEpochsHandler common.EnableEpochsHandler + epochNotifier process.EpochNotifier + enableRoundsHandler common.EnableRoundsHandler + roundNotifier process.RoundNotifier txTypeHandler process.TxTypeHandler scheduledTxsExecutionHandler process.ScheduledTxsExecutionHandler processedMiniBlocksTracker process.ProcessedMiniBlocksTracker txExecutionOrderHandler common.TxExecutionOrderHandler + txCacheSelectionConfig config.TxCacheSelectionConfig + txVersionChecker process.TxVersionCheckerHandler } // NewPreProcessorsContainerFactory is responsible for creating a new preProcessors factory object -func NewPreProcessorsContainerFactory( - shardCoordinator sharding.Coordinator, - store dataRetriever.StorageService, - marshalizer marshal.Marshalizer, - hasher hashing.Hasher, - dataPool dataRetriever.PoolsHolder, - pubkeyConverter core.PubkeyConverter, - accounts state.AccountsAdapter, - requestHandler process.RequestHandler, - txProcessor process.TransactionProcessor, - scProcessor process.SmartContractProcessor, - scResultProcessor process.SmartContractResultProcessor, - rewardsTxProcessor process.RewardTransactionProcessor, - economicsFee process.FeeHandler, - gasHandler process.GasHandler, - blockTracker preprocess.BlockTracker, - blockSizeComputation preprocess.BlockSizeComputationHandler, - balanceComputation preprocess.BalanceComputationHandler, - enableEpochsHandler common.EnableEpochsHandler, - txTypeHandler process.TxTypeHandler, - scheduledTxsExecutionHandler process.ScheduledTxsExecutionHandler, - processedMiniBlocksTracker process.ProcessedMiniBlocksTracker, - txExecutionOrderHandler common.TxExecutionOrderHandler, -) (*preProcessorsContainerFactory, error) { +func NewPreProcessorsContainerFactory(args ArgsPreProcessorsContainerFactory) (*preProcessorsContainerFactory, error) { - if check.IfNil(shardCoordinator) { + if check.IfNil(args.ShardCoordinator) { return nil, process.ErrNilShardCoordinator } - if check.IfNil(store) { + if check.IfNil(args.Store) { return nil, process.ErrNilStore } - if check.IfNil(marshalizer) { + if check.IfNil(args.Marshalizer) { return nil, process.ErrNilMarshalizer } - if check.IfNil(hasher) { + if check.IfNil(args.Hasher) { return nil, process.ErrNilHasher } - if check.IfNil(dataPool) { + if check.IfNil(args.DataPool) { return nil, process.ErrNilDataPoolHolder } - if check.IfNil(pubkeyConverter) { + if check.IfNil(args.PubkeyConverter) { return nil, process.ErrNilPubkeyConverter } - if check.IfNil(txProcessor) { + if check.IfNil(args.TxProcessor) { return nil, process.ErrNilTxProcessor } - if check.IfNil(accounts) { + if check.IfNil(args.Accounts) { return nil, process.ErrNilAccountsAdapter } - if check.IfNil(scProcessor) { + if check.IfNil(args.AccountsProposal) { + return nil, fmt.Errorf("%w for proposal", process.ErrNilAccountsAdapter) + } + if check.IfNil(args.ScProcessor) { return nil, process.ErrNilSmartContractProcessor } - if check.IfNil(scResultProcessor) { + if check.IfNil(args.ScResultProcessor) { return nil, process.ErrNilSmartContractResultProcessor } - if check.IfNil(rewardsTxProcessor) { + if check.IfNil(args.RewardsTxProcessor) { return nil, process.ErrNilRewardsTxProcessor } - if check.IfNil(requestHandler) { + if check.IfNil(args.RequestHandler) { return nil, process.ErrNilRequestHandler } - if check.IfNil(economicsFee) { + if check.IfNil(args.EconomicsFee) { return nil, process.ErrNilEconomicsFeeHandler } - if check.IfNil(gasHandler) { + if check.IfNil(args.GasHandler) { return nil, process.ErrNilGasHandler } - if check.IfNil(blockTracker) { + if check.IfNil(args.BlockTracker) { return nil, process.ErrNilBlockTracker } - if check.IfNil(blockSizeComputation) { + if check.IfNil(args.BlockSizeComputation) { return nil, process.ErrNilBlockSizeComputationHandler } - if check.IfNil(balanceComputation) { + if check.IfNil(args.BalanceComputation) { return nil, process.ErrNilBalanceComputationHandler } - if check.IfNil(enableEpochsHandler) { + if check.IfNil(args.EnableEpochsHandler) { return nil, process.ErrNilEnableEpochsHandler } - if check.IfNil(txTypeHandler) { + if check.IfNil(args.EnableRoundsHandler) { + return nil, process.ErrNilEnableRoundsHandler + } + if check.IfNil(args.TxTypeHandler) { return nil, process.ErrNilTxTypeHandler } - if check.IfNil(scheduledTxsExecutionHandler) { + if check.IfNil(args.ScheduledTxsExecutionHandler) { return nil, process.ErrNilScheduledTxsExecutionHandler } - if check.IfNil(processedMiniBlocksTracker) { + if check.IfNil(args.ProcessedMiniBlocksTracker) { return nil, process.ErrNilProcessedMiniBlocksTracker } - if check.IfNil(txExecutionOrderHandler) { + if check.IfNil(args.TxExecutionOrderHandler) { return nil, process.ErrNilTxExecutionOrderHandler } + if check.IfNil(args.TxVersionChecker) { + return nil, process.ErrNilTransactionVersionChecker + } return &preProcessorsContainerFactory{ - shardCoordinator: shardCoordinator, - store: store, - marshalizer: marshalizer, - hasher: hasher, - dataPool: dataPool, - pubkeyConverter: pubkeyConverter, - txProcessor: txProcessor, - accounts: accounts, - scProcessor: scProcessor, - scResultProcessor: scResultProcessor, - rewardsTxProcessor: rewardsTxProcessor, - requestHandler: requestHandler, - economicsFee: economicsFee, - gasHandler: gasHandler, - blockTracker: blockTracker, - blockSizeComputation: blockSizeComputation, - balanceComputation: balanceComputation, - enableEpochsHandler: enableEpochsHandler, - txTypeHandler: txTypeHandler, - scheduledTxsExecutionHandler: scheduledTxsExecutionHandler, - processedMiniBlocksTracker: processedMiniBlocksTracker, - txExecutionOrderHandler: txExecutionOrderHandler, + shardCoordinator: args.ShardCoordinator, + store: args.Store, + marshalizer: args.Marshalizer, + hasher: args.Hasher, + dataPool: args.DataPool, + pubkeyConverter: args.PubkeyConverter, + txProcessor: args.TxProcessor, + accounts: args.Accounts, + accountsProposal: args.AccountsProposal, + scProcessor: args.ScProcessor, + scResultProcessor: args.ScResultProcessor, + rewardsTxProcessor: args.RewardsTxProcessor, + requestHandler: args.RequestHandler, + economicsFee: args.EconomicsFee, + gasHandler: args.GasHandler, + blockTracker: args.BlockTracker, + blockSizeComputation: args.BlockSizeComputation, + balanceComputation: args.BalanceComputation, + enableEpochsHandler: args.EnableEpochsHandler, + epochNotifier: args.EpochNotifier, + enableRoundsHandler: args.EnableRoundsHandler, + roundNotifier: args.RoundNotifier, + txTypeHandler: args.TxTypeHandler, + scheduledTxsExecutionHandler: args.ScheduledTxsExecutionHandler, + processedMiniBlocksTracker: args.ProcessedMiniBlocksTracker, + txExecutionOrderHandler: args.TxExecutionOrderHandler, + txCacheSelectionConfig: args.TxCacheSelectionConfig, + txVersionChecker: args.TxVersionChecker, }, nil } @@ -210,88 +243,124 @@ func (ppcm *preProcessorsContainerFactory) Create() (process.PreProcessorsContai func (ppcm *preProcessorsContainerFactory) createTxPreProcessor() (process.PreProcessor, error) { args := preprocess.ArgsTransactionPreProcessor{ - TxDataPool: ppcm.dataPool.Transactions(), - Store: ppcm.store, - Hasher: ppcm.hasher, - Marshalizer: ppcm.marshalizer, + BasePreProcessorArgs: preprocess.BasePreProcessorArgs{ + DataPool: ppcm.dataPool.Transactions(), + Store: ppcm.store, + Hasher: ppcm.hasher, + Marshalizer: ppcm.marshalizer, + ShardCoordinator: ppcm.shardCoordinator, + Accounts: ppcm.accounts, + AccountsProposal: ppcm.accountsProposal, + OnRequestTransaction: ppcm.requestHandler.RequestTransactions, + GasHandler: ppcm.gasHandler, + PubkeyConverter: ppcm.pubkeyConverter, + BlockSizeComputation: ppcm.blockSizeComputation, + BalanceComputation: ppcm.balanceComputation, + ProcessedMiniBlocksTracker: ppcm.processedMiniBlocksTracker, + TxExecutionOrderHandler: ppcm.txExecutionOrderHandler, + EconomicsFee: ppcm.economicsFee, + EnableEpochsHandler: ppcm.enableEpochsHandler, + EpochNotifier: ppcm.epochNotifier, + EnableRoundsHandler: ppcm.enableRoundsHandler, + RoundNotifier: ppcm.roundNotifier, + }, TxProcessor: ppcm.txProcessor, - ShardCoordinator: ppcm.shardCoordinator, - Accounts: ppcm.accounts, - OnRequestTransaction: ppcm.requestHandler.RequestTransaction, - EconomicsFee: ppcm.economicsFee, - GasHandler: ppcm.gasHandler, BlockTracker: ppcm.blockTracker, BlockType: block.TxBlock, - PubkeyConverter: ppcm.pubkeyConverter, - BlockSizeComputation: ppcm.blockSizeComputation, - BalanceComputation: ppcm.balanceComputation, - EnableEpochsHandler: ppcm.enableEpochsHandler, TxTypeHandler: ppcm.txTypeHandler, ScheduledTxsExecutionHandler: ppcm.scheduledTxsExecutionHandler, - ProcessedMiniBlocksTracker: ppcm.processedMiniBlocksTracker, - TxExecutionOrderHandler: ppcm.txExecutionOrderHandler, + TxCacheSelectionConfig: ppcm.txCacheSelectionConfig, + TxVersionCheckerHandler: ppcm.txVersionChecker, } - txPreprocessor, err := preprocess.NewTransactionPreprocessor(args) - - return txPreprocessor, err + return preprocess.NewTransactionPreprocessor(args) } func (ppcm *preProcessorsContainerFactory) createSmartContractResultPreProcessor() (process.PreProcessor, error) { - scrPreprocessor, err := preprocess.NewSmartContractResultPreprocessor( - ppcm.dataPool.UnsignedTransactions(), - ppcm.store, - ppcm.hasher, - ppcm.marshalizer, - ppcm.scResultProcessor, - ppcm.shardCoordinator, - ppcm.accounts, - ppcm.requestHandler.RequestUnsignedTransactions, - ppcm.gasHandler, - ppcm.economicsFee, - ppcm.pubkeyConverter, - ppcm.blockSizeComputation, - ppcm.balanceComputation, - ppcm.enableEpochsHandler, - ppcm.processedMiniBlocksTracker, - ppcm.txExecutionOrderHandler, - ) - - return scrPreprocessor, err + args := preprocess.SmartContractResultsArgs{ + BasePreProcessorArgs: preprocess.BasePreProcessorArgs{ + DataPool: ppcm.dataPool.UnsignedTransactions(), + Store: ppcm.store, + Hasher: ppcm.hasher, + Marshalizer: ppcm.marshalizer, + ShardCoordinator: ppcm.shardCoordinator, + Accounts: ppcm.accounts, + AccountsProposal: ppcm.accountsProposal, + OnRequestTransaction: ppcm.requestHandler.RequestUnsignedTransactions, + GasHandler: ppcm.gasHandler, + PubkeyConverter: ppcm.pubkeyConverter, + BlockSizeComputation: ppcm.blockSizeComputation, + BalanceComputation: ppcm.balanceComputation, + ProcessedMiniBlocksTracker: ppcm.processedMiniBlocksTracker, + TxExecutionOrderHandler: ppcm.txExecutionOrderHandler, + EconomicsFee: ppcm.economicsFee, + EnableEpochsHandler: ppcm.enableEpochsHandler, + EpochNotifier: ppcm.epochNotifier, + EnableRoundsHandler: ppcm.enableRoundsHandler, + RoundNotifier: ppcm.roundNotifier, + }, + ScrProcessor: ppcm.scResultProcessor, + } + return preprocess.NewSmartContractResultPreprocessor(args) } func (ppcm *preProcessorsContainerFactory) createRewardsTransactionPreProcessor() (process.PreProcessor, error) { - rewardTxPreprocessor, err := preprocess.NewRewardTxPreprocessor( - ppcm.dataPool.RewardTransactions(), - ppcm.store, - ppcm.hasher, - ppcm.marshalizer, - ppcm.rewardsTxProcessor, - ppcm.shardCoordinator, - ppcm.accounts, - ppcm.requestHandler.RequestRewardTransactions, - ppcm.gasHandler, - ppcm.pubkeyConverter, - ppcm.blockSizeComputation, - ppcm.balanceComputation, - ppcm.processedMiniBlocksTracker, - ppcm.txExecutionOrderHandler, - ) - return rewardTxPreprocessor, err + args := preprocess.RewardsPreProcessorArgs{ + BasePreProcessorArgs: preprocess.BasePreProcessorArgs{ + DataPool: ppcm.dataPool.RewardTransactions(), + Store: ppcm.store, + Hasher: ppcm.hasher, + Marshalizer: ppcm.marshalizer, + ShardCoordinator: ppcm.shardCoordinator, + Accounts: ppcm.accounts, + AccountsProposal: ppcm.accountsProposal, + OnRequestTransaction: ppcm.requestHandler.RequestRewardTransactions, + GasHandler: ppcm.gasHandler, + PubkeyConverter: ppcm.pubkeyConverter, + BlockSizeComputation: ppcm.blockSizeComputation, + BalanceComputation: ppcm.balanceComputation, + ProcessedMiniBlocksTracker: ppcm.processedMiniBlocksTracker, + TxExecutionOrderHandler: ppcm.txExecutionOrderHandler, + EconomicsFee: ppcm.economicsFee, + EnableEpochsHandler: ppcm.enableEpochsHandler, + EpochNotifier: ppcm.epochNotifier, + EnableRoundsHandler: ppcm.enableRoundsHandler, + RoundNotifier: ppcm.roundNotifier, + }, + RewardProcessor: ppcm.rewardsTxProcessor, + } + return preprocess.NewRewardTxPreprocessor(args) } func (ppcm *preProcessorsContainerFactory) createValidatorInfoPreProcessor() (process.PreProcessor, error) { - validatorInfoPreprocessor, err := preprocess.NewValidatorInfoPreprocessor( - ppcm.hasher, - ppcm.marshalizer, - ppcm.blockSizeComputation, - ppcm.dataPool.ValidatorsInfo(), - ppcm.store, - ppcm.enableEpochsHandler, - ) + args := preprocess.ValidatorInfoPreProcessorArgs{ + BasePreProcessorArgs: preprocess.BasePreProcessorArgs{ + DataPool: ppcm.dataPool.ValidatorsInfo(), + Store: ppcm.store, + Hasher: ppcm.hasher, + Marshalizer: ppcm.marshalizer, + ShardCoordinator: ppcm.shardCoordinator, + Accounts: ppcm.accounts, + AccountsProposal: ppcm.accountsProposal, + OnRequestTransaction: func(_ uint32, peerChangeHashes [][]byte) { + ppcm.requestHandler.RequestValidatorsInfo(peerChangeHashes) + }, + GasHandler: ppcm.gasHandler, + PubkeyConverter: ppcm.pubkeyConverter, + BlockSizeComputation: ppcm.blockSizeComputation, + BalanceComputation: ppcm.balanceComputation, + ProcessedMiniBlocksTracker: ppcm.processedMiniBlocksTracker, + TxExecutionOrderHandler: ppcm.txExecutionOrderHandler, + EconomicsFee: ppcm.economicsFee, + EnableEpochsHandler: ppcm.enableEpochsHandler, + EpochNotifier: ppcm.epochNotifier, + EnableRoundsHandler: ppcm.enableRoundsHandler, + RoundNotifier: ppcm.roundNotifier, + }, + } - return validatorInfoPreprocessor, err + return preprocess.NewValidatorInfoPreprocessor(args) } // IsInterfaceNil returns true if there is no value under the interface diff --git a/process/factory/shard/preProcessorsContainerFactory_test.go b/process/factory/shard/preProcessorsContainerFactory_test.go index 46c09f1530f..530a98d9a81 100644 --- a/process/factory/shard/preProcessorsContainerFactory_test.go +++ b/process/factory/shard/preProcessorsContainerFactory_test.go @@ -3,6 +3,7 @@ package shard import ( "testing" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/mock" @@ -11,6 +12,7 @@ import ( dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" + "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" stateMock "github.com/multiversx/mx-chain-go/testscommon/state" storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" @@ -21,33 +23,22 @@ func createMockPubkeyConverter() *testscommon.PubkeyConverterMock { return testscommon.NewPubkeyConverterMock(32) } +func createMockTxCacheSelectionConfig() config.TxCacheSelectionConfig { + return config.TxCacheSelectionConfig{ + SelectionGasBandwidthIncreasePercent: 400, + SelectionGasBandwidthIncreaseScheduledPercent: 260, + SelectionGasRequested: 10_000_000_000, + SelectionMaxNumTxs: 30000, + SelectionLoopDurationCheckInterval: 10, + } +} + func TestNewPreProcessorsContainerFactory_NilShardCoordinator(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - nil, - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.ShardCoordinator = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilShardCoordinator, err) assert.Nil(t, ppcm) @@ -56,30 +47,9 @@ func TestNewPreProcessorsContainerFactory_NilShardCoordinator(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilStore(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - nil, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.Store = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilStore, err) assert.Nil(t, ppcm) @@ -88,30 +58,9 @@ func TestNewPreProcessorsContainerFactory_NilStore(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilMarshalizer(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - nil, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.Marshalizer = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilMarshalizer, err) assert.Nil(t, ppcm) @@ -120,30 +69,9 @@ func TestNewPreProcessorsContainerFactory_NilMarshalizer(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilHasher(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - nil, - dataRetrieverMock.NewPoolsHolderMock(), - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.Hasher = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilHasher, err) assert.Nil(t, ppcm) @@ -152,30 +80,9 @@ func TestNewPreProcessorsContainerFactory_NilHasher(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilDataPool(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - nil, - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.DataPool = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilDataPoolHolder, err) assert.Nil(t, ppcm) @@ -184,30 +91,9 @@ func TestNewPreProcessorsContainerFactory_NilDataPool(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilAddrConv(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - nil, - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.PubkeyConverter = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilPubkeyConverter, err) assert.Nil(t, ppcm) @@ -216,62 +102,31 @@ func TestNewPreProcessorsContainerFactory_NilAddrConv(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilAccounts(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - createMockPubkeyConverter(), - nil, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.Accounts = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilAccountsAdapter, err) assert.Nil(t, ppcm) } +func TestNewPreProcessorsContainerFactory_NilAccountsProposal(t *testing.T) { + t.Parallel() + + args := createPreProcessorsContainerFactoryArgs() + args.AccountsProposal = nil + ppcm, err := NewPreProcessorsContainerFactory(args) + + assert.ErrorIs(t, err, process.ErrNilAccountsAdapter) + assert.Nil(t, ppcm) +} + func TestNewPreProcessorsContainerFactory_NilTxProcessor(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - nil, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.TxProcessor = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilTxProcessor, err) assert.Nil(t, ppcm) @@ -280,30 +135,9 @@ func TestNewPreProcessorsContainerFactory_NilTxProcessor(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilSCProcessor(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - nil, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.ScProcessor = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilSmartContractProcessor, err) assert.Nil(t, ppcm) @@ -312,30 +146,9 @@ func TestNewPreProcessorsContainerFactory_NilSCProcessor(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilSCR(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - nil, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.ScResultProcessor = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilSmartContractResultProcessor, err) assert.Nil(t, ppcm) @@ -344,30 +157,9 @@ func TestNewPreProcessorsContainerFactory_NilSCR(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilRewardTxProcessor(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - nil, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.RewardsTxProcessor = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilRewardsTxProcessor, err) assert.Nil(t, ppcm) @@ -376,30 +168,9 @@ func TestNewPreProcessorsContainerFactory_NilRewardTxProcessor(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilRequestHandler(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - nil, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.RequestHandler = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilRequestHandler, err) assert.Nil(t, ppcm) @@ -408,30 +179,9 @@ func TestNewPreProcessorsContainerFactory_NilRequestHandler(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilFeeHandler(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - nil, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.EconomicsFee = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilEconomicsFeeHandler, err) assert.Nil(t, ppcm) @@ -440,30 +190,9 @@ func TestNewPreProcessorsContainerFactory_NilFeeHandler(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilGasHandler(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - nil, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.GasHandler = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilGasHandler, err) assert.Nil(t, ppcm) @@ -472,30 +201,9 @@ func TestNewPreProcessorsContainerFactory_NilGasHandler(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilBlockTracker(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - nil, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.BlockTracker = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilBlockTracker, err) assert.Nil(t, ppcm) @@ -504,30 +212,9 @@ func TestNewPreProcessorsContainerFactory_NilBlockTracker(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilBlockSizeComputationHandler(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - nil, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.BlockSizeComputation = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilBlockSizeComputationHandler, err) assert.Nil(t, ppcm) @@ -536,30 +223,9 @@ func TestNewPreProcessorsContainerFactory_NilBlockSizeComputationHandler(t *test func TestNewPreProcessorsContainerFactory_NilBalanceComputationHandler(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - nil, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.BalanceComputation = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilBalanceComputationHandler, err) assert.Nil(t, ppcm) @@ -568,62 +234,31 @@ func TestNewPreProcessorsContainerFactory_NilBalanceComputationHandler(t *testin func TestNewPreProcessorsContainerFactory_NilEnableEpochsHandler(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - nil, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.EnableEpochsHandler = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilEnableEpochsHandler, err) assert.Nil(t, ppcm) } +func TestNewPreProcessorsContainerFactory_NilEnableRoundsHandler(t *testing.T) { + t.Parallel() + + args := createPreProcessorsContainerFactoryArgs() + args.EnableRoundsHandler = nil + ppcm, err := NewPreProcessorsContainerFactory(args) + + assert.Equal(t, process.ErrNilEnableRoundsHandler, err) + assert.Nil(t, ppcm) +} + func TestNewPreProcessorsContainerFactory_NilTxTypeHandler(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - nil, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.TxTypeHandler = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilTxTypeHandler, err) assert.Nil(t, ppcm) @@ -632,30 +267,9 @@ func TestNewPreProcessorsContainerFactory_NilTxTypeHandler(t *testing.T) { func TestNewPreProcessorsContainerFactory_NilScheduledTxsExecutionHandler(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - nil, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.ScheduledTxsExecutionHandler = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilScheduledTxsExecutionHandler, err) assert.Nil(t, ppcm) @@ -664,30 +278,9 @@ func TestNewPreProcessorsContainerFactory_NilScheduledTxsExecutionHandler(t *tes func TestNewPreProcessorsContainerFactory_NilProcessedMiniBlocksTracker(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - nil, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.ProcessedMiniBlocksTracker = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilProcessedMiniBlocksTracker, err) assert.Nil(t, ppcm) @@ -696,30 +289,9 @@ func TestNewPreProcessorsContainerFactory_NilProcessedMiniBlocksTracker(t *testi func TestNewPreProcessorsContainerFactory_NilTxExecutionOrderHandler(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - nil, - ) + args := createPreProcessorsContainerFactoryArgs() + args.TxExecutionOrderHandler = nil + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Equal(t, process.ErrNilTxExecutionOrderHandler, err) assert.Nil(t, ppcm) @@ -728,30 +300,8 @@ func TestNewPreProcessorsContainerFactory_NilTxExecutionOrderHandler(t *testing. func TestNewPreProcessorsContainerFactory(t *testing.T) { t.Parallel() - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataRetrieverMock.NewPoolsHolderMock(), - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Nil(t, err) assert.NotNil(t, ppcm) @@ -765,30 +315,9 @@ func TestPreProcessorsContainerFactory_CreateErrTxPreproc(t *testing.T) { return nil } - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataPool, - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.DataPool = dataPool + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Nil(t, err) assert.NotNil(t, ppcm) @@ -807,38 +336,16 @@ func TestPreProcessorsContainerFactory_CreateErrScrPreproc(t *testing.T) { dataPool.UnsignedTransactionsCalled = func() dataRetriever.ShardedDataCacherNotifier { return nil } - - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataPool, - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.DataPool = dataPool + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Nil(t, err) assert.NotNil(t, ppcm) container, err := ppcm.Create() assert.Nil(t, container) - assert.Equal(t, process.ErrNilUTxDataPool, err) + assert.Equal(t, process.ErrNilTransactionPool, err) } func TestPreProcessorsContainerFactory_Create(t *testing.T) { @@ -854,30 +361,9 @@ func TestPreProcessorsContainerFactory_Create(t *testing.T) { return &testscommon.ShardedDataStub{} } - ppcm, err := NewPreProcessorsContainerFactory( - mock.NewMultiShardsCoordinatorMock(3), - &storageStubs.ChainStorerStub{}, - &mock.MarshalizerMock{}, - &hashingMocks.HasherMock{}, - dataPool, - createMockPubkeyConverter(), - &stateMock.AccountsStub{}, - &testscommon.RequestHandlerStub{}, - &testscommon.TxProcessorMock{}, - &testscommon.SCProcessorMock{}, - &testscommon.SmartContractResultsProcessorMock{}, - &testscommon.RewardTxProcessorMock{}, - &economicsmocks.EconomicsHandlerMock{}, - &testscommon.GasHandlerStub{}, - &mock.BlockTrackerMock{}, - &testscommon.BlockSizeComputationStub{}, - &testscommon.BalanceComputationStub{}, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &testscommon.TxTypeHandlerMock{}, - &testscommon.ScheduledTxsExecutionStub{}, - &testscommon.ProcessedMiniBlocksTrackerStub{}, - &commonMock.TxExecutionOrderHandlerStub{}, - ) + args := createPreProcessorsContainerFactoryArgs() + args.DataPool = dataPool + ppcm, err := NewPreProcessorsContainerFactory(args) assert.Nil(t, err) assert.NotNil(t, ppcm) @@ -886,3 +372,36 @@ func TestPreProcessorsContainerFactory_Create(t *testing.T) { assert.Nil(t, err) assert.Equal(t, 4, container.Len()) } + +func createPreProcessorsContainerFactoryArgs() ArgsPreProcessorsContainerFactory { + return ArgsPreProcessorsContainerFactory{ + ShardCoordinator: mock.NewMultiShardsCoordinatorMock(3), + Store: &storageStubs.ChainStorerStub{}, + Marshalizer: &mock.MarshalizerMock{}, + Hasher: &hashingMocks.HasherMock{}, + DataPool: dataRetrieverMock.NewPoolsHolderMock(), + PubkeyConverter: createMockPubkeyConverter(), + Accounts: &stateMock.AccountsStub{}, + AccountsProposal: &stateMock.AccountsStub{}, + RequestHandler: &testscommon.RequestHandlerStub{}, + TxProcessor: &testscommon.TxProcessorMock{}, + ScProcessor: &testscommon.SCProcessorMock{}, + ScResultProcessor: &testscommon.SmartContractResultsProcessorMock{}, + RewardsTxProcessor: &testscommon.RewardTxProcessorMock{}, + EconomicsFee: &economicsmocks.EconomicsHandlerMock{}, + GasHandler: &testscommon.GasHandlerStub{}, + BlockTracker: &mock.BlockTrackerMock{}, + BlockSizeComputation: &testscommon.BlockSizeComputationStub{}, + BalanceComputation: &testscommon.BalanceComputationStub{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + RoundNotifier: &epochNotifier.RoundNotifierStub{}, + TxTypeHandler: &testscommon.TxTypeHandlerMock{}, + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + TxExecutionOrderHandler: &commonMock.TxExecutionOrderHandlerStub{}, + TxCacheSelectionConfig: createMockTxCacheSelectionConfig(), + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + } +} diff --git a/process/headerCheck/headerIntegrityVerifier.go b/process/headerCheck/headerIntegrityVerifier.go index dead1035acd..9c98a603bda 100644 --- a/process/headerCheck/headerIntegrityVerifier.go +++ b/process/headerCheck/headerIntegrityVerifier.go @@ -38,8 +38,8 @@ func NewHeaderIntegrityVerifier( } // GetVersion returns the version by providing the epoch -func (hdrIntVer *headerIntegrityVerifier) GetVersion(epoch uint32) string { - return hdrIntVer.headerVersionHandler.GetVersion(epoch) +func (hdrIntVer *headerIntegrityVerifier) GetVersion(epoch uint32, round uint64) string { + return hdrIntVer.headerVersionHandler.GetVersion(epoch, round) } // Verify will check the header's fields such as the chain ID or the software version diff --git a/process/headerCheck/headerIntegrityVerifier_test.go b/process/headerCheck/headerIntegrityVerifier_test.go index 8809ba68548..d0dd7f3c1ac 100644 --- a/process/headerCheck/headerIntegrityVerifier_test.go +++ b/process/headerCheck/headerIntegrityVerifier_test.go @@ -99,7 +99,7 @@ func TestHeaderIntegrityVerifier_GetVersionShouldWork(t *testing.T) { t.Parallel() hvh := &testscommon.HeaderVersionHandlerStub{ - GetVersionCalled: func(epoch uint32) string { + GetVersionCalled: func(epoch uint32, _ uint64) string { return "v1" }, } @@ -108,5 +108,5 @@ func TestHeaderIntegrityVerifier_GetVersionShouldWork(t *testing.T) { hvh, ) - assert.Equal(t, "v1", hdrIntVer.GetVersion(1)) + assert.Equal(t, "v1", hdrIntVer.GetVersion(1, 1)) } diff --git a/process/headerCheck/headerSignatureVerify.go b/process/headerCheck/headerSignatureVerify.go index 8adf9503074..878172f8bb8 100644 --- a/process/headerCheck/headerSignatureVerify.go +++ b/process/headerCheck/headerSignatureVerify.go @@ -1,6 +1,8 @@ package headerCheck import ( + "encoding/hex" + "errors" "fmt" "time" @@ -20,6 +22,7 @@ import ( ) const headerWaitDelayAtTransition = 50 * time.Millisecond +const maxHeaderWaitRetriesAtTransition = 100 var _ process.InterceptedHeaderSigVerifier = (*HeaderSigVerifier)(nil) @@ -38,6 +41,7 @@ type ArgsHeaderSigVerifier struct { HeadersPool dataRetriever.HeadersPool ProofsPool dataRetriever.ProofsPool StorageService dataRetriever.StorageService + PubKeysHandler pubKeysHandler } // HeaderSigVerifier is component used to check if a header is valid @@ -53,6 +57,7 @@ type HeaderSigVerifier struct { headersPool dataRetriever.HeadersPool proofsPool dataRetriever.ProofsPool storageService dataRetriever.StorageService + pubKeysHandler pubKeysHandler } // NewHeaderSigVerifier will create a new instance of HeaderSigVerifier @@ -74,6 +79,7 @@ func NewHeaderSigVerifier(arguments *ArgsHeaderSigVerifier) (*HeaderSigVerifier, headersPool: arguments.HeadersPool, proofsPool: arguments.ProofsPool, storageService: arguments.StorageService, + pubKeysHandler: arguments.PubKeysHandler, }, nil } @@ -121,6 +127,9 @@ func checkArgsHeaderSigVerifier(arguments *ArgsHeaderSigVerifier) error { if check.IfNil(arguments.StorageService) { return process.ErrNilStorageService } + if check.IfNil(arguments.PubKeysHandler) { + return process.ErrNilPubKeysHandler + } return nil } @@ -291,28 +300,40 @@ func (hsv *HeaderSigVerifier) VerifySignatureForHash(header data.HeaderHandler, return err } - return multiSigVerifier.VerifyAggregatedSig(pubKeysSigners, hash, signature) + pubKeys, err := hsv.pubKeysHandler.GetPubKeysFromBytes(pubKeysSigners) + if err != nil { + return err + } + + return multiSigVerifier.VerifyAggregatedSigV2(pubKeys, hash, signature) } func (hsv *HeaderSigVerifier) getHeaderForProofAtTransition(proof data.HeaderProofHandler) (data.HeaderHandler, error) { var header data.HeaderHandler var err error - for { + for i := 0; i < maxHeaderWaitRetriesAtTransition; i++ { header, err = process.GetHeader(proof.GetHeaderHash(), hsv.headersPool, hsv.storageService, hsv.marshalizer, proof.GetHeaderShardId()) if err == nil { - break + return header, nil } log.Debug("getHeaderForProofAtTransition: failed to get header, will wait and try again", "headerHash", proof.GetHeaderHash(), + "attempt", i+1, + "maxAttempts", maxHeaderWaitRetriesAtTransition, "error", err.Error(), ) + if !errors.Is(err, process.ErrMissingHeader) { + return nil, err + } + time.Sleep(headerWaitDelayAtTransition) } - return header, nil + return nil, fmt.Errorf("%w: failed to get header after %d attempts for hash %s", + err, maxHeaderWaitRetriesAtTransition, hex.EncodeToString(proof.GetHeaderHash())) } func (hsv *HeaderSigVerifier) verifyHeaderProofAtTransition(proof data.HeaderProofHandler) error { @@ -341,7 +362,12 @@ func (hsv *HeaderSigVerifier) verifyHeaderProofAtTransition(proof data.HeaderPro return err } - return multiSigVerifier.VerifyAggregatedSig(consensusPubKeys, proof.GetHeaderHash(), proof.GetAggregatedSignature()) + pubKeys, err := hsv.pubKeysHandler.GetPubKeysFromBytes(consensusPubKeys) + if err != nil { + return err + } + + return multiSigVerifier.VerifyAggregatedSigV2(pubKeys, proof.GetHeaderHash(), proof.GetAggregatedSignature()) } // VerifyHeaderProof checks if the proof is correct for the header @@ -367,7 +393,12 @@ func (hsv *HeaderSigVerifier) VerifyHeaderProof(proofHandler data.HeaderProofHan return err } - return multiSigVerifier.VerifyAggregatedSig(consensusPubKeys, proofHandler.GetHeaderHash(), proofHandler.GetAggregatedSignature()) + pubKeys, err := hsv.pubKeysHandler.GetPubKeysFromBytes(consensusPubKeys) + if err != nil { + return err + } + + return multiSigVerifier.VerifyAggregatedSigV2(pubKeys, proofHandler.GetHeaderHash(), proofHandler.GetAggregatedSignature()) } // VerifyRandSeed will check if rand seed is correct diff --git a/process/headerCheck/headerSignatureVerify_test.go b/process/headerCheck/headerSignatureVerify_test.go index a3ea9f874f2..19220acedac 100644 --- a/process/headerCheck/headerSignatureVerify_test.go +++ b/process/headerCheck/headerSignatureVerify_test.go @@ -20,6 +20,7 @@ import ( "github.com/multiversx/mx-chain-go/sharding/nodesCoordinator" "github.com/multiversx/mx-chain-go/storage" "github.com/multiversx/mx-chain-go/testscommon" + consensusMocks "github.com/multiversx/mx-chain-go/testscommon/consensus" "github.com/multiversx/mx-chain-go/testscommon/cryptoMocks" dataRetrieverMocks "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" @@ -65,6 +66,7 @@ func createHeaderSigVerifierArgs() *ArgsHeaderSigVerifier { }, ProofsPool: &dataRetrieverMocks.ProofsPoolMock{}, StorageService: &genericMocks.ChainStorerMock{}, + PubKeysHandler: &consensusMocks.SigningHandlerStub{}, } } @@ -680,7 +682,7 @@ func TestHeaderSigVerifier_VerifySignatureOk(t *testing.T) { args.NodesCoordinator = nc args.MultiSigContainer = cryptoMocks.NewMultiSignerContainerMock(&cryptoMocks.MultisignerMock{ - VerifyAggregatedSigCalled: func(pubKeysSigners [][]byte, message []byte, aggSig []byte) error { + VerifyAggregatedSigV2Called: func(pubKeysSigners []crypto.PublicKey, message []byte, aggSig []byte) error { wasCalled = true return nil }}) @@ -714,7 +716,7 @@ func TestHeaderSigVerifier_VerifySignatureNotEnoughSigsShouldErrWhenFallbackThre }, } multiSigVerifier := &cryptoMocks.MultisignerMock{ - VerifyAggregatedSigCalled: func(pubKeysSigners [][]byte, message []byte, aggSig []byte) error { + VerifyAggregatedSigV2Called: func(pubKeysSigners []crypto.PublicKey, message []byte, aggSig []byte) error { wasCalled = true return nil }, @@ -753,7 +755,7 @@ func TestHeaderSigVerifier_VerifySignatureOkWhenFallbackThresholdCouldBeApplied( }, } multiSigVerifier := &cryptoMocks.MultisignerMock{ - VerifyAggregatedSigCalled: func(pubKeysSigners [][]byte, message []byte, aggSig []byte) error { + VerifyAggregatedSigV2Called: func(pubKeysSigners []crypto.PublicKey, message []byte, aggSig []byte) error { wasCalled = true return nil }} @@ -812,7 +814,7 @@ func TestHeaderSigVerifier_VerifySignatureWithEquivalentProofsActivated(t *testi args.NodesCoordinator = nc args.MultiSigContainer = cryptoMocks.NewMultiSignerContainerMock(&cryptoMocks.MultisignerMock{ - VerifyAggregatedSigCalled: func(pubKeysSigners [][]byte, message []byte, aggSig []byte) error { + VerifyAggregatedSigV2Called: func(pubKeysSigners []crypto.PublicKey, message []byte, aggSig []byte) error { wasCalled = true return nil }}) @@ -855,6 +857,18 @@ func getFilledHeader() data.HeaderHandler { } } +func getFilledHeaderV2() data.HeaderHandler { + return &dataBlock.HeaderV2{ + Header: &dataBlock.Header{ + PrevHash: []byte("prev hash"), + PrevRandSeed: []byte("prev rand seed"), + RandSeed: []byte("rand seed"), + PubKeysBitmap: []byte{0xFF}, + LeaderSignature: []byte("leader signature"), + }, + } +} + func TestHeaderSigVerifier_VerifyHeaderProof(t *testing.T) { t.Parallel() @@ -892,7 +906,7 @@ func TestHeaderSigVerifier_VerifyHeaderProof(t *testing.T) { }, } args.MultiSigContainer = &cryptoMocks.MultiSignerContainerStub{ - GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSigner, error) { + GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSignerV2, error) { cnt++ if cnt > 1 { return nil, expectedErr @@ -923,9 +937,9 @@ func TestHeaderSigVerifier_VerifyHeaderProof(t *testing.T) { }, } args.MultiSigContainer = &cryptoMocks.MultiSignerContainerStub{ - GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSigner, error) { + GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSignerV2, error) { return &cryptoMocks.MultiSignerStub{ - VerifyAggregatedSigCalled: func(pubKeysSigners [][]byte, message []byte, aggSig []byte) error { + VerifyAggregatedSigV2Called: func(pubKeysSigners []crypto.PublicKey, message []byte, aggSig []byte) error { wasVerifyAggregatedSigCalled = true return nil }, @@ -958,8 +972,8 @@ func TestHeaderSigVerifier_VerifyHeaderProof(t *testing.T) { args.StorageService = &testscommonStorage.ChainStorerStub{ GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { return &testscommonStorage.StorerStub{ - SearchFirstCalled: func(key []byte) ([]byte, error) { - return nil, errors.New("not found") + GetCalled: func(key []byte) ([]byte, error) { + return nil, process.ErrMissingHeader }, }, nil }, @@ -970,10 +984,10 @@ func TestHeaderSigVerifier_VerifyHeaderProof(t *testing.T) { GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { if numCalls < 2 { numCalls++ - return nil, errors.New("not found") + return nil, process.ErrMissingHeader } - return getFilledHeader(), nil + return getFilledHeaderV2(), nil }, } args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ @@ -982,9 +996,9 @@ func TestHeaderSigVerifier_VerifyHeaderProof(t *testing.T) { }, } args.MultiSigContainer = &cryptoMocks.MultiSignerContainerStub{ - GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSigner, error) { + GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSignerV2, error) { return &cryptoMocks.MultiSignerStub{ - VerifyAggregatedSigCalled: func(pubKeysSigners [][]byte, message []byte, aggSig []byte) error { + VerifyAggregatedSigV2Called: func(pubKeysSigners []crypto.PublicKey, message []byte, aggSig []byte) error { wasVerifyAggregatedSigCalled = true return nil }, @@ -1022,9 +1036,9 @@ func TestHeaderSigVerifier_VerifyHeaderProof(t *testing.T) { }, } args.MultiSigContainer = &cryptoMocks.MultiSignerContainerStub{ - GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSigner, error) { + GetMultiSignerCalled: func(epoch uint32) (crypto.MultiSignerV2, error) { return &cryptoMocks.MultiSignerStub{ - VerifyAggregatedSigCalled: func(pubKeysSigners [][]byte, message []byte, aggSig []byte) error { + VerifyAggregatedSigV2Called: func(pubKeysSigners []crypto.PublicKey, message []byte, aggSig []byte) error { wasVerifyAggregatedSigCalled = true return nil }, diff --git a/process/headerCheck/interface.go b/process/headerCheck/interface.go new file mode 100644 index 00000000000..82346fadb1f --- /dev/null +++ b/process/headerCheck/interface.go @@ -0,0 +1,8 @@ +package headerCheck + +import crypto "github.com/multiversx/mx-chain-crypto-go" + +type pubKeysHandler interface { + GetPubKeysFromBytes(pubKeysBytes [][]byte) ([]crypto.PublicKey, error) + IsInterfaceNil() bool +} diff --git a/process/heartbeat/interceptedHeartbeat.go b/process/heartbeat/interceptedHeartbeat.go index 2e1f08a9972..529d8698600 100644 --- a/process/heartbeat/interceptedHeartbeat.go +++ b/process/heartbeat/interceptedHeartbeat.go @@ -111,6 +111,11 @@ func (ihb *interceptedHeartbeat) CheckValidity() error { return nil } +// ShouldAllowDuplicates returns if this type of intercepted data should allow duplicates +func (ihb *interceptedHeartbeat) ShouldAllowDuplicates() bool { + return true +} + // IsForCurrentShard always returns true func (ihb *interceptedHeartbeat) IsForCurrentShard() bool { return true diff --git a/process/heartbeat/interceptedHeartbeat_test.go b/process/heartbeat/interceptedHeartbeat_test.go index 46d504e9b70..d6dbcb8bb6e 100644 --- a/process/heartbeat/interceptedHeartbeat_test.go +++ b/process/heartbeat/interceptedHeartbeat_test.go @@ -11,6 +11,7 @@ import ( "github.com/multiversx/mx-chain-go/heartbeat" "github.com/multiversx/mx-chain-go/process" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func createDefaultInterceptedHeartbeat() *heartbeat.HeartbeatV2 { @@ -189,4 +190,5 @@ func TestInterceptedHeartbeat_Getters(t *testing.T) { assert.Equal(t, []byte(""), ihb.Hash()) providedHBSize := getSizeOfHeartbeat(providedHB) assert.Equal(t, providedHBSize, ihb.SizeInBytes()) + require.True(t, ihb.ShouldAllowDuplicates()) } diff --git a/process/heartbeat/interceptedPeerAuthentication.go b/process/heartbeat/interceptedPeerAuthentication.go index a10e5e6dd8d..4325c798cc9 100644 --- a/process/heartbeat/interceptedPeerAuthentication.go +++ b/process/heartbeat/interceptedPeerAuthentication.go @@ -158,6 +158,11 @@ func (ipa *interceptedPeerAuthentication) CheckValidity() error { return nil } +// ShouldAllowDuplicates returns if this type of intercepted data should allow duplicates +func (ipa *interceptedPeerAuthentication) ShouldAllowDuplicates() bool { + return true +} + // IsForCurrentShard always returns true func (ipa *interceptedPeerAuthentication) IsForCurrentShard() bool { return true diff --git a/process/heartbeat/interceptedPeerAuthentication_test.go b/process/heartbeat/interceptedPeerAuthentication_test.go index fdc19e6a130..3f1f8ec52ca 100644 --- a/process/heartbeat/interceptedPeerAuthentication_test.go +++ b/process/heartbeat/interceptedPeerAuthentication_test.go @@ -341,4 +341,5 @@ func TestInterceptedPeerAuthentication_Getters(t *testing.T) { assert.Equal(t, expectedPeerAuthentication.Pid, identifiers[1]) providedPASize := getSizeOfPA(providedPA) assert.Equal(t, providedPASize, ipa.SizeInBytes()) + assert.True(t, ipa.ShouldAllowDuplicates()) } diff --git a/process/interceptors/baseDataInterceptor.go b/process/interceptors/baseDataInterceptor.go index cec00abd756..85a4ea91e25 100644 --- a/process/interceptors/baseDataInterceptor.go +++ b/process/interceptors/baseDataInterceptor.go @@ -6,6 +6,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process" @@ -21,6 +22,7 @@ type baseDataInterceptor struct { debugHandler process.InterceptedDebugger preferredPeersHolder process.PreferredPeersHolderHandler interceptedDataVerifier process.InterceptedDataVerifier + managedPeersHolder common.ManagedPeersHolder } func (bdi *baseDataInterceptor) preProcessMesage(message p2p.MessageP2P, fromConnectedPeer core.PeerID) error { @@ -59,12 +61,28 @@ func (bdi *baseDataInterceptor) shouldSkipAntifloodChecks(fromConnectedPeer core } func (bdi *baseDataInterceptor) isMessageFromSelfToSelf(fromConnectedPeer core.PeerID, message p2p.MessageP2P) bool { - return bytes.Equal(message.Signature(), message.From()) && - bytes.Equal(message.From(), bdi.currentPeerId.Bytes()) && - fromConnectedPeer == bdi.currentPeerId + isPidManagedByCurrentNode := bdi.managedPeersHolder.IsPidManagedByCurrentNode(fromConnectedPeer) + isSelfPidSingleKey := bytes.Equal(message.From(), bdi.currentPeerId.Bytes()) + isPidManagedBySelf := isPidManagedByCurrentNode || isSelfPidSingleKey + isSamePid := bytes.Equal(message.From(), fromConnectedPeer.Bytes()) + sigEqualsFrom := bytes.Equal(message.Signature(), message.From()) + return isPidManagedBySelf && isSamePid && sigEqualsFrom } -func (bdi *baseDataInterceptor) processInterceptedData(data process.InterceptedData, msg p2p.MessageP2P) { +func (bdi *baseDataInterceptor) isMessageFromSelfOriginator(message p2p.MessageP2P) bool { + pidFrom := core.PeerID(message.From()) + isPidManagedByCurrentNode := bdi.managedPeersHolder.IsPidManagedByCurrentNode(pidFrom) + isSelfSingleKey := bytes.Equal(message.From(), bdi.currentPeerId.Bytes()) + return isPidManagedByCurrentNode || isSelfSingleKey +} + +func (bdi *baseDataInterceptor) processInterceptedData( + data process.InterceptedData, + msg p2p.MessageP2P, + fromConnectedPeer core.PeerID, +) bool { + bdi.processDebugInterceptedDataSuccess(data, msg, fromConnectedPeer) + err := bdi.processor.Validate(data, msg.Peer()) if err != nil { log.Trace("intercepted data is not valid", @@ -77,10 +95,10 @@ func (bdi *baseDataInterceptor) processInterceptedData(data process.InterceptedD ) bdi.processDebugInterceptedData(data, err) - return + return false } - err = bdi.processor.Save(data, msg.Peer(), bdi.topic) + savedData, err := bdi.processor.Save(data, msg.Peer(), bdi.topic, msg.BroadcastMethod()) if err != nil { log.Trace("intercepted data can not be processed", "hash", data.Hash(), @@ -92,7 +110,11 @@ func (bdi *baseDataInterceptor) processInterceptedData(data process.InterceptedD ) bdi.processDebugInterceptedData(data, err) - return + return savedData + } + + if savedData { + bdi.interceptedDataVerifier.MarkVerified(data, msg.BroadcastMethod()) } log.Trace("intercepted data is processed", @@ -102,7 +124,10 @@ func (bdi *baseDataInterceptor) processInterceptedData(data process.InterceptedD "seq no", p2p.MessageOriginatorSeq(msg), "intercepted data", data.String(), ) + bdi.processDebugInterceptedData(data, err) + + return savedData } func (bdi *baseDataInterceptor) processDebugInterceptedData(interceptedData process.InterceptedData, err error) { @@ -113,6 +138,12 @@ func (bdi *baseDataInterceptor) processDebugInterceptedData(interceptedData proc bdi.mutDebugHandler.RUnlock() } +func (bdi *baseDataInterceptor) processDebugInterceptedDataSuccess(data process.InterceptedData, msg p2p.MessageP2P, fromConnectedPeer core.PeerID) { + bdi.mutDebugHandler.RLock() + bdi.debugHandler.LogReceivedData(data, msg, fromConnectedPeer) + bdi.mutDebugHandler.RUnlock() +} + func (bdi *baseDataInterceptor) receivedDebugInterceptedData(interceptedData process.InterceptedData) { identifiers := interceptedData.Identifiers() diff --git a/process/interceptors/baseDataInterceptor_test.go b/process/interceptors/baseDataInterceptor_test.go index ad7f3d66147..df31239c194 100644 --- a/process/interceptors/baseDataInterceptor_test.go +++ b/process/interceptors/baseDataInterceptor_test.go @@ -3,6 +3,7 @@ package interceptors import ( "errors" "testing" + "time" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-go/p2p" @@ -15,7 +16,7 @@ import ( const fromConnectedPeer = "from connected peer" -//------- preProcessMessage +// ------- preProcessMessage func newBaseDataInterceptorForPreProcess( throttler process.InterceptorThrottler, @@ -26,14 +27,17 @@ func newBaseDataInterceptorForPreProcess( throttler: throttler, antifloodHandler: antifloodHandler, preferredPeersHolder: preferredPeersHolder, + managedPeersHolder: &testscommon.ManagedPeersHolderStub{}, } } func newBaseDataInterceptorForProcess(processor process.InterceptorProcessor, debugHandler process.InterceptedDebugger, topic string) *baseDataInterceptor { return &baseDataInterceptor{ - topic: topic, - processor: processor, - debugHandler: debugHandler, + topic: topic, + processor: processor, + debugHandler: debugHandler, + interceptedDataVerifier: defaultInterceptedDataVerifier(time.Second), + managedPeersHolder: &testscommon.ManagedPeersHolderStub{}, } } @@ -214,7 +218,7 @@ func TestPreProcessMessage_CanProcessFromPreferredPeer(t *testing.T) { assert.Equal(t, int32(1), throttler.StartProcessingCount()) } -//------- processInterceptedData +// ------- processInterceptedData func TestProcessInterceptedData_NotValidShouldCallDoneAndNotCallProcessed(t *testing.T) { t.Parallel() @@ -224,14 +228,14 @@ func TestProcessInterceptedData_NotValidShouldCallDoneAndNotCallProcessed(t *tes ValidateCalled: func(data process.InterceptedData) error { return errors.New("not valid") }, - SaveCalled: func(data process.InterceptedData) error { + SaveCalled: func(data process.InterceptedData) (bool, error) { processCalled = true - return nil + return true, nil }, } bdi := newBaseDataInterceptorForProcess(processor, &mock.InterceptedDebugHandlerStub{}, "topic") - bdi.processInterceptedData(&testscommon.InterceptedDataStub{}, &p2pmocks.P2PMessageMock{}) + bdi.processInterceptedData(&testscommon.InterceptedDataStub{}, &p2pmocks.P2PMessageMock{}, fromConnectedPeer) assert.False(t, processCalled) } @@ -244,14 +248,14 @@ func TestProcessInterceptedData_ValidShouldCallDoneAndCallProcessed(t *testing.T ValidateCalled: func(data process.InterceptedData) error { return nil }, - SaveCalled: func(data process.InterceptedData) error { + SaveCalled: func(data process.InterceptedData) (bool, error) { processCalled = true - return nil + return true, nil }, } bdi := newBaseDataInterceptorForProcess(processor, &mock.InterceptedDebugHandlerStub{}, "topic") - bdi.processInterceptedData(&testscommon.InterceptedDataStub{}, &p2pmocks.P2PMessageMock{}) + bdi.processInterceptedData(&testscommon.InterceptedDataStub{}, &p2pmocks.P2PMessageMock{}, fromConnectedPeer) assert.True(t, processCalled) } @@ -264,19 +268,19 @@ func TestProcessInterceptedData_ProcessErrorShouldCallDone(t *testing.T) { ValidateCalled: func(data process.InterceptedData) error { return nil }, - SaveCalled: func(data process.InterceptedData) error { + SaveCalled: func(data process.InterceptedData) (bool, error) { processCalled = true - return errors.New("error while processing") + return false, errors.New("error while processing") }, } bdi := newBaseDataInterceptorForProcess(processor, &mock.InterceptedDebugHandlerStub{}, "topic") - bdi.processInterceptedData(&testscommon.InterceptedDataStub{}, &p2pmocks.P2PMessageMock{}) + bdi.processInterceptedData(&testscommon.InterceptedDataStub{}, &p2pmocks.P2PMessageMock{}, fromConnectedPeer) assert.True(t, processCalled) } -//------- debug +// ------- debug func TestProcessDebugInterceptedData_ShouldWork(t *testing.T) { t.Parallel() diff --git a/process/interceptors/disabled/disabledInterceptedChunksProcessor.go b/process/interceptors/disabled/disabledInterceptedChunksProcessor.go index b84b0bcb998..1c1b1090489 100644 --- a/process/interceptors/disabled/disabledInterceptedChunksProcessor.go +++ b/process/interceptors/disabled/disabledInterceptedChunksProcessor.go @@ -2,6 +2,7 @@ package disabled import ( "github.com/multiversx/mx-chain-core-go/data/batch" + "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process" ) @@ -14,7 +15,7 @@ func NewDisabledInterceptedChunksProcessor() *disabledInterceptedChunksProcessor } // CheckBatch returns a checked chunk result that signals that no chunk has been received -func (d *disabledInterceptedChunksProcessor) CheckBatch(_ *batch.Batch, _ process.WhiteListHandler) (process.CheckedChunkResult, error) { +func (d *disabledInterceptedChunksProcessor) CheckBatch(_ *batch.Batch, _ process.WhiteListHandler, _ p2p.BroadcastMethod) (process.CheckedChunkResult, error) { return process.CheckedChunkResult{ IsChunk: false, HaveAllChunks: false, @@ -22,6 +23,10 @@ func (d *disabledInterceptedChunksProcessor) CheckBatch(_ *batch.Batch, _ proces }, nil } +// MarkVerified does nothing +func (d *disabledInterceptedChunksProcessor) MarkVerified(_ *batch.Batch, _ p2p.BroadcastMethod) { +} + // Close returns nil func (d *disabledInterceptedChunksProcessor) Close() error { return nil diff --git a/process/interceptors/epochStartMetaBlockInterceptor.go b/process/interceptors/epochStartMetaBlockInterceptor.go index 0559c1cf7ef..032feb8c7b5 100644 --- a/process/interceptors/epochStartMetaBlockInterceptor.go +++ b/process/interceptors/epochStartMetaBlockInterceptor.go @@ -6,7 +6,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" - "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" @@ -30,7 +30,7 @@ type epochStartMetaBlockInterceptor struct { consensusPercentage float32 mutReceivedMetaBlocks sync.RWMutex - mapReceivedMetaBlocks map[string]*block.MetaBlock + mapReceivedMetaBlocks map[string]data.MetaHeaderHandler mapMetaBlocksFromPeers map[string][]core.PeerID registeredHandlers []func(topic string, hash []byte, data interface{}) @@ -50,7 +50,7 @@ func NewEpochStartMetaBlockInterceptor(args ArgsEpochStartMetaBlockInterceptor) hasher: args.Hasher, numConnectedPeersProvider: args.NumConnectedPeersProvider, consensusPercentage: consensusPercentageFloat, - mapReceivedMetaBlocks: make(map[string]*block.MetaBlock), + mapReceivedMetaBlocks: make(map[string]data.MetaHeaderHandler), mapMetaBlocksFromPeers: make(map[string][]core.PeerID), registeredHandlers: make([]func(topic string, hash []byte, data interface{}), 0), }, nil @@ -58,8 +58,7 @@ func NewEpochStartMetaBlockInterceptor(args ArgsEpochStartMetaBlockInterceptor) // ProcessReceivedMessage will handle received messages containing epoch start meta blocks func (e *epochStartMetaBlockInterceptor) ProcessReceivedMessage(message p2p.MessageP2P, fromConnectedPeer core.PeerID, _ p2p.MessageHandler) ([]byte, error) { - var epochStartMb block.MetaBlock - err := e.marshalizer.Unmarshal(&epochStartMb, message.Data()) + epochStartMb, err := process.UnmarshalMetaHeader(e.marshalizer, message.Data()) if err != nil { return nil, err } @@ -77,7 +76,7 @@ func (e *epochStartMetaBlockInterceptor) ProcessReceivedMessage(message p2p.Mess log.Trace("received epoch start meta", "epoch", epochStartMb.GetEpoch(), "from peer", fromConnectedPeer.Pretty()) e.mutReceivedMetaBlocks.Lock() - e.mapReceivedMetaBlocks[string(mbHash)] = &epochStartMb + e.mapReceivedMetaBlocks[string(mbHash)] = epochStartMb e.addToPeerList(string(mbHash), fromConnectedPeer) e.mutReceivedMetaBlocks.Unlock() @@ -101,7 +100,7 @@ func (e *epochStartMetaBlockInterceptor) addToPeerList(hash string, peer core.Pe e.mapMetaBlocksFromPeers[hash] = append(e.mapMetaBlocksFromPeers[hash], peer) } -func (e *epochStartMetaBlockInterceptor) checkMaps() (*block.MetaBlock, bool) { +func (e *epochStartMetaBlockInterceptor) checkMaps() (data.MetaHeaderHandler, bool) { e.mutReceivedMetaBlocks.RLock() defer e.mutReceivedMetaBlocks.RUnlock() @@ -122,7 +121,7 @@ func (e *epochStartMetaBlockInterceptor) processEntry( peersList []core.PeerID, hash string, numPeersTarget int, -) (*block.MetaBlock, bool) { +) (data.MetaHeaderHandler, bool) { if len(peersList) >= numPeersTarget { log.Debug("got consensus for epoch start metablock", "len", len(peersList)) return e.mapReceivedMetaBlocks[hash], true @@ -147,7 +146,7 @@ func (e *epochStartMetaBlockInterceptor) RegisterHandler(handler func(topic stri e.mutHandlers.Unlock() } -func (e *epochStartMetaBlockInterceptor) handleFoundEpochStartMetaBlock(metaBlock *block.MetaBlock) { +func (e *epochStartMetaBlockInterceptor) handleFoundEpochStartMetaBlock(metaBlock data.MetaHeaderHandler) { e.mutHandlers.RLock() for _, handler := range e.registeredHandlers { handler(factory.MetachainBlocksTopic, []byte(""), metaBlock) @@ -160,7 +159,7 @@ func (e *epochStartMetaBlockInterceptor) handleFoundEpochStartMetaBlock(metaBloc func (e *epochStartMetaBlockInterceptor) resetMaps() { e.mutReceivedMetaBlocks.Lock() e.mapMetaBlocksFromPeers = make(map[string][]core.PeerID) - e.mapReceivedMetaBlocks = make(map[string]*block.MetaBlock) + e.mapReceivedMetaBlocks = make(map[string]data.MetaHeaderHandler) e.mutReceivedMetaBlocks.Unlock() } diff --git a/process/interceptors/epochStartMetaBlockInterceptor_test.go b/process/interceptors/epochStartMetaBlockInterceptor_test.go index e4c916c8a6d..8b14204911b 100644 --- a/process/interceptors/epochStartMetaBlockInterceptor_test.go +++ b/process/interceptors/epochStartMetaBlockInterceptor_test.go @@ -6,6 +6,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/stretchr/testify/require" @@ -112,8 +113,8 @@ func TestEpochStartMetaBlockInterceptor_EntireFlowShouldWorkAndSetTheEpoch(t *te expectedEpoch := uint32(37) wasCalled := false args := getArgsEpochStartMetaBlockInterceptor() - handlerFunc := func(topic string, hash []byte, data interface{}) { - mbEpoch := data.(*block.MetaBlock).Epoch + handlerFunc := func(topic string, hash []byte, blockData interface{}) { + mbEpoch := blockData.(data.MetaHeaderHandler).GetEpoch() require.Equal(t, expectedEpoch, mbEpoch) wasCalled = true } diff --git a/process/interceptors/export_test.go b/process/interceptors/export_test.go index 7f6771365f1..b4ed8649bac 100644 --- a/process/interceptors/export_test.go +++ b/process/interceptors/export_test.go @@ -35,3 +35,11 @@ func (mdi *MultiDataInterceptor) ChunksProcessor() process.InterceptedChunksProc return mdi.chunksProcessor } + +// PutInCache - +func (idv *interceptedDataVerifier) PutInCache(interceptedData process.InterceptedData, status interceptedDataStatus) { + idv.km.Lock(string(interceptedData.Hash())) + defer idv.km.Unlock(string(interceptedData.Hash())) + + idv.cache.Put(interceptedData.Hash(), status, interceptedDataStatusBytesSize) +} diff --git a/process/interceptors/factory/argInterceptedDataFactory.go b/process/interceptors/factory/argInterceptedDataFactory.go index dbc7350436d..446addc7b6d 100644 --- a/process/interceptors/factory/argInterceptedDataFactory.go +++ b/process/interceptors/factory/argInterceptedDataFactory.go @@ -6,7 +6,6 @@ import ( "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" crypto "github.com/multiversx/mx-chain-crypto-go" - "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/sharding" @@ -37,8 +36,9 @@ type interceptedDataCryptoComponentsHolder interface { BlockSignKeyGen() crypto.KeyGenerator TxSingleSigner() crypto.SingleSigner BlockSigner() crypto.SingleSigner - GetMultiSigner(epoch uint32) (crypto.MultiSigner, error) + GetMultiSigner(epoch uint32) (crypto.MultiSignerV2, error) PublicKey() crypto.PublicKey + ManagedPeersHolder() common.ManagedPeersHolder IsInterfaceNil() bool } diff --git a/process/interceptors/factory/interceptedDataVerifierFactory.go b/process/interceptors/factory/interceptedDataVerifierFactory.go index 2775bbdc61a..4bbfe66627e 100644 --- a/process/interceptors/factory/interceptedDataVerifierFactory.go +++ b/process/interceptors/factory/interceptedDataVerifierFactory.go @@ -5,23 +5,23 @@ import ( "sync" "time" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/interceptors" "github.com/multiversx/mx-chain-go/storage" "github.com/multiversx/mx-chain-go/storage/cache" + "github.com/multiversx/mx-chain-go/storage/disabled" ) // InterceptedDataVerifierFactoryArgs holds the required arguments for interceptedDataVerifierFactory type InterceptedDataVerifierFactoryArgs struct { - CacheSpan time.Duration - CacheExpiry time.Duration + InterceptedDataVerifierConfig config.InterceptedDataVerifierConfig } // interceptedDataVerifierFactory encapsulates the required arguments to create InterceptedDataVerifier // Furthermore it will hold all such instances in an internal map. type interceptedDataVerifierFactory struct { - cacheSpan time.Duration - cacheExpiry time.Duration + cfg config.InterceptedDataVerifierConfig interceptedDataVerifierMap map[string]storage.Cacher mutex sync.Mutex @@ -30,8 +30,7 @@ type interceptedDataVerifierFactory struct { // NewInterceptedDataVerifierFactory will create a factory instance that will create instance of InterceptedDataVerifiers func NewInterceptedDataVerifierFactory(args InterceptedDataVerifierFactoryArgs) *interceptedDataVerifierFactory { return &interceptedDataVerifierFactory{ - cacheSpan: args.CacheSpan, - cacheExpiry: args.CacheExpiry, + cfg: args.InterceptedDataVerifierConfig, interceptedDataVerifierMap: make(map[string]storage.Cacher), mutex: sync.Mutex{}, } @@ -39,10 +38,7 @@ func NewInterceptedDataVerifierFactory(args InterceptedDataVerifierFactoryArgs) // Create will return an instance of InterceptedDataVerifier func (idvf *interceptedDataVerifierFactory) Create(topic string) (process.InterceptedDataVerifier, error) { - internalCache, err := cache.NewTimeCacher(cache.ArgTimeCacher{ - DefaultSpan: idvf.cacheSpan, - CacheExpiry: idvf.cacheExpiry, - }) + internalCache, err := idvf.createCache() if err != nil { return nil, err } @@ -54,6 +50,17 @@ func (idvf *interceptedDataVerifierFactory) Create(topic string) (process.Interc return interceptors.NewInterceptedDataVerifier(internalCache) } +func (idvf *interceptedDataVerifierFactory) createCache() (storage.Cacher, error) { + if !idvf.cfg.EnableCaching { + return disabled.NewCache(), nil + } + + return cache.NewTimeCacher(cache.ArgTimeCacher{ + DefaultSpan: time.Duration(idvf.cfg.CacheSpanInSec) * time.Second, + CacheExpiry: time.Duration(idvf.cfg.CacheExpiryInSec) * time.Second, + }) +} + // Close will close all the sweeping routines created by the cache. func (idvf *interceptedDataVerifierFactory) Close() error { for topic, cacher := range idvf.interceptedDataVerifierMap { diff --git a/process/interceptors/factory/interceptedDataVerifierFactory_test.go b/process/interceptors/factory/interceptedDataVerifierFactory_test.go index 45f42ec05fd..4a77976b220 100644 --- a/process/interceptors/factory/interceptedDataVerifierFactory_test.go +++ b/process/interceptors/factory/interceptedDataVerifierFactory_test.go @@ -2,15 +2,18 @@ package factory import ( "testing" - "time" + "github.com/multiversx/mx-chain-go/config" "github.com/stretchr/testify/require" ) func createMockArgInterceptedDataVerifierFactory() InterceptedDataVerifierFactoryArgs { return InterceptedDataVerifierFactoryArgs{ - CacheSpan: time.Second, - CacheExpiry: time.Second, + InterceptedDataVerifierConfig: config.InterceptedDataVerifierConfig{ + EnableCaching: true, + CacheSpanInSec: 1, + CacheExpiryInSec: 1, + }, } } @@ -34,11 +37,41 @@ func TestNewInterceptedDataVerifierFactory(t *testing.T) { func TestInterceptedDataVerifierFactory_Create(t *testing.T) { t.Parallel() - factory := NewInterceptedDataVerifierFactory(createMockArgInterceptedDataVerifierFactory()) - require.NotNil(t, factory) + t.Run("should fail if cache creation fails", func(t *testing.T) { + t.Parallel() + + args := createMockArgInterceptedDataVerifierFactory() + args.InterceptedDataVerifierConfig.CacheSpanInSec = 0 + factory := NewInterceptedDataVerifierFactory(args) + require.NotNil(t, factory) + + interceptedDataVerifier, err := factory.Create("mockTopic") + require.Error(t, err) + require.Nil(t, interceptedDataVerifier) + }) + t.Run("should work with caching enabled", func(t *testing.T) { + t.Parallel() + + factory := NewInterceptedDataVerifierFactory(createMockArgInterceptedDataVerifierFactory()) + require.NotNil(t, factory) + + interceptedDataVerifier, err := factory.Create("mockTopic") + require.NoError(t, err) + + require.False(t, interceptedDataVerifier.IsInterfaceNil()) + require.NoError(t, factory.Close()) + }) + t.Run("should work with caching disabled", func(t *testing.T) { + t.Parallel() + + args := createMockArgInterceptedDataVerifierFactory() + args.InterceptedDataVerifierConfig.EnableCaching = false + factory := NewInterceptedDataVerifierFactory(args) + require.NotNil(t, factory) - interceptedDataVerifier, err := factory.Create("mockTopic") - require.NoError(t, err) + interceptedDataVerifier, err := factory.Create("mockTopic") + require.NoError(t, err) - require.False(t, interceptedDataVerifier.IsInterfaceNil()) + require.False(t, interceptedDataVerifier.IsInterfaceNil()) + }) } diff --git a/process/interceptors/factory/interceptedEquivalentProofsFactory.go b/process/interceptors/factory/interceptedEquivalentProofsFactory.go index 17822096283..7930e58aa87 100644 --- a/process/interceptors/factory/interceptedEquivalentProofsFactory.go +++ b/process/interceptors/factory/interceptedEquivalentProofsFactory.go @@ -28,6 +28,7 @@ type interceptedEquivalentProofsFactory struct { hasher hashing.Hasher proofSizeChecker common.FieldsSizeChecker km sync.KeyRWMutexHandler + validityAttester process.ValidityAttester } // NewInterceptedEquivalentProofsFactory creates a new instance of interceptedEquivalentProofsFactory @@ -40,6 +41,7 @@ func NewInterceptedEquivalentProofsFactory(args ArgInterceptedEquivalentProofsFa hasher: args.CoreComponents.Hasher(), proofSizeChecker: args.CoreComponents.FieldsSizeChecker(), km: sync.NewKeyRWMutex(), + validityAttester: args.ValidityAttester, } } @@ -54,6 +56,7 @@ func (factory *interceptedEquivalentProofsFactory) Create(buff []byte, _ core.Pe Hasher: factory.hasher, ProofSizeChecker: factory.proofSizeChecker, KeyRWMutexHandler: factory.km, + ValidityAttester: factory.validityAttester, } return interceptedBlocks.NewInterceptedEquivalentProof(args) } diff --git a/process/interceptors/factory/interceptedEquivalentProofsFactory_test.go b/process/interceptors/factory/interceptedEquivalentProofsFactory_test.go index a972f24bfe8..26331df5cd4 100644 --- a/process/interceptors/factory/interceptedEquivalentProofsFactory_test.go +++ b/process/interceptors/factory/interceptedEquivalentProofsFactory_test.go @@ -5,7 +5,6 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" - "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" "github.com/stretchr/testify/require" "github.com/multiversx/mx-chain-go/consensus/mock" @@ -14,6 +13,7 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/consensus" "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" + "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" ) func createMockArgInterceptedEquivalentProofsFactory() ArgInterceptedEquivalentProofsFactory { @@ -24,9 +24,13 @@ func createMockArgInterceptedEquivalentProofsFactory() ArgInterceptedEquivalentP Hash: &hashingMocks.HasherMock{}, FieldsSizeCheckerField: &testscommon.FieldsSizeCheckerMock{}, }, + CryptoComponents: &processMock.CryptoComponentsMock{ + ManagedPeers: &testscommon.ManagedPeersHolderStub{}, + }, ShardCoordinator: &mock.ShardCoordinatorMock{}, HeaderSigVerifier: &consensus.HeaderSigVerifierMock{}, NodesCoordinator: &shardingMocks.NodesCoordinatorStub{}, + ValidityAttester: &processMock.ValidityAttesterStub{}, }, ProofsPool: &dataRetriever.ProofsPoolMock{}, } diff --git a/process/interceptors/factory/interceptedMetaHeaderDataFactory_test.go b/process/interceptors/factory/interceptedMetaHeaderDataFactory_test.go index f962fb9806e..e36b1f1954b 100644 --- a/process/interceptors/factory/interceptedMetaHeaderDataFactory_test.go +++ b/process/interceptors/factory/interceptedMetaHeaderDataFactory_test.go @@ -16,7 +16,6 @@ import ( "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/block/interceptedBlocks" - "github.com/multiversx/mx-chain-go/process/mock" processMocks "github.com/multiversx/mx-chain-go/process/mock" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/consensus" @@ -33,19 +32,19 @@ var errSignerMockVerifySigFails = errors.New("errSignerMockVerifySigFails") var sigOk = []byte("signature") func createMockKeyGen() crypto.KeyGenerator { - return &mock.SingleSignKeyGenMock{ + return &processMocks.SingleSignKeyGenMock{ PublicKeyFromByteArrayCalled: func(b []byte) (key crypto.PublicKey, e error) { if string(b) == "" { return nil, errSingleSignKeyGenMock } - return &mock.SingleSignPublicKey{}, nil + return &processMocks.SingleSignPublicKey{}, nil }, } } func createMockSigner() crypto.SingleSigner { - return &mock.SignerMock{ + return &processMocks.SignerMock{ VerifyStub: func(public crypto.PublicKey, msg []byte, sig []byte) error { if !bytes.Equal(sig, sigOk) { return errSignerMockVerifySigFails @@ -63,14 +62,14 @@ func createMockFeeHandler() process.FeeHandler { return &economicsmocks.EconomicsHandlerMock{} } -func createMockComponentHolders() (*mock.CoreComponentsMock, *mock.CryptoComponentsMock) { +func createMockComponentHolders() (*processMocks.CoreComponentsMock, *processMocks.CryptoComponentsMock) { gracePeriod, _ := graceperiod.NewEpochChangeGracePeriod([]config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}) - coreComponents := &mock.CoreComponentsMock{ - IntMarsh: &mock.MarshalizerMock{}, - TxMarsh: &mock.MarshalizerMock{}, + coreComponents := &processMocks.CoreComponentsMock{ + IntMarsh: &processMocks.MarshalizerMock{}, + TxMarsh: &processMocks.MarshalizerMock{}, Hash: &hashingMocks.HasherMock{}, TxSignHasherField: &hashingMocks.HasherMock{}, - UInt64ByteSliceConv: mock.NewNonceHashConverterMock(), + UInt64ByteSliceConv: processMocks.NewNonceHashConverterMock(), AddrPubKeyConv: createMockPubkeyConverter(), ChainIdCalled: func() string { return "chainID" @@ -81,8 +80,9 @@ func createMockComponentHolders() (*mock.CoreComponentsMock, *mock.CryptoCompone EnableEpochsHandlerField: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, FieldsSizeCheckerField: &testscommon.FieldsSizeCheckerMock{}, EpochChangeGracePeriodHandlerField: gracePeriod, + ProcessConfigsHandlerField: testscommon.GetDefaultProcessConfigsHandler(), } - cryptoComponents := &mock.CryptoComponentsMock{ + cryptoComponents := &processMocks.CryptoComponentsMock{ BlockSig: createMockSigner(), TxSig: createMockSigner(), MultiSigContainer: cryptoMocks.NewMultiSignerContainerMock(cryptoMocks.NewMultiSigner()), @@ -94,21 +94,21 @@ func createMockComponentHolders() (*mock.CoreComponentsMock, *mock.CryptoCompone } func createMockArgMetaHeaderFactoryArgument( - coreComponents *mock.CoreComponentsMock, - cryptoComponents *mock.CryptoComponentsMock, + coreComponents *processMocks.CoreComponentsMock, + cryptoComponents *processMocks.CryptoComponentsMock, ) *ArgInterceptedMetaHeaderFactory { return &ArgInterceptedMetaHeaderFactory{ ArgInterceptedDataFactory: ArgInterceptedDataFactory{ CoreComponents: coreComponents, CryptoComponents: cryptoComponents, - ShardCoordinator: mock.NewOneShardCoordinatorMock(), + ShardCoordinator: processMocks.NewOneShardCoordinatorMock(), NodesCoordinator: shardingMocks.NewNodesCoordinatorMock(), FeeHandler: createMockFeeHandler(), WhiteListerVerifiedTxs: &testscommon.WhiteListHandlerStub{}, HeaderSigVerifier: &consensus.HeaderSigVerifierMock{}, - ValidityAttester: &mock.ValidityAttesterStub{}, - HeaderIntegrityVerifier: &mock.HeaderIntegrityVerifierStub{}, - EpochStartTrigger: &mock.EpochStartTriggerStub{}, + ValidityAttester: &processMocks.ValidityAttesterStub{}, + HeaderIntegrityVerifier: &processMocks.HeaderIntegrityVerifierStub{}, + EpochStartTrigger: &processMocks.EpochStartTriggerStub{}, ArgsParser: &testscommon.ArgumentParserMock{}, PeerSignatureHandler: &processMocks.PeerSignatureHandlerStub{}, SignaturesHandler: &processMocks.SignaturesHandlerStub{}, @@ -119,20 +119,20 @@ func createMockArgMetaHeaderFactoryArgument( } func createMockArgument( - coreComponents *mock.CoreComponentsMock, - cryptoComponents *mock.CryptoComponentsMock, + coreComponents *processMocks.CoreComponentsMock, + cryptoComponents *processMocks.CryptoComponentsMock, ) *ArgInterceptedDataFactory { return &ArgInterceptedDataFactory{ CoreComponents: coreComponents, CryptoComponents: cryptoComponents, - ShardCoordinator: mock.NewOneShardCoordinatorMock(), + ShardCoordinator: processMocks.NewOneShardCoordinatorMock(), NodesCoordinator: shardingMocks.NewNodesCoordinatorMock(), FeeHandler: createMockFeeHandler(), WhiteListerVerifiedTxs: &testscommon.WhiteListHandlerStub{}, HeaderSigVerifier: &consensus.HeaderSigVerifierMock{}, - ValidityAttester: &mock.ValidityAttesterStub{}, - HeaderIntegrityVerifier: &mock.HeaderIntegrityVerifierStub{}, - EpochStartTrigger: &mock.EpochStartTriggerStub{}, + ValidityAttester: &processMocks.ValidityAttesterStub{}, + HeaderIntegrityVerifier: &processMocks.HeaderIntegrityVerifierStub{}, + EpochStartTrigger: &processMocks.EpochStartTriggerStub{}, ArgsParser: &testscommon.ArgumentParserMock{}, PeerSignatureHandler: &processMocks.PeerSignatureHandlerStub{}, SignaturesHandler: &processMocks.SignaturesHandlerStub{}, @@ -258,7 +258,7 @@ func TestNewInterceptedMetaHeaderDataFactory_ShouldWorkAndCreate(t *testing.T) { assert.Nil(t, err) assert.False(t, imh.IsInterfaceNil()) - marshalizer := &mock.MarshalizerMock{} + marshalizer := &processMocks.MarshalizerMock{} emptyMetaHeader := &block.Header{} emptyMetaHeaderBuff, _ := marshalizer.Marshal(emptyMetaHeader) interceptedData, err := imh.Create(emptyMetaHeaderBuff, "") diff --git a/process/interceptors/interceptedDataVerifier.go b/process/interceptors/interceptedDataVerifier.go index 4b36518f230..9c875b04fcd 100644 --- a/process/interceptors/interceptedDataVerifier.go +++ b/process/interceptors/interceptedDataVerifier.go @@ -2,11 +2,12 @@ package interceptors import ( "errors" + "strings" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/core/sync" "github.com/multiversx/mx-chain-go/common" - + "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/storage" ) @@ -14,8 +15,9 @@ import ( type interceptedDataStatus int8 const ( - validInterceptedData interceptedDataStatus = iota - interceptedDataStatusBytesSize = 8 + validInterceptedData interceptedDataStatus = iota + pendingValidInterceptedData + interceptedDataStatusBytesSize = 8 ) type interceptedDataVerifier struct { @@ -38,23 +40,41 @@ func NewInterceptedDataVerifier(cache storage.Cacher) (*interceptedDataVerifier, // Verify will check if the intercepted data has been validated before and put in the time cache. // It will retrieve the status in the cache if it exists, otherwise it will validate it and store the status of the // validation in the cache. Note that the entries are stored for a set period of time -func (idv *interceptedDataVerifier) Verify(interceptedData process.InterceptedData) error { +func (idv *interceptedDataVerifier) Verify(interceptedData process.InterceptedData, topic string, broadcastMethod p2p.BroadcastMethod) error { if len(interceptedData.Hash()) == 0 { return interceptedData.CheckValidity() } hash := string(interceptedData.Hash()) + idv.km.RLock(hash) + val, ok := idv.cache.Get(interceptedData.Hash()) + idv.km.RUnlock(hash) + + if ok { + err := checkCachedData(val, topic, broadcastMethod, interceptedData) + if err != nil { + return err + } + + return nil + } + + // Validate and mark with write lock to prevent race condition idv.km.Lock(hash) defer idv.km.Unlock(hash) - if val, ok := idv.cache.Get(interceptedData.Hash()); ok { - if val == validInterceptedData { - return nil + // Double-check cache after acquiring write lock + val, ok = idv.cache.Get(interceptedData.Hash()) + if ok { + err := checkCachedData(val, topic, broadcastMethod, interceptedData) + if err != nil { + return err } - return process.ErrInvalidInterceptedData + return nil } + // Validate the data err := interceptedData.CheckValidity() if err != nil { logInterceptedDataCheckValidityErr(interceptedData, err) @@ -63,10 +83,50 @@ func (idv *interceptedDataVerifier) Verify(interceptedData process.InterceptedDa return process.ErrInvalidInterceptedData } - idv.cache.Put(interceptedData.Hash(), validInterceptedData, interceptedDataStatusBytesSize) + // Mark as verified pending immediately after successful validation + if !isDirectSend(broadcastMethod) { + idv.cache.Put(interceptedData.Hash(), pendingValidInterceptedData, interceptedDataStatusBytesSize) + } + return nil } +// MarkVerified marks the intercepted data as verified +func (idv *interceptedDataVerifier) MarkVerified(interceptedData process.InterceptedData, broadcastMethod p2p.BroadcastMethod) { + if len(interceptedData.Hash()) == 0 { + return + } + if isDirectSend(broadcastMethod) { + return + } + + idv.km.Lock(string(interceptedData.Hash())) + defer idv.km.Unlock(string(interceptedData.Hash())) + + idv.cache.Put(interceptedData.Hash(), validInterceptedData, interceptedDataStatusBytesSize) +} + +func isDirectSend(broadcastMethod p2p.BroadcastMethod) bool { + return broadcastMethod == p2p.Direct +} + +func shouldCheckForDuplicates( + topic string, + broadcastMethod p2p.BroadcastMethod, + cachedValue interface{}, +) bool { + if isDirectSend(broadcastMethod) { + return false // skip deduplication on direct messages + } + + // if the value was not yet marked as verified, do not check for duplicates yet + isPending := cachedValue == pendingValidInterceptedData + + topicSplit := strings.Split(topic, "_") + isCrossShardTopic := len(topicSplit) == 3 || len(topicSplit) == 1 // cross _0_1 or global topic + return isCrossShardTopic && !isPending +} + func logInterceptedDataCheckValidityErr(interceptedData process.InterceptedData, err error) { if errors.Is(err, common.ErrAlreadyExistingEquivalentProof) { log.Trace("Intercepted data is invalid", "hash", interceptedData.Hash(), "err", err) @@ -76,6 +136,31 @@ func logInterceptedDataCheckValidityErr(interceptedData process.InterceptedData, log.Debug("Intercepted data is invalid", "hash", interceptedData.Hash(), "err", err) } +func checkCachedData( + cachedValue interface{}, + topic string, + broadcastMethod p2p.BroadcastMethod, + interceptedData process.InterceptedData, +) error { + if !isValidInterceptedData(cachedValue) { + return process.ErrInvalidInterceptedData + } + + if !shouldCheckForDuplicates(topic, broadcastMethod, cachedValue) { + return nil + } + + if !interceptedData.ShouldAllowDuplicates() { + return process.ErrDuplicatedInterceptedDataNotAllowed + } + + return nil +} + +func isValidInterceptedData(val interface{}) bool { + return val == validInterceptedData || val == pendingValidInterceptedData +} + // IsInterfaceNil returns true if there is no value under the interface func (idv *interceptedDataVerifier) IsInterfaceNil() bool { return idv == nil diff --git a/process/interceptors/interceptedDataVerifier_test.go b/process/interceptors/interceptedDataVerifier_test.go index 503eb790f32..7e55e77e9d3 100644 --- a/process/interceptors/interceptedDataVerifier_test.go +++ b/process/interceptors/interceptedDataVerifier_test.go @@ -6,6 +6,8 @@ import ( "time" "github.com/multiversx/mx-chain-core-go/core/atomic" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/p2p" "github.com/stretchr/testify/require" "github.com/multiversx/mx-chain-go/process" @@ -63,15 +65,87 @@ func TestInterceptedDataVerifier_EmptyHash(t *testing.T) { }, } - err := verifier.Verify(interceptedData) + err := verifier.Verify(interceptedData, "topic", p2p.Broadcast) require.NoError(t, err) require.Equal(t, 1, checkValidityCounter) - err = verifier.Verify(interceptedData) + err = verifier.Verify(interceptedData, "topic", p2p.Broadcast) require.NoError(t, err) require.Equal(t, 2, checkValidityCounter) } +func TestInterceptedDataVerifier_checkCachedData(t *testing.T) { + t.Parallel() + + t.Run("already cached invalid data should error", func(t *testing.T) { + t.Parallel() + + interceptedData := &testscommon.InterceptedDataStub{ + CheckValidityCalled: func() error { + require.Fail(t, "should have not been called") + return nil + }, + HashCalled: func() []byte { + return []byte("hash") + }, + } + + verifier := defaultInterceptedDataVerifier(defaultSpan) + + verifier.PutInCache(interceptedData, interceptedDataStatus(2)) // not validInterceptedData + + err := verifier.Verify(interceptedData, "topic", p2p.Broadcast) + require.Equal(t, process.ErrInvalidInterceptedData, err) + }) + t.Run("already cached intra shard data should work", func(t *testing.T) { + t.Parallel() + + interceptedData := &testscommon.InterceptedDataStub{ + CheckValidityCalled: func() error { + require.Fail(t, "should have not been called") + return nil + }, + HashCalled: func() []byte { + return []byte("hash") + }, + ShouldAllowDuplicatesCalled: func() bool { + require.Fail(t, "should have not been called") + return true + }, + } + + verifier := defaultInterceptedDataVerifier(defaultSpan) + + verifier.PutInCache(interceptedData, validInterceptedData) + + err := verifier.Verify(interceptedData, "topic_1", p2p.Direct) // intra shard + require.NoError(t, err) + }) + t.Run("already cached data that does not allow duplicates should error", func(t *testing.T) { + t.Parallel() + + interceptedData := &testscommon.InterceptedDataStub{ + CheckValidityCalled: func() error { + require.Fail(t, "should have not been called") + return nil + }, + HashCalled: func() []byte { + return []byte("hash") + }, + ShouldAllowDuplicatesCalled: func() bool { + return false + }, + } + + verifier := defaultInterceptedDataVerifier(defaultSpan) + + verifier.PutInCache(interceptedData, validInterceptedData) + + err := verifier.Verify(interceptedData, "topic_0_1", p2p.Broadcast) + require.Equal(t, process.ErrDuplicatedInterceptedDataNotAllowed, err) + }) +} + func TestInterceptedDataVerifier_CheckValidityShouldWork(t *testing.T) { t.Parallel() @@ -89,19 +163,31 @@ func TestInterceptedDataVerifier_CheckValidityShouldWork(t *testing.T) { return []byte("hash") }, } + interceptedDataWithNoHash := &testscommon.InterceptedDataStub{ + HashCalled: func() []byte { + return []byte("") // peerAuthentication + }, + } verifier := defaultInterceptedDataVerifier(defaultSpan) - err := verifier.Verify(interceptedData) + err := verifier.Verify(interceptedData, "topic", p2p.Broadcast) require.NoError(t, err) + verifier.MarkVerified(interceptedData, p2p.Broadcast) // intra shard, for coverage only + verifier.MarkVerified(interceptedData, p2p.Direct) // Direct send, for coverage only + + verifier.MarkVerified(interceptedDataWithNoHash, p2p.Broadcast) // no hash, for coverage only + + verifier.MarkVerified(interceptedData, p2p.Broadcast) + errCount := atomic.Counter{} wg := sync.WaitGroup{} for i := 0; i < 100; i++ { wg.Add(1) go func() { defer wg.Done() - err := verifier.Verify(interceptedData) + err := verifier.Verify(interceptedData, "topic", p2p.Broadcast) if err != nil { errCount.Add(1) } @@ -132,7 +218,7 @@ func TestInterceptedDataVerifier_CheckValidityShouldNotWork(t *testing.T) { verifier := defaultInterceptedDataVerifier(defaultSpan) - err := verifier.Verify(interceptedData) + err := verifier.Verify(interceptedData, "topic", p2p.Broadcast) require.Equal(t, process.ErrInvalidInterceptedData, err) errCount := atomic.Counter{} @@ -141,7 +227,7 @@ func TestInterceptedDataVerifier_CheckValidityShouldNotWork(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - err := verifier.Verify(interceptedData) + err := verifier.Verify(interceptedData, "topic", p2p.Broadcast) if err != nil { errCount.Add(1) } @@ -153,6 +239,24 @@ func TestInterceptedDataVerifier_CheckValidityShouldNotWork(t *testing.T) { require.Equal(t, int64(101), checkValidityCounter.Get()) } +func TestInterceptedDataVerifier_CheckValidityInterceptedProof(t *testing.T) { + t.Parallel() + + interceptedData := &testscommon.InterceptedDataStub{ + CheckValidityCalled: func() error { + return common.ErrAlreadyExistingEquivalentProof // for coverage only + }, + HashCalled: func() []byte { + return []byte("hash") + }, + } + + verifier := defaultInterceptedDataVerifier(defaultSpan) + + err := verifier.Verify(interceptedData, "topic", p2p.Broadcast) + require.Equal(t, process.ErrInvalidInterceptedData, err) +} + func TestInterceptedDataVerifier_CheckExpiryTime(t *testing.T) { t.Parallel() @@ -177,12 +281,14 @@ func TestInterceptedDataVerifier_CheckExpiryTime(t *testing.T) { verifier := defaultInterceptedDataVerifier(expiryTestDuration) // First retrieval, check validity is reached. - err := verifier.Verify(interceptedData) + err := verifier.Verify(interceptedData, "topic", p2p.Broadcast) require.NoError(t, err) require.Equal(t, int64(1), checkValidityCounter.Get()) + verifier.MarkVerified(interceptedData, p2p.Broadcast) + // Second retrieval should be from the cache. - err = verifier.Verify(interceptedData) + err = verifier.Verify(interceptedData, "topic", p2p.Broadcast) require.NoError(t, err) require.Equal(t, int64(1), checkValidityCounter.Get()) @@ -190,7 +296,7 @@ func TestInterceptedDataVerifier_CheckExpiryTime(t *testing.T) { <-time.After(expiryTestDuration + 100*time.Millisecond) // Third retrieval should reach validity check again. - err = verifier.Verify(interceptedData) + err = verifier.Verify(interceptedData, "topic", p2p.Broadcast) require.NoError(t, err) require.Equal(t, int64(2), checkValidityCounter.Get()) }) @@ -216,12 +322,12 @@ func TestInterceptedDataVerifier_CheckExpiryTime(t *testing.T) { verifier := defaultInterceptedDataVerifier(expiryTestDuration) // First retrieval, check validity is reached. - err := verifier.Verify(interceptedData) + err := verifier.Verify(interceptedData, "topic", p2p.Broadcast) require.Equal(t, process.ErrInvalidInterceptedData, err) require.Equal(t, int64(1), checkValidityCounter.Get()) // Second retrieval - err = verifier.Verify(interceptedData) + err = verifier.Verify(interceptedData, "topic", p2p.Broadcast) require.Equal(t, process.ErrInvalidInterceptedData, err) require.Equal(t, int64(2), checkValidityCounter.Get()) @@ -229,8 +335,56 @@ func TestInterceptedDataVerifier_CheckExpiryTime(t *testing.T) { <-time.After(expiryTestDuration + 100*time.Millisecond) // Third retrieval should reach validity check again. - err = verifier.Verify(interceptedData) + err = verifier.Verify(interceptedData, "topic", p2p.Broadcast) require.Equal(t, process.ErrInvalidInterceptedData, err) require.Equal(t, int64(3), checkValidityCounter.Get()) }) } + +func TestInterceptedDataVerifier_ConcurrentCheckValidity(t *testing.T) { + t.Parallel() + + verifier := defaultInterceptedDataVerifier(defaultSpan) + + checkValidityCounter := atomic.Counter{} + hash := []byte("hash") + + interceptedData := &testscommon.InterceptedDataStub{ + CheckValidityCalled: func() error { + checkValidityCounter.Add(1) + // widen the race window + time.Sleep(10 * time.Millisecond) + return nil + }, + HashCalled: func() []byte { + return hash + }, + } + + numConcurrent := 10 + wg := sync.WaitGroup{} + + // Launch multiple concurrent Verify calls for the same hash + for i := 0; i < numConcurrent; i++ { + wg.Add(1) + go func() { + defer wg.Done() + + err := verifier.Verify(interceptedData, "topic_0_1", p2p.Broadcast) + require.NoError(t, err) + }() + } + + wg.Wait() + + require.Equal(t, int64(1), checkValidityCounter.Get(), "CheckValidity should only be called once per unique hash") + + verifier.MarkVerified(interceptedData, p2p.Broadcast) + + // Future calls should not re-run CheckValidity (they should still succeed) + err := verifier.Verify(interceptedData, "topic_0_1", p2p.Broadcast) + require.NoError(t, err) + + // CheckValidity should not be called again + require.Equal(t, int64(1), checkValidityCounter.Get()) +} diff --git a/process/interceptors/multiDataInterceptor.go b/process/interceptors/multiDataInterceptor.go index 76b33046b03..a2fba37382c 100644 --- a/process/interceptors/multiDataInterceptor.go +++ b/process/interceptors/multiDataInterceptor.go @@ -33,6 +33,7 @@ type ArgMultiDataInterceptor struct { PreferredPeersHolder process.PreferredPeersHolderHandler CurrentPeerId core.PeerID InterceptedDataVerifier process.InterceptedDataVerifier + ManagedPeersHolder common.ManagedPeersHolder } // MultiDataInterceptor is used for intercepting packed multi data @@ -81,6 +82,9 @@ func NewMultiDataInterceptor(arg ArgMultiDataInterceptor) (*MultiDataInterceptor if len(arg.CurrentPeerId) == 0 { return nil, process.ErrEmptyPeerID } + if check.IfNil(arg.ManagedPeersHolder) { + return nil, process.ErrNilManagedPeersHolder + } multiDataIntercept := &MultiDataInterceptor{ baseDataInterceptor: &baseDataInterceptor{ @@ -92,6 +96,7 @@ func NewMultiDataInterceptor(arg ArgMultiDataInterceptor) (*MultiDataInterceptor preferredPeersHolder: arg.PreferredPeersHolder, debugHandler: handler.NewDisabledInterceptorDebugHandler(), interceptedDataVerifier: arg.InterceptedDataVerifier, + managedPeersHolder: arg.ManagedPeersHolder, }, marshalizer: arg.Marshalizer, hasher: arg.Hasher, @@ -143,7 +148,7 @@ func (mdi *MultiDataInterceptor) ProcessReceivedMessage(message p2p.MessageP2P, } mdi.mutChunksProcessor.RLock() - checkChunksRes, err := mdi.chunksProcessor.CheckBatch(&b, mdi.whiteListRequest) + checkChunksRes, err := mdi.chunksProcessor.CheckBatch(&b, mdi.whiteListRequest, message.BroadcastMethod()) mdi.mutChunksProcessor.RUnlock() if err != nil { mdi.throttler.EndProcessing() @@ -165,7 +170,13 @@ func (mdi *MultiDataInterceptor) ProcessReceivedMessage(message p2p.MessageP2P, for index, dataBuff := range multiDataBuff { var interceptedData process.InterceptedData - interceptedData, err = mdi.interceptedData(dataBuff, message.Peer(), fromConnectedPeer) + interceptedData, err = mdi.interceptedData( + dataBuff, + message.Peer(), + fromConnectedPeer, + message.Topic(), + message.BroadcastMethod(), + ) listInterceptedData[index] = interceptedData if err != nil { @@ -200,8 +211,17 @@ func (mdi *MultiDataInterceptor) ProcessReceivedMessage(message p2p.MessageP2P, } go func() { + cntSaves := 0 for _, interceptedData := range listInterceptedData { - mdi.processInterceptedData(interceptedData, message) + dataSaved := mdi.processInterceptedData(interceptedData, message, fromConnectedPeer) + if dataSaved { + cntSaves++ + } + } + + allInfoSaved := cntSaves == len(listInterceptedData) + if allInfoSaved { + mdi.chunksProcessor.MarkVerified(&b, message.BroadcastMethod()) } mdi.throttler.EndProcessing() }() @@ -231,7 +251,13 @@ func (mdi *MultiDataInterceptor) createInterceptedMultiDataMsgID(interceptedMult return mdi.hasher.Compute(string(data)) } -func (mdi *MultiDataInterceptor) interceptedData(dataBuff []byte, originator core.PeerID, fromConnectedPeer core.PeerID) (process.InterceptedData, error) { +func (mdi *MultiDataInterceptor) interceptedData( + dataBuff []byte, + originator core.PeerID, + fromConnectedPeer core.PeerID, + topic string, + broadcastMethod p2p.BroadcastMethod, +) (process.InterceptedData, error) { interceptedData, err := mdi.factory.Create(dataBuff, originator) if err != nil { // this situation is so severe that we need to black list de peers @@ -244,7 +270,7 @@ func (mdi *MultiDataInterceptor) interceptedData(dataBuff []byte, originator cor mdi.receivedDebugInterceptedData(interceptedData) - err = mdi.interceptedDataVerifier.Verify(interceptedData) + err = mdi.interceptedDataVerifier.Verify(interceptedData, topic, broadcastMethod) if err != nil { mdi.processDebugInterceptedData(interceptedData, err) diff --git a/process/interceptors/multiDataInterceptor_test.go b/process/interceptors/multiDataInterceptor_test.go index 3f0e303af1c..2bf647fc8c9 100644 --- a/process/interceptors/multiDataInterceptor_test.go +++ b/process/interceptors/multiDataInterceptor_test.go @@ -10,6 +10,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data/batch" + "github.com/multiversx/mx-chain-go/p2p" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -39,6 +40,7 @@ func createMockArgMultiDataInterceptor() interceptors.ArgMultiDataInterceptor { PreferredPeersHolder: &p2pmocks.PeersHolderStub{}, CurrentPeerId: "pid", InterceptedDataVerifier: &mock.InterceptedDataVerifierMock{}, + ManagedPeersHolder: &testscommon.ManagedPeersHolderStub{}, } } @@ -152,6 +154,17 @@ func TestNewMultiDataInterceptor_EmptyPeerIDShouldErr(t *testing.T) { assert.Equal(t, process.ErrEmptyPeerID, err) } +func TestNewMultiDataInterceptor_NilManagedPeersHolderShouldErr(t *testing.T) { + t.Parallel() + + arg := createMockArgMultiDataInterceptor() + arg.ManagedPeersHolder = nil + mdi, err := interceptors.NewMultiDataInterceptor(arg) + + assert.True(t, check.IfNil(mdi)) + assert.Equal(t, process.ErrNilManagedPeersHolder, err) +} + func TestNewMultiDataInterceptor(t *testing.T) { t.Parallel() @@ -379,7 +392,7 @@ func testProcessReceiveMessageMultiData(t *testing.T, isForCurrentShard bool, ex arg.Processor = createMockInterceptorStub(&checkCalledNum, &processCalledNum) arg.Throttler = throttler arg.InterceptedDataVerifier = &mock.InterceptedDataVerifierMock{ - VerifyCalled: func(interceptedData process.InterceptedData) error { + VerifyCalled: func(interceptedData process.InterceptedData, topic string, broadcastMethod p2p.BroadcastMethod) error { return interceptedData.CheckValidity() }, } @@ -421,7 +434,7 @@ func TestMultiDataInterceptor_ProcessReceivedMessageCheckBatchErrors(t *testing. expectedErr := errors.New("expected error") _ = mdi.SetChunkProcessor( &mock.ChunkProcessorStub{ - CheckBatchCalled: func(b *batch.Batch, w process.WhiteListHandler) (process.CheckedChunkResult, error) { + CheckBatchCalled: func(b *batch.Batch, w process.WhiteListHandler, _ p2p.BroadcastMethod) (process.CheckedChunkResult, error) { return process.CheckedChunkResult{}, expectedErr }, }, @@ -460,7 +473,7 @@ func TestMultiDataInterceptor_ProcessReceivedMessageCheckBatchIsIncomplete(t *te mdi, _ := interceptors.NewMultiDataInterceptor(arg) _ = mdi.SetChunkProcessor( &mock.ChunkProcessorStub{ - CheckBatchCalled: func(b *batch.Batch, w process.WhiteListHandler) (process.CheckedChunkResult, error) { + CheckBatchCalled: func(b *batch.Batch, w process.WhiteListHandler, _ p2p.BroadcastMethod) (process.CheckedChunkResult, error) { return process.CheckedChunkResult{ IsChunk: true, HaveAllChunks: false, @@ -516,7 +529,7 @@ func TestMultiDataInterceptor_ProcessReceivedMessageCheckBatchIsComplete(t *test mdi, _ := interceptors.NewMultiDataInterceptor(arg) _ = mdi.SetChunkProcessor( &mock.ChunkProcessorStub{ - CheckBatchCalled: func(b *batch.Batch, w process.WhiteListHandler) (process.CheckedChunkResult, error) { + CheckBatchCalled: func(b *batch.Batch, w process.WhiteListHandler, _ p2p.BroadcastMethod) (process.CheckedChunkResult, error) { return process.CheckedChunkResult{ IsChunk: true, HaveAllChunks: true, @@ -648,7 +661,7 @@ func processReceivedMessageMultiDataInvalidVersion(t *testing.T, expectedErr err }, } arg.InterceptedDataVerifier = &mock.InterceptedDataVerifierMock{ - VerifyCalled: func(interceptedData process.InterceptedData) error { + VerifyCalled: func(interceptedData process.InterceptedData, topic string, broadcastMethod p2p.BroadcastMethod) error { return interceptedData.CheckValidity() }, } diff --git a/process/interceptors/processor/argTxInterceptorProcessor.go b/process/interceptors/processor/argTxInterceptorProcessor.go index 8ac38b93150..cefac0a32d5 100644 --- a/process/interceptors/processor/argTxInterceptorProcessor.go +++ b/process/interceptors/processor/argTxInterceptorProcessor.go @@ -3,11 +3,13 @@ package processor import ( "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/storage" ) // ArgTxInterceptorProcessor is the argument for the interceptor processor used for transactions // (balance txs, smart contract results, reward and so on) type ArgTxInterceptorProcessor struct { - ShardedDataCache dataRetriever.ShardedDataCacherNotifier - TxValidator process.TxValidator + ShardedDataCache dataRetriever.ShardedDataCacherNotifier + TxValidator process.TxValidator + DirectSentTransactionsCache storage.Cacher } diff --git a/process/interceptors/processor/equivalentProofsInterceptorProcessor.go b/process/interceptors/processor/equivalentProofsInterceptorProcessor.go index 3b9e6997e27..f3f1beb1869 100644 --- a/process/interceptors/processor/equivalentProofsInterceptorProcessor.go +++ b/process/interceptors/processor/equivalentProofsInterceptorProcessor.go @@ -2,6 +2,7 @@ package processor import ( "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process" ) @@ -22,8 +23,8 @@ func (epip *equivalentProofsInterceptorProcessor) Validate(_ process.Intercepted // Save returns nil // proof is added after validity checks, at intercepted data level -func (epip *equivalentProofsInterceptorProcessor) Save(_ process.InterceptedData, _ core.PeerID, _ string) error { - return nil +func (epip *equivalentProofsInterceptorProcessor) Save(_ process.InterceptedData, _ core.PeerID, _ string, _ p2p.BroadcastMethod) (bool, error) { + return true, nil } // RegisterHandler registers a callback function to be notified of incoming equivalent proofs diff --git a/process/interceptors/processor/equivalentProofsInterceptorProcessor_test.go b/process/interceptors/processor/equivalentProofsInterceptorProcessor_test.go index 943134404ad..c93e31ed626 100644 --- a/process/interceptors/processor/equivalentProofsInterceptorProcessor_test.go +++ b/process/interceptors/processor/equivalentProofsInterceptorProcessor_test.go @@ -26,7 +26,7 @@ func TestNewEquivalentProofsInterceptorProcessor(t *testing.T) { require.Nil(t, epip.Validate(nil, "")) // coverage only - err := epip.Save(nil, "", "") + _, err := epip.Save(nil, "", "", "") require.Nil(t, err) // coverage only diff --git a/process/interceptors/processor/hdrInterceptorProcessor.go b/process/interceptors/processor/hdrInterceptorProcessor.go index 524153a136a..b5e4e5f97c0 100644 --- a/process/interceptors/processor/hdrInterceptorProcessor.go +++ b/process/interceptors/processor/hdrInterceptorProcessor.go @@ -6,6 +6,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" @@ -70,17 +71,17 @@ func (hip *HdrInterceptorProcessor) Validate(data process.InterceptedData, _ cor // Save will save the received data into the headers cacher as hash<->[plain header structure] // and in headersNonces as nonce<->hash -func (hip *HdrInterceptorProcessor) Save(data process.InterceptedData, _ core.PeerID, topic string) error { +func (hip *HdrInterceptorProcessor) Save(data process.InterceptedData, _ core.PeerID, topic string, _ p2p.BroadcastMethod) (bool, error) { interceptedHdr, ok := data.(process.HdrValidatorHandler) if !ok { - return process.ErrWrongTypeAssertion + return false, process.ErrWrongTypeAssertion } go hip.notify(interceptedHdr.HeaderHandler(), interceptedHdr.Hash(), topic) hip.headers.AddHeader(interceptedHdr.Hash(), interceptedHdr.HeaderHandler()) - return nil + return true, nil } // RegisterHandler registers a callback function to be notified of incoming headers diff --git a/process/interceptors/processor/hdrInterceptorProcessor_test.go b/process/interceptors/processor/hdrInterceptorProcessor_test.go index 2e5dd17584a..a7fd14e42ed 100644 --- a/process/interceptors/processor/hdrInterceptorProcessor_test.go +++ b/process/interceptors/processor/hdrInterceptorProcessor_test.go @@ -162,7 +162,7 @@ func TestHdrInterceptorProcessor_SaveNilDataShouldErr(t *testing.T) { hip, _ := processor.NewHdrInterceptorProcessor(createMockHdrArgument()) - err := hip.Save(nil, "", "") + _, err := hip.Save(nil, "", "", "") assert.Equal(t, process.ErrWrongTypeAssertion, err) } @@ -211,7 +211,7 @@ func TestHdrInterceptorProcessor_SaveShouldWork(t *testing.T) { chanCalled <- struct{}{} }) - err := hip.Save(hdrInterceptedData, "", "") + _, err := hip.Save(hdrInterceptedData, "", "", "") assert.Nil(t, err) assert.True(t, wasAddedHeaders) diff --git a/process/interceptors/processor/heartbeatInterceptorProcessor.go b/process/interceptors/processor/heartbeatInterceptorProcessor.go index e58e0652b67..c6a3f4f8cc8 100644 --- a/process/interceptors/processor/heartbeatInterceptorProcessor.go +++ b/process/interceptors/processor/heartbeatInterceptorProcessor.go @@ -4,6 +4,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-go/heartbeat" + "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/sharding" "github.com/multiversx/mx-chain-go/storage" @@ -58,10 +59,10 @@ func (hip *heartbeatInterceptorProcessor) Validate(_ process.InterceptedData, _ } // Save will save the intercepted heartbeat inside the heartbeat cacher -func (hip *heartbeatInterceptorProcessor) Save(data process.InterceptedData, fromConnectedPeer core.PeerID, _ string) error { +func (hip *heartbeatInterceptorProcessor) Save(data process.InterceptedData, fromConnectedPeer core.PeerID, _ string, _ p2p.BroadcastMethod) (bool, error) { interceptedHeartbeat, ok := data.(interceptedHeartbeatMessageHandler) if !ok { - return process.ErrWrongTypeAssertion + return false, process.ErrWrongTypeAssertion } hip.heartbeatCacher.Put(fromConnectedPeer.Bytes(), interceptedHeartbeat.Message(), interceptedHeartbeat.SizeInBytes()) @@ -69,10 +70,10 @@ func (hip *heartbeatInterceptorProcessor) Save(data process.InterceptedData, fro return hip.updatePeerInfo(interceptedHeartbeat.Message(), fromConnectedPeer) } -func (hip *heartbeatInterceptorProcessor) updatePeerInfo(message interface{}, fromConnectedPeer core.PeerID) error { +func (hip *heartbeatInterceptorProcessor) updatePeerInfo(message interface{}, fromConnectedPeer core.PeerID) (bool, error) { heartbeatData, ok := message.(*heartbeat.HeartbeatV2) if !ok { - return process.ErrWrongTypeAssertion + return false, process.ErrWrongTypeAssertion } hip.peerShardMapper.PutPeerIdShardId(fromConnectedPeer, hip.shardCoordinator.SelfId()) @@ -80,7 +81,7 @@ func (hip *heartbeatInterceptorProcessor) updatePeerInfo(message interface{}, fr log.Trace("Heartbeat message saved") - return nil + return true, nil } // RegisterHandler registers a callback function to be notified of incoming hearbeat diff --git a/process/interceptors/processor/heartbeatInterceptorProcessor_test.go b/process/interceptors/processor/heartbeatInterceptorProcessor_test.go index 1667e35abc6..598db37d12e 100644 --- a/process/interceptors/processor/heartbeatInterceptorProcessor_test.go +++ b/process/interceptors/processor/heartbeatInterceptorProcessor_test.go @@ -103,7 +103,8 @@ func TestHeartbeatInterceptorProcessor_Save(t *testing.T) { hip, err := processor.NewHeartbeatInterceptorProcessor(createHeartbeatInterceptorProcessArg()) assert.Nil(t, err) assert.False(t, hip.IsInterfaceNil()) - assert.Equal(t, process.ErrWrongTypeAssertion, hip.Save(nil, "", "")) + _, err = hip.Save(nil, "", "", "") + assert.Equal(t, process.ErrWrongTypeAssertion, err) }) t.Run("invalid heartbeat data should error", func(t *testing.T) { t.Parallel() @@ -124,7 +125,8 @@ func TestHeartbeatInterceptorProcessor_Save(t *testing.T) { paip, err := processor.NewHeartbeatInterceptorProcessor(args) assert.Nil(t, err) assert.False(t, paip.IsInterfaceNil()) - assert.Equal(t, process.ErrWrongTypeAssertion, paip.Save(providedData, "", "")) + _, err = paip.Save(providedData, "", "", "") + assert.Equal(t, process.ErrWrongTypeAssertion, err) assert.False(t, wasPutPeerIdShardIdCalled) assert.False(t, wasPutPeerIdSubTypeCalled) }) @@ -168,7 +170,7 @@ func TestHeartbeatInterceptorProcessor_Save(t *testing.T) { assert.Nil(t, err) assert.False(t, hip.IsInterfaceNil()) - err = hip.Save(providedHb, providedPid, "") + _, err = hip.Save(providedHb, providedPid, "", "") assert.Nil(t, err) assert.True(t, wasCalled) assert.True(t, wasPutPeerIdShardIdCalled) diff --git a/process/interceptors/processor/miniblockInterceptorProcessor.go b/process/interceptors/processor/miniblockInterceptorProcessor.go index 18950f12d59..21776927437 100644 --- a/process/interceptors/processor/miniblockInterceptorProcessor.go +++ b/process/interceptors/processor/miniblockInterceptorProcessor.go @@ -8,6 +8,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/block/interceptedBlocks" "github.com/multiversx/mx-chain-go/sharding" @@ -71,10 +72,10 @@ func (mip *MiniblockInterceptorProcessor) Validate(_ process.InterceptedData, _ // Save will save the received miniblocks inside the miniblock cacher after a new validation round // that will be done on each miniblock -func (mip *MiniblockInterceptorProcessor) Save(data process.InterceptedData, _ core.PeerID, topic string) error { +func (mip *MiniblockInterceptorProcessor) Save(data process.InterceptedData, _ core.PeerID, topic string, _ p2p.BroadcastMethod) (bool, error) { interceptedMiniblock, ok := data.(*interceptedBlocks.InterceptedMiniblock) if !ok { - return process.ErrWrongTypeAssertion + return false, process.ErrWrongTypeAssertion } miniblock := interceptedMiniblock.Miniblock() @@ -90,12 +91,12 @@ func (mip *MiniblockInterceptorProcessor) Save(data process.InterceptedData, _ c "receiver shard", miniblock.ReceiverShardID, "hash", hash, ) - return nil + return false, nil } mip.miniblockCache.HasOrAdd(hash, miniblock, miniblock.Size()) - return nil + return true, nil } // RegisterHandler registers a callback function to be notified of incoming miniBlocks diff --git a/process/interceptors/processor/miniblockInterceptorProcessor_test.go b/process/interceptors/processor/miniblockInterceptorProcessor_test.go index 149befd1a98..8ef8ed6d7e8 100644 --- a/process/interceptors/processor/miniblockInterceptorProcessor_test.go +++ b/process/interceptors/processor/miniblockInterceptorProcessor_test.go @@ -122,7 +122,7 @@ func TestMiniblockInterceptorProcessor_SaveWrongTypeAssertion(t *testing.T) { mip, _ := processor.NewMiniblockInterceptorProcessor(createMockMiniblockArgument()) - err := mip.Save(nil, "", "") + _, err := mip.Save(nil, "", "", "") assert.Equal(t, process.ErrWrongTypeAssertion, err) } @@ -138,7 +138,7 @@ func TestMiniblockInterceptorProcessor_NilMiniblockShouldNotAdd(t *testing.T) { } mip, _ := processor.NewMiniblockInterceptorProcessor(arg) - err := mip.Save(nil, "", "") + _, err := mip.Save(nil, "", "", "") assert.Equal(t, process.ErrWrongTypeAssertion, err) } @@ -189,7 +189,7 @@ func TestMiniblockInterceptorProcessor_SaveMiniblockWithSenderInSameShardShouldA mip, _ := processor.NewMiniblockInterceptorProcessor(arg) inTxBlkBdy := createInteceptedMiniblock(miniblock) - err := mip.Save(inTxBlkBdy, "", "") + _, err := mip.Save(inTxBlkBdy, "", "", "") assert.Nil(t, err) } @@ -223,7 +223,7 @@ func TestMiniblockInterceptorProcessor_SaveMiniblocksWithReceiverInSameShardShou chanCalled <- struct{}{} }) - err := mip.Save(inTxBlkBdy, "", "") + _, err := mip.Save(inTxBlkBdy, "", "", "") assert.Nil(t, err) timeout := time.Second * 2 @@ -258,7 +258,7 @@ func TestMiniblockInterceptorProcessor_SaveMiniblockCrossShardForMeNotWhiteListe tbip, _ := processor.NewMiniblockInterceptorProcessor(arg) inTxBlkBdy := createInteceptedMiniblock(miniblock) - err := tbip.Save(inTxBlkBdy, "", "") + _, err := tbip.Save(inTxBlkBdy, "", "", "") assert.Nil(t, err) } @@ -287,7 +287,7 @@ func TestMiniblockInterceptorProcessor_SaveMiniblockCrossShardForMeWhiteListedSh tbip, _ := processor.NewMiniblockInterceptorProcessor(arg) inTxBlkBdy := createInteceptedMiniblock(miniblock) - err := tbip.Save(inTxBlkBdy, "", "") + _, err := tbip.Save(inTxBlkBdy, "", "", "") assert.Nil(t, err) assert.True(t, addedInPool) } diff --git a/process/interceptors/processor/peerAuthenticationInterceptorProcessor.go b/process/interceptors/processor/peerAuthenticationInterceptorProcessor.go index 5864dcfcbf8..f5f9c3beeb3 100644 --- a/process/interceptors/processor/peerAuthenticationInterceptorProcessor.go +++ b/process/interceptors/processor/peerAuthenticationInterceptorProcessor.go @@ -5,6 +5,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-go/heartbeat" + "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/storage" ) @@ -64,31 +65,31 @@ func (paip *peerAuthenticationInterceptorProcessor) Validate(_ process.Intercept } // Save will save the intercepted peer authentication inside the peer authentication cacher -func (paip *peerAuthenticationInterceptorProcessor) Save(data process.InterceptedData, _ core.PeerID, _ string) error { +func (paip *peerAuthenticationInterceptorProcessor) Save(data process.InterceptedData, _ core.PeerID, _ string, _ p2p.BroadcastMethod) (bool, error) { interceptedPeerAuthenticationData, ok := data.(interceptedPeerAuthenticationMessageHandler) if !ok { - return process.ErrWrongTypeAssertion + return false, process.ErrWrongTypeAssertion } payloadBuff := interceptedPeerAuthenticationData.Payload() payload := &heartbeat.Payload{} err := paip.marshaller.Unmarshal(payload, payloadBuff) if err != nil { - return err + return false, err } isHardforkTrigger, err := paip.hardforkTrigger.TriggerReceived(nil, []byte(payload.HardforkMessage), interceptedPeerAuthenticationData.Pubkey()) if isHardforkTrigger { - return err + return false, err } return paip.updatePeerInfo(interceptedPeerAuthenticationData.Message(), interceptedPeerAuthenticationData.SizeInBytes()) } -func (paip *peerAuthenticationInterceptorProcessor) updatePeerInfo(message interface{}, messageSize int) error { +func (paip *peerAuthenticationInterceptorProcessor) updatePeerInfo(message interface{}, messageSize int) (bool, error) { peerAuthenticationData, ok := message.(*heartbeat.PeerAuthentication) if !ok { - return process.ErrWrongTypeAssertion + return false, process.ErrWrongTypeAssertion } pidBytes := peerAuthenticationData.GetPid() @@ -97,7 +98,7 @@ func (paip *peerAuthenticationInterceptorProcessor) updatePeerInfo(message inter log.Trace("PeerAuthentication message saved") - return nil + return true, nil } // RegisterHandler registers a callback function to be notified of incoming peer authentication diff --git a/process/interceptors/processor/peerAuthenticationInterceptorProcessor_test.go b/process/interceptors/processor/peerAuthenticationInterceptorProcessor_test.go index 3a1db0b6b66..a2bf2633fc6 100644 --- a/process/interceptors/processor/peerAuthenticationInterceptorProcessor_test.go +++ b/process/interceptors/processor/peerAuthenticationInterceptorProcessor_test.go @@ -127,7 +127,8 @@ func TestPeerAuthenticationInterceptorProcessor_Save(t *testing.T) { paip, err := processor.NewPeerAuthenticationInterceptorProcessor(createPeerAuthenticationInterceptorProcessArg()) assert.Nil(t, err) assert.False(t, paip.IsInterfaceNil()) - assert.Equal(t, process.ErrWrongTypeAssertion, paip.Save(nil, "", "")) + _, err = paip.Save(nil, "", "", "") + assert.Equal(t, process.ErrWrongTypeAssertion, err) }) t.Run("invalid peer auth data should error", func(t *testing.T) { t.Parallel() @@ -144,7 +145,8 @@ func TestPeerAuthenticationInterceptorProcessor_Save(t *testing.T) { paip, err := processor.NewPeerAuthenticationInterceptorProcessor(args) assert.Nil(t, err) assert.False(t, paip.IsInterfaceNil()) - assert.Equal(t, process.ErrWrongTypeAssertion, paip.Save(providedData, "", "")) + _, err = paip.Save(providedData, "", "", "") + assert.Equal(t, process.ErrWrongTypeAssertion, err) assert.False(t, wasCalled) }) t.Run("unmarshal returns error", func(t *testing.T) { @@ -161,7 +163,7 @@ func TestPeerAuthenticationInterceptorProcessor_Save(t *testing.T) { assert.Nil(t, err) assert.False(t, paip.IsInterfaceNil()) - err = paip.Save(createMockInterceptedPeerAuthentication(), "", "") + _, err = paip.Save(createMockInterceptedPeerAuthentication(), "", "", "") assert.Equal(t, expectedError, err) }) t.Run("trigger received returns error", func(t *testing.T) { @@ -178,7 +180,7 @@ func TestPeerAuthenticationInterceptorProcessor_Save(t *testing.T) { assert.Nil(t, err) assert.False(t, paip.IsInterfaceNil()) - err = paip.Save(createMockInterceptedPeerAuthentication(), "", "") + _, err = paip.Save(createMockInterceptedPeerAuthentication(), "", "", "") assert.Equal(t, expectedError, err) }) t.Run("should work", func(t *testing.T) { @@ -216,7 +218,7 @@ func TestPeerAuthenticationInterceptorProcessor_Save(t *testing.T) { assert.Nil(t, err) assert.False(t, paip.IsInterfaceNil()) - err = paip.Save(providedIPA, providedPid, "") + _, err = paip.Save(providedIPA, providedPid, "", "") assert.Nil(t, err) assert.True(t, wasPutCalled) assert.True(t, wasUpdatePeerIDPublicKeyPairCalled) diff --git a/process/interceptors/processor/peerShardInterceptorProcessor.go b/process/interceptors/processor/peerShardInterceptorProcessor.go index 14d7752f9c6..4af4ec3d883 100644 --- a/process/interceptors/processor/peerShardInterceptorProcessor.go +++ b/process/interceptors/processor/peerShardInterceptorProcessor.go @@ -5,6 +5,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process" ) @@ -39,20 +40,20 @@ func (processor *peerShardInterceptorProcessor) Validate(_ process.InterceptedDa } // Save will save the intercepted validator info into peer shard mapper -func (processor *peerShardInterceptorProcessor) Save(data process.InterceptedData, fromConnectedPeer core.PeerID, _ string) error { +func (processor *peerShardInterceptorProcessor) Save(data process.InterceptedData, fromConnectedPeer core.PeerID, _ string, _ p2p.BroadcastMethod) (bool, error) { shardPeerShard, ok := data.(shardProvider) if !ok { - return process.ErrWrongTypeAssertion + return false, process.ErrWrongTypeAssertion } - shardID, err := strconv.Atoi(shardPeerShard.ShardID()) + shardID, err := strconv.ParseUint(shardPeerShard.ShardID(), 10, 32) if err != nil { - return err + return false, err } processor.peerShardMapper.PutPeerIdShardId(fromConnectedPeer, uint32(shardID)) - return nil + return true, nil } // RegisterHandler registers a callback function to be notified of incoming shard validator info, currently not implemented diff --git a/process/interceptors/processor/peerShardInterceptorProcessor_test.go b/process/interceptors/processor/peerShardInterceptorProcessor_test.go index 5e879ad6bea..f8a432898f7 100644 --- a/process/interceptors/processor/peerShardInterceptorProcessor_test.go +++ b/process/interceptors/processor/peerShardInterceptorProcessor_test.go @@ -68,7 +68,7 @@ func TestPeerShardInterceptorProcessor_Save(t *testing.T) { arg.DataBuff, _ = arg.Marshaller.Marshal(&heartbeatMessages.HeartbeatV2{}) ihb, _ := heartbeat.NewInterceptedHeartbeat(arg) - err = processor.Save(ihb, "", "") + _, err = processor.Save(ihb, "", "", "") assert.Equal(t, process.ErrWrongTypeAssertion, err) assert.False(t, wasCalled) }) @@ -99,7 +99,7 @@ func TestPeerShardInterceptorProcessor_Save(t *testing.T) { } data, _ := processP2P.NewInterceptedPeerShard(arg) - err = processor.Save(data, "", "") + _, err = processor.Save(data, "", "", "") assert.NotNil(t, err) assert.False(t, wasCalled) }) @@ -130,7 +130,7 @@ func TestPeerShardInterceptorProcessor_Save(t *testing.T) { } data, _ := processP2P.NewInterceptedPeerShard(arg) - err = processor.Save(data, "", "") + _, err = processor.Save(data, "", "", "") assert.Nil(t, err) assert.True(t, wasCalled) }) diff --git a/process/interceptors/processor/trieNodeChunksProcessor.go b/process/interceptors/processor/trieNodeChunksProcessor.go index f9c584562c4..4bf8d72f241 100644 --- a/process/interceptors/processor/trieNodeChunksProcessor.go +++ b/process/interceptors/processor/trieNodeChunksProcessor.go @@ -8,6 +8,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data/batch" "github.com/multiversx/mx-chain-core-go/hashing" + "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/interceptors/processor/chunk" "github.com/multiversx/mx-chain-go/storage" @@ -102,7 +103,7 @@ func (proc *trieNodeChunksProcessor) processLoop(ctx context.Context) { } // CheckBatch will check the batch returning a checked chunk result containing result processing -func (proc *trieNodeChunksProcessor) CheckBatch(b *batch.Batch, whiteListHandler process.WhiteListHandler) (process.CheckedChunkResult, error) { +func (proc *trieNodeChunksProcessor) CheckBatch(b *batch.Batch, whiteListHandler process.WhiteListHandler, _ p2p.BroadcastMethod) (process.CheckedChunkResult, error) { batchValid, err := proc.batchIsValid(b, whiteListHandler) if !batchValid { return process.CheckedChunkResult{ @@ -132,6 +133,10 @@ func (proc *trieNodeChunksProcessor) CheckBatch(b *batch.Batch, whiteListHandler } } +// MarkVerified does nothing +func (proc *trieNodeChunksProcessor) MarkVerified(_ *batch.Batch, _ p2p.BroadcastMethod) { +} + func (proc *trieNodeChunksProcessor) processCheckRequest(cr checkRequest) { shouldNotCreateChunk := cr.batch.ChunkIndex != 0 result := process.CheckedChunkResult{ @@ -140,7 +145,7 @@ func (proc *trieNodeChunksProcessor) processCheckRequest(cr checkRequest) { chunkObject, found := proc.chunksCacher.Get(cr.batch.Reference) if !found { if shouldNotCreateChunk { - //we received other chunks from a previous, completed large trie node, return + // we received other chunks from a previous, completed large trie node, return proc.writeCheckedChunkResultOnChan(cr, result) return @@ -151,7 +156,7 @@ func (proc *trieNodeChunksProcessor) processCheckRequest(cr checkRequest) { chunkData, ok := chunkObject.(chunkHandler) if !ok { if shouldNotCreateChunk { - //we received other chunks from a previous, completed large trie node, return + // we received other chunks from a previous, completed large trie node, return proc.writeCheckedChunkResultOnChan(cr, result) return } @@ -210,7 +215,7 @@ func (proc *trieNodeChunksProcessor) doRequests(ctx context.Context) { for _, ref := range references { select { case <-ctx.Done(): - //early exit + // early exit return default: } @@ -234,7 +239,7 @@ func (proc *trieNodeChunksProcessor) requestMissingForReference(reference []byte for _, missingChunkIndex := range missing { select { case <-ctx.Done(): - //early exit + // early exit return default: } @@ -247,7 +252,7 @@ func (proc *trieNodeChunksProcessor) requestMissingForReference(reference []byte func (proc *trieNodeChunksProcessor) Close() error { log.Debug("trieNodeChunkProcessor.Close()", "key", proc.topic) defer func() { - //this instruction should be called last as to release hanging go routines + // this instruction should be called last as to release hanging go routines close(proc.chanClose) }() diff --git a/process/interceptors/processor/trieNodeChunksProcessor_test.go b/process/interceptors/processor/trieNodeChunksProcessor_test.go index ad63ca7adc6..97260694fb5 100644 --- a/process/interceptors/processor/trieNodeChunksProcessor_test.go +++ b/process/interceptors/processor/trieNodeChunksProcessor_test.go @@ -9,6 +9,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data/batch" + "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/testscommon" @@ -116,6 +117,7 @@ func TestTrieNodeChunksProcessor_CheckBatchInvalidBatch(t *testing.T) { MaxChunks: 1, }, createMockWhiteLister(true), + p2p.Broadcast, ) assert.Nil(t, err) assert.Equal(t, emptyCheckedChunkResult, chunkResult) @@ -128,6 +130,7 @@ func TestTrieNodeChunksProcessor_CheckBatchInvalidBatch(t *testing.T) { MaxChunks: 2, }, createMockWhiteLister(true), + p2p.Broadcast, ) assert.Equal(t, err, process.ErrIncompatibleReference) assert.Equal(t, emptyCheckedChunkResult, chunkResult) @@ -140,6 +143,7 @@ func TestTrieNodeChunksProcessor_CheckBatchInvalidBatch(t *testing.T) { MaxChunks: 2, }, createMockWhiteLister(true), + p2p.Broadcast, ) assert.Nil(t, err) assert.Equal(t, emptyCheckedChunkResult, chunkResult) @@ -161,6 +165,7 @@ func TestTrieNodeChunksProcessor_NilWhitelistHandler(t *testing.T) { MaxChunks: 2, }, nil, + p2p.Broadcast, ) assert.Equal(t, process.ErrNilWhiteListHandler, err) assert.Equal(t, emptyCheckedChunkResult, chunkResult) @@ -183,6 +188,7 @@ func TestTrieNodeChunksProcessor_NotWhiteListed(t *testing.T) { MaxChunks: 2, }, createMockWhiteLister(false), + p2p.Broadcast, ) assert.Equal(t, process.ErrTrieNodeIsNotWhitelisted, err) assert.Equal(t, emptyCheckedChunkResult, chunkResult) @@ -209,6 +215,7 @@ func TestTrieNodeChunksProcessor_CheckBatchShouldWork(t *testing.T) { MaxChunks: 2, }, createMockWhiteLister(true), + p2p.Broadcast, ) assert.Nil(t, err) assert.Equal(t, expectedCheckedChunkResult, chunkResult) @@ -222,6 +229,7 @@ func TestTrieNodeChunksProcessor_CheckBatchShouldWork(t *testing.T) { MaxChunks: 2, }, createMockWhiteLister(true), + p2p.Broadcast, ) assert.Nil(t, err) @@ -233,6 +241,8 @@ func TestTrieNodeChunksProcessor_CheckBatchShouldWork(t *testing.T) { assert.Equal(t, expectedCheckedChunkResult, chunkResult) assert.Equal(t, 0, args.ChunksCacher.Len()) + tncp.MarkVerified(nil, p2p.Broadcast) // for coverage only + _ = tncp.Close() } @@ -255,6 +265,7 @@ func TestTrieNodeChunksProcessor_CheckBatchNotTheFirstBatch(t *testing.T) { MaxChunks: 2, }, createMockWhiteLister(true), + p2p.Broadcast, ) assert.Nil(t, err) assert.Equal(t, expectedCheckedChunkResult, chunkResult) @@ -270,6 +281,7 @@ func TestTrieNodeChunksProcessor_CheckBatchNotTheFirstBatch(t *testing.T) { MaxChunks: 2, }, createMockWhiteLister(true), + p2p.Broadcast, ) assert.Nil(t, err) assert.Equal(t, expectedCheckedChunkResult, chunkResult) @@ -298,6 +310,7 @@ func TestTrieNodeChunksProcessor_CheckBatchComponentClosed(t *testing.T) { MaxChunks: 2, }, createMockWhiteLister(true), + p2p.Broadcast, ) assert.Equal(t, process.ErrProcessClosed, err) assert.Equal(t, expectedCheckedChunkResult, chunkResult) @@ -325,6 +338,7 @@ func TestTrieNodeChunksProcessor_RequestShouldWork(t *testing.T) { MaxChunks: 3, }, createMockWhiteLister(true), + p2p.Broadcast, ) assert.Nil(t, err) diff --git a/process/interceptors/processor/trieNodeInterceptorProcessor.go b/process/interceptors/processor/trieNodeInterceptorProcessor.go index da2a452dc66..2d2b1fd8fd8 100644 --- a/process/interceptors/processor/trieNodeInterceptorProcessor.go +++ b/process/interceptors/processor/trieNodeInterceptorProcessor.go @@ -3,6 +3,7 @@ package processor import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/storage" ) @@ -31,14 +32,14 @@ func (tnip *TrieNodeInterceptorProcessor) Validate(_ process.InterceptedData, _ } // Save saves the intercepted trie node in the intercepted nodes cacher -func (tnip *TrieNodeInterceptorProcessor) Save(data process.InterceptedData, _ core.PeerID, _ string) error { +func (tnip *TrieNodeInterceptorProcessor) Save(data process.InterceptedData, _ core.PeerID, _ string, _ p2p.BroadcastMethod) (bool, error) { nodeData, ok := data.(interceptedDataSizeHandler) if !ok { - return process.ErrWrongTypeAssertion + return false, process.ErrWrongTypeAssertion } tnip.interceptedNodes.Put(data.Hash(), nodeData, nodeData.SizeInBytes()+len(data.Hash())) - return nil + return true, nil } // RegisterHandler registers a callback function to be notified of incoming trie nodes diff --git a/process/interceptors/processor/trieNodeInterceptorProcessor_test.go b/process/interceptors/processor/trieNodeInterceptorProcessor_test.go index b580f4ab65a..cc93442ce46 100644 --- a/process/interceptors/processor/trieNodeInterceptorProcessor_test.go +++ b/process/interceptors/processor/trieNodeInterceptorProcessor_test.go @@ -46,7 +46,7 @@ func TestTrieNodesInterceptorProcessor_SaveWrongTypeAssertion(t *testing.T) { tnip, _ := processor.NewTrieNodesInterceptorProcessor(cache.NewCacherMock()) - err := tnip.Save(nil, "", "") + _, err := tnip.Save(nil, "", "", "") assert.Equal(t, process.ErrWrongTypeAssertion, err) } @@ -72,7 +72,7 @@ func TestTrieNodesInterceptorProcessor_SaveShouldPutInCacher(t *testing.T) { } tnip, _ := processor.NewTrieNodesInterceptorProcessor(cacher) - err := tnip.Save(interceptedTrieNode, "", "") + _, err := tnip.Save(interceptedTrieNode, "", "", "") assert.Nil(t, err) assert.True(t, putCalled) } diff --git a/process/interceptors/processor/txInterceptorProcessor.go b/process/interceptors/processor/txInterceptorProcessor.go index 3ad36786004..9139ca57780 100644 --- a/process/interceptors/processor/txInterceptorProcessor.go +++ b/process/interceptors/processor/txInterceptorProcessor.go @@ -3,7 +3,9 @@ package processor import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/storage" logger "github.com/multiversx/mx-chain-logger-go" ) @@ -13,8 +15,9 @@ var txLog = logger.GetOrCreate("process/interceptors/processor/txlog") // TxInterceptorProcessor is the processor used when intercepting transactions // (smart contract results, receipts, transaction) structs which satisfy TransactionHandler interface. type TxInterceptorProcessor struct { - shardedPool process.ShardedPool - txValidator process.TxValidator + shardedPool process.ShardedPool + txValidator process.TxValidator + directSentTransactionsCache storage.Cacher } // NewTxInterceptorProcessor creates a new TxInterceptorProcessor instance @@ -28,10 +31,14 @@ func NewTxInterceptorProcessor(argument *ArgTxInterceptorProcessor) (*TxIntercep if check.IfNil(argument.TxValidator) { return nil, process.ErrNilTxValidator } + if check.IfNil(argument.DirectSentTransactionsCache) { + return nil, process.ErrNilDirectSentCache + } return &TxInterceptorProcessor{ - shardedPool: argument.ShardedDataCache, - txValidator: argument.TxValidator, + shardedPool: argument.ShardedDataCache, + txValidator: argument.TxValidator, + directSentTransactionsCache: argument.DirectSentTransactionsCache, }, nil } @@ -46,10 +53,10 @@ func (txip *TxInterceptorProcessor) Validate(data process.InterceptedData, _ cor } // Save will save the received data into the cacher -func (txip *TxInterceptorProcessor) Save(data process.InterceptedData, peerOriginator core.PeerID, _ string) error { +func (txip *TxInterceptorProcessor) Save(data process.InterceptedData, peerOriginator core.PeerID, _ string, broadcastMethod p2p.BroadcastMethod) (bool, error) { interceptedTx, ok := data.(process.InterceptedTransactionHandler) if !ok { - return process.ErrWrongTypeAssertion + return false, process.ErrWrongTypeAssertion } err := txip.txValidator.CheckTxWhiteList(data) @@ -61,7 +68,7 @@ func (txip *TxInterceptorProcessor) Save(data process.InterceptedData, peerOrigi "sender shard", interceptedTx.SenderShardId(), "receiver shard", interceptedTx.ReceiverShardId(), ) - return nil + return false, nil } txLog.Trace("received transaction", "pid", peerOriginator.Pretty(), "hash", data.Hash()) @@ -73,7 +80,17 @@ func (txip *TxInterceptorProcessor) Save(data process.InterceptedData, peerOrigi cacherIdentifier, ) - return nil + isIntraShard := interceptedTx.SenderShardId() == interceptedTx.ReceiverShardId() + if isDirectSend(broadcastMethod) && isIntraShard { + txip.directSentTransactionsCache.Put(data.Hash(), struct{}{}, 0) + } + + if isBroadcast(broadcastMethod) && isIntraShard { + // remove it if exists and is received via broadcast method to avoid flooding the network + txip.directSentTransactionsCache.Remove(data.Hash()) + } + + return true, nil } // RegisterHandler registers a callback function to be notified of incoming transactions @@ -81,6 +98,10 @@ func (txip *TxInterceptorProcessor) RegisterHandler(_ func(topic string, hash [] log.Error("txInterceptorProcessor.RegisterHandler", "error", "not implemented") } +func isBroadcast(method p2p.BroadcastMethod) bool { + return method == p2p.Broadcast +} + // IsInterfaceNil returns true if there is no value under the interface func (txip *TxInterceptorProcessor) IsInterfaceNil() bool { return txip == nil diff --git a/process/interceptors/processor/txInterceptorProcessor_test.go b/process/interceptors/processor/txInterceptorProcessor_test.go index 439fecf9aca..56444f40fe8 100644 --- a/process/interceptors/processor/txInterceptorProcessor_test.go +++ b/process/interceptors/processor/txInterceptorProcessor_test.go @@ -8,17 +8,20 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/interceptors/processor" "github.com/multiversx/mx-chain-go/process/mock" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/cache" "github.com/stretchr/testify/assert" ) func createMockTxArgument() *processor.ArgTxInterceptorProcessor { return &processor.ArgTxInterceptorProcessor{ - ShardedDataCache: testscommon.NewShardedDataStub(), - TxValidator: &mock.TxValidatorStub{}, + ShardedDataCache: testscommon.NewShardedDataStub(), + TxValidator: &mock.TxValidatorStub{}, + DirectSentTransactionsCache: &cache.CacherStub{}, } } @@ -53,6 +56,17 @@ func TestNewTxInterceptorProcessor_NilTxValidatorShouldErr(t *testing.T) { assert.Equal(t, process.ErrNilTxValidator, err) } +func TestNewTxInterceptorProcessor_NilDirectSentTransactionsCacheShouldErr(t *testing.T) { + t.Parallel() + + arg := createMockTxArgument() + arg.DirectSentTransactionsCache = nil + txip, err := processor.NewTxInterceptorProcessor(arg) + + assert.Nil(t, txip) + assert.Equal(t, process.ErrNilDirectSentCache, err) +} + func TestNewTxInterceptorProcessor_ShouldWork(t *testing.T) { t.Parallel() @@ -62,7 +76,7 @@ func TestNewTxInterceptorProcessor_ShouldWork(t *testing.T) { assert.Nil(t, err) } -//------- Validate +// ------- Validate func TestTxInterceptorProcessor_ValidateNilTxShouldErr(t *testing.T) { t.Parallel() @@ -116,14 +130,14 @@ func TestTxInterceptorProcessor_ValidateReturnsTrueShouldWork(t *testing.T) { assert.Nil(t, err) } -//------- Save +// ------- Save func TestTxInterceptorProcessor_SaveNilDataShouldErr(t *testing.T) { t.Parallel() txip, _ := processor.NewTxInterceptorProcessor(createMockTxArgument()) - err := txip.Save(nil, "", "") + _, err := txip.Save(nil, "", "", "") assert.Equal(t, process.ErrWrongTypeAssertion, err) } @@ -158,16 +172,33 @@ func TestTxInterceptorProcessor_SaveShouldWork(t *testing.T) { shardedDataCache.AddDataCalled = func(key []byte, data interface{}, sizeInBytes int, cacheId string) { addedWasCalled = true } + wasPutCalled := false + wasRemoveCalled := false + arg.DirectSentTransactionsCache = &cache.CacherStub{ + PutCalled: func(key []byte, value interface{}, sizeInBytes int) (evicted bool) { + wasPutCalled = true + return false + }, + RemoveCalled: func(key []byte) { + wasRemoveCalled = true + }, + } txip, _ := processor.NewTxInterceptorProcessor(arg) - err := txip.Save(txInterceptedData, "", "") - + _, err := txip.Save(txInterceptedData, "", "", p2p.Direct) assert.Nil(t, err) assert.True(t, addedWasCalled) + assert.True(t, wasPutCalled) + assert.False(t, wasRemoveCalled) + + // same tx but from broadcast should remove it from cache + _, err = txip.Save(txInterceptedData, "", "", p2p.Broadcast) + assert.Nil(t, err) + assert.True(t, wasRemoveCalled) } -//------- IsInterfaceNil +// ------- IsInterfaceNil func TestTxInterceptorProcessor_IsInterfaceNil(t *testing.T) { t.Parallel() diff --git a/process/interceptors/processor/uniqueChunksProcessor.go b/process/interceptors/processor/uniqueChunksProcessor.go new file mode 100644 index 00000000000..5720af40cdb --- /dev/null +++ b/process/interceptors/processor/uniqueChunksProcessor.go @@ -0,0 +1,102 @@ +package processor + +import ( + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/core/sync" + "github.com/multiversx/mx-chain-core-go/data/batch" + "github.com/multiversx/mx-chain-core-go/hashing" + "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/p2p" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/storage" +) + +type uniqueChunksProcessor struct { + km sync.KeyRWMutexHandler + cache storage.Cacher + marshaller marshal.Marshalizer + hasher hashing.Hasher +} + +// NewUniqueChunksProcessor creates a new instance of unique chunks processor +func NewUniqueChunksProcessor( + cache storage.Cacher, + marshaller marshal.Marshalizer, + hasher hashing.Hasher, +) (*uniqueChunksProcessor, error) { + if check.IfNil(cache) { + return nil, process.ErrNilInterceptedDataCache + } + if check.IfNil(marshaller) { + return nil, process.ErrNilMarshalizer + } + if check.IfNil(hasher) { + return nil, process.ErrNilHasher + } + + return &uniqueChunksProcessor{ + km: sync.NewKeyRWMutex(), + cache: cache, + marshaller: marshaller, + hasher: hasher, + }, nil +} + +// CheckBatch verifies if the provided batch is already received +func (proc *uniqueChunksProcessor) CheckBatch(b *batch.Batch, _ process.WhiteListHandler, broadcastMethod p2p.BroadcastMethod) (process.CheckedChunkResult, error) { + if b == nil || len(b.Data) == 0 { + return process.CheckedChunkResult{}, nil + } + if isDirectSend(broadcastMethod) { + return process.CheckedChunkResult{}, nil + } + + batchHash, err := core.CalculateHash(proc.marshaller, proc.hasher, b) + if err != nil { + return process.CheckedChunkResult{}, err + } + batchHashStr := string(batchHash) + + proc.km.RLock(batchHashStr) + defer proc.km.RUnlock(batchHashStr) + + if _, ok := proc.cache.Get(batchHash); ok { + return process.CheckedChunkResult{}, process.ErrDuplicatedInterceptedDataNotAllowed + } + + return process.CheckedChunkResult{}, nil +} + +// MarkVerified marks the batch as verified +func (proc *uniqueChunksProcessor) MarkVerified(b *batch.Batch, broadcastMethod p2p.BroadcastMethod) { + if b == nil || len(b.Data) == 0 { + return + } + if isDirectSend(broadcastMethod) { + return + } + batchHash, err := core.CalculateHash(proc.marshaller, proc.hasher, b) + if err != nil { + return + } + batchHashStr := string(batchHash) + proc.km.Lock(batchHashStr) + defer proc.km.Unlock(batchHashStr) + + proc.cache.Put(batchHash, struct{}{}, 0) +} + +func isDirectSend(broadcastMethod p2p.BroadcastMethod) bool { + return broadcastMethod == p2p.Direct +} + +// Close closes the internal cache +func (proc *uniqueChunksProcessor) Close() error { + return proc.cache.Close() +} + +// IsInterfaceNil returns true if there is no value under the interface +func (proc *uniqueChunksProcessor) IsInterfaceNil() bool { + return proc == nil +} diff --git a/process/interceptors/processor/uniqueChunksProcessor_test.go b/process/interceptors/processor/uniqueChunksProcessor_test.go new file mode 100644 index 00000000000..5113e5cd4d1 --- /dev/null +++ b/process/interceptors/processor/uniqueChunksProcessor_test.go @@ -0,0 +1,141 @@ +package processor_test + +import ( + "errors" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data/batch" + "github.com/multiversx/mx-chain-go/p2p" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/interceptors/processor" + "github.com/multiversx/mx-chain-go/process/mock" + "github.com/multiversx/mx-chain-go/testscommon/cache" + "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" + "github.com/stretchr/testify/require" +) + +func TestNewUniqueChunksProcessor(t *testing.T) { + t.Parallel() + + t.Run("nil cache should error", func(t *testing.T) { + t.Parallel() + + ucp, err := processor.NewUniqueChunksProcessor(nil, &mock.MarshalizerMock{}, &mock.HasherStub{}) + require.True(t, check.IfNil(ucp)) + require.Equal(t, process.ErrNilInterceptedDataCache, err) + }) + t.Run("nil marshaller should error", func(t *testing.T) { + t.Parallel() + + ucp, err := processor.NewUniqueChunksProcessor(&cache.CacherStub{}, nil, &mock.HasherStub{}) + require.True(t, check.IfNil(ucp)) + require.Equal(t, process.ErrNilMarshalizer, err) + }) + t.Run("nil hasher should error", func(t *testing.T) { + t.Parallel() + + ucp, err := processor.NewUniqueChunksProcessor(&cache.CacherStub{}, &mock.MarshalizerMock{}, nil) + require.True(t, check.IfNil(ucp)) + require.Equal(t, process.ErrNilHasher, err) + }) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + ucp, err := processor.NewUniqueChunksProcessor(&cache.CacherStub{}, &mock.MarshalizerMock{}, &mock.HasherStub{}) + require.False(t, check.IfNil(ucp)) + require.NoError(t, err) + + err = ucp.Close() // coverage only + require.NoError(t, err) + }) +} + +func TestUniqueChunksProcessor_IsInterfaceNil(t *testing.T) { + t.Parallel() + + ucp, _ := processor.NewUniqueChunksProcessor(nil, &mock.MarshalizerMock{}, &mock.HasherStub{}) + require.True(t, ucp.IsInterfaceNil()) + + ucp, _ = processor.NewUniqueChunksProcessor(&cache.CacherStub{}, &mock.MarshalizerMock{}, &mock.HasherStub{}) + require.False(t, ucp.IsInterfaceNil()) +} + +func TestUniqueChunksProcessor_CheckBatch(t *testing.T) { + t.Parallel() + + t.Run("nil batch should return empty result", func(t *testing.T) { + t.Parallel() + + ucp, _ := processor.NewUniqueChunksProcessor(&cache.CacherStub{}, &mock.MarshalizerMock{}, &mock.HasherStub{}) + result, err := ucp.CheckBatch(nil, nil, p2p.Broadcast) + require.Equal(t, process.CheckedChunkResult{}, result) + require.NoError(t, err) + }) + t.Run("marshaling error should return error", func(t *testing.T) { + t.Parallel() + + expectedErr := errors.New("marshal error") + marshaller := &mock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + return nil, expectedErr + }, + } + + ucp, _ := processor.NewUniqueChunksProcessor(&cache.CacherStub{}, marshaller, &mock.HasherStub{}) + result, err := ucp.CheckBatch(&batch.Batch{Data: [][]byte{{1, 2, 3}}}, nil, p2p.Broadcast) + require.Equal(t, process.CheckedChunkResult{}, result) + require.Equal(t, expectedErr, err) + }) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + b := &batch.Batch{Data: [][]byte{{1, 2, 3}}} + hasher := &hashingMocks.HasherMock{} + marshaller := &mock.MarshalizerMock{} + batchHash, _ := core.CalculateHash(marshaller, hasher, b) + + cacheMock := cache.NewCacherMock() + ucp, _ := processor.NewUniqueChunksProcessor(cacheMock, marshaller, hasher) + + // First check should succeed + result, err := ucp.CheckBatch(b, nil, p2p.Broadcast) + require.Equal(t, process.CheckedChunkResult{}, result) + require.NoError(t, err) + + ucp.MarkVerified(b, p2p.Broadcast) + + // Verify it was added to cache + _, ok := cacheMock.Get(batchHash) + require.True(t, ok) + + // Second check with same batch should fail + result, err = ucp.CheckBatch(b, nil, p2p.Broadcast) + require.Equal(t, process.CheckedChunkResult{}, result) + require.Equal(t, process.ErrDuplicatedInterceptedDataNotAllowed, err) + }) +} + +func TestUniqueChunksProcessor_MarkVerified(t *testing.T) { + t.Parallel() + + b := &batch.Batch{Data: [][]byte{{1, 2, 3}}} + hasher := &hashingMocks.HasherMock{} + marshaller := &mock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + return nil, errors.New("marshal error") + }, + } + cacheMock := cache.NewCacherMock() + ucp, _ := processor.NewUniqueChunksProcessor(cacheMock, marshaller, hasher) + + // nil batch, early exit + ucp.MarkVerified(nil, p2p.Broadcast) + + // Direct send, early exit + ucp.MarkVerified(b, p2p.Direct) + + // marshal error, early exit + ucp.MarkVerified(b, p2p.Broadcast) +} diff --git a/process/interceptors/processor/validatorInfoInterceptorProcessor.go b/process/interceptors/processor/validatorInfoInterceptorProcessor.go index d002f634704..6d3793bdc7f 100644 --- a/process/interceptors/processor/validatorInfoInterceptorProcessor.go +++ b/process/interceptors/processor/validatorInfoInterceptorProcessor.go @@ -4,6 +4,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-go/dataRetriever" + "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process" ) @@ -42,10 +43,10 @@ func (viip *validatorInfoInterceptorProcessor) Validate(_ process.InterceptedDat } // Save will save the intercepted validator info into the cache -func (viip *validatorInfoInterceptorProcessor) Save(data process.InterceptedData, _ core.PeerID, _ string) error { +func (viip *validatorInfoInterceptorProcessor) Save(data process.InterceptedData, _ core.PeerID, _ string, _ p2p.BroadcastMethod) (bool, error) { ivi, ok := data.(interceptedValidatorInfo) if !ok { - return process.ErrWrongTypeAssertion + return false, process.ErrWrongTypeAssertion } validatorInfo := ivi.ValidatorInfo() @@ -56,7 +57,7 @@ func (viip *validatorInfoInterceptorProcessor) Save(data process.InterceptedData strCache := process.ShardCacherIdentifier(core.MetachainShardId, core.AllShardId) viip.validatorInfoPool.AddData(hash, validatorInfo, validatorInfo.Size(), strCache) - return nil + return true, nil } // RegisterHandler registers a callback function to be notified of incoming validator info diff --git a/process/interceptors/processor/validatorInfoInterceptorProcessor_test.go b/process/interceptors/processor/validatorInfoInterceptorProcessor_test.go index d4b56cdc430..ccf237c54e8 100644 --- a/process/interceptors/processor/validatorInfoInterceptorProcessor_test.go +++ b/process/interceptors/processor/validatorInfoInterceptorProcessor_test.go @@ -75,7 +75,8 @@ func TestValidatorInfoInterceptorProcessor_Save(t *testing.T) { proc, err := processor.NewValidatorInfoInterceptorProcessor(createMockArgValidatorInfoInterceptorProcessor()) assert.Nil(t, err) - assert.Equal(t, process.ErrWrongTypeAssertion, proc.Save(nil, "", "")) + _, err = proc.Save(nil, "", "", "") + assert.Equal(t, process.ErrWrongTypeAssertion, err) }) t.Run("invalid validator info should error", func(t *testing.T) { t.Parallel() @@ -92,7 +93,8 @@ func TestValidatorInfoInterceptorProcessor_Save(t *testing.T) { proc, _ := processor.NewValidatorInfoInterceptorProcessor(args) require.False(t, check.IfNil(proc)) - assert.Equal(t, process.ErrWrongTypeAssertion, proc.Save(providedData, "", "")) + _, err := proc.Save(providedData, "", "", "") + assert.Equal(t, process.ErrWrongTypeAssertion, err) assert.False(t, wasCalled) }) t.Run("should work", func(t *testing.T) { @@ -117,7 +119,8 @@ func TestValidatorInfoInterceptorProcessor_Save(t *testing.T) { proc, _ := processor.NewValidatorInfoInterceptorProcessor(args) require.False(t, check.IfNil(proc)) - assert.Nil(t, proc.Save(providedData, "", providedEpochStr)) + _, err := proc.Save(providedData, "", providedEpochStr, "") + assert.NoError(t, err) assert.True(t, wasHasOrAddCalled) }) } diff --git a/process/interceptors/singleDataInterceptor.go b/process/interceptors/singleDataInterceptor.go index da15d00170e..f39da2a261d 100644 --- a/process/interceptors/singleDataInterceptor.go +++ b/process/interceptors/singleDataInterceptor.go @@ -2,6 +2,7 @@ package interceptors import ( "errors" + "strings" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" @@ -23,6 +24,7 @@ type ArgSingleDataInterceptor struct { PreferredPeersHolder process.PreferredPeersHolderHandler CurrentPeerId core.PeerID InterceptedDataVerifier process.InterceptedDataVerifier + ManagedPeersHolder common.ManagedPeersHolder } // SingleDataInterceptor is used for intercepting packed multi data @@ -61,6 +63,9 @@ func NewSingleDataInterceptor(arg ArgSingleDataInterceptor) (*SingleDataIntercep if len(arg.CurrentPeerId) == 0 { return nil, process.ErrEmptyPeerID } + if check.IfNil(arg.ManagedPeersHolder) { + return nil, process.ErrNilManagedPeersHolder + } singleDataIntercept := &SingleDataInterceptor{ baseDataInterceptor: &baseDataInterceptor{ @@ -72,6 +77,7 @@ func NewSingleDataInterceptor(arg ArgSingleDataInterceptor) (*SingleDataIntercep preferredPeersHolder: arg.PreferredPeersHolder, debugHandler: handler.NewDisabledInterceptorDebugHandler(), interceptedDataVerifier: arg.InterceptedDataVerifier, + managedPeersHolder: arg.ManagedPeersHolder, }, factory: arg.DataFactory, whiteListRequest: arg.WhiteListRequest, @@ -101,20 +107,29 @@ func (sdi *SingleDataInterceptor) ProcessReceivedMessage(message p2p.MessageP2P, } sdi.receivedDebugInterceptedData(interceptedData) - err = sdi.interceptedDataVerifier.Verify(interceptedData) - if err != nil { - sdi.throttler.EndProcessing() - sdi.processDebugInterceptedData(interceptedData, err) - - isWrongVersion := errors.Is(err, process.ErrInvalidTransactionVersion) || errors.Is(err, process.ErrInvalidChainID) - if isWrongVersion { - // this situation is so severe that we need to black list de peers - reason := "wrong version of received intercepted data, topic " + sdi.topic + ", error " + err.Error() - sdi.antifloodHandler.BlacklistPeer(message.Peer(), reason, common.InvalidMessageBlacklistDuration) - sdi.antifloodHandler.BlacklistPeer(fromConnectedPeer, reason, common.InvalidMessageBlacklistDuration) + messageID := interceptedData.Hash() + isInterceptedEquivalentProof := strings.HasPrefix(message.Topic(), common.EquivalentProofsTopic) + isMessageFromSelfOriginator := sdi.isMessageFromSelfOriginator(message) + isMessageFromSelfToSelf := sdi.isMessageFromSelfToSelf(fromConnectedPeer, message) + shouldSkipInterceptedDataVerification := isMessageFromSelfOriginator && isInterceptedEquivalentProof && !isMessageFromSelfToSelf + if !shouldSkipInterceptedDataVerification { + err = sdi.interceptedDataVerifier.Verify(interceptedData, message.Topic(), message.BroadcastMethod()) + if err != nil { + sdi.throttler.EndProcessing() + sdi.processDebugInterceptedData(interceptedData, err) + + isWrongVersion := errors.Is(err, process.ErrInvalidTransactionVersion) || errors.Is(err, process.ErrInvalidChainID) + if isWrongVersion { + // this situation is so severe that we need to black list de peers + reason := "wrong version of received intercepted data, topic " + sdi.topic + ", error " + err.Error() + sdi.antifloodHandler.BlacklistPeer(message.Peer(), reason, common.InvalidMessageBlacklistDuration) + sdi.antifloodHandler.BlacklistPeer(fromConnectedPeer, reason, common.InvalidMessageBlacklistDuration) + } + + return nil, err } - - return nil, err + } else { + sdi.interceptedDataVerifier.MarkVerified(interceptedData, message.BroadcastMethod()) } errOriginator := sdi.antifloodHandler.IsOriginatorEligibleForTopic(message.Peer(), sdi.topic) @@ -127,7 +142,6 @@ func (sdi *SingleDataInterceptor) ProcessReceivedMessage(message p2p.MessageP2P, return nil, errOriginator } - messageID := interceptedData.Hash() isForCurrentShard := interceptedData.IsForCurrentShard() shouldProcess := isForCurrentShard || isWhiteListed if !shouldProcess { @@ -145,7 +159,7 @@ func (sdi *SingleDataInterceptor) ProcessReceivedMessage(message p2p.MessageP2P, } go func() { - sdi.processInterceptedData(interceptedData, message) + sdi.processInterceptedData(interceptedData, message, fromConnectedPeer) sdi.throttler.EndProcessing() }() diff --git a/process/interceptors/singleDataInterceptor_test.go b/process/interceptors/singleDataInterceptor_test.go index 84aa285ff6c..58043a2c9a5 100644 --- a/process/interceptors/singleDataInterceptor_test.go +++ b/process/interceptors/singleDataInterceptor_test.go @@ -8,6 +8,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/p2p" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -29,6 +30,7 @@ func createMockArgSingleDataInterceptor() interceptors.ArgSingleDataInterceptor PreferredPeersHolder: &p2pmocks.PeersHolderStub{}, CurrentPeerId: "pid", InterceptedDataVerifier: createMockInterceptedDataVerifier(), + ManagedPeersHolder: &testscommon.ManagedPeersHolderStub{}, } } @@ -41,12 +43,12 @@ func createMockInterceptorStub(checkCalledNum *int32, processCalledNum *int32) p return nil }, - SaveCalled: func(data process.InterceptedData) error { + SaveCalled: func(data process.InterceptedData) (bool, error) { if processCalledNum != nil { atomic.AddInt32(processCalledNum, 1) } - return nil + return true, nil }, } } @@ -61,7 +63,7 @@ func createMockThrottler() *mock.InterceptorThrottlerStub { func createMockInterceptedDataVerifier() *mock.InterceptedDataVerifierMock { return &mock.InterceptedDataVerifierMock{ - VerifyCalled: func(interceptedData process.InterceptedData) error { + VerifyCalled: func(interceptedData process.InterceptedData, topic string, broadcastMethod p2p.BroadcastMethod) error { return interceptedData.CheckValidity() }, } @@ -155,6 +157,17 @@ func TestNewSingleDataInterceptor_EmptyPeerIDShouldErr(t *testing.T) { assert.Equal(t, process.ErrEmptyPeerID, err) } +func TestNewSingleDataInterceptor_NilManagedPeersHolderShouldErr(t *testing.T) { + t.Parallel() + + arg := createMockArgSingleDataInterceptor() + arg.ManagedPeersHolder = nil + sdi, err := interceptors.NewSingleDataInterceptor(arg) + + assert.Nil(t, sdi) + assert.Equal(t, process.ErrNilManagedPeersHolder, err) +} + func TestNewSingleDataInterceptor_NilInterceptedDataVerifierShouldErr(t *testing.T) { t.Parallel() diff --git a/process/interface.go b/process/interface.go index cf340306ee3..18c14ecfa59 100644 --- a/process/interface.go +++ b/process/interface.go @@ -27,7 +27,10 @@ import ( cryptoCommon "github.com/multiversx/mx-chain-go/common/crypto" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/epochStart" + "github.com/multiversx/mx-chain-go/ntp" "github.com/multiversx/mx-chain-go/p2p" + "github.com/multiversx/mx-chain-go/process/asyncExecution/cache" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionTrack" "github.com/multiversx/mx-chain-go/process/block/bootstrapStorage" "github.com/multiversx/mx-chain-go/process/block/processedMb" "github.com/multiversx/mx-chain-go/sharding" @@ -36,6 +39,18 @@ import ( "github.com/multiversx/mx-chain-go/storage" ) +// TxShardInfo contains information about the sender and receiver shard IDs of a transaction. +type TxShardInfo struct { + SenderShardID uint32 + ReceiverShardID uint32 +} + +// TxInfo contains a transaction handler and its associated shard information. +type TxInfo struct { + Tx data.TransactionHandler + *TxShardInfo +} + // TransactionProcessor is the main interface for transaction execution engine type TransactionProcessor interface { ProcessTransaction(transaction *transaction.Transaction) (vmcommon.ReturnCode, error) @@ -125,6 +140,7 @@ type InterceptedDataFactory interface { // InterceptedData represents the interceptor's view of the received data type InterceptedData interface { CheckValidity() error + ShouldAllowDuplicates() bool IsForCurrentShard() bool IsInterfaceNil() bool Hash() []byte @@ -136,7 +152,7 @@ type InterceptedData interface { // InterceptorProcessor further validates and saves received data type InterceptorProcessor interface { Validate(data InterceptedData, fromConnectedPeer core.PeerID) error - Save(data InterceptedData, fromConnectedPeer core.PeerID, topic string) error + Save(data InterceptedData, fromConnectedPeer core.PeerID, topic string, broadcastMethod p2p.BroadcastMethod) (dataSaved bool, err error) RegisterHandler(handler func(topic string, hash []byte, data interface{})) IsInterfaceNil() bool } @@ -158,17 +174,20 @@ type TransactionCoordinator interface { SaveTxsToStorage(body *block.Body) RestoreBlockDataFromStorage(body *block.Body) (int, error) RemoveBlockDataFromPool(body *block.Body) error - RemoveTxsFromPool(body *block.Body) error + RemoveTxsFromPool(body *block.Body, rootHashHolder common.RootHashHolder) error ProcessBlockTransaction(header data.HeaderHandler, body *block.Body, haveTime func() time.Duration) error + GetCreatedMiniBlocksFromMe() block.MiniBlockSlice CreateBlockStarted() CreateMbsAndProcessCrossShardTransactionsDstMe(header data.HeaderHandler, processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo, haveTime func() bool, haveAdditionalTime func() bool, scheduledMode bool) (block.MiniBlockSlice, uint32, bool, error) CreateMbsAndProcessTransactionsFromMe(haveTime func() bool, randomness []byte) block.MiniBlockSlice CreatePostProcessMiniBlocks() block.MiniBlockSlice CreateMarshalizedData(body *block.Body) map[string][][]byte + CreateMarshalledDataForHeader(header data.HeaderHandler, body *block.Body, miniBlocksMap map[string]block.MiniBlockSlice) map[string][][]byte GetAllCurrentUsedTxs(blockType block.Type) map[string]data.TransactionHandler - GetAllCurrentLogs() []*data.LogData + GetAllCurrentLogs() []data.LogDataHandler + GetUnExecutableTransactions() map[string]struct{} CreateReceiptsHash() ([]byte, error) VerifyCreatedBlockTransactions(hdr data.HeaderHandler, body *block.Body) error @@ -179,6 +198,13 @@ type TransactionCoordinator interface { AddTxsFromMiniBlocks(miniBlocks block.MiniBlockSlice) AddTransactions(txHandlers []data.TransactionHandler, blockType block.Type) IsInterfaceNil() bool + + SelectOutgoingTransactions(nonce uint64, haveTimeForSelection func() bool) (selectedTxHashes [][]byte, selectedPendingIncomingMiniBlocks []data.MiniBlockHeaderHandler) + CreateMbsCrossShardDstMe( + header data.HeaderHandler, + processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo, + ) (addedMiniBlocksAndHashes []block.MiniblockAndHash, pendingMiniBlocksAndHashes []block.MiniblockAndHash, numTransactions uint32, allMiniBlocksAdded bool, hasMissingData bool, err error) + ProposedDirectSentTransactionsToBroadcast(proposedBody data.BodyHandler) map[string][][]byte } // SmartContractProcessor is the main interface for the smart contract caller engine @@ -234,15 +260,19 @@ type PreProcessor interface { IsDataPrepared(requestedTxs int, haveTime func() time.Duration) error RemoveBlockDataFromPools(body *block.Body, miniBlockPool storage.Cacher) error - RemoveTxsFromPools(body *block.Body) error + RemoveTxsFromPools(body *block.Body, rootHashHolder common.RootHashHolder) error RestoreBlockDataIntoPools(body *block.Body, miniBlockPool storage.Cacher) (int, error) SaveTxsToStorage(body *block.Body) error ProcessBlockTransactions(header data.HeaderHandler, body *block.Body, haveTime func() bool) error + GetCreatedMiniBlocksFromMe() block.MiniBlockSlice + GetUnExecutableTransactions() map[string]struct{} + RequestBlockTransactions(body *block.Body) int - RequestTransactionsForMiniBlock(miniBlock *block.MiniBlock) int + GetTransactionsAndRequestMissingForMiniBlock(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) ProcessMiniBlock(miniBlock *block.MiniBlock, haveTime func() bool, haveAdditionalTime func() bool, scheduledMode bool, partialMbExecutionMode bool, indexOfLastTxProcessed int, preProcessorExecutionInfoHandler PreProcessorExecutionInfoHandler) ([][]byte, int, bool, error) + SelectOutgoingTransactions(bandwidth uint64, nonce uint64, haveTimeForSelection func() bool) ([][]byte, []data.TransactionHandler, error) CreateAndProcessMiniBlocks(haveTime func() bool, randomness []byte) (block.MiniBlockSlice, error) GetAllCurrentUsedTxs() map[string]data.TransactionHandler @@ -254,20 +284,82 @@ type PreProcessor interface { // BlockProcessor is the main interface for block execution engine type BlockProcessor interface { ProcessBlock(header data.HeaderHandler, body data.BodyHandler, haveTime func() time.Duration) error + ProcessBlockProposal(header data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) + CommitBlockProposalState(headerHandler data.HeaderHandler) error + RevertBlockProposalState() ProcessScheduledBlock(header data.HeaderHandler, body data.BodyHandler, haveTime func() time.Duration) error CommitBlock(header data.HeaderHandler, body data.BodyHandler) error RevertCurrentBlock() PruneStateOnRollback(currHeader data.HeaderHandler, currHeaderHash []byte, prevHeader data.HeaderHandler, prevHeaderHash []byte) RevertStateToBlock(header data.HeaderHandler, rootHash []byte) error CreateNewHeader(round uint64, nonce uint64) (data.HeaderHandler, error) + CreateNewHeaderProposal(round uint64, nonce uint64) (data.HeaderHandler, error) RestoreBlockIntoPools(header data.HeaderHandler, body data.BodyHandler) error CreateBlock(initialHdr data.HeaderHandler, haveTime func() bool) (data.HeaderHandler, data.BodyHandler, error) - MarshalizedDataToBroadcast(header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) + CreateBlockProposal(initialHdr data.HeaderHandler, haveTime func() bool) (data.HeaderHandler, data.BodyHandler, error) + MarshalizedDataToBroadcast(headerHash []byte, header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) DecodeBlockBody(dta []byte) data.BodyHandler DecodeBlockHeader(dta []byte) data.HeaderHandler SetNumProcessedObj(numObj uint64) RestoreBlockBodyIntoPools(body data.BodyHandler) error NonceOfFirstCommittedBlock() core.OptionalUint64 + VerifyBlockProposal( + headerHandler data.HeaderHandler, + bodyHandler data.BodyHandler, + haveTime func() time.Duration, + ) error + OnProposedBlock( + proposedBody data.BodyHandler, + proposedHeader data.HeaderHandler, + proposedHash []byte, + ) error + OnBackfilledBlock( + proposedBody data.BodyHandler, + proposedHeader data.HeaderHandler, + proposedHash []byte, + ) error + OnExecutedBlock(header data.HeaderHandler, rootHash []byte) error + ProposedDirectSentTransactionsToBroadcast(proposedBody data.BodyHandler) map[string][][]byte + PruneTrieAsyncHeader() + Close() error + IsInterfaceNil() bool +} + +// BlocksCache defines what a block queue should be able to do +type BlocksCache interface { + GetByNonce(nonce uint64) (cache.HeaderBodyPair, bool) + AddOrReplace(pair cache.HeaderBodyPair) error + Remove(nonce uint64) + Clean() + RemoveAtNonceAndHigher(providedNonce uint64) []uint64 + IsInterfaceNil() bool +} + +// HeadersExecutor defines what a headers executor should be able to do +type HeadersExecutor interface { + StartExecution() + PauseExecution() + ResumeExecution() + GetSignalProcessCompletionChan() chan uint64 + Close() error + IsInterfaceNil() bool +} + +// ExecutionManager defines a component able to manage the components responsible for block execution +type ExecutionManager interface { + StartExecution() + SetHeadersExecutor(executor HeadersExecutor) error + AddPairForExecution(pair cache.HeaderBodyPair) error + GetPendingExecutionResults() ([]data.BaseExecutionResultHandler, error) + CleanConfirmedExecutionResults(header data.HeaderHandler) error + CleanOnConsensusReached(headerHash []byte, header data.HeaderHandler) + SetLastNotarizedResult(executionResult data.BaseExecutionResultHandler) error + GetLastNotarizedExecutionResult() (data.BaseExecutionResultHandler, error) + RemoveAtNonceAndHigher(nonce uint64) error + ResetAndResumeExecution(lastNotarizedResult data.BaseExecutionResultHandler) error + RemovePendingExecutionResultsFromNonce(nonce uint64) error + PopDismissedResults() []executionTrack.DismissedBatch + GetSignalProcessCompletionChan() chan uint64 Close() error IsInterfaceNil() bool } @@ -287,6 +379,7 @@ type ScheduledBlockProcessor interface { // ValidatorStatisticsProcessor is the main interface for validators' consensus participation statistics type ValidatorStatisticsProcessor interface { UpdatePeerState(header data.MetaHeaderHandler, cache map[string]data.HeaderHandler) ([]byte, error) + UpdatePeerStateV3(header data.MetaHeaderHandler, cache map[string]data.HeaderHandler, metaExecutionResult data.MetaExecutionResultHandler) ([]byte, error) RevertPeerState(header data.MetaHeaderHandler) error Process(shardValidatorInfo data.ShardValidatorInfoHandler) error IsInterfaceNil() bool @@ -304,7 +397,7 @@ type ValidatorStatisticsProcessor interface { // TransactionLogProcessor is the main interface for saving logs generated by smart contract calls type TransactionLogProcessor interface { - GetAllCurrentLogs() []*data.LogData + GetAllCurrentLogs() []data.LogDataHandler GetLog(txHash []byte) (data.LogHandler, error) SaveLog(txHash []byte, tx data.TransactionHandler, vmLogs []*vmcommon.LogEntry) error Clean() @@ -313,7 +406,7 @@ type TransactionLogProcessor interface { // TransactionLogProcessorDatabase is interface the for saving logs also in RAM type TransactionLogProcessorDatabase interface { - GetLogFromCache(txHash []byte) (*data.LogData, bool) + GetLogFromCache(txHash []byte) (data.LogDataHandler, bool) EnableLogToBeSavedInCache() Clean() IsInterfaceNil() bool @@ -470,6 +563,10 @@ type VirtualMachinesContainerFactory interface { // EpochStartTriggerHandler defines that actions which are needed by processor for start of epoch type EpochStartTriggerHandler interface { Update(round uint64, nonce uint64) + SetEpochChange(round uint64) + ShouldProposeEpochChange(round uint64, nonce uint64) bool + SetEpochChangeProposed(value bool) + GetEpochChangeProposed() bool IsEpochStart() bool Epoch() uint32 MetaEpoch() uint32 @@ -594,14 +691,34 @@ type DataPacker interface { IsInterfaceNil() bool } +// RequestForEpochHandler defines the methods through which request to data for a specific epoch can be made +type RequestForEpochHandler interface { + RequestShardHeaderForEpoch(shardID uint32, hash []byte, epoch uint32) + RequestMetaHeaderForEpoch(hash []byte, epoch uint32) + RequestMetaHeaderByNonceForEpoch(nonce uint64, epoch uint32) + RequestShardHeaderByNonceForEpoch(shardID uint32, nonce uint64, epoch uint32) + RequestTransactionsForEpoch(destShardID uint32, txHashes [][]byte, epoch uint32) + RequestUnsignedTransactionsForEpoch(destShardID uint32, scrHashes [][]byte, epoch uint32) + RequestRewardTransactionsForEpoch(destShardID uint32, rewardTxHashes [][]byte, epoch uint32) + RequestMiniBlockForEpoch(destShardID uint32, miniBlockHash []byte, epoch uint32) + RequestMiniBlocksForEpoch(destShardID uint32, miniBlocksHashes [][]byte, epoch uint32) + RequestTrieNodesForEpoch(destShardID uint32, hashes [][]byte, topic string, epoch uint32) + RequestValidatorInfoForEpoch(hash []byte, epoch uint32) + RequestValidatorsInfoForEpoch(hashes [][]byte, epoch uint32) + RequestEquivalentProofByHashForEpoch(headerShard uint32, headerHash []byte, epoch uint32) + RequestEquivalentProofByNonceForEpoch(headerShard uint32, headerNonce uint64, epoch uint32) + IsInterfaceNil() bool +} + // RequestHandler defines the methods through which request to data can be made type RequestHandler interface { + RequestForEpochHandler SetEpoch(epoch uint32) RequestShardHeader(shardID uint32, hash []byte) RequestMetaHeader(hash []byte) RequestMetaHeaderByNonce(nonce uint64) RequestShardHeaderByNonce(shardID uint32, nonce uint64) - RequestTransaction(destShardID uint32, txHashes [][]byte) + RequestTransactions(destShardID uint32, txHashes [][]byte) RequestUnsignedTransactions(destShardID uint32, scrHashes [][]byte) RequestRewardTransactions(destShardID uint32, txHashes [][]byte) RequestMiniBlock(destShardID uint32, miniblockHash []byte) @@ -690,8 +807,13 @@ type RewardsHandler interface { // EndOfEpochEconomics defines the functionality that is needed to compute end of epoch economics data type EndOfEpochEconomics interface { - ComputeEndOfEpochEconomics(metaBlock *block.MetaBlock) (*block.Economics, error) - VerifyRewardsPerBlock(metaBlock *block.MetaBlock, correctedProtocolSustainability *big.Int, computedEconomics *block.Economics) error + ComputeEndOfEpochEconomics(metaBlock data.MetaHeaderHandler) (*block.Economics, error) + ComputeEndOfEpochEconomicsV3( + metaBlock data.MetaHeaderHandler, + prevBlockExecutionResults data.BaseMetaExecutionResultHandler, + epochStartHandler data.EpochStartHandler, + ) (*block.Economics, error) + VerifyRewardsPerBlock(metaBlock data.MetaHeaderHandler, correctedProtocolSustainability *big.Int, computedEconomics data.EconomicsHandler) error IsInterfaceNil() bool } @@ -700,10 +822,13 @@ type feeHandler interface { DeveloperPercentage() float64 GasPerDataByte() uint64 MaxGasLimitPerBlock(shardID uint32) uint64 + MaxGasLimitPerBlockInEpoch(shardID uint32, epoch uint32) uint64 MaxGasLimitPerMiniBlock(shardID uint32) uint64 MaxGasLimitPerBlockForSafeCrossShard() uint64 + MaxGasLimitPerBlockForSafeCrossShardInEpoch(epoch uint32) uint64 MaxGasLimitPerMiniBlockForSafeCrossShard() uint64 MaxGasLimitPerTx() uint64 + MaxGasLimitPerTxInEpoch(epoch uint32) uint64 ComputeGasLimit(tx data.TransactionWithFeeHandler) uint64 ComputeMoveBalanceFee(tx data.TransactionWithFeeHandler) *big.Int ComputeTxFee(tx data.TransactionWithFeeHandler) *big.Int @@ -711,6 +836,7 @@ type feeHandler interface { ComputeFeeForProcessing(tx data.TransactionWithFeeHandler, gasToUse uint64) *big.Int MinGasPrice() uint64 MaxGasPriceSetGuardian() uint64 + BlockCapacityOverestimationFactor() uint64 GasPriceModifier() float64 MinGasLimit() uint64 ExtraGasLimitGuardedTx() uint64 @@ -868,6 +994,7 @@ type BootstrapperFromStorage interface { // RequestBlockBodyHandler is the interface needed by process block type RequestBlockBodyHandler interface { GetBlockBodyFromPool(headerHandler data.HeaderHandler) (data.BodyHandler, error) + GetProposedAndExecutedMiniBlockHeaders(headerHandler data.HeaderHandler) (data.BodyHandler, error) } // InterceptedHeaderSigVerifier is the interface needed at interceptors level to check that a header's signature is correct @@ -884,7 +1011,7 @@ type InterceptedHeaderSigVerifier interface { // HeaderIntegrityVerifier encapsulates methods useful to check that a header's integrity is correct type HeaderIntegrityVerifier interface { Verify(header data.HeaderHandler) error - GetVersion(epoch uint32) string + GetVersion(epoch uint32, round uint64) string IsInterfaceNil() bool } @@ -895,7 +1022,9 @@ type BlockTracker interface { AddTrackedHeader(header data.HeaderHandler, hash []byte) CheckBlockAgainstFinal(headerHandler data.HeaderHandler) error CheckBlockAgainstRoundHandler(headerHandler data.HeaderHandler) error - CheckBlockAgainstWhitelist(interceptedData InterceptedData) bool + CheckAgainstWhitelist(interceptedData InterceptedData) bool + CheckProofAgainstFinal(proof data.HeaderProofHandler) error + CheckProofAgainstRoundHandler(proof data.HeaderProofHandler) error CleanupHeadersBehindNonce(shardID uint32, selfNotarizedNonce uint64, crossNotarizedNonce uint64) CleanupInvalidCrossHeaders(metaNewEpoch uint32, metaRoundAttestingEpoch uint64) ComputeLongestChain(shardID uint32, header data.HeaderHandler) ([]data.HeaderHandler, [][]byte) @@ -919,6 +1048,8 @@ type BlockTracker interface { RemoveLastNotarizedHeaders() RestoreToGenesis() ShouldAddHeader(headerHandler data.HeaderHandler) bool + ComputeOwnShardStuck(lastExecutionResultsInfo data.BaseExecutionResultHandler, currentNonce uint64) + IsOwnShardStuck() bool IsInterfaceNil() bool } @@ -977,7 +1108,8 @@ type SCQueryService interface { // EpochStartDataCreator defines the functionality for node to create epoch start data type EpochStartDataCreator interface { CreateEpochStartData() (*block.EpochStart, error) - VerifyEpochStartDataForMetablock(metaBlock *block.MetaBlock) error + CreateEpochStartShardDataMetablockV3(metablock data.MetaHeaderHandler) ([]block.EpochStartShardData, error) + VerifyEpochStartDataForMetablock(metaBlock data.MetaHeaderHandler) error IsInterfaceNil() bool } @@ -990,6 +1122,12 @@ type RewardsCreator interface { metaBlock data.MetaHeaderHandler, validatorsInfo state.ShardValidatorsInfoMapHandler, computedEconomics *block.Economics, ) error GetAcceleratorRewards() *big.Int + CreateRewardsMiniBlocksV3( + metaBlock data.MetaHeaderHandler, + validatorsInfo state.ShardValidatorsInfoMapHandler, + computedEconomics *block.Economics, + prevBlockExecutionResults data.BaseMetaExecutionResultHandler, + ) (block.MiniBlockSlice, error) GetLocalTxCache() epochStart.TransactionCacher CreateMarshalledData(body *block.Body) map[string][][]byte GetRewardsTxs(body *block.Body) map[string]data.TransactionHandler @@ -1030,7 +1168,9 @@ type EpochStartSystemSCProcessor interface { type ValidityAttester interface { CheckBlockAgainstFinal(headerHandler data.HeaderHandler) error CheckBlockAgainstRoundHandler(headerHandler data.HeaderHandler) error - CheckBlockAgainstWhitelist(interceptedData InterceptedData) bool + CheckAgainstWhitelist(interceptedData InterceptedData) bool + CheckProofAgainstFinal(proof data.HeaderProofHandler) error + CheckProofAgainstRoundHandler(proof data.HeaderProofHandler) error IsInterfaceNil() bool } @@ -1051,6 +1191,7 @@ type RoundTimeDurationHandler interface { // RoundHandler defines the actions which should be handled by a round implementation type RoundHandler interface { Index() int64 + TimeDuration() time.Duration IsInterfaceNil() bool } @@ -1068,6 +1209,8 @@ type RatingsInfoHandler interface { SignedBlocksThreshold() float32 MetaChainRatingsStepHandler() RatingsStepHandler ShardChainRatingsStepHandler() RatingsStepHandler + MetaChainRatingsStepHandlerForEpoch(epoch uint32) RatingsStepHandler + ShardChainRatingsStepHandlerForEpoch(epoch uint32) RatingsStepHandler SelectionChances() []SelectionChance SetStatusHandler(handler core.AppStatusHandler) error IsInterfaceNil() bool @@ -1140,6 +1283,7 @@ type WhiteListHandler interface { // InterceptedDebugger defines an interface for debugging the intercepted data type InterceptedDebugger interface { + LogReceivedData(data InterceptedData, msg p2p.MessageP2P, fromConnectedPeer core.PeerID) LogReceivedHashes(topic string, hashes [][]byte) LogProcessedHashes(topic string, hashes [][]byte, err error) IsInterfaceNil() bool @@ -1206,12 +1350,6 @@ type RoundNotifier interface { IsInterfaceNil() bool } -// EnableRoundsHandler is an interface which can be queried to check for round activation features/fixes -type EnableRoundsHandler interface { - IsDisableAsyncCallV1Enabled() bool - IsInterfaceNil() bool -} - // ESDTPauseHandler provides IsPaused function for an ESDT token type ESDTPauseHandler interface { IsPaused(token []byte) bool @@ -1262,6 +1400,10 @@ type CoreComponentsHolder interface { ChainParametersHandler() ChainParametersHandler FieldsSizeChecker() common.FieldsSizeChecker EpochChangeGracePeriodHandler() common.EpochChangeGracePeriodHandler + ProcessConfigsHandler() common.ProcessConfigsHandler + CommonConfigsHandler() common.CommonConfigsHandler + AntifloodConfigsHandler() common.AntifloodConfigsHandler + SyncTimer() ntp.SyncTimer IsInterfaceNil() bool } @@ -1271,7 +1413,7 @@ type CryptoComponentsHolder interface { BlockSignKeyGen() crypto.KeyGenerator TxSingleSigner() crypto.SingleSigner BlockSigner() crypto.SingleSigner - GetMultiSigner(epoch uint32) (crypto.MultiSigner, error) + GetMultiSigner(epoch uint32) (crypto.MultiSignerV2, error) MultiSignerContainer() cryptoCommon.MultiSignerContainer SetMultiSignerContainer(ms cryptoCommon.MultiSignerContainer) error PeerSignatureHandler() crypto.PeerSignatureHandler @@ -1304,7 +1446,8 @@ type CheckedChunkResult struct { // InterceptedChunksProcessor defines the component that is able to process chunks of intercepted data type InterceptedChunksProcessor interface { - CheckBatch(b *batch.Batch, whiteListHandler WhiteListHandler) (CheckedChunkResult, error) + CheckBatch(b *batch.Batch, whiteListHandler WhiteListHandler, broadcastMethod p2p.BroadcastMethod) (CheckedChunkResult, error) + MarkVerified(b *batch.Batch, broadcastMethod p2p.BroadcastMethod) Close() error IsInterfaceNil() bool } @@ -1429,13 +1572,16 @@ type Debugger interface { type SentSignaturesTracker interface { StartRound() SignatureSent(pkBytes []byte) + RecordSignedNonce(pkBytes []byte, nonce uint64, headerHash []byte) + GetSignedHash(pkBytes []byte, nonce uint64) ([]byte, bool) ResetCountersForManagedBlockSigner(signerPk []byte) IsInterfaceNil() bool } // InterceptedDataVerifier defines a component able to verify intercepted data validity type InterceptedDataVerifier interface { - Verify(interceptedData InterceptedData) error + Verify(interceptedData InterceptedData, topic string, broadcastMethod p2p.BroadcastMethod) error + MarkVerified(interceptedData InterceptedData, broadcastMethod p2p.BroadcastMethod) IsInterfaceNil() bool } @@ -1453,3 +1599,93 @@ type ProofsPool interface { IsProofInPoolEqualTo(headerProof data.HeaderProofHandler) bool IsInterfaceNil() bool } + +// GasComputation defines a component able to select the maximum number of outgoing transactions and incoming mini blocks +// to fill the block with respect to the gas limits +type GasComputation interface { + AddIncomingMiniBlocks( + miniBlocks []data.MiniBlockHeaderHandler, + transactions map[string][]data.TransactionHandler, + ) (lastMiniBlockIndex int, pendingMiniBlocks int, err error) + AddOutgoingTransactions( + txHashes [][]byte, + transactions []data.TransactionHandler, + ) (addedTxHashes [][]byte, pendingMiniBlocksAdded []data.MiniBlockHeaderHandler, err error) + GetBandwidthForTransactions() uint64 + RevertIncomingMiniBlocks(miniBlockHashes [][]byte) + TotalGasConsumedInSelfShard() uint64 + TotalGasConsumedInShard(shard uint32) uint64 + DecreaseIncomingLimit() + DecreaseOutgoingLimit() + ZeroIncomingLimit() + ZeroOutgoingLimit() + ResetIncomingLimit() + ResetOutgoingLimit() + CanAddPendingIncomingMiniBlocks() bool + Reset() + IsInterfaceNil() bool +} + +// ShardCoordinator defines what a shard state coordinator should hold +type ShardCoordinator interface { + SelfId() uint32 + ComputeId(address []byte) uint32 + IsInterfaceNil() bool +} + +// AOTSelectionResult contains the result of pre-selected transactions +type AOTSelectionResult struct { + TxHashes [][]byte + GasProvided uint64 + PredictedBlockNonce uint64 + Randomness []byte + SelectionTimestamp time.Time +} + +// AOTTransactionSelector orchestrates ahead-of-time transaction selection +type AOTTransactionSelector interface { + TriggerAOTSelection(committedHeader data.HeaderHandler, currentRound uint64) + GetPreSelectedTransactions(blockNonce uint64) (*AOTSelectionResult, bool) + CancelOngoingSelection() + Close() error + IsInterfaceNil() bool +} + +// ExecutionResultsTracker is the interface that defines the methods for tracking execution results +type ExecutionResultsTracker interface { + AddExecutionResult(executionResult data.BaseExecutionResultHandler) (bool, error) + GetPendingExecutionResults() ([]data.BaseExecutionResultHandler, error) + GetPendingExecutionResultByHash(hash []byte) (data.BaseExecutionResultHandler, error) + GetPendingExecutionResultByNonce(nonce uint64) (data.BaseExecutionResultHandler, error) + GetLastNotarizedExecutionResult() (data.BaseExecutionResultHandler, error) + SetLastNotarizedResult(executionResult data.BaseExecutionResultHandler) error + RemoveFromNonce(nonce uint64) error + Clean(lastNotarizedResult data.BaseExecutionResultHandler) + CleanConfirmedExecutionResults(header data.HeaderHandler) error + CleanOnConsensusReached(headerHash []byte, header data.HeaderHandler) + PopDismissedResults() []executionTrack.DismissedBatch + IsInterfaceNil() bool +} + +// BlockDataRequester defines the methods needed by the processor to request missing data +type BlockDataRequester interface { + RequestBlockTransactions(body *block.Body) + RequestMiniBlocksAndTransactions(header data.HeaderHandler) + GetFinalCrossMiniBlockInfoAndRequestMissing(header data.HeaderHandler) []*data.MiniBlockInfo + IsDataPreparedForProcessing(haveTime func() time.Duration) error + Reset() + IsInterfaceNil() bool +} + +// InclusionEstimator decides how many execution results can be included in the next block +type InclusionEstimator interface { + Decide(lastNotarised *common.LastExecutionResultForInclusion, pending []data.BaseExecutionResultHandler, currentHeaderRound uint64) (allowed int) + IsInterfaceNil() bool +} + +// ShardInfoCreator defines the functionality to create shard info +type ShardInfoCreator interface { + CreateShardInfoV3(metaHeader data.MetaHeaderHandler, shardHeaders []data.HeaderHandler, shardHeaderHashes [][]byte) ([]data.ShardDataProposalHandler, []data.ShardDataHandler, error) + CreateShardInfoFromLegacyMeta(metaHeader data.MetaHeaderHandler, shardHeaders []data.ShardHeaderHandler, shardHeaderHashes [][]byte) ([]data.ShardDataHandler, error) + IsInterfaceNil() bool +} diff --git a/process/missingData/missingDataResolver.go b/process/missingData/missingDataResolver.go new file mode 100644 index 00000000000..22cb1aff0a2 --- /dev/null +++ b/process/missingData/missingDataResolver.go @@ -0,0 +1,397 @@ +package missingData + +import ( + "sync" + "time" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + logger "github.com/multiversx/mx-chain-logger-go" + + "github.com/multiversx/mx-chain-go/dataRetriever" + "github.com/multiversx/mx-chain-go/process" +) + +const checkMissingDataStep = 10 * time.Millisecond + +var log = logger.GetOrCreate("missingDataResolver") + +// ResolverArgs holds the arguments needed to create a Resolver +type ResolverArgs struct { + HeadersPool dataRetriever.HeadersPool + ProofsPool dataRetriever.ProofsPool + RequestHandler process.RequestHandler + BlockDataRequester process.BlockDataRequester +} + +// Resolver is responsible for requesting and tracking missing headers and proofs. +type Resolver struct { + mutHeaders sync.RWMutex + missingHeaders map[string]struct{} + mutProofs sync.RWMutex + missingProofs map[string]struct{} + headersPool dataRetriever.HeadersPool + proofsPool dataRetriever.ProofsPool + requestHandler process.RequestHandler + blockDataRequester process.BlockDataRequester +} + +// NewMissingDataResolver creates a new instance of Resolver. +func NewMissingDataResolver(args ResolverArgs) (*Resolver, error) { + if check.IfNil(args.HeadersPool) { + return nil, process.ErrNilHeadersDataPool + } + if check.IfNil(args.ProofsPool) { + return nil, process.ErrNilProofsPool + } + if check.IfNil(args.RequestHandler) { + return nil, process.ErrNilRequestHandler + } + if check.IfNil(args.BlockDataRequester) { + return nil, process.ErrNilBlockDataRequester + } + + r := &Resolver{ + missingHeaders: make(map[string]struct{}), + missingProofs: make(map[string]struct{}), + headersPool: args.HeadersPool, + proofsPool: args.ProofsPool, + requestHandler: args.RequestHandler, + blockDataRequester: args.BlockDataRequester, + } + + r.monitorReceivedData() + return r, nil +} + +// RequestMissingMetaHeadersBlocking requests the missing meta headers and proofs for the given shard header. +func (r *Resolver) RequestMissingMetaHeadersBlocking( + shardHeader data.ShardHeaderHandler, + timeout time.Duration, +) error { + err := r.RequestMissingMetaHeaders(shardHeader) + if err != nil { + return err + } + + return r.WaitForMissingData(timeout) +} + +// RequestMissingMetaHeaders requests the missing meta headers and proofs for the given shard header. +func (r *Resolver) RequestMissingMetaHeaders( + shardHeader data.ShardHeaderHandler, +) error { + if check.IfNil(shardHeader) { + return process.ErrNilBlockHeader + } + + metaBlockHashes := shardHeader.GetMetaBlockHashes() + if shardHeader.IsStartOfEpochBlock() { + epochStartMetaHash := shardHeader.GetEpochStartMetaHash() + metaBlockHashes = append(metaBlockHashes, epochStartMetaHash) + } + + for i := 0; i < len(metaBlockHashes); i++ { + r.requestHeaderIfNeeded(core.MetachainShardId, metaBlockHashes[i]) + r.requestProofIfNeeded(core.MetachainShardId, metaBlockHashes[i]) + } + return nil +} + +func (r *Resolver) addMissingHeader(hash []byte) bool { + r.mutHeaders.Lock() + r.missingHeaders[string(hash)] = struct{}{} + r.mutHeaders.Unlock() + + // avoid missing notifications if the header just arrived + _, err := r.headersPool.GetHeaderByHash(hash) + if err == nil { + r.mutHeaders.Lock() + delete(r.missingHeaders, string(hash)) + r.mutHeaders.Unlock() + } + + return err != nil +} + +func (r *Resolver) addMissingProof(shardID uint32, hash []byte) bool { + r.mutProofs.Lock() + r.missingProofs[string(hash)] = struct{}{} + r.mutProofs.Unlock() + + // avoid missing notifications if the proof just arrived + hasProof := r.proofsPool.HasProof(shardID, hash) + if hasProof { + r.mutProofs.Lock() + delete(r.missingProofs, string(hash)) + r.mutProofs.Unlock() + } + + return !hasProof +} + +func (r *Resolver) markHeaderReceived(hash []byte) { + r.mutHeaders.Lock() + delete(r.missingHeaders, string(hash)) + r.mutHeaders.Unlock() +} + +func (r *Resolver) markProofReceived(hash []byte) { + r.mutProofs.Lock() + delete(r.missingProofs, string(hash)) + r.mutProofs.Unlock() +} + +func (r *Resolver) allHeadersReceived() bool { + r.mutHeaders.RLock() + defer r.mutHeaders.RUnlock() + + return len(r.missingHeaders) == 0 +} + +func (r *Resolver) allProofsReceived() bool { + r.mutProofs.RLock() + defer r.mutProofs.RUnlock() + + return len(r.missingProofs) == 0 +} + +func (r *Resolver) allDataReceived() bool { + return r.allHeadersReceived() && r.allProofsReceived() +} + +func (r *Resolver) receivedProof(proof data.HeaderProofHandler) { + r.markProofReceived(proof.GetHeaderHash()) +} + +func (r *Resolver) receivedHeader(_ data.HeaderHandler, headerHash []byte) { + r.markHeaderReceived(headerHash) +} + +func (r *Resolver) monitorReceivedData() { + r.headersPool.RegisterHandler(r.receivedHeader) + r.proofsPool.RegisterHandler(r.receivedProof) +} + +func (r *Resolver) requestHeaderIfNeeded( + shardID uint32, + headerHash []byte, +) { + _, err := r.headersPool.GetHeaderByHash(headerHash) + if err == nil { + return + } + + added := r.addMissingHeader(headerHash) + if !added { + return + } + + if shardID == core.MetachainShardId { + go r.requestHandler.RequestMetaHeader(headerHash) + } else { + go r.requestHandler.RequestShardHeader(shardID, headerHash) + } +} + +func (r *Resolver) requestProofIfNeeded(shardID uint32, headerHash []byte) { + if r.proofsPool.HasProof(shardID, headerHash) { + return + } + + added := r.addMissingProof(shardID, headerHash) + if !added { + return + } + + go r.requestHandler.RequestEquivalentProofByHash(shardID, headerHash) +} + +// WaitForMissingData waits until all missing data is received or the timeout is reached. +// TODO: maybe use channels instead of polling +func (r *Resolver) WaitForMissingData(timeout time.Duration) error { + waitDeadline := time.Now().Add(timeout) + + stepHaveTime := func(stepTimeout time.Duration) func() time.Duration { + stepDeadline := time.Now().Add(stepTimeout) + haveTime := func() time.Duration { + return time.Until(stepDeadline) + } + return haveTime + } + + err := r.blockDataRequester.IsDataPreparedForProcessing(stepHaveTime(checkMissingDataStep)) + for { + if r.allDataReceived() && err == nil { + log.Debug("missingDataResolver.WaitForMissingData: all missing data received") + return nil + } + + if time.Now().After(waitDeadline) { + r.mutHeaders.RLock() + numMissingHeaders := len(r.missingHeaders) + r.mutHeaders.RUnlock() + + r.mutProofs.RLock() + numMissingProofs := len(r.missingProofs) + r.mutProofs.RUnlock() + + log.Debug("missingDataResolver.WaitForMissingData: timeout reached while waiting for missing data", + "missingHeaders", numMissingHeaders, + "missingProofs", numMissingProofs, + "IsDataPreparedError", err) + + return process.ErrTimeIsOut + } + + if err != nil { + err = r.blockDataRequester.IsDataPreparedForProcessing(stepHaveTime(checkMissingDataStep)) + } + + time.Sleep(checkMissingDataStep) + } +} + +// RequestMissingShardHeadersBlocking requests the missing shard headers and proofs for the given meta header and waits for results. +func (r *Resolver) RequestMissingShardHeadersBlocking( + metaHeader data.MetaHeaderHandler, + timeout time.Duration, +) error { + err := r.RequestMissingShardHeaders(metaHeader) + if err != nil { + return err + } + + return r.WaitForMissingData(timeout) +} + +// RequestMissingShardHeaders requests the missing shard headers and proofs for the given meta header. +func (r *Resolver) RequestMissingShardHeaders( + metaHeader data.MetaHeaderHandler, +) error { + if check.IfNil(metaHeader) { + return process.ErrNilMetaBlockHeader + } + + if metaHeader.IsStartOfEpochBlock() && metaHeader.GetEpochStartHandler() != nil { + r.requestEpochStartLastFinalizedHeaders(metaHeader.GetEpochStartHandler()) + } + + shardDataProposedNonces := make(map[uint32]uint64) + for _, shardProposalData := range metaHeader.GetShardInfoProposalHandlers() { + shardID := shardProposalData.GetShardID() + storeNonceToShardDataIfGreater(shardDataProposedNonces, shardProposalData.GetNonce(), shardID) + + r.requestHeaderIfNeeded(shardID, shardProposalData.GetHeaderHash()) + r.requestProofIfNeeded(shardID, shardProposalData.GetHeaderHash()) + } + + shardDataFinalizedNonces := getShardDataFinalizedNonces(metaHeader.GetShardInfoHandlers()) + r.requestNonceGapsIfNeeded(shardDataFinalizedNonces, shardDataProposedNonces) + + return nil +} + +func (r *Resolver) requestEpochStartLastFinalizedHeaders(epochStartHandler data.EpochStartHandler) { + for _, finalizedHdr := range epochStartHandler.GetLastFinalizedHeaderHandlers() { + r.requestHeaderIfNeeded(finalizedHdr.GetShardID(), finalizedHdr.GetHeaderHash()) + r.requestProofIfNeeded(finalizedHdr.GetShardID(), finalizedHdr.GetHeaderHash()) + } +} + +func storeNonceToShardDataIfGreater(shardDataProposedNonces map[uint32]uint64, nonce uint64, shardID uint32) { + if nonce > shardDataProposedNonces[shardID] { + shardDataProposedNonces[shardID] = nonce + } +} + +func getShardDataFinalizedNonces(shardInfoHandlers []data.ShardDataHandler) map[uint32]uint64 { + shardDataFinalizedNonces := make(map[uint32]uint64) + for _, shardData := range shardInfoHandlers { + shardDataFinalizedNonces[shardData.GetShardID()] = shardData.GetNonce() + } + return shardDataFinalizedNonces +} + +func (r *Resolver) requestNonceGapsIfNeeded(shardDataFinalizedNonces, shardDataProposedNonces map[uint32]uint64) { + for shardID, proposedNonce := range shardDataProposedNonces { + lastFinalizedNonce, found := shardDataFinalizedNonces[shardID] + if !found { + continue + } + + if proposedNonce <= lastFinalizedNonce { + log.Warn("requestNonceGapsIfNeeded: proposed nonce is not greater than finalized nonce, skipping", + "shardID", shardID, + "proposedNonce", proposedNonce, + "lastFinalizedNonce", lastFinalizedNonce) + continue + } + + nonceGap := proposedNonce - lastFinalizedNonce + if nonceGap < 2 { + continue + } + + r.requestShardHeadersAndProofsByNonce(shardID, lastFinalizedNonce+1, proposedNonce) + } +} + +// requestShardHeadersAndProofsByNonce will request shard headers and proofs if needed without blocking +func (r *Resolver) requestShardHeadersAndProofsByNonce(shardID uint32, startNonce, endNonce uint64) { + for shardNonceToRequest := startNonce; shardNonceToRequest < endNonce; shardNonceToRequest++ { + r.requestShardHeaderByNonceIfNeeded(shardID, shardNonceToRequest) + r.requestShardProofByNonceIfNeeded(shardID, shardNonceToRequest) + } +} + +func (r *Resolver) requestShardHeaderByNonceIfNeeded(shardID uint32, nonce uint64) { + if _, _, err := r.headersPool.GetHeadersByNonceAndShardId(nonce, shardID); err == nil { + return + } + + go r.requestHandler.RequestShardHeaderByNonce(shardID, nonce) +} + +func (r *Resolver) requestShardProofByNonceIfNeeded(shardID uint32, nonce uint64) { + if _, err := r.proofsPool.GetProofByNonce(nonce, shardID); err == nil { + return + } + + go r.requestHandler.RequestEquivalentProofByNonce(shardID, nonce) +} + +// RequestBlockTransactions requests the transactions for the given block body. +func (r *Resolver) RequestBlockTransactions(body *block.Body) { + r.blockDataRequester.RequestBlockTransactions(body) +} + +// RequestMiniBlocksAndTransactions requests mini blocks and transactions if missing +func (r *Resolver) RequestMiniBlocksAndTransactions(header data.HeaderHandler) { + r.blockDataRequester.RequestMiniBlocksAndTransactions(header) +} + +// GetFinalCrossMiniBlockInfoAndRequestMissing returns the final cross mini block infos and requests missing mini blocks and transactions +func (r *Resolver) GetFinalCrossMiniBlockInfoAndRequestMissing(header data.HeaderHandler) []*data.MiniBlockInfo { + return r.blockDataRequester.GetFinalCrossMiniBlockInfoAndRequestMissing(header) +} + +// Reset clears the internal state of the Resolver. +func (r *Resolver) Reset() { + r.mutHeaders.Lock() + r.missingHeaders = make(map[string]struct{}) + r.mutHeaders.Unlock() + + r.mutProofs.Lock() + r.missingProofs = make(map[string]struct{}) + r.mutProofs.Unlock() + + r.blockDataRequester.Reset() +} + +// IsInterfaceNil returns true if there is no value under the interface +func (r *Resolver) IsInterfaceNil() bool { + return r == nil +} diff --git a/process/missingData/missingDataResolver_test.go b/process/missingData/missingDataResolver_test.go new file mode 100644 index 00000000000..2befa6ff3ab --- /dev/null +++ b/process/missingData/missingDataResolver_test.go @@ -0,0 +1,1523 @@ +package missingData + +import ( + "errors" + "fmt" + "math" + "sync" + "testing" + "time" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" + "github.com/multiversx/mx-chain-go/testscommon/pool" + "github.com/multiversx/mx-chain-go/testscommon/preprocMocks" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" +) + +type requestHeader func(mdr *Resolver) error + +func testRequestMissingHeaderAndProofsAllReceived( + t *testing.T, + requestHeaderFunc requestHeader, + headerHash []byte, +) { + var headerReceivedHandler func(header data.HeaderHandler, _ []byte) + var proofReceivedHandler func(proof data.HeaderProofHandler) + + proofsPool := &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return false + }, + RegisterHandlerCalled: func(handler func(headerProof data.HeaderProofHandler)) { + proofReceivedHandler = handler + }, + } + headersPool := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if string(hash) == string(headerHash) { + return nil, errors.New("not found") + } + return nil, nil + }, + RegisterHandlerCalled: func(handler func(header data.HeaderHandler, _ []byte)) { + headerReceivedHandler = handler + }, + } + commonArgs := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: &testscommon.RequestHandlerStub{}, + BlockDataRequester: &preprocMocks.BlockDataRequesterStub{}, + } + mdr, _ := NewMissingDataResolver(commonArgs) + + go func() { + time.Sleep(50 * time.Millisecond) + headerReceivedHandler(&block.HeaderV2{}, headerHash) + proofReceivedHandler(&processMocks.HeaderProofHandlerStub{ + GetHeaderHashCalled: func() []byte { + return headerHash + }, + }) + }() + + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + err := requestHeaderFunc(mdr) + require.Nil(t, err) + require.True(t, mdr.allHeadersReceived()) + require.True(t, mdr.allProofsReceived()) + wg.Done() + }() + wg.Wait() +} + +func TestNewMissingDataResolver(t *testing.T) { + t.Parallel() + + proofsPool := &dataRetriever.ProofsPoolMock{} + headersPool := &pool.HeadersPoolStub{} + requestHandler := &testscommon.RequestHandlerStub{} + blockDataRequester := &preprocMocks.BlockDataRequesterStub{} + commonArgs := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + + t.Run("valid inputs ok", func(t *testing.T) { + t.Parallel() + + mdr, err := NewMissingDataResolver(commonArgs) + require.NotNil(t, mdr) + require.Nil(t, err) + }) + + t.Run("nil headersPool should err", func(t *testing.T) { + t.Parallel() + + commonArgsCopy := commonArgs + commonArgsCopy.HeadersPool = nil + mdr, err := NewMissingDataResolver(commonArgsCopy) + require.Nil(t, mdr) + require.Equal(t, process.ErrNilHeadersDataPool, err) + }) + + t.Run("nil proofsPool should err", func(t *testing.T) { + t.Parallel() + + commonArgsCopy := commonArgs + commonArgsCopy.ProofsPool = nil + mdr, err := NewMissingDataResolver(commonArgsCopy) + require.Nil(t, mdr) + require.Equal(t, process.ErrNilProofsPool, err) + }) + + t.Run("nil requestHandler should err", func(t *testing.T) { + t.Parallel() + + commonArgsCopy := commonArgs + commonArgsCopy.RequestHandler = nil + mdr, err := NewMissingDataResolver(commonArgsCopy) + require.Nil(t, mdr) + require.Equal(t, process.ErrNilRequestHandler, err) + }) + t.Run("nil blockDataRequester should err", func(t *testing.T) { + t.Parallel() + + commonArgsCopy := commonArgs + commonArgsCopy.BlockDataRequester = nil + mdr, err := NewMissingDataResolver(commonArgsCopy) + require.Nil(t, mdr) + require.Equal(t, process.ErrNilBlockDataRequester, err) + }) +} + +func TestResolver_AddAndMarkMissingData(t *testing.T) { + t.Parallel() + + proofNotFoundError := errors.New("proof not found") + headerNotFoundError := errors.New("header not found") + headerHash := []byte("headerHash") + proofHash := []byte("proofHash") + + proofsPool := &dataRetriever.ProofsPoolMock{ + GetProofCalled: func(_ uint32, _ []byte) (data.HeaderProofHandler, error) { + return nil, proofNotFoundError + }, + } + headersPool := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, headerNotFoundError + }, + } + requestHandler := &testscommon.RequestHandlerStub{} + + blockDataRequester := &preprocMocks.BlockDataRequesterStub{} + commonArgs := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + t.Run("add missing and mark header", func(t *testing.T) { + t.Parallel() + + mdr, _ := NewMissingDataResolver(commonArgs) + mdr.addMissingHeader(headerHash) + require.Contains(t, mdr.missingHeaders, string(headerHash)) + require.False(t, mdr.allHeadersReceived()) + + mdr.markHeaderReceived(headerHash) + require.NotContains(t, mdr.missingHeaders, string(headerHash)) + require.True(t, mdr.allHeadersReceived()) + }) + + t.Run("add missing and mark proof", func(t *testing.T) { + t.Parallel() + + mdr, _ := NewMissingDataResolver(commonArgs) + mdr.addMissingProof(0, proofHash) + require.Contains(t, mdr.missingProofs, string(proofHash)) + require.False(t, mdr.allProofsReceived()) + + mdr.markProofReceived(proofHash) + require.NotContains(t, mdr.missingProofs, string(proofHash)) + require.True(t, mdr.allProofsReceived()) + }) +} + +func TestResolver_requestHeaderIfNeeded(t *testing.T) { + t.Parallel() + + existingMetaHeader := "existingMetaHeader" + existingShardHeader := "existingShardHeader" + notFoundError := errors.New("not found") + + headersPool := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + switch string(hash) { + case existingMetaHeader: + return &block.MetaBlock{}, nil + case existingShardHeader: + return &block.HeaderV2{}, nil + default: + return nil, notFoundError + } + }, + } + proofsPool := &dataRetriever.ProofsPoolMock{} + + requestHandler := &testscommon.RequestHandlerStub{ + RequestMetaHeaderCalled: func(hash []byte) {}, + RequestShardHeaderCalled: func(shardID uint32, hash []byte) {}, + } + blockDataRequester := &preprocMocks.BlockDataRequesterStub{} + commonArgs := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + + t.Run("header already exists", func(t *testing.T) { + t.Parallel() + + mdr, _ := NewMissingDataResolver(commonArgs) + mdr.requestHeaderIfNeeded(core.MetachainShardId, []byte(existingShardHeader)) + require.NotContains(t, mdr.missingHeaders, existingShardHeader) + require.True(t, mdr.allHeadersReceived()) + }) + + t.Run("meta header missing", func(t *testing.T) { + t.Parallel() + + mdr, _ := NewMissingDataResolver(commonArgs) + mdr.requestHeaderIfNeeded(core.MetachainShardId, []byte("missingHeader")) + require.False(t, mdr.allHeadersReceived()) + }) + t.Run("shard header missing", func(t *testing.T) { + t.Parallel() + + mdr, _ := NewMissingDataResolver(commonArgs) + mdr.requestHeaderIfNeeded(1, []byte("missingShardHeader")) + require.False(t, mdr.allHeadersReceived()) + }) + t.Run("header arriving in pool after first check, should not request", func(t *testing.T) { + t.Parallel() + + numCall := 0 + headersPool := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if string(hash) == existingMetaHeader && numCall == 0 { + numCall++ + } else if string(hash) == existingMetaHeader && numCall == 1 { + numCall++ + return &block.MetaBlock{}, nil + } + return nil, notFoundError + }, + } + requestHandler := &testscommon.RequestHandlerStub{ + RequestMetaHeaderCalled: func(hash []byte) { + require.Fail(t, "RequestMetaHeader should not be called again for existing header") + }, + } + + commonArgs := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + mdr, _ := NewMissingDataResolver(commonArgs) + mdr.requestHeaderIfNeeded(core.MetachainShardId, []byte(existingMetaHeader)) + require.True(t, mdr.allHeadersReceived()) + }) +} + +func TestResolver_requestProofIfNeeded(t *testing.T) { + t.Parallel() + + proofHash := []byte("proofHash") + headersPool := &pool.HeadersPoolStub{} + requestHandler := &testscommon.RequestHandlerStub{} + blockDataRequester := &preprocMocks.BlockDataRequesterStub{} + commonArgs := ResolverArgs{ + HeadersPool: headersPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + t.Run("proof already exists", func(t *testing.T) { + t.Parallel() + proofsPool := &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(_ uint32, _ []byte) bool { + return true + }, + } + + commonArgsCopy := commonArgs + commonArgsCopy.ProofsPool = proofsPool + mdr, _ := NewMissingDataResolver(commonArgsCopy) + mdr.requestProofIfNeeded(0, proofHash) + require.NotContains(t, mdr.missingProofs, string(proofHash)) + require.True(t, mdr.allProofsReceived()) + }) + + t.Run("proof missing", func(t *testing.T) { + t.Parallel() + proofsPool := &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(_ uint32, _ []byte) bool { + return false + }, + } + commonArgsCopy := commonArgs + commonArgsCopy.ProofsPool = proofsPool + mdr, _ := NewMissingDataResolver(commonArgsCopy) + mdr.requestProofIfNeeded(0, []byte("missingProof")) + require.False(t, mdr.allProofsReceived()) + }) + t.Run("proof arriving in pool after first check, should not request", func(t *testing.T) { + t.Parallel() + + numCall := 0 + proofsPool := &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(_ uint32, hash []byte) bool { + numCall++ + return numCall > 1 + }, + } + + requestHandler := &testscommon.RequestHandlerStub{ + RequestEquivalentProofByHashCalled: func(shardID uint32, hash []byte) { + require.Fail(t, "RequestHeaderProof should not be called again for existing proof") + }, + } + commonArgsCopy := commonArgs + commonArgsCopy.ProofsPool = proofsPool + commonArgsCopy.RequestHandler = requestHandler + mdr, _ := NewMissingDataResolver(commonArgsCopy) + mdr.requestProofIfNeeded(0, proofHash) + require.True(t, mdr.allProofsReceived()) + }) +} + +func TestResolver_WaitForMissingData(t *testing.T) { + t.Parallel() + + headerHash := []byte("headerHash") + errorHeaderNotFound := errors.New("header not found") + proofsPool := &dataRetriever.ProofsPoolMock{} + blockDataRequester := &preprocMocks.BlockDataRequesterStub{} + + t.Run("data received before timeout", func(t *testing.T) { + t.Parallel() + + headersPool := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if string(hash) == string(headerHash) { + return nil, errorHeaderNotFound + } + return nil, nil + }, + } + requestHandler := &testscommon.RequestHandlerStub{} + commonArgs := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + mdr, _ := NewMissingDataResolver(commonArgs) + mdr.addMissingHeader(headerHash) + go func() { + time.Sleep(50 * time.Millisecond) + mdr.markHeaderReceived(headerHash) + }() + + err := mdr.WaitForMissingData(100 * time.Millisecond) + require.Nil(t, err) + }) + + t.Run("timeout waiting for data", func(t *testing.T) { + t.Parallel() + headersPool := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if string(hash) == string(headerHash) { + return nil, errorHeaderNotFound + } + return nil, nil + }, + } + requestHandler := &testscommon.RequestHandlerStub{} + commonArgs := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + mdr, _ := NewMissingDataResolver(commonArgs) + _ = mdr.addMissingHeader(headerHash) + err := mdr.WaitForMissingData(50 * time.Millisecond) + require.Equal(t, process.ErrTimeIsOut, err) + }) + t.Run("timeout waiting for block data requester", func(t *testing.T) { + t.Parallel() + headersPool := &pool.HeadersPoolStub{} + requestHandler := &testscommon.RequestHandlerStub{} + blockDataRequester := &preprocMocks.BlockDataRequesterStub{ + IsDataPreparedForProcessingCalled: func(haveTime func() time.Duration) error { + for haveTime() > 0 { + time.Sleep(10 * time.Millisecond) + } + + return fmt.Errorf("missing data") + }, + } + commonArgs := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + mdr, _ := NewMissingDataResolver(commonArgs) + err := mdr.WaitForMissingData(50 * time.Millisecond) + require.NotNil(t, err) + require.Equal(t, process.ErrTimeIsOut, err) + }) +} + +func TestResolver_MonitorReceivedData(t *testing.T) { + t.Parallel() + + var headerReceivedHandler func(data.HeaderHandler, []byte) + var proofReceivedHandler func(data.HeaderProofHandler) + proofsPool := &dataRetriever.ProofsPoolMock{} + headersPool := &pool.HeadersPoolStub{} + requestHandler := &testscommon.RequestHandlerStub{} + + headerHash := "headerHash" + headersPool.RegisterHandlerCalled = func(handler func(data.HeaderHandler, []byte)) { + headerReceivedHandler = handler + } + proofsPool.RegisterHandlerCalled = func(handler func(data.HeaderProofHandler)) { + proofReceivedHandler = handler + } + blockDataRequester := &preprocMocks.BlockDataRequesterStub{} + commonArgs := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + mdr, _ := NewMissingDataResolver(commonArgs) + + t.Run("monitor headers and proofs", func(t *testing.T) { + t.Parallel() + + mdr.addMissingHeader([]byte(headerHash)) + mdr.addMissingProof(0, []byte(headerHash)) // the proof key is the header hash + + // receive both header and proof for header + go headerReceivedHandler(&block.HeaderV2{}, []byte(headerHash)) + go proofReceivedHandler(&processMocks.HeaderProofHandlerStub{ + GetHeaderHashCalled: func() []byte { + return []byte(headerHash) + }, + }) + time.Sleep(50 * time.Millisecond) + require.True(t, mdr.allDataReceived()) + }) +} + +func TestResolver_RequestMissingMetaHeadersBlocking(t *testing.T) { + t.Parallel() + + headerNotFoundErr := errors.New("header not found") + metaHeaderHash := []byte("metaHeaderHash") + shardHeader := &block.HeaderV2{ + Header: &block.Header{ + MetaBlockHashes: [][]byte{metaHeaderHash}, + }, + } + + proofsPool := &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return false + }, + } + headersPool := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if string(hash) == string(metaHeaderHash) { + return nil, headerNotFoundErr + } + return nil, nil + }, + } + requestHandler := &testscommon.RequestHandlerStub{} + blockDataRequester := &preprocMocks.BlockDataRequesterStub{} + commonArgs := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + mdr, _ := NewMissingDataResolver(commonArgs) + + t.Run("nil shard header should err", func(t *testing.T) { + t.Parallel() + + err := mdr.RequestMissingMetaHeadersBlocking(nil, 100*time.Millisecond) + require.Equal(t, process.ErrNilBlockHeader, err) + }) + + t.Run("request missing meta headers and proofs, none received", func(t *testing.T) { + t.Parallel() + + err := mdr.RequestMissingMetaHeadersBlocking(shardHeader, 100*time.Millisecond) + require.Equal(t, process.ErrTimeIsOut, err) + require.False(t, mdr.allHeadersReceived()) + require.False(t, mdr.allProofsReceived()) + }) + t.Run("requesting missing meta headers for start of epoch block", func(t *testing.T) { + t.Parallel() + + requestedMetaHeaders := make([][]byte, 0) + mutRequestedData := sync.Mutex{} + headersPool := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, headerNotFoundErr + }, + } + args := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: &testscommon.RequestHandlerStub{ + RequestMetaHeaderCalled: func(hash []byte) { + mutRequestedData.Lock() + requestedMetaHeaders = append(requestedMetaHeaders, hash) + mutRequestedData.Unlock() + }, + }, + BlockDataRequester: blockDataRequester, + } + mdr, _ := NewMissingDataResolver(args) + + metaHash1 := []byte("metaHash1") + metaHash2 := []byte("metaHash2") + startOfEpochMetaHash := []byte("startOfEpochMetaHash") + startOfEpochHeader := &block.HeaderV2{ + Header: &block.Header{ + MetaBlockHashes: [][]byte{metaHash1, metaHash2}, + EpochStartMetaHash: startOfEpochMetaHash, + }, + } + + expectedMetaHeadersRequested := [][]byte{metaHash1, metaHash2, startOfEpochMetaHash} + err := mdr.RequestMissingMetaHeadersBlocking(startOfEpochHeader, 50*time.Millisecond) + require.Equal(t, process.ErrTimeIsOut, err) + require.False(t, mdr.allHeadersReceived()) + require.False(t, mdr.allProofsReceived()) + + mutRequestedData.Lock() + defer mutRequestedData.Unlock() + require.Equal(t, len(expectedMetaHeadersRequested), len(requestedMetaHeaders)) + + for i := 0; i < len(expectedMetaHeadersRequested); i++ { + require.Contains(t, requestedMetaHeaders, expectedMetaHeadersRequested[i]) + } + }) + t.Run("request missing meta headers and proofs, all received", func(t *testing.T) { + t.Parallel() + + requestMetaHdrFunc := func(mdr *Resolver) error { + return mdr.RequestMissingMetaHeadersBlocking(shardHeader, 200*time.Millisecond) + } + testRequestMissingHeaderAndProofsAllReceived(t, requestMetaHdrFunc, metaHeaderHash) + }) +} + +func TestResolver_RequestMissingShardHeadersBlocking(t *testing.T) { + t.Parallel() + + headerNotFoundErr := errors.New("header not found") + shardHeaderHash := []byte("shardHeaderHash") + metaHeader := &block.MetaBlockV3{ + ShardInfoProposal: []block.ShardDataProposal{ + { + Nonce: 4, + ShardID: 1, + HeaderHash: shardHeaderHash, + }, + }, + } + + proofsPool := &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return false + }, + } + headersPool := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + if string(hash) == string(shardHeaderHash) { + return nil, headerNotFoundErr + } + return nil, nil + }, + } + + commonArgs := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: &testscommon.RequestHandlerStub{}, + BlockDataRequester: &preprocMocks.BlockDataRequesterStub{}, + } + + t.Run("nil meta header, should return err", func(t *testing.T) { + t.Parallel() + + mdr, _ := NewMissingDataResolver(commonArgs) + err := mdr.RequestMissingShardHeadersBlocking(nil, 100*time.Millisecond) + require.Equal(t, process.ErrNilMetaBlockHeader, err) + }) + + t.Run("requesting missing shard headers with start of epoch block", func(t *testing.T) { + t.Parallel() + + headersPoolMock := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, headerNotFoundErr + }, + } + + proofsPoolMock := &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return false + }, + } + + requestedShardProofs := make([][]byte, 0) + mutRequestedProofs := sync.Mutex{} + + requestedShardHeaders := make([][]byte, 0) + mutRequestedData := sync.Mutex{} + + requestHandlerMock := &testscommon.RequestHandlerStub{ + RequestShardHeaderCalled: func(shardID uint32, hash []byte) { + require.True(t, shardID == 1 || shardID == 2) + + mutRequestedData.Lock() + requestedShardHeaders = append(requestedShardHeaders, hash) + mutRequestedData.Unlock() + }, + RequestEquivalentProofByHashCalled: func(headerShard uint32, headerHash []byte) { + require.True(t, headerShard == 1 || headerShard == 2) + + mutRequestedProofs.Lock() + requestedShardProofs = append(requestedShardProofs, headerHash) + mutRequestedProofs.Unlock() + }, + } + + args := ResolverArgs{ + HeadersPool: headersPoolMock, + ProofsPool: proofsPoolMock, + RequestHandler: requestHandlerMock, + BlockDataRequester: commonArgs.BlockDataRequester, + } + mdr, _ := NewMissingDataResolver(args) + + shard1HdrHashEpochStart := []byte("hdrHashEpochStart1") + shard2HdrHashEpochStart := []byte("hdrHashEpochStart2") + + shard1HdrHashProposal := []byte("hdrHashProposal1") + shard2HdrHashProposal := []byte("hdrHashProposal2") + + metaHeaderEpochStart := &block.MetaBlockV3{ + ShardInfoProposal: []block.ShardDataProposal{ + { + Nonce: 5, + ShardID: 1, + HeaderHash: shard1HdrHashProposal, + }, + { + Nonce: 5, + ShardID: 2, + HeaderHash: shard2HdrHashProposal, + }, + }, + // no nonce gap, should not request these hashes + ShardInfo: []block.ShardData{ + { + Nonce: 4, + ShardID: 1, + HeaderHash: []byte("hdrHash1"), + }, + { + Nonce: 4, + ShardID: 2, + HeaderHash: []byte("hdrHash2"), + }, + }, + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{ + { + ShardID: 1, + HeaderHash: shard1HdrHashEpochStart, + }, + { + ShardID: 2, + HeaderHash: shard2HdrHashEpochStart, + }, + }, + }, + } + + expectedShardHeadersRequested := [][]byte{ + shard1HdrHashEpochStart, + shard2HdrHashEpochStart, + shard1HdrHashProposal, + shard2HdrHashProposal, + } + + err := mdr.RequestMissingShardHeadersBlocking(metaHeaderEpochStart, 50*time.Millisecond) + require.Equal(t, process.ErrTimeIsOut, err) + require.False(t, mdr.allHeadersReceived()) + require.False(t, mdr.allProofsReceived()) + + mutRequestedData.Lock() + require.ElementsMatch(t, expectedShardHeadersRequested, requestedShardHeaders) + mutRequestedData.Unlock() + + mutRequestedProofs.Lock() + require.ElementsMatch(t, expectedShardHeadersRequested, requestedShardProofs) + mutRequestedProofs.Unlock() + }) + + t.Run("requesting missing shard headers with nonce gaps", func(t *testing.T) { + t.Parallel() + + headersPoolMock := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, headerNotFoundErr + }, + GetHeaderByNonceAndShardIdCalled: func(hdrNonce uint64, shardId uint32) ([]data.HeaderHandler, [][]byte, error) { + if hdrNonce == 2 && shardId == 2 { + return []data.HeaderHandler{}, [][]byte{}, nil + } + + return nil, nil, headerNotFoundErr + }, + } + + proofsPoolMock := &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return false + }, + GetProofByNonceCalled: func(headerNonce uint64, shardID uint32) (data.HeaderProofHandler, error) { + if headerNonce == 2 && shardID == 2 { + return &processMocks.HeaderProofHandlerStub{}, nil + } + + return nil, headerNotFoundErr + }, + } + + requestedShardProofs := make([][]byte, 0) + mutRequestedProofs := sync.Mutex{} + + requestedShardHeaders := make([][]byte, 0) + mutRequestedData := sync.Mutex{} + + requestProofNonces := make(map[uint32][]uint64) + requestShardHeaderNonces := make(map[uint32][]uint64) + + requestHandlerMock := &testscommon.RequestHandlerStub{ + RequestShardHeaderCalled: func(shardID uint32, hash []byte) { + require.True(t, shardID == 1 || shardID == 2) + + mutRequestedData.Lock() + requestedShardHeaders = append(requestedShardHeaders, hash) + mutRequestedData.Unlock() + }, + RequestEquivalentProofByHashCalled: func(headerShard uint32, headerHash []byte) { + require.True(t, headerShard == 1 || headerShard == 2) + + mutRequestedProofs.Lock() + requestedShardProofs = append(requestedShardProofs, headerHash) + mutRequestedProofs.Unlock() + }, + + RequestShardHeaderByNonceCalled: func(shardID uint32, nonce uint64) { + require.True(t, shardID == 1 || shardID == 2) + + mutRequestedData.Lock() + requestShardHeaderNonces[shardID] = append(requestShardHeaderNonces[shardID], nonce) + mutRequestedData.Unlock() + }, + RequestEquivalentProofByNonceCalled: func(headerShard uint32, headerNonce uint64) { + require.True(t, headerShard == 1 || headerShard == 2) + + mutRequestedProofs.Lock() + requestProofNonces[headerShard] = append(requestProofNonces[headerShard], headerNonce) + mutRequestedProofs.Unlock() + }, + } + + args := ResolverArgs{ + HeadersPool: headersPoolMock, + ProofsPool: proofsPoolMock, + RequestHandler: requestHandlerMock, + BlockDataRequester: commonArgs.BlockDataRequester, + } + mdr, _ := NewMissingDataResolver(args) + + shard1HdrHashFinalized := []byte("hdrHashFinalized1") + shard2HdrHashFinalized := []byte("hdrHashFinalized2") + + shard1HdrHashProposal := []byte("hdrHashProposal1") + shard2HdrHashProposal1 := []byte("hdrHashProposal2") + shard2HdrHashProposal2 := []byte("hdrHashProposal3") + + metaHeaderEpochStart := &block.MetaBlockV3{ + ShardInfoProposal: []block.ShardDataProposal{ + { + Nonce: 5, + ShardID: 1, + HeaderHash: shard1HdrHashProposal, + }, + // two proposals for shard 2, should request gaps from max nonce = 5 + { + Nonce: 5, + ShardID: 2, + HeaderHash: shard2HdrHashProposal1, + }, + { + Nonce: 4, + ShardID: 2, + HeaderHash: shard2HdrHashProposal2, + }, + }, + // nonce gaps per shard: + // - shard1: 4 + // - shard2: 3,4 (nonce 2 will be found in pool) + ShardInfo: []block.ShardData{ + { + Nonce: 3, + ShardID: 1, + HeaderHash: shard1HdrHashFinalized, + }, + { + Nonce: 1, + ShardID: 2, + HeaderHash: shard2HdrHashFinalized, + }, + }, + } + + expectedShardHeadersRequested := [][]byte{ + shard1HdrHashProposal, + shard2HdrHashProposal1, + shard2HdrHashProposal2, + } + + err := mdr.RequestMissingShardHeadersBlocking(metaHeaderEpochStart, 50*time.Millisecond) + require.Equal(t, process.ErrTimeIsOut, err) + require.False(t, mdr.allHeadersReceived()) + require.False(t, mdr.allProofsReceived()) + + mutRequestedData.Lock() + require.ElementsMatch(t, expectedShardHeadersRequested, requestedShardHeaders) + mutRequestedData.Unlock() + + mutRequestedProofs.Lock() + require.ElementsMatch(t, expectedShardHeadersRequested, requestedShardProofs) + mutRequestedProofs.Unlock() + + mutRequestedProofs.Lock() + require.Len(t, requestProofNonces, 2) + require.ElementsMatch(t, requestProofNonces[1], []uint64{4}) + require.ElementsMatch(t, requestProofNonces[2], []uint64{3, 4}) + mutRequestedProofs.Unlock() + + mutRequestedData.Lock() + require.Len(t, requestShardHeaderNonces, 2) + require.ElementsMatch(t, requestShardHeaderNonces[1], []uint64{4}) + require.ElementsMatch(t, requestShardHeaderNonces[2], []uint64{3, 4}) + mutRequestedData.Unlock() + }) + + t.Run("request missing shard headers and proofs, all received", func(t *testing.T) { + t.Parallel() + + requestShardHdrFunc := func(mdr *Resolver) error { + return mdr.RequestMissingShardHeadersBlocking(metaHeader, 200*time.Millisecond) + } + testRequestMissingHeaderAndProofsAllReceived(t, requestShardHdrFunc, shardHeaderHash) + }) +} + +func TestResolver_RequestBlockTransactions(t *testing.T) { + t.Parallel() + + var called bool + proofsPool := &dataRetriever.ProofsPoolMock{} + headersPool := &pool.HeadersPoolStub{} + requestHandler := &testscommon.RequestHandlerStub{} + blockDataRequester := &preprocMocks.BlockDataRequesterStub{ + RequestBlockTransactionsCalled: func(_ *block.Body) { + called = true + }, + } + commonArgs := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + + mdr, _ := NewMissingDataResolver(commonArgs) + body := &block.Body{} + mdr.RequestBlockTransactions(body) + require.True(t, called) +} + +func TestResolver_RequestMiniBlocksAndTransactions(t *testing.T) { + t.Parallel() + var called bool + proofsPool := &dataRetriever.ProofsPoolMock{} + headersPool := &pool.HeadersPoolStub{} + requestHandler := &testscommon.RequestHandlerStub{} + blockDataRequester := &preprocMocks.BlockDataRequesterStub{ + RequestMiniBlocksAndTransactionsCalled: func(_ data.HeaderHandler) { + called = true + }, + } + + commonArgs := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + + mdr, _ := NewMissingDataResolver(commonArgs) + header := &block.HeaderV2{} + mdr.RequestMiniBlocksAndTransactions(header) + require.True(t, called) +} + +func TestResolver_GetFinalCrossMiniBlockInfoAndRequestMissing(t *testing.T) { + t.Parallel() + + proofsPool := &dataRetriever.ProofsPoolMock{} + headersPool := &pool.HeadersPoolStub{} + requestHandler := &testscommon.RequestHandlerStub{} + + expectedValue := []*data.MiniBlockInfo{ + {Hash: []byte("mbHash"), SenderShardID: 1}, + {Hash: []byte("mbHash2"), SenderShardID: 2}, + } + + blockDataRequester := &preprocMocks.BlockDataRequesterStub{ + GetFinalCrossMiniBlockInfoAndRequestMissingCalled: func(_ data.HeaderHandler) []*data.MiniBlockInfo { + return expectedValue + }, + } + commonArgs := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + + mdr, _ := NewMissingDataResolver(commonArgs) + header := &block.HeaderV2{} + finalCrossMiniBlocks := mdr.GetFinalCrossMiniBlockInfoAndRequestMissing(header) + require.Equal(t, expectedValue, finalCrossMiniBlocks) +} + +func TestResolver_Reset(t *testing.T) { + t.Parallel() + + called := false + proofsPool := &dataRetriever.ProofsPoolMock{} + headersPool := &pool.HeadersPoolStub{} + requestHandler := &testscommon.RequestHandlerStub{} + blockDataRequester := &preprocMocks.BlockDataRequesterStub{ + ResetCalled: func() { + called = true + }, + } + commonArgs := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + + mdr, _ := NewMissingDataResolver(commonArgs) + mdr.addMissingHeader([]byte("headerHash")) + mdr.addMissingProof(0, []byte("proofHash")) + require.False(t, mdr.allDataReceived()) + + mdr.Reset() + require.True(t, mdr.allDataReceived()) + require.True(t, called) +} + +func TestResolver_requestNonceGapsIfNeeded(t *testing.T) { + t.Parallel() + + t.Run("proposed nonce less than finalized nonce should not request (underflow prevention)", func(t *testing.T) { + t.Parallel() + + numRequests := 0 + headersPool := &pool.HeadersPoolStub{} + proofsPool := &dataRetriever.ProofsPoolMock{} + requestHandler := &testscommon.RequestHandlerStub{ + RequestShardHeaderByNonceCalled: func(_ uint32, _ uint64) { + numRequests++ + }, + RequestEquivalentProofByNonceCalled: func(_ uint32, _ uint64) { + numRequests++ + }, + } + blockDataRequester := &preprocMocks.BlockDataRequesterStub{} + args := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + mdr, _ := NewMissingDataResolver(args) + + finalizedNonces := map[uint32]uint64{0: 10} + proposedNonces := map[uint32]uint64{0: 5} + mdr.requestNonceGapsIfNeeded(finalizedNonces, proposedNonces) + + require.Equal(t, 0, numRequests) + }) + + t.Run("proposed nonce equal to finalized nonce should not request", func(t *testing.T) { + t.Parallel() + + numRequests := 0 + headersPool := &pool.HeadersPoolStub{} + proofsPool := &dataRetriever.ProofsPoolMock{} + requestHandler := &testscommon.RequestHandlerStub{ + RequestShardHeaderByNonceCalled: func(_ uint32, _ uint64) { + numRequests++ + }, + RequestEquivalentProofByNonceCalled: func(_ uint32, _ uint64) { + numRequests++ + }, + } + blockDataRequester := &preprocMocks.BlockDataRequesterStub{} + args := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + mdr, _ := NewMissingDataResolver(args) + + finalizedNonces := map[uint32]uint64{0: 10} + proposedNonces := map[uint32]uint64{0: 10} + mdr.requestNonceGapsIfNeeded(finalizedNonces, proposedNonces) + + require.Equal(t, 0, numRequests) + }) + + t.Run("proposed nonce is finalized+1, gap of 1 should not request", func(t *testing.T) { + t.Parallel() + + numRequests := 0 + headersPool := &pool.HeadersPoolStub{} + proofsPool := &dataRetriever.ProofsPoolMock{} + requestHandler := &testscommon.RequestHandlerStub{ + RequestShardHeaderByNonceCalled: func(_ uint32, _ uint64) { + numRequests++ + }, + RequestEquivalentProofByNonceCalled: func(_ uint32, _ uint64) { + numRequests++ + }, + } + blockDataRequester := &preprocMocks.BlockDataRequesterStub{} + args := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + mdr, _ := NewMissingDataResolver(args) + + finalizedNonces := map[uint32]uint64{0: 10} + proposedNonces := map[uint32]uint64{0: 11} + mdr.requestNonceGapsIfNeeded(finalizedNonces, proposedNonces) + + require.Equal(t, 0, numRequests) + }) + + t.Run("MaxUint64 finalized with MaxUint64-1 proposed should not request (underflow scenario)", func(t *testing.T) { + t.Parallel() + + numRequests := 0 + headersPool := &pool.HeadersPoolStub{} + proofsPool := &dataRetriever.ProofsPoolMock{} + requestHandler := &testscommon.RequestHandlerStub{ + RequestShardHeaderByNonceCalled: func(_ uint32, _ uint64) { + numRequests++ + }, + RequestEquivalentProofByNonceCalled: func(_ uint32, _ uint64) { + numRequests++ + }, + } + blockDataRequester := &preprocMocks.BlockDataRequesterStub{} + args := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + mdr, _ := NewMissingDataResolver(args) + + finalizedNonces := map[uint32]uint64{0: math.MaxUint64} + proposedNonces := map[uint32]uint64{0: math.MaxUint64 - 1} + mdr.requestNonceGapsIfNeeded(finalizedNonces, proposedNonces) + + require.Equal(t, 0, numRequests) + }) + + t.Run("normal gap of 2 should request exactly 1 nonce", func(t *testing.T) { + t.Parallel() + + requestedNonces := make([]uint64, 0) + mut := sync.Mutex{} + headerNotFoundErr := errors.New("not found") + headersPool := &pool.HeadersPoolStub{ + GetHeaderByNonceAndShardIdCalled: func(_ uint64, _ uint32) ([]data.HeaderHandler, [][]byte, error) { + return nil, nil, headerNotFoundErr + }, + } + proofsPool := &dataRetriever.ProofsPoolMock{ + GetProofByNonceCalled: func(_ uint64, _ uint32) (data.HeaderProofHandler, error) { + return nil, headerNotFoundErr + }, + } + requestHandler := &testscommon.RequestHandlerStub{ + RequestShardHeaderByNonceCalled: func(shardID uint32, nonce uint64) { + mut.Lock() + requestedNonces = append(requestedNonces, nonce) + mut.Unlock() + }, + RequestEquivalentProofByNonceCalled: func(_ uint32, _ uint64) {}, + } + blockDataRequester := &preprocMocks.BlockDataRequesterStub{} + args := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + mdr, _ := NewMissingDataResolver(args) + + // finalized=5, proposed=7: gap=2, should request nonce 6 + finalizedNonces := map[uint32]uint64{0: 5} + proposedNonces := map[uint32]uint64{0: 7} + mdr.requestNonceGapsIfNeeded(finalizedNonces, proposedNonces) + + // wait for goroutines spawned by requestShardHeaderByNonceIfNeeded + time.Sleep(50 * time.Millisecond) + + mut.Lock() + require.Equal(t, []uint64{6}, requestedNonces) + mut.Unlock() + }) + + t.Run("shard not found in finalized nonces should not request", func(t *testing.T) { + t.Parallel() + + numRequests := 0 + headersPool := &pool.HeadersPoolStub{} + proofsPool := &dataRetriever.ProofsPoolMock{} + requestHandler := &testscommon.RequestHandlerStub{ + RequestShardHeaderByNonceCalled: func(_ uint32, _ uint64) { + numRequests++ + }, + RequestEquivalentProofByNonceCalled: func(_ uint32, _ uint64) { + numRequests++ + }, + } + blockDataRequester := &preprocMocks.BlockDataRequesterStub{} + args := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + mdr, _ := NewMissingDataResolver(args) + + // proposed has shard 0, finalized has shard 1 - no match + finalizedNonces := map[uint32]uint64{1: 5} + proposedNonces := map[uint32]uint64{0: 10} + mdr.requestNonceGapsIfNeeded(finalizedNonces, proposedNonces) + + require.Equal(t, 0, numRequests) + }) + + t.Run("finalized nonce 0 proposed nonce 0 should not request", func(t *testing.T) { + t.Parallel() + + numRequests := 0 + headersPool := &pool.HeadersPoolStub{} + proofsPool := &dataRetriever.ProofsPoolMock{} + requestHandler := &testscommon.RequestHandlerStub{ + RequestShardHeaderByNonceCalled: func(_ uint32, _ uint64) { + numRequests++ + }, + RequestEquivalentProofByNonceCalled: func(_ uint32, _ uint64) { + numRequests++ + }, + } + blockDataRequester := &preprocMocks.BlockDataRequesterStub{} + args := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + mdr, _ := NewMissingDataResolver(args) + + finalizedNonces := map[uint32]uint64{0: 0} + proposedNonces := map[uint32]uint64{0: 0} + mdr.requestNonceGapsIfNeeded(finalizedNonces, proposedNonces) + + require.Equal(t, 0, numRequests) + }) + + t.Run("multiple shards with mixed valid and invalid gaps", func(t *testing.T) { + t.Parallel() + + requestedShardNonces := make(map[uint32][]uint64) + mut := sync.Mutex{} + headerNotFoundErr := errors.New("not found") + headersPool := &pool.HeadersPoolStub{ + GetHeaderByNonceAndShardIdCalled: func(_ uint64, _ uint32) ([]data.HeaderHandler, [][]byte, error) { + return nil, nil, headerNotFoundErr + }, + } + proofsPool := &dataRetriever.ProofsPoolMock{ + GetProofByNonceCalled: func(_ uint64, _ uint32) (data.HeaderProofHandler, error) { + return nil, headerNotFoundErr + }, + } + requestHandler := &testscommon.RequestHandlerStub{ + RequestShardHeaderByNonceCalled: func(shardID uint32, nonce uint64) { + mut.Lock() + requestedShardNonces[shardID] = append(requestedShardNonces[shardID], nonce) + mut.Unlock() + }, + RequestEquivalentProofByNonceCalled: func(_ uint32, _ uint64) {}, + } + blockDataRequester := &preprocMocks.BlockDataRequesterStub{} + args := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + mdr, _ := NewMissingDataResolver(args) + + finalizedNonces := map[uint32]uint64{ + 0: 10, // shard 0: valid gap of 3 + 1: 20, // shard 1: proposed < finalized (invalid) + 2: math.MaxUint64, // shard 2: underflow scenario (invalid) + } + proposedNonces := map[uint32]uint64{ + 0: 13, // gap=3, valid + 1: 5, // proposed < finalized, should skip + 2: math.MaxUint64 - 1, // underflow, should skip + } + mdr.requestNonceGapsIfNeeded(finalizedNonces, proposedNonces) + + // wait for goroutines spawned by requestShardHeaderByNonceIfNeeded + time.Sleep(50 * time.Millisecond) + + mut.Lock() + // only shard 0 should have requests (nonces 11, 12) + require.Len(t, requestedShardNonces, 1) + require.ElementsMatch(t, []uint64{11, 12}, requestedShardNonces[0]) + mut.Unlock() + }) +} + +func TestResolver_RequestMissingShardHeaders_NonceGapProtection(t *testing.T) { + t.Parallel() + + headerNotFoundErr := errors.New("header not found") + + t.Run("proposed nonce less than finalized should not trigger nonce gap requests", func(t *testing.T) { + t.Parallel() + + nonceRequestCount := 0 + mut := sync.Mutex{} + + headersPool := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(_ []byte) (data.HeaderHandler, error) { + return nil, headerNotFoundErr + }, + GetHeaderByNonceAndShardIdCalled: func(_ uint64, _ uint32) ([]data.HeaderHandler, [][]byte, error) { + return nil, nil, headerNotFoundErr + }, + } + proofsPool := &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(_ uint32, _ []byte) bool { return false }, + GetProofByNonceCalled: func(_ uint64, _ uint32) (data.HeaderProofHandler, error) { + return nil, headerNotFoundErr + }, + } + requestHandler := &testscommon.RequestHandlerStub{ + RequestShardHeaderCalled: func(_ uint32, _ []byte) {}, + RequestEquivalentProofByHashCalled: func(_ uint32, _ []byte) {}, + RequestShardHeaderByNonceCalled: func(_ uint32, _ uint64) { + mut.Lock() + nonceRequestCount++ + mut.Unlock() + }, + RequestEquivalentProofByNonceCalled: func(_ uint32, _ uint64) { + mut.Lock() + nonceRequestCount++ + mut.Unlock() + }, + } + blockDataRequester := &preprocMocks.BlockDataRequesterStub{} + args := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + mdr, _ := NewMissingDataResolver(args) + + // byzantine node: proposed nonce 5, finalized nonce 100 + metaHeader := &block.MetaBlockV3{ + ShardInfoProposal: []block.ShardDataProposal{ + {Nonce: 5, ShardID: 1, HeaderHash: []byte("hash1")}, + }, + ShardInfo: []block.ShardData{ + {Nonce: 100, ShardID: 1, HeaderHash: []byte("hash2")}, + }, + } + + err := mdr.RequestMissingShardHeaders(metaHeader) + require.Nil(t, err) + + // wait briefly for any goroutines + time.Sleep(50 * time.Millisecond) + + mut.Lock() + require.Equal(t, 0, nonceRequestCount) + mut.Unlock() + }) + + t.Run("MaxUint64 finalized nonce with small proposed should not trigger nonce gap requests", func(t *testing.T) { + t.Parallel() + + nonceRequestCount := 0 + mut := sync.Mutex{} + + headersPool := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(_ []byte) (data.HeaderHandler, error) { + return nil, headerNotFoundErr + }, + GetHeaderByNonceAndShardIdCalled: func(_ uint64, _ uint32) ([]data.HeaderHandler, [][]byte, error) { + return nil, nil, headerNotFoundErr + }, + } + proofsPool := &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(_ uint32, _ []byte) bool { return false }, + GetProofByNonceCalled: func(_ uint64, _ uint32) (data.HeaderProofHandler, error) { + return nil, headerNotFoundErr + }, + } + requestHandler := &testscommon.RequestHandlerStub{ + RequestShardHeaderCalled: func(_ uint32, _ []byte) {}, + RequestEquivalentProofByHashCalled: func(_ uint32, _ []byte) {}, + RequestShardHeaderByNonceCalled: func(_ uint32, _ uint64) { + mut.Lock() + nonceRequestCount++ + mut.Unlock() + }, + RequestEquivalentProofByNonceCalled: func(_ uint32, _ uint64) { + mut.Lock() + nonceRequestCount++ + mut.Unlock() + }, + } + blockDataRequester := &preprocMocks.BlockDataRequesterStub{} + args := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + mdr, _ := NewMissingDataResolver(args) + + // byzantine meta header: MaxUint64 finalized, small proposed - would cause startNonce wrap to 0 + metaHeader := &block.MetaBlockV3{ + ShardInfoProposal: []block.ShardDataProposal{ + {Nonce: 1000, ShardID: 1, HeaderHash: []byte("hash1")}, + }, + ShardInfo: []block.ShardData{ + {Nonce: math.MaxUint64, ShardID: 1, HeaderHash: []byte("hash2")}, + }, + } + + err := mdr.RequestMissingShardHeaders(metaHeader) + require.Nil(t, err) + + time.Sleep(50 * time.Millisecond) + + mut.Lock() + require.Equal(t, 0, nonceRequestCount) + mut.Unlock() + }) + + t.Run("valid gap within bounds should trigger correct nonce gap requests", func(t *testing.T) { + t.Parallel() + + requestedNonces := make(map[uint32][]uint64) + mut := sync.Mutex{} + + headersPool := &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(_ []byte) (data.HeaderHandler, error) { + return nil, headerNotFoundErr + }, + GetHeaderByNonceAndShardIdCalled: func(_ uint64, _ uint32) ([]data.HeaderHandler, [][]byte, error) { + return nil, nil, headerNotFoundErr + }, + } + proofsPool := &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(_ uint32, _ []byte) bool { return false }, + GetProofByNonceCalled: func(_ uint64, _ uint32) (data.HeaderProofHandler, error) { + return nil, headerNotFoundErr + }, + } + requestHandler := &testscommon.RequestHandlerStub{ + RequestShardHeaderCalled: func(_ uint32, _ []byte) {}, + RequestEquivalentProofByHashCalled: func(_ uint32, _ []byte) {}, + RequestShardHeaderByNonceCalled: func(shardID uint32, nonce uint64) { + mut.Lock() + requestedNonces[shardID] = append(requestedNonces[shardID], nonce) + mut.Unlock() + }, + RequestEquivalentProofByNonceCalled: func(_ uint32, _ uint64) {}, + } + blockDataRequester := &preprocMocks.BlockDataRequesterStub{} + args := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + mdr, _ := NewMissingDataResolver(args) + + // finalized=10, proposed=15: gap=5, should request nonces 11,12,13,14 + metaHeader := &block.MetaBlockV3{ + ShardInfoProposal: []block.ShardDataProposal{ + {Nonce: 15, ShardID: 1, HeaderHash: []byte("hash1")}, + }, + ShardInfo: []block.ShardData{ + {Nonce: 10, ShardID: 1, HeaderHash: []byte("hash2")}, + }, + } + + err := mdr.RequestMissingShardHeaders(metaHeader) + require.Nil(t, err) + + time.Sleep(50 * time.Millisecond) + + mut.Lock() + require.ElementsMatch(t, []uint64{11, 12, 13, 14}, requestedNonces[1]) + mut.Unlock() + }) +} + +func TestResolver_IsInterfaceNil(t *testing.T) { + t.Parallel() + + proofsPool := &dataRetriever.ProofsPoolMock{} + headersPool := &pool.HeadersPoolStub{} + requestHandler := &testscommon.RequestHandlerStub{} + blockDataRequester := &preprocMocks.BlockDataRequesterStub{} + commonArgs := ResolverArgs{ + HeadersPool: headersPool, + ProofsPool: proofsPool, + RequestHandler: requestHandler, + BlockDataRequester: blockDataRequester, + } + t.Run("nil receiver should be nil", func(t *testing.T) { + t.Parallel() + + var mdr *Resolver + require.True(t, check.IfNil(mdr)) + }) + + t.Run("non-nil receiver should not be nil", func(t *testing.T) { + t.Parallel() + + mdr, _ := NewMissingDataResolver(commonArgs) + require.False(t, check.IfNil(mdr)) + }) +} diff --git a/process/mock/blockTrackerMock.go b/process/mock/blockTrackerMock.go index 8ff1a93eca6..d7730ff84ee 100644 --- a/process/mock/blockTrackerMock.go +++ b/process/mock/blockTrackerMock.go @@ -9,6 +9,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/track" "github.com/multiversx/mx-chain-go/sharding" @@ -25,8 +26,10 @@ type BlockTrackerMock struct { AddCrossNotarizedHeaderCalled func(shardID uint32, crossNotarizedHeader data.HeaderHandler, crossNotarizedHeaderHash []byte) AddSelfNotarizedHeaderCalled func(shardID uint32, selfNotarizedHeader data.HeaderHandler, selfNotarizedHeaderHash []byte) CheckBlockAgainstFinalCalled func(headerHandler data.HeaderHandler) error + CheckProofAgainstFinalCalled func(proof data.HeaderProofHandler) error CheckBlockAgainstRoundHandlerCalled func(headerHandler data.HeaderHandler) error - CheckBlockAgainstWhitelistCalled func(interceptedData process.InterceptedData) bool + CheckProofAgainstRoundHandlerCalled func(proof data.HeaderProofHandler) error + CheckAgainstWhitelistCalled func(interceptedData process.InterceptedData) bool CleanupHeadersBehindNonceCalled func(shardID uint32, selfNotarizedNonce uint64, crossNotarizedNonce uint64) ComputeLongestChainCalled func(shardID uint32, header data.HeaderHandler) ([]data.HeaderHandler, [][]byte) ComputeLongestMetaChainFromLastNotarizedCalled func() ([]data.HeaderHandler, [][]byte, error) @@ -42,6 +45,7 @@ type BlockTrackerMock struct { GetTrackedHeadersWithNonceCalled func(shardID uint32, nonce uint64) ([]data.HeaderHandler, [][]byte) ShouldSkipMiniBlocksCreationFromSelfCalled func() bool IsShardStuckCalled func(shardId uint32) bool + IsOwnShardStuckCalled func() bool RegisterCrossNotarizedHeadersHandlerCalled func(handler func(shardID uint32, headers []data.HeaderHandler, headersHashes [][]byte)) RegisterSelfNotarizedFromCrossHeadersHandlerCalled func(handler func(shardID uint32, headers []data.HeaderHandler, headersHashes [][]byte)) RegisterSelfNotarizedHeadersHandlerCalled func(handler func(shardID uint32, headers []data.HeaderHandler, headersHashes [][]byte)) @@ -49,6 +53,7 @@ type BlockTrackerMock struct { RemoveLastNotarizedHeadersCalled func() RestoreToGenesisCalled func() ShouldAddHeaderCalled func(headerHandler data.HeaderHandler) bool + ComputeOwnShardStuckCalled func(lastExecutionResultsInfo data.BaseExecutionResultHandler, currentNonce uint64) shardCoordinator sharding.Coordinator @@ -181,10 +186,28 @@ func (btm *BlockTrackerMock) CheckBlockAgainstFinal(headerHandler data.HeaderHan return nil } -// CheckBlockAgainstWhitelist - -func (btm *BlockTrackerMock) CheckBlockAgainstWhitelist(interceptedData process.InterceptedData) bool { - if btm.CheckBlockAgainstWhitelistCalled != nil { - return btm.CheckBlockAgainstWhitelistCalled(interceptedData) +// CheckProofAgainstFinal - +func (btm *BlockTrackerMock) CheckProofAgainstFinal(proof data.HeaderProofHandler) error { + if btm.CheckProofAgainstFinalCalled != nil { + return btm.CheckProofAgainstFinalCalled(proof) + } + + return nil +} + +// CheckProofAgainstRoundHandler - +func (btm *BlockTrackerMock) CheckProofAgainstRoundHandler(proof data.HeaderProofHandler) error { + if btm.CheckProofAgainstRoundHandlerCalled != nil { + return btm.CheckProofAgainstRoundHandlerCalled(proof) + } + + return nil +} + +// CheckAgainstWhitelist - +func (btm *BlockTrackerMock) CheckAgainstWhitelist(interceptedData process.InterceptedData) bool { + if btm.CheckAgainstWhitelistCalled != nil { + return btm.CheckAgainstWhitelistCalled(interceptedData) } return false @@ -226,6 +249,10 @@ func (btm *BlockTrackerMock) ComputeLongestChain(shardID uint32, header data.Hea // ComputeLongestMetaChainFromLastNotarized - func (btm *BlockTrackerMock) ComputeLongestMetaChainFromLastNotarized() ([]data.HeaderHandler, [][]byte, error) { + if btm.ComputeLongestMetaChainFromLastNotarizedCalled != nil { + return btm.ComputeLongestMetaChainFromLastNotarizedCalled() + } + lastCrossNotarizedHeader, _, err := btm.GetLastCrossNotarizedHeader(core.MetachainShardId) if err != nil { return nil, nil, err @@ -238,6 +265,10 @@ func (btm *BlockTrackerMock) ComputeLongestMetaChainFromLastNotarized() ([]data. // ComputeLongestShardsChainsFromLastNotarized - func (btm *BlockTrackerMock) ComputeLongestShardsChainsFromLastNotarized() ([]data.HeaderHandler, [][]byte, map[uint32][]data.HeaderHandler, error) { + if btm.ComputeLongestShardsChainsFromLastNotarizedCalled != nil { + return btm.ComputeLongestShardsChainsFromLastNotarizedCalled() + } + hdrsMap := make(map[uint32][]data.HeaderHandler) hdrsHashesMap := make(map[uint32][][]byte) @@ -454,6 +485,15 @@ func (btm *BlockTrackerMock) IsShardStuck(shardId uint32) bool { return false } +// IsOwnShardStuck - +func (btm *BlockTrackerMock) IsOwnShardStuck() bool { + if btm.IsOwnShardStuckCalled != nil { + return btm.IsOwnShardStuckCalled() + } + + return false +} + // ShouldSkipMiniBlocksCreationFromSelf - func (btm *BlockTrackerMock) ShouldSkipMiniBlocksCreationFromSelf() bool { if btm.ShouldSkipMiniBlocksCreationFromSelfCalled != nil { @@ -514,6 +554,13 @@ func (btm *BlockTrackerMock) ShouldAddHeader(headerHandler data.HeaderHandler) b return true } +// ComputeOwnShardStuck - +func (btm *BlockTrackerMock) ComputeOwnShardStuck(lastExecutionResultsInfo data.BaseExecutionResultHandler, currentNonce uint64) { + if btm.ComputeOwnShardStuckCalled != nil { + btm.ComputeOwnShardStuckCalled(lastExecutionResultsInfo, currentNonce) + } +} + // IsInterfaceNil - func (btm *BlockTrackerMock) IsInterfaceNil() bool { return btm == nil diff --git a/process/mock/chunkProcessorStub.go b/process/mock/chunkProcessorStub.go index 9682fe20302..89d90935366 100644 --- a/process/mock/chunkProcessorStub.go +++ b/process/mock/chunkProcessorStub.go @@ -2,24 +2,33 @@ package mock import ( "github.com/multiversx/mx-chain-core-go/data/batch" + "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process" ) // ChunkProcessorStub - type ChunkProcessorStub struct { - CheckBatchCalled func(b *batch.Batch, w process.WhiteListHandler) (process.CheckedChunkResult, error) - CloseCalled func() error + CheckBatchCalled func(b *batch.Batch, w process.WhiteListHandler, broadcastMethod p2p.BroadcastMethod) (process.CheckedChunkResult, error) + MarkVerifiedCalled func(b *batch.Batch, broadcastMethod p2p.BroadcastMethod) + CloseCalled func() error } // CheckBatch - -func (c *ChunkProcessorStub) CheckBatch(b *batch.Batch, w process.WhiteListHandler) (process.CheckedChunkResult, error) { +func (c *ChunkProcessorStub) CheckBatch(b *batch.Batch, w process.WhiteListHandler, broadcastMethod p2p.BroadcastMethod) (process.CheckedChunkResult, error) { if c.CheckBatchCalled != nil { - return c.CheckBatchCalled(b, w) + return c.CheckBatchCalled(b, w, broadcastMethod) } return process.CheckedChunkResult{}, nil } +// MarkVerified - +func (c *ChunkProcessorStub) MarkVerified(b *batch.Batch, broadcastMethod p2p.BroadcastMethod) { + if c.MarkVerifiedCalled != nil { + c.MarkVerifiedCalled(b, broadcastMethod) + } +} + // Close - func (c *ChunkProcessorStub) Close() error { if c.CloseCalled != nil { diff --git a/process/mock/coreComponentsMock.go b/process/mock/coreComponentsMock.go index 6f6d147d8f3..c7526f677ae 100644 --- a/process/mock/coreComponentsMock.go +++ b/process/mock/coreComponentsMock.go @@ -1,12 +1,15 @@ package mock import ( + "sync/atomic" + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data/endProcess" "github.com/multiversx/mx-chain-core-go/data/typeConverters" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/ntp" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/consensus" @@ -33,7 +36,7 @@ type CoreComponentsMock struct { EpochNotifierField process.EpochNotifier EnableEpochsHandlerField common.EnableEpochsHandler RoundNotifierField process.RoundNotifier - EnableRoundsHandlerField process.EnableRoundsHandler + EnableRoundsHandlerField common.EnableRoundsHandler RoundField consensus.RoundHandler StatusField core.AppStatusHandler ChanStopNode chan endProcess.ArgEndProcess @@ -45,6 +48,20 @@ type CoreComponentsMock struct { ChainParametersSubscriberField process.ChainParametersSubscriber FieldsSizeCheckerField common.FieldsSizeChecker EpochChangeGracePeriodHandlerField common.EpochChangeGracePeriodHandler + ProcessConfigsHandlerField common.ProcessConfigsHandler + CommonConfigsHandlerField common.CommonConfigsHandler + SyncTimerField ntp.SyncTimer + AntifloodConfigsHandlerField common.AntifloodConfigsHandler + ClosingNodeStartedField *atomic.Bool +} + +// SyncTimer - +func (ccm *CoreComponentsMock) SyncTimer() ntp.SyncTimer { + if ccm.SyncTimerField != nil { + return ccm.SyncTimerField + } + + return nil } // ChanStopNodeProcess - @@ -151,8 +168,8 @@ func (ccm *CoreComponentsMock) RoundNotifier() process.RoundNotifier { return ccm.RoundNotifierField } -// EnableEpochsHandler - -func (ccm *CoreComponentsMock) EnableRoundsHandler() process.EnableRoundsHandler { +// EnableRoundsHandler - +func (ccm *CoreComponentsMock) EnableRoundsHandler() common.EnableRoundsHandler { return ccm.EnableRoundsHandlerField } @@ -200,6 +217,26 @@ func (ccm *CoreComponentsMock) EpochChangeGracePeriodHandler() common.EpochChang return ccm.EpochChangeGracePeriodHandlerField } +// ProcessConfigsHandler - +func (ccm *CoreComponentsMock) ProcessConfigsHandler() common.ProcessConfigsHandler { + return ccm.ProcessConfigsHandlerField +} + +// CommonConfigsHandler - +func (ccm *CoreComponentsMock) CommonConfigsHandler() common.CommonConfigsHandler { + return ccm.CommonConfigsHandlerField +} + +// AntifloodConfigsHandler - +func (ccm *CoreComponentsMock) AntifloodConfigsHandler() common.AntifloodConfigsHandler { + return ccm.AntifloodConfigsHandlerField +} + +// ClosingNodeStarted - +func (ccm *CoreComponentsMock) ClosingNodeStarted() *atomic.Bool { + return ccm.ClosingNodeStartedField +} + // IsInterfaceNil - func (ccm *CoreComponentsMock) IsInterfaceNil() bool { return ccm == nil diff --git a/process/mock/cryptoComponentsMock.go b/process/mock/cryptoComponentsMock.go index 4b02784b69b..cca3f3950ac 100644 --- a/process/mock/cryptoComponentsMock.go +++ b/process/mock/cryptoComponentsMock.go @@ -4,7 +4,7 @@ import ( "errors" "sync" - "github.com/multiversx/mx-chain-crypto-go" + crypto "github.com/multiversx/mx-chain-crypto-go" "github.com/multiversx/mx-chain-go/common" cryptoCommon "github.com/multiversx/mx-chain-go/common/crypto" ) @@ -34,7 +34,7 @@ func (ccm *CryptoComponentsMock) TxSingleSigner() crypto.SingleSigner { } // GetMultiSigner - -func (ccm *CryptoComponentsMock) GetMultiSigner(epoch uint32) (crypto.MultiSigner, error) { +func (ccm *CryptoComponentsMock) GetMultiSigner(epoch uint32) (crypto.MultiSignerV2, error) { ccm.mutMultiSig.RLock() defer ccm.mutMultiSig.RUnlock() diff --git a/process/mock/endOfEpochTriggerStub.go b/process/mock/endOfEpochTriggerStub.go index ea9b6323f64..8f5810ea711 100644 --- a/process/mock/endOfEpochTriggerStub.go +++ b/process/mock/endOfEpochTriggerStub.go @@ -15,11 +15,37 @@ type EpochStartTriggerStub struct { ReceivedHeaderCalled func(handler data.HeaderHandler) UpdateCalled func(round uint64, nonce uint64) ProcessedCalled func(header data.HeaderHandler) + SetProcessedCalled func(header data.HeaderHandler, body data.BodyHandler) EpochStartRoundCalled func() uint64 LastCommitedEpochStartHdrCalled func() (data.HeaderHandler, error) GetEpochStartHdrFromStorageCalled func(epoch uint32) (data.HeaderHandler, error) EpochFinalityAttestingRoundCalled func() uint64 EpochStartMetaHdrHashCalled func() []byte + ShouldProposeEpochChangeCalled func(round uint64, nonce uint64) bool + SetEpochChangeCalled func(round uint64) + SetEpochChangeProposedCalled func(value bool) + GetEpochChangeProposedCalled func() bool +} + +func (e *EpochStartTriggerStub) SetEpochChangeProposed(value bool) { + if e.SetEpochChangeProposedCalled != nil { + e.SetEpochChangeProposedCalled(value) + } +} + +// GetEpochChangeProposed - +func (e *EpochStartTriggerStub) GetEpochChangeProposed() bool { + if e.GetEpochChangeProposedCalled != nil { + return e.GetEpochChangeProposedCalled() + } + return false +} + +// SetEpochChange - +func (e *EpochStartTriggerStub) SetEpochChange(round uint64) { + if e.SetEpochChangeCalled != nil { + e.SetEpochChangeCalled(round) + } } // RevertStateToBlock - @@ -110,9 +136,9 @@ func (e *EpochStartTriggerStub) Update(round uint64, nonce uint64) { } // SetProcessed - -func (e *EpochStartTriggerStub) SetProcessed(header data.HeaderHandler, _ data.BodyHandler) { - if e.ProcessedCalled != nil { - e.ProcessedCalled(header) +func (e *EpochStartTriggerStub) SetProcessed(header data.HeaderHandler, body data.BodyHandler) { + if e.SetProcessedCalled != nil { + e.SetProcessedCalled(header, body) } } @@ -148,6 +174,14 @@ func (e *EpochStartTriggerStub) MetaEpoch() uint32 { return 0 } +// ShouldProposeEpochChange - +func (e *EpochStartTriggerStub) ShouldProposeEpochChange(round uint64, nonce uint64) bool { + if e.ShouldProposeEpochChangeCalled != nil { + return e.ShouldProposeEpochChangeCalled(round, nonce) + } + return false +} + // Close - func (e *EpochStartTriggerStub) Close() error { return nil diff --git a/process/mock/epochEconomicsStub.go b/process/mock/epochEconomicsStub.go index 7a65f7c3fcf..473d56a9c35 100644 --- a/process/mock/epochEconomicsStub.go +++ b/process/mock/epochEconomicsStub.go @@ -3,19 +3,25 @@ package mock import ( "math/big" + "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" ) // EpochEconomicsStub - type EpochEconomicsStub struct { - ComputeEndOfEpochEconomicsCalled func(metaBlock *block.MetaBlock) (*block.Economics, error) - VerifyRewardsPerBlockCalled func( - metaBlock *block.MetaBlock, correctedProtocolSustainability *big.Int, computedEconomics *block.Economics, + ComputeEndOfEpochEconomicsCalled func(metaBlock data.MetaHeaderHandler) (*block.Economics, error) + ComputeEndOfEpochEconomicsV3Called func( + metaBlock data.MetaHeaderHandler, + prevBlockExecutionResults data.BaseMetaExecutionResultHandler, + epochStartHandler data.EpochStartHandler, + ) (*block.Economics, error) + VerifyRewardsPerBlockCalled func( + metaBlock data.MetaHeaderHandler, correctedProtocolSustainability *big.Int, computedEconomics data.EconomicsHandler, ) error } // ComputeEndOfEpochEconomics - -func (e *EpochEconomicsStub) ComputeEndOfEpochEconomics(metaBlock *block.MetaBlock) (*block.Economics, error) { +func (e *EpochEconomicsStub) ComputeEndOfEpochEconomics(metaBlock data.MetaHeaderHandler) (*block.Economics, error) { if e.ComputeEndOfEpochEconomicsCalled != nil { return e.ComputeEndOfEpochEconomicsCalled(metaBlock) } @@ -24,11 +30,23 @@ func (e *EpochEconomicsStub) ComputeEndOfEpochEconomics(metaBlock *block.MetaBlo }, nil } +// ComputeEndOfEpochEconomicsV3 - +func (e *EpochEconomicsStub) ComputeEndOfEpochEconomicsV3( + metaBlock data.MetaHeaderHandler, + prevBlockExecutionResults data.BaseMetaExecutionResultHandler, + epochStartHandler data.EpochStartHandler, +) (*block.Economics, error) { + if e.ComputeEndOfEpochEconomicsV3Called != nil { + return e.ComputeEndOfEpochEconomicsV3Called(metaBlock, prevBlockExecutionResults, epochStartHandler) + } + return &block.Economics{}, nil +} + // VerifyRewardsPerBlock - func (e *EpochEconomicsStub) VerifyRewardsPerBlock( - metaBlock *block.MetaBlock, + metaBlock data.MetaHeaderHandler, correctedProtocolSustainability *big.Int, - computedEconomics *block.Economics, + computedEconomics data.EconomicsHandler, ) error { if e.VerifyRewardsPerBlockCalled != nil { return e.VerifyRewardsPerBlockCalled(metaBlock, correctedProtocolSustainability, computedEconomics) diff --git a/process/mock/epochStartDataCreatorStub.go b/process/mock/epochStartDataCreatorStub.go index dd38c5a1198..4c807a681ea 100644 --- a/process/mock/epochStartDataCreatorStub.go +++ b/process/mock/epochStartDataCreatorStub.go @@ -1,14 +1,17 @@ package mock import ( - "github.com/multiversx/mx-chain-core-go/data/block" "math/big" + + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" ) // EpochStartDataCreatorStub - type EpochStartDataCreatorStub struct { - CreateEpochStartDataCalled func() (*block.EpochStart, error) - VerifyEpochStartDataForMetablockCalled func(metaBlock *block.MetaBlock) error + CreateEpochStartDataCalled func() (*block.EpochStart, error) + CreateEpochStartShardDataMetablockV3Called func(metablock data.MetaHeaderHandler) ([]block.EpochStartShardData, error) + VerifyEpochStartDataForMetablockCalled func(metaBlock data.MetaHeaderHandler) error } // CreateEpochStartData - @@ -23,8 +26,16 @@ func (e *EpochStartDataCreatorStub) CreateEpochStartData() (*block.EpochStart, e }, nil } +// CreateEpochStartShardDataMetablockV3 - +func (e *EpochStartDataCreatorStub) CreateEpochStartShardDataMetablockV3(metablock data.MetaHeaderHandler) ([]block.EpochStartShardData, error) { + if e.CreateEpochStartShardDataMetablockV3Called != nil { + return e.CreateEpochStartShardDataMetablockV3Called(metablock) + } + return []block.EpochStartShardData{{}}, nil +} + // VerifyEpochStartDataForMetablock - -func (e *EpochStartDataCreatorStub) VerifyEpochStartDataForMetablock(metaBlock *block.MetaBlock) error { +func (e *EpochStartDataCreatorStub) VerifyEpochStartDataForMetablock(metaBlock data.MetaHeaderHandler) error { if e.VerifyEpochStartDataForMetablockCalled != nil { return e.VerifyEpochStartDataForMetablockCalled(metaBlock) } diff --git a/process/mock/finalityAttesterStub.go b/process/mock/finalityAttesterStub.go index 87a578cf641..dae632d68b5 100644 --- a/process/mock/finalityAttesterStub.go +++ b/process/mock/finalityAttesterStub.go @@ -2,6 +2,7 @@ package mock import ( "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/process" ) @@ -9,7 +10,9 @@ import ( type ValidityAttesterStub struct { CheckBlockAgainstRoundHandlerCalled func(headerHandler data.HeaderHandler) error CheckBlockAgainstFinalCalled func(headerHandler data.HeaderHandler) error - CheckBlockAgainstWhitelistCalled func(interceptedData process.InterceptedData) bool + CheckAgainstWhitelistCalled func(interceptedData process.InterceptedData) bool + CheckProofAgainstFinalCalled func(proof data.HeaderProofHandler) error + CheckProofAgainstRoundHandlerCalled func(proof data.HeaderProofHandler) error } // CheckBlockAgainstRoundHandler - @@ -30,15 +33,33 @@ func (vas *ValidityAttesterStub) CheckBlockAgainstFinal(headerHandler data.Heade return nil } -// CheckBlockAgainstWhitelist - -func (vas *ValidityAttesterStub) CheckBlockAgainstWhitelist(interceptedData process.InterceptedData) bool { - if vas.CheckBlockAgainstWhitelistCalled != nil { - return vas.CheckBlockAgainstWhitelistCalled(interceptedData) +// CheckAgainstWhitelist - +func (vas *ValidityAttesterStub) CheckAgainstWhitelist(interceptedData process.InterceptedData) bool { + if vas.CheckAgainstWhitelistCalled != nil { + return vas.CheckAgainstWhitelistCalled(interceptedData) } return false } +// CheckProofAgainstFinal - +func (vas *ValidityAttesterStub) CheckProofAgainstFinal(proof data.HeaderProofHandler) error { + if vas.CheckProofAgainstFinalCalled != nil { + return vas.CheckProofAgainstFinalCalled(proof) + } + + return nil +} + +// CheckProofAgainstRoundHandler - +func (vas *ValidityAttesterStub) CheckProofAgainstRoundHandler(proof data.HeaderProofHandler) error { + if vas.CheckProofAgainstRoundHandlerCalled != nil { + return vas.CheckProofAgainstRoundHandlerCalled(proof) + } + + return nil +} + // IsInterfaceNil - func (vas *ValidityAttesterStub) IsInterfaceNil() bool { return vas == nil diff --git a/process/mock/headerIntegrityVerifierStub.go b/process/mock/headerIntegrityVerifierStub.go index 3d793b89924..5fa049cd3bf 100644 --- a/process/mock/headerIntegrityVerifierStub.go +++ b/process/mock/headerIntegrityVerifierStub.go @@ -5,7 +5,7 @@ import "github.com/multiversx/mx-chain-core-go/data" // HeaderIntegrityVerifierStub - type HeaderIntegrityVerifierStub struct { VerifyCalled func(header data.HeaderHandler) error - GetVersionCalled func(epoch uint32) string + GetVersionCalled func(epoch uint32, round uint64) string } // Verify - @@ -18,9 +18,9 @@ func (h *HeaderIntegrityVerifierStub) Verify(header data.HeaderHandler) error { } // GetVersion - -func (h *HeaderIntegrityVerifierStub) GetVersion(epoch uint32) string { +func (h *HeaderIntegrityVerifierStub) GetVersion(epoch uint32, round uint64) string { if h.GetVersionCalled != nil { - return h.GetVersionCalled(epoch) + return h.GetVersionCalled(epoch, round) } return "version" diff --git a/process/mock/interceptedDataVerifierMock.go b/process/mock/interceptedDataVerifierMock.go index 6668a6ea625..6cb965c2a33 100644 --- a/process/mock/interceptedDataVerifierMock.go +++ b/process/mock/interceptedDataVerifierMock.go @@ -1,23 +1,32 @@ package mock import ( + "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process" ) // InterceptedDataVerifierMock - type InterceptedDataVerifierMock struct { - VerifyCalled func(interceptedData process.InterceptedData) error + VerifyCalled func(interceptedData process.InterceptedData, topic string, broadcastMethod p2p.BroadcastMethod) error + MarkVerifiedCalled func(interceptedData process.InterceptedData, broadcastMethod p2p.BroadcastMethod) } // Verify - -func (idv *InterceptedDataVerifierMock) Verify(interceptedData process.InterceptedData) error { +func (idv *InterceptedDataVerifierMock) Verify(interceptedData process.InterceptedData, topic string, broadcastMethod p2p.BroadcastMethod) error { if idv.VerifyCalled != nil { - return idv.VerifyCalled(interceptedData) + return idv.VerifyCalled(interceptedData, topic, broadcastMethod) } return nil } +// MarkVerified - +func (idv *InterceptedDataVerifierMock) MarkVerified(interceptedData process.InterceptedData, broadcastMethod p2p.BroadcastMethod) { + if idv.MarkVerifiedCalled != nil { + idv.MarkVerifiedCalled(interceptedData, broadcastMethod) + } +} + // IsInterfaceNil - func (idv *InterceptedDataVerifierMock) IsInterfaceNil() bool { return idv == nil diff --git a/process/mock/interceptedDebugHandlerStub.go b/process/mock/interceptedDebugHandlerStub.go index cbc7870c351..ca3971efe3c 100644 --- a/process/mock/interceptedDebugHandlerStub.go +++ b/process/mock/interceptedDebugHandlerStub.go @@ -1,9 +1,23 @@ package mock +import ( + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-go/p2p" + "github.com/multiversx/mx-chain-go/process" +) + // InterceptedDebugHandlerStub - type InterceptedDebugHandlerStub struct { LogReceivedHashesCalled func(topic string, hashes [][]byte) LogProcessedHashesCalled func(topic string, hashes [][]byte, err error) + LogReceivedDataCalled func(data process.InterceptedData, msg p2p.MessageP2P, fromConnectedPeer core.PeerID) +} + +// LogReceivedData - +func (idhs *InterceptedDebugHandlerStub) LogReceivedData(data process.InterceptedData, msg p2p.MessageP2P, fromConnectedPeer core.PeerID) { + if idhs.LogReceivedDataCalled != nil { + idhs.LogReceivedDataCalled(data, msg, fromConnectedPeer) + } } // LogReceivedHashes - diff --git a/process/mock/interceptedTrieNodeStub.go b/process/mock/interceptedTrieNodeStub.go index 08b1fa676a1..07247b35d34 100644 --- a/process/mock/interceptedTrieNodeStub.go +++ b/process/mock/interceptedTrieNodeStub.go @@ -2,13 +2,14 @@ package mock // InterceptedTrieNodeStub - type InterceptedTrieNodeStub struct { - CheckValidityCalled func() error - IsForCurrentShardCalled func() bool - SizeInBytesCalled func() int - HashField []byte - StringField string - IdentifiersField [][]byte - TypeField string + CheckValidityCalled func() error + IsForCurrentShardCalled func() bool + SizeInBytesCalled func() int + ShouldAllowDuplicatesCalled func() bool + HashField []byte + StringField string + IdentifiersField [][]byte + TypeField string } // CheckValidity - @@ -20,6 +21,15 @@ func (ins *InterceptedTrieNodeStub) CheckValidity() error { return nil } +// ShouldAllowDuplicates - +func (ins *InterceptedTrieNodeStub) ShouldAllowDuplicates() bool { + if ins.ShouldAllowDuplicatesCalled != nil { + return ins.ShouldAllowDuplicatesCalled() + } + + return true +} + // IsForCurrentShard - func (ins *InterceptedTrieNodeStub) IsForCurrentShard() bool { if ins.IsForCurrentShardCalled != nil { diff --git a/process/mock/interceptorProcessorStub.go b/process/mock/interceptorProcessorStub.go index f4e576ef01d..f0d69a49c2e 100644 --- a/process/mock/interceptorProcessorStub.go +++ b/process/mock/interceptorProcessorStub.go @@ -2,13 +2,14 @@ package mock import ( "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process" ) // InterceptorProcessorStub - type InterceptorProcessorStub struct { ValidateCalled func(data process.InterceptedData) error - SaveCalled func(data process.InterceptedData) error + SaveCalled func(data process.InterceptedData) (bool, error) RegisterHandlerCalled func(handler func(topic string, hash []byte, data interface{})) } @@ -18,7 +19,7 @@ func (ips *InterceptorProcessorStub) Validate(data process.InterceptedData, _ co } // Save - -func (ips *InterceptorProcessorStub) Save(data process.InterceptedData, _ core.PeerID, _ string) error { +func (ips *InterceptorProcessorStub) Save(data process.InterceptedData, _ core.PeerID, _ string, _ p2p.BroadcastMethod) (bool, error) { return ips.SaveCalled(data) } diff --git a/process/mock/raterMock.go b/process/mock/raterMock.go index 248fefe16b6..bad67679e4e 100644 --- a/process/mock/raterMock.go +++ b/process/mock/raterMock.go @@ -22,11 +22,11 @@ type RaterMock struct { GetRatingCalled func(string) uint32 GetStartRatingCalled func() uint32 GetSignedBlocksThresholdCalled func() float32 - ComputeIncreaseProposerCalled func(shardId uint32, rating uint32) uint32 - ComputeDecreaseProposerCalled func(shardId uint32, rating uint32, consecutiveMissedBlocks uint32) uint32 - RevertIncreaseProposerCalled func(shardId uint32, rating uint32, nrReverts uint32) uint32 - ComputeIncreaseValidatorCalled func(shardId uint32, rating uint32) uint32 - ComputeDecreaseValidatorCalled func(shardId uint32, rating uint32) uint32 + ComputeIncreaseProposerCalled func(shardId uint32, rating uint32, epoch uint32) uint32 + ComputeDecreaseProposerCalled func(shardId uint32, rating uint32, consecutiveMissedBlocks uint32, epoch uint32) uint32 + RevertIncreaseValidatorCalled func(shardId uint32, rating uint32, nrReverts uint32, epoch uint32) uint32 + ComputeIncreaseValidatorCalled func(shardId uint32, rating uint32, epoch uint32) uint32 + ComputeDecreaseValidatorCalled func(shardId uint32, rating uint32, epoch uint32) uint32 GetChancesCalled func(val uint32) uint32 } @@ -39,7 +39,7 @@ func GetNewMockRater() *RaterMock { raterMock.GetStartRatingCalled = func() uint32 { return raterMock.StartRating } - raterMock.ComputeIncreaseProposerCalled = func(shardId uint32, rating uint32) uint32 { + raterMock.ComputeIncreaseProposerCalled = func(shardId uint32, rating uint32, epoch uint32) uint32 { var ratingStep int32 if shardId == core.MetachainShardId { ratingStep = raterMock.MetaIncreaseProposer @@ -48,7 +48,7 @@ func GetNewMockRater() *RaterMock { } return raterMock.computeRating(rating, ratingStep) } - raterMock.RevertIncreaseProposerCalled = func(shardId uint32, rating uint32, nrReverts uint32) uint32 { + raterMock.RevertIncreaseValidatorCalled = func(shardId uint32, rating uint32, nrReverts uint32, epoch uint32) uint32 { var ratingStep int32 if shardId == core.MetachainShardId { ratingStep = raterMock.MetaIncreaseValidator @@ -58,7 +58,7 @@ func GetNewMockRater() *RaterMock { computedStep := -ratingStep * int32(nrReverts) return raterMock.computeRating(rating, computedStep) } - raterMock.ComputeDecreaseProposerCalled = func(shardId uint32, rating uint32, consecutiveMissedBlocks uint32) uint32 { + raterMock.ComputeDecreaseProposerCalled = func(shardId uint32, rating uint32, consecutiveMissedBlocks uint32, epoch uint32) uint32 { var ratingStep int32 if shardId == core.MetachainShardId { ratingStep = raterMock.MetaDecreaseProposer @@ -67,7 +67,7 @@ func GetNewMockRater() *RaterMock { } return raterMock.computeRating(rating, ratingStep) } - raterMock.ComputeIncreaseValidatorCalled = func(shardId uint32, rating uint32) uint32 { + raterMock.ComputeIncreaseValidatorCalled = func(shardId uint32, rating uint32, epoch uint32) uint32 { var ratingStep int32 if shardId == core.MetachainShardId { ratingStep = raterMock.MetaIncreaseValidator @@ -76,7 +76,7 @@ func GetNewMockRater() *RaterMock { } return raterMock.computeRating(rating, ratingStep) } - raterMock.ComputeDecreaseValidatorCalled = func(shardId uint32, rating uint32) uint32 { + raterMock.ComputeDecreaseValidatorCalled = func(shardId uint32, rating uint32, epoch uint32) uint32 { var ratingStep int32 if shardId == core.MetachainShardId { ratingStep = raterMock.MetaDecreaseValidator @@ -125,28 +125,28 @@ func (rm *RaterMock) GetSignedBlocksThreshold() float32 { } // ComputeIncreaseProposer - -func (rm *RaterMock) ComputeIncreaseProposer(shardId uint32, currentRating uint32) uint32 { - return rm.ComputeIncreaseProposerCalled(shardId, currentRating) +func (rm *RaterMock) ComputeIncreaseProposer(shardId uint32, currentRating uint32, epoch uint32) uint32 { + return rm.ComputeIncreaseProposerCalled(shardId, currentRating, epoch) } // ComputeDecreaseProposer - -func (rm *RaterMock) ComputeDecreaseProposer(shardId uint32, currentRating uint32, consecutiveMisses uint32) uint32 { - return rm.ComputeDecreaseProposerCalled(shardId, currentRating, consecutiveMisses) +func (rm *RaterMock) ComputeDecreaseProposer(shardId uint32, currentRating uint32, consecutiveMisses uint32, epoch uint32) uint32 { + return rm.ComputeDecreaseProposerCalled(shardId, currentRating, consecutiveMisses, epoch) } // RevertIncreaseValidator - -func (rm *RaterMock) RevertIncreaseValidator(shardId uint32, currentRating uint32, nrReverts uint32) uint32 { - return rm.RevertIncreaseProposerCalled(shardId, currentRating, nrReverts) +func (rm *RaterMock) RevertIncreaseValidator(shardId uint32, currentRating uint32, nrReverts uint32, epoch uint32) uint32 { + return rm.RevertIncreaseValidatorCalled(shardId, currentRating, nrReverts, epoch) } // ComputeIncreaseValidator - -func (rm *RaterMock) ComputeIncreaseValidator(shardId uint32, currentRating uint32) uint32 { - return rm.ComputeIncreaseValidatorCalled(shardId, currentRating) +func (rm *RaterMock) ComputeIncreaseValidator(shardId uint32, currentRating uint32, epoch uint32) uint32 { + return rm.ComputeIncreaseValidatorCalled(shardId, currentRating, epoch) } // ComputeDecreaseValidator - -func (rm *RaterMock) ComputeDecreaseValidator(shardId uint32, currentRating uint32) uint32 { - return rm.ComputeDecreaseValidatorCalled(shardId, currentRating) +func (rm *RaterMock) ComputeDecreaseValidator(shardId uint32, currentRating uint32, epoch uint32) uint32 { + return rm.ComputeDecreaseValidatorCalled(shardId, currentRating, epoch) } // GetChance - diff --git a/process/mock/ratingsInfoMock.go b/process/mock/ratingsInfoMock.go index 25250ebee30..c8f7cd3641b 100644 --- a/process/mock/ratingsInfoMock.go +++ b/process/mock/ratingsInfoMock.go @@ -47,6 +47,16 @@ func (rd *RatingsInfoMock) MetaChainRatingsStepHandler() process.RatingsStepHand return rd.MetaRatingsStepDataProperty } +// ShardChainRatingsStepHandlerForEpoch - +func (rd *RatingsInfoMock) ShardChainRatingsStepHandlerForEpoch(_ uint32) process.RatingsStepHandler { + return rd.ShardRatingsStepDataProperty +} + +// MetaChainRatingsStepHandlerForEpoch - +func (rd *RatingsInfoMock) MetaChainRatingsStepHandlerForEpoch(_ uint32) process.RatingsStepHandler { + return rd.MetaRatingsStepDataProperty +} + // ShardChainRatingsStepHandler - func (rd *RatingsInfoMock) ShardChainRatingsStepHandler() process.RatingsStepHandler { return rd.ShardRatingsStepDataProperty diff --git a/process/mock/rounderMock.go b/process/mock/rounderMock.go index 90d5f356405..5f1234c3aa6 100644 --- a/process/mock/rounderMock.go +++ b/process/mock/rounderMock.go @@ -59,6 +59,11 @@ func (rndm *RoundHandlerMock) RemainingTime(_ time.Time, _ time.Duration) time.D return rndm.RoundTimeDuration } +// GetTimeStampForRound - +func (rndm *RoundHandlerMock) GetTimeStampForRound(round uint64) uint64 { + return 0 +} + // IsInterfaceNil returns true if there is no value under the interface func (rndm *RoundHandlerMock) IsInterfaceNil() bool { return rndm == nil diff --git a/process/mock/syncTimerMock.go b/process/mock/syncTimerMock.go index 2fa41d42341..3b3e2d54b8b 100644 --- a/process/mock/syncTimerMock.go +++ b/process/mock/syncTimerMock.go @@ -10,6 +10,10 @@ type SyncTimerMock struct { CurrentTimeCalled func() time.Time } +// ForceSync - +func (stm *SyncTimerMock) ForceSync() { +} + // StartSyncingTime method does the time synchronization at every syncPeriod time elapsed. This should be started as a go routine func (stm *SyncTimerMock) StartSyncingTime() { panic("implement me") diff --git a/process/mock/txLogsProcessorStub.go b/process/mock/txLogsProcessorStub.go index 18e1e368274..f6e37708f73 100644 --- a/process/mock/txLogsProcessorStub.go +++ b/process/mock/txLogsProcessorStub.go @@ -9,7 +9,7 @@ import ( type TxLogsProcessorStub struct { GetLogCalled func(txHash []byte) (data.LogHandler, error) SaveLogCalled func(txHash []byte, tx data.TransactionHandler, vmLogs []*vmcommon.LogEntry) error - GetAllCurrentLogsCalled func() []*data.LogData + GetAllCurrentLogsCalled func() []data.LogDataHandler } // GetLog - @@ -35,7 +35,7 @@ func (txls *TxLogsProcessorStub) SaveLog(txHash []byte, tx data.TransactionHandl } // GetAllCurrentLogs - -func (txls *TxLogsProcessorStub) GetAllCurrentLogs() []*data.LogData { +func (txls *TxLogsProcessorStub) GetAllCurrentLogs() []data.LogDataHandler { if txls.GetAllCurrentLogsCalled != nil { return txls.GetAllCurrentLogsCalled() } diff --git a/process/p2p/interceptedPeerShard.go b/process/p2p/interceptedPeerShard.go index b992eb9a94d..65c142a8fbd 100644 --- a/process/p2p/interceptedPeerShard.go +++ b/process/p2p/interceptedPeerShard.go @@ -82,6 +82,11 @@ func (ips *interceptedPeerShard) CheckValidity() error { return nil } +// ShouldAllowDuplicates returns if this type of intercepted data should allow duplicates +func (ips *interceptedPeerShard) ShouldAllowDuplicates() bool { + return true +} + // IsForCurrentShard always returns true func (ips *interceptedPeerShard) IsForCurrentShard() bool { return true diff --git a/process/p2p/interceptedPeerShard_test.go b/process/p2p/interceptedPeerShard_test.go index 408099e96c2..b91240008f7 100644 --- a/process/p2p/interceptedPeerShard_test.go +++ b/process/p2p/interceptedPeerShard_test.go @@ -140,4 +140,5 @@ func TestInterceptedPeerShard_Getters(t *testing.T) { assert.True(t, bytes.Equal([]byte(""), identifiers[0])) assert.Equal(t, fmt.Sprintf("shard=%s", providedShard), idci.String()) assert.Equal(t, providedShard, idci.ShardID()) + assert.True(t, idci.ShouldAllowDuplicates()) } diff --git a/process/peer/export_test.go b/process/peer/export_test.go index be742c16804..277f4db52a4 100644 --- a/process/peer/export_test.go +++ b/process/peer/export_test.go @@ -63,6 +63,11 @@ func (vs *validatorStatistics) UpdateShardDataPeerState( return vs.updateShardDataPeerState(header, cacheMap) } +// SearchInMap - +func (vs *validatorStatistics) SearchInMap(hash []byte, cacheMap map[string]data.HeaderHandler, shardID uint32) (data.HeaderHandler, error) { + return vs.searchInMap(hash, cacheMap, shardID) +} + // GetActualList - func GetActualList(peerAccount state.PeerAccountHandler) string { return getActualList(peerAccount) diff --git a/process/peer/interceptedValidatorInfo.go b/process/peer/interceptedValidatorInfo.go index 708675721fc..4ade08517f4 100644 --- a/process/peer/interceptedValidatorInfo.go +++ b/process/peer/interceptedValidatorInfo.go @@ -81,6 +81,11 @@ func (ivi *interceptedValidatorInfo) CheckValidity() error { return nil } +// ShouldAllowDuplicates returns if this type of intercepted data should allow duplicates +func (ivi *interceptedValidatorInfo) ShouldAllowDuplicates() bool { + return false +} + // IsForCurrentShard always returns true func (ivi *interceptedValidatorInfo) IsForCurrentShard() bool { return true diff --git a/process/peer/interceptedValidatorInfo_test.go b/process/peer/interceptedValidatorInfo_test.go index f4bc60fe30a..2581a051592 100644 --- a/process/peer/interceptedValidatorInfo_test.go +++ b/process/peer/interceptedValidatorInfo_test.go @@ -156,4 +156,5 @@ func TestInterceptedValidatorInfo_Getters(t *testing.T) { assert.True(t, strings.Contains(str, fmt.Sprintf("list=%s", validatorInfo.List))) assert.True(t, strings.Contains(str, fmt.Sprintf("index=%d", validatorInfo.Index))) assert.True(t, strings.Contains(str, fmt.Sprintf("tempRating=%d", validatorInfo.TempRating))) + require.False(t, ivi.ShouldAllowDuplicates()) } diff --git a/process/peer/process.go b/process/peer/process.go index 24c89a08dc6..32554aaed50 100644 --- a/process/peer/process.go +++ b/process/peer/process.go @@ -14,6 +14,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/common/configs/dto" logger "github.com/multiversx/mx-chain-logger-go" "github.com/multiversx/mx-chain-go/common" @@ -45,41 +46,41 @@ const ( // ArgValidatorStatisticsProcessor holds all dependencies for the validatorStatistics type ArgValidatorStatisticsProcessor struct { - Marshalizer marshal.Marshalizer - NodesCoordinator nodesCoordinator.NodesCoordinator - ShardCoordinator sharding.Coordinator - DataPool DataPool - StorageService dataRetriever.StorageService - PubkeyConv core.PubkeyConverter - PeerAdapter state.AccountsAdapter - Rater sharding.PeerAccountListAndRatingHandler - RewardsHandler process.RewardsHandler - MaxComputableRounds uint64 - MaxConsecutiveRoundsOfRatingDecrease uint64 - NodesSetup sharding.GenesisNodesSetupHandler - GenesisNonce uint64 - RatingEnableEpoch uint32 - EnableEpochsHandler common.EnableEpochsHandler + Marshalizer marshal.Marshalizer + NodesCoordinator nodesCoordinator.NodesCoordinator + ShardCoordinator sharding.Coordinator + DataPool DataPool + StorageService dataRetriever.StorageService + PubkeyConv core.PubkeyConverter + PeerAdapter state.AccountsAdapter + Rater sharding.PeerAccountListAndRatingHandler + RewardsHandler process.RewardsHandler + MaxComputableRounds uint64 + NodesSetup sharding.GenesisNodesSetupHandler + GenesisNonce uint64 + RatingEnableEpoch uint32 + EnableEpochsHandler common.EnableEpochsHandler + ProcessConfigsHandler common.ProcessConfigsHandler } type validatorStatistics struct { - marshalizer marshal.Marshalizer - dataPool DataPool - storageService dataRetriever.StorageService - nodesCoordinator nodesCoordinator.NodesCoordinator - shardCoordinator sharding.Coordinator - pubkeyConv core.PubkeyConverter - peerAdapter state.AccountsAdapter - rater sharding.PeerAccountListAndRatingHandler - rewardsHandler process.RewardsHandler - maxComputableRounds uint64 - maxConsecutiveRoundsOfRatingDecrease uint64 - missedBlocksCounters validatorRoundCounters - mutValidatorStatistics sync.RWMutex - genesisNonce uint64 - ratingEnableEpoch uint32 - lastFinalizedRootHash []byte - enableEpochsHandler common.EnableEpochsHandler + marshalizer marshal.Marshalizer + dataPool DataPool + storageService dataRetriever.StorageService + nodesCoordinator nodesCoordinator.NodesCoordinator + shardCoordinator sharding.Coordinator + pubkeyConv core.PubkeyConverter + peerAdapter state.AccountsAdapter + rater sharding.PeerAccountListAndRatingHandler + rewardsHandler process.RewardsHandler + maxComputableRounds uint64 + missedBlocksCounters validatorRoundCounters + mutValidatorStatistics sync.RWMutex + genesisNonce uint64 + ratingEnableEpoch uint32 + lastFinalizedRootHash []byte + enableEpochsHandler common.EnableEpochsHandler + processConfigsHandler common.ProcessConfigsHandler } // NewValidatorStatisticsProcessor instantiates a new validatorStatistics structure responsible for keeping account of @@ -110,9 +111,6 @@ func NewValidatorStatisticsProcessor(arguments ArgValidatorStatisticsProcessor) if arguments.MaxComputableRounds == 0 { return nil, process.ErrZeroMaxComputableRounds } - if arguments.MaxConsecutiveRoundsOfRatingDecrease == 0 { - return nil, process.ErrZeroMaxConsecutiveRoundsOfRatingDecrease - } if check.IfNil(arguments.Rater) { return nil, process.ErrNilRater } @@ -125,6 +123,9 @@ func NewValidatorStatisticsProcessor(arguments ArgValidatorStatisticsProcessor) if check.IfNil(arguments.EnableEpochsHandler) { return nil, process.ErrNilEnableEpochsHandler } + if check.IfNil(arguments.ProcessConfigsHandler) { + return nil, process.ErrNilProcessConfigsHandler + } err := core.CheckHandlerCompatibility(arguments.EnableEpochsHandler, []core.EnableEpochFlag{ common.StopDecreasingValidatorRatingWhenStuckFlag, common.SwitchJailWaitingFlag, @@ -136,20 +137,20 @@ func NewValidatorStatisticsProcessor(arguments ArgValidatorStatisticsProcessor) } vs := &validatorStatistics{ - peerAdapter: arguments.PeerAdapter, - pubkeyConv: arguments.PubkeyConv, - nodesCoordinator: arguments.NodesCoordinator, - shardCoordinator: arguments.ShardCoordinator, - dataPool: arguments.DataPool, - storageService: arguments.StorageService, - marshalizer: arguments.Marshalizer, - missedBlocksCounters: make(validatorRoundCounters), - rater: arguments.Rater, - rewardsHandler: arguments.RewardsHandler, - maxComputableRounds: arguments.MaxComputableRounds, - maxConsecutiveRoundsOfRatingDecrease: arguments.MaxConsecutiveRoundsOfRatingDecrease, - genesisNonce: arguments.GenesisNonce, - enableEpochsHandler: arguments.EnableEpochsHandler, + peerAdapter: arguments.PeerAdapter, + pubkeyConv: arguments.PubkeyConv, + nodesCoordinator: arguments.NodesCoordinator, + shardCoordinator: arguments.ShardCoordinator, + dataPool: arguments.DataPool, + storageService: arguments.StorageService, + marshalizer: arguments.Marshalizer, + missedBlocksCounters: make(validatorRoundCounters), + rater: arguments.Rater, + rewardsHandler: arguments.RewardsHandler, + maxComputableRounds: arguments.MaxComputableRounds, + genesisNonce: arguments.GenesisNonce, + enableEpochsHandler: arguments.EnableEpochsHandler, + processConfigsHandler: arguments.ProcessConfigsHandler, } err = vs.saveInitialState(arguments.NodesSetup) @@ -340,9 +341,31 @@ func (vs *validatorStatistics) SaveNodesCoordinatorUpdates(epoch uint32) (bool, return nodeForcedToRemain, nil } +type feeCalculator func(previousHeader data.HeaderHandler) *big.Int + // UpdatePeerState takes a header, updates the peer state for all of the // consensus members and returns the new root hash func (vs *validatorStatistics) UpdatePeerState(header data.MetaHeaderHandler, cache map[string]data.HeaderHandler) ([]byte, error) { + feeCalculatorFunc := func(prevHeader data.HeaderHandler) *big.Int { + return big.NewInt(0).Sub(prevHeader.GetAccumulatedFees(), prevHeader.GetDeveloperFees()) + } + return vs.baseUpdatePeerState(header, cache, feeCalculatorFunc) +} + +// UpdatePeerStateV3 takes a headerV3, updates the peer state for all of the +// consensus members and returns the new root hash +func (vs *validatorStatistics) UpdatePeerStateV3(header data.MetaHeaderHandler, cache map[string]data.HeaderHandler, metaExecutionResult data.MetaExecutionResultHandler) ([]byte, error) { + feeCalculatorFunc := func(_ data.HeaderHandler) *big.Int { + return big.NewInt(0).Sub(metaExecutionResult.GetAccumulatedFees(), metaExecutionResult.GetDeveloperFees()) + } + return vs.baseUpdatePeerState(header, cache, feeCalculatorFunc) +} + +func (vs *validatorStatistics) baseUpdatePeerState( + header data.MetaHeaderHandler, + cache map[string]data.HeaderHandler, + calculateFees feeCalculator, +) ([]byte, error) { if header.GetNonce() == vs.genesisNonce { return vs.peerAdapter.RootHash() } @@ -406,11 +429,12 @@ func (vs *validatorStatistics) UpdatePeerState(header data.MetaHeaderHandler, ca log.Debug("UpdatePeerState - registering meta previous leader fees", "metaNonce", previousHeader.GetNonce()) bitmap := vs.getBitmapForHeader(previousHeader) + err = vs.updateValidatorInfoOnSuccessfulBlock( leader, consensusGroup, bitmap, - big.NewInt(0).Sub(previousHeader.GetAccumulatedFees(), previousHeader.GetDeveloperFees()), + calculateFees(previousHeader), previousHeader.GetShardID(), previousHeader.GetEpoch(), ) @@ -686,7 +710,7 @@ func (vs *validatorStatistics) verifySignaturesBelowSignedThreshold( increasedRatingTimes = validator.GetValidatorSuccess() + validator.GetValidatorIgnoredSignatures() } - newTempRating := vs.rater.RevertIncreaseValidator(shardId, validator.GetTempRating(), increasedRatingTimes) + newTempRating := vs.rater.RevertIncreaseValidator(shardId, validator.GetTempRating(), increasedRatingTimes, epoch) pa, err := vs.loadPeerAccount(validator.GetPublicKey()) if err != nil { return err @@ -780,7 +804,7 @@ func (vs *validatorStatistics) checkForMissedBlocks( return nil } if vs.enableEpochsHandler.IsFlagEnabled(common.StopDecreasingValidatorRatingWhenStuckFlag) { - if missedRounds > vs.maxConsecutiveRoundsOfRatingDecrease { + if missedRounds > vs.processConfigsHandler.GetValue(dto.MaxConsecutiveRoundsOfRatingDecrease) { return nil } } @@ -837,10 +861,7 @@ func (vs *validatorStatistics) computeDecrease( vs.mutValidatorStatistics.Unlock() swInner.Start("ComputeDecreaseProposer") - newRating := vs.rater.ComputeDecreaseProposer( - shardID, - leaderPeerAcc.GetTempRating(), - leaderPeerAcc.GetConsecutiveProposerMisses()) + newRating := vs.rater.ComputeDecreaseProposer(shardID, leaderPeerAcc.GetTempRating(), leaderPeerAcc.GetConsecutiveProposerMisses(), epoch) swInner.Stop("ComputeDecreaseProposer") swInner.Start("SetConsecutiveProposerMisses") @@ -892,7 +913,7 @@ func (vs *validatorStatistics) decreaseForConsensusValidators( } vs.missedBlocksCounters.decreaseValidator(consensusGroup[j].PubKey()) - newRating := vs.rater.ComputeDecreaseValidator(shardId, validatorPeerAccount.GetTempRating()) + newRating := vs.rater.ComputeDecreaseValidator(shardId, validatorPeerAccount.GetTempRating(), epoch) validatorPeerAccount.SetTempRating(newRating) vs.jailValidatorIfBadRatingAndInactive(validatorPeerAccount) err := vs.peerAdapter.SaveAccount(validatorPeerAccount) @@ -907,7 +928,17 @@ func (vs *validatorStatistics) decreaseForConsensusValidators( // RevertPeerState takes the current and previous headers and undos the peer state // for all of the consensus members func (vs *validatorStatistics) RevertPeerState(header data.MetaHeaderHandler) error { - rootHashHolder := holders.NewDefaultRootHashesHolder(header.GetValidatorStatsRootHash()) + rootHash := header.GetValidatorStatsRootHash() + if header.IsHeaderV3() { + baseMetaExecRes, ok := header.GetLastExecutionResultHandler().(data.LastMetaExecutionResultHandler) + if !ok { + return process.ErrWrongTypeAssertion + } + + rootHash = baseMetaExecRes.GetExecutionResultHandler().GetValidatorStatsRootHash() + } + + rootHashHolder := holders.NewDefaultRootHashesHolder(rootHash) return vs.peerAdapter.RecreateTrie(rootHashHolder) } @@ -926,64 +957,65 @@ func (vs *validatorStatistics) updateShardDataPeerState( header data.HeaderHandler, cacheMap map[string]data.HeaderHandler, ) error { - metaHeader, ok := header.(*block.MetaBlock) + metaHeader, ok := header.(data.MetaHeaderHandler) if !ok { return process.ErrInvalidMetaHeader } var currentHeader data.HeaderHandler - for _, h := range metaHeader.ShardInfo { - if h.Nonce == vs.genesisNonce { + for _, h := range metaHeader.GetShardInfoHandlers() { + if h.GetNonce() == vs.genesisNonce { continue } - currentHeader, ok = cacheMap[string(h.HeaderHash)] + currentHeader, ok = cacheMap[string(h.GetHeaderHash())] if !ok { return fmt.Errorf("%w - updateShardDataPeerState header from cache - hash: %s, round: %v, nonce: %v", process.ErrMissingHeader, - hex.EncodeToString(h.HeaderHash), + hex.EncodeToString(h.GetHeaderHash()), h.GetRound(), h.GetNonce()) } epoch := computeEpoch(currentHeader) - leader, shardConsensus, shardInfoErr := vs.nodesCoordinator.ComputeConsensusGroup(h.PrevRandSeed, h.Round, h.ShardID, epoch) + leader, shardConsensus, shardInfoErr := vs.nodesCoordinator.ComputeConsensusGroup(h.GetPrevRandSeed(), h.GetRound(), h.GetShardID(), epoch) if shardInfoErr != nil { return shardInfoErr } - log.Debug("updateShardDataPeerState - registering shard leader fees", "shard headerHash", h.HeaderHash, "accumulatedFees", h.AccumulatedFees.String(), "developerFees", h.DeveloperFees.String()) - bitmap := h.PubKeysBitmap - if vs.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, h.Epoch) { - bitmap = vs.getBitmapForFullConsensus(h.ShardID, h.Epoch) + log.Debug("updateShardDataPeerState - registering shard leader fees", "shard headerHash", h.GetHeaderHash(), "accumulatedFees", h.GetAccumulatedFees().String(), "developerFees", h.GetDeveloperFees().String()) + bitmap := h.GetPubKeysBitmap() + if vs.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, h.GetEpoch()) { + bitmap = vs.getBitmapForFullConsensus(h.GetShardID(), h.GetEpoch()) } + shardInfoErr = vs.updateValidatorInfoOnSuccessfulBlock( leader, shardConsensus, bitmap, - big.NewInt(0).Sub(h.AccumulatedFees, h.DeveloperFees), - h.ShardID, + big.NewInt(0).Sub(h.GetAccumulatedFees(), h.GetDeveloperFees()), + h.GetShardID(), currentHeader.GetEpoch(), ) if shardInfoErr != nil { return shardInfoErr } - if h.Nonce == vs.genesisNonce+1 { + if h.GetNonce() == vs.genesisNonce+1 { continue } - prevShardData, shardInfoErr := vs.searchInMap(h.PrevHash, cacheMap) + prevShardData, shardInfoErr := vs.searchInMap(h.GetPrevHash(), cacheMap, h.GetShardID()) if shardInfoErr != nil { return shardInfoErr } shardInfoErr = vs.checkForMissedBlocks( - h.Round, + h.GetRound(), prevShardData.GetRound(), prevShardData.GetRandSeed(), - h.ShardID, + h.GetShardID(), epoch, ) if shardInfoErr != nil { @@ -994,11 +1026,15 @@ func (vs *validatorStatistics) updateShardDataPeerState( return nil } -func (vs *validatorStatistics) searchInMap(hash []byte, cacheMap map[string]data.HeaderHandler) (data.HeaderHandler, error) { - blkHandler := cacheMap[string(hash)] - if check.IfNil(blkHandler) { +func (vs *validatorStatistics) searchInMap( + hash []byte, + cacheMap map[string]data.HeaderHandler, + shardID uint32, +) (data.HeaderHandler, error) { + blkHandler, err := vs.searchInMapAndHeadersPool(hash, cacheMap, shardID) + if err != nil { return nil, fmt.Errorf("%w : searchInMap hash = %s", - process.ErrMissingHeader, logger.DisplayByteSlice(hash)) + err, logger.DisplayByteSlice(hash)) } blk, ok := blkHandler.(data.ShardHeaderHandler) @@ -1009,6 +1045,31 @@ func (vs *validatorStatistics) searchInMap(hash []byte, cacheMap map[string]data return blk, nil } +func (vs *validatorStatistics) searchInMapAndHeadersPool( + hash []byte, + cacheMap map[string]data.HeaderHandler, + shardID uint32, +) (data.HeaderHandler, error) { + header, ok := cacheMap[string(hash)] + if ok && !check.IfNil(header) { + return header, nil + } + + header, err := process.GetHeader( + hash, + vs.dataPool.Headers(), + vs.storageService, + vs.marshalizer, + shardID, + ) + if err != nil { + log.Debug("searchInMapAndHeadersPool", "hash", hash) + return nil, err + } + + return header, nil +} + func (vs *validatorStatistics) initializeNode( node nodesCoordinator.GenesisNodeInfoHandler, shardID uint32, @@ -1077,7 +1138,7 @@ func (vs *validatorStatistics) updateValidatorInfoOnSuccessfulBlock( case leaderSuccess: peerAcc.IncreaseLeaderSuccessRate(1) peerAcc.SetConsecutiveProposerMisses(0) - newRating = vs.rater.ComputeIncreaseProposer(shardId, peerAcc.GetTempRating()) + newRating = vs.rater.ComputeIncreaseProposer(shardId, peerAcc.GetTempRating(), epoch) var leaderAccumulatedFees *big.Int if vs.enableEpochsHandler.IsFlagEnabled(common.StakingV2FlagAfterEpoch) { leaderAccumulatedFees = core.GetIntTrimmedPercentageOfValue(accumulatedFees, vs.rewardsHandler.LeaderPercentageInEpoch(epoch)) @@ -1089,13 +1150,15 @@ func (vs *validatorStatistics) updateValidatorInfoOnSuccessfulBlock( log.Debug("updateValidatorInfoOnSuccessfulBlock", "leaderAccumulatedFees in current block", leaderAccumulatedFees.String(), "leader fees in Epoch", peerAcc.GetAccumulatedFees().String(), - "leader", core.GetTrimmedPk(string(peerAcc.AddressBytes()))) + "newRating", newRating, + "leader", core.GetTrimmedPk(string(peerAcc.AddressBytes())), + ) case validatorSuccess: peerAcc.IncreaseValidatorSuccessRate(1) - newRating = vs.rater.ComputeIncreaseValidator(shardId, peerAcc.GetTempRating()) + newRating = vs.rater.ComputeIncreaseValidator(shardId, peerAcc.GetTempRating(), epoch) case validatorIgnoredSignature: peerAcc.IncreaseValidatorIgnoredSignaturesRate(1) - newRating = vs.rater.ComputeIncreaseValidator(shardId, peerAcc.GetTempRating()) + newRating = vs.rater.ComputeIncreaseValidator(shardId, peerAcc.GetTempRating(), epoch) } peerAcc.SetTempRating(newRating) @@ -1257,11 +1320,11 @@ func (vs *validatorStatistics) decreaseAll( currentTempRating := validatorPeerAccount.GetTempRating() for ct := uint32(0); ct < leaderAppearances; ct++ { - currentTempRating = vs.rater.ComputeDecreaseProposer(shardID, currentTempRating, 0) + currentTempRating = vs.rater.ComputeDecreaseProposer(shardID, currentTempRating, 0, epoch) } for ct := uint32(0); ct < consensusGroupAppearances; ct++ { - currentTempRating = vs.rater.ComputeDecreaseValidator(shardID, currentTempRating) + currentTempRating = vs.rater.ComputeDecreaseValidator(shardID, currentTempRating, epoch) } if i == 0 { diff --git a/process/peer/process_test.go b/process/peer/process_test.go index 58ed68d1046..da20e572091 100644 --- a/process/peer/process_test.go +++ b/process/peer/process_test.go @@ -14,11 +14,8 @@ import ( "github.com/multiversx/mx-chain-core-go/core/keyValStorage" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/common/configs/dto" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" @@ -30,13 +27,18 @@ import ( "github.com/multiversx/mx-chain-go/state/accounts" "github.com/multiversx/mx-chain-go/storage" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" "github.com/multiversx/mx-chain-go/testscommon/genesisMocks" + "github.com/multiversx/mx-chain-go/testscommon/pool" "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" stateMock "github.com/multiversx/mx-chain-go/testscommon/state" storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const ( @@ -60,11 +62,9 @@ func createMockPubkeyConverter() *testscommon.PubkeyConverterMock { } func createMockArguments() peer.ArgValidatorStatisticsProcessor { - cfg := &config.Config{EpochStartConfig: config.EpochStartConfig{RoundsPerEpoch: 14400}} - cfg.GeneralSettings.ChainParametersByEpoch = []config.ChainParametersByEpochConfig{{RoundDuration: 6000}} argsNewEconomicsData := economics.ArgsNewEconomicsData{ - GeneralConfig: cfg, + ChainParamsHandler: &chainParameters.ChainParametersHolderMock{}, Economics: &config.EconomicsConfig{ GlobalSettings: config.GlobalSettings{ GenesisTotalSupply: "2000000000000000000000", @@ -126,17 +126,24 @@ func createMockArguments() peer.ArgValidatorStatisticsProcessor { return nil }, }, - StorageService: &storageStubs.ChainStorerStub{}, - NodesCoordinator: &shardingMocks.NodesCoordinatorMock{}, - ShardCoordinator: mock.NewOneShardCoordinatorMock(), - PubkeyConv: createMockPubkeyConverter(), - PeerAdapter: getAccountsMock(), - Rater: createMockRater(), - RewardsHandler: economicsData, - MaxComputableRounds: 1000, - MaxConsecutiveRoundsOfRatingDecrease: 2000, - NodesSetup: &genesisMocks.NodesSetupStub{}, - EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(common.SwitchJailWaitingFlag, common.BelowSignedThresholdFlag), + StorageService: &storageStubs.ChainStorerStub{}, + NodesCoordinator: &shardingMocks.NodesCoordinatorMock{}, + ShardCoordinator: mock.NewOneShardCoordinatorMock(), + PubkeyConv: createMockPubkeyConverter(), + PeerAdapter: getAccountsMock(), + Rater: createMockRater(), + RewardsHandler: economicsData, + MaxComputableRounds: 1000, + NodesSetup: &genesisMocks.NodesSetupStub{}, + EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(common.SwitchJailWaitingFlag, common.BelowSignedThresholdFlag), + ProcessConfigsHandler: &testscommon.ProcessConfigsHandlerStub{ + GetValueCalled: func(variable dto.ConfigVariable) uint64 { + if variable == dto.MaxConsecutiveRoundsOfRatingDecrease { + return 2000 + } + return 10 + }, + }, } return arguments } @@ -227,17 +234,6 @@ func TestNewValidatorStatisticsProcessor_ZeroMaxComputableRoundsShouldErr(t *tes assert.Equal(t, process.ErrZeroMaxComputableRounds, err) } -func TestNewValidatorStatisticsProcessor_ZeroMaxConsecutiveRoundsOfRatingDecreaseShouldErr(t *testing.T) { - t.Parallel() - - arguments := createMockArguments() - arguments.MaxConsecutiveRoundsOfRatingDecrease = 0 - validatorStatistics, err := peer.NewValidatorStatisticsProcessor(arguments) - - assert.Nil(t, validatorStatistics) - assert.Equal(t, process.ErrZeroMaxConsecutiveRoundsOfRatingDecrease, err) -} - func TestNewValidatorStatisticsProcessor_NilRaterShouldErr(t *testing.T) { t.Parallel() @@ -785,6 +781,21 @@ func generateTestMetaBlockHeaders(cache map[string]data.HeaderHandler) (*block.M return prevHeader, header } +func generateTestMetaBlockV3Headers(cache map[string]data.HeaderHandler) (*block.MetaBlockV3, *block.MetaBlockV3) { + prevHeader := &block.MetaBlockV3{ + Round: 1, + Epoch: 1, + Nonce: 1, + PrevRandSeed: []byte("prevRandSeed"), + } + + header := getMetaV3HeaderHandler([]byte("header")) + header.RandSeed = []byte{1} + + cache[string(header.GetPrevHash())] = prevHeader + return prevHeader, header +} + func generateTestShardBlockHeaders(cache map[string]data.HeaderHandler) (*block.Header, *block.Header) { prevHeader := &block.Header{ Round: 1, @@ -1022,6 +1033,37 @@ func TestValidatorStatisticsProcessor_UpdatePeerState_DecreasesMissedMetaBlock_P assert.Equal(t, uint32(1), missedValidator.DecreaseValidatorSuccessRateValue) } +func TestValidatorStatisticsProcessor_UpdatePeerStateV3(t *testing.T) { + t.Parallel() + + consensusGroup := make(map[string][]nodesCoordinator.Validator) + + arguments := createUpdateTestArgs(consensusGroup) + arguments.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + } + validator := shardingMocks.NewValidatorMock([]byte("pk1"), 1, 1) + arguments.NodesCoordinator = &shardingMocks.NodesCoordinatorStub{ + ComputeConsensusGroupCalled: func(randomness []byte, round uint64, shardId uint32, epoch uint32) (leader nodesCoordinator.Validator, validatorsGroup []nodesCoordinator.Validator, err error) { + return validator, []nodesCoordinator.Validator{validator}, nil + }, + } + validatorStatistics, _ := peer.NewValidatorStatisticsProcessor(arguments) + + cache := createMockCache() + prevHeader, header := generateTestMetaBlockV3Headers(cache) + prevHeader.EpochStart.LastFinalizedHeaders = []block.EpochStartShardData{{ShardID: 0}} + + metaExecResult := &block.MetaExecutionResult{ + AccumulatedFees: big.NewInt(1), + DeveloperFees: big.NewInt(1), + } + _, err := validatorStatistics.UpdatePeerStateV3(header, cache, metaExecResult) + require.Nil(t, err) +} + func TestValidatorStatisticsProcessor_UpdateShardDataPeerState_IncreasesConsensusCurrentShardBlock_SameEpoch(t *testing.T) { t.Parallel() @@ -1431,7 +1473,14 @@ func TestValidatorStatisticsProcessor_CheckForMissedBlocksMissedRoundsGreaterTha arguments.MaxComputableRounds = 1 enableEpochsHandler, _ := arguments.EnableEpochsHandler.(*enableEpochsHandlerMock.EnableEpochsHandlerStub) enableEpochsHandler.RemoveActiveFlags(common.StopDecreasingValidatorRatingWhenStuckFlag) - arguments.MaxConsecutiveRoundsOfRatingDecrease = 4 + arguments.ProcessConfigsHandler = &testscommon.ProcessConfigsHandlerStub{ + GetValueCalled: func(variable dto.ConfigVariable) uint64 { + if variable == dto.MaxConsecutiveRoundsOfRatingDecrease { + return 4 + } + return 0 + }, + } validatorStatistics, _ := peer.NewValidatorStatisticsProcessor(arguments) @@ -1963,6 +2012,16 @@ func getMetaHeaderHandler(randSeed []byte) *block.MetaBlock { } } +func getMetaV3HeaderHandler(randSeed []byte) *block.MetaBlockV3 { + return &block.MetaBlockV3{ + Round: 2, + Epoch: 1, + Nonce: 2, + PrevRandSeed: randSeed, + PrevHash: randSeed, + } +} + func getShardHeaderHandler(randSeed []byte) *block.Header { return &block.Header{ Nonce: 2, @@ -2421,7 +2480,7 @@ func TestValidatorStatistics_ProcessRatingsEndOfEpochAfterEquivalentProofsShould rater.GetSignedBlocksThresholdCalled = func() float32 { return 0.025 // would have passed the `if computedThreshold <= signedThreshold` condition } - rater.RevertIncreaseProposerCalled = func(shardId uint32, rating uint32, nrReverts uint32) uint32 { + rater.RevertIncreaseValidatorCalled = func(shardId uint32, rating uint32, nrReverts uint32, epoch uint32) uint32 { require.Fail(t, "should have not been called") return 0 } @@ -2903,3 +2962,93 @@ func TestValidatorStatisticsProcessor_getActualList(t *testing.T) { computedJailedList := peer.GetActualList(jailedPeer) assert.Equal(t, jailedList, computedJailedList) } + +func TestValidatorStatistics_SearchInMap(t *testing.T) { + t.Parallel() + + t.Run("should work when found in cache", func(t *testing.T) { + t.Parallel() + + cache := createMockCache() + + _, header := generateTestShardBlockHeaders(cache) + headerHash := []byte("headerHash") + cache[string(headerHash)] = header + + arguments := createMockArguments() + + validatorStatistics, _ := peer.NewValidatorStatisticsProcessor(arguments) + + retHeader, err := validatorStatistics.SearchInMap(headerHash, cache, 0) + require.Nil(t, err) + require.Equal(t, header, retHeader) + }) + + t.Run("should work when found in headers pool", func(t *testing.T) { + t.Parallel() + + cache := createMockCache() + + _, header := generateTestShardBlockHeaders(cache) + + headerHash := []byte("headerHash") + + arguments := createMockArguments() + + dataPool := &dataRetrieverMock.PoolsHolderStub{ + HeadersCalled: func() dataRetriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + require.Equal(t, headerHash, hash) + return header, nil + }, + } + }, + } + arguments.DataPool = dataPool + + validatorStatistics, _ := peer.NewValidatorStatisticsProcessor(arguments) + + retHeader, err := validatorStatistics.SearchInMap(headerHash, cache, 0) + require.Nil(t, err) + require.Equal(t, header, retHeader) + }) + + t.Run("should fail when not found in headers pool", func(t *testing.T) { + t.Parallel() + + cache := createMockCache() + + headerHash := []byte("headerHash") + + arguments := createMockArguments() + + expectedErr := errors.New("exp err") + dataPool := &dataRetrieverMock.PoolsHolderStub{ + HeadersCalled: func() dataRetriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, expectedErr + }, + } + }, + } + arguments.DataPool = dataPool + + arguments.StorageService = &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + return nil, expectedErr + }, + }, nil + }, + } + + validatorStatistics, _ := peer.NewValidatorStatisticsProcessor(arguments) + + retHeader, err := validatorStatistics.SearchInMap(headerHash, cache, 0) + require.ErrorIs(t, err, process.ErrMissingHeader) + require.Nil(t, retHeader) + }) +} diff --git a/process/rating/blockSigningRater.go b/process/rating/blockSigningRater.go index dc90748befd..ddce4450ae9 100644 --- a/process/rating/blockSigningRater.go +++ b/process/rating/blockSigningRater.go @@ -8,6 +8,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/sharding" logger "github.com/multiversx/mx-chain-logger-go" @@ -27,13 +28,21 @@ type BlockSigningRater struct { signedBlocksThreshold float32 maxRating uint32 minRating uint32 + ratingsHandler process.RatingsInfoHandler shardRatingsStepHandler process.RatingsStepHandler metaRatingsStepHandler process.RatingsStepHandler + enableEpochsHandler common.EnableEpochsHandler ratingChances []process.RatingChanceHandler } // NewBlockSigningRater creates a new RaterHandler of Type BlockSigningRater -func NewBlockSigningRater(ratingsData process.RatingsInfoHandler) (*BlockSigningRater, error) { +func NewBlockSigningRater( + ratingsData process.RatingsInfoHandler, + enableEpochsHandler common.EnableEpochsHandler, +) (*BlockSigningRater, error) { + if check.IfNil(enableEpochsHandler) { + return nil, process.ErrNilEnableEpochsHandler + } err := verifyRatingsData(ratingsData) if err != nil { return nil, err @@ -71,8 +80,10 @@ func NewBlockSigningRater(ratingsData process.RatingsInfoHandler) (*BlockSigning minRating: ratingsData.MinRating(), maxRating: ratingsData.MaxRating(), signedBlocksThreshold: ratingsData.SignedBlocksThreshold(), + ratingsHandler: ratingsData, shardRatingsStepHandler: ratingsData.ShardChainRatingsStepHandler(), metaRatingsStepHandler: ratingsData.MetaChainRatingsStepHandler(), + enableEpochsHandler: enableEpochsHandler, ratingChances: ratingChances, }, nil } @@ -159,26 +170,42 @@ func (bsr *BlockSigningRater) GetSignedBlocksThreshold() float32 { } // ComputeIncreaseProposer computes the new rating for the increaseLeader -func (bsr *BlockSigningRater) ComputeIncreaseProposer(shardId uint32, currentRating uint32) uint32 { +func (bsr *BlockSigningRater) ComputeIncreaseProposer(shardId uint32, currentRating uint32, epoch uint32) uint32 { log.Trace("ComputeIncreaseProposer", "shardId", shardId, "currentRating", currentRating) var ratingStep int32 if shardId == core.MetachainShardId { - ratingStep = bsr.metaRatingsStepHandler.ProposerIncreaseRatingStep() + ratingStep = bsr.getMetaChainRatingsStepHandler(epoch).ProposerIncreaseRatingStep() } else { - ratingStep = bsr.shardRatingsStepHandler.ProposerIncreaseRatingStep() + ratingStep = bsr.getShardChainRatingsStepHandler(epoch).ProposerIncreaseRatingStep() } return bsr.computeRating(ratingStep, currentRating) } +func (bsr *BlockSigningRater) getShardChainRatingsStepHandler(epoch uint32) process.RatingsStepHandler { + if bsr.enableEpochsHandler.IsFlagEnabledInEpoch(common.SupernovaFlag, epoch) { + return bsr.ratingsHandler.ShardChainRatingsStepHandlerForEpoch(epoch) + } + + return bsr.shardRatingsStepHandler +} + +func (bsr *BlockSigningRater) getMetaChainRatingsStepHandler(epoch uint32) process.RatingsStepHandler { + if bsr.enableEpochsHandler.IsFlagEnabledInEpoch(common.SupernovaFlag, epoch) { + return bsr.ratingsHandler.MetaChainRatingsStepHandlerForEpoch(epoch) + } + + return bsr.metaRatingsStepHandler +} + // RevertIncreaseValidator computes the new rating based on how many reverts have to be done for the validator -func (bsr *BlockSigningRater) RevertIncreaseValidator(shardId uint32, currentRating uint32, nrReverts uint32) uint32 { +func (bsr *BlockSigningRater) RevertIncreaseValidator(shardId uint32, currentRating uint32, nrReverts uint32, epoch uint32) uint32 { log.Trace("RevertIncreaseValidator", "shardId", shardId, "currentRating", currentRating, "nrReverts", nrReverts) var ratingStep int32 if shardId == core.MetachainShardId { - ratingStep = bsr.metaRatingsStepHandler.ValidatorIncreaseRatingStep() + ratingStep = bsr.getMetaChainRatingsStepHandler(epoch).ValidatorIncreaseRatingStep() } else { - ratingStep = bsr.shardRatingsStepHandler.ValidatorIncreaseRatingStep() + ratingStep = bsr.getShardChainRatingsStepHandler(epoch).ValidatorIncreaseRatingStep() } decreaseValueBigInt := big.NewInt(0).Mul(big.NewInt(int64(-ratingStep)), big.NewInt(int64(nrReverts))) @@ -194,17 +221,17 @@ func (bsr *BlockSigningRater) RevertIncreaseValidator(shardId uint32, currentRat } // ComputeDecreaseProposer computes the new rating for the decreaseLeader -func (bsr *BlockSigningRater) ComputeDecreaseProposer(shardId uint32, currentRating uint32, consecutiveMisses uint32) uint32 { +func (bsr *BlockSigningRater) ComputeDecreaseProposer(shardId uint32, currentRating uint32, consecutiveMisses uint32, epoch uint32) uint32 { log.Trace("ComputeDecreaseProposer", "shardId", shardId, "currentRating", currentRating, "consecutiveMisses", consecutiveMisses) var proposerDecreaseRatingStep int32 var consecutiveBlocksPenalty float32 if shardId == core.MetachainShardId { - proposerDecreaseRatingStep = bsr.metaRatingsStepHandler.ProposerDecreaseRatingStep() - consecutiveBlocksPenalty = bsr.metaRatingsStepHandler.ConsecutiveMissedBlocksPenalty() + proposerDecreaseRatingStep = bsr.getMetaChainRatingsStepHandler(epoch).ProposerDecreaseRatingStep() + consecutiveBlocksPenalty = bsr.getMetaChainRatingsStepHandler(epoch).ConsecutiveMissedBlocksPenalty() } else { - proposerDecreaseRatingStep = bsr.shardRatingsStepHandler.ProposerDecreaseRatingStep() - consecutiveBlocksPenalty = bsr.shardRatingsStepHandler.ConsecutiveMissedBlocksPenalty() + proposerDecreaseRatingStep = bsr.getShardChainRatingsStepHandler(epoch).ProposerDecreaseRatingStep() + consecutiveBlocksPenalty = bsr.getShardChainRatingsStepHandler(epoch).ConsecutiveMissedBlocksPenalty() } var consecutiveMissesIncrease int32 @@ -223,25 +250,25 @@ func (bsr *BlockSigningRater) ComputeDecreaseProposer(shardId uint32, currentRat } // ComputeIncreaseValidator computes the new rating for the increaseValidator -func (bsr *BlockSigningRater) ComputeIncreaseValidator(shardId uint32, currentRating uint32) uint32 { +func (bsr *BlockSigningRater) ComputeIncreaseValidator(shardId uint32, currentRating uint32, epoch uint32) uint32 { log.Trace("ComputeIncreaseValidator", "shardId", shardId, "currentRating", currentRating) var ratingStep int32 if shardId == core.MetachainShardId { - ratingStep = bsr.metaRatingsStepHandler.ValidatorIncreaseRatingStep() + ratingStep = bsr.getMetaChainRatingsStepHandler(epoch).ValidatorIncreaseRatingStep() } else { - ratingStep = bsr.shardRatingsStepHandler.ValidatorIncreaseRatingStep() + ratingStep = bsr.getShardChainRatingsStepHandler(epoch).ValidatorIncreaseRatingStep() } return bsr.computeRating(ratingStep, currentRating) } // ComputeDecreaseValidator computes the new rating for the decreaseValidator -func (bsr *BlockSigningRater) ComputeDecreaseValidator(shardId uint32, currentRating uint32) uint32 { +func (bsr *BlockSigningRater) ComputeDecreaseValidator(shardId uint32, currentRating uint32, epoch uint32) uint32 { log.Trace("ComputeDecreaseValidator", "shardId", shardId, "currentRating", currentRating) var ratingStep int32 if shardId == core.MetachainShardId { - ratingStep = bsr.metaRatingsStepHandler.ValidatorDecreaseRatingStep() + ratingStep = bsr.getMetaChainRatingsStepHandler(epoch).ValidatorDecreaseRatingStep() } else { - ratingStep = bsr.shardRatingsStepHandler.ValidatorDecreaseRatingStep() + ratingStep = bsr.getShardChainRatingsStepHandler(epoch).ValidatorDecreaseRatingStep() } return bsr.computeRating(ratingStep, currentRating) } diff --git a/process/rating/blockSigningRater_test.go b/process/rating/blockSigningRater_test.go index 3c481318b04..e3d2f90df17 100644 --- a/process/rating/blockSigningRater_test.go +++ b/process/rating/blockSigningRater_test.go @@ -10,6 +10,7 @@ import ( "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/mock" "github.com/multiversx/mx-chain-go/process/rating" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -68,16 +69,69 @@ func createDefaultRatingsData() *mock.RatingsInfoMock { } func TestBlockSigningRater_UpdateRatingsShouldUpdateRatingWhenProposed(t *testing.T) { - initialRatingValue := uint32(5) - rd := createDefaultRatingsData() - shardId := uint32(0) + t.Parallel() - bsr, _ := rating.NewBlockSigningRater(rd) - computedRating := bsr.ComputeIncreaseProposer(shardId, initialRatingValue) + t.Run("shard before supernova", func(t *testing.T) { + t.Parallel() + initialRatingValue := uint32(5) + rd := createDefaultRatingsData() + shardId := uint32(0) - expectedValue := uint32(int32(initialRatingValue) + proposerIncreaseRatingStep) + bsr, _ := rating.NewBlockSigningRater(rd, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) + computedRating := bsr.ComputeIncreaseProposer(shardId, initialRatingValue, 0) - assert.Equal(t, expectedValue, computedRating) + expectedValue := uint32(int32(initialRatingValue) + proposerIncreaseRatingStep) + + assert.Equal(t, expectedValue, computedRating) + }) + t.Run("shard after supernova", func(t *testing.T) { + t.Parallel() + + initialRatingValue := uint32(5) + rd := createDefaultRatingsData() + shardId := uint32(0) + + bsr, _ := rating.NewBlockSigningRater(rd, &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + }) + + // after supernova, it should be fetched again on call from the ratingsData component, thus the following change should be visible + ratingsStepMock, ok := rd.ShardRatingsStepDataProperty.(*mock.RatingStepMock) + require.True(t, ok) + ratingsStepMock.ProposerIncreaseRatingStepProperty++ + + computedRating := bsr.ComputeIncreaseProposer(shardId, initialRatingValue, 0) + + expectedValue := uint32(int32(initialRatingValue) + proposerIncreaseRatingStep + 1) + + assert.Equal(t, expectedValue, computedRating) + }) + t.Run("meta after supernova", func(t *testing.T) { + t.Parallel() + + initialRatingValue := uint32(5) + rd := createDefaultRatingsData() + shardId := core.MetachainShardId + + bsr, _ := rating.NewBlockSigningRater(rd, &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + }) + + // after supernova, it should be fetched again on call from the ratingsData component, thus the following change should be visible + ratingsStepMock, ok := rd.MetaRatingsStepDataProperty.(*mock.RatingStepMock) + require.True(t, ok) + ratingsStepMock.ProposerIncreaseRatingStepProperty++ + + computedRating := bsr.ComputeIncreaseProposer(shardId, initialRatingValue, 0) + + expectedValue := uint32(int32(initialRatingValue) + metaProposerIncreaseRatingStep + 1) + + assert.Equal(t, expectedValue, computedRating) + }) } func TestBlockSigningRater_UpdateRatingsShouldUpdateRatingWhenValidator(t *testing.T) { @@ -85,8 +139,8 @@ func TestBlockSigningRater_UpdateRatingsShouldUpdateRatingWhenValidator(t *testi rd := createDefaultRatingsData() shardId := uint32(0) - bsr, _ := rating.NewBlockSigningRater(rd) - computedRating := bsr.ComputeIncreaseValidator(shardId, initialRatingValue) + bsr, _ := rating.NewBlockSigningRater(rd, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) + computedRating := bsr.ComputeIncreaseValidator(shardId, initialRatingValue, 0) expectedValue := uint32(int32(initialRatingValue) + validatorIncreaseRatingStep) @@ -98,8 +152,8 @@ func TestBlockSigningRater_UpdateRatingsShouldUpdateRatingWhenValidatorButNotAcc rd := createDefaultRatingsData() shardId := uint32(0) - bsr, _ := rating.NewBlockSigningRater(rd) - computedRating := bsr.ComputeDecreaseValidator(shardId, initialRatingValue) + bsr, _ := rating.NewBlockSigningRater(rd, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) + computedRating := bsr.ComputeDecreaseValidator(shardId, initialRatingValue, 0) expectedValue := uint32(int32(initialRatingValue) + validatorDecreaseRatingStep) @@ -111,8 +165,8 @@ func TestBlockSigningRater_UpdateRatingsShouldUpdateRatingWhenProposerButNotAcce rd := createDefaultRatingsData() shardId := uint32(0) - bsr, _ := rating.NewBlockSigningRater(rd) - computedRating := bsr.ComputeDecreaseProposer(shardId, initialRatingValue, 0) + bsr, _ := rating.NewBlockSigningRater(rd, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) + computedRating := bsr.ComputeDecreaseProposer(shardId, initialRatingValue, 0, 0) expectedValue := uint32(int32(initialRatingValue) + proposerDecreaseRatingStep) @@ -124,8 +178,8 @@ func TestBlockSigningRater_UpdateRatingsShouldNotIncreaseAboveMaxRating(t *testi rd := createDefaultRatingsData() shardId := uint32(0) - bsr, _ := rating.NewBlockSigningRater(rd) - computedRating := bsr.ComputeIncreaseProposer(shardId, initialRatingValue) + bsr, _ := rating.NewBlockSigningRater(rd, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) + computedRating := bsr.ComputeIncreaseProposer(shardId, initialRatingValue, 0) expectedValue := maxRating @@ -137,8 +191,8 @@ func TestBlockSigningRater_UpdateRatingsShouldNotDecreaseBelowMinRating(t *testi rd := createDefaultRatingsData() shardId := uint32(0) - bsr, _ := rating.NewBlockSigningRater(rd) - computedRating := bsr.ComputeDecreaseProposer(shardId, initialRatingValue, 0) + bsr, _ := rating.NewBlockSigningRater(rd, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) + computedRating := bsr.ComputeDecreaseProposer(shardId, initialRatingValue, 0, 0) expectedValue := minRating @@ -147,7 +201,7 @@ func TestBlockSigningRater_UpdateRatingsShouldNotDecreaseBelowMinRating(t *testi func TestBlockSigningRater_UpdateRatingsWithMultiplePeersShouldReturnRatings(t *testing.T) { rd := createDefaultRatingsData() - bsr, _ := rating.NewBlockSigningRater(rd) + bsr, _ := rating.NewBlockSigningRater(rd, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) shardId := uint32(0) pk1 := "pk1" @@ -166,10 +220,10 @@ func TestBlockSigningRater_UpdateRatingsWithMultiplePeersShouldReturnRatings(t * ratingsMap[pk3] = pk3Rating ratingsMap[pk4] = pk4Rating - pk1ComputedRating := bsr.ComputeIncreaseProposer(shardId, ratingsMap[pk1]) - pk2ComputedRating := bsr.ComputeDecreaseProposer(shardId, ratingsMap[pk2], 0) - pk3ComputedRating := bsr.ComputeIncreaseValidator(shardId, ratingsMap[pk3]) - pk4ComputedRating := bsr.ComputeDecreaseValidator(shardId, ratingsMap[pk4]) + pk1ComputedRating := bsr.ComputeIncreaseProposer(shardId, ratingsMap[pk1], 0) + pk2ComputedRating := bsr.ComputeDecreaseProposer(shardId, ratingsMap[pk2], 0, 0) + pk3ComputedRating := bsr.ComputeIncreaseValidator(shardId, ratingsMap[pk3], 0) + pk4ComputedRating := bsr.ComputeDecreaseValidator(shardId, ratingsMap[pk4], 0) expectedPk1 := uint32(int32(ratingsMap[pk1]) + proposerIncreaseRatingStep) expectedPk2 := uint32(int32(ratingsMap[pk2]) + proposerDecreaseRatingStep) @@ -184,7 +238,7 @@ func TestBlockSigningRater_UpdateRatingsWithMultiplePeersShouldReturnRatings(t * func TestBlockSigningRater_UpdateRatingsOnMetaWithMultiplePeersShouldReturnRatings(t *testing.T) { rd := createDefaultRatingsData() - bsr, _ := rating.NewBlockSigningRater(rd) + bsr, _ := rating.NewBlockSigningRater(rd, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) pk1 := "pk1" pk2 := "pk2" @@ -202,10 +256,10 @@ func TestBlockSigningRater_UpdateRatingsOnMetaWithMultiplePeersShouldReturnRatin ratingsMap[pk3] = pk3Rating ratingsMap[pk4] = pk4Rating - pk1ComputedRating := bsr.ComputeIncreaseProposer(core.MetachainShardId, ratingsMap[pk1]) - pk2ComputedRating := bsr.ComputeDecreaseProposer(core.MetachainShardId, ratingsMap[pk2], 0) - pk3ComputedRating := bsr.ComputeIncreaseValidator(core.MetachainShardId, ratingsMap[pk3]) - pk4ComputedRating := bsr.ComputeDecreaseValidator(core.MetachainShardId, ratingsMap[pk4]) + pk1ComputedRating := bsr.ComputeIncreaseProposer(core.MetachainShardId, ratingsMap[pk1], 0) + pk2ComputedRating := bsr.ComputeDecreaseProposer(core.MetachainShardId, ratingsMap[pk2], 0, 0) + pk3ComputedRating := bsr.ComputeIncreaseValidator(core.MetachainShardId, ratingsMap[pk3], 0) + pk4ComputedRating := bsr.ComputeDecreaseValidator(core.MetachainShardId, ratingsMap[pk4], 0) expectedPk1 := uint32(int32(ratingsMap[pk1]) + metaProposerIncreaseRatingStep) expectedPk2 := uint32(int32(ratingsMap[pk2]) + metaProposerDecreaseRatingStep) @@ -222,7 +276,7 @@ func TestBlockSigningRater_NewBlockSigningRaterWithChancesNilShouldErr(t *testin ratingsData := createDefaultRatingsData() ratingsData.SelectionChancesProperty = nil - bsr, err := rating.NewBlockSigningRater(ratingsData) + bsr, err := rating.NewBlockSigningRater(ratingsData, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) assert.Nil(t, bsr) assert.Equal(t, process.ErrNoChancesProvided, err) @@ -239,7 +293,7 @@ func TestBlockSigningRater_NewBlockSigningRaterWithDupplicateMaxThresholdShouldE ratingsData := createDefaultRatingsData() ratingsData.SelectionChancesProperty = chances - bsr, err := rating.NewBlockSigningRater(ratingsData) + bsr, err := rating.NewBlockSigningRater(ratingsData, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) assert.Nil(t, bsr) assert.Equal(t, process.ErrDuplicateThreshold, err) @@ -249,7 +303,7 @@ func TestBlockSigningRater_NewBlockSigningRaterWithZeroMinRatingShouldErr(t *tes ratingsData := createDefaultRatingsData() ratingsData.MinRatingProperty = 0 - _, err := rating.NewBlockSigningRater(ratingsData) + _, err := rating.NewBlockSigningRater(ratingsData, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) assert.Equal(t, process.ErrMinRatingSmallerThanOne, err) } @@ -258,7 +312,7 @@ func TestBlockSigningRater_NewBlockSigningRaterWithMinGreaterThanMaxShouldErr(t ratingsData.MinRatingProperty = 100 ratingsData.MaxRatingProperty = 90 - bsr, err := rating.NewBlockSigningRater(ratingsData) + bsr, err := rating.NewBlockSigningRater(ratingsData, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) assert.Nil(t, bsr) assert.True(t, errors.Is(err, process.ErrMaxRatingIsSmallerThanMinRating)) @@ -268,7 +322,7 @@ func TestBlockSigningRater_NewBlockSigningRaterWithSignedBlocksThresholdNegative ratingsData := createDefaultRatingsData() ratingsData.SignedBlocksThresholdProperty = -0.01 - bsr, err := rating.NewBlockSigningRater(ratingsData) + bsr, err := rating.NewBlockSigningRater(ratingsData, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) assert.Nil(t, bsr) assert.True(t, errors.Is(err, process.ErrSignedBlocksThresholdNotBetweenZeroAndOne)) @@ -278,7 +332,7 @@ func TestBlockSigningRater_NewBlockSigningRaterWithSignedBlocksThresholdAbove1Sh ratingsData := createDefaultRatingsData() ratingsData.SignedBlocksThresholdProperty = 1.01 - bsr, err := rating.NewBlockSigningRater(ratingsData) + bsr, err := rating.NewBlockSigningRater(ratingsData, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) assert.Nil(t, bsr) assert.True(t, errors.Is(err, process.ErrSignedBlocksThresholdNotBetweenZeroAndOne)) @@ -295,7 +349,7 @@ func TestBlockSigningRater_NewBlockSigningRaterWithNonExistingMaxThresholdZeroSh ratingsData := createDefaultRatingsData() ratingsData.SelectionChancesProperty = chances - bsr, err := rating.NewBlockSigningRater(ratingsData) + bsr, err := rating.NewBlockSigningRater(ratingsData, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) assert.Nil(t, bsr) assert.Equal(t, process.ErrNilMinChanceIfZero, err) @@ -310,7 +364,7 @@ func TestBlockSigningRater_NewBlockSigningRaterWithNoValueForMaxThresholdShouldE ratingsData := createDefaultRatingsData() ratingsData.SelectionChancesProperty = chances - bsr, err := rating.NewBlockSigningRater(ratingsData) + bsr, err := rating.NewBlockSigningRater(ratingsData, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) assert.Nil(t, bsr) assert.Equal(t, process.ErrNoChancesForMaxThreshold, err) @@ -323,7 +377,7 @@ func TestBlockSigningRater_NewBlockSigningRaterWitStartRatingSmallerThanMinShoul ratingsData.StartRatingProperty = 40 ratingsData.MaxRatingProperty = 100 - bsr, err := rating.NewBlockSigningRater(ratingsData) + bsr, err := rating.NewBlockSigningRater(ratingsData, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) assert.Nil(t, bsr) assert.True(t, errors.Is(err, process.ErrStartRatingNotBetweenMinAndMax)) @@ -335,12 +389,29 @@ func TestBlockSigningRater_NewBlockSigningRaterWitStartRatingGreaterThanMaxdShou ratingsData.StartRatingProperty = 110 ratingsData.MaxRatingProperty = 100 - bsr, err := rating.NewBlockSigningRater(ratingsData) + bsr, err := rating.NewBlockSigningRater(ratingsData, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) assert.Nil(t, bsr) assert.True(t, errors.Is(err, process.ErrStartRatingNotBetweenMinAndMax)) } +func TestBlockSigningRater_NewBlockSigningRaterWithNilEnableEpochsHandlerShouldErr(t *testing.T) { + t.Parallel() + + ratingsData := createDefaultRatingsData() + bsr, err := rating.NewBlockSigningRater(ratingsData, nil) + require.Equal(t, process.ErrNilEnableEpochsHandler, err) + assert.Nil(t, bsr) +} + +func TestBlockSigningRater_NewBlockSigningRaterWithNilRatingsDataShouldErr(t *testing.T) { + t.Parallel() + + bsr, err := rating.NewBlockSigningRater(nil, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) + require.Equal(t, process.ErrNilRatingsInfoHandler, err) + assert.Nil(t, bsr) +} + func TestBlockSigningRater_RevertIncreaseValidator(t *testing.T) { ratingsData := createDefaultRatingsData() startRating := ratingsData.StartRating() @@ -348,25 +419,25 @@ func TestBlockSigningRater_RevertIncreaseValidator(t *testing.T) { validatorIncrease[0] = ratingsData.ShardChainRatingsStepHandler().ValidatorIncreaseRatingStep() validatorIncrease[core.MetachainShardId] = ratingsData.MetaChainRatingsStepHandler().ValidatorIncreaseRatingStep() - bsr, _ := rating.NewBlockSigningRater(ratingsData) + bsr, _ := rating.NewBlockSigningRater(ratingsData, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) for shardId := range validatorIncrease { - zeroReverts := bsr.RevertIncreaseValidator(shardId, ratingsData.StartRating(), 0) + zeroReverts := bsr.RevertIncreaseValidator(shardId, ratingsData.StartRating(), 0, 0) require.Equal(t, startRating, zeroReverts) - oneRevert := bsr.RevertIncreaseValidator(shardId, ratingsData.StartRating(), 1) + oneRevert := bsr.RevertIncreaseValidator(shardId, ratingsData.StartRating(), 1, 0) require.Equal(t, uint32(int32(startRating)-validatorIncrease[shardId]), oneRevert) - tenReverts := bsr.RevertIncreaseValidator(shardId, ratingsData.StartRating(), 10) + tenReverts := bsr.RevertIncreaseValidator(shardId, ratingsData.StartRating(), 10, 0) require.Equal(t, uint32(int32(startRating)-10*validatorIncrease[shardId]), tenReverts) - hundredReverts := bsr.RevertIncreaseValidator(shardId, ratingsData.StartRating(), 100) + hundredReverts := bsr.RevertIncreaseValidator(shardId, ratingsData.StartRating(), 100, 0) require.Equal(t, ratingsData.MinRating(), hundredReverts) - ratingBelowMinRating := bsr.RevertIncreaseValidator(shardId, ratingsData.MinRating()-1, 0) + ratingBelowMinRating := bsr.RevertIncreaseValidator(shardId, ratingsData.MinRating()-1, 0, 0) require.Equal(t, ratingsData.MinRating(), ratingBelowMinRating) - ratingBelowMinRating = bsr.RevertIncreaseValidator(shardId, ratingsData.MinRating()-1, 1) + ratingBelowMinRating = bsr.RevertIncreaseValidator(shardId, ratingsData.MinRating()-1, 1, 0) require.Equal(t, ratingsData.MinRating(), ratingBelowMinRating) } } @@ -387,17 +458,17 @@ func TestBlockSigningRater_RevertIncreaseValidatorWithOverFlow(t *testing.T) { ChancePercent: 10, } - bsr, _ := rating.NewBlockSigningRater(ratingsData) - zeroReverts := bsr.RevertIncreaseValidator(0, ratingsData.StartRating(), 0) + bsr, _ := rating.NewBlockSigningRater(ratingsData, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) + zeroReverts := bsr.RevertIncreaseValidator(0, ratingsData.StartRating(), 0, 0) assert.Equal(t, ratingsData.StartRating(), zeroReverts) - oneRevert := bsr.RevertIncreaseValidator(0, ratingsData.StartRating(), 1) + oneRevert := bsr.RevertIncreaseValidator(0, ratingsData.StartRating(), 1, 0) assert.Equal(t, ratingsData.MinRating(), oneRevert) - overFlowRevert := bsr.RevertIncreaseValidator(0, ratingsData.StartRating(), 2) + overFlowRevert := bsr.RevertIncreaseValidator(0, ratingsData.StartRating(), 2, 0) assert.Equal(t, ratingsData.MinRating(), overFlowRevert) - overFlowRevert = bsr.RevertIncreaseValidator(0, ratingsData.StartRating(), math.MaxUint32) + overFlowRevert = bsr.RevertIncreaseValidator(0, ratingsData.StartRating(), math.MaxUint32, 0) assert.Equal(t, ratingsData.MinRating(), overFlowRevert) } @@ -406,7 +477,7 @@ func TestBlockSigningRater_NewBlockSigningRaterWithCorrectValueShouldWork(t *tes shardRatingsStepHandler := ratingsData.ShardChainRatingsStepHandler() metaRatingsStepHandler := ratingsData.MetaRatingsStepDataProperty - bsr, err := rating.NewBlockSigningRater(ratingsData) + bsr, err := rating.NewBlockSigningRater(ratingsData, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) assert.NotNil(t, bsr) assert.Nil(t, err) @@ -415,18 +486,18 @@ func TestBlockSigningRater_NewBlockSigningRaterWithCorrectValueShouldWork(t *tes assert.Equal(t, ratingsData.StartRating(), bsr.GetStartRating()) assert.Equal(t, ratingsData.SignedBlocksThreshold(), bsr.GetSignedBlocksThreshold()) - assert.Equal(t, uint32(testValue+shardRatingsStepHandler.ValidatorIncreaseRatingStep()), bsr.ComputeIncreaseValidator(0, uint32(testValue))) - assert.Equal(t, uint32(testValue+shardRatingsStepHandler.ValidatorDecreaseRatingStep()), bsr.ComputeDecreaseValidator(0, uint32(testValue))) - assert.Equal(t, uint32(testValue+shardRatingsStepHandler.ProposerIncreaseRatingStep()), bsr.ComputeIncreaseProposer(0, uint32(testValue))) - assert.Equal(t, uint32(testValue+shardRatingsStepHandler.ProposerDecreaseRatingStep()), bsr.ComputeDecreaseProposer(0, uint32(testValue), 0)) - assert.Equal(t, uint32(testValue-shardRatingsStepHandler.ValidatorIncreaseRatingStep()), bsr.RevertIncreaseValidator(0, uint32(testValue), 1)) + assert.Equal(t, uint32(testValue+shardRatingsStepHandler.ValidatorIncreaseRatingStep()), bsr.ComputeIncreaseValidator(0, uint32(testValue), 0)) + assert.Equal(t, uint32(testValue+shardRatingsStepHandler.ValidatorDecreaseRatingStep()), bsr.ComputeDecreaseValidator(0, uint32(testValue), 0)) + assert.Equal(t, uint32(testValue+shardRatingsStepHandler.ProposerIncreaseRatingStep()), bsr.ComputeIncreaseProposer(0, uint32(testValue), 0)) + assert.Equal(t, uint32(testValue+shardRatingsStepHandler.ProposerDecreaseRatingStep()), bsr.ComputeDecreaseProposer(0, uint32(testValue), 0, 0)) + assert.Equal(t, uint32(testValue-shardRatingsStepHandler.ValidatorIncreaseRatingStep()), bsr.RevertIncreaseValidator(0, uint32(testValue), 1, 0)) assert.Equal(t, ratingsData.StartRating(), bsr.GetStartRating()) - assert.Equal(t, uint32(testValue+metaRatingsStepHandler.ValidatorIncreaseRatingStep()), bsr.ComputeIncreaseValidator(core.MetachainShardId, uint32(testValue))) - assert.Equal(t, uint32(testValue+metaRatingsStepHandler.ValidatorDecreaseRatingStep()), bsr.ComputeDecreaseValidator(core.MetachainShardId, uint32(testValue))) - assert.Equal(t, uint32(testValue+metaRatingsStepHandler.ProposerIncreaseRatingStep()), bsr.ComputeIncreaseProposer(core.MetachainShardId, uint32(testValue))) - assert.Equal(t, uint32(testValue+metaRatingsStepHandler.ProposerDecreaseRatingStep()), bsr.ComputeDecreaseProposer(core.MetachainShardId, uint32(testValue), 0)) - assert.Equal(t, uint32(testValue-metaRatingsStepHandler.ValidatorIncreaseRatingStep()), bsr.RevertIncreaseValidator(core.MetachainShardId, uint32(testValue), 1)) + assert.Equal(t, uint32(testValue+metaRatingsStepHandler.ValidatorIncreaseRatingStep()), bsr.ComputeIncreaseValidator(core.MetachainShardId, uint32(testValue), 0)) + assert.Equal(t, uint32(testValue+metaRatingsStepHandler.ValidatorDecreaseRatingStep()), bsr.ComputeDecreaseValidator(core.MetachainShardId, uint32(testValue), 0)) + assert.Equal(t, uint32(testValue+metaRatingsStepHandler.ProposerIncreaseRatingStep()), bsr.ComputeIncreaseProposer(core.MetachainShardId, uint32(testValue), 0)) + assert.Equal(t, uint32(testValue+metaRatingsStepHandler.ProposerDecreaseRatingStep()), bsr.ComputeDecreaseProposer(core.MetachainShardId, uint32(testValue), 0, 0)) + assert.Equal(t, uint32(testValue-metaRatingsStepHandler.ValidatorIncreaseRatingStep()), bsr.RevertIncreaseValidator(core.MetachainShardId, uint32(testValue), 1, 0)) assert.Equal(t, ratingsData.SelectionChances()[0].GetChancePercent(), bsr.GetChance(uint32(0))) assert.Equal(t, ratingsData.SelectionChances()[1].GetChancePercent(), bsr.GetChance(uint32(9))) @@ -440,7 +511,7 @@ func TestBlockSigningRater_NewBlockSigningRaterWithCorrectValueShouldWork(t *tes func TestBlockSigningRater_GetChancesForStartRatingdReturnStartRatingChance(t *testing.T) { ratingsData := createDefaultRatingsData() - bsr, _ := rating.NewBlockSigningRater(ratingsData) + bsr, _ := rating.NewBlockSigningRater(ratingsData, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) chance := bsr.GetChance(startRating) @@ -457,7 +528,7 @@ func TestBlockSigningRater_GetChancesForSetRatingShouldReturnCorrectRating(t *te &rating.SelectionChance{MaxThreshold: 75, ChancePercent: 100}, &rating.SelectionChance{MaxThreshold: 100, ChancePercent: 110}, } - bsr, _ := rating.NewBlockSigningRater(rd) + bsr, _ := rating.NewBlockSigningRater(rd, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) ratingValue := uint32(80) chances := bsr.GetChance(ratingValue) @@ -477,7 +548,7 @@ func TestBlockSigningRater_GetChancesForSetRatingShouldReturnCorrectRatingForThr &rating.SelectionChance{MaxThreshold: 100, ChancePercent: 110}, } - bsr, _ := rating.NewBlockSigningRater(rd) + bsr, _ := rating.NewBlockSigningRater(rd, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) ratingValue := uint32(75) chances := bsr.GetChance(ratingValue) @@ -491,7 +562,7 @@ func TestBlockSigningRater_PositiveDecreaseRatingStep(t *testing.T) { ratingStep := createRatingStepMock() ratingStep.ProposerDecreaseRatingStepProperty = 0 rd.MetaRatingsStepDataProperty = ratingStep - bsr, err := rating.NewBlockSigningRater(rd) + bsr, err := rating.NewBlockSigningRater(rd, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) require.Nil(t, bsr) require.True(t, errors.Is(err, process.ErrDecreaseRatingsStepMoreThanMinusOne)) require.True(t, strings.Contains(err.Error(), "meta")) @@ -500,7 +571,7 @@ func TestBlockSigningRater_PositiveDecreaseRatingStep(t *testing.T) { ratingStep = createRatingStepMock() ratingStep.ValidatorDecreaseRatingStepProperty = 0 rd.MetaRatingsStepDataProperty = ratingStep - bsr, err = rating.NewBlockSigningRater(rd) + bsr, err = rating.NewBlockSigningRater(rd, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) require.Nil(t, bsr) require.True(t, errors.Is(err, process.ErrDecreaseRatingsStepMoreThanMinusOne)) require.True(t, strings.Contains(err.Error(), "meta")) @@ -509,7 +580,7 @@ func TestBlockSigningRater_PositiveDecreaseRatingStep(t *testing.T) { ratingStep = createRatingStepMock() ratingStep.ProposerDecreaseRatingStepProperty = 0 rd.ShardRatingsStepDataProperty = ratingStep - bsr, err = rating.NewBlockSigningRater(rd) + bsr, err = rating.NewBlockSigningRater(rd, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) require.Nil(t, bsr) require.True(t, errors.Is(err, process.ErrDecreaseRatingsStepMoreThanMinusOne)) require.True(t, strings.Contains(err.Error(), "shard")) @@ -518,7 +589,7 @@ func TestBlockSigningRater_PositiveDecreaseRatingStep(t *testing.T) { ratingStep = createRatingStepMock() ratingStep.ValidatorDecreaseRatingStepProperty = 0 rd.ShardRatingsStepDataProperty = ratingStep - bsr, err = rating.NewBlockSigningRater(rd) + bsr, err = rating.NewBlockSigningRater(rd, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) require.Nil(t, bsr) require.True(t, errors.Is(err, process.ErrDecreaseRatingsStepMoreThanMinusOne)) require.True(t, strings.Contains(err.Error(), "shard")) @@ -529,7 +600,7 @@ func TestBlockSigningRater_ConsecutiveBlocksPenaltyLessThanOne(t *testing.T) { ratingStep := createRatingStepMock() ratingStep.ConsecutiveMissedBlocksPenaltyProperty = 0.5 rd.MetaRatingsStepDataProperty = ratingStep - bsr, err := rating.NewBlockSigningRater(rd) + bsr, err := rating.NewBlockSigningRater(rd, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) require.Nil(t, bsr) require.True(t, errors.Is(err, process.ErrConsecutiveMissedBlocksPenaltyLowerThanOne)) require.True(t, strings.Contains(err.Error(), "meta")) @@ -538,7 +609,7 @@ func TestBlockSigningRater_ConsecutiveBlocksPenaltyLessThanOne(t *testing.T) { ratingStep = createRatingStepMock() ratingStep.ConsecutiveMissedBlocksPenaltyProperty = 0.5 rd.ShardRatingsStepDataProperty = ratingStep - bsr, err = rating.NewBlockSigningRater(rd) + bsr, err = rating.NewBlockSigningRater(rd, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) require.Nil(t, bsr) require.True(t, errors.Is(err, process.ErrConsecutiveMissedBlocksPenaltyLowerThanOne)) require.True(t, strings.Contains(err.Error(), "shard")) @@ -580,31 +651,31 @@ func TestBlockSigningRater_ComputeDecreaseProposer(t *testing.T) { penalty[0] = ratingsData.ShardChainRatingsStepHandler().ConsecutiveMissedBlocksPenalty() penalty[core.MetachainShardId] = ratingsData.MetaChainRatingsStepHandler().ConsecutiveMissedBlocksPenalty() - bsr, _ := rating.NewBlockSigningRater(ratingsData) + bsr, _ := rating.NewBlockSigningRater(ratingsData, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) var consecutiveMisses uint32 for shardId := range proposerDecrease { consecutiveMisses = 0 - zeroConsecutive := bsr.ComputeDecreaseProposer(shardId, ratingsData.StartRating(), consecutiveMisses) + zeroConsecutive := bsr.ComputeDecreaseProposer(shardId, ratingsData.StartRating(), consecutiveMisses, 0) decreaseStep := float64(proposerDecrease[shardId]) * math.Pow(float64(penalty[shardId]), float64(consecutiveMisses)) require.Equal(t, uint32(int32(startRating)+int32(decreaseStep)), zeroConsecutive) consecutiveMisses = 1 - oneConsecutive := bsr.ComputeDecreaseProposer(shardId, ratingsData.StartRating(), consecutiveMisses) + oneConsecutive := bsr.ComputeDecreaseProposer(shardId, ratingsData.StartRating(), consecutiveMisses, 0) decreaseStep = float64(proposerDecrease[shardId]) * math.Pow(float64(penalty[shardId]), float64(consecutiveMisses)) require.Equal(t, uint32(int32(startRating)+int32(decreaseStep)), oneConsecutive) consecutiveMisses = 5 - fiveConsecutive := bsr.ComputeDecreaseProposer(shardId, ratingsData.StartRating(), consecutiveMisses) + fiveConsecutive := bsr.ComputeDecreaseProposer(shardId, ratingsData.StartRating(), consecutiveMisses, 0) decreaseStep = float64(proposerDecrease[shardId]) * math.Pow(float64(penalty[shardId]), float64(consecutiveMisses)) require.Equal(t, uint32(int32(startRating)+int32(decreaseStep)), fiveConsecutive) consecutiveMisses = 0 - ratingBelowMinRating := bsr.ComputeDecreaseProposer(shardId, ratingsData.MinRating()-1, consecutiveMisses) + ratingBelowMinRating := bsr.ComputeDecreaseProposer(shardId, ratingsData.MinRating()-1, consecutiveMisses, 0) require.Equal(t, ratingsData.MinRating(), ratingBelowMinRating) consecutiveMisses = 1 - ratingBelowMinRating = bsr.ComputeDecreaseProposer(shardId, ratingsData.MinRating()-1, consecutiveMisses) + ratingBelowMinRating = bsr.ComputeDecreaseProposer(shardId, ratingsData.MinRating()-1, consecutiveMisses, 0) require.Equal(t, ratingsData.MinRating(), ratingBelowMinRating) } } @@ -629,30 +700,30 @@ func TestBlockSigningRater_ComputeDecreaseProposerWithOverFlow(t *testing.T) { consecutiveMissedBlocksPenalty := ratingsData.ShardChainRatingsStepHandler().ConsecutiveMissedBlocksPenalty() startRating := ratingsData.StartRating() - bsr, _ := rating.NewBlockSigningRater(ratingsData) + bsr, _ := rating.NewBlockSigningRater(ratingsData, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}) var consecutiveMisses uint32 consecutiveMisses = 0 - zeroMisses := bsr.ComputeDecreaseProposer(0, ratingsData.StartRating(), consecutiveMisses) + zeroMisses := bsr.ComputeDecreaseProposer(0, ratingsData.StartRating(), consecutiveMisses, 0) decreaseStep := float64(proposerDecreaseRatingStep) * math.Pow(float64(consecutiveMissedBlocksPenalty), float64(consecutiveMisses)) assert.Equal(t, uint32(int32(startRating)+int32(decreaseStep)), zeroMisses) consecutiveMisses = 1 - oneMisses := bsr.ComputeDecreaseProposer(0, ratingsData.StartRating(), consecutiveMisses) + oneMisses := bsr.ComputeDecreaseProposer(0, ratingsData.StartRating(), consecutiveMisses, 0) decreaseStep = float64(proposerDecreaseRatingStep) * math.Pow(float64(consecutiveMissedBlocksPenalty), float64(consecutiveMisses)) assert.Equal(t, uint32(int32(startRating)+int32(decreaseStep)), oneMisses) consecutiveMisses = 2 - twoMisses := bsr.ComputeDecreaseProposer(0, ratingsData.StartRating(), consecutiveMisses) + twoMisses := bsr.ComputeDecreaseProposer(0, ratingsData.StartRating(), consecutiveMisses, 0) decreaseStep = float64(proposerDecreaseRatingStep) * math.Pow(float64(consecutiveMissedBlocksPenalty), float64(consecutiveMisses)) assert.Equal(t, uint32(int32(startRating)+int32(decreaseStep)), twoMisses) consecutiveMisses = 10 - tenMisses := bsr.ComputeDecreaseProposer(0, ratingsData.StartRating(), consecutiveMisses) + tenMisses := bsr.ComputeDecreaseProposer(0, ratingsData.StartRating(), consecutiveMisses, 0) assert.Equal(t, ratingsData.MinRating(), tenMisses) consecutiveMisses = 100 - maxMisses := bsr.ComputeDecreaseProposer(0, ratingsData.StartRating(), consecutiveMisses) + maxMisses := bsr.ComputeDecreaseProposer(0, ratingsData.StartRating(), consecutiveMisses, 0) assert.Equal(t, ratingsData.MinRating(), maxMisses) } diff --git a/process/rating/ratingsData.go b/process/rating/ratingsData.go index d602745e088..843bbfa2a4b 100644 --- a/process/rating/ratingsData.go +++ b/process/rating/ratingsData.go @@ -40,27 +40,25 @@ type computeRatingStepArg struct { // RatingsData will store information about ratingsComputation type RatingsData struct { - startRating uint32 - maxRating uint32 - minRating uint32 - signedBlocksThreshold float32 - currentRatingsStepData ratingsStepsData - ratingsStepsConfig []ratingsStepsData - selectionChances []process.SelectionChance - chainParametersHandler process.ChainParametersHandler - ratingsSetup config.RatingsConfig - roundDurationInMilliseconds uint64 - mutConfiguration sync.RWMutex - statusHandler core.AppStatusHandler - mutStatusHandler sync.RWMutex + startRating uint32 + maxRating uint32 + minRating uint32 + signedBlocksThreshold float32 + currentRatingsStepData ratingsStepsData + ratingsStepsConfig []ratingsStepsData + selectionChances []process.SelectionChance + chainParametersHandler process.ChainParametersHandler + ratingsSetup config.RatingsConfig + mutConfiguration sync.RWMutex + statusHandler core.AppStatusHandler + mutStatusHandler sync.RWMutex } // RatingsDataArg contains information for the creation of the new ratingsData type RatingsDataArg struct { - EpochNotifier process.EpochNotifier - Config config.RatingsConfig - ChainParametersHolder process.ChainParametersHandler - RoundDurationMilliseconds uint64 + EpochNotifier process.EpochNotifier + Config config.RatingsConfig + ChainParametersHolder process.ChainParametersHandler } // NewRatingsData creates a new RatingsData instance @@ -102,7 +100,7 @@ func NewRatingsData(args RatingsDataArg) (*RatingsData, error) { arg := computeRatingStepArg{ shardSize: currentChainParameters.ShardMinNumNodes, consensusSize: currentChainParameters.ShardConsensusGroupSize, - roundTimeMillis: args.RoundDurationMilliseconds, + roundTimeMillis: currentChainParameters.RoundDuration, startRating: ratingsConfig.General.StartRating, maxRating: ratingsConfig.General.MaxRating, hoursToMaxRatingFromStartRating: shardChainRatingSteps.HoursToMaxRatingFromStartRating, @@ -120,7 +118,7 @@ func NewRatingsData(args RatingsDataArg) (*RatingsData, error) { arg = computeRatingStepArg{ shardSize: currentChainParameters.MetachainMinNumNodes, consensusSize: currentChainParameters.MetachainConsensusGroupSize, - roundTimeMillis: args.RoundDurationMilliseconds, + roundTimeMillis: currentChainParameters.RoundDuration, startRating: ratingsConfig.General.StartRating, maxRating: ratingsConfig.General.MaxRating, hoursToMaxRatingFromStartRating: metaChainRatingSteps.HoursToMaxRatingFromStartRating, @@ -141,16 +139,15 @@ func NewRatingsData(args RatingsDataArg) (*RatingsData, error) { } ratingData := &RatingsData{ - startRating: ratingsConfig.General.StartRating, - maxRating: ratingsConfig.General.MaxRating, - minRating: ratingsConfig.General.MinRating, - signedBlocksThreshold: ratingsConfig.General.SignedBlocksThreshold, - currentRatingsStepData: ratingsConfigValue, - selectionChances: chances, - chainParametersHandler: args.ChainParametersHolder, - ratingsSetup: ratingsConfig, - roundDurationInMilliseconds: args.RoundDurationMilliseconds, - statusHandler: statusHandler.NewNilStatusHandler(), + startRating: ratingsConfig.General.StartRating, + maxRating: ratingsConfig.General.MaxRating, + minRating: ratingsConfig.General.MinRating, + signedBlocksThreshold: ratingsConfig.General.SignedBlocksThreshold, + currentRatingsStepData: ratingsConfigValue, + selectionChances: chances, + chainParametersHandler: args.ChainParametersHolder, + ratingsSetup: ratingsConfig, + statusHandler: statusHandler.NewNilStatusHandler(), } err = ratingData.computeRatingStepsConfig(args.ChainParametersHolder.AllChainParameters()) @@ -229,7 +226,7 @@ func (rd *RatingsData) computeRatingStepsConfigForEpoch( shardRatingsStepsArgs := computeRatingStepArg{ shardSize: chainParams.ShardMinNumNodes, consensusSize: chainParams.ShardConsensusGroupSize, - roundTimeMillis: rd.roundDurationInMilliseconds, + roundTimeMillis: chainParams.RoundDuration, startRating: rd.ratingsSetup.General.StartRating, maxRating: rd.ratingsSetup.General.MaxRating, hoursToMaxRatingFromStartRating: shardChainRatingSteps.HoursToMaxRatingFromStartRating, @@ -247,7 +244,7 @@ func (rd *RatingsData) computeRatingStepsConfigForEpoch( metaRatingsStepsArgs := computeRatingStepArg{ shardSize: chainParams.MetachainMinNumNodes, consensusSize: chainParams.MetachainConsensusGroupSize, - roundTimeMillis: rd.roundDurationInMilliseconds, + roundTimeMillis: chainParams.RoundDuration, startRating: rd.ratingsSetup.General.StartRating, maxRating: rd.ratingsSetup.General.MaxRating, hoursToMaxRatingFromStartRating: metaChainRatingSteps.HoursToMaxRatingFromStartRating, @@ -307,6 +304,10 @@ func (rd *RatingsData) EpochConfirmed(epoch uint32, _ uint64) { } func (rd *RatingsData) getMatchingVersion(epoch uint32) (ratingsStepsData, error) { + if rd.currentRatingsStepData.enableEpoch == epoch { + return rd.currentRatingsStepData, nil + } + // the config values are sorted in descending order, so the matching version is the first one whose enable epoch is less or equal than the provided epoch for _, ratingsStepConfig := range rd.ratingsStepsConfig { if ratingsStepConfig.enableEpoch <= epoch { @@ -466,6 +467,20 @@ func (rd *RatingsData) MetaChainRatingsStepHandler() process.RatingsStepHandler return rd.currentRatingsStepData.metaRatingsStepData } +// MetaChainRatingsStepHandlerForEpoch returns the RatingsStepHandler used for the Metachain in a specific epoch +func (rd *RatingsData) MetaChainRatingsStepHandlerForEpoch(epoch uint32) process.RatingsStepHandler { + rd.mutConfiguration.RLock() + defer rd.mutConfiguration.RUnlock() + + stepsForEpoch, err := rd.getMatchingVersion(epoch) + if err != nil { + log.Warn("%w, MetaChainRatingsStepHandlerForEpoch failed to get matching version for epoch %d, returning current one", err, epoch) + return rd.currentRatingsStepData.shardRatingsStepData + } + + return stepsForEpoch.metaRatingsStepData +} + // ShardChainRatingsStepHandler returns the RatingsStepHandler used for the ShardChains func (rd *RatingsData) ShardChainRatingsStepHandler() process.RatingsStepHandler { rd.mutConfiguration.RLock() @@ -474,6 +489,20 @@ func (rd *RatingsData) ShardChainRatingsStepHandler() process.RatingsStepHandler return rd.currentRatingsStepData.shardRatingsStepData } +// ShardChainRatingsStepHandlerForEpoch returns the RatingsStepHandler used for the ShardChains in a specific epoch +func (rd *RatingsData) ShardChainRatingsStepHandlerForEpoch(epoch uint32) process.RatingsStepHandler { + rd.mutConfiguration.RLock() + defer rd.mutConfiguration.RUnlock() + + stepsForEpoch, err := rd.getMatchingVersion(epoch) + if err != nil { + log.Warn("%w, ShardChainRatingsStepHandlerForEpoch failed to get matching version for epoch %d, returning current one", err, epoch) + return rd.currentRatingsStepData.shardRatingsStepData + } + + return stepsForEpoch.shardRatingsStepData +} + // SetStatusHandler sets the provided status handler if not nil func (rd *RatingsData) SetStatusHandler(handler core.AppStatusHandler) error { if check.IfNil(handler) { diff --git a/process/rating/ratingsData_test.go b/process/rating/ratingsData_test.go index 4ac3ced60fd..f1cd9b80119 100644 --- a/process/rating/ratingsData_test.go +++ b/process/rating/ratingsData_test.go @@ -42,7 +42,7 @@ func createDummyRatingsData() RatingsDataArg { ChainParametersHolder: &chainParameters.ChainParametersHandlerStub{ CurrentChainParametersCalled: func() config.ChainParametersByEpochConfig { return config.ChainParametersByEpochConfig{ - RoundDuration: 4000, + RoundDuration: roundDurationMilliseconds, Hysteresis: 0.2, EnableEpoch: 0, ShardConsensusGroupSize: shardConsensusSize, @@ -55,7 +55,7 @@ func createDummyRatingsData() RatingsDataArg { AllChainParametersCalled: func() []config.ChainParametersByEpochConfig { return []config.ChainParametersByEpochConfig{ { - RoundDuration: 4000, + RoundDuration: roundDurationMilliseconds, Hysteresis: 0.2, EnableEpoch: 0, ShardConsensusGroupSize: shardConsensusSize, @@ -67,8 +67,7 @@ func createDummyRatingsData() RatingsDataArg { } }, }, - RoundDurationMilliseconds: roundDurationMilliseconds, - EpochNotifier: &epochNotifier.EpochNotifierStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, } } @@ -423,7 +422,7 @@ func TestRatingsData_EpochConfirmed(t *testing.T) { chainParams := make([]config.ChainParametersByEpochConfig, 0) for i := uint32(0); i <= 15; i += 5 { newChainParams := config.ChainParametersByEpochConfig{ - RoundDuration: 4000, + RoundDuration: roundDurationMilliseconds, Hysteresis: 0.2, EnableEpoch: i, ShardConsensusGroupSize: shardConsensusSize, @@ -775,10 +774,12 @@ func TestRatingsData_RatingsCorrectValues(t *testing.T) { assert.Equal(t, maxRating, ratingsData.MaxRating()) assert.Equal(t, signedBlocksThreshold, ratingsData.SignedBlocksThreshold()) assert.Equal(t, shardValidatorIncreaseRatingStep, ratingsData.ShardChainRatingsStepHandler().ValidatorIncreaseRatingStep()) + assert.Equal(t, shardValidatorIncreaseRatingStep, ratingsData.ShardChainRatingsStepHandlerForEpoch(0).ValidatorIncreaseRatingStep()) assert.Equal(t, shardValidatorDecreaseRatingStep, ratingsData.ShardChainRatingsStepHandler().ValidatorDecreaseRatingStep()) assert.Equal(t, shardProposerIncreaseRatingStep, ratingsData.ShardChainRatingsStepHandler().ProposerIncreaseRatingStep()) assert.Equal(t, shardProposerDecreaseRatingStep, ratingsData.ShardChainRatingsStepHandler().ProposerDecreaseRatingStep()) assert.Equal(t, metaValidatorIncreaseRatingStep, ratingsData.MetaChainRatingsStepHandler().ValidatorIncreaseRatingStep()) + assert.Equal(t, metaValidatorIncreaseRatingStep, ratingsData.MetaChainRatingsStepHandlerForEpoch(0).ValidatorIncreaseRatingStep()) assert.Equal(t, metaValidatorDecreaseRatingStep, ratingsData.MetaChainRatingsStepHandler().ValidatorDecreaseRatingStep()) assert.Equal(t, metaProposerIncreaseRatingStep, ratingsData.MetaChainRatingsStepHandler().ProposerIncreaseRatingStep()) assert.Equal(t, metaProposerDecreaseRatingStep, ratingsData.MetaChainRatingsStepHandler().ProposerDecreaseRatingStep()) diff --git a/process/receipts/errors.go b/process/receipts/errors.go index 1bc334e99ca..68113fe7b75 100644 --- a/process/receipts/errors.go +++ b/process/receipts/errors.go @@ -10,3 +10,5 @@ var errCannotLoadReceipts = errors.New("cannot load receipts") var errNilReceiptsHolder = errors.New("nil receipts holder") var errNilBlockHeader = errors.New("nil block header") var errEmptyBlockHash = errors.New("empty block hash") +var errNilExecutionResult = errors.New("nil execution result") +var errNilInvalidExecutionResultType = errors.New("invalid execution result type") diff --git a/process/receipts/receiptsRepository.go b/process/receipts/receiptsRepository.go index 6dec9bb5982..822adf53214 100644 --- a/process/receipts/receiptsRepository.go +++ b/process/receipts/receiptsRepository.go @@ -9,12 +9,14 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/batch" + "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/storage" - logger "github.com/multiversx/mx-chain-logger-go" ) var log = logger.GetOrCreate("process/receipts") @@ -69,10 +71,15 @@ func (repository *receiptsRepository) SaveReceipts(holder common.ReceiptsHolder, return errEmptyBlockHash } - storageKey := repository.decideStorageKey(header.GetReceiptsHash(), headerHash) - + receiptHash := header.GetReceiptsHash() + storageKey := repository.decideStorageKey(receiptHash, headerHash) log.Debug("receiptsRepository.SaveReceipts()", "headerNonce", header.GetNonce(), "storageKey", storageKey) + return repository.saveReceipts(storageKey, holder) + +} + +func (repository *receiptsRepository) saveReceipts(storageKey []byte, holder common.ReceiptsHolder) error { receiptsBytes, err := marshalReceiptsHolder(holder, repository.marshaller) if err != nil { return err @@ -91,9 +98,45 @@ func (repository *receiptsRepository) SaveReceipts(holder common.ReceiptsHolder, return nil } +// SaveReceiptsForExecResult saves the receipts in the storer (receipts unit) for a given execution result +func (repository *receiptsRepository) SaveReceiptsForExecResult(holder common.ReceiptsHolder, execResult data.BaseExecutionResultHandler) error { + if check.IfNil(holder) { + return errNilReceiptsHolder + } + if check.IfNil(execResult) { + return errNilExecutionResult + } + + receiptHash, err := getReceiptHashFromBaseExecutionResult(execResult) + if err != nil { + return err + } + + storageKey := repository.decideStorageKey(receiptHash, execResult.GetHeaderHash()) + log.Debug("receiptsRepository.SaveReceiptsForExecResult()", "executionResult nonce", execResult.GetHeaderNonce(), "storageKey", storageKey) + + return repository.saveReceipts(storageKey, holder) +} + +// TODO: maybe move to common +func getReceiptHashFromBaseExecutionResult(execResult data.BaseExecutionResultHandler) ([]byte, error) { + if check.IfNil(execResult) { + return nil, errNilExecutionResult + } + + switch er := execResult.(type) { + case *block.ExecutionResult: + return er.GetReceiptsHash(), nil + case *block.MetaExecutionResult: + return er.GetReceiptsHash(), nil + default: + return nil, errNilInvalidExecutionResultType + } +} + // LoadReceipts loads the receipts, given a block header -func (repository *receiptsRepository) LoadReceipts(header data.HeaderHandler, headerHash []byte) (common.ReceiptsHolder, error) { - storageKey := repository.decideStorageKey(header.GetReceiptsHash(), headerHash) +func (repository *receiptsRepository) LoadReceipts(receiptsHash []byte, header data.HeaderHandler, headerHash []byte) (common.ReceiptsHolder, error) { + storageKey := repository.decideStorageKey(receiptsHash, headerHash) batchBytes, err := repository.storer.GetFromEpoch(storageKey, header.GetEpoch()) if err != nil { diff --git a/process/receipts/receiptsRepository_test.go b/process/receipts/receiptsRepository_test.go index 7d5cdd37030..bb949f648d6 100644 --- a/process/receipts/receiptsRepository_test.go +++ b/process/receipts/receiptsRepository_test.go @@ -213,19 +213,19 @@ func TestReceiptsRepository_LoadReceipts(t *testing.T) { _ = store.Put(dataRetriever.ReceiptsUnit, nonEmptyReceiptsHash, receiptsAtKeyReceiptsHashBytes) t.Run("when header.GetReceiptsHash() == emptyReceiptsHash", func(t *testing.T) { - loaded, err := repository.LoadReceipts(&block.Header{ReceiptsHash: emptyReceiptsHash}, headerHash) + loaded, err := repository.LoadReceipts(emptyReceiptsHash, &block.Header{ReceiptsHash: emptyReceiptsHash}, headerHash) require.Nil(t, err) require.Equal(t, receiptsAtKeyHeaderHash, loaded) }) t.Run("when header.GetReceiptsHash() != emptyReceiptsHash", func(t *testing.T) { - loaded, err := repository.LoadReceipts(&block.Header{ReceiptsHash: nonEmptyReceiptsHash}, headerHash) + loaded, err := repository.LoadReceipts(nonEmptyReceiptsHash, &block.Header{ReceiptsHash: nonEmptyReceiptsHash}, headerHash) require.Nil(t, err) require.Equal(t, receiptsAtKeyReceiptsHash, loaded) }) t.Run("when no receipts for given header", func(t *testing.T) { - loadedHolder, err := repository.LoadReceipts(&block.Header{ReceiptsHash: emptyReceiptsHash}, []byte("abba")) + loadedHolder, err := repository.LoadReceipts(emptyReceiptsHash, &block.Header{ReceiptsHash: emptyReceiptsHash}, []byte("abba")) require.Nil(t, err) require.Equal(t, createEmptyReceiptsHolder(), loadedHolder) }) @@ -261,7 +261,7 @@ func TestReceiptsRepository_NoPanicOnSaveOrLoadWhenBadStorage(t *testing.T) { t.Run("load from bad storage", func(t *testing.T) { header := &block.Header{ReceiptsHash: []byte("aaaa")} - loaded, err := repository.LoadReceipts(header, []byte("bbbb")) + loaded, err := repository.LoadReceipts(header.ReceiptsHash, header, []byte("bbbb")) require.NotNil(t, err) require.ErrorIs(t, err, errCannotLoadReceipts) require.Nil(t, loaded) @@ -296,3 +296,138 @@ func TestCreateEmptyReceiptsHash(t *testing.T) { require.Nil(t, err) require.Equal(t, "0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8", hex.EncodeToString(emptyReceiptsHash)) } + +func Test_SaveReceiptsForExecResult(t *testing.T) { + t.Parallel() + + t.Run("should return errNilReceiptsHolder in case of nil holder", func(t *testing.T) { + t.Parallel() + + counter := 0 + repository, err := NewReceiptsRepository(ArgsNewReceiptsRepository{ + Marshaller: marshallerMock.MarshalizerMock{}, + Hasher: &testscommon.HasherStub{}, + Store: &testsCommonStorage.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &testsCommonStorage.StorerStub{ + PutCalled: func(key, data []byte) error { + counter++ + return nil + }, + }, nil + }, + }, + }) + require.Nil(t, err) + + err = repository.SaveReceiptsForExecResult(nil, &block.BaseExecutionResult{}) + require.Equal(t, errNilReceiptsHolder, err) + require.Equal(t, 0, counter) + }) + + t.Run("should return errNilExecutionResult in case of nil execution result", func(t *testing.T) { + t.Parallel() + + counter := 0 + repository, err := NewReceiptsRepository(ArgsNewReceiptsRepository{ + Marshaller: marshallerMock.MarshalizerMock{}, + Hasher: &testscommon.HasherStub{}, + Store: &testsCommonStorage.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &testsCommonStorage.StorerStub{ + PutCalled: func(key, data []byte) error { + counter++ + return nil + }, + }, nil + }, + }, + }) + require.Nil(t, err) + + err = repository.SaveReceiptsForExecResult(holders.NewReceiptsHolder(nil), nil) + require.Equal(t, errNilExecutionResult, err) + require.Equal(t, 0, counter) + }) + + t.Run("should return errNilInvalidExecutionResultType in case of wrong type of exec result", func(t *testing.T) { + t.Parallel() + + counter := 0 + repository, err := NewReceiptsRepository(ArgsNewReceiptsRepository{ + Marshaller: marshallerMock.MarshalizerMock{}, + Hasher: &testscommon.HasherStub{}, + Store: &testsCommonStorage.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &testsCommonStorage.StorerStub{ + PutCalled: func(key, data []byte) error { + counter++ + return nil + }, + }, nil + }, + }, + }) + require.Nil(t, err) + + err = repository.SaveReceiptsForExecResult(holders.NewReceiptsHolder(nil), &block.BaseMetaExecutionResult{}) + require.Equal(t, errNilInvalidExecutionResultType, err) + require.Equal(t, 0, counter) + }) + + t.Run("if saving receipts fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + expectedError := errors.New("expected error") + repository, err := NewReceiptsRepository(ArgsNewReceiptsRepository{ + Hasher: &testscommon.HasherStub{}, + Store: genericMocks.NewChainStorerMock(0), + Marshaller: marshallerMock.MarshalizerMock{}, + }) + require.Nil(t, err) + + repository.marshaller = &testscommon.MarshallerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + return nil, expectedError + }, + } + err = repository.SaveReceiptsForExecResult( + holders.NewReceiptsHolder([]*block.MiniBlock{ + {}, + }), + &block.ExecutionResult{ + ReceiptsHash: []byte("receiptsHash"), + }) + require.ErrorContains(t, err, expectedError.Error()) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + counter := 0 + repository, err := NewReceiptsRepository(ArgsNewReceiptsRepository{ + Hasher: &testscommon.HasherStub{}, + Store: &testsCommonStorage.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &testsCommonStorage.StorerStub{ + PutCalled: func(key, data []byte) error { + counter++ + return nil + }, + }, nil + }, + }, Marshaller: marshallerMock.MarshalizerMock{}, + }) + require.Nil(t, err) + + err = repository.SaveReceiptsForExecResult( + holders.NewReceiptsHolder([]*block.MiniBlock{ + {}, + }), + &block.ExecutionResult{ + ReceiptsHash: []byte("receiptsHash"), + }) + require.Nil(t, err) + require.Equal(t, 1, counter) + }) +} diff --git a/process/rewardTransaction/interceptedRewardTransaction.go b/process/rewardTransaction/interceptedRewardTransaction.go index e96a3cf0eca..4a2b80e04dc 100644 --- a/process/rewardTransaction/interceptedRewardTransaction.go +++ b/process/rewardTransaction/interceptedRewardTransaction.go @@ -134,6 +134,11 @@ func (inRTx *InterceptedRewardTransaction) CheckValidity() error { return nil } +// ShouldAllowDuplicates returns if this type of intercepted data should allow duplicates +func (inRTx *InterceptedRewardTransaction) ShouldAllowDuplicates() bool { + return false +} + // IsForCurrentShard returns true if this transaction is meant to be processed by the node from this shard func (inRTx *InterceptedRewardTransaction) IsForCurrentShard() bool { return inRTx.isForCurrentShard diff --git a/process/rewardTransaction/interceptedRewardTransaction_test.go b/process/rewardTransaction/interceptedRewardTransaction_test.go index 03f87c791e4..9abc181afc9 100644 --- a/process/rewardTransaction/interceptedRewardTransaction_test.go +++ b/process/rewardTransaction/interceptedRewardTransaction_test.go @@ -153,6 +153,7 @@ func TestNewInterceptedRewardTransaction_TestGetters(t *testing.T) { txHash := irt.Hasher().Compute(string(txBuff)) assert.Equal(t, txHash, irt.Hash()) + assert.False(t, irt.ShouldAllowDuplicates()) } func TestNewInterceptedRewardTransaction_NonceShouldBeZero(t *testing.T) { diff --git a/process/smartContract/export_test.go b/process/smartContract/export_test.go new file mode 100644 index 00000000000..002e53675e9 --- /dev/null +++ b/process/smartContract/export_test.go @@ -0,0 +1,11 @@ +package smartContract + +import ( + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/process" +) + +// ExtractBlockHeaderAndRootHash - +func (service *SCQueryService) ExtractBlockHeaderAndRootHash(query *process.SCQuery) (data.HeaderHandler, []byte, []byte, error) { + return service.extractBlockHeaderAndRootHash(query) +} diff --git a/process/smartContract/hooks/blockChainHook.go b/process/smartContract/hooks/blockChainHook.go index 5757f02438b..3f581d2f320 100644 --- a/process/smartContract/hooks/blockChainHook.go +++ b/process/smartContract/hooks/blockChainHook.go @@ -19,6 +19,10 @@ import ( "github.com/multiversx/mx-chain-core-go/data/typeConverters" "github.com/multiversx/mx-chain-core-go/hashing/keccak" "github.com/multiversx/mx-chain-core-go/marshal" + logger "github.com/multiversx/mx-chain-logger-go" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/multiversx/mx-chain-vm-common-go/parsers" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" @@ -30,9 +34,6 @@ import ( "github.com/multiversx/mx-chain-go/storage" "github.com/multiversx/mx-chain-go/storage/factory" "github.com/multiversx/mx-chain-go/storage/storageunit" - logger "github.com/multiversx/mx-chain-logger-go" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" - "github.com/multiversx/mx-chain-vm-common-go/parsers" ) var _ process.BlockChainHookHandler = (*BlockChainHookImpl)(nil) @@ -66,7 +67,7 @@ type ArgBlockChainHook struct { Counter BlockChainHookCounter MissingTrieNodesNotifier common.MissingTrieNodesNotifier EpochStartTrigger EpochStartTriggerHandler - RoundHandler RoundHandler // TODO: @laurci - this needs to be replaced when changing the round duration + RoundHandler RoundHandler } // BlockChainHookImpl is a wrapper over AccountsAdapter that satisfy vmcommon.BlockchainHook interface @@ -85,7 +86,7 @@ type BlockChainHookImpl struct { enableEpochsHandler common.EnableEpochsHandler counter BlockChainHookCounter epochStartTrigger EpochStartTriggerHandler - roundHandler RoundHandler // TODO: @laurci - this needs to be replaced when changing the round duration + roundHandler RoundHandler mutCurrentHdr sync.RWMutex currentHdr data.HeaderHandler @@ -338,16 +339,16 @@ func (bh *BlockChainHookImpl) processMaxReadsCounters() error { func (bh *BlockChainHookImpl) GetBlockhash(nonce uint64) ([]byte, error) { defer stopMeasure(startMeasure("GetBlockhash")) - hdr := bh.blockChain.GetCurrentBlockHeader() - - if check.IfNil(hdr) { + lastExecHdr := bh.blockChain.GetLastExecutedBlockHeader() + if check.IfNil(lastExecHdr) { return nil, process.ErrNilBlockHeader } - if nonce > hdr.GetNonce() { + if nonce > lastExecHdr.GetNonce() { return nil, process.ErrInvalidNonceRequest } - if nonce == hdr.GetNonce() { - return bh.blockChain.GetCurrentBlockHeaderHash(), nil + if nonce == lastExecHdr.GetNonce() { + _, lastExecHash, _ := bh.blockChain.GetLastExecutedBlockInfo() + return lastExecHash, nil } if bh.enableEpochsHandler.IsFlagEnabled(common.DoNotReturnOldBlockInBlockchainHookFlag) { return nil, process.ErrInvalidNonceRequest @@ -364,64 +365,77 @@ func (bh *BlockChainHookImpl) GetBlockhash(nonce uint64) ([]byte, error) { return nil, err } - if header.GetEpoch() != hdr.GetEpoch() { + if header.GetEpoch() != lastExecHdr.GetEpoch() { return nil, process.ErrInvalidBlockRequestOldEpoch } return hash, nil } -// LastNonce returns the nonce from the last committed block +// LastNonce returns the nonce from the last executed block func (bh *BlockChainHookImpl) LastNonce() uint64 { - if !check.IfNil(bh.blockChain.GetCurrentBlockHeader()) { - return bh.blockChain.GetCurrentBlockHeader().GetNonce() + lastExecHdr := bh.blockChain.GetLastExecutedBlockHeader() + if check.IfNil(lastExecHdr) { + return 0 } - return 0 + + return lastExecHdr.GetNonce() } -// LastRound returns the round from the last committed block +// LastRound returns the round from the last executed block func (bh *BlockChainHookImpl) LastRound() uint64 { - if !check.IfNil(bh.blockChain.GetCurrentBlockHeader()) { - return bh.blockChain.GetCurrentBlockHeader().GetRound() + lastExecHdr := bh.blockChain.GetLastExecutedBlockHeader() + if check.IfNil(lastExecHdr) { + return 0 } - return 0 + + return lastExecHdr.GetRound() } -// LastTimeStamp returns the timeStamp from the last committed block +// LastTimeStamp returns the timeStamp from the last executed block func (bh *BlockChainHookImpl) LastTimeStamp() uint64 { - if !check.IfNil(bh.blockChain.GetCurrentBlockHeader()) { - return bh.blockChain.GetCurrentBlockHeader().GetTimeStamp() + lastExecHdr := bh.blockChain.GetLastExecutedBlockHeader() + if check.IfNil(lastExecHdr) { + return 0 } - return 0 + + return lastExecHdr.GetTimeStamp() } -// LastTimeStampMs returns the timeStamp in milliseconds from the last committed block +// LastTimeStampMs returns the timeStamp in milliseconds from the last executed block func (bh *BlockChainHookImpl) LastTimeStampMs() uint64 { - if !check.IfNil(bh.blockChain.GetCurrentBlockHeader()) { - return common.ConvertTimeStampSecToMs(bh.blockChain.GetCurrentBlockHeader().GetTimeStamp()) + lastExecHdr := bh.blockChain.GetLastExecutedBlockHeader() + if check.IfNil(lastExecHdr) { + return 0 } - return 0 + + _, timestampMs, _ := common.GetHeaderTimestamps(lastExecHdr, bh.enableEpochsHandler) + + return timestampMs } -// LastRandomSeed returns the random seed from the last committed block +// LastRandomSeed returns the random seed from the last executed block func (bh *BlockChainHookImpl) LastRandomSeed() []byte { - if !check.IfNil(bh.blockChain.GetCurrentBlockHeader()) { - return bh.blockChain.GetCurrentBlockHeader().GetRandSeed() + lastExecHdr := bh.blockChain.GetLastExecutedBlockHeader() + if check.IfNil(lastExecHdr) { + return make([]byte, 0) } - return make([]byte, 0) + + return lastExecHdr.GetRandSeed() } -// LastEpoch returns the epoch from the last committed block +// LastEpoch returns the epoch from the last executed block func (bh *BlockChainHookImpl) LastEpoch() uint32 { - if !check.IfNil(bh.blockChain.GetCurrentBlockHeader()) { - return bh.blockChain.GetCurrentBlockHeader().GetEpoch() + lastExecHdr := bh.blockChain.GetLastExecutedBlockHeader() + if check.IfNil(lastExecHdr) { + return 0 } - return 0 + + return lastExecHdr.GetEpoch() } // RoundTime returns the duration of a round func (bh *BlockChainHookImpl) RoundTime() uint64 { - // TODO: @laurci - this needs to be replaced when changing the round duration roundDuration := bh.roundHandler.TimeDuration() return uint64(roundDuration.Milliseconds()) @@ -432,7 +446,8 @@ func (bh *BlockChainHookImpl) EpochStartBlockTimeStampMs() uint64 { bh.mutEpochStartHdr.RLock() defer bh.mutEpochStartHdr.RUnlock() - timestampMs := common.ConvertTimeStampSecToMs(bh.epochStartHdr.GetTimeStamp()) + _, timestampMs, _ := common.GetHeaderTimestamps(bh.epochStartHdr, bh.enableEpochsHandler) + return timestampMs } @@ -454,7 +469,7 @@ func (bh *BlockChainHookImpl) EpochStartBlockRound() uint64 { // GetStateRootHash returns the state root hash from the last committed block func (bh *BlockChainHookImpl) GetStateRootHash() []byte { - rootHash := bh.blockChain.GetCurrentBlockRootHash() + rootHash := bh.getCurrentRootHash() if len(rootHash) > 0 { return rootHash } @@ -462,6 +477,11 @@ func (bh *BlockChainHookImpl) GetStateRootHash() []byte { return make([]byte, 0) } +func (bh *BlockChainHookImpl) getCurrentRootHash() []byte { + _, _, lastExecutedRootHash := bh.blockChain.GetLastExecutedBlockInfo() + return lastExecutedRootHash +} + // CurrentNonce returns the nonce from the current block func (bh *BlockChainHookImpl) CurrentNonce() uint64 { bh.mutCurrentHdr.RLock() @@ -491,7 +511,9 @@ func (bh *BlockChainHookImpl) CurrentTimeStampMs() uint64 { bh.mutCurrentHdr.RLock() defer bh.mutCurrentHdr.RUnlock() - return common.ConvertTimeStampSecToMs(bh.currentHdr.GetTimeStamp()) + _, timestampMs, _ := common.GetHeaderTimestamps(bh.currentHdr, bh.enableEpochsHandler) + + return timestampMs } // CurrentRandomSeed returns the random seed from the current header diff --git a/process/smartContract/hooks/blockChainHook_test.go b/process/smartContract/hooks/blockChainHook_test.go index 76ed626a465..803b816182d 100644 --- a/process/smartContract/hooks/blockChainHook_test.go +++ b/process/smartContract/hooks/blockChainHook_test.go @@ -727,7 +727,7 @@ func TestBlockChainHookImpl_GetBlockhashNilBlockHeaderExpectError(t *testing.T) args := createMockBlockChainHookArgs() args.BlockChain = &testscommon.ChainHandlerStub{ - GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { return nil }, } @@ -741,10 +741,14 @@ func TestBlockChainHookImpl_GetBlockhashNilBlockHeaderExpectError(t *testing.T) func TestBlockChainHookImpl_GetBlockhashInvalidNonceExpectError(t *testing.T) { t.Parallel() + lastExecHdr := &block.Header{Nonce: 1} args := createMockBlockChainHookArgs() args.BlockChain = &testscommon.ChainHandlerStub{ - GetCurrentBlockHeaderCalled: func() data.HeaderHandler { - return &block.Header{Nonce: 1} + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return lastExecHdr + }, + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return 1, []byte("hash"), []byte("rootHash") }, } @@ -757,15 +761,15 @@ func TestBlockChainHookImpl_GetBlockhashInvalidNonceExpectError(t *testing.T) { func TestBlockChainHookImpl_GetBlockhashShouldReturnCurrentBlockHeaderHash(t *testing.T) { t.Parallel() - hdrToRet := &block.Header{Nonce: 2} + lastExecHdr := &block.Header{Nonce: 2} hashToRet := []byte("hash") args := createMockBlockChainHookArgs() args.BlockChain = &testscommon.ChainHandlerStub{ - GetCurrentBlockHeaderCalled: func() data.HeaderHandler { - return hdrToRet + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return lastExecHdr }, - GetCurrentBlockHeaderHashCalled: func() []byte { - return hashToRet + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return 2, hashToRet, []byte("rootHash") }, } bh, _ := hooks.NewBlockChainHookImpl(args) @@ -778,10 +782,14 @@ func TestBlockChainHookImpl_GetBlockhashShouldReturnCurrentBlockHeaderHash(t *te func TestBlockChainHookImpl_GetBlockhashFromStorerErrorReadingFromStorage(t *testing.T) { t.Parallel() + lastExecHdr := &block.Header{Nonce: 10} args := createMockBlockChainHookArgs() args.BlockChain = &testscommon.ChainHandlerStub{ - GetCurrentBlockHeaderCalled: func() data.HeaderHandler { - return &block.Header{Nonce: 10} + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return lastExecHdr + }, + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return 10, []byte("hash"), []byte("rootHash") }, } storer := &storageStubs.StorerStub{ @@ -813,9 +821,12 @@ func TestBlockChainHookImpl_GetBlockhashFromStorerInSameEpoch(t *testing.T) { marshalledHeader, _ := args.Marshalizer.Marshal(header) args.BlockChain = &testscommon.ChainHandlerStub{ - GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { return header }, + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return nonce, []byte("execHash"), []byte("rootHash") + }, } storerBlockHeader := &storageStubs.StorerStub{ @@ -860,9 +871,12 @@ func TestBlockChainHookImpl_GetBlockhashFromStorerInSameEpochWithFlagEnabled(t * shardID := args.ShardCoordinator.SelfId() args.BlockChain = &testscommon.ChainHandlerStub{ - GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { return header }, + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return nonce, []byte("hash"), []byte("rootHash") + }, } storerBlockHeader := &storageStubs.StorerStub{ @@ -902,13 +916,17 @@ func TestBlockChainHookImpl_GetBlockhashFromOldEpochExpectError(t *testing.T) { hdrToRet := &block.Header{Nonce: 2, Epoch: 2} hashToRet := []byte("hash") + lastExecHdr := &block.Header{Nonce: 10, Epoch: 10} args := createMockBlockChainHookArgs() marshaledData, _ := args.Marshalizer.Marshal(hdrToRet) args.BlockChain = &testscommon.ChainHandlerStub{ - GetCurrentBlockHeaderCalled: func() data.HeaderHandler { - return &block.Header{Nonce: 10, Epoch: 10} + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return lastExecHdr + }, + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return 10, []byte("execHash"), []byte("rootHash") }, } args.StorageService = &storageStubs.ChainStorerStub{ @@ -942,7 +960,7 @@ func TestBlockChainHookImpl_GettersFromBlockchainCurrentHeader(t *testing.T) { args := createMockBlockChainHookArgs() args.BlockChain = &testscommon.ChainHandlerStub{ - GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { return nil }, } @@ -955,6 +973,7 @@ func TestBlockChainHookImpl_GettersFromBlockchainCurrentHeader(t *testing.T) { assert.Equal(t, []byte{}, bh.LastRandomSeed()) assert.Equal(t, []byte{}, bh.GetStateRootHash()) }) + t.Run("custom header, expect correct values are returned", func(t *testing.T) { t.Parallel() @@ -964,7 +983,7 @@ func TestBlockChainHookImpl_GettersFromBlockchainCurrentHeader(t *testing.T) { randSeed := []byte("a") rootHash := []byte("b") epoch := uint32(7) - hdrToRet := &block.Header{ + lastExecutedHdr := &block.Header{ Nonce: nonce, Round: round, TimeStamp: timestamp, @@ -975,11 +994,11 @@ func TestBlockChainHookImpl_GettersFromBlockchainCurrentHeader(t *testing.T) { args := createMockBlockChainHookArgs() args.BlockChain = &testscommon.ChainHandlerStub{ - GetCurrentBlockHeaderCalled: func() data.HeaderHandler { - return hdrToRet + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return lastExecutedHdr }, - GetCurrentBlockRootHashCalled: func() []byte { - return hdrToRet.RootHash + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return nonce, []byte("hash"), rootHash }, } bh, _ := hooks.NewBlockChainHookImpl(args) @@ -991,6 +1010,7 @@ func TestBlockChainHookImpl_GettersFromBlockchainCurrentHeader(t *testing.T) { assert.Equal(t, randSeed, bh.LastRandomSeed()) assert.Equal(t, rootHash, bh.GetStateRootHash()) }) + t.Run("custom header, do not return old block is set, expect default values", func(t *testing.T) { t.Parallel() @@ -1000,7 +1020,7 @@ func TestBlockChainHookImpl_GettersFromBlockchainCurrentHeader(t *testing.T) { randSeed := []byte("a") rootHash := []byte("b") epoch := uint32(7) - hdrToRet := &block.Header{ + lastExecutedHdr := &block.Header{ Nonce: nonce, Round: round, TimeStamp: timestamp, @@ -1012,11 +1032,11 @@ func TestBlockChainHookImpl_GettersFromBlockchainCurrentHeader(t *testing.T) { args := createMockBlockChainHookArgs() args.EnableEpochsHandler = enableEpochsHandlerMock.NewEnableEpochsHandlerStub(common.DoNotReturnOldBlockInBlockchainHookFlag) args.BlockChain = &testscommon.ChainHandlerStub{ - GetCurrentBlockHeaderCalled: func() data.HeaderHandler { - return hdrToRet + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return lastExecutedHdr }, - GetCurrentBlockRootHashCalled: func() []byte { - return hdrToRet.RootHash + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return nonce, []byte("hash"), rootHash }, } bh, _ := hooks.NewBlockChainHookImpl(args) @@ -1028,6 +1048,43 @@ func TestBlockChainHookImpl_GettersFromBlockchainCurrentHeader(t *testing.T) { assert.Equal(t, epoch, bh.LastEpoch()) assert.Equal(t, rootHash, bh.GetStateRootHash()) }) + + t.Run("custom header v3, expect correct values are returned", func(t *testing.T) { + t.Parallel() + + nonce := uint64(37) + round := uint64(5) + timestamp := uint64(1234) + randSeed := []byte("a") + rootHash1 := []byte("c") + epoch := uint32(7) + + lastExecutedHdr := &block.HeaderV3{ + Nonce: nonce, + Round: round, + TimestampMs: timestamp, + RandSeed: randSeed, + Epoch: epoch, + } + + args := createMockBlockChainHookArgs() + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return lastExecutedHdr + }, + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return nonce, []byte("hash"), rootHash1 + }, + } + bh, _ := hooks.NewBlockChainHookImpl(args) + + assert.Equal(t, nonce, bh.LastNonce()) + assert.Equal(t, round, bh.LastRound()) + assert.Equal(t, timestamp, bh.LastTimeStamp()) + assert.Equal(t, epoch, bh.LastEpoch()) + assert.Equal(t, randSeed, bh.LastRandomSeed()) + assert.Equal(t, rootHash1, bh.GetStateRootHash()) + }) } func TestBlockChainHookImpl_GettersFromCurrentHeader(t *testing.T) { @@ -2674,46 +2731,188 @@ func TestBlockChainHookImpl_IsLimitedTransfer(t *testing.T) { func TestBlockChainHookImpl_CurrentTimeStampMs(t *testing.T) { t.Parallel() - timestamp := uint64(1234) + t.Run("before supernova", func(t *testing.T) { + t.Parallel() - hdr := &block.Header{ - TimeStamp: timestamp, - } + timestamp := uint64(1234) - args := createMockBlockChainHookArgs() - bh, _ := hooks.NewBlockChainHookImpl(args) + hdr := &block.Header{ + TimeStamp: timestamp, + } - err := bh.SetCurrentHeader(hdr) - require.Nil(t, err) + args := createMockBlockChainHookArgs() + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag != common.SupernovaFlag + }, + } + bh, _ := hooks.NewBlockChainHookImpl(args) - assert.Equal(t, timestamp, bh.CurrentTimeStamp()) + err := bh.SetCurrentHeader(hdr) + require.Nil(t, err) - expTimestampMs := common.ConvertTimeStampSecToMs(timestamp) - assert.Equal(t, expTimestampMs, bh.CurrentTimeStampMs()) + assert.Equal(t, timestamp, bh.CurrentTimeStamp()) + + expTimestampMs := common.ConvertTimeStampSecToMs(timestamp) + assert.Equal(t, expTimestampMs, bh.CurrentTimeStampMs()) + }) + + t.Run("after supernova", func(t *testing.T) { + t.Parallel() + + timestampMs := uint64(1234567) + + hdr := &block.Header{ + TimeStamp: timestampMs, + } + + args := createMockBlockChainHookArgs() + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag + }, + } + bh, _ := hooks.NewBlockChainHookImpl(args) + + err := bh.SetCurrentHeader(hdr) + require.Nil(t, err) + + assert.Equal(t, timestampMs, bh.CurrentTimeStampMs()) + }) } func TestBlockChainHookImpl_LastTimeStampMs(t *testing.T) { t.Parallel() - timestamp := uint64(1234) + t.Run("before supernova activated", func(t *testing.T) { + t.Parallel() - hdr := &block.Header{ - TimeStamp: timestamp, - } + timestamp := uint64(1234) - args := createMockBlockChainHookArgs() - args.BlockChain = &testscommon.ChainHandlerStub{ - GetCurrentBlockHeaderCalled: func() data.HeaderHandler { - return hdr - }, - } + lastExecHdr := &block.Header{ + TimeStamp: timestamp, + } - bh, _ := hooks.NewBlockChainHookImpl(args) + args := createMockBlockChainHookArgs() + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return lastExecHdr + }, + } + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return flag != common.SupernovaFlag + }, + } + + bh, _ := hooks.NewBlockChainHookImpl(args) + + lastTimeStampMs := bh.LastTimeStampMs() + + expLastTimeStamp := common.ConvertTimeStampSecToMs(timestamp) + require.Equal(t, expLastTimeStamp, lastTimeStampMs) + }) + + t.Run("after supernova activated", func(t *testing.T) { + t.Parallel() + + timestampMs := uint64(1234567) + lastExecHdr := &block.Header{ + TimeStamp: timestampMs, + } + + args := createMockBlockChainHookArgs() + args.BlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return lastExecHdr + }, + } + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag + }, + } + + bh, _ := hooks.NewBlockChainHookImpl(args) + + lastTimeStampMs := bh.LastTimeStampMs() + require.Equal(t, timestampMs, lastTimeStampMs) + }) +} + +func TestBlockChainHookImpl_EpochStartBlockTimeStampMs(t *testing.T) { + t.Parallel() + + t.Run("before supernova activated", func(t *testing.T) { + t.Parallel() + + epoch := uint32(7) + + epochStartTimestamp := uint64(1234) + + epochStartHdr := &block.Header{ + TimeStamp: epochStartTimestamp, + Epoch: epoch, + EpochStartMetaHash: []byte("meta"), + } + + hdr := &block.Header{ + Epoch: epoch, + } + + args := createMockBlockChainHookArgs() + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag != common.SupernovaFlag + }, + } - lastTimeStampMs := bh.LastTimeStampMs() + bh, _ := hooks.NewBlockChainHookImpl(args) - expLastTimeStamp := common.ConvertTimeStampSecToMs(timestamp) - require.Equal(t, expLastTimeStamp, lastTimeStampMs) + err := bh.SetCurrentHeader(epochStartHdr) + require.Nil(t, err) + + err = bh.SetCurrentHeader(hdr) + require.Nil(t, err) + + expLastTimeStamp := common.ConvertTimeStampSecToMs(epochStartTimestamp) + require.Equal(t, expLastTimeStamp, bh.EpochStartBlockTimeStampMs()) + }) + + t.Run("after supernova activated", func(t *testing.T) { + t.Parallel() + + epoch := uint32(7) + + epochStartTimestamp := uint64(1234567) + + epochStartHdr := &block.Header{ + TimeStamp: epochStartTimestamp, + Epoch: epoch, + EpochStartMetaHash: []byte("meta"), + } + + hdr := &block.Header{ + Epoch: epoch, + } + + args := createMockBlockChainHookArgs() + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag + }, + } + + bh, _ := hooks.NewBlockChainHookImpl(args) + + err := bh.SetCurrentHeader(epochStartHdr) + require.Nil(t, err) + + err = bh.SetCurrentHeader(hdr) + require.Nil(t, err) + + require.Equal(t, epochStartTimestamp, bh.EpochStartBlockTimeStampMs()) + }) } func TestBlockChainHookImpl_ConcurrencyTimeOperations(t *testing.T) { diff --git a/process/smartContract/process.go b/process/smartContract/process.go index d44d1641bb3..c4cd2a735b3 100644 --- a/process/smartContract/process.go +++ b/process/smartContract/process.go @@ -65,7 +65,7 @@ type scProcessor struct { builtInFunctions vmcommon.BuiltInFunctionContainer wasmVMChangeLocker common.Locker - enableRoundsHandler process.EnableRoundsHandler + enableRoundsHandler common.EnableRoundsHandler enableEpochsHandler common.EnableEpochsHandler badTxForwarder process.IntermediateTransactionHandler scrForwarder process.IntermediateTransactionHandler @@ -2330,7 +2330,7 @@ func (sc *scProcessor) createSmartContractResults( isCrossShard := sc.shardCoordinator.ComputeId(outAcc.Address) != sc.shardCoordinator.SelfId() if isCrossShard && result.CallType == vmData.AsynchronousCall && - sc.enableRoundsHandler.IsDisableAsyncCallV1Enabled() { + sc.enableRoundsHandler.IsFlagEnabled(common.DisableAsyncCallV1Flag) { return false, nil, process.ErrAsyncCallsDisabled } diff --git a/process/smartContract/processProxy/processProxy_test.go b/process/smartContract/processProxy/processProxy_test.go index c0186db7385..e4b410eb901 100644 --- a/process/smartContract/processProxy/processProxy_test.go +++ b/process/smartContract/processProxy/processProxy_test.go @@ -17,13 +17,13 @@ import ( "github.com/multiversx/mx-chain-go/process/mock" "github.com/multiversx/mx-chain-go/process/smartContract/scrCommon" "github.com/multiversx/mx-chain-go/state" - "github.com/multiversx/mx-chain-go/storage/txcache" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" epochNotifierMock "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" stateMock "github.com/multiversx/mx-chain-go/testscommon/state" + "github.com/multiversx/mx-chain-go/txcache" vmcommon "github.com/multiversx/mx-chain-vm-common-go" "github.com/multiversx/mx-chain-vm-common-go/builtInFunctions" "github.com/stretchr/testify/assert" diff --git a/process/smartContract/process_test.go b/process/smartContract/process_test.go index cc1444627e4..54acb623608 100644 --- a/process/smartContract/process_test.go +++ b/process/smartContract/process_test.go @@ -13,13 +13,6 @@ import ( "github.com/multiversx/mx-chain-core-go/data/smartContractResult" "github.com/multiversx/mx-chain-core-go/data/transaction" vmData "github.com/multiversx/mx-chain-core-go/data/vm" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" - "github.com/multiversx/mx-chain-vm-common-go/builtInFunctions" - "github.com/multiversx/mx-chain-vm-common-go/parsers" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/common/enablers" "github.com/multiversx/mx-chain-go/common/forking" @@ -33,8 +26,8 @@ import ( "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/state/accounts" "github.com/multiversx/mx-chain-go/storage/storageunit" - "github.com/multiversx/mx-chain-go/storage/txcache" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" @@ -42,6 +35,13 @@ import ( stateMock "github.com/multiversx/mx-chain-go/testscommon/state" "github.com/multiversx/mx-chain-go/testscommon/trie" "github.com/multiversx/mx-chain-go/testscommon/vmcommonMocks" + "github.com/multiversx/mx-chain-go/txcache" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/multiversx/mx-chain-vm-common-go/builtInFunctions" + "github.com/multiversx/mx-chain-vm-common-go/parsers" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const setGuardianCost = 250000 @@ -4200,11 +4200,9 @@ func TestProcess_createCompletedTxEvent(t *testing.T) { } func createRealEconomicsDataArgs() *economics.ArgsNewEconomicsData { - cfg := &config.Config{EpochStartConfig: config.EpochStartConfig{RoundsPerEpoch: 14400}} - cfg.GeneralSettings.ChainParametersByEpoch = []config.ChainParametersByEpochConfig{{RoundDuration: 6000}} return &economics.ArgsNewEconomicsData{ - GeneralConfig: cfg, + ChainParamsHandler: &chainParameters.ChainParametersHolderMock{}, Economics: &config.EconomicsConfig{ GlobalSettings: config.GlobalSettings{ GenesisTotalSupply: "20000000000000000000000000", @@ -4550,8 +4548,8 @@ func TestScProcessor_DisableAsyncCalls(t *testing.T) { arguments.ShardCoordinator = shardCoordinator arguments.EnableEpochsHandler = enableEpochsHandlerMock.NewEnableEpochsHandlerStub() arguments.EnableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ - IsDisableAsyncCallV1EnabledCalled: func() bool { - return false + IsFlagEnabledCalled: func(flag common.EnableRoundFlag) bool { + return flag != common.DisableAsyncCallV1Flag }, } sc, _ := NewSmartContractProcessor(arguments) @@ -4581,8 +4579,8 @@ func TestScProcessor_DisableAsyncCalls(t *testing.T) { require.NotNil(t, scResults) arguments.EnableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ - IsDisableAsyncCallV1EnabledCalled: func() bool { - return true + IsFlagEnabledCalled: func(flag common.EnableRoundFlag) bool { + return flag == common.DisableAsyncCallV1Flag }, } sc, _ = NewSmartContractProcessor(arguments) diff --git a/process/smartContract/processorV2/process_test.go b/process/smartContract/processorV2/process_test.go index 297f15a0651..28e644e86a8 100644 --- a/process/smartContract/processorV2/process_test.go +++ b/process/smartContract/processorV2/process_test.go @@ -15,14 +15,6 @@ import ( "github.com/multiversx/mx-chain-core-go/data/smartContractResult" "github.com/multiversx/mx-chain-core-go/data/transaction" vmData "github.com/multiversx/mx-chain-core-go/data/vm" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" - "github.com/multiversx/mx-chain-vm-common-go/builtInFunctions" - "github.com/multiversx/mx-chain-vm-common-go/parsers" - "github.com/multiversx/mx-chain-vm-go/vmhost" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/process" @@ -36,8 +28,8 @@ import ( "github.com/multiversx/mx-chain-go/state" stateFactory "github.com/multiversx/mx-chain-go/state/factory" "github.com/multiversx/mx-chain-go/storage/storageunit" - "github.com/multiversx/mx-chain-go/storage/txcache" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" @@ -46,6 +38,14 @@ import ( stateMock "github.com/multiversx/mx-chain-go/testscommon/state" testsCommonStorage "github.com/multiversx/mx-chain-go/testscommon/storage" "github.com/multiversx/mx-chain-go/testscommon/vmcommonMocks" + "github.com/multiversx/mx-chain-go/txcache" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/multiversx/mx-chain-vm-common-go/builtInFunctions" + "github.com/multiversx/mx-chain-vm-common-go/parsers" + "github.com/multiversx/mx-chain-vm-go/vmhost" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const maxEpoch = math.MaxUint32 @@ -482,7 +482,7 @@ func TestScProcessor_DeploySmartContractBadParse(t *testing.T) { allLogs := tsc.GetTxLogsProcessor().GetAllCurrentLogs() require.Equal(t, 1, len(allLogs)) - require.Equal(t, expectedError, string(allLogs[0].LogHandler.GetLogEvents()[0].GetData())) + require.Equal(t, expectedError, string(allLogs[0].GetLogHandler().GetLogEvents()[0].GetData())) require.Equal(t, vmcommon.UserError, returnCode) require.Equal(t, uint64(1), acntSrc.GetNonce()) require.True(t, acntSrc.GetBalance().Cmp(tx.Value) == 0) @@ -529,7 +529,7 @@ func TestScProcessor_DeploySmartContractRunError(t *testing.T) { expectedError := "@" + hex.EncodeToString([]byte(createError.Error())) allLogs := tsc.GetTxLogsProcessor().GetAllCurrentLogs() require.Equal(t, 1, len(allLogs)) - require.Equal(t, expectedError, string(allLogs[0].LogHandler.GetLogEvents()[0].GetData())) + require.Equal(t, expectedError, string(allLogs[0].GetLogHandler().GetLogEvents()[0].GetData())) } func TestScProcessor_BuiltInCallSmartContractSenderFailed(t *testing.T) { @@ -4276,11 +4276,9 @@ func TestProcess_createCompletedTxEvent(t *testing.T) { } func createRealEconomicsDataArgs() *economics.ArgsNewEconomicsData { - cfg := &config.Config{EpochStartConfig: config.EpochStartConfig{RoundsPerEpoch: 14400}} - cfg.GeneralSettings.ChainParametersByEpoch = []config.ChainParametersByEpochConfig{{RoundDuration: 6000}} return &economics.ArgsNewEconomicsData{ - GeneralConfig: cfg, + ChainParamsHandler: &chainParameters.ChainParametersHolderMock{}, Economics: &config.EconomicsConfig{ GlobalSettings: config.GlobalSettings{ GenesisTotalSupply: "20000000000000000000000000", diff --git a/process/smartContract/processorV2/testScProcessor.go b/process/smartContract/processorV2/testScProcessor.go index 52a63c6c308..675d54bb084 100644 --- a/process/smartContract/processorV2/testScProcessor.go +++ b/process/smartContract/processorV2/testScProcessor.go @@ -28,7 +28,7 @@ func (tsp *TestScProcessor) GetCompositeTestError() error { allLogs := tsp.GetTxLogsProcessor().GetAllCurrentLogs() for _, logs := range allLogs { - for _, event := range logs.GetLogEvents() { + for _, event := range logs.GetLogHandler().GetLogEvents() { if string(event.GetIdentifier()) == signalError { returnError = wrapErrorIfNotContains(returnError, string(event.GetTopics()[1])) } diff --git a/process/smartContract/processorV2/vmOutputAccountsProcessor.go b/process/smartContract/processorV2/vmOutputAccountsProcessor.go index 8c76cf06051..6c3673631c0 100644 --- a/process/smartContract/processorV2/vmOutputAccountsProcessor.go +++ b/process/smartContract/processorV2/vmOutputAccountsProcessor.go @@ -158,7 +158,7 @@ func (oap *VMOutputAccountsProcessor) processStorageUpdatesStep( return nil } -// updateSmartContractCode upgrades code for "direct" deployments & upgrades and for "indirect" deployments & upgrades +// updateSmartContractCodeStep upgrades code for "direct" deployments & upgrades and for "indirect" deployments & upgrades // It receives: // // (1) the account as found in the State diff --git a/process/smartContract/scQueryService.go b/process/smartContract/scQueryService.go index f8571908756..9abcd05f493 100644 --- a/process/smartContract/scQueryService.go +++ b/process/smartContract/scQueryService.go @@ -196,13 +196,13 @@ func (service *SCQueryService) executeScCall(query *process.SCQuery, gasPrice ui return nil, nil, process.ErrNodeIsNotSynced } - blockHeader, blockRootHash, err := service.extractBlockHeaderAndRootHash(query) + blockHeader, blockRootHash, headerHash, err := service.extractBlockHeaderAndRootHash(query) if err != nil { return nil, nil, err } if len(blockRootHash) > 0 { - err = service.apiBlockChain.SetCurrentBlockHeaderAndRootHash(blockHeader, blockRootHash) + err = service.setCurrentBlockInfo(blockHeader, blockRootHash, headerHash) if err != nil { return nil, nil, err } @@ -232,7 +232,7 @@ func (service *SCQueryService) executeScCall(query *process.SCQuery, gasPrice ui rootHashBeforeExecution := make([]byte, 0) if shouldCheckRootHashChanges { - rootHashBeforeExecution = service.apiBlockChain.GetCurrentBlockRootHash() + rootHashBeforeExecution = service.getCurrentBlockRootHash(blockHeader) } service.wasmVMChangeLocker.RLock() @@ -251,7 +251,7 @@ func (service *SCQueryService) executeScCall(query *process.SCQuery, gasPrice ui } if query.SameScState { - err = service.checkForRootHashChanges(rootHashBeforeExecution) + err = service.checkForRootHashChanges(rootHashBeforeExecution, blockHeader) if err != nil { return nil, nil, err } @@ -270,6 +270,44 @@ func (service *SCQueryService) executeScCall(query *process.SCQuery, gasPrice ui return vmOutput, blockInfo, nil } +func (service *SCQueryService) setCurrentBlockInfo( + header data.HeaderHandler, + rootHash []byte, + blockHash []byte, +) error { + if header.IsHeaderV3() { + return service.setCurrentBlockInfoV3(header, rootHash, blockHash) + } + + service.apiBlockChain.SetLastExecutedBlockHeaderAndRootHash(header, blockHash, rootHash) + + return service.apiBlockChain.SetCurrentBlockHeaderAndRootHash(header, rootHash) +} + +func (service *SCQueryService) getCurrentBlockRootHash( + header data.HeaderHandler, +) []byte { + if header.IsHeaderV3() { + _, _, rootHash := service.apiBlockChain.GetLastExecutedBlockInfo() + return rootHash + } + + return service.apiBlockChain.GetCurrentBlockRootHash() +} + +func (service *SCQueryService) setCurrentBlockInfoV3( + header data.HeaderHandler, + rootHash []byte, + blockHash []byte, +) error { + // for header v3, the context here will be created based on last executed block + // so here current block header (in apiBlockChain) is not set anymore) + + service.apiBlockChain.SetLastExecutedBlockHeaderAndRootHash(header, blockHash, rootHash) + + return nil +} + func (service *SCQueryService) recreateTrie(blockRootHash []byte, blockHeader data.HeaderHandler) error { if check.IfNil(blockHeader) { return process.ErrNilBlockHeader @@ -287,40 +325,108 @@ func (service *SCQueryService) recreateTrie(blockRootHash []byte, blockHeader da } // TODO: extract duplicated code with nodeBlocks.go -func (service *SCQueryService) extractBlockHeaderAndRootHash(query *process.SCQuery) (data.HeaderHandler, []byte, error) { +func (service *SCQueryService) extractBlockHeaderAndRootHash(query *process.SCQuery) (data.HeaderHandler, []byte, []byte, error) { if len(query.BlockHash) > 0 { currentHeader, err := service.getBlockHeaderByHash(query.BlockHash) if err != nil { - return nil, nil, err + return nil, nil, nil, err } - return service.getRootHashForBlock(currentHeader) + return service.getHeaderAndRootHashForBlock(currentHeader, query.BlockHash) } if query.BlockNonce.HasValue { - currentHeader, _, err := service.getBlockHeaderByNonce(query.BlockNonce.Value) + currentHeader, currentHeaderHash, err := service.getBlockHeaderByNonce(query.BlockNonce.Value) if err != nil { - return nil, nil, err + return nil, nil, nil, err } - return service.getRootHashForBlock(currentHeader) + return service.getHeaderAndRootHashForBlock(currentHeader, currentHeaderHash) + } + + return service.getCurrentBlockHeaderAndRootHash() +} + +func (service *SCQueryService) getCurrentBlockHeaderAndRootHash() (data.HeaderHandler, []byte, []byte, error) { + currentHeader := service.mainBlockChain.GetCurrentBlockHeader() + if currentHeader != nil && currentHeader.IsHeaderV3() { + return service.getCurrentBlockHeaderAndRootHashV3() } - return service.mainBlockChain.GetCurrentBlockHeader(), service.mainBlockChain.GetCurrentBlockRootHash(), nil + return service.mainBlockChain.GetCurrentBlockHeader(), service.mainBlockChain.GetCurrentBlockRootHash(), service.mainBlockChain.GetCurrentBlockHeaderHash(), nil } -func (service *SCQueryService) getRootHashForBlock(currentHeader data.HeaderHandler) (data.HeaderHandler, []byte, error) { - blockHeader, _, err := service.getBlockHeaderByNonce(currentHeader.GetNonce() + 1) +func (service *SCQueryService) getCurrentBlockHeaderAndRootHashV3() (data.HeaderHandler, []byte, []byte, error) { + _, headerHash, rootHash := service.mainBlockChain.GetLastExecutedBlockInfo() + + // current header will now be last executed header + lastExecutedHeader := service.mainBlockChain.GetLastExecutedBlockHeader() + + return lastExecutedHeader, rootHash, headerHash, nil +} + +func (service *SCQueryService) getHeaderAndRootHashForBlock(currentHeader data.HeaderHandler, currentHeaderHash []byte) (data.HeaderHandler, []byte, []byte, error) { + if currentHeader.IsHeaderV3() { + return service.getHeaderAndRootHashForBlockV3(currentHeader, currentHeaderHash) + } + + blockHeader, headerHash, err := service.getBlockHeaderByNonce(currentHeader.GetNonce() + 1) if err != nil { - return nil, nil, err + return nil, nil, nil, err } additionalData := blockHeader.GetAdditionalData() if check.IfNil(additionalData) { - return currentHeader, currentHeader.GetRootHash(), nil + return currentHeader, currentHeader.GetRootHash(), currentHeaderHash, nil + } + + return blockHeader, additionalData.GetScheduledRootHash(), headerHash, nil +} + +func (service *SCQueryService) getRootHashByExecutionResult( + currentHeaderHash []byte, +) ([]byte, error) { + execResStorer, err := service.storageService.GetStorer(dataRetriever.ExecutionResultsUnit) + if err != nil { + return nil, err + } + + execResBytes, err := execResStorer.Get(currentHeaderHash) + if err != nil { + return nil, err + } + + execRes, err := process.UnmarshalExecutionResult(service.marshaller, execResBytes) + if err != nil { + return nil, err + } + + return execRes.GetRootHash(), nil +} + +func (service *SCQueryService) getHeaderAndRootHashForBlockV3( + currentHeader data.HeaderHandler, + currentHeaderHash []byte, +) (data.HeaderHandler, []byte, []byte, error) { + // try to get root hash from execution result and return provided header + // if block already executed, execution result info should be available + rootHash, err := service.getRootHashByExecutionResult(currentHeaderHash) + if err == nil { + return currentHeader, rootHash, currentHeaderHash, nil + } + + // otherwise, provide header and root hash based on last execution result from the provided header + lastExecutionResult, err := common.ExtractBaseExecutionResultHandler(currentHeader.GetLastExecutionResultHandler()) + if err != nil { + return nil, nil, nil, err + } + + blockHeader, err := service.getBlockHeaderByHash(lastExecutionResult.GetHeaderHash()) + if err != nil { + return nil, nil, nil, err } - return blockHeader, additionalData.GetScheduledRootHash(), nil + return blockHeader, lastExecutionResult.GetRootHash(), lastExecutionResult.GetHeaderHash(), nil } func (service *SCQueryService) getBlockHeaderByHash(headerHash []byte) (data.HeaderHandler, error) { @@ -446,8 +552,11 @@ func (service *SCQueryService) getBlockHashByNonce(nonce uint64) ([]byte, error) ) } -func (service *SCQueryService) checkForRootHashChanges(rootHashBefore []byte) error { - rootHashAfter := service.apiBlockChain.GetCurrentBlockRootHash() +func (service *SCQueryService) checkForRootHashChanges( + rootHashBefore []byte, + header data.HeaderHandler, +) error { + rootHashAfter := service.getCurrentBlockRootHash(header) if bytes.Equal(rootHashBefore, rootHashAfter) { return nil diff --git a/process/smartContract/scQueryService_test.go b/process/smartContract/scQueryService_test.go index a5ec1d2fed5..a706edf38a1 100644 --- a/process/smartContract/scQueryService_test.go +++ b/process/smartContract/scQueryService_test.go @@ -15,6 +15,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" @@ -1243,29 +1244,81 @@ func TestSCQueryService_ShouldWorkIfNodeIsSynced(t *testing.T) { func TestSCQueryService_ShouldFailIfStateChanged(t *testing.T) { t.Parallel() - args := createMockArgumentsForSCQuery() - rootHashCalledCounter := 0 - args.APIBlockChain = &testscommon.ChainHandlerStub{ - GetCurrentBlockRootHashCalled: func() []byte { - rootHashCalledCounter++ - println(rootHashCalledCounter) - if rootHashCalledCounter < 2 { // first call is during root hash extraction for recreate trie - return []byte("first root hash") - } + t.Run("header v2", func(t *testing.T) { + t.Parallel() - return []byte("second root hash") - }, - } + args := createMockArgumentsForSCQuery() + rootHashCalledCounter := 0 + args.MainBlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV2{} + }, + } + args.APIBlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockRootHashCalled: func() []byte { + rootHashCalledCounter++ + println(rootHashCalledCounter) + if rootHashCalledCounter < 2 { // first call is during root hash extraction for recreate trie + return []byte("first root hash") + } - qs, _ := NewSCQueryService(args) + return []byte("second root hash") + }, + } - res, _, err := qs.ExecuteQuery(&process.SCQuery{ - SameScState: true, - ScAddress: []byte(DummyScAddress), - FuncName: "function", + qs, _ := NewSCQueryService(args) + + res, _, err := qs.ExecuteQuery(&process.SCQuery{ + SameScState: true, + ScAddress: []byte(DummyScAddress), + FuncName: "function", + }) + require.Nil(t, res) + require.ErrorIs(t, err, process.ErrStateChangedWhileExecutingVmQuery) + }) + + t.Run("header v3", func(t *testing.T) { + t.Parallel() + + args := createMockArgumentsForSCQuery() + rootHashCalledCounter := 0 + args.MainBlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV3{} + }, + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + RootHash: []byte("references block root hash"), + }, + }, + } + }, + } + args.APIBlockChain = &testscommon.ChainHandlerStub{ + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + rootHashCalledCounter++ + println(rootHashCalledCounter) + if rootHashCalledCounter < 2 { // first call is during root hash extraction for recreate trie + return 0, []byte{}, []byte("first root hash") + } + + return 0, []byte{}, []byte("second root hash") + }, + } + + qs, _ := NewSCQueryService(args) + + res, _, err := qs.ExecuteQuery(&process.SCQuery{ + SameScState: true, + ScAddress: []byte(DummyScAddress), + FuncName: "function", + }) + require.Nil(t, res) + + require.ErrorIs(t, err, process.ErrStateChangedWhileExecutingVmQuery) }) - require.Nil(t, res) - require.ErrorIs(t, err, process.ErrStateChangedWhileExecutingVmQuery) } func TestSCQueryService_ShouldWorkIfStateDidntChange(t *testing.T) { @@ -1396,3 +1449,227 @@ func TestNewSCQueryService_CloseShouldWork(t *testing.T) { assert.Nil(t, err) assert.True(t, closeCalled) } + +func TestSCQueryService_ExtractBlockHeaderAndRootHash(t *testing.T) { + t.Parallel() + + t.Run("if not query hash and nonce and not header v3, get current blockchain info", func(t *testing.T) { + t.Parallel() + + argsNewSCQuery := createMockArgumentsForSCQuery() + argsNewSCQuery.Marshaller = &marshallerMock.MarshalizerMock{} + argsNewSCQuery.BlockChainHook = &testscommon.BlockChainHookStub{} + + currHeader := &block.HeaderV2{} + currRootHash := []byte("rootHash1") + currHeaderHash := []byte("headerHash1") + + argsNewSCQuery.MainBlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return currHeader + }, + GetCurrentBlockRootHashCalled: func() []byte { + return currRootHash + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return currHeaderHash + }, + } + + argsNewSCQuery.HistoryRepository = &dblookupext.HistoryRepositoryStub{ + GetEpochByHashCalled: func(hash []byte) (uint32, error) { + return 2, nil + }, + } + + argsNewSCQuery.StorageService = &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{}, nil + }, + } + + qs, err := NewSCQueryService(argsNewSCQuery) + require.NoError(t, err) + + query := process.SCQuery{ + ScAddress: []byte(DummyScAddress), + FuncName: "function", + Arguments: [][]byte{}, + } + + retHeader, retRootHash, retHeaderHash, err := qs.ExtractBlockHeaderAndRootHash(&query) + require.NoError(t, err) + + require.Equal(t, currHeader, retHeader) + require.Equal(t, currRootHash, retRootHash) + require.Equal(t, currHeaderHash, retHeaderHash) + }) + + t.Run("if not query hash and nonce and header v3, get current blockchain info", func(t *testing.T) { + t.Parallel() + + argsNewSCQuery := createMockArgumentsForSCQuery() + argsNewSCQuery.Marshaller = &marshallerMock.MarshalizerMock{} + argsNewSCQuery.BlockChainHook = &testscommon.BlockChainHookStub{} + + header := &block.HeaderV3{} + rootHash := []byte("rootHash1") + headerHash := []byte("headerHash1") + + argsNewSCQuery.MainBlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + GetCurrentBlockRootHashCalled: func() []byte { + require.Fail(t, "should have not been called") + return nil + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + require.Fail(t, "should have not been called") + return nil + }, + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return 0, headerHash, rootHash + }, + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + } + + argsNewSCQuery.HistoryRepository = &dblookupext.HistoryRepositoryStub{ + GetEpochByHashCalled: func(hash []byte) (uint32, error) { + return 2, nil + }, + } + + argsNewSCQuery.StorageService = &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{}, nil + }, + } + + qs, err := NewSCQueryService(argsNewSCQuery) + require.NoError(t, err) + + query := process.SCQuery{ + ScAddress: []byte(DummyScAddress), + FuncName: "function", + Arguments: [][]byte{}, + } + + retHeader, retRootHash, retHeaderHash, err := qs.ExtractBlockHeaderAndRootHash(&query) + require.NoError(t, err) + + require.Equal(t, header, retHeader) + require.Equal(t, rootHash, retRootHash) + require.Equal(t, headerHash, retHeaderHash) + }) + + t.Run("if query hash and header v3, get current blockchain info", func(t *testing.T) { + t.Parallel() + + marshaller := &marshal.GogoProtoMarshalizer{} + + argsNewSCQuery := createMockArgumentsForSCQuery() + argsNewSCQuery.Marshaller = marshaller + argsNewSCQuery.BlockChainHook = &testscommon.BlockChainHookStub{} + + header := &block.HeaderV3{ + Nonce: 10, + LastExecutionResult: &block.ExecutionResultInfo{}, + } + headerBytes, _ := marshaller.Marshal(header) + + rootHash := []byte("rootHash1") + headerHash := []byte("headerHash1") + queryHash := []byte("headerHash2") + + execRes := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: headerHash, + RootHash: rootHash, + }, + } + execResBytes, _ := marshaller.Marshal(execRes) + + argsNewSCQuery.MainBlockChain = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + GetCurrentBlockRootHashCalled: func() []byte { + require.Fail(t, "should have not been called") + return nil + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + require.Fail(t, "should have not been called") + return nil + }, + GetLastExecutedBlockInfoCalled: func() (uint64, []byte, []byte) { + return 0, headerHash, rootHash + }, + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + } + + argsNewSCQuery.HistoryRepository = &dblookupext.HistoryRepositoryStub{ + IsEnabledCalled: func() bool { + return false + }, + GetEpochByHashCalled: func(hash []byte) (uint32, error) { + return 2, nil + }, + } + + argsNewSCQuery.StorageService = &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + if unitType == dataRetriever.BlockHeaderUnit { + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + if bytes.Equal(key, queryHash) { + return headerBytes, nil + } + + return nil, errors.New("not found") + }, + }, nil + } + + if unitType == dataRetriever.ExecutionResultsUnit { + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + if bytes.Equal(key, queryHash) { + return execResBytes, nil + } + + return nil, errors.New("not found") + }, + }, nil + } + + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + return nil, errors.New("not found") + }, + }, nil + }, + } + + qs, err := NewSCQueryService(argsNewSCQuery) + require.NoError(t, err) + + query := process.SCQuery{ + ScAddress: []byte(DummyScAddress), + FuncName: "function", + Arguments: [][]byte{}, + BlockHash: queryHash, + } + + retHeader, retRootHash, retHeaderHash, err := qs.ExtractBlockHeaderAndRootHash(&query) + require.NoError(t, err) + + require.Equal(t, header, retHeader) + require.Equal(t, rootHash, retRootHash) + require.Equal(t, queryHash, retHeaderHash) + }) +} diff --git a/process/smartContract/scrCommon/common.go b/process/smartContract/scrCommon/common.go index 8cd8efd6484..9c1dbbaadc9 100644 --- a/process/smartContract/scrCommon/common.go +++ b/process/smartContract/scrCommon/common.go @@ -49,7 +49,7 @@ type ArgsNewSmartContractProcessor struct { GasSchedule core.GasScheduleNotifier TxLogsProcessor process.TransactionLogProcessor BadTxForwarder process.IntermediateTransactionHandler - EnableRoundsHandler process.EnableRoundsHandler + EnableRoundsHandler common.EnableRoundsHandler EnableEpochsHandler common.EnableEpochsHandler EnableEpochs config.EnableEpochs VMOutputCacher storage.Cacher diff --git a/process/smartContract/testScProcessor.go b/process/smartContract/testScProcessor.go index d602619c61e..3d4ccde011a 100644 --- a/process/smartContract/testScProcessor.go +++ b/process/smartContract/testScProcessor.go @@ -31,7 +31,7 @@ func (tsp *TestScProcessor) GetCompositeTestError() error { if tsp.enableEpochsHandler.IsFlagEnabled(common.CleanUpInformativeSCRsFlag) { allLogs := tsp.txLogsProcessor.GetAllCurrentLogs() for _, logs := range allLogs { - for _, event := range logs.GetLogEvents() { + for _, event := range logs.GetLogHandler().GetLogEvents() { if string(event.GetIdentifier()) == core.SignalErrorOperation { returnError = wrapErrorIfNotContains(returnError, string(event.GetTopics()[1])) } diff --git a/process/sync/argBootstrapper.go b/process/sync/argBootstrapper.go index 587ecedd258..1e7c35bd11c 100644 --- a/process/sync/argBootstrapper.go +++ b/process/sync/argBootstrapper.go @@ -27,7 +27,7 @@ type ArgBaseBootstrapper struct { ChainHandler data.ChainHandler RoundHandler consensus.RoundHandler BlockProcessor process.BlockProcessor - WaitTime time.Duration + ExecutionManager process.ExecutionManager Hasher hashing.Hasher Marshalizer marshal.Marshalizer ForkDetector process.ForkDetector @@ -48,8 +48,11 @@ type ArgBaseBootstrapper struct { IsInImportMode bool ScheduledTxsExecutionHandler process.ScheduledTxsExecutionHandler ProcessWaitTime time.Duration + ProcessWaitTimeSupernova time.Duration RepopulateTokensSupplies bool EnableEpochsHandler common.EnableEpochsHandler + EnableRoundsHandler common.EnableRoundsHandler + ProcessConfigsHandler common.ProcessConfigsHandler } // ArgShardBootstrapper holds all dependencies required by the bootstrap data factory in order to create diff --git a/process/sync/baseForkDetector.go b/process/sync/baseForkDetector.go index 04e3938d6ff..68a86a59c62 100644 --- a/process/sync/baseForkDetector.go +++ b/process/sync/baseForkDetector.go @@ -46,16 +46,21 @@ type baseForkDetector struct { fork forkInfo mutFork sync.RWMutex - blackListHandler process.TimeCacher - genesisTime int64 - blockTracker process.BlockTracker - forkDetector forkDetector - genesisNonce uint64 - genesisRound uint64 - maxForkHeaderEpoch uint32 - genesisEpoch uint32 - enableEpochsHandler common.EnableEpochsHandler - proofsPool process.ProofsPool + shardID uint32 + blackListHandler process.TimeCacher + genesisTime int64 + supernovaGenesisTime int64 + blockTracker process.BlockTracker + forkDetector forkDetector + genesisNonce uint64 + genesisRound uint64 + maxForkHeaderEpoch uint32 + genesisEpoch uint32 + enableEpochsHandler common.EnableEpochsHandler + enableRoundsHandler common.EnableRoundsHandler + proofsPool process.ProofsPool + chainParametersHandler common.ChainParametersHandler + processConfigsHandler common.ProcessConfigsHandler } // SetRollBackNonce sets the nonce where the chain should roll back @@ -109,7 +114,6 @@ func (bfd *baseForkDetector) checkBlockBasicValidity( nonceDif := int64(header.GetNonce()) - int64(bfd.finalCheckpoint().nonce) // TODO: Analyze if the acceptance of some headers which came for the next round could generate some attack vectors nextRound := bfd.roundHandler.Index() + 1 - genesisTimeFromHeader := bfd.computeGenesisTimeFromHeader(header) bfd.blackListHandler.Sweep() if bfd.blackListHandler.Has(string(header.GetPrevHash())) { @@ -117,7 +121,9 @@ func (bfd *baseForkDetector) checkBlockBasicValidity( return process.ErrHeaderIsBlackListed } // TODO: This check could be removed when this protection mechanism would be implemented on interceptors side - if genesisTimeFromHeader != bfd.genesisTime { + + err := bfd.checkGenesisTimeForHeader(header) + if err != nil { process.AddHeaderToBlackList(bfd.blackListHandler, headerHash) return ErrGenesisTimeMissmatch } @@ -229,6 +235,14 @@ func (bfd *baseForkDetector) RemoveHeader(nonce uint64, hash []byte) { return } + if bfd.proofsPool.HasProof(bfd.shardID, hash) { + log.Debug("baseForkDetector.RemoveHeader: proof available for the given header, skipping removal", + "nonce", nonce, + "hash", hash, + "final checkpoint nonce", finalCheckpointNonce) + return + } + bfd.removeCheckpointWithNonce(nonce) preservedHdrsInfo := make([]*headerInfo, 0) @@ -639,8 +653,9 @@ func (bfd *baseForkDetector) isConsensusStuck() bool { return false } - roundsDifference := bfd.roundHandler.Index() - int64(bfd.lastCheckpoint().round) - if roundsDifference <= process.MaxRoundsWithoutCommittedBlock { + lastCheckpointRound := bfd.lastCheckpoint().round + roundsDifference := bfd.roundHandler.Index() - int64(lastCheckpointRound) + if roundsDifference <= bfd.getMaxRoundsWithoutCommittedBlock(uint64(bfd.roundHandler.Index())) { return false } @@ -651,6 +666,10 @@ func (bfd *baseForkDetector) isConsensusStuck() bool { return true } +func (bfd *baseForkDetector) getMaxRoundsWithoutCommittedBlock(round uint64) int64 { + return int64(bfd.processConfigsHandler.GetMaxRoundsWithoutCommittedBlock(round)) +} + func (bfd *baseForkDetector) isSyncing() bool { noncesDifference := int64(bfd.ProbableHighestNonce()) - int64(bfd.lastCheckpoint().nonce) isSyncing := noncesDifference > process.NonceDifferenceWhenSynced @@ -699,9 +718,140 @@ func (bfd *baseForkDetector) cleanupReceivedHeadersHigherThanNonce(nonce uint64) bfd.mutHeaders.Unlock() } -func (bfd *baseForkDetector) computeGenesisTimeFromHeader(headerHandler data.HeaderHandler) int64 { - genesisTime := int64(headerHandler.GetTimeStamp() - (headerHandler.GetRound()-bfd.genesisRound)*uint64(bfd.roundHandler.TimeDuration().Seconds())) - return genesisTime +func (bfd *baseForkDetector) checkGenesisTimeForHeaderBeforeSupernova( + headerHandler data.HeaderHandler, +) error { + chainParams, err := bfd.chainParametersHandler.ChainParametersForEpoch(headerHandler.GetEpoch()) + if err != nil { + return err + } + roundDuration := int64(chainParams.RoundDuration) + + // The round duration is provided as milliseconds in the configuration. It needs to be + // converted to seconds to ensure correct calculations for genesis time before + // supernova activation. + roundDuration /= 1000 + + roundDifference := int64(headerHandler.GetRound() - bfd.genesisRound) + genesisTime := int64(headerHandler.GetTimeStamp()) - roundDifference*roundDuration + + if genesisTime != bfd.genesisTime { + log.Error("checkGenesisTimeForHeaderBeforeSupernova: genesis time mismatch", + "localGenesisTime", bfd.genesisTime, + "calculatedGenesisTime", genesisTime, + "header timestamp", headerHandler.GetTimeStamp(), + ) + + return ErrGenesisTimeMissmatch + } + + return nil +} + +func (bfd *baseForkDetector) getPrevSupernovaActivationEpoch(currentEpoch uint32) uint32 { + // in this interval, chain parameters have to be taken from the epoch previous to supernova + if currentEpoch == 0 { + return currentEpoch + } + + return currentEpoch - 1 +} + +func (bfd *baseForkDetector) checkGenesisTimeForHeaderAfterSupernovaWithoutRoundActivation( + headerHandler data.HeaderHandler, +) error { + chainParams, err := bfd.chainParametersHandler.ChainParametersForEpoch(bfd.getPrevSupernovaActivationEpoch(headerHandler.GetEpoch())) + if err != nil { + return err + } + roundDuration := int64(chainParams.RoundDuration) + roundDifference := int64(headerHandler.GetRound() - bfd.genesisRound) + genesisTime := int64(headerHandler.GetTimeStamp()) - roundDifference*roundDuration + + log.Trace("getGenesisTimeForHeaderAfterSupernovaWithoutRoundActivation", + "roundDuration", roundDuration, + "roundDifference", roundDifference, + "calculated genesisTime", genesisTime, + "genesisTime", bfd.genesisTime, + ) + + // if supernova is activated from genesis (epoch zero) this reduction is not needed since + // genesisTime from config will be directly as milliseconds; otherwise it has to be + // reduced to seconds granularity, in this specific interval (when supernova epoch is + // activated but supernova round is not yet activated) + supernovaActivatedInEpochZero := bfd.enableEpochsHandler.IsFlagEnabledInEpoch(common.SupernovaFlag, 0) + if !supernovaActivatedInEpochZero { + genesisTime /= 1000 + } + + if genesisTime != bfd.genesisTime { + log.Error("checkGenesisTimeForHeaderAfterSupernovaWithoutRoundActivation: genesis time mismatch", + "localGenesisTime", bfd.genesisTime, + "calculatedGenesisTime", genesisTime, + "header timestamp", headerHandler.GetTimeStamp(), + ) + return ErrGenesisTimeMissmatch + } + + return nil +} + +func (bfd *baseForkDetector) checkGenesisTimeForHeaderAfterSupernovaWithRoundActivation( + headerHandler data.HeaderHandler, +) error { + activationRound := bfd.enableRoundsHandler.GetActivationRound(common.SupernovaRoundFlag) + + chainParams, err := bfd.chainParametersHandler.ChainParametersForEpoch(headerHandler.GetEpoch()) + if err != nil { + return err + } + roundDuration := int64(chainParams.RoundDuration) + + roundDifference := int64(headerHandler.GetRound()) - int64(activationRound) + if roundDifference < 0 { + log.Warn("current round lower than supernova activation round", + "current round", headerHandler.GetRound(), + "supernova activationRound", activationRound, + ) + + return ErrGenesisTimeMissmatch + } + + genesisTime := int64(headerHandler.GetTimeStamp()) - roundDifference*roundDuration + + log.Trace("getGenesisTimeForHeaderAfterSupernovaWithRoundActivation", + "activationRound", activationRound, + "roundDuration", roundDuration, + "roundDifference", roundDifference, + "genesisTime", genesisTime, + "supernovaGenesisTime", bfd.supernovaGenesisTime, + ) + + if genesisTime != bfd.supernovaGenesisTime { + log.Error("checkGenesisTimeForHeaderAfterSupernovaWithRoundActivation: genesis time mismatch", + "localGenesisTime", bfd.supernovaGenesisTime, + "calculatedGenesisTime", genesisTime, + "header timestamp", headerHandler.GetTimeStamp(), + ) + return ErrGenesisTimeMissmatch + } + + return nil +} + +func (bfd *baseForkDetector) checkGenesisTimeForHeader(headerHandler data.HeaderHandler) error { + supernovaInEpochActivated := bfd.enableEpochsHandler.IsFlagEnabledInEpoch(common.SupernovaFlag, headerHandler.GetEpoch()) + supernovaInRoundActivated := bfd.enableRoundsHandler.IsFlagEnabledInRound(common.SupernovaRoundFlag, headerHandler.GetRound()) + + if !supernovaInEpochActivated { + return bfd.checkGenesisTimeForHeaderBeforeSupernova(headerHandler) + } + + if !supernovaInRoundActivated { + return bfd.checkGenesisTimeForHeaderAfterSupernovaWithoutRoundActivation(headerHandler) + } + + return bfd.checkGenesisTimeForHeaderAfterSupernovaWithRoundActivation(headerHandler) } func (bfd *baseForkDetector) addHeader( diff --git a/process/sync/baseForkDetector_test.go b/process/sync/baseForkDetector_test.go index 0d23431b263..2fa476de1df 100644 --- a/process/sync/baseForkDetector_test.go +++ b/process/sync/baseForkDetector_test.go @@ -1,11 +1,13 @@ package sync_test import ( - "github.com/multiversx/mx-chain-go/testscommon/processMocks" "math" "testing" "time" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" @@ -29,8 +31,13 @@ func TestNewBasicForkDetector_ShouldErrNilRoundHandler(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) assert.Equal(t, process.ErrNilRoundHandler, err) assert.Nil(t, bfd) @@ -45,8 +52,13 @@ func TestNewBasicForkDetector_ShouldErrNilBlackListHandler(t *testing.T) { nil, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) assert.Equal(t, process.ErrNilBlackListCacher, err) assert.Nil(t, bfd) @@ -61,8 +73,13 @@ func TestNewBasicForkDetector_ShouldErrNilBlockTracker(t *testing.T) { &testscommon.TimeCacheStub{}, nil, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) assert.Equal(t, process.ErrNilBlockTracker, err) assert.Nil(t, bfd) @@ -77,8 +94,13 @@ func TestNewBasicForkDetector_ShouldErrNilEnableEpochsHandler(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, nil, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) assert.Equal(t, process.ErrNilEnableEpochsHandler, err) assert.Nil(t, bfd) @@ -93,8 +115,13 @@ func TestNewBasicForkDetector_ShouldErrNilProofsPool(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, nil, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) assert.Equal(t, process.ErrNilProofsPool, err) assert.Nil(t, bfd) @@ -109,8 +136,13 @@ func TestNewBasicForkDetector_ShouldWork(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) assert.Nil(t, err) assert.NotNil(t, bfd) @@ -130,8 +162,13 @@ func TestBasicForkDetector_CheckBlockValidityShouldErrGenesisTimeMissmatch(t *te &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, genesisTime, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) err := bfd.CheckBlockValidity(&block.Header{Nonce: 1, Round: round, TimeStamp: incorrectTimeStamp}, []byte("hash")) @@ -150,8 +187,13 @@ func TestBasicForkDetector_CheckBlockValidityShouldErrLowerRoundInBlock(t *testi &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) bfd.SetFinalCheckpoint(1, 1, nil) err := bfd.CheckBlockValidity(&block.Header{PubKeysBitmap: []byte("X")}, []byte("hash")) @@ -167,8 +209,13 @@ func TestBasicForkDetector_CheckBlockValidityShouldErrLowerNonceInBlock(t *testi &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) bfd.SetFinalCheckpoint(2, 2, nil) err := bfd.CheckBlockValidity(&block.Header{Nonce: 1, Round: 3, PubKeysBitmap: []byte("X")}, []byte("hash")) @@ -184,8 +231,13 @@ func TestBasicForkDetector_CheckBlockValidityShouldErrHigherRoundInBlock(t *test &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) err := bfd.CheckBlockValidity(&block.Header{Nonce: 1, Round: 2, PubKeysBitmap: []byte("X")}, []byte("hash")) assert.Equal(t, sync.ErrHigherRoundInBlock, err) @@ -200,8 +252,13 @@ func TestBasicForkDetector_CheckBlockValidityShouldErrHigherNonceInBlock(t *test &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) err := bfd.CheckBlockValidity(&block.Header{Nonce: 2, Round: 1, PubKeysBitmap: []byte("X")}, []byte("hash")) assert.Equal(t, sync.ErrHigherNonceInBlock, err) @@ -216,8 +273,13 @@ func TestBasicForkDetector_CheckBlockValidityShouldWork(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) err := bfd.CheckBlockValidity(&block.Header{Nonce: 1, Round: 1, PubKeysBitmap: []byte("X")}, []byte("hash")) assert.Nil(t, err) @@ -236,8 +298,13 @@ func TestBasicForkDetector_RemoveHeadersShouldWork(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) roundHandlerMock.RoundIndex = 1 @@ -269,8 +336,13 @@ func TestBasicForkDetector_CheckForkOnlyOneShardHeaderOnANonceShouldReturnFalse( &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) _ = bfd.AddHeader( &block.Header{Nonce: 0, PubKeysBitmap: []byte("X")}, @@ -299,8 +371,13 @@ func TestBasicForkDetector_CheckForkOnlyReceivedHeadersShouldReturnFalse(t *test &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) _ = bfd.AddHeader( &block.Header{Nonce: 0, PubKeysBitmap: []byte("X")}, @@ -331,8 +408,13 @@ func TestBasicForkDetector_CheckForkOnlyOneShardHeaderOnANonceReceivedAndProcess &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) _ = bfd.AddHeader( &block.Header{Nonce: 0, PubKeysBitmap: []byte("X")}, @@ -363,8 +445,12 @@ func TestBasicForkDetector_CheckForkMetaHeaderProcessedShouldReturnFalse(t *test &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) _ = bfd.AddHeader( &block.MetaBlock{Nonce: 1, Round: 3, PubKeysBitmap: []byte("X")}, @@ -393,8 +479,12 @@ func TestBasicForkDetector_CheckForkMetaHeaderProcessedShouldReturnFalseWhenLowe &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) roundHandlerMock.RoundIndex = 5 _ = bfd.AddHeader( @@ -439,8 +529,12 @@ func TestBasicForkDetector_CheckForkMetaHeaderProcessedShouldReturnFalseWhenEqua &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) roundHandlerMock.RoundIndex = 5 _ = bfd.AddHeader( @@ -484,8 +578,13 @@ func TestBasicForkDetector_CheckForkShardHeaderProcessedShouldReturnTrueWhenEqua &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) hdr1 := &block.Header{Nonce: 1, Round: 4, PubKeysBitmap: []byte("X")} @@ -550,8 +649,12 @@ func TestBasicForkDetector_CheckForkMetaHeaderProcessedShouldReturnTrueWhenEqual &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) roundHandlerMock.RoundIndex = 5 _ = bfd.AddHeader( @@ -594,8 +697,13 @@ func TestBasicForkDetector_CheckForkShardHeaderProcessedShouldReturnTrueWhenEqua &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) hdr1 := &block.Header{Nonce: 1, Round: 4, PubKeysBitmap: []byte("X")} @@ -659,8 +767,12 @@ func TestBasicForkDetector_CheckForkShouldReturnTrue(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) roundHandlerMock.RoundIndex = 4 _ = bfd.AddHeader( @@ -705,8 +817,12 @@ func TestBasicForkDetector_CheckForkShouldReturnFalseWhenForkIsOnFinalCheckpoint &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) roundHandlerMock.RoundIndex = 1 _ = bfd.AddHeader( @@ -743,8 +859,12 @@ func TestBasicForkDetector_CheckForkShouldReturnFalseWhenForkIsOnHigherEpochBloc &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) roundHandlerMock.RoundIndex = 2 _ = bfd.AddHeader( @@ -787,8 +907,13 @@ func TestBasicForkDetector_RemovePastHeadersShouldWork(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) _ = bfd.AddHeader(hdr1, hash1, process.BHReceived, nil, nil) _ = bfd.AddHeader(hdr2, hash2, process.BHReceived, nil, nil) @@ -823,8 +948,13 @@ func TestBasicForkDetector_RemoveInvalidReceivedHeadersShouldWork(t *testing.T) &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) roundHandlerMock.RoundIndex = 11 _ = bfd.AddHeader(hdr0, hash0, process.BHReceived, nil, nil) @@ -863,8 +993,13 @@ func TestBasicForkDetector_RemoveCheckpointHeaderNonceShouldResetCheckpoint(t *t &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) _ = bfd.AddHeader(hdr1, hash1, process.BHProcessed, nil, nil) @@ -884,8 +1019,12 @@ func TestBasicForkDetector_GetHighestFinalBlockNonce(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) hdr1 := &block.MetaBlock{Nonce: 2, Round: 1, PubKeysBitmap: []byte("X")} @@ -923,12 +1062,16 @@ func TestBasicForkDetector_ProbableHighestNonce(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{ IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { return flag != common.AndromedaFlag }, }, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) roundHandlerMock.RoundIndex = 11 @@ -986,8 +1129,13 @@ func TestShardForkDetector_ShouldAddBlockInForkDetectorShouldWork(t *testing.T) &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) hdr := &block.Header{Nonce: 1, Round: 1} @@ -1011,8 +1159,13 @@ func TestShardForkDetector_ShouldAddBlockInForkDetectorShouldErrLowerRoundInBloc &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) hdr := &block.Header{Nonce: 1, Round: 1} @@ -1030,8 +1183,12 @@ func TestMetaForkDetector_ShouldAddBlockInForkDetectorShouldWork(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) hdr := &block.MetaBlock{Nonce: 1, Round: 1} @@ -1055,8 +1212,12 @@ func TestMetaForkDetector_ShouldAddBlockInForkDetectorShouldErrLowerRoundInBlock &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) hdr := &block.MetaBlock{Nonce: 1, Round: 1} @@ -1074,8 +1235,13 @@ func TestShardForkDetector_AddNotarizedHeadersShouldNotChangeTheFinalCheckpoint( &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) hdr1 := &block.Header{Nonce: 3, Round: 3} hash1 := []byte("hash1") @@ -1127,8 +1293,13 @@ func TestBaseForkDetector_IsConsensusStuckNotSyncingShouldReturnFalse(t *testing &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) bfd.SetProbableHighestNonce(1) @@ -1145,8 +1316,12 @@ func TestBaseForkDetector_IsConsensusStuckNoncesDifferencesNotEnoughShouldReturn &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) roundHandlerMock.RoundIndex = 10 @@ -1162,8 +1337,12 @@ func TestBaseForkDetector_IsConsensusStuckNotInProperRoundShouldReturnFalse(t *t &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) roundHandlerMock.RoundIndex = 11 @@ -1173,49 +1352,59 @@ func TestBaseForkDetector_IsConsensusStuckNotInProperRoundShouldReturnFalse(t *t func TestBaseForkDetector_IsConsensusStuckShouldReturnTrue(t *testing.T) { t.Parallel() - roundHandlerMock := &mock.RoundHandlerMock{} - bfd, _ := sync.NewMetaForkDetector( - roundHandlerMock, - &testscommon.TimeCacheStub{}, - &mock.BlockTrackerMock{}, - 0, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &dataRetriever.ProofsPoolMock{}, - ) - - // last checkpoint will be (round = 0 , nonce = 0) - // round difference is higher than 10 - // round index is divisible by RoundModulusTrigger -> 5 - // => consensus is stuck - roundHandlerMock.RoundIndex = 20 - assert.True(t, bfd.IsConsensusStuck()) -} - -func TestBaseForkDetector_ComputeTimeDuration(t *testing.T) { - t.Parallel() - - roundDuration := uint64(1) - roundHandlerMock := &mock.RoundHandlerMock{ - RoundTimeDuration: time.Second, - } - - genesisTime := int64(9000) - hdrTimeStamp := uint64(10000) - hdrRound := uint64(20) - bfd, _ := sync.NewShardForkDetector( - roundHandlerMock, - &testscommon.TimeCacheStub{}, - &mock.BlockTrackerMock{}, - genesisTime, - &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - &dataRetriever.ProofsPoolMock{}, - ) - - hdr1 := &block.Header{Nonce: 1, Round: hdrRound, PubKeysBitmap: []byte("X"), TimeStamp: hdrTimeStamp} - - expectedTimeStamp := hdrTimeStamp - (hdrRound * roundDuration) - timeDuration := bfd.ComputeGenesisTimeFromHeader(hdr1) - assert.Equal(t, int64(expectedTimeStamp), timeDuration) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + roundHandlerMock := &mock.RoundHandlerMock{} + bfd, _ := sync.NewMetaForkDetector( + roundHandlerMock, + &testscommon.TimeCacheStub{}, + &mock.BlockTrackerMock{}, + 0, + 0, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, + &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + ) + + // last checkpoint will be (round = 0 , nonce = 0) + // round difference is higher than 10 + // round index is divisible by RoundModulusTrigger -> 5 + // => consensus is stuck + roundHandlerMock.RoundIndex = 20 + assert.True(t, bfd.IsConsensusStuck()) + }) + + t.Run("should work, with supernova", func(t *testing.T) { + t.Parallel() + + roundHandlerMock := &mock.RoundHandlerMock{} + bfd, _ := sync.NewMetaForkDetector( + roundHandlerMock, + &testscommon.TimeCacheStub{}, + &mock.BlockTrackerMock{}, + 0, + 0, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return flag == common.SupernovaRoundFlag + }, + }, + &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + ) + + // last checkpoint will be (round = 0 , nonce = 0) + // round difference is higher than 10 + // round index is divisible by RoundModulusTrigger -> 5 + // => consensus is stuck + roundHandlerMock.RoundIndex = 200 + assert.True(t, bfd.IsConsensusStuck()) + }) } func TestShardForkDetector_RemoveHeaderShouldComputeFinalCheckpoint(t *testing.T) { @@ -1227,8 +1416,13 @@ func TestShardForkDetector_RemoveHeaderShouldComputeFinalCheckpoint(t *testing.T &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) hdr1 := &block.Header{Nonce: 3, Round: 3} hash1 := []byte("hash1") @@ -1270,8 +1464,12 @@ func TestBasicForkDetector_CheckForkMetaHeaderProcessedShouldWorkOnEqualRoundWit &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) roundHandlerMock.RoundIndex = 5 _ = bfd.AddHeader( @@ -1320,8 +1518,12 @@ func TestBasicForkDetector_SetFinalToLastCheckpointShouldWork(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) roundHandlerMock.RoundIndex = 1000 @@ -1350,12 +1552,16 @@ func TestBaseForkDetector_GetNotarizedHeaderHash(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{ IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { return flag != common.AndromedaFlag }, }, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) roundHandlerMock.RoundIndex = 10 @@ -1406,8 +1612,12 @@ func TestBaseForkDetector_ReceivedProof(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, enableEpochsHandlerStub, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) proof := &processMocks.HeaderProofHandlerStub{ @@ -1464,16 +1674,20 @@ func TestBaseForkDetector_BlockWithoutProofShouldReturnEarly(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{ IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { return true }, }, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{ HasProofCalled: func(shardID uint32, headerHash []byte) bool { return false }, }, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) roundHandlerMock.RoundIndex = 10 @@ -1503,12 +1717,17 @@ func TestBaseForkDetector_ReceivedProofForBlockHeaderShouldSetProof(t *testing.T &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{ IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { return true }, }, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) hdrInfo := &sync.HeaderInfo{ diff --git a/process/sync/baseSync.go b/process/sync/baseSync.go index f66330346df..8e54f2d5221 100644 --- a/process/sync/baseSync.go +++ b/process/sync/baseSync.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/hex" + "errors" "fmt" "math" "sync" @@ -15,11 +16,19 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" outportcore "github.com/multiversx/mx-chain-core-go/data/outport" + "github.com/multiversx/mx-chain-core-go/data/rewardTx" + "github.com/multiversx/mx-chain-core-go/data/smartContractResult" + "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-core-go/data/typeConverters" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/epochStart" + "github.com/multiversx/mx-chain-go/process/asyncExecution/cache" + "github.com/multiversx/mx-chain-go/update" + updateSync "github.com/multiversx/mx-chain-go/update/sync" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/consensus" "github.com/multiversx/mx-chain-go/dataRetriever" @@ -36,11 +45,17 @@ import ( var log = logger.GetOrCreate("process/sync") +type txSizeHandler interface { + Size() int +} + var _ closing.Closer = (*baseBootstrap)(nil) // sleepTime defines the time in milliseconds between each iteration made in syncBlocks method const sleepTime = 5 * time.Millisecond +const sleepTimeOnFail = 400 * time.Millisecond const minimumProcessWaitTime = time.Millisecond * 100 +const defaultTimeToWaitForRequestedData = 5 * time.Minute // hdrInfo hold the data related to a header type hdrInfo struct { @@ -60,10 +75,12 @@ type baseBootstrap struct { historyRepo dblookupext.HistoryRepository headers dataRetriever.HeadersPool proofs dataRetriever.ProofsPool + dataPool dataRetriever.PoolsHolder - chainHandler data.ChainHandler - blockProcessor process.BlockProcessor - store dataRetriever.StorageService + chainHandler data.ChainHandler + blockProcessor process.BlockProcessor + executionManager process.ExecutionManager + store dataRetriever.StorageService roundHandler consensus.RoundHandler hasher hashing.Hasher @@ -76,6 +93,7 @@ type baseBootstrap struct { blockBootstrapper blockBootstrapper blackListHandler process.TimeCacher enableEpochsHandler common.EnableEpochsHandler + enableRoundsHandler common.EnableRoundsHandler mutHeader sync.RWMutex headerNonce *uint64 @@ -88,7 +106,6 @@ type baseBootstrap struct { statusHandler core.AppStatusHandler chStopSync chan bool - waitTime time.Duration mutNodeState sync.RWMutex isNodeSynchronized bool @@ -117,8 +134,9 @@ type baseBootstrap struct { storageBootstrapper process.BootstrapperFromStorage currentEpochProvider process.CurrentNetworkEpochProviderHandler - outportHandler outport.OutportHandler - accountsDBSyncer process.AccountsDBSyncer + outportHandler outport.OutportHandler + accountsDBSyncer process.AccountsDBSyncer + processConfigsHandler common.ProcessConfigsHandler chRcvMiniBlocks chan bool mutRcvMiniBlocks sync.Mutex @@ -129,8 +147,24 @@ type baseBootstrap struct { isInImportMode bool scheduledTxsExecutionHandler process.ScheduledTxsExecutionHandler processWaitTime time.Duration + processWaitTimeSupernova time.Duration + preparedForSync bool + preparedForSyncAtBootstrap bool repopulateTokensSupplies bool + + miniBlocksSyncer epochStart.PendingMiniBlocksSyncHandler + txSyncer update.TransactionsSyncHandler + + signalProcessCompletionChan chan uint64 +} + +func (boot *baseBootstrap) getProcessWaitTime(round uint64) time.Duration { + if boot.enableRoundsHandler.IsFlagEnabledInRound(common.SupernovaRoundFlag, round) { + return boot.processWaitTimeSupernova + } + + return boot.processWaitTime } // setRequestedHeaderNonce method sets the header nonce requested by the sync mechanism @@ -183,7 +217,7 @@ func (boot *baseBootstrap) checkProofCorrespondsToRequestedHash(headerProof data // if header is also received, release the chan and set requested to nil // otherwise wait for the header - _, err := boot.headers.GetHeaderByHash(headerProof.GetHeaderHash()) + _, err := boot.getHeader(headerProof.GetHeaderHash()) hasHeader := err == nil if hasHeader { boot.setRequestedHeaderHash(nil) @@ -208,7 +242,7 @@ func (boot *baseBootstrap) checkProofCorrespondsToRequestedNonce(headerProof dat // if header is also received, release the chan and set requested to nil // otherwise wait for the header - _, err := boot.headers.GetHeaderByHash(headerProof.GetHeaderHash()) + _, err := boot.getHeader(headerProof.GetHeaderHash()) hasHeader := err == nil if hasHeader { boot.setRequestedHeaderNonce(nil) @@ -227,7 +261,7 @@ func (boot *baseBootstrap) processReceivedHeader(headerHandler data.HeaderHandle return } - log.Trace("received header from network", + log.Debug("sync: received header from network", "shard", headerHandler.GetShardID(), "round", headerHandler.GetRound(), "nonce", headerHandler.GetNonce(), @@ -370,6 +404,34 @@ func (boot *baseBootstrap) getNonceForNextBlock() uint64 { return nonce } +// getCurrentBlock will get the current block +func (boot *baseBootstrap) getCurrentBlock() data.HeaderHandler { + currentBlockHeader := boot.chainHandler.GetCurrentBlockHeader() + if !check.IfNil(currentBlockHeader) { + return currentBlockHeader + } + return boot.chainHandler.GetGenesisHeader() +} + +// getCurrentRootHashLegacy will get the current root hash +func (boot *baseBootstrap) getCurrentRootHashLegacy() []byte { + currentRootHash := boot.chainHandler.GetCurrentBlockRootHash() + if len(currentRootHash) != 0 { + return currentRootHash + } + genesisHeader := boot.chainHandler.GetGenesisHeader() + return genesisHeader.GetRootHash() +} + +// getCurrentBlockHash will get the current block hash +func (boot *baseBootstrap) getCurrentBlockHash() []byte { + currentHash := boot.chainHandler.GetCurrentBlockHeaderHash() + if len(currentHash) != 0 { + return currentHash + } + return boot.chainHandler.GetGenesisHeaderHash() +} + // getNonceForCurrentBlock will get the nonce for the current block func (boot *baseBootstrap) getNonceForCurrentBlock() uint64 { nonce := boot.chainHandler.GetGenesisHeader().GetNonce() // genesis block nonce @@ -390,22 +452,26 @@ func (boot *baseBootstrap) getEpochOfCurrentBlock() uint32 { return epoch } +func (boot *baseBootstrap) getWaitTime() time.Duration { + return boot.roundHandler.TimeDuration() +} + // waitForHeaderAndProofByNonce method wait for header with the requested nonce to be received func (boot *baseBootstrap) waitForHeaderAndProofByNonce() error { select { case <-boot.chRcvHdrNonce: return nil - case <-time.After(boot.waitTime): + case <-time.After(boot.getWaitTime()): return process.ErrTimeIsOut } } -// waitForHeaderHash method wait for header with the requested hash to be received +// waitForHeaderAndProofByHash method wait for header with the requested hash to be received func (boot *baseBootstrap) waitForHeaderAndProofByHash() error { select { case <-boot.chRcvHdrHash: return nil - case <-time.After(boot.waitTime): + case <-time.After(boot.getWaitTime()): return process.ErrTimeIsOut } } @@ -479,7 +545,10 @@ func (boot *baseBootstrap) shouldTryToRequestHeaders() bool { return true } - return boot.roundHandler.Index()%process.RoundModulusTriggerWhenSyncIsStuck == 0 + roundIndex := boot.roundHandler.Index() + roundModulusTriggerWhenSyncIsStuck := boot.processConfigsHandler.GetRoundModulusTriggerWhenSyncIsStuck(uint64(roundIndex)) + + return roundIndex%int64(roundModulusTriggerWhenSyncIsStuck) == 0 } func (boot *baseBootstrap) requestHeadersIfSyncIsStuck() { @@ -490,7 +559,7 @@ func (boot *baseBootstrap) requestHeadersIfSyncIsStuck() { } roundDiff := uint64(boot.roundHandler.Index()) - lastSyncedRound - if roundDiff <= process.MaxRoundsWithoutNewBlockReceived { + if roundDiff <= boot.getMaxRoundsWithoutBlockReceived(lastSyncedRound) { return } @@ -510,6 +579,10 @@ func (boot *baseBootstrap) requestHeadersIfSyncIsStuck() { boot.requestHeaders(fromNonce, toNonce) } +func (boot *baseBootstrap) getMaxRoundsWithoutBlockReceived(round uint64) uint64 { + return uint64(boot.processConfigsHandler.GetMaxRoundsWithoutNewBlockReceivedByRound(round)) +} + func (boot *baseBootstrap) removeHeaderFromPools(header data.HeaderHandler) []byte { hash, err := core.CalculateHash(boot.marshalizer, boot.hasher, header) if err != nil { @@ -563,6 +636,9 @@ func checkBaseBootstrapParameters(arguments ArgBaseBootstrapper) error { if check.IfNil(arguments.BlockProcessor) { return process.ErrNilBlockProcessor } + if check.IfNil(arguments.ExecutionManager) { + return process.ErrNilExecutionManager + } if check.IfNil(arguments.Hasher) { return process.ErrNilHasher } @@ -617,9 +693,15 @@ func checkBaseBootstrapParameters(arguments ArgBaseBootstrapper) error { if arguments.ProcessWaitTime < minimumProcessWaitTime { return fmt.Errorf("%w, minimum is %v, provided is %v", process.ErrInvalidProcessWaitTime, minimumProcessWaitTime, arguments.ProcessWaitTime) } + if arguments.ProcessWaitTimeSupernova < minimumProcessWaitTime { + return fmt.Errorf("%w for Supernova, minimum is %v, provided is %v", process.ErrInvalidProcessWaitTime, minimumProcessWaitTime, arguments.ProcessWaitTimeSupernova) + } if check.IfNil(arguments.EnableEpochsHandler) { return process.ErrNilEnableEpochsHandler } + if check.IfNil(arguments.EnableRoundsHandler) { + return process.ErrNilEnableRoundsHandler + } return nil } @@ -628,7 +710,9 @@ func (boot *baseBootstrap) requestHeadersFromNonceIfMissing(fromNonce uint64) { toNonce := core.MinUint64(fromNonce+process.MaxHeadersToRequestInAdvance-1, boot.forkDetector.ProbableHighestNonce()) if fromNonce > toNonce { - return + // request at least the next header so the fork detector + // can discover blocks beyond probableHighestNonce + toNonce = fromNonce } log.Debug("requestHeadersFromNonceIfMissing", @@ -664,21 +748,55 @@ func (boot *baseBootstrap) syncBlocks(ctx context.Context) { } log.Debug("SyncBlock", "error", err.Error()) + + select { + case nonce := <-boot.signalProcessCompletionChan: + log.Debug("SyncBlock - error - notification process finished", "nonce", nonce) + case <-time.After(sleepTimeOnFail): + } + } else { + // Non-blocking drain of completion signal when sync succeeds + select { + case nonce := <-boot.signalProcessCompletionChan: + log.Debug("SyncBlock - success - notification process finished", "nonce", nonce) + default: + } } } } +func (boot *baseBootstrap) getMaxSyncWithErrorsAllowed( + header data.HeaderHandler, +) uint32 { + round := uint64(0) + if !check.IfNil(header) { + round = header.GetRound() + } + + return boot.processConfigsHandler.GetMaxSyncWithErrorsAllowed(round) +} + func (boot *baseBootstrap) doJobOnSyncBlockFail(bodyHandler data.BodyHandler, headerHandler data.HeaderHandler, err error) { + if errors.Is(err, process.ErrBlockProcessorBusy) { + // block processor is busy with another call (e.g. consensus processing the same block); + // no processing started, nothing to track or roll back - just retry on next sync iteration + return + } + processBlockStarted := !check.IfNil(bodyHandler) && !check.IfNil(headerHandler) - isProcessWithError := processBlockStarted && err != process.ErrTimeIsOut + isProcessWithError := processBlockStarted && !errors.Is(err, process.ErrTimeIsOut) numSyncedWithErrors := boot.incrementSyncedWithErrorsForNonce(boot.getNonceForNextBlock()) - allowedSyncWithErrorsLimitReached := numSyncedWithErrors >= process.MaxSyncWithErrorsAllowed + allowedSyncWithErrorsLimitReached := numSyncedWithErrors >= boot.getMaxSyncWithErrorsAllowed(headerHandler) isInProperRound := process.IsInProperRound(boot.roundHandler.Index()) isSyncWithErrorsLimitReachedInProperRound := allowedSyncWithErrorsLimitReached && isInProperRound + lastCommittedBlock := boot.chainHandler.GetCurrentBlockHeader() + lastCommittedBlockHash := boot.chainHandler.GetCurrentBlockHeaderHash() + shouldAllowRollback := boot.shouldAllowRollback(lastCommittedBlock, lastCommittedBlockHash) + shouldRollBack := isProcessWithError || isSyncWithErrorsLimitReachedInProperRound - if shouldRollBack { + if shouldRollBack && shouldAllowRollback { if !check.IfNil(headerHandler) { hash := boot.removeHeaderFromPools(headerHandler) boot.forkDetector.RemoveHeader(headerHandler.GetNonce(), hash) @@ -705,16 +823,58 @@ func (boot *baseBootstrap) incrementSyncedWithErrorsForNonce(nonce uint64) uint3 return numSyncedWithErrors } -// syncBlock method actually does the synchronization. It requests the next block header from the pool -// and if it is not found there it will be requested from the network. After the header is received, -// it requests the block body in the same way(pool and then, if it is not found in the pool, from network). -// If either header and body are received the ProcessBlock and CommitBlock method will be called successively. -// These methods will execute the block and its transactions. Finally, if everything works, the block will be committed -// in the blockchain, and all this mechanism will be reiterated for the next block. +func (boot *baseBootstrap) prepareForSyncAtBoostrapIfNeeded() error { + // this will be triggered only once, after a full node restart. + // it is needed for the case when the node will go through bootstrap process and start + // directly into execution flow, because it is already synced (ex: if the entire shard + // was down and when the node will came back it will still be in sync, because the + // shard did not advance while the node was down). + // in case of shuffle out and moving to another shard, the node will not have to + // go through this flow, it will go through sync flow directly, so it will not be + // a problem that preparedForSyncAtBootstrap is already set + + if boot.preparedForSyncAtBootstrap { + return nil + } + + // at this point, current header should be the last applied header at bootstrap + currentHeader := boot.getCurrentBlock() + + if !currentHeader.IsHeaderV3() { + boot.preparedForSyncAtBootstrap = true + + return nil + } + + // syncing nonce is taken as next nonce, this is for preparedForSyncIfNeeded to work + // properly in this case + syncingNonce := currentHeader.GetNonce() + 1 + + log.Debug("prepareForSyncAtBoostrapIfNeeded", + "currHeader nonce", currentHeader.GetNonce(), + ) + + err := boot.prepareForSyncIfNeeded(syncingNonce) + if err != nil { + return err + } + + boot.preparedForSyncAtBootstrap = true + + return nil +} + func (boot *baseBootstrap) syncBlock() error { boot.computeNodeState() nodeState := boot.GetNodeState() + if nodeState != common.NsNotSynchronized { + err := boot.prepareForSyncAtBoostrapIfNeeded() + if err != nil { + return err + } + + boot.preparedForSync = false // reset the state for next loop return nil } @@ -755,13 +915,14 @@ func (boot *baseBootstrap) syncBlock() error { defer func() { if err != nil { - log.Warn("sync block failed", "error", err) + log.Debug("sync block failed", "error", err) boot.doJobOnSyncBlockFail(body, header, err) } }() - header, err = boot.getNextHeaderRequestingIfMissing() + var headerHash []byte + header, headerHash, err = boot.getNextHeaderRequestingIfMissing() if err != nil { return err } @@ -773,8 +934,32 @@ func (boot *baseBootstrap) syncBlock() error { return err } + if header.IsHeaderV3() { + // update err to enable the deferred treatment + err = boot.syncBlockV3(body, header, headerHash) + return err + } + + // update err to enable the deferred treatment + err = boot.syncBlockLegacy(body, header) + + return err +} + +// syncBlockLegacy method actually does the synchronization. It requests the next block header from the pool +// and if it is not found there it will be requested from the network. After the header is received, +// it requests the block body in the same way(pool and then, if it is not found in the pool, from network). +// If either header and body are received the ProcessBlock and CommitBlock method will be called successively. +// These methods will execute the block and its transactions. Finally, if everything works, the block will be committed +// in the blockchain, and all this mechanism will be reiterated for the next block. +func (boot *baseBootstrap) syncBlockLegacy(body data.BodyHandler, header data.HeaderHandler) error { + err := boot.prepareForLegacySyncIfNeeded() + if err != nil { + return err + } + startTime := time.Now() - waitTime := boot.processWaitTime + waitTime := boot.getProcessWaitTime(header.GetRound()) haveTime := func() time.Duration { return waitTime - time.Since(startTime) } @@ -823,6 +1008,451 @@ func (boot *baseBootstrap) syncBlock() error { return nil } +func (boot *baseBootstrap) prepareForLegacySyncIfNeeded() error { + if boot.preparedForSync { + return nil + } + + currentHeader := boot.getCurrentBlock() + currentRootHash := boot.getCurrentRootHashLegacy() + txPool := boot.poolsHolder.Transactions() + err := txPool.OnExecutedBlock(currentHeader, currentRootHash) + if err != nil { + txPool.ResetTracker() + return err + } + + boot.preparedForSync = true + + return nil +} + +// syncBlockV3 method actually does the synchronization. It requests the next block header from the pool +// and if it is not found there it will be requested from the network. After the header is received, +// it requests the block body in the same way(pool and then, if it is not found in the pool, from network). +// Once received, the header is verified through VerifyBlockProposal, but not before warming up the tx pool. +// Finally, if everything works, the block will be committed and added into the processing queue. +// And all this mechanism will be reiterated for the next block. +func (boot *baseBootstrap) syncBlockV3(body data.BodyHandler, header data.HeaderHandler, headerHash []byte) error { + err := boot.prepareForSyncIfNeeded(header.GetNonce()) + if err != nil { + return err + } + + startTime := time.Now() + waitTime := boot.getProcessWaitTime(header.GetRound()) + haveTime := func() time.Duration { + return waitTime - time.Since(startTime) + } + + startVerifyBlockTime := time.Now() + err = boot.blockProcessor.VerifyBlockProposal(header, body, haveTime) + elapsedTime := time.Since(startVerifyBlockTime) + log.Debug("elapsed time to verify block", + "time [s]", elapsedTime, + "nonce", header.GetNonce(), + ) + if err != nil { + return err + } + + err = boot.executionManager.AddPairForExecution(cache.HeaderBodyPair{ + Header: header, + Body: body, + HeaderHash: headerHash, + }) + if err != nil { + return err + } + + startCommitBlockTime := time.Now() + err = boot.blockProcessor.CommitBlock(header, body) + elapsedTime = time.Since(startCommitBlockTime) + if elapsedTime >= common.CommitMaxTime { + log.Warn("syncBlock.CommitBlock", "elapsed time", elapsedTime) + } else { + log.Debug("elapsed time to commit block", + "time [s]", elapsedTime, + "nonce", header.GetNonce(), + ) + } + if err != nil { + return err + } + + log.Debug("block has been synced successfully", + "nonce", header.GetNonce(), + ) + + boot.cleanNoncesSyncedWithErrorsBehindFinal() + boot.cleanProofsBehindFinal(header) + + return nil +} + +// getMiniBlocksToSync will check already synced miniblocks and return only miniblocks that are not in pool +func (boot *baseBootstrap) getMiniBlocksToSync( + miniBlocks []data.MiniBlockHeaderHandler, +) []data.MiniBlockHeaderHandler { + miniBlocksToSync := make([]data.MiniBlockHeaderHandler, 0) + + for _, mb := range miniBlocks { + _, ok := boot.dataPool.MiniBlocks().Get(mb.GetHash()) + if ok { + continue + } + + miniBlocksToSync = append(miniBlocksToSync, mb) + } + + return miniBlocksToSync +} + +func (boot *baseBootstrap) syncMiniBlocksAndTxsForHeader( + header data.HeaderHandler, +) error { + miniBlocksToSync := boot.getMiniBlocksToSync(header.GetMiniBlockHeaderHandlers()) + + boot.miniBlocksSyncer.ClearFields() + ctx, cancel := context.WithTimeout(context.Background(), defaultTimeToWaitForRequestedData) + err := boot.miniBlocksSyncer.SyncPendingMiniBlocks(miniBlocksToSync, ctx) + cancel() + if err != nil { + return err + } + + miniBlocks, err := boot.miniBlocksSyncer.GetMiniBlocks() + if err != nil { + return err + } + + // sync all txs into pools + + boot.txSyncer.ClearFields() + ctx, cancel = context.WithTimeout(context.Background(), defaultTimeToWaitForRequestedData) + err = boot.txSyncer.SyncTransactionsFor(miniBlocks, header.GetEpoch(), ctx) + cancel() + if err != nil { + return err + } + + return nil +} + +func (boot *baseBootstrap) prepareForSyncIfNeeded( + syncingNonce uint64, +) error { + if boot.preparedForSync { + return nil + } + + currentHeader := boot.getCurrentBlock() + currentHeaderHash := boot.getCurrentBlockHash() + lastExecResultNonce, lastExecResultHash, err := boot.getExecutionResultHeaderNonceForSyncStart(syncingNonce, currentHeader, currentHeaderHash) + if err != nil { + return err + } + + if currentHeader.GetNonce() <= lastExecResultNonce { + boot.preparedForSync = true + return nil + } + + // Walk backward from currentHeader following PrevHash pointers to collect + // the canonical chain of committed headers between the last execution result + // and the syncing header. Hash-based lookups are used instead of nonce-based + // pool lookups to avoid ambiguity when multiple headers exist for the same nonce. + type backfillEntry struct { + header data.HeaderHandler + headerHash []byte + } + + headersToAdd := make([]backfillEntry, 0, currentHeader.GetNonce()-lastExecResultNonce) + walker := currentHeader + walkerHash := currentHeaderHash + + for walker.GetNonce() > lastExecResultNonce { + headersToAdd = append(headersToAdd, backfillEntry{ + header: walker, + headerHash: walkerHash, + }) + + if walker.GetNonce() == lastExecResultNonce+1 { + if len(lastExecResultHash) > 0 && !bytes.Equal(walker.GetPrevHash(), lastExecResultHash) { + return fmt.Errorf("%w: backfill chain at nonce %d has prevHash mismatch with last execution result hash", + process.ErrBlockHashDoesNotMatch, walker.GetNonce()) + } + break + } + + prevHash := walker.GetPrevHash() + prevHeader, errGetHdr := boot.getHeader(prevHash) + if errGetHdr != nil { + log.Debug("prepareForSyncIfNeeded: failed to get header by hash during backfill", + "hash", prevHash, + "expected nonce", walker.GetNonce()-1, + "error", errGetHdr, + ) + return errGetHdr + } + + expectedNonce := walker.GetNonce() - 1 + if prevHeader.GetNonce() != expectedNonce { + return fmt.Errorf("%w: backfill walk at nonce %d resolved prevHash to nonce %d, expected %d", + process.ErrWrongNonceInBlock, walker.GetNonce(), prevHeader.GetNonce(), expectedNonce) + } + + walker = prevHeader + walkerHash = prevHash + } + + // add headers for execution in forward (ascending nonce) order + for i := len(headersToAdd) - 1; i >= 0; i-- { + info := headersToAdd[i] + + err = boot.syncMiniBlocksAndTxsForHeader(info.header) + if err != nil { + return err + } + + body, errGetBody := boot.blockBootstrapper.getBlockBody(info.header) + if errGetBody != nil { + return errGetBody + } + + err = boot.saveProposedTxsToPool(info.header, body) + if err != nil { + return err + } + + errOnBackfilledBlock := boot.blockProcessor.OnBackfilledBlock( + body, + info.header, + info.headerHash, + ) + if errOnBackfilledBlock != nil { + return errOnBackfilledBlock + } + + errAdd := boot.executionManager.AddPairForExecution(cache.HeaderBodyPair{ + Header: info.header, + Body: body, + HeaderHash: info.headerHash, + }) + if errAdd != nil { + return errAdd + } + } + + boot.preparedForSync = true + + return nil +} + +func (boot *baseBootstrap) saveProposedTxsToPool( + header data.HeaderHandler, + body data.BodyHandler, +) error { + if !header.IsHeaderV3() { + return nil + } + + bodyPtr, ok := body.(*block.Body) + if !ok { + return process.ErrWrongTypeAssertion + } + + separatedBodies := process.SeparateBodyByType(bodyPtr) + + for blockType, blockBody := range separatedBodies { + dataPool, err := process.GetDataPoolByBlockType(blockType, boot.dataPool) + if err != nil { + return err + } + + unit, err := process.GetStorageUnitByBlockType(blockType) + if err != nil { + return err + } + + storer, err := boot.store.GetStorer(unit) + if err != nil { + return err + } + + for i := 0; i < len(blockBody.MiniBlocks); i++ { + miniBlock := blockBody.MiniBlocks[i] + err = boot.saveTxsToPool(dataPool, storer, miniBlock, blockType) + if err != nil { + return err + } + } + } + + return nil +} + +func (boot *baseBootstrap) saveTxsToPool( + dataPool dataRetriever.ShardedDataCacherNotifier, + storer storage.Storer, + miniBlock *block.MiniBlock, + blockType block.Type, +) error { + txHashes := miniBlock.TxHashes + + for _, txHash := range txHashes { + // continue if already in pool + _, ok := dataPool.SearchFirstData(txHash) + if ok { + continue + } + + txBuff, err := storer.Get(txHash) + if err != nil { + return err + } + + tx, err := boot.unmarshalTxByBlockType(blockType, txBuff) + if err != nil { + return err + } + + cacherIdentifier := process.ShardCacherIdentifier(miniBlock.SenderShardID, miniBlock.ReceiverShardID) + dataPool.AddData( + txHash, + tx, + tx.Size(), + cacherIdentifier, + ) + } + + return nil +} + +func (boot *baseBootstrap) getExecutionResultHeaderNonceForSyncStart( + syncingNonce uint64, + currentHeader data.HeaderHandler, + currentHeaderHash []byte, +) (uint64, []byte, error) { + lastNotarizedExecResult, err := process.GetPrevBlockLastExecutionResult(boot.chainHandler) + if err != nil { + return 0, nil, err + } + + lastNotarizedExecResultsHandler, err := common.ExtractBaseExecutionResultHandler(lastNotarizedExecResult) + if err != nil { + return 0, nil, err + } + + log.Debug("getExecutionResultHeaderNonceForSyncStart", + "syncingNonce", syncingNonce, + "currHeader nonce", currentHeader.GetNonce(), + "currHeader hash", currentHeaderHash, + "lastNotarizedExecRes nonce", lastNotarizedExecResultsHandler.GetHeaderNonce(), + "lastNotarizedExecRes hash", lastNotarizedExecResultsHandler.GetHeaderHash(), + "lastNotarizedExecRes rootHash", lastNotarizedExecResultsHandler.GetRootHash(), + ) + + lastNotarizedExecutedHash := lastNotarizedExecResultsHandler.GetHeaderHash() + lastNotarizedExecutedHeader, err := boot.getHeader(lastNotarizedExecutedHash) + if err != nil { + return 0, nil, err + } + + rootHash := lastNotarizedExecResultsHandler.GetRootHash() + + txPool := boot.poolsHolder.Transactions() + err = txPool.OnExecutedBlock(lastNotarizedExecutedHeader, rootHash) + if err != nil { + txPool.ResetTracker() + return 0, nil, err + } + + lastExecutionResultNonce := lastNotarizedExecutedHeader.GetNonce() + defer func() { + log.Debug("getExecutionResultHeaderNonceForSyncStart", "lastExecutionResultNonce", lastExecutionResultNonce) + }() + + // check with pending execution + pendingExecutionResults, err := boot.executionManager.GetPendingExecutionResults() + if err != nil { + return 0, nil, err + } + var pendingExecutionResult data.BaseExecutionResultHandler + for idx := len(pendingExecutionResults) - 1; idx >= 0; idx-- { + pendingExecutionResult = pendingExecutionResults[idx] + if pendingExecutionResult.GetHeaderNonce() <= lastExecutionResultNonce { + log.Warn("getExecutionResultHeaderNonceForSyncStart found pending execution result with lower or equal nonce than last executed", + "pending nonce", pendingExecutionResult.GetHeaderNonce(), + "lastExecutionResultNonce", lastExecutionResultNonce, + ) + continue + } + + if boot.hasProofInCacheOrStorage(pendingExecutionResult.GetHeaderHash()) { + return pendingExecutionResult.GetHeaderNonce(), pendingExecutionResult.GetHeaderHash(), nil + } + } + + return lastExecutionResultNonce, lastNotarizedExecutedHash, nil +} + +func (boot *baseBootstrap) hasProofInCacheOrStorage(hash []byte) bool { + if boot.proofs.HasProof(boot.shardCoordinator.SelfId(), hash) { + return true + } + + proofsStorer, errGetStorer := boot.store.GetStorer(dataRetriever.ProofsUnit) + if errGetStorer != nil { + return false + } + + proofBytes, err := proofsStorer.Get(hash) + if err != nil { + return false + } + + proof := &block.HeaderProof{} + err = boot.marshalizer.Unmarshal(proof, proofBytes) + if err != nil { + // return true here, since the proof exists in storer + log.Warn("hasProofInCacheOrStorage invalid proof in storage", "error", err.Error(), "hash", hash) + return true + } + + boot.proofs.AddProof(proof) + + return true +} + +func (boot *baseBootstrap) unmarshalTxByBlockType( + blockType block.Type, + txBuff []byte, +) (txSizeHandler, error) { + var tx txSizeHandler + var err error + + switch blockType { + case block.TxBlock, block.InvalidBlock: + tx = &transaction.Transaction{} + case block.SmartContractResultBlock: + tx = &smartContractResult.SmartContractResult{} + case block.RewardsBlock: + tx = &rewardTx.RewardTx{} + case block.PeerBlock: + tx = &state.ShardValidatorInfo{} + default: + return nil, fmt.Errorf("unsupported block type: %d", blockType) + } + + err = boot.marshalizer.Unmarshal(tx, txBuff) + if err != nil { + return nil, err + } + + return tx, nil +} + func (boot *baseBootstrap) handleTrieSyncError(err error, ctx context.Context) { shouldOutputLog := err != nil && !common.IsContextDone(ctx) if shouldOutputLog { @@ -993,6 +1623,10 @@ func (boot *baseBootstrap) rollBack(revertUsingForkNonce bool) error { } func (boot *baseBootstrap) shouldAllowRollback(currHeader data.HeaderHandler, currHeaderHash []byte) bool { + if check.IfNil(currHeader) || currHeader.IsHeaderV3() { + return false + } + finalBlockNonce := boot.forkDetector.GetHighestFinalBlockNonce() finalBlockHash := boot.forkDetector.GetHighestFinalBlockHash() isRollBackBehindFinal := currHeader.GetNonce() <= finalBlockNonce @@ -1085,7 +1719,7 @@ func (boot *baseBootstrap) getRootHashFromBlock(hdr data.HeaderHandler, hdrHash return hdrRootHash } -func (boot *baseBootstrap) getNextHeaderRequestingIfMissing() (data.HeaderHandler, error) { +func (boot *baseBootstrap) getNextHeaderRequestingIfMissing() (data.HeaderHandler, []byte, error) { nonce := boot.getNonceForNextBlock() boot.setRequestedHeaderHash(nil) @@ -1096,9 +1730,15 @@ func (boot *baseBootstrap) getNextHeaderRequestingIfMissing() (data.HeaderHandle hash = boot.forkInfo.Hash } + // if there is a proof for the current nonce, use the header hash from proof + proof, err := boot.dataPool.Proofs().GetProofByNonce(nonce, boot.shardCoordinator.SelfId()) + if err == nil { + hash = proof.GetHeaderHash() + } + if hash != nil { header, err := boot.getHeaderWithHashRequestingIfMissing(hash) - return header, err + return header, hash, err } return boot.getHeaderWithNonceRequestingIfMissing(nonce) @@ -1155,12 +1795,12 @@ func (boot *baseBootstrap) checkNeedsProofByHash(hash []byte, header data.Header // getHeaderWithNonceRequestingIfMissing method gets the header with a given nonce from pool. If it is not found there, it will // be requested from network -func (boot *baseBootstrap) getHeaderWithNonceRequestingIfMissing(nonce uint64) (data.HeaderHandler, error) { +func (boot *baseBootstrap) getHeaderWithNonceRequestingIfMissing(nonce uint64) (data.HeaderHandler, []byte, error) { hdr, hash, err := boot.getHeaderFromPoolWithNonce(nonce) hasHeader := err == nil if hasHeader && boot.hasProof(hash, hdr) { - return hdr, nil + return hdr, hash, nil } needsProof := boot.checkNeedsProofByNonce(nonce, hdr, hash) @@ -1173,19 +1813,20 @@ func (boot *baseBootstrap) getHeaderWithNonceRequestingIfMissing(nonce uint64) ( err = boot.waitForHeaderAndProofByNonce() if err != nil { - return nil, err + return nil, nil, err } hdr, hash, err = boot.getHeaderFromPoolWithNonce(nonce) if err != nil { - return nil, err + log.Debug("getHeaderWithNonceRequestingIfMissing: failed to get header with nonce", "nonce", nonce, "error", err) + return nil, nil, err } if !boot.hasProof(hash, hdr) { - return nil, process.ErrMissingHeaderProof + return nil, nil, process.ErrMissingHeaderProof } - return hdr, nil + return hdr, hash, nil } func (boot *baseBootstrap) checkNeedsProofByNonce( @@ -1322,6 +1963,7 @@ func (boot *baseBootstrap) getHeader(hash []byte) (data.HeaderHandler, error) { return process.GetShardHeader(hash, boot.headers, boot.marshalizer, boot.store) } +// getHeaderFromPool will try to get the header from pool func (boot *baseBootstrap) getHeaderFromPool(hash []byte) (data.HeaderHandler, error) { if boot.shardCoordinator.SelfId() == core.MetachainShardId { return process.GetMetaHeaderFromPool(hash, boot.headers) @@ -1389,6 +2031,11 @@ func (boot *baseBootstrap) restoreState( boot.chainHandler.SetCurrentBlockHeaderHash(currHeaderHash) + // for legacy (non-V3) headers, keep last executed block header in sync with current block header + if check.IfNil(currHeader) || !currHeader.IsHeaderV3() { + boot.chainHandler.SetLastExecutedBlockHeaderAndRootHash(currHeader, currHeaderHash, currRootHash) + } + err = boot.scheduledTxsExecutionHandler.RollBackToBlock(currHeaderHash) if err != nil { scheduledInfo := &process.ScheduledInfo{ @@ -1419,6 +2066,11 @@ func (boot *baseBootstrap) setCurrentBlockInfo( boot.chainHandler.SetCurrentBlockHeaderHash(headerHash) + // for legacy (non-V3) headers, keep last executed block header in sync with current block header + if check.IfNil(header) || !header.IsHeaderV3() { + boot.chainHandler.SetLastExecutedBlockHeaderAndRootHash(header, headerHash, rootHash) + } + return nil } @@ -1489,6 +2141,21 @@ func (boot *baseBootstrap) getMiniBlocksRequestingIfMissing(hashes [][]byte) (bl return getOrderedMiniBlocks(hashes, miniBlocksAndHashes) } +func (boot *baseBootstrap) getHeaderMiniBlocksRequestingIfMissing( + header data.HeaderHandler, +) (block.MiniBlockSlice, error) { + miniBlockHeaderHandlers := header.GetMiniBlockHeaderHandlers() + + hashes := make([][]byte, len(miniBlockHeaderHandlers)) + for i, miniBlockHeaderHandler := range miniBlockHeaderHandlers { + hashes[i] = miniBlockHeaderHandler.GetHash() + } + + boot.setRequestedMiniBlocks(nil) + + return boot.getMiniBlocksRequestingIfMissing(hashes) +} + func getOrderedMiniBlocks( hashes [][]byte, miniBlocksAndHashes []*block.MiniblockAndHash, @@ -1517,7 +2184,7 @@ func (boot *baseBootstrap) waitForMiniBlocks() error { select { case <-boot.chRcvMiniBlocks: return nil - case <-time.After(boot.waitTime): + case <-time.After(boot.getWaitTime()): return process.ErrTimeIsOut } } @@ -1528,6 +2195,7 @@ func (boot *baseBootstrap) init() { boot.chRcvHdrNonce = make(chan bool) boot.chRcvHdrHash = make(chan bool) boot.chRcvMiniBlocks = make(chan bool) + boot.signalProcessCompletionChan = boot.executionManager.GetSignalProcessCompletionChan() boot.setRequestedHeaderNonce(nil) boot.setRequestedHeaderHash(nil) @@ -1654,9 +2322,70 @@ func (boot *baseBootstrap) cleanChannels() { nrReads = core.EmptyChannel(boot.chRcvMiniBlocks) log.Debug("close baseSync: emptied channel", "chRcvMiniBlocks nrReads", nrReads) + + if boot.signalProcessCompletionChan != nil { + nrReads = common.EmptyUint64Channel(boot.signalProcessCompletionChan) + log.Debug("close baseSync: emptied channel", "signalProcessCompletionChan nrReads", nrReads) + } +} + +func (boot *baseBootstrap) getHeaderMiniBlocks( + header data.HeaderHandler, +) (block.MiniBlockSlice, error) { + miniBlockHeaders := header.GetMiniBlockHeaderHandlers() + + hashes := make([][]byte, len(miniBlockHeaders)) + for i, miniBlockHeader := range miniBlockHeaders { + hashes[i] = miniBlockHeader.GetHash() + } + + miniBlocksAndHashes, missingMiniBlocksHashes := boot.miniBlocksProvider.GetMiniBlocks(hashes) + if len(missingMiniBlocksHashes) > 0 { + return nil, process.ErrMissingBody + } + + miniBlocks := make([]*block.MiniBlock, len(miniBlocksAndHashes)) + for index, miniBlockAndHash := range miniBlocksAndHashes { + miniBlocks[index] = miniBlockAndHash.Miniblock + } + + return miniBlocks, nil } // IsInterfaceNil returns true if there is no value under the interface func (boot *baseBootstrap) IsInterfaceNil() bool { return boot == nil } + +func (boot *baseBootstrap) createTxSyncer() error { + var err error + + miniBlocksStorer, err := boot.store.GetStorer(dataRetriever.MiniBlockUnit) + if err != nil { + return err + } + + syncMiniBlocksArgs := updateSync.ArgsNewPendingMiniBlocksSyncer{ + Storage: miniBlocksStorer, + Cache: boot.dataPool.MiniBlocks(), + Marshalizer: boot.marshalizer, + RequestHandler: boot.requestHandler, + } + boot.miniBlocksSyncer, err = updateSync.NewPendingMiniBlocksSyncer(syncMiniBlocksArgs) + if err != nil { + return err + } + + syncTxsArgs := updateSync.ArgsNewTransactionsSyncer{ + DataPools: boot.dataPool, + Storages: boot.store, + Marshaller: boot.marshalizer, + RequestHandler: boot.requestHandler, + } + boot.txSyncer, err = updateSync.NewTransactionsSyncer(syncTxsArgs) + if err != nil { + return err + } + + return nil +} diff --git a/process/sync/baseSync_test.go b/process/sync/baseSync_test.go index 1379780da3c..6aff17eb4b5 100644 --- a/process/sync/baseSync_test.go +++ b/process/sync/baseSync_test.go @@ -2,6 +2,7 @@ package sync import ( "context" + "errors" "sync/atomic" "testing" "time" @@ -9,10 +10,20 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/data/rewardTx" + "github.com/multiversx/mx-chain-core-go/data/smartContractResult" + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/mock" + "github.com/multiversx/mx-chain-go/state" + "github.com/multiversx/mx-chain-go/storage" "github.com/multiversx/mx-chain-go/testscommon" + dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" + storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -251,6 +262,7 @@ func TestBaseSync_shouldAllowRollback(t *testing.T) { return *firstBlockNonce }, }, + executionManager: &processMocks.ExecutionManagerMock{}, } t.Run("should allow rollback nonces above final", func(t *testing.T) { @@ -354,4 +366,279 @@ func TestBaseSync_shouldAllowRollback(t *testing.T) { require.False(t, boot.shouldAllowRollback(header, finalBlockHash)) require.False(t, boot.shouldAllowRollback(header, notFinalBlockHash)) }) + + t.Run("should not allow rollback of a header v3", func(t *testing.T) { + header := &testscommon.HeaderHandlerStub{ + GetNonceCalled: func() uint64 { + return 11 + }, + IsHeaderV3Called: func() bool { + return true + }, + } + require.False(t, boot.shouldAllowRollback(header, finalBlockHash)) + }) +} + +func TestBaseBootstrap_PrepareForSyncAtBootstrapIfNeeded(t *testing.T) { + t.Parallel() + + t.Run("should run only once", func(t *testing.T) { + t.Parallel() + + lastExecHeaderHash := []byte("lastExecHeaderHash") + + lastHeader := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: lastExecHeaderHash, + HeaderNonce: 9, + }, + }, + Nonce: 10, + } + + numCalls := 0 + boot := &baseBootstrap{ + chainHandler: &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + numCalls++ + return lastHeader + }, + }, + preparedForSync: true, // prepared for sync already called, we are not testing here + // the behaviour of preparedForSyncIfNeeded + } + + err := boot.PrepareForSyncAtBoostrapIfNeeded() + require.Nil(t, err) + + require.Equal(t, 1, numCalls) + + err = boot.PrepareForSyncAtBoostrapIfNeeded() + require.Nil(t, err) + + require.Equal(t, 1, numCalls) // still 1 call + }) + + t.Run("should not trigger for non header v3", func(t *testing.T) { + t.Parallel() + + lastHeader := &block.Header{ + Nonce: 10, + } + + numCalls := 0 + boot := &baseBootstrap{ + chainHandler: &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + numCalls++ + return lastHeader + }, + }, + preparedForSync: false, + } + + err := boot.PrepareForSyncAtBoostrapIfNeeded() + require.Nil(t, err) + + require.Equal(t, 1, numCalls) + + err = boot.PrepareForSyncAtBoostrapIfNeeded() + require.Nil(t, err) + + require.Equal(t, 1, numCalls) // still 1 call + }) +} + +func TestBaseBootstrap_SaveProposedTxsToPool(t *testing.T) { + t.Parallel() + + marshaller := &marshal.GogoProtoMarshalizer{} + + txCalls := 0 + scCalls := 0 + rwCalls := 0 + peerCalls := 0 + + boot := &baseBootstrap{ + marshalizer: marshaller, + dataPool: &dataRetrieverMock.PoolsHolderStub{ + TransactionsCalled: func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + AddDataCalled: func(key []byte, data interface{}, sizeInBytes int, cacheID string) { + txCalls++ + }, + } + }, + UnsignedTransactionsCalled: func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + AddDataCalled: func(key []byte, data interface{}, sizeInBytes int, cacheID string) { + scCalls++ + }, + } + }, + RewardTransactionsCalled: func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + AddDataCalled: func(key []byte, data interface{}, sizeInBytes int, cacheID string) { + rwCalls++ + }, + } + }, + ValidatorsInfoCalled: func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + AddDataCalled: func(key []byte, data interface{}, sizeInBytes int, cacheID string) { + peerCalls++ + }, + } + }, + }, + store: &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + switch string(key) { + case "txHash1": + tx := &transaction.Transaction{ + Nonce: 1, + } + txBytes, _ := marshaller.Marshal(tx) + return txBytes, nil + case "txHash2": + tx := &transaction.Transaction{ + Nonce: 2, + } + txBytes, _ := marshaller.Marshal(tx) + return txBytes, nil + case "txHash3": + tx := &smartContractResult.SmartContractResult{ + Nonce: 3, + CodeMetadata: []byte("codeMetadata"), + } + txBytes, _ := marshaller.Marshal(tx) + return txBytes, nil + case "txHash4": + tx := &rewardTx.RewardTx{ + Round: 1, + } + txBytes, _ := marshaller.Marshal(tx) + return txBytes, nil + case "txHash5": + tx := &state.ShardValidatorInfo{ + PublicKey: []byte("pubKey"), + } + txBytes, _ := marshaller.Marshal(tx) + return txBytes, nil + default: + return nil, errors.New("err") + } + }, + }, nil + }, + }, + } + + header := &block.HeaderV3{} + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + &block.MiniBlock{ + TxHashes: [][]byte{[]byte("txHash1")}, + Type: block.TxBlock, + }, + &block.MiniBlock{ + TxHashes: [][]byte{[]byte("txHash2")}, + Type: block.InvalidBlock, + }, + &block.MiniBlock{ + TxHashes: [][]byte{[]byte("txHash3")}, + Type: block.SmartContractResultBlock, + }, + &block.MiniBlock{ + TxHashes: [][]byte{[]byte("txHash4")}, + Type: block.RewardsBlock, + }, + &block.MiniBlock{ + TxHashes: [][]byte{[]byte("txHash5")}, + Type: block.PeerBlock, + }, + }, + } + + err := boot.SaveProposedTxsToPool(header, body) + require.Nil(t, err) + + require.Equal(t, 2, txCalls) + require.Equal(t, 1, scCalls) + require.Equal(t, 1, rwCalls) + require.Equal(t, 1, peerCalls) +} + +func TestBaseBootstrap_SyncBlocksWakesUpOnSignal(t *testing.T) { + t.Parallel() + + signalChan := make(chan uint64, 1) + var numCalls uint32 + syncError := errors.New("sync error to trigger wait") + + boot := &baseBootstrap{ + chStopSync: make(chan bool), + signalProcessCompletionChan: signalChan, + syncStarter: &mock.SyncStarterStub{ + SyncBlockCalled: func() error { + atomic.AddUint32(&numCalls, 1) + return syncError + }, + }, + networkWatcher: &mock.NetworkConnectionWatcherStub{ + IsConnectedToTheNetworkCalled: func() bool { + return true + }, + }, + roundHandler: &mock.RoundHandlerMock{ + BeforeGenesisCalled: func() bool { + return false + }, + }, + } + + ctx, cancelFunc := context.WithCancel(context.Background()) + + go boot.syncBlocks(ctx) + + // Wait for first sync call + time.Sleep(50 * time.Millisecond) + initialCalls := atomic.LoadUint32(&numCalls) + require.GreaterOrEqual(t, initialCalls, uint32(1)) + + // Signal the channel - this should wake up the loop immediately + signalChan <- 42 + + // Wait a short time - much less than sleepTimeOnFail (400ms) + time.Sleep(50 * time.Millisecond) + + // Should have made another call due to signal wakeup + finalCalls := atomic.LoadUint32(&numCalls) + require.Greater(t, finalCalls, initialCalls) + + cancelFunc() +} + +func TestBaseBootstrap_CleanChannelsDrainsSignalChannel(t *testing.T) { + t.Parallel() + + signalChan := make(chan uint64, 5) + signalChan <- 1 + signalChan <- 2 + signalChan <- 3 + + boot := &baseBootstrap{ + chRcvHdrNonce: make(chan bool, 1), + chRcvHdrHash: make(chan bool, 1), + chRcvMiniBlocks: make(chan bool, 1), + signalProcessCompletionChan: signalChan, + } + + boot.cleanChannels() + + assert.Equal(t, 0, len(signalChan)) } diff --git a/process/sync/export_test.go b/process/sync/export_test.go index f8f172b733e..427e2369864 100644 --- a/process/sync/export_test.go +++ b/process/sync/export_test.go @@ -274,9 +274,9 @@ func (bfd *baseForkDetector) AddCheckPoint(round uint64, nonce uint64, hash []by bfd.addCheckpoint(&checkpointInfo{round: round, nonce: nonce, hash: hash}) } -// ComputeGenesisTimeFromHeader - -func (bfd *baseForkDetector) ComputeGenesisTimeFromHeader(headerHandler data.HeaderHandler) int64 { - return bfd.computeGenesisTimeFromHeader(headerHandler) +// CheckGenesisTimeForHeader - +func (bfd *baseForkDetector) CheckGenesisTimeForHeader(headerHandler data.HeaderHandler) error { + return bfd.checkGenesisTimeForHeader(headerHandler) } // InitNotarizedMap - @@ -352,3 +352,16 @@ func (boot *baseBootstrap) IsInImportMode() bool { func (boot *baseBootstrap) ProcessWaitTime() time.Duration { return boot.processWaitTime } + +// PrepareForSyncAtBoostrapIfNeeded - +func (boot *baseBootstrap) PrepareForSyncAtBoostrapIfNeeded() error { + return boot.prepareForSyncAtBoostrapIfNeeded() +} + +// SaveProposedTxsToPool - +func (boot *baseBootstrap) SaveProposedTxsToPool( + header data.HeaderHandler, + body data.BodyHandler, +) error { + return boot.saveProposedTxsToPool(header, body) +} diff --git a/process/sync/interface.go b/process/sync/interface.go index e6965337ea1..268ce7e4207 100644 --- a/process/sync/interface.go +++ b/process/sync/interface.go @@ -14,7 +14,7 @@ type blockBootstrapper interface { getPrevHeader(data.HeaderHandler, storage.Storer) (data.HeaderHandler, error) getBlockBody(headerHandler data.HeaderHandler) (data.BodyHandler, error) getHeaderWithHashRequestingIfMissing(hash []byte) (data.HeaderHandler, error) - getHeaderWithNonceRequestingIfMissing(nonce uint64) (data.HeaderHandler, error) + getHeaderWithNonceRequestingIfMissing(nonce uint64) (data.HeaderHandler, []byte, error) getBlockBodyRequestingIfMissing(headerHandler data.HeaderHandler) (data.BodyHandler, error) isForkTriggeredByMeta() bool requestHeaderByNonce(nonce uint64) diff --git a/process/sync/metaForkDetector.go b/process/sync/metaForkDetector.go index 991bfee7140..07221d92e59 100644 --- a/process/sync/metaForkDetector.go +++ b/process/sync/metaForkDetector.go @@ -25,10 +25,13 @@ func NewMetaForkDetector( blackListHandler process.TimeCacher, blockTracker process.BlockTracker, genesisTime int64, + supernovaGenesisTime int64, enableEpochsHandler common.EnableEpochsHandler, + enableRoundsHandler common.EnableRoundsHandler, proofsPool process.ProofsPool, + chainParametersHandler common.ChainParametersHandler, + processConfigsHandler common.ProcessConfigsHandler, ) (*metaForkDetector, error) { - if check.IfNil(roundHandler) { return nil, process.ErrNilRoundHandler } @@ -41,9 +44,18 @@ func NewMetaForkDetector( if check.IfNil(enableEpochsHandler) { return nil, process.ErrNilEnableEpochsHandler } + if check.IfNil(enableRoundsHandler) { + return nil, process.ErrNilEnableRoundsHandler + } if check.IfNil(proofsPool) { return nil, process.ErrNilProofsPool } + if check.IfNil(chainParametersHandler) { + return nil, process.ErrNilChainParametersHandler + } + if check.IfNil(processConfigsHandler) { + return nil, process.ErrNilProcessConfigsHandler + } genesisHdr, _, err := blockTracker.GetSelfNotarizedHeader(core.MetachainShardId, 0) if err != nil { @@ -51,15 +63,20 @@ func NewMetaForkDetector( } bfd := &baseForkDetector{ - roundHandler: roundHandler, - blackListHandler: blackListHandler, - genesisTime: genesisTime, - blockTracker: blockTracker, - genesisNonce: genesisHdr.GetNonce(), - genesisRound: genesisHdr.GetRound(), - genesisEpoch: genesisHdr.GetEpoch(), - enableEpochsHandler: enableEpochsHandler, - proofsPool: proofsPool, + roundHandler: roundHandler, + blackListHandler: blackListHandler, + genesisTime: genesisTime, + supernovaGenesisTime: supernovaGenesisTime, + blockTracker: blockTracker, + genesisNonce: genesisHdr.GetNonce(), + genesisRound: genesisHdr.GetRound(), + genesisEpoch: genesisHdr.GetEpoch(), + enableEpochsHandler: enableEpochsHandler, + enableRoundsHandler: enableRoundsHandler, + proofsPool: proofsPool, + chainParametersHandler: chainParametersHandler, + processConfigsHandler: processConfigsHandler, + shardID: core.MetachainShardId, } bfd.headers = make(map[uint64][]*headerInfo) diff --git a/process/sync/metaForkDetector_test.go b/process/sync/metaForkDetector_test.go index da308ae35cb..176d1b07df1 100644 --- a/process/sync/metaForkDetector_test.go +++ b/process/sync/metaForkDetector_test.go @@ -1,20 +1,25 @@ package sync_test import ( + "errors" "testing" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/mock" "github.com/multiversx/mx-chain-go/process/sync" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNewMetaForkDetector_NilRoundHandlerShouldErr(t *testing.T) { @@ -25,8 +30,12 @@ func TestNewMetaForkDetector_NilRoundHandlerShouldErr(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) assert.True(t, check.IfNil(sfd)) assert.Equal(t, process.ErrNilRoundHandler, err) @@ -40,8 +49,12 @@ func TestNewMetaForkDetector_NilBlackListShouldErr(t *testing.T) { nil, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) assert.True(t, check.IfNil(sfd)) assert.Equal(t, process.ErrNilBlackListCacher, err) @@ -55,8 +68,12 @@ func TestNewMetaForkDetector_NilBlockTrackerShouldErr(t *testing.T) { &testscommon.TimeCacheStub{}, nil, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) assert.True(t, check.IfNil(sfd)) assert.Equal(t, process.ErrNilBlockTracker, err) @@ -70,13 +87,36 @@ func TestNewMetaForkDetector_NilEnableEpochsHandlerShouldErr(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, nil, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) assert.True(t, check.IfNil(sfd)) assert.Equal(t, process.ErrNilEnableEpochsHandler, err) } +func TestNewMetaForkDetector_NilEnableRoundsHandlerShouldErr(t *testing.T) { + t.Parallel() + + sfd, err := sync.NewMetaForkDetector( + &mock.RoundHandlerMock{}, + &testscommon.TimeCacheStub{}, + &mock.BlockTrackerMock{}, + 0, + 0, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + nil, + &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + ) + assert.True(t, check.IfNil(sfd)) + assert.Equal(t, process.ErrNilEnableRoundsHandler, err) +} + func TestNewMetaForkDetector_NilProofsPoolShouldErr(t *testing.T) { t.Parallel() @@ -85,8 +125,12 @@ func TestNewMetaForkDetector_NilProofsPoolShouldErr(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, nil, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) assert.True(t, check.IfNil(sfd)) assert.Equal(t, process.ErrNilProofsPool, err) @@ -100,8 +144,12 @@ func TestNewMetaForkDetector_OkParamsShouldWork(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) assert.Nil(t, err) assert.False(t, check.IfNil(sfd)) @@ -121,8 +169,12 @@ func TestMetaForkDetector_AddHeaderNilHeaderShouldErr(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) err := bfd.AddHeader(nil, make([]byte, 0), process.BHProcessed, nil, nil) assert.Equal(t, sync.ErrNilHeader, err) @@ -137,8 +189,12 @@ func TestMetaForkDetector_AddHeaderNilHashShouldErr(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) err := bfd.AddHeader(&block.Header{}, nil, process.BHProcessed, nil, nil) assert.Equal(t, sync.ErrNilHash, err) @@ -155,8 +211,12 @@ func TestMetaForkDetector_AddHeaderNotPresentShouldWork(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) err := bfd.AddHeader(hdr, hash, process.BHProcessed, nil, nil) @@ -180,8 +240,12 @@ func TestMetaForkDetector_AddHeaderPresentShouldAppend(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) _ = bfd.AddHeader(hdr1, hash1, process.BHProcessed, nil, nil) @@ -205,8 +269,12 @@ func TestMetaForkDetector_AddHeaderWithProcessedBlockShouldSetCheckpoint(t *test &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) _ = bfd.AddHeader(hdr1, hash1, process.BHProcessed, nil, nil) assert.Equal(t, hdr1.Nonce, bfd.LastCheckpointNonce()) @@ -223,16 +291,20 @@ func TestMetaForkDetector_AddHeaderWithProcessedBlockAndFlagShouldSetCheckpoint( &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{ IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { return true }, }, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{ HasProofCalled: func(shardID uint32, headerHash []byte) bool { return true }, }, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) _ = bfd.AddHeader(hdr1, hash1, process.BHProcessed, nil, nil) assert.Equal(t, hdr1.Nonce, bfd.FinalCheckpointNonce()) @@ -250,8 +322,12 @@ func TestMetaForkDetector_AddHeaderPresentShouldNotRewriteState(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) _ = bfd.AddHeader(hdr1, hash, process.BHReceived, nil, nil) @@ -274,8 +350,12 @@ func TestMetaForkDetector_AddHeaderHigherNonceThanRoundShouldErr(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) err := bfd.AddHeader( &block.Header{Nonce: 1, Round: 0, PubKeysBitmap: []byte("X")}, @@ -286,3 +366,222 @@ func TestMetaForkDetector_AddHeaderHigherNonceThanRoundShouldErr(t *testing.T) { ) assert.Equal(t, sync.ErrHigherNonceInBlock, err) } + +func TestMetaForkDetector_ComputeGenesisTimeFromHeader(t *testing.T) { + t.Parallel() + + t.Run("legacy genesis time calculation", func(t *testing.T) { + t.Parallel() + + roundDuration := uint64(100) + roundHandlerMock := &mock.RoundHandlerMock{} + + genesisTime := int64(9000) + hdrTimeStamp := uint64(10000) + hdrRound := uint64(10) + bfd, _ := sync.NewMetaForkDetector( + roundHandlerMock, + &testscommon.TimeCacheStub{}, + &mock.BlockTrackerMock{}, + genesisTime, + 0, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag != common.SupernovaFlag + }, + }, + &testscommon.EnableRoundsHandlerStub{}, + &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(epoch uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{ + RoundDuration: roundDuration * 1000, + }, nil + }, + }, + testscommon.GetDefaultProcessConfigsHandler(), + ) + + hdr1 := &block.Header{Nonce: 1, Round: hdrRound, PubKeysBitmap: []byte("X"), TimeStamp: hdrTimeStamp} + + err := bfd.CheckGenesisTimeForHeader(hdr1) + require.Nil(t, err) + }) + + t.Run("legacy genesis time calculation, should fail if not able to get round duration", func(t *testing.T) { + t.Parallel() + + expErr := errors.New("expected err") + + genesisTime := int64(9000) + hdrTimeStamp := uint64(10000) + hdrRound := uint64(10) + bfd, _ := sync.NewMetaForkDetector( + &mock.RoundHandlerMock{}, + &testscommon.TimeCacheStub{}, + &mock.BlockTrackerMock{}, + genesisTime, + 0, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag != common.SupernovaFlag + }, + }, + &testscommon.EnableRoundsHandlerStub{}, + &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(epoch uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{}, expErr + }, + }, + testscommon.GetDefaultProcessConfigsHandler(), + ) + + hdr1 := &block.Header{Nonce: 1, Round: hdrRound, PubKeysBitmap: []byte("X"), TimeStamp: hdrTimeStamp} + + err := bfd.CheckGenesisTimeForHeader(hdr1) + require.Equal(t, expErr, err) + }) + + t.Run("supernova activated in epoch but not in round", func(t *testing.T) { + t.Parallel() + + roundDuration := uint64(100) + roundHandlerMock := &mock.RoundHandlerMock{} + + hdrEpoch := uint32(2) + + genesisTime := int64(9000) + hdrTimeStamp := uint64(10000000) // as milliseconds + hdrRound := uint64(10) + bfd, _ := sync.NewMetaForkDetector( + roundHandlerMock, + &testscommon.TimeCacheStub{}, + &mock.BlockTrackerMock{}, + genesisTime, + 0, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag && epoch > 0 + }, + }, + &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledCalled: func(flag common.EnableRoundFlag) bool { + return flag != common.SupernovaRoundFlag + }, + }, + &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(epoch uint32) (config.ChainParametersByEpochConfig, error) { + if epoch == hdrEpoch-1 { + return config.ChainParametersByEpochConfig{ + RoundDuration: roundDuration * 1000, + }, nil + } + + return config.ChainParametersByEpochConfig{}, errors.New("err") + }, + }, + testscommon.GetDefaultProcessConfigsHandler(), + ) + + hdr1 := &block.Header{ + Nonce: 1, + Round: hdrRound, + Epoch: hdrEpoch, + PubKeysBitmap: []byte("X"), + TimeStamp: hdrTimeStamp, + } + + err := bfd.CheckGenesisTimeForHeader(hdr1) + assert.Nil(t, err) + }) + + t.Run("supernova activated in epoch but not in round, should fail if not able to get round duration", func(t *testing.T) { + t.Parallel() + + expErr := errors.New("expected err") + + genesisTime := int64(9000) + hdrTimeStamp := uint64(10000) + hdrRound := uint64(10) + bfd, _ := sync.NewMetaForkDetector( + &mock.RoundHandlerMock{}, + &testscommon.TimeCacheStub{}, + &mock.BlockTrackerMock{}, + genesisTime, + 0, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag && epoch > 0 + }, + }, + &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledCalled: func(flag common.EnableRoundFlag) bool { + return flag != common.SupernovaRoundFlag + }, + }, + &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(epoch uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{}, expErr + }, + }, + testscommon.GetDefaultProcessConfigsHandler(), + ) + + hdr1 := &block.Header{Nonce: 1, Round: hdrRound, Epoch: 2, PubKeysBitmap: []byte("X"), TimeStamp: hdrTimeStamp} + + err := bfd.CheckGenesisTimeForHeader(hdr1) + require.Equal(t, expErr, err) + }) + + t.Run("supernova activated in epoch and round", func(t *testing.T) { + t.Parallel() + + roundDuration := uint64(1000) + roundHandlerMock := &mock.RoundHandlerMock{} + + genesisTime := int64(900) + supernovaGenesisTime := int64(90000) + hdrTimeStamp := uint64(100000) // as milliseconds + + hdrRound := uint64(20) + supernovaActivationRound := uint64(10) + + bfd, _ := sync.NewMetaForkDetector( + roundHandlerMock, + &testscommon.TimeCacheStub{}, + &mock.BlockTrackerMock{}, + genesisTime, + supernovaGenesisTime, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag + }, + }, + &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return flag == common.SupernovaRoundFlag + }, + GetActivationRoundCalled: func(flag common.EnableRoundFlag) uint64 { + return supernovaActivationRound + }, + }, + &dataRetriever.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(epoch uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{ + RoundDuration: roundDuration, + }, nil + }, + }, + testscommon.GetDefaultProcessConfigsHandler(), + ) + + hdr1 := &block.Header{Nonce: 1, Round: hdrRound, PubKeysBitmap: []byte("X"), TimeStamp: hdrTimeStamp} + + err := bfd.CheckGenesisTimeForHeader(hdr1) + require.Nil(t, err) + }) +} diff --git a/process/sync/metablock.go b/process/sync/metablock.go index b821bfe4d3c..71b12d956a4 100644 --- a/process/sync/metablock.go +++ b/process/sync/metablock.go @@ -55,11 +55,12 @@ func NewMetaBootstrap(arguments ArgMetaBootstrapper) (*MetaBootstrap, error) { base := &baseBootstrap{ chainHandler: arguments.ChainHandler, blockProcessor: arguments.BlockProcessor, + executionManager: arguments.ExecutionManager, store: arguments.Store, headers: arguments.PoolsHolder.Headers(), proofs: arguments.PoolsHolder.Proofs(), + dataPool: arguments.PoolsHolder, roundHandler: arguments.RoundHandler, - waitTime: arguments.WaitTime, hasher: arguments.Hasher, marshalizer: arguments.Marshalizer, forkDetector: arguments.ForkDetector, @@ -82,7 +83,10 @@ func NewMetaBootstrap(arguments ArgMetaBootstrapper) (*MetaBootstrap, error) { historyRepo: arguments.HistoryRepo, scheduledTxsExecutionHandler: arguments.ScheduledTxsExecutionHandler, processWaitTime: arguments.ProcessWaitTime, + processWaitTimeSupernova: arguments.ProcessWaitTimeSupernova, enableEpochsHandler: arguments.EnableEpochsHandler, + enableRoundsHandler: arguments.EnableRoundsHandler, + processConfigsHandler: arguments.ProcessConfigsHandler, } if base.isInImportMode { @@ -111,30 +115,25 @@ func NewMetaBootstrap(arguments ArgMetaBootstrapper) (*MetaBootstrap, error) { return nil, err } + err = base.createTxSyncer() + if err != nil { + return nil, err + } + base.init() return &boot, nil } func (boot *MetaBootstrap) getBlockBody(headerHandler data.HeaderHandler) (data.BodyHandler, error) { - header, ok := headerHandler.(*block.MetaBlock) + header, ok := headerHandler.(data.MetaHeaderHandler) if !ok { return nil, process.ErrWrongTypeAssertion } - hashes := make([][]byte, len(header.MiniBlockHeaders)) - for i := 0; i < len(header.MiniBlockHeaders); i++ { - hashes[i] = header.MiniBlockHeaders[i].Hash - } - - miniBlocksAndHashes, missingMiniBlocksHashes := boot.miniBlocksProvider.GetMiniBlocks(hashes) - if len(missingMiniBlocksHashes) > 0 { - return nil, process.ErrMissingBody - } - - miniBlocks := make([]*block.MiniBlock, len(miniBlocksAndHashes)) - for index, miniBlockAndHash := range miniBlocksAndHashes { - miniBlocks[index] = miniBlockAndHash.Miniblock + miniBlocks, err := boot.getHeaderMiniBlocks(header) + if err != nil { + return nil, err } return &block.Body{MiniBlocks: miniBlocks}, nil @@ -169,8 +168,7 @@ func (boot *MetaBootstrap) setLastEpochStartRound() { return } - epochStartMetaBlock := &block.MetaBlock{} - err = boot.marshalizer.Unmarshal(epochStartMetaBlock, epochStartHdr) + epochStartMetaBlock, err := process.UnmarshalMetaHeader(boot.marshalizer, epochStartHdr) if err != nil { return } @@ -236,13 +234,7 @@ func (boot *MetaBootstrap) getPrevHeader( return nil, err } - prevHeader := &block.MetaBlock{} - err = boot.marshalizer.Unmarshal(prevHeader, buffHeader) - if err != nil { - return nil, err - } - - return prevHeader, nil + return process.UnmarshalMetaHeader(boot.marshalizer, buffHeader) } func (boot *MetaBootstrap) getCurrHeader() (data.HeaderHandler, error) { @@ -251,7 +243,7 @@ func (boot *MetaBootstrap) getCurrHeader() (data.HeaderHandler, error) { return nil, process.ErrNilBlockHeader } - header, ok := blockHeader.(*block.MetaBlock) + header, ok := blockHeader.(data.MetaHeaderHandler) if !ok { return nil, process.ErrWrongTypeAssertion } @@ -260,19 +252,12 @@ func (boot *MetaBootstrap) getCurrHeader() (data.HeaderHandler, error) { } func (boot *MetaBootstrap) getBlockBodyRequestingIfMissing(headerHandler data.HeaderHandler) (data.BodyHandler, error) { - header, ok := headerHandler.(*block.MetaBlock) + header, ok := headerHandler.(data.MetaHeaderHandler) if !ok { return nil, process.ErrWrongTypeAssertion } - hashes := make([][]byte, len(header.MiniBlockHeaders)) - for i := 0; i < len(header.MiniBlockHeaders); i++ { - hashes[i] = header.MiniBlockHeaders[i].Hash - } - - boot.setRequestedMiniBlocks(nil) - - miniBlockSlice, err := boot.getMiniBlocksRequestingIfMissing(hashes) + miniBlockSlice, err := boot.getHeaderMiniBlocksRequestingIfMissing(header) if err != nil { return nil, err } @@ -289,22 +274,23 @@ func (boot *MetaBootstrap) requestMiniBlocksFromHeaderWithNonceIfMissing(headerH return } - header, ok := headerHandler.(*block.MetaBlock) + header, ok := headerHandler.(data.MetaHeaderHandler) if !ok { log.Warn("cannot convert headerHandler in block.MetaBlock") return } - hashes := make([][]byte, 0) - for i := 0; i < len(header.MiniBlockHeaders); i++ { - hashes = append(hashes, header.MiniBlockHeaders[i].Hash) + miniBlockHeaders := header.GetMiniBlockHeaderHandlers() + hashes := make([][]byte, len(miniBlockHeaders)) + for i, miniBlockHeader := range miniBlockHeaders { + hashes[i] = miniBlockHeader.GetHash() } _, missingMiniBlocksHashes := boot.miniBlocksProvider.GetMiniBlocksFromPool(hashes) if len(missingMiniBlocksHashes) > 0 { log.Trace("requesting in advance mini blocks", "num miniblocks", len(missingMiniBlocksHashes), - "header nonce", header.Nonce, + "header nonce", header.GetNonce(), ) boot.requestHandler.RequestMiniBlocks(boot.shardCoordinator.SelfId(), missingMiniBlocksHashes) } diff --git a/process/sync/metablock_test.go b/process/sync/metablock_test.go index 8649b23cca1..3ac8b1ace9b 100644 --- a/process/sync/metablock_test.go +++ b/process/sync/metablock_test.go @@ -8,6 +8,7 @@ import ( "reflect" "strings" goSync "sync" + "sync/atomic" "testing" "time" @@ -15,6 +16,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -27,6 +29,7 @@ import ( "github.com/multiversx/mx-chain-go/storage" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/cache" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/dblookupext" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" @@ -40,7 +43,7 @@ import ( func createMetaBlockProcessor(blk data.ChainHandler) *testscommon.BlockProcessorStub { blockProcessorMock := &testscommon.BlockProcessorStub{ ProcessBlockCalled: func(hdr data.HeaderHandler, bdy data.BodyHandler, haveTime func() time.Duration) error { - _ = blk.SetCurrentBlockHeaderAndRootHash(hdr.(*block.MetaBlock), hdr.GetRootHash()) + _ = blk.SetCurrentBlockHeaderAndRootHash(hdr.(data.MetaHeaderHandler), hdr.GetRootHash()) return nil }, RevertCurrentBlockCalled: func() { @@ -66,7 +69,7 @@ func CreateMetaBootstrapMockArguments() sync.ArgMetaBootstrapper { ChainHandler: initBlockchain(), RoundHandler: &mock.RoundHandlerMock{}, BlockProcessor: &testscommon.BlockProcessorStub{}, - WaitTime: waitTime, + ExecutionManager: &processMocks.ExecutionManagerMock{}, Hasher: &hashingMocks.HasherMock{}, Marshalizer: &mock.MarshalizerMock{}, ForkDetector: &mock.ForkDetectorMock{}, @@ -87,8 +90,11 @@ func CreateMetaBootstrapMockArguments() sync.ArgMetaBootstrapper { HistoryRepo: &dblookupext.HistoryRepositoryStub{}, ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, ProcessWaitTime: testProcessWaitTime, + ProcessWaitTimeSupernova: testProcessWaitTime, RepopulateTokensSupplies: false, EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + ProcessConfigsHandler: testscommon.GetDefaultProcessConfigsHandler(), } argsMetaBootstrapper := sync.ArgMetaBootstrapper{ @@ -494,6 +500,21 @@ func TestNewMetaBootstrap_OkValsShouldWork(t *testing.T) { // ------- processing +func createDefaultRoundArgs() round.ArgsRound { + genesisTime := time.Now() + return round.ArgsRound{ + GenesisTimeStamp: genesisTime, + SupernovaGenesisTimeStamp: genesisTime, + CurrentTimeStamp: genesisTime.Add(2 * 100 * time.Millisecond), + RoundTimeDuration: 100 * time.Millisecond, + SupernovaTimeDuration: 100 * time.Millisecond, + SyncTimer: &mock.SyncTimerMock{}, + StartRound: 0, + SupernovaStartRound: 0, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + } +} + func TestMetaBootstrap_ShouldReturnTimeIsOutWhenMissingHeader(t *testing.T) { t.Parallel() @@ -521,12 +542,8 @@ func TestMetaBootstrap_ShouldReturnTimeIsOutWhenMissingHeader(t *testing.T) { return nil } args.ForkDetector = forkDetector - args.RoundHandler, _ = round.NewRound(time.Now(), - time.Now().Add(2*100*time.Millisecond), - 100*time.Millisecond, - &mock.SyncTimerMock{}, - 0, - ) + + args.RoundHandler, _ = round.NewRound(createDefaultRoundArgs()) args.BlockProcessor = createMetaBlockProcessor(args.ChainHandler) bs, _ := sync.NewMetaBootstrap(args) @@ -633,13 +650,10 @@ func TestMetaBootstrap_SyncShouldSyncOneBlock(t *testing.T) { return nil, nil } args.Accounts = account - args.RoundHandler, _ = round.NewRound( - time.Now(), - time.Now().Add(200*time.Millisecond), - 100*time.Millisecond, - &mock.SyncTimerMock{}, - 0, - ) + + roundArgs := createDefaultRoundArgs() + roundArgs.CurrentTimeStamp = time.Now().Add(200 * time.Millisecond) + args.RoundHandler, _ = round.NewRound(roundArgs) bs, _ := sync.NewMetaBootstrap(args) _ = bs.StartSyncingBlocks() @@ -719,13 +733,7 @@ func TestMetaBootstrap_ShouldReturnNilErr(t *testing.T) { return nil } args.ForkDetector = forkDetector - args.RoundHandler, _ = round.NewRound( - time.Now(), - time.Now().Add(2*100*time.Millisecond), - 100*time.Millisecond, - &mock.SyncTimerMock{}, - 0, - ) + args.RoundHandler, _ = round.NewRound(createDefaultRoundArgs()) bs, _ := sync.NewMetaBootstrap(args) r := bs.SyncBlock(context.Background()) @@ -794,13 +802,7 @@ func TestMetaBootstrap_SyncBlockShouldReturnErrorWhenProcessBlockFailed(t *testi return nil } args.ForkDetector = forkDetector - args.RoundHandler, _ = round.NewRound( - time.Now(), - time.Now().Add(2*100*time.Millisecond), - 100*time.Millisecond, - &mock.SyncTimerMock{}, - 0, - ) + args.RoundHandler, _ = round.NewRound(createDefaultRoundArgs()) bs, _ := sync.NewMetaBootstrap(args) err := bs.SyncBlock(context.Background()) @@ -820,7 +822,11 @@ func TestMetaBootstrap_GetNodeStateShouldReturnSynchronizedWhenCurrentBlockIsNil return 0 } args.ForkDetector = forkDetector - args.RoundHandler, _ = round.NewRound(time.Now(), time.Now(), 200*time.Millisecond, &mock.SyncTimerMock{}, 0) + + roundArgs := createDefaultRoundArgs() + roundArgs.CurrentTimeStamp = time.Now() + roundArgs.RoundTimeDuration = 200 * time.Millisecond + args.RoundHandler, _ = round.NewRound(roundArgs) bs, err := sync.NewMetaBootstrap(args) assert.Nil(t, err) @@ -845,7 +851,11 @@ func TestMetaBootstrap_GetNodeStateShouldReturnNotSynchronizedWhenCurrentBlockIs return []byte("hash") } args.ForkDetector = forkDetector - args.RoundHandler, _ = round.NewRound(time.Now(), time.Now().Add(100*time.Millisecond), 100*time.Millisecond, &mock.SyncTimerMock{}, 0) + + roundArgs := createDefaultRoundArgs() + roundArgs.CurrentTimeStamp = time.Now().Add(100 * time.Millisecond) + roundArgs.RoundTimeDuration = 200 * time.Millisecond + args.RoundHandler, _ = round.NewRound(roundArgs) bs, _ := sync.NewMetaBootstrap(args) bs.ComputeNodeState() @@ -909,7 +919,10 @@ func TestMetaBootstrap_GetNodeStateShouldReturnNotSynchronizedWhenNodeIsNotSynce return 1 } args.ForkDetector = forkDetector - args.RoundHandler, _ = round.NewRound(time.Now(), time.Now().Add(100*time.Millisecond), 100*time.Millisecond, &mock.SyncTimerMock{}, 0) + + roundArgs := createDefaultRoundArgs() + roundArgs.CurrentTimeStamp = time.Now().Add(100 * time.Millisecond) + args.RoundHandler, _ = round.NewRound(roundArgs) bs, _ := sync.NewMetaBootstrap(args) bs.ComputeNodeState() @@ -960,8 +973,12 @@ func TestMetaBootstrap_GetNodeStateShouldReturnNotSynchronizedWhenForkIsDetected &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) bs, _ := sync.NewMetaBootstrap(args) @@ -1027,8 +1044,12 @@ func TestMetaBootstrap_GetNodeStateShouldReturnSynchronizedWhenForkIsDetectedAnd &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), ) bs, _ := sync.NewMetaBootstrap(args) @@ -1069,7 +1090,10 @@ func TestMetaBootstrap_GetHeaderFromPoolShouldReturnNil(t *testing.T) { return 0 } args.ForkDetector = forkDetector - args.RoundHandler, _ = round.NewRound(time.Now(), time.Now(), 200*time.Millisecond, &mock.SyncTimerMock{}, 0) + roundArgs := createDefaultRoundArgs() + roundArgs.CurrentTimeStamp = time.Now() + roundArgs.RoundTimeDuration = 200 * time.Millisecond + args.RoundHandler, _ = round.NewRound(roundArgs) bs, _ := sync.NewMetaBootstrap(args) hdr, _, _ := process.GetMetaHeaderFromPoolWithNonce(0, pools.HeadersCalled()) @@ -1697,13 +1721,7 @@ func TestMetaBootstrap_SyncBlockErrGetNodeDBShouldSyncAccounts(t *testing.T) { return nil } args.ForkDetector = forkDetector - args.RoundHandler, _ = round.NewRound( - time.Now(), - time.Now().Add(2*100*time.Millisecond), - 100*time.Millisecond, - &mock.SyncTimerMock{}, - 0, - ) + args.RoundHandler, _ = round.NewRound(createDefaultRoundArgs()) accountsSyncCalled := false args.AccountsDBSyncer = &mock.AccountsDBSyncerStub{ SyncAccountsCalled: func(rootHash []byte, _ common.StorageMarker) error { @@ -1787,12 +1805,7 @@ func TestMetaBootstrap_SyncBlock_WithEquivalentProofs(t *testing.T) { return nil } args.ForkDetector = forkDetector - args.RoundHandler, _ = round.NewRound(time.Now(), - time.Now().Add(2*100*time.Millisecond), - 100*time.Millisecond, - &mock.SyncTimerMock{}, - 0, - ) + args.RoundHandler, _ = round.NewRound(createDefaultRoundArgs()) args.BlockProcessor = createMetaBlockProcessor(args.ChainHandler) pools := createMockPools() @@ -1845,12 +1858,7 @@ func TestMetaBootstrap_SyncBlock_WithEquivalentProofs(t *testing.T) { return nil } args.ForkDetector = forkDetector - args.RoundHandler, _ = round.NewRound(time.Now(), - time.Now().Add(2*100*time.Millisecond), - 100*time.Millisecond, - &mock.SyncTimerMock{}, - 0, - ) + args.RoundHandler, _ = round.NewRound(createDefaultRoundArgs()) args.BlockProcessor = createMetaBlockProcessor(args.ChainHandler) pools := createMockPools() @@ -1865,12 +1873,12 @@ func TestMetaBootstrap_SyncBlock_WithEquivalentProofs(t *testing.T) { } } - numHeaderCalls := 0 + var numHeaderCalls atomic.Uint64 pools.HeadersCalled = func() dataRetriever.HeadersPool { sds := &mock.HeadersCacherStub{} sds.GetHeaderByNonceAndShardIdCalled = func(hdrNonce uint64, shardId uint32) (handlers []data.HeaderHandler, i [][]byte, e error) { - if numHeaderCalls == 0 { - numHeaderCalls++ + if numHeaderCalls.Load() == 0 { + numHeaderCalls.Add(1) return nil, nil, errors.New("err") } @@ -1947,12 +1955,7 @@ func TestMetaBootstrap_SyncBlock_WithEquivalentProofs(t *testing.T) { return hash } args.ForkDetector = forkDetector - args.RoundHandler, _ = round.NewRound(time.Now(), - time.Now().Add(2*100*time.Millisecond), - 100*time.Millisecond, - &mock.SyncTimerMock{}, - 0, - ) + args.RoundHandler, _ = round.NewRound(createDefaultRoundArgs()) args.BlockProcessor = createMetaBlockProcessor(args.ChainHandler) pools := createMockPools() @@ -1963,6 +1966,9 @@ func TestMetaBootstrap_SyncBlock_WithEquivalentProofs(t *testing.T) { GetProofCalled: func(shardID uint32, headerHash []byte) (data.HeaderProofHandler, error) { return nil, errors.New("missing proof") }, + GetProofByNonceCalled: func(headerNonce uint64, shardID uint32) (data.HeaderProofHandler, error) { + return nil, errors.New("missing proof") + }, HasProofCalled: func(shardID uint32, headerHash []byte) bool { if numProofCalls == 0 { numProofCalls++ diff --git a/process/sync/shardForkDetector.go b/process/sync/shardForkDetector.go index a45ed8bd77b..84d2b058ac0 100644 --- a/process/sync/shardForkDetector.go +++ b/process/sync/shardForkDetector.go @@ -26,10 +26,14 @@ func NewShardForkDetector( blackListHandler process.TimeCacher, blockTracker process.BlockTracker, genesisTime int64, + supernovaGenesisTime int64, enableEpochsHandler common.EnableEpochsHandler, + enableRoundsHandler common.EnableRoundsHandler, proofsPool process.ProofsPool, + chainParametersHandler common.ChainParametersHandler, + processConfigsHandler common.ProcessConfigsHandler, + shardID uint32, ) (*shardForkDetector, error) { - if check.IfNil(roundHandler) { return nil, process.ErrNilRoundHandler } @@ -42,9 +46,18 @@ func NewShardForkDetector( if check.IfNil(enableEpochsHandler) { return nil, process.ErrNilEnableEpochsHandler } + if check.IfNil(enableRoundsHandler) { + return nil, process.ErrNilEnableRoundsHandler + } if check.IfNil(proofsPool) { return nil, process.ErrNilProofsPool } + if check.IfNil(chainParametersHandler) { + return nil, process.ErrNilChainParametersHandler + } + if check.IfNil(processConfigsHandler) { + return nil, process.ErrNilProcessConfigsHandler + } genesisHdr, _, err := blockTracker.GetSelfNotarizedHeader(core.MetachainShardId, 0) if err != nil { @@ -52,15 +65,20 @@ func NewShardForkDetector( } bfd := &baseForkDetector{ - roundHandler: roundHandler, - blackListHandler: blackListHandler, - genesisTime: genesisTime, - blockTracker: blockTracker, - genesisNonce: genesisHdr.GetNonce(), - genesisRound: genesisHdr.GetRound(), - genesisEpoch: genesisHdr.GetEpoch(), - enableEpochsHandler: enableEpochsHandler, - proofsPool: proofsPool, + roundHandler: roundHandler, + blackListHandler: blackListHandler, + genesisTime: genesisTime, + supernovaGenesisTime: supernovaGenesisTime, + blockTracker: blockTracker, + genesisNonce: genesisHdr.GetNonce(), + genesisRound: genesisHdr.GetRound(), + genesisEpoch: genesisHdr.GetEpoch(), + enableEpochsHandler: enableEpochsHandler, + enableRoundsHandler: enableRoundsHandler, + proofsPool: proofsPool, + chainParametersHandler: chainParametersHandler, + processConfigsHandler: processConfigsHandler, + shardID: shardID, } bfd.headers = make(map[uint64][]*headerInfo) diff --git a/process/sync/shardForkDetector_test.go b/process/sync/shardForkDetector_test.go index d3b37a0dfd1..dad554e5e7a 100644 --- a/process/sync/shardForkDetector_test.go +++ b/process/sync/shardForkDetector_test.go @@ -1,19 +1,25 @@ package sync_test import ( + "errors" "testing" + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/mock" "github.com/multiversx/mx-chain-go/process/sync" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNewShardForkDetector_NilRoundHandlerShouldErr(t *testing.T) { @@ -24,8 +30,13 @@ func TestNewShardForkDetector_NilRoundHandlerShouldErr(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) assert.True(t, check.IfNil(sfd)) assert.Equal(t, process.ErrNilRoundHandler, err) @@ -39,8 +50,13 @@ func TestNewShardForkDetector_NilBlackListShouldErr(t *testing.T) { nil, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) assert.True(t, check.IfNil(sfd)) assert.Equal(t, process.ErrNilBlackListCacher, err) @@ -54,8 +70,13 @@ func TestNewShardForkDetector_NilBlockTrackerShouldErr(t *testing.T) { &testscommon.TimeCacheStub{}, nil, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) assert.True(t, check.IfNil(sfd)) assert.Equal(t, process.ErrNilBlockTracker, err) @@ -69,13 +90,38 @@ func TestNewShardForkDetector_NilEnableEpochsHandlerShouldErr(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, nil, + &testscommon.EnableRoundsHandlerStub{}, &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) assert.True(t, check.IfNil(sfd)) assert.Equal(t, process.ErrNilEnableEpochsHandler, err) } +func TestNewShardForkDetector_NilEnableRoundsHandlerShouldErr(t *testing.T) { + t.Parallel() + + sfd, err := sync.NewShardForkDetector( + &mock.RoundHandlerMock{}, + &testscommon.TimeCacheStub{}, + &mock.BlockTrackerMock{}, + 0, + 0, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + nil, + &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, + ) + assert.True(t, check.IfNil(sfd)) + assert.Equal(t, process.ErrNilEnableRoundsHandler, err) +} + func TestNewShardForkDetector_NilProofsPoolShouldErr(t *testing.T) { t.Parallel() @@ -84,8 +130,13 @@ func TestNewShardForkDetector_NilProofsPoolShouldErr(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, nil, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) assert.True(t, check.IfNil(sfd)) assert.Equal(t, process.ErrNilProofsPool, err) @@ -99,8 +150,13 @@ func TestNewShardForkDetector_OkParamsShouldWork(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) assert.Nil(t, err) assert.False(t, check.IfNil(sfd)) @@ -120,8 +176,13 @@ func TestShardForkDetector_AddHeaderNilHeaderShouldErr(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) err := bfd.AddHeader(nil, make([]byte, 0), process.BHProcessed, nil, nil) assert.Equal(t, sync.ErrNilHeader, err) @@ -136,8 +197,13 @@ func TestShardForkDetector_AddHeaderNilHashShouldErr(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) err := bfd.AddHeader(&block.Header{}, nil, process.BHProcessed, nil, nil) assert.Equal(t, sync.ErrNilHash, err) @@ -154,8 +220,13 @@ func TestShardForkDetector_AddHeaderNotPresentShouldWork(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) err := bfd.AddHeader(hdr, hash, process.BHProcessed, nil, nil) assert.Nil(t, err) @@ -178,8 +249,13 @@ func TestShardForkDetector_AddHeaderPresentShouldAppend(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) _ = bfd.AddHeader(hdr1, hash1, process.BHProcessed, nil, nil) err := bfd.AddHeader(hdr2, hash2, process.BHProcessed, nil, nil) @@ -202,8 +278,13 @@ func TestShardForkDetector_AddHeaderWithProcessedBlockShouldSetCheckpoint(t *tes &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) _ = bfd.AddHeader(hdr1, hash1, process.BHProcessed, nil, nil) assert.Equal(t, hdr1.Nonce, bfd.LastCheckpointNonce()) @@ -221,8 +302,13 @@ func TestShardForkDetector_AddHeaderPresentShouldNotRewriteState(t *testing.T) { &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) _ = bfd.AddHeader(hdr1, hash, process.BHReceived, nil, nil) err := bfd.AddHeader(hdr2, hash, process.BHProcessed, nil, nil) @@ -244,10 +330,232 @@ func TestShardForkDetector_AddHeaderHigherNonceThanRoundShouldErr(t *testing.T) &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) err := bfd.AddHeader( &block.Header{Nonce: 1, Round: 0, PubKeysBitmap: []byte("X")}, []byte("hash1"), process.BHProcessed, nil, nil) assert.Equal(t, sync.ErrHigherNonceInBlock, err) } + +func TestShardForkDetector_ComputeGenesisTimeFromHeader(t *testing.T) { + t.Parallel() + + t.Run("legacy genesis time calculation", func(t *testing.T) { + t.Parallel() + + roundDuration := uint64(100) + roundHandlerMock := &mock.RoundHandlerMock{} + + genesisTime := int64(9000) + hdrTimeStamp := uint64(10000) + hdrRound := uint64(10) + bfd, _ := sync.NewShardForkDetector( + roundHandlerMock, + &testscommon.TimeCacheStub{}, + &mock.BlockTrackerMock{}, + genesisTime, + 0, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag != common.SupernovaFlag + }, + }, + &testscommon.EnableRoundsHandlerStub{}, + &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(epoch uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{ + RoundDuration: roundDuration * 1000, + }, nil + }, + }, + testscommon.GetDefaultProcessConfigsHandler(), + 0, + ) + + hdr1 := &block.Header{Nonce: 1, Round: hdrRound, PubKeysBitmap: []byte("X"), TimeStamp: hdrTimeStamp} + + err := bfd.CheckGenesisTimeForHeader(hdr1) + require.Nil(t, err) + }) + + t.Run("legacy genesis time calculation, should fail if not able to get round duration", func(t *testing.T) { + t.Parallel() + + expErr := errors.New("expected err") + + genesisTime := int64(9000) + hdrTimeStamp := uint64(10000) + hdrRound := uint64(10) + bfd, _ := sync.NewMetaForkDetector( + &mock.RoundHandlerMock{}, + &testscommon.TimeCacheStub{}, + &mock.BlockTrackerMock{}, + genesisTime, + 0, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag != common.SupernovaFlag + }, + }, + &testscommon.EnableRoundsHandlerStub{}, + &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(epoch uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{}, expErr + }, + }, + testscommon.GetDefaultProcessConfigsHandler(), + ) + + hdr1 := &block.Header{Nonce: 1, Round: hdrRound, PubKeysBitmap: []byte("X"), TimeStamp: hdrTimeStamp} + + err := bfd.CheckGenesisTimeForHeader(hdr1) + require.Equal(t, expErr, err) + }) + + t.Run("supernova activated in epoch but not in round", func(t *testing.T) { + t.Parallel() + + roundDuration := uint64(100) + roundHandlerMock := &mock.RoundHandlerMock{} + + genesisTime := int64(9000) + hdrTimeStamp := uint64(10000000) // as milliseconds + hdrRound := uint64(10) + bfd, _ := sync.NewShardForkDetector( + roundHandlerMock, + &testscommon.TimeCacheStub{}, + &mock.BlockTrackerMock{}, + genesisTime, + 0, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag && epoch > 0 + }, + }, + &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledCalled: func(flag common.EnableRoundFlag) bool { + return flag != common.SupernovaRoundFlag + }, + }, + &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(epoch uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{ + RoundDuration: roundDuration * 1000, + }, nil + }, + }, + testscommon.GetDefaultProcessConfigsHandler(), + 0, + ) + + hdr1 := &block.Header{ + Nonce: 1, + Round: hdrRound, + Epoch: 1, + PubKeysBitmap: []byte("X"), + TimeStamp: hdrTimeStamp, + } + + err := bfd.CheckGenesisTimeForHeader(hdr1) + assert.Nil(t, err) + }) + + t.Run("supernova activated in epoch but not in round, should fail if not able to get round duration", func(t *testing.T) { + t.Parallel() + + expErr := errors.New("expected err") + + genesisTime := int64(9000) + hdrTimeStamp := uint64(10000) + hdrRound := uint64(10) + bfd, _ := sync.NewShardForkDetector( + &mock.RoundHandlerMock{}, + &testscommon.TimeCacheStub{}, + &mock.BlockTrackerMock{}, + genesisTime, + 0, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag && epoch > 0 + }, + }, + &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledCalled: func(flag common.EnableRoundFlag) bool { + return flag != common.SupernovaRoundFlag + }, + }, + &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(epoch uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{}, expErr + }, + }, + testscommon.GetDefaultProcessConfigsHandler(), + 0, + ) + + hdr1 := &block.Header{Nonce: 1, Round: hdrRound, Epoch: 2, PubKeysBitmap: []byte("X"), TimeStamp: hdrTimeStamp} + + err := bfd.CheckGenesisTimeForHeader(hdr1) + require.Equal(t, expErr, err) + }) + + t.Run("supernova activated in epoch and round", func(t *testing.T) { + t.Parallel() + + roundDuration := uint64(1000) + roundHandlerMock := &mock.RoundHandlerMock{} + + genesisTime := int64(900) + supernovaGenesisTime := int64(90000) + hdrTimeStamp := uint64(100000) // as milliseconds + + hdrRound := uint64(20) + supernovaActivationRound := uint64(10) + + bfd, _ := sync.NewShardForkDetector( + roundHandlerMock, + &testscommon.TimeCacheStub{}, + &mock.BlockTrackerMock{}, + genesisTime, + supernovaGenesisTime, + &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag + }, + }, + &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return flag == common.SupernovaRoundFlag + }, + GetActivationRoundCalled: func(flag common.EnableRoundFlag) uint64 { + return supernovaActivationRound + }, + }, + &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{ + ChainParametersForEpochCalled: func(epoch uint32) (config.ChainParametersByEpochConfig, error) { + return config.ChainParametersByEpochConfig{ + RoundDuration: roundDuration, + }, nil + }, + }, + testscommon.GetDefaultProcessConfigsHandler(), + 0, + ) + + hdr1 := &block.Header{Nonce: 1, Round: hdrRound, PubKeysBitmap: []byte("X"), TimeStamp: hdrTimeStamp} + + err := bfd.CheckGenesisTimeForHeader(hdr1) + require.Nil(t, err) + }) +} diff --git a/process/sync/shardblock.go b/process/sync/shardblock.go index 1986a394ff6..6fe62307530 100644 --- a/process/sync/shardblock.go +++ b/process/sync/shardblock.go @@ -42,11 +42,12 @@ func NewShardBootstrap(arguments ArgShardBootstrapper) (*ShardBootstrap, error) base := &baseBootstrap{ chainHandler: arguments.ChainHandler, blockProcessor: arguments.BlockProcessor, + executionManager: arguments.ExecutionManager, store: arguments.Store, headers: arguments.PoolsHolder.Headers(), proofs: arguments.PoolsHolder.Proofs(), + dataPool: arguments.PoolsHolder, roundHandler: arguments.RoundHandler, - waitTime: arguments.WaitTime, hasher: arguments.Hasher, marshalizer: arguments.Marshalizer, forkDetector: arguments.ForkDetector, @@ -69,8 +70,11 @@ func NewShardBootstrap(arguments ArgShardBootstrapper) (*ShardBootstrap, error) historyRepo: arguments.HistoryRepo, scheduledTxsExecutionHandler: arguments.ScheduledTxsExecutionHandler, processWaitTime: arguments.ProcessWaitTime, + processWaitTimeSupernova: arguments.ProcessWaitTimeSupernova, repopulateTokensSupplies: arguments.RepopulateTokensSupplies, enableEpochsHandler: arguments.EnableEpochsHandler, + enableRoundsHandler: arguments.EnableRoundsHandler, + processConfigsHandler: arguments.ProcessConfigsHandler, } if base.isInImportMode { @@ -97,6 +101,11 @@ func NewShardBootstrap(arguments ArgShardBootstrapper) (*ShardBootstrap, error) return nil, err } + err = base.createTxSyncer() + if err != nil { + return nil, err + } + base.init() return &boot, nil @@ -108,19 +117,9 @@ func (boot *ShardBootstrap) getBlockBody(headerHandler data.HeaderHandler) (data return nil, process.ErrWrongTypeAssertion } - hashes := make([][]byte, len(header.GetMiniBlockHeaderHandlers())) - for i := 0; i < len(header.GetMiniBlockHeaderHandlers()); i++ { - hashes[i] = header.GetMiniBlockHeaderHandlers()[i].GetHash() - } - - miniBlocksAndHashes, missingMiniBlocksHashes := boot.miniBlocksProvider.GetMiniBlocks(hashes) - if len(missingMiniBlocksHashes) > 0 { - return nil, process.ErrMissingBody - } - - miniBlocks := make([]*block.MiniBlock, len(miniBlocksAndHashes)) - for index, miniBlockAndHash := range miniBlocksAndHashes { - miniBlocks[index] = miniBlockAndHash.Miniblock + miniBlocks, err := boot.getHeaderMiniBlocks(header) + if err != nil { + return nil, err } return &block.Body{MiniBlocks: miniBlocks}, nil @@ -245,14 +244,7 @@ func (boot *ShardBootstrap) getBlockBodyRequestingIfMissing(headerHandler data.H return nil, process.ErrWrongTypeAssertion } - hashes := make([][]byte, len(header.GetMiniBlockHeaderHandlers())) - for i := 0; i < len(header.GetMiniBlockHeaderHandlers()); i++ { - hashes[i] = header.GetMiniBlockHeaderHandlers()[i].GetHash() - } - - boot.setRequestedMiniBlocks(nil) - - miniBlockSlice, err := boot.getMiniBlocksRequestingIfMissing(hashes) + miniBlockSlice, err := boot.getHeaderMiniBlocksRequestingIfMissing(header) if err != nil { return nil, err } diff --git a/process/sync/shardblock_test.go b/process/sync/shardblock_test.go index cba152bca5c..6447e654ee4 100644 --- a/process/sync/shardblock_test.go +++ b/process/sync/shardblock_test.go @@ -9,6 +9,7 @@ import ( "reflect" "strings" goSync "sync" + "sync/atomic" "testing" "time" @@ -16,9 +17,13 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/marshal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + headersCache "github.com/multiversx/mx-chain-go/process/asyncExecution/cache" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/consensus" "github.com/multiversx/mx-chain-go/consensus/round" @@ -32,6 +37,7 @@ import ( "github.com/multiversx/mx-chain-go/storage/storageunit" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/cache" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/dblookupext" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" @@ -42,10 +48,160 @@ import ( storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" ) -// waitTime defines the time in milliseconds until node waits the requested info from the network -const waitTime = 100 * time.Millisecond const testProcessWaitTime = time.Second +var errExpected = errors.New("expected error") + +func setupStore(marshaller marshal.Marshalizer, prevHdr data.HeaderHandler, returnError error) dataRetriever.StorageService { + return &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + if returnError != nil { + return nil, returnError + } + prevHdrBytes, _ := marshaller.Marshal(prevHdr) + return prevHdrBytes, nil + }, + }, nil + }, + } +} + +func setupForkDetector(highestNonce uint64) process.ForkDetector { + return &mock.ForkDetectorMock{ + CheckForkCalled: func() *process.ForkInfo { + return process.NewForkInfo() + }, + GetHighestFinalBlockNonceCalled: func() uint64 { + return highestNonce + }, + GetHighestFinalBlockHashCalled: func() []byte { + return []byte("hash") + }, + ProbableHighestNonceCalled: func() uint64 { + return highestNonce + }, + RemoveHeaderCalled: func(nonce uint64, hash []byte) {}, + GetNotarizedHeaderHashCalled: func(nonce uint64) []byte { + return nil + }, + } +} + +type headerAndHash struct { + header data.HeaderHandler + hash []byte +} + +func setupPools(headersAndHashes ...headerAndHash) dataRetriever.PoolsHolder { + pools := dataRetrieverMock.NewPoolsHolderStub() + pools.HeadersCalled = func() dataRetriever.HeadersPool { + return &mock.HeadersCacherStub{ + GetHeaderByNonceAndShardIdCalled: func(hdrNonce uint64, shardId uint32) ([]data.HeaderHandler, [][]byte, error) { + for _, hh := range headersAndHashes { + if hh.header.GetNonce() == hdrNonce { + return []data.HeaderHandler{hh.header}, [][]byte{hh.hash}, nil + } + } + + return nil, nil, errors.New("err") + }, + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + for i, hh := range headersAndHashes { + if string(hh.hash) != string(hash) { + continue + } + + if i > 0 { + return headersAndHashes[i-1].header, nil + } + + return &block.HeaderV3{ + Nonce: 1, + BlockBodyType: block.TxBlock, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 0, + HeaderHash: []byte("hash0"), + }, + }, + }, nil + } + + return nil, errors.New("err") + }, + } + } + pools.MiniBlocksCalled = func() storage.Cacher { + cs := cache.NewCacherStub() + cs.RegisterHandlerCalled = func(i func(key []byte, value interface{})) {} + cs.GetCalled = func(key []byte) (value interface{}, ok bool) { + return make(block.MiniBlockSlice, 0), true + } + return cs + } + pools.TransactionsCalled = func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + OnExecutedBlockCalled: func(header data.HeaderHandler, rootHash []byte) error { + return nil + }, + } + } + pools.ProofsCalled = func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{} + } + return pools +} + +// setupPoolsDirectHashMapping creates a pools holder where GetHeaderByHash returns the header +// that actually has the given hash (direct mapping), rather than the off-by-one mapping in setupPools. +// This is needed for tests that exercise the backward hash-walk in prepareForSyncIfNeeded. +func setupPoolsDirectHashMapping(headersAndHashes ...headerAndHash) dataRetriever.PoolsHolder { + pools := dataRetrieverMock.NewPoolsHolderStub() + pools.HeadersCalled = func() dataRetriever.HeadersPool { + return &mock.HeadersCacherStub{ + GetHeaderByNonceAndShardIdCalled: func(hdrNonce uint64, shardId uint32) ([]data.HeaderHandler, [][]byte, error) { + for _, hh := range headersAndHashes { + if hh.header.GetNonce() == hdrNonce { + return []data.HeaderHandler{hh.header}, [][]byte{hh.hash}, nil + } + } + + return nil, nil, errors.New("err") + }, + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + for _, hh := range headersAndHashes { + if string(hh.hash) == string(hash) { + return hh.header, nil + } + } + + return nil, errors.New("err") + }, + } + } + pools.MiniBlocksCalled = func() storage.Cacher { + cs := cache.NewCacherStub() + cs.RegisterHandlerCalled = func(i func(key []byte, value interface{})) {} + cs.GetCalled = func(key []byte) (value interface{}, ok bool) { + return make(block.MiniBlockSlice, 0), true + } + return cs + } + pools.TransactionsCalled = func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + OnExecutedBlockCalled: func(header data.HeaderHandler, rootHash []byte) error { + return nil + }, + } + } + pools.ProofsCalled = func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{} + } + return pools +} + type removedFlags struct { flagHdrRemovedFromHeaders bool flagHdrRemovedFromStorage bool @@ -115,6 +271,7 @@ func createFullStore() dataRetriever.StorageService { store.AddStorer(dataRetriever.ScheduledSCRsUnit, generateTestUnit()) store.AddStorer(dataRetriever.UserAccountsUnit, generateTestUnit()) store.AddStorer(dataRetriever.PeerAccountsUnit, generateTestUnit()) + store.AddStorer(dataRetriever.UnsignedTransactionUnit, generateTestUnit()) return store } @@ -185,13 +342,9 @@ func initNetworkWatcher() process.NetworkConnectionWatcher { } func initRoundHandler() consensus.RoundHandler { - rnd, _ := round.NewRound( - time.Now(), - time.Now(), - 100*time.Millisecond, - &mock.SyncTimerMock{}, - 0, - ) + roundArgs := createDefaultRoundArgs() + roundArgs.CurrentTimeStamp = time.Now() + rnd, _ := round.NewRound(roundArgs) return rnd } @@ -203,7 +356,7 @@ func CreateShardBootstrapMockArguments() sync.ArgShardBootstrapper { ChainHandler: initBlockchain(), RoundHandler: &mock.RoundHandlerMock{}, BlockProcessor: &testscommon.BlockProcessorStub{}, - WaitTime: waitTime, + ExecutionManager: &processMocks.ExecutionManagerMock{}, Hasher: &hashingMocks.HasherMock{}, Marshalizer: &mock.MarshalizerMock{}, ForkDetector: &mock.ForkDetectorMock{}, @@ -224,8 +377,11 @@ func CreateShardBootstrapMockArguments() sync.ArgShardBootstrapper { HistoryRepo: &dblookupext.HistoryRepositoryStub{}, ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, ProcessWaitTime: testProcessWaitTime, + ProcessWaitTimeSupernova: testProcessWaitTime, RepopulateTokensSupplies: false, EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + ProcessConfigsHandler: testscommon.GetDefaultProcessConfigsHandler(), } argsShardBootstrapper := sync.ArgShardBootstrapper{ @@ -465,6 +621,19 @@ func TestNewShardBootstrap_InvalidProcessTimeShouldErr(t *testing.T) { assert.True(t, errors.Is(err, process.ErrInvalidProcessWaitTime)) } +func TestNewShardBootstrap_InvalidProcessWaitTimeSupernovaShouldErr(t *testing.T) { + t.Parallel() + + args := CreateShardBootstrapMockArguments() + args.ProcessWaitTimeSupernova = time.Millisecond*100 - 1 + + bs, err := sync.NewShardBootstrap(args) + + assert.True(t, check.IfNil(bs)) + assert.True(t, errors.Is(err, process.ErrInvalidProcessWaitTime)) + assert.Contains(t, err.Error(), "Supernova") +} + func TestNewShardBootstrap_NilEnableEpochsHandlerShouldErr(t *testing.T) { t.Parallel() @@ -559,7 +728,7 @@ func TestNewShardBootstrap_OkValsShouldWork(t *testing.T) { assert.False(t, check.IfNil(bs)) assert.Nil(t, err) - assert.Equal(t, 2, wasCalled) + assert.Equal(t, 3, wasCalled) assert.False(t, bs.IsInterfaceNil()) assert.True(t, bs.IsInImportMode()) @@ -601,13 +770,7 @@ func TestBootstrap_ShouldReturnTimeIsOutWhenMissingHeader(t *testing.T) { return nil } args.ForkDetector = forkDetector - args.RoundHandler, _ = round.NewRound( - time.Now(), - time.Now().Add(2*100*time.Millisecond), - 100*time.Millisecond, - &mock.SyncTimerMock{}, - 0, - ) + args.RoundHandler, _ = round.NewRound(createDefaultRoundArgs()) args.BlockProcessor = createBlockProcessor(args.ChainHandler) bs, _ := sync.NewShardBootstrap(args) @@ -662,13 +825,7 @@ func TestBootstrap_ShouldReturnTimeIsOutWhenMissingBody(t *testing.T) { return nil } args.ForkDetector = forkDetector - args.RoundHandler, _ = round.NewRound( - time.Now(), - time.Now().Add(2*100*time.Millisecond), - 100*time.Millisecond, - &mock.SyncTimerMock{}, - 0, - ) + args.RoundHandler, _ = round.NewRound(createDefaultRoundArgs()) bs, _ := sync.NewShardBootstrap(args) bs.RequestHeaderWithNonce(2) @@ -802,13 +959,7 @@ func TestBootstrap_SyncShouldSyncOneBlock(t *testing.T) { return nil, nil } args.Accounts = account - args.RoundHandler, _ = round.NewRound( - time.Now(), - time.Now().Add(200*time.Millisecond), - 100*time.Millisecond, - &mock.SyncTimerMock{}, - 0, - ) + args.RoundHandler, _ = round.NewRound(createDefaultRoundArgs()) bs, _ := sync.NewShardBootstrap(args) _ = bs.StartSyncingBlocks() @@ -891,13 +1042,7 @@ func TestBootstrap_ShouldReturnNilErr(t *testing.T) { return nil } args.ForkDetector = forkDetector - args.RoundHandler, _ = round.NewRound( - time.Now(), - time.Now().Add(2*100*time.Millisecond), - 100*time.Millisecond, - &mock.SyncTimerMock{}, - 0, - ) + args.RoundHandler, _ = round.NewRound(createDefaultRoundArgs()) bs, _ := sync.NewShardBootstrap(args) r := bs.SyncBlock(context.Background()) @@ -983,13 +1128,7 @@ func TestBootstrap_SyncBlockShouldReturnErrorWhenProcessBlockFailed(t *testing.T return nil } args.ForkDetector = forkDetector - args.RoundHandler, _ = round.NewRound( - time.Now(), - time.Now().Add(2*100*time.Millisecond), - 100*time.Millisecond, - &mock.SyncTimerMock{}, - 0, - ) + args.RoundHandler, _ = round.NewRound(createDefaultRoundArgs()) bs, _ := sync.NewShardBootstrap(args) @@ -1032,13 +1171,10 @@ func TestBootstrap_GetNodeStateShouldReturnNotSynchronizedWhenCurrentBlockIsNilA return 1 } args.ForkDetector = forkDetector - args.RoundHandler, _ = round.NewRound( - time.Now(), - time.Now().Add(100*time.Millisecond), - 100*time.Millisecond, - &mock.SyncTimerMock{}, - 0, - ) + + roundArgs := createDefaultRoundArgs() + roundArgs.CurrentTimeStamp = time.Now().Add(100 * time.Millisecond) + args.RoundHandler, _ = round.NewRound(roundArgs) bs, _ := sync.NewShardBootstrap(args) bs.ComputeNodeState() @@ -1102,13 +1238,10 @@ func TestBootstrap_GetNodeStateShouldReturnNotSynchronizedWhenNodeIsNotSynced(t return 1 } args.ForkDetector = forkDetector - args.RoundHandler, _ = round.NewRound( - time.Now(), - time.Now().Add(100*time.Millisecond), - 100*time.Millisecond, - &mock.SyncTimerMock{}, - 0, - ) + + roundArgs := createDefaultRoundArgs() + roundArgs.CurrentTimeStamp = time.Now().Add(100 * time.Millisecond) + args.RoundHandler, _ = round.NewRound(roundArgs) bs, _ := sync.NewShardBootstrap(args) bs.ComputeNodeState() @@ -1168,8 +1301,13 @@ func TestBootstrap_GetNodeStateShouldReturnNotSynchronizedWhenForkIsDetectedAndI &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) bs, _ := sync.NewShardBootstrap(args) @@ -1245,8 +1383,13 @@ func TestBootstrap_GetNodeStateShouldReturnSynchronizedWhenForkIsDetectedAndItRe &testscommon.TimeCacheStub{}, &mock.BlockTrackerMock{}, 0, + 0, &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + &testscommon.EnableRoundsHandlerStub{}, &dataRetrieverMock.ProofsPoolMock{}, + &chainParameters.ChainParametersHandlerStub{}, + testscommon.GetDefaultProcessConfigsHandler(), + 0, ) bs, _ := sync.NewShardBootstrap(args) @@ -1846,7 +1989,8 @@ func TestBootstrap_GetTxBodyHavingHashFoundInStorageShouldWork(t *testing.T) { }, } - bs, _ := sync.NewShardBootstrap(args) + bs, err := sync.NewShardBootstrap(args) + require.Nil(t, err) gotMbsAndHashes, _ := bs.GetMiniBlocks(requestedHash) assert.Equal(t, mbsAndHashes, gotMbsAndHashes) @@ -2004,6 +2148,36 @@ func TestShardBootstrap_RequestMiniBlocksFromHeaderWithNonceIfMissing(t *testing assert.True(t, requestDataWasCalled) } +func TestShardBootstrap_DoJobOnSyncBlockFailShouldSkipWhenBlockProcessorBusy(t *testing.T) { + t.Parallel() + + args := CreateShardBootstrapMockArguments() + + forkDetectorMock := &mock.ForkDetectorMock{ + ResetProbableHighestNonceCalled: func() { + require.Fail(t, "should not have called ResetProbableHighestNonce") + }, + } + args.ForkDetector = forkDetectorMock + args.ChainHandler = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.Header{Nonce: 1} + }, + GetGenesisHeaderCalled: func() data.HeaderHandler { + return &block.Header{} + }, + } + + bs, _ := sync.NewShardBootstrap(args) + + initialSyncErrors := bs.GetNumSyncedWithErrorsForNonce(2) + + bs.DoJobOnSyncBlockFail(&block.Body{}, &block.Header{Nonce: 2}, process.ErrBlockProcessorBusy) + + afterSyncErrors := bs.GetNumSyncedWithErrorsForNonce(2) + assert.Equal(t, initialSyncErrors, afterSyncErrors, "sync error counter should not be incremented for busy processor") +} + func TestShardBootstrap_DoJobOnSyncBlockFailShouldNotResetProbableHighestNonceWhenAreNotEnoughErrorsPerNonce(t *testing.T) { t.Parallel() @@ -2084,16 +2258,22 @@ func TestShardBootstrap_DoJobOnSyncBlockFailShouldResetProbableHighestNonce(t *t args.ForkDetector = forkDetectorMock args.ChainHandler = &testscommon.ChainHandlerStub{ GetCurrentBlockHeaderCalled: func() data.HeaderHandler { - return &block.Header{Nonce: 1} + return &block.Header{Nonce: 2} }, GetGenesisHeaderCalled: func() data.HeaderHandler { return &block.Header{} }, } + // revert final block should not reset probable highest nonce bs, _ := sync.NewShardBootstrap(args) bs.SetNumSyncedWithErrorsForNonce(2, 9) bs.DoJobOnSyncBlockFail(nil, nil, errors.New("error")) + assert.False(t, wasCalled) + + // revert non final block should reset probable highest nonce + bs.SetNumSyncedWithErrorsForNonce(3, 9) + bs.DoJobOnSyncBlockFail(nil, nil, errors.New("error")) assert.True(t, wasCalled) } @@ -2201,13 +2381,7 @@ func TestShardBootstrap_SyncBlockGetNodeDBErrorShouldSync(t *testing.T) { return nil } args.ForkDetector = forkDetector - args.RoundHandler, _ = round.NewRound( - time.Now(), - time.Now().Add(2*100*time.Millisecond), - 100*time.Millisecond, - &mock.SyncTimerMock{}, - 0, - ) + args.RoundHandler, _ = round.NewRound(createDefaultRoundArgs()) syncCalled := false args.AccountsDBSyncer = &mock.AccountsDBSyncerStub{ @@ -2263,12 +2437,7 @@ func TestShardBootstrap_SyncBlock_WithEquivalentProofs(t *testing.T) { return nil } args.ForkDetector = forkDetector - args.RoundHandler, _ = round.NewRound(time.Now(), - time.Now().Add(2*100*time.Millisecond), - 100*time.Millisecond, - &mock.SyncTimerMock{}, - 0, - ) + args.RoundHandler, _ = round.NewRound(createDefaultRoundArgs()) args.BlockProcessor = createBlockProcessor(args.ChainHandler) pools := createMockPools() @@ -2321,12 +2490,7 @@ func TestShardBootstrap_SyncBlock_WithEquivalentProofs(t *testing.T) { return nil } args.ForkDetector = forkDetector - args.RoundHandler, _ = round.NewRound(time.Now(), - time.Now().Add(2*100*time.Millisecond), - 100*time.Millisecond, - &mock.SyncTimerMock{}, - 0, - ) + args.RoundHandler, _ = round.NewRound(createDefaultRoundArgs()) args.BlockProcessor = createBlockProcessor(args.ChainHandler) pools := createMockPools() @@ -2341,12 +2505,12 @@ func TestShardBootstrap_SyncBlock_WithEquivalentProofs(t *testing.T) { } } - numHeaderCalls := 0 + var numHeaderCalls atomic.Uint64 pools.HeadersCalled = func() dataRetriever.HeadersPool { sds := &mock.HeadersCacherStub{} sds.GetHeaderByNonceAndShardIdCalled = func(hdrNonce uint64, shardId uint32) (handlers []data.HeaderHandler, i [][]byte, e error) { - if numHeaderCalls == 0 { - numHeaderCalls++ + if numHeaderCalls.Load() == 0 { + numHeaderCalls.Add(1) return nil, nil, errors.New("err") } @@ -2423,12 +2587,7 @@ func TestShardBootstrap_SyncBlock_WithEquivalentProofs(t *testing.T) { return hash } args.ForkDetector = forkDetector - args.RoundHandler, _ = round.NewRound(time.Now(), - time.Now().Add(2*100*time.Millisecond), - 100*time.Millisecond, - &mock.SyncTimerMock{}, - 0, - ) + args.RoundHandler, _ = round.NewRound(createDefaultRoundArgs()) args.BlockProcessor = createBlockProcessor(args.ChainHandler) pools := createMockPools() @@ -2439,6 +2598,9 @@ func TestShardBootstrap_SyncBlock_WithEquivalentProofs(t *testing.T) { GetProofCalled: func(shardID uint32, headerHash []byte) (data.HeaderProofHandler, error) { return nil, errors.New("missing proof") }, + GetProofByNonceCalled: func(headerNonce uint64, shardID uint32) (data.HeaderProofHandler, error) { + return nil, errors.New("missing proof") + }, HasProofCalled: func(shardID uint32, headerHash []byte) bool { if numProofCalls == 0 { numProofCalls++ @@ -2500,3 +2662,841 @@ func TestShardBootstrap_NilInnerBootstrapperClose(t *testing.T) { bootstrapper := &sync.ShardBootstrap{} assert.Nil(t, bootstrapper.Close()) } + +func TestShardBootstrap_SyncBlockV3(t *testing.T) { + t.Parallel() + + createSyncBlockV3Args := func() sync.ArgShardBootstrapper { + args := CreateShardBootstrapMockArguments() + + args.EnableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return flag == common.SupernovaRoundFlag + }, + } + + hdr := &block.HeaderV3{ + Nonce: 2, + LastExecutionResult: &block.ExecutionResultInfo{ + NotarizedInRound: 1, + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 1, + }, + }} + args.ChainHandler = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return hdr + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash") + }, + } + + header := &block.HeaderV3{ + Nonce: 3, + Round: 1, + BlockBodyType: block.TxBlock, + LastExecutionResult: &block.ExecutionResultInfo{ + NotarizedInRound: 2, + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 2, + }, + }, + } + args.PoolsHolder = setupPools(headerAndHash{ + header: header, + hash: []byte("aaa"), + }) + + prevHdr := &block.HeaderV3{Nonce: 1} + args.Store = setupStore(args.Marshalizer, prevHdr, nil) + args.ForkDetector = setupForkDetector(3) + + return args + } + + t.Run("should work on the ideal case(subsequent nonces)", func(t *testing.T) { + t.Parallel() + + args := createSyncBlockV3Args() + + verifyBlockProposalCalled := false + commitBlockCalled := false + args.BlockProcessor = &testscommon.BlockProcessorStub{ + VerifyBlockProposalCalled: func(header data.HeaderHandler, body data.BodyHandler, haveTime func() time.Duration) error { + verifyBlockProposalCalled = true + return nil + }, + CommitBlockCalled: func(header data.HeaderHandler, body data.BodyHandler) error { + commitBlockCalled = true + return nil + }, + } + + addToQueueCalled := false + args.ExecutionManager = &processMocks.ExecutionManagerMock{ + AddPairForExecutionCalled: func(pair headersCache.HeaderBodyPair) error { + addToQueueCalled = true + return nil + }, + } + + bs, err := sync.NewShardBootstrap(args) + require.Nil(t, err) + + err = bs.SyncBlock(context.Background()) + assert.Nil(t, err) + assert.True(t, verifyBlockProposalCalled) + assert.True(t, commitBlockCalled) + assert.True(t, addToQueueCalled) + }) + + t.Run("should work and prepare the tx pool with multiple blocks", func(t *testing.T) { + t.Parallel() + + // test details: + // current header nonce: 4 + // fork detector highest nonce: 5 -> will sync nonce 5 + // current header holds last execution result with nonce 2 + // so when syncing header 5, we expect to prepare the pool + // with nonces 3, 4 (backfill via backward hash-walk) + nonce 5 (synced block) + args := createSyncBlockV3Args() + + hash1 := []byte("hash1") + hash2 := []byte("hash2") + hash3 := []byte("hash3") + hash4 := []byte("hash4") + hash5 := []byte("hash5") + + header2 := &block.HeaderV3{ + Nonce: 2, + PrevHash: hash1, + BlockBodyType: block.TxBlock, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 1, + HeaderHash: hash1, + }, + }, + } + header3 := &block.HeaderV3{ + Nonce: 3, + PrevHash: hash2, + BlockBodyType: block.TxBlock, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 2, + HeaderHash: hash2, + }, + }, + } + header4 := &block.HeaderV3{ + Nonce: 4, + PrevHash: hash3, + BlockBodyType: block.TxBlock, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 2, + HeaderHash: hash2, + }, + }, + } + header5 := &block.HeaderV3{ + Nonce: 5, + PrevHash: hash4, + BlockBodyType: block.TxBlock, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 4, + HeaderHash: hash4, + }, + }, + } + args.PoolsHolder = setupPoolsDirectHashMapping( + headerAndHash{header: header2, hash: hash2}, + headerAndHash{header: header3, hash: hash3}, + headerAndHash{header: header4, hash: hash4}, + headerAndHash{header: header5, hash: hash5}, + ) + args.ChainHandler = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header4 // forcing to sync nonce 5 + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return hash4 + }, + } + + args.Store = setupStore(args.Marshalizer, header2, nil) + args.ForkDetector = setupForkDetector(5) + + verifyBlockProposalCalled := false + commitBlockCalled := false + args.BlockProcessor = &testscommon.BlockProcessorStub{ + VerifyBlockProposalCalled: func(header data.HeaderHandler, body data.BodyHandler, haveTime func() time.Duration) error { + verifyBlockProposalCalled = true + return nil + }, + CommitBlockCalled: func(header data.HeaderHandler, body data.BodyHandler) error { + commitBlockCalled = true + return nil + }, + } + + cntAddToQueue := 0 + args.ExecutionManager = &processMocks.ExecutionManagerMock{ + AddPairForExecutionCalled: func(pair headersCache.HeaderBodyPair) error { + cntAddToQueue++ + return nil + }, + } + + bs, err := sync.NewShardBootstrap(args) + require.Nil(t, err) + + err = bs.SyncBlock(context.Background()) + assert.Nil(t, err) + assert.True(t, verifyBlockProposalCalled) + assert.True(t, commitBlockCalled) + assert.Equal(t, 3, cntAddToQueue) // two backfilled (nonces 3,4) + one synced (nonce 5) + }) + + t.Run("should error when GetPrevBlockLastExecutionResult fails", func(t *testing.T) { + t.Parallel() + + args := createSyncBlockV3Args() + chainHandler, ok := args.ChainHandler.(*testscommon.ChainHandlerStub) + require.True(t, ok) + chainHandler.GetCurrentBlockHeaderHashCalled = func() []byte { + return nil + } + + bs, err := sync.NewShardBootstrap(args) + require.Nil(t, err) + + err = bs.SyncBlock(context.Background()) + assert.Error(t, err) + }) + + t.Run("should error when VerifyBlockProposal fails", func(t *testing.T) { + t.Parallel() + + args := createSyncBlockV3Args() + blockProcessor := &testscommon.BlockProcessorStub{ + VerifyBlockProposalCalled: func(header data.HeaderHandler, body data.BodyHandler, haveTime func() time.Duration) error { + return errExpected + }, + } + args.BlockProcessor = blockProcessor + + bs, err := sync.NewShardBootstrap(args) + require.Nil(t, err) + + err = bs.SyncBlock(context.Background()) + assert.Equal(t, errExpected, err) + }) + + t.Run("should error when OnExecutedBlock fails on the ideal case", func(t *testing.T) { + t.Parallel() + + args := createSyncBlockV3Args() + poolsStub, ok := args.PoolsHolder.(*dataRetrieverMock.PoolsHolderStub) + require.True(t, ok) + resetTrackerCalled := false + poolsStub.TransactionsCalled = func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + OnExecutedBlockCalled: func(blockHeader data.HeaderHandler, rootHash []byte) error { + return errExpected + }, + ResetTrackerCalled: func() { + resetTrackerCalled = true + }, + } + } + args.PoolsHolder = poolsStub + + bs, err := sync.NewShardBootstrap(args) + require.Nil(t, err) + + err = bs.SyncBlock(context.Background()) + assert.Equal(t, errExpected, err) + assert.True(t, resetTrackerCalled) + }) + + t.Run("should error when OnProposedBlock fails on the ideal case", func(t *testing.T) { + t.Parallel() + + args := createSyncBlockV3Args() + blockProcessor := &testscommon.BlockProcessorStub{ + OnBackfilledBlockCalled: func(proposedBody data.BodyHandler, proposedHeader data.HeaderHandler, proposedHash []byte) error { + return errExpected + }, + } + args.BlockProcessor = blockProcessor + + bs, err := sync.NewShardBootstrap(args) + require.Nil(t, err) + + err = bs.SyncBlock(context.Background()) + assert.Equal(t, errExpected, err) + }) + + t.Run("should error when OnExecutedBlock fails on the bigger gap case", func(t *testing.T) { + t.Parallel() + + // test details: + // current header nonce: 4 + // fork detector highest nonce: 5 -> will sync nonce 5 + // current header holds last execution result with nonce 2 + args := createSyncBlockV3Args() + header2 := &block.HeaderV3{ + Nonce: 2, + BlockBodyType: block.TxBlock, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 1, + HeaderHash: []byte("hash1"), + }, + }, + } + header4 := &block.HeaderV3{ + Nonce: 4, + BlockBodyType: block.TxBlock, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 2, + HeaderHash: []byte("hash3"), + }, + }, + } + header5 := &block.HeaderV3{ + Nonce: 5, + BlockBodyType: block.TxBlock, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 4, + HeaderHash: []byte("hash4"), + }, + }, + } + args.PoolsHolder = setupPools( + headerAndHash{ + header: header2, + hash: []byte("hash2"), + }, + headerAndHash{ + header: header4, + hash: []byte("hash4"), + }, + headerAndHash{ + header: header5, + hash: []byte("hash5"), + }, + ) + poolsStub, ok := args.PoolsHolder.(*dataRetrieverMock.PoolsHolderStub) + require.True(t, ok) + resetTrackerCalled := false + poolsStub.TransactionsCalled = func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + OnExecutedBlockCalled: func(blockHeader data.HeaderHandler, rootHash []byte) error { + return errExpected + }, + ResetTrackerCalled: func() { + resetTrackerCalled = true + }, + } + } + args.PoolsHolder = poolsStub + args.ChainHandler = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header4 // forcing to sync nonce 5 + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash4") + }, + } + + args.Store = setupStore(args.Marshalizer, header2, nil) + args.ForkDetector = setupForkDetector(5) + + args.ExecutionManager = &processMocks.ExecutionManagerMock{ + AddPairForExecutionCalled: func(pair headersCache.HeaderBodyPair) error { + return errExpected + }, + } + + bs, err := sync.NewShardBootstrap(args) + require.Nil(t, err) + + err = bs.SyncBlock(context.Background()) + assert.Equal(t, errExpected, err) + assert.True(t, resetTrackerCalled) + }) + + t.Run("should error when AddOrReplace fails on the bigger gap case", func(t *testing.T) { + t.Parallel() + + // test details: + // current header nonce: 4 + // fork detector highest nonce: 5 -> will sync nonce 5 + // current header holds last execution result with nonce 2 + args := createSyncBlockV3Args() + + hash1 := []byte("hash1") + hash2 := []byte("hash2") + hash3 := []byte("hash3") + hash4 := []byte("hash4") + hash5 := []byte("hash5") + + header2 := &block.HeaderV3{ + Nonce: 2, + PrevHash: hash1, + BlockBodyType: block.TxBlock, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 1, + HeaderHash: hash1, + }, + }, + } + header3 := &block.HeaderV3{ + Nonce: 3, + PrevHash: hash2, + BlockBodyType: block.TxBlock, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 2, + HeaderHash: hash2, + }, + }, + } + header4 := &block.HeaderV3{ + Nonce: 4, + PrevHash: hash3, + BlockBodyType: block.TxBlock, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 2, + HeaderHash: hash2, + }, + }, + } + header5 := &block.HeaderV3{ + Nonce: 5, + PrevHash: hash4, + BlockBodyType: block.TxBlock, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 4, + HeaderHash: hash4, + }, + }, + } + args.PoolsHolder = setupPoolsDirectHashMapping( + headerAndHash{header: header2, hash: hash2}, + headerAndHash{header: header3, hash: hash3}, + headerAndHash{header: header4, hash: hash4}, + headerAndHash{header: header5, hash: hash5}, + ) + args.ChainHandler = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header4 // forcing to sync nonce 5 + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return hash4 + }, + } + + args.Store = setupStore(args.Marshalizer, header2, nil) + args.ForkDetector = setupForkDetector(5) + + args.ExecutionManager = &processMocks.ExecutionManagerMock{ + AddPairForExecutionCalled: func(pair headersCache.HeaderBodyPair) error { + return errExpected + }, + } + + bs, err := sync.NewShardBootstrap(args) + require.Nil(t, err) + + err = bs.SyncBlock(context.Background()) + assert.Equal(t, errExpected, err) + }) + + t.Run("should error when OnProposedBlock fails on the bigger gap case", func(t *testing.T) { + t.Parallel() + + // test details: + // current header nonce: 4 + // fork detector highest nonce: 5 -> will sync nonce 5 + // current header holds last execution result with nonce 2 + args := createSyncBlockV3Args() + + hash1 := []byte("hash1") + hash2 := []byte("hash2") + hash3 := []byte("hash3") + hash4 := []byte("hash4") + hash5 := []byte("hash5") + + header2 := &block.HeaderV3{ + Nonce: 2, + PrevHash: hash1, + BlockBodyType: block.TxBlock, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 1, + HeaderHash: hash1, + }, + }, + } + header3 := &block.HeaderV3{ + Nonce: 3, + PrevHash: hash2, + BlockBodyType: block.TxBlock, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 2, + HeaderHash: hash2, + }, + }, + } + header4 := &block.HeaderV3{ + Nonce: 4, + PrevHash: hash3, + BlockBodyType: block.TxBlock, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 2, + HeaderHash: hash2, + }, + }, + } + header5 := &block.HeaderV3{ + Nonce: 5, + PrevHash: hash4, + BlockBodyType: block.TxBlock, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 4, + HeaderHash: hash4, + }, + }, + } + args.PoolsHolder = setupPoolsDirectHashMapping( + headerAndHash{header: header2, hash: hash2}, + headerAndHash{header: header3, hash: hash3}, + headerAndHash{header: header4, hash: hash4}, + headerAndHash{header: header5, hash: hash5}, + ) + blockProcessor := &testscommon.BlockProcessorStub{ + OnBackfilledBlockCalled: func(proposedBody data.BodyHandler, proposedHeader data.HeaderHandler, proposedHash []byte) error { + return errExpected + }, + } + args.BlockProcessor = blockProcessor + args.ChainHandler = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header4 // forcing to sync nonce 5 + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return hash4 + }, + } + + args.Store = setupStore(args.Marshalizer, header2, nil) + args.ForkDetector = setupForkDetector(5) + + bs, err := sync.NewShardBootstrap(args) + require.Nil(t, err) + + err = bs.SyncBlock(context.Background()) + assert.Equal(t, errExpected, err) + }) + + t.Run("should error when CommitBlock fails", func(t *testing.T) { + t.Parallel() + + args := createSyncBlockV3Args() + blockProcessor := &testscommon.BlockProcessorStub{ + VerifyBlockProposalCalled: func(header data.HeaderHandler, body data.BodyHandler, haveTime func() time.Duration) error { + return nil + }, + CommitBlockCalled: func(header data.HeaderHandler, body data.BodyHandler) error { + return errExpected + }, + } + args.BlockProcessor = blockProcessor + + bs, err := sync.NewShardBootstrap(args) + require.Nil(t, err) + + err = bs.SyncBlock(context.Background()) + assert.Equal(t, errExpected, err) + }) + + t.Run("should return early when node is synchronized", func(t *testing.T) { + t.Parallel() + + args := createSyncBlockV3Args() + + hdr := block.HeaderV3{ + Nonce: 2, + LastExecutionResult: &block.ExecutionResultInfo{ + NotarizedInRound: 1, + ExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 1, + }, + }} + blkc := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &hdr + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash") + }, + } + args.ChainHandler = blkc + + args.ForkDetector = setupForkDetector(hdr.Nonce) // synced + + bs, err := sync.NewShardBootstrap(args) + require.Nil(t, err) + + err = bs.SyncBlock(context.Background()) + assert.Nil(t, err) + }) + + t.Run("should error when last execution result is nil", func(t *testing.T) { + t.Parallel() + + args := createSyncBlockV3Args() + + hdr := testscommon.HeaderHandlerStub{ + IsHeaderV3Called: func() bool { + return true + }, + GetLastExecutionResultHandlerCalled: func() data.LastExecutionResultHandler { + return nil + }, + } + blkc := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &hdr + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash") + }, + } + args.ChainHandler = blkc + + hash := []byte("aaa") + header := &block.HeaderV3{ + Nonce: 2, + } + + pools := dataRetrieverMock.NewPoolsHolderStub() + pools.HeadersCalled = func() dataRetriever.HeadersPool { + sds := &mock.HeadersCacherStub{} + sds.GetHeaderByNonceAndShardIdCalled = func(hdrNonce uint64, shardId uint32) (handlers []data.HeaderHandler, i [][]byte, e error) { + if hdrNonce == header.Nonce { + return []data.HeaderHandler{header}, [][]byte{hash}, nil + } + return nil, nil, errors.New("err") + } + + return sds + } + pools.MiniBlocksCalled = func() storage.Cacher { + cs := cache.NewCacherStub() + cs.RegisterHandlerCalled = func(i func(key []byte, value interface{})) {} + cs.GetCalled = func(key []byte) (value interface{}, ok bool) { + return make(block.MiniBlockSlice, 0), true + } + + return cs + } + pools.TransactionsCalled = func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + OnExecutedBlockCalled: func(header data.HeaderHandler, rootHash []byte) error { + return nil + }, + } + } + pools.ProofsCalled = func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{} + } + args.PoolsHolder = pools + + bs, err := sync.NewShardBootstrap(args) + require.Nil(t, err) + + err = bs.SyncBlock(context.Background()) + assert.Equal(t, process.ErrNilLastExecutionResultHandler, err) + }) + + t.Run("should error when AddOrReplace to queue fails", func(t *testing.T) { + t.Parallel() + + args := createSyncBlockV3Args() + args.ExecutionManager = &processMocks.ExecutionManagerMock{ + AddPairForExecutionCalled: func(pair headersCache.HeaderBodyPair) error { + return errExpected + }, + } + + bs, err := sync.NewShardBootstrap(args) + require.Nil(t, err) + + err = bs.SyncBlock(context.Background()) + assert.Equal(t, errExpected, err) + }) + + t.Run("should error when getNextHeaderRequestingIfMissing fails", func(t *testing.T) { + t.Parallel() + + args := createSyncBlockV3Args() + + prevHdr := &block.HeaderV3{Nonce: 1} + args.Store = setupStore(args.Marshalizer, prevHdr, nil) + + // Setup pools that don't have the next header + pools := dataRetrieverMock.NewPoolsHolderStub() + pools.HeadersCalled = func() dataRetriever.HeadersPool { + return &mock.HeadersCacherStub{ + GetHeaderByNonceAndShardIdCalled: func(hdrNonce uint64, shardId uint32) ([]data.HeaderHandler, [][]byte, error) { + return nil, nil, errors.New("header not found") + }, + } + } + pools.MiniBlocksCalled = func() storage.Cacher { + cs := cache.NewCacherStub() + cs.RegisterHandlerCalled = func(i func(key []byte, value interface{})) {} + return cs + } + pools.TransactionsCalled = func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{} + } + pools.ProofsCalled = func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{} + } + args.PoolsHolder = pools + + bs, err := sync.NewShardBootstrap(args) + require.Nil(t, err) + + err = bs.SyncBlock(context.Background()) + assert.Equal(t, process.ErrTimeIsOut, err) + }) +} + +func TestShardBootstrap_SyncBlockLegacy(t *testing.T) { + t.Parallel() + + createSyncBlockLegacyArgs := func() sync.ArgShardBootstrapper { + args := CreateShardBootstrapMockArguments() + + currentHdr := &block.Header{ + Nonce: 2, + RootHash: []byte("currentRootHash"), + } + args.ChainHandler = &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return currentHdr + }, + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("currentHash") + }, + GetCurrentBlockRootHashCalled: func() []byte { + return []byte("currentRootHash") + }, + } + + // Header to sync (non-V3) + header := &block.Header{ + Nonce: 3, + Round: 1, + RootHash: []byte("rootHash"), + } + args.PoolsHolder = setupPools(headerAndHash{ + header: header, + hash: []byte("aaa"), + }) + + args.ForkDetector = setupForkDetector(3) + + return args + } + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + args := createSyncBlockLegacyArgs() + + processBlockCalled := false + processScheduledBlockCalled := false + commitBlockCalled := false + args.BlockProcessor = &testscommon.BlockProcessorStub{ + ProcessBlockCalled: func(header data.HeaderHandler, body data.BodyHandler, haveTime func() time.Duration) error { + processBlockCalled = true + return nil + }, + ProcessScheduledBlockCalled: func(header data.HeaderHandler, body data.BodyHandler, haveTime func() time.Duration) error { + processScheduledBlockCalled = true + return nil + }, + CommitBlockCalled: func(header data.HeaderHandler, body data.BodyHandler) error { + commitBlockCalled = true + return nil + }, + } + poolsStub, ok := args.PoolsHolder.(*dataRetrieverMock.PoolsHolderStub) + require.True(t, ok) + onExecutedBlockCalled := false + poolsStub.TransactionsCalled = func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + OnExecutedBlockCalled: func(header data.HeaderHandler, rootHash []byte) error { + onExecutedBlockCalled = true + return nil + }, + } + } + args.PoolsHolder = poolsStub + + bs, err := sync.NewShardBootstrap(args) + require.Nil(t, err) + + err = bs.SyncBlock(context.Background()) + assert.Nil(t, err) + assert.True(t, processBlockCalled) + assert.True(t, processScheduledBlockCalled) + assert.True(t, commitBlockCalled) + assert.True(t, onExecutedBlockCalled) + + // coverage only. should not prepare again + err = bs.SyncBlock(context.Background()) + assert.Nil(t, err) + }) + + t.Run("should error when OnExecutedBlock fails", func(t *testing.T) { + t.Parallel() + + args := createSyncBlockLegacyArgs() + + poolsStub, ok := args.PoolsHolder.(*dataRetrieverMock.PoolsHolderStub) + require.True(t, ok) + poolsStub.TransactionsCalled = func() dataRetriever.ShardedDataCacherNotifier { + return &testscommon.ShardedDataStub{ + OnExecutedBlockCalled: func(header data.HeaderHandler, rootHash []byte) error { + return errExpected + }, + } + } + args.PoolsHolder = poolsStub + + bs, err := sync.NewShardBootstrap(args) + require.Nil(t, err) + + err = bs.SyncBlock(context.Background()) + assert.Equal(t, errExpected, err) + }) +} diff --git a/process/sync/storageBootstrap/baseStorageBootstrapper.go b/process/sync/storageBootstrap/baseStorageBootstrapper.go index d42a9456f3d..54249239120 100644 --- a/process/sync/storageBootstrap/baseStorageBootstrapper.go +++ b/process/sync/storageBootstrap/baseStorageBootstrapper.go @@ -1,6 +1,7 @@ package storageBootstrap import ( + "errors" "fmt" "github.com/multiversx/mx-chain-core-go/core" @@ -24,6 +25,8 @@ import ( var log = logger.GetOrCreate("process/sync") +var errExecutionResultNotFound = errors.New("execution result not found in referenced execution results on header") + const maxNumOfConsecutiveNoncesNotFoundAccepted = 10 // ArgsBaseStorageBootstrapper is structure used to create a new storage bootstrapper @@ -48,6 +51,7 @@ type ArgsBaseStorageBootstrapper struct { AppStatusHandler core.AppStatusHandler EnableEpochsHandler common.EnableEpochsHandler ProofsPool process.ProofsPool + ExecutionManager process.ExecutionManager } // ArgsShardStorageBootstrapper is structure used to create a new storage bootstrapper for shard @@ -85,6 +89,7 @@ type storageBootstrapper struct { appStatusHandler core.AppStatusHandler enableEpochsHandler common.EnableEpochsHandler proofsPool process.ProofsPool + executionManager process.ExecutionManager } func (st *storageBootstrapper) loadBlocks() error { @@ -123,13 +128,21 @@ func (st *storageBootstrapper) loadBlocks() error { break } - storageHeadersInfo = append(storageHeadersInfo, headerInfo) - if uint64(round) > st.bootstrapRoundIndex { + log.Debug("loadBlocks: removing round info", + "round", round, + "last header nonce", headerInfo.LastHeader.GetNonce(), + "lastRound", headerInfo.GetLastRound(), + ) + + st.cleanupStorage(headerInfo.LastHeader) + st.bootstrapper.cleanupNotarizedStorage(headerInfo.LastHeader.Hash) round = headerInfo.LastRound continue } + storageHeadersInfo = append(storageHeadersInfo, headerInfo) + _, numHdrs := metricsLoader.UpdateMetricsFromStorage(st.store, st.uint64Converter, st.marshalizer, st.appStatusHandler, headerInfo.LastHeader.Nonce) st.blkExecutor.SetNumProcessedObj(numHdrs) @@ -279,12 +292,11 @@ func (st *storageBootstrapper) applyHeaderInfo(hdrInfo bootstrapStorage.Bootstra return process.ErrInvalidChainID } - rootHash := headerFromStorage.GetRootHash() - scheduledRootHash, err := st.scheduledTxsExecutionHandler.GetScheduledRootHashForHeader(headerHash) - if err == nil { - rootHash = scheduledRootHash + rootHash, err := st.getRootHashForBlock(headerFromStorage, headerHash) + if err != nil { + log.Debug("cannot get rootHash for header", "nonce", headerFromStorage.GetNonce(), "error", err.Error()) + return err } - log.Debug("storageBootstrapper.applyHeaderInfo", "rootHash", rootHash, "scheduledRootHash", scheduledRootHash) err = st.blkExecutor.RevertStateToBlock(headerFromStorage, rootHash) if err != nil { @@ -294,19 +306,44 @@ func (st *storageBootstrapper) applyHeaderInfo(hdrInfo bootstrapStorage.Bootstra err = st.applyBlock(headerHash, headerFromStorage, rootHash) if err != nil { - log.Debug("cannot apply block for header ", "nonce", headerFromStorage.GetNonce(), "error", err.Error()) + log.Debug("cannot apply block for header", "nonce", headerFromStorage.GetNonce(), "error", err.Error()) return err } err = st.getAndApplyProofForHeader(headerHash, headerFromStorage) if err != nil { - log.Debug("cannot apply proof for header ", "nonce", headerFromStorage.GetNonce(), "error", err.Error()) + log.Debug("cannot apply proof for header", "nonce", headerFromStorage.GetNonce(), "error", err.Error()) return err } return nil } +func (st *storageBootstrapper) getRootHashForBlock( + header data.HeaderHandler, + headerHash []byte, +) ([]byte, error) { + if header.IsHeaderV3() { + lastExecutionResult, err := common.ExtractBaseExecutionResultHandler(header.GetLastExecutionResultHandler()) + if err != nil { + return nil, err + } + rootHash := lastExecutionResult.GetRootHash() + log.Debug("storageBootstrapper.getRootHashForBlock", "rootHash", rootHash) + + return lastExecutionResult.GetRootHash(), nil + } + + rootHash := header.GetRootHash() + scheduledRootHash, err := st.scheduledTxsExecutionHandler.GetScheduledRootHashForHeader(headerHash) + if err == nil { + rootHash = scheduledRootHash + } + log.Debug("storageBootstrapper.getRootHashForBlock", "rootHash", rootHash, "scheduledRootHash", scheduledRootHash) + + return rootHash, nil +} + func (st *storageBootstrapper) getBootInfos(hdrInfo bootstrapStorage.BootstrapData) ([]bootstrapStorage.BootstrapData, error) { highestFinalBlockNonce := hdrInfo.HighestFinalBlockNonce highestBlockNonce := hdrInfo.LastHeader.Nonce @@ -457,13 +494,11 @@ func (st *storageBootstrapper) cleanupStorage(headerInfo bootstrapStorage.Bootst } func (st *storageBootstrapper) applyBlock(headerHash []byte, header data.HeaderHandler, rootHash []byte) error { - err := st.blkc.SetCurrentBlockHeaderAndRootHash(header, rootHash) + err := st.setCurrentBlockInfo(header, headerHash, rootHash) if err != nil { return err } - st.blkc.SetCurrentBlockHeaderHash(headerHash) - if !st.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, header.GetEpoch()) { return nil } @@ -479,6 +514,132 @@ func (st *storageBootstrapper) applyBlock(headerHash []byte, header data.HeaderH return nil } +func (st *storageBootstrapper) setCurrentBlockInfo( + header data.HeaderHandler, + headerHash []byte, + rootHash []byte, +) error { + if header.IsHeaderV3() { + // tx pool context is already handled on sync flow + + return st.setCurrentBlockInfoV3(header, headerHash) + } + + err := st.blkExecutor.OnExecutedBlock(header, rootHash) + if err != nil { + return err + } + + err = st.blkc.SetCurrentBlockHeaderAndRootHash(header, rootHash) + if err != nil { + return err + } + + st.blkc.SetCurrentBlockHeaderHash(headerHash) + + // set also last executed block info and header + // this will be useful at transition to Supernova with headers v3 + st.blkc.SetLastExecutedBlockHeaderAndRootHash(header, headerHash, rootHash) + + lastExecResHandler, err := common.GetOrCreateLastExecutionResultForPrevHeader(header, headerHash) + if err != nil { + return err + } + + return st.executionManager.SetLastNotarizedResult(lastExecResHandler) +} + +func (st *storageBootstrapper) setCurrentBlockInfoV3( + header data.HeaderHandler, + headerHash []byte, +) error { + lastBaseExecutionResult, err := common.ExtractBaseExecutionResultHandler(header.GetLastExecutionResultHandler()) + if err != nil { + return err + } + + // at this point, last executed header reference by current header should be available in storage + // it was synced in epoch start bootstrap for header v3 + lastExecutedHeader, err := st.bootstrapper.getHeader(lastBaseExecutionResult.GetHeaderHash()) + if err != nil { + log.Debug("storageBootstrapper: cannot get header from storage", "nonce", lastBaseExecutionResult.GetHeaderNonce, "error", err.Error()) + return err + } + + st.blkc.SetLastExecutedBlockHeaderAndRootHash(lastExecutedHeader, lastBaseExecutionResult.GetHeaderHash(), lastBaseExecutionResult.GetRootHash()) + + st.blkc.SetCurrentBlockHeaderHash(headerHash) + err = st.blkc.SetCurrentBlockHeader(header) + if err != nil { + return err + } + + // when setting execution result it should be the full structure, not base execution results + lastExecutionResult, err := st.getLastExecutionResult(header, lastBaseExecutionResult.GetHeaderNonce()) + if err != nil { + return err + } + + err = st.executionManager.SetLastNotarizedResult(lastExecutionResult) + if err != nil { + return err + } + + st.blkc.SetLastExecutionResult(lastExecutionResult) + + return nil +} + +func (st *storageBootstrapper) getLastExecutionResult( + header data.HeaderHandler, + lastExecutedNonce uint64, +) (data.BaseExecutionResultHandler, error) { + execResult, found := findExecutionResultOnHeader(header, lastExecutedNonce) + if found { + return execResult, nil + } + + // if last execution result not found in execution results referenced by current block + // we iterate in reverse to find full last execution result (in last execution result + // there is only base execution result info) + + // between current header and last executed header (the header which proposed the + // last execution result that we need here) we should be able to find the full execution result + + hdrHash := header.GetPrevHash() + hdrNonce := header.GetNonce() - 1 + + for hdrNonce > lastExecutedNonce { + hdr, err := st.bootstrapper.getHeader(hdrHash) + if err != nil { + return nil, err + } + + execResult, found := findExecutionResultOnHeader(hdr, lastExecutedNonce) + if found { + return execResult, nil + } + + hdrHash = hdr.GetPrevHash() + hdrNonce = hdr.GetNonce() + } + + return nil, errExecutionResultNotFound +} + +func findExecutionResultOnHeader( + header data.HeaderHandler, + lastExecutedNonce uint64, +) (data.BaseExecutionResultHandler, bool) { + for _, execResult := range header.GetExecutionResultsHandlers() { + if execResult.GetHeaderNonce() == lastExecutedNonce { + return execResult, true + } + } + + return nil, false +} + func (st *storageBootstrapper) getAndApplyProofForHeader(headerHash []byte, header data.HeaderHandler) error { if !st.enableEpochsHandler.IsFlagEnabledInEpoch(common.AndromedaFlag, header.GetEpoch()) { return nil @@ -518,6 +679,8 @@ func (st *storageBootstrapper) restoreBlockChainToGenesis() { } st.blkc.SetCurrentBlockHeaderHash(nil) + // keep last executed block header in sync when reverting to genesis + st.blkc.SetLastExecutedBlockHeaderAndRootHash(nil, nil, nil) } func checkBaseStorageBootstrapperArguments(args ArgsBaseStorageBootstrapper) error { @@ -575,6 +738,9 @@ func checkBaseStorageBootstrapperArguments(args ArgsBaseStorageBootstrapper) err if check.IfNil(args.ProofsPool) { return process.ErrNilProofsPool } + if check.IfNil(args.ExecutionManager) { + return process.ErrNilExecutionManager + } return nil } diff --git a/process/sync/storageBootstrap/baseStorageBootstrapper_test.go b/process/sync/storageBootstrap/baseStorageBootstrapper_test.go index 1a4e1d9c3ad..2165f92e7a8 100644 --- a/process/sync/storageBootstrap/baseStorageBootstrapper_test.go +++ b/process/sync/storageBootstrap/baseStorageBootstrapper_test.go @@ -7,20 +7,22 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" - dataRetrieverMocks "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" - "github.com/stretchr/testify/assert" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/mock" "github.com/multiversx/mx-chain-go/storage" "github.com/multiversx/mx-chain-go/testscommon" + dataRetrieverMocks "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" epochNotifierMock "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" "github.com/multiversx/mx-chain-go/testscommon/genericMocks" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" "github.com/multiversx/mx-chain-go/testscommon/statusHandler" storageStubs "github.com/multiversx/mx-chain-go/testscommon/storage" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func createMockShardStorageBootstrapperArgs() ArgsBaseStorageBootstrapper { @@ -49,6 +51,7 @@ func createMockShardStorageBootstrapperArgs() ArgsBaseStorageBootstrapper { AppStatusHandler: &statusHandler.AppStatusHandlerMock{}, EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, ProofsPool: &dataRetrieverMocks.ProofsPoolMock{}, + ExecutionManager: &processMocks.ExecutionManagerMock{}, } return argsBaseBootstrapper @@ -201,6 +204,15 @@ func TestBaseStorageBootstrapper_CheckBaseStorageBootstrapperArguments(t *testin err := checkBaseStorageBootstrapperArguments(args) assert.Equal(t, process.ErrNilAppStatusHandler, err) }) + t.Run("nil execution manager - should error", func(t *testing.T) { + t.Parallel() + + args := createMockShardStorageBootstrapperArgs() + args.ExecutionManager = nil + + err := checkBaseStorageBootstrapperArguments(args) + assert.Equal(t, process.ErrNilExecutionManager, err) + }) } func TestBaseStorageBootstrapper_RestoreBlockBodyIntoPoolsShouldErrMissingHeader(t *testing.T) { @@ -390,3 +402,112 @@ func TestBaseStorageBootstrapper_GetBlockBodyShouldWork(t *testing.T) { assert.Nil(t, err) assert.Equal(t, expectedBody, body) } + +func TestBaseStorageBootstrapper_setCurrentBlockInfoV3(t *testing.T) { + t.Parallel() + + t.Run("in case of nil LastExecutionResult should fail", func(t *testing.T) { + t.Parallel() + + baseArgs := createMockShardStorageBootstrapperArgs() + args := ArgsShardStorageBootstrapper{ + ArgsBaseStorageBootstrapper: baseArgs, + } + + ssb, _ := NewShardStorageBootstrapper(args) + err := ssb.setCurrentBlockInfoV3(&block.HeaderV3{ + LastExecutionResult: nil, + }, nil) + + require.Equal(t, process.ErrNilLastExecutionResultHandler, err) + }) + + t.Run("if getting the header fails, the error should be propagated", func(t *testing.T) { + t.Parallel() + + errExpected := errors.New("expected error") + baseArgs := createMockShardStorageBootstrapperArgs() + baseArgs.Marshalizer = &testscommon.MarshallerStub{ + UnmarshalCalled: func(obj interface{}, buff []byte) error { + return errExpected + }, + } + args := ArgsShardStorageBootstrapper{ + ArgsBaseStorageBootstrapper: baseArgs, + } + + ssb, _ := NewShardStorageBootstrapper(args) + err := ssb.setCurrentBlockInfoV3(&block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hashExecResult"), + }, + }, + }, []byte("hash")) + + require.Equal(t, process.ErrUnmarshalWithoutSuccess, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + baseArgs := createMockShardStorageBootstrapperArgs() + + args := ArgsShardStorageBootstrapper{ + ArgsBaseStorageBootstrapper: baseArgs, + } + + args.Store = &storageStubs.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageStubs.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + header := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hashExecResult"), + }, + }, + ExecutionResults: []*block.ExecutionResult{ + { + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderNonce: 10, + HeaderHash: []byte("hashExecResult"), + }, + }, + }, + } + headerBytes, _ := baseArgs.Marshalizer.Marshal(header) + + return headerBytes, nil + }, + }, nil + }, + } + + counter := 0 + ssb, _ := NewShardStorageBootstrapper(args) + ssb.blkc = &testscommon.ChainHandlerStub{ + SetLastExecutedBlockHeaderAndRootHashCalled: func(header data.HeaderHandler, blockHash []byte, rootHash []byte) { + counter += 1 + }, + SetCurrentBlockHeaderHashCalled: func(bytes []byte) { + counter += 1 + }, + SetCurrentBlockHeaderCalled: func(header data.HeaderHandler) error { + counter += 1 + return nil + }, + } + err := ssb.setCurrentBlockInfoV3(&block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hashExecResult"), + HeaderNonce: 10, + }, + }, + }, []byte("hash")) + + require.Nil(t, err) + require.Equal(t, 3, counter) + }) +} diff --git a/process/sync/storageBootstrap/metaStorageBootstrapper.go b/process/sync/storageBootstrap/metaStorageBootstrapper.go index c236018229f..64dc475562b 100644 --- a/process/sync/storageBootstrap/metaStorageBootstrapper.go +++ b/process/sync/storageBootstrap/metaStorageBootstrapper.go @@ -44,6 +44,7 @@ func NewMetaStorageBootstrapper(arguments ArgsMetaStorageBootstrapper) (*metaSto appStatusHandler: arguments.AppStatusHandler, enableEpochsHandler: arguments.EnableEpochsHandler, proofsPool: arguments.ProofsPool, + executionManager: arguments.ExecutionManager, } boot := metaStorageBootstrapper{ @@ -113,9 +114,9 @@ func (msb *metaStorageBootstrapper) cleanupNotarizedStorage(metaBlockHash []byte return } - shardHeaderHashes := make([][]byte, len(metaBlock.ShardInfo)) - for i := 0; i < len(metaBlock.ShardInfo); i++ { - shardHeaderHashes[i] = metaBlock.ShardInfo[i].HeaderHash + shardHeaderHashes := make([][]byte, len(metaBlock.GetShardInfoHandlers())) + for i := 0; i < len(metaBlock.GetShardInfoHandlers()); i++ { + shardHeaderHashes[i] = metaBlock.GetShardInfoHandlers()[i].GetHeaderHash() } for _, shardHeaderHash := range shardHeaderHashes { @@ -159,7 +160,6 @@ func (msb *metaStorageBootstrapper) cleanupNotarizedStorageForHigherNoncesIfExis func (msb *metaStorageBootstrapper) applySelfNotarizedHeaders( bootstrapHeadersInfo []bootstrapStorage.BootstrapHeaderInfo, ) ([]data.HeaderHandler, [][]byte, error) { - for _, bootstrapHeaderInfo := range bootstrapHeadersInfo { selfNotarizedHeader, err := msb.getHeader(bootstrapHeaderInfo.Hash) if err != nil { diff --git a/process/sync/storageBootstrap/metaStorageBootstrapper_test.go b/process/sync/storageBootstrap/metaStorageBootstrapper_test.go new file mode 100644 index 00000000000..f967cf1defc --- /dev/null +++ b/process/sync/storageBootstrap/metaStorageBootstrapper_test.go @@ -0,0 +1,266 @@ +package storageBootstrap + +import ( + "bytes" + "errors" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/dataRetriever" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/block/bootstrapStorage" + "github.com/multiversx/mx-chain-go/process/mock" + "github.com/multiversx/mx-chain-go/storage" + "github.com/multiversx/mx-chain-go/testscommon" + dataRetrieverMocks "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" + epochNotifierMock "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" + "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" + "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" + "github.com/multiversx/mx-chain-go/testscommon/statusHandler" + storageMock "github.com/multiversx/mx-chain-go/testscommon/storage" +) + +func TestMetaStorageBootstrapper_LoadFromStorageShouldCleanupRoundsAboveBootstrapRoundIndex(t *testing.T) { + t.Parallel() + + t.Run("multiple rounds above bootstrapRoundIndex are cleaned before valid one", func(t *testing.T) { + t.Parallel() + + marshaller := &marshallerMock.MarshalizerMock{} + + // bootstrapRoundIndex is 97, highest round is 100 + // rounds 100, 99, 98 are above the index and should be cleaned + // round 97 should be processed + bootstrapRoundIdx := uint64(97) + + hdr := &block.MetaBlockV3{ + Nonce: 96, + Round: 96, + ChainID: []byte("1"), + } + hdrHash := []byte("header hash 96") + hdrBytes, _ := marshaller.Marshal(hdr) + + cleanupCount := 0 + savedLastRound := int64(0) + wasCalledBlockchainSetHeader := false + + args := ArgsMetaStorageBootstrapper{ + ArgsBaseStorageBootstrapper: ArgsBaseStorageBootstrapper{ + BootStorer: &mock.BoostrapStorerMock{ + GetHighestRoundCalled: func() int64 { + return 100 + }, + GetCalled: func(round int64) (bootstrapStorage.BootstrapData, error) { + return bootstrapStorage.BootstrapData{ + LastHeader: bootstrapStorage.BootstrapHeaderInfo{ + ShardId: core.MetachainShardId, + Epoch: hdr.GetEpoch(), + Nonce: hdr.GetNonce(), + Hash: hdrHash, + }, + HighestFinalBlockNonce: hdr.GetNonce(), + LastRound: round - 1, + }, nil + }, + SaveLastRoundCalled: func(round int64) error { + savedLastRound = round + return nil + }, + }, + ForkDetector: &mock.ForkDetectorMock{ + AddHeaderCalled: func(header data.HeaderHandler, hash []byte, state process.BlockHeaderState, selfNotarizedHeaders []data.HeaderHandler, selfNotarizedHeadersHashes [][]byte) error { + return nil + }, + }, + BlockProcessor: &testscommon.BlockProcessorStub{}, + ChainHandler: &testscommon.ChainHandlerStub{ + GetGenesisHeaderCalled: func() data.HeaderHandler { + return nil + }, + SetCurrentBlockHeaderAndRootHashCalled: func(header data.HeaderHandler, rootHash []byte) error { + wasCalledBlockchainSetHeader = true + return nil + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + if wasCalledBlockchainSetHeader { + return hdr + } + return nil + }, + }, + Marshalizer: marshaller, + Store: &storageMock.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageMock.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + if bytes.Equal(key, hdrHash) { + return hdrBytes, nil + } + return nil, errors.New("key not found") + }, + RemoveCalled: func(key []byte) error { + cleanupCount++ + return nil + }, + SearchFirstCalled: func(key []byte) ([]byte, error) { + return nil, errors.New("not found") + }, + }, nil + }, + }, + Uint64Converter: testscommon.NewNonceHashConverterMock(), + BootstrapRoundIndex: bootstrapRoundIdx, + ShardCoordinator: testscommon.NewMultiShardsCoordinatorMock(1), + NodesCoordinator: &shardingMocks.NodesCoordinatorMock{}, + EpochStartTrigger: &mock.EpochStartTriggerStub{}, + BlockTracker: &mock.BlockTrackerMock{ + AddTrackedHeaderCalled: func(header data.HeaderHandler, hash []byte) {}, + }, + ChainID: "1", + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + MiniblocksProvider: &mock.MiniBlocksProviderStub{}, + EpochNotifier: &epochNotifierMock.EpochNotifierStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + AppStatusHandler: &statusHandler.AppStatusHandlerMock{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + ProofsPool: &dataRetrieverMocks.ProofsPoolMock{}, + ExecutionManager: &processMocks.ExecutionManagerMock{}, + }, + PendingMiniBlocksHandler: &mock.PendingMiniBlocksHandlerStub{}, + } + + msb, err := NewMetaStorageBootstrapper(args) + require.Nil(t, err) + + err = msb.LoadFromStorage() + assert.Nil(t, err) + assert.Equal(t, int64(97), savedLastRound) + // rounds 100, 99, 98 were above bootstrapRoundIndex=97, each triggers a Remove in cleanupStorage + assert.True(t, cleanupCount >= 3) + assert.True(t, wasCalledBlockchainSetHeader) + }) + + t.Run("bootstrapRoundIndex zero should cleaned all", func(t *testing.T) { + t.Parallel() + + marshaller := &marshallerMock.MarshalizerMock{} + + bootstrapRoundIdx := uint64(0) + + hdr := &block.MetaBlockV3{ + Nonce: 96, + Round: 96, + ChainID: []byte("1"), + } + hdrHash := []byte("header hash 96") + hdrBytes, _ := marshaller.Marshal(hdr) + + cleanupCount := 0 + savedLastRound := int64(0) + wasCalledBlockchainSetHeader := false + + args := ArgsMetaStorageBootstrapper{ + ArgsBaseStorageBootstrapper: ArgsBaseStorageBootstrapper{ + BootStorer: &mock.BoostrapStorerMock{ + GetHighestRoundCalled: func() int64 { + return 100 + }, + GetCalled: func(round int64) (bootstrapStorage.BootstrapData, error) { + return bootstrapStorage.BootstrapData{ + LastHeader: bootstrapStorage.BootstrapHeaderInfo{ + ShardId: core.MetachainShardId, + Epoch: hdr.GetEpoch(), + Nonce: hdr.GetNonce(), + Hash: hdrHash, + }, + HighestFinalBlockNonce: hdr.GetNonce(), + LastRound: round - 1, + }, nil + }, + SaveLastRoundCalled: func(round int64) error { + savedLastRound = round + return nil + }, + }, + ForkDetector: &mock.ForkDetectorMock{ + AddHeaderCalled: func(header data.HeaderHandler, hash []byte, state process.BlockHeaderState, selfNotarizedHeaders []data.HeaderHandler, selfNotarizedHeadersHashes [][]byte) error { + return nil + }, + }, + BlockProcessor: &testscommon.BlockProcessorStub{}, + ChainHandler: &testscommon.ChainHandlerStub{ + GetGenesisHeaderCalled: func() data.HeaderHandler { + return nil + }, + SetCurrentBlockHeaderAndRootHashCalled: func(header data.HeaderHandler, rootHash []byte) error { + wasCalledBlockchainSetHeader = true + return nil + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + if wasCalledBlockchainSetHeader { + return hdr + } + return nil + }, + }, + Marshalizer: marshaller, + Store: &storageMock.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageMock.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + if bytes.Equal(key, hdrHash) { + return hdrBytes, nil + } + return nil, errors.New("key not found") + }, + RemoveCalled: func(key []byte) error { + cleanupCount++ + return nil + }, + SearchFirstCalled: func(key []byte) ([]byte, error) { + return nil, errors.New("not found") + }, + }, nil + }, + }, + Uint64Converter: testscommon.NewNonceHashConverterMock(), + BootstrapRoundIndex: bootstrapRoundIdx, + ShardCoordinator: testscommon.NewMultiShardsCoordinatorMock(1), + NodesCoordinator: &shardingMocks.NodesCoordinatorMock{}, + EpochStartTrigger: &mock.EpochStartTriggerStub{}, + BlockTracker: &mock.BlockTrackerMock{ + AddTrackedHeaderCalled: func(header data.HeaderHandler, hash []byte) {}, + }, + ChainID: "1", + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + MiniblocksProvider: &mock.MiniBlocksProviderStub{}, + EpochNotifier: &epochNotifierMock.EpochNotifierStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + AppStatusHandler: &statusHandler.AppStatusHandlerMock{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + ProofsPool: &dataRetrieverMocks.ProofsPoolMock{}, + ExecutionManager: &processMocks.ExecutionManagerMock{}, + }, + PendingMiniBlocksHandler: &mock.PendingMiniBlocksHandlerStub{}, + } + + msb, err := NewMetaStorageBootstrapper(args) + require.Nil(t, err) + + err = msb.LoadFromStorage() + assert.Nil(t, err) + assert.Equal(t, int64(0), savedLastRound) + + // all rounds info should be cleaned + assert.Equal(t, 100, cleanupCount) + assert.True(t, wasCalledBlockchainSetHeader) + }) +} diff --git a/process/sync/storageBootstrap/metricsLoader/loadPersistentMetrics.go b/process/sync/storageBootstrap/metricsLoader/loadPersistentMetrics.go index 017842d719d..e18621b6f73 100644 --- a/process/sync/storageBootstrap/metricsLoader/loadPersistentMetrics.go +++ b/process/sync/storageBootstrap/metricsLoader/loadPersistentMetrics.go @@ -47,7 +47,7 @@ func getTotalTxsAndHdrs(metrics map[string]uint64) (uint64, uint64) { return numTxs, numHdrs } -// LoadMetricsFromDb will load from storage metrics +// loadMetricsFromDb will load from storage metrics func loadMetricsFromDb(store dataRetriever.StorageService, uint64ByteSliceConverter typeConverters.Uint64ByteSliceConverter, marshalizer marshal.Marshalizer, nonce uint64, ) (map[string]uint64, map[string]string) { nonceBytes := uint64ByteSliceConverter.ToByteSlice(nonce) diff --git a/process/sync/storageBootstrap/shardStorageBootstrapper.go b/process/sync/storageBootstrap/shardStorageBootstrapper.go index ebc8992df05..32a02655fbf 100644 --- a/process/sync/storageBootstrap/shardStorageBootstrapper.go +++ b/process/sync/storageBootstrap/shardStorageBootstrapper.go @@ -3,8 +3,6 @@ package storageBootstrap import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" - "github.com/multiversx/mx-chain-core-go/data/block" - "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/block/bootstrapStorage" @@ -45,6 +43,7 @@ func NewShardStorageBootstrapper(arguments ArgsShardStorageBootstrapper) (*shard appStatusHandler: arguments.AppStatusHandler, enableEpochsHandler: arguments.EnableEpochsHandler, proofsPool: arguments.ProofsPool, + executionManager: arguments.ExecutionManager, } boot := shardStorageBootstrapper{ @@ -119,7 +118,7 @@ func (ssb *shardStorageBootstrapper) cleanupNotarizedStorage(shardHeaderHash []b } for _, metaBlockHash := range shardHeader.GetMetaBlockHashes() { - var metaBlock *block.MetaBlock + var metaBlock data.MetaHeaderHandler metaBlock, err = process.GetMetaHeaderFromStorage(metaBlockHash, ssb.marshalizer, ssb.store) if err != nil { log.Debug("meta block is not found in MetaBlockUnit storage", @@ -187,7 +186,7 @@ func (ssb *shardStorageBootstrapper) cleanupNotarizedStorageForHigherNoncesIfExi } } -func (ssb *shardStorageBootstrapper) removeMetaFromMetaHeaderNonceToHashUnit(metaBlock *block.MetaBlock, metaBlockHash []byte) { +func (ssb *shardStorageBootstrapper) removeMetaFromMetaHeaderNonceToHashUnit(metaBlock data.MetaHeaderHandler, metaBlockHash []byte) { nonceToByteSlice := ssb.uint64Converter.ToByteSlice(metaBlock.GetNonce()) metaHdrNonceHashStorer, err := ssb.store.GetStorer(dataRetriever.MetaHdrNonceHashDataUnit) if err != nil { @@ -207,7 +206,7 @@ func (ssb *shardStorageBootstrapper) removeMetaFromMetaHeaderNonceToHashUnit(met } } -func (ssb *shardStorageBootstrapper) removeMetaFromMetaBlockUnit(metaBlock *block.MetaBlock, metaBlockHash []byte) { +func (ssb *shardStorageBootstrapper) removeMetaFromMetaBlockUnit(metaBlock data.MetaHeaderHandler, metaBlockHash []byte) { metaBlockStorer, err := ssb.store.GetStorer(dataRetriever.MetaBlockUnit) if err != nil { log.Debug("could not get storage unit", diff --git a/process/sync/storageBootstrap/shardStorageBootstrapper_test.go b/process/sync/storageBootstrap/shardStorageBootstrapper_test.go index 8ab7f337e93..19e70309f11 100644 --- a/process/sync/storageBootstrap/shardStorageBootstrapper_test.go +++ b/process/sync/storageBootstrap/shardStorageBootstrapper_test.go @@ -9,6 +9,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" dataRetrieverMocks "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -40,12 +41,14 @@ func TestShardStorageBootstrapper_LoadFromStorageShouldWork(t *testing.T) { marshaller := &marshallerMock.MarshalizerMock{} startRound := 4000 - hdr := &block.Header{ - Nonce: 3999, - Round: 3999, - RootHash: []byte("roothash"), - ShardID: 0, - ChainID: []byte("1"), + hdr := &block.HeaderV2{ + Header: &block.Header{ + Nonce: 3999, + Round: 3999, + RootHash: []byte("roothash"), + ShardID: 0, + ChainID: []byte("1"), + }, } hdrHash := []byte("header hash") hdrBytes, _ := marshaller.Marshal(hdr) @@ -61,9 +64,9 @@ func TestShardStorageBootstrapper_LoadFromStorageShouldWork(t *testing.T) { GetCalled: func(round int64) (bootstrapStorage.BootstrapData, error) { return bootstrapStorage.BootstrapData{ LastHeader: bootstrapStorage.BootstrapHeaderInfo{ - ShardId: hdr.ShardID, - Epoch: hdr.Epoch, - Nonce: hdr.Nonce, + ShardId: hdr.GetShardID(), + Epoch: hdr.GetEpoch(), + Nonce: hdr.GetNonce(), Hash: hdrHash, }, HighestFinalBlockNonce: 3999, @@ -96,7 +99,7 @@ func TestShardStorageBootstrapper_LoadFromStorageShouldWork(t *testing.T) { }, SetCurrentBlockHeaderAndRootHashCalled: func(header data.HeaderHandler, rootHash []byte) error { assert.Equal(t, hdr, header) - assert.Equal(t, hdr.RootHash, rootHash) + assert.Equal(t, hdr.GetRootHash(), rootHash) wasCalledBlockchainSetHeader = true return nil @@ -128,7 +131,7 @@ func TestShardStorageBootstrapper_LoadFromStorageShouldWork(t *testing.T) { wasCalledBlockTrackerAddTrackedHeader = true }, }, - ChainID: string(hdr.ChainID), + ChainID: string(hdr.GetChainID()), ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, MiniblocksProvider: &mock.MiniBlocksProviderStub{}, EpochNotifier: &epochNotifierMock.EpochNotifierStub{ @@ -141,6 +144,7 @@ func TestShardStorageBootstrapper_LoadFromStorageShouldWork(t *testing.T) { AppStatusHandler: &statusHandler.AppStatusHandlerMock{}, EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, ProofsPool: &dataRetrieverMocks.ProofsPoolMock{}, + ExecutionManager: &processMocks.ExecutionManagerMock{}, }, } @@ -258,3 +262,241 @@ func TestShardStorageBootstrapper_GetCrossNotarizedHeaderNonceShouldWork(t *test assert.Nil(t, err) assert.Equal(t, uint64(2), nonce) } + +func TestShardStorageBootstrapper_LoadFromStorageShouldCleanupRoundsAboveBootstrapRoundIndex(t *testing.T) { + t.Parallel() + + t.Run("multiple rounds above bootstrapRoundIndex are cleaned before valid one", func(t *testing.T) { + t.Parallel() + + marshaller := &marshallerMock.MarshalizerMock{} + + // bootstrapRoundIndex is 97, highest round is 100 + // rounds 100, 99, 98 are above the index and should be cleaned + // round 97 should be processed + bootstrapRoundIdx := uint64(97) + + hdr := &block.HeaderV3{ + Nonce: 96, + Round: 96, + ShardID: 0, + ChainID: []byte("1"), + } + hdrHash := []byte("header hash 96") + hdrBytes, _ := marshaller.Marshal(hdr) + + cleanupCount := 0 + savedLastRound := int64(0) + wasCalledBlockchainSetHeader := false + + args := ArgsShardStorageBootstrapper{ + ArgsBaseStorageBootstrapper{ + BootStorer: &mock.BoostrapStorerMock{ + GetHighestRoundCalled: func() int64 { + return 100 + }, + GetCalled: func(round int64) (bootstrapStorage.BootstrapData, error) { + return bootstrapStorage.BootstrapData{ + LastHeader: bootstrapStorage.BootstrapHeaderInfo{ + ShardId: hdr.GetShardID(), + Epoch: hdr.GetEpoch(), + Nonce: hdr.GetNonce(), + Hash: hdrHash, + }, + HighestFinalBlockNonce: hdr.GetNonce(), + LastRound: round - 1, + }, nil + }, + SaveLastRoundCalled: func(round int64) error { + savedLastRound = round + return nil + }, + }, + ForkDetector: &mock.ForkDetectorMock{ + AddHeaderCalled: func(header data.HeaderHandler, hash []byte, state process.BlockHeaderState, selfNotarizedHeaders []data.HeaderHandler, selfNotarizedHeadersHashes [][]byte) error { + return nil + }, + }, + BlockProcessor: &testscommon.BlockProcessorStub{}, + ChainHandler: &testscommon.ChainHandlerStub{ + GetGenesisHeaderCalled: func() data.HeaderHandler { + return nil + }, + SetCurrentBlockHeaderAndRootHashCalled: func(header data.HeaderHandler, rootHash []byte) error { + wasCalledBlockchainSetHeader = true + return nil + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + if wasCalledBlockchainSetHeader { + return hdr + } + return nil + }, + }, + Marshalizer: marshaller, + Store: &storageMock.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageMock.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + if bytes.Equal(key, hdrHash) { + return hdrBytes, nil + } + return nil, errors.New("key not found") + }, + RemoveCalled: func(key []byte) error { + cleanupCount++ + return nil + }, + SearchFirstCalled: func(key []byte) ([]byte, error) { + return nil, errors.New("not found") + }, + }, nil + }, + }, + Uint64Converter: testscommon.NewNonceHashConverterMock(), + BootstrapRoundIndex: bootstrapRoundIdx, + ShardCoordinator: testscommon.NewMultiShardsCoordinatorMock(1), + NodesCoordinator: &shardingMocks.NodesCoordinatorMock{}, + EpochStartTrigger: &mock.EpochStartTriggerStub{}, + BlockTracker: &mock.BlockTrackerMock{ + AddTrackedHeaderCalled: func(header data.HeaderHandler, hash []byte) {}, + }, + ChainID: "1", + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + MiniblocksProvider: &mock.MiniBlocksProviderStub{}, + EpochNotifier: &epochNotifierMock.EpochNotifierStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + AppStatusHandler: &statusHandler.AppStatusHandlerMock{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + ProofsPool: &dataRetrieverMocks.ProofsPoolMock{}, + ExecutionManager: &processMocks.ExecutionManagerMock{}, + }, + } + + ssb, err := NewShardStorageBootstrapper(args) + require.Nil(t, err) + + err = ssb.LoadFromStorage() + assert.Nil(t, err) + assert.Equal(t, int64(97), savedLastRound) + // rounds 100, 99, 98 were above bootstrapRoundIndex=97, each triggers a Remove in cleanupStorage + assert.True(t, cleanupCount >= 3) + assert.True(t, wasCalledBlockchainSetHeader) + }) + + t.Run("bootstrapRoundIndex zero should cleaned all", func(t *testing.T) { + t.Parallel() + + marshaller := &marshallerMock.MarshalizerMock{} + + bootstrapRoundIdx := uint64(0) + + hdr := &block.HeaderV3{ + Nonce: 96, + Round: 96, + ShardID: 0, + ChainID: []byte("1"), + } + hdrHash := []byte("header hash 96") + hdrBytes, _ := marshaller.Marshal(hdr) + + cleanupCount := 0 + savedLastRound := int64(0) + wasCalledBlockchainSetHeader := false + + args := ArgsShardStorageBootstrapper{ + ArgsBaseStorageBootstrapper{ + BootStorer: &mock.BoostrapStorerMock{ + GetHighestRoundCalled: func() int64 { + return 100 + }, + GetCalled: func(round int64) (bootstrapStorage.BootstrapData, error) { + return bootstrapStorage.BootstrapData{ + LastHeader: bootstrapStorage.BootstrapHeaderInfo{ + ShardId: hdr.GetShardID(), + Epoch: hdr.GetEpoch(), + Nonce: hdr.GetNonce(), + Hash: hdrHash, + }, + HighestFinalBlockNonce: hdr.GetNonce(), + LastRound: round - 1, + }, nil + }, + SaveLastRoundCalled: func(round int64) error { + savedLastRound = round + return nil + }, + }, + ForkDetector: &mock.ForkDetectorMock{ + AddHeaderCalled: func(header data.HeaderHandler, hash []byte, state process.BlockHeaderState, selfNotarizedHeaders []data.HeaderHandler, selfNotarizedHeadersHashes [][]byte) error { + return nil + }, + }, + BlockProcessor: &testscommon.BlockProcessorStub{}, + ChainHandler: &testscommon.ChainHandlerStub{ + GetGenesisHeaderCalled: func() data.HeaderHandler { + return nil + }, + SetCurrentBlockHeaderAndRootHashCalled: func(header data.HeaderHandler, rootHash []byte) error { + wasCalledBlockchainSetHeader = true + return nil + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + if wasCalledBlockchainSetHeader { + return hdr + } + return nil + }, + }, + Marshalizer: marshaller, + Store: &storageMock.ChainStorerStub{ + GetStorerCalled: func(unitType dataRetriever.UnitType) (storage.Storer, error) { + return &storageMock.StorerStub{ + GetCalled: func(key []byte) ([]byte, error) { + if bytes.Equal(key, hdrHash) { + return hdrBytes, nil + } + return nil, errors.New("key not found") + }, + RemoveCalled: func(key []byte) error { + cleanupCount++ + return nil + }, + SearchFirstCalled: func(key []byte) ([]byte, error) { + return nil, errors.New("not found") + }, + }, nil + }, + }, + Uint64Converter: testscommon.NewNonceHashConverterMock(), + BootstrapRoundIndex: bootstrapRoundIdx, + ShardCoordinator: testscommon.NewMultiShardsCoordinatorMock(1), + NodesCoordinator: &shardingMocks.NodesCoordinatorMock{}, + EpochStartTrigger: &mock.EpochStartTriggerStub{}, + BlockTracker: &mock.BlockTrackerMock{ + AddTrackedHeaderCalled: func(header data.HeaderHandler, hash []byte) {}, + }, + ChainID: "1", + ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, + MiniblocksProvider: &mock.MiniBlocksProviderStub{}, + EpochNotifier: &epochNotifierMock.EpochNotifierStub{}, + ProcessedMiniBlocksTracker: &testscommon.ProcessedMiniBlocksTrackerStub{}, + AppStatusHandler: &statusHandler.AppStatusHandlerMock{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + ProofsPool: &dataRetrieverMocks.ProofsPoolMock{}, + ExecutionManager: &processMocks.ExecutionManagerMock{}, + }, + } + + ssb, err := NewShardStorageBootstrapper(args) + require.Nil(t, err) + + err = ssb.LoadFromStorage() + assert.Nil(t, err) + assert.Equal(t, int64(0), savedLastRound) + + // all rounds info should be cleaned + assert.Equal(t, 100, cleanupCount) + assert.True(t, wasCalledBlockchainSetHeader) + }) +} diff --git a/process/throttle/antiflood/blackList/errors.go b/process/throttle/antiflood/blackList/errors.go new file mode 100644 index 00000000000..df5ab6745a0 --- /dev/null +++ b/process/throttle/antiflood/blackList/errors.go @@ -0,0 +1,6 @@ +package blackList + +import "errors" + +// ErrEmptyConfigVarNameForNumFloodingRounds signals that an empty config variable name for num flooding rounds has been provided +var ErrEmptyConfigVarNameForNumFloodingRounds = errors.New("empty config variable name for num flooding rounds") diff --git a/process/throttle/antiflood/blackList/p2pBlackListProcessor.go b/process/throttle/antiflood/blackList/p2pBlackListProcessor.go index 3d2819b320b..6047f53fc8a 100644 --- a/process/throttle/antiflood/blackList/p2pBlackListProcessor.go +++ b/process/throttle/antiflood/blackList/p2pBlackListProcessor.go @@ -6,27 +6,23 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/storage" - "github.com/multiversx/mx-chain-logger-go" + logger "github.com/multiversx/mx-chain-logger-go" ) var log = logger.GetOrCreate("process/throttle/antiflood/blacklist") -const minBanDuration = time.Second -const minFloodingRounds = 2 const sizeBlacklistInfo = 4 type p2pBlackListProcessor struct { - thresholdNumReceivedFlood uint32 - numFloodingRounds uint32 - thresholdSizeReceivedFlood uint64 - cacher storage.Cacher - peerBlacklistCacher process.PeerBlackListCacher - banDuration time.Duration - selfPid core.PeerID - name string + cacher storage.Cacher + peerBlacklistCacher process.PeerBlackListCacher + selfPid core.PeerID + name common.FloodPreventerType + antifloodConfigs common.AntifloodConfigsHandler } // NewP2PBlackListProcessor creates a new instance of p2pQuotaBlacklistProcessor able to determine @@ -35,42 +31,26 @@ type p2pBlackListProcessor struct { func NewP2PBlackListProcessor( cacher storage.Cacher, peerBlacklistCacher process.PeerBlackListCacher, - thresholdNumReceivedFlood uint32, - thresholdSizeReceivedFlood uint64, - numFloodingRounds uint32, - banDuration time.Duration, - name string, + name common.FloodPreventerType, selfPid core.PeerID, + antifloodConfigs common.AntifloodConfigsHandler, ) (*p2pBlackListProcessor, error) { - if check.IfNil(cacher) { return nil, fmt.Errorf("%w, NewP2PBlackListProcessor", process.ErrNilCacher) } if check.IfNil(peerBlacklistCacher) { return nil, fmt.Errorf("%w, NewP2PBlackListProcessor", process.ErrNilBlackListCacher) } - if thresholdNumReceivedFlood == 0 { - return nil, fmt.Errorf("%w, thresholdNumReceivedFlood == 0", process.ErrInvalidValue) - } - if thresholdSizeReceivedFlood == 0 { - return nil, fmt.Errorf("%w, thresholdSizeReceivedFlood == 0", process.ErrInvalidValue) - } - if numFloodingRounds < minFloodingRounds { - return nil, fmt.Errorf("%w, numFloodingRounds < %d", process.ErrInvalidValue, minFloodingRounds) - } - if banDuration < minBanDuration { - return nil, fmt.Errorf("%w for ban duration in NewP2PBlackListProcessor", process.ErrInvalidValue) + if check.IfNil(antifloodConfigs) { + return nil, fmt.Errorf("%w, NewP2PBlackListProcessor", process.ErrNilAntifloodConfigsHandler) } return &p2pBlackListProcessor{ - cacher: cacher, - peerBlacklistCacher: peerBlacklistCacher, - thresholdNumReceivedFlood: thresholdNumReceivedFlood, - thresholdSizeReceivedFlood: thresholdSizeReceivedFlood, - numFloodingRounds: numFloodingRounds, - banDuration: banDuration, - selfPid: selfPid, - name: name, + cacher: cacher, + peerBlacklistCacher: peerBlacklistCacher, + selfPid: selfPid, + name: name, + antifloodConfigs: antifloodConfigs, }, nil } @@ -85,14 +65,17 @@ func (pbp *p2pBlackListProcessor) ResetStatistics() { continue } - if val >= pbp.numFloodingRounds-1 { //-1 because the reset function is called before the AddQuota + if val >= uint32(pbp.getNumFloodingRoundsVar())-1 { //-1 because the reset function is called before the AddQuota pbp.cacher.Remove(key) pid := core.PeerID(key) + + banDuration := pbp.getBadDuration() log.Debug("added new peer to black list", "peer ID", pid.Pretty(), - "ban period", pbp.banDuration, + "ban period", banDuration, ) - err := pbp.peerBlacklistCacher.Upsert(pid, pbp.banDuration) + + err := pbp.peerBlacklistCacher.Upsert(pid, banDuration) if err != nil { log.Warn("error adding peer id in peer ids cache", ""+ "pid", p2p.PeerIdToShortString(pid), @@ -103,6 +86,16 @@ func (pbp *p2pBlackListProcessor) ResetStatistics() { } } +func (pbp *p2pBlackListProcessor) getBadDuration() time.Duration { + currentConfig := pbp.antifloodConfigs.GetFloodPreventerConfigByType(pbp.name) + return time.Duration(currentConfig.BlackList.PeerBanDurationInSeconds) * time.Second +} + +func (pbp *p2pBlackListProcessor) getNumFloodingRoundsVar() uint32 { + currentConfig := pbp.antifloodConfigs.GetFloodPreventerConfigByType(pbp.name) + return currentConfig.BlackList.NumFloodingRounds +} + func (pbp *p2pBlackListProcessor) getFloodingValue(key []byte) (uint32, bool) { obj, ok := pbp.cacher.Peek(key) if !ok { @@ -116,7 +109,7 @@ func (pbp *p2pBlackListProcessor) getFloodingValue(key []byte) (uint32, bool) { // AddQuota checks if the received quota for an identifier has exceeded the set thresholds func (pbp *p2pBlackListProcessor) AddQuota(pid core.PeerID, numReceived uint32, sizeReceived uint64, _ uint32, _ uint64) { - isFloodingPeer := numReceived >= pbp.thresholdNumReceivedFlood || sizeReceived >= pbp.thresholdSizeReceivedFlood + isFloodingPeer := numReceived >= pbp.getThresholdNumReceivedFlood() || sizeReceived >= pbp.getThresholdSizeReceivedFlood() if !isFloodingPeer { return } @@ -133,6 +126,16 @@ func (pbp *p2pBlackListProcessor) AddQuota(pid core.PeerID, numReceived uint32, } +func (pbp *p2pBlackListProcessor) getThresholdSizeReceivedFlood() uint64 { + currentConfig := pbp.antifloodConfigs.GetFloodPreventerConfigByType(pbp.name) + return currentConfig.BlackList.ThresholdSizePerInterval +} + +func (pbp *p2pBlackListProcessor) getThresholdNumReceivedFlood() uint32 { + currentConfig := pbp.antifloodConfigs.GetFloodPreventerConfigByType(pbp.name) + return currentConfig.BlackList.ThresholdNumMessagesPerInterval +} + func (pbp *p2pBlackListProcessor) incrementStatsFloodingPeer(pid core.PeerID) { obj, ok := pbp.cacher.Get(pid.Bytes()) if !ok { diff --git a/process/throttle/antiflood/blackList/p2pBlackListProcessor_test.go b/process/throttle/antiflood/blackList/p2pBlackListProcessor_test.go index 686b49031d1..f88e50ebb13 100644 --- a/process/throttle/antiflood/blackList/p2pBlackListProcessor_test.go +++ b/process/throttle/antiflood/blackList/p2pBlackListProcessor_test.go @@ -7,6 +7,9 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/mock" @@ -26,12 +29,9 @@ func TestNewP2PQuotaBlacklistProcessor_NilCacherShouldErr(t *testing.T) { pbp, err := blackList.NewP2PBlackListProcessor( nil, &mock.PeerBlackListHandlerStub{}, - 1, - 1, - 2, - time.Second, "", selfPid, + &testscommon.AntifloodConfigsHandlerStub{}, ) assert.True(t, check.IfNil(pbp)) @@ -44,102 +44,24 @@ func TestNewP2PQuotaBlacklistProcessor_NilBlackListHandlerShouldErr(t *testing.T pbp, err := blackList.NewP2PBlackListProcessor( cache.NewCacherStub(), nil, - 1, - 1, - 2, - time.Second, "", selfPid, + &testscommon.AntifloodConfigsHandlerStub{}, ) assert.True(t, check.IfNil(pbp)) assert.True(t, errors.Is(err, process.ErrNilBlackListCacher)) } -func TestNewP2PQuotaBlacklistProcessor_InvalidThresholdNumReceivedFloodShouldErr(t *testing.T) { - t.Parallel() - - pbp, err := blackList.NewP2PBlackListProcessor( - cache.NewCacherStub(), - &mock.PeerBlackListHandlerStub{}, - 0, - 1, - 2, - time.Second, - "", - selfPid, - ) - - assert.True(t, check.IfNil(pbp)) - assert.True(t, errors.Is(err, process.ErrInvalidValue)) -} - -func TestNewP2PQuotaBlacklistProcessor_InvalidThresholdSizeReceivedFloodShouldErr(t *testing.T) { - t.Parallel() - - pbp, err := blackList.NewP2PBlackListProcessor( - cache.NewCacherStub(), - &mock.PeerBlackListHandlerStub{}, - 1, - 0, - 2, - time.Second, - "", - selfPid, - ) - - assert.True(t, check.IfNil(pbp)) - assert.True(t, errors.Is(err, process.ErrInvalidValue)) -} - -func TestNewP2PQuotaBlacklistProcessor_InvalidNumFloodingRoundsShouldErr(t *testing.T) { - t.Parallel() - - pbp, err := blackList.NewP2PBlackListProcessor( - cache.NewCacherStub(), - &mock.PeerBlackListHandlerStub{}, - 1, - 1, - 1, - time.Second, - "", - selfPid, - ) - - assert.True(t, check.IfNil(pbp)) - assert.True(t, errors.Is(err, process.ErrInvalidValue)) -} - -func TestNewP2PQuotaBlacklistProcessor_InvalidBanDurationShouldErr(t *testing.T) { - t.Parallel() - - pbp, err := blackList.NewP2PBlackListProcessor( - cache.NewCacherStub(), - &mock.PeerBlackListHandlerStub{}, - 1, - 1, - 2, - time.Millisecond, - "", - selfPid, - ) - - assert.True(t, check.IfNil(pbp)) - assert.True(t, errors.Is(err, process.ErrInvalidValue)) -} - func TestNewP2PQuotaBlacklistProcessor_ShouldWork(t *testing.T) { t.Parallel() pbp, err := blackList.NewP2PBlackListProcessor( cache.NewCacherStub(), &mock.PeerBlackListHandlerStub{}, - 1, - 1, - 2, - time.Second, "", selfPid, + &testscommon.AntifloodConfigsHandlerStub{}, ) assert.False(t, check.IfNil(pbp)) @@ -166,12 +88,20 @@ func TestP2PQuotaBlacklistProcessor_AddQuotaUnderThresholdShouldNotCallGetOrPut( }, }, &mock.PeerBlackListHandlerStub{}, - thresholdNum, - thresholdSize, - 2, - time.Second, "", selfPid, + &testscommon.AntifloodConfigsHandlerStub{ + GetFloodPreventerConfigByTypeCalled: func(configType common.FloodPreventerType) config.FloodPreventerConfig { + return config.FloodPreventerConfig{ + BlackList: config.BlackListConfig{ + ThresholdNumMessagesPerInterval: thresholdNum, + ThresholdSizePerInterval: thresholdSize, + NumFloodingRounds: 10, + PeerBanDurationInSeconds: 1, + }, + } + }, + }, ) pbp.AddQuota("identifier", thresholdNum-1, thresholdSize-1, 1, 1) @@ -199,12 +129,20 @@ func TestP2PQuotaBlacklistProcessor_AddQuotaOverThresholdInexistentDataOnGetShou }, }, &mock.PeerBlackListHandlerStub{}, - thresholdNum, - thresholdSize, - 2, - time.Second, "", selfPid, + &testscommon.AntifloodConfigsHandlerStub{ + GetFloodPreventerConfigByTypeCalled: func(configType common.FloodPreventerType) config.FloodPreventerConfig { + return config.FloodPreventerConfig{ + BlackList: config.BlackListConfig{ + ThresholdNumMessagesPerInterval: thresholdNum, + ThresholdSizePerInterval: thresholdSize, + NumFloodingRounds: 10, + PeerBanDurationInSeconds: 1, + }, + } + }, + }, ) pbp.AddQuota(identifier, thresholdNum, thresholdSize, 1, 1) @@ -234,12 +172,20 @@ func TestP2PQuotaBlacklistProcessor_AddQuotaOverThresholdDataNotValidOnGetShould }, }, &mock.PeerBlackListHandlerStub{}, - thresholdNum, - thresholdSize, - 2, - time.Second, "", selfPid, + &testscommon.AntifloodConfigsHandlerStub{ + GetFloodPreventerConfigByTypeCalled: func(configType common.FloodPreventerType) config.FloodPreventerConfig { + return config.FloodPreventerConfig{ + BlackList: config.BlackListConfig{ + ThresholdNumMessagesPerInterval: thresholdNum, + ThresholdSizePerInterval: thresholdSize, + NumFloodingRounds: 10, + PeerBanDurationInSeconds: 1, + }, + } + }, + }, ) pbp.AddQuota(identifier, thresholdNum, thresholdSize, 1, 1) @@ -270,12 +216,20 @@ func TestP2PQuotaBlacklistProcessor_AddQuotaShouldIncrement(t *testing.T) { }, }, &mock.PeerBlackListHandlerStub{}, - thresholdNum, - thresholdSize, - 2, - time.Second, "", selfPid, + &testscommon.AntifloodConfigsHandlerStub{ + GetFloodPreventerConfigByTypeCalled: func(configType common.FloodPreventerType) config.FloodPreventerConfig { + return config.FloodPreventerConfig{ + BlackList: config.BlackListConfig{ + ThresholdNumMessagesPerInterval: thresholdNum, + ThresholdSizePerInterval: thresholdSize, + NumFloodingRounds: 10, + PeerBanDurationInSeconds: 1, + }, + } + }, + }, ) pbp.AddQuota(identifier, thresholdNum, thresholdSize, 1, 1) @@ -302,12 +256,20 @@ func TestP2PQuotaBlacklistProcessor_AddQuotaForSelfShouldNotIncrement(t *testing }, }, &mock.PeerBlackListHandlerStub{}, - thresholdNum, - thresholdSize, - 2, - time.Second, "", selfPid, + &testscommon.AntifloodConfigsHandlerStub{ + GetFloodPreventerConfigByTypeCalled: func(configType common.FloodPreventerType) config.FloodPreventerConfig { + return config.FloodPreventerConfig{ + BlackList: config.BlackListConfig{ + ThresholdNumMessagesPerInterval: thresholdNum, + ThresholdSizePerInterval: thresholdSize, + NumFloodingRounds: 10, + PeerBanDurationInSeconds: 1, + }, + } + }, + }, ) pbp.AddQuota(selfPid, thresholdNum, thresholdSize, 1, 1) @@ -340,12 +302,20 @@ func TestP2PQuotaBlacklistProcessor_ResetStatisticsRemoveNilValueKey(t *testing. }, }, &mock.PeerBlackListHandlerStub{}, - thresholdNum, - thresholdSize, - 2, - time.Second, "", selfPid, + &testscommon.AntifloodConfigsHandlerStub{ + GetFloodPreventerConfigByTypeCalled: func(configType common.FloodPreventerType) config.FloodPreventerConfig { + return config.FloodPreventerConfig{ + BlackList: config.BlackListConfig{ + ThresholdNumMessagesPerInterval: thresholdNum, + ThresholdSizePerInterval: thresholdSize, + NumFloodingRounds: 10, + PeerBanDurationInSeconds: 1, + }, + } + }, + }, ) pbp.ResetStatistics() @@ -376,12 +346,20 @@ func TestP2PQuotaBlacklistProcessor_ResetStatisticsShouldRemoveInvalidValueKey(t }, }, &mock.PeerBlackListHandlerStub{}, - thresholdNum, - thresholdSize, - 2, - time.Second, "", selfPid, + &testscommon.AntifloodConfigsHandlerStub{ + GetFloodPreventerConfigByTypeCalled: func(configType common.FloodPreventerType) config.FloodPreventerConfig { + return config.FloodPreventerConfig{ + BlackList: config.BlackListConfig{ + ThresholdNumMessagesPerInterval: thresholdNum, + ThresholdSizePerInterval: thresholdSize, + NumFloodingRounds: 10, + PeerBanDurationInSeconds: 1, + }, + } + }, + }, ) pbp.ResetStatistics() @@ -420,12 +398,20 @@ func TestP2PQuotaBlacklistProcessor_ResetStatisticsUnderNumFloodingRoundsShouldN return nil }, }, - thresholdNum, - thresholdSize, - numFloodingRounds, - duration, "", selfPid, + &testscommon.AntifloodConfigsHandlerStub{ + GetFloodPreventerConfigByTypeCalled: func(configType common.FloodPreventerType) config.FloodPreventerConfig { + return config.FloodPreventerConfig{ + BlackList: config.BlackListConfig{ + ThresholdNumMessagesPerInterval: thresholdNum, + ThresholdSizePerInterval: thresholdSize, + NumFloodingRounds: 0, + PeerBanDurationInSeconds: 1, + }, + } + }, + }, ) pbp.ResetStatistics() @@ -465,12 +451,20 @@ func TestP2PQuotaBlacklistProcessor_ResetStatisticsOverNumFloodingRoundsShouldBl return nil }, }, - thresholdNum, - thresholdSize, - numFloodingRounds, - duration, "", selfPid, + &testscommon.AntifloodConfigsHandlerStub{ + GetFloodPreventerConfigByTypeCalled: func(configType common.FloodPreventerType) config.FloodPreventerConfig { + return config.FloodPreventerConfig{ + BlackList: config.BlackListConfig{ + ThresholdNumMessagesPerInterval: thresholdNum, + ThresholdSizePerInterval: thresholdSize, + NumFloodingRounds: numFloodingRounds, + PeerBanDurationInSeconds: 3892, + }, + } + }, + }, ) pbp.ResetStatistics() diff --git a/process/throttle/antiflood/factory/p2pAntifloodAndBlacklistFactory.go b/process/throttle/antiflood/factory/p2pAntifloodAndBlacklistFactory.go index bfbf29617c6..2ad93afaaa9 100644 --- a/process/throttle/antiflood/factory/p2pAntifloodAndBlacklistFactory.go +++ b/process/throttle/antiflood/factory/p2pAntifloodAndBlacklistFactory.go @@ -7,6 +7,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" antifloodDebug "github.com/multiversx/mx-chain-go/debug/antiflood" "github.com/multiversx/mx-chain-go/p2p" @@ -25,10 +26,6 @@ import ( var log = logger.GetOrCreate("p2p/antiflood/factory") const defaultSpan = 300 * time.Second -const fastReactingIdentifier = "fast_reacting" -const slowReactingIdentifier = "slow_reacting" -const outOfSpecsIdentifier = "out_of_specs" -const outputIdentifier = "output" var durationSweepP2PBlacklist = time.Second * 5 @@ -42,12 +39,21 @@ type AntiFloodComponents struct { } // NewP2PAntiFloodComponents will return instances of antiflood and blacklist, based on the config -func NewP2PAntiFloodComponents(ctx context.Context, config config.Config, statusHandler core.AppStatusHandler, currentPid core.PeerID) (*AntiFloodComponents, error) { +func NewP2PAntiFloodComponents( + ctx context.Context, + mainConfig config.Config, + statusHandler core.AppStatusHandler, + currentPid core.PeerID, + antifloodConfigsHandler common.AntifloodConfigsHandler, +) (*AntiFloodComponents, error) { if check.IfNil(statusHandler) { return nil, p2p.ErrNilStatusHandler } - if config.Antiflood.Enabled { - return initP2PAntiFloodComponents(ctx, config, statusHandler, currentPid) + if check.IfNil(antifloodConfigsHandler) { + return nil, process.ErrNilAntifloodConfigsHandler + } + if antifloodConfigsHandler.IsEnabled() { + return initP2PAntiFloodComponents(ctx, mainConfig, statusHandler, currentPid, antifloodConfigsHandler) } return &AntiFloodComponents{ @@ -64,6 +70,7 @@ func initP2PAntiFloodComponents( mainConfig config.Config, statusHandler core.AppStatusHandler, currentPid core.PeerID, + antifloodConfigsHandler common.AntifloodConfigsHandler, ) (*AntiFloodComponents, error) { timeCache := cache.NewTimeCache(defaultSpan) p2pPeerBlackList, err := cache.NewPeerTimeCache(timeCache) @@ -75,10 +82,9 @@ func initP2PAntiFloodComponents( fastReactingFloodPreventer, err := createFloodPreventer( ctx, - mainConfig.Antiflood.FastReacting, - mainConfig.Antiflood.Cache, + antifloodConfigsHandler, statusHandler, - fastReactingIdentifier, + common.FastReacting, p2pPeerBlackList, currentPid, ) @@ -88,10 +94,9 @@ func initP2PAntiFloodComponents( slowReactingFloodPreventer, err := createFloodPreventer( ctx, - mainConfig.Antiflood.SlowReacting, - mainConfig.Antiflood.Cache, + antifloodConfigsHandler, statusHandler, - slowReactingIdentifier, + common.SlowReacting, p2pPeerBlackList, currentPid, ) @@ -101,10 +106,9 @@ func initP2PAntiFloodComponents( outOfSpecsFloodPreventer, err := createFloodPreventer( ctx, - mainConfig.Antiflood.OutOfSpecs, - mainConfig.Antiflood.Cache, + antifloodConfigsHandler, statusHandler, - outOfSpecsIdentifier, + common.OutOfSpecs, p2pPeerBlackList, currentPid, ) @@ -112,12 +116,14 @@ func initP2PAntiFloodComponents( return nil, fmt.Errorf("%w when creating out of specs flood preventer", err) } - topicFloodPreventer, err := floodPreventers.NewTopicFloodPreventer(mainConfig.Antiflood.Topic.DefaultMaxMessagesPerSec) + initialAntifloodConf := antifloodConfigsHandler.GetCurrentConfig() + + topicFloodPreventer, err := floodPreventers.NewTopicFloodPreventer(initialAntifloodConf.Topic.DefaultMaxMessagesPerSec) if err != nil { return nil, err } - topicMaxMessages := mainConfig.Antiflood.Topic.MaxMessages + topicMaxMessages := initialAntifloodConf.Topic.MaxMessages setMaxMessages(topicFloodPreventer, topicMaxMessages) p2pAntiflood, err := antiflood.NewP2PAntiflood( @@ -212,14 +218,16 @@ func startSweepingTimeCaches(ctx context.Context, p2pPeerBlackList process.PeerB func createFloodPreventer( ctx context.Context, - floodPreventerConfig config.FloodPreventerConfig, - antifloodCacheConfig config.CacheConfig, + antifloodConfigsHandler common.AntifloodConfigsHandler, statusHandler core.AppStatusHandler, - quotaIdentifier string, + quotaIdentifier common.FloodPreventerType, blackListHandler process.PeerBlackListCacher, selfPid core.PeerID, ) (process.FloodPreventer, error) { - cacheConfig := storageFactory.GetCacherFromConfig(antifloodCacheConfig) + initialAntifloodConf := antifloodConfigsHandler.GetCurrentConfig() + + // TODO: this config section have to be loaded with new configration from the start + cacheConfig := storageFactory.GetCacherFromConfig(initialAntifloodConf.Cache) blackListCache, err := storageunit.NewCache(cacheConfig) if err != nil { return nil, err @@ -228,12 +236,9 @@ func createFloodPreventer( blackListProcessor, err := blackList.NewP2PBlackListProcessor( blackListCache, blackListHandler, - floodPreventerConfig.BlackList.ThresholdNumMessagesPerInterval, - floodPreventerConfig.BlackList.ThresholdSizePerInterval, - floodPreventerConfig.BlackList.NumFloodingRounds, - time.Duration(floodPreventerConfig.BlackList.PeerBanDurationInSeconds)*time.Second, quotaIdentifier, selfPid, + antifloodConfigsHandler, ) if err != nil { return nil, err @@ -249,39 +254,33 @@ func createFloodPreventer( return nil, err } - basePeerMaxMessagesPerInterval := floodPreventerConfig.PeerMaxInput.BaseMessagesPerInterval - peerMaxTotalSizePerInterval := floodPreventerConfig.PeerMaxInput.TotalSizePerInterval - reservedPercent := floodPreventerConfig.ReservedPercent - argFloodPreventer := floodPreventers.ArgQuotaFloodPreventer{ - Name: quotaIdentifier, - Cacher: antifloodCache, - StatusHandlers: []floodPreventers.QuotaStatusHandler{quotaProcessor, blackListProcessor}, - BaseMaxNumMessagesPerPeer: basePeerMaxMessagesPerInterval, - MaxTotalSizePerPeer: peerMaxTotalSizePerInterval, - PercentReserved: reservedPercent, - IncreaseThreshold: floodPreventerConfig.PeerMaxInput.IncreaseFactor.Threshold, - IncreaseFactor: floodPreventerConfig.PeerMaxInput.IncreaseFactor.Factor, + Name: quotaIdentifier, + Cacher: antifloodCache, + StatusHandlers: []floodPreventers.QuotaStatusHandler{quotaProcessor, blackListProcessor}, + AntifloodConfigs: antifloodConfigsHandler, } floodPreventer, err := floodPreventers.NewQuotaFloodPreventer(argFloodPreventer) if err != nil { return nil, err } + floodPreventerConfig := antifloodConfigsHandler.GetFloodPreventerConfigByType(quotaIdentifier) + log.Debug("started antiflood & blacklist component", "type", quotaIdentifier, "interval in seconds", floodPreventerConfig.IntervalInSeconds, - "base peerMaxMessagesPerInterval", basePeerMaxMessagesPerInterval, - "peerMaxTotalSizePerInterval", core.ConvertBytes(peerMaxTotalSizePerInterval), + "base peerMaxMessagesPerInterval", floodPreventerConfig.PeerMaxInput.BaseMessagesPerInterval, + "peerMaxTotalSizePerInterval", core.ConvertBytes(floodPreventerConfig.PeerMaxInput.TotalSizePerInterval), "peerBanDurationInSeconds", floodPreventerConfig.BlackList.PeerBanDurationInSeconds, "thresholdNumMessagesPerSecond", floodPreventerConfig.BlackList.ThresholdNumMessagesPerInterval, "thresholdSizePerSecond", floodPreventerConfig.BlackList.ThresholdSizePerInterval, - "numFloodingRounds", floodPreventerConfig.BlackList.NumFloodingRounds, "increase threshold", floodPreventerConfig.PeerMaxInput.IncreaseFactor.Threshold, "increase factor", floodPreventerConfig.PeerMaxInput.IncreaseFactor.Factor, ) go func() { + floodPreventerConfig := antifloodConfigsHandler.GetFloodPreventerConfigByType(quotaIdentifier) wait := time.Duration(floodPreventerConfig.IntervalInSeconds) * time.Second for { diff --git a/process/throttle/antiflood/factory/p2pAntifloodAndBlacklistFactory_test.go b/process/throttle/antiflood/factory/p2pAntifloodAndBlacklistFactory_test.go index 52715507062..0f74ebebc57 100644 --- a/process/throttle/antiflood/factory/p2pAntifloodAndBlacklistFactory_test.go +++ b/process/throttle/antiflood/factory/p2pAntifloodAndBlacklistFactory_test.go @@ -9,6 +9,7 @@ import ( "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/p2p" "github.com/multiversx/mx-chain-go/process/throttle/antiflood/disabled" + "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/statusHandler" "github.com/stretchr/testify/assert" ) @@ -20,7 +21,7 @@ func TestNewP2PAntiFloodAndBlackList_NilStatusHandlerShouldErr(t *testing.T) { ctx := context.Background() cfg := config.Config{} - components, err := NewP2PAntiFloodComponents(ctx, cfg, nil, currentPid) + components, err := NewP2PAntiFloodComponents(ctx, cfg, nil, currentPid, &testscommon.AntifloodConfigsHandlerStub{}) assert.Nil(t, components) assert.Equal(t, p2p.ErrNilStatusHandler, err) } @@ -35,7 +36,7 @@ func TestNewP2PAntiFloodAndBlackList_ShouldWorkAndReturnDisabledImplementations( } ash := statusHandler.NewAppStatusHandlerMock() ctx := context.Background() - components, err := NewP2PAntiFloodComponents(ctx, cfg, ash, currentPid) + components, err := NewP2PAntiFloodComponents(ctx, cfg, ash, currentPid, &testscommon.AntifloodConfigsHandlerStub{}) assert.NotNil(t, components) assert.Nil(t, err) @@ -50,26 +51,32 @@ func TestNewP2PAntiFloodAndBlackList_ShouldWorkAndReturnDisabledImplementations( func TestNewP2PAntiFloodAndBlackList_ShouldWorkAndReturnOkImplementations(t *testing.T) { t.Parallel() - cfg := config.Config{ - Antiflood: config.AntifloodConfig{ - Enabled: true, - Cache: config.CacheConfig{ - Type: "LRU", - Capacity: 10, - Shards: 2, - }, - FastReacting: createFloodPreventerConfig(), - SlowReacting: createFloodPreventerConfig(), - OutOfSpecs: createFloodPreventerConfig(), - Topic: config.TopicAntifloodConfig{ - DefaultMaxMessagesPerSec: 10, - }, + cfg := config.Config{} + + antifloodConfigHandler := &testscommon.AntifloodConfigsHandlerStub{ + IsEnabledCalled: func() bool { + return true + }, + GetCurrentConfigCalled: func() config.AntifloodConfigByRound { + return config.AntifloodConfigByRound{ + Cache: config.CacheConfig{ + Type: "LRU", + Capacity: 10, + Shards: 2, + }, + FastReacting: createFloodPreventerConfig(), + SlowReacting: createFloodPreventerConfig(), + OutOfSpecs: createFloodPreventerConfig(), + Topic: config.TopicAntifloodConfig{ + DefaultMaxMessagesPerSec: 10, + }, + } }, } ash := statusHandler.NewAppStatusHandlerMock() ctx := context.Background() - components, err := NewP2PAntiFloodComponents(ctx, cfg, ash, currentPid) + components, err := NewP2PAntiFloodComponents(ctx, cfg, ash, currentPid, antifloodConfigHandler) assert.Nil(t, err) assert.NotNil(t, components.AntiFloodHandler) assert.NotNil(t, components.BlacklistHandler) @@ -90,7 +97,6 @@ func createFloodPreventerConfig() config.FloodPreventerConfig { BlackList: config.BlackListConfig{ ThresholdNumMessagesPerInterval: 10, ThresholdSizePerInterval: 10, - NumFloodingRounds: 10, PeerBanDurationInSeconds: 10, }, } diff --git a/process/throttle/antiflood/factory/p2pOutputAntiflood.go b/process/throttle/antiflood/factory/p2pOutputAntiflood.go index fdc0e596999..1369ccc6db1 100644 --- a/process/throttle/antiflood/factory/p2pOutputAntiflood.go +++ b/process/throttle/antiflood/factory/p2pOutputAntiflood.go @@ -3,6 +3,9 @@ package factory import ( "context" + "github.com/multiversx/mx-chain-core-go/core/check" + + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/throttle/antiflood" @@ -12,35 +15,38 @@ import ( "github.com/multiversx/mx-chain-go/storage/storageunit" ) -const outputReservedPercent = float32(0) - // NewP2POutputAntiFlood will return an instance of an output antiflood component based on the config -func NewP2POutputAntiFlood(ctx context.Context, mainConfig config.Config) (process.P2PAntifloodHandler, error) { - if mainConfig.Antiflood.Enabled { - return initP2POutputAntiFlood(ctx, mainConfig) +func NewP2POutputAntiFlood( + ctx context.Context, + antifloodConfigsHandler common.AntifloodConfigsHandler, +) (process.P2PAntifloodHandler, error) { + if check.IfNil(antifloodConfigsHandler) { + return nil, process.ErrNilAntifloodConfigsHandler + } + if antifloodConfigsHandler.IsEnabled() { + return initP2POutputAntiFlood(ctx, antifloodConfigsHandler) } return &disabled.AntiFlood{}, nil } -func initP2POutputAntiFlood(ctx context.Context, mainConfig config.Config) (process.P2PAntifloodHandler, error) { - cacheConfig := storageFactory.GetCacherFromConfig(mainConfig.Antiflood.Cache) +func initP2POutputAntiFlood( + ctx context.Context, + antifloodConfigsHandler common.AntifloodConfigsHandler, +) (process.P2PAntifloodHandler, error) { + currentConfig := antifloodConfigsHandler.GetCurrentConfig() + + cacheConfig := storageFactory.GetCacherFromConfig(currentConfig.Cache) antifloodCache, err := storageunit.NewCache(cacheConfig) if err != nil { return nil, err } - basePeerMaxMessagesPerInterval := mainConfig.Antiflood.PeerMaxOutput.BaseMessagesPerInterval - peerMaxTotalSizePerInterval := mainConfig.Antiflood.PeerMaxOutput.TotalSizePerInterval arg := floodPreventers.ArgQuotaFloodPreventer{ - Name: outputIdentifier, - Cacher: antifloodCache, - StatusHandlers: make([]floodPreventers.QuotaStatusHandler, 0), - BaseMaxNumMessagesPerPeer: basePeerMaxMessagesPerInterval, - MaxTotalSizePerPeer: peerMaxTotalSizePerInterval, - PercentReserved: outputReservedPercent, - IncreaseThreshold: 0, - IncreaseFactor: 0, + Name: common.Output, + Cacher: antifloodCache, + StatusHandlers: make([]floodPreventers.QuotaStatusHandler, 0), + AntifloodConfigs: antifloodConfigsHandler, } floodPreventer, err := floodPreventers.NewQuotaFloodPreventer(arg) diff --git a/process/throttle/antiflood/factory/p2pOutputAntiflood_test.go b/process/throttle/antiflood/factory/p2pOutputAntiflood_test.go index 7b316a9538a..d4590c834d2 100644 --- a/process/throttle/antiflood/factory/p2pOutputAntiflood_test.go +++ b/process/throttle/antiflood/factory/p2pOutputAntiflood_test.go @@ -7,19 +7,21 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/process/throttle/antiflood/disabled" + "github.com/multiversx/mx-chain-go/testscommon" "github.com/stretchr/testify/assert" ) func TestNewP2POutputAntiFlood_ShouldWorkAndReturnDisabledImplementations(t *testing.T) { t.Parallel() - cfg := config.Config{ - Antiflood: config.AntifloodConfig{ - Enabled: false, + antifloodConfigHandler := &testscommon.AntifloodConfigsHandlerStub{ + IsEnabledCalled: func() bool { + return false }, } + ctx := context.Background() - af, err := NewP2POutputAntiFlood(ctx, cfg) + af, err := NewP2POutputAntiFlood(ctx, antifloodConfigHandler) assert.NotNil(t, af) assert.Nil(t, err) @@ -30,47 +32,29 @@ func TestNewP2POutputAntiFlood_ShouldWorkAndReturnDisabledImplementations(t *tes func TestNewP2POutputAntiFlood_BadCacheConfigShouldErr(t *testing.T) { t.Parallel() - cfg := config.Config{ - Antiflood: config.AntifloodConfig{ - Enabled: true, - Cache: config.CacheConfig{ - Type: "unknown type", - Capacity: 10, - Shards: 2, - }, - PeerMaxOutput: config.AntifloodLimitsConfig{ - BaseMessagesPerInterval: 10, - TotalSizePerInterval: 10, - }, + antifloodConfigHandler := &testscommon.AntifloodConfigsHandlerStub{ + IsEnabledCalled: func() bool { + return true }, - } - - ctx := context.Background() - af, err := NewP2POutputAntiFlood(ctx, cfg) - assert.NotNil(t, err) - assert.True(t, check.IfNil(af)) -} - -func TestNewP2POutputAntiFlood_BadConfigShouldErr(t *testing.T) { - t.Parallel() - - cfg := config.Config{ - Antiflood: config.AntifloodConfig{ - Enabled: true, - Cache: config.CacheConfig{ - Type: "LRU", - Capacity: 10, - Shards: 2, - }, - PeerMaxOutput: config.AntifloodLimitsConfig{ - BaseMessagesPerInterval: 0, - TotalSizePerInterval: 10, - }, + GetCurrentConfigCalled: func() config.AntifloodConfigByRound { + return config.AntifloodConfigByRound{ + Cache: config.CacheConfig{ + Type: "unknown type", + Capacity: 10, + Shards: 2, + }, + PeerMaxOutput: config.FloodPreventerConfig{ + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: 10, + TotalSizePerInterval: 10, + }, + }, + } }, } ctx := context.Background() - af, err := NewP2POutputAntiFlood(ctx, cfg) + af, err := NewP2POutputAntiFlood(ctx, antifloodConfigHandler) assert.NotNil(t, err) assert.True(t, check.IfNil(af)) } @@ -78,23 +62,29 @@ func TestNewP2POutputAntiFlood_BadConfigShouldErr(t *testing.T) { func TestNewP2POutputAntiFlood_ShouldWorkAndReturnOkImplementations(t *testing.T) { t.Parallel() - cfg := config.Config{ - Antiflood: config.AntifloodConfig{ - Enabled: true, - Cache: config.CacheConfig{ - Type: "LRU", - Capacity: 10, - Shards: 2, - }, - PeerMaxOutput: config.AntifloodLimitsConfig{ - BaseMessagesPerInterval: 10, - TotalSizePerInterval: 10, - }, + antifloodConfigHandler := &testscommon.AntifloodConfigsHandlerStub{ + IsEnabledCalled: func() bool { + return true + }, + GetCurrentConfigCalled: func() config.AntifloodConfigByRound { + return config.AntifloodConfigByRound{ + Cache: config.CacheConfig{ + Type: "LRU", + Capacity: 10, + Shards: 2, + }, + PeerMaxOutput: config.FloodPreventerConfig{ + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: 10, + TotalSizePerInterval: 10, + }, + }, + } }, } ctx := context.Background() - af, err := NewP2POutputAntiFlood(ctx, cfg) + af, err := NewP2POutputAntiFlood(ctx, antifloodConfigHandler) assert.Nil(t, err) assert.NotNil(t, af) } diff --git a/process/throttle/antiflood/floodPreventers/quotaFloodPreventer.go b/process/throttle/antiflood/floodPreventers/quotaFloodPreventer.go index bc229748e98..da00b085abc 100644 --- a/process/throttle/antiflood/floodPreventers/quotaFloodPreventer.go +++ b/process/throttle/antiflood/floodPreventers/quotaFloodPreventer.go @@ -6,29 +6,25 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/storage" ) // ArgQuotaFloodPreventer defines the arguments for a quota flood preventer type ArgQuotaFloodPreventer struct { - Name string - Cacher storage.Cacher - StatusHandlers []QuotaStatusHandler - MaxTotalSizePerPeer uint64 - PercentReserved float32 - IncreaseFactor float32 - IncreaseThreshold uint32 - BaseMaxNumMessagesPerPeer uint32 + Name common.FloodPreventerType + Cacher storage.Cacher + StatusHandlers []QuotaStatusHandler + AntifloodConfigs common.AntifloodConfigsHandler } var _ process.FloodPreventer = (*quotaFloodPreventer)(nil) const minMessages = 1 -const minTotalSize = 1 //1Byte +const minTotalSize = 1 // 1Byte const initNumMessages = 1 -const maxPercentReserved = 90.0 -const minPercentReserved = 0.0 const quotaStructSize = 24 type quota struct { @@ -45,16 +41,12 @@ func (q *quota) Size() int { // quotaFloodPreventer represents a cache of quotas per peer used in antiflooding mechanism type quotaFloodPreventer struct { - name string + name common.FloodPreventerType mutOperation sync.RWMutex cacher storage.Cacher statusHandlers []QuotaStatusHandler computedMaxNumMessagesPerPeer uint32 - baseMaxNumMessagesPerPeer uint32 - maxTotalSizePerPeer uint64 - percentReserved float32 - increaseThreshold uint32 - increaseFactor float32 + antifloodConfigs common.AntifloodConfigsHandler } // NewQuotaFloodPreventer creates a new flood preventer based on quota / peer @@ -68,52 +60,19 @@ func NewQuotaFloodPreventer(arg ArgQuotaFloodPreventer) (*quotaFloodPreventer, e return nil, process.ErrNilQuotaStatusHandler } } - if arg.BaseMaxNumMessagesPerPeer < minMessages { - return nil, fmt.Errorf("%w, maxMessagesPerPeer: provided %d, minimum %d", - process.ErrInvalidValue, - arg.BaseMaxNumMessagesPerPeer, - minMessages, - ) - } - if arg.MaxTotalSizePerPeer < minTotalSize { - return nil, fmt.Errorf("%w, maxTotalSizePerPeer: provided %d, minimum %d", - process.ErrInvalidValue, - arg.MaxTotalSizePerPeer, - minTotalSize, - ) - } - if arg.PercentReserved > maxPercentReserved { - return nil, fmt.Errorf("%w, percentReserved: provided %0.3f, maximum %0.3f", - process.ErrInvalidValue, - arg.PercentReserved, - maxPercentReserved, - ) - } - if arg.PercentReserved < minPercentReserved { - return nil, fmt.Errorf("%w, percentReserved: provided %0.3f, minimum %0.3f", - process.ErrInvalidValue, - arg.PercentReserved, - minPercentReserved, - ) + if check.IfNil(arg.AntifloodConfigs) { + return nil, process.ErrNilAntifloodConfigsHandler } - if arg.IncreaseFactor < 0 { - return nil, fmt.Errorf("%w, increaseFactor is negative: provided %0.3f", - process.ErrInvalidValue, - arg.IncreaseFactor, - ) + + qfp := "aFloodPreventer{ + name: arg.Name, + cacher: arg.Cacher, + statusHandlers: arg.StatusHandlers, + antifloodConfigs: arg.AntifloodConfigs, } + qfp.computedMaxNumMessagesPerPeer = qfp.getBbaseMaxNumMessagesPerPeer() - return "aFloodPreventer{ - name: arg.Name, - cacher: arg.Cacher, - statusHandlers: arg.StatusHandlers, - computedMaxNumMessagesPerPeer: arg.BaseMaxNumMessagesPerPeer, - baseMaxNumMessagesPerPeer: arg.BaseMaxNumMessagesPerPeer, - maxTotalSizePerPeer: arg.MaxTotalSizePerPeer, - percentReserved: arg.PercentReserved, - increaseThreshold: arg.IncreaseThreshold, - increaseFactor: arg.IncreaseFactor, - }, nil + return qfp, nil } // IncreaseLoad tries to increment the counter values held at "pid" position @@ -147,7 +106,7 @@ func (qfp *quotaFloodPreventer) increaseLoad(pid core.PeerID, size uint64) error q.sizeReceivedMessages += size maxNumMessagesReached := qfp.isMaximumReached(uint64(qfp.computedMaxNumMessagesPerPeer), uint64(q.numReceivedMessages)) - maxSizeMessagesReached := qfp.isMaximumReached(qfp.maxTotalSizePerPeer, q.sizeReceivedMessages) + maxSizeMessagesReached := qfp.isMaximumReached(qfp.getMaxTotalSizePerInternal(), q.sizeReceivedMessages) isPeerQuotaReached := maxNumMessagesReached || maxSizeMessagesReached if isPeerQuotaReached { return fmt.Errorf("%w for pid %s", process.ErrSystemBusy, pid.Pretty()) @@ -160,7 +119,7 @@ func (qfp *quotaFloodPreventer) increaseLoad(pid core.PeerID, size uint64) error } func (qfp *quotaFloodPreventer) isMaximumReached(absoluteMax uint64, counted uint64) bool { - max := uint64(100-qfp.percentReserved) * absoluteMax / 100 + max := uint64(100-qfp.getReservedPercent()) * absoluteMax / 100 return counted > max } @@ -183,7 +142,7 @@ func (qfp *quotaFloodPreventer) Reset() { qfp.resetStatusHandlers() qfp.createStatistics() - //TODO change this if cacher.Clear() is time consuming + // TODO change this if cacher.Clear() is time consuming qfp.cacher.Clear() } @@ -238,11 +197,11 @@ func (qfp *quotaFloodPreventer) ApplyConsensusSize(size int) { ) return } - if qfp.increaseThreshold > uint32(size) { + if qfp.getIncreaseThreshold() > uint32(size) { log.Debug("consensus size did not reach the threshold for quota flood preventer", "name", qfp.name, "provided", size, - "threshold", qfp.increaseThreshold, + "threshold", qfp.getIncreaseThreshold(), ) return } @@ -250,22 +209,47 @@ func (qfp *quotaFloodPreventer) ApplyConsensusSize(size int) { qfp.mutOperation.Lock() defer qfp.mutOperation.Unlock() - numNodesOverThreshold := float32(uint32(size) - qfp.increaseThreshold) - value := numNodesOverThreshold * qfp.increaseFactor + numNodesOverThreshold := float32(uint32(size) - qfp.getIncreaseThreshold()) + value := numNodesOverThreshold * qfp.getIncreaseFactor() oldComputed := qfp.computedMaxNumMessagesPerPeer - qfp.computedMaxNumMessagesPerPeer = qfp.baseMaxNumMessagesPerPeer + uint32(value) + qfp.computedMaxNumMessagesPerPeer = qfp.getBbaseMaxNumMessagesPerPeer() + uint32(value) log.Debug("quotaFloodPreventer.ApplyConsensusSize", "name", qfp.name, "provided", size, - "threshold", qfp.increaseThreshold, - "factor", qfp.increaseFactor, - "base", qfp.baseMaxNumMessagesPerPeer, + "threshold", qfp.getIncreaseThreshold(), + "factor", qfp.getIncreaseFactor(), + "base", qfp.getBbaseMaxNumMessagesPerPeer(), "old computed", oldComputed, "new computed", qfp.computedMaxNumMessagesPerPeer, ) } +func (qfp *quotaFloodPreventer) getBbaseMaxNumMessagesPerPeer() uint32 { + currentConfig := qfp.antifloodConfigs.GetFloodPreventerConfigByType(qfp.name) + return currentConfig.PeerMaxInput.BaseMessagesPerInterval +} + +func (qfp *quotaFloodPreventer) getMaxTotalSizePerInternal() uint64 { + currentConfig := qfp.antifloodConfigs.GetFloodPreventerConfigByType(qfp.name) + return currentConfig.PeerMaxInput.TotalSizePerInterval +} + +func (qfp *quotaFloodPreventer) getReservedPercent() float32 { + currentConfig := qfp.antifloodConfigs.GetFloodPreventerConfigByType(qfp.name) + return currentConfig.ReservedPercent +} + +func (qfp *quotaFloodPreventer) getIncreaseThreshold() uint32 { + currentConfig := qfp.antifloodConfigs.GetFloodPreventerConfigByType(qfp.name) + return currentConfig.PeerMaxInput.IncreaseFactor.Threshold +} + +func (qfp *quotaFloodPreventer) getIncreaseFactor() float32 { + currentConfig := qfp.antifloodConfigs.GetFloodPreventerConfigByType(qfp.name) + return currentConfig.PeerMaxInput.IncreaseFactor.Factor +} + // IsInterfaceNil returns true if there is no value under the interface func (qfp *quotaFloodPreventer) IsInterfaceNil() bool { return qfp == nil diff --git a/process/throttle/antiflood/floodPreventers/quotaFloodPreventer_test.go b/process/throttle/antiflood/floodPreventers/quotaFloodPreventer_test.go index 5dc21b68e35..7ed41d1f244 100644 --- a/process/throttle/antiflood/floodPreventers/quotaFloodPreventer_test.go +++ b/process/throttle/antiflood/floodPreventers/quotaFloodPreventer_test.go @@ -10,8 +10,11 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/mock" + "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/cache" "github.com/stretchr/testify/assert" @@ -19,14 +22,31 @@ import ( func createDefaultArgument() ArgQuotaFloodPreventer { return ArgQuotaFloodPreventer{ - Name: "test", - Cacher: cache.NewCacherStub(), - StatusHandlers: []QuotaStatusHandler{&mock.QuotaStatusHandlerStub{}}, - BaseMaxNumMessagesPerPeer: minMessages, - MaxTotalSizePerPeer: minTotalSize, - PercentReserved: 10, - IncreaseThreshold: 0, - IncreaseFactor: 0, + Name: "test", + Cacher: cache.NewCacherStub(), + StatusHandlers: []QuotaStatusHandler{&mock.QuotaStatusHandlerStub{}}, + AntifloodConfigs: &testscommon.AntifloodConfigsHandlerStub{ + GetFloodPreventerConfigByTypeCalled: func(configType common.FloodPreventerType) config.FloodPreventerConfig { + return config.FloodPreventerConfig{ + IntervalInSeconds: 1, + ReservedPercent: 10, + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: minMessages, + TotalSizePerInterval: minTotalSize, + IncreaseFactor: config.IncreaseFactorConfig{ + Threshold: 0, + Factor: 0, + }, + }, + BlackList: config.BlackListConfig{ + ThresholdNumMessagesPerInterval: 10, + ThresholdSizePerInterval: 10, + NumFloodingRounds: 10, + PeerBanDurationInSeconds: 10, + }, + } + }, + }, } } @@ -54,61 +74,6 @@ func TestNewQuotaFloodPreventer_NilStatusHandlerShouldErr(t *testing.T) { assert.Equal(t, process.ErrNilQuotaStatusHandler, err) } -func TestNewQuotaFloodPreventer_LowerMinMessagesPerPeerShouldErr(t *testing.T) { - t.Parallel() - - arg := createDefaultArgument() - arg.BaseMaxNumMessagesPerPeer = minMessages - 1 - qfp, err := NewQuotaFloodPreventer(arg) - - assert.True(t, check.IfNil(qfp)) - assert.True(t, errors.Is(err, process.ErrInvalidValue)) -} - -func TestNewQuotaFloodPreventer_LowerMinSizePerPeerShouldErr(t *testing.T) { - t.Parallel() - - arg := createDefaultArgument() - arg.MaxTotalSizePerPeer = minTotalSize - 1 - qfp, err := NewQuotaFloodPreventer(arg) - - assert.True(t, check.IfNil(qfp)) - assert.True(t, errors.Is(err, process.ErrInvalidValue)) -} - -func TestNewQuotaFloodPreventer_HigherPercentReservedShouldErr(t *testing.T) { - t.Parallel() - - arg := createDefaultArgument() - arg.PercentReserved = maxPercentReserved + 0.01 - qfp, err := NewQuotaFloodPreventer(arg) - - assert.True(t, check.IfNil(qfp)) - assert.True(t, errors.Is(err, process.ErrInvalidValue)) -} - -func TestNewQuotaFloodPreventer_LowerPercentReservedShouldErr(t *testing.T) { - t.Parallel() - - arg := createDefaultArgument() - arg.PercentReserved = minPercentReserved - 0.01 - qfp, err := NewQuotaFloodPreventer(arg) - - assert.True(t, check.IfNil(qfp)) - assert.True(t, errors.Is(err, process.ErrInvalidValue)) -} - -func TestNewQuotaFloodPreventer_NegativeIncreaseFactorShouldErr(t *testing.T) { - t.Parallel() - - arg := createDefaultArgument() - arg.IncreaseFactor = -0.001 - qfp, err := NewQuotaFloodPreventer(arg) - - assert.True(t, check.IfNil(qfp)) - assert.True(t, errors.Is(err, process.ErrInvalidValue)) -} - func TestNewQuotaFloodPreventer_ShouldWork(t *testing.T) { t.Parallel() @@ -154,8 +119,18 @@ func TestNewQuotaFloodPreventer_IncreaseLoadIdentifierNotPresentPutQuotaAndRetur return }, } - arg.BaseMaxNumMessagesPerPeer = minMessages * 4 - arg.MaxTotalSizePerPeer = minTotalSize * 1 + + arg.AntifloodConfigs = &testscommon.AntifloodConfigsHandlerStub{ + GetFloodPreventerConfigByTypeCalled: func(configType common.FloodPreventerType) config.FloodPreventerConfig { + return config.FloodPreventerConfig{ + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: minMessages * 4, + TotalSizePerInterval: minTotalSize * 1, + }, + } + }, + } + qfp, _ := NewQuotaFloodPreventer(arg) err := qfp.IncreaseLoad("identifier", size) @@ -186,8 +161,18 @@ func TestNewQuotaFloodPreventer_IncreaseLoadNotQuotaSavedInCacheShouldPutQuotaAn return }, } - arg.BaseMaxNumMessagesPerPeer = minMessages * 4 - arg.MaxTotalSizePerPeer = minTotalSize * 1 + + arg.AntifloodConfigs = &testscommon.AntifloodConfigsHandlerStub{ + GetFloodPreventerConfigByTypeCalled: func(configType common.FloodPreventerType) config.FloodPreventerConfig { + return config.FloodPreventerConfig{ + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: minMessages * 4, + TotalSizePerInterval: minTotalSize * 1, + }, + } + }, + } + qfp, _ := NewQuotaFloodPreventer(arg) err := qfp.IncreaseLoad("identifier", size) @@ -212,8 +197,18 @@ func TestNewQuotaFloodPreventer_IncreaseLoadUnderMaxValuesShouldIncrementAndRetu return existingQuota, true }, } - arg.BaseMaxNumMessagesPerPeer = minMessages * 10 - arg.MaxTotalSizePerPeer = minTotalSize * 10 + + arg.AntifloodConfigs = &testscommon.AntifloodConfigsHandlerStub{ + GetFloodPreventerConfigByTypeCalled: func(configType common.FloodPreventerType) config.FloodPreventerConfig { + return config.FloodPreventerConfig{ + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: minMessages * 10, + TotalSizePerInterval: minTotalSize * 10, + }, + } + }, + } + qfp, _ := NewQuotaFloodPreventer(arg) err := qfp.IncreaseLoad("identifier", size) @@ -243,8 +238,18 @@ func TestNewQuotaFloodPreventer_IncreaseLoadOverMaxPeerNumMessagesShouldNotPutAn return false }, } - arg.BaseMaxNumMessagesPerPeer = minMessages * 4 - arg.MaxTotalSizePerPeer = minTotalSize * 10 + + arg.AntifloodConfigs = &testscommon.AntifloodConfigsHandlerStub{ + GetFloodPreventerConfigByTypeCalled: func(configType common.FloodPreventerType) config.FloodPreventerConfig { + return config.FloodPreventerConfig{ + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: minMessages * 4, + TotalSizePerInterval: minTotalSize * 10, + }, + } + }, + } + qfp, _ := NewQuotaFloodPreventer(arg) err := qfp.IncreaseLoad("identifier", minTotalSize) @@ -272,8 +277,17 @@ func TestNewQuotaFloodPreventer_IncreaseLoadOverMaxPeerSizeShouldNotPutAndReturn return false }, } - arg.BaseMaxNumMessagesPerPeer = minMessages * 4 - arg.MaxTotalSizePerPeer = minTotalSize * 10 + + arg.AntifloodConfigs = &testscommon.AntifloodConfigsHandlerStub{ + GetFloodPreventerConfigByTypeCalled: func(configType common.FloodPreventerType) config.FloodPreventerConfig { + return config.FloodPreventerConfig{ + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: minMessages * 4, + TotalSizePerInterval: minTotalSize * 10, + }, + } + }, + } qfp, _ := NewQuotaFloodPreventer(arg) err := qfp.IncreaseLoad("identifier", minTotalSize) @@ -421,21 +435,34 @@ func TestNewQuotaFloodPreventer_IncreaseLoadWithMockCacherShouldWork(t *testing. numMessages := uint32(100) arg := createDefaultArgument() arg.Cacher = cache.NewCacherMock() - arg.BaseMaxNumMessagesPerPeer = numMessages - arg.MaxTotalSizePerPeer = math.MaxUint64 - arg.PercentReserved = float32(17) + + percentReserved := float32(17) + + arg.AntifloodConfigs = &testscommon.AntifloodConfigsHandlerStub{ + GetFloodPreventerConfigByTypeCalled: func(configType common.FloodPreventerType) config.FloodPreventerConfig { + return config.FloodPreventerConfig{ + ReservedPercent: percentReserved, + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: numMessages, + TotalSizePerInterval: math.MaxUint64, + IncreaseFactor: config.IncreaseFactorConfig{}, + }, + } + }, + } + qfp, _ := NewQuotaFloodPreventer(arg) identifier := core.PeerID("id") - for i := uint32(0); i < numMessages-uint32(arg.PercentReserved); i++ { + for i := uint32(0); i < numMessages-uint32(percentReserved); i++ { err := qfp.IncreaseLoad(identifier, 1) assert.Nil(t, err, fmt.Sprintf("failed at the %d iteration", i)) } - for i := uint32(0); i < uint32(arg.PercentReserved)*2; i++ { + for i := uint32(0); i < uint32(percentReserved)*2; i++ { err := qfp.IncreaseLoad(identifier, 1) assert.True(t, errors.Is(err, process.ErrSystemBusy), - fmt.Sprintf("failed at the %d iteration", numMessages-uint32(arg.PercentReserved)+i)) + fmt.Sprintf("failed at the %d iteration", numMessages-uint32(percentReserved)+i)) } } @@ -445,25 +472,50 @@ func TestQuotaFloodPreventer_ApplyConsensusSizeInvalidConsensusSize(t *testing.T t.Parallel() arg := createDefaultArgument() - arg.BaseMaxNumMessagesPerPeer = uint32(4682) + baseMaxNumMessagesPerPeer := uint32(4682) + + arg.AntifloodConfigs = &testscommon.AntifloodConfigsHandlerStub{ + GetFloodPreventerConfigByTypeCalled: func(configType common.FloodPreventerType) config.FloodPreventerConfig { + return config.FloodPreventerConfig{ + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: baseMaxNumMessagesPerPeer, + }, + } + }, + } + qfp, _ := NewQuotaFloodPreventer(arg) qfp.ApplyConsensusSize(0) - assert.Equal(t, arg.BaseMaxNumMessagesPerPeer, qfp.computedMaxNumMessagesPerPeer) + assert.Equal(t, baseMaxNumMessagesPerPeer, qfp.computedMaxNumMessagesPerPeer) } func TestQuotaFloodPreventer_ApplyConsensusUnderThreshold(t *testing.T) { t.Parallel() arg := createDefaultArgument() - arg.BaseMaxNumMessagesPerPeer = uint32(4682) - arg.IncreaseThreshold = 100 + + baseMaxNumMessagesPerPeer := uint32(4682) + + arg.AntifloodConfigs = &testscommon.AntifloodConfigsHandlerStub{ + GetFloodPreventerConfigByTypeCalled: func(configType common.FloodPreventerType) config.FloodPreventerConfig { + return config.FloodPreventerConfig{ + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: baseMaxNumMessagesPerPeer, + IncreaseFactor: config.IncreaseFactorConfig{ + Threshold: 100, + }, + }, + } + }, + } + qfp, _ := NewQuotaFloodPreventer(arg) qfp.ApplyConsensusSize(99) - assert.Equal(t, arg.BaseMaxNumMessagesPerPeer, qfp.computedMaxNumMessagesPerPeer) + assert.Equal(t, baseMaxNumMessagesPerPeer, qfp.computedMaxNumMessagesPerPeer) } func TestQuotaFloodPreventer_ApplyConsensusShouldWork(t *testing.T) { @@ -471,9 +523,23 @@ func TestQuotaFloodPreventer_ApplyConsensusShouldWork(t *testing.T) { arg := createDefaultArgument() arg.Cacher = cache.NewCacherMock() - arg.BaseMaxNumMessagesPerPeer = 2000 - arg.IncreaseThreshold = 1000 - arg.IncreaseFactor = 0.25 + + arg.AntifloodConfigs = &testscommon.AntifloodConfigsHandlerStub{ + GetFloodPreventerConfigByTypeCalled: func(configType common.FloodPreventerType) config.FloodPreventerConfig { + return config.FloodPreventerConfig{ + IntervalInSeconds: 1, + ReservedPercent: 10, + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: 2000, + IncreaseFactor: config.IncreaseFactorConfig{ + Threshold: 1000, + Factor: 0.25, + }, + }, + } + }, + } + qfp, _ := NewQuotaFloodPreventer(arg) absoluteExpected := uint32(2250) qfp.ApplyConsensusSize(2000) diff --git a/process/track/argBlockProcessor.go b/process/track/argBlockProcessor.go index 6194e1c5e60..1a976080ed7 100644 --- a/process/track/argBlockProcessor.go +++ b/process/track/argBlockProcessor.go @@ -24,7 +24,9 @@ type ArgBlockProcessor struct { SelfNotarizedHeadersNotifier blockNotifierHandler FinalMetachainHeadersNotifier blockNotifierHandler RoundHandler process.RoundHandler + ProcessConfigsHandler common.ProcessConfigsHandler EnableEpochsHandler common.EnableEpochsHandler + EnableRoundsHandler common.EnableRoundsHandler ProofsPool process.ProofsPool Marshaller marshal.Marshalizer Hasher hashing.Hasher diff --git a/process/track/argBlockTrack.go b/process/track/argBlockTrack.go index e3512966d00..43ae7837f6b 100644 --- a/process/track/argBlockTrack.go +++ b/process/track/argBlockTrack.go @@ -26,8 +26,10 @@ type ArgBaseTracker struct { WhitelistHandler process.WhiteListHandler FeeHandler process.FeeHandler EnableEpochsHandler common.EnableEpochsHandler + EnableRoundsHandler common.EnableRoundsHandler ProofsPool process.ProofsPool EpochChangeGracePeriodHandler common.EpochChangeGracePeriodHandler + ProcessConfigsHandler common.ProcessConfigsHandler IsImportDBMode bool } diff --git a/process/track/baseBlockTrack.go b/process/track/baseBlockTrack.go index e99be3c9fa7..424671e981f 100644 --- a/process/track/baseBlockTrack.go +++ b/process/track/baseBlockTrack.go @@ -9,7 +9,6 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" - "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" logger "github.com/multiversx/mx-chain-logger-go" @@ -24,6 +23,8 @@ var _ process.ValidityAttester = (*baseBlockTrack)(nil) var log = logger.GetOrCreate("process/track") +const maxNonceDifference = 3 // TODO move this to a config file + // HeaderInfo holds the information about a header type HeaderInfo struct { Hash []byte @@ -51,7 +52,11 @@ type baseBlockTrack struct { whitelistHandler process.WhiteListHandler feeHandler process.FeeHandler enableEpochsHandler common.EnableEpochsHandler + enableRoundsHandler common.EnableRoundsHandler epochChangeGracePeriodHandler common.EpochChangeGracePeriodHandler + processConfigsHandler common.ProcessConfigsHandler + ownShardTracker OwnShardTrackerHandler + requestHandler process.RequestHandler mutHeaders sync.RWMutex headers map[uint32]map[uint64][]*HeaderInfo @@ -101,6 +106,11 @@ func createBaseBlockTrack(arguments ArgBaseTracker) (*baseBlockTrack, error) { return nil, err } + tracker, err := NewOwnShardTracker(arguments.EnableEpochsHandler, maxNonceDifference) + if err != nil { + return nil, fmt.Errorf("failed to create own shard tracker: %w", err) + } + bbt := &baseBlockTrack{ hasher: arguments.Hasher, headerValidator: arguments.HeaderValidator, @@ -121,7 +131,11 @@ func createBaseBlockTrack(arguments ArgBaseTracker) (*baseBlockTrack, error) { whitelistHandler: arguments.WhitelistHandler, feeHandler: arguments.FeeHandler, enableEpochsHandler: arguments.EnableEpochsHandler, + enableRoundsHandler: arguments.EnableRoundsHandler, epochChangeGracePeriodHandler: arguments.EpochChangeGracePeriodHandler, + processConfigsHandler: arguments.ProcessConfigsHandler, + ownShardTracker: tracker, + requestHandler: arguments.RequestHandler, } return bbt, nil @@ -135,7 +149,8 @@ func (bbt *baseBlockTrack) receivedProof(proof data.HeaderProofHandler) { headerHash := proof.GetHeaderHash() header, err := bbt.getHeaderForProof(proof) if err != nil { - log.Debug("baseBlockTrack.receivedProof with missing header", "headerHash", headerHash) + log.Debug("baseBlockTrack.receivedProof with missing header, requesting it", "headerHash", headerHash) + bbt.requestHeaderForProof(proof) return } log.Debug("received proof from network in block tracker", @@ -149,6 +164,15 @@ func (bbt *baseBlockTrack) receivedProof(proof data.HeaderProofHandler) { bbt.receivedHeader(header, headerHash) } +func (bbt *baseBlockTrack) requestHeaderForProof(proof data.HeaderProofHandler) { + if proof.GetHeaderShardId() == common.MetachainShardId { + bbt.requestHandler.RequestMetaHeader(proof.GetHeaderHash()) + return + } + + bbt.requestHandler.RequestShardHeader(proof.GetHeaderShardId(), proof.GetHeaderHash()) +} + func (bbt *baseBlockTrack) getHeaderForProof(proof data.HeaderProofHandler) (data.HeaderHandler, error) { return process.GetHeader(proof.GetHeaderHash(), bbt.headersPool, bbt.store, bbt.marshalizer, proof.GetHeaderShardId()) } @@ -198,9 +222,9 @@ func (bbt *baseBlockTrack) receivedShardHeader(headerHandler data.HeaderHandler, } func (bbt *baseBlockTrack) receivedMetaBlock(headerHandler data.HeaderHandler, metaBlockHash []byte) { - metaBlock, ok := headerHandler.(*block.MetaBlock) + metaBlock, ok := headerHandler.(data.MetaHeaderHandler) if !ok { - log.Warn("cannot convert data.HeaderHandler in *block.Metablock") + log.Warn("cannot convert data.HeaderHandler in data.MetaHeaderHandler") return } @@ -449,11 +473,24 @@ func (bbt *baseBlockTrack) CheckBlockAgainstRoundHandler(headerHandler data.Head return process.ErrNilHeaderHandler } + return bbt.checkAgainstRoundHandler(headerHandler.GetRound()) +} + +// CheckProofAgainstRoundHandler verifies the provided proof against the roundHandler's current round +func (bbt *baseBlockTrack) CheckProofAgainstRoundHandler(proof data.HeaderProofHandler) error { + if check.IfNil(proof) { + return process.ErrNilHeaderProof + } + + return bbt.checkAgainstRoundHandler(proof.GetHeaderRound()) +} + +func (bbt *baseBlockTrack) checkAgainstRoundHandler(round uint64) error { nextRound := bbt.roundHandler.Index() + 1 - if int64(headerHandler.GetRound()) > nextRound { + if int64(round) > nextRound { return fmt.Errorf("%w header round: %d, next chronology round: %d", process.ErrHigherRoundInBlock, - headerHandler.GetRound(), + round, nextRound) } @@ -466,39 +503,52 @@ func (bbt *baseBlockTrack) CheckBlockAgainstFinal(headerHandler data.HeaderHandl return process.ErrNilHeaderHandler } - finalHeader, _, err := bbt.getFinalHeader(headerHandler.GetShardID()) + return bbt.checkAgainstFinal(headerHandler.GetShardID(), headerHandler.GetRound(), headerHandler.GetNonce()) +} + +// CheckProofAgainstFinal checks if the given proof is valid related to the final header +func (bbt *baseBlockTrack) CheckProofAgainstFinal(proof data.HeaderProofHandler) error { + if check.IfNil(proof) { + return process.ErrNilHeaderProof + } + + return bbt.checkAgainstFinal(proof.GetHeaderShardId(), proof.GetHeaderRound(), proof.GetHeaderNonce()) +} + +func (bbt *baseBlockTrack) checkAgainstFinal(shardID uint32, round uint64, nonce uint64) error { + finalHeader, _, err := bbt.getFinalHeader(shardID) if err != nil { - return fmt.Errorf("%w: header shard: %d, header round: %d, header nonce: %d", + return fmt.Errorf("%w: shard: %d, round: %d, nonce: %d", err, - headerHandler.GetShardID(), - headerHandler.GetRound(), - headerHandler.GetNonce()) + shardID, + round, + nonce) } - roundDif := int64(headerHandler.GetRound()) - int64(finalHeader.GetRound()) - nonceDif := int64(headerHandler.GetNonce()) - int64(finalHeader.GetNonce()) + roundDif := int64(round) - int64(finalHeader.GetRound()) + nonceDif := int64(nonce) - int64(finalHeader.GetNonce()) if roundDif < 0 { - return fmt.Errorf("%w for header round: %d, final header round: %d", + return fmt.Errorf("%w for round: %d, final header round: %d", process.ErrLowerRoundInBlock, - headerHandler.GetRound(), + round, finalHeader.GetRound()) } if nonceDif < 0 { - return fmt.Errorf("%w for header nonce: %d, final header nonce: %d", + return fmt.Errorf("%w for nonce: %d, final header nonce: %d", process.ErrLowerNonceInBlock, - headerHandler.GetNonce(), + nonce, finalHeader.GetNonce()) } if roundDif < nonceDif { return fmt.Errorf("%w for "+ - "header round: %d, final header round: %d, round dif: %d"+ - "header nonce: %d, final header nonce: %d, nonce dif: %d", + "round: %d, final header round: %d, round dif: %d, "+ + "nonce: %d, final header nonce: %d, nonce dif: %d", process.ErrHigherNonceInBlock, - headerHandler.GetRound(), + round, finalHeader.GetRound(), roundDif, - headerHandler.GetNonce(), + nonce, finalHeader.GetNonce(), nonceDif) } @@ -514,8 +564,8 @@ func (bbt *baseBlockTrack) getFinalHeader(shardID uint32) (data.HeaderHandler, [ return bbt.selfNotarizer.GetFirstNotarizedHeader(shardID) } -// CheckBlockAgainstWhitelist returns if the provided intercepted data (blocks) is whitelisted or not -func (bbt *baseBlockTrack) CheckBlockAgainstWhitelist(interceptedData process.InterceptedData) bool { +// CheckAgainstWhitelist returns if the provided intercepted data is whitelisted or not +func (bbt *baseBlockTrack) CheckAgainstWhitelist(interceptedData process.InterceptedData) bool { return bbt.whitelistHandler.IsWhiteListed(interceptedData) } @@ -654,9 +704,12 @@ func (bbt *baseBlockTrack) ShouldSkipMiniBlocksCreationFromSelf() bool { return false } + currentEpoch := bbt.enableEpochsHandler.GetCurrentEpoch() + maxMetaNoncesBehind := bbt.processConfigsHandler.GetMaxMetaNoncesBehindForGlobalStuckByEpoch(currentEpoch) + shards := bbt.shardCoordinator.NumberOfShards() for shardID := uint32(0); shardID < shards; shardID++ { - if bbt.isShardBehind(shardID, process.MaxMetaNoncesBehindForGlobalStuck) { + if bbt.isShardBehind(shardID, uint64(maxMetaNoncesBehind)) { return true } } @@ -674,7 +727,9 @@ func (bbt *baseBlockTrack) IsShardStuck(shardID uint32) bool { return bbt.isMetaStuck() } - return bbt.isShardBehind(shardID, process.MaxMetaNoncesBehind) + currentEpoch := bbt.enableEpochsHandler.GetCurrentEpoch() + maxMetaNoncesBehind := bbt.processConfigsHandler.GetMaxMetaNoncesBehindByEpoch(currentEpoch) + return bbt.isShardBehind(shardID, uint64(maxMetaNoncesBehind)) } func (bbt *baseBlockTrack) isShardBehind(shardID uint32, maxMetaNoncesBehind uint64) bool { @@ -739,7 +794,11 @@ func (bbt *baseBlockTrack) computeMetaBlocksBehind() int64 { func (bbt *baseBlockTrack) isMetaStuck() bool { noncesBehind := bbt.computeMetaBlocksBehind() - isMetaStuck := noncesBehind > process.MaxShardNoncesBehind + + currentEpoch := bbt.enableEpochsHandler.GetCurrentEpoch() + maxShardNoncesBehind := bbt.processConfigsHandler.GetMaxShardNoncesBehindByEpoch(currentEpoch) + + isMetaStuck := noncesBehind > int64(maxShardNoncesBehind) return isMetaStuck } @@ -839,6 +898,9 @@ func checkTrackerNilParameters(arguments ArgBaseTracker) error { if check.IfNil(arguments.EpochChangeGracePeriodHandler) { return process.ErrNilEpochChangeGracePeriodHandler } + if check.IfNil(arguments.ProcessConfigsHandler) { + return process.ErrNilProcessConfigsHandler + } return nil } @@ -952,3 +1014,13 @@ func (bbt *baseBlockTrack) isHeaderOutOfRange(headerHandler data.HeaderHandler) isHeaderOutOfRange := headerHandler.GetNonce() > lastCrossNotarizedHeader.GetNonce()+process.MaxHeadersToWhitelistInAdvance return isHeaderOutOfRange } + +// ComputeOwnShardStuck checks if the own shard is stuck and updates the own shard tracker accordingly +func (bbt *baseBlockTrack) ComputeOwnShardStuck(lastExecutionResultsInfo data.BaseExecutionResultHandler, currentNonce uint64) { + bbt.ownShardTracker.ComputeOwnShardStuck(lastExecutionResultsInfo, currentNonce) +} + +// IsOwnShardStuck returns true if the own shard is stuck, false otherwise +func (bbt *baseBlockTrack) IsOwnShardStuck() bool { + return bbt.ownShardTracker.IsOwnShardStuck() +} diff --git a/process/track/baseBlockTrack_test.go b/process/track/baseBlockTrack_test.go index 4846ef5e8c5..efc9d0ccc67 100644 --- a/process/track/baseBlockTrack_test.go +++ b/process/track/baseBlockTrack_test.go @@ -10,10 +10,12 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" logger "github.com/multiversx/mx-chain-logger-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common/configs" "github.com/multiversx/mx-chain-go/common/graceperiod" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" @@ -34,6 +36,8 @@ import ( const maxGasLimitPerBlock = uint64(1500000000) const maxGasLimitPerMiniBlock = uint64(250000000) +const maxMetaNoncesBehind = 15 +const maxShardNoncesBehind = 15 func createGenesisBlocks(shardCoordinator sharding.Coordinator) map[uint32]data.HeaderHandler { genesisBlocks := make(map[uint32]data.HeaderHandler) @@ -132,6 +136,28 @@ func CreateShardTrackerMockArguments() track.ArgShardTracker { GracePeriodInRounds: 1, }}) + processConfigsByEpoch, _ := configs.NewProcessConfigsHandler([]config.ProcessConfigByEpoch{{ + EnableEpoch: 0, + MaxMetaNoncesBehind: 15, + MaxMetaNoncesBehindForGlobalStuck: 30, + MaxShardNoncesBehind: 15, + }}, + []config.ProcessConfigByRound{ + { + EnableRound: 0, + MaxRoundsWithoutNewBlockReceived: 10, + MaxRoundsToKeepUnprocessedTransactions: 50, + MaxRoundsToKeepUnprocessedMiniBlocks: 50, + NumFloodingRoundsSlowReacting: 20, + NumFloodingRoundsFastReacting: 30, + NumFloodingRoundsOutOfSpecs: 40, + MaxConsecutiveRoundsOfRatingDecrease: 600, + MaxBlockProcessingTimeMs: 1000, + }, + }, + &epochNotifier.RoundNotifierStub{}, + ) + arguments := track.ArgShardTracker{ ArgBaseTracker: track.ArgBaseTracker{ Hasher: &hashingMocks.HasherMock{}, @@ -150,7 +176,9 @@ func CreateShardTrackerMockArguments() track.ArgShardTracker { return false }, }, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, EpochChangeGracePeriodHandler: epochChangeGracePeriod, + ProcessConfigsHandler: processConfigsByEpoch, ProofsPool: &dataRetrieverMock.ProofsPoolMock{}, }, } @@ -203,7 +231,9 @@ func CreateMetaTrackerMockArguments() track.ArgMetaTracker { return false }, }, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, EpochChangeGracePeriodHandler: epochChangeGracePeriod, + ProcessConfigsHandler: testscommon.GetDefaultProcessConfigsHandler(), ProofsPool: &dataRetrieverMock.ProofsPoolMock{}, }, } @@ -1828,7 +1858,7 @@ func TestIsShardStuck_ShouldReturnFalseWhenMetaIsNotStuck(t *testing.T) { shardArguments := CreateShardTrackerMockArguments() sbt, _ := track.NewShardBlockTrack(shardArguments) - sbt.AddSelfNotarizedHeader(0, &block.Header{Nonce: nonce + process.MaxShardNoncesBehind}, nil) + sbt.AddSelfNotarizedHeader(0, &block.Header{Nonce: nonce + maxShardNoncesBehind}, nil) sbt.AddSelfNotarizedHeader(shardID, &block.Header{Nonce: nonce}, nil) assert.False(t, sbt.IsShardStuck(shardID)) @@ -1842,7 +1872,7 @@ func TestIsShardStuck_ShouldReturnTrueWhenMetaIsStuck(t *testing.T) { shardArguments := CreateShardTrackerMockArguments() sbt, _ := track.NewShardBlockTrack(shardArguments) - sbt.AddSelfNotarizedHeader(0, &block.Header{Nonce: nonce + process.MaxShardNoncesBehind + 1}, nil) + sbt.AddSelfNotarizedHeader(0, &block.Header{Nonce: nonce + maxShardNoncesBehind + 1}, nil) sbt.AddSelfNotarizedHeader(shardID, &block.Header{Nonce: nonce}, nil) assert.True(t, sbt.IsShardStuck(shardID)) @@ -1858,7 +1888,7 @@ func TestIsShardStuck_ShouldReturnFalseWhenLastShardProcessedMetaNonceIsZero(t * sbt.SetLastShardProcessedMetaNonce(shardID, nonce) - sbt.AddTrackedHeader(&block.MetaBlock{Nonce: nonce + process.MaxMetaNoncesBehind + 1}, []byte("hash")) + sbt.AddTrackedHeader(&block.MetaBlock{Nonce: nonce + maxMetaNoncesBehind + 1}, []byte("hash")) assert.False(t, sbt.IsShardStuck(shardID)) } @@ -1872,10 +1902,10 @@ func TestIsShardStuck_ShouldWorkOnLastShardProcessedMetaNonceDifferences(t *test sbt.SetLastShardProcessedMetaNonce(shardID, nonce) - sbt.AddTrackedHeader(&block.MetaBlock{Nonce: nonce + process.MaxMetaNoncesBehind}, []byte("hash")) + sbt.AddTrackedHeader(&block.MetaBlock{Nonce: nonce + maxMetaNoncesBehind}, []byte("hash")) assert.False(t, sbt.IsShardStuck(shardID)) - sbt.AddTrackedHeader(&block.MetaBlock{Nonce: nonce + process.MaxMetaNoncesBehind + 1}, []byte("hash")) + sbt.AddTrackedHeader(&block.MetaBlock{Nonce: nonce + maxMetaNoncesBehind + 1}, []byte("hash")) assert.True(t, sbt.IsShardStuck(shardID)) } @@ -1894,7 +1924,7 @@ func TestIsShardStuck_ShouldWork(t *testing.T) { Nonce: 1, ShardInfo: []block.ShardData{ { - NumPendingMiniBlocks: uint32(maxNumMiniBlocksForSameReceiverInOneBlock*process.MaxMetaNoncesBehind*uint64(shardArguments.ShardCoordinator.NumberOfShards()) - 1), + NumPendingMiniBlocks: uint32(maxNumMiniBlocksForSameReceiverInOneBlock*maxMetaNoncesBehind*uint64(shardArguments.ShardCoordinator.NumberOfShards()) - 1), }, }, PrevHash: startHeaderHash, @@ -1907,7 +1937,7 @@ func TestIsShardStuck_ShouldWork(t *testing.T) { Nonce: 2, ShardInfo: []block.ShardData{ { - NumPendingMiniBlocks: uint32(maxNumMiniBlocksForSameReceiverInOneBlock * process.MaxMetaNoncesBehind * uint64(shardArguments.ShardCoordinator.NumberOfShards())), + NumPendingMiniBlocks: uint32(maxNumMiniBlocksForSameReceiverInOneBlock * maxMetaNoncesBehind * uint64(shardArguments.ShardCoordinator.NumberOfShards())), }, }, PrevHash: hdr1Hash, @@ -2826,7 +2856,7 @@ func TestMetaBlockTrack_GetTrackedMetaBlockWithHashShouldWork(t *testing.T) { metaBlock, err = mbt.GetTrackedMetaBlockWithHash(hash) assert.Nil(t, err) - assert.Equal(t, nonce+1, metaBlock.Nonce) + assert.Equal(t, nonce+1, metaBlock.GetNonce()) } func TestShardBlockTrack_GetTrackedShardHeaderWithNonceAndHashShouldWork(t *testing.T) { @@ -2856,3 +2886,116 @@ func TestShardBlockTrack_GetTrackedShardHeaderWithNonceAndHashShouldWork(t *test assert.Equal(t, nonce, header.GetNonce()) assert.Equal(t, shardID, header.GetShardID()) } + +func TestBaseBlockTrack_OwnShardStuck(t *testing.T) { + t.Parallel() + + args := CreateShardTrackerMockArguments() + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return true + }, + } + sbt, _ := track.NewShardBlockTrack(args) + require.NotNil(t, sbt) + + require.False(t, sbt.IsOwnShardStuck()) + + currentNonce := uint64(100) + lastExecResult := &block.BaseExecutionResult{ + HeaderNonce: 50, + } + sbt.ComputeOwnShardStuck(lastExecResult, currentNonce) + require.True(t, sbt.IsOwnShardStuck()) +} + +func TestBaseBlockTrack_receivedProof(t *testing.T) { + t.Parallel() + + t.Run("missing meta header should request it", func(t *testing.T) { + t.Parallel() + + providedMetaProof := &block.HeaderProof{ + HeaderHash: []byte("header hash"), + HeaderShardId: core.MetachainShardId, + } + args := CreateShardTrackerMockArguments() + wasRequestCalled := false + args.RequestHandler = &testscommon.RequestHandlerStub{ + RequestMetaHeaderCalled: func(hash []byte) { + require.Equal(t, string(providedMetaProof.HeaderHash), string(hash)) + wasRequestCalled = true + }, + } + + sbt, _ := track.NewShardBlockTrack(args) + require.NotNil(t, sbt) + + sbt.ReceivedProof(nil) // coverage only + sbt.ReceivedProof(providedMetaProof) + require.True(t, wasRequestCalled) + }) + t.Run("missing shard header should request it", func(t *testing.T) { + t.Parallel() + + providedShardProof := &block.HeaderProof{ + HeaderHash: []byte("header hash"), + HeaderShardId: 0, + } + args := CreateShardTrackerMockArguments() + wasRequestCalled := false + args.RequestHandler = &testscommon.RequestHandlerStub{ + RequestShardHeaderCalled: func(shardID uint32, hash []byte) { + require.Equal(t, string(providedShardProof.HeaderHash), string(hash)) + wasRequestCalled = true + }, + } + + sbt, _ := track.NewShardBlockTrack(args) + require.NotNil(t, sbt) + + sbt.ReceivedProof(providedShardProof) + require.True(t, wasRequestCalled) + }) + t.Run("should work and call received header", func(t *testing.T) { + t.Parallel() + + providedShardProof := &block.HeaderProof{ + HeaderHash: []byte("header hash"), + HeaderShardId: 0, + HeaderNonce: 1, + } + args := CreateShardTrackerMockArguments() + wasHasProofCalled := false + args.PoolsHolder = &dataRetrieverMock.PoolsHolderStub{ + HeadersCalled: func() dataRetriever.HeadersPool { + return &mock.HeadersCacherStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.Header{ + Nonce: 1, + }, nil + }, + } + }, + ProofsCalled: func() dataRetriever.ProofsPool { + return &dataRetrieverMock.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + wasHasProofCalled = true + return true + }, + } + }, + } + args.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + } + + sbt, _ := track.NewShardBlockTrack(args) + require.NotNil(t, sbt) + + sbt.ReceivedProof(providedShardProof) + require.True(t, wasHasProofCalled) + }) +} diff --git a/process/track/blockNotarizer.go b/process/track/blockNotarizer.go index 0d6b581fd6e..6520d3ff810 100644 --- a/process/track/blockNotarizer.go +++ b/process/track/blockNotarizer.go @@ -9,6 +9,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/sharding" ) diff --git a/process/track/blockProcessor.go b/process/track/blockProcessor.go index 04eb126b077..c27151b56d3 100644 --- a/process/track/blockProcessor.go +++ b/process/track/blockProcessor.go @@ -28,8 +28,10 @@ type blockProcessor struct { selfNotarizedHeadersNotifier blockNotifierHandler finalMetachainHeadersNotifier blockNotifierHandler roundHandler process.RoundHandler + processConfigsHandler common.ProcessConfigsHandler enableEpochsHandler common.EnableEpochsHandler + enableRoundsHandler common.EnableRoundsHandler proofsPool process.ProofsPool marshaller marshal.Marshalizer hasher hashing.Hasher @@ -58,7 +60,9 @@ func NewBlockProcessor(arguments ArgBlockProcessor) (*blockProcessor, error) { selfNotarizedHeadersNotifier: arguments.SelfNotarizedHeadersNotifier, finalMetachainHeadersNotifier: arguments.FinalMetachainHeadersNotifier, roundHandler: arguments.RoundHandler, + processConfigsHandler: arguments.ProcessConfigsHandler, enableEpochsHandler: arguments.EnableEpochsHandler, + enableRoundsHandler: arguments.EnableRoundsHandler, proofsPool: arguments.ProofsPool, headersPool: arguments.HeadersPool, marshaller: arguments.Marshaller, @@ -454,7 +458,7 @@ func (bp *blockProcessor) requestHeadersIfNothingNewIsReceived( return } - shouldRequestHeaders := bp.roundHandler.Index()-int64(highestRoundInReceivedHeaders) > process.MaxRoundsWithoutNewBlockReceived && + shouldRequestHeaders := bp.roundHandler.Index()-int64(highestRoundInReceivedHeaders) > bp.getMaxRoundsWithoutBlockReceived(highestRoundInReceivedHeaders) && int64(latestValidHeader.GetNonce())-int64(lastNotarizedHeaderNonce) <= process.MaxHeadersToRequestInAdvance if !shouldRequestHeaders { return @@ -478,6 +482,10 @@ func (bp *blockProcessor) requestHeadersIfNothingNewIsReceived( bp.requestHeaders(shardID, fromNonce) } +func (bp *blockProcessor) getMaxRoundsWithoutBlockReceived(round uint64) int64 { + return int64(bp.processConfigsHandler.GetMaxRoundsWithoutNewBlockReceivedByRound(round)) +} + func (bp *blockProcessor) requestHeaders(shardID uint32, fromNonce uint64) { toNonce := fromNonce + bp.blockFinality for nonce := fromNonce; nonce <= toNonce; nonce++ { @@ -551,6 +559,9 @@ func checkBlockProcessorNilParameters(arguments ArgBlockProcessor) error { if check.IfNil(arguments.HeadersPool) { return process.ErrNilHeadersDataPool } + if check.IfNil(arguments.ProcessConfigsHandler) { + return process.ErrNilProcessConfigsHandler + } return nil } diff --git a/process/track/blockProcessor_test.go b/process/track/blockProcessor_test.go index 4ab8bda8e15..b6acae95ad4 100644 --- a/process/track/blockProcessor_test.go +++ b/process/track/blockProcessor_test.go @@ -26,6 +26,8 @@ import ( "github.com/multiversx/mx-chain-go/process/track" ) +const defaultMaxRoundsWithoutNewBlockReceived = 10 + func CreateBlockProcessorMockArguments() track.ArgBlockProcessor { shardCoordinatorMock := mock.NewMultipleShardsCoordinatorMock() argsHeaderValidator := processBlock.ArgsHeaderValidator{ @@ -62,12 +64,14 @@ func CreateBlockProcessorMockArguments() track.ArgBlockProcessor { return 1 }, }, - RoundHandler: &mock.RoundHandlerMock{}, - EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, - ProofsPool: &dataRetriever.ProofsPoolMock{}, - Marshaller: &testscommon.MarshallerStub{}, - Hasher: &hashingMocks.HasherMock{}, - HeadersPool: &pool.HeadersPoolStub{}, + RoundHandler: &mock.RoundHandlerMock{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, + ProofsPool: &dataRetriever.ProofsPoolMock{}, + Marshaller: &testscommon.MarshallerStub{}, + Hasher: &hashingMocks.HasherMock{}, + HeadersPool: &pool.HeadersPoolStub{}, + ProcessConfigsHandler: testscommon.GetDefaultProcessConfigsHandler(), } return arguments @@ -960,7 +964,7 @@ func testRequestHeaders(t *testing.T, roundIndex uint64, round uint64, nonce uin } blockProcessorArguments.RoundHandler = &mock.RoundHandlerMock{ - RoundIndex: process.MaxRoundsWithoutNewBlockReceived + int64(roundIndex), + RoundIndex: defaultMaxRoundsWithoutNewBlockReceived + int64(roundIndex), } bp, _ := track.NewBlockProcessor(blockProcessorArguments) @@ -1006,7 +1010,7 @@ func TestRequestHeadersIfNothingNewIsReceived_ShouldRequestIfHighestRoundFromRec } blockProcessorArguments.RoundHandler = &mock.RoundHandlerMock{ - RoundIndex: process.MaxRoundsWithoutNewBlockReceived + 4, + RoundIndex: defaultMaxRoundsWithoutNewBlockReceived + 4, } bp, _ := track.NewBlockProcessor(blockProcessorArguments) diff --git a/process/track/export_test.go b/process/track/export_test.go index 8cbcccb2919..9c251bfc7f2 100644 --- a/process/track/export_test.go +++ b/process/track/export_test.go @@ -39,7 +39,7 @@ func (sbt *shardBlockTrack) GetTrackedShardHeaderWithNonceAndHash(shardID uint32 // metaBlockTrack // GetTrackedMetaBlockWithHash - -func (mbt *metaBlockTrack) GetTrackedMetaBlockWithHash(hash []byte) (*block.MetaBlock, error) { +func (mbt *metaBlockTrack) GetTrackedMetaBlockWithHash(hash []byte) (data.MetaHeaderHandler, error) { return mbt.getTrackedMetaBlockWithHash(hash) } @@ -50,6 +50,11 @@ func (bbt *baseBlockTrack) ReceivedHeader(headerHandler data.HeaderHandler, head bbt.receivedHeader(headerHandler, headerHash) } +// ReceivedProof - +func (bbt *baseBlockTrack) ReceivedProof(proof data.HeaderProofHandler) { + bbt.receivedProof(proof) +} + // CheckTrackerNilParameters - func CheckTrackerNilParameters(arguments ArgBaseTracker) error { return checkTrackerNilParameters(arguments) diff --git a/process/track/interface.go b/process/track/interface.go index 1dbfa2caa2c..77c153e0786 100644 --- a/process/track/interface.go +++ b/process/track/interface.go @@ -55,3 +55,9 @@ type KeysHandler interface { ResetRoundsWithoutReceivedMessages(pkBytes []byte, pid core.PeerID) IsInterfaceNil() bool } + +// OwnShardTrackerHandler defines the operations implemented by a component that will track the own shard +type OwnShardTrackerHandler interface { + ComputeOwnShardStuck(lastExecutionResultsInfo data.BaseExecutionResultHandler, currentNonce uint64) + IsOwnShardStuck() bool +} diff --git a/process/track/metaBlockTrack.go b/process/track/metaBlockTrack.go index b15ed33b383..9c73ed9ba09 100644 --- a/process/track/metaBlockTrack.go +++ b/process/track/metaBlockTrack.go @@ -5,7 +5,6 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" - "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-go/process" ) @@ -47,7 +46,9 @@ func NewMetaBlockTrack(arguments ArgMetaTracker) (*metaBlockTrack, error) { SelfNotarizedHeadersNotifier: bbt.selfNotarizedHeadersNotifier, FinalMetachainHeadersNotifier: bbt.finalMetachainHeadersNotifier, RoundHandler: arguments.RoundHandler, + ProcessConfigsHandler: arguments.ProcessConfigsHandler, EnableEpochsHandler: arguments.EnableEpochsHandler, + EnableRoundsHandler: arguments.EnableRoundsHandler, ProofsPool: arguments.ProofsPool, Marshaller: arguments.Marshalizer, Hasher: arguments.Hasher, @@ -97,14 +98,14 @@ func (mbt *metaBlockTrack) GetSelfHeaders(headerHandler data.HeaderHandler) []*H return selfMetaBlocksInfo } -func (mbt *metaBlockTrack) getTrackedMetaBlockWithHash(hash []byte) (*block.MetaBlock, error) { +func (mbt *metaBlockTrack) getTrackedMetaBlockWithHash(hash []byte) (data.MetaHeaderHandler, error) { metaBlocks, metaBlocksHashes := mbt.GetTrackedHeaders(core.MetachainShardId) for i := 0; i < len(metaBlocks); i++ { if !bytes.Equal(metaBlocksHashes[i], hash) { continue } - metaBlock, ok := metaBlocks[i].(*block.MetaBlock) + metaBlock, ok := metaBlocks[i].(data.MetaHeaderHandler) if !ok { return nil, process.ErrWrongTypeAssertion } diff --git a/process/track/ownShardTracker.go b/process/track/ownShardTracker.go new file mode 100644 index 00000000000..77562fd7f64 --- /dev/null +++ b/process/track/ownShardTracker.go @@ -0,0 +1,55 @@ +package track + +import ( + "sync/atomic" + + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/process" +) + +const minNonceDifference = 1 + +type ownShardTracker struct { + enableEpochsHandler common.EnableEpochsHandler + // maximum difference between the nonce of the last notarized execution result and the current header nonce + maxNonceDifference uint64 + ownShardStuck atomic.Bool +} + +// NewOwnShardTracker creates a new instance of ownShardTracker +func NewOwnShardTracker(enableEpochsHandler common.EnableEpochsHandler, maxNonceDifference uint64) (*ownShardTracker, error) { + if check.IfNil(enableEpochsHandler) { + return nil, process.ErrNilEnableEpochsHandler + } + if maxNonceDifference < minNonceDifference { + return nil, process.ErrInvalidMaxNonceDifference + } + + return &ownShardTracker{ + enableEpochsHandler: enableEpochsHandler, + maxNonceDifference: maxNonceDifference, + ownShardStuck: atomic.Bool{}, + }, nil +} + +// ComputeOwnShardStuck computes if the own shard is stuck based on the last execution results info and the current nonce. +func (ost *ownShardTracker) ComputeOwnShardStuck(lastExecutionResultsInfo data.BaseExecutionResultHandler, currentNonce uint64) { + if !ost.enableEpochsHandler.IsFlagEnabled(common.SupernovaFlag) { + return + } + + lastNotarizedNonce := lastExecutionResultsInfo.GetHeaderNonce() + if currentNonce > lastNotarizedNonce && currentNonce-lastNotarizedNonce > ost.maxNonceDifference { + ost.ownShardStuck.Store(true) + return + } + ost.ownShardStuck.Store(false) +} + +// IsOwnShardStuck returns true if the own shard is stuck, false otherwise +func (ost *ownShardTracker) IsOwnShardStuck() bool { + return ost.ownShardStuck.Load() +} diff --git a/process/track/ownShardTracker_test.go b/process/track/ownShardTracker_test.go new file mode 100644 index 00000000000..a0eeb108511 --- /dev/null +++ b/process/track/ownShardTracker_test.go @@ -0,0 +1,82 @@ +package track + +import ( + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/stretchr/testify/assert" + + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" +) + +func TestNewOwnShardTracker(t *testing.T) { + t.Parallel() + + t.Run("nil enableEpochsHandler", func(t *testing.T) { + t.Parallel() + + tracker, err := NewOwnShardTracker(nil, 10) + assert.Nil(t, tracker) + assert.Equal(t, process.ErrNilEnableEpochsHandler, err) + }) + t.Run("invalid maxNonceDifference", func(t *testing.T) { + t.Parallel() + + tracker, err := NewOwnShardTracker(&enableEpochsHandlerMock.EnableEpochsHandlerStub{}, 0) + assert.Nil(t, tracker) + assert.Equal(t, process.ErrInvalidMaxNonceDifference, err) + }) + t.Run("valid parameters", func(t *testing.T) { + t.Parallel() + + tracker, err := NewOwnShardTracker(&enableEpochsHandlerMock.EnableEpochsHandlerStub{}, 3) + assert.NotNil(t, tracker) + assert.Nil(t, err) + assert.False(t, tracker.IsOwnShardStuck()) + }) +} + +func TestComputeOwnShardStuck(t *testing.T) { + t.Parallel() + + t.Run("supernova flag disabled", func(t *testing.T) { + t.Parallel() + + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return false + }, + } + tracker, _ := NewOwnShardTracker(enableEpochsHandler, 10) + tracker.ComputeOwnShardStuck(&block.BaseExecutionResult{HeaderNonce: 10}, 30) + assert.False(t, tracker.IsOwnShardStuck()) + }) + + t.Run("supernova flag enabled, nonce difference within limit", func(t *testing.T) { + t.Parallel() + + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return true + }, + } + tracker, _ := NewOwnShardTracker(enableEpochsHandler, 10) + tracker.ComputeOwnShardStuck(&block.BaseExecutionResult{HeaderNonce: 10}, 15) + assert.False(t, tracker.IsOwnShardStuck()) + }) + + t.Run("supernova flag enabled, nonce difference exceeds limit", func(t *testing.T) { + t.Parallel() + + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool { + return true + }, + } + tracker, _ := NewOwnShardTracker(enableEpochsHandler, 10) + tracker.ComputeOwnShardStuck(&block.BaseExecutionResult{HeaderNonce: 10}, 30) + assert.True(t, tracker.IsOwnShardStuck()) + }) +} diff --git a/process/track/sentSignaturesTracker.go b/process/track/sentSignaturesTracker.go index 515f56a61f6..6777b098da5 100644 --- a/process/track/sentSignaturesTracker.go +++ b/process/track/sentSignaturesTracker.go @@ -14,6 +14,7 @@ const externalPeerID = core.PeerID("external peer id") type sentSignaturesTracker struct { mut sync.RWMutex sentFromSelf map[string]struct{} + signedNonces map[string]map[uint64][]byte // pk -> nonce -> headerHash keysHandler KeysHandler } @@ -25,6 +26,7 @@ func NewSentSignaturesTracker(keysHandler KeysHandler) (*sentSignaturesTracker, return &sentSignaturesTracker{ sentFromSelf: make(map[string]struct{}), + signedNonces: make(map[string]map[uint64][]byte), keysHandler: keysHandler, }, nil } @@ -57,6 +59,51 @@ func (tracker *sentSignaturesTracker) ResetCountersForManagedBlockSigner(signerP tracker.keysHandler.ResetRoundsWithoutReceivedMessages(signerPk, externalPeerID) } +// RecordSignedNonce records that a public key has signed a header hash for a given nonce. +// First-write-wins: if an entry already exists for this (pk, nonce), it is not overwritten. +// Automatically cleans up entries for nonces more than 1 behind the recorded nonce. +func (tracker *sentSignaturesTracker) RecordSignedNonce(pkBytes []byte, nonce uint64, headerHash []byte) { + pk := string(pkBytes) + + tracker.mut.Lock() + defer tracker.mut.Unlock() + + nonceMap, exists := tracker.signedNonces[pk] + if !exists { + nonceMap = make(map[uint64][]byte) + tracker.signedNonces[pk] = nonceMap + } + + if _, alreadyRecorded := nonceMap[nonce]; alreadyRecorded { + return + } + + hashCopy := make([]byte, len(headerHash)) + copy(hashCopy, headerHash) + nonceMap[nonce] = hashCopy + + for oldNonce := range nonceMap { + if nonce > oldNonce && nonce-oldNonce > 1 { + delete(nonceMap, oldNonce) + } + } +} + +// GetSignedHash returns the header hash previously signed by the given public key for the given nonce. +// Returns (hash, true) if found, (nil, false) otherwise. +func (tracker *sentSignaturesTracker) GetSignedHash(pkBytes []byte, nonce uint64) ([]byte, bool) { + tracker.mut.RLock() + defer tracker.mut.RUnlock() + + nonceMap, exists := tracker.signedNonces[string(pkBytes)] + if !exists { + return nil, false + } + + hash, found := nonceMap[nonce] + return hash, found +} + // IsInterfaceNil returns true if there is no value under the interface func (tracker *sentSignaturesTracker) IsInterfaceNil() bool { return tracker == nil diff --git a/process/track/sentSignaturesTracker_test.go b/process/track/sentSignaturesTracker_test.go index 8a60dba37dd..e646edd0e4b 100644 --- a/process/track/sentSignaturesTracker_test.go +++ b/process/track/sentSignaturesTracker_test.go @@ -4,8 +4,10 @@ import ( "testing" "github.com/multiversx/mx-chain-core-go/core" - "github.com/multiversx/mx-chain-go/testscommon" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/testscommon" ) func TestNewSentSignaturesTracker(t *testing.T) { @@ -89,3 +91,144 @@ func TestSentSignaturesTracker_ResetCountersForManagedBlockSigner(t *testing.T) }) }) } + +func TestSentSignaturesTracker_RecordSignedNonceAndGetSignedHash(t *testing.T) { + t.Parallel() + + t.Run("GetSignedHash returns false when no entry exists", func(t *testing.T) { + t.Parallel() + + tracker, _ := NewSentSignaturesTracker(&testscommon.KeysHandlerStub{}) + hash, exists := tracker.GetSignedHash([]byte("pk1"), 100) + assert.Nil(t, hash) + assert.False(t, exists) + }) + + t.Run("RecordSignedNonce stores entry and GetSignedHash retrieves it", func(t *testing.T) { + t.Parallel() + + tracker, _ := NewSentSignaturesTracker(&testscommon.KeysHandlerStub{}) + pk := []byte("pk1") + nonce := uint64(100) + headerHash := []byte("hash_A") + + tracker.RecordSignedNonce(pk, nonce, headerHash) + hash, exists := tracker.GetSignedHash(pk, nonce) + + require.True(t, exists) + assert.Equal(t, headerHash, hash) + }) + + t.Run("first-write-wins: second write for same pk+nonce is ignored", func(t *testing.T) { + t.Parallel() + + tracker, _ := NewSentSignaturesTracker(&testscommon.KeysHandlerStub{}) + pk := []byte("pk1") + nonce := uint64(100) + firstHash := []byte("hash_A") + secondHash := []byte("hash_B") + + tracker.RecordSignedNonce(pk, nonce, firstHash) + tracker.RecordSignedNonce(pk, nonce, secondHash) + + hash, exists := tracker.GetSignedHash(pk, nonce) + require.True(t, exists) + assert.Equal(t, firstHash, hash, "first-write-wins: should return the first hash") + }) + + t.Run("different pks are independent", func(t *testing.T) { + t.Parallel() + + tracker, _ := NewSentSignaturesTracker(&testscommon.KeysHandlerStub{}) + nonce := uint64(100) + + tracker.RecordSignedNonce([]byte("pk1"), nonce, []byte("hash_A")) + tracker.RecordSignedNonce([]byte("pk2"), nonce, []byte("hash_B")) + + hash1, exists1 := tracker.GetSignedHash([]byte("pk1"), nonce) + hash2, exists2 := tracker.GetSignedHash([]byte("pk2"), nonce) + + require.True(t, exists1) + require.True(t, exists2) + assert.Equal(t, []byte("hash_A"), hash1) + assert.Equal(t, []byte("hash_B"), hash2) + }) + + t.Run("cleanup removes entries more than 1 nonce behind", func(t *testing.T) { + t.Parallel() + + tracker, _ := NewSentSignaturesTracker(&testscommon.KeysHandlerStub{}) + pk := []byte("pk1") + + tracker.RecordSignedNonce(pk, 100, []byte("hash_100")) + tracker.RecordSignedNonce(pk, 101, []byte("hash_101")) + + // Both should exist (101 - 100 = 1, not > 1) + _, exists100 := tracker.GetSignedHash(pk, 100) + _, exists101 := tracker.GetSignedHash(pk, 101) + assert.True(t, exists100, "nonce 100 should still exist (1 behind)") + assert.True(t, exists101) + + // Recording nonce 102 should clean up nonce 100 (102 - 100 = 2 > 1) + tracker.RecordSignedNonce(pk, 102, []byte("hash_102")) + + _, exists100 = tracker.GetSignedHash(pk, 100) + _, exists101 = tracker.GetSignedHash(pk, 101) + _, exists102 := tracker.GetSignedHash(pk, 102) + + assert.False(t, exists100, "nonce 100 should be cleaned up (2 behind)") + assert.True(t, exists101, "nonce 101 should still exist (1 behind)") + assert.True(t, exists102) + }) + + t.Run("cleanup does not affect other pks", func(t *testing.T) { + t.Parallel() + + tracker, _ := NewSentSignaturesTracker(&testscommon.KeysHandlerStub{}) + + tracker.RecordSignedNonce([]byte("pk1"), 100, []byte("hash_pk1_100")) + tracker.RecordSignedNonce([]byte("pk2"), 100, []byte("hash_pk2_100")) + + // Advancing pk1 to nonce 102 should clean up pk1's nonce 100 but not pk2's + tracker.RecordSignedNonce([]byte("pk1"), 102, []byte("hash_pk1_102")) + + _, pk1Exists := tracker.GetSignedHash([]byte("pk1"), 100) + _, pk2Exists := tracker.GetSignedHash([]byte("pk2"), 100) + + assert.False(t, pk1Exists, "pk1 nonce 100 should be cleaned up") + assert.True(t, pk2Exists, "pk2 nonce 100 should NOT be cleaned up by pk1's advancement") + }) + + t.Run("stores a copy of the header hash, not a reference", func(t *testing.T) { + t.Parallel() + + tracker, _ := NewSentSignaturesTracker(&testscommon.KeysHandlerStub{}) + pk := []byte("pk1") + nonce := uint64(100) + headerHash := []byte("hash_A") + + tracker.RecordSignedNonce(pk, nonce, headerHash) + + // Mutate the original slice + headerHash[0] = 'X' + + hash, exists := tracker.GetSignedHash(pk, nonce) + require.True(t, exists) + assert.Equal(t, []byte("hash_A"), hash, "stored hash should not be affected by mutation of the original") + }) + + t.Run("StartRound does not clear signedNonces", func(t *testing.T) { + t.Parallel() + + tracker, _ := NewSentSignaturesTracker(&testscommon.KeysHandlerStub{}) + pk := []byte("pk1") + nonce := uint64(100) + + tracker.RecordSignedNonce(pk, nonce, []byte("hash_A")) + tracker.StartRound() + + hash, exists := tracker.GetSignedHash(pk, nonce) + require.True(t, exists, "signedNonces should persist across StartRound") + assert.Equal(t, []byte("hash_A"), hash) + }) +} diff --git a/process/track/shardBlockTrack.go b/process/track/shardBlockTrack.go index 7a5617e7896..0ff14066dcd 100644 --- a/process/track/shardBlockTrack.go +++ b/process/track/shardBlockTrack.go @@ -5,7 +5,6 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" - "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-go/process" ) @@ -47,7 +46,9 @@ func NewShardBlockTrack(arguments ArgShardTracker) (*shardBlockTrack, error) { SelfNotarizedHeadersNotifier: bbt.selfNotarizedHeadersNotifier, FinalMetachainHeadersNotifier: bbt.finalMetachainHeadersNotifier, RoundHandler: arguments.RoundHandler, + ProcessConfigsHandler: arguments.ProcessConfigsHandler, EnableEpochsHandler: arguments.EnableEpochsHandler, + EnableRoundsHandler: arguments.EnableRoundsHandler, ProofsPool: arguments.ProofsPool, Marshaller: arguments.Marshalizer, Hasher: arguments.Hasher, @@ -73,29 +74,29 @@ func NewShardBlockTrack(arguments ArgShardTracker) (*shardBlockTrack, error) { func (sbt *shardBlockTrack) GetSelfHeaders(headerHandler data.HeaderHandler) []*HeaderInfo { selfHeadersInfo := make([]*HeaderInfo, 0) - metaBlock, ok := headerHandler.(*block.MetaBlock) + metaBlock, ok := headerHandler.(data.MetaHeaderHandler) if !ok { log.Debug("GetSelfHeaders", "error", process.ErrWrongTypeAssertion) return selfHeadersInfo } - for _, shardInfo := range metaBlock.ShardInfo { - if shardInfo.ShardID != sbt.shardCoordinator.SelfId() { + for _, shardInfo := range metaBlock.GetShardInfoHandlers() { + if shardInfo.GetShardID() != sbt.shardCoordinator.SelfId() { continue } - header, err := process.GetShardHeader(shardInfo.HeaderHash, sbt.headersPool, sbt.marshalizer, sbt.store) + header, err := process.GetShardHeader(shardInfo.GetHeaderHash(), sbt.headersPool, sbt.marshalizer, sbt.store) if err != nil { log.Trace("GetSelfHeaders.GetShardHeader", "error", err.Error()) - header, err = sbt.getTrackedShardHeaderWithNonceAndHash(shardInfo.ShardID, shardInfo.Nonce, shardInfo.HeaderHash) + header, err = sbt.getTrackedShardHeaderWithNonceAndHash(shardInfo.GetShardID(), shardInfo.GetNonce(), shardInfo.GetHeaderHash()) if err != nil { log.Trace("GetSelfHeaders.getTrackedShardHeaderWithNonceAndHash", "error", err.Error()) continue } } - selfHeadersInfo = append(selfHeadersInfo, &HeaderInfo{Hash: shardInfo.HeaderHash, Header: header}) + selfHeadersInfo = append(selfHeadersInfo, &HeaderInfo{Hash: shardInfo.GetHeaderHash(), Header: header}) } return selfHeadersInfo @@ -148,21 +149,21 @@ func (sbt *shardBlockTrack) ComputeCrossInfo(headers []data.HeaderHandler) { return } - metaBlock, ok := headers[lenHeaders-1].(*block.MetaBlock) + metaBlock, ok := headers[lenHeaders-1].(data.MetaHeaderHandler) if !ok { log.Debug("ComputeCrossInfo", "error", process.ErrWrongTypeAssertion) return } - for _, shardInfo := range metaBlock.ShardInfo { - sbt.blockBalancer.SetNumPendingMiniBlocks(shardInfo.ShardID, shardInfo.NumPendingMiniBlocks) - sbt.blockBalancer.SetLastShardProcessedMetaNonce(shardInfo.ShardID, shardInfo.LastIncludedMetaNonce) + for _, shardInfo := range metaBlock.GetShardInfoHandlers() { + sbt.blockBalancer.SetNumPendingMiniBlocks(shardInfo.GetShardID(), shardInfo.GetNumPendingMiniBlocks()) + sbt.blockBalancer.SetLastShardProcessedMetaNonce(shardInfo.GetShardID(), shardInfo.GetLastIncludedMetaNonce()) } log.Debug("compute cross info from meta block", - "epoch", metaBlock.Epoch, - "round", metaBlock.Round, - "nonce", metaBlock.Nonce, + "epoch", metaBlock.GetEpoch(), + "round", metaBlock.GetRound(), + "nonce", metaBlock.GetNonce(), ) for shardID := uint32(0); shardID < sbt.shardCoordinator.NumberOfShards(); shardID++ { diff --git a/process/transaction/interceptedTransaction.go b/process/transaction/interceptedTransaction.go index 2b857222416..b19aee3425c 100644 --- a/process/transaction/interceptedTransaction.go +++ b/process/transaction/interceptedTransaction.go @@ -217,6 +217,11 @@ func (inTx *InterceptedTransaction) CheckValidity() error { return nil } +// ShouldAllowDuplicates returns if this type of intercepted data should allow duplicates +func (inTx *InterceptedTransaction) ShouldAllowDuplicates() bool { + return false +} + func (inTx *InterceptedTransaction) checkRecursiveRelayed(userTx *transaction.Transaction) error { if common.IsValidRelayedTxV3(userTx) { return process.ErrRecursiveRelayedTxIsNotAllowed diff --git a/process/transaction/interceptedTransaction_test.go b/process/transaction/interceptedTransaction_test.go index 47ee3d62c11..d7234ce1006 100644 --- a/process/transaction/interceptedTransaction_test.go +++ b/process/transaction/interceptedTransaction_test.go @@ -1245,6 +1245,7 @@ func TestInterceptedTransaction_OkValsGettersShouldWork(t *testing.T) { assert.Equal(t, recvShard, txi.ReceiverShardId()) assert.False(t, txi.IsForCurrentShard()) assert.Equal(t, tx, txi.Transaction()) + assert.False(t, txi.ShouldAllowDuplicates()) } func TestInterceptedTransaction_ScTxDeployRecvShardIdShouldBeSendersShardId(t *testing.T) { @@ -1968,7 +1969,7 @@ func TestInterceptedTransaction_checkMaxGasPrice(t *testing.T) { errMaxGasPrice = inTx2.CheckMaxGasPrice() require.Nil(t, errMaxGasPrice) }) - t.Run("not guarded Tx, not setGuardian always OK", func(t *testing.T) { + t.Run("not guarded tx, not setGuardian always OK", func(t *testing.T) { tx1 := *testTx1 tx1.Data = []byte("dummy") tx2 := *testTx2 @@ -1991,7 +1992,7 @@ func TestInterceptedTransaction_checkMaxGasPrice(t *testing.T) { errMaxGasPrice = inTx2.CheckMaxGasPrice() require.Nil(t, errMaxGasPrice) }) - t.Run("not guarded Tx with setGuardian call and price lower than max or equal OK", func(t *testing.T) { + t.Run("not guarded tx with setGuardian call and price lower than max or equal OK", func(t *testing.T) { tx1 := *testTx1 tx1.GasPrice = maxAllowedGasPriceSetGuardian tx2 := *testTx2 @@ -2014,7 +2015,7 @@ func TestInterceptedTransaction_checkMaxGasPrice(t *testing.T) { errMaxGasPrice = inTx2.CheckMaxGasPrice() require.Nil(t, errMaxGasPrice) }) - t.Run("not guarded Tx with setGuardian call and price higher than max err", func(t *testing.T) { + t.Run("not guarded tx with setGuardian call and price higher than max err", func(t *testing.T) { tx1 := *testTx1 tx1.GasPrice = maxAllowedGasPriceSetGuardian * 2 txVersionChecker := &testscommon.TxVersionCheckerStub{ diff --git a/process/transaction/metaProcess.go b/process/transaction/metaProcess.go index fdc17e7cd18..bae0cd085b7 100644 --- a/process/transaction/metaProcess.go +++ b/process/transaction/metaProcess.go @@ -8,16 +8,17 @@ import ( "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/sharding" "github.com/multiversx/mx-chain-go/state" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" ) var _ process.TransactionProcessor = (*metaTxProcessor)(nil) -// txProcessor implements TransactionProcessor interface and can modify account states according to a transaction +// metaTxProcessor implements TransactionProcessor interface and can modify account states according to a transaction type metaTxProcessor struct { *baseTxProcessor enableEpochsHandler common.EnableEpochsHandler diff --git a/process/transaction/shardProcess.go b/process/transaction/shardProcess.go index 65b11871ac4..718492ab8e0 100644 --- a/process/transaction/shardProcess.go +++ b/process/transaction/shardProcess.go @@ -65,7 +65,7 @@ type ArgsNewTxProcessor struct { BadTxForwarder process.IntermediateTransactionHandler ArgsParser process.ArgumentsParser ScrForwarder process.IntermediateTransactionHandler - EnableRoundsHandler process.EnableRoundsHandler + EnableRoundsHandler common.EnableRoundsHandler EnableEpochsHandler common.EnableEpochsHandler TxVersionChecker process.TxVersionCheckerHandler GuardianChecker process.GuardianChecker @@ -879,7 +879,6 @@ func (txProc *txProcessor) addNonExecutableLog(executionErr error, originalTxHas } return txProc.txLogsProcessor.SaveLog(originalTxHash, originalTx, []*vmcommon.LogEntry{logEntry}) - } func (txProc *txProcessor) processMoveBalanceCostRelayedUserTx( diff --git a/process/transactionEvaluator/interface.go b/process/transactionEvaluator/interface.go index 0b6d2620d72..979e7098c61 100644 --- a/process/transactionEvaluator/interface.go +++ b/process/transactionEvaluator/interface.go @@ -15,5 +15,5 @@ type TransactionProcessor interface { // DataFieldParser defines what a data field parser should be able to do type DataFieldParser interface { - Parse(dataField []byte, sender, receiver []byte, numOfShards uint32) *datafield.ResponseParseData + Parse(dataField []byte, sender, receiver []byte, numOfShards uint32, epoch uint32) *datafield.ResponseParseData } diff --git a/process/transactionEvaluator/simulationAccountsDB.go b/process/transactionEvaluator/simulationAccountsDB.go index 5b02702009d..a4d9fa34ef1 100644 --- a/process/transactionEvaluator/simulationAccountsDB.go +++ b/process/transactionEvaluator/simulationAccountsDB.go @@ -6,9 +6,10 @@ import ( "sync" "github.com/multiversx/mx-chain-core-go/core/check" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/state" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" ) // simulationAccountsDB is a wrapper over an accounts db which works read-only. write operation are disabled @@ -132,6 +133,11 @@ func (r *simulationAccountsDB) RecreateTrie(_ common.RootHashHolder) error { return nil } +// RecreateTrieIfNeeded won't do anything as write operations are disabled on this component +func (r *simulationAccountsDB) RecreateTrieIfNeeded(_ common.RootHashHolder) error { + return nil +} + // PruneTrie won't do anything as write operations are disabled on this component func (r *simulationAccountsDB) PruneTrie(_ []byte, _ state.TriePruningIdentifier, _ state.PruningHandler) { } @@ -140,6 +146,10 @@ func (r *simulationAccountsDB) PruneTrie(_ []byte, _ state.TriePruningIdentifier func (r *simulationAccountsDB) CancelPrune(_ []byte, _ state.TriePruningIdentifier) { } +// ResetPruning won't do anything as write operations are disabled on this component +func (r *simulationAccountsDB) ResetPruning() { +} + // SnapshotState won't do anything as write operations are disabled on this component func (r *simulationAccountsDB) SnapshotState(_ []byte, _ uint32) { } @@ -184,6 +194,11 @@ func (r *simulationAccountsDB) Close() error { return nil } +// GetEvictionWaitingListSize returns 0 for the simulation accounts DB +func (adb *simulationAccountsDB) GetEvictionWaitingListSize() int { + return 0 +} + // IsInterfaceNil returns true if there is no value under the interface func (r *simulationAccountsDB) IsInterfaceNil() bool { return r == nil diff --git a/process/transactionEvaluator/transactionSimulator.go b/process/transactionEvaluator/transactionSimulator.go index d01dc4ef85d..af02dd2d763 100644 --- a/process/transactionEvaluator/transactionSimulator.go +++ b/process/transactionEvaluator/transactionSimulator.go @@ -270,7 +270,8 @@ func (ts *transactionSimulator) adaptSmartContractResult(scr *smartContractResul ReturnMessage: string(scr.ReturnMessage), GasLimit: scr.GasLimit, }) - res := ts.dataFieldParser.Parse(scr.Data, scr.SndAddr, scr.RcvAddr, ts.shardCoordinator.NumberOfShards()) + currentEpoch := ts.blockChainHook.CurrentEpoch() + res := ts.dataFieldParser.Parse(scr.Data, scr.SndAddr, scr.RcvAddr, ts.shardCoordinator.NumberOfShards(), currentEpoch) receiversEncoded, err := ts.addressPubKeyConverter.EncodeSlice(res.Receivers) if err != nil { diff --git a/process/transactionEvaluator/transactionSimulator_test.go b/process/transactionEvaluator/transactionSimulator_test.go index 6b11136b4dc..ba00f45b106 100644 --- a/process/transactionEvaluator/transactionSimulator_test.go +++ b/process/transactionEvaluator/transactionSimulator_test.go @@ -16,9 +16,9 @@ import ( "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/mock" "github.com/multiversx/mx-chain-go/storage/storageunit" - "github.com/multiversx/mx-chain-go/storage/txcache" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" + "github.com/multiversx/mx-chain-go/txcache" vmcommon "github.com/multiversx/mx-chain-vm-common-go" datafield "github.com/multiversx/mx-chain-vm-common-go/parsers/dataField" "github.com/stretchr/testify/assert" diff --git a/process/transactionLog/printTxLogProcessor.go b/process/transactionLog/printTxLogProcessor.go index 6a512219d6a..0fdd2267446 100644 --- a/process/transactionLog/printTxLogProcessor.go +++ b/process/transactionLog/printTxLogProcessor.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/transaction" vmcommon "github.com/multiversx/mx-chain-vm-common-go" ) @@ -22,8 +23,8 @@ func (tlp *printTxLogProcessor) GetLog(_ []byte) (data.LogHandler, error) { } // GetLogFromCache - -func (tlp *printTxLogProcessor) GetLogFromCache(_ []byte) (*data.LogData, bool) { - return &data.LogData{}, false +func (tlp *printTxLogProcessor) GetLogFromCache(_ []byte) (data.LogDataHandler, bool) { + return &transaction.LogData{}, false } // EnableLogToBeSavedInCache - @@ -35,8 +36,8 @@ func (tlp *printTxLogProcessor) Clean() { } // GetAllCurrentLogs - -func (tlp *printTxLogProcessor) GetAllCurrentLogs() []*data.LogData { - return []*data.LogData{} +func (tlp *printTxLogProcessor) GetAllCurrentLogs() []data.LogDataHandler { + return make([]data.LogDataHandler, 0) } // SaveLog - diff --git a/process/transactionLog/process.go b/process/transactionLog/process.go index 39b74f4b02a..861d3484b6c 100644 --- a/process/transactionLog/process.go +++ b/process/transactionLog/process.go @@ -28,7 +28,7 @@ type ArgTxLogProcessor struct { } type txLogProcessor struct { - logs []*data.LogData + logs []data.LogDataHandler logsIndices map[string]int mut sync.RWMutex storer storage.Storer @@ -54,7 +54,7 @@ func NewTxLogProcessor(args ArgTxLogProcessor) (*txLogProcessor, error) { return &txLogProcessor{ storer: storer, marshalizer: args.Marshalizer, - logs: make([]*data.LogData, 0), + logs: make([]data.LogDataHandler, 0), logsIndices: make(map[string]int), mut: sync.RWMutex{}, }, nil @@ -67,7 +67,8 @@ func (tlp *txLogProcessor) GetLog(txHash []byte) (data.LogHandler, error) { index, ok := tlp.logsIndices[string(txHash)] if ok { - return tlp.logs[index], nil + logData := tlp.logs[index] + return logData.GetLogHandler(), nil } txLogBuff, err := tlp.storer.Get(txHash) @@ -86,7 +87,7 @@ func (tlp *txLogProcessor) GetLog(txHash []byte) (data.LogHandler, error) { // GetLogFromCache retrieves a log generated by a transaction from ram or from storage // TODO remove this function and change the interface no longer needed -func (tlp *txLogProcessor) GetLogFromCache(txHash []byte) (*data.LogData, bool) { +func (tlp *txLogProcessor) GetLogFromCache(txHash []byte) (data.LogDataHandler, bool) { tlp.mut.RLock() defer tlp.mut.RUnlock() @@ -95,22 +96,27 @@ func (tlp *txLogProcessor) GetLogFromCache(txHash []byte) (*data.LogData, bool) return tlp.logs[index], true } - txLog, err := tlp.GetLog(txHash) + txLogHandler, err := tlp.GetLog(txHash) if err != nil { return nil, false } - return &data.LogData{ - LogHandler: txLog, - TxHash: string(txHash), + txLog, ok := txLogHandler.(*transaction.Log) + if !ok { + return nil, false + } + + return &transaction.LogData{ + Log: txLog, + TxHash: string(txHash), }, true } // GetAllCurrentLogs will return all generated logs for the current block -func (tlp *txLogProcessor) GetAllCurrentLogs() []*data.LogData { +func (tlp *txLogProcessor) GetAllCurrentLogs() []data.LogDataHandler { tlp.mut.RLock() - logsSlice := make([]*data.LogData, 0, len(tlp.logs)) - logsSlice = append(logsSlice, tlp.logs...) + logsSlice := make([]data.LogDataHandler, len(tlp.logs)) + copy(logsSlice, tlp.logs) tlp.mut.RUnlock() return logsSlice @@ -124,7 +130,7 @@ func (tlp *txLogProcessor) EnableLogToBeSavedInCache() { func (tlp *txLogProcessor) Clean() { tlp.mut.Lock() tlp.logsIndices = make(map[string]int) - tlp.logs = make([]*data.LogData, 0) + tlp.logs = make([]data.LogDataHandler, 0) tlp.mut.Unlock() } @@ -173,9 +179,9 @@ func (tlp *txLogProcessor) SaveLog(txHash []byte, tx data.TransactionHandler, lo func (tlp *txLogProcessor) saveLogToCache(txHash []byte, log *transaction.Log) { tlp.mut.Lock() - tlp.logs = append(tlp.logs, &data.LogData{ - TxHash: string(txHash), - LogHandler: log, + tlp.logs = append(tlp.logs, &transaction.LogData{ + TxHash: string(txHash), + Log: log, }) tlp.logsIndices[string(txHash)] = len(tlp.logs) - 1 tlp.mut.Unlock() diff --git a/process/transactionLog/process_test.go b/process/transactionLog/process_test.go index c4f58322056..860cf786766 100644 --- a/process/transactionLog/process_test.go +++ b/process/transactionLog/process_test.go @@ -209,7 +209,7 @@ func TestTxLogProcessor_GetLogFromCache(t *testing.T) { logData, found := txLogProcessor.GetLogFromCache([]byte("txhash")) require.True(t, found) - require.Equal(t, "txhash", logData.TxHash) + require.Equal(t, "txhash", logData.GetTxHash()) } func TestTxLogProcessor_GetLogFromCacheNotInCacheShouldReturnFromStorage(t *testing.T) { diff --git a/process/txsSender/txsSender.go b/process/txsSender/txsSender.go index 3a4dfa292ba..8e388f0e3bd 100644 --- a/process/txsSender/txsSender.go +++ b/process/txsSender/txsSender.go @@ -11,7 +11,6 @@ import ( "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-go/common" - "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/factory" @@ -27,11 +26,11 @@ const SendTransactionsPipe = "send transactions pipe" // ArgsTxsSenderWithAccumulator is a holder struct for all necessary arguments to create a NewTxsSenderWithAccumulator type ArgsTxsSenderWithAccumulator struct { - Marshaller marshal.Marshalizer - ShardCoordinator storage.ShardCoordinator - NetworkMessenger NetworkMessenger - AccumulatorConfig config.TxAccumulatorConfig - DataPacker process.DataPacker + Marshaller marshal.Marshalizer + ShardCoordinator storage.ShardCoordinator + NetworkMessenger NetworkMessenger + DataPacker process.DataPacker + AntifloodConfigHandler common.AntifloodConfigsHandler } type txsSender struct { @@ -60,10 +59,16 @@ func NewTxsSenderWithAccumulator(args ArgsTxsSenderWithAccumulator) (*txsSender, if check.IfNil(args.DataPacker) { return nil, dataRetriever.ErrNilDataPacker } + if check.IfNil(args.AntifloodConfigHandler) { + return nil, process.ErrNilAntifloodConfigsHandler + } + + currentConf := args.AntifloodConfigHandler.GetCurrentConfig() + // TODO: time accumulator component has to be changes to have dinamic values txAccumulator, err := accumulator.NewTimeAccumulator( - time.Duration(args.AccumulatorConfig.MaxAllowedTimeInMilliseconds)*time.Millisecond, - time.Duration(args.AccumulatorConfig.MaxDeviationTimeInMilliseconds)*time.Millisecond, + time.Duration(currentConf.TxAccumulator.MaxAllowedTimeInMilliseconds)*time.Millisecond, + time.Duration(currentConf.TxAccumulator.MaxDeviationTimeInMilliseconds)*time.Millisecond, log, ) if err != nil { diff --git a/process/txsSender/txsSender_test.go b/process/txsSender/txsSender_test.go index 50ee7af2876..b1e90f31082 100644 --- a/process/txsSender/txsSender_test.go +++ b/process/txsSender/txsSender_test.go @@ -10,7 +10,6 @@ import ( "testing" "time" - "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/atomic" "github.com/multiversx/mx-chain-core-go/core/partitioning" "github.com/multiversx/mx-chain-core-go/data/batch" @@ -60,17 +59,6 @@ func TestNewTxsSenderWithAccumulator(t *testing.T) { }, expectedError: process.ErrNilMessenger, }, - { - args: func() ArgsTxsSenderWithAccumulator { - args := generateMockArgsTxsSender() - args.AccumulatorConfig = config.TxAccumulatorConfig{ - MaxAllowedTimeInMilliseconds: 0, - MaxDeviationTimeInMilliseconds: 0, - } - return args - }, - expectedError: core.ErrInvalidValue, - }, { args: func() ArgsTxsSenderWithAccumulator { args := generateMockArgsTxsSender() @@ -185,9 +173,15 @@ func TestTxsSender_SendBulkTransactions(t *testing.T) { ShardCoordinator: shardCoordinator, NetworkMessenger: mes, DataPacker: dataPacker, - AccumulatorConfig: config.TxAccumulatorConfig{ - MaxAllowedTimeInMilliseconds: 250, - MaxDeviationTimeInMilliseconds: 25, + AntifloodConfigHandler: &testscommon.AntifloodConfigsHandlerStub{ + GetCurrentConfigCalled: func() config.AntifloodConfigByRound { + return config.AntifloodConfigByRound{ + TxAccumulator: config.TxAccumulatorConfig{ + MaxAllowedTimeInMilliseconds: 250, + MaxDeviationTimeInMilliseconds: 25, + }, + } + }, }, } sender, _ := NewTxsSenderWithAccumulator(args) @@ -431,10 +425,16 @@ func generateMockArgsTxsSender() ArgsTxsSenderWithAccumulator { MaxDeviationTimeInMilliseconds: 1, } return ArgsTxsSenderWithAccumulator{ - Marshaller: marshaller, - ShardCoordinator: &testscommon.ShardsCoordinatorMock{}, - NetworkMessenger: &p2pmocks.MessengerStub{}, - DataPacker: dataPacker, - AccumulatorConfig: accumulatorConfig, + Marshaller: marshaller, + ShardCoordinator: &testscommon.ShardsCoordinatorMock{}, + NetworkMessenger: &p2pmocks.MessengerStub{}, + DataPacker: dataPacker, + AntifloodConfigHandler: &testscommon.AntifloodConfigsHandlerStub{ + GetCurrentConfigCalled: func() config.AntifloodConfigByRound { + return config.AntifloodConfigByRound{ + TxAccumulator: accumulatorConfig, + } + }, + }, } } diff --git a/process/unsigned/interceptedUnsignedTransaction.go b/process/unsigned/interceptedUnsignedTransaction.go index 1b5639bb892..c793d52024a 100644 --- a/process/unsigned/interceptedUnsignedTransaction.go +++ b/process/unsigned/interceptedUnsignedTransaction.go @@ -97,6 +97,11 @@ func (inUTx *InterceptedUnsignedTransaction) CheckValidity() error { return nil } +// ShouldAllowDuplicates returns if this type of intercepted data should allow duplicates +func (inUTx *InterceptedUnsignedTransaction) ShouldAllowDuplicates() bool { + return false +} + func (inUTx *InterceptedUnsignedTransaction) processFields(uTxBuffWithSig []byte) error { inUTx.hash = inUTx.hasher.Compute(string(uTxBuffWithSig)) diff --git a/process/unsigned/interceptedUnsignedTransaction_test.go b/process/unsigned/interceptedUnsignedTransaction_test.go index 102b76c0975..c1c27ddda75 100644 --- a/process/unsigned/interceptedUnsignedTransaction_test.go +++ b/process/unsigned/interceptedUnsignedTransaction_test.go @@ -327,6 +327,7 @@ func TestInterceptedUnsignedTransaction_OkValsGettersShouldWork(t *testing.T) { assert.Equal(t, nonce, txi.Nonce()) assert.Equal(t, big.NewInt(0), txi.Fee()) assert.Equal(t, senderAddress, txi.SenderAddress()) + assert.False(t, txi.ShouldAllowDuplicates()) } // ------- IsInterfaceNil diff --git a/redundancy/export_test.go b/redundancy/export_test.go index 65d44e8b0ab..063064b3580 100644 --- a/redundancy/export_test.go +++ b/redundancy/export_test.go @@ -10,6 +10,11 @@ func (nr *nodeRedundancy) GetRoundsOfInactivity() int { return nr.handler.RoundsOfInactivity() } +// CalcMaxRoundsOfInactivity - +func (nr *nodeRedundancy) CalcMaxRoundsOfInactivity() int { + return nr.calcMaxRoundsOfInactivity() +} + // SetRoundsOfInactivity - func (nr *nodeRedundancy) SetRoundsOfInactivity(roundsOfInactivity int) { nr.handler.ResetRoundsOfInactivity() diff --git a/redundancy/redundancy.go b/redundancy/redundancy.go index 66a49684638..930f178411a 100644 --- a/redundancy/redundancy.go +++ b/redundancy/redundancy.go @@ -6,6 +6,9 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-crypto-go" + cmn "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/common/configs/dto" + "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/redundancy/common" logger "github.com/multiversx/mx-chain-logger-go" ) @@ -23,14 +26,16 @@ type nodeRedundancy struct { mutNodeRedundancy sync.RWMutex lastRoundIndexCheck int64 handler redundancyHandler - maxRoundsOfInactivity int messenger P2PMessenger observerPrivateKey crypto.PrivateKey + processConfigsHandler cmn.ProcessConfigsHandler + redundancyLevel int } // ArgNodeRedundancy represents the DTO structure used by the nodeRedundancy's constructor type ArgNodeRedundancy struct { - MaxRoundsOfInactivity int + RedundancyLevel int + ProcessConfigsHandler cmn.ProcessConfigsHandler Messenger P2PMessenger ObserverPrivateKey crypto.PrivateKey } @@ -43,24 +48,33 @@ func NewNodeRedundancy(arg ArgNodeRedundancy) (*nodeRedundancy, error) { if check.IfNil(arg.ObserverPrivateKey) { return nil, ErrNilObserverPrivateKey } - err := common.CheckMaxRoundsOfInactivity(arg.MaxRoundsOfInactivity) - if err != nil { - return nil, err + if check.IfNil(arg.ProcessConfigsHandler) { + return nil, process.ErrNilProcessConfigsHandler } nr := &nodeRedundancy{ handler: common.NewRedundancyHandler(), - maxRoundsOfInactivity: arg.MaxRoundsOfInactivity, messenger: arg.Messenger, observerPrivateKey: arg.ObserverPrivateKey, + redundancyLevel: arg.RedundancyLevel, + processConfigsHandler: arg.ProcessConfigsHandler, + } + + err := common.CheckMaxRoundsOfInactivity(nr.calcMaxRoundsOfInactivity()) + if err != nil { + return nil, err } return nr, nil } +func (nr *nodeRedundancy) calcMaxRoundsOfInactivity() int { + return nr.redundancyLevel * int(nr.processConfigsHandler.GetValue(dto.MaxRoundsOfInactivityAccepted)) +} + // IsRedundancyNode returns true if the current instance is used as a redundancy node func (nr *nodeRedundancy) IsRedundancyNode() bool { - return !common.IsMainNode(nr.maxRoundsOfInactivity) + return !common.IsMainNode(nr.calcMaxRoundsOfInactivity()) } // IsMainMachineActive returns true if the main or lower level redundancy machines are active @@ -68,7 +82,7 @@ func (nr *nodeRedundancy) IsMainMachineActive() bool { nr.mutNodeRedundancy.RLock() defer nr.mutNodeRedundancy.RUnlock() - return nr.handler.IsMainMachineActive(nr.maxRoundsOfInactivity) + return nr.handler.IsMainMachineActive(nr.calcMaxRoundsOfInactivity()) } // AdjustInactivityIfNeeded increments rounds of inactivity for main or lower level redundancy machines if needed @@ -80,13 +94,14 @@ func (nr *nodeRedundancy) AdjustInactivityIfNeeded(selfPubKey string, consensusP return } - if nr.handler.IsMainMachineActive(nr.maxRoundsOfInactivity) { + maxRoundsOfInactivity := nr.calcMaxRoundsOfInactivity() + if nr.handler.IsMainMachineActive(maxRoundsOfInactivity) { log.Debug("main or lower level redundancy machines are active for single-key operation", - "max rounds of inactivity", nr.maxRoundsOfInactivity, + "max rounds of inactivity", maxRoundsOfInactivity, "current rounds of inactivity", nr.handler.RoundsOfInactivity()) } else { log.Warn("main or lower level redundancy machines are inactive for single-key operation", - "max rounds of inactivity", nr.maxRoundsOfInactivity, + "max rounds of inactivity", maxRoundsOfInactivity, "current rounds of inactivity", nr.handler.RoundsOfInactivity()) } diff --git a/redundancy/redundancy_test.go b/redundancy/redundancy_test.go index 41a49bbd2da..aa9b00fa3ac 100644 --- a/redundancy/redundancy_test.go +++ b/redundancy/redundancy_test.go @@ -4,15 +4,19 @@ import ( "testing" "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/redundancy" "github.com/multiversx/mx-chain-go/redundancy/mock" + "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/p2pmocks" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func createMockArguments(maxRoundsOfInactivity int) redundancy.ArgNodeRedundancy { return redundancy.ArgNodeRedundancy{ - MaxRoundsOfInactivity: maxRoundsOfInactivity, + RedundancyLevel: maxRoundsOfInactivity, + ProcessConfigsHandler: &testscommon.ProcessConfigsHandlerStub{}, Messenger: &p2pmocks.MessengerStub{}, ObserverPrivateKey: &mock.PrivateKeyStub{}, } @@ -40,6 +44,17 @@ func TestNewNodeRedundancy_ShouldErrNilObserverPrivateKey(t *testing.T) { assert.Equal(t, redundancy.ErrNilObserverPrivateKey, err) } +func TestNewNodeRedundancy_ShouldErrNilProcessConfigsHandler(t *testing.T) { + t.Parallel() + + arg := createMockArguments(0) + arg.ProcessConfigsHandler = nil + nr, err := redundancy.NewNodeRedundancy(arg) + + require.Nil(t, nr) + require.Equal(t, process.ErrNilProcessConfigsHandler, err) +} + func TestNewNodeRedundancy_ShouldErrIfMaxRoundsOfInactivityIsInvalid(t *testing.T) { t.Parallel() @@ -169,10 +184,10 @@ func TestAdjustInactivityIfNeeded_ShouldNotAdjustIfSelfPubKeyIsNotContainedInCon nr.AdjustInactivityIfNeeded(selfPubKey, consensusPubKeys, 1) assert.Equal(t, 0, nr.GetRoundsOfInactivity()) - nr.SetRoundsOfInactivity(arg.MaxRoundsOfInactivity) + nr.SetRoundsOfInactivity(nr.CalcMaxRoundsOfInactivity()) nr.AdjustInactivityIfNeeded(selfPubKey, consensusPubKeys, 2) - assert.Equal(t, arg.MaxRoundsOfInactivity, nr.GetRoundsOfInactivity()) + assert.Equal(t, nr.CalcMaxRoundsOfInactivity(), nr.GetRoundsOfInactivity()) } func TestAdjustInactivityIfNeeded_ShouldAdjustOnlyOneTimeInTheSameRound(t *testing.T) { diff --git a/scripts/testnet/include/config.sh b/scripts/testnet/include/config.sh index 16ed10fba39..85d8783a4a4 100644 --- a/scripts/testnet/include/config.sh +++ b/scripts/testnet/include/config.sh @@ -118,13 +118,6 @@ updateNodeConfig() { updateTOMLValue config_observer.toml "ChainID" "\"local-testnet"\" fi - if [ $ROUNDS_PER_EPOCH -ne 0 ]; then - sed -i "s,RoundsPerEpoch.*$,RoundsPerEpoch = $ROUNDS_PER_EPOCH," config_observer.toml - sed -i "s,MinRoundsBetweenEpochs.*$,MinRoundsBetweenEpochs = $ROUNDS_PER_EPOCH," config_observer.toml - sed -i "s,RoundsPerEpoch.*$,RoundsPerEpoch = $ROUNDS_PER_EPOCH," config_validator.toml - sed -i "s,MinRoundsBetweenEpochs.*$,MinRoundsBetweenEpochs = $ROUNDS_PER_EPOCH," config_validator.toml - fi - cp nodesSetup_edit.json nodesSetup.json rm nodesSetup_edit.json @@ -150,8 +143,15 @@ updateChainParameters() { sed -i "s,ShardMinNumNodes\([^,]*\),ShardMinNumNodes = $SHARD_CONSENSUS_SIZE," $tomlFile sed -i "s,MetachainConsensusGroupSize\([^,]*\),MetachainConsensusGroupSize = $META_CONSENSUS_SIZE," $tomlFile sed -i "s,MetachainMinNumNodes\([^,]*\),MetachainMinNumNodes = $META_CONSENSUS_SIZE," $tomlFile - sed -i "s,RoundDuration\([^,]*\),RoundDuration = $ROUND_DURATION_IN_MS," $tomlFile + sed -i "s,RoundDuration = 6000,RoundDuration = $ROUND_DURATION_IN_MS," $tomlFile sed -i "s,Hysteresis\([^,]*\),Hysteresis = $HYSTERESIS," $tomlFile + + if [ $ROUNDS_PER_EPOCH -ne 0 ]; then + NEW_ROUNDS_PER_EPOCH=$((ROUNDS_PER_EPOCH*ROUND_DURATION_IN_MS / 600)) + sed -i "s,RoundsPerEpoch = 2000,RoundsPerEpoch = $NEW_ROUNDS_PER_EPOCH," $tomlFile + sed -i "s,RoundsPerEpoch = 200,RoundsPerEpoch = $ROUNDS_PER_EPOCH," $tomlFile + sed -i "s,MinRoundsBetweenEpochs = 20,MinRoundsBetweenEpochs = $ROUNDS_PER_EPOCH," $tomlFile + fi } updateConfigsForStakingV4() { diff --git a/sharding/chainParametersHolder.go b/sharding/chainParametersHolder.go index 341460d2dbd..a05e2f4ffdb 100644 --- a/sharding/chainParametersHolder.go +++ b/sharding/chainParametersHolder.go @@ -8,6 +8,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/epochStart" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" @@ -97,6 +98,15 @@ func validateChainParameters(chainParametersConfig []config.ChainParametersByEpo if chainParameters.MetachainMinNumNodes < chainParameters.MetachainConsensusGroupSize { return fmt.Errorf("%w for chain parameters with index %d", ErrMinNodesPerShardSmallerThanConsensusSize, idx) } + if chainParameters.RoundsPerEpoch < 1 { + return fmt.Errorf("%w, RoundsPerEpoch < 1", epochStart.ErrInvalidSettingsForEpochStartTrigger) + } + if chainParameters.MinRoundsBetweenEpochs < 1 { + return fmt.Errorf("%w, MinRoundsBetweenEpochs < 1", epochStart.ErrInvalidSettingsForEpochStartTrigger) + } + if chainParameters.MinRoundsBetweenEpochs > chainParameters.RoundsPerEpoch { + return fmt.Errorf("%w, MinRoundsBetweenEpochs > RoundsPerEpoch", epochStart.ErrInvalidSettingsForEpochStartTrigger) + } } return nil diff --git a/sharding/chainParametersHolder_test.go b/sharding/chainParametersHolder_test.go index 7ec5876cc7d..0f760a7d4ef 100644 --- a/sharding/chainParametersHolder_test.go +++ b/sharding/chainParametersHolder_test.go @@ -31,6 +31,8 @@ func TestNewChainParametersHolder(t *testing.T) { RoundDuration: 4000, Hysteresis: 0.2, Adaptivity: false, + RoundsPerEpoch: 20, + MinRoundsBetweenEpochs: 10, }, }, ChainParametersNotifier: &commonmocks.ChainParametersNotifierStub{}, @@ -162,7 +164,7 @@ func TestChainParametersHolder_EpochStartActionShouldCallTheNotifier(t *testing. receivedValues = append(receivedValues, params.ShardConsensusGroupSize) }, } - paramsHolder, _ := NewChainParametersHolder(ArgsChainParametersHolder{ + paramsHolder, err := NewChainParametersHolder(ArgsChainParametersHolder{ ChainParameters: []config.ChainParametersByEpochConfig{ { EnableEpoch: 0, @@ -170,6 +172,8 @@ func TestChainParametersHolder_EpochStartActionShouldCallTheNotifier(t *testing. ShardMinNumNodes: 7, MetachainConsensusGroupSize: 7, MetachainMinNumNodes: 7, + RoundsPerEpoch: 20, + MinRoundsBetweenEpochs: 10, }, { EnableEpoch: 5, @@ -177,11 +181,14 @@ func TestChainParametersHolder_EpochStartActionShouldCallTheNotifier(t *testing. ShardMinNumNodes: 38, MetachainConsensusGroupSize: 7, MetachainMinNumNodes: 7, + RoundsPerEpoch: 20, + MinRoundsBetweenEpochs: 10, }, }, EpochStartEventNotifier: &mock.EpochStartNotifierStub{}, ChainParametersNotifier: notifier, }) + require.NoError(t, err) paramsHolder.EpochStartAction(&block.MetaBlock{Epoch: 5}) require.Equal(t, []uint32{5, 37}, receivedValues) @@ -200,14 +207,17 @@ func TestChainParametersHolder_ChainParametersForEpoch(t *testing.T) { ShardMinNumNodes: 7, MetachainConsensusGroupSize: 7, MetachainMinNumNodes: 7, + RoundsPerEpoch: 20, + MinRoundsBetweenEpochs: 10, }, } - paramsHolder, _ := NewChainParametersHolder(ArgsChainParametersHolder{ + paramsHolder, err := NewChainParametersHolder(ArgsChainParametersHolder{ ChainParameters: params, EpochStartEventNotifier: &mock.EpochStartNotifierStub{}, ChainParametersNotifier: &commonmocks.ChainParametersNotifierStub{}, }) + require.NoError(t, err) res, _ := paramsHolder.ChainParametersForEpoch(0) require.Equal(t, uint32(5), res.ShardConsensusGroupSize) @@ -232,6 +242,8 @@ func TestChainParametersHolder_ChainParametersForEpoch(t *testing.T) { ShardMinNumNodes: 7, MetachainConsensusGroupSize: 7, MetachainMinNumNodes: 7, + RoundsPerEpoch: 20, + MinRoundsBetweenEpochs: 10, }, { EnableEpoch: 10, @@ -239,6 +251,8 @@ func TestChainParametersHolder_ChainParametersForEpoch(t *testing.T) { ShardMinNumNodes: 70, MetachainConsensusGroupSize: 70, MetachainMinNumNodes: 70, + RoundsPerEpoch: 20, + MinRoundsBetweenEpochs: 10, }, { EnableEpoch: 100, @@ -246,14 +260,17 @@ func TestChainParametersHolder_ChainParametersForEpoch(t *testing.T) { ShardMinNumNodes: 700, MetachainConsensusGroupSize: 700, MetachainMinNumNodes: 700, + RoundsPerEpoch: 20, + MinRoundsBetweenEpochs: 10, }, } - paramsHolder, _ := NewChainParametersHolder(ArgsChainParametersHolder{ + paramsHolder, err := NewChainParametersHolder(ArgsChainParametersHolder{ ChainParameters: params, EpochStartEventNotifier: &mock.EpochStartNotifierStub{}, ChainParametersNotifier: &commonmocks.ChainParametersNotifierStub{}, }) + require.NoError(t, err) for i := 0; i < 200; i++ { res, _ := paramsHolder.ChainParametersForEpoch(uint32(i)) @@ -281,6 +298,8 @@ func TestChainParametersHolder_CurrentChainParameters(t *testing.T) { ShardMinNumNodes: 7, MetachainConsensusGroupSize: 7, MetachainMinNumNodes: 7, + RoundsPerEpoch: 20, + MinRoundsBetweenEpochs: 10, }, { EnableEpoch: 10, @@ -288,14 +307,17 @@ func TestChainParametersHolder_CurrentChainParameters(t *testing.T) { ShardMinNumNodes: 70, MetachainConsensusGroupSize: 70, MetachainMinNumNodes: 70, + RoundsPerEpoch: 20, + MinRoundsBetweenEpochs: 10, }, } - paramsHolder, _ := NewChainParametersHolder(ArgsChainParametersHolder{ + paramsHolder, err := NewChainParametersHolder(ArgsChainParametersHolder{ ChainParameters: params, EpochStartEventNotifier: &mock.EpochStartNotifierStub{}, ChainParametersNotifier: &commonmocks.ChainParametersNotifierStub{}, }) + require.NoError(t, err) paramsHolder.EpochStartAction(&block.MetaBlock{Epoch: 0}) require.Equal(t, uint32(5), paramsHolder.CurrentChainParameters().ShardConsensusGroupSize) @@ -320,6 +342,8 @@ func TestChainParametersHolder_AllChainParameters(t *testing.T) { ShardMinNumNodes: 7, MetachainConsensusGroupSize: 7, MetachainMinNumNodes: 7, + RoundsPerEpoch: 20, + MinRoundsBetweenEpochs: 10, }, { EnableEpoch: 10, @@ -327,14 +351,17 @@ func TestChainParametersHolder_AllChainParameters(t *testing.T) { ShardMinNumNodes: 70, MetachainConsensusGroupSize: 70, MetachainMinNumNodes: 70, + RoundsPerEpoch: 20, + MinRoundsBetweenEpochs: 10, }, } - paramsHolder, _ := NewChainParametersHolder(ArgsChainParametersHolder{ + paramsHolder, err := NewChainParametersHolder(ArgsChainParametersHolder{ ChainParameters: params, EpochStartEventNotifier: &mock.EpochStartNotifierStub{}, ChainParametersNotifier: &commonmocks.ChainParametersNotifierStub{}, }) + require.NoError(t, err) returnedAllChainsParameters := paramsHolder.AllChainParameters() require.Equal(t, params, returnedAllChainsParameters) @@ -346,6 +373,8 @@ func TestChainParametersHolder_ConcurrentOperations(t *testing.T) { for i := uint32(0); i <= 100; i += 5 { chainParams = append(chainParams, config.ChainParametersByEpochConfig{ RoundDuration: 4000, + RoundsPerEpoch: 200, + MinRoundsBetweenEpochs: 20, Hysteresis: 0.2, EnableEpoch: i, ShardConsensusGroupSize: i*10 + 1, @@ -356,11 +385,12 @@ func TestChainParametersHolder_ConcurrentOperations(t *testing.T) { }) } - paramsHolder, _ := NewChainParametersHolder(ArgsChainParametersHolder{ + paramsHolder, err := NewChainParametersHolder(ArgsChainParametersHolder{ ChainParameters: chainParams, EpochStartEventNotifier: &mock.EpochStartNotifierStub{}, ChainParametersNotifier: &commonmocks.ChainParametersNotifierStub{}, }) + require.NoError(t, err) numOperations := 500 wg := sync.WaitGroup{} diff --git a/sharding/interface.go b/sharding/interface.go index 06191510539..71b1eddccc2 100644 --- a/sharding/interface.go +++ b/sharding/interface.go @@ -31,15 +31,15 @@ type PeerAccountListAndRatingHandler interface { // GetSignedBlocksThreshold gets the threshold for the minimum signed blocks GetSignedBlocksThreshold() float32 // ComputeIncreaseProposer computes the new rating for the increaseLeader - ComputeIncreaseProposer(shardId uint32, currentRating uint32) uint32 + ComputeIncreaseProposer(shardId uint32, currentRating uint32, epoch uint32) uint32 // ComputeDecreaseProposer computes the new rating for the decreaseLeader - ComputeDecreaseProposer(shardId uint32, currentRating uint32, consecutiveMisses uint32) uint32 + ComputeDecreaseProposer(shardId uint32, currentRating uint32, consecutiveMisses uint32, epoch uint32) uint32 // RevertIncreaseValidator computes the new rating if a revert for increaseProposer should be done - RevertIncreaseValidator(shardId uint32, currentRating uint32, nrReverts uint32) uint32 + RevertIncreaseValidator(shardId uint32, currentRating uint32, nrReverts uint32, epoch uint32) uint32 // ComputeIncreaseValidator computes the new rating for the increaseValidator - ComputeIncreaseValidator(shardId uint32, currentRating uint32) uint32 + ComputeIncreaseValidator(shardId uint32, currentRating uint32, epoch uint32) uint32 // ComputeDecreaseValidator computes the new rating for the decreaseValidator - ComputeDecreaseValidator(shardId uint32, currentRating uint32) uint32 + ComputeDecreaseValidator(shardId uint32, currentRating uint32, epoch uint32) uint32 // IsInterfaceNil verifies if the interface is nil IsInterfaceNil() bool } diff --git a/sharding/mock/enableEpochsHandlerMock.go b/sharding/mock/enableEpochsHandlerMock.go index 48dfcedfa52..d0fb78bd7cb 100644 --- a/sharding/mock/enableEpochsHandlerMock.go +++ b/sharding/mock/enableEpochsHandlerMock.go @@ -12,6 +12,11 @@ type EnableEpochsHandlerMock struct { CurrentEpoch uint32 } +// GetAllEnableEpochs - +func (mock *EnableEpochsHandlerMock) GetAllEnableEpochs() map[string]uint32 { + return map[string]uint32{} +} + // GetActivationEpoch - func (mock *EnableEpochsHandlerMock) GetActivationEpoch(flag core.EnableEpochFlag) uint32 { switch flag { diff --git a/sharding/nodesCoordinator/hashValidatorShuffler.go b/sharding/nodesCoordinator/hashValidatorShuffler.go index 27e2459e2dd..4a763c174d0 100644 --- a/sharding/nodesCoordinator/hashValidatorShuffler.go +++ b/sharding/nodesCoordinator/hashValidatorShuffler.go @@ -734,7 +734,7 @@ func computeNeededNodes(destination []Validator, source []Validator, maxNumNodes return numNeededNodes } -// distributeNewNodes distributes a list of validators to the given validators map +// distributeValidators distributes a list of validators to the given validators map func distributeValidators(destLists map[uint32][]Validator, validators []Validator, randomness []byte, balanced bool) error { if len(destLists) == 0 { return ErrNilOrEmptyDestinationForDistribute diff --git a/sharding/nodesCoordinator/indexHashedNodesCoordinator.go b/sharding/nodesCoordinator/indexHashedNodesCoordinator.go index 9d1fe16551a..956c76778a7 100644 --- a/sharding/nodesCoordinator/indexHashedNodesCoordinator.go +++ b/sharding/nodesCoordinator/indexHashedNodesCoordinator.go @@ -701,7 +701,7 @@ func (ihnc *indexHashedNodesCoordinator) EpochStartPrepare(metaHdr data.HeaderHa return } - _, castOk := metaHdr.(*block.MetaBlock) + _, castOk := metaHdr.(data.MetaHeaderHandler) if !castOk { log.Error("could not process EpochStartPrepare on nodesCoordinator - not metaBlock") return diff --git a/state/accounts/userAccount.go b/state/accounts/userAccount.go index 4d7d280fdcf..d7bb1055e08 100644 --- a/state/accounts/userAccount.go +++ b/state/accounts/userAccount.go @@ -166,7 +166,7 @@ func (a *userAccount) GetAllLeaves( leavesChannels *common.TrieIteratorChannels, ctx context.Context, ) error { - dt := a.dataTrieInteractor.DataTrie() + dt := a.DataTrie() if check.IfNil(dt) { return errors.ErrNilTrie } @@ -181,7 +181,7 @@ func (a *userAccount) GetAllLeaves( // IsDataTrieMigrated returns true if the data trie is migrated to the latest version func (a *userAccount) IsDataTrieMigrated() (bool, error) { - dt := a.dataTrieInteractor.DataTrie() + dt := a.DataTrie() if check.IfNil(dt) { return false, errors.ErrNilTrie } diff --git a/state/accountsDB.go b/state/accountsDB.go index dafb1cbb0ce..9d57fcc06a5 100644 --- a/state/accountsDB.go +++ b/state/accountsDB.go @@ -89,6 +89,7 @@ type AccountsDB struct { mutOp sync.RWMutex loadCodeMeasurements *loadingMeasurements addressConverter core.PubkeyConverter + pruningEnabled bool stackDebug []byte } @@ -105,6 +106,7 @@ type ArgsAccountsDB struct { AddressConverter core.PubkeyConverter SnapshotsManager SnapshotsManager StateAccessesCollector StateAccessesCollector + PruningEnabled bool } // NewAccountsDB creates a new account manager @@ -134,6 +136,7 @@ func createAccountsDb(args ArgsAccountsDB) *AccountsDB { addressConverter: args.AddressConverter, snapshotsManger: args.SnapshotsManager, stateAccessesCollector: args.StateAccessesCollector, + pruningEnabled: args.PruningEnabled, } } @@ -526,7 +529,7 @@ func (adb *AccountsDB) loadDataTrieConcurrentSafe(accountHandler baseAccountHand return nil } -// SaveDataTrie is used to save the data trie (not committing it) and to recompute the new Root value +// saveDataTrie is used to save the data trie (not committing it) and to recompute the new Root value // If data is not dirtied, method will not create its JournalEntries to keep track of data modification func (adb *AccountsDB) saveDataTrie(accountHandler baseAccountHandler) ([]*stateChange.DataTrieChange, error) { newValues, oldValues, err := accountHandler.SaveDirtyData(adb.mainTrie) @@ -919,11 +922,6 @@ func (adb *AccountsDB) commit() ([]byte, error) { log.Trace("accountsDB.Commit started") adb.entries = make([]JournalEntry, 0) - err := adb.stateAccessesCollector.Store() - if err != nil { - return nil, err - } - oldHashes := make(common.ModifiedHashes) newHashes := make(common.ModifiedHashes) // Step 1. commit all data tries @@ -939,7 +937,7 @@ func (adb *AccountsDB) commit() ([]byte, error) { oldRoot := adb.mainTrie.GetOldRoot() // Step 2. commit main trie - err = adb.commitTrie(adb.mainTrie, oldHashes, newHashes) + err := adb.commitTrie(adb.mainTrie, oldHashes, newHashes) if err != nil { return nil, err } @@ -950,6 +948,11 @@ func (adb *AccountsDB) commit() ([]byte, error) { return nil, err } + err = adb.stateAccessesCollector.CommitCollectedAccesses(newRoot) + if err != nil { + return nil, err + } + err = adb.markForEviction(oldRoot, newRoot, oldHashes, newHashes) if err != nil { return nil, err @@ -971,7 +974,7 @@ func (adb *AccountsDB) markForEviction( oldHashes common.ModifiedHashes, newHashes common.ModifiedHashes, ) error { - if !adb.mainTrie.GetStorageManager().IsPruningEnabled() { + if !adb.pruningEnabled { return nil } @@ -985,7 +988,7 @@ func (adb *AccountsDB) markForEviction( } func (adb *AccountsDB) commitTrie(tr common.Trie, oldHashes common.ModifiedHashes, newHashes common.ModifiedHashes) error { - if adb.mainTrie.GetStorageManager().IsPruningEnabled() { + if adb.pruningEnabled { oldTrieHashes := tr.GetObsoleteHashes() newTrieHashes, err := tr.GetDirtyHashes() if err != nil { @@ -1029,6 +1032,20 @@ func (adb *AccountsDB) RecreateTrie(options common.RootHashHolder) error { return nil } +// RecreateTrieIfNeeded is used to reload the trie based on the provided options if the root hash is different than the current one +func (adb *AccountsDB) RecreateTrieIfNeeded(options common.RootHashHolder) error { + err := adb.recreateTrieIfNeeded(options) + if err != nil { + return err + } + + adb.mutOp.Lock() + adb.lastRootHash = options.GetRootHash() + adb.mutOp.Unlock() + + return nil +} + func (adb *AccountsDB) recreateTrie(options common.RootHashHolder) error { log.Trace("accountsDB.RecreateTrie", "root hash holder", options.String()) defer func() { @@ -1052,6 +1069,23 @@ func (adb *AccountsDB) recreateTrie(options common.RootHashHolder) error { return nil } +func (adb *AccountsDB) recreateTrieIfNeeded(options common.RootHashHolder) error { + currentRootHash, err := adb.getMainTrie().RootHash() + if err != nil { + return err + } + + if bytes.Equal(currentRootHash, options.GetRootHash()) { + log.Trace("accountsDB.RecreateTrieIfNeeded - no need to recreate", "root hash", currentRootHash) + return nil + } + + adb.mutOp.Lock() + defer adb.mutOp.Unlock() + + return adb.recreateTrie(options) +} + // RecreateAllTries recreates all the tries from the accounts DB func (adb *AccountsDB) RecreateAllTries(rootHash []byte) (map[string]common.Trie, error) { leavesChannels := &common.TrieIteratorChannels{ @@ -1143,7 +1177,7 @@ func (adb *AccountsDB) GetTrie(rootHash []byte) (common.Trie, error) { return adb.getMainTrie().Recreate(rootHashHolder) } -// Journalize adds a new object to entries list. +// journalize adds a new object to entries list. func (adb *AccountsDB) journalize(entry JournalEntry) { if check.IfNil(entry) { return @@ -1180,14 +1214,35 @@ func (adb *AccountsDB) GetStackDebugFirstEntry() []byte { func (adb *AccountsDB) PruneTrie(rootHash []byte, identifier TriePruningIdentifier, handler PruningHandler) { log.Trace("accountsDB.PruneTrie", "root hash", rootHash) - adb.storagePruningManager.PruneTrie(rootHash, identifier, adb.getMainTrie().GetStorageManager(), handler) + adb.mutOp.Lock() + defer adb.mutOp.Unlock() + + adb.storagePruningManager.PruneTrie(rootHash, identifier, adb.mainTrie.GetStorageManager(), handler) } // CancelPrune clears the trie's evictionWaitingList func (adb *AccountsDB) CancelPrune(rootHash []byte, identifier TriePruningIdentifier) { log.Trace("accountsDB.CancelPrune", "root hash", rootHash) - adb.storagePruningManager.CancelPrune(rootHash, identifier, adb.getMainTrie().GetStorageManager()) + adb.mutOp.Lock() + defer adb.mutOp.Unlock() + + adb.storagePruningManager.CancelPrune(rootHash, identifier, adb.mainTrie.GetStorageManager()) +} + +// GetEvictionWaitingListSize returns the number of entries in the eviction waiting list cache +func (adb *AccountsDB) GetEvictionWaitingListSize() int { + adb.mutOp.RLock() + defer adb.mutOp.RUnlock() + return adb.storagePruningManager.EvictionWaitingListCacheLen() +} + +// ResetPruning will reset all collected data needed for pruning +func (adb *AccountsDB) ResetPruning() { + adb.mutOp.Lock() + defer adb.mutOp.Unlock() + + adb.storagePruningManager.Reset() } // SnapshotState triggers the snapshotting process of the state trie @@ -1219,7 +1274,7 @@ func emptyErrChanReturningHadContained(errChan chan error) bool { // IsPruningEnabled returns true if state pruning is enabled func (adb *AccountsDB) IsPruningEnabled() bool { - return adb.getMainTrie().GetStorageManager().IsPruningEnabled() + return adb.pruningEnabled } // GetAllLeaves returns all the leaves from a given rootHash diff --git a/state/accountsDBApi.go b/state/accountsDBApi.go index 768042570c2..cc940c77e5e 100644 --- a/state/accountsDBApi.go +++ b/state/accountsDBApi.go @@ -166,21 +166,46 @@ func (accountsDB *accountsDBApi) RootHash() ([]byte, error) { return blockInfo.GetRootHash(), nil } -// RecreateTrie is a not permitted operation in this implementation and thus, will return an error +// RecreateTrie is used to reload the trie based on the provided options func (accountsDB *accountsDBApi) RecreateTrie(options common.RootHashHolder) error { + if check.IfNil(options) { + return ErrNilRootHashHolder + } + accountsDB.mutRecreatedTrieBlockInfo.Lock() defer accountsDB.mutRecreatedTrieBlockInfo.Unlock() + newBlockInfo := holders.NewBlockInfo([]byte{}, 0, options.GetRootHash()) + if newBlockInfo.Equal(accountsDB.blockInfo) { + return nil + } + + err := accountsDB.innerAccountsAdapter.RecreateTrie(options) + if err != nil { + accountsDB.blockInfo = nil + return err + } + + accountsDB.blockInfo = newBlockInfo + + return nil +} + +// RecreateTrieIfNeeded is used to reload the trie based on the provided options only if the root hash is different than the current one +func (accountsDB *accountsDBApi) RecreateTrieIfNeeded(options common.RootHashHolder) error { if check.IfNil(options) { return ErrNilRootHashHolder } + accountsDB.mutRecreatedTrieBlockInfo.Lock() + defer accountsDB.mutRecreatedTrieBlockInfo.Unlock() + newBlockInfo := holders.NewBlockInfo([]byte{}, 0, options.GetRootHash()) if newBlockInfo.Equal(accountsDB.blockInfo) { return nil } - err := accountsDB.innerAccountsAdapter.RecreateTrie(options) + err := accountsDB.innerAccountsAdapter.RecreateTrieIfNeeded(options) if err != nil { accountsDB.blockInfo = nil return err @@ -199,6 +224,10 @@ func (accountsDB *accountsDBApi) PruneTrie(_ []byte, _ TriePruningIdentifier, _ func (accountsDB *accountsDBApi) CancelPrune(_ []byte, _ TriePruningIdentifier) { } +// ResetPruning is a not permitted operation in this implementation and thus, does nothing +func (accountsDB *accountsDBApi) ResetPruning() { +} + // SnapshotState is a not permitted operation in this implementation and thus, does nothing func (accountsDB *accountsDBApi) SnapshotState(_ []byte, _ uint32) { } @@ -279,6 +308,11 @@ func (accountsDB *accountsDBApi) GetCodeWithBlockInfo(codeHash []byte, _ common. return accountsDB.innerAccountsAdapter.GetCode(codeHash), blockInfo, nil } +// GetEvictionWaitingListSize returns 0 for the API accounts adapter +func (adb *accountsDBApi) GetEvictionWaitingListSize() int { + return 0 +} + // IsInterfaceNil returns true if there is no value under the interface func (accountsDB *accountsDBApi) IsInterfaceNil() bool { return accountsDB == nil diff --git a/state/accountsDBApiWithHistory.go b/state/accountsDBApiWithHistory.go index 7e9ad7bf760..8515aa657f2 100644 --- a/state/accountsDBApiWithHistory.go +++ b/state/accountsDBApiWithHistory.go @@ -99,6 +99,11 @@ func (accountsDB *accountsDBApiWithHistory) RecreateTrie(_ common.RootHashHolder return ErrOperationNotPermitted } +// RecreateTrieIfNeeded is a not permitted operation in this implementation and thus, will return an error +func (accountsDB *accountsDBApiWithHistory) RecreateTrieIfNeeded(_ common.RootHashHolder) error { + return ErrOperationNotPermitted +} + // PruneTrie is a not permitted operation in this implementation and thus, does nothing func (accountsDB *accountsDBApiWithHistory) PruneTrie(_ []byte, _ TriePruningIdentifier, _ PruningHandler) { } @@ -107,6 +112,10 @@ func (accountsDB *accountsDBApiWithHistory) PruneTrie(_ []byte, _ TriePruningIde func (accountsDB *accountsDBApiWithHistory) CancelPrune(_ []byte, _ TriePruningIdentifier) { } +// ResetPruning is a not permitted operation in this implementation and thus, does nothing +func (accountsDB *accountsDBApiWithHistory) ResetPruning() { +} + // SnapshotState is a not permitted operation in this implementation and thus, does nothing func (accountsDB *accountsDBApiWithHistory) SnapshotState(_ []byte, _ uint32) { } @@ -237,6 +246,11 @@ func (accountsDB *accountsDBApiWithHistory) recreateTrieUnprotected(options comm return nil } +// GetEvictionWaitingListSize returns 0 for the API accounts adapter with history +func (adb *accountsDBApiWithHistory) GetEvictionWaitingListSize() int { + return 0 +} + // IsInterfaceNil returns true if there is no value under the interface func (accountsDB *accountsDBApiWithHistory) IsInterfaceNil() bool { return accountsDB == nil diff --git a/state/accountsDBApi_test.go b/state/accountsDBApi_test.go index 35f80992d5a..95caa143fcb 100644 --- a/state/accountsDBApi_test.go +++ b/state/accountsDBApi_test.go @@ -16,7 +16,9 @@ import ( "github.com/multiversx/mx-chain-go/state/parsers" "github.com/multiversx/mx-chain-go/testscommon" mockState "github.com/multiversx/mx-chain-go/testscommon/state" + "github.com/multiversx/mx-chain-go/testscommon/storageManager" testTrie "github.com/multiversx/mx-chain-go/testscommon/trie" + "github.com/multiversx/mx-chain-go/trie" vmcommon "github.com/multiversx/mx-chain-vm-common-go" "github.com/stretchr/testify/assert" @@ -213,17 +215,157 @@ func TestAccountsDBApi_NotPermittedOperations(t *testing.T) { func TestAccountsDBApi_RecreateTrie(t *testing.T) { t.Parallel() - wasCalled := false - accountsApi, _ := state.NewAccountsDBApi(&mockState.AccountsStub{ - RecreateTrieCalled: func(rootHash common.RootHashHolder) error { - wasCalled = true - return nil - }, - }, createBlockInfoProviderStub(dummyRootHash)) + t.Run("should error if the roothash holder is nil", func(t *testing.T) { + t.Parallel() + accountsApi, _ := state.NewAccountsDBApi(&mockState.AccountsStub{ + RecreateTrieCalled: func(options common.RootHashHolder) error { + assert.Fail(t, "should have not called accountsApi.RecreateTrie") + + return nil + }, + }, createBlockInfoProviderStub(dummyRootHash)) + + err := accountsApi.RecreateTrie(nil) + assert.Equal(t, trie.ErrNilRootHashHolder, err) + }) + + t.Run("should error if the inner account RecreateTrie errors", func(t *testing.T) { + t.Parallel() + expectedErr := errors.New("root hash err") + trieStub := &testTrie.TrieStub{ + GetStorageManagerCalled: func() common.StorageManager { + return &storageManager.StorageManagerStub{} + }, + } + trieStub.RecreateCalled = func(_ common.RootHashHolder) (tree common.Trie, e error) { + return nil, expectedErr + } + + adb := generateAccountDBFromTrie(trieStub) + accountsApi, _ := state.NewAccountsDBApi(adb, createBlockInfoProviderStub(dummyRootHash)) + + err := accountsApi.RecreateTrie(holders.NewDefaultRootHashesHolder([]byte("hash"))) + assert.Equal(t, expectedErr, err) + }) + + t.Run("should early exit if block info is the same", func(t *testing.T) { + t.Parallel() + currentHash := []byte("hash1") + accountsApi, _ := state.NewAccountsDBApi(&mockState.AccountsStub{ + RecreateTrieIfNeededCalled: func(options common.RootHashHolder) error { + assert.Fail(t, "should have not called accountsApi.RecreateTrieIfNeeded") + + return nil + }, + }, createBlockInfoProviderStub(dummyRootHash)) + accountsApi.SetCurrentBlockInfo(holders.NewBlockInfo(nil, 0, currentHash)) + + err := accountsApi.RecreateTrie(holders.NewDefaultRootHashesHolder(currentHash)) + assert.NoError(t, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + wasCalled := false + accountsApi, _ := state.NewAccountsDBApi(&mockState.AccountsStub{ + RecreateTrieCalled: func(rootHash common.RootHashHolder) error { + wasCalled = true + return nil + }, + }, createBlockInfoProviderStub(dummyRootHash)) - err := accountsApi.RecreateTrie(holders.NewDefaultRootHashesHolder([]byte{})) - assert.NoError(t, err) - assert.True(t, wasCalled) + err := accountsApi.RecreateTrie(holders.NewDefaultRootHashesHolder([]byte{})) + assert.NoError(t, err) + assert.True(t, wasCalled) + }) +} + +func TestAccountsDB_RecreateTrieIfNeeded(t *testing.T) { + t.Parallel() + + t.Run("should error if the roothash holder is nil", func(t *testing.T) { + t.Parallel() + accountsApi, _ := state.NewAccountsDBApi(&mockState.AccountsStub{ + RecreateTrieIfNeededCalled: func(options common.RootHashHolder) error { + assert.Fail(t, "should have not called accountsApi.RecreateTrieIfNeeded") + + return nil + }, + }, createBlockInfoProviderStub(dummyRootHash)) + + err := accountsApi.RecreateTrieIfNeeded(nil) + assert.Equal(t, trie.ErrNilRootHashHolder, err) + }) + + t.Run("should error if the inner account RecreateTrieIfNeeded errors", func(t *testing.T) { + t.Parallel() + expectedErr := errors.New("root hash err") + trieStub := &testTrie.TrieStub{ + RootCalled: func() ([]byte, error) { + return nil, expectedErr + }, + GetStorageManagerCalled: func() common.StorageManager { + return &storageManager.StorageManagerStub{} + }, + } + adb := generateAccountDBFromTrie(trieStub) + accountsApi, _ := state.NewAccountsDBApi(adb, createBlockInfoProviderStub(dummyRootHash)) + + err := accountsApi.RecreateTrieIfNeeded(holders.NewDefaultRootHashesHolder([]byte("hash"))) + assert.Equal(t, expectedErr, err) + }) + + t.Run("should early exit if block info is the same", func(t *testing.T) { + t.Parallel() + currentHash := []byte("hash1") + trieStub := &testTrie.TrieStub{ + RootCalled: func() ([]byte, error) { + return currentHash, nil + }, + GetStorageManagerCalled: func() common.StorageManager { + return &storageManager.StorageManagerStub{} + }, + } + adb := generateAccountDBFromTrie(trieStub) + accountsApi, _ := state.NewAccountsDBApi(adb, createBlockInfoProviderStub(dummyRootHash)) + accountsApi.SetCurrentBlockInfo(holders.NewBlockInfo(nil, 0, currentHash)) + + err := accountsApi.RecreateTrieIfNeeded(holders.NewDefaultRootHashesHolder(currentHash)) + assert.NoError(t, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + currentHash := []byte("hash1") + newHash := []byte("hash2") + recreatedCalled := false + + // mock trie to check whether RecreateTrie was triggered + trieStub := &testTrie.TrieStub{ + RootCalled: func() ([]byte, error) { + return currentHash, nil + }, + RecreateCalled: func(_ common.RootHashHolder) (common.Trie, error) { + recreatedCalled = true + return &testTrie.TrieStub{}, nil + }, + } + + adb := generateAccountDBFromTrie(trieStub) + accountsApi, _ := state.NewAccountsDBApi(adb, createBlockInfoProviderStub(dummyRootHash)) + + // same hash → no recreation + optsSame := holders.NewDefaultRootHashesHolder(currentHash) + err := accountsApi.RecreateTrieIfNeeded(optsSame) + assert.NoError(t, err) + assert.False(t, recreatedCalled, "should not recreate if same root hash") + + // different hash → should recreate + optsDifferent := holders.NewDefaultRootHashesHolder(newHash) + err = accountsApi.RecreateTrieIfNeeded(optsDifferent) + assert.NoError(t, err) + assert.True(t, recreatedCalled, "should recreate if root hash differs") + }) } func TestAccountsDBApi_RecreateTrieFromEpoch(t *testing.T) { diff --git a/state/accountsDB_test.go b/state/accountsDB_test.go index 268565b8b44..2f2125c7737 100644 --- a/state/accountsDB_test.go +++ b/state/accountsDB_test.go @@ -181,6 +181,7 @@ func getDefaultStateComponents( AddressConverter: &testscommon.PubkeyConverterMock{}, SnapshotsManager: snapshotsManager, StateAccessesCollector: collector, + PruningEnabled: true, } adb, _ := state.NewAccountsDB(argsAccountsDB) @@ -451,7 +452,7 @@ func TestAccountsDB_MigrateDataTrieLeafCollectsDeleteAndUpdateStateChanges(t *te txHash := []byte("accountCreationTxHash") adb.SetTxHashForLatestStateAccesses(txHash) - _, err := adb.ResetStateAccessesCollector() + _, err := adb.ResetStateAccessesCollector([]byte{}) assert.Nil(t, err) // enable auto-balance and migrate the data trie leaf @@ -473,7 +474,10 @@ func TestAccountsDB_MigrateDataTrieLeafCollectsDeleteAndUpdateStateChanges(t *te txHash = []byte("accountMigrationTxHash") adb.SetTxHashForLatestStateAccesses(txHash) - collectedStateAccesses, err := adb.ResetStateAccessesCollector() + rootHash, err := adb.Commit() + assert.Nil(t, err) + + collectedStateAccesses, err := adb.ResetStateAccessesCollector(rootHash) assert.Nil(t, err) assert.Equal(t, 1, len(collectedStateAccesses)) @@ -523,7 +527,7 @@ func TestAccountsDB_DeleteStateChangesHaveProperVersion(t *testing.T) { txHash := []byte("accountCreationTxHash") adb.SetTxHashForLatestStateAccesses(txHash) - _, err := adb.ResetStateAccessesCollector() + _, err := adb.ResetStateAccessesCollector([]byte{}) assert.Nil(t, err) // enable auto-balance and delete the data trie leaf @@ -536,7 +540,10 @@ func TestAccountsDB_DeleteStateChangesHaveProperVersion(t *testing.T) { txHash = []byte("DeleteDataTrieTxHash") adb.SetTxHashForLatestStateAccesses(txHash) - collectedStateAccesses, err := adb.ResetStateAccessesCollector() + rootHash, err := adb.Commit() + assert.Nil(t, err) + + collectedStateAccesses, err := adb.ResetStateAccessesCollector(rootHash) assert.Nil(t, err) assert.Equal(t, 1, len(collectedStateAccesses)) @@ -595,7 +602,10 @@ func stepCreateAccountWithDataTrieAndCode( serializedAcc, _ := marshaller.Marshal(userAcc) codeHash := userAcc.GetCodeHash() - stateChangesForTx, err := adb.ResetStateAccessesCollector() + rootHash, err := adb.Commit() + assert.Nil(t, err) + + stateChangesForTx, err := adb.ResetStateAccessesCollector(rootHash) assert.Nil(t, err) assert.Equal(t, 1, len(stateChangesForTx)) @@ -642,7 +652,10 @@ func stepMigrateDataTrieValAndChangeCode( adb.SetTxHashForLatestStateAccesses(txHash) - stateChangesForTx, err := adb.ResetStateAccessesCollector() + rootHash, err := adb.Commit() + assert.Nil(t, err) + + stateChangesForTx, err := adb.ResetStateAccessesCollector(rootHash) assert.Nil(t, err) assert.Equal(t, 1, len(stateChangesForTx)) assert.Equal(t, 3, len(stateChangesForTx[string(txHash)].StateAccess)) @@ -1244,6 +1257,77 @@ func TestAccountsDB_RecreateTrieOkValsShouldWork(t *testing.T) { assert.True(t, wasCalled) } +// ------- RecreateTrieIfNeeded + +func TestAccountsDB_RecreateTrieIfNeededShouldRecreate(t *testing.T) { + t.Parallel() + + currentHash := []byte("hash1") + newHash := []byte("hash2") + + recreatedCalled := false + trieStub := &trieMock.TrieStub{ + RootCalled: func() ([]byte, error) { + return currentHash, nil + }, + RecreateCalled: func(_ common.RootHashHolder) (common.Trie, error) { + recreatedCalled = true + return &trieMock.TrieStub{}, nil + }, + GetStorageManagerCalled: func() common.StorageManager { + return &storageManager.StorageManagerStub{} + }, + } + + adb := generateAccountDBFromTrie(trieStub) + holder := holders.NewDefaultRootHashesHolder(newHash) + + err := adb.RecreateTrieIfNeeded(holder) + assert.Nil(t, err) + assert.True(t, recreatedCalled) +} + +func TestAccountsDB_RecreateTrieIfNeededShouldNotRecreate(t *testing.T) { + t.Parallel() + + currentHash := []byte("hash1") + recreatedCalled := false + trieStub := &trieMock.TrieStub{ + RootCalled: func() ([]byte, error) { + return currentHash, nil + }, + RecreateCalled: func(_ common.RootHashHolder) (common.Trie, error) { + recreatedCalled = true + return &trieMock.TrieStub{}, nil + }, + } + + adb := generateAccountDBFromTrie(trieStub) + holder := holders.NewDefaultRootHashesHolder(currentHash) + + err := adb.RecreateTrieIfNeeded(holder) + assert.Nil(t, err) + assert.False(t, recreatedCalled) +} + +func TestAccountsDB_RecreateTrieIfNeededRootHashError(t *testing.T) { + t.Parallel() + + expectedErr := errors.New("root hash err") + trieStub := &trieMock.TrieStub{ + RootCalled: func() ([]byte, error) { + return nil, expectedErr + }, + GetStorageManagerCalled: func() common.StorageManager { + return &storageManager.StorageManagerStub{} + }, + } + + adb := generateAccountDBFromTrie(trieStub) + err := adb.RecreateTrieIfNeeded(holders.NewDefaultRootHashesHolder([]byte("x"))) + assert.Equal(t, expectedErr, err) +} + func TestAccountsDB_SnapshotState(t *testing.T) { t.Parallel() @@ -1574,19 +1658,16 @@ func TestAccountsDB_SnapshotStateCallsRemoveFromAllActiveEpochs(t *testing.T) { func TestAccountsDB_IsPruningEnabled(t *testing.T) { t.Parallel() - trieStub := &trieMock.TrieStub{ - GetStorageManagerCalled: func() common.StorageManager { - return &storageManager.StorageManagerStub{ - IsPruningEnabledCalled: func() bool { - return true - }, - } - }, - } - adb := generateAccountDBFromTrie(trieStub) - res := adb.IsPruningEnabled() + args := createMockAccountsDBArgs() + args.PruningEnabled = true + adb, err := state.NewAccountsDB(args) + assert.Nil(t, err) + assert.True(t, adb.IsPruningEnabled()) - assert.Equal(t, true, res) + args.PruningEnabled = false + adb, err = state.NewAccountsDB(args) + assert.Nil(t, err) + assert.False(t, adb.IsPruningEnabled()) } func TestAccountsDB_RevertToSnapshotOutOfBounds(t *testing.T) { @@ -2052,6 +2133,7 @@ func TestAccountsDB_MainTrieAutomaticallyMarksCodeUpdatesForEviction(t *testing. spm, _ := storagePruningManager.NewStoragePruningManager(ewl, 5) argsAccountsDB := createMockAccountsDBArgs() + argsAccountsDB.PruningEnabled = true argsAccountsDB.Trie = tr argsAccountsDB.Hasher = hasher argsAccountsDB.Marshaller = marshaller @@ -2134,6 +2216,7 @@ func TestAccountsDB_RemoveAccountMarksObsoleteHashesForEviction(t *testing.T) { spm, _ := storagePruningManager.NewStoragePruningManager(ewl, 5) argsAccountsDB := createMockAccountsDBArgs() + argsAccountsDB.PruningEnabled = true argsAccountsDB.Trie = tr argsAccountsDB.Hasher = hasher argsAccountsDB.Marshaller = marshaller @@ -3117,6 +3200,7 @@ func TestAccountsDB_RevertTxWhichMigratesDataRemovesMigratedData(t *testing.T) { tr, _ := trie.NewTrie(tsm, marshaller, hasher, enableEpochsHandler, uint(5)) spm := &stateMock.StoragePruningManagerStub{} argsAccountsDB := createMockAccountsDBArgs() + argsAccountsDB.PruningEnabled = true argsAccountsDB.Trie = tr argsAccountsDB.Hasher = hasher argsAccountsDB.Marshaller = marshaller @@ -3191,7 +3275,7 @@ func testAccountMethodsConcurrency( addresses [][]byte, rootHash []byte, ) { - numOperations := 100 + numOperations := 1000 marshaller := &marshallerMock.MarshalizerMock{} wg := sync.WaitGroup{} wg.Add(numOperations) @@ -3207,7 +3291,7 @@ func testAccountMethodsConcurrency( assert.Nil(t, err) for i := 0; i < numOperations; i++ { go func(idx int) { - switch idx % 21 { + switch idx % 22 { case 0: _, _ = adb.GetExistingAccount(addresses[idx]) case 1: @@ -3250,6 +3334,8 @@ func testAccountMethodsConcurrency( _ = adb.GetStackDebugFirstEntry() case 20: _ = adb.SetSyncer(&mock.AccountsDBSyncerStub{}) + case 21: + _ = adb.RecreateTrieIfNeeded(holders.NewDefaultRootHashesHolder(rootHash)) } wg.Done() }(i) diff --git a/state/accountsEphemeralProvider.go b/state/accountsEphemeralProvider.go new file mode 100644 index 00000000000..6f8728faa85 --- /dev/null +++ b/state/accountsEphemeralProvider.go @@ -0,0 +1,112 @@ +package state + +import ( + "errors" + "math/big" + + "github.com/multiversx/mx-chain-core-go/core/check" +) + +// AccountsEphemeralProvider acts as an "ephemeral" provider for accounts. +// Make sure such a provider isn't reused among multiple transactions selections or multiple processing (of blocks) phases, +// since it contains a **never-invalidating cache** (deliberate, by design). +// Create it privately (don't receive it in constructors), use it privately, make sure it's forgotten afterwards. Don't keep lasting references to it. +// Note: this structure is exported on purpose (less ceremonious code where it's being used, no extra interfaces needed). +type AccountsEphemeralProvider struct { + accounts AccountsAdapter + // Not concurrency-safe, but should never be accessed concurrently. + cache map[string]UserAccountHandler +} + +// NewAccountsEphemeralProvider creates a new "ephemeral" provider for accounts. +func NewAccountsEphemeralProvider(accounts AccountsAdapter) (*AccountsEphemeralProvider, error) { + if check.IfNil(accounts) { + return nil, ErrNilAccountsAdapter + } + + return &AccountsEphemeralProvider{ + accounts: accounts, + cache: make(map[string]UserAccountHandler), + }, nil +} + +// GetRootHash returns the current root hash +func (provider *AccountsEphemeralProvider) GetRootHash() ([]byte, error) { + return provider.accounts.RootHash() +} + +// GetAccountNonce returns the nonce of the account, and whether it's currently existing on-chain. +func (provider *AccountsEphemeralProvider) GetAccountNonce(address []byte) (uint64, bool, error) { + account, err := provider.GetUserAccount(address) + if err != nil { + // Unexpected failure. + return 0, false, err + } + if check.IfNil(account) { + // New (unknown) account. + return 0, false, nil + } + + return account.GetNonce(), true, nil +} + +// GetAccountNonceAndBalance returns the nonce of the account, the balance of the account, and whether it's currently existing on-chain. +func (provider *AccountsEphemeralProvider) GetAccountNonceAndBalance(address []byte) (uint64, *big.Int, bool, error) { + account, err := provider.GetUserAccount(address) + if err != nil { + // Unexpected failure. + return 0, nil, false, err + } + if check.IfNil(account) { + // New (unknown) account. + return 0, big.NewInt(0), false, nil + } + + return account.GetNonce(), account.GetBalance(), true, nil +} + +// GetUserAccount returns the user account, as found on blockchain. If missing (account not found), nil is returned (with no error). +func (provider *AccountsEphemeralProvider) GetUserAccount(address []byte) (UserAccountHandler, error) { + account, ok := provider.cache[string(address)] + if ok { + // Existing or new (unknown) account, previously-cached. + return account, nil + } + + account, err := provider.getExistingAccountTypedAsUserAccount(address) + + var errAccountNotFoundAtBlock *ErrAccountNotFoundAtBlock + isAccountNotFoundError := errors.Is(err, ErrAccNotFound) || errors.As(err, &errAccountNotFoundAtBlock) + + if err != nil && !isAccountNotFoundError { + // Unexpected failure (error different from "ErrAccNotFound"). + // Account won't be cached. + return nil, err + } + + // Existing account or new (unknown), we'll cache it (actual object or nil). + provider.cache[string(address)] = account + + // Generally speaking, this isn't a good pattern: returning both nil (for unknown accounts), and a nil error. + // However, this is a non-exported method, which should only be called with care, within this struct only. + return account, nil +} + +func (provider *AccountsEphemeralProvider) getExistingAccountTypedAsUserAccount(address []byte) (UserAccountHandler, error) { + account, err := provider.accounts.GetExistingAccount(address) + if err != nil { + return nil, err + } + + userAccount, ok := account.(UserAccountHandler) + if !ok { + return nil, ErrWrongTypeAssertion + } + + return userAccount, nil +} + +// IsInterfaceNil returns true if there is no value under the interface +func (provider *AccountsEphemeralProvider) IsInterfaceNil() bool { + return provider == nil +} diff --git a/state/accountsEphemeralProvider_test.go b/state/accountsEphemeralProvider_test.go new file mode 100644 index 00000000000..32d280609a1 --- /dev/null +++ b/state/accountsEphemeralProvider_test.go @@ -0,0 +1,291 @@ +package state_test + +import ( + "bytes" + "errors" + "math/big" + "testing" + + "github.com/multiversx/mx-chain-go/state" + stateMock "github.com/multiversx/mx-chain-go/testscommon/state" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/stretchr/testify/require" +) + +func TestNewAccountsEphemeralProvider(t *testing.T) { + t.Parallel() + + t.Run("nil accounts adapter should error", func(t *testing.T) { + t.Parallel() + + provider, err := state.NewAccountsEphemeralProvider(nil) + require.Error(t, err, state.ErrNilAccountsAdapter) + require.Nil(t, provider) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + provider, err := state.NewAccountsEphemeralProvider(&stateMock.AccountsStub{}) + require.NoError(t, err) + require.NotNil(t, provider) + }) +} + +func TestAccountsEphemeralProvider_GetRootHash(t *testing.T) { + t.Parallel() + + accounts := &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return []byte("abba"), nil + }, + } + + provider, err := state.NewAccountsEphemeralProvider(accounts) + require.NoError(t, err) + require.NotNil(t, provider) + + rootHash, err := provider.GetRootHash() + require.Nil(t, err) + require.Equal(t, []byte("abba"), rootHash) +} + +func TestAccountsEphemeralProvider_GetAccountNonce(t *testing.T) { + t.Parallel() + + accounts := &stateMock.AccountsStub{} + + accounts.GetExistingAccountCalled = func(address []byte) (vmcommon.AccountHandler, error) { + if bytes.Equal(address, []byte("alice")) { + return &stateMock.UserAccountStub{ + Address: []byte("alice"), + Nonce: 42, + }, nil + } + + if bytes.Equal(address, []byte("bob")) { + return &stateMock.UserAccountStub{ + Address: []byte("bob"), + Nonce: 7, + }, nil + } + + if bytes.Equal(address, []byte("carol")) { + return nil, state.ErrAccNotFound + } + + return nil, errors.New("arbitrary error") + } + + provider, err := state.NewAccountsEphemeralProvider(accounts) + require.NoError(t, err) + require.NotNil(t, provider) + + nonce, exists, err := provider.GetAccountNonce([]byte("alice")) + require.NoError(t, err) + require.Equal(t, uint64(42), nonce) + require.True(t, exists) + + nonce, exists, err = provider.GetAccountNonce([]byte("bob")) + require.NoError(t, err) + require.Equal(t, uint64(7), nonce) + require.True(t, exists) + + // If account is not found, no error is returned. + nonce, exists, err = provider.GetAccountNonce([]byte("carol")) + require.NoError(t, err) + require.Equal(t, uint64(0), nonce) + require.False(t, exists) + + nonce, exists, err = provider.GetAccountNonce([]byte("judy")) + require.ErrorContains(t, err, "arbitrary error") + require.Equal(t, uint64(0), nonce) + require.False(t, exists) +} + +func TestAccountsEphemeralProvider_GetAccountNonceAndBalance(t *testing.T) { + t.Parallel() + + accounts := &stateMock.AccountsStub{} + + accounts.GetExistingAccountCalled = func(address []byte) (vmcommon.AccountHandler, error) { + if bytes.Equal(address, []byte("alice")) { + return &stateMock.UserAccountStub{ + Address: []byte("alice"), + Nonce: 42, + Balance: big.NewInt(3000000000000000000), + }, nil + } + + if bytes.Equal(address, []byte("bob")) { + return &stateMock.UserAccountStub{ + Address: []byte("bob"), + Nonce: 7, + Balance: big.NewInt(1000000000000000000), + }, nil + } + + if bytes.Equal(address, []byte("carol")) { + return nil, state.ErrAccNotFound + } + + return nil, errors.New("arbitrary error") + } + + provider, err := state.NewAccountsEphemeralProvider(accounts) + require.NoError(t, err) + require.NotNil(t, provider) + + nonce, balance, exists, err := provider.GetAccountNonceAndBalance([]byte("alice")) + require.NoError(t, err) + require.Equal(t, uint64(42), nonce) + require.Equal(t, "3000000000000000000", balance.String()) + require.True(t, exists) + + nonce, balance, exists, err = provider.GetAccountNonceAndBalance([]byte("bob")) + require.NoError(t, err) + require.Equal(t, uint64(7), nonce) + require.Equal(t, "1000000000000000000", balance.String()) + require.True(t, exists) + + // If account is not found, no error is returned. + nonce, balance, exists, err = provider.GetAccountNonceAndBalance([]byte("carol")) + require.NoError(t, err) + require.Equal(t, uint64(0), nonce) + require.Equal(t, "0", balance.String()) + require.False(t, exists) + + nonce, balance, exists, err = provider.GetAccountNonceAndBalance([]byte("judy")) + require.ErrorContains(t, err, "arbitrary error") + require.Equal(t, uint64(0), nonce) + require.Nil(t, balance) + require.False(t, exists) +} + +func TestAccountsEphemeralProvider_GetUserAccount(t *testing.T) { + t.Parallel() + + accounts := &stateMock.AccountsStub{} + + accounts.GetExistingAccountCalled = func(address []byte) (vmcommon.AccountHandler, error) { + if bytes.Equal(address, []byte("alice")) { + return &stateMock.UserAccountStub{ + Address: []byte("alice"), + Nonce: 42, + Balance: big.NewInt(3000000000000000000), + }, nil + } + + if bytes.Equal(address, []byte("bob")) { + return &stateMock.UserAccountStub{ + Address: []byte("bob"), + Nonce: 7, + Balance: big.NewInt(1000000000000000000), + }, nil + } + + if bytes.Equal(address, []byte("carol")) { + return nil, state.ErrAccNotFound + } + + return nil, errors.New("arbitrary error") + } + + provider, err := state.NewAccountsEphemeralProvider(accounts) + require.NoError(t, err) + require.NotNil(t, provider) + + account, err := provider.GetUserAccount([]byte("alice")) + require.NoError(t, err) + require.Equal(t, uint64(42), account.GetNonce()) + require.Equal(t, "3000000000000000000", account.GetBalance().String()) + + account, err = provider.GetUserAccount([]byte("bob")) + require.NoError(t, err) + require.Equal(t, uint64(7), account.GetNonce()) + require.Equal(t, "1000000000000000000", account.GetBalance().String()) + + // If account is not found, no error is returned. + account, err = provider.GetUserAccount([]byte("carol")) + require.NoError(t, err) + require.Nil(t, account) + + account, err = provider.GetUserAccount([]byte("judy")) + require.ErrorContains(t, err, "arbitrary error") + require.Nil(t, account) +} + +func TestAccountsEphemeralProvider_GetUserAccount_cacheIsSharedAmongCalls(t *testing.T) { + t.Parallel() + + accounts := &stateMock.AccountsStub{} + + numCallsGetExistingAccount := 0 + + accounts.GetExistingAccountCalled = func(address []byte) (vmcommon.AccountHandler, error) { + numCallsGetExistingAccount++ + + if bytes.Equal(address, []byte("carol")) { + // Missing (not found) accounts should be cached, as well. + return nil, state.ErrAccNotFound + } + + return &stateMock.UserAccountStub{Nonce: 7, Balance: big.NewInt(42)}, nil + } + + provider, err := state.NewAccountsEphemeralProvider(accounts) + require.NoError(t, err) + require.NotNil(t, provider) + + account, err := provider.GetUserAccount([]byte("alice")) + require.NotNil(t, account) + require.Nil(t, err) + require.Equal(t, 1, numCallsGetExistingAccount) + + account, err = provider.GetUserAccount([]byte("alice")) + require.NotNil(t, account) + require.Nil(t, err) + require.Equal(t, 1, numCallsGetExistingAccount) + + nonce, balance, exists, err := provider.GetAccountNonceAndBalance([]byte("alice")) + require.Equal(t, uint64(7), nonce) + require.Equal(t, uint64(42), balance.Uint64()) + require.True(t, exists) + require.Nil(t, err) + require.Equal(t, 1, numCallsGetExistingAccount) + + account, err = provider.GetUserAccount([]byte("bob")) + require.NotNil(t, account) + require.Nil(t, err) + require.Equal(t, 2, numCallsGetExistingAccount) + + account, err = provider.GetUserAccount([]byte("bob")) + require.NotNil(t, account) + require.Nil(t, err) + require.Equal(t, 2, numCallsGetExistingAccount) + + nonce, balance, exists, err = provider.GetAccountNonceAndBalance([]byte("bob")) + require.Equal(t, uint64(7), nonce) + require.Equal(t, uint64(42), balance.Uint64()) + require.True(t, exists) + require.Nil(t, err) + require.Equal(t, 2, numCallsGetExistingAccount) + + // Missing (not found) accounts are cached, as well. + account, err = provider.GetUserAccount([]byte("carol")) + require.Nil(t, account) + require.Nil(t, err) + require.Equal(t, 3, numCallsGetExistingAccount) + + account, err = provider.GetUserAccount([]byte("carol")) + require.Nil(t, account) + require.Nil(t, err) + require.Equal(t, 3, numCallsGetExistingAccount) + + nonce, balance, exists, err = provider.GetAccountNonceAndBalance([]byte("carol")) + require.Equal(t, uint64(0), nonce) + require.Equal(t, uint64(0), balance.Uint64()) + require.False(t, exists) + require.Nil(t, err) + require.Equal(t, 3, numCallsGetExistingAccount) +} diff --git a/state/blockInfoProviders/currentBlockInfo.go b/state/blockInfoProviders/currentBlockInfo.go index f38595f20df..b0fa29926e6 100644 --- a/state/blockInfoProviders/currentBlockInfo.go +++ b/state/blockInfoProviders/currentBlockInfo.go @@ -2,7 +2,7 @@ package blockInfoProviders import ( "github.com/multiversx/mx-chain-core-go/core/check" - chainData "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/common/holders" logger "github.com/multiversx/mx-chain-logger-go" @@ -11,11 +11,11 @@ import ( var log = logger.GetOrCreate("state/blockinfoproviders") type currentBlockInfo struct { - chainHandler chainData.ChainHandler + chainHandler data.ChainHandler } // NewCurrentBlockInfo creates a new instance of type currentBlockInfo -func NewCurrentBlockInfo(chainHandler chainData.ChainHandler) (*currentBlockInfo, error) { +func NewCurrentBlockInfo(chainHandler data.ChainHandler) (*currentBlockInfo, error) { if check.IfNil(chainHandler) { return nil, ErrNilChainHandler } @@ -27,16 +27,23 @@ func NewCurrentBlockInfo(chainHandler chainData.ChainHandler) (*currentBlockInfo // GetBlockInfo returns the current block info func (provider *currentBlockInfo) GetBlockInfo() common.BlockInfo { - block := provider.chainHandler.GetCurrentBlockHeader() + block := provider.chainHandler.GetLastExecutedBlockHeader() if check.IfNil(block) { log.Debug("currentBlockInfo.GetBlockInfo: returning empty block info", "reason", "block is nil") return holders.NewBlockInfo(nil, 0, nil) } - hash := provider.chainHandler.GetCurrentBlockHeaderHash() - rootHash := provider.chainHandler.GetCurrentBlockRootHash() + headerHash, nonce, rootHash := provider.getCurrentBlockInfo(block) + return holders.NewBlockInfo(headerHash, nonce, rootHash) +} + +func (provider *currentBlockInfo) getCurrentBlockInfo(header data.HeaderHandler) ([]byte, uint64, []byte) { + if !header.IsHeaderV3() { + return provider.chainHandler.GetCurrentBlockHeaderHash(), header.GetNonce(), provider.chainHandler.GetCurrentBlockRootHash() + } - return holders.NewBlockInfo(hash, block.GetNonce(), rootHash) + nonce, headerHash, rootHash := provider.chainHandler.GetLastExecutedBlockInfo() + return headerHash, nonce, rootHash } // IsInterfaceNil returns true if there is no value under the interface diff --git a/state/blockInfoProviders/currentBlockInfo_test.go b/state/blockInfoProviders/currentBlockInfo_test.go index b1d0c8792de..848445f0542 100644 --- a/state/blockInfoProviders/currentBlockInfo_test.go +++ b/state/blockInfoProviders/currentBlockInfo_test.go @@ -56,7 +56,7 @@ func TestCurrentBlockInfo_GetBlockInfo(t *testing.T) { hash := []byte("hash") rootHash := []byte("root hash") chainHandler := &testscommon.ChainHandlerStub{ - GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + GetLastExecutedBlockHeaderCalled: func() data.HeaderHandler { return &block.Header{ Nonce: nonce, } diff --git a/state/blockInfoProviders/finalBlockInfo.go b/state/blockInfoProviders/finalBlockInfo.go index 51ce9b090bc..82c3ef5c222 100644 --- a/state/blockInfoProviders/finalBlockInfo.go +++ b/state/blockInfoProviders/finalBlockInfo.go @@ -7,6 +7,9 @@ import ( "github.com/multiversx/mx-chain-go/common/holders" ) +// TODO: refactor to remove (or reuse for Supernova) current and final block info implementations +// - after Andromeda: current (meaning last commited block) is also final +// - after Supernova: current info (meaning last executed results info) will be used in the same way as final info (as last executed results info) type finalBlockInfo struct { chainHandler chainData.ChainHandler } diff --git a/state/disabled/disabledStateChangesCollector.go b/state/disabled/disabledStateChangesCollector.go index ac9615d2cf8..ae0615315b0 100644 --- a/state/disabled/disabledStateChangesCollector.go +++ b/state/disabled/disabledStateChangesCollector.go @@ -43,13 +43,16 @@ func (d *disabledStateAccessesCollector) RevertToIndex(_ int) error { return nil } -// GetCollectedAccesses - -func (d *disabledStateAccessesCollector) GetCollectedAccesses() map[string]*data.StateAccesses { +// GetStateAccessesForRootHash - +func (d *disabledStateAccessesCollector) GetStateAccessesForRootHash(_ []byte) map[string]*data.StateAccesses { return nil } -// Store - -func (d *disabledStateAccessesCollector) Store() error { +func (d *disabledStateAccessesCollector) RemoveStateAccessesForRootHash(_ []byte) { +} + +// CommitCollectedAccesses - +func (d *disabledStateAccessesCollector) CommitCollectedAccesses(_ []byte) error { return nil } diff --git a/state/export_test.go b/state/export_test.go index 59d9e6fdfbf..bbc209312e4 100644 --- a/state/export_test.go +++ b/state/export_test.go @@ -29,8 +29,8 @@ func (adb *AccountsDB) GetObsoleteHashes() map[string][][]byte { } // ResetStateAccessesCollector - -func (adb *AccountsDB) ResetStateAccessesCollector() (map[string]*data.StateAccesses, error) { - stateChanges := adb.stateAccessesCollector.GetCollectedAccesses() +func (adb *AccountsDB) ResetStateAccessesCollector(rootHash []byte) (map[string]*data.StateAccesses, error) { + stateChanges := adb.stateAccessesCollector.GetStateAccessesForRootHash(rootHash) adb.stateAccessesCollector.Reset() return stateChanges, nil diff --git a/state/factory/accountsAdapterAPICreator_test.go b/state/factory/accountsAdapterAPICreator_test.go index ebc75fe97f7..a735fc7f9ce 100644 --- a/state/factory/accountsAdapterAPICreator_test.go +++ b/state/factory/accountsAdapterAPICreator_test.go @@ -12,7 +12,6 @@ import ( "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" mockState "github.com/multiversx/mx-chain-go/testscommon/state" - stateMock "github.com/multiversx/mx-chain-go/testscommon/state" "github.com/multiversx/mx-chain-go/testscommon/storageManager" mockTrie "github.com/multiversx/mx-chain-go/testscommon/trie" ) @@ -30,7 +29,7 @@ func createMockAccountsArgs() state.ArgsAccountsDB { StoragePruningManager: &mockState.StoragePruningManagerStub{}, AddressConverter: &testscommon.PubkeyConverterMock{}, SnapshotsManager: &mockState.SnapshotsManagerStub{}, - StateAccessesCollector: &stateMock.StateAccessesCollectorStub{}, + StateAccessesCollector: &mockState.StateAccessesCollectorStub{}, } } diff --git a/state/interface.go b/state/interface.go index b52d27c09d7..b9c8b988f5f 100644 --- a/state/interface.go +++ b/state/interface.go @@ -81,8 +81,11 @@ type AccountsAdapter interface { GetCode(codeHash []byte) []byte RootHash() ([]byte, error) RecreateTrie(options common.RootHashHolder) error + RecreateTrieIfNeeded(options common.RootHashHolder) error PruneTrie(rootHash []byte, identifier TriePruningIdentifier, handler PruningHandler) CancelPrune(rootHash []byte, identifier TriePruningIdentifier) + ResetPruning() + GetEvictionWaitingListSize() int SnapshotState(rootHash []byte, epoch uint32) IsPruningEnabled() bool GetAllLeaves(leavesChannels *common.TrieIteratorChannels, ctx context.Context, rootHash []byte, trieLeafParser common.TrieLeafParser) error @@ -177,6 +180,8 @@ type DBRemoveCacher interface { Put([]byte, common.ModifiedHashes) error Evict([]byte) (common.ModifiedHashes, error) ShouldKeepHash(hash string, identifier TriePruningIdentifier) (bool, error) + CacheLen() int + Reset() IsInterfaceNil() bool Close() error } @@ -193,6 +198,8 @@ type StoragePruningManager interface { MarkForEviction([]byte, []byte, common.ModifiedHashes, common.ModifiedHashes) error PruneTrie(rootHash []byte, identifier TriePruningIdentifier, tsm common.StorageManager, handler PruningHandler) CancelPrune(rootHash []byte, identifier TriePruningIdentifier, tsm common.StorageManager) + EvictionWaitingListCacheLen() int + Reset() Close() error IsInterfaceNil() bool } @@ -361,8 +368,9 @@ type StateAccessesCollector interface { AddStateAccess(stateAccess *data.StateAccess) GetAccountChanges(oldAccount, account vmcommon.AccountHandler) uint32 Reset() - GetCollectedAccesses() map[string]*data.StateAccesses - Store() error + GetStateAccessesForRootHash(rootHash []byte) map[string]*data.StateAccesses + RemoveStateAccessesForRootHash(rootHash []byte) + CommitCollectedAccesses(rootHash []byte) error AddTxHashToCollectedStateAccesses(txHash []byte) SetIndexToLatestStateAccesses(index int) error RevertToIndex(index int) error diff --git a/state/journalEntries.go b/state/journalEntries.go index 24f65f78312..4aa2b79e9e2 100644 --- a/state/journalEntries.go +++ b/state/journalEntries.go @@ -107,7 +107,7 @@ func (jea *journalEntryCode) IsInterfaceNil() bool { return jea == nil } -// JournalEntryAccount represents a journal entry for account fields change +// journalEntryAccount represents a journal entry for account fields change type journalEntryAccount struct { account vmcommon.AccountHandler } @@ -133,7 +133,7 @@ func (jea *journalEntryAccount) IsInterfaceNil() bool { return jea == nil } -// JournalEntryAccountCreation represents a journal entry for account creation +// journalEntryAccountCreation represents a journal entry for account creation type journalEntryAccountCreation struct { address []byte updater Updater diff --git a/state/snapshotStatistics.go b/state/snapshotStatistics.go index 812a99ed263..272c96bf212 100644 --- a/state/snapshotStatistics.go +++ b/state/snapshotStatistics.go @@ -4,6 +4,7 @@ import ( "sync" "time" + "github.com/multiversx/mx-chain-core-go/core/atomic" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/trie/statistics" ) @@ -13,9 +14,10 @@ type snapshotStatistics struct { startTime time.Time - wgSnapshot *sync.WaitGroup - wgSync *sync.WaitGroup - mutex sync.RWMutex + numThrottlerWaits atomic.Counter + wgSnapshot *sync.WaitGroup + wgSync *sync.WaitGroup + mutex sync.RWMutex } func newSnapshotStatistics(snapshotDelta int, syncDelta int) *snapshotStatistics { @@ -80,6 +82,7 @@ func (ss *snapshotStatistics) PrintStats(identifier string, rootHash []byte) { "type", identifier, "duration", time.Since(ss.startTime).Truncate(time.Second), "rootHash", rootHash, + "numThrottlerWaits", ss.numThrottlerWaits.Get(), ) ss.trieStatisticsCollector.Print() } @@ -89,6 +92,11 @@ func (ss *snapshotStatistics) GetSnapshotNumNodes() uint64 { return ss.trieStatisticsCollector.GetNumNodes() } +// IncrementThrottlerWaits increments the number of throttler waits. +func (ss *snapshotStatistics) IncrementThrottlerWaits() { + ss.numThrottlerWaits.Increment() +} + // IsInterfaceNil returns true if there is no value under the interface func (ss *snapshotStatistics) IsInterfaceNil() bool { return ss == nil diff --git a/state/snapshotsManager.go b/state/snapshotsManager.go index c0ea45ba075..9a7e2dd7c52 100644 --- a/state/snapshotsManager.go +++ b/state/snapshotsManager.go @@ -252,6 +252,9 @@ func (sm *snapshotsManager) snapshotState( stats.NewSnapshotStarted() trieStorageManager.TakeSnapshot("", rootHash, rootHash, iteratorChannels, missingNodesChannel, stats, epoch) + + // TODO if the leaf was retrieved from epoch db n-2, the data trie should start retrieving from + // epoch n-2 and checking only older dbs sm.snapshotUserAccountDataTrie(rootHash, iteratorChannels, missingNodesChannel, stats, epoch, trieStorageManager) stats.SnapshotFinished() diff --git a/state/stateAccesses/collector.go b/state/stateAccesses/collector.go index 676a0a9f4a5..04348fa03cb 100644 --- a/state/stateAccesses/collector.go +++ b/state/stateAccesses/collector.go @@ -17,14 +17,24 @@ import ( var log = logger.GetOrCreate("state/stateAccesses") +const maxNumBlocksInMemory = 20 + +type stateAccessesForTxs map[string]*data.StateAccesses + type collector struct { - collectRead bool - collectWrite bool - withAccountChanges bool - stateAccesses []*data.StateAccess - stateAccessesForTxs map[string]*data.StateAccesses - storer state.StateAccessesStorer - stateAccessesMut sync.RWMutex + collectRead bool + collectWrite bool + withAccountChanges bool + stateAccesses []*data.StateAccess + stateAccessesForTxs stateAccessesForTxs + stateAccessesForBlock map[string]stateAccessesForTxs + stateAccessesForBlockMut sync.RWMutex + + storer state.StateAccessesStorer + stateAccessesMut sync.RWMutex + + lastCollectedRootHash []byte + lastCommitHasStateChanges bool } // NewCollector will create a new collector which gathers the state accesses based on the provided options. @@ -34,8 +44,9 @@ func NewCollector(storer state.StateAccessesStorer, opts ...CollectorOption) (*c } c := &collector{ - stateAccesses: make([]*data.StateAccess, 0), - stateAccessesForTxs: make(map[string]*data.StateAccesses), + stateAccesses: make([]*data.StateAccess, 0), + stateAccessesForTxs: make(map[string]*data.StateAccesses), + stateAccessesForBlock: make(map[string]stateAccessesForTxs), } for _, opt := range opts { opt(c) @@ -77,22 +88,54 @@ func (c *collector) GetAccountChanges(oldAccount, account vmcommon.AccountHandle // Reset resets the state accesses collector func (c *collector) Reset() { c.stateAccessesMut.Lock() - defer c.stateAccessesMut.Unlock() c.stateAccesses = make([]*data.StateAccess, 0) c.stateAccessesForTxs = make(map[string]*data.StateAccesses) - log.Trace("reset state accesses collector") + + if c.lastCommitHasStateChanges { + c.removeStateAccessesForRootHash(c.lastCollectedRootHash) + log.Trace("removed last collected root hash from stateAccessesForBlock", "rootHash", c.lastCollectedRootHash) + } + c.stateAccessesMut.Unlock() + + c.stateAccessesForBlockMut.RLock() + if len(c.stateAccessesForBlock) > maxNumBlocksInMemory { + // TODO remove oldest entries instead of logging a warning + log.Warn("max number of blocks in memory exceeded", "numBlocksInMemory", len(c.stateAccessesForBlock)) + } + + log.Trace("reset state accesses collector", "num state accesses for block", len(c.stateAccessesForBlock)) + c.stateAccessesForBlockMut.RUnlock() } -// GetCollectedAccesses will return the collected state accesses -func (c *collector) GetCollectedAccesses() map[string]*data.StateAccesses { - return c.getStateAccessesForTxs() +// GetStateAccessesForRootHash will return the collected state accesses +func (c *collector) GetStateAccessesForRootHash(rootHash []byte) map[string]*data.StateAccesses { + c.stateAccessesForBlockMut.RLock() + defer c.stateAccessesForBlockMut.RUnlock() + + stateAccessesForRootHash, ok := c.stateAccessesForBlock[string(rootHash)] + if !ok { + log.Warn("stateAccessesForRootHash not found in stateAccessesForBlock", "rootHash", rootHash) + return nil + } + + return stateAccessesForRootHash } -func (c *collector) getStateAccessesForTxs() map[string]*data.StateAccesses { - c.stateAccessesMut.Lock() - defer c.stateAccessesMut.Unlock() +// RemoveStateAccessesForRootHash will remove the collected state accesses for the given root hash +func (c *collector) RemoveStateAccessesForRootHash(rootHash []byte) { + c.removeStateAccessesForRootHash(rootHash) +} + +func (c *collector) removeStateAccessesForRootHash(rootHash []byte) { + c.stateAccessesForBlockMut.Lock() + defer c.stateAccessesForBlockMut.Unlock() + delete(c.stateAccessesForBlock, string(rootHash)) + log.Trace("removed stateAccessesForRootHash from stateAccessesForBlock", "rootHash", rootHash) +} + +func (c *collector) getStateAccessesForTxs() map[string]*data.StateAccesses { if len(c.stateAccessesForTxs) != 0 && len(c.stateAccesses) == 0 { return c.stateAccessesForTxs } @@ -187,9 +230,38 @@ func stateAccessToString(stateAccess *data.StateAccess) string { ) } -// Store will call the Store method of the underlying storer, giving it the collected state accesses. -func (c *collector) Store() error { - return c.storer.Store(c.getStateAccessesForTxs()) +// CommitCollectedAccesses will call the Store method of the underlying storer, giving it the collected state accesses. +func (c *collector) CommitCollectedAccesses(rootHash []byte) error { + c.stateAccessesMut.Lock() + + collectedStateAccesses := c.getStateAccessesForTxs() + if len(collectedStateAccesses) == 0 { + c.lastCollectedRootHash = rootHash + c.lastCommitHasStateChanges = false + c.stateAccessesMut.Unlock() + + log.Trace("no state accesses collected, skipping commit", "rootHash", rootHash) + + return nil + } + + c.stateAccessesForTxs = make(map[string]*data.StateAccesses) + c.stateAccesses = make([]*data.StateAccess, 0) + + c.lastCollectedRootHash = rootHash + c.lastCommitHasStateChanges = true + c.stateAccessesMut.Unlock() + + c.stateAccessesForBlockMut.Lock() + c.stateAccessesForBlock[string(rootHash)] = collectedStateAccesses + log.Trace("state accesses collected", "numStateAccesses", len(collectedStateAccesses), "rootHash", rootHash) + + if len(c.stateAccessesForBlock) > maxNumBlocksInMemory { + log.Warn("max number of blocks in memory exceeded", "numBlocksInMemory", len(c.stateAccessesForBlock)) + } + c.stateAccessesForBlockMut.Unlock() + + return c.storer.Store(collectedStateAccesses) } // AddTxHashToCollectedStateAccesses will try to set txHash field to each state access diff --git a/state/stateAccesses/collector_test.go b/state/stateAccesses/collector_test.go index a9296ba0b65..2e9e5845c7b 100644 --- a/state/stateAccesses/collector_test.go +++ b/state/stateAccesses/collector_test.go @@ -4,7 +4,9 @@ import ( "errors" "fmt" "math/big" + "math/rand" "strconv" + "sync" "testing" "github.com/multiversx/mx-chain-core-go/core" @@ -120,10 +122,8 @@ func TestStateAccessesCollector_AddTxHashToCollectedStateAccesses(t *testing.T) c, _ := NewCollector(disabled.NewDisabledStateAccessesStorer(), WithCollectWrite()) assert.Equal(t, 0, len(c.stateAccesses)) - assert.Equal(t, 0, len(c.GetCollectedAccesses())) c.AddTxHashToCollectedStateAccesses([]byte("txHash0")) assert.Equal(t, 0, len(c.stateAccesses)) - assert.Equal(t, 0, len(c.GetCollectedAccesses())) stateAccess := &data.StateAccess{ Type: data.Write, @@ -134,10 +134,13 @@ func TestStateAccessesCollector_AddTxHashToCollectedStateAccesses(t *testing.T) c.AddStateAccess(stateAccess) c.AddTxHashToCollectedStateAccesses([]byte("txHash")) + rootHash := []byte("rootHash") assert.Equal(t, 1, len(c.stateAccesses)) - assert.Equal(t, 1, len(c.GetCollectedAccesses())) + err := c.CommitCollectedAccesses(rootHash) + assert.Nil(t, err) + assert.Equal(t, 1, len(c.GetStateAccessesForRootHash(rootHash))) - stateAccessesForTx := c.GetCollectedAccesses() + stateAccessesForTx := c.GetStateAccessesForRootHash(rootHash) stateAccesses, ok := stateAccessesForTx["txHash"] require.True(t, ok) assert.Equal(t, 1, len(stateAccessesForTx)) @@ -259,16 +262,18 @@ func TestStateAccessesCollector_Reset(t *testing.T) { } c.AddTxHashToCollectedStateAccesses([]byte("txHash")) assert.Equal(t, numStateAccesses, len(c.stateAccesses)) - assert.Equal(t, 1, len(c.GetCollectedAccesses())) - assert.Equal(t, 1, len(c.stateAccessesForTxs)) + rootHash := []byte("rootHash") + err := c.CommitCollectedAccesses(rootHash) + assert.Nil(t, err) + assert.Equal(t, 1, len(c.GetStateAccessesForRootHash(rootHash))) + assert.Equal(t, 1, len(c.stateAccessesForBlock)) c.Reset() assert.Equal(t, 0, len(c.stateAccesses)) assert.Equal(t, 0, len(c.stateAccessesForTxs)) - assert.Equal(t, 0, len(c.GetCollectedAccesses())) } -func TestStateAccessesCollector_GetCollectedAccesses(t *testing.T) { +func TestStateAccessesCollector_GetStateAccessesForRootHash(t *testing.T) { t.Parallel() t.Run("collect only write", func(t *testing.T) { @@ -296,7 +301,9 @@ func TestStateAccessesCollector_GetCollectedAccesses(t *testing.T) { } } - stateAccessesForTx := c.GetCollectedAccesses() + rootHash := []byte("rootHash") + _ = c.CommitCollectedAccesses(rootHash) + stateAccessesForTx := c.GetStateAccessesForRootHash(rootHash) require.Len(t, stateAccessesForTx, 1) require.Len(t, stateAccessesForTx["hash0"].StateAccess, 10) @@ -344,7 +351,9 @@ func TestStateAccessesCollector_GetCollectedAccesses(t *testing.T) { } } - stateAccessesForTx := c.GetCollectedAccesses() + rootHash := []byte("rootHash") + _ = c.CommitCollectedAccesses(rootHash) + stateAccessesForTx := c.GetStateAccessesForRootHash(rootHash) require.Len(t, stateAccessesForTx, 1) require.Len(t, stateAccessesForTx["hash1"].StateAccess, 10) @@ -392,7 +401,9 @@ func TestStateAccessesCollector_GetCollectedAccesses(t *testing.T) { } } - stateAccessesForTx := c.GetCollectedAccesses() + rootHash := []byte("rootHash") + _ = c.CommitCollectedAccesses(rootHash) + stateAccessesForTx := c.GetStateAccessesForRootHash(rootHash) require.Len(t, stateAccessesForTx, 2) require.Len(t, stateAccessesForTx["hash0"].StateAccess, 10) @@ -451,7 +462,9 @@ func TestStateAccessesCollector_GetCollectedAccesses(t *testing.T) { MainTrieVal: []byte("mainTrieVal2"), }) - stateChangesForTx := c.GetCollectedAccesses() + rootHash := []byte("rootHash") + _ = c.CommitCollectedAccesses(rootHash) + stateChangesForTx := c.GetStateAccessesForRootHash(rootHash) require.Len(t, stateChangesForTx, 1) require.Len(t, stateChangesForTx["hash"].StateAccess, 2) @@ -487,7 +500,9 @@ func TestStateAccessesCollector_GetCollectedAccesses(t *testing.T) { MainTrieVal: []byte("mainTrieVal2"), }) - stateChangesForTx := c.GetCollectedAccesses() + rootHash := []byte("rootHash") + _ = c.CommitCollectedAccesses(rootHash) + stateChangesForTx := c.GetStateAccessesForRootHash(rootHash) require.Len(t, stateChangesForTx, 1) require.Len(t, stateChangesForTx["hash"].StateAccess, 2) @@ -523,7 +538,9 @@ func TestStateAccessesCollector_GetCollectedAccesses(t *testing.T) { MainTrieVal: []byte("mainTrieVal2"), }) - stateChangesForTx := c.GetCollectedAccesses() + rootHash := []byte("rootHash") + _ = c.CommitCollectedAccesses(rootHash) + stateChangesForTx := c.GetStateAccessesForRootHash(rootHash) require.Len(t, stateChangesForTx, 1) require.Len(t, stateChangesForTx["hash"].StateAccess, 1) @@ -565,7 +582,9 @@ func TestStateAccessesCollector_GetCollectedAccesses(t *testing.T) { AccountChanges: defaultAccChanges, }) - stateChangesForTx := c.GetCollectedAccesses() + rootHash := []byte("rootHash") + _ = c.CommitCollectedAccesses(rootHash) + stateChangesForTx := c.GetStateAccessesForRootHash(rootHash) require.Len(t, stateChangesForTx, 1) require.Len(t, stateChangesForTx["hash"].StateAccess, 1) @@ -606,7 +625,9 @@ func TestStateAccessesCollector_GetCollectedAccesses(t *testing.T) { AccountChanges: data.NonceChanged | data.CodeHashChanged | data.DeveloperRewardChanged | data.UserNameChanged, }) - stateChangesForTx := c.GetCollectedAccesses() + rootHash := []byte("rootHash") + _ = c.CommitCollectedAccesses(rootHash) + stateChangesForTx := c.GetStateAccessesForRootHash(rootHash) require.Len(t, stateChangesForTx, 1) require.Len(t, stateChangesForTx["hash"].StateAccess, 1) @@ -653,7 +674,7 @@ func TestStateAccessesCollector_GetCollectedAccesses(t *testing.T) { }, }) - stateChangesForTx := c.GetCollectedAccesses() + stateChangesForTx := c.getStateAccessesForTxs() require.Len(t, stateChangesForTx, 1) require.Len(t, stateChangesForTx["hash"].StateAccess, 1) require.Len(t, stateChangesForTx["hash"].StateAccess[0].DataTrieChanges, 2) @@ -677,7 +698,9 @@ func TestStateAccessesCollector_GetCollectedAccesses(t *testing.T) { }, }) - stateChangesForTx = c.GetCollectedAccesses() + rootHash := []byte("rootHash") + _ = c.CommitCollectedAccesses(rootHash) + stateChangesForTx = c.GetStateAccessesForRootHash(rootHash) require.Len(t, stateChangesForTx, 1) require.Len(t, stateChangesForTx["hash"].StateAccess, 1) require.Len(t, stateChangesForTx["hash"].StateAccess[0].DataTrieChanges, 2) @@ -738,7 +761,9 @@ func TestStateAccessesCollector_GetCollectedAccesses(t *testing.T) { } } - stateChangesForTx := c.GetCollectedAccesses() + rootHash := []byte("rootHash") + _ = c.CommitCollectedAccesses(rootHash) + stateChangesForTx := c.GetStateAccessesForRootHash(rootHash) require.Len(t, stateChangesForTx, 1) require.Len(t, stateChangesForTx["txHash"].StateAccess, 2) @@ -832,7 +857,7 @@ func TestCollector_GetAccountChanges(t *testing.T) { }) } -func TestCollector_Store(t *testing.T) { +func TestCollector_CommitCollectedAccesses(t *testing.T) { t.Parallel() t.Run("with storer", func(t *testing.T) { @@ -855,9 +880,9 @@ func TestCollector_Store(t *testing.T) { } c.AddTxHashToCollectedStateAccesses([]byte("txHash1")) - err := c.Store() + rootHash := []byte("rootHash") + err := c.CommitCollectedAccesses(rootHash) require.Nil(t, err) - require.True(t, putCalled) }) @@ -872,7 +897,8 @@ func TestCollector_Store(t *testing.T) { } c.AddTxHashToCollectedStateAccesses([]byte("txHash1")) - err := c.Store() + rootHash := []byte("rootHash") + err := c.CommitCollectedAccesses(rootHash) require.Nil(t, err) }) } @@ -914,3 +940,69 @@ func TestStateAccessToString(t *testing.T) { " accountChanges: 3" assert.Equal(t, expectedStr, strStateAccess) } + +func TestStateAccessesCollector_RemoveStateAccessesSForRootHash(t *testing.T) { + t.Parallel() + + c, _ := NewCollector(disabled.NewDisabledStateAccessesStorer(), WithCollectWrite()) + assert.Equal(t, 0, len(c.stateAccessesForBlock)) + + numStateAccesses := 10 + for i := 0; i < numStateAccesses; i++ { + c.AddStateAccess(getWriteStateAccess()) + } + c.AddTxHashToCollectedStateAccesses([]byte("txHash")) + assert.Equal(t, numStateAccesses, len(c.stateAccesses)) + rootHash := []byte("rootHash") + err := c.CommitCollectedAccesses(rootHash) + assert.Nil(t, err) + assert.Equal(t, 1, len(c.GetStateAccessesForRootHash(rootHash))) + assert.Equal(t, 1, len(c.stateAccessesForBlock)) + + c.RemoveStateAccessesForRootHash(rootHash) + assert.Equal(t, 0, len(c.stateAccessesForBlock)) +} + +func TestStateAccessesCollector_Concurrency(t *testing.T) { + t.Parallel() + + c, _ := NewCollector(disabled.NewDisabledStateAccessesStorer(), WithCollectRead(), WithCollectWrite()) + + numOperations := 1000 + + wg := sync.WaitGroup{} + wg.Add(numOperations) + + for i := 0; i < numOperations; i++ { + go func(idx int) { + switch idx % 10 { + case 0: + c.AddStateAccess(getWriteStateAccess()) + case 1: + c.AddStateAccess(getReadStateAccess()) + case 2: + c.AddTxHashToCollectedStateAccesses([]byte("txHash" + fmt.Sprintf("%d", rand.Intn(100)))) + case 3: + _ = c.CommitCollectedAccesses([]byte("rootHash")) + case 4: + c.GetAccountChanges(&mockState.UserAccountStub{}, &mockState.UserAccountStub{}) + case 5: + c.GetStateAccessesForRootHash([]byte("rootHash")) + case 6: + c.RemoveStateAccessesForRootHash([]byte("rootHash")) + case 7: + c.Reset() + case 8: + _ = c.RevertToIndex(rand.Intn(10)) + case 9: + _ = c.SetIndexToLatestStateAccesses(rand.Intn(10)) + default: + assert.Fail(t, "should have not been called") + } + + wg.Done() + }(i) + } + + wg.Wait() +} diff --git a/state/storagePruningManager/disabled/disabledStoragePruningManager.go b/state/storagePruningManager/disabled/disabledStoragePruningManager.go index 6de7e2b0845..15b9412d602 100644 --- a/state/storagePruningManager/disabled/disabledStoragePruningManager.go +++ b/state/storagePruningManager/disabled/disabledStoragePruningManager.go @@ -14,24 +14,32 @@ func NewDisabledStoragePruningManager() *disabledStoragePruningManager { } // MarkForEviction does nothing for this implementation -func (i *disabledStoragePruningManager) MarkForEviction(_ []byte, _ []byte, _ common.ModifiedHashes, _ common.ModifiedHashes) error { +func (d *disabledStoragePruningManager) MarkForEviction(_ []byte, _ []byte, _ common.ModifiedHashes, _ common.ModifiedHashes) error { return nil } // PruneTrie does nothing for this implementation -func (i *disabledStoragePruningManager) PruneTrie(_ []byte, _ state.TriePruningIdentifier, _ common.StorageManager, _ state.PruningHandler) { +func (d *disabledStoragePruningManager) PruneTrie(_ []byte, _ state.TriePruningIdentifier, _ common.StorageManager, _ state.PruningHandler) { } // CancelPrune does nothing for this implementation -func (i *disabledStoragePruningManager) CancelPrune(_ []byte, _ state.TriePruningIdentifier, _ common.StorageManager) { +func (d *disabledStoragePruningManager) CancelPrune(_ []byte, _ state.TriePruningIdentifier, _ common.StorageManager) { } +// Reset does nothing for this implementation +func (d *disabledStoragePruningManager) Reset() {} + // Close does nothing for this implementation -func (i *disabledStoragePruningManager) Close() error { +func (d *disabledStoragePruningManager) Close() error { return nil } +// EvictionWaitingListCacheLen returns 0 for the disabled storage pruning manager +func (d *disabledStoragePruningManager) EvictionWaitingListCacheLen() int { + return 0 +} + // IsInterfaceNil returns true if there is no value under the interface -func (i *disabledStoragePruningManager) IsInterfaceNil() bool { - return i == nil +func (d *disabledStoragePruningManager) IsInterfaceNil() bool { + return d == nil } diff --git a/state/storagePruningManager/evictionWaitingList/memoryEvictionWaitingList.go b/state/storagePruningManager/evictionWaitingList/memoryEvictionWaitingList.go index 52aa401c5ba..a3121833180 100644 --- a/state/storagePruningManager/evictionWaitingList/memoryEvictionWaitingList.go +++ b/state/storagePruningManager/evictionWaitingList/memoryEvictionWaitingList.go @@ -6,9 +6,10 @@ import ( "sync" "github.com/multiversx/mx-chain-core-go/data" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/state" - logger "github.com/multiversx/mx-chain-logger-go" ) var log = logger.GetOrCreate("state/evictionWaitingList") @@ -205,6 +206,26 @@ func (mewl *memoryEvictionWaitingList) ShouldKeepHash(hash string, identifier st return false, nil } +// Reset will reinitialize the eviction waiting list, by emptying the cache and reversed cache. It will not change the sizes of the caches. +func (mewl *memoryEvictionWaitingList) Reset() { + mewl.opMutex.Lock() + + for key := range mewl.cache { + log.Debug("trie nodes eviction waiting list reset", "rootHash", []byte(key)) + } + + mewl.cache = make(map[string]*rootHashData) + mewl.reversedCache = make(map[string]*hashInfo) + mewl.opMutex.Unlock() +} + +// CacheLen returns the number of entries in the cache +func (mewl *memoryEvictionWaitingList) CacheLen() int { + mewl.opMutex.RLock() + defer mewl.opMutex.RUnlock() + return len(mewl.cache) +} + // Close returns nil func (mewl *memoryEvictionWaitingList) Close() error { return nil diff --git a/state/storagePruningManager/evictionWaitingList/memoryEvictionWaitingList_test.go b/state/storagePruningManager/evictionWaitingList/memoryEvictionWaitingList_test.go index 21099502f93..482d4c8c40d 100644 --- a/state/storagePruningManager/evictionWaitingList/memoryEvictionWaitingList_test.go +++ b/state/storagePruningManager/evictionWaitingList/memoryEvictionWaitingList_test.go @@ -7,9 +7,10 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" + "github.com/stretchr/testify/assert" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/state" - "github.com/stretchr/testify/assert" ) func getDefaultArgsForMemoryEvictionWaitingList() MemoryEvictionWaitingListArgs { @@ -359,3 +360,37 @@ func TestMemoryEvictionWaitingList_RemoveFromInversedCache(t *testing.T) { assert.Nil(t, info) assert.False(t, exists) } + +func TestMemoryEvictionWaitingList_Reset(t *testing.T) { + t.Parallel() + + mewl, _ := NewMemoryEvictionWaitingList(getDefaultArgsForMemoryEvictionWaitingList()) + + _ = mewl.Put([]byte("root1"), common.ModifiedHashes{"hash1": {}, "hash2": {}}) + _ = mewl.Put([]byte("root2"), common.ModifiedHashes{"hash3": {}, "hash4": {}}) + + assert.Equal(t, 2, len(mewl.cache)) + assert.Equal(t, 4, len(mewl.reversedCache)) + mewl.Reset() + assert.Equal(t, 0, len(mewl.cache)) + assert.Equal(t, 0, len(mewl.reversedCache)) +} + +func TestMemoryEvictionWaitingList_CacheLen(t *testing.T) { + t.Parallel() + + mewl, _ := NewMemoryEvictionWaitingList(getDefaultArgsForMemoryEvictionWaitingList()) + assert.Equal(t, 0, mewl.CacheLen()) + + _ = mewl.Put([]byte("root1"), common.ModifiedHashes{"hash1": {}}) + assert.Equal(t, 1, mewl.CacheLen()) + + _ = mewl.Put([]byte("root2"), common.ModifiedHashes{"hash2": {}}) + assert.Equal(t, 2, mewl.CacheLen()) + + _, _ = mewl.Evict([]byte("root1")) + assert.Equal(t, 1, mewl.CacheLen()) + + mewl.Reset() + assert.Equal(t, 0, mewl.CacheLen()) +} diff --git a/state/storagePruningManager/storagePruningManager.go b/state/storagePruningManager/storagePruningManager.go index 757d04cc9ed..aa51caa9e05 100644 --- a/state/storagePruningManager/storagePruningManager.go +++ b/state/storagePruningManager/storagePruningManager.go @@ -7,10 +7,11 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/state/storagePruningManager/pruningBuffer" - logger "github.com/multiversx/mx-chain-logger-go" ) type pruningOperation byte @@ -75,7 +76,6 @@ func (spm *storagePruningManager) markForEviction( } rootHash = append(rootHash, byte(identifier)) - err := spm.dbEvictionWaitingList.Put(rootHash, hashes) if err != nil { return err @@ -234,6 +234,19 @@ func (spm *storagePruningManager) removeFromDb( return nil } +// EvictionWaitingListCacheLen returns the number of entries in the eviction waiting list cache +func (spm *storagePruningManager) EvictionWaitingListCacheLen() int { + return spm.dbEvictionWaitingList.CacheLen() +} + +func (spm *storagePruningManager) Reset() { + bufferedHashes := spm.pruningBuffer.RemoveAll() + for _, hash := range bufferedHashes { + log.Trace("trie storage manager reset", "hash", hash) + } + spm.dbEvictionWaitingList.Reset() +} + // Close will handle the closing of the underlying components func (spm *storagePruningManager) Close() error { return spm.dbEvictionWaitingList.Close() diff --git a/state/storagePruningManager/storagePruningManager_test.go b/state/storagePruningManager/storagePruningManager_test.go index c6913f9b7c0..0ca2df57801 100644 --- a/state/storagePruningManager/storagePruningManager_test.go +++ b/state/storagePruningManager/storagePruningManager_test.go @@ -19,7 +19,6 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" stateMock "github.com/multiversx/mx-chain-go/testscommon/state" - testStorage "github.com/multiversx/mx-chain-go/testscommon/state" "github.com/multiversx/mx-chain-go/testscommon/storage" "github.com/multiversx/mx-chain-go/trie" ) @@ -55,7 +54,7 @@ func getDefaultTrieAndAccountsDbAndStoragePruningManager() (common.Trie, *state. Marshaller: marshaller, AddressConverter: &testscommon.PubkeyConverterMock{}, ProcessStatusHandler: &testscommon.ProcessStatusHandlerStub{}, - StateMetrics: &testStorage.StateMetricsStub{}, + StateMetrics: &stateMock.StateMetricsStub{}, AccountFactory: accCreator, ChannelsProvider: iteratorChannelsProvider.NewUserStateIteratorChannelsProvider(), LastSnapshotMarker: lastSnapshotMarker.NewLastSnapshotMarker(), @@ -261,3 +260,91 @@ func TestStoragePruningManager_MarkForEviction_removeDuplicatedKeys(t *testing.T _, ok = map2["hash4"] assert.False(t, ok) } + +func TestStoragePruningManager_Reset(t *testing.T) { + t.Parallel() + + args := storage.GetStorageManagerArgs() + trieStorage, _ := trie.NewTrieStorageManager(args) + ewlArgs := evictionWaitingList.MemoryEvictionWaitingListArgs{ + RootHashesSize: 100, + HashesSize: 10000, + } + ewl, _ := evictionWaitingList.NewMemoryEvictionWaitingList(ewlArgs) + spm, _ := NewStoragePruningManager(ewl, 1000) + + err := spm.MarkForEviction([]byte("rootHash"), []byte("newRootHash"), map[string]struct{}{"hash1": {}, "hash2": {}}, map[string]struct{}{"hash3": {}, "hash4": {}}) + assert.Nil(t, err) + err = spm.markForEviction([]byte("rootHash2"), map[string]struct{}{"hash5": {}, "hash6": {}}, state.NewRoot) + assert.Nil(t, err) + + trieStorage.EnterPruningBufferingMode() + spm.PruneTrie([]byte("rootHash"), state.OldRoot, trieStorage, state.NewPruningHandler(state.EnableDataRemoval)) + spm.CancelPrune([]byte("newRootHash"), state.NewRoot, trieStorage) + trieStorage.ExitPruningBufferingMode() + + assert.Equal(t, 2, spm.pruningBuffer.Len()) + + spm.Reset() + assert.Equal(t, 0, spm.pruningBuffer.Len()) + + // rootHash2 should not be added to the pruning buffer because ewl was also reset when spm.Reset() was called + trieStorage.EnterPruningBufferingMode() + spm.PruneTrie([]byte("rootHash2"), state.NewRoot, trieStorage, state.NewPruningHandler(state.EnableDataRemoval)) + trieStorage.ExitPruningBufferingMode() + assert.Equal(t, 0, spm.pruningBuffer.Len()) +} + +func TestStoragePruningManager_DuplicateKeyIncrementsNumReferences(t *testing.T) { + t.Parallel() + + ewlArgs := evictionWaitingList.MemoryEvictionWaitingListArgs{ + RootHashesSize: 100, + HashesSize: 10000, + } + ewl, _ := evictionWaitingList.NewMemoryEvictionWaitingList(ewlArgs) + spm, _ := NewStoragePruningManager(ewl, 1000) + + // Put R0|OldRoot twice (simulates delayed pruning with recycled root hash) + oldHashes1 := map[string]struct{}{"h1": {}, "h2": {}} + newHashes1 := map[string]struct{}{"h3": {}} + err := spm.MarkForEviction([]byte("R0"), []byte("R1"), oldHashes1, newHashes1) + assert.Nil(t, err) + assert.Equal(t, 2, spm.EvictionWaitingListCacheLen()) + + // Second MarkForEviction with same oldRoot R0 increments numReferences + oldHashes2 := map[string]struct{}{"h4": {}, "h5": {}} + newHashes2 := map[string]struct{}{"h6": {}} + err = spm.MarkForEviction([]byte("R0"), []byte("R2"), oldHashes2, newHashes2) + assert.Nil(t, err) + assert.Equal(t, 3, spm.EvictionWaitingListCacheLen()) // R0|OldRoot, R1|NewRoot, R2|NewRoot + + // First Evict decrements numReferences, returns empty (entry still alive) + evicted, errEvict := ewl.Evict(append([]byte("R0"), byte(state.OldRoot))) + assert.Nil(t, errEvict) + assert.Equal(t, 0, len(evicted)) + + // Second Evict removes entry and returns hashes + evicted, errEvict = ewl.Evict(append([]byte("R0"), byte(state.OldRoot))) + assert.Nil(t, errEvict) + assert.True(t, len(evicted) > 0) +} + +func TestStoragePruningManager_EvictionWaitingListCacheLen(t *testing.T) { + t.Parallel() + + ewlArgs := evictionWaitingList.MemoryEvictionWaitingListArgs{ + RootHashesSize: 100, + HashesSize: 10000, + } + ewl, _ := evictionWaitingList.NewMemoryEvictionWaitingList(ewlArgs) + spm, _ := NewStoragePruningManager(ewl, 1000) + + assert.Equal(t, 0, spm.EvictionWaitingListCacheLen()) + + err := spm.MarkForEviction([]byte("old"), []byte("new"), + map[string]struct{}{"h1": {}}, + map[string]struct{}{"h2": {}}) + assert.Nil(t, err) + assert.Equal(t, 2, spm.EvictionWaitingListCacheLen()) +} diff --git a/statusHandler/chainParamsMetricsHandler.go b/statusHandler/chainParamsMetricsHandler.go new file mode 100644 index 00000000000..857b963af6d --- /dev/null +++ b/statusHandler/chainParamsMetricsHandler.go @@ -0,0 +1,38 @@ +package statusHandler + +import ( + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" +) + +type chainParamsMetricsHandler struct { + appStatusHandler core.AppStatusHandler +} + +// NewChainParamsMetricsHandler will create a new chain parameters metrics handler component +func NewChainParamsMetricsHandler(appStatusHandler core.AppStatusHandler) (*chainParamsMetricsHandler, error) { + if check.IfNil(appStatusHandler) { + return nil, ErrNilAppStatusHandler + } + + return &chainParamsMetricsHandler{ + appStatusHandler: appStatusHandler, + }, nil +} + +// ChainParametersChanged will be called when new chain parameters are confirmed on the network +func (cpm *chainParamsMetricsHandler) ChainParametersChanged(chainParameters config.ChainParametersByEpochConfig) { + cpm.appStatusHandler.SetUInt64Value(common.MetricRoundsPerEpoch, uint64(chainParameters.RoundsPerEpoch)) + cpm.appStatusHandler.SetUInt64Value(common.MetricShardConsensusGroupSize, uint64(chainParameters.ShardConsensusGroupSize)) + cpm.appStatusHandler.SetUInt64Value(common.MetricMetaConsensusGroupSize, uint64(chainParameters.MetachainConsensusGroupSize)) + cpm.appStatusHandler.SetUInt64Value(common.MetricNumNodesPerShard, uint64(chainParameters.ShardMinNumNodes)) + cpm.appStatusHandler.SetUInt64Value(common.MetricNumMetachainNodes, uint64(chainParameters.MetachainMinNumNodes)) + cpm.appStatusHandler.SetUInt64Value(common.MetricRoundDuration, chainParameters.RoundDuration) +} + +// IsInterfaceNil returns true if there is no value under the interface +func (cpm *chainParamsMetricsHandler) IsInterfaceNil() bool { + return cpm == nil +} diff --git a/statusHandler/chainParamsMetricsHandler_test.go b/statusHandler/chainParamsMetricsHandler_test.go new file mode 100644 index 00000000000..11b742c0397 --- /dev/null +++ b/statusHandler/chainParamsMetricsHandler_test.go @@ -0,0 +1,45 @@ +package statusHandler_test + +import ( + "testing" + + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/statusHandler" + statusHandlerMock "github.com/multiversx/mx-chain-go/testscommon/statusHandler" + "github.com/stretchr/testify/require" +) + +func TestNewChainParamsMetricsHandler(t *testing.T) { + t.Parallel() + + t.Run("nil app status handler", func(t *testing.T) { + t.Parallel() + + cpm, err := statusHandler.NewChainParamsMetricsHandler(nil) + require.Nil(t, cpm) + require.Equal(t, statusHandler.ErrNilAppStatusHandler, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + cpm, err := statusHandler.NewChainParamsMetricsHandler(&statusHandlerMock.AppStatusHandlerStub{}) + require.Nil(t, err) + require.False(t, cpm.IsInterfaceNil()) + }) +} + +func TestChainParamsMetricsHandler_ChainParametersChanged(t *testing.T) { + t.Parallel() + + numCalls := 0 + cpm, _ := statusHandler.NewChainParamsMetricsHandler(&statusHandlerMock.AppStatusHandlerStub{ + SetUInt64ValueHandler: func(key string, value uint64) { + numCalls++ + }, + }) + + cpm.ChainParametersChanged(config.ChainParametersByEpochConfig{}) + + require.Equal(t, 6, numCalls) +} diff --git a/statusHandler/errors.go b/statusHandler/errors.go index d3f25cd3a4d..b5558db846d 100644 --- a/statusHandler/errors.go +++ b/statusHandler/errors.go @@ -19,3 +19,9 @@ var ErrNilUint64Converter = errors.New("uint64converter is nil") // ErrNilStorage signals that a nil storage has been provided var ErrNilStorage = errors.New("nil storage") + +// ErrNilEnableEpochsHandler signals that a nil enable epochs handler has been provided +var ErrNilEnableEpochsHandler = errors.New("nil enable epochs handler") + +// ErrNilEnableRoundsHandler signals that a nil enable rounds handler has been provided +var ErrNilEnableRoundsHandler = errors.New("nil enable rounds handler") diff --git a/statusHandler/export_test.go b/statusHandler/export_test.go index 97f7bbc1a1a..beaf1a14d20 100644 --- a/statusHandler/export_test.go +++ b/statusHandler/export_test.go @@ -1,5 +1,8 @@ package statusHandler +// StatusMetrics is an alias for statusMetrics to be used in tests +type StatusMetrics = statusMetrics + // StatusMetricsMap will return all metrics in a map func (sm *statusMetrics) StatusMetricsMap() map[string]interface{} { return sm.getMetricsWithKeyFilterMutexProtected(func(_ string) bool { diff --git a/statusHandler/p2pQuota/p2pQuotaProcessor.go b/statusHandler/p2pQuota/p2pQuotaProcessor.go index a1662c36b88..240571cdad3 100644 --- a/statusHandler/p2pQuota/p2pQuotaProcessor.go +++ b/statusHandler/p2pQuota/p2pQuotaProcessor.go @@ -24,11 +24,11 @@ type p2pQuotaProcessor struct { peakPeerQuota *quota peakNumReceivers uint64 handler core.AppStatusHandler - quotaIdentifier string + quotaIdentifier common.FloodPreventerType } // NewP2PQuotaProcessor creates a new p2pQuotaProcessor instance -func NewP2PQuotaProcessor(handler core.AppStatusHandler, quotaIdentifier string) (*p2pQuotaProcessor, error) { +func NewP2PQuotaProcessor(handler core.AppStatusHandler, quotaIdentifier common.FloodPreventerType) (*p2pQuotaProcessor, error) { if check.IfNil(handler) { return nil, statusHandler.ErrNilAppStatusHandler } @@ -98,7 +98,7 @@ func (pqp *p2pQuotaProcessor) moveStatisticsInAppStatusHandler( } func (pqp *p2pQuotaProcessor) getMetric(metric string) string { - return metric + "_" + pqp.quotaIdentifier + return metric + "_" + string(pqp.quotaIdentifier) } // AddQuota adds a quota statistics diff --git a/statusHandler/p2pQuota/p2pQuotaProcessor_test.go b/statusHandler/p2pQuota/p2pQuotaProcessor_test.go index d98a4a27116..602b432cf1d 100644 --- a/statusHandler/p2pQuota/p2pQuotaProcessor_test.go +++ b/statusHandler/p2pQuota/p2pQuotaProcessor_test.go @@ -66,7 +66,7 @@ func TestP2PQuotaProcessor_ResetStatisticsShouldEmptyStatsAndCallSetOnAllMetrics status := statusHandlerMock.NewAppStatusHandlerMock() quotaIdentifier := "identifier" - pqp, _ := p2pQuota.NewP2PQuotaProcessor(status, quotaIdentifier) + pqp, _ := p2pQuota.NewP2PQuotaProcessor(status, common.FloodPreventerType(quotaIdentifier)) pqp.AddQuota(identifier, uint32(numReceived), sizeReceived, uint32(numProcessed), sizeProcessed) pqp.ResetStatistics() @@ -96,7 +96,7 @@ func TestP2PQuotaProcessor_ResetStatisticsShouldSetPeerStatisticsTops(t *testing status := statusHandlerMock.NewAppStatusHandlerMock() quotaIdentifier := "identifier" - pqp, _ := p2pQuota.NewP2PQuotaProcessor(status, quotaIdentifier) + pqp, _ := p2pQuota.NewP2PQuotaProcessor(status, common.FloodPreventerType(quotaIdentifier)) pqp.AddQuota(identifier1, uint32(numReceived1), sizeReceived1, uint32(numProcessed1), sizeProcessed1) pqp.ResetStatistics() pqp.AddQuota(identifier2, uint32(numReceived2), sizeReceived2, uint32(numProcessed2), sizeProcessed2) diff --git a/statusHandler/persister/persistentHandler.go b/statusHandler/persister/persistentHandler.go index b4e0906c389..2c6f532de99 100644 --- a/statusHandler/persister/persistentHandler.go +++ b/statusHandler/persister/persistentHandler.go @@ -58,6 +58,7 @@ func (psh *PersistentStatusHandler) initMap() { psh.persistentMetrics.Store(common.MetricNumProcessedTxs, initUint) psh.persistentMetrics.Store(common.MetricNumShardHeadersProcessed, initUint) psh.persistentMetrics.Store(common.MetricNonce, initUint) + psh.persistentMetrics.Store(common.MetricLastExecutedNonce, initUint) psh.persistentMetrics.Store(common.MetricBlockTimestamp, initUint) psh.persistentMetrics.Store(common.MetricBlockTimestampMs, initUint) psh.persistentMetrics.Store(common.MetricCurrentRound, initUint) diff --git a/statusHandler/processStatusHandler.go b/statusHandler/processStatusHandler.go index c54104f81a0..b18e6fff767 100644 --- a/statusHandler/processStatusHandler.go +++ b/statusHandler/processStatusHandler.go @@ -29,6 +29,22 @@ func (psh *processStatusHandler) SetBusy(reason string) { psh.mutStatus.Unlock() } +// TrySetBusy will atomically check if idle and set the internal state to "busy". +// Returns true if the state was successfully set to busy, false if already busy. +func (psh *processStatusHandler) TrySetBusy(reason string) bool { + psh.mutStatus.Lock() + defer psh.mutStatus.Unlock() + + if !psh.isIdle { + log.Debug("processStatusHandler.TrySetBusy: already busy", "reason", reason) + return false + } + + log.Debug("processStatusHandler.TrySetBusy", "reason", reason) + psh.isIdle = false + return true +} + // SetIdle will set the internal state to "idle" func (psh *processStatusHandler) SetIdle() { log.Debug("processStatusHandler.SetIdle") diff --git a/statusHandler/processStatusHandler_test.go b/statusHandler/processStatusHandler_test.go index e0443396dfb..8f863a5d4e8 100644 --- a/statusHandler/processStatusHandler_test.go +++ b/statusHandler/processStatusHandler_test.go @@ -2,6 +2,7 @@ package statusHandler import ( "sync" + "sync/atomic" "testing" "github.com/multiversx/mx-chain-core-go/core/check" @@ -34,6 +35,68 @@ func TestProcessStatusHandler_AllMethods(t *testing.T) { assert.True(t, psh.IsIdle()) } +func TestProcessStatusHandler_TrySetBusy(t *testing.T) { + t.Parallel() + + t.Run("should succeed when idle", func(t *testing.T) { + t.Parallel() + + psh := NewProcessStatusHandler() + assert.True(t, psh.IsIdle()) + + result := psh.TrySetBusy("reason") + assert.True(t, result) + assert.False(t, psh.IsIdle()) + }) + + t.Run("should fail when already busy", func(t *testing.T) { + t.Parallel() + + psh := NewProcessStatusHandler() + psh.SetBusy("first reason") + + result := psh.TrySetBusy("second reason") + assert.False(t, result) + assert.False(t, psh.IsIdle()) + }) + + t.Run("should succeed after SetIdle", func(t *testing.T) { + t.Parallel() + + psh := NewProcessStatusHandler() + psh.SetBusy("first reason") + psh.SetIdle() + + result := psh.TrySetBusy("second reason") + assert.True(t, result) + assert.False(t, psh.IsIdle()) + }) + + t.Run("second TrySetBusy should fail when first succeeded", func(t *testing.T) { + t.Parallel() + + psh := NewProcessStatusHandler() + + result1 := psh.TrySetBusy("first") + assert.True(t, result1) + + result2 := psh.TrySetBusy("second") + assert.False(t, result2) + assert.False(t, psh.IsIdle()) + }) + + t.Run("TrySetBusy should succeed again after SetIdle following a successful TrySetBusy", func(t *testing.T) { + t.Parallel() + + psh := NewProcessStatusHandler() + + assert.True(t, psh.TrySetBusy("first")) + psh.SetIdle() + assert.True(t, psh.TrySetBusy("second")) + assert.False(t, psh.IsIdle()) + }) +} + func TestNewProcessStatusHandler_ParallelCalls(t *testing.T) { t.Parallel() @@ -61,3 +124,26 @@ func TestNewProcessStatusHandler_ParallelCalls(t *testing.T) { wg.Wait() } + +func TestNewProcessStatusHandler_TrySetBusyConcurrency(t *testing.T) { + t.Parallel() + + psh := NewProcessStatusHandler() + numGoroutines := 100 + successCount := int32(0) + wg := sync.WaitGroup{} + wg.Add(numGoroutines) + + for i := 0; i < numGoroutines; i++ { + go func() { + defer wg.Done() + if psh.TrySetBusy("concurrent reason") { + atomic.AddInt32(&successCount, 1) + } + }() + } + + wg.Wait() + assert.Equal(t, int32(1), atomic.LoadInt32(&successCount)) + assert.False(t, psh.IsIdle()) +} diff --git a/statusHandler/statusMetricsProvider.go b/statusHandler/statusMetricsProvider.go index 6d149aa99c4..70dc62898a3 100644 --- a/statusHandler/statusMetricsProvider.go +++ b/statusHandler/statusMetricsProvider.go @@ -5,6 +5,8 @@ import ( "strings" "sync" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/common" ) @@ -18,15 +20,27 @@ type statusMetrics struct { int64Metrics map[string]int64 mutInt64Operations sync.RWMutex + + enableEpochsHandler common.EnableEpochsHandler + enableRoundsHandler common.EnableRoundsHandler } // NewStatusMetrics will return an instance of the struct -func NewStatusMetrics() *statusMetrics { - return &statusMetrics{ - uint64Metrics: make(map[string]uint64), - stringMetrics: make(map[string]string), - int64Metrics: make(map[string]int64), +func NewStatusMetrics(enableEpochsHandler common.EnableEpochsHandler, enableRoundsHandler common.EnableRoundsHandler) (*statusMetrics, error) { + if check.IfNil(enableEpochsHandler) { + return nil, ErrNilEnableEpochsHandler } + if check.IfNil(enableRoundsHandler) { + return nil, ErrNilEnableRoundsHandler + } + + return &statusMetrics{ + uint64Metrics: make(map[string]uint64), + stringMetrics: make(map[string]string), + int64Metrics: make(map[string]int64), + enableEpochsHandler: enableEpochsHandler, + enableRoundsHandler: enableRoundsHandler, + }, nil } // IsInterfaceNil returns true if there is no value under the interface @@ -328,7 +342,6 @@ func (sm *statusMetrics) EnableEpochsMetrics() (map[string]interface{}, error) { enableEpochsMetrics[common.MetricIsPayableBySCEnableEpoch] = sm.uint64Metrics[common.MetricIsPayableBySCEnableEpoch] enableEpochsMetrics[common.MetricCleanUpInformativeSCRsEnableEpoch] = sm.uint64Metrics[common.MetricCleanUpInformativeSCRsEnableEpoch] enableEpochsMetrics[common.MetricStorageAPICostOptimizationEnableEpoch] = sm.uint64Metrics[common.MetricStorageAPICostOptimizationEnableEpoch] - enableEpochsMetrics[common.MetricTransformToMultiShardCreateEnableEpoch] = sm.uint64Metrics[common.MetricTransformToMultiShardCreateEnableEpoch] enableEpochsMetrics[common.MetricESDTRegisterAndSetAllRolesEnableEpoch] = sm.uint64Metrics[common.MetricESDTRegisterAndSetAllRolesEnableEpoch] enableEpochsMetrics[common.MetricDoNotReturnOldBlockInBlockchainHookEnableEpoch] = sm.uint64Metrics[common.MetricDoNotReturnOldBlockInBlockchainHookEnableEpoch] enableEpochsMetrics[common.MetricAddFailedRelayedTxToInvalidMBsDisableEpoch] = sm.uint64Metrics[common.MetricAddFailedRelayedTxToInvalidMBsDisableEpoch] @@ -389,6 +402,8 @@ func (sm *statusMetrics) EnableEpochsMetrics() (map[string]interface{}, error) { enableEpochsMetrics[common.MetricBarnardOpcodesEnableEpoch] = sm.uint64Metrics[common.MetricBarnardOpcodesEnableEpoch] enableEpochsMetrics[common.MetricAutomaticActivationOfNodesDisableEpoch] = sm.uint64Metrics[common.MetricAutomaticActivationOfNodesDisableEpoch] enableEpochsMetrics[common.MetricFixGetBalanceEnableEpoch] = sm.uint64Metrics[common.MetricFixGetBalanceEnableEpoch] + enableEpochsMetrics[common.MetricTailInflationEnableEpoch] = sm.uint64Metrics[common.MetricTailInflationEnableEpoch] + enableEpochsMetrics[common.MetricSupernovaEnableEpoch] = sm.uint64Metrics[common.MetricSupernovaEnableEpoch] numNodesChangeConfig := sm.uint64Metrics[common.MetricMaxNodesChangeEnableEpoch+"_count"] @@ -413,6 +428,16 @@ func (sm *statusMetrics) EnableEpochsMetrics() (map[string]interface{}, error) { return enableEpochsMetrics, nil } +// EnableEpochsMetricsV2 returns all enable epoch flags with their activation epochs +func (sm *statusMetrics) EnableEpochsMetricsV2() map[string]uint32 { + return sm.enableEpochsHandler.GetAllEnableEpochs() +} + +// EnableRoundsMetrics returns all enable round flags with their activation rounds +func (sm *statusMetrics) EnableRoundsMetrics() map[string]uint64 { + return sm.enableRoundsHandler.GetAllEnableRounds() +} + // NetworkMetrics will return metrics related to current configuration func (sm *statusMetrics) NetworkMetrics() (map[string]interface{}, error) { networkMetrics := make(map[string]interface{}) @@ -433,6 +458,7 @@ func (sm *statusMetrics) saveUint64NetworkMetricsInMap(networkMetrics map[string currentNonce := sm.uint64Metrics[common.MetricNonce] nonceAtEpochStart := sm.uint64Metrics[common.MetricNonceAtEpochStart] networkMetrics[common.MetricNonce] = currentNonce + networkMetrics[common.MetricLastExecutedNonce] = sm.uint64Metrics[common.MetricLastExecutedNonce] networkMetrics[common.MetricBlockTimestamp] = sm.uint64Metrics[common.MetricBlockTimestamp] networkMetrics[common.MetricBlockTimestampMs] = sm.uint64Metrics[common.MetricBlockTimestampMs] networkMetrics[common.MetricHighestFinalBlock] = sm.uint64Metrics[common.MetricHighestFinalBlock] @@ -443,6 +469,7 @@ func (sm *statusMetrics) saveUint64NetworkMetricsInMap(networkMetrics map[string networkMetrics[common.MetricRoundsPerEpoch] = sm.uint64Metrics[common.MetricRoundsPerEpoch] networkMetrics[common.MetricRoundsPassedInCurrentEpoch] = computeDelta(currentRound, roundNumberAtEpochStart) networkMetrics[common.MetricNoncesPassedInCurrentEpoch] = computeDelta(currentNonce, nonceAtEpochStart) + networkMetrics[common.MetricProposedNonce] = sm.uint64Metrics[common.MetricProposedNonce] } func (sm *statusMetrics) saveStringNetworkMetricsInMap(networkMetrics map[string]interface{}) { diff --git a/statusHandler/statusMetricsProvider_test.go b/statusHandler/statusMetricsProvider_test.go index 71ca6e95b91..f9794e0afca 100644 --- a/statusHandler/statusMetricsProvider_test.go +++ b/statusHandler/statusMetricsProvider_test.go @@ -9,22 +9,47 @@ import ( "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/statusHandler" + "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func createStatusMetrics() *statusHandler.StatusMetrics { + sm, _ := statusHandler.NewStatusMetrics(&enableEpochsHandlerMock.EnableEpochsHandlerStub{}, &testscommon.EnableRoundsHandlerStub{}) + return sm +} + func TestNewStatusMetricsProvider(t *testing.T) { t.Parallel() - sm := statusHandler.NewStatusMetrics() - assert.NotNil(t, sm) - assert.False(t, sm.IsInterfaceNil()) + t.Run("nil enable epochs handler should error", func(t *testing.T) { + t.Parallel() + + sm, err := statusHandler.NewStatusMetrics(nil, &testscommon.EnableRoundsHandlerStub{}) + assert.Nil(t, sm) + assert.Equal(t, statusHandler.ErrNilEnableEpochsHandler, err) + }) + t.Run("nil enable rounds handler should error", func(t *testing.T) { + t.Parallel() + + sm, err := statusHandler.NewStatusMetrics(&enableEpochsHandlerMock.EnableEpochsHandlerStub{}, nil) + assert.Nil(t, sm) + assert.Equal(t, statusHandler.ErrNilEnableRoundsHandler, err) + }) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + sm := createStatusMetrics() + assert.NotNil(t, sm) + assert.False(t, sm.IsInterfaceNil()) + }) } func TestStatusMetricsProvider_IncrementCallNonExistingKey(t *testing.T) { t.Parallel() - sm := statusHandler.NewStatusMetrics() + sm := createStatusMetrics() key1 := "test-key1" sm.Increment(key1) @@ -36,7 +61,7 @@ func TestStatusMetricsProvider_IncrementCallNonExistingKey(t *testing.T) { func TestStatusMetricsProvider_IncrementNonUint64ValueShouldNotWork(t *testing.T) { t.Parallel() - sm := statusHandler.NewStatusMetrics() + sm := createStatusMetrics() key1 := "test-key2" value1 := "value2" // set a key which is initialized with a string and the Increment method won't affect the key @@ -50,7 +75,7 @@ func TestStatusMetricsProvider_IncrementNonUint64ValueShouldNotWork(t *testing.T func TestStatusMetricsProvider_IncrementShouldWork(t *testing.T) { t.Parallel() - sm := statusHandler.NewStatusMetrics() + sm := createStatusMetrics() key1 := "test-key3" sm.SetUInt64Value(key1, 0) sm.Increment(key1) @@ -63,7 +88,7 @@ func TestStatusMetricsProvider_IncrementShouldWork(t *testing.T) { func TestStatusMetricsProvider_Decrement(t *testing.T) { t.Parallel() - sm := statusHandler.NewStatusMetrics() + sm := createStatusMetrics() key := "test-key4" sm.SetUInt64Value(key, 2) sm.Decrement(key) @@ -76,7 +101,7 @@ func TestStatusMetricsProvider_Decrement(t *testing.T) { func TestStatusMetricsProvider_SetInt64Value(t *testing.T) { t.Parallel() - sm := statusHandler.NewStatusMetrics() + sm := createStatusMetrics() key := "test-key5" value := int64(5) sm.SetInt64Value(key, value) @@ -90,7 +115,7 @@ func TestStatusMetricsProvider_SetInt64Value(t *testing.T) { func TestStatusMetricsProvider_SetStringValue(t *testing.T) { t.Parallel() - sm := statusHandler.NewStatusMetrics() + sm := createStatusMetrics() key := "test-key6" value := "value" sm.SetStringValue(key, value) @@ -104,7 +129,7 @@ func TestStatusMetricsProvider_SetStringValue(t *testing.T) { func TestStatusMetricsProvider_AddUint64Value(t *testing.T) { t.Parallel() - sm := statusHandler.NewStatusMetrics() + sm := createStatusMetrics() key := "test-key6" value := uint64(100) sm.SetUInt64Value(key, value) @@ -117,7 +142,7 @@ func TestStatusMetricsProvider_AddUint64Value(t *testing.T) { func TestStatusMetrics_StatusMetricsWithoutP2PPrometheusStringShouldPutDefaultShardIDLabel(t *testing.T) { t.Parallel() - sm := statusHandler.NewStatusMetrics() + sm := createStatusMetrics() key1, value1 := "test-key7", uint64(100) key2, value2 := "test-key8", "value8" sm.SetUInt64Value(key1, value1) @@ -133,7 +158,7 @@ func TestStatusMetrics_StatusMetricsWithoutP2PPrometheusStringShouldPutCorrectSh t.Parallel() shardID := uint32(37) - sm := statusHandler.NewStatusMetrics() + sm := createStatusMetrics() key1, value1 := "test-key7", uint64(100) key2, value2 := "test-key8", "value8" key3, value3 := common.MetricShardId, shardID @@ -151,7 +176,7 @@ func TestStatusMetrics_StatusMetricsWithoutP2PPrometheusStringShouldComputeRound t.Parallel() shardID := uint32(2) - sm := statusHandler.NewStatusMetrics() + sm := createStatusMetrics() sm.SetUInt64Value(common.MetricRoundsPassedInCurrentEpoch, 0) sm.SetUInt64Value(common.MetricNoncesPassedInCurrentEpoch, 0) sm.SetUInt64Value(common.MetricShardId, uint64(shardID)) @@ -159,6 +184,7 @@ func TestStatusMetrics_StatusMetricsWithoutP2PPrometheusStringShouldComputeRound sm.SetUInt64Value(common.MetricCurrentRound, 137) sm.SetUInt64Value(common.MetricNonceAtEpochStart, 100) sm.SetUInt64Value(common.MetricNonce, 138) + sm.SetUInt64Value(common.MetricLastExecutedNonce, 136) strRes, _ := sm.StatusMetricsWithoutP2PPrometheusString() @@ -169,7 +195,7 @@ func TestStatusMetrics_StatusMetricsWithoutP2PPrometheusStringShouldComputeRound func TestStatusMetrics_NetworkConfig(t *testing.T) { t.Parallel() - sm := statusHandler.NewStatusMetrics() + sm := createStatusMetrics() sm.SetUInt64Value(common.MetricNumShardsWithoutMetachain, 1) sm.SetUInt64Value(common.MetricNumNodesPerShard, 100) @@ -228,22 +254,25 @@ func TestStatusMetrics_NetworkConfig(t *testing.T) { func TestStatusMetrics_NetworkMetrics(t *testing.T) { t.Parallel() - sm := statusHandler.NewStatusMetrics() + sm := createStatusMetrics() sm.SetUInt64Value(common.MetricCurrentRound, 200) sm.SetUInt64Value(common.MetricRoundAtEpochStart, 100) sm.SetUInt64Value(common.MetricNonce, 180) + sm.SetUInt64Value(common.MetricLastExecutedNonce, 179) sm.SetUInt64Value(common.MetricBlockTimestamp, 18000) sm.SetUInt64Value(common.MetricBlockTimestampMs, 18000000) sm.SetUInt64Value(common.MetricHighestFinalBlock, 181) sm.SetUInt64Value(common.MetricNonceAtEpochStart, 95) sm.SetUInt64Value(common.MetricEpochNumber, 1) sm.SetUInt64Value(common.MetricRoundsPerEpoch, 50) + sm.SetUInt64Value(common.MetricProposedNonce, 1501) expectedConfig := map[string]interface{}{ "erd_current_round": uint64(200), "erd_round_at_epoch_start": uint64(100), "erd_nonce": uint64(180), + "erd_last_executed_nonce": uint64(179), "erd_block_timestamp": uint64(18000), "erd_block_timestamp_ms": uint64(18000000), "erd_highest_final_nonce": uint64(181), @@ -252,6 +281,7 @@ func TestStatusMetrics_NetworkMetrics(t *testing.T) { "erd_rounds_per_epoch": uint64(50), "erd_rounds_passed_in_current_epoch": uint64(100), "erd_nonces_passed_in_current_epoch": uint64(85), + "erd_proposed_nonce": uint64(1501), } t.Run("no cross check value", func(t *testing.T) { @@ -271,11 +301,12 @@ func TestStatusMetrics_NetworkMetrics(t *testing.T) { func TestStatusMetrics_StatusMetricsMapWithoutP2P(t *testing.T) { t.Parallel() - sm := statusHandler.NewStatusMetrics() + sm := createStatusMetrics() sm.SetUInt64Value(common.MetricCurrentRound, 100) sm.SetUInt64Value(common.MetricRoundAtEpochStart, 200) sm.SetUInt64Value(common.MetricNonce, 300) + sm.SetUInt64Value(common.MetricLastExecutedNonce, 298) sm.SetUInt64Value(common.MetricBlockTimestamp, 30000) sm.SetUInt64Value(common.MetricBlockTimestampMs, 30000000) sm.SetStringValue(common.MetricAppVersion, "400") @@ -289,6 +320,7 @@ func TestStatusMetrics_StatusMetricsMapWithoutP2P(t *testing.T) { require.Equal(t, uint64(100), res[common.MetricCurrentRound]) require.Equal(t, uint64(200), res[common.MetricRoundAtEpochStart]) require.Equal(t, uint64(300), res[common.MetricNonce]) + require.Equal(t, uint64(298), res[common.MetricLastExecutedNonce]) require.Equal(t, uint64(30000), res[common.MetricBlockTimestamp]) require.Equal(t, "400", res[common.MetricAppVersion]) require.NotContains(t, res, common.MetricRoundsPassedInCurrentEpoch) @@ -300,7 +332,7 @@ func TestStatusMetrics_StatusMetricsMapWithoutP2P(t *testing.T) { func TestStatusMetrics_EnableEpochMetrics(t *testing.T) { t.Parallel() - sm := statusHandler.NewStatusMetrics() + sm := createStatusMetrics() sm.SetUInt64Value(common.MetricScDeployEnableEpoch, uint64(4)) sm.SetUInt64Value(common.MetricBuiltInFunctionsEnableEpoch, uint64(4)) @@ -355,7 +387,6 @@ func TestStatusMetrics_EnableEpochMetrics(t *testing.T) { sm.SetUInt64Value(common.MetricFrontRunningProtectionEnableEpoch, uint64(4)) sm.SetUInt64Value(common.MetricIsPayableBySCEnableEpoch, uint64(4)) sm.SetUInt64Value(common.MetricStorageAPICostOptimizationEnableEpoch, uint64(4)) - sm.SetUInt64Value(common.MetricTransformToMultiShardCreateEnableEpoch, uint64(4)) sm.SetUInt64Value(common.MetricESDTRegisterAndSetAllRolesEnableEpoch, uint64(4)) sm.SetUInt64Value(common.MetricDoNotReturnOldBlockInBlockchainHookEnableEpoch, uint64(4)) sm.SetUInt64Value(common.MetricAddFailedRelayedTxToInvalidMBsDisableEpoch, uint64(4)) @@ -416,6 +447,8 @@ func TestStatusMetrics_EnableEpochMetrics(t *testing.T) { sm.SetUInt64Value(common.MetricBarnardOpcodesEnableEpoch, uint64(4)) sm.SetUInt64Value(common.MetricAutomaticActivationOfNodesDisableEpoch, uint64(4)) sm.SetUInt64Value(common.MetricFixGetBalanceEnableEpoch, uint64(4)) + sm.SetUInt64Value(common.MetricTailInflationEnableEpoch, uint64(4)) + sm.SetUInt64Value(common.MetricSupernovaEnableEpoch, uint64(4)) maxNodesChangeConfig := []map[string]uint64{ { @@ -495,7 +528,6 @@ func TestStatusMetrics_EnableEpochMetrics(t *testing.T) { common.MetricFrontRunningProtectionEnableEpoch: uint64(4), common.MetricIsPayableBySCEnableEpoch: uint64(4), common.MetricStorageAPICostOptimizationEnableEpoch: uint64(4), - common.MetricTransformToMultiShardCreateEnableEpoch: uint64(4), common.MetricESDTRegisterAndSetAllRolesEnableEpoch: uint64(4), common.MetricDoNotReturnOldBlockInBlockchainHookEnableEpoch: uint64(4), common.MetricAddFailedRelayedTxToInvalidMBsDisableEpoch: uint64(4), @@ -556,6 +588,8 @@ func TestStatusMetrics_EnableEpochMetrics(t *testing.T) { common.MetricBarnardOpcodesEnableEpoch: uint64(4), common.MetricAutomaticActivationOfNodesDisableEpoch: uint64(4), common.MetricFixGetBalanceEnableEpoch: uint64(4), + common.MetricTailInflationEnableEpoch: uint64(4), + common.MetricSupernovaEnableEpoch: uint64(4), common.MetricMaxNodesChangeEnableEpoch: []map[string]interface{}{ { common.MetricEpochEnable: uint64(0), @@ -577,7 +611,7 @@ func TestStatusMetrics_EnableEpochMetrics(t *testing.T) { func TestStatusMetrics_RatingsConfig(t *testing.T) { t.Parallel() - sm := statusHandler.NewStatusMetrics() + sm := createStatusMetrics() sm.SetUInt64Value(common.MetricRatingsGeneralStartRating, uint64(5001)) sm.SetUInt64Value(common.MetricRatingsGeneralMaxRating, uint64(10000)) @@ -667,7 +701,7 @@ func TestStatusMetrics_RatingsConfig(t *testing.T) { func TestStatusMetrics_BootstrapMetrics(t *testing.T) { t.Parallel() - sm := statusHandler.NewStatusMetrics() + sm := createStatusMetrics() sm.SetUInt64Value(common.MetricTrieSyncNumReceivedBytes, uint64(5001)) sm.SetUInt64Value(common.MetricTrieSyncNumProcessedNodes, uint64(10000)) @@ -689,7 +723,7 @@ func TestStatusMetrics_BootstrapMetrics(t *testing.T) { func TestStatusMetrics_IncrementConcurrentOperations(t *testing.T) { t.Parallel() - sm := statusHandler.NewStatusMetrics() + sm := createStatusMetrics() testKey := "test key" sm.SetUInt64Value(testKey, 0) @@ -713,7 +747,7 @@ func TestStatusMetrics_IncrementConcurrentOperations(t *testing.T) { func TestStatusMetrics_ConcurrentIncrementAndDecrement(t *testing.T) { t.Parallel() - sm := statusHandler.NewStatusMetrics() + sm := createStatusMetrics() initialValue := uint64(5000) @@ -747,7 +781,7 @@ func TestStatusMetrics_ConcurrentIncrementAndDecrement(t *testing.T) { func TestStatusMetrics_ConcurrentOperations(t *testing.T) { t.Parallel() - sm := statusHandler.NewStatusMetrics() + sm := createStatusMetrics() startTime := time.Now() diff --git a/storage/factory/storageServiceFactory.go b/storage/factory/storageServiceFactory.go index 7f279facdeb..7b0f122aead 100644 --- a/storage/factory/storageServiceFactory.go +++ b/storage/factory/storageServiceFactory.go @@ -245,6 +245,16 @@ func (psf *StorageServiceFactory) createAndAddBaseStorageUnits( } store.AddStorer(dataRetriever.ProofsUnit, proofsUnit) + executionResultsUnitArgs, err := psf.createPruningStorerArgs(psf.generalConfig.ExecutionResultsStorage, disabledCustomDatabaseRemover) + if err != nil { + return err + } + executionResultsUnit, err := psf.createPruningPersister(executionResultsUnitArgs) + if err != nil { + return fmt.Errorf("%w for ExecutionResultsStorage", err) + } + store.AddStorer(dataRetriever.ExecutionResultsUnit, executionResultsUnit) + metaHdrHashNonceUnit, err := psf.createStaticStorageUnit(psf.generalConfig.MetaHdrNonceHashStorage, shardID, emptyDBPathSuffix) if err != nil { return fmt.Errorf("%w for MetaHdrNonceHashStorage", err) diff --git a/storage/factory/storageServiceFactory_test.go b/storage/factory/storageServiceFactory_test.go index 9f3081337b9..af60755e85f 100644 --- a/storage/factory/storageServiceFactory_test.go +++ b/storage/factory/storageServiceFactory_test.go @@ -48,6 +48,7 @@ func createMockArgument(t *testing.T) StorageServiceFactoryArgs { PeerBlockBodyStorage: createMockStorageConfig("PeerBlockBodyStorage"), TrieEpochRootHashStorage: createMockStorageConfig("TrieEpochRootHashStorage"), ProofsStorage: createMockStorageConfig("ProofsStorage"), + ExecutionResultsStorage: createMockStorageConfig("ExecutionResultsStorage"), DbLookupExtensions: config.DbLookupExtensionsConfig{ Enabled: true, DbLookupMaxActivePersisters: 10, @@ -409,7 +410,7 @@ func TestStorageServiceFactory_CreateForShard(t *testing.T) { assert.Nil(t, err) assert.False(t, check.IfNil(storageService)) allStorers := storageService.GetAllStorers() - expectedStorers := 24 + expectedStorers := 25 assert.Equal(t, expectedStorers, len(allStorers)) storer, _ := storageService.GetStorer(dataRetriever.UserAccountsUnit) @@ -431,7 +432,7 @@ func TestStorageServiceFactory_CreateForShard(t *testing.T) { assert.False(t, check.IfNil(storageService)) allStorers := storageService.GetAllStorers() numDBLookupExtensionUnits := 6 - expectedStorers := 24 - numDBLookupExtensionUnits + expectedStorers := 25 - numDBLookupExtensionUnits assert.Equal(t, expectedStorers, len(allStorers)) _ = storageService.CloseAll() }) @@ -445,7 +446,7 @@ func TestStorageServiceFactory_CreateForShard(t *testing.T) { assert.Nil(t, err) assert.False(t, check.IfNil(storageService)) allStorers := storageService.GetAllStorers() - expectedStorers := 24 // we still have a storer for trie epoch root hash + expectedStorers := 25 // we still have a storer for trie epoch root hash assert.Equal(t, expectedStorers, len(allStorers)) _ = storageService.CloseAll() }) @@ -459,7 +460,7 @@ func TestStorageServiceFactory_CreateForShard(t *testing.T) { assert.Nil(t, err) assert.False(t, check.IfNil(storageService)) allStorers := storageService.GetAllStorers() - expectedStorers := 24 + expectedStorers := 25 assert.Equal(t, expectedStorers, len(allStorers)) storer, _ := storageService.GetStorer(dataRetriever.UserAccountsUnit) @@ -528,7 +529,7 @@ func TestStorageServiceFactory_CreateForMeta(t *testing.T) { allStorers := storageService.GetAllStorers() missingStorers := 2 // PeerChangesUnit and ShardHdrNonceHashDataUnit numShardHdrStorage := 3 - expectedStorers := 24 - missingStorers + numShardHdrStorage + expectedStorers := 25 - missingStorers + numShardHdrStorage assert.Equal(t, expectedStorers, len(allStorers)) storer, _ := storageService.GetStorer(dataRetriever.UserAccountsUnit) @@ -551,7 +552,7 @@ func TestStorageServiceFactory_CreateForMeta(t *testing.T) { allStorers := storageService.GetAllStorers() missingStorers := 2 // PeerChangesUnit and ShardHdrNonceHashDataUnit numShardHdrStorage := 3 - expectedStorers := 24 - missingStorers + numShardHdrStorage + expectedStorers := 25 - missingStorers + numShardHdrStorage assert.Equal(t, expectedStorers, len(allStorers)) storer, _ := storageService.GetStorer(dataRetriever.UserAccountsUnit) diff --git a/storage/latestData/latestDataProvider.go b/storage/latestData/latestDataProvider.go index 2b894627de3..c1b183fbb7a 100644 --- a/storage/latestData/latestDataProvider.go +++ b/storage/latestData/latestDataProvider.go @@ -11,16 +11,15 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data" - "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/marshal" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" - "github.com/multiversx/mx-chain-go/epochStart/metachain" - "github.com/multiversx/mx-chain-go/epochStart/shardchain" + "github.com/multiversx/mx-chain-go/epochStart" "github.com/multiversx/mx-chain-go/process/block/bootstrapStorage" "github.com/multiversx/mx-chain-go/storage" "github.com/multiversx/mx-chain-go/storage/factory" - logger "github.com/multiversx/mx-chain-logger-go" ) var log = logger.GetOrCreate("storage/latestData") @@ -234,19 +233,19 @@ func (ldp *latestDataProvider) loadEpochStartRound( return 0, err } - var state *block.MetaTriggerRegistry + var state data.MetaTriggerRegistryHandler marshaller := &marshal.GogoProtoMarshalizer{} if shardID == core.MetachainShardId { - state, err = metachain.UnmarshalTrigger(marshaller, trigData) + state, err = epochStart.UnmarshalMetaTrigger(marshaller, trigData) if err != nil { return 0, err } - return state.CurrEpochStartRound, nil + return state.GetCurrEpochStartRound(), nil } var trigHandler data.TriggerRegistryHandler - trigHandler, err = shardchain.UnmarshalTrigger(marshaller, trigData) + trigHandler, err = epochStart.UnmarshalShardTrigger(marshaller, trigData) if err != nil { return 0, err } @@ -265,7 +264,7 @@ func (ldp *latestDataProvider) GetLastEpochFromDirNames(epochDirs []string, inde for _, dirname := range epochDirs { epochStr := re.FindString(dirname) - epoch, err := strconv.ParseInt(epochStr, 10, 64) + epoch, err := strconv.ParseUint(epochStr, 10, 32) if err != nil { return 0, err } diff --git a/storage/pruning/export_test.go b/storage/pruning/export_test.go index e2fd96abcae..a259d79a70f 100644 --- a/storage/pruning/export_test.go +++ b/storage/pruning/export_test.go @@ -9,6 +9,11 @@ import ( "github.com/multiversx/mx-chain-go/storage/mock" ) +// GetIsClosed - +func (pd *persisterData) GetIsClosed() bool { + return pd.getIsClosed() +} + // NewEmptyPruningStorer - func NewEmptyPruningStorer() *PruningStorer { return &PruningStorer{ @@ -16,7 +21,7 @@ func NewEmptyPruningStorer() *PruningStorer { } } -// AddMockActivePersister - +// AddMockActivePersisters - func (ps *PruningStorer) AddMockActivePersisters(epochs []uint32, ordered bool, withMap bool) { for _, e := range epochs { pd := &persisterData{ @@ -42,6 +47,11 @@ func (ps *PruningStorer) ClearPersisters() { ps.persistersMapByEpoch = make(map[uint32]*persisterData) } +// PersistersMapByEpoch - +func (ps *PruningStorer) PersistersMapByEpoch() map[uint32]*persisterData { + return ps.persistersMapByEpoch +} + // AddMockActivePersister - func (ps *PruningStorer) AddMockActivePersister(epoch uint32, persister storage.Persister) { pd := &persisterData{ @@ -97,6 +107,11 @@ func (ps *PruningStorer) ChangeEpochSimple(epochNum uint32) error { return ps.changeEpoch(&block.Header{Epoch: epochNum}) } +// CreateNextEpochPersisterIfNeeded - +func (ps *PruningStorer) CreateNextEpochPersisterIfNeeded(epoch uint32) { + ps.createNextEpochPersisterIfNeeded(epoch) +} + // ChangeEpochWithExisting - func (ps *PruningStorer) ChangeEpochWithExisting(epoch uint32) error { return ps.changeEpochWithExisting(epoch) @@ -115,6 +130,23 @@ func (ps *PruningStorer) GetActivePersistersEpochs() []uint32 { return sliceToRet } +// GetPersistersEpochs - +func (ps *PruningStorer) GetPersistersEpochs() []uint32 { + ps.lock.RLock() + defer ps.lock.RUnlock() + + persistersEpochs := make([]uint32, 0) + for epoch := range ps.persistersMapByEpoch { + persistersEpochs = append(persistersEpochs, epoch) + } + + sort.Slice(persistersEpochs, func(i, j int) bool { + return persistersEpochs[i] > persistersEpochs[j] + }) + + return persistersEpochs +} + // GetActivePersistersEpochs - func (ps *PruningStorer) SetCacher(cacher storage.Cacher) { ps.lock.Lock() diff --git a/storage/pruning/fullHistoryPruningStorer.go b/storage/pruning/fullHistoryPruningStorer.go index eeabefd4a7b..9ad4d3634a8 100644 --- a/storage/pruning/fullHistoryPruningStorer.go +++ b/storage/pruning/fullHistoryPruningStorer.go @@ -81,6 +81,14 @@ func (fhps *FullHistoryPruningStorer) GetFromEpoch(key []byte, epoch uint32) ([] // GetBulkFromEpoch will search a bulk of keys in the persister for the given epoch // doesn't return an error if a key or any isn't found func (fhps *FullHistoryPruningStorer) GetBulkFromEpoch(keys [][]byte, epoch uint32) ([]data.KeyValuePair, error) { + res, err := fhps.searchBulkInEpoch(keys, epoch) + if err == nil && len(res) > 0 { + return res, nil + } + return fhps.searchBulkInEpoch(keys, epoch+1) +} + +func (fhps *FullHistoryPruningStorer) searchBulkInEpoch(keys [][]byte, epoch uint32) ([]data.KeyValuePair, error) { persister, err := fhps.getOrOpenPersister(epoch) if err != nil { return nil, err @@ -178,7 +186,8 @@ func (fhps *FullHistoryPruningStorer) getOrOpenPersister(epoch uint32) (storage. pdata, exists = fhps.getPersisterData(epochString, epoch) if !exists { - newPdata, errPersisterData := createPersisterDataForEpoch(fhps.args, epoch, fhps.shardId) + filePath := createPersisterPathForEpoch(fhps.args, epoch, fhps.shardId) + newPdata, errPersisterData := createPersisterDataForEpoch(fhps.args.PersisterFactory, filePath, epoch) if errPersisterData != nil { return nil, errPersisterData } diff --git a/storage/pruning/fullHistoryPruningStorer_test.go b/storage/pruning/fullHistoryPruningStorer_test.go index d1274499bb9..1102297b056 100644 --- a/storage/pruning/fullHistoryPruningStorer_test.go +++ b/storage/pruning/fullHistoryPruningStorer_test.go @@ -204,6 +204,36 @@ func TestNewFullHistoryPruningStorer_GetBulkFromEpoch(t *testing.T) { assert.Equal(t, expected, res) } +func TestNewFullHistoryPruningStorer_GetBulkFromMultipleEpochs(t *testing.T) { + t.Parallel() + + args := getDefaultArgs() + fhArgs := pruning.FullHistoryStorerArgs{ + StorerArgs: args, + NumOfOldActivePersisters: 5, + } + fhps, _ := pruning.NewFullHistoryPruningStorer(fhArgs) + testVal0, testVal1 := []byte("value0"), []byte("value1") + testKey0, testKey1 := []byte("key0"), []byte("key1") + testEpoch := uint32(7) + + _ = fhps.PutInEpoch(testKey0, testVal0, testEpoch+1) + _ = fhps.PutInEpoch(testKey1, testVal1, testEpoch+1) + + // clean cache + fhps.ClearCache() + + res, err := fhps.GetBulkFromEpoch([][]byte{testKey0, testKey1}, testEpoch) + assert.Nil(t, err) + + expected := []data.KeyValuePair{ + {Key: testKey0, Value: testVal0}, + {Key: testKey1, Value: testVal1}, + } + assert.Equal(t, expected, res) + +} + func TestNewFullHistoryPruningStorer_GetBulkFromEpochShouldNotLoadFromCache(t *testing.T) { t.Parallel() diff --git a/storage/pruning/pruningStorer.go b/storage/pruning/pruningStorer.go index 0238f987e1d..4bf1af05e1d 100644 --- a/storage/pruning/pruningStorer.go +++ b/storage/pruning/pruningStorer.go @@ -58,7 +58,7 @@ func (pd *persisterData) setIsClosed(closed bool) { // Close closes the underlying persister func (pd *persisterData) Close() error { pd.setIsClosed(true) - err := pd.persister.Close() + err := pd.getPersister().Close() return err } @@ -231,7 +231,8 @@ func initPersistersInEpoch( } log.Debug("initPersistersInEpoch(): createPersisterDataForEpoch", "identifier", args.Identifier, "epoch", epoch, "shardID", shardIDStr) - p, err := createPersisterDataForEpoch(args, uint32(epoch), shardIDStr) + filePath := createPersisterPathForEpoch(args, uint32(epoch), shardIDStr) + p, err := createPersisterDataForEpoch(args.PersisterFactory, filePath, uint32(epoch)) if err != nil { return nil, nil, err } @@ -239,7 +240,7 @@ func initPersistersInEpoch( persistersMapByEpoch[uint32(epoch)] = p ShouldClosePersister := args.PersistersTracker.ShouldClosePersister(epoch) - args.PersistersTracker.CollectPersisterData(p.persister) + args.PersistersTracker.CollectPersisterData(p.getPersister()) if ShouldClosePersister { err = p.Close() @@ -252,9 +253,48 @@ func initPersistersInEpoch( } } + initNextEpochPersisterIfNeeded(args, shardIDStr, persistersMapByEpoch) + return persisters, persistersMapByEpoch, nil } +func initNextEpochPersisterIfNeeded( + args StorerArgs, + shardIDStr string, + persistersMapByEpoch map[uint32]*persisterData, +) { + epoch := args.EpochsData.StartingEpoch + epoch++ + + // TODO: if booting from storage in an epoch > 0, shardId needs to be taken from somewhere else + // e.g. determined from directories in persister path or taken from boot storer + filePath := createPersisterPathForEpoch(args, epoch, shardIDStr) + + createPersisterForEpoch(epoch, filePath, persistersMapByEpoch, args.PersisterFactory) + +} + +func createPersisterForEpoch( + epoch uint32, + filePath string, + persistersMapByEpoch map[uint32]*persisterData, + persisterFactory DbFactoryHandler, +) { + _, ok := persistersMapByEpoch[epoch] + if ok { + log.Debug("createPersisterForEpoch: persister already in map", "epoch", epoch) + return + } + + p, err := createPersisterDataForEpoch(persisterFactory, filePath, epoch) + if err != nil { + log.Warn("createPersisterForEpoch", "epoch", epoch, "error", err.Error()) + return + } + + persistersMapByEpoch[epoch] = p +} + func createPersisterIfPruningDisabled( args StorerArgs, shardIDStr string, @@ -263,7 +303,8 @@ func createPersisterIfPruningDisabled( persistersMapByEpoch := make(map[uint32]*persisterData) epoch := uint32(0) - p, err := createPersisterDataForEpoch(args, epoch, shardIDStr) + filePath := createPersisterPathForEpoch(args, epoch, shardIDStr) + p, err := createPersisterDataForEpoch(args.PersisterFactory, filePath, epoch) if err != nil { return nil, nil, err } @@ -449,7 +490,7 @@ func (ps *PruningStorer) Get(key []byte) ([]byte, error) { for idx := 0; idx < len(ps.activePersisters); idx++ { ps.stateStatsHandler.IncrPersister(ps.activePersisters[idx].epoch) - val, err := ps.activePersisters[idx].persister.Get(key) + val, err := ps.activePersisters[idx].getPersister().Get(key) if err != nil { if errors.Is(err, storage.ErrDBIsClosed) { numClosedDbs++ @@ -484,6 +525,20 @@ func (ps *PruningStorer) Close() error { closedSuccessfully = false } } + + // close also any remaining persisters by map + // this includes the persister created in advance + for _, pd := range ps.persistersMapByEpoch { + if pd.getIsClosed() { + continue + } + + err := pd.Close() + if err != nil { + log.Warn("cannot close pd", "error", err) + closedSuccessfully = false + } + } ps.lock.RUnlock() ps.cacher.Clear() @@ -637,6 +692,15 @@ func (ps *PruningStorer) Has(key []byte) error { // SetEpochForPutOperation will set the epoch to be used when using the put operation func (ps *PruningStorer) SetEpochForPutOperation(epoch uint32) { + ps.lock.RLock() + epochForPutOperation := ps.epochForPutOperation + ps.lock.RUnlock() + + // do not try to aquire full lock if epoch already set + if epoch == epochForPutOperation { + return + } + ps.lock.Lock() ps.epochForPutOperation = epoch ps.lock.Unlock() @@ -655,7 +719,7 @@ func (ps *PruningStorer) RemoveFromCurrentEpoch(key []byte) error { persisterToUse := ps.activePersisters[0] ps.stateStatsHandler.IncrWritePersister(persisterToUse.epoch) - return persisterToUse.persister.Remove(key) + return persisterToUse.getPersister().Remove(key) } // Remove removes the data associated to the given key from both cache and persistence medium @@ -667,7 +731,7 @@ func (ps *PruningStorer) Remove(key []byte) error { defer ps.lock.RUnlock() for _, pd := range ps.activePersisters { ps.stateStatsHandler.IncrWritePersister(pd.epoch) - err = pd.persister.Remove(key) + err = pd.getPersister().Remove(key) if err == nil { return nil } @@ -746,6 +810,8 @@ func (ps *PruningStorer) registerHandler(handler EpochStartNotifier) { if err != nil { log.Warn("change epoch in storer", "error", err.Error()) } + + go ps.createNextEpochPersisterIfNeeded(hdr.GetEpoch()) }, func(metaHdr data.HeaderHandler) { err := ps.saveHeaderForEpochStartPrepare(metaHdr) @@ -763,7 +829,7 @@ func (ps *PruningStorer) saveHeaderForEpochStartPrepare(header data.HeaderHandle defer ps.mutEpochPrepareHdr.Unlock() var ok bool - ps.epochPrepareHdr, ok = header.(*block.MetaBlock) + ps.epochPrepareHdr, ok = header.(data.MetaHeaderHandler) if !ok { return storage.ErrWrongTypeAssertion } @@ -784,6 +850,8 @@ func (ps *PruningStorer) changeEpoch(header data.HeaderHandler) error { return nil } + shardID := core.GetShardIDString(ps.shardCoordinator.SelfId()) + _, ok := ps.persistersMapByEpoch[epoch] if ok { err := ps.changeEpochWithExisting(epoch) @@ -796,7 +864,6 @@ func (ps *PruningStorer) changeEpoch(header data.HeaderHandler) error { return ps.removeOldPersistersIfNeeded(header) } - shardID := core.GetShardIDString(ps.shardCoordinator.SelfId()) filePath := ps.pathManager.PathForEpoch(shardID, epoch, ps.identifier) db, err := ps.persisterFactory.Create(filePath) if err != nil { @@ -819,6 +886,18 @@ func (ps *PruningStorer) changeEpoch(header data.HeaderHandler) error { return ps.removeOldPersistersIfNeeded(header) } +func (ps *PruningStorer) createNextEpochPersisterIfNeeded(epoch uint32) { + epoch++ + + shardID := core.GetShardIDString(ps.shardCoordinator.SelfId()) + filePath := ps.pathManager.PathForEpoch(shardID, epoch, ps.identifier) + + ps.lock.Lock() + defer ps.lock.Unlock() + + createPersisterForEpoch(epoch, filePath, ps.persistersMapByEpoch, ps.persisterFactory) +} + func (ps *PruningStorer) removeOldPersistersIfNeeded(header data.HeaderHandler) error { epoch := header.GetEpoch() wasExtended := ps.extendSavedEpochsIfNeeded(header) @@ -845,14 +924,14 @@ func (ps *PruningStorer) removeOldPersistersIfNeeded(header data.HeaderHandler) // should be called under mutex protection func (ps *PruningStorer) extendSavedEpochsIfNeeded(header data.HeaderHandler) bool { epoch := header.GetEpoch() - metaBlock, mbOk := header.(*block.MetaBlock) + metaBlock, mbOk := header.(data.MetaHeaderHandler) if !mbOk { ps.mutEpochPrepareHdr.RLock() epochPrepareHdr := ps.epochPrepareHdr ps.mutEpochPrepareHdr.RUnlock() if epochPrepareHdr != nil { var ok bool - metaBlock, ok = epochPrepareHdr.(*block.MetaBlock) + metaBlock, ok = epochPrepareHdr.(data.MetaHeaderHandler) if !ok { log.Warn("PruningStorer.extendSavedEpochsIfNeeded", "error", "invalid type assertion") return false @@ -861,7 +940,7 @@ func (ps *PruningStorer) extendSavedEpochsIfNeeded(header data.HeaderHandler) bo return false } } - shouldExtend := metaBlock.Epoch != epochForDefaultEpochPrepareHdr + shouldExtend := metaBlock.GetEpoch() != epochForDefaultEpochPrepareHdr if !shouldExtend { return false } @@ -908,7 +987,7 @@ func (ps *PruningStorer) changeEpochWithExisting(epoch uint32) error { for e := int64(epoch); e >= oldestEpochActive; e-- { p, ok := ps.persistersMapByEpoch[uint32(e)] if !ok { - return nil + continue } persisters = append(persisters, p) } @@ -937,7 +1016,7 @@ func (ps *PruningStorer) extendActivePersisters(from uint32, to uint32) error { for e := int(to); e >= int(from); e-- { p, ok := ps.persistersMapByEpoch[uint32(e)] if !ok { - return nil + continue } persisters = append(persisters, p) } @@ -1078,12 +1157,12 @@ func createPersisterPathForEpoch(args StorerArgs, epoch uint32, shard string) st return filePath } -func createPersisterDataForEpoch(args StorerArgs, epoch uint32, shard string) (*persisterData, error) { - // TODO: if booting from storage in an epoch > 0, shardId needs to be taken from somewhere else - // e.g. determined from directories in persister path or taken from boot storer - filePath := createPersisterPathForEpoch(args, epoch, shard) - - db, err := args.PersisterFactory.Create(filePath) +func createPersisterDataForEpoch( + persisterFactory DbFactoryHandler, + filePath string, + epoch uint32, +) (*persisterData, error) { + db, err := persisterFactory.Create(filePath) if err != nil { log.Warn("persister create error", "error", err.Error()) return nil, err @@ -1099,11 +1178,11 @@ func createPersisterDataForEpoch(args StorerArgs, epoch uint32, shard string) (* return p, nil } -func computeOldestEpoch(metaBlock *block.MetaBlock) uint32 { - oldestEpoch := metaBlock.Epoch - for _, lastHdr := range metaBlock.EpochStart.LastFinalizedHeaders { - if lastHdr.Epoch < oldestEpoch { - oldestEpoch = lastHdr.Epoch +func computeOldestEpoch(metaBlock data.MetaHeaderHandler) uint32 { + oldestEpoch := metaBlock.GetEpoch() + for _, lastHdr := range metaBlock.GetEpochStartHandler().GetLastFinalizedHeaderHandlers() { + if lastHdr.GetEpoch() < oldestEpoch { + oldestEpoch = lastHdr.GetEpoch() } } diff --git a/storage/pruning/pruningStorer_test.go b/storage/pruning/pruningStorer_test.go index 248cc53cda2..994de340cdc 100644 --- a/storage/pruning/pruningStorer_test.go +++ b/storage/pruning/pruningStorer_test.go @@ -28,6 +28,7 @@ import ( "github.com/multiversx/mx-chain-go/storage/pruning" "github.com/multiversx/mx-chain-go/storage/storageunit" "github.com/multiversx/mx-chain-go/testscommon" + storateMocks "github.com/multiversx/mx-chain-go/testscommon/storage" logger "github.com/multiversx/mx-chain-logger-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -891,6 +892,52 @@ func TestPruningStorer_ClosePersisters(t *testing.T) { require.Equal(t, []uint32{4, 5, 6}, ps.PersistersMapByEpochToSlice()) }) + t.Run("should remove old databases from map + database created in advance", func(t *testing.T) { + t.Parallel() + + args := getDefaultArgs() + args.OldDataCleanerProvider = &testscommon.OldDataCleanerProviderStub{ + ShouldCleanCalled: func() bool { + return true + }, + } + args.EpochsData.NumOfActivePersisters = 2 + args.EpochsData.NumOfEpochsToKeep = 3 + + ps, _ := pruning.NewPruningStorer(args) + + require.Equal(t, []uint32{0, 1}, ps.PersistersMapByEpochToSlice()) // one persister created in advance + require.Equal(t, 1, ps.GetNumActivePersisters()) + + _ = ps.ChangeEpochSimple(1) + _ = ps.ChangeEpochSimple(2) + _ = ps.ChangeEpochSimple(3) + _ = ps.ChangeEpochSimple(4) + + require.Equal(t, 2, ps.GetNumActivePersisters()) + require.Equal(t, []uint32{2, 3, 4}, ps.PersistersMapByEpochToSlice()) + + ps.CreateNextEpochPersisterIfNeeded(4) + + require.Equal(t, 2, ps.GetNumActivePersisters()) + require.Equal(t, []uint32{2, 3, 4, 5}, ps.PersistersMapByEpochToSlice()) + + require.True(t, ps.PersistersMapByEpoch()[2].GetIsClosed()) + require.False(t, ps.PersistersMapByEpoch()[3].GetIsClosed()) + require.False(t, ps.PersistersMapByEpoch()[4].GetIsClosed()) + require.False(t, ps.PersistersMapByEpoch()[5].GetIsClosed()) + + err := ps.Close() + require.NoError(t, err) + require.Equal(t, 2, ps.GetNumActivePersisters()) + require.Equal(t, []uint32{2, 3, 4, 5}, ps.PersistersMapByEpochToSlice()) + + require.True(t, ps.PersistersMapByEpoch()[2].GetIsClosed()) + require.True(t, ps.PersistersMapByEpoch()[3].GetIsClosed()) + require.True(t, ps.PersistersMapByEpoch()[4].GetIsClosed()) + require.True(t, ps.PersistersMapByEpoch()[5].GetIsClosed()) + }) + t.Run("should remove old databases from map + destroy them", func(t *testing.T) { t.Parallel() @@ -1192,6 +1239,10 @@ func TestPruningStorer_GetOldestEpoch(t *testing.T) { args.PersistersTracker = pruning.NewPersistersTracker(epochsData) ps, _ := pruning.NewPruningStorer(args) + // on init, it will create persister for next epoch in advance so + // we have to clear all persisters from map for this test + ps.ClearPersisters() + epoch, err := ps.GetOldestEpoch() assert.NotNil(t, err) assert.Zero(t, epoch) @@ -1318,6 +1369,183 @@ func TestPruningStorer_RemoveFromCurrentEpoch(t *testing.T) { assert.Equal(t, value, recovered) } +func TestPruningStorer_CreateNextEpochPersisterIfNeeded(t *testing.T) { + t.Parallel() + + t.Run("should not create if persister already in map", func(t *testing.T) { + t.Parallel() + + args := getDefaultArgs() + + args.PersistersTracker = &storateMocks.PersistersTrackerStub{ + HasInitializedEnoughPersistersCalled: func(epoch int64) bool { + return true + }, + } + + createCalls := 0 + args.PersisterFactory = &mock.PersisterFactoryStub{ + CreateCalled: func(path string) (storage.Persister, error) { + if createCalls > 1 { + require.Fail(t, "should have not been called") + } + + return &mock.PersisterStub{}, nil + }, + } + args.EpochsData.NumOfActivePersisters = 3 + args.EpochsData.NumOfEpochsToKeep = 4 + + ps, _ := pruning.NewPruningStorer(args) + + mockPersister0 := &mock.PersisterStub{} + ps.AddMockActivePersister(2, mockPersister0) + ps.AddMockActivePersister(3, mockPersister0) + + ps.CreateNextEpochPersisterIfNeeded(2) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + args := getDefaultArgs() + args.CustomDatabaseRemover = &testscommon.CustomDatabaseRemoverStub{ + ShouldRemoveCalled: func(dbIdentifier string, epoch uint32) bool { + return true + }, + } + + args.EpochsData.NumOfActivePersisters = 3 + args.EpochsData.NumOfEpochsToKeep = 4 + + ps, _ := pruning.NewPruningStorer(args) + + _ = ps.ChangeEpochSimple(1) + _ = ps.ChangeEpochSimple(2) + _ = ps.ChangeEpochSimple(3) + _ = ps.ChangeEpochSimple(4) + + activeEpochs := ps.GetActivePersistersEpochs() + assert.Equal(t, 3, len(activeEpochs)) + + epochs := ps.GetPersistersEpochs() + assert.Equal(t, 4, len(epochs)) + + ps.CreateNextEpochPersisterIfNeeded(4) + + activeEpochs = ps.GetActivePersistersEpochs() + assert.Equal(t, 3, len(activeEpochs)) + + epochs = ps.GetPersistersEpochs() + assert.Equal(t, 5, len(epochs)) + + metaBlock := &block.MetaBlock{ + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{{Epoch: 3}}, + Economics: block.Economics{}, + }, + Epoch: 5, + } + _ = ps.ChangeEpoch(metaBlock) + + ps.CreateNextEpochPersisterIfNeeded(5) + + activeEpochs = ps.GetActivePersistersEpochs() + assert.Equal(t, 3, len(activeEpochs)) + + epochs = ps.GetPersistersEpochs() + assert.Equal(t, 5, len(epochs)) + + metaBlock = &block.MetaBlock{ + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{{Epoch: 4}}, + Economics: block.Economics{}, + }, + Epoch: 6, + } + _ = ps.ChangeEpoch(metaBlock) + + // called for an old epoch, should not affect + ps.CreateNextEpochPersisterIfNeeded(5) + + activeEpochs = ps.GetActivePersistersEpochs() + assert.Equal(t, 3, len(activeEpochs)) + + epochs = ps.GetPersistersEpochs() + assert.Equal(t, 4, len(epochs)) + }) + + t.Run("should work for async execution headers v3", func(t *testing.T) { + t.Parallel() + + args := getDefaultArgs() + args.CustomDatabaseRemover = &testscommon.CustomDatabaseRemoverStub{ + ShouldRemoveCalled: func(dbIdentifier string, epoch uint32) bool { + return true + }, + } + + args.EpochsData.NumOfActivePersisters = 3 + args.EpochsData.NumOfEpochsToKeep = 4 + + ps, _ := pruning.NewPruningStorer(args) + + _ = ps.ChangeEpochSimple(1) + _ = ps.ChangeEpochSimple(2) + _ = ps.ChangeEpochSimple(3) + _ = ps.ChangeEpochSimple(4) + + activeEpochs := ps.GetActivePersistersEpochs() + assert.Equal(t, 3, len(activeEpochs)) + + epochs := ps.GetPersistersEpochs() + assert.Equal(t, 4, len(epochs)) + + ps.CreateNextEpochPersisterIfNeeded(4) + + activeEpochs = ps.GetActivePersistersEpochs() + assert.Equal(t, 3, len(activeEpochs)) + + epochs = ps.GetPersistersEpochs() + assert.Equal(t, 5, len(epochs)) + + metaBlock := &block.MetaBlockV3{ + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{{Epoch: 3}}, + Economics: block.Economics{}, + }, + Epoch: 5, + } + _ = ps.ChangeEpoch(metaBlock) + + ps.CreateNextEpochPersisterIfNeeded(5) + + activeEpochs = ps.GetActivePersistersEpochs() + assert.Equal(t, 3, len(activeEpochs)) + + epochs = ps.GetPersistersEpochs() + assert.Equal(t, 5, len(epochs)) + + metaBlock = &block.MetaBlockV3{ + EpochStart: block.EpochStart{ + LastFinalizedHeaders: []block.EpochStartShardData{{Epoch: 4}}, + Economics: block.Economics{}, + }, + Epoch: 6, + } + _ = ps.ChangeEpoch(metaBlock) + + // called for an old epoch, should not affect + ps.CreateNextEpochPersisterIfNeeded(5) + + activeEpochs = ps.GetActivePersistersEpochs() + assert.Equal(t, 3, len(activeEpochs)) + + epochs = ps.GetPersistersEpochs() + assert.Equal(t, 4, len(epochs)) + }) +} + func TestPruningStorer_IsInterfaceNil(t *testing.T) { t.Parallel() diff --git a/storage/pruning/triePruningStorer.go b/storage/pruning/triePruningStorer.go index a52b2483e84..133dae9e9ae 100644 --- a/storage/pruning/triePruningStorer.go +++ b/storage/pruning/triePruningStorer.go @@ -51,7 +51,7 @@ func (ps *triePruningStorer) lastEpochNeeded() uint32 { lastEpochNeeded := uint32(0) for i := 0; i < len(ps.activePersisters); i++ { lastEpochNeeded = ps.activePersisters[i].epoch - val, err := ps.activePersisters[i].persister.Get([]byte(common.ActiveDBKey)) + val, err := ps.activePersisters[i].getPersister().Get([]byte(common.ActiveDBKey)) if err != nil { continue } @@ -91,8 +91,8 @@ func (ps *triePruningStorer) PutInEpochWithoutCache(key []byte, data []byte, epo return nil } -// GetFromOldEpochsWithoutAddingToCache searches the old epochs for the given key without adding to the cache -func (ps *triePruningStorer) GetFromOldEpochsWithoutAddingToCache(key []byte, maxEpochToSearchFrom uint32) ([]byte, core.OptionalUint32, error) { +// GetWithoutAddingToCache searches for the given key without adding to the cache +func (ps *triePruningStorer) GetWithoutAddingToCache(key []byte, maxEpochToSearchFrom uint32) ([]byte, core.OptionalUint32, error) { v, ok := ps.cacher.Get(key) if ok && !bytes.Equal([]byte(common.ActiveDBKey), key) { ps.stateStatsHandler.IncrSnapshotCache() @@ -103,16 +103,17 @@ func (ps *triePruningStorer) GetFromOldEpochsWithoutAddingToCache(key []byte, ma defer ps.lock.RUnlock() numClosedDbs := 0 - for idx := 1; idx < len(ps.activePersisters); idx++ { - if ps.activePersisters[idx].epoch >= maxEpochToSearchFrom { + for idx := 0; idx < len(ps.activePersisters); idx++ { + if ps.activePersisters[idx].epoch > maxEpochToSearchFrom { continue } - val, err := ps.activePersisters[idx].persister.Get(key) + val, err := ps.activePersisters[idx].getPersister().Get(key) if err != nil { if errors.Is(err, storage.ErrDBIsClosed) { numClosedDbs++ } + ps.stateStatsHandler.IncrSnapshotPersister(ps.activePersisters[idx].epoch) continue } @@ -126,7 +127,7 @@ func (ps *triePruningStorer) GetFromOldEpochsWithoutAddingToCache(key []byte, ma return val, epoch, nil } - if numClosedDbs+1 == len(ps.activePersisters) && len(ps.activePersisters) > 1 { + if numClosedDbs == len(ps.activePersisters) { return nil, core.OptionalUint32{}, storage.ErrDBIsClosed } @@ -142,7 +143,7 @@ func (ps *triePruningStorer) GetFromLastEpoch(key []byte) ([]byte, error) { return nil, fmt.Errorf("key %s not found in %s", hex.EncodeToString(key), ps.identifier) } - return ps.activePersisters[lastEpochIndex].persister.Get(key) + return ps.activePersisters[lastEpochIndex].getPersister().Get(key) } // GetFromCurrentEpoch searches only the current epoch storer for the given key @@ -154,7 +155,7 @@ func (ps *triePruningStorer) GetFromCurrentEpoch(key []byte) ([]byte, error) { return nil, fmt.Errorf("key %s not found in %s", hex.EncodeToString(key), ps.identifier) } - persister := ps.activePersisters[currentEpochIndex].persister + persister := ps.activePersisters[currentEpochIndex].getPersister() ps.lock.RUnlock() return persister.Get(key) @@ -180,7 +181,7 @@ func (ps *triePruningStorer) RemoveFromAllActiveEpochs(key []byte) error { ps.lock.RLock() defer ps.lock.RUnlock() for _, pd := range ps.activePersisters { - err = pd.persister.Remove(key) + err = pd.getPersister().Remove(key) if err != nil { return err } diff --git a/storage/pruning/triePruningStorer_test.go b/storage/pruning/triePruningStorer_test.go index e6a699f1329..77cc21f6933 100644 --- a/storage/pruning/triePruningStorer_test.go +++ b/storage/pruning/triePruningStorer_test.go @@ -40,7 +40,7 @@ func TestNewTriePruningStorer(t *testing.T) { }) } -func TestTriePruningStorer_GetFromOldEpochsWithoutCacheSearchesOnlyOldEpochsAndReturnsEpoch(t *testing.T) { +func TestTriePruningStorer_GetWithoutCacheReturnsEpoch(t *testing.T) { t.Parallel() args := getDefaultArgs() @@ -64,17 +64,17 @@ func TestTriePruningStorer_GetFromOldEpochsWithoutCacheSearchesOnlyOldEpochsAndR assert.Nil(t, err) assert.Equal(t, 0, len(cacher.Keys())) - res, epoch, err := ps.GetFromOldEpochsWithoutAddingToCache(testKey1, 10) + res, epoch, err := ps.GetWithoutAddingToCache(testKey1, 10) assert.Equal(t, testVal1, res) assert.Nil(t, err) assert.True(t, epoch.HasValue) assert.Equal(t, uint32(0), epoch.Value) - res, epoch, err = ps.GetFromOldEpochsWithoutAddingToCache(testKey2, 10) - assert.Nil(t, res) - assert.NotNil(t, err) - assert.False(t, epoch.HasValue) - assert.True(t, strings.Contains(err.Error(), "not found")) + res, epoch, err = ps.GetWithoutAddingToCache(testKey2, 10) + assert.Equal(t, testVal2, res) + assert.Nil(t, err) + assert.True(t, epoch.HasValue) + assert.Equal(t, uint32(1), epoch.Value) } func TestTriePruningStorer_GetFromOldEpochsWithCache(t *testing.T) { @@ -95,7 +95,7 @@ func TestTriePruningStorer_GetFromOldEpochsWithCache(t *testing.T) { assert.Nil(t, err) ps.SetEpochForPutOperation(1) - res, epoch, err := ps.GetFromOldEpochsWithoutAddingToCache(testKey1, 10) + res, epoch, err := ps.GetWithoutAddingToCache(testKey1, 10) assert.Equal(t, testVal1, res) assert.Nil(t, err) assert.False(t, epoch.HasValue) @@ -117,7 +117,7 @@ func TestTriePruningStorer_GetFromOldEpochsWithoutCacheLessActivePersisters(t *t assert.Equal(t, 1, ps.GetNumActivePersisters()) _ = ps.ChangeEpochSimple(1) - val, _, err := ps.GetFromOldEpochsWithoutAddingToCache(testKey1, 10) + val, _, err := ps.GetWithoutAddingToCache(testKey1, 10) assert.Nil(t, err) assert.Equal(t, testVal1, val) } @@ -140,7 +140,7 @@ func TestTriePruningStorer_GetFromOldEpochsWithoutCacheMoreActivePersisters(t *t _ = ps.ChangeEpochSimple(2) _ = ps.ChangeEpochSimple(3) - val, _, err := ps.GetFromOldEpochsWithoutAddingToCache(testKey1, 10) + val, _, err := ps.GetWithoutAddingToCache(testKey1, 10) assert.Nil(t, err) assert.Equal(t, testVal1, val) } @@ -176,84 +176,11 @@ func TestTriePruningStorer_GetFromOldEpochsWithoutCacheAllPersistersClosed(t *te _ = ps.ChangeEpochSimple(3) _ = ps.Close() - val, _, err := ps.GetFromOldEpochsWithoutAddingToCache([]byte("key"), 10) + val, _, err := ps.GetWithoutAddingToCache([]byte("key"), 10) assert.Nil(t, val) assert.Equal(t, storage.ErrDBIsClosed, err) } -func TestTriePruningStorer_GetFromOldEpochsWithoutCacheDoesNotSearchInCurrentStorer(t *testing.T) { - t.Parallel() - - args := getDefaultArgs() - ps, _ := pruning.NewTriePruningStorer(args) - cacher := cache.NewCacherStub() - cacher.PutCalled = func(_ []byte, _ interface{}, _ int) bool { - require.Fail(t, "this should not be called") - return false - } - ps.SetCacher(cacher) - testKey1 := []byte("key1") - testVal1 := []byte("value1") - - err := ps.PutInEpochWithoutCache(testKey1, testVal1, 0) - assert.Nil(t, err) - ps.ClearCache() - - res, _, err := ps.GetFromOldEpochsWithoutAddingToCache(testKey1, 10) - assert.Nil(t, res) - assert.NotNil(t, err) - assert.True(t, strings.Contains(err.Error(), "not found")) -} - -func TestTriePruningStorer_GetFromLastEpochSearchesOnlyLastEpoch(t *testing.T) { - t.Parallel() - - args := getDefaultArgs() - ps, _ := pruning.NewTriePruningStorer(args) - cacher := cache.NewCacherMock() - ps.SetCacher(cacher) - - testKey1 := []byte("key1") - testVal1 := []byte("value1") - testKey2 := []byte("key2") - testVal2 := []byte("value2") - testKey3 := []byte("key3") - testVal3 := []byte("value3") - - err := ps.PutInEpochWithoutCache(testKey1, testVal1, 0) - assert.Nil(t, err) - - err = ps.ChangeEpochSimple(1) - assert.Nil(t, err) - ps.SetEpochForPutOperation(1) - - err = ps.PutInEpochWithoutCache(testKey2, testVal2, 1) - assert.Nil(t, err) - assert.Equal(t, 0, len(cacher.Keys())) - - err = ps.ChangeEpochSimple(2) - assert.Nil(t, err) - ps.SetEpochForPutOperation(2) - - err = ps.PutInEpochWithoutCache(testKey3, testVal3, 2) - assert.Nil(t, err) - assert.Equal(t, 0, len(cacher.Keys())) - - res, err := ps.GetFromLastEpoch(testKey2) - assert.Equal(t, testVal2, res) - assert.Nil(t, err) - - res, err = ps.GetFromLastEpoch(testKey1) - assert.Nil(t, res) - assert.NotNil(t, err) - assert.True(t, strings.Contains(err.Error(), "not found")) - - res, err = ps.GetFromLastEpoch(testKey3) - assert.Nil(t, res) - assert.NotNil(t, err) - assert.True(t, strings.Contains(err.Error(), "not found")) -} - func TestTriePruningStorer_GetFromCurrentEpochSearchesOnlyCurrentEpoch(t *testing.T) { t.Parallel() diff --git a/storage/txcache/txcache.go b/storage/txcache/txcache.go deleted file mode 100644 index 01e9212ea15..00000000000 --- a/storage/txcache/txcache.go +++ /dev/null @@ -1,51 +0,0 @@ -package txcache - -import ( - "github.com/multiversx/mx-chain-storage-go/txcache" - "github.com/multiversx/mx-chain-storage-go/types" -) - -// WrappedTransaction contains a transaction, its hash and extra information -type WrappedTransaction = txcache.WrappedTransaction - -// AccountState represents the state of an account (as seen by the mempool) -type AccountState = types.AccountState - -// MempoolHost provides blockchain information for mempool operations -type MempoolHost = txcache.MempoolHost - -// SelectionSession provides blockchain information for transaction selection -type SelectionSession = txcache.SelectionSession - -// ForEachTransaction is an iterator callback -type ForEachTransaction = txcache.ForEachTransaction - -// ConfigDestinationMe holds cache configuration -type ConfigDestinationMe = txcache.ConfigDestinationMe - -// ConfigSourceMe holds cache configuration -type ConfigSourceMe = txcache.ConfigSourceMe - -// TxCache represents a cache-like structure (it has a fixed capacity and implements an eviction mechanism) for holding transactions -type TxCache = txcache.TxCache - -// DisabledCache represents a disabled cache -type DisabledCache = txcache.DisabledCache - -// CrossTxCache holds cross-shard transactions (where destination == me) -type CrossTxCache = txcache.CrossTxCache - -// NewTxCache creates a new transaction cache -func NewTxCache(config ConfigSourceMe, host MempoolHost) (*TxCache, error) { - return txcache.NewTxCache(config, host) -} - -// NewDisabledCache creates a new disabled cache -func NewDisabledCache() *DisabledCache { - return txcache.NewDisabledCache() -} - -// NewCrossTxCache creates a new transactions cache -func NewCrossTxCache(config ConfigDestinationMe) (*CrossTxCache, error) { - return txcache.NewCrossTxCache(config) -} diff --git a/storage/txcache/txcache_test.go b/storage/txcache/txcache_test.go deleted file mode 100644 index 5b84daba2d5..00000000000 --- a/storage/txcache/txcache_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package txcache - -import ( - "strings" - "testing" - - "github.com/multiversx/mx-chain-go/testscommon/txcachemocks" - "github.com/multiversx/mx-chain-storage-go/common" - "github.com/stretchr/testify/assert" -) - -func TestNewTxCache(t *testing.T) { - t.Parallel() - - t.Run("nil parameter should error", func(t *testing.T) { - t.Parallel() - - cfg := ConfigSourceMe{ - Name: "test", - NumChunks: 1, - NumBytesThreshold: 1000, - NumBytesPerSenderThreshold: 100, - CountThreshold: 10, - CountPerSenderThreshold: 100, - NumItemsToPreemptivelyEvict: 1, - } - - cache, err := NewTxCache(cfg, nil) - assert.Nil(t, cache) - assert.ErrorContains(t, err, "nil mempool host") - }) - t.Run("should work", func(t *testing.T) { - t.Parallel() - - cfg := ConfigSourceMe{ - Name: "test", - NumChunks: 1, - NumBytesThreshold: 1000, - NumBytesPerSenderThreshold: 100, - CountThreshold: 10, - CountPerSenderThreshold: 100, - NumItemsToPreemptivelyEvict: 1, - } - - cache, err := NewTxCache(cfg, txcachemocks.NewMempoolHostMock()) - assert.NotNil(t, cache) - assert.Nil(t, err) - }) -} - -func TestNewDisabledCache(t *testing.T) { - t.Parallel() - - cache := NewDisabledCache() - assert.NotNil(t, cache) -} - -func TestNewCrossTxCache(t *testing.T) { - t.Parallel() - - t.Run("invalid config should error", func(t *testing.T) { - t.Parallel() - - cfg := ConfigDestinationMe{ - Name: "", - NumChunks: 1, - MaxNumItems: 100, - MaxNumBytes: 1000, - NumItemsToPreemptivelyEvict: 1, - } - - cache, err := NewCrossTxCache(cfg) - assert.Nil(t, cache) - assert.ErrorIs(t, err, common.ErrInvalidConfig) - assert.True(t, strings.Contains(err.Error(), "config.Name is invalid")) - }) - t.Run("should work", func(t *testing.T) { - t.Parallel() - - cfg := ConfigDestinationMe{ - Name: "test", - NumChunks: 1, - MaxNumItems: 100, - MaxNumBytes: 1000, - NumItemsToPreemptivelyEvict: 1, - } - - cache, err := NewCrossTxCache(cfg) - assert.NotNil(t, cache) - assert.Nil(t, err) - }) -} diff --git a/testscommon/antifloodConfigsHandlerStub.go b/testscommon/antifloodConfigsHandlerStub.go new file mode 100644 index 00000000000..ea78b9ae154 --- /dev/null +++ b/testscommon/antifloodConfigsHandlerStub.go @@ -0,0 +1,45 @@ +package testscommon + +import ( + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" +) + +// AntifloodConfigsHandlerStub - +type AntifloodConfigsHandlerStub struct { + GetCurrentConfigCalled func() config.AntifloodConfigByRound + IsEnabledCalled func() bool + GetFloodPreventerConfigByTypeCalled func(configType common.FloodPreventerType) config.FloodPreventerConfig +} + +// GetCurrentConfig - +func (stub *AntifloodConfigsHandlerStub) GetCurrentConfig() config.AntifloodConfigByRound { + if stub.GetCurrentConfigCalled != nil { + return stub.GetCurrentConfigCalled() + } + + return GetDefaultAntifloodConfig().ConfigsByRound[0] +} + +// IsEnabled - +func (stub *AntifloodConfigsHandlerStub) IsEnabled() bool { + if stub.IsEnabledCalled != nil { + return stub.IsEnabledCalled() + } + + return false +} + +// GetFloodPreventerConfigByType - +func (stub *AntifloodConfigsHandlerStub) GetFloodPreventerConfigByType(configType common.FloodPreventerType) config.FloodPreventerConfig { + if stub.GetFloodPreventerConfigByTypeCalled != nil { + return stub.GetFloodPreventerConfigByTypeCalled(configType) + } + + return config.FloodPreventerConfig{} +} + +// IsInterfaceNil - +func (stub *AntifloodConfigsHandlerStub) IsInterfaceNil() bool { + return stub == nil +} diff --git a/testscommon/aotStubs/aotSelectorStub.go b/testscommon/aotStubs/aotSelectorStub.go new file mode 100644 index 00000000000..ee04f9f281f --- /dev/null +++ b/testscommon/aotStubs/aotSelectorStub.go @@ -0,0 +1,50 @@ +package aotStubs + +import ( + "github.com/multiversx/mx-chain-core-go/data" + + "github.com/multiversx/mx-chain-go/process" +) + +// AOTSelectorStub - +type AOTSelectorStub struct { + TriggerAOTSelectionCalled func(committedHeader data.HeaderHandler, currentRound uint64) + GetPreSelectedTransactionsCalled func(blockNonce uint64) (*process.AOTSelectionResult, bool) + CancelOngoingSelectionCalled func() + CloseCalled func() error +} + +// TriggerAOTSelection - +func (stub *AOTSelectorStub) TriggerAOTSelection(committedHeader data.HeaderHandler, currentRound uint64) { + if stub.TriggerAOTSelectionCalled != nil { + stub.TriggerAOTSelectionCalled(committedHeader, currentRound) + } +} + +// GetPreSelectedTransactions - +func (stub *AOTSelectorStub) GetPreSelectedTransactions(blockNonce uint64) (*process.AOTSelectionResult, bool) { + if stub.GetPreSelectedTransactionsCalled != nil { + return stub.GetPreSelectedTransactionsCalled(blockNonce) + } + return nil, false +} + +// CancelOngoingSelection - +func (stub *AOTSelectorStub) CancelOngoingSelection() { + if stub.CancelOngoingSelectionCalled != nil { + stub.CancelOngoingSelectionCalled() + } +} + +// Close - +func (stub *AOTSelectorStub) Close() error { + if stub.CloseCalled != nil { + return stub.CloseCalled() + } + return nil +} + +// IsInterfaceNil returns true if there is no value under the interface +func (stub *AOTSelectorStub) IsInterfaceNil() bool { + return stub == nil +} diff --git a/testscommon/blockChainHookStub.go b/testscommon/blockChainHookStub.go index 3b0f52aea90..41784cd01d2 100644 --- a/testscommon/blockChainHookStub.go +++ b/testscommon/blockChainHookStub.go @@ -245,7 +245,7 @@ func (stub *BlockChainHookStub) EpochStartBlockRound() uint64 { return 0 } -// EpochStartBlockTimeStamp - +// EpochStartBlockTimeStampMs - func (stub *BlockChainHookStub) EpochStartBlockTimeStampMs() uint64 { if stub.EpochStartBlockTimeStampMsCalled != nil { return stub.EpochStartBlockTimeStampMsCalled() diff --git a/testscommon/blockProcessorStub.go b/testscommon/blockProcessorStub.go index 8c72193ad3d..2b06d3015b7 100644 --- a/testscommon/blockProcessorStub.go +++ b/testscommon/blockProcessorStub.go @@ -11,20 +11,44 @@ import ( type BlockProcessorStub struct { SetNumProcessedObjCalled func(numObj uint64) ProcessBlockCalled func(header data.HeaderHandler, body data.BodyHandler, haveTime func() time.Duration) error + ProcessBlockProposalCalled func(header data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) + CommitBlockProposalStateCalled func(headerHandler data.HeaderHandler) error + RevertBlockProposalStateCalled func() ProcessScheduledBlockCalled func(header data.HeaderHandler, body data.BodyHandler, haveTime func() time.Duration) error CommitBlockCalled func(header data.HeaderHandler, body data.BodyHandler) error RevertCurrentBlockCalled func() PruneStateOnRollbackCalled func(currHeader data.HeaderHandler, currHeaderHash []byte, prevHeader data.HeaderHandler, prevHeaderHash []byte) CreateBlockCalled func(initialHdrData data.HeaderHandler, haveTime func() bool) (data.HeaderHandler, data.BodyHandler, error) + CreateBlockProposalCalled func(initialHdr data.HeaderHandler, haveTime func() bool) (data.HeaderHandler, data.BodyHandler, error) RestoreBlockIntoPoolsCalled func(header data.HeaderHandler, body data.BodyHandler) error RestoreBlockBodyIntoPoolsCalled func(body data.BodyHandler) error - MarshalizedDataToBroadcastCalled func(header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) + MarshalizedDataToBroadcastCalled func(headerHash []byte, header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) DecodeBlockBodyCalled func(dta []byte) data.BodyHandler DecodeBlockHeaderCalled func(dta []byte) data.HeaderHandler CreateNewHeaderCalled func(round uint64, nonce uint64) (data.HeaderHandler, error) + CreateNewHeaderProposalCalled func(round uint64, nonce uint64) (data.HeaderHandler, error) RevertStateToBlockCalled func(header data.HeaderHandler, rootHash []byte) error NonceOfFirstCommittedBlockCalled func() core.OptionalUint64 CloseCalled func() error + VerifyBlockProposalCalled func( + headerHandler data.HeaderHandler, + bodyHandler data.BodyHandler, + haveTime func() time.Duration, + ) error + OnProposedBlockCalled func( + proposedBody data.BodyHandler, + proposedHeader data.HeaderHandler, + proposedHash []byte, + ) error + OnBackfilledBlockCalled func( + _ data.BodyHandler, + _ data.HeaderHandler, + _ []byte, + ) error + OnExecutedBlockCalled func(header data.HeaderHandler, rootHash []byte) error + RemoveHeaderFromPoolCalled func(headerHash []byte) + ProposedDirectSentTransactionsToBroadcastCalled func(proposedBody data.BodyHandler) map[string][][]byte + PruneTrieAsyncHeaderCalled func() } // SetNumProcessedObj - @@ -43,6 +67,31 @@ func (bps *BlockProcessorStub) ProcessBlock(header data.HeaderHandler, body data return nil } +// ProcessBlockProposal mocks processing a block +func (bps *BlockProcessorStub) ProcessBlockProposal(header data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + if bps.ProcessBlockProposalCalled != nil { + return bps.ProcessBlockProposalCalled(header, headerHash, body) + } + + return nil, nil +} + +// CommitBlockProposalState - +func (bps *BlockProcessorStub) CommitBlockProposalState(headerHandler data.HeaderHandler) error { + if bps.CommitBlockProposalStateCalled != nil { + return bps.CommitBlockProposalStateCalled(headerHandler) + } + + return nil +} + +// RevertBlockProposalState - +func (bps *BlockProcessorStub) RevertBlockProposalState() { + if bps.RevertBlockProposalStateCalled != nil { + bps.RevertBlockProposalStateCalled() + } +} + // ProcessScheduledBlock mocks processing a scheduled block func (bps *BlockProcessorStub) ProcessScheduledBlock(header data.HeaderHandler, body data.BodyHandler, haveTime func() time.Duration) error { if bps.ProcessScheduledBlockCalled != nil { @@ -84,6 +133,15 @@ func (bps *BlockProcessorStub) CreateBlock(initialHdrData data.HeaderHandler, ha return nil, nil, ErrNotImplemented } +// CreateBlockProposal - +func (bps *BlockProcessorStub) CreateBlockProposal(initialHdr data.HeaderHandler, haveTime func() bool) (data.HeaderHandler, data.BodyHandler, error) { + if bps.CreateBlockProposalCalled != nil { + return bps.CreateBlockProposalCalled(initialHdr, haveTime) + } + + return nil, nil, ErrNotImplemented +} + // RestoreBlockIntoPools - func (bps *BlockProcessorStub) RestoreBlockIntoPools(header data.HeaderHandler, body data.BodyHandler) error { if bps.RestoreBlockIntoPoolsCalled != nil { @@ -103,9 +161,9 @@ func (bps *BlockProcessorStub) RestoreBlockBodyIntoPools(body data.BodyHandler) } // MarshalizedDataToBroadcast - -func (bps *BlockProcessorStub) MarshalizedDataToBroadcast(header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) { +func (bps *BlockProcessorStub) MarshalizedDataToBroadcast(headerHash []byte, header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) { if bps.MarshalizedDataToBroadcastCalled != nil { - return bps.MarshalizedDataToBroadcastCalled(header, body) + return bps.MarshalizedDataToBroadcastCalled(headerHash, header, body) } return nil, nil, ErrNotImplemented @@ -138,6 +196,15 @@ func (bps *BlockProcessorStub) CreateNewHeader(round uint64, nonce uint64) (data return nil, ErrNotImplemented } +// CreateNewHeaderProposal - +func (bps *BlockProcessorStub) CreateNewHeaderProposal(round uint64, nonce uint64) (data.HeaderHandler, error) { + if bps.CreateNewHeaderProposalCalled != nil { + return bps.CreateNewHeaderProposalCalled(round, nonce) + } + + return nil, ErrNotImplemented +} + // RevertStateToBlock recreates the state tries to the root hashes indicated by the provided header func (bps *BlockProcessorStub) RevertStateToBlock(header data.HeaderHandler, rootHash []byte) error { if bps.RevertStateToBlockCalled != nil { @@ -167,6 +234,73 @@ func (bps *BlockProcessorStub) Close() error { return nil } +// VerifyBlockProposal - +func (bps *BlockProcessorStub) VerifyBlockProposal( + headerHandler data.HeaderHandler, + bodyHandler data.BodyHandler, + haveTime func() time.Duration, +) error { + if bps.VerifyBlockProposalCalled != nil { + return bps.VerifyBlockProposalCalled(headerHandler, bodyHandler, haveTime) + } + return nil +} + +// OnProposedBlock - +func (bps *BlockProcessorStub) OnProposedBlock( + proposedBody data.BodyHandler, + proposedHeader data.HeaderHandler, + proposedHash []byte, +) error { + if bps.OnProposedBlockCalled != nil { + return bps.OnProposedBlockCalled(proposedBody, proposedHeader, proposedHash) + } + return nil +} + +// OnBackfilledBlock - +func (bps *BlockProcessorStub) OnBackfilledBlock( + proposedBody data.BodyHandler, + proposedHeader data.HeaderHandler, + proposedHash []byte, +) error { + if bps.OnBackfilledBlockCalled != nil { + return bps.OnBackfilledBlockCalled(proposedBody, proposedHeader, proposedHash) + } + return nil +} + +// OnExecutedBlock - +func (bps *BlockProcessorStub) OnExecutedBlock(header data.HeaderHandler, rootHash []byte) error { + if bps.OnExecutedBlockCalled != nil { + return bps.OnExecutedBlockCalled(header, rootHash) + } + + return nil +} + +// RemoveHeaderFromPool - +func (bps *BlockProcessorStub) RemoveHeaderFromPool(headerHash []byte) { + if bps.RemoveHeaderFromPoolCalled != nil { + bps.RemoveHeaderFromPoolCalled(headerHash) + } +} + +// ProposedDirectSentTransactionsToBroadcast - +func (bps *BlockProcessorStub) ProposedDirectSentTransactionsToBroadcast(proposedBody data.BodyHandler) map[string][][]byte { + if bps.ProposedDirectSentTransactionsToBroadcastCalled != nil { + return bps.ProposedDirectSentTransactionsToBroadcastCalled(proposedBody) + } + return nil +} + +// PruneTrieAsyncHeader - +func (bps *BlockProcessorStub) PruneTrieAsyncHeader() { + if bps.PruneTrieAsyncHeaderCalled != nil { + bps.PruneTrieAsyncHeaderCalled() + } +} + // IsInterfaceNil returns true if there is no value under the interface func (bps *BlockProcessorStub) IsInterfaceNil() bool { return bps == nil diff --git a/testscommon/blockSizeComputationStub.go b/testscommon/blockSizeComputationStub.go index c4da8f51d92..0e12b7b83c8 100644 --- a/testscommon/blockSizeComputationStub.go +++ b/testscommon/blockSizeComputationStub.go @@ -7,6 +7,8 @@ type BlockSizeComputationStub struct { AddNumTxsCalled func(int) IsMaxBlockSizeReachedCalled func(int, int) bool IsMaxBlockSizeWithoutThrottleReachedCalled func(int, int) bool + DecNumMiniBlocksCalled func(numMiniBlocks int) + DecNumTxsCalled func(numTxs int) } // Init - @@ -30,6 +32,20 @@ func (bscs *BlockSizeComputationStub) AddNumTxs(numTxs int) { } } +// DecNumMiniBlocks - +func (bscs *BlockSizeComputationStub) DecNumMiniBlocks(numMiniBlocks int) { + if bscs.DecNumMiniBlocksCalled != nil { + bscs.DecNumMiniBlocksCalled(numMiniBlocks) + } +} + +// DecNumTxs - +func (bscs *BlockSizeComputationStub) DecNumTxs(numTxs int) { + if bscs.DecNumTxsCalled != nil { + bscs.DecNumTxsCalled(numTxs) + } +} + // IsMaxBlockSizeWithoutThrottleReached - func (bscs *BlockSizeComputationStub) IsMaxBlockSizeWithoutThrottleReached(numNewMiniBlocks int, numNewTxs int) bool { if bscs.IsMaxBlockSizeWithoutThrottleReachedCalled != nil { diff --git a/testscommon/bootstrapMocks/epochStart/epochStartShardDataStub.go b/testscommon/bootstrapMocks/epochStart/epochStartShardDataStub.go index 97dd441d713..1b0f0045aad 100644 --- a/testscommon/bootstrapMocks/epochStart/epochStartShardDataStub.go +++ b/testscommon/bootstrapMocks/epochStart/epochStartShardDataStub.go @@ -22,6 +22,15 @@ type EpochStartShardDataStub struct { SetFirstPendingMetaBlockCalled func([]byte) error SetLastFinishedMetaBlockCalled func([]byte) error SetPendingMiniBlockHeadersCalled func([]data.MiniBlockHeaderHandler) error + GetScheduledRootHashCalled func() []byte +} + +// GetScheduledRootHash - +func (essds *EpochStartShardDataStub) GetScheduledRootHash() []byte { + if essds.GetScheduledRootHashCalled != nil { + return essds.GetScheduledRootHashCalled() + } + return []byte("root hash") } // GetShardID - diff --git a/testscommon/chainHandlerMock.go b/testscommon/chainHandlerMock.go index 736e1b0bdf4..90ed6e1bbd9 100644 --- a/testscommon/chainHandlerMock.go +++ b/testscommon/chainHandlerMock.go @@ -16,6 +16,22 @@ type ChainHandlerMock struct { finalBlockNonce uint64 finalBlockHash []byte finalBlockRootHash []byte + + lastExecutedBlockNonce uint64 + lastExecutedBlockHash []byte + lastExecutedBlockRootHash []byte + lastExecutedBlockHeader data.HeaderHandler + lastExecutedResult data.BaseExecutionResultHandler +} + +// GetLastExecutionResult - +func (mock *ChainHandlerMock) GetLastExecutionResult() data.BaseExecutionResultHandler { + return mock.lastExecutedResult +} + +// SetLastExecutionResult - +func (mock *ChainHandlerMock) SetLastExecutionResult(result data.BaseExecutionResultHandler) { + mock.lastExecutedResult = result } // GetGenesisHeader - @@ -78,6 +94,30 @@ func (mock *ChainHandlerMock) GetFinalBlockInfo() (nonce uint64, blockHash []byt return mock.finalBlockNonce, mock.finalBlockHash, mock.finalBlockRootHash } +// GetLastExecutedBlockInfo - +func (mock *ChainHandlerMock) GetLastExecutedBlockInfo() (nonce uint64, blockHash []byte, rootHash []byte) { + return mock.lastExecutedBlockNonce, mock.lastExecutedBlockHash, mock.lastExecutedBlockRootHash +} + +// SetCurrentBlockHeader - +func (mock *ChainHandlerMock) SetCurrentBlockHeader(header data.HeaderHandler) error { + mock.currentBlockHeader = header + return nil +} + +// GetLastExecutedBlockHeader - +func (mock *ChainHandlerMock) GetLastExecutedBlockHeader() data.HeaderHandler { + return mock.lastExecutedBlockHeader +} + +// SetLastExecutedBlockHeaderAndRootHash - +func (mock *ChainHandlerMock) SetLastExecutedBlockHeaderAndRootHash(header data.HeaderHandler, blockHash []byte, rootHash []byte) { + mock.lastExecutedBlockHeader = header + mock.lastExecutedBlockNonce = header.GetNonce() + mock.lastExecutedBlockHash = blockHash + mock.lastExecutedBlockRootHash = rootHash +} + // IsInterfaceNil - func (mock *ChainHandlerMock) IsInterfaceNil() bool { return mock == nil diff --git a/testscommon/chainHandlerStub.go b/testscommon/chainHandlerStub.go index 1c27ea15808..3d3f0852457 100644 --- a/testscommon/chainHandlerStub.go +++ b/testscommon/chainHandlerStub.go @@ -6,17 +6,38 @@ import ( // ChainHandlerStub - type ChainHandlerStub struct { - GetGenesisHeaderCalled func() data.HeaderHandler - SetGenesisHeaderCalled func(handler data.HeaderHandler) error - GetGenesisHeaderHashCalled func() []byte - SetGenesisHeaderHashCalled func([]byte) - GetCurrentBlockHeaderCalled func() data.HeaderHandler - SetCurrentBlockHeaderAndRootHashCalled func(header data.HeaderHandler, rootHash []byte) error - GetCurrentBlockHeaderHashCalled func() []byte - SetCurrentBlockHeaderHashCalled func([]byte) - GetCurrentBlockRootHashCalled func() []byte - SetFinalBlockInfoCalled func(nonce uint64, headerHash []byte, rootHash []byte) - GetFinalBlockInfoCalled func() (nonce uint64, blockHash []byte, rootHash []byte) + GetGenesisHeaderCalled func() data.HeaderHandler + SetGenesisHeaderCalled func(handler data.HeaderHandler) error + GetGenesisHeaderHashCalled func() []byte + SetGenesisHeaderHashCalled func([]byte) + GetCurrentBlockHeaderCalled func() data.HeaderHandler + SetCurrentBlockHeaderAndRootHashCalled func(header data.HeaderHandler, rootHash []byte) error + GetCurrentBlockHeaderHashCalled func() []byte + SetCurrentBlockHeaderHashCalled func([]byte) + GetCurrentBlockRootHashCalled func() []byte + SetFinalBlockInfoCalled func(nonce uint64, headerHash []byte, rootHash []byte) + GetFinalBlockInfoCalled func() (nonce uint64, blockHash []byte, rootHash []byte) + GetLastExecutedBlockInfoCalled func() (uint64, []byte, []byte) + SetCurrentBlockHeaderCalled func(header data.HeaderHandler) error + GetLastExecutedBlockHeaderCalled func() data.HeaderHandler + SetLastExecutedBlockHeaderAndRootHashCalled func(header data.HeaderHandler, blockHash []byte, rootHash []byte) + GetLastExecutionResultCalled func() data.BaseExecutionResultHandler + SetLastExecutionResultCalled func(result data.BaseExecutionResultHandler) +} + +// GetLastExecutionResult - +func (stub *ChainHandlerStub) GetLastExecutionResult() data.BaseExecutionResultHandler { + if stub.GetLastExecutionResultCalled != nil { + return stub.GetLastExecutionResultCalled() + } + return nil +} + +// SetLastExecutionResult - +func (stub *ChainHandlerStub) SetLastExecutionResult(result data.BaseExecutionResultHandler) { + if stub.SetLastExecutionResultCalled != nil { + stub.SetLastExecutionResultCalled(result) + } } // GetGenesisHeader - @@ -107,6 +128,40 @@ func (stub *ChainHandlerStub) GetFinalBlockInfo() (nonce uint64, blockHash []byt return 0, nil, nil } +// GetLastExecutedBlockInfo - +func (stub *ChainHandlerStub) GetLastExecutedBlockInfo() (nonce uint64, blockHash []byte, rootHash []byte) { + if stub.GetLastExecutedBlockInfoCalled != nil { + return stub.GetLastExecutedBlockInfoCalled() + } + + return 0, nil, nil +} + +// SetCurrentBlockHeader - +func (stub *ChainHandlerStub) SetCurrentBlockHeader(header data.HeaderHandler) error { + if stub.SetCurrentBlockHeaderCalled != nil { + return stub.SetCurrentBlockHeaderCalled(header) + } + + return nil +} + +// GetLastExecutedBlockHeader - +func (stub *ChainHandlerStub) GetLastExecutedBlockHeader() data.HeaderHandler { + if stub.GetLastExecutedBlockHeaderCalled != nil { + return stub.GetLastExecutedBlockHeaderCalled() + } + + return nil +} + +// SetLastExecutedBlockHeaderAndRootHash - +func (stub *ChainHandlerStub) SetLastExecutedBlockHeaderAndRootHash(header data.HeaderHandler, blockHash []byte, rootHash []byte) { + if stub.SetLastExecutedBlockHeaderAndRootHashCalled != nil { + stub.SetLastExecutedBlockHeaderAndRootHashCalled(header, blockHash, rootHash) + } +} + // IsInterfaceNil - func (stub *ChainHandlerStub) IsInterfaceNil() bool { return stub == nil diff --git a/testscommon/chainParameters/chainParametersHolderMock.go b/testscommon/chainParameters/chainParametersHolderMock.go index 50721aa5716..9781c7b56a2 100644 --- a/testscommon/chainParameters/chainParametersHolderMock.go +++ b/testscommon/chainParameters/chainParametersHolderMock.go @@ -5,6 +5,7 @@ import ( ) var testChainParams = config.ChainParametersByEpochConfig{ + RoundsPerEpoch: 14400, RoundDuration: 6000, Hysteresis: 0, EnableEpoch: 0, diff --git a/testscommon/components/components.go b/testscommon/components/components.go index 19389096b72..a8d7fb25b50 100644 --- a/testscommon/components/components.go +++ b/testscommon/components/components.go @@ -35,7 +35,6 @@ import ( "github.com/multiversx/mx-chain-go/genesis/data" "github.com/multiversx/mx-chain-go/p2p" p2pConfig "github.com/multiversx/mx-chain-go/p2p/config" - p2pFactory "github.com/multiversx/mx-chain-go/p2p/factory" "github.com/multiversx/mx-chain-go/sharding" "github.com/multiversx/mx-chain-go/sharding/nodesCoordinator" "github.com/multiversx/mx-chain-go/state" @@ -115,6 +114,9 @@ func GetCoreArgs() coreComp.CoreComponentsFactoryArgs { "DisableAsyncCallV1": { Round: "18446744073709551615", }, + "SupernovaEnableRound": { + Round: "9999999", + }, }, }, } @@ -316,10 +318,21 @@ func GetNetworkFactoryArgs() networkComp.NetworkComponentsFactoryArgs { TopRatedCacheCapacity: 1000, BadRatedCacheCapacity: 1000, }, - PoolsCleanersConfig: config.PoolsCleanersConfig{ - MaxRoundsToKeepUnprocessedMiniBlocks: 50, - MaxRoundsToKeepUnprocessedTransactions: 50, + GeneralSettings: config.GeneralSettingsConfig{ + ProcessConfigsByRound: []config.ProcessConfigByRound{ + { + MaxRoundsToKeepUnprocessedMiniBlocks: 50, + MaxRoundsToKeepUnprocessedTransactions: 50, + NumFloodingRoundsSlowReacting: 20, + NumFloodingRoundsFastReacting: 30, + NumFloodingRoundsOutOfSpecs: 40, + MaxConsecutiveRoundsOfRatingDecrease: 600, + MaxBlockProcessingTimeMs: 1000, + NumHeadersToRequestInAdvance: 10, + }, + }, }, + Antiflood: testscommon.GetDefaultAntifloodConfig(), } appStatusHandler := statusHandlerMock.NewAppStatusHandlerMock() @@ -331,7 +344,6 @@ func GetNetworkFactoryArgs() networkComp.NetworkComponentsFactoryArgs { NodeOperationMode: common.NormalOperation, MainConfig: mainConfig, StatusHandler: appStatusHandler, - Marshalizer: &mock.MarshalizerMock{}, RatingsConfig: config.RatingsConfig{ General: config.General{}, ShardChain: config.ShardChain{}, @@ -345,8 +357,8 @@ func GetNetworkFactoryArgs() networkComp.NetworkComponentsFactoryArgs { UnitValue: 1.0, }, }, - Syncer: &p2pFactory.LocalSyncTimer{}, CryptoComponents: cryptoCompMock, + CoreComponents: GetCoreComponents(), } } @@ -583,7 +595,8 @@ func GetProcessArgs( MinStakeValue: "1", UnJailValue: "1", MinStepValue: "1", - UnBondPeriod: 0, + UnBondPeriod: 1, + UnBondPeriodSupernova: 2, NumRoundsWithoutBleed: 0, MaximumPercentageToBleed: 0, BleedPercentagePerRound: 0, @@ -626,6 +639,12 @@ func GetProcessArgs( }, }, }, + EconomicsConfig: config.EconomicsConfig{ + FeeSettings: config.FeeSettings{ + BlockCapacityOverestimationFactor: 200, + PercentDecreaseLimitsStep: 10, + }, + }, } } diff --git a/testscommon/components/configs.go b/testscommon/components/configs.go index d8631a3e020..b3672ef1d32 100644 --- a/testscommon/components/configs.go +++ b/testscommon/components/configs.go @@ -2,6 +2,7 @@ package components import ( "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/testscommon" ) // GetGeneralConfig - @@ -113,10 +114,6 @@ func GetGeneralConfig() config.Config { TopRatedCacheCapacity: 1000, BadRatedCacheCapacity: 1000, }, - PoolsCleanersConfig: config.PoolsCleanersConfig{ - MaxRoundsToKeepUnprocessedMiniBlocks: 50, - MaxRoundsToKeepUnprocessedTransactions: 50, - }, BuiltInFunctions: config.BuiltInFunctionsConfig{ AutomaticCrawlerAddresses: []string{ "erd1he8wwxn4az3j82p7wwqsdk794dm7hcrwny6f8dfegkfla34udx7qrf7xje", // shard 0 @@ -156,6 +153,7 @@ func GetGeneralConfig() config.Config { MinTransactionVersion: 1, GenesisMaxNumberOfShards: 3, SetGuardianEpochsDelay: 20, + MaxProposalNonceGap: 10, ChainParametersByEpoch: []config.ChainParametersByEpochConfig{ { EnableEpoch: 0, @@ -166,9 +164,41 @@ func GetGeneralConfig() config.Config { MetachainMinNumNodes: 1, Hysteresis: 0, Adaptivity: false, + RoundsPerEpoch: 20, + MinRoundsBetweenEpochs: 10, }, }, EpochChangeGracePeriodByEpoch: []config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}, + ProcessConfigsByEpoch: []config.ProcessConfigByEpoch{{ + EnableEpoch: 0, + MaxMetaNoncesBehind: 15, + MaxMetaNoncesBehindForGlobalStuck: 30, + MaxShardNoncesBehind: 15, + }}, + ProcessConfigsByRound: []config.ProcessConfigByRound{ + { + EnableRound: 0, + MaxRoundsWithoutNewBlockReceived: 10, + MaxRoundsWithoutCommittedBlock: 10, + MaxRoundsToKeepUnprocessedMiniBlocks: 50, + MaxRoundsToKeepUnprocessedTransactions: 50, + NumFloodingRoundsFastReacting: 20, + NumFloodingRoundsOutOfSpecs: 20, + NumFloodingRoundsSlowReacting: 20, + MaxConsecutiveRoundsOfRatingDecrease: 600, + MaxBlockProcessingTimeMs: 1000, + NumHeadersToRequestInAdvance: 10, + }, + }, + EpochStartConfigsByEpoch: []config.EpochStartConfigByEpoch{ + {EnableEpoch: 0, GracePeriodRounds: 25, ExtraDelayForRequestBlockInfoInMilliseconds: 3000}, + }, + EpochStartConfigsByRound: []config.EpochStartConfigByRound{ + {EnableRound: 0, MaxRoundsWithoutCommittedStartInEpochBlock: 50}, + }, + ConsensusConfigsByEpoch: []config.ConsensusConfigByEpoch{ + {EnableEpoch: 0, NumRoundsToWaitBeforeSignalingChronologyStuck: 10}, + }, }, Marshalizer: config.MarshalizerConfig{ Type: TestMarshalizer, @@ -198,11 +228,6 @@ func GetGeneralConfig() config.Config { Versions: config.VersionsConfig{ DefaultVersion: "1", VersionsByEpochs: nil, - Cache: config.CacheConfig{ - Type: "LRU", - Capacity: 1000, - Shards: 1, - }, }, Hardfork: config.HardforkConfig{ PublicKeyToListenFrom: DummyPk, @@ -227,14 +252,13 @@ func GetGeneralConfig() config.Config { MaxOpenFiles: 10, }, }, + Antiflood: testscommon.GetDefaultAntifloodConfig(), } } // GetEpochStartConfig - func GetEpochStartConfig() config.EpochStartConfig { return config.EpochStartConfig{ - MinRoundsBetweenEpochs: 20, - RoundsPerEpoch: 20, MaxShuffledOutRestartThreshold: 0.2, MinShuffledOutRestartThreshold: 0.1, MinNumConnectedPeersToStart: 2, diff --git a/testscommon/components/default.go b/testscommon/components/default.go index 0e88eb371ef..014592107d5 100644 --- a/testscommon/components/default.go +++ b/testscommon/components/default.go @@ -20,6 +20,7 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" "github.com/multiversx/mx-chain-go/testscommon/nodeTypeProviderMock" "github.com/multiversx/mx-chain-go/testscommon/p2pmocks" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" "github.com/multiversx/mx-chain-go/testscommon/stakingcommon" stateMock "github.com/multiversx/mx-chain-go/testscommon/state" @@ -45,18 +46,19 @@ func GetDefaultCoreComponents() *mock.CoreComponentsMock { MinTransactionVersionCalled: func() uint32 { return 1 }, - WatchdogTimer: &testscommon.WatchdogMock{}, - AlarmSch: &testscommon.AlarmSchedulerStub{}, - NtpSyncTimer: &testscommon.SyncTimerStub{}, - RoundHandlerField: &testscommon.RoundHandlerMock{}, - EconomicsHandler: &economicsmocks.EconomicsHandlerMock{}, - RatingsConfig: &testscommon.RatingsInfoMock{}, - RatingHandler: &testscommon.RaterMock{}, - NodesConfig: &genesisMocks.NodesSetupStub{}, - StartTime: time.Time{}, - NodeTypeProviderField: &nodeTypeProviderMock.NodeTypeProviderStub{}, - EpochChangeNotifier: &epochNotifierMock.EpochNotifierStub{}, - EnableEpochsHandlerField: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + WatchdogTimer: &testscommon.WatchdogMock{}, + AlarmSch: &testscommon.AlarmSchedulerStub{}, + NtpSyncTimer: &testscommon.SyncTimerStub{}, + RoundHandlerField: &testscommon.RoundHandlerMock{}, + EconomicsHandler: &economicsmocks.EconomicsHandlerMock{}, + RatingsConfig: &testscommon.RatingsInfoMock{}, + RatingHandler: &testscommon.RaterMock{}, + NodesConfig: &genesisMocks.NodesSetupStub{}, + StartTime: time.Time{}, + NodeTypeProviderField: &nodeTypeProviderMock.NodeTypeProviderStub{}, + EpochChangeNotifier: &epochNotifierMock.EpochNotifierStub{}, + EnableEpochsHandlerField: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + AntifloodConfigsHandlerField: &testscommon.AntifloodConfigsHandlerStub{}, } } @@ -105,6 +107,7 @@ func GetDefaultStateComponents() *factory.StateComponentsMock { dataRetriever.PeerAccountsUnit.String(): &storageManager.StorageManagerStub{}, }, MissingNodesNotifier: &testscommon.MissingTrieNodesNotifierStub{}, + ChangesCollector: &stateMock.StateAccessesCollectorStub{}, } } @@ -130,7 +133,7 @@ func GetDefaultProcessComponents(shardCoordinator sharding.Coordinator) *mock.Pr EpochTrigger: &testscommon.EpochStartTriggerStub{}, EpochNotifier: &mock.EpochStartNotifierStub{}, ForkDetect: &mock.ForkDetectorMock{}, - BlockProcess: &testscommon.BlockProcessorStub{}, + ExecManager: &processMocks.ExecutionManagerMock{}, BlackListHdl: &testscommon.TimeCacheStub{}, BootSore: &mock.BootstrapStorerMock{}, HeaderSigVerif: &consensus.HeaderSigVerifierMock{}, diff --git a/testscommon/consensus/initializers/initializers.go b/testscommon/consensus/initializers/initializers.go index 187c8f02892..9e8ea41c015 100644 --- a/testscommon/consensus/initializers/initializers.go +++ b/testscommon/consensus/initializers/initializers.go @@ -2,6 +2,7 @@ package initializers import ( crypto "github.com/multiversx/mx-chain-crypto-go" + "github.com/multiversx/mx-chain-go/consensus/mock" "golang.org/x/exp/slices" "github.com/multiversx/mx-chain-go/consensus" @@ -90,6 +91,7 @@ func InitConsensusStateWithArgsVerifySignature(keysHandler consensus.KeysHandler rcns, rthr, rstatus, + &mock.NodeRedundancyHandlerStub{}, ) cns.Data = []byte("X") cns.SetRoundIndex(0) @@ -148,6 +150,7 @@ func createConsensusStateWithNodes(eligibleNodesPubKeys map[string]struct{}, con rcns, rthr, rstatus, + &mock.NodeRedundancyHandlerStub{}, ) cns.Data = []byte("X") diff --git a/testscommon/consensus/mockTestInitializer.go b/testscommon/consensus/mockTestInitializer.go index 85b946c13df..e066f0cea1a 100644 --- a/testscommon/consensus/mockTestInitializer.go +++ b/testscommon/consensus/mockTestInitializer.go @@ -7,6 +7,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/marshal" crypto "github.com/multiversx/mx-chain-crypto-go" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" "github.com/multiversx/mx-chain-go/consensus" "github.com/multiversx/mx-chain-go/consensus/mock" @@ -20,6 +21,7 @@ import ( epochNotifierMock "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" epochstartmock "github.com/multiversx/mx-chain-go/testscommon/epochstartmock" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" + "github.com/multiversx/mx-chain-go/testscommon/round" "github.com/multiversx/mx-chain-go/testscommon/shardingMocks" ) @@ -53,7 +55,7 @@ func InitBlockProcessorMock(marshaller marshal.Marshalizer) *testscommon.BlockPr return header } - blockProcessorMock.MarshalizedDataToBroadcastCalled = func(header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) { + blockProcessorMock.MarshalizedDataToBroadcastCalled = func(hash []byte, header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) { return make(map[uint32][]byte), make(map[string][][]byte), nil } blockProcessorMock.CreateNewHeaderCalled = func(round uint64, nonce uint64) (data.HeaderHandler, error) { @@ -90,7 +92,7 @@ func InitBlockProcessorHeaderV2Mock() *testscommon.BlockProcessorStub { ScheduledRootHash: []byte{}, } } - blockProcessorMock.MarshalizedDataToBroadcastCalled = func(header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) { + blockProcessorMock.MarshalizedDataToBroadcastCalled = func(hash []byte, header data.HeaderHandler, body data.BodyHandler) (map[uint32][]byte, map[string][][]byte, error) { return make(map[uint32][]byte), make(map[string][][]byte), nil } blockProcessorMock.CreateNewHeaderCalled = func(round uint64, nonce uint64) (data.HeaderHandler, error) { @@ -113,15 +115,27 @@ func InitMultiSignerMock() *cryptoMocks.MultisignerMock { multiSigner.VerifySignatureShareCalled = func(publicKey []byte, message []byte, sig []byte) error { return nil } + multiSigner.VerifySignatureShareV2Called = func(publicKey crypto.PublicKey, message []byte, sig []byte) error { + return nil + } multiSigner.VerifyAggregatedSigCalled = func(pubKeysSigners [][]byte, message []byte, aggSig []byte) error { return nil } + multiSigner.VerifyAggregatedSigV2Called = func(pubKeysSigners []crypto.PublicKey, message []byte, aggSig []byte) error { + return nil + } multiSigner.AggregateSigsCalled = func(pubKeysSigners [][]byte, signatures [][]byte) ([]byte, error) { return []byte("aggregatedSig"), nil } + multiSigner.AggregateSigsV2Called = func(pubKeysSigners []crypto.PublicKey, signatures [][]byte) ([]byte, error) { + return []byte("aggregatedSig"), nil + } multiSigner.CreateSignatureShareCalled = func(privateKeyBytes []byte, message []byte) ([]byte, error) { return []byte("partialSign"), nil } + multiSigner.CreateSignatureShareV2Called = func(privateKeyBytes crypto.PrivateKey, message []byte) ([]byte, error) { + return []byte("partialSign"), nil + } return multiSigner } @@ -165,7 +179,7 @@ func InitConsensusCore() *spos.ConsensusCore { } // InitConsensusCoreWithMultiSigner - -func InitConsensusCoreWithMultiSigner(multiSigner crypto.MultiSigner) *spos.ConsensusCore { +func InitConsensusCoreWithMultiSigner(multiSigner crypto.MultiSignerV2) *spos.ConsensusCore { blockChain := &testscommon.ChainHandlerStub{ GetGenesisHeaderCalled: func() data.HeaderHandler { return &block.Header{ @@ -175,6 +189,7 @@ func InitConsensusCoreWithMultiSigner(multiSigner crypto.MultiSigner) *spos.Cons } marshalizerMock := mock.MarshalizerMock{} blockProcessorMock := InitBlockProcessorMock(marshalizerMock) + executionManager := &processMocks.ExecutionManagerMock{} bootstrapperMock := &bootstrapperStubs.BootstrapperStub{} broadcastMessengerMock := &BroadcastMessengerMock{ BroadcastConsensusMessageCalled: func(message *consensus.Message) error { @@ -184,7 +199,7 @@ func InitConsensusCoreWithMultiSigner(multiSigner crypto.MultiSigner) *spos.Cons chronologyHandlerMock := InitChronologyHandlerMock() hasherMock := &hashingMocks.HasherMock{} - roundHandlerMock := &RoundHandlerMock{} + roundHandlerMock := &round.RoundHandlerMock{} shardCoordinatorMock := mock.ShardCoordinatorMock{} syncTimerMock := &SyncTimerMock{} nodesCoordinator := &shardingMocks.NodesCoordinatorMock{ @@ -216,12 +231,14 @@ func InitConsensusCoreWithMultiSigner(multiSigner crypto.MultiSigner) *spos.Cons multiSignerContainer := cryptoMocks.NewMultiSignerContainerMock(multiSigner) signingHandler := &SigningHandlerStub{} enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{} + enableRoundsHandler := &testscommon.EnableRoundsHandlerStub{} equivalentProofsPool := &dataRetriever.ProofsPoolMock{} epochNotifier := &epochNotifierMock.EpochNotifierStub{} container, _ := spos.NewConsensusCore(&spos.ConsensusCoreArgs{ BlockChain: blockChain, BlockProcessor: blockProcessorMock, + ExecutionManager: executionManager, Bootstrapper: bootstrapperMock, BroadcastMessenger: broadcastMessengerMock, ChronologyHandler: chronologyHandlerMock, @@ -243,6 +260,7 @@ func InitConsensusCoreWithMultiSigner(multiSigner crypto.MultiSigner) *spos.Cons PeerBlacklistHandler: peerBlacklistHandler, SigningHandler: signingHandler, EnableEpochsHandler: enableEpochsHandler, + EnableRoundsHandler: enableRoundsHandler, EquivalentProofsPool: equivalentProofsPool, EpochNotifier: epochNotifier, InvalidSignersCache: &InvalidSignersCacheMock{}, diff --git a/testscommon/consensus/roundSyncControllerMock.go b/testscommon/consensus/roundSyncControllerMock.go new file mode 100644 index 00000000000..14742172892 --- /dev/null +++ b/testscommon/consensus/roundSyncControllerMock.go @@ -0,0 +1,26 @@ +package consensus + +// NtpSyncControllerMock - +type NtpSyncControllerMock struct { + AddOutOfRangeNonceCalled func(round uint64, hash string) + AddLeaderNonceAsOutOfRangeCalled func(round uint64, hash string) +} + +// AddOutOfRangeNonce - +func (mock *NtpSyncControllerMock) AddOutOfRangeNonce(nonce uint64, hash string) { + if mock.AddOutOfRangeNonceCalled != nil { + mock.AddOutOfRangeNonceCalled(nonce, hash) + } +} + +// AddLeaderNonceAsOutOfRange - +func (mock *NtpSyncControllerMock) AddLeaderNonceAsOutOfRange(nonce uint64, hash string) { + if mock.AddLeaderNonceAsOutOfRangeCalled != nil { + mock.AddLeaderNonceAsOutOfRangeCalled(nonce, hash) + } +} + +// IsInterfaceNil - +func (mock *NtpSyncControllerMock) IsInterfaceNil() bool { + return mock == nil +} diff --git a/testscommon/consensus/signingHandlerStub.go b/testscommon/consensus/signingHandlerStub.go index e389ce864b3..79127a17797 100644 --- a/testscommon/consensus/signingHandlerStub.go +++ b/testscommon/consensus/signingHandlerStub.go @@ -1,5 +1,7 @@ package consensus +import crypto "github.com/multiversx/mx-chain-crypto-go" + // SigningHandlerStub implements SigningHandler interface type SigningHandlerStub struct { ResetCalled func(pubKeys []string) error @@ -12,6 +14,7 @@ type SigningHandlerStub struct { AggregateSigsCalled func(bitmap []byte, epoch uint32) ([]byte, error) SetAggregatedSigCalled func(_ []byte) error VerifyCalled func(msg []byte, bitmap []byte, epoch uint32) error + GetPubKeysFromBytesCalled func(pubKeysBytes [][]byte) ([]crypto.PublicKey, error) } // Reset - @@ -104,6 +107,17 @@ func (stub *SigningHandlerStub) Verify(msg []byte, bitmap []byte, epoch uint32) return nil } +// GetPubKeysFromBytes - +func (stub *SigningHandlerStub) GetPubKeysFromBytes( + pubKeysBytes [][]byte, +) ([]crypto.PublicKey, error) { + if stub.GetPubKeysFromBytesCalled != nil { + return stub.GetPubKeysFromBytesCalled(pubKeysBytes) + } + + return nil, nil +} + // IsInterfaceNil - func (stub *SigningHandlerStub) IsInterfaceNil() bool { return stub == nil diff --git a/testscommon/consensus/sposWorkerMock.go b/testscommon/consensus/sposWorkerMock.go index 657f01ca7ca..2096c8b116a 100644 --- a/testscommon/consensus/sposWorkerMock.go +++ b/testscommon/consensus/sposWorkerMock.go @@ -7,6 +7,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-go/consensus" + "github.com/multiversx/mx-chain-go/consensus/spos" "github.com/multiversx/mx-chain-go/p2p" ) @@ -35,6 +36,7 @@ type SposWorkerMock struct { ReceivedProofCalled func(proofHandler consensus.ProofHandler) ResetConsensusRoundStateCalled func() ResetInvalidSignersCacheCalled func() + ConsensusMetricsCalled func() spos.ConsensusMetricsHandler } // ResetConsensusRoundState - @@ -181,3 +183,12 @@ func (sposWorkerMock *SposWorkerMock) ResetInvalidSignersCache() { sposWorkerMock.ResetInvalidSignersCacheCalled() } } + +// ConsensusMetrics - +func (sposWorkerMock *SposWorkerMock) ConsensusMetrics() spos.ConsensusMetricsHandler { + if sposWorkerMock.ConsensusMetricsCalled != nil { + return sposWorkerMock.ConsensusMetricsCalled() + } + consensusMetrics, _ := spos.NewConsensusMetrics(nil) + return consensusMetrics +} diff --git a/testscommon/consensus/syncTimerMock.go b/testscommon/consensus/syncTimerMock.go index 32b92bbe33b..f60c38ef044 100644 --- a/testscommon/consensus/syncTimerMock.go +++ b/testscommon/consensus/syncTimerMock.go @@ -10,6 +10,10 @@ type SyncTimerMock struct { CurrentTimeCalled func() time.Time } +// ForceSync - +func (stm *SyncTimerMock) ForceSync() { +} + // StartSyncingTime method does the time synchronization at every syncPeriod time elapsed. This should be started as a go routine func (stm *SyncTimerMock) StartSyncingTime() { panic("implement me") diff --git a/testscommon/cryptoMocks/multiSignerContainerMock.go b/testscommon/cryptoMocks/multiSignerContainerMock.go index 3eccdee6f46..85560dae1ee 100644 --- a/testscommon/cryptoMocks/multiSignerContainerMock.go +++ b/testscommon/cryptoMocks/multiSignerContainerMock.go @@ -4,16 +4,16 @@ import crypto "github.com/multiversx/mx-chain-crypto-go" // MultiSignerContainerMock - type MultiSignerContainerMock struct { - MultiSigner crypto.MultiSigner + MultiSigner crypto.MultiSignerV2 } // NewMultiSignerContainerMock - -func NewMultiSignerContainerMock(multiSigner crypto.MultiSigner) *MultiSignerContainerMock { +func NewMultiSignerContainerMock(multiSigner crypto.MultiSignerV2) *MultiSignerContainerMock { return &MultiSignerContainerMock{MultiSigner: multiSigner} } // GetMultiSigner - -func (mscm *MultiSignerContainerMock) GetMultiSigner(_ uint32) (crypto.MultiSigner, error) { +func (mscm *MultiSignerContainerMock) GetMultiSigner(_ uint32) (crypto.MultiSignerV2, error) { return mscm.MultiSigner, nil } diff --git a/testscommon/cryptoMocks/multiSignerContainerStub.go b/testscommon/cryptoMocks/multiSignerContainerStub.go index 1332916be80..e0a10a0bc1f 100644 --- a/testscommon/cryptoMocks/multiSignerContainerStub.go +++ b/testscommon/cryptoMocks/multiSignerContainerStub.go @@ -4,11 +4,11 @@ import crypto "github.com/multiversx/mx-chain-crypto-go" // MultiSignerContainerStub - type MultiSignerContainerStub struct { - GetMultiSignerCalled func(epoch uint32) (crypto.MultiSigner, error) + GetMultiSignerCalled func(epoch uint32) (crypto.MultiSignerV2, error) } // GetMultiSigner - -func (stub *MultiSignerContainerStub) GetMultiSigner(epoch uint32) (crypto.MultiSigner, error) { +func (stub *MultiSignerContainerStub) GetMultiSigner(epoch uint32) (crypto.MultiSignerV2, error) { if stub.GetMultiSignerCalled != nil { return stub.GetMultiSignerCalled(epoch) } diff --git a/testscommon/cryptoMocks/multiSignerStub.go b/testscommon/cryptoMocks/multiSignerStub.go index 61f9262a108..119851ea4fa 100644 --- a/testscommon/cryptoMocks/multiSignerStub.go +++ b/testscommon/cryptoMocks/multiSignerStub.go @@ -1,11 +1,17 @@ package cryptoMocks +import crypto "github.com/multiversx/mx-chain-crypto-go" + // MultiSignerStub implements crypto multisigner type MultiSignerStub struct { - VerifyAggregatedSigCalled func(pubKeysSigners [][]byte, message []byte, aggSig []byte) error - CreateSignatureShareCalled func(privateKeyBytes []byte, message []byte) ([]byte, error) - VerifySignatureShareCalled func(publicKey []byte, message []byte, sig []byte) error - AggregateSigsCalled func(pubKeysSigners [][]byte, signatures [][]byte) ([]byte, error) + CreateSignatureShareCalled func(privateKeyBytes []byte, message []byte) ([]byte, error) + CreateSignatureShareV2Called func(privateKeyBytes crypto.PrivateKey, message []byte) ([]byte, error) + VerifySignatureShareCalled func(publicKey []byte, message []byte, sig []byte) error + VerifySignatureShareV2Called func(publicKey crypto.PublicKey, message []byte, sig []byte) error + AggregateSigsCalled func(pubKeysSigners [][]byte, signatures [][]byte) ([]byte, error) + AggregateSigsV2Called func(pubKeysSigners []crypto.PublicKey, signatures [][]byte) ([]byte, error) + VerifyAggregatedSigCalled func(pubKeysSigners [][]byte, message []byte, aggSig []byte) error + VerifyAggregatedSigV2Called func(pubKeysSigners []crypto.PublicKey, message []byte, aggSig []byte) error } // VerifyAggregatedSig - @@ -17,6 +23,15 @@ func (stub *MultiSignerStub) VerifyAggregatedSig(pubKeysSigners [][]byte, messag return nil } +// VerifyAggregatedSigV2 - +func (stub *MultiSignerStub) VerifyAggregatedSigV2(pubKeysSigners []crypto.PublicKey, message []byte, aggSig []byte) error { + if stub.VerifyAggregatedSigV2Called != nil { + return stub.VerifyAggregatedSigV2Called(pubKeysSigners, message, aggSig) + } + + return nil +} + // CreateSignatureShare - func (stub *MultiSignerStub) CreateSignatureShare(privateKeyBytes []byte, message []byte) ([]byte, error) { if stub.CreateSignatureShareCalled != nil { @@ -26,6 +41,15 @@ func (stub *MultiSignerStub) CreateSignatureShare(privateKeyBytes []byte, messag return nil, nil } +// CreateSignatureShareV2 - +func (stub *MultiSignerStub) CreateSignatureShareV2(privateKey crypto.PrivateKey, message []byte) ([]byte, error) { + if stub.CreateSignatureShareV2Called != nil { + return stub.CreateSignatureShareV2Called(privateKey, message) + } + + return nil, nil +} + // VerifySignatureShare - func (stub *MultiSignerStub) VerifySignatureShare(publicKey []byte, message []byte, sig []byte) error { if stub.VerifySignatureShareCalled != nil { @@ -35,6 +59,15 @@ func (stub *MultiSignerStub) VerifySignatureShare(publicKey []byte, message []by return nil } +// VerifySignatureShareV2 - +func (stub *MultiSignerStub) VerifySignatureShareV2(publicKey crypto.PublicKey, message []byte, sig []byte) error { + if stub.VerifySignatureShareV2Called != nil { + return stub.VerifySignatureShareV2Called(publicKey, message, sig) + } + + return nil +} + // AggregateSigs - func (stub *MultiSignerStub) AggregateSigs(pubKeysSigners [][]byte, signatures [][]byte) ([]byte, error) { if stub.AggregateSigsCalled != nil { @@ -44,6 +77,15 @@ func (stub *MultiSignerStub) AggregateSigs(pubKeysSigners [][]byte, signatures [ return nil, nil } +// AggregateSigsV2 - +func (stub *MultiSignerStub) AggregateSigsV2(pubKeys []crypto.PublicKey, signatures [][]byte) ([]byte, error) { + if stub.AggregateSigsV2Called != nil { + return stub.AggregateSigsV2Called(pubKeys, signatures) + } + + return nil, nil +} + // IsInterfaceNil - func (stub *MultiSignerStub) IsInterfaceNil() bool { return stub == nil diff --git a/testscommon/cryptoMocks/multisignerMock.go b/testscommon/cryptoMocks/multisignerMock.go index bce88e5b330..89986efccaa 100644 --- a/testscommon/cryptoMocks/multisignerMock.go +++ b/testscommon/cryptoMocks/multisignerMock.go @@ -2,16 +2,22 @@ package cryptoMocks import ( "bytes" + + crypto "github.com/multiversx/mx-chain-crypto-go" ) const signatureSize = 48 // MultisignerMock is used to mock the multisignature scheme type MultisignerMock struct { - CreateSignatureShareCalled func(privateKeyBytes []byte, message []byte) ([]byte, error) - VerifySignatureShareCalled func(publicKey []byte, message []byte, sig []byte) error - AggregateSigsCalled func(pubKeysSigners [][]byte, signatures [][]byte) ([]byte, error) - VerifyAggregatedSigCalled func(pubKeysSigners [][]byte, message []byte, aggSig []byte) error + CreateSignatureShareCalled func(privateKeyBytes []byte, message []byte) ([]byte, error) + CreateSignatureShareV2Called func(privateKeyBytes crypto.PrivateKey, message []byte) ([]byte, error) + VerifySignatureShareCalled func(publicKey []byte, message []byte, sig []byte) error + VerifySignatureShareV2Called func(publicKey crypto.PublicKey, message []byte, sig []byte) error + AggregateSigsCalled func(pubKeysSigners [][]byte, signatures [][]byte) ([]byte, error) + AggregateSigsV2Called func(pubKeysSigners []crypto.PublicKey, signatures [][]byte) ([]byte, error) + VerifyAggregatedSigCalled func(pubKeysSigners [][]byte, message []byte, aggSig []byte) error + VerifyAggregatedSigV2Called func(pubKeysSigners []crypto.PublicKey, message []byte, aggSig []byte) error } // NewMultiSigner - @@ -28,6 +34,15 @@ func (mm *MultisignerMock) CreateSignatureShare(privateKeyBytes []byte, message return bytes.Repeat([]byte{0xAA}, signatureSize), nil } +// CreateSignatureShareV2 - +func (mm *MultisignerMock) CreateSignatureShareV2(privateKeyBytes crypto.PrivateKey, message []byte) ([]byte, error) { + if mm.CreateSignatureShareV2Called != nil { + return mm.CreateSignatureShareV2Called(privateKeyBytes, message) + } + + return bytes.Repeat([]byte{0xAA}, signatureSize), nil +} + // VerifySignatureShare - func (mm *MultisignerMock) VerifySignatureShare(publicKey []byte, message []byte, sig []byte) error { if mm.VerifySignatureShareCalled != nil { @@ -36,6 +51,14 @@ func (mm *MultisignerMock) VerifySignatureShare(publicKey []byte, message []byte return nil } +// VerifySignatureShareV2 - +func (mm *MultisignerMock) VerifySignatureShareV2(publicKey crypto.PublicKey, message []byte, sig []byte) error { + if mm.VerifySignatureShareV2Called != nil { + return mm.VerifySignatureShareV2Called(publicKey, message, sig) + } + return nil +} + // AggregateSigs - func (mm *MultisignerMock) AggregateSigs(pubKeysSigners [][]byte, signatures [][]byte) ([]byte, error) { if mm.AggregateSigsCalled != nil { @@ -45,6 +68,15 @@ func (mm *MultisignerMock) AggregateSigs(pubKeysSigners [][]byte, signatures [][ return bytes.Repeat([]byte{0xAA}, signatureSize), nil } +// AggregateSigsV2 - +func (mm *MultisignerMock) AggregateSigsV2(pubKeysSigners []crypto.PublicKey, signatures [][]byte) ([]byte, error) { + if mm.AggregateSigsV2Called != nil { + return mm.AggregateSigsV2Called(pubKeysSigners, signatures) + } + + return bytes.Repeat([]byte{0xAA}, signatureSize), nil +} + // VerifyAggregatedSig - func (mm *MultisignerMock) VerifyAggregatedSig(pubKeysSigners [][]byte, message []byte, aggSig []byte) error { if mm.VerifyAggregatedSigCalled != nil { @@ -53,6 +85,14 @@ func (mm *MultisignerMock) VerifyAggregatedSig(pubKeysSigners [][]byte, message return nil } +// VerifyAggregatedSigV2 - +func (mm *MultisignerMock) VerifyAggregatedSigV2(pubKeysSigners []crypto.PublicKey, message []byte, aggSig []byte) error { + if mm.VerifyAggregatedSigV2Called != nil { + return mm.VerifyAggregatedSigV2Called(pubKeysSigners, message, aggSig) + } + return nil +} + // IsInterfaceNil - func (mm *MultisignerMock) IsInterfaceNil() bool { return mm == nil diff --git a/testscommon/dataFieldParserStub.go b/testscommon/dataFieldParserStub.go index fcbe84497c7..2d6d4552a6c 100644 --- a/testscommon/dataFieldParserStub.go +++ b/testscommon/dataFieldParserStub.go @@ -4,13 +4,13 @@ import datafield "github.com/multiversx/mx-chain-vm-common-go/parsers/dataField" // DataFieldParserStub - type DataFieldParserStub struct { - ParseCalled func(dataField []byte, sender, receiver []byte, numOfShards uint32) *datafield.ResponseParseData + ParseCalled func(dataField []byte, sender, receiver []byte, numOfShards uint32, epoch uint32) *datafield.ResponseParseData } // Parse - -func (df *DataFieldParserStub) Parse(dataField []byte, sender, receiver []byte, numOfShards uint32) *datafield.ResponseParseData { +func (df *DataFieldParserStub) Parse(dataField []byte, sender, receiver []byte, numOfShards uint32, epoch uint32) *datafield.ResponseParseData { if df.ParseCalled != nil { - return df.ParseCalled(dataField, sender, receiver, numOfShards) + return df.ParseCalled(dataField, sender, receiver, numOfShards, epoch) } return nil diff --git a/testscommon/dataRetriever/equivalentProofRequesterStub.go b/testscommon/dataRetriever/equivalentProofRequesterStub.go new file mode 100644 index 00000000000..296f1090770 --- /dev/null +++ b/testscommon/dataRetriever/equivalentProofRequesterStub.go @@ -0,0 +1,15 @@ +package dataRetriever + +// EquivalentProofRequesterStub - +type EquivalentProofRequesterStub struct { + *RequesterStub + RequestDataFromNonceCalled func(nonceShardKey []byte, epoch uint32) error +} + +// RequestDataFromNonce - +func (stub *EquivalentProofRequesterStub) RequestDataFromNonce(nonceShardKey []byte, epoch uint32) error { + if stub.RequestDataFromNonceCalled != nil { + return stub.RequestDataFromNonceCalled(nonceShardKey, epoch) + } + return nil +} diff --git a/testscommon/dataRetriever/poolFactory.go b/testscommon/dataRetriever/poolFactory.go index a708795510a..7e8b8e0699c 100644 --- a/testscommon/dataRetriever/poolFactory.go +++ b/testscommon/dataRetriever/poolFactory.go @@ -20,7 +20,7 @@ import ( "github.com/multiversx/mx-chain-go/trie/factory" ) -var peerAuthDuration = 10 * time.Second +var timeCacheDuration = 10 * time.Second func panicIfError(message string, err error) { if err != nil { @@ -43,6 +43,10 @@ func CreateTxPool(numShards uint32, selfShard uint32) (dataRetriever.ShardedData SelfShardID: selfShard, TxGasHandler: txcachemocks.NewTxGasHandlerMock(), Marshalizer: &marshal.GogoProtoMarshalizer{}, + TxCacheBoundsConfig: config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: 33_554_432, + MaxTrackedBlocks: 100, + }, }, ) } @@ -122,6 +126,20 @@ func createPoolHolderArgs(numShards uint32, selfShard uint32) dataPool.DataPoolA proofsPool := proofscache.NewProofsPool(3, 100) + cacherConfig = storageunit.CacheConfig{Capacity: 1000, Type: storageunit.LRUCache} + executedMiniBlocks, err := storageunit.NewCache(cacherConfig) + panicIfError("CreatePoolsHolder", err) + + cacherConfig = storageunit.CacheConfig{Capacity: 1000, Type: storageunit.LRUCache} + postProcessTransactions, err := storageunit.NewCache(cacherConfig) + panicIfError("CreatePoolsHolder", err) + + directSentTransactions, err := cache.NewTimeCacher(cache.ArgTimeCacher{ + DefaultSpan: timeCacheDuration, + CacheExpiry: timeCacheDuration, + }) + panicIfError("CreatePoolsHolder", err) + currentBlockTransactions := dataPool.NewCurrentBlockTransactionsPool() currentEpochValidatorInfo := dataPool.NewCurrentEpochValidatorInfoPool() dataPoolArgs := dataPool.DataPoolArgs{ @@ -140,6 +158,9 @@ func createPoolHolderArgs(numShards uint32, selfShard uint32) dataPool.DataPoolA Heartbeats: heartbeatPool, ValidatorsInfo: validatorsInfo, Proofs: proofsPool, + ExecutedMiniBlocks: executedMiniBlocks, + PostProcessTransactions: postProcessTransactions, + DirectSentTransactions: directSentTransactions, } return dataPoolArgs @@ -221,8 +242,8 @@ func CreatePoolsHolderWithTxPool(txPool dataRetriever.ShardedDataCacherNotifier) panicIfError("CreatePoolsHolderWithTxPool", err) peerAuthPool, err := cache.NewTimeCacher(cache.ArgTimeCacher{ - DefaultSpan: peerAuthDuration, - CacheExpiry: peerAuthDuration, + DefaultSpan: timeCacheDuration, + CacheExpiry: timeCacheDuration, }) panicIfError("CreatePoolsHolderWithTxPool", err) @@ -232,6 +253,20 @@ func CreatePoolsHolderWithTxPool(txPool dataRetriever.ShardedDataCacherNotifier) proofsPool := proofscache.NewProofsPool(3, 100) + cacherConfig = storageunit.CacheConfig{Capacity: 1000, Type: storageunit.LRUCache} + executedMiniBlocks, err := storageunit.NewCache(cacherConfig) + panicIfError("CreatePoolsHolderWithTxPool", err) + + cacherConfig = storageunit.CacheConfig{Capacity: 1000, Type: storageunit.LRUCache} + postProcessTransactions, err := storageunit.NewCache(cacherConfig) + panicIfError("CreatePoolsHolderWithTxPool", err) + + directSentTransactions, err := cache.NewTimeCacher(cache.ArgTimeCacher{ + DefaultSpan: timeCacheDuration, + CacheExpiry: timeCacheDuration, + }) + panicIfError("CreatePoolsHolderWithTxPool", err) + currentBlockTransactions := dataPool.NewCurrentBlockTransactionsPool() currentEpochValidatorInfo := dataPool.NewCurrentEpochValidatorInfoPool() dataPoolArgs := dataPool.DataPoolArgs{ @@ -250,6 +285,9 @@ func CreatePoolsHolderWithTxPool(txPool dataRetriever.ShardedDataCacherNotifier) Heartbeats: heartbeatPool, ValidatorsInfo: validatorsInfo, Proofs: proofsPool, + ExecutedMiniBlocks: executedMiniBlocks, + PostProcessTransactions: postProcessTransactions, + DirectSentTransactions: directSentTransactions, } holder, err := dataPool.NewDataPool(dataPoolArgs) panicIfError("CreatePoolsHolderWithTxPool", err) diff --git a/testscommon/dataRetriever/poolsHolderMock.go b/testscommon/dataRetriever/poolsHolderMock.go index 6dc5266c062..903cc92fb3e 100644 --- a/testscommon/dataRetriever/poolsHolderMock.go +++ b/testscommon/dataRetriever/poolsHolderMock.go @@ -21,21 +21,24 @@ import ( // PoolsHolderMock - type PoolsHolderMock struct { - transactions dataRetriever.ShardedDataCacherNotifier - unsignedTransactions dataRetriever.ShardedDataCacherNotifier - rewardTransactions dataRetriever.ShardedDataCacherNotifier - headers dataRetriever.HeadersPool - miniBlocks storage.Cacher - peerChangesBlocks storage.Cacher - trieNodes storage.Cacher - trieNodesChunks storage.Cacher - smartContracts storage.Cacher - currBlockTxs dataRetriever.TransactionCacher - currEpochValidatorInfo dataRetriever.ValidatorInfoCacher - peerAuthentications storage.Cacher - heartbeats storage.Cacher - validatorsInfo dataRetriever.ShardedDataCacherNotifier - proofs dataRetriever.ProofsPool + transactions dataRetriever.ShardedDataCacherNotifier + unsignedTransactions dataRetriever.ShardedDataCacherNotifier + rewardTransactions dataRetriever.ShardedDataCacherNotifier + headers dataRetriever.HeadersPool + miniBlocks storage.Cacher + peerChangesBlocks storage.Cacher + trieNodes storage.Cacher + trieNodesChunks storage.Cacher + smartContracts storage.Cacher + currBlockTxs dataRetriever.TransactionCacher + currEpochValidatorInfo dataRetriever.ValidatorInfoCacher + peerAuthentications storage.Cacher + heartbeats storage.Cacher + validatorsInfo dataRetriever.ShardedDataCacherNotifier + proofs dataRetriever.ProofsPool + executedMiniBlocks storage.Cacher + postProcessTransactions storage.Cacher + directSentTransactions storage.Cacher } // NewPoolsHolderMock - @@ -55,6 +58,10 @@ func NewPoolsHolderMock() *PoolsHolderMock { TxGasHandler: txcachemocks.NewTxGasHandlerMock(), Marshalizer: &marshal.GogoProtoMarshalizer{}, NumberOfShards: 1, + TxCacheBoundsConfig: config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: 33_554_432, + MaxTrackedBlocks: 100, + }, }, ) panicIfError("NewPoolsHolderMock", err) @@ -112,6 +119,18 @@ func NewPoolsHolderMock() *PoolsHolderMock { holder.proofs = proofscache.NewProofsPool(3, 100) + holder.executedMiniBlocks, err = storageunit.NewCache(storageunit.CacheConfig{Type: storageunit.LRUCache, Capacity: 10000, Shards: 1, SizeInBytes: 0}) + panicIfError("NewPoolsHolderMock", err) + + holder.postProcessTransactions, err = storageunit.NewCache(storageunit.CacheConfig{Type: storageunit.LRUCache, Capacity: 10000, Shards: 1, SizeInBytes: 0}) + panicIfError("NewPoolsHolderMock", err) + + holder.directSentTransactions, err = cache.NewTimeCacher(cache.ArgTimeCacher{ + DefaultSpan: 10 * time.Second, + CacheExpiry: 10 * time.Second, + }) + panicIfError("NewPoolsHolderMock", err) + return holder } @@ -205,6 +224,26 @@ func (holder *PoolsHolderMock) Proofs() dataRetriever.ProofsPool { return holder.proofs } +// ExecutedMiniBlocks - +func (holder *PoolsHolderMock) ExecutedMiniBlocks() storage.Cacher { + return holder.executedMiniBlocks +} + +// PostProcessTransactions - +func (holder *PoolsHolderMock) PostProcessTransactions() storage.Cacher { + return holder.postProcessTransactions +} + +// DirectSentTransactions - +func (holder *PoolsHolderMock) DirectSentTransactions() storage.Cacher { + return holder.directSentTransactions +} + +// SetProofsPool - +func (holder *PoolsHolderMock) SetProofsPool(proofsPool dataRetriever.ProofsPool) { + holder.proofs = proofsPool +} + // Close - func (holder *PoolsHolderMock) Close() error { var lastError error diff --git a/testscommon/dataRetriever/poolsHolderStub.go b/testscommon/dataRetriever/poolsHolderStub.go index 7d9051d6f10..52c476f8ab7 100644 --- a/testscommon/dataRetriever/poolsHolderStub.go +++ b/testscommon/dataRetriever/poolsHolderStub.go @@ -9,23 +9,26 @@ import ( // PoolsHolderStub - type PoolsHolderStub struct { - HeadersCalled func() dataRetriever.HeadersPool - TransactionsCalled func() dataRetriever.ShardedDataCacherNotifier - UnsignedTransactionsCalled func() dataRetriever.ShardedDataCacherNotifier - RewardTransactionsCalled func() dataRetriever.ShardedDataCacherNotifier - MiniBlocksCalled func() storage.Cacher - MetaBlocksCalled func() storage.Cacher - CurrBlockTxsCalled func() dataRetriever.TransactionCacher - CurrEpochValidatorInfoCalled func() dataRetriever.ValidatorInfoCacher - TrieNodesCalled func() storage.Cacher - TrieNodesChunksCalled func() storage.Cacher - PeerChangesBlocksCalled func() storage.Cacher - SmartContractsCalled func() storage.Cacher - PeerAuthenticationsCalled func() storage.Cacher - HeartbeatsCalled func() storage.Cacher - ValidatorsInfoCalled func() dataRetriever.ShardedDataCacherNotifier - ProofsCalled func() dataRetriever.ProofsPool - CloseCalled func() error + HeadersCalled func() dataRetriever.HeadersPool + TransactionsCalled func() dataRetriever.ShardedDataCacherNotifier + UnsignedTransactionsCalled func() dataRetriever.ShardedDataCacherNotifier + RewardTransactionsCalled func() dataRetriever.ShardedDataCacherNotifier + MiniBlocksCalled func() storage.Cacher + MetaBlocksCalled func() storage.Cacher + CurrBlockTxsCalled func() dataRetriever.TransactionCacher + CurrEpochValidatorInfoCalled func() dataRetriever.ValidatorInfoCacher + TrieNodesCalled func() storage.Cacher + TrieNodesChunksCalled func() storage.Cacher + PeerChangesBlocksCalled func() storage.Cacher + SmartContractsCalled func() storage.Cacher + PeerAuthenticationsCalled func() storage.Cacher + HeartbeatsCalled func() storage.Cacher + ValidatorsInfoCalled func() dataRetriever.ShardedDataCacherNotifier + ProofsCalled func() dataRetriever.ProofsPool + ExecutedMiniBlocksCalled func() storage.Cacher + PostProcessTransactionsCalled func() storage.Cacher + DirectSentTransactionsCalled func() storage.Cacher + CloseCalled func() error } // NewPoolsHolderStub - @@ -177,6 +180,33 @@ func (holder *PoolsHolderStub) Proofs() dataRetriever.ProofsPool { return nil } +// ExecutedMiniBlocks - +func (holder *PoolsHolderStub) ExecutedMiniBlocks() storage.Cacher { + if holder.ExecutedMiniBlocksCalled != nil { + return holder.ExecutedMiniBlocksCalled() + } + + return nil +} + +// DirectSentTransactions - +func (holder *PoolsHolderStub) DirectSentTransactions() storage.Cacher { + if holder.DirectSentTransactionsCalled != nil { + return holder.DirectSentTransactionsCalled() + } + + return nil +} + +// PostProcessTransactions - +func (holder *PoolsHolderStub) PostProcessTransactions() storage.Cacher { + if holder.PostProcessTransactionsCalled != nil { + return holder.PostProcessTransactionsCalled() + } + + return nil +} + // Close - func (holder *PoolsHolderStub) Close() error { if holder.CloseCalled != nil { diff --git a/testscommon/dblookupext/historyRepositoryStub.go b/testscommon/dblookupext/historyRepositoryStub.go index 044f50ea018..75ced68e852 100644 --- a/testscommon/dblookupext/historyRepositoryStub.go +++ b/testscommon/dblookupext/historyRepositoryStub.go @@ -11,7 +11,7 @@ import ( // HistoryRepositoryStub - type HistoryRepositoryStub struct { - RecordBlockCalled func(blockHeaderHash []byte, blockHeader data.HeaderHandler, blockBody data.BodyHandler, scrsPool map[string]data.TransactionHandler, receipts map[string]data.TransactionHandler, createdIntraMiniBlocks []*block.MiniBlock, logs []*data.LogData) error + RecordBlockCalled func(blockHeaderHash []byte, blockHeader data.HeaderHandler, blockBody data.BodyHandler, scrsPool map[string]data.TransactionHandler, receipts map[string]data.TransactionHandler, createdIntraMiniBlocks []*block.MiniBlock, logs []data.LogDataHandler) error OnNotarizedBlocksCalled func(shardID uint32, headers []data.HeaderHandler, headersHashes [][]byte) GetMiniblockMetadataByTxHashCalled func(hash []byte) (*dblookupext.MiniblockMetadata, error) GetEpochByHashCalled func(hash []byte) (uint32, error) @@ -28,7 +28,7 @@ func (hp *HistoryRepositoryStub) RecordBlock( scrsPool map[string]data.TransactionHandler, receipts map[string]data.TransactionHandler, createdIntraMiniBlocks []*block.MiniBlock, - logs []*data.LogData, + logs []data.LogDataHandler, ) error { if hp.RecordBlockCalled != nil { return hp.RecordBlockCalled(blockHeaderHash, blockHeader, blockBody, scrsPool, receipts, createdIntraMiniBlocks, logs) diff --git a/testscommon/economicsmocks/economicsHandlerMock.go b/testscommon/economicsmocks/economicsHandlerMock.go index ca5c5edfaf9..84ef11e9d08 100644 --- a/testscommon/economicsmocks/economicsHandlerMock.go +++ b/testscommon/economicsmocks/economicsHandlerMock.go @@ -17,10 +17,13 @@ type EconomicsHandlerMock struct { SetMinGasPriceCalled func(minGasPrice uint64) SetMinGasLimitCalled func(minGasLimit uint64) MaxGasLimitPerBlockCalled func(shardID uint32) uint64 + MaxGasLimitPerBlockInEpochCalled func(shardID uint32, epoch uint32) uint64 MaxGasLimitPerMiniBlockCalled func(shardID uint32) uint64 MaxGasLimitPerBlockForSafeCrossShardCalled func() uint64 + MaxGasLimitPerBlockForSafeCrossShardInEpochCalled func(epoch uint32) uint64 MaxGasLimitPerMiniBlockForSafeCrossShardCalled func() uint64 MaxGasLimitPerTxCalled func() uint64 + MaxGasLimitPerTxInEpochCalled func(epoch uint32) uint64 ComputeGasLimitCalled func(tx data.TransactionWithFeeHandler) uint64 ComputeFeeCalled func(tx data.TransactionWithFeeHandler) *big.Int CheckValidityTxValuesCalled func(tx data.TransactionWithFeeHandler) error @@ -50,6 +53,7 @@ type EconomicsHandlerMock struct { ComputeTxFeeBasedOnGasUsedInEpochCalled func(tx data.TransactionWithFeeHandler, gasUsed uint64, epoch uint32) *big.Int GenesisTotalSupplyCalled func() *big.Int MaxGasPriceSetGuardianCalled func() uint64 + BlockCapacityOverestimationFactorCalled func() uint64 LeaderPercentageInEpochCalled func(epoch uint32) float64 DeveloperPercentageInEpochCalled func(epoch uint32) float64 ProtocolSustainabilityPercentageInEpochCalled func(epoch uint32) float64 @@ -122,6 +126,14 @@ func (ehm *EconomicsHandlerMock) MaxGasPriceSetGuardian() uint64 { return 0 } +// BlockCapacityOverestimationFactor - +func (ehm *EconomicsHandlerMock) BlockCapacityOverestimationFactor() uint64 { + if ehm.BlockCapacityOverestimationFactorCalled != nil { + return ehm.BlockCapacityOverestimationFactorCalled() + } + return 0 +} + // GasPerDataByte - func (ehm *EconomicsHandlerMock) GasPerDataByte() uint64 { return 0 @@ -166,6 +178,14 @@ func (ehm *EconomicsHandlerMock) MaxGasLimitPerBlock(shardID uint32) uint64 { return 0 } +// MaxGasLimitPerBlockInEpoch - +func (ehm *EconomicsHandlerMock) MaxGasLimitPerBlockInEpoch(shardID uint32, epoch uint32) uint64 { + if ehm.MaxGasLimitPerBlockInEpochCalled != nil { + return ehm.MaxGasLimitPerBlockInEpochCalled(shardID, epoch) + } + return 0 +} + // MaxGasLimitPerMiniBlock - func (ehm *EconomicsHandlerMock) MaxGasLimitPerMiniBlock(shardID uint32) uint64 { if ehm.MaxGasLimitPerMiniBlockCalled != nil { @@ -182,6 +202,14 @@ func (ehm *EconomicsHandlerMock) MaxGasLimitPerBlockForSafeCrossShard() uint64 { return 0 } +// MaxGasLimitPerBlockForSafeCrossShardInEpoch - +func (ehm *EconomicsHandlerMock) MaxGasLimitPerBlockForSafeCrossShardInEpoch(epoch uint32) uint64 { + if ehm.MaxGasLimitPerBlockForSafeCrossShardInEpochCalled != nil { + return ehm.MaxGasLimitPerBlockForSafeCrossShardInEpochCalled(epoch) + } + return 0 +} + // MaxGasLimitPerMiniBlockForSafeCrossShard - func (ehm *EconomicsHandlerMock) MaxGasLimitPerMiniBlockForSafeCrossShard() uint64 { if ehm.MaxGasLimitPerMiniBlockForSafeCrossShardCalled != nil { @@ -198,6 +226,14 @@ func (ehm *EconomicsHandlerMock) MaxGasLimitPerTx() uint64 { return 0 } +// MaxGasLimitPerTxInEpoch - +func (ehm *EconomicsHandlerMock) MaxGasLimitPerTxInEpoch(epoch uint32) uint64 { + if ehm.MaxGasLimitPerTxInEpochCalled != nil { + return ehm.MaxGasLimitPerTxInEpochCalled(epoch) + } + return 0 +} + // ComputeGasLimit - func (ehm *EconomicsHandlerMock) ComputeGasLimit(tx data.TransactionWithFeeHandler) uint64 { if ehm.ComputeGasLimitCalled != nil { diff --git a/testscommon/enableEpochsHandlerMock/enableEpochsHandlerStub.go b/testscommon/enableEpochsHandlerMock/enableEpochsHandlerStub.go index bf633508147..00813dfe96e 100644 --- a/testscommon/enableEpochsHandlerMock/enableEpochsHandlerStub.go +++ b/testscommon/enableEpochsHandlerMock/enableEpochsHandlerStub.go @@ -15,6 +15,7 @@ type EnableEpochsHandlerStub struct { IsFlagEnabledCalled func(flag core.EnableEpochFlag) bool IsFlagEnabledInEpochCalled func(flag core.EnableEpochFlag, epoch uint32) bool GetActivationEpochCalled func(flag core.EnableEpochFlag) uint32 + GetAllEnableEpochsCalled func() map[string]uint32 } // NewEnableEpochsHandlerStubWithNoFlagsDefined - @@ -107,6 +108,14 @@ func (stub *EnableEpochsHandlerStub) GetCurrentEpoch() uint32 { return 0 } +// GetAllEnableEpochs - +func (stub *EnableEpochsHandlerStub) GetAllEnableEpochs() map[string]uint32 { + if stub.GetAllEnableEpochsCalled != nil { + return stub.GetAllEnableEpochsCalled() + } + return make(map[string]uint32) +} + // IsInterfaceNil - func (stub *EnableEpochsHandlerStub) IsInterfaceNil() bool { return stub == nil diff --git a/testscommon/enableRoundsHandlerStub.go b/testscommon/enableRoundsHandlerStub.go index 08a27f0eb72..06c425e737c 100644 --- a/testscommon/enableRoundsHandlerStub.go +++ b/testscommon/enableRoundsHandlerStub.go @@ -1,20 +1,80 @@ package testscommon +import "github.com/multiversx/mx-chain-go/common" + // EnableRoundsHandlerStub - type EnableRoundsHandlerStub struct { - IsDisableAsyncCallV1EnabledCalled func() bool + RoundConfirmedCalled func(round uint64, timestamp uint64) + IsFlagDefinedCalled func(flag common.EnableRoundFlag) bool + IsFlagEnabledCalled func(flag common.EnableRoundFlag) bool + IsFlagEnabledInRoundCalled func(flag common.EnableRoundFlag, round uint64) bool + GetActivationRoundCalled func(flag common.EnableRoundFlag) uint64 + GetCurrentRoundCalled func() uint64 + GetAllEnableRoundsCalled func() map[string]uint64 +} + +// RoundConfirmed - +func (e *EnableRoundsHandlerStub) RoundConfirmed(round uint64, timestamp uint64) { + if e.RoundConfirmedCalled != nil { + e.RoundConfirmedCalled(round, timestamp) + } +} + +// IsFlagDefined - +func (e *EnableRoundsHandlerStub) IsFlagDefined(flag common.EnableRoundFlag) bool { + if e.IsFlagDefinedCalled != nil { + return e.IsFlagDefinedCalled(flag) + } + + return false +} + +// IsFlagEnabled - +func (e *EnableRoundsHandlerStub) IsFlagEnabled(flag common.EnableRoundFlag) bool { + if e.IsFlagEnabledCalled != nil { + return e.IsFlagEnabledCalled(flag) + } + + return false } -// IsDisableAsyncCallV1Enabled - -func (stub *EnableRoundsHandlerStub) IsDisableAsyncCallV1Enabled() bool { - if stub.IsDisableAsyncCallV1EnabledCalled != nil { - return stub.IsDisableAsyncCallV1EnabledCalled() +// IsFlagEnabledInRound - +func (e *EnableRoundsHandlerStub) IsFlagEnabledInRound(flag common.EnableRoundFlag, round uint64) bool { + if e.IsFlagEnabledInRoundCalled != nil { + return e.IsFlagEnabledInRoundCalled(flag, round) } return false } +// GetActivationRound - +func (e *EnableRoundsHandlerStub) GetActivationRound(flag common.EnableRoundFlag) uint64 { + if e.GetActivationRoundCalled != nil { + return e.GetActivationRoundCalled(flag) + } + + return 0 +} + +// GetCurrentRound - +func (e *EnableRoundsHandlerStub) GetCurrentRound() uint64 { + if e.GetCurrentRoundCalled != nil { + return e.GetCurrentRoundCalled() + } + + return 0 +} + +// GetAllEnableRounds - +func (e *EnableRoundsHandlerStub) GetAllEnableRounds() map[string]uint64 { + if e.GetAllEnableRoundsCalled != nil { + return e.GetAllEnableRoundsCalled() + } + + return make(map[string]uint64) +} + // IsInterfaceNil - -func (stub *EnableRoundsHandlerStub) IsInterfaceNil() bool { - return stub == nil +func (e *EnableRoundsHandlerStub) IsInterfaceNil() bool { + return e == nil } diff --git a/testscommon/epochStartConfigsHandlerStub.go b/testscommon/epochStartConfigsHandlerStub.go new file mode 100644 index 00000000000..21edfbd3202 --- /dev/null +++ b/testscommon/epochStartConfigsHandlerStub.go @@ -0,0 +1,63 @@ +package testscommon + +import ( + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/common/configs" + "github.com/multiversx/mx-chain-go/config" +) + +// GetDefaultCommonConfigsHandler - +func GetDefaultCommonConfigsHandler() common.CommonConfigsHandler { + commonConfigsHandler, _ := configs.NewCommonConfigsHandler( + []config.EpochStartConfigByEpoch{ + {EnableEpoch: 0, GracePeriodRounds: 25, ExtraDelayForRequestBlockInfoInMilliseconds: 3000}, + }, + []config.EpochStartConfigByRound{ + {EnableRound: 0, MaxRoundsWithoutCommittedStartInEpochBlock: 50}, + }, + []config.ConsensusConfigByEpoch{ + {EnableEpoch: 0, NumRoundsToWaitBeforeSignalingChronologyStuck: 10}, + }, + ) + + return commonConfigsHandler +} + +// CommonConfigsHandlerStub - +type CommonConfigsHandlerStub struct { + GetGracePeriodRoundsByEpochCalled func(epoch uint32) uint32 + GetExtraDelayForRequestBlockInfoInMsCalled func(epoch uint32) uint32 + GetMaxRoundsWithoutCommittedStartInEpochBlockInRoundCalled func(round uint64) uint32 +} + +// GetGracePeriodRoundsByEpoch - +func (e *CommonConfigsHandlerStub) GetGracePeriodRoundsByEpoch(epoch uint32) uint32 { + if e.GetGracePeriodRoundsByEpochCalled != nil { + return e.GetGracePeriodRoundsByEpochCalled(epoch) + } + + return 0 +} + +// GetExtraDelayForRequestBlockInfoInMs - +func (e *CommonConfigsHandlerStub) GetExtraDelayForRequestBlockInfoInMs(epoch uint32) uint32 { + if e.GetExtraDelayForRequestBlockInfoInMsCalled != nil { + return e.GetExtraDelayForRequestBlockInfoInMsCalled(epoch) + } + + return 0 +} + +// GetMaxRoundsWithoutCommittedStartInEpochBlockInRound - +func (e *CommonConfigsHandlerStub) GetMaxRoundsWithoutCommittedStartInEpochBlockInRound(round uint64) uint32 { + if e.GetMaxRoundsWithoutCommittedStartInEpochBlockInRoundCalled != nil { + return e.GetMaxRoundsWithoutCommittedStartInEpochBlockInRoundCalled(round) + } + + return 0 +} + +// IsInterfaceNil - +func (e *CommonConfigsHandlerStub) IsInterfaceNil() bool { + return e == nil +} diff --git a/testscommon/epochStartTriggerStub.go b/testscommon/epochStartTriggerStub.go index d3fecda505d..66f4d37ddea 100644 --- a/testscommon/epochStartTriggerStub.go +++ b/testscommon/epochStartTriggerStub.go @@ -20,6 +20,17 @@ type EpochStartTriggerStub struct { EpochStartRoundCalled func() uint64 EpochFinalityAttestingRoundCalled func() uint64 EpochStartMetaHdrHashCalled func() []byte + ShouldProposeEpochChangeCalled func(round uint64, nonce uint64) bool + SetEpochChangeCalled func(round uint64) + SetEpochChangeProposedCalled func(value bool) + GetEpochChangeProposedCalled func() bool +} + +// SetEpochChange - +func (e *EpochStartTriggerStub) SetEpochChange(round uint64) { + if e.SetEpochChangeCalled != nil { + e.SetEpochChangeCalled(round) + } } // RevertStateToBlock - @@ -102,6 +113,21 @@ func (e *EpochStartTriggerStub) EpochStartRound() uint64 { return 0 } +// SetEpochChangeProposed - +func (e *EpochStartTriggerStub) SetEpochChangeProposed(value bool) { + if e.SetEpochChangeProposedCalled != nil { + e.SetEpochChangeProposedCalled(value) + } +} + +// GetEpochChangeProposed - +func (e *EpochStartTriggerStub) GetEpochChangeProposed() bool { + if e.GetEpochChangeProposedCalled != nil { + return e.GetEpochChangeProposedCalled() + } + return false +} + // Update - func (e *EpochStartTriggerStub) Update(round uint64, nonce uint64) { if e.UpdateCalled != nil { @@ -147,6 +173,15 @@ func (e *EpochStartTriggerStub) MetaEpoch() uint32 { return 0 } +// ShouldProposeEpochChange - +func (e *EpochStartTriggerStub) ShouldProposeEpochChange(round uint64, nonce uint64) bool { + if e.ShouldProposeEpochChangeCalled != nil { + return e.ShouldProposeEpochChangeCalled(round, nonce) + } + + return false +} + // Close - func (e *EpochStartTriggerStub) Close() error { return nil diff --git a/testscommon/execResComputationStub.go b/testscommon/execResComputationStub.go new file mode 100644 index 00000000000..5172a4086e6 --- /dev/null +++ b/testscommon/execResComputationStub.go @@ -0,0 +1,45 @@ +package testscommon + +import "github.com/multiversx/mx-chain-go/process/estimator" + +// ExecResSizeLimitCheckerStub - +type ExecResSizeLimitCheckerStub struct { + AddNumExecResCalled func(numExecRes int) + IsMaxExecResSizeReachedCalled func(numNewExecRes int) bool +} + +// AddNumExecRes - +func (stub *ExecResSizeLimitCheckerStub) AddNumExecRes(numExecRes int) { + if stub.AddNumExecResCalled != nil { + stub.AddNumExecResCalled(numExecRes) + } +} + +// IsMaxExecResSizeReached - +func (stub *ExecResSizeLimitCheckerStub) IsMaxExecResSizeReached(numNewExecRes int) bool { + if stub.IsMaxExecResSizeReachedCalled != nil { + return stub.IsMaxExecResSizeReachedCalled(numNewExecRes) + } + + return false +} + +// IsInterfaceNil - +func (stub *ExecResSizeLimitCheckerStub) IsInterfaceNil() bool { + return stub == nil +} + +// ExecResSizeComputationStub - +type ExecResSizeComputationStub struct { + NewComputationCalled func() estimator.ExecResSizeLimitCheckerHandler +} + +// NewComputation - +func (stub *ExecResSizeComputationStub) NewComputation() estimator.ExecResSizeLimitCheckerHandler { + return &ExecResSizeLimitCheckerStub{} +} + +// IsInterfaceNil - +func (stub *ExecResSizeComputationStub) IsInterfaceNil() bool { + return stub == nil +} diff --git a/testscommon/executionTrack/executionResultsTrackerStub.go b/testscommon/executionTrack/executionResultsTrackerStub.go new file mode 100644 index 00000000000..47a8401503b --- /dev/null +++ b/testscommon/executionTrack/executionResultsTrackerStub.go @@ -0,0 +1,114 @@ +package executionTrack + +import ( + "github.com/multiversx/mx-chain-core-go/data" + + execTrack "github.com/multiversx/mx-chain-go/process/asyncExecution/executionTrack" +) + +// ExecutionResultsTrackerStub is a stub implementation of the ExecutionResultsTracker interface +type ExecutionResultsTrackerStub struct { + AddExecutionResultCalled func(executionResult data.BaseExecutionResultHandler) (bool, error) + GetPendingExecutionResultsCalled func() ([]data.BaseExecutionResultHandler, error) + GetPendingExecutionResultByHashCalled func(hash []byte) (data.BaseExecutionResultHandler, error) + GetPendingExecutionResultByNonceCalled func(nonce uint64) (data.BaseExecutionResultHandler, error) + GetLastNotarizedExecutionResultCalled func() (data.BaseExecutionResultHandler, error) + SetLastNotarizedResultCalled func(executionResult data.BaseExecutionResultHandler) error + RemoveFromNonceCalled func(nonce uint64) error + CleanCalled func(lastNotarizedResult data.BaseExecutionResultHandler) + CleanConfirmedExecutionResultsCalled func(header data.HeaderHandler) error + CleanOnConsensusReachedCalled func(headerHash []byte, header data.HeaderHandler) + PopDismissedResultsCalled func() []execTrack.DismissedBatch +} + +// AddExecutionResult - +func (ets *ExecutionResultsTrackerStub) AddExecutionResult(executionResult data.BaseExecutionResultHandler) (bool, error) { + if ets.AddExecutionResultCalled != nil { + return ets.AddExecutionResultCalled(executionResult) + } + return true, nil +} + +// GetPendingExecutionResults - +func (ets *ExecutionResultsTrackerStub) GetPendingExecutionResults() ([]data.BaseExecutionResultHandler, error) { + if ets.GetPendingExecutionResultsCalled != nil { + return ets.GetPendingExecutionResultsCalled() + } + return nil, nil +} + +// GetPendingExecutionResultByHash - +func (ets *ExecutionResultsTrackerStub) GetPendingExecutionResultByHash(hash []byte) (data.BaseExecutionResultHandler, error) { + if ets.GetPendingExecutionResultByHashCalled != nil { + return ets.GetPendingExecutionResultByHashCalled(hash) + } + return nil, nil +} + +// GetPendingExecutionResultByNonce - +func (ets *ExecutionResultsTrackerStub) GetPendingExecutionResultByNonce(nonce uint64) (data.BaseExecutionResultHandler, error) { + if ets.GetPendingExecutionResultByNonceCalled != nil { + return ets.GetPendingExecutionResultByNonceCalled(nonce) + } + return nil, nil +} + +// GetLastNotarizedExecutionResult - +func (ets *ExecutionResultsTrackerStub) GetLastNotarizedExecutionResult() (data.BaseExecutionResultHandler, error) { + if ets.GetLastNotarizedExecutionResultCalled != nil { + return ets.GetLastNotarizedExecutionResultCalled() + } + return nil, nil +} + +// SetLastNotarizedResult - +func (ets *ExecutionResultsTrackerStub) SetLastNotarizedResult(executionResult data.BaseExecutionResultHandler) error { + if ets.SetLastNotarizedResultCalled != nil { + return ets.SetLastNotarizedResultCalled(executionResult) + } + return nil +} + +// RemoveFromNonce - +func (ets *ExecutionResultsTrackerStub) RemoveFromNonce(nonce uint64) error { + if ets.RemoveFromNonceCalled != nil { + return ets.RemoveFromNonceCalled(nonce) + } + return nil +} + +// Clean - +func (ets *ExecutionResultsTrackerStub) Clean(lastNotarizedResult data.BaseExecutionResultHandler) { + if ets.CleanCalled != nil { + ets.CleanCalled(lastNotarizedResult) + } +} + +// CleanConfirmedExecutionResults - +func (ets *ExecutionResultsTrackerStub) CleanConfirmedExecutionResults(header data.HeaderHandler) error { + if ets.CleanConfirmedExecutionResultsCalled != nil { + return ets.CleanConfirmedExecutionResultsCalled(header) + } + + return nil +} + +// CleanOnConsensusReached - +func (ets *ExecutionResultsTrackerStub) CleanOnConsensusReached(headerHash []byte, header data.HeaderHandler) { + if ets.CleanOnConsensusReachedCalled != nil { + ets.CleanOnConsensusReachedCalled(headerHash, header) + } +} + +// PopDismissedResults - +func (stub *ExecutionResultsTrackerStub) PopDismissedResults() []execTrack.DismissedBatch { + if stub.PopDismissedResultsCalled != nil { + return stub.PopDismissedResultsCalled() + } + return nil +} + +// IsInterfaceNil checks if the interface is nil +func (ets *ExecutionResultsTrackerStub) IsInterfaceNil() bool { + return ets == nil +} diff --git a/testscommon/factory/coreComponentsHolderStub.go b/testscommon/factory/coreComponentsHolderStub.go index 1739c6efc4c..437a3972502 100644 --- a/testscommon/factory/coreComponentsHolderStub.go +++ b/testscommon/factory/coreComponentsHolderStub.go @@ -1,6 +1,7 @@ package factory import ( + "sync/atomic" "time" "github.com/multiversx/mx-chain-core-go/core" @@ -42,10 +43,11 @@ type CoreComponentsHolderStub struct { GenesisNodesSetupCalled func() sharding.GenesisNodesSetupHandler NodesShufflerCalled func() nodesCoordinator.NodesShuffler EpochNotifierCalled func() process.EpochNotifier - EnableRoundsHandlerCalled func() process.EnableRoundsHandler + EnableRoundsHandlerCalled func() common.EnableRoundsHandler EpochStartNotifierWithConfirmCalled func() factory.EpochStartNotifierWithConfirm ChanStopNodeProcessCalled func() chan endProcess.ArgEndProcess GenesisTimeCalled func() time.Time + SupernovaGenesisTimeCalled func() time.Time ChainIDCalled func() string MinTransactionVersionCalled func() uint32 TxVersionCheckerCalled func() process.TxVersionCheckerHandler @@ -60,6 +62,10 @@ type CoreComponentsHolderStub struct { ChainParametersHandlerCalled func() process.ChainParametersHandler FieldsSizeCheckerCalled func() common.FieldsSizeChecker EpochChangeGracePeriodHandlerCalled func() common.EpochChangeGracePeriodHandler + ProcessConfigsHandlerCalled func() common.ProcessConfigsHandler + CommonConfigsHandlerCalled func() common.CommonConfigsHandler + AntifloodConfigsHandlerCalled func() common.AntifloodConfigsHandler + ClosingNodeStartedCalled func() *atomic.Bool } // NewCoreComponentsHolderStubFromRealComponent - @@ -104,6 +110,9 @@ func NewCoreComponentsHolderStubFromRealComponent(coreComponents factory.CoreCom ChainParametersSubscriberCalled: coreComponents.ChainParametersSubscriber, FieldsSizeCheckerCalled: coreComponents.FieldsSizeChecker, EpochChangeGracePeriodHandlerCalled: coreComponents.EpochChangeGracePeriodHandler, + ProcessConfigsHandlerCalled: coreComponents.ProcessConfigsHandler, + CommonConfigsHandlerCalled: coreComponents.CommonConfigsHandler, + AntifloodConfigsHandlerCalled: coreComponents.AntifloodConfigsHandler, } } @@ -276,7 +285,7 @@ func (stub *CoreComponentsHolderStub) EpochNotifier() process.EpochNotifier { } // EnableRoundsHandler - -func (stub *CoreComponentsHolderStub) EnableRoundsHandler() process.EnableRoundsHandler { +func (stub *CoreComponentsHolderStub) EnableRoundsHandler() common.EnableRoundsHandler { if stub.EnableRoundsHandlerCalled != nil { return stub.EnableRoundsHandlerCalled() } @@ -307,6 +316,14 @@ func (stub *CoreComponentsHolderStub) GenesisTime() time.Time { return time.Unix(0, 0) } +// SupernovaGenesisTime - +func (stub *CoreComponentsHolderStub) SupernovaGenesisTime() time.Time { + if stub.SupernovaGenesisTimeCalled != nil { + return stub.SupernovaGenesisTimeCalled() + } + return time.UnixMilli(0) +} + // ChainID - func (stub *CoreComponentsHolderStub) ChainID() string { if stub.ChainIDCalled != nil { @@ -419,6 +436,40 @@ func (stub *CoreComponentsHolderStub) EpochChangeGracePeriodHandler() common.Epo return nil } +// ProcessConfigsHandler - +func (stub *CoreComponentsHolderStub) ProcessConfigsHandler() common.ProcessConfigsHandler { + if stub.ProcessConfigsHandlerCalled != nil { + return stub.ProcessConfigsHandlerCalled() + } + return nil +} + +// CommonConfigsHandler - +func (stub *CoreComponentsHolderStub) CommonConfigsHandler() common.CommonConfigsHandler { + if stub.CommonConfigsHandlerCalled != nil { + return stub.CommonConfigsHandlerCalled() + } + return nil +} + +// AntifloodConfigsHandler - +func (stub *CoreComponentsHolderStub) AntifloodConfigsHandler() common.AntifloodConfigsHandler { + if stub.AntifloodConfigsHandlerCalled != nil { + return stub.AntifloodConfigsHandlerCalled() + } + + return nil +} + +// ClosingNodeStarted - +func (stub *CoreComponentsHolderStub) ClosingNodeStarted() *atomic.Bool { + if stub.ClosingNodeStartedCalled != nil { + return stub.ClosingNodeStartedCalled() + } + + return &atomic.Bool{} +} + // IsInterfaceNil - func (stub *CoreComponentsHolderStub) IsInterfaceNil() bool { return stub == nil diff --git a/testscommon/factory/genericPartialComponent.go b/testscommon/factory/genericPartialComponent.go new file mode 100644 index 00000000000..20be6e9db62 --- /dev/null +++ b/testscommon/factory/genericPartialComponent.go @@ -0,0 +1,66 @@ +package factory + +import ( + "fmt" + "reflect" + "unsafe" +) + +// ConstructPartialComponentForTest initializes the given component by setting its subcomponents +func ConstructPartialComponentForTest(component interface{}, subcomponents map[string]interface{}) error { + + // initialize nil pointer fields + rv := reflect.ValueOf(component).Elem() + for i := 0; i < rv.NumField(); i++ { + field := rv.Field(i) + ft := rv.Type().Field(i) + + // handle embedded *structs that are nil + if ft.Anonymous && field.Kind() == reflect.Ptr && field.IsNil() && field.Type().Elem().Kind() == reflect.Struct { + newVal := reflect.New(field.Type().Elem()) + if !field.CanSet() { + field = reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem() + } + field.Set(newVal) + } + } + + // set subcomponents + setField := func(target any, name string, component any) error { + rv := reflect.ValueOf(target).Elem() + field := rv.FieldByName(name) + if !field.IsValid() { + return fmt.Errorf("invalid field: %s", name) + } + if !field.CanSet() { + // bypass export check (ok in tests, same package) + field = reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem() + } + + val := reflect.ValueOf(component) + switch { + case val.Type().AssignableTo(field.Type()): + field.Set(val) + case val.Type().ConvertibleTo(field.Type()): + field.Set(val.Convert(field.Type())) + case val.Kind() != reflect.Ptr && field.Kind() == reflect.Ptr && val.Type().AssignableTo(field.Type().Elem()): + ptr := reflect.New(val.Type()) + ptr.Elem().Set(val) + field.Set(ptr) + case val.Kind() != reflect.Ptr && field.Kind() == reflect.Ptr && val.Type().ConvertibleTo(field.Type().Elem()): + ptr := reflect.New(field.Type().Elem()) + ptr.Elem().Set(val.Convert(field.Type().Elem())) + field.Set(ptr) + default: + return fmt.Errorf("cannot set field %s (got %s, expected %s)", name, val.Type(), field.Type()) + } + return nil + } + + for name, subComponent := range subcomponents { + if err := setField(component, name, subComponent); err != nil { + return err + } + } + return nil +} diff --git a/testscommon/factory/stateComponentsMock.go b/testscommon/factory/stateComponentsMock.go index 429d172b481..5399bc4f5f9 100644 --- a/testscommon/factory/stateComponentsMock.go +++ b/testscommon/factory/stateComponentsMock.go @@ -8,16 +8,18 @@ import ( // StateComponentsMock - type StateComponentsMock struct { - PeersAcc state.AccountsAdapter - Accounts state.AccountsAdapter - AccountsAPI state.AccountsAdapter - AccountsAdapterAPICalled func() state.AccountsAdapter - AccountsRepo state.AccountsRepository - Tries common.TriesHolder - StorageManagers map[string]common.StorageManager - MissingNodesNotifier common.MissingTrieNodesNotifier - LeavesRetriever common.TrieLeavesRetriever - ChangesCollector state.StateAccessesCollector + PeersAcc state.AccountsAdapter + Accounts state.AccountsAdapter + AccountsAPI state.AccountsAdapter + AccountsProposal state.AccountsAdapter + AccountsAdapterAPICalled func() state.AccountsAdapter + AccountsAdapterProposalCalled func() state.AccountsAdapter + AccountsRepo state.AccountsRepository + Tries common.TriesHolder + StorageManagers map[string]common.StorageManager + MissingNodesNotifier common.MissingTrieNodesNotifier + LeavesRetriever common.TrieLeavesRetriever + ChangesCollector state.StateAccessesCollector } // NewStateComponentsMockFromRealComponent - @@ -26,11 +28,13 @@ func NewStateComponentsMockFromRealComponent(stateComponents factory.StateCompon PeersAcc: stateComponents.PeerAccounts(), Accounts: stateComponents.AccountsAdapter(), AccountsAPI: stateComponents.AccountsAdapterAPI(), + AccountsProposal: stateComponents.AccountsAdapterProposal(), AccountsRepo: stateComponents.AccountsRepository(), Tries: stateComponents.TriesContainer(), StorageManagers: stateComponents.TrieStorageManagers(), MissingNodesNotifier: stateComponents.MissingTrieNodesNotifier(), LeavesRetriever: stateComponents.TrieLeavesRetriever(), + ChangesCollector: stateComponents.StateAccessesCollector(), } } @@ -67,6 +71,14 @@ func (scm *StateComponentsMock) AccountsAdapterAPI() state.AccountsAdapter { return scm.AccountsAPI } +// AccountsAdapterProposal - +func (scm *StateComponentsMock) AccountsAdapterProposal() state.AccountsAdapter { + if scm.AccountsAdapterProposalCalled != nil { + return scm.AccountsAdapterProposalCalled() + } + return scm.AccountsProposal +} + // AccountsRepository - func (scm *StateComponentsMock) AccountsRepository() state.AccountsRepository { return scm.AccountsRepo diff --git a/testscommon/gasComputationMock.go b/testscommon/gasComputationMock.go new file mode 100644 index 00000000000..e3cff690a5c --- /dev/null +++ b/testscommon/gasComputationMock.go @@ -0,0 +1,144 @@ +package testscommon + +import ( + "github.com/multiversx/mx-chain-core-go/data" +) + +// GasComputationMock - +type GasComputationMock struct { + AddIncomingMiniBlocksCalled func( + miniBlocks []data.MiniBlockHeaderHandler, + transactions map[string][]data.TransactionHandler, + ) (int, int, error) + AddOutgoingTransactionsCalled func( + txHashes [][]byte, + transactions []data.TransactionHandler, + ) ([][]byte, []data.MiniBlockHeaderHandler, error) + GetBandwidthForTransactionsCalled func() uint64 + TotalGasConsumedInSelfShardCalled func() uint64 + TotalGasConsumedInShardCalled func(shard uint32) uint64 + DecreaseIncomingLimitCalled func() + DecreaseOutgoingLimitCalled func() + ZeroIncomingLimitCalled func() + ZeroOutgoingLimitCalled func() + ResetIncomingLimitCalled func() + ResetOutgoingLimitCalled func() + ResetCalled func() + RevertIncomingMiniBlocksCalled func(miniBlockHashes [][]byte) + CanAddPendingIncomingMiniBlocksCalled func() bool +} + +// AddIncomingMiniBlocks - +func (mock *GasComputationMock) AddIncomingMiniBlocks( + miniBlocks []data.MiniBlockHeaderHandler, + transactions map[string][]data.TransactionHandler, +) (int, int, error) { + if mock.AddIncomingMiniBlocksCalled != nil { + return mock.AddIncomingMiniBlocksCalled(miniBlocks, transactions) + } + return 0, 0, nil +} + +// AddOutgoingTransactions - +func (mock *GasComputationMock) AddOutgoingTransactions( + txHashes [][]byte, + transactions []data.TransactionHandler, +) ([][]byte, []data.MiniBlockHeaderHandler, error) { + if mock.AddOutgoingTransactionsCalled != nil { + return mock.AddOutgoingTransactionsCalled(txHashes, transactions) + } + return nil, nil, nil +} + +// RevertIncomingMiniBlocks - +func (mock *GasComputationMock) RevertIncomingMiniBlocks(miniBlockHashes [][]byte) { + if mock.RevertIncomingMiniBlocksCalled != nil { + mock.RevertIncomingMiniBlocksCalled(miniBlockHashes) + } +} + +// GetBandwidthForTransactions - +func (mock *GasComputationMock) GetBandwidthForTransactions() uint64 { + if mock.GetBandwidthForTransactionsCalled != nil { + return mock.GetBandwidthForTransactionsCalled() + } + return 0 +} + +// TotalGasConsumedInSelfShard - +func (mock *GasComputationMock) TotalGasConsumedInSelfShard() uint64 { + if mock.TotalGasConsumedInSelfShardCalled != nil { + return mock.TotalGasConsumedInSelfShardCalled() + } + return 0 +} + +// TotalGasConsumedInShard - +func (mock *GasComputationMock) TotalGasConsumedInShard(shard uint32) uint64 { + if mock.TotalGasConsumedInShardCalled != nil { + return mock.TotalGasConsumedInShardCalled(shard) + } + return 0 +} + +// DecreaseIncomingLimit - +func (mock *GasComputationMock) DecreaseIncomingLimit() { + if mock.DecreaseIncomingLimitCalled != nil { + mock.DecreaseIncomingLimitCalled() + } +} + +// DecreaseOutgoingLimit - +func (mock *GasComputationMock) DecreaseOutgoingLimit() { + if mock.DecreaseOutgoingLimitCalled != nil { + mock.DecreaseOutgoingLimitCalled() + } +} + +// ZeroIncomingLimit - +func (mock *GasComputationMock) ZeroIncomingLimit() { + if mock.ZeroIncomingLimitCalled != nil { + mock.ZeroIncomingLimitCalled() + } +} + +// ZeroOutgoingLimit - +func (mock *GasComputationMock) ZeroOutgoingLimit() { + if mock.ZeroOutgoingLimitCalled != nil { + mock.ZeroOutgoingLimitCalled() + } +} + +// ResetIncomingLimit - +func (mock *GasComputationMock) ResetIncomingLimit() { + if mock.ResetIncomingLimitCalled != nil { + mock.ResetIncomingLimitCalled() + } +} + +// ResetOutgoingLimit - +func (mock *GasComputationMock) ResetOutgoingLimit() { + if mock.ResetOutgoingLimitCalled != nil { + mock.ResetOutgoingLimitCalled() + } +} + +// CanAddPendingIncomingMiniBlocks - +func (mock *GasComputationMock) CanAddPendingIncomingMiniBlocks() bool { + if mock.CanAddPendingIncomingMiniBlocksCalled != nil { + return mock.CanAddPendingIncomingMiniBlocksCalled() + } + return true +} + +// Reset - +func (mock *GasComputationMock) Reset() { + if mock.ResetCalled != nil { + mock.ResetCalled() + } +} + +// IsInterfaceNil - +func (mock *GasComputationMock) IsInterfaceNil() bool { + return mock == nil +} diff --git a/testscommon/gasEpochStateHandlerStub.go b/testscommon/gasEpochStateHandlerStub.go new file mode 100644 index 00000000000..5b79f8d002f --- /dev/null +++ b/testscommon/gasEpochStateHandlerStub.go @@ -0,0 +1,36 @@ +package testscommon + +// GasEpochStateHandlerStub - +type GasEpochStateHandlerStub struct { + EpochConfirmedCalled func(epoch uint32) + RoundConfirmedCalled func(round uint64) + GetEpochForLimitsAndOverEstimationFactorCalled func() (uint32, uint64) +} + +// EpochConfirmed - +func (g *GasEpochStateHandlerStub) EpochConfirmed(epoch uint32) { + if g.EpochConfirmedCalled != nil { + g.EpochConfirmedCalled(epoch) + } +} + +// RoundConfirmed - +func (g *GasEpochStateHandlerStub) RoundConfirmed(round uint64) { + if g.RoundConfirmedCalled != nil { + g.RoundConfirmedCalled(round) + } +} + +// GetEpochForLimitsAndOverEstimationFactor - +func (g *GasEpochStateHandlerStub) GetEpochForLimitsAndOverEstimationFactor() (uint32, uint64) { + if g.GetEpochForLimitsAndOverEstimationFactorCalled != nil { + return g.GetEpochForLimitsAndOverEstimationFactorCalled() + } + + return 0, 0 +} + +// IsInterfaceNil returns true if there is no value under the interface +func (g *GasEpochStateHandlerStub) IsInterfaceNil() bool { + return g == nil +} diff --git a/testscommon/generalConfig.go b/testscommon/generalConfig.go index 3448122e630..df01b6ec29c 100644 --- a/testscommon/generalConfig.go +++ b/testscommon/generalConfig.go @@ -50,13 +50,14 @@ func GetGeneralConfig() config.Config { CacheRefreshIntervalInSec: uint32(100), }, GeneralSettings: config.GeneralSettingsConfig{ - StartInEpochEnabled: true, - GenesisMaxNumberOfShards: 100, - MaxComputableRounds: 1000, - MaxConsecutiveRoundsOfRatingDecrease: 2000, - SyncProcessTimeInMillis: 6000, - SetGuardianEpochsDelay: 20, - StatusPollingIntervalSec: 10, + StartInEpochEnabled: true, + GenesisMaxNumberOfShards: 100, + MaxComputableRounds: 1000, + SyncProcessTimeInMillis: 6000, + SyncProcessTimeSupernovaInMillis: 3000, + SetGuardianEpochsDelay: 20, + StatusPollingIntervalSec: 10, + MaxProposalNonceGap: 10, ChainParametersByEpoch: []config.ChainParametersByEpochConfig{ { EnableEpoch: 0, @@ -67,13 +68,43 @@ func GetGeneralConfig() config.Config { MetachainMinNumNodes: 1, Hysteresis: 0, Adaptivity: false, + RoundsPerEpoch: 10, + MinRoundsBetweenEpochs: 5, }, }, EpochChangeGracePeriodByEpoch: []config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}, + ProcessConfigsByEpoch: []config.ProcessConfigByEpoch{{ + EnableEpoch: 0, + MaxMetaNoncesBehind: 15, + MaxMetaNoncesBehindForGlobalStuck: 30, + MaxShardNoncesBehind: 15, + }}, + ProcessConfigsByRound: []config.ProcessConfigByRound{ + { + EnableRound: 0, + MaxRoundsWithoutNewBlockReceived: 10, + MaxRoundsWithoutCommittedBlock: 10, + MaxRoundsToKeepUnprocessedMiniBlocks: 50, + MaxRoundsToKeepUnprocessedTransactions: 50, + NumFloodingRoundsSlowReacting: 20, + NumFloodingRoundsFastReacting: 30, + NumFloodingRoundsOutOfSpecs: 40, + MaxConsecutiveRoundsOfRatingDecrease: 2000, + MaxBlockProcessingTimeMs: 1000, + NumHeadersToRequestInAdvance: 10, + }, + }, + EpochStartConfigsByEpoch: []config.EpochStartConfigByEpoch{ + {EnableEpoch: 0, GracePeriodRounds: 25, ExtraDelayForRequestBlockInfoInMilliseconds: 3000}, + }, + EpochStartConfigsByRound: []config.EpochStartConfigByRound{ + {EnableRound: 0, MaxRoundsWithoutCommittedStartInEpochBlock: 50}, + }, + ConsensusConfigsByEpoch: []config.ConsensusConfigByEpoch{ + {EnableEpoch: 0, NumRoundsToWaitBeforeSignalingChronologyStuck: 10}, + }, }, EpochStartConfig: config.EpochStartConfig{ - MinRoundsBetweenEpochs: 5, - RoundsPerEpoch: 10, MinNumConnectedPeersToStart: 2, MinNumOfPeersToConsiderBlockValid: 2, }, @@ -136,6 +167,18 @@ func GetGeneralConfig() config.Config { SizeInBytesPerSender: 10000000, Shards: 1, }, + TxCacheBounds: config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: 33_554_432, + MaxTrackedBlocks: 100, + PropagationGracePeriodMs: 0, + }, + TxCacheSelection: config.TxCacheSelectionConfig{ + SelectionGasBandwidthIncreasePercent: 400, + SelectionGasBandwidthIncreaseScheduledPercent: 260, + SelectionGasRequested: 10_000_000_000, + SelectionMaxNumTxs: 30000, + SelectionLoopDurationCheckInterval: 10, + }, UnsignedTransactionDataPool: config.CacheConfig{ Capacity: 10000, SizeInBytes: 1000000000, @@ -221,6 +264,16 @@ func GetGeneralConfig() config.Config { MaxOpenFiles: 10, }, }, + ExecutionResultsStorage: config.StorageConfig{ + Cache: getLRUCacheConfig(), + DB: config.DBConfig{ + FilePath: AddTimestampSuffix("ExecutionResults"), + Type: string(storageunit.MemoryDB), + BatchDelaySeconds: 30, + MaxBatchSize: 6, + MaxOpenFiles: 10, + }, + }, MetaHdrNonceHashStorage: config.StorageConfig{ Cache: getLRUCacheConfig(), DB: config.DBConfig{ @@ -363,11 +416,6 @@ func GetGeneralConfig() config.Config { }, }, Versions: config.VersionsConfig{ - Cache: config.CacheConfig{ - Type: "LRU", - Capacity: 1000, - Shards: 1, - }, DefaultVersion: "default", VersionsByEpochs: []config.VersionByEpochs{ { @@ -385,14 +433,7 @@ func GetGeneralConfig() config.Config { TrieSyncerVersion: 2, CheckNodesOnDisk: false, }, - Antiflood: config.AntifloodConfig{ - NumConcurrentResolverJobs: 2, - NumConcurrentResolvingTrieNodesJobs: 1, - TxAccumulator: config.TxAccumulatorConfig{ - MaxAllowedTimeInMilliseconds: 10, - MaxDeviationTimeInMilliseconds: 1, - }, - }, + Antiflood: GetDefaultAntifloodConfig(), Requesters: config.RequesterConfig{ NumCrossShardPeers: 2, NumTotalPeers: 3, @@ -432,10 +473,6 @@ func GetGeneralConfig() config.Config { TopRatedCacheCapacity: 1000, BadRatedCacheCapacity: 1000, }, - PoolsCleanersConfig: config.PoolsCleanersConfig{ - MaxRoundsToKeepUnprocessedMiniBlocks: 50, - MaxRoundsToKeepUnprocessedTransactions: 50, - }, BuiltInFunctions: config.BuiltInFunctionsConfig{ AutomaticCrawlerAddresses: []string{ "erd1he8wwxn4az3j82p7wwqsdk794dm7hcrwny6f8dfegkfla34udx7qrf7xje", // shard 0 @@ -456,6 +493,21 @@ func GetGeneralConfig() config.Config { CacheSpanInSec: 1, CacheExpiryInSec: 1, }, + ExecutedMiniBlocksCache: getLRUCacheConfig(), + PostProcessTransactionsCache: getLRUCacheConfig(), + BlockSizeThrottleConfig: config.BlockSizeThrottleConfig{ + MinSizeInBytes: 1, + MaxSizeInBytes: 10000, + MaxExecResSizeInBytes: 10000, + }, + ExecutionResultInclusionEstimator: config.ExecutionResultInclusionEstimatorConfig{ + SafetyMargin: 0, + MaxResultsPerBlock: 10, + }, + DirectSentTransactions: config.DirectSentTransactionsConfig{ + CacheSpanInSec: 1, + CacheExpiryInSec: 1, + }, } } @@ -466,3 +518,154 @@ func getLRUCacheConfig() config.CacheConfig { Shards: 1, } } + +// GetDefaultAntifloodConfig - +func GetDefaultAntifloodConfig() config.AntifloodConfig { + return config.AntifloodConfig{ + Enabled: true, + ConfigsByRound: []config.AntifloodConfigByRound{ + { + Round: 0, + NumConcurrentResolverJobs: 10, + NumConcurrentResolvingTrieNodesJobs: 3, + Cache: config.CacheConfig{ + Type: "LRU", + Capacity: 10, + Shards: 2, + }, + PeerMaxOutput: config.FloodPreventerConfig{ + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: 10, + TotalSizePerInterval: 10, + }, + }, + Topic: config.TopicAntifloodConfig{ + DefaultMaxMessagesPerSec: 10, + }, + TxAccumulator: config.TxAccumulatorConfig{ + MaxAllowedTimeInMilliseconds: 10, + MaxDeviationTimeInMilliseconds: 1, + }, + FastReacting: config.FloodPreventerConfig{ + BlackList: config.BlackListConfig{ + ThresholdNumMessagesPerInterval: 100, + ThresholdSizePerInterval: 1024, + PeerBanDurationInSeconds: 100, + NumFloodingRounds: 5, + }, + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: 100, + TotalSizePerInterval: 1024, + IncreaseFactor: config.IncreaseFactorConfig{ + Factor: 1.0, + }, + }, + ReservedPercent: 50.0, + }, + SlowReacting: config.FloodPreventerConfig{ + BlackList: config.BlackListConfig{ + ThresholdNumMessagesPerInterval: 100, + ThresholdSizePerInterval: 1024, + PeerBanDurationInSeconds: 100, + NumFloodingRounds: 5, + }, + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: 100, + TotalSizePerInterval: 1024, + IncreaseFactor: config.IncreaseFactorConfig{ + Factor: 1.0, + }, + }, + ReservedPercent: 50.0, + }, + OutOfSpecs: config.FloodPreventerConfig{ + BlackList: config.BlackListConfig{ + ThresholdNumMessagesPerInterval: 100, + ThresholdSizePerInterval: 1024, + PeerBanDurationInSeconds: 100, + NumFloodingRounds: 5, + }, + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: 100, + TotalSizePerInterval: 1024, + IncreaseFactor: config.IncreaseFactorConfig{ + Factor: 1.0, + }, + }, + ReservedPercent: 50.0, + }, + }, + { + Round: 100, + NumConcurrentResolverJobs: 10, + NumConcurrentResolvingTrieNodesJobs: 3, + Cache: config.CacheConfig{ + Type: "LRU", + Capacity: 10, + Shards: 2, + }, + PeerMaxOutput: config.FloodPreventerConfig{ + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: 101, + TotalSizePerInterval: 102, + }, + }, + Topic: config.TopicAntifloodConfig{ + DefaultMaxMessagesPerSec: 103, + }, + TxAccumulator: config.TxAccumulatorConfig{ + MaxAllowedTimeInMilliseconds: 104, + MaxDeviationTimeInMilliseconds: 11, + }, + FastReacting: config.FloodPreventerConfig{ + BlackList: config.BlackListConfig{ + ThresholdNumMessagesPerInterval: 201, + ThresholdSizePerInterval: 2041, + PeerBanDurationInSeconds: 201, + NumFloodingRounds: 12, + }, + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: 202, + TotalSizePerInterval: 2042, + IncreaseFactor: config.IncreaseFactorConfig{ + Factor: 2.0, + }, + }, + ReservedPercent: 60.0, + }, + SlowReacting: config.FloodPreventerConfig{ + BlackList: config.BlackListConfig{ + ThresholdNumMessagesPerInterval: 203, + ThresholdSizePerInterval: 2043, + PeerBanDurationInSeconds: 203, + NumFloodingRounds: 13, + }, + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: 203, + TotalSizePerInterval: 2043, + IncreaseFactor: config.IncreaseFactorConfig{ + Factor: 2.0, + }, + }, + ReservedPercent: 60.0, + }, + OutOfSpecs: config.FloodPreventerConfig{ + BlackList: config.BlackListConfig{ + ThresholdNumMessagesPerInterval: 204, + ThresholdSizePerInterval: 2044, + PeerBanDurationInSeconds: 204, + NumFloodingRounds: 14, + }, + PeerMaxInput: config.AntifloodLimitsConfig{ + BaseMessagesPerInterval: 204, + TotalSizePerInterval: 2044, + IncreaseFactor: config.IncreaseFactorConfig{ + Factor: 2.0, + }, + }, + ReservedPercent: 60.0, + }, + }, + }, + } +} diff --git a/testscommon/headerHandlerStub.go b/testscommon/headerHandlerStub.go index 3ce4bfd22e1..d46f7819172 100644 --- a/testscommon/headerHandlerStub.go +++ b/testscommon/headerHandlerStub.go @@ -7,39 +7,93 @@ import ( "github.com/multiversx/mx-chain-core-go/data/headerVersionData" ) +// HeaderHandlerWithExecutionResultsStub - +type HeaderHandlerWithExecutionResultsStub struct { + HeaderHandlerStub + GetExecutionResultsCalled func() []data.ExecutionResultHandler +} + +// GetExecutionResults - +func (hh *HeaderHandlerWithExecutionResultsStub) GetExecutionResults() []data.ExecutionResultHandler { + if hh.GetExecutionResultsCalled != nil { + return hh.GetExecutionResultsCalled() + } + + return nil +} + // HeaderHandlerStub - type HeaderHandlerStub struct { - EpochField uint32 - RoundField uint64 - TimestampField uint64 - BlockBodyTypeInt32Field int32 - GetMiniBlockHeadersWithDstCalled func(destId uint32) map[string]uint32 - GetOrderedCrossMiniblocksWithDstCalled func(destId uint32) []*data.MiniBlockInfo - GetPubKeysBitmapCalled func() []byte - GetSignatureCalled func() []byte - GetRootHashCalled func() []byte - GetRandSeedCalled func() []byte - GetPrevRandSeedCalled func() []byte - GetPrevHashCalled func() []byte - CloneCalled func() data.HeaderHandler - GetChainIDCalled func() []byte - CheckChainIDCalled func(reference []byte) error - GetReservedCalled func() []byte - IsStartOfEpochBlockCalled func() bool - HasScheduledMiniBlocksCalled func() bool - GetNonceCalled func() uint64 - CheckFieldsForNilCalled func() error - SetShardIDCalled func(shardID uint32) error - SetPrevHashCalled func(hash []byte) error - SetPrevRandSeedCalled func(seed []byte) error - SetPubKeysBitmapCalled func(bitmap []byte) error - SetChainIDCalled func(chainID []byte) error - SetTimeStampCalled func(timestamp uint64) error - SetRandSeedCalled func(seed []byte) error - SetSignatureCalled func(signature []byte) error - SetLeaderSignatureCalled func(signature []byte) error - GetShardIDCalled func() uint32 - SetRootHashCalled func(hash []byte) error + EpochField uint32 + RoundField uint64 + TimestampField uint64 + BlockBodyTypeInt32Field int32 + GetMiniBlockHeadersWithDstCalled func(destId uint32) map[string]uint32 + GetProposedMiniBlockHeadersWithDstCalled func(destId uint32) map[string]uint32 + GetOrderedCrossMiniblocksWithDstCalled func(destId uint32) []*data.MiniBlockInfo + GetPubKeysBitmapCalled func() []byte + GetSignatureCalled func() []byte + GetRootHashCalled func() []byte + GetRandSeedCalled func() []byte + GetPrevRandSeedCalled func() []byte + GetPrevHashCalled func() []byte + CloneCalled func() data.HeaderHandler + GetChainIDCalled func() []byte + CheckChainIDCalled func(reference []byte) error + GetReservedCalled func() []byte + IsStartOfEpochBlockCalled func() bool + HasScheduledMiniBlocksCalled func() bool + GetNonceCalled func() uint64 + CheckFieldsForNilCalled func() error + CheckFieldsIntegrityCalled func() error + SetShardIDCalled func(shardID uint32) error + SetPrevHashCalled func(hash []byte) error + SetPrevRandSeedCalled func(seed []byte) error + SetPubKeysBitmapCalled func(bitmap []byte) error + SetChainIDCalled func(chainID []byte) error + SetTimeStampCalled func(timestamp uint64) error + SetRandSeedCalled func(seed []byte) error + SetSignatureCalled func(signature []byte) error + SetLeaderSignatureCalled func(signature []byte) error + GetShardIDCalled func() uint32 + SetRootHashCalled func(hash []byte) error + GetGasLimitCalled func() uint32 + GetLastExecutionResultHandlerCalled func() data.LastExecutionResultHandler + GetExecutionResultsHandlersCalled func() []data.BaseExecutionResultHandler + IsHeaderV3Called func() bool + GetMiniBlockHeaderHandlersCalled func() []data.MiniBlockHeaderHandler + SetEpochStartMetaHashCalled func(hash []byte) error + GetShardInfoHandlersCalled func() []data.ShardDataHandler + SetLastExecutionResultHandlerCalled func(resultHandler data.LastExecutionResultHandler) error + SetExecutionResultsHandlersCalled func(resultHandlers []data.BaseExecutionResultHandler) error + SetEpochCalled func(epoch uint32) error + SetMiniBlockHeaderHandlersCalled func(mbsHandlers []data.MiniBlockHeaderHandler) error + SetTxCountCalled func(count uint32) error + SetMetaBlockHashesCalled func(hashes [][]byte) error + SetEpochStartHandlerCalled func(epochStartHandler data.EpochStartHandler) error + SetRoundCalled func(round uint64) error + SetNonceCalled func(nonce uint64) error + SetShardInfoHandlersCalled func(shardInfo []data.ShardDataHandler) error + GetShardInfoProposalHandlersCalled func() []data.ShardDataProposalHandler + SetShardInfoProposalHandlersCalled func(shardInfo []data.ShardDataProposalHandler) error + IsEpochChangeProposedCalled func() bool + GetEpochStartHandlerCalled func() data.EpochStartHandler +} + +// SetEpochStartHandler - +func (hhs *HeaderHandlerStub) SetEpochStartHandler(epochStartHandler data.EpochStartHandler) error { + if hhs.SetEpochStartHandlerCalled != nil { + return hhs.SetEpochStartHandlerCalled(epochStartHandler) + } + return nil +} + +// IsHeaderV3 - checks if the header is a V3 header +func (hhs *HeaderHandlerStub) IsHeaderV3() bool { + if hhs.IsHeaderV3Called != nil { + return hhs.IsHeaderV3Called() + } + return false } // GetAccumulatedFees - @@ -122,12 +176,19 @@ func (hhs *HeaderHandlerStub) GetTimeStamp() uint64 { // GetRootHash - func (hhs *HeaderHandlerStub) GetRootHash() []byte { - return hhs.GetRootHashCalled() + if hhs.GetRootHashCalled != nil { + return hhs.GetRootHashCalled() + } + + return nil } // GetPrevHash - func (hhs *HeaderHandlerStub) GetPrevHash() []byte { - return hhs.GetPrevHashCalled() + if hhs.GetPrevHashCalled != nil { + return hhs.GetPrevHashCalled() + } + return nil } // GetPrevRandSeed - @@ -153,17 +214,29 @@ func (hhs *HeaderHandlerStub) GetPubKeysBitmap() []byte { // GetSignature - func (hhs *HeaderHandlerStub) GetSignature() []byte { - return hhs.GetSignatureCalled() + if hhs.GetSignatureCalled != nil { + return hhs.GetSignatureCalled() + } + + return nil } // GetLeaderSignature - func (hhs *HeaderHandlerStub) GetLeaderSignature() []byte { - return hhs.GetSignatureCalled() + if hhs.GetSignatureCalled != nil { + return hhs.GetSignatureCalled() + } + + return nil } // GetChainID - func (hhs *HeaderHandlerStub) GetChainID() []byte { - return hhs.GetChainIDCalled() + if hhs.GetChainIDCalled != nil { + return hhs.GetChainIDCalled() + } + + return nil } // GetTxCount - @@ -181,18 +254,27 @@ func (hhs *HeaderHandlerStub) GetReserved() []byte { } // SetNonce - -func (hhs *HeaderHandlerStub) SetNonce(_ uint64) error { - panic("implement me") +func (hhs *HeaderHandlerStub) SetNonce(nonce uint64) error { + if hhs.SetNonceCalled != nil { + return hhs.SetNonceCalled(nonce) + } + return nil } // SetEpoch - -func (hhs *HeaderHandlerStub) SetEpoch(_ uint32) error { - panic("implement me") +func (hhs *HeaderHandlerStub) SetEpoch(epoch uint32) error { + if hhs.SetEpochCalled != nil { + return hhs.SetEpochCalled(epoch) + } + return nil } // SetRound - -func (hhs *HeaderHandlerStub) SetRound(_ uint64) error { - panic("implement me") +func (hhs *HeaderHandlerStub) SetRound(round uint64) error { + if hhs.SetRoundCalled != nil { + return hhs.SetRoundCalled(round) + } + return nil } // SetTimeStamp - @@ -268,13 +350,29 @@ func (hhs *HeaderHandlerStub) SetChainID(chainID []byte) error { } // SetTxCount - -func (hhs *HeaderHandlerStub) SetTxCount(_ uint32) error { - panic("implement me") +func (hhs *HeaderHandlerStub) SetTxCount(count uint32) error { + if hhs.SetTxCountCalled != nil { + return hhs.SetTxCountCalled(count) + } + return nil } // GetMiniBlockHeadersWithDst - func (hhs *HeaderHandlerStub) GetMiniBlockHeadersWithDst(destId uint32) map[string]uint32 { - return hhs.GetMiniBlockHeadersWithDstCalled(destId) + if hhs.GetMiniBlockHeadersWithDstCalled(destId) != nil { + return hhs.GetMiniBlockHeadersWithDstCalled(destId) + } + + return make(map[string]uint32) +} + +// GetProposedMiniBlockHeadersWithDst - +func (hhs *HeaderHandlerStub) GetProposedMiniBlockHeadersWithDst(destId uint32) map[string]uint32 { + if hhs.GetProposedMiniBlockHeadersWithDstCalled != nil { + return hhs.GetProposedMiniBlockHeadersWithDstCalled(destId) + } + + return make(map[string]uint32) } // GetOrderedCrossMiniblocksWithDst - @@ -289,7 +387,10 @@ func (hhs *HeaderHandlerStub) GetMiniBlockHeadersHashes() [][]byte { // GetMiniBlockHeaderHandlers - func (hhs *HeaderHandlerStub) GetMiniBlockHeaderHandlers() []data.MiniBlockHeaderHandler { - panic("implement me") + if hhs.GetMiniBlockHeaderHandlersCalled != nil { + return hhs.GetMiniBlockHeaderHandlersCalled() + } + return make([]data.MiniBlockHeaderHandler, 0) } // GetMetaBlockHashes - @@ -313,8 +414,11 @@ func (hhs *HeaderHandlerStub) SetValidatorStatsRootHash(_ []byte) error { } // SetMiniBlockHeaderHandlers - -func (hhs *HeaderHandlerStub) SetMiniBlockHeaderHandlers(_ []data.MiniBlockHeaderHandler) error { - panic("implement me") +func (hhs *HeaderHandlerStub) SetMiniBlockHeaderHandlers(mbsHandlers []data.MiniBlockHeaderHandler) error { + if hhs.SetMiniBlockHeaderHandlersCalled != nil { + return hhs.SetMiniBlockHeaderHandlersCalled(mbsHandlers) + } + return nil } // IsInterfaceNil returns true if there is no value under the interface @@ -343,18 +447,27 @@ func (hhs *HeaderHandlerStub) SetReceiptsHash(_ []byte) error { } // SetMetaBlockHashes - -func (hhs *HeaderHandlerStub) SetMetaBlockHashes(_ [][]byte) error { +func (hhs *HeaderHandlerStub) SetMetaBlockHashes(hashes [][]byte) error { + if hhs.SetMetaBlockHashesCalled != nil { + return hhs.SetMetaBlockHashesCalled(hashes) + } return nil } // SetEpochStartMetaHash - -func (hhs *HeaderHandlerStub) SetEpochStartMetaHash(_ []byte) error { +func (hhs *HeaderHandlerStub) SetEpochStartMetaHash(hash []byte) error { + if hhs.SetEpochStartMetaHashCalled != nil { + return hhs.SetEpochStartMetaHashCalled(hash) + } return nil } // GetShardInfoHandlers - func (hhs *HeaderHandlerStub) GetShardInfoHandlers() []data.ShardDataHandler { - panic("implement me") + if hhs.GetShardInfoHandlersCalled != nil { + return hhs.GetShardInfoHandlersCalled() + } + return make([]data.ShardDataHandler, 0) } // GetEpochStartHandler - @@ -373,8 +486,11 @@ func (hhs *HeaderHandlerStub) SetDevFeesInEpoch(_ *big.Int) error { } // SetShardInfoHandlers - -func (hhs *HeaderHandlerStub) SetShardInfoHandlers(_ []data.ShardDataHandler) error { - panic("implement me") +func (hhs *HeaderHandlerStub) SetShardInfoHandlers(shardInfo []data.ShardDataHandler) error { + if hhs.SetShardInfoHandlersCalled != nil { + return hhs.SetShardInfoHandlersCalled(shardInfo) + } + return nil } // SetAccumulatedFeesInEpoch - @@ -421,6 +537,15 @@ func (hhs *HeaderHandlerStub) CheckFieldsForNil() error { return nil } +// CheckFieldsIntegrity - +func (hhs *HeaderHandlerStub) CheckFieldsIntegrity() error { + if hhs.CheckFieldsIntegrityCalled != nil { + return hhs.CheckFieldsIntegrityCalled() + } + + return nil +} + // HasScheduledMiniBlocks - func (hhs *HeaderHandlerStub) HasScheduledMiniBlocks() bool { if hhs.HasScheduledMiniBlocksCalled != nil { @@ -435,3 +560,79 @@ func (hhs *HeaderHandlerStub) SetBlockBodyTypeInt32(blockBodyType int32) error { return nil } + +// GetLastExecutionResultHandler - +func (hhs *HeaderHandlerStub) GetLastExecutionResultHandler() data.LastExecutionResultHandler { + if hhs.GetLastExecutionResultHandlerCalled != nil { + return hhs.GetLastExecutionResultHandlerCalled() + } + + return nil +} + +// GetExecutionResultsHandlers - +func (hhs *HeaderHandlerStub) GetExecutionResultsHandlers() []data.BaseExecutionResultHandler { + if hhs.GetExecutionResultsHandlersCalled != nil { + return hhs.GetExecutionResultsHandlersCalled() + } + + return nil +} + +// SetLastExecutionResultHandler - +func (hhs *HeaderHandlerStub) SetLastExecutionResultHandler(resultHandler data.LastExecutionResultHandler) error { + if hhs.SetLastExecutionResultHandlerCalled != nil { + return hhs.SetLastExecutionResultHandlerCalled(resultHandler) + } + return nil +} + +// SetExecutionResultsHandlers - +func (hhs *HeaderHandlerStub) SetExecutionResultsHandlers(resultHandlers []data.BaseExecutionResultHandler) error { + if hhs.SetExecutionResultsHandlersCalled != nil { + return hhs.SetExecutionResultsHandlersCalled(resultHandlers) + } + + return nil +} + +// GetAccumulatedFeesInEpoch - +func (hhs *HeaderHandlerStub) GetAccumulatedFeesInEpoch() *big.Int { + return nil +} + +// SetEpochChangeProposed - +func (hhs *HeaderHandlerStub) SetEpochChangeProposed(_ bool) {} + +// IsEpochChangeProposed - +func (hhs *HeaderHandlerStub) IsEpochChangeProposed() bool { + if hhs.IsEpochChangeProposedCalled != nil { + return hhs.IsEpochChangeProposedCalled() + } + return false +} + +// GetGasLimit - +func (hhs *HeaderHandlerStub) GetGasLimit() uint32 { + if hhs.GetGasLimitCalled != nil { + return hhs.GetGasLimitCalled() + } + + return 0 +} + +// GetShardInfoProposalHandlers - +func (hhs *HeaderHandlerStub) GetShardInfoProposalHandlers() []data.ShardDataProposalHandler { + if hhs.GetShardInfoProposalHandlersCalled != nil { + return hhs.GetShardInfoProposalHandlersCalled() + } + return nil +} + +// SetShardInfoProposalHandlers - +func (hhs *HeaderHandlerStub) SetShardInfoProposalHandlers(shardInfo []data.ShardDataProposalHandler) error { + if hhs.SetShardInfoProposalHandlersCalled != nil { + return hhs.SetShardInfoProposalHandlersCalled(shardInfo) + } + return nil +} diff --git a/testscommon/headerVersionHandlerStub.go b/testscommon/headerVersionHandlerStub.go index 3f9241ff5dd..6f93007731a 100644 --- a/testscommon/headerVersionHandlerStub.go +++ b/testscommon/headerVersionHandlerStub.go @@ -4,15 +4,15 @@ import "github.com/multiversx/mx-chain-core-go/data" // HeaderVersionHandlerStub - type HeaderVersionHandlerStub struct { - GetVersionCalled func(epoch uint32) string + GetVersionCalled func(epoch uint32, round uint64) string VerifyCalled func(hdr data.HeaderHandler) error IsInterfaceNilCalled func() bool } // GetVersion - -func (hvm *HeaderVersionHandlerStub) GetVersion(epoch uint32) string { +func (hvm *HeaderVersionHandlerStub) GetVersion(epoch uint32, round uint64) string { if hvm.GetVersionCalled != nil { - return hvm.GetVersionCalled(epoch) + return hvm.GetVersionCalled(epoch, round) } return "*" } diff --git a/testscommon/headersForBlockMock.go b/testscommon/headersForBlockMock.go new file mode 100644 index 00000000000..270fb4db6e3 --- /dev/null +++ b/testscommon/headersForBlockMock.go @@ -0,0 +1,125 @@ +package testscommon + +import ( + "time" + + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/process/block/headerForBlock" +) + +// HeadersForBlockMock - +type HeadersForBlockMock struct { + AddHeaderUsedInBlockCalled func(hash string, header data.HeaderHandler) + AddHeaderNotUsedInBlockCalled func(hash string, header data.HeaderHandler) + RequestShardHeadersCalled func(metaBlock data.MetaHeaderHandler) + RequestMetaHeadersCalled func(shardHeader data.ShardHeaderHandler) + WaitForHeadersIfNeededCalled func(haveTime func() time.Duration) error + GetHeaderInfoCalled func(hash string) (headerForBlock.HeaderInfo, bool) + GetHeadersInfoMapCalled func() map[string]headerForBlock.HeaderInfo + GetHeadersMapCalled func() map[string]data.HeaderHandler + ComputeHeadersForCurrentBlockInfoCalled func(usedInBlock bool) (map[uint32][]headerForBlock.NonceAndHashInfo, error) + ComputeHeadersForCurrentBlockCalled func(usedInBlock bool) (map[uint32][]data.HeaderHandler, error) + GetMissingDataCalled func() (uint32, uint32, uint32) + ResetCalled func() +} + +// AddHeaderUsedInBlock - +func (mock *HeadersForBlockMock) AddHeaderUsedInBlock(hash string, header data.HeaderHandler) { + if mock.AddHeaderUsedInBlockCalled != nil { + mock.AddHeaderUsedInBlockCalled(hash, header) + } +} + +// AddHeaderNotUsedInBlock - +func (mock *HeadersForBlockMock) AddHeaderNotUsedInBlock(hash string, header data.HeaderHandler) { + if mock.AddHeaderNotUsedInBlockCalled != nil { + mock.AddHeaderNotUsedInBlockCalled(hash, header) + } +} + +// RequestShardHeaders - +func (mock *HeadersForBlockMock) RequestShardHeaders(metaBlock data.MetaHeaderHandler) { + if mock.RequestShardHeadersCalled != nil { + mock.RequestShardHeadersCalled(metaBlock) + } +} + +// RequestMetaHeaders - +func (mock *HeadersForBlockMock) RequestMetaHeaders(shardHeader data.ShardHeaderHandler) { + if mock.RequestMetaHeadersCalled != nil { + mock.RequestMetaHeadersCalled(shardHeader) + } +} + +// WaitForHeadersIfNeeded - +func (mock *HeadersForBlockMock) WaitForHeadersIfNeeded(haveTime func() time.Duration) error { + if mock.WaitForHeadersIfNeededCalled != nil { + return mock.WaitForHeadersIfNeededCalled(haveTime) + } + + return nil +} + +// GetHeaderInfo - +func (mock *HeadersForBlockMock) GetHeaderInfo(hash string) (headerForBlock.HeaderInfo, bool) { + if mock.GetHeaderInfoCalled != nil { + return mock.GetHeaderInfoCalled(hash) + } + + return nil, false +} + +// GetHeadersInfoMap - +func (mock *HeadersForBlockMock) GetHeadersInfoMap() map[string]headerForBlock.HeaderInfo { + if mock.GetHeadersInfoMapCalled != nil { + return mock.GetHeadersInfoMapCalled() + } + + return nil +} + +// GetHeadersMap - +func (mock *HeadersForBlockMock) GetHeadersMap() map[string]data.HeaderHandler { + if mock.GetHeadersMapCalled != nil { + return mock.GetHeadersMapCalled() + } + + return nil +} + +// ComputeHeadersForCurrentBlockInfo - +func (mock *HeadersForBlockMock) ComputeHeadersForCurrentBlockInfo(usedInBlock bool) (map[uint32][]headerForBlock.NonceAndHashInfo, error) { + if mock.ComputeHeadersForCurrentBlockInfoCalled != nil { + return mock.ComputeHeadersForCurrentBlockInfoCalled(usedInBlock) + } + return make(map[uint32][]headerForBlock.NonceAndHashInfo), nil +} + +// ComputeHeadersForCurrentBlock - +func (mock *HeadersForBlockMock) ComputeHeadersForCurrentBlock(usedInBlock bool) (map[uint32][]data.HeaderHandler, error) { + if mock.ComputeHeadersForCurrentBlockCalled != nil { + return mock.ComputeHeadersForCurrentBlockCalled(usedInBlock) + } + return make(map[uint32][]data.HeaderHandler), nil +} + +// GetMissingData - +func (mock *HeadersForBlockMock) GetMissingData() (uint32, uint32, uint32) { + if mock.GetMissingDataCalled != nil { + return mock.GetMissingDataCalled() + } + + return 0, 0, 0 +} + +// Reset - +func (mock *HeadersForBlockMock) Reset() { + if mock.ResetCalled != nil { + mock.ResetCalled() + } +} + +// IsInterfaceNil - +func (mock *HeadersForBlockMock) IsInterfaceNil() bool { + return mock == nil +} diff --git a/testscommon/headersForBlockMocks/crossShardMetaDataMock.go b/testscommon/headersForBlockMocks/crossShardMetaDataMock.go new file mode 100644 index 00000000000..799e4d2b64f --- /dev/null +++ b/testscommon/headersForBlockMocks/crossShardMetaDataMock.go @@ -0,0 +1,34 @@ +package headersForBlockMocks + +// CrossShardMetaDataMock - +type CrossShardMetaDataMock struct { + GetNonceCalled func() uint64 + GetShardIdCalled func() uint32 + GetHeaderHashCalled func() []byte +} + +// GetNonce - +func (crossShardMetaDataMock *CrossShardMetaDataMock) GetNonce() uint64 { + if crossShardMetaDataMock.GetNonceCalled != nil { + return crossShardMetaDataMock.GetNonceCalled() + } + + return 0 +} + +// GetShardID - +func (crossShardMetaDataMock *CrossShardMetaDataMock) GetShardID() uint32 { + if crossShardMetaDataMock.GetShardIdCalled != nil { + return crossShardMetaDataMock.GetShardIdCalled() + } + + return 0 +} + +// GetHeaderHash - +func (crossShardMetaDataMock *CrossShardMetaDataMock) GetHeaderHash() []byte { + if crossShardMetaDataMock.GetHeaderHashCalled != nil { + return crossShardMetaDataMock.GetHeaderHashCalled() + } + return nil +} diff --git a/testscommon/interceptedDataStub.go b/testscommon/interceptedDataStub.go index 4af55d5635e..38ff4930be8 100644 --- a/testscommon/interceptedDataStub.go +++ b/testscommon/interceptedDataStub.go @@ -2,12 +2,13 @@ package testscommon // InterceptedDataStub - type InterceptedDataStub struct { - CheckValidityCalled func() error - IsForCurrentShardCalled func() bool - HashCalled func() []byte - TypeCalled func() string - IdentifiersCalled func() [][]byte - StringCalled func() string + CheckValidityCalled func() error + IsForCurrentShardCalled func() bool + HashCalled func() []byte + TypeCalled func() string + IdentifiersCalled func() [][]byte + StringCalled func() string + ShouldAllowDuplicatesCalled func() bool } // CheckValidity - @@ -19,6 +20,15 @@ func (ids *InterceptedDataStub) CheckValidity() error { return nil } +// ShouldAllowDuplicates - +func (ids *InterceptedDataStub) ShouldAllowDuplicates() bool { + if ids.ShouldAllowDuplicatesCalled != nil { + return ids.ShouldAllowDuplicatesCalled() + } + + return true +} + // IsForCurrentShard - func (ids *InterceptedDataStub) IsForCurrentShard() bool { if ids.IsForCurrentShardCalled != nil { diff --git a/testscommon/mbSelection/mbSelectionSessionStub.go b/testscommon/mbSelection/mbSelectionSessionStub.go new file mode 100644 index 00000000000..fd3ac8f0d6a --- /dev/null +++ b/testscommon/mbSelection/mbSelectionSessionStub.go @@ -0,0 +1,121 @@ +package mbSelection + +import ( + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" +) + +// MiniBlockSelectionSessionStub - +type MiniBlockSelectionSessionStub struct { + ResetSelectionSessionCalled func() + GetMiniBlockHeaderHandlersCalled func() []data.MiniBlockHeaderHandler + GetMiniBlocksCalled func() block.MiniBlockSlice + GetMiniBlockHashesCalled func() [][]byte + AddReferencedHeaderCalled func(metaBlock data.HeaderHandler, metaBlockHash []byte) + GetReferencedHeaderHashesCalled func() [][]byte + GetReferencedHeadersCalled func() []data.HeaderHandler + GetLastHeaderCalled func() data.HeaderHandler + GetGasProvidedCalled func() uint64 + GetNumTxsAddedCalled func() uint32 + AddMiniBlocksAndHashesCalled func(miniBlocksAndHashes []block.MiniblockAndHash) error + CreateAndAddMiniBlockFromTransactionsCalled func(txHashes [][]byte) error +} + +// ResetSelectionSession - +func (mbss *MiniBlockSelectionSessionStub) ResetSelectionSession() { + if mbss.ResetSelectionSessionCalled != nil { + mbss.ResetSelectionSessionCalled() + } +} + +// GetMiniBlockHeaderHandlers - +func (mbss *MiniBlockSelectionSessionStub) GetMiniBlockHeaderHandlers() []data.MiniBlockHeaderHandler { + if mbss.GetMiniBlockHeaderHandlersCalled != nil { + return mbss.GetMiniBlockHeaderHandlersCalled() + } + return nil +} + +// GetMiniBlocks - +func (mbss *MiniBlockSelectionSessionStub) GetMiniBlocks() block.MiniBlockSlice { + if mbss.GetMiniBlocksCalled != nil { + return mbss.GetMiniBlocksCalled() + } + return nil +} + +// GetMiniBlockHashes - +func (mbss *MiniBlockSelectionSessionStub) GetMiniBlockHashes() [][]byte { + if mbss.GetMiniBlockHashesCalled != nil { + return mbss.GetMiniBlockHashesCalled() + } + return nil +} + +// AddReferencedHeader - +func (mbss *MiniBlockSelectionSessionStub) AddReferencedHeader(metaBlock data.HeaderHandler, metaBlockHash []byte) { + if mbss.AddReferencedHeaderCalled != nil { + mbss.AddReferencedHeaderCalled(metaBlock, metaBlockHash) + } +} + +// GetReferencedHeaderHashes - +func (mbss *MiniBlockSelectionSessionStub) GetReferencedHeaderHashes() [][]byte { + if mbss.GetReferencedHeaderHashesCalled != nil { + return mbss.GetReferencedHeaderHashesCalled() + } + return nil +} + +// GetReferencedHeaders - +func (mbss *MiniBlockSelectionSessionStub) GetReferencedHeaders() []data.HeaderHandler { + if mbss.GetReferencedHeadersCalled != nil { + return mbss.GetReferencedHeadersCalled() + } + return nil +} + +// GetLastHeader - +func (mbss *MiniBlockSelectionSessionStub) GetLastHeader() data.HeaderHandler { + if mbss.GetLastHeaderCalled != nil { + return mbss.GetLastHeaderCalled() + } + return nil +} + +// GetGasProvided - +func (mbss *MiniBlockSelectionSessionStub) GetGasProvided() uint64 { + if mbss.GetGasProvidedCalled != nil { + return mbss.GetGasProvidedCalled() + } + return 0 +} + +// GetNumTxsAdded - +func (mbss *MiniBlockSelectionSessionStub) GetNumTxsAdded() uint32 { + if mbss.GetNumTxsAddedCalled != nil { + return mbss.GetNumTxsAddedCalled() + } + return 0 +} + +// AddMiniBlocksAndHashes - +func (mbss *MiniBlockSelectionSessionStub) AddMiniBlocksAndHashes(miniBlocksAndHashes []block.MiniblockAndHash) error { + if mbss.AddMiniBlocksAndHashesCalled != nil { + return mbss.AddMiniBlocksAndHashesCalled(miniBlocksAndHashes) + } + return nil +} + +// CreateAndAddMiniBlockFromTransactions - +func (mbss *MiniBlockSelectionSessionStub) CreateAndAddMiniBlockFromTransactions(txHashes [][]byte) error { + if mbss.CreateAndAddMiniBlockFromTransactionsCalled != nil { + return mbss.CreateAndAddMiniBlockFromTransactionsCalled(txHashes) + } + return nil +} + +// IsInterfaceNil returns true if there is no value under the interface +func (mbss *MiniBlockSelectionSessionStub) IsInterfaceNil() bool { + return mbss == nil +} diff --git a/testscommon/memDbMock.go b/testscommon/memDbMock.go index 1ca6578e748..64d9ea1999f 100644 --- a/testscommon/memDbMock.go +++ b/testscommon/memDbMock.go @@ -19,6 +19,7 @@ type MemDbMock struct { GetCalled func(key []byte) ([]byte, error) GetIdentifierCalled func() string GetStateStatsHandlerCalled func() common.StateStatisticsHandler + GetCalledNoReturn func(key []byte) } // NewMemDbMock creates a new memorydb object @@ -53,6 +54,9 @@ func (s *MemDbMock) Get(key []byte) ([]byte, error) { if s.GetCalled != nil { return s.GetCalled(key) } + if s.GetCalledNoReturn != nil { + s.GetCalledNoReturn(key) + } if !ok { return nil, fmt.Errorf("key: %s not found", base64.StdEncoding.EncodeToString(key)) diff --git a/testscommon/preprocMocks/blockDataRequesterStub.go b/testscommon/preprocMocks/blockDataRequesterStub.go new file mode 100644 index 00000000000..698cd1b1904 --- /dev/null +++ b/testscommon/preprocMocks/blockDataRequesterStub.go @@ -0,0 +1,59 @@ +package preprocMocks + +import ( + "time" + + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" +) + +// BlockDataRequesterStub - +type BlockDataRequesterStub struct { + RequestBlockTransactionsCalled func(body *block.Body) + RequestMiniBlocksAndTransactionsCalled func(header data.HeaderHandler) + GetFinalCrossMiniBlockInfoAndRequestMissingCalled func(header data.HeaderHandler) []*data.MiniBlockInfo + IsDataPreparedForProcessingCalled func(haveTime func() time.Duration) error + ResetCalled func() +} + +// RequestBlockTransactions - +func (bdr *BlockDataRequesterStub) RequestBlockTransactions(body *block.Body) { + if bdr.RequestBlockTransactionsCalled != nil { + bdr.RequestBlockTransactionsCalled(body) + } +} + +// RequestMiniBlocksAndTransactions - +func (bdr *BlockDataRequesterStub) RequestMiniBlocksAndTransactions(header data.HeaderHandler) { + if bdr.RequestMiniBlocksAndTransactionsCalled != nil { + bdr.RequestMiniBlocksAndTransactionsCalled(header) + } +} + +// GetFinalCrossMiniBlockInfoAndRequestMissing - +func (bdr *BlockDataRequesterStub) GetFinalCrossMiniBlockInfoAndRequestMissing(header data.HeaderHandler) []*data.MiniBlockInfo { + if bdr.GetFinalCrossMiniBlockInfoAndRequestMissingCalled != nil { + return bdr.GetFinalCrossMiniBlockInfoAndRequestMissingCalled(header) + } + return nil +} + +// IsDataPreparedForProcessing - +func (bdr *BlockDataRequesterStub) IsDataPreparedForProcessing(haveTime func() time.Duration) error { + if bdr.IsDataPreparedForProcessingCalled != nil { + return bdr.IsDataPreparedForProcessingCalled(haveTime) + } + return nil +} + +// Reset - +func (bdr *BlockDataRequesterStub) Reset() { + if bdr.ResetCalled != nil { + bdr.ResetCalled() + } +} + +// IsInterfaceNil - +func (bdr *BlockDataRequesterStub) IsInterfaceNil() bool { + return bdr == nil +} diff --git a/process/mock/preProcessorContainerMock.go b/testscommon/preprocMocks/preProcessorContainerMock.go similarity index 98% rename from process/mock/preProcessorContainerMock.go rename to testscommon/preprocMocks/preProcessorContainerMock.go index 4284a0fd505..6d4e07d9a33 100644 --- a/process/mock/preProcessorContainerMock.go +++ b/testscommon/preprocMocks/preProcessorContainerMock.go @@ -1,7 +1,8 @@ -package mock +package preprocMocks import ( "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/process" ) diff --git a/process/mock/preprocessorMock.go b/testscommon/preprocMocks/preprocessorMock.go similarity index 54% rename from process/mock/preprocessorMock.go rename to testscommon/preprocMocks/preprocessorMock.go index f3f026abd57..b9ae12b4bd8 100644 --- a/process/mock/preprocessorMock.go +++ b/testscommon/preprocMocks/preprocessorMock.go @@ -1,31 +1,36 @@ -package mock +package preprocMocks import ( "time" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/storage" ) // PreProcessorMock - type PreProcessorMock struct { - CreateBlockStartedCalled func() - IsDataPreparedCalled func(requestedTxs int, haveTime func() time.Duration) error - RemoveBlockDataFromPoolsCalled func(body *block.Body, miniBlockPool storage.Cacher) error - RemoveTxsFromPoolsCalled func(body *block.Body) error - RestoreBlockDataIntoPoolsCalled func(body *block.Body, miniBlockPool storage.Cacher) (int, error) - SaveTxsToStorageCalled func(body *block.Body) error - ProcessBlockTransactionsCalled func(header data.HeaderHandler, body *block.Body, haveTime func() bool) error - RequestBlockTransactionsCalled func(body *block.Body) int - CreateMarshalledDataCalled func(txHashes [][]byte) ([][]byte, error) - RequestTransactionsForMiniBlockCalled func(miniBlock *block.MiniBlock) int - ProcessMiniBlockCalled func(miniBlock *block.MiniBlock, haveTime func() bool, haveAdditionalTime func() bool, scheduledMode bool, partialMbExecutionMode bool, indexOfLastTxProcessed int, preProcessorExecutionInfoHandler process.PreProcessorExecutionInfoHandler) ([][]byte, int, bool, error) - CreateAndProcessMiniBlocksCalled func(haveTime func() bool) (block.MiniBlockSlice, error) - GetAllCurrentUsedTxsCalled func() map[string]data.TransactionHandler - AddTxsFromMiniBlocksCalled func(miniBlocks block.MiniBlockSlice) - AddTransactionsCalled func(txHandlers []data.TransactionHandler) + CreateBlockStartedCalled func() + IsDataPreparedCalled func(requestedTxs int, haveTime func() time.Duration) error + RemoveBlockDataFromPoolsCalled func(body *block.Body, miniBlockPool storage.Cacher) error + RemoveTxsFromPoolsCalled func(body *block.Body, rootHashHolder common.RootHashHolder) error + RestoreBlockDataIntoPoolsCalled func(body *block.Body, miniBlockPool storage.Cacher) (int, error) + SaveTxsToStorageCalled func(body *block.Body) error + ProcessBlockTransactionsCalled func(header data.HeaderHandler, body *block.Body, haveTime func() bool) error + GetCreatedMiniBlocksFromMeCalled func() block.MiniBlockSlice + GetUnExecutableTransactionsCalled func() map[string]struct{} + RequestBlockTransactionsCalled func(body *block.Body) int + CreateMarshalledDataCalled func(txHashes [][]byte) ([][]byte, error) + GetTransactionsAndRequestMissingForMiniBlockCalled func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) + ProcessMiniBlockCalled func(miniBlock *block.MiniBlock, haveTime func() bool, haveAdditionalTime func() bool, scheduledMode bool, partialMbExecutionMode bool, indexOfLastTxProcessed int, preProcessorExecutionInfoHandler process.PreProcessorExecutionInfoHandler) ([][]byte, int, bool, error) + CreateAndProcessMiniBlocksCalled func(haveTime func() bool) (block.MiniBlockSlice, error) + SelectOutgoingTransactionsCalled func(bandwidth uint64, nonce uint64, haveTimeForSelection func() bool) ([][]byte, []data.TransactionHandler, error) + GetAllCurrentUsedTxsCalled func() map[string]data.TransactionHandler + AddTxsFromMiniBlocksCalled func(miniBlocks block.MiniBlockSlice) + AddTransactionsCalled func(txHandlers []data.TransactionHandler) } // CreateBlockStarted - @@ -53,11 +58,11 @@ func (ppm *PreProcessorMock) RemoveBlockDataFromPools(body *block.Body, miniBloc } // RemoveTxsFromPools - -func (ppm *PreProcessorMock) RemoveTxsFromPools(body *block.Body) error { +func (ppm *PreProcessorMock) RemoveTxsFromPools(body *block.Body, rootHashHolder common.RootHashHolder) error { if ppm.RemoveTxsFromPoolsCalled == nil { return nil } - return ppm.RemoveTxsFromPoolsCalled(body) + return ppm.RemoveTxsFromPoolsCalled(body, rootHashHolder) } // RestoreBlockDataIntoPools - @@ -84,6 +89,22 @@ func (ppm *PreProcessorMock) ProcessBlockTransactions(header data.HeaderHandler, return ppm.ProcessBlockTransactionsCalled(header, body, haveTime) } +// GetCreatedMiniBlocksFromMe - +func (ppm *PreProcessorMock) GetCreatedMiniBlocksFromMe() block.MiniBlockSlice { + if ppm.GetCreatedMiniBlocksFromMeCalled == nil { + return nil + } + return ppm.GetCreatedMiniBlocksFromMeCalled() +} + +// GetUnExecutableTransactions - +func (ppm *PreProcessorMock) GetUnExecutableTransactions() map[string]struct{} { + if ppm.GetUnExecutableTransactionsCalled == nil { + return nil + } + return ppm.GetUnExecutableTransactionsCalled() +} + // RequestBlockTransactions - func (ppm *PreProcessorMock) RequestBlockTransactions(body *block.Body) int { if ppm.RequestBlockTransactionsCalled == nil { @@ -100,12 +121,12 @@ func (ppm *PreProcessorMock) CreateMarshalledData(txHashes [][]byte) ([][]byte, return ppm.CreateMarshalledDataCalled(txHashes) } -// RequestTransactionsForMiniBlock - -func (ppm *PreProcessorMock) RequestTransactionsForMiniBlock(miniBlock *block.MiniBlock) int { - if ppm.RequestTransactionsForMiniBlockCalled == nil { - return 0 +// GetTransactionsAndRequestMissingForMiniBlock - +func (ppm *PreProcessorMock) GetTransactionsAndRequestMissingForMiniBlock(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + if ppm.GetTransactionsAndRequestMissingForMiniBlockCalled == nil { + return make([]data.TransactionHandler, 0), 0 } - return ppm.RequestTransactionsForMiniBlockCalled(miniBlock) + return ppm.GetTransactionsAndRequestMissingForMiniBlockCalled(miniBlock) } // ProcessMiniBlock - @@ -124,6 +145,18 @@ func (ppm *PreProcessorMock) ProcessMiniBlock( return ppm.ProcessMiniBlockCalled(miniBlock, haveTime, haveAdditionalTime, scheduledMode, partialMbExecutionMode, indexOfLastTxProcessed, preProcessorExecutionInfoHandler) } +// SelectOutgoingTransactions selects the outgoing transactions +func (ppm *PreProcessorMock) SelectOutgoingTransactions( + bandwidth uint64, + nonce uint64, + haveTimeForSelection func() bool, +) ([][]byte, []data.TransactionHandler, error) { + if ppm.SelectOutgoingTransactionsCalled == nil { + return nil, nil, nil + } + return ppm.SelectOutgoingTransactionsCalled(bandwidth, nonce, haveTimeForSelection) +} + // CreateAndProcessMiniBlocks creates miniblocks from storage and processes the reward transactions added into the miniblocks // as long as it has time func (ppm *PreProcessorMock) CreateAndProcessMiniBlocks(haveTime func() bool, _ []byte) (block.MiniBlockSlice, error) { diff --git a/testscommon/preprocMocks/txsForBlockStub.go b/testscommon/preprocMocks/txsForBlockStub.go new file mode 100644 index 00000000000..2c76923a98b --- /dev/null +++ b/testscommon/preprocMocks/txsForBlockStub.go @@ -0,0 +1,98 @@ +package preprocMocks + +import ( + "time" + + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + + "github.com/multiversx/mx-chain-go/dataRetriever" + "github.com/multiversx/mx-chain-go/process" +) + +// TxsForBlockStub - +type TxsForBlockStub struct { + ResetCalled func() + AddTransactionCalled func(txHash []byte, tx data.TransactionHandler, senderShardID uint32, receiverShardID uint32) + WaitForRequestedDataCalled func(waitTime time.Duration) error + GetTxInfoByHashCalled func(hash []byte) (*process.TxInfo, bool) + GetAllCurrentUsedTxsCalled func() map[string]data.TransactionHandler + GetMissingTxsCountCalled func() int + ReceivedTransactionCalled func(txHash []byte, tx data.TransactionHandler) + HasMissingTransactionsCalled func() bool + ComputeExistingAndRequestMissingCalled func(body *block.Body, isMiniBlockCorrect func(block.Type) bool, txPool dataRetriever.ShardedDataCacherNotifier, onRequestTxs func(shardID uint32, txHashes [][]byte)) int +} + +// Reset - +func (tfbs *TxsForBlockStub) Reset() { + if tfbs.ResetCalled != nil { + tfbs.ResetCalled() + } +} + +// AddTransaction - +func (tfbs *TxsForBlockStub) AddTransaction(txHash []byte, tx data.TransactionHandler, senderShardID uint32, receiverShardID uint32) { + if tfbs.AddTransactionCalled != nil { + tfbs.AddTransactionCalled(txHash, tx, senderShardID, receiverShardID) + } +} + +// WaitForRequestedData - +func (tfbs *TxsForBlockStub) WaitForRequestedData(waitTime time.Duration) error { + if tfbs.WaitForRequestedDataCalled != nil { + return tfbs.WaitForRequestedDataCalled(waitTime) + } + return nil +} + +// GetTxInfoByHash - +func (tfbs *TxsForBlockStub) GetTxInfoByHash(hash []byte) (*process.TxInfo, bool) { + if tfbs.GetTxInfoByHashCalled != nil { + return tfbs.GetTxInfoByHashCalled(hash) + } + return nil, false +} + +// GetAllCurrentUsedTxs - +func (tfbs *TxsForBlockStub) GetAllCurrentUsedTxs() map[string]data.TransactionHandler { + if tfbs.GetAllCurrentUsedTxsCalled != nil { + return tfbs.GetAllCurrentUsedTxsCalled() + } + return nil +} + +// GetMissingTxsCount - +func (tfbs *TxsForBlockStub) GetMissingTxsCount() int { + if tfbs.GetMissingTxsCountCalled != nil { + return tfbs.GetMissingTxsCountCalled() + } + return 0 +} + +// ReceivedTransaction - +func (tfbs *TxsForBlockStub) ReceivedTransaction(txHash []byte, tx data.TransactionHandler) { + if tfbs.ReceivedTransactionCalled != nil { + tfbs.ReceivedTransactionCalled(txHash, tx) + } +} + +// HasMissingTransactions - +func (tfbs *TxsForBlockStub) HasMissingTransactions() bool { + if tfbs.HasMissingTransactionsCalled != nil { + return tfbs.HasMissingTransactionsCalled() + } + return false +} + +// ComputeExistingAndRequestMissing - +func (tfbs *TxsForBlockStub) ComputeExistingAndRequestMissing(body *block.Body, isMiniBlockCorrect func(block.Type) bool, txPool dataRetriever.ShardedDataCacherNotifier, onRequestTxs func(shardID uint32, txHashes [][]byte)) int { + if tfbs.ComputeExistingAndRequestMissingCalled != nil { + return tfbs.ComputeExistingAndRequestMissingCalled(body, isMiniBlockCorrect, txPool, onRequestTxs) + } + return 0 +} + +// IsInterfaceNil returns true if there is no value under the interface +func (tfbs *TxsForBlockStub) IsInterfaceNil() bool { + return tfbs == nil +} diff --git a/testscommon/processConfigsHandlerStub.go b/testscommon/processConfigsHandlerStub.go new file mode 100644 index 00000000000..542a88e0f25 --- /dev/null +++ b/testscommon/processConfigsHandlerStub.go @@ -0,0 +1,170 @@ +package testscommon + +import ( + "time" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/common/configs" + "github.com/multiversx/mx-chain-go/common/configs/dto" + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" +) + +// GetDefaultProcessConfigsHandler - +func GetDefaultProcessConfigsHandler() common.ProcessConfigsHandler { + processConfigsHandler, _ := configs.NewProcessConfigsHandler([]config.ProcessConfigByEpoch{{ + EnableEpoch: 0, + MaxMetaNoncesBehind: 15, + MaxMetaNoncesBehindForGlobalStuck: 30, + MaxShardNoncesBehind: 15, + }}, + []config.ProcessConfigByRound{ + { + EnableRound: 0, + MaxRoundsWithoutNewBlockReceived: 10, + MaxRoundsWithoutCommittedBlock: 10, + RoundModulusTriggerWhenSyncIsStuck: 20, + MaxSyncWithErrorsAllowed: 10, + MaxRoundsToKeepUnprocessedMiniBlocks: 50, + MaxRoundsToKeepUnprocessedTransactions: 50, + NumFloodingRoundsSlowReacting: 20, + NumFloodingRoundsFastReacting: 30, + NumFloodingRoundsOutOfSpecs: 40, + MaxConsecutiveRoundsOfRatingDecrease: 600, + MaxBlockProcessingTimeMs: 1000, + NumHeadersToRequestInAdvance: 10, + }, + }, + &epochNotifier.RoundNotifierStub{}, + ) + + return processConfigsHandler +} + +// ProcessConfigsHandlerStub - +type ProcessConfigsHandlerStub struct { + GetMaxMetaNoncesBehindByEpochCalled func(epoch uint32) uint32 + GetMaxMetaNoncesBehindForGlobalStuckByEpochCalled func(epoch uint32) uint32 + GetMaxShardNoncesBehindByEpochCalled func(epoch uint32) uint32 + GetMaxRoundsWithoutNewBlockReceivedByRoundCalled func(round uint64) uint32 + GetMaxRoundsWithoutCommittedBlockCalled func(round uint64) uint32 + GetRoundModulusTriggerWhenSyncIsStuckCalled func(round uint64) uint32 + GetMaxSyncWithErrorsAllowedCalled func(round uint64) uint32 + GetMaxRoundsToKeepUnprocessedTransactionsCalled func(round uint64) uint64 + GetMaxRoundsToKeepUnprocessedMiniBlocksCalled func(round uint64) uint64 + GetValueCalled func(variable dto.ConfigVariable) uint64 + GetMaxBlockProcessingTimeCalled func(round uint64) time.Duration + GetNumHeadersToRequestInAdvanceCalled func(round uint64) uint64 +} + +// GetMaxMetaNoncesBehindByEpoch - +func (p *ProcessConfigsHandlerStub) GetMaxMetaNoncesBehindByEpoch(epoch uint32) uint32 { + if p.GetMaxMetaNoncesBehindByEpochCalled != nil { + return p.GetMaxMetaNoncesBehindByEpochCalled(epoch) + } + + return 0 +} + +// GetMaxMetaNoncesBehindForGlobalStuckByEpoch - +func (p *ProcessConfigsHandlerStub) GetMaxMetaNoncesBehindForGlobalStuckByEpoch(epoch uint32) uint32 { + if p.GetMaxMetaNoncesBehindForGlobalStuckByEpochCalled != nil { + return p.GetMaxMetaNoncesBehindForGlobalStuckByEpochCalled(epoch) + } + + return 0 +} + +// GetMaxShardNoncesBehindByEpoch - +func (p *ProcessConfigsHandlerStub) GetMaxShardNoncesBehindByEpoch(epoch uint32) uint32 { + if p.GetMaxShardNoncesBehindByEpochCalled != nil { + return p.GetMaxShardNoncesBehindByEpochCalled(epoch) + } + + return 0 +} + +// GetMaxRoundsWithoutNewBlockReceivedByRound - +func (p *ProcessConfigsHandlerStub) GetMaxRoundsWithoutNewBlockReceivedByRound(round uint64) uint32 { + if p.GetMaxRoundsWithoutNewBlockReceivedByRoundCalled != nil { + return p.GetMaxRoundsWithoutNewBlockReceivedByRoundCalled(round) + } + + return 0 +} + +// GetMaxRoundsWithoutCommittedBlock - +func (p *ProcessConfigsHandlerStub) GetMaxRoundsWithoutCommittedBlock(round uint64) uint32 { + if p.GetMaxRoundsWithoutCommittedBlockCalled != nil { + return p.GetMaxRoundsWithoutCommittedBlockCalled(round) + } + + return 0 +} + +// GetRoundModulusTriggerWhenSyncIsStuck - +func (p *ProcessConfigsHandlerStub) GetRoundModulusTriggerWhenSyncIsStuck(round uint64) uint32 { + if p.GetRoundModulusTriggerWhenSyncIsStuckCalled != nil { + return p.GetRoundModulusTriggerWhenSyncIsStuckCalled(round) + } + + return 0 +} + +// GetMaxSyncWithErrorsAllowed - +func (p *ProcessConfigsHandlerStub) GetMaxSyncWithErrorsAllowed(round uint64) uint32 { + if p.GetMaxSyncWithErrorsAllowedCalled != nil { + return p.GetMaxSyncWithErrorsAllowedCalled(round) + } + + return 0 +} + +// GetMaxRoundsToKeepUnprocessedTransactions - +func (p *ProcessConfigsHandlerStub) GetMaxRoundsToKeepUnprocessedTransactions(round uint64) uint64 { + if p.GetMaxRoundsToKeepUnprocessedTransactionsCalled != nil { + return p.GetMaxRoundsToKeepUnprocessedTransactionsCalled(round) + } + return 0 +} + +// GetMaxRoundsToKeepUnprocessedMiniBlocks - +func (p *ProcessConfigsHandlerStub) GetMaxRoundsToKeepUnprocessedMiniBlocks(round uint64) uint64 { + if p.GetMaxRoundsToKeepUnprocessedMiniBlocksCalled != nil { + return p.GetMaxRoundsToKeepUnprocessedMiniBlocksCalled(round) + } + + return 0 +} + +// GetValue - +func (p *ProcessConfigsHandlerStub) GetValue(variable dto.ConfigVariable) uint64 { + if p.GetValueCalled != nil { + return p.GetValueCalled(variable) + } + + return 1 +} + +// GetMaxBlockProcessingTime - +func (p *ProcessConfigsHandlerStub) GetMaxBlockProcessingTime(round uint64) time.Duration { + if p.GetMaxBlockProcessingTimeCalled != nil { + return p.GetMaxBlockProcessingTimeCalled(round) + } + + return time.Millisecond +} + +// GetNumHeadersToRequestInAdvance - +func (p *ProcessConfigsHandlerStub) GetNumHeadersToRequestInAdvance(round uint64) uint64 { + if p.GetNumHeadersToRequestInAdvanceCalled != nil { + return p.GetNumHeadersToRequestInAdvanceCalled(round) + } + + return 10 +} + +// IsInterfaceNil - +func (p *ProcessConfigsHandlerStub) IsInterfaceNil() bool { + return p == nil +} diff --git a/testscommon/processMocks/blockProcessorStub.go b/testscommon/processMocks/blockProcessorStub.go new file mode 100644 index 00000000000..ddf34cdf24b --- /dev/null +++ b/testscommon/processMocks/blockProcessorStub.go @@ -0,0 +1,48 @@ +package processMocks + +import "github.com/multiversx/mx-chain-core-go/data" + +// BlockProcessorStub - +type BlockProcessorStub struct { + ProcessBlockProposalCalled func(handler data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) + CommitBlockProposalStateCalled func(headerHandler data.HeaderHandler) error + RevertBlockProposalStateCalled func() + PruneTrieAsyncHeaderCalled func() +} + +// ProcessBlockProposal - +func (bp *BlockProcessorStub) ProcessBlockProposal(header data.HeaderHandler, headerHash []byte, body data.BodyHandler) (data.BaseExecutionResultHandler, error) { + if bp.ProcessBlockProposalCalled != nil { + return bp.ProcessBlockProposalCalled(header, headerHash, body) + } + + return nil, nil +} + +// CommitBlockProposalState - +func (bp *BlockProcessorStub) CommitBlockProposalState(headerHandler data.HeaderHandler) error { + if bp.CommitBlockProposalStateCalled != nil { + return bp.CommitBlockProposalStateCalled(headerHandler) + } + + return nil +} + +// RevertBlockProposalState - +func (bp *BlockProcessorStub) RevertBlockProposalState() { + if bp.RevertBlockProposalStateCalled != nil { + bp.RevertBlockProposalStateCalled() + } +} + +// PruneTrieAsyncHeader - +func (bp *BlockProcessorStub) PruneTrieAsyncHeader() { + if bp.PruneTrieAsyncHeaderCalled != nil { + bp.PruneTrieAsyncHeaderCalled() + } +} + +// IsInterfaceNil - +func (bp *BlockProcessorStub) IsInterfaceNil() bool { + return bp == nil +} diff --git a/testscommon/processMocks/blocksCacheMock.go b/testscommon/processMocks/blocksCacheMock.go new file mode 100644 index 00000000000..8a3c5d77e11 --- /dev/null +++ b/testscommon/processMocks/blocksCacheMock.go @@ -0,0 +1,54 @@ +package processMocks + +import ( + "github.com/multiversx/mx-chain-go/process/asyncExecution/cache" +) + +// BlocksCacheMock is a mock implementation of the BlocksCache interface +type BlocksCacheMock struct { + AddOrReplaceCalled func(pair cache.HeaderBodyPair) error + RemoveAtNonceAndHigherCalled func(nonce uint64) []uint64 + CleanCalled func() + GetByNonceCalled func(nonce uint64) (cache.HeaderBodyPair, bool) +} + +// GetByNonce - +func (bqm *BlocksCacheMock) GetByNonce(nonce uint64) (cache.HeaderBodyPair, bool) { + if bqm.GetByNonceCalled != nil { + return bqm.GetByNonceCalled(nonce) + } + + return cache.HeaderBodyPair{}, false +} + +// Remove - +func (bqm *BlocksCacheMock) Remove(_ uint64) { +} + +// AddOrReplace - +func (bqm *BlocksCacheMock) AddOrReplace(pair cache.HeaderBodyPair) error { + if bqm.AddOrReplaceCalled != nil { + return bqm.AddOrReplaceCalled(pair) + } + return nil +} + +// RemoveAtNonceAndHigher - +func (bqm *BlocksCacheMock) RemoveAtNonceAndHigher(nonce uint64) []uint64 { + if bqm.RemoveAtNonceAndHigherCalled != nil { + return bqm.RemoveAtNonceAndHigherCalled(nonce) + } + return nil +} + +// Clean - +func (bqm *BlocksCacheMock) Clean() { + if bqm.CleanCalled != nil { + bqm.CleanCalled() + } +} + +// IsInterfaceNil - +func (bqm *BlocksCacheMock) IsInterfaceNil() bool { + return bqm == nil +} diff --git a/testscommon/processMocks/executionManagerMock.go b/testscommon/processMocks/executionManagerMock.go new file mode 100644 index 00000000000..f4f3e42c9f1 --- /dev/null +++ b/testscommon/processMocks/executionManagerMock.go @@ -0,0 +1,142 @@ +package processMocks + +import ( + "github.com/multiversx/mx-chain-core-go/data" + + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/asyncExecution/cache" + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionTrack" +) + +// ExecutionManagerMock is a mock implementation of the ExecutionManager interface +type ExecutionManagerMock struct { + StartExecutionCalled func() + SetHeadersExecutorCalled func(executor process.HeadersExecutor) error + AddPairForExecutionCalled func(pair cache.HeaderBodyPair) error + GetPendingExecutionResultsCalled func() ([]data.BaseExecutionResultHandler, error) + CleanConfirmedExecutionResultsCalled func(header data.HeaderHandler) error + CleanOnConsensusReachedCalled func(headerHash []byte, header data.HeaderHandler) + SetLastNotarizedResultCalled func(executionResult data.BaseExecutionResultHandler) error + RemoveAtNonceAndHigherCalled func(nonce uint64) error + ResetAndResumeExecutionCalled func(lastNotarizedResult data.BaseExecutionResultHandler) error + GetLastNotarizedExecutionResultCalled func() (data.BaseExecutionResultHandler, error) + RemovePendingExecutionResultsFromNonceCalled func(nonce uint64) error + GetSignalProcessCompletionChanCalled func() chan uint64 + PopDismissedResultsCalled func() []executionTrack.DismissedBatch + CloseCalled func() error +} + +// StartExecution - +func (emm *ExecutionManagerMock) StartExecution() { + if emm.StartExecutionCalled != nil { + emm.StartExecutionCalled() + } +} + +// SetHeadersExecutor - +func (emm *ExecutionManagerMock) SetHeadersExecutor(executor process.HeadersExecutor) error { + if emm.SetHeadersExecutorCalled != nil { + return emm.SetHeadersExecutorCalled(executor) + } + return nil +} + +// AddPairForExecution - +func (emm *ExecutionManagerMock) AddPairForExecution(pair cache.HeaderBodyPair) error { + if emm.AddPairForExecutionCalled != nil { + return emm.AddPairForExecutionCalled(pair) + } + return nil +} + +// GetPendingExecutionResults - +func (emm *ExecutionManagerMock) GetPendingExecutionResults() ([]data.BaseExecutionResultHandler, error) { + if emm.GetPendingExecutionResultsCalled != nil { + return emm.GetPendingExecutionResultsCalled() + } + return nil, nil +} + +// CleanConfirmedExecutionResults - +func (emm *ExecutionManagerMock) CleanConfirmedExecutionResults(header data.HeaderHandler) error { + if emm.CleanConfirmedExecutionResultsCalled != nil { + return emm.CleanConfirmedExecutionResultsCalled(header) + } + return nil +} + +// CleanOnConsensusReached - +func (emm *ExecutionManagerMock) CleanOnConsensusReached(headerHash []byte, header data.HeaderHandler) { + if emm.CleanOnConsensusReachedCalled != nil { + emm.CleanOnConsensusReachedCalled(headerHash, header) + } +} + +// SetLastNotarizedResult - +func (emm *ExecutionManagerMock) SetLastNotarizedResult(executionResult data.BaseExecutionResultHandler) error { + if emm.SetLastNotarizedResultCalled != nil { + return emm.SetLastNotarizedResultCalled(executionResult) + } + return nil +} + +// RemoveAtNonceAndHigher - +func (emm *ExecutionManagerMock) RemoveAtNonceAndHigher(nonce uint64) error { + if emm.RemoveAtNonceAndHigherCalled != nil { + return emm.RemoveAtNonceAndHigherCalled(nonce) + } + return nil +} + +// ResetAndResumeExecution - +func (emm *ExecutionManagerMock) ResetAndResumeExecution(lastNotarizedResult data.BaseExecutionResultHandler) error { + if emm.ResetAndResumeExecutionCalled != nil { + return emm.ResetAndResumeExecutionCalled(lastNotarizedResult) + } + return nil +} + +// GetLastNotarizedExecutionResult - +func (emm *ExecutionManagerMock) GetLastNotarizedExecutionResult() (data.BaseExecutionResultHandler, error) { + if emm.GetLastNotarizedExecutionResultCalled != nil { + return emm.GetLastNotarizedExecutionResultCalled() + } + return nil, nil +} + +// RemovePendingExecutionResultsFromNonce - +func (emm *ExecutionManagerMock) RemovePendingExecutionResultsFromNonce(nonce uint64) error { + if emm.RemovePendingExecutionResultsFromNonceCalled != nil { + return emm.RemovePendingExecutionResultsFromNonceCalled(nonce) + } + return nil +} + +// GetSignalProcessCompletionChan - +func (emm *ExecutionManagerMock) GetSignalProcessCompletionChan() chan uint64 { + if emm.GetSignalProcessCompletionChanCalled != nil { + return emm.GetSignalProcessCompletionChanCalled() + } + return nil +} + +// Close - +func (emm *ExecutionManagerMock) Close() error { + if emm.CloseCalled != nil { + return emm.CloseCalled() + } + return nil +} + +// PopDismissedResults - +func (emm *ExecutionManagerMock) PopDismissedResults() []executionTrack.DismissedBatch { + if emm.PopDismissedResultsCalled != nil { + return emm.PopDismissedResultsCalled() + } + return nil +} + +// IsInterfaceNil - +func (emm *ExecutionManagerMock) IsInterfaceNil() bool { + return emm == nil +} diff --git a/testscommon/processMocks/executionResultsVerifierMock.go b/testscommon/processMocks/executionResultsVerifierMock.go new file mode 100644 index 00000000000..9de75836d83 --- /dev/null +++ b/testscommon/processMocks/executionResultsVerifierMock.go @@ -0,0 +1,21 @@ +package processMocks + +import "github.com/multiversx/mx-chain-core-go/data" + +// ExecutionResultsVerifierMock - +type ExecutionResultsVerifierMock struct { + VerifyHeaderExecutionResultsCalled func(header data.HeaderHandler) error +} + +// VerifyHeaderExecutionResults - +func (mock *ExecutionResultsVerifierMock) VerifyHeaderExecutionResults(header data.HeaderHandler) error { + if mock.VerifyHeaderExecutionResultsCalled != nil { + return mock.VerifyHeaderExecutionResultsCalled(header) + } + return nil +} + +// IsInterfaceNil - +func (mock *ExecutionResultsVerifierMock) IsInterfaceNil() bool { + return mock == nil +} diff --git a/testscommon/processMocks/executionTrackerStub.go b/testscommon/processMocks/executionTrackerStub.go new file mode 100644 index 00000000000..1e6f896cc1f --- /dev/null +++ b/testscommon/processMocks/executionTrackerStub.go @@ -0,0 +1,122 @@ +package processMocks + +import ( + "github.com/multiversx/mx-chain-core-go/data" + + "github.com/multiversx/mx-chain-go/process/asyncExecution/executionTrack" +) + +// ExecutionTrackerStub - +type ExecutionTrackerStub struct { + AddExecutionResultCalled func(executionResult data.BaseExecutionResultHandler) (bool, error) + GetPendingExecutionResultsCalled func() ([]data.BaseExecutionResultHandler, error) + GetPendingExecutionResultByHashCalled func(hash []byte) (data.BaseExecutionResultHandler, error) + GetPendingExecutionResultByNonceCalled func(nonce uint64) (data.BaseExecutionResultHandler, error) + GetLastNotarizedExecutionResultCalled func() (data.BaseExecutionResultHandler, error) + SetLastNotarizedResultCalled func(executionResult data.BaseExecutionResultHandler) error + RemoveFromNonceCalled func(nonce uint64) error + CleanCalled func(lastNotarizedResult data.BaseExecutionResultHandler) + CleanConfirmedExecutionResultsCalled func(header data.HeaderHandler) error + CleanOnConsensusReachedCalled func(headerHash []byte, header data.HeaderHandler) + PopDismissedResultsCalled func() []executionTrack.DismissedBatch +} + +// PopDismissedResults - +func (e *ExecutionTrackerStub) PopDismissedResults() []executionTrack.DismissedBatch { + if e.PopDismissedResultsCalled != nil { + return e.PopDismissedResultsCalled() + } + + return nil +} + +// AddExecutionResult - +func (e *ExecutionTrackerStub) AddExecutionResult(executionResult data.BaseExecutionResultHandler) (bool, error) { + if e.AddExecutionResultCalled != nil { + return e.AddExecutionResultCalled(executionResult) + } + + return true, nil +} + +// GetPendingExecutionResults - +func (e *ExecutionTrackerStub) GetPendingExecutionResults() ([]data.BaseExecutionResultHandler, error) { + if e.GetPendingExecutionResultsCalled != nil { + return e.GetPendingExecutionResultsCalled() + } + + return nil, nil +} + +// GetPendingExecutionResultByHash - +func (e *ExecutionTrackerStub) GetPendingExecutionResultByHash(hash []byte) (data.BaseExecutionResultHandler, error) { + if e.GetPendingExecutionResultByHashCalled != nil { + return e.GetPendingExecutionResultByHashCalled(hash) + } + + return nil, nil +} + +// GetPendingExecutionResultByNonce - +func (e *ExecutionTrackerStub) GetPendingExecutionResultByNonce(nonce uint64) (data.BaseExecutionResultHandler, error) { + if e.GetPendingExecutionResultByNonceCalled != nil { + return e.GetPendingExecutionResultByNonceCalled(nonce) + } + + return nil, nil +} + +// GetLastNotarizedExecutionResult - +func (e *ExecutionTrackerStub) GetLastNotarizedExecutionResult() (data.BaseExecutionResultHandler, error) { + if e.GetLastNotarizedExecutionResultCalled != nil { + return e.GetLastNotarizedExecutionResultCalled() + } + + return nil, nil +} + +// SetLastNotarizedResult - +func (e *ExecutionTrackerStub) SetLastNotarizedResult(executionResult data.BaseExecutionResultHandler) error { + if e.SetLastNotarizedResultCalled != nil { + return e.SetLastNotarizedResultCalled(executionResult) + } + + return nil +} + +// RemoveFromNonce - +func (e *ExecutionTrackerStub) RemoveFromNonce(nonce uint64) error { + if e.RemoveFromNonceCalled != nil { + return e.RemoveFromNonceCalled(nonce) + } + + return nil +} + +// Clean - +func (e *ExecutionTrackerStub) Clean(lastNotarizedResult data.BaseExecutionResultHandler) { + if e.CleanCalled != nil { + e.CleanCalled(lastNotarizedResult) + } +} + +// CleanConfirmedExecutionResults - +func (e *ExecutionTrackerStub) CleanConfirmedExecutionResults(header data.HeaderHandler) error { + if e.CleanConfirmedExecutionResultsCalled != nil { + return e.CleanConfirmedExecutionResultsCalled(header) + } + + return nil +} + +// CleanOnConsensusReached - +func (e *ExecutionTrackerStub) CleanOnConsensusReached(headerHash []byte, header data.HeaderHandler) { + if e.CleanOnConsensusReachedCalled != nil { + e.CleanOnConsensusReachedCalled(headerHash, header) + } +} + +// IsInterfaceNil - +func (e *ExecutionTrackerStub) IsInterfaceNil() bool { + return e == nil +} diff --git a/testscommon/processMocks/gracePeriodErrStub.go b/testscommon/processMocks/gracePeriodErrStub.go new file mode 100644 index 00000000000..79627f99fb1 --- /dev/null +++ b/testscommon/processMocks/gracePeriodErrStub.go @@ -0,0 +1,13 @@ +package processMocks + +import "errors" + +type GracePeriodErrStub struct{} + +// GetGracePeriodForEpoch always returns an error. +func (GracePeriodErrStub) GetGracePeriodForEpoch(_ uint32) (uint32, error) { + return 0, errors.New("epochChangeGracePeriodHandler forced error") +} + +// IsInterfaceNil - +func (GracePeriodErrStub) IsInterfaceNil() bool { return false } diff --git a/testscommon/processMocks/headerValidatorMock.go b/testscommon/processMocks/headerValidatorMock.go new file mode 100644 index 00000000000..75001804334 --- /dev/null +++ b/testscommon/processMocks/headerValidatorMock.go @@ -0,0 +1,21 @@ +package processMocks + +import "github.com/multiversx/mx-chain-core-go/data" + +// HeaderValidatorMock - +type HeaderValidatorMock struct { + IsHeaderConstructionValidCalled func(currHdr, prevHdr data.HeaderHandler) error +} + +// IsHeaderConstructionValid - +func (hvm *HeaderValidatorMock) IsHeaderConstructionValid(currHdr, prevHdr data.HeaderHandler) error { + if hvm.IsHeaderConstructionValidCalled != nil { + return hvm.IsHeaderConstructionValidCalled(currHdr, prevHdr) + } + return nil +} + +// IsInterfaceNil returns if underlying object is true +func (hvm *HeaderValidatorMock) IsInterfaceNil() bool { + return hvm == nil +} diff --git a/testscommon/processMocks/headersExecutorMock.go b/testscommon/processMocks/headersExecutorMock.go new file mode 100644 index 00000000000..5ae86e9a59e --- /dev/null +++ b/testscommon/processMocks/headersExecutorMock.go @@ -0,0 +1,52 @@ +package processMocks + +// HeadersExecutorMock - +type HeadersExecutorMock struct { + StartExecutionCalled func() + PauseExecutionCalled func() + ResumeExecutionCalled func() + GetSignalProcessCompletionChanCalled func() chan uint64 + CloseCalled func() error +} + +// StartExecution - +func (mock *HeadersExecutorMock) StartExecution() { + if mock.StartExecutionCalled != nil { + mock.StartExecutionCalled() + } +} + +// PauseExecution - +func (mock *HeadersExecutorMock) PauseExecution() { + if mock.PauseExecutionCalled != nil { + mock.PauseExecutionCalled() + } +} + +// ResumeExecution - +func (mock *HeadersExecutorMock) ResumeExecution() { + if mock.ResumeExecutionCalled != nil { + mock.ResumeExecutionCalled() + } +} + +// GetSignalProcessCompletionChan - +func (mock *HeadersExecutorMock) GetSignalProcessCompletionChan() chan uint64 { + if mock.GetSignalProcessCompletionChanCalled != nil { + return mock.GetSignalProcessCompletionChanCalled() + } + return nil +} + +// Close - +func (mock *HeadersExecutorMock) Close() error { + if mock.CloseCalled != nil { + return mock.CloseCalled() + } + return nil +} + +// IsInterfaceNil - +func (mock *HeadersExecutorMock) IsInterfaceNil() bool { + return mock == nil +} diff --git a/testscommon/processMocks/inclusionEstimatorMock.go b/testscommon/processMocks/inclusionEstimatorMock.go new file mode 100644 index 00000000000..74da17ae980 --- /dev/null +++ b/testscommon/processMocks/inclusionEstimatorMock.go @@ -0,0 +1,25 @@ +package processMocks + +import ( + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/common" +) + +// InclusionEstimatorMock - +type InclusionEstimatorMock struct { + DecideCalled func(lastNotarised *common.LastExecutionResultForInclusion, pending []data.BaseExecutionResultHandler, currentHdrTsMs uint64) (allowed int) +} + +// Decide - +func (mock *InclusionEstimatorMock) Decide(lastNotarised *common.LastExecutionResultForInclusion, pending []data.BaseExecutionResultHandler, currentHdrTsMs uint64) (allowed int) { + if mock.DecideCalled != nil { + return mock.DecideCalled(lastNotarised, pending, currentHdrTsMs) + } + + return 0 +} + +// IsInterfaceNil - +func (mock *InclusionEstimatorMock) IsInterfaceNil() bool { + return mock == nil +} diff --git a/testscommon/processMocks/missingDataResolverMock.go b/testscommon/processMocks/missingDataResolverMock.go new file mode 100644 index 00000000000..d6d7531e9ea --- /dev/null +++ b/testscommon/processMocks/missingDataResolverMock.go @@ -0,0 +1,86 @@ +package processMocks + +import ( + "time" + + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" +) + +// MissingDataResolverMock - +type MissingDataResolverMock struct { + RequestMissingMetaHeadersBlockingCalled func(shardHeader data.ShardHeaderHandler, timeout time.Duration) error + RequestMissingMetaHeadersCalled func(shardHeader data.ShardHeaderHandler) error + WaitForMissingDataCalled func(timeout time.Duration) error + RequestBlockTransactionsCalled func(body *block.Body) + RequestMiniBlocksAndTransactionsCalled func(header data.HeaderHandler) + GetFinalCrossMiniBlockInfoAndRequestMissingCalled func(header data.HeaderHandler) []*data.MiniBlockInfo + RequestMissingShardHeadersCalled func(header data.MetaHeaderHandler) error + ResetCalled func() +} + +// RequestMissingMetaHeadersBlocking - +func (mock *MissingDataResolverMock) RequestMissingMetaHeadersBlocking(shardHeader data.ShardHeaderHandler, timeout time.Duration) error { + if mock.RequestMissingMetaHeadersBlockingCalled != nil { + return mock.RequestMissingMetaHeadersBlockingCalled(shardHeader, timeout) + } + return nil +} + +// RequestMissingMetaHeaders - +func (mock *MissingDataResolverMock) RequestMissingMetaHeaders(shardHeader data.ShardHeaderHandler) error { + if mock.RequestMissingMetaHeadersCalled != nil { + return mock.RequestMissingMetaHeadersCalled(shardHeader) + } + return nil +} + +// WaitForMissingData - +func (mock *MissingDataResolverMock) WaitForMissingData(timeout time.Duration) error { + if mock.WaitForMissingDataCalled != nil { + return mock.WaitForMissingDataCalled(timeout) + } + return nil +} + +// RequestBlockTransactions - +func (mock *MissingDataResolverMock) RequestBlockTransactions(body *block.Body) { + if mock.RequestBlockTransactionsCalled != nil { + mock.RequestBlockTransactionsCalled(body) + } +} + +// RequestMiniBlocksAndTransactions - +func (mock *MissingDataResolverMock) RequestMiniBlocksAndTransactions(header data.HeaderHandler) { + if mock.RequestMiniBlocksAndTransactionsCalled != nil { + mock.RequestMiniBlocksAndTransactionsCalled(header) + } +} + +// GetFinalCrossMiniBlockInfoAndRequestMissing - +func (mock *MissingDataResolverMock) GetFinalCrossMiniBlockInfoAndRequestMissing(header data.HeaderHandler) []*data.MiniBlockInfo { + if mock.GetFinalCrossMiniBlockInfoAndRequestMissingCalled != nil { + return mock.GetFinalCrossMiniBlockInfoAndRequestMissingCalled(header) + } + return make([]*data.MiniBlockInfo, 0) +} + +// RequestMissingShardHeaders - +func (mock *MissingDataResolverMock) RequestMissingShardHeaders(header data.MetaHeaderHandler) error { + if mock.RequestMissingShardHeadersCalled != nil { + return mock.RequestMissingShardHeadersCalled(header) + } + return nil +} + +// Reset - +func (mock *MissingDataResolverMock) Reset() { + if mock.ResetCalled != nil { + mock.ResetCalled() + } +} + +// IsInterfaceNil - +func (mock *MissingDataResolverMock) IsInterfaceNil() bool { + return mock == nil +} diff --git a/testscommon/processMocks/shardInfoCreatorMock.go b/testscommon/processMocks/shardInfoCreatorMock.go new file mode 100644 index 00000000000..67c612a8bf7 --- /dev/null +++ b/testscommon/processMocks/shardInfoCreatorMock.go @@ -0,0 +1,40 @@ +package processMocks + +import ( + "github.com/multiversx/mx-chain-core-go/data" +) + +// ShardInfoCreatorMock is a mock implementation of ShardInfoCreator interface +type ShardInfoCreatorMock struct { + CreateShardInfoV3Called func(metaHeader data.MetaHeaderHandler, shardHeaders []data.HeaderHandler, shardHeaderHashes [][]byte) ([]data.ShardDataProposalHandler, []data.ShardDataHandler, error) + CreateShardInfoFromLegacyMetaCalled func(metaHeader data.MetaHeaderHandler, shardHeaders []data.ShardHeaderHandler, shardHeaderHashes [][]byte) ([]data.ShardDataHandler, error) +} + +// CreateShardInfoV3 - +func (scm *ShardInfoCreatorMock) CreateShardInfoV3( + metaHeader data.MetaHeaderHandler, + shardHeaders []data.HeaderHandler, + shardHeaderHashes [][]byte, +) ([]data.ShardDataProposalHandler, []data.ShardDataHandler, error) { + if scm.CreateShardInfoV3Called != nil { + return scm.CreateShardInfoV3Called(metaHeader, shardHeaders, shardHeaderHashes) + } + return nil, nil, nil +} + +// CreateShardInfoFromLegacyMeta - +func (scm *ShardInfoCreatorMock) CreateShardInfoFromLegacyMeta( + metaHeader data.MetaHeaderHandler, + shardHeaders []data.ShardHeaderHandler, + shardHeaderHashes [][]byte, +) ([]data.ShardDataHandler, error) { + if scm.CreateShardInfoFromLegacyMetaCalled != nil { + return scm.CreateShardInfoFromLegacyMetaCalled(metaHeader, shardHeaders, shardHeaderHashes) + } + return nil, nil +} + +// IsInterfaceNil - +func (scm *ShardInfoCreatorMock) IsInterfaceNil() bool { + return scm == nil +} diff --git a/testscommon/processStatusHandlerStub.go b/testscommon/processStatusHandlerStub.go index dcb39b90b18..c293b4f91b9 100644 --- a/testscommon/processStatusHandlerStub.go +++ b/testscommon/processStatusHandlerStub.go @@ -2,9 +2,10 @@ package testscommon // ProcessStatusHandlerStub - type ProcessStatusHandlerStub struct { - SetBusyCalled func(reason string) - SetIdleCalled func() - IsIdleCalled func() bool + SetBusyCalled func(reason string) + TrySetBusyCalled func(reason string) bool + SetIdleCalled func() + IsIdleCalled func() bool } // SetBusy - @@ -14,6 +15,15 @@ func (stub *ProcessStatusHandlerStub) SetBusy(reason string) { } } +// TrySetBusy - +func (stub *ProcessStatusHandlerStub) TrySetBusy(reason string) bool { + if stub.TrySetBusyCalled != nil { + return stub.TrySetBusyCalled(reason) + } + + return true +} + // SetIdle - func (stub *ProcessStatusHandlerStub) SetIdle() { if stub.SetIdleCalled != nil { diff --git a/testscommon/pubkeyConverterMock.go b/testscommon/pubkeyConverterMock.go index 88e09085528..92e971de93d 100644 --- a/testscommon/pubkeyConverterMock.go +++ b/testscommon/pubkeyConverterMock.go @@ -8,7 +8,8 @@ import ( // PubkeyConverterMock - type PubkeyConverterMock struct { - len int + len int + DecodeCalled func(humanReadable string) ([]byte, error) } // NewPubkeyConverterMock - @@ -20,6 +21,9 @@ func NewPubkeyConverterMock(addressLen int) *PubkeyConverterMock { // Decode - func (pcm *PubkeyConverterMock) Decode(humanReadable string) ([]byte, error) { + if pcm.DecodeCalled != nil { + return pcm.DecodeCalled(humanReadable) + } return hex.DecodeString(humanReadable) } diff --git a/testscommon/raterMock.go b/testscommon/raterMock.go index 18d7f94c85e..504c3ba93ce 100644 --- a/testscommon/raterMock.go +++ b/testscommon/raterMock.go @@ -22,12 +22,11 @@ type RaterMock struct { GetRatingCalled func(string) uint32 GetStartRatingCalled func() uint32 GetSignedBlocksThresholdCalled func() float32 - ComputeIncreaseProposerCalled func(shardId uint32, rating uint32) uint32 - ComputeDecreaseProposerCalled func(shardId uint32, rating uint32, consecutiveMissedBlocks uint32) uint32 - RevertIncreaseProposerCalled func(shardId uint32, rating uint32, nrReverts uint32) uint32 - RevertIncreaseValidatorCalled func(shardId uint32, rating uint32, nrReverts uint32) uint32 - ComputeIncreaseValidatorCalled func(shardId uint32, rating uint32) uint32 - ComputeDecreaseValidatorCalled func(shardId uint32, rating uint32) uint32 + ComputeIncreaseProposerCalled func(shardId uint32, rating uint32, epoch uint32) uint32 + ComputeDecreaseProposerCalled func(shardId uint32, rating uint32, consecutiveMissedBlocks uint32, epoch uint32) uint32 + RevertIncreaseValidatorCalled func(shardId uint32, rating uint32, nrReverts uint32, epoch uint32) uint32 + ComputeIncreaseValidatorCalled func(shardId uint32, rating uint32, epoch uint32) uint32 + ComputeDecreaseValidatorCalled func(shardId uint32, rating uint32, epoch uint32) uint32 GetChancesCalled func(val uint32) uint32 } @@ -40,7 +39,7 @@ func GetNewMockRater() *RaterMock { raterMock.GetStartRatingCalled = func() uint32 { return raterMock.StartRating } - raterMock.ComputeIncreaseProposerCalled = func(shardId uint32, rating uint32) uint32 { + raterMock.ComputeIncreaseProposerCalled = func(shardId uint32, rating uint32, epoch uint32) uint32 { var ratingStep int32 if shardId == core.MetachainShardId { ratingStep = raterMock.MetaIncreaseProposer @@ -49,7 +48,7 @@ func GetNewMockRater() *RaterMock { } return raterMock.computeRating(rating, ratingStep) } - raterMock.RevertIncreaseProposerCalled = func(shardId uint32, rating uint32, nrReverts uint32) uint32 { + raterMock.RevertIncreaseValidatorCalled = func(shardId uint32, rating uint32, nrReverts uint32, epoch uint32) uint32 { var ratingStep int32 if shardId == core.MetachainShardId { ratingStep = raterMock.MetaIncreaseValidator @@ -59,7 +58,7 @@ func GetNewMockRater() *RaterMock { computedStep := -ratingStep * int32(nrReverts) return raterMock.computeRating(rating, computedStep) } - raterMock.ComputeDecreaseProposerCalled = func(shardId uint32, rating uint32, consecutiveMissedBlocks uint32) uint32 { + raterMock.ComputeDecreaseProposerCalled = func(shardId uint32, rating uint32, consecutiveMissedBlocks uint32, epoch uint32) uint32 { var ratingStep int32 if shardId == core.MetachainShardId { ratingStep = raterMock.MetaDecreaseProposer @@ -68,7 +67,7 @@ func GetNewMockRater() *RaterMock { } return raterMock.computeRating(rating, ratingStep) } - raterMock.ComputeIncreaseValidatorCalled = func(shardId uint32, rating uint32) uint32 { + raterMock.ComputeIncreaseValidatorCalled = func(shardId uint32, rating uint32, epoch uint32) uint32 { var ratingStep int32 if shardId == core.MetachainShardId { ratingStep = raterMock.MetaIncreaseValidator @@ -77,7 +76,7 @@ func GetNewMockRater() *RaterMock { } return raterMock.computeRating(rating, ratingStep) } - raterMock.ComputeDecreaseValidatorCalled = func(shardId uint32, rating uint32) uint32 { + raterMock.ComputeDecreaseValidatorCalled = func(shardId uint32, rating uint32, epoch uint32) uint32 { var ratingStep int32 if shardId == core.MetachainShardId { ratingStep = raterMock.MetaDecreaseValidator @@ -129,41 +128,41 @@ func (rm *RaterMock) GetSignedBlocksThreshold() float32 { } // ComputeIncreaseProposer - -func (rm *RaterMock) ComputeIncreaseProposer(shardId uint32, currentRating uint32) uint32 { +func (rm *RaterMock) ComputeIncreaseProposer(shardId uint32, currentRating uint32, epoch uint32) uint32 { if rm.ComputeIncreaseProposerCalled != nil { - return rm.ComputeIncreaseProposerCalled(shardId, currentRating) + return rm.ComputeIncreaseProposerCalled(shardId, currentRating, epoch) } return 1 } // ComputeDecreaseProposer - -func (rm *RaterMock) ComputeDecreaseProposer(shardId uint32, currentRating uint32, consecutiveMisses uint32) uint32 { +func (rm *RaterMock) ComputeDecreaseProposer(shardId uint32, currentRating uint32, consecutiveMisses uint32, epoch uint32) uint32 { if rm.ComputeDecreaseProposerCalled != nil { - return rm.ComputeDecreaseProposerCalled(shardId, currentRating, consecutiveMisses) + return rm.ComputeDecreaseProposerCalled(shardId, currentRating, consecutiveMisses, epoch) } return 1 } // RevertIncreaseValidator - -func (rm *RaterMock) RevertIncreaseValidator(shardId uint32, currentRating uint32, nrReverts uint32) uint32 { +func (rm *RaterMock) RevertIncreaseValidator(shardId uint32, currentRating uint32, nrReverts uint32, epoch uint32) uint32 { if rm.RevertIncreaseValidatorCalled != nil { - return rm.RevertIncreaseProposerCalled(shardId, currentRating, nrReverts) + return rm.RevertIncreaseValidatorCalled(shardId, currentRating, nrReverts, epoch) } return 1 } // ComputeIncreaseValidator - -func (rm *RaterMock) ComputeIncreaseValidator(shardId uint32, currentRating uint32) uint32 { +func (rm *RaterMock) ComputeIncreaseValidator(shardId uint32, currentRating uint32, epoch uint32) uint32 { if rm.ComputeIncreaseValidatorCalled != nil { - return rm.ComputeIncreaseValidatorCalled(shardId, currentRating) + return rm.ComputeIncreaseValidatorCalled(shardId, currentRating, epoch) } return 1 } // ComputeDecreaseValidator - -func (rm *RaterMock) ComputeDecreaseValidator(shardId uint32, currentRating uint32) uint32 { +func (rm *RaterMock) ComputeDecreaseValidator(shardId uint32, currentRating uint32, epoch uint32) uint32 { if rm.ComputeDecreaseValidatorCalled != nil { - return rm.ComputeDecreaseValidatorCalled(shardId, currentRating) + return rm.ComputeDecreaseValidatorCalled(shardId, currentRating, epoch) } return 1 } diff --git a/testscommon/ratingsInfoMock.go b/testscommon/ratingsInfoMock.go index 5ca786994e8..ac44527a31f 100644 --- a/testscommon/ratingsInfoMock.go +++ b/testscommon/ratingsInfoMock.go @@ -52,6 +52,16 @@ func (rd *RatingsInfoMock) ShardChainRatingsStepHandler() process.RatingsStepHan return rd.ShardRatingsStepDataProperty } +// ShardChainRatingsStepHandlerForEpoch - +func (rd *RatingsInfoMock) ShardChainRatingsStepHandlerForEpoch(_ uint32) process.RatingsStepHandler { + return rd.ShardRatingsStepDataProperty +} + +// MetaChainRatingsStepHandlerForEpoch - +func (rd *RatingsInfoMock) MetaChainRatingsStepHandlerForEpoch(_ uint32) process.RatingsStepHandler { + return rd.MetaRatingsStepDataProperty +} + // SetStatusHandler - func (rd *RatingsInfoMock) SetStatusHandler(handler core.AppStatusHandler) error { if rd.SetStatusHandlerCalled != nil { diff --git a/testscommon/realConfigsHandling.go b/testscommon/realConfigsHandling.go index c59661b7234..17a4f253390 100644 --- a/testscommon/realConfigsHandling.go +++ b/testscommon/realConfigsHandling.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" ) @@ -96,6 +97,9 @@ func CreateTestConfigs(tempDir string, originalConfigsPath string) (*config.Conf fullArchiveP2PConfig.Node.MinNumPeersToWaitForOnBootstrap = 0 fullArchiveP2PConfig.Node.ThresholdMinConnectedPeers = 0 + // also for txpool selection, ignore the propagation grace period + generalConfig.TxCacheBounds.PropagationGracePeriodMs = 0 + return &config.Configs{ GeneralConfig: generalConfig, ApiRoutesConfig: apiConfig, diff --git a/testscommon/receiptsRepositoryStub.go b/testscommon/receiptsRepositoryStub.go index 4b3025979d1..094e2165c26 100644 --- a/testscommon/receiptsRepositoryStub.go +++ b/testscommon/receiptsRepositoryStub.go @@ -2,14 +2,16 @@ package testscommon import ( "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/common/holders" ) // ReceiptsRepositoryStub - type ReceiptsRepositoryStub struct { - SaveReceiptsCalled func(holder common.ReceiptsHolder, header data.HeaderHandler, headerHash []byte) error - LoadReceiptsCalled func(header data.HeaderHandler, headerHash []byte) (common.ReceiptsHolder, error) + SaveReceiptsCalled func(holder common.ReceiptsHolder, header data.HeaderHandler, headerHash []byte) error + SaveReceiptsForExecResultCalled func(holder common.ReceiptsHolder, execResult data.BaseExecutionResultHandler) error + LoadReceiptsCalled func(receiptsHash []byte, header data.HeaderHandler, headerHash []byte) (common.ReceiptsHolder, error) } // SaveReceipts - @@ -21,10 +23,19 @@ func (stub *ReceiptsRepositoryStub) SaveReceipts(holder common.ReceiptsHolder, h return nil } +// SaveReceiptsForExecResult - +func (stub *ReceiptsRepositoryStub) SaveReceiptsForExecResult(holder common.ReceiptsHolder, execResult data.BaseExecutionResultHandler) error { + if stub.SaveReceiptsForExecResultCalled != nil { + return stub.SaveReceiptsForExecResultCalled(holder, execResult) + } + + return nil +} + // LoadReceipts - -func (stub *ReceiptsRepositoryStub) LoadReceipts(header data.HeaderHandler, headerHash []byte) (common.ReceiptsHolder, error) { +func (stub *ReceiptsRepositoryStub) LoadReceipts(receiptsHash []byte, header data.HeaderHandler, headerHash []byte) (common.ReceiptsHolder, error) { if stub.LoadReceiptsCalled != nil { - return stub.LoadReceiptsCalled(header, headerHash) + return stub.LoadReceiptsCalled(receiptsHash, header, headerHash) } return holders.NewReceiptsHolder(nil), nil diff --git a/testscommon/requestHandlerStub.go b/testscommon/requestHandlerStub.go index 3f911cd79a3..ba815c94378 100644 --- a/testscommon/requestHandlerStub.go +++ b/testscommon/requestHandlerStub.go @@ -4,26 +4,40 @@ import "time" // RequestHandlerStub - type RequestHandlerStub struct { - RequestShardHeaderCalled func(shardID uint32, hash []byte) - RequestMetaHeaderCalled func(hash []byte) - RequestMetaHeaderByNonceCalled func(nonce uint64) - RequestShardHeaderByNonceCalled func(shardID uint32, nonce uint64) - RequestTransactionHandlerCalled func(destShardID uint32, txHashes [][]byte) - RequestScrHandlerCalled func(destShardID uint32, txHashes [][]byte) - RequestRewardTxHandlerCalled func(destShardID uint32, txHashes [][]byte) - RequestMiniBlockHandlerCalled func(destShardID uint32, miniblockHash []byte) - RequestMiniBlocksHandlerCalled func(destShardID uint32, miniblocksHashes [][]byte) - RequestTrieNodesCalled func(destShardID uint32, hashes [][]byte, topic string) - RequestStartOfEpochMetaBlockCalled func(epoch uint32) - SetNumPeersToQueryCalled func(key string, intra int, cross int) error - GetNumPeersToQueryCalled func(key string) (int, int, error) - RequestTrieNodeCalled func(requestHash []byte, topic string, chunkIndex uint32) - CreateTrieNodeIdentifierCalled func(requestHash []byte, chunkIndex uint32) []byte - RequestPeerAuthenticationsByHashesCalled func(destShardID uint32, hashes [][]byte) - RequestValidatorInfoCalled func(hash []byte) - RequestValidatorsInfoCalled func(hashes [][]byte) - RequestEquivalentProofByHashCalled func(headerShard uint32, headerHash []byte) - RequestEquivalentProofByNonceCalled func(headerShard uint32, headerNonce uint64) + RequestShardHeaderCalled func(shardID uint32, hash []byte) + RequestShardHeaderForEpochCalled func(shardID uint32, hash []byte, epoch uint32) + RequestMetaHeaderCalled func(hash []byte) + RequestMetaHeaderForEpochCalled func(hash []byte, epoch uint32) + RequestMetaHeaderByNonceCalled func(nonce uint64) + RequestMetaHeaderByNonceForEpochCalled func(nonce uint64, epoch uint32) + RequestShardHeaderByNonceCalled func(shardID uint32, nonce uint64) + RequestShardHeaderByNonceForEpochCalled func(shardID uint32, nonce uint64, epoch uint32) + RequestTransactionHandlerCalled func(destShardID uint32, txHashes [][]byte) + RequestTransactionsForEpochCalled func(destShardID uint32, txHashes [][]byte, epoch uint32) + RequestScrHandlerCalled func(destShardID uint32, txHashes [][]byte) + RequestScrHandlerForEpochCalled func(destShardID uint32, txHashes [][]byte, epoch uint32) + RequestRewardTxHandlerCalled func(destShardID uint32, txHashes [][]byte) + RequestRewardTxHandlerForEpochCalled func(destShardID uint32, rewardTxHashes [][]byte, epoch uint32) + RequestMiniBlockHandlerCalled func(destShardID uint32, miniBlockHash []byte) + RequestMiniBlockForEpochCalled func(destShardID uint32, miniBlockHash []byte, epoch uint32) + RequestMiniBlocksHandlerCalled func(destShardID uint32, miniBlocksHashes [][]byte) + RequestMiniBlocksForEpochCalled func(destShardID uint32, miniBlocksHashes [][]byte, epoch uint32) + RequestTrieNodesCalled func(destShardID uint32, hashes [][]byte, topic string) + RequestStartOfEpochMetaBlockCalled func(epoch uint32) + SetNumPeersToQueryCalled func(key string, intra int, cross int) error + GetNumPeersToQueryCalled func(key string) (int, int, error) + RequestTrieNodeCalled func(requestHash []byte, topic string, chunkIndex uint32) + RequestTrieNodesForEpochCalled func(destShardID uint32, hashes [][]byte, topic string, epoch uint32) + CreateTrieNodeIdentifierCalled func(requestHash []byte, chunkIndex uint32) []byte + RequestPeerAuthenticationsByHashesCalled func(destShardID uint32, hashes [][]byte) + RequestValidatorInfoCalled func(hash []byte) + RequestValidatorInfoForEpochCalled func(hash []byte, epoch uint32) + RequestValidatorsInfoCalled func(hashes [][]byte) + RequestValidatorsInfoForEpochCalled func(hashes [][]byte, epoch uint32) + RequestEquivalentProofByHashCalled func(headerShard uint32, headerHash []byte) + RequestEquivalentProofByHashForEpochCalled func(headerShard uint32, headerHash []byte, epoch uint32) + RequestEquivalentProofByNonceCalled func(headerShard uint32, headerNonce uint64) + RequestEquivalentProofByNonceForEpochCalled func(headerShard uint32, headerNonce uint64, epoch uint32) } // SetNumPeersToQuery - @@ -69,6 +83,14 @@ func (rhs *RequestHandlerStub) RequestShardHeader(shardID uint32, hash []byte) { rhs.RequestShardHeaderCalled(shardID, hash) } +// RequestShardHeaderForEpoch - +func (rhs *RequestHandlerStub) RequestShardHeaderForEpoch(shardID uint32, hash []byte, epoch uint32) { + if rhs.RequestShardHeaderForEpochCalled == nil { + return + } + rhs.RequestShardHeaderForEpochCalled(shardID, hash, epoch) +} + // RequestMetaHeader - func (rhs *RequestHandlerStub) RequestMetaHeader(hash []byte) { if rhs.RequestMetaHeaderCalled == nil { @@ -77,6 +99,14 @@ func (rhs *RequestHandlerStub) RequestMetaHeader(hash []byte) { rhs.RequestMetaHeaderCalled(hash) } +// RequestMetaHeaderForEpoch - +func (rhs *RequestHandlerStub) RequestMetaHeaderForEpoch(hash []byte, epoch uint32) { + if rhs.RequestMetaHeaderForEpochCalled == nil { + return + } + rhs.RequestMetaHeaderForEpochCalled(hash, epoch) +} + // RequestMetaHeaderByNonce - func (rhs *RequestHandlerStub) RequestMetaHeaderByNonce(nonce uint64) { if rhs.RequestMetaHeaderByNonceCalled == nil { @@ -85,6 +115,14 @@ func (rhs *RequestHandlerStub) RequestMetaHeaderByNonce(nonce uint64) { rhs.RequestMetaHeaderByNonceCalled(nonce) } +// RequestMetaHeaderByNonceForEpoch - +func (rhs *RequestHandlerStub) RequestMetaHeaderByNonceForEpoch(nonce uint64, epoch uint32) { + if rhs.RequestMetaHeaderByNonceForEpochCalled == nil { + return + } + rhs.RequestMetaHeaderByNonceForEpochCalled(nonce, epoch) +} + // RequestShardHeaderByNonce - func (rhs *RequestHandlerStub) RequestShardHeaderByNonce(shardID uint32, nonce uint64) { if rhs.RequestShardHeaderByNonceCalled == nil { @@ -93,14 +131,30 @@ func (rhs *RequestHandlerStub) RequestShardHeaderByNonce(shardID uint32, nonce u rhs.RequestShardHeaderByNonceCalled(shardID, nonce) } -// RequestTransaction - -func (rhs *RequestHandlerStub) RequestTransaction(destShardID uint32, txHashes [][]byte) { +// RequestShardHeaderByNonceForEpoch - +func (rhs *RequestHandlerStub) RequestShardHeaderByNonceForEpoch(shardID uint32, nonce uint64, epoch uint32) { + if rhs.RequestShardHeaderByNonceForEpochCalled == nil { + return + } + rhs.RequestShardHeaderByNonceForEpochCalled(shardID, nonce, epoch) +} + +// RequestTransactions - +func (rhs *RequestHandlerStub) RequestTransactions(destShardID uint32, txHashes [][]byte) { if rhs.RequestTransactionHandlerCalled == nil { return } rhs.RequestTransactionHandlerCalled(destShardID, txHashes) } +// RequestTransactionsForEpoch - +func (rhs *RequestHandlerStub) RequestTransactionsForEpoch(destShardID uint32, txHashes [][]byte, epoch uint32) { + if rhs.RequestTransactionsForEpochCalled == nil { + return + } + rhs.RequestTransactionsForEpochCalled(destShardID, txHashes, epoch) +} + // RequestUnsignedTransactions - func (rhs *RequestHandlerStub) RequestUnsignedTransactions(destShardID uint32, txHashes [][]byte) { if rhs.RequestScrHandlerCalled == nil { @@ -109,6 +163,14 @@ func (rhs *RequestHandlerStub) RequestUnsignedTransactions(destShardID uint32, t rhs.RequestScrHandlerCalled(destShardID, txHashes) } +// RequestUnsignedTransactionsForEpoch - +func (rhs *RequestHandlerStub) RequestUnsignedTransactionsForEpoch(destShardID uint32, txHashes [][]byte, epoch uint32) { + if rhs.RequestScrHandlerForEpochCalled == nil { + return + } + rhs.RequestScrHandlerForEpochCalled(destShardID, txHashes, epoch) +} + // RequestRewardTransactions - func (rhs *RequestHandlerStub) RequestRewardTransactions(destShardID uint32, txHashes [][]byte) { if rhs.RequestRewardTxHandlerCalled == nil { @@ -117,6 +179,13 @@ func (rhs *RequestHandlerStub) RequestRewardTransactions(destShardID uint32, txH rhs.RequestRewardTxHandlerCalled(destShardID, txHashes) } +func (rhs *RequestHandlerStub) RequestRewardTransactionsForEpoch(destShardID uint32, rewardTxHashes [][]byte, epoch uint32) { + if rhs.RequestRewardTxHandlerForEpochCalled == nil { + return + } + rhs.RequestRewardTxHandlerForEpochCalled(destShardID, rewardTxHashes, epoch) +} + // RequestMiniBlock - func (rhs *RequestHandlerStub) RequestMiniBlock(destShardID uint32, miniblockHash []byte) { if rhs.RequestMiniBlockHandlerCalled == nil { @@ -125,6 +194,14 @@ func (rhs *RequestHandlerStub) RequestMiniBlock(destShardID uint32, miniblockHas rhs.RequestMiniBlockHandlerCalled(destShardID, miniblockHash) } +// RequestMiniBlockForEpoch - +func (rhs *RequestHandlerStub) RequestMiniBlockForEpoch(destShardID uint32, miniblockHash []byte, epoch uint32) { + if rhs.RequestMiniBlockForEpochCalled == nil { + return + } + rhs.RequestMiniBlockForEpochCalled(destShardID, miniblockHash, epoch) +} + // RequestMiniBlocks - func (rhs *RequestHandlerStub) RequestMiniBlocks(destShardID uint32, miniblocksHashes [][]byte) { if rhs.RequestMiniBlocksHandlerCalled == nil { @@ -133,6 +210,14 @@ func (rhs *RequestHandlerStub) RequestMiniBlocks(destShardID uint32, miniblocksH rhs.RequestMiniBlocksHandlerCalled(destShardID, miniblocksHashes) } +// RequestMiniBlocksForEpoch - +func (rhs *RequestHandlerStub) RequestMiniBlocksForEpoch(destShardID uint32, miniblocksHashes [][]byte, epoch uint32) { + if rhs.RequestMiniBlocksForEpochCalled == nil { + return + } + rhs.RequestMiniBlocksForEpochCalled(destShardID, miniblocksHashes, epoch) +} + // RequestTrieNodes - func (rhs *RequestHandlerStub) RequestTrieNodes(destShardID uint32, hashes [][]byte, topic string) { if rhs.RequestTrieNodesCalled == nil { @@ -141,6 +226,14 @@ func (rhs *RequestHandlerStub) RequestTrieNodes(destShardID uint32, hashes [][]b rhs.RequestTrieNodesCalled(destShardID, hashes, topic) } +// RequestTrieNodesForEpoch - +func (rhs *RequestHandlerStub) RequestTrieNodesForEpoch(destShardID uint32, hashes [][]byte, topic string, epoch uint32) { + if rhs.RequestTrieNodesForEpochCalled == nil { + return + } + rhs.RequestTrieNodesForEpochCalled(destShardID, hashes, topic, epoch) +} + // CreateTrieNodeIdentifier - func (rhs *RequestHandlerStub) CreateTrieNodeIdentifier(requestHash []byte, chunkIndex uint32) []byte { if rhs.CreateTrieNodeIdentifierCalled != nil { @@ -171,6 +264,13 @@ func (rhs *RequestHandlerStub) RequestValidatorInfo(hash []byte) { } } +// RequestValidatorInfoForEpoch - +func (rhs *RequestHandlerStub) RequestValidatorInfoForEpoch(hash []byte, epoch uint32) { + if rhs.RequestValidatorInfoForEpochCalled != nil { + rhs.RequestValidatorInfoForEpochCalled(hash, epoch) + } +} + // RequestValidatorsInfo - func (rhs *RequestHandlerStub) RequestValidatorsInfo(hashes [][]byte) { if rhs.RequestValidatorsInfoCalled != nil { @@ -178,6 +278,13 @@ func (rhs *RequestHandlerStub) RequestValidatorsInfo(hashes [][]byte) { } } +// RequestValidatorsInfoForEpoch - +func (rhs *RequestHandlerStub) RequestValidatorsInfoForEpoch(hashes [][]byte, epoch uint32) { + if rhs.RequestValidatorsInfoForEpochCalled != nil { + rhs.RequestValidatorsInfoForEpochCalled(hashes, epoch) + } +} + // RequestEquivalentProofByHash - func (rhs *RequestHandlerStub) RequestEquivalentProofByHash(headerShard uint32, headerHash []byte) { if rhs.RequestEquivalentProofByHashCalled != nil { @@ -185,6 +292,13 @@ func (rhs *RequestHandlerStub) RequestEquivalentProofByHash(headerShard uint32, } } +// RequestEquivalentProofByHashForEpoch - +func (rhs *RequestHandlerStub) RequestEquivalentProofByHashForEpoch(headerShard uint32, headerHash []byte, epoch uint32) { + if rhs.RequestEquivalentProofByHashForEpochCalled != nil { + rhs.RequestEquivalentProofByHashForEpochCalled(headerShard, headerHash, epoch) + } +} + // RequestEquivalentProofByNonce - func (rhs *RequestHandlerStub) RequestEquivalentProofByNonce(headerShard uint32, headerNonce uint64) { if rhs.RequestEquivalentProofByNonceCalled != nil { @@ -192,6 +306,13 @@ func (rhs *RequestHandlerStub) RequestEquivalentProofByNonce(headerShard uint32, } } +// RequestEquivalentProofByNonceForEpoch - +func (rhs *RequestHandlerStub) RequestEquivalentProofByNonceForEpoch(headerShard uint32, headerNonce uint64, epoch uint32) { + if rhs.RequestEquivalentProofByNonceForEpochCalled != nil { + rhs.RequestEquivalentProofByNonceForEpochCalled(headerShard, headerNonce, epoch) + } +} + // IsInterfaceNil returns true if there is no value under the interface func (rhs *RequestHandlerStub) IsInterfaceNil() bool { return rhs == nil diff --git a/testscommon/rewardsCreatorStub.go b/testscommon/rewardsCreatorStub.go index 9a31ae8ea36..2080a192132 100644 --- a/testscommon/rewardsCreatorStub.go +++ b/testscommon/rewardsCreatorStub.go @@ -5,6 +5,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/epochStart" "github.com/multiversx/mx-chain-go/state" ) @@ -14,16 +15,28 @@ type RewardsCreatorStub struct { CreateRewardsMiniBlocksCalled func( metaBlock data.MetaHeaderHandler, validatorsInfo state.ShardValidatorsInfoMapHandler, computedEconomics *block.Economics, ) (block.MiniBlockSlice, error) + CreateRewardsMiniBlocksHeaderV3Called func( + metaBlock data.MetaHeaderHandler, + validatorsInfo state.ShardValidatorsInfoMapHandler, + computedEconomics *block.Economics, + prevBlockExecutionResults data.BaseMetaExecutionResultHandler, + ) (block.MiniBlockSlice, error) VerifyRewardsMiniBlocksCalled func( metaBlock data.MetaHeaderHandler, validatorsInfo state.ShardValidatorsInfoMapHandler, computedEconomics *block.Economics, ) error - GetAcceleratorRewardsCalled func() *big.Int - GetLocalTxCacheCalled func() epochStart.TransactionCacher - CreateMarshalledDataCalled func(body *block.Body) map[string][][]byte - GetRewardsTxsCalled func(body *block.Body) map[string]data.TransactionHandler - SaveBlockDataToStorageCalled func(metaBlock data.MetaHeaderHandler, body *block.Body) - DeleteBlockDataFromStorageCalled func(metaBlock data.MetaHeaderHandler, body *block.Body) - RemoveBlockDataFromPoolsCalled func(metaBlock data.MetaHeaderHandler, body *block.Body) + CreateRewardsMiniBlocksV3called func( + metaBlock data.MetaHeaderHandler, + validatorsInfo state.ShardValidatorsInfoMapHandler, + computedEconomics *block.Economics, + prevBlockExecutionResults data.BaseMetaExecutionResultHandler, + ) (block.MiniBlockSlice, error) + GetAcceleratorRewardsCalled func() *big.Int + GetLocalTxCacheCalled func() epochStart.TransactionCacher + CreateMarshalledDataCalled func(body *block.Body) map[string][][]byte + GetRewardsTxsCalled func(body *block.Body) map[string]data.TransactionHandler + SaveBlockDataToStorageCalled func(metaBlock data.MetaHeaderHandler, body *block.Body) + DeleteBlockDataFromStorageCalled func(metaBlock data.MetaHeaderHandler, body *block.Body) + RemoveBlockDataFromPoolsCalled func(metaBlock data.MetaHeaderHandler, body *block.Body) } // CreateRewardsMiniBlocks - @@ -39,6 +52,19 @@ func (rcs *RewardsCreatorStub) CreateRewardsMiniBlocks( return nil, nil } +// CreateRewardsMiniBlocksV3 - +func (rcs *RewardsCreatorStub) CreateRewardsMiniBlocksV3( + metaBlock data.MetaHeaderHandler, + validatorsInfo state.ShardValidatorsInfoMapHandler, + computedEconomics *block.Economics, + prevBlockExecutionResults data.BaseMetaExecutionResultHandler, +) (block.MiniBlockSlice, error) { + if rcs.CreateRewardsMiniBlocksV3called != nil { + return rcs.CreateRewardsMiniBlocksV3called(metaBlock, validatorsInfo, computedEconomics, prevBlockExecutionResults) + } + return nil, nil +} + // VerifyRewardsMiniBlocks - func (rcs *RewardsCreatorStub) VerifyRewardsMiniBlocks( metaBlock data.MetaHeaderHandler, @@ -105,6 +131,19 @@ func (rcs *RewardsCreatorStub) RemoveBlockDataFromPools(metaBlock data.MetaHeade } } +// CreateRewardsMiniBlocksHeaderV3 - +func (rcs *RewardsCreatorStub) CreateRewardsMiniBlocksHeaderV3( + metaBlock data.MetaHeaderHandler, + validatorsInfo state.ShardValidatorsInfoMapHandler, + computedEconomics *block.Economics, + prevBlockExecutionResults data.BaseMetaExecutionResultHandler, +) (block.MiniBlockSlice, error) { + if rcs.CreateRewardsMiniBlocksHeaderV3Called != nil { + return rcs.CreateRewardsMiniBlocksHeaderV3Called(metaBlock, validatorsInfo, computedEconomics, prevBlockExecutionResults) + } + return nil, nil +} + // IsInterfaceNil - func (rcs *RewardsCreatorStub) IsInterfaceNil() bool { return rcs == nil diff --git a/testscommon/consensus/rounderMock.go b/testscommon/round/rounderMock.go similarity index 71% rename from testscommon/consensus/rounderMock.go rename to testscommon/round/rounderMock.go index 2855033a823..21488b048f5 100644 --- a/testscommon/consensus/rounderMock.go +++ b/testscommon/round/rounderMock.go @@ -1,4 +1,4 @@ -package consensus +package round import ( "time" @@ -8,12 +8,13 @@ import ( type RoundHandlerMock struct { RoundIndex int64 - IndexCalled func() int64 - TimeDurationCalled func() time.Duration - TimeStampCalled func() time.Time - UpdateRoundCalled func(time.Time, time.Time) - RemainingTimeCalled func(startTime time.Time, maxTime time.Duration) time.Duration - BeforeGenesisCalled func() bool + IndexCalled func() int64 + TimeDurationCalled func() time.Duration + TimeStampCalled func() time.Time + UpdateRoundCalled func(time.Time, time.Time) + RemainingTimeCalled func(startTime time.Time, maxTime time.Duration) time.Duration + GetTimeStampForRoundCalled func(round uint64) uint64 + BeforeGenesisCalled func() bool } // BeforeGenesis - @@ -73,6 +74,15 @@ func (rndm *RoundHandlerMock) RemainingTime(startTime time.Time, maxTime time.Du return 4000 * time.Millisecond } +// GetTimeStampForRound - +func (rndm *RoundHandlerMock) GetTimeStampForRound(round uint64) uint64 { + if rndm.GetTimeStampForRoundCalled != nil { + return rndm.GetTimeStampForRoundCalled(round) + } + + return 0 +} + // IsInterfaceNil returns true if there is no value under the interface func (rndm *RoundHandlerMock) IsInterfaceNil() bool { return rndm == nil diff --git a/testscommon/roundHandlerMock.go b/testscommon/roundHandlerMock.go index 598e11feb42..7f5451ac9e9 100644 --- a/testscommon/roundHandlerMock.go +++ b/testscommon/roundHandlerMock.go @@ -10,13 +10,14 @@ type RoundHandlerMock struct { indexMut sync.RWMutex index int64 - IndexCalled func() int64 - TimeDurationCalled func() time.Duration - TimeStampCalled func() time.Time - UpdateRoundCalled func(time.Time, time.Time) - RemainingTimeCalled func(startTime time.Time, maxTime time.Duration) time.Duration - BeforeGenesisCalled func() bool - IncrementIndexCalled func() + IndexCalled func() int64 + TimeDurationCalled func() time.Duration + TimeStampCalled func() time.Time + UpdateRoundCalled func(time.Time, time.Time) + RemainingTimeCalled func(startTime time.Time, maxTime time.Duration) time.Duration + BeforeGenesisCalled func() bool + IncrementIndexCalled func() + GetTimeStampForRoundCalled func(round uint64) uint64 } // BeforeGenesis - @@ -88,6 +89,15 @@ func (rndm *RoundHandlerMock) IncrementIndex() { } } +// GetTimeStampForRound - +func (rndm *RoundHandlerMock) GetTimeStampForRound(round uint64) uint64 { + if rndm.GetTimeStampForRoundCalled != nil { + return rndm.GetTimeStampForRoundCalled(round) + } + + return 0 +} + // IsInterfaceNil returns true if there is no value under the interface func (rndm *RoundHandlerMock) IsInterfaceNil() bool { return rndm == nil diff --git a/testscommon/sentSignatureTrackerStub.go b/testscommon/sentSignatureTrackerStub.go index c051d0c60a7..c3c2fe18c9b 100644 --- a/testscommon/sentSignatureTrackerStub.go +++ b/testscommon/sentSignatureTrackerStub.go @@ -4,6 +4,8 @@ package testscommon type SentSignatureTrackerStub struct { StartRoundCalled func() SignatureSentCalled func(pkBytes []byte) + RecordSignedNonceCalled func(pkBytes []byte, nonce uint64, headerHash []byte) + GetSignedHashCalled func(pkBytes []byte, nonce uint64) ([]byte, bool) ResetCountersForManagedBlockSignerCalled func(signerPk []byte) } @@ -21,6 +23,21 @@ func (stub *SentSignatureTrackerStub) SignatureSent(pkBytes []byte) { } } +// RecordSignedNonce - +func (stub *SentSignatureTrackerStub) RecordSignedNonce(pkBytes []byte, nonce uint64, headerHash []byte) { + if stub.RecordSignedNonceCalled != nil { + stub.RecordSignedNonceCalled(pkBytes, nonce, headerHash) + } +} + +// GetSignedHash - +func (stub *SentSignatureTrackerStub) GetSignedHash(pkBytes []byte, nonce uint64) ([]byte, bool) { + if stub.GetSignedHashCalled != nil { + return stub.GetSignedHashCalled(pkBytes, nonce) + } + return nil, false +} + // ResetCountersForManagedBlockSigner - func (stub *SentSignatureTrackerStub) ResetCountersForManagedBlockSigner(signerPk []byte) { if stub.ResetCountersForManagedBlockSignerCalled != nil { diff --git a/testscommon/shardedDataCacheNotifierMock.go b/testscommon/shardedDataCacheNotifierMock.go index f6043415b08..93157be03cf 100644 --- a/testscommon/shardedDataCacheNotifierMock.go +++ b/testscommon/shardedDataCacheNotifierMock.go @@ -2,9 +2,13 @@ package testscommon import ( "sync" + "time" "github.com/multiversx/mx-chain-core-go/core/counting" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/storage" cacheMocks "github.com/multiversx/mx-chain-go/testscommon/cache" ) @@ -13,6 +17,17 @@ import ( type ShardedDataCacheNotifierMock struct { mutCaches sync.RWMutex caches map[string]storage.Cacher + + CleanupSelfShardTxCacheCalled func(session interface{}, randomness uint64, maxNum int, cleanupLoopMaximumDuration time.Duration) + OnExecutedBlockCalled func(blockHeader data.HeaderHandler, rootHash []byte) error + OnProposedBlockCalled func( + blockHash []byte, + blockBody *block.Body, + blockHeader data.HeaderHandler, + accountsProvider common.AccountNonceAndBalanceProvider, + latestExecutedHash []byte, + ) error + ResetTrackerCalled func() } // NewShardedDataCacheNotifierMock - @@ -109,7 +124,7 @@ func (mock *ShardedDataCacheNotifierMock) ClearShardStore(cacheId string) { // GetCounts - func (mock *ShardedDataCacheNotifierMock) GetCounts() counting.CountsWithSize { - return nil + return &counting.NullCounts{} } // Keys - @@ -125,7 +140,63 @@ func (mock *ShardedDataCacheNotifierMock) Keys() [][]byte { return keys } +// CleanupSelfShardTxCache - +func (mock *ShardedDataCacheNotifierMock) CleanupSelfShardTxCache(accountsProvider common.AccountNonceProvider, randomness uint64, maxNum int, cleanupLoopMaximumDuration time.Duration) { + if mock.CleanupSelfShardTxCacheCalled != nil { + mock.CleanupSelfShardTxCacheCalled(accountsProvider, randomness, maxNum, cleanupLoopMaximumDuration) + } +} + +// OnExecutedBlock - +func (mock *ShardedDataCacheNotifierMock) OnExecutedBlock(blockHeader data.HeaderHandler, rootHash []byte) error { + if mock.OnExecutedBlockCalled != nil { + return mock.OnExecutedBlockCalled(blockHeader, rootHash) + } + + return nil +} + +// OnProposedBlock - +func (mock *ShardedDataCacheNotifierMock) OnProposedBlock( + blockHash []byte, + blockBody *block.Body, + blockHeader data.HeaderHandler, + accountsProvider common.AccountNonceAndBalanceProvider, + latestExecutedHash []byte, +) error { + if mock.OnProposedBlockCalled != nil { + return mock.OnProposedBlockCalled(blockHash, blockBody, blockHeader, accountsProvider, latestExecutedHash) + } + return nil +} + +// OnBackfilledBlock - +func (mock *ShardedDataCacheNotifierMock) OnBackfilledBlock( + _ []byte, + _ *block.Body, + _ data.HeaderHandler, +) error { + return nil +} + +// ResetTracker - +func (mock *ShardedDataCacheNotifierMock) ResetTracker() { + if mock.ResetTrackerCalled != nil { + mock.ResetTrackerCalled() + } +} + // IsInterfaceNil - func (mock *ShardedDataCacheNotifierMock) IsInterfaceNil() bool { return mock == nil } + +// GetNumTrackedBlocks - +func (sd *ShardedDataCacheNotifierMock) GetNumTrackedBlocks() uint64 { + return 0 +} + +// GetNumTrackedAccounts - +func (sd *ShardedDataCacheNotifierMock) GetNumTrackedAccounts() uint64 { + return 0 +} diff --git a/testscommon/shardedDataStub.go b/testscommon/shardedDataStub.go index 2a082afe96f..dd2ad968515 100644 --- a/testscommon/shardedDataStub.go +++ b/testscommon/shardedDataStub.go @@ -1,7 +1,13 @@ package testscommon import ( + "time" + "github.com/multiversx/mx-chain-core-go/core/counting" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/storage" ) @@ -22,6 +28,18 @@ type ShardedDataStub struct { CreateShardStoreCalled func(destCacheID string) GetCountsCalled func() counting.CountsWithSize KeysCalled func() [][]byte + CleanupSelfShardTxCacheCalled func(session interface{}, randomness uint64, maxNum int, cleanupLoopMaximumDuration time.Duration) + GetNumTrackedBlocksCalled func() uint64 + GetNumTrackedAccountsCalled func() uint64 + OnExecutedBlockCalled func(blockHeader data.HeaderHandler, rootHash []byte) error + OnProposedBlockCalled func( + blockHash []byte, + blockBody *block.Body, + blockHeader data.HeaderHandler, + accountsProvider common.AccountNonceAndBalanceProvider, + latestExecutedHash []byte, + ) error + ResetTrackerCalled func() } // NewShardedDataStub - @@ -126,7 +144,71 @@ func (sd *ShardedDataStub) Keys() [][]byte { return nil } -// IsInterfaceNil returns true if there is no value under the interface +// CleanupSelfShardTxCache - +func (sd *ShardedDataStub) CleanupSelfShardTxCache(accountsProvider common.AccountNonceProvider, randomness uint64, maxNum int, cleanupLoopMaximumDuration time.Duration) { + if sd.CleanupSelfShardTxCacheCalled != nil { + sd.CleanupSelfShardTxCacheCalled(accountsProvider, randomness, maxNum, cleanupLoopMaximumDuration) + } +} + +// GetNumTrackedBlocks - +func (sd *ShardedDataStub) GetNumTrackedBlocks() uint64 { + if sd.GetNumTrackedBlocksCalled != nil { + return sd.GetNumTrackedBlocksCalled() + } + + return 0 +} + +// GetNumTrackedAccounts - +func (sd *ShardedDataStub) GetNumTrackedAccounts() uint64 { + if sd.GetNumTrackedAccountsCalled != nil { + return sd.GetNumTrackedAccountsCalled() + } + + return 0 +} + +// OnExecutedBlock - +func (sd *ShardedDataStub) OnExecutedBlock(blockHeader data.HeaderHandler, rootHash []byte) error { + if sd.OnExecutedBlockCalled != nil { + return sd.OnExecutedBlockCalled(blockHeader, rootHash) + } + + return nil +} + +// OnProposedBlock - +func (sd *ShardedDataStub) OnProposedBlock( + blockHash []byte, + blockBody *block.Body, + blockHeader data.HeaderHandler, + accountsProvider common.AccountNonceAndBalanceProvider, + latestExecutedHash []byte, +) error { + if sd.OnProposedBlockCalled != nil { + return sd.OnProposedBlockCalled(blockHash, blockBody, blockHeader, accountsProvider, latestExecutedHash) + } + return nil +} + +// OnBackfilledBlock - +func (sd *ShardedDataStub) OnBackfilledBlock( + _ []byte, + _ *block.Body, + _ data.HeaderHandler, +) error { + return nil +} + +// ResetTracker - +func (sd *ShardedDataStub) ResetTracker() { + if sd.ResetTrackerCalled != nil { + sd.ResetTrackerCalled() + } +} + +// IsInterfaceNil - func (sd *ShardedDataStub) IsInterfaceNil() bool { return sd == nil } diff --git a/testscommon/shardingMocks/nodesCoordinatorMock.go b/testscommon/shardingMocks/nodesCoordinatorMock.go index 033c394a91f..a4a9b5243fa 100644 --- a/testscommon/shardingMocks/nodesCoordinatorMock.go +++ b/testscommon/shardingMocks/nodesCoordinatorMock.go @@ -2,6 +2,7 @@ package shardingMocks import ( "bytes" + "errors" "fmt" "github.com/multiversx/mx-chain-core-go/core" @@ -234,8 +235,17 @@ func (ncm *NodesCoordinatorMock) ComputeConsensusGroup( validatorsGroup := make([]nodesCoordinator.Validator, 0) + shardValidators := ncm.Validators[shardId] for i := uint32(0); i < consensusSize; i++ { - validatorsGroup = append(validatorsGroup, ncm.Validators[shardId][i]) + if int(i) >= len(shardValidators) { + return nil, nil, errors.New("npc.ComputeConsensusGroup - invalid consensus group size") + } + + validatorsGroup = append(validatorsGroup, shardValidators[i]) + } + + if len(validatorsGroup) == 0 { + return nil, nil, errors.New("npc.ComputeConsensusGroup - empty validators group") } return validatorsGroup[0], validatorsGroup, nil diff --git a/testscommon/snapshotPruningStorerMock.go b/testscommon/snapshotPruningStorerMock.go index babe6160a25..cdd10cb46f5 100644 --- a/testscommon/snapshotPruningStorerMock.go +++ b/testscommon/snapshotPruningStorerMock.go @@ -14,8 +14,8 @@ func NewSnapshotPruningStorerMock() *SnapshotPruningStorerMock { return &SnapshotPruningStorerMock{NewMemDbMock()} } -// GetFromOldEpochsWithoutAddingToCache - -func (spsm *SnapshotPruningStorerMock) GetFromOldEpochsWithoutAddingToCache(key []byte, _ uint32) ([]byte, core.OptionalUint32, error) { +// GetWithoutAddingToCache - +func (spsm *SnapshotPruningStorerMock) GetWithoutAddingToCache(key []byte, _ uint32) ([]byte, core.OptionalUint32, error) { val, err := spsm.Get(key) return val, core.OptionalUint32{}, err diff --git a/testscommon/stakingcommon/stakingCommon.go b/testscommon/stakingcommon/stakingCommon.go index fbcb5ca30ea..61b3f2a3c62 100644 --- a/testscommon/stakingcommon/stakingCommon.go +++ b/testscommon/stakingcommon/stakingCommon.go @@ -11,6 +11,7 @@ import ( economicsHandler "github.com/multiversx/mx-chain-go/process/economics" "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/chainParameters" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" "github.com/multiversx/mx-chain-go/vm" @@ -232,11 +233,9 @@ func CreateEconomicsData() process.EconomicsDataHandler { maxGasLimitPerBlock := strconv.FormatUint(1500000000, 10) minGasPrice := strconv.FormatUint(10, 10) minGasLimit := strconv.FormatUint(10, 10) - cfg := &config.Config{EpochStartConfig: config.EpochStartConfig{RoundsPerEpoch: 14400}} - cfg.GeneralSettings.ChainParametersByEpoch = []config.ChainParametersByEpochConfig{{RoundDuration: 6000}} argsNewEconomicsData := economicsHandler.ArgsNewEconomicsData{ - GeneralConfig: cfg, + ChainParamsHandler: &chainParameters.ChainParametersHolderMock{}, Economics: &config.EconomicsConfig{ GlobalSettings: config.GlobalSettings{ GenesisTotalSupply: "2000000000000000000000", diff --git a/testscommon/state/accountWrapperMock.go b/testscommon/state/accountWrapperMock.go index 94b1acad8ad..557b36b601c 100644 --- a/testscommon/state/accountWrapperMock.go +++ b/testscommon/state/accountWrapperMock.go @@ -222,6 +222,11 @@ func (awm *AccountWrapMock) AccountDataHandler() vmcommon.AccountDataHandler { return awm.trackableDataTrie } +// SetNonce - +func (awm *AccountWrapMock) SetNonce(nonce uint64) { + awm.nonce = nonce +} + // GetNonce gets the nonce of the account func (awm *AccountWrapMock) GetNonce() uint64 { return awm.nonce diff --git a/testscommon/state/accountsAdapterStub.go b/testscommon/state/accountsAdapterStub.go index 65a2211d927..60e8898b5e4 100644 --- a/testscommon/state/accountsAdapterStub.go +++ b/testscommon/state/accountsAdapterStub.go @@ -4,9 +4,10 @@ import ( "context" "errors" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/state" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" ) var errNotImplemented = errors.New("not implemented") @@ -26,8 +27,11 @@ type AccountsStub struct { RevertToSnapshotCalled func(snapshot int) error RootHashCalled func() ([]byte, error) RecreateTrieCalled func(options common.RootHashHolder) error + RecreateTrieIfNeededCalled func(options common.RootHashHolder) error PruneTrieCalled func(rootHash []byte, identifier state.TriePruningIdentifier, handler state.PruningHandler) CancelPruneCalled func(rootHash []byte, identifier state.TriePruningIdentifier) + ResetPruningCalled func() + GetEvictionWaitingListSizeCalled func() int SnapshotStateCalled func(rootHash []byte, epoch uint32) IsPruningEnabledCalled func() bool GetAllLeavesCalled func(leavesChannels *common.TrieIteratorChannels, ctx context.Context, rootHash []byte, trieLeafParser common.TrieLeafParser) error @@ -186,6 +190,15 @@ func (as *AccountsStub) RecreateTrie(options common.RootHashHolder) error { return errNotImplemented } +// RecreateTrieIfNeeded - +func (as *AccountsStub) RecreateTrieIfNeeded(options common.RootHashHolder) error { + if as.RecreateTrieIfNeededCalled != nil { + return as.RecreateTrieIfNeededCalled(options) + } + + return errNotImplemented +} + // PruneTrie - func (as *AccountsStub) PruneTrie(rootHash []byte, identifier state.TriePruningIdentifier, handler state.PruningHandler) { as.PruneTrieCalled(rootHash, identifier, handler) @@ -198,6 +211,13 @@ func (as *AccountsStub) CancelPrune(rootHash []byte, identifier state.TriePrunin } } +// ResetPruning - +func (as *AccountsStub) ResetPruning() { + if as.ResetPruningCalled != nil { + as.ResetPruningCalled() + } +} + // SnapshotState - func (as *AccountsStub) SnapshotState(rootHash []byte, epoch uint32) { if as.SnapshotStateCalled != nil { @@ -266,6 +286,14 @@ func (as *AccountsStub) Close() error { return nil } +// GetEvictionWaitingListSize - +func (as *AccountsStub) GetEvictionWaitingListSize() int { + if as.GetEvictionWaitingListSizeCalled != nil { + return as.GetEvictionWaitingListSizeCalled() + } + return 0 +} + // IsInterfaceNil returns true if there is no value under the interface func (as *AccountsStub) IsInterfaceNil() bool { return as == nil diff --git a/testscommon/state/evictionWaitingListMock.go b/testscommon/state/evictionWaitingListMock.go index c071440d7b1..67e5ab702ae 100644 --- a/testscommon/state/evictionWaitingListMock.go +++ b/testscommon/state/evictionWaitingListMock.go @@ -81,6 +81,20 @@ func (ewl *EvictionWaitingListMock) ShouldKeepHash(hash string, identifier state return false, nil } +// Reset will reinitialize the cache +func (ewl *EvictionWaitingListMock) Reset() { + ewl.OpMutex.Lock() + ewl.Cache = make(map[string]common.ModifiedHashes) + ewl.OpMutex.Unlock() +} + +// CacheLen - +func (ewl *EvictionWaitingListMock) CacheLen() int { + ewl.OpMutex.RLock() + defer ewl.OpMutex.RUnlock() + return len(ewl.Cache) +} + // Close - func (ewl *EvictionWaitingListMock) Close() error { return nil diff --git a/testscommon/state/stateChangesCollectorStub.go b/testscommon/state/stateChangesCollectorStub.go index bc07e808ce5..d63ebdba279 100644 --- a/testscommon/state/stateChangesCollectorStub.go +++ b/testscommon/state/stateChangesCollectorStub.go @@ -13,8 +13,9 @@ type StateAccessesCollectorStub struct { AddTxHashToCollectedStateAccessesCalled func(txHash []byte) SetIndexToLatestStateAccessesCalled func(index int) error RevertToIndexCalled func(index int) error - GetCollectedAccessesCalled func() map[string]*stateChange.StateAccesses - StoreCalled func() error + GetStateAccessesForRootHashCalled func(rootHash []byte) map[string]*stateChange.StateAccesses + RemoveStateAccessesForRootHashCalled func(rootHash []byte) + CommitCollectedAccessesCalled func(rootHash []byte) error IsInterfaceNilCalled func() bool } @@ -65,19 +66,26 @@ func (s *StateAccessesCollectorStub) RevertToIndex(index int) error { return nil } -// GetCollectedAccesses - -func (s *StateAccessesCollectorStub) GetCollectedAccesses() map[string]*stateChange.StateAccesses { - if s.GetCollectedAccessesCalled != nil { - return s.GetCollectedAccessesCalled() +// GetStateAccessesForRootHash - +func (s *StateAccessesCollectorStub) GetStateAccessesForRootHash(rootHash []byte) map[string]*stateChange.StateAccesses { + if s.GetStateAccessesForRootHashCalled != nil { + return s.GetStateAccessesForRootHashCalled(rootHash) } return nil } -// Store - -func (s *StateAccessesCollectorStub) Store() error { - if s.StoreCalled != nil { - return s.StoreCalled() +// RemoveStateAccessesForRootHash - +func (s *StateAccessesCollectorStub) RemoveStateAccessesForRootHash(rootHash []byte) { + if s.RemoveStateAccessesForRootHashCalled != nil { + s.RemoveStateAccessesForRootHashCalled(rootHash) + } +} + +// CommitCollectedAccesses - +func (s *StateAccessesCollectorStub) CommitCollectedAccesses(rootHash []byte) error { + if s.CommitCollectedAccessesCalled != nil { + return s.CommitCollectedAccessesCalled(rootHash) } return nil diff --git a/testscommon/state/storagePruningManagerStub.go b/testscommon/state/storagePruningManagerStub.go index 92c697c5224..3e72ec56aec 100644 --- a/testscommon/state/storagePruningManagerStub.go +++ b/testscommon/state/storagePruningManagerStub.go @@ -7,10 +7,12 @@ import ( // StoragePruningManagerStub - type StoragePruningManagerStub struct { - MarkForEvictionCalled func(bytes []byte, bytes2 []byte, hashes common.ModifiedHashes, hashes2 common.ModifiedHashes) error - PruneTrieCalled func(rootHash []byte, identifier state.TriePruningIdentifier, tsm common.StorageManager, handler state.PruningHandler) - CancelPruneCalled func(rootHash []byte, identifier state.TriePruningIdentifier, tsm common.StorageManager) - CloseCalled func() error + MarkForEvictionCalled func(bytes []byte, bytes2 []byte, hashes common.ModifiedHashes, hashes2 common.ModifiedHashes) error + PruneTrieCalled func(rootHash []byte, identifier state.TriePruningIdentifier, tsm common.StorageManager, handler state.PruningHandler) + CancelPruneCalled func(rootHash []byte, identifier state.TriePruningIdentifier, tsm common.StorageManager) + ResetCalled func() + EvictionWaitingListCacheLenCalled func() int + CloseCalled func() error } // MarkForEviction - @@ -36,6 +38,13 @@ func (stub *StoragePruningManagerStub) CancelPrune(rootHash []byte, identifier s } } +// Reset - +func (stub *StoragePruningManagerStub) Reset() { + if stub.ResetCalled != nil { + stub.ResetCalled() + } +} + // Close - func (stub *StoragePruningManagerStub) Close() error { if stub.CloseCalled != nil { @@ -45,6 +54,14 @@ func (stub *StoragePruningManagerStub) Close() error { return nil } +// EvictionWaitingListCacheLen - +func (stub *StoragePruningManagerStub) EvictionWaitingListCacheLen() int { + if stub.EvictionWaitingListCacheLenCalled != nil { + return stub.EvictionWaitingListCacheLenCalled() + } + return 0 +} + // IsInterfaceNil - func (stub *StoragePruningManagerStub) IsInterfaceNil() bool { return stub == nil diff --git a/testscommon/state/testTriePruningStorer.go b/testscommon/state/testTriePruningStorer.go index fdf8c7a5d09..db0fae0eec2 100644 --- a/testscommon/state/testTriePruningStorer.go +++ b/testscommon/state/testTriePruningStorer.go @@ -85,3 +85,11 @@ func (pm *persisterMap) GetPersister(path string) storage.Persister { return persister } + +// AddPersister adds a persister for the given path +func (pm *persisterMap) AddPersister(path string, persister storage.Persister) { + pm.mutex.Lock() + defer pm.mutex.Unlock() + + pm.persisters[path] = persister +} diff --git a/testscommon/statusHandler/appStatusHandlerStub.go b/testscommon/statusHandler/appStatusHandlerStub.go index 245c1d1515a..2446104ce14 100644 --- a/testscommon/statusHandler/appStatusHandlerStub.go +++ b/testscommon/statusHandler/appStatusHandlerStub.go @@ -9,10 +9,14 @@ type AppStatusHandlerStub struct { SetInt64ValueHandler func(key string, value int64) SetStringValueHandler func(key string, value string) CloseHandler func() + IsInterfaceNilCalled func() bool } // IsInterfaceNil - func (ashs *AppStatusHandlerStub) IsInterfaceNil() bool { + if ashs.IsInterfaceNilCalled != nil { + return ashs.IsInterfaceNilCalled() + } return ashs == nil } diff --git a/testscommon/statusMetricsStub.go b/testscommon/statusMetricsStub.go index a0302cd2ab4..ad9593b4c60 100644 --- a/testscommon/statusMetricsStub.go +++ b/testscommon/statusMetricsStub.go @@ -8,6 +8,8 @@ type StatusMetricsStub struct { NetworkMetricsCalled func() (map[string]interface{}, error) EconomicsMetricsCalled func() (map[string]interface{}, error) EnableEpochsMetricsCalled func() (map[string]interface{}, error) + EnableEpochsMetricsV2Called func() map[string]uint32 + EnableRoundsMetricsCalled func() map[string]uint64 RatingsMetricsCalled func() (map[string]interface{}, error) StatusMetricsWithoutP2PPrometheusStringCalled func() (string, error) BootstrapMetricsCalled func() (map[string]interface{}, error) @@ -70,6 +72,22 @@ func (sms *StatusMetricsStub) EnableEpochsMetrics() (map[string]interface{}, err return baseReturnValues() } +// EnableEpochsMetricsV2 - +func (sms *StatusMetricsStub) EnableEpochsMetricsV2() map[string]uint32 { + if sms.EnableEpochsMetricsV2Called != nil { + return sms.EnableEpochsMetricsV2Called() + } + return make(map[string]uint32) +} + +// EnableRoundsMetrics - +func (sms *StatusMetricsStub) EnableRoundsMetrics() map[string]uint64 { + if sms.EnableRoundsMetricsCalled != nil { + return sms.EnableRoundsMetricsCalled() + } + return make(map[string]uint64) +} + // RatingsMetrics - func (sms *StatusMetricsStub) RatingsMetrics() (map[string]interface{}, error) { if sms.RatingsMetricsCalled != nil { diff --git a/testscommon/storage/persistersTrackerStub.go b/testscommon/storage/persistersTrackerStub.go new file mode 100644 index 00000000000..d4435f77d91 --- /dev/null +++ b/testscommon/storage/persistersTrackerStub.go @@ -0,0 +1,40 @@ +package storage + +import "github.com/multiversx/mx-chain-go/storage" + +// PersistersTrackerStub - +type PersistersTrackerStub struct { + HasInitializedEnoughPersistersCalled func(epoch int64) bool + ShouldClosePersisterCalled func(epoch int64) bool + CollectPersisterDataCalled func(p storage.Persister) +} + +// HasInitializedEnoughPersisters - +func (p *PersistersTrackerStub) HasInitializedEnoughPersisters(epoch int64) bool { + if p.HasInitializedEnoughPersistersCalled != nil { + return p.HasInitializedEnoughPersistersCalled(epoch) + } + + return false +} + +// ShouldClosePersister - +func (p *PersistersTrackerStub) ShouldClosePersister(epoch int64) bool { + if p.ShouldClosePersisterCalled != nil { + return p.ShouldClosePersisterCalled(epoch) + } + + return false +} + +// CollectPersisterData - +func (p *PersistersTrackerStub) CollectPersisterData(persister storage.Persister) { + if p.CollectPersisterDataCalled != nil { + p.CollectPersisterDataCalled(persister) + } +} + +// IsInterfaceNil - +func (p *PersistersTrackerStub) IsInterfaceNil() bool { + return p == nil +} diff --git a/testscommon/syncTimerMock.go b/testscommon/syncTimerMock.go index 14f949d0b08..6f312bc12f5 100644 --- a/testscommon/syncTimerMock.go +++ b/testscommon/syncTimerMock.go @@ -8,6 +8,10 @@ import ( type SyncTimerStub struct { } +// ForceSync - +func (sts *SyncTimerStub) ForceSync() { +} + // StartSyncingTime - func (sts *SyncTimerStub) StartSyncingTime() { } diff --git a/testscommon/testConfigs.go b/testscommon/testConfigs.go index fc0840e5237..9ebca88cb60 100644 --- a/testscommon/testConfigs.go +++ b/testscommon/testConfigs.go @@ -9,6 +9,9 @@ func GetDefaultRoundsConfig() config.RoundConfig { "DisableAsyncCallV1": { Round: "18446744073709551615", }, + "SupernovaEnableRound": { + Round: "9999999", + }, }, } } @@ -27,10 +30,5 @@ func GetDefaultHeaderVersionConfig() config.VersionsConfig { Version: "2", }, }, - Cache: config.CacheConfig{ - Name: "VersionsCache", - Type: "LRU", - Capacity: 100, - }, } } diff --git a/testscommon/transactionCoordinatorMock.go b/testscommon/transactionCoordinatorMock.go index 96d71b3ae75..b4822a672ab 100644 --- a/testscommon/transactionCoordinatorMock.go +++ b/testscommon/transactionCoordinatorMock.go @@ -5,6 +5,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/block/processedMb" @@ -19,13 +20,17 @@ type TransactionCoordinatorMock struct { SaveTxsToStorageCalled func(body *block.Body) RestoreBlockDataFromStorageCalled func(body *block.Body) (int, error) RemoveBlockDataFromPoolCalled func(body *block.Body) error - RemoveTxsFromPoolCalled func(body *block.Body) error + RemoveTxsFromPoolCalled func(body *block.Body, rootHashHolder common.RootHashHolder) error ProcessBlockTransactionCalled func(header data.HeaderHandler, body *block.Body, haveTime func() time.Duration) error + GetCreatedMiniBlocksFromMeCalled func() block.MiniBlockSlice CreateBlockStartedCalled func() + CreateMbsCrossShardDstMeCalled func(header data.HeaderHandler, processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo) ([]block.MiniblockAndHash, []block.MiniblockAndHash, uint32, bool, bool, error) CreateMbsAndProcessCrossShardTransactionsDstMeCalled func(header data.HeaderHandler, processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo, haveTime func() bool, haveAdditionalTime func() bool, scheduledMode bool) (block.MiniBlockSlice, uint32, bool, error) CreateMbsAndProcessTransactionsFromMeCalled func(haveTime func() bool) block.MiniBlockSlice CreateMarshalizedDataCalled func(body *block.Body) map[string][][]byte + CreateMarshalledDataForHeaderCalled func(header data.HeaderHandler, body *block.Body, miniBlocksMap map[string]block.MiniBlockSlice) map[string][][]byte GetCreatedInShardMiniBlocksCalled func() []*block.MiniBlock + SelectOutgoingTransactionsCalled func(nonce uint64, haveTimeForSelection func() bool) ([][]byte, []data.MiniBlockHeaderHandler) GetAllCurrentUsedTxsCalled func(blockType block.Type) map[string]data.TransactionHandler VerifyCreatedBlockTransactionsCalled func(hdr data.HeaderHandler, body *block.Body) error CreatePostProcessMiniBlocksCalled func() block.MiniBlockSlice @@ -35,10 +40,22 @@ type TransactionCoordinatorMock struct { AddTxsFromMiniBlocksCalled func(miniBlocks block.MiniBlockSlice) AddTransactionsCalled func(txHandlers []data.TransactionHandler, blockType block.Type) ComputeTransactionTypeInEpochCalled func(tx data.TransactionHandler, epoch uint32) (process.TransactionType, process.TransactionType, bool) + CreateReceiptsHashCalled func() ([]byte, error) + GetUnExecutableTransactionsCalled func() map[string]struct{} + ProposedDirectSentTransactionsToBroadcastCalled func(proposedBody data.BodyHandler) map[string][][]byte miniBlocks []*block.MiniBlock } +// GetUnExecutableTransactions - +func (tcm *TransactionCoordinatorMock) GetUnExecutableTransactions() map[string]struct{} { + if tcm.GetUnExecutableTransactionsCalled != nil { + return tcm.GetUnExecutableTransactionsCalled() + } + + return nil +} + // ComputeTransactionTypeInEpoch - func (tcm *TransactionCoordinatorMock) ComputeTransactionTypeInEpoch(tx data.TransactionHandler, epoch uint32) (process.TransactionType, process.TransactionType, bool) { if tcm.ComputeTransactionTypeInEpochCalled == nil { @@ -49,7 +66,7 @@ func (tcm *TransactionCoordinatorMock) ComputeTransactionTypeInEpoch(tx data.Tra } // GetAllCurrentLogs - -func (tcm *TransactionCoordinatorMock) GetAllCurrentLogs() []*data.LogData { +func (tcm *TransactionCoordinatorMock) GetAllCurrentLogs() []data.LogDataHandler { return nil } @@ -63,6 +80,9 @@ func (tcm *TransactionCoordinatorMock) CreatePostProcessMiniBlocks() block.MiniB // CreateReceiptsHash - func (tcm *TransactionCoordinatorMock) CreateReceiptsHash() ([]byte, error) { + if tcm.CreateReceiptsHashCalled != nil { + return tcm.CreateReceiptsHashCalled() + } return []byte("receiptHash"), nil } @@ -130,12 +150,12 @@ func (tcm *TransactionCoordinatorMock) RemoveBlockDataFromPool(body *block.Body) } // RemoveTxsFromPool - -func (tcm *TransactionCoordinatorMock) RemoveTxsFromPool(body *block.Body) error { +func (tcm *TransactionCoordinatorMock) RemoveTxsFromPool(body *block.Body, rootHashHolder common.RootHashHolder) error { if tcm.RemoveTxsFromPoolCalled == nil { return nil } - return tcm.RemoveTxsFromPoolCalled(body) + return tcm.RemoveTxsFromPoolCalled(body, rootHashHolder) } // ProcessBlockTransaction - @@ -147,6 +167,15 @@ func (tcm *TransactionCoordinatorMock) ProcessBlockTransaction(header data.Heade return tcm.ProcessBlockTransactionCalled(header, body, haveTime) } +// GetCreatedMiniBlocksFromMe - +func (tcm *TransactionCoordinatorMock) GetCreatedMiniBlocksFromMe() block.MiniBlockSlice { + if tcm.GetCreatedMiniBlocksFromMeCalled == nil { + return nil + } + + return tcm.GetCreatedMiniBlocksFromMeCalled() +} + // CreateBlockStarted - func (tcm *TransactionCoordinatorMock) CreateBlockStarted() { if tcm.CreateBlockStartedCalled == nil { @@ -156,6 +185,18 @@ func (tcm *TransactionCoordinatorMock) CreateBlockStarted() { tcm.CreateBlockStartedCalled() } +// CreateMbsCrossShardDstMe - +func (tcm *TransactionCoordinatorMock) CreateMbsCrossShardDstMe( + header data.HeaderHandler, + processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo, +) ([]block.MiniblockAndHash, []block.MiniblockAndHash, uint32, bool, bool, error) { + if tcm.CreateMbsCrossShardDstMeCalled == nil { + return nil, nil, 0, false, false, nil + } + + return tcm.CreateMbsCrossShardDstMeCalled(header, processedMiniBlocksInfo) +} + // CreateMbsAndProcessCrossShardTransactionsDstMe - func (tcm *TransactionCoordinatorMock) CreateMbsAndProcessCrossShardTransactionsDstMe( header data.HeaderHandler, @@ -171,6 +212,15 @@ func (tcm *TransactionCoordinatorMock) CreateMbsAndProcessCrossShardTransactions return tcm.CreateMbsAndProcessCrossShardTransactionsDstMeCalled(header, processedMiniBlocksInfo, haveTime, haveAdditionalTime, scheduledMode) } +// SelectOutgoingTransactions - +func (tcm *TransactionCoordinatorMock) SelectOutgoingTransactions(nonce uint64, haveTimeForSelection func() bool) ([][]byte, []data.MiniBlockHeaderHandler) { + if tcm.SelectOutgoingTransactionsCalled == nil { + return make([][]byte, 0), make([]data.MiniBlockHeaderHandler, 0) + } + + return tcm.SelectOutgoingTransactionsCalled(nonce, haveTimeForSelection) +} + // CreateMbsAndProcessTransactionsFromMe - func (tcm *TransactionCoordinatorMock) CreateMbsAndProcessTransactionsFromMe(haveTime func() bool, _ []byte) block.MiniBlockSlice { if tcm.CreateMbsAndProcessTransactionsFromMeCalled == nil { @@ -189,6 +239,15 @@ func (tcm *TransactionCoordinatorMock) CreateMarshalizedData(body *block.Body) m return tcm.CreateMarshalizedDataCalled(body) } +// CreateMarshalledDataForHeader - +func (tcm *TransactionCoordinatorMock) CreateMarshalledDataForHeader(header data.HeaderHandler, body *block.Body, miniBlocksMap map[string]block.MiniBlockSlice) map[string][][]byte { + if tcm.CreateMarshalledDataForHeaderCalled == nil { + return make(map[string][][]byte) + } + + return tcm.CreateMarshalledDataForHeaderCalled(header, body, miniBlocksMap) +} + // GetAllCurrentUsedTxs - func (tcm *TransactionCoordinatorMock) GetAllCurrentUsedTxs(blockType block.Type) map[string]data.TransactionHandler { if tcm.GetAllCurrentUsedTxsCalled == nil { @@ -262,10 +321,20 @@ func (tcm *TransactionCoordinatorMock) AddTransactions(txHandlers []data.Transac tcm.AddTransactionsCalled(txHandlers, blockType) } +// ClearStoredMbs - func (tcm *TransactionCoordinatorMock) ClearStoredMbs() { tcm.miniBlocks = make([]*block.MiniBlock, 0) } +// ProposedDirectSentTransactionsToBroadcast - +func (tcm *TransactionCoordinatorMock) ProposedDirectSentTransactionsToBroadcast(proposedBody data.BodyHandler) map[string][][]byte { + if tcm.ProposedDirectSentTransactionsToBroadcastCalled == nil { + return nil + } + + return tcm.ProposedDirectSentTransactionsToBroadcastCalled(proposedBody) +} + // IsInterfaceNil returns true if there is no value under the interface func (tcm *TransactionCoordinatorMock) IsInterfaceNil() bool { return tcm == nil diff --git a/testscommon/trie/snapshotPruningStorerStub.go b/testscommon/trie/snapshotPruningStorerStub.go index 728cef8c677..8de709ab2cd 100644 --- a/testscommon/trie/snapshotPruningStorerStub.go +++ b/testscommon/trie/snapshotPruningStorerStub.go @@ -8,22 +8,22 @@ import ( // SnapshotPruningStorerStub - type SnapshotPruningStorerStub struct { *testscommon.MemDbMock - GetFromOldEpochsWithoutAddingToCacheCalled func(key []byte, maxEpochToSearchFrom uint32) ([]byte, core.OptionalUint32, error) - GetFromLastEpochCalled func(key []byte) ([]byte, error) - GetFromCurrentEpochCalled func(key []byte) ([]byte, error) - GetFromEpochCalled func(key []byte, epoch uint32) ([]byte, error) - PutInEpochCalled func(key []byte, data []byte, epoch uint32) error - PutInEpochWithoutCacheCalled func(key []byte, data []byte, epoch uint32) error - GetLatestStorageEpochCalled func() (uint32, error) - RemoveFromCurrentEpochCalled func(key []byte) error - CloseCalled func() error - RemoveFromAllActiveEpochsCalled func(key []byte) error + GetWithoutAddingToCacheCalled func(key []byte, maxEpochToSearchFrom uint32) ([]byte, core.OptionalUint32, error) + GetFromLastEpochCalled func(key []byte) ([]byte, error) + GetFromCurrentEpochCalled func(key []byte) ([]byte, error) + GetFromEpochCalled func(key []byte, epoch uint32) ([]byte, error) + PutInEpochCalled func(key []byte, data []byte, epoch uint32) error + PutInEpochWithoutCacheCalled func(key []byte, data []byte, epoch uint32) error + GetLatestStorageEpochCalled func() (uint32, error) + RemoveFromCurrentEpochCalled func(key []byte) error + CloseCalled func() error + RemoveFromAllActiveEpochsCalled func(key []byte) error } -// GetFromOldEpochsWithoutAddingToCache - -func (spss *SnapshotPruningStorerStub) GetFromOldEpochsWithoutAddingToCache(key []byte, maxEpochToSearchFrom uint32) ([]byte, core.OptionalUint32, error) { - if spss.GetFromOldEpochsWithoutAddingToCacheCalled != nil { - return spss.GetFromOldEpochsWithoutAddingToCacheCalled(key, maxEpochToSearchFrom) +// GetWithoutAddingToCache - +func (spss *SnapshotPruningStorerStub) GetWithoutAddingToCache(key []byte, maxEpochToSearchFrom uint32) ([]byte, core.OptionalUint32, error) { + if spss.GetWithoutAddingToCacheCalled != nil { + return spss.GetWithoutAddingToCacheCalled(key, maxEpochToSearchFrom) } return nil, core.OptionalUint32{}, nil diff --git a/testscommon/trie/statisticsMock.go b/testscommon/trie/statisticsMock.go index 311b9d802e0..0a58be3e82f 100644 --- a/testscommon/trie/statisticsMock.go +++ b/testscommon/trie/statisticsMock.go @@ -6,6 +6,7 @@ import ( // MockStatistics - type MockStatistics struct { + AddTrieStatsCalled func(common.TrieStatisticsHandler, common.TrieType) WaitForSnapshotsToFinishCalled func() } @@ -25,7 +26,10 @@ func (m *MockStatistics) WaitForSnapshotsToFinish() { } // AddTrieStats - -func (m *MockStatistics) AddTrieStats(_ common.TrieStatisticsHandler, _ common.TrieType) { +func (m *MockStatistics) AddTrieStats(stats common.TrieStatisticsHandler, t common.TrieType) { + if m.AddTrieStatsCalled != nil { + m.AddTrieStatsCalled(stats, t) + } } // GetSnapshotDuration - @@ -38,6 +42,9 @@ func (m *MockStatistics) GetSnapshotNumNodes() uint64 { return 0 } +// IncrementThrottlerWaits - +func (m *MockStatistics) IncrementThrottlerWaits() {} + // IsInterfaceNil returns true if there is no value under the interface func (m *MockStatistics) IsInterfaceNil() bool { return m == nil diff --git a/testscommon/txProcessorMock.go b/testscommon/txProcessorMock.go index 7b39ef87d13..a47141c5047 100644 --- a/testscommon/txProcessorMock.go +++ b/testscommon/txProcessorMock.go @@ -5,8 +5,9 @@ import ( "github.com/multiversx/mx-chain-core-go/data/smartContractResult" "github.com/multiversx/mx-chain-core-go/data/transaction" - "github.com/multiversx/mx-chain-go/state" vmcommon "github.com/multiversx/mx-chain-vm-common-go" + + "github.com/multiversx/mx-chain-go/state" ) // TxProcessorMock - diff --git a/testscommon/txProcessorStub.go b/testscommon/txProcessorStub.go index 7407800ef6f..3c29acecb3a 100644 --- a/testscommon/txProcessorStub.go +++ b/testscommon/txProcessorStub.go @@ -2,8 +2,9 @@ package testscommon import ( "github.com/multiversx/mx-chain-core-go/data/transaction" - "github.com/multiversx/mx-chain-go/state" vmcommon "github.com/multiversx/mx-chain-vm-common-go" + + "github.com/multiversx/mx-chain-go/state" ) // TxProcessorStub - diff --git a/testscommon/txcachemocks/accountNonceAndBalanceProviderMock.go b/testscommon/txcachemocks/accountNonceAndBalanceProviderMock.go new file mode 100644 index 00000000000..522b5b75346 --- /dev/null +++ b/testscommon/txcachemocks/accountNonceAndBalanceProviderMock.go @@ -0,0 +1,127 @@ +package txcachemocks + +import ( + "math/big" + "sync" + + "github.com/multiversx/mx-chain-core-go/core/check" + stateMock "github.com/multiversx/mx-chain-go/testscommon/state" +) + +// AccountNonceAndBalanceProviderMock - +type AccountNonceAndBalanceProviderMock struct { + mutex sync.Mutex + accountByAddress map[string]*stateMock.UserAccountStub + + NumCallsGetAccountNonce int + NumCallsGetAccountNonceAndBalance int + GetAccountNonceCalled func(address []byte) (uint64, bool, error) + GetAccountNonceAndBalanceCalled func(address []byte) (uint64, *big.Int, bool, error) + GetRootHashCalled func() ([]byte, error) +} + +// NewAccountNonceAndBalanceProviderMock - +func NewAccountNonceAndBalanceProviderMock() *AccountNonceAndBalanceProviderMock { + return &AccountNonceAndBalanceProviderMock{ + accountByAddress: make(map[string]*stateMock.UserAccountStub), + } +} + +// NewAccountNonceAndBalanceProviderMockWithAccounts - +func NewAccountNonceAndBalanceProviderMockWithAccounts(accountsByAddress map[string]*stateMock.UserAccountStub) *AccountNonceAndBalanceProviderMock { + return &AccountNonceAndBalanceProviderMock{ + accountByAddress: accountsByAddress, + } +} + +// SetNonce - +func (mock *AccountNonceAndBalanceProviderMock) SetNonce(address []byte, nonce uint64) { + mock.mutex.Lock() + defer mock.mutex.Unlock() + + key := string(address) + + if mock.accountByAddress[key] == nil { + mock.accountByAddress[key] = newDefaultAccount() + } + + mock.accountByAddress[key].Nonce = nonce +} + +// SetBalance - +func (mock *AccountNonceAndBalanceProviderMock) SetBalance(address []byte, balance *big.Int) { + mock.mutex.Lock() + defer mock.mutex.Unlock() + + key := string(address) + + if mock.accountByAddress[key] == nil { + mock.accountByAddress[key] = newDefaultAccount() + } + + mock.accountByAddress[key].Balance = balance +} + +// GetAccountNonce - +func (mock *AccountNonceAndBalanceProviderMock) GetAccountNonce(address []byte) (uint64, bool, error) { + mock.mutex.Lock() + defer mock.mutex.Unlock() + + mock.NumCallsGetAccountNonce++ + + if mock.GetAccountNonceCalled != nil { + return mock.GetAccountNonceCalled(address) + } + + account, ok := mock.accountByAddress[string(address)] + if ok { + if check.IfNil(account) { + // This mock allows one to add "nil" (unknown) accounts in "AccountByAddress". + return 0, false, nil + } + + return account.Nonce, true, nil + } + + account = newDefaultAccount() + return account.Nonce, true, nil +} + +// GetAccountNonceAndBalance - +func (mock *AccountNonceAndBalanceProviderMock) GetAccountNonceAndBalance(address []byte) (uint64, *big.Int, bool, error) { + mock.mutex.Lock() + defer mock.mutex.Unlock() + + mock.NumCallsGetAccountNonceAndBalance++ + + if mock.GetAccountNonceAndBalanceCalled != nil { + return mock.GetAccountNonceAndBalanceCalled(address) + } + + account, ok := mock.accountByAddress[string(address)] + if ok { + if check.IfNil(account) { + // This mock allows one to add "nil" (unknown) accounts in "AccountByAddress". + return 0, big.NewInt(0), false, nil + } + + return account.Nonce, account.Balance, true, nil + } + + account = newDefaultAccount() + return account.Nonce, account.Balance, true, nil +} + +// GetRootHash - +func (mock *AccountNonceAndBalanceProviderMock) GetRootHash() ([]byte, error) { + if mock.GetRootHashCalled != nil { + return mock.GetRootHashCalled() + } + + return nil, nil +} + +// IsInterfaceNil - +func (mock *AccountNonceAndBalanceProviderMock) IsInterfaceNil() bool { + return mock == nil +} diff --git a/testscommon/txcachemocks/mempoolHostMock.go b/testscommon/txcachemocks/mempoolHostMock.go index 3e9ecc995ec..e90d7115d1a 100644 --- a/testscommon/txcachemocks/mempoolHostMock.go +++ b/testscommon/txcachemocks/mempoolHostMock.go @@ -3,18 +3,33 @@ package txcachemocks import ( "math/big" + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" ) // MempoolHostMock - type MempoolHostMock struct { + minGasLimit uint64 + minGasPrice uint64 + gasPerDataByte uint64 + gasPriceModifier float64 + extraGasLimitForGuarded uint64 + extraGasLimitForRelayed uint64 + ComputeTxFeeCalled func(tx data.TransactionWithFeeHandler) *big.Int GetTransferredValueCalled func(tx data.TransactionHandler) *big.Int } // NewMempoolHostMock - func NewMempoolHostMock() *MempoolHostMock { - return &MempoolHostMock{} + return &MempoolHostMock{ + minGasLimit: 50_000, + minGasPrice: 1_000_000_000, + gasPerDataByte: 1500, + gasPriceModifier: 0.01, + extraGasLimitForGuarded: 50_000, + extraGasLimitForRelayed: 50_000, + } } // ComputeTxFee - @@ -23,7 +38,29 @@ func (mock *MempoolHostMock) ComputeTxFee(tx data.TransactionWithFeeHandler) *bi return mock.ComputeTxFeeCalled(tx) } - return big.NewInt(0) + dataLength := uint64(len(tx.GetData())) + gasPriceForMovement := tx.GetGasPrice() + gasPriceForProcessing := uint64(float64(gasPriceForMovement) * mock.gasPriceModifier) + + gasLimitForMovement := mock.minGasLimit + dataLength*mock.gasPerDataByte + + if txAsGuarded, ok := tx.(data.GuardedTransactionHandler); ok && len(txAsGuarded.GetGuardianAddr()) > 0 { + gasLimitForMovement += mock.extraGasLimitForGuarded + } + + if txAsRelayed, ok := tx.(data.RelayedTransactionHandler); ok && len(txAsRelayed.GetRelayerAddr()) > 0 { + gasLimitForMovement += mock.extraGasLimitForRelayed + } + + if tx.GetGasLimit() < gasLimitForMovement { + panic("tx.GetGasLimit() < gasLimitForMovement") + } + + gasLimitForProcessing := tx.GetGasLimit() - gasLimitForMovement + feeForMovement := core.SafeMul(gasPriceForMovement, gasLimitForMovement) + feeForProcessing := core.SafeMul(gasPriceForProcessing, gasLimitForProcessing) + fee := big.NewInt(0).Add(feeForMovement, feeForProcessing) + return fee } // GetTransferredValue - diff --git a/testscommon/txcachemocks/selectionSessionMock.go b/testscommon/txcachemocks/selectionSessionMock.go new file mode 100644 index 00000000000..ba8c7aa51eb --- /dev/null +++ b/testscommon/txcachemocks/selectionSessionMock.go @@ -0,0 +1,127 @@ +package txcachemocks + +import ( + "math/big" + "sync" + + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + stateMock "github.com/multiversx/mx-chain-go/testscommon/state" +) + +// SelectionSessionMock - +type SelectionSessionMock struct { + mutex sync.Mutex + accountByAddress map[string]*stateMock.UserAccountStub + + NumCallsGetAccountNonceAndBalance int + GetAccountNonceAndBalanceCalled func(address []byte) (uint64, *big.Int, bool, error) + GetRootHashCalled func() ([]byte, error) + IsIncorrectlyGuardedCalled func(tx data.TransactionHandler) bool + IsGuardedCalled func(tx data.TransactionHandler) bool +} + +// NewSelectionSessionMock - +func NewSelectionSessionMock() *SelectionSessionMock { + return &SelectionSessionMock{ + accountByAddress: make(map[string]*stateMock.UserAccountStub), + } +} + +// NewSelectionSessionMockWithAccounts - +func NewSelectionSessionMockWithAccounts(accountsByAddress map[string]*stateMock.UserAccountStub) *SelectionSessionMock { + return &SelectionSessionMock{ + accountByAddress: accountsByAddress, + } +} + +// SetNonce - +func (mock *SelectionSessionMock) SetNonce(address []byte, nonce uint64) { + mock.mutex.Lock() + defer mock.mutex.Unlock() + + key := string(address) + + if mock.accountByAddress[key] == nil { + mock.accountByAddress[key] = newDefaultAccount() + } + + mock.accountByAddress[key].Nonce = nonce +} + +// SetBalance - +func (mock *SelectionSessionMock) SetBalance(address []byte, balance *big.Int) { + mock.mutex.Lock() + defer mock.mutex.Unlock() + + key := string(address) + + if mock.accountByAddress[key] == nil { + mock.accountByAddress[key] = newDefaultAccount() + } + + mock.accountByAddress[key].Balance = balance +} + +// GetAccountNonceAndBalance - +func (mock *SelectionSessionMock) GetAccountNonceAndBalance(address []byte) (uint64, *big.Int, bool, error) { + mock.mutex.Lock() + defer mock.mutex.Unlock() + + mock.NumCallsGetAccountNonceAndBalance++ + + if mock.GetAccountNonceAndBalanceCalled != nil { + return mock.GetAccountNonceAndBalanceCalled(address) + } + + account, ok := mock.accountByAddress[string(address)] + if ok { + if check.IfNil(account) { + // This mock allows one to add "nil" (unknown) accounts in "AccountByAddress". + return 0, big.NewInt(0), false, nil + } + + return account.Nonce, account.Balance, true, nil + } + + account = newDefaultAccount() + return account.Nonce, account.Balance, true, nil +} + +// GetRootHash - +func (mock *SelectionSessionMock) GetRootHash() ([]byte, error) { + if mock.GetRootHashCalled != nil { + return mock.GetRootHashCalled() + } + return nil, nil +} + +// IsIncorrectlyGuarded - +func (mock *SelectionSessionMock) IsIncorrectlyGuarded(tx data.TransactionHandler) bool { + if mock.IsIncorrectlyGuardedCalled != nil { + return mock.IsIncorrectlyGuardedCalled(tx) + } + + return false +} + +// IsGuarded - +func (mock *SelectionSessionMock) IsGuarded(tx data.TransactionHandler) bool { + if mock.IsGuardedCalled != nil { + return mock.IsGuardedCalled(tx) + } + + return false +} + +// IsInterfaceNil - +func (mock *SelectionSessionMock) IsInterfaceNil() bool { + return mock == nil +} + +func newDefaultAccount() *stateMock.UserAccountStub { + return &stateMock.UserAccountStub{ + Nonce: 0, + Balance: big.NewInt(1000000000000000000), + } +} diff --git a/testscommon/txcachestubs/txCacheStub.go b/testscommon/txcachestubs/txCacheStub.go new file mode 100644 index 00000000000..a83e2e907a6 --- /dev/null +++ b/testscommon/txcachestubs/txCacheStub.go @@ -0,0 +1,50 @@ +package txcachestubs + +import ( + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/txcache" +) + +// TxCacheStub - +type TxCacheStub struct { + SelectTransactionsCalled func(session txcache.SelectionSession, options common.TxSelectionOptions, currentBlockNonce uint64) ([]*txcache.WrappedTransaction, uint64, error) + SimulateSelectTransactionsCalled func(session txcache.SelectionSession, options common.TxSelectionOptions, currentBlockNonce uint64) ([]*txcache.WrappedTransaction, uint64, error) + GetVirtualNonceAndRootHashCalled func(sender []byte) (uint64, []byte, error) + SetAOTSelectionPreempterCalled func(preempter common.AOTSelectionPreempter) +} + +// SelectTransactions - +func (stub *TxCacheStub) SelectTransactions(session txcache.SelectionSession, options common.TxSelectionOptions, currentBlockNonce uint64) ([]*txcache.WrappedTransaction, uint64, error) { + if stub.SelectTransactionsCalled != nil { + return stub.SelectTransactionsCalled(session, options, currentBlockNonce) + } + return nil, 0, nil +} + +// SimulateSelectTransactions - +func (stub *TxCacheStub) SimulateSelectTransactions(session txcache.SelectionSession, options common.TxSelectionOptions, currentBlockNonce uint64) ([]*txcache.WrappedTransaction, uint64, error) { + if stub.SimulateSelectTransactionsCalled != nil { + return stub.SimulateSelectTransactionsCalled(session, options, currentBlockNonce) + } + return nil, 0, nil +} + +// GetVirtualNonceAndRootHash - +func (stub *TxCacheStub) GetVirtualNonceAndRootHash(sender []byte) (uint64, []byte, error) { + if stub.GetVirtualNonceAndRootHashCalled != nil { + return stub.GetVirtualNonceAndRootHashCalled(sender) + } + return 0, nil, nil +} + +// SetAOTSelectionPreempter - +func (stub *TxCacheStub) SetAOTSelectionPreempter(preempter common.AOTSelectionPreempter) { + if stub.SetAOTSelectionPreempterCalled != nil { + stub.SetAOTSelectionPreempterCalled(preempter) + } +} + +// IsInterfaceNil - +func (stub *TxCacheStub) IsInterfaceNil() bool { + return stub == nil +} diff --git a/testscommon/utils.go b/testscommon/utils.go index daf015f6574..ed51d1265b5 100644 --- a/testscommon/utils.go +++ b/testscommon/utils.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/storage" "github.com/multiversx/mx-chain-go/storage/database" "github.com/multiversx/mx-chain-go/storage/storageunit" @@ -28,3 +29,16 @@ func CreateMemUnit() storage.Storer { unit, _ := storageunit.NewStorageUnit(cache, persist) return unit } + +// NewNTPGoogleConfig creates an NTPConfig object that configures NTP to use a predefined list of hosts. This is useful +// for tests, for example, to avoid loading a configuration file just to have a NTPConfig +func NewNTPGoogleConfig() config.NTPConfig { + return config.NTPConfig{ + Hosts: []string{"time.google.com", "time.cloudflare.com", "time.apple.com", "time.windows.com"}, + Port: 123, + Version: 0, + TimeoutMilliseconds: 100, + SyncPeriodSeconds: 3600, + OutOfBoundsThreshold: 120, + } +} diff --git a/testscommon/validatorStatisticsProcessorStub.go b/testscommon/validatorStatisticsProcessorStub.go index 4d588610d31..7759133d857 100644 --- a/testscommon/validatorStatisticsProcessorStub.go +++ b/testscommon/validatorStatisticsProcessorStub.go @@ -2,12 +2,14 @@ package testscommon import ( "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/state" ) // ValidatorStatisticsProcessorStub - type ValidatorStatisticsProcessorStub struct { UpdatePeerStateCalled func(header data.MetaHeaderHandler) ([]byte, error) + UpdatePeerStateV3Called func(header data.MetaHeaderHandler, metaExecutionResult data.MetaExecutionResultHandler) ([]byte, error) RevertPeerStateCalled func(header data.MetaHeaderHandler) error GetPeerAccountCalled func(address []byte) (state.PeerAccountHandler, error) RootHashCalled func() ([]byte, error) @@ -71,6 +73,14 @@ func (vsp *ValidatorStatisticsProcessorStub) UpdatePeerState(header data.MetaHea return nil, nil } +// UpdatePeerStateV3 - +func (vsp *ValidatorStatisticsProcessorStub) UpdatePeerStateV3(header data.MetaHeaderHandler, _ map[string]data.HeaderHandler, metaExecResult data.MetaExecutionResultHandler) ([]byte, error) { + if vsp.UpdatePeerStateV3Called != nil { + return vsp.UpdatePeerStateV3Called(header, metaExecResult) + } + return nil, nil +} + // ProcessRatingsEndOfEpoch - func (vsp *ValidatorStatisticsProcessorStub) ProcessRatingsEndOfEpoch(validatorInfos state.ShardValidatorsInfoMapHandler, epoch uint32) error { if vsp.ProcessRatingsEndOfEpochCalled != nil { diff --git a/testscommon/versionedHeaderFactoryStub.go b/testscommon/versionedHeaderFactoryStub.go index e18a01439b3..0e91f2e2b1b 100644 --- a/testscommon/versionedHeaderFactoryStub.go +++ b/testscommon/versionedHeaderFactoryStub.go @@ -4,13 +4,13 @@ import "github.com/multiversx/mx-chain-core-go/data" // VersionedHeaderFactoryStub - type VersionedHeaderFactoryStub struct { - CreateCalled func(epoch uint32) data.HeaderHandler + CreateCalled func(epoch uint32, round uint64) data.HeaderHandler } // Create - -func (vhfs *VersionedHeaderFactoryStub) Create(epoch uint32) data.HeaderHandler { +func (vhfs *VersionedHeaderFactoryStub) Create(epoch uint32, round uint64) data.HeaderHandler { if vhfs.CreateCalled != nil { - return vhfs.CreateCalled(epoch) + return vhfs.CreateCalled(epoch, round) } return nil } diff --git a/trie/branchNode.go b/trie/branchNode.go index 6fac5a13581..8e3584d0589 100644 --- a/trie/branchNode.go +++ b/trie/branchNode.go @@ -11,9 +11,10 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" + vmcommon "github.com/multiversx/mx-chain-vm-common-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/trie/leavesRetriever/trieNodeData" - vmcommon "github.com/multiversx/mx-chain-vm-common-go" ) var _ = node(&branchNode{}) @@ -291,65 +292,49 @@ func (bn *branchNode) commitDirty(level byte, maxTrieLevelInMemory uint, originD return nil } +// TODO refactor long parameter list func (bn *branchNode) commitSnapshot( - db common.TrieStorageInteractor, + db snapshotDb, + maxEpochToSearchFrom uint32, leavesChan chan core.KeyValueHolder, missingNodesChan chan []byte, ctx context.Context, stats common.TrieStatisticsHandler, idleProvider IdleNodeProvider, + nodeBytes []byte, depthLevel int, ) error { if shouldStopIfContextDoneBlockingIfBusy(ctx, idleProvider) { return core.ErrContextClosing } - err := bn.isEmptyOrNil() - if err != nil { - return fmt.Errorf("commit snapshot error %w", err) - } - - for i := range bn.children { - err = resolveIfCollapsed(bn, byte(i), db) - childIsMissing, err := treatCommitSnapshotError(err, bn.EncodedChildren[i], missingNodesChan) - if err != nil { - return err - } - if childIsMissing { - continue - } - - if bn.children[i] == nil { + for i := range bn.EncodedChildren { + if len(bn.EncodedChildren[i]) == 0 { continue } - err = bn.children[i].commitSnapshot(db, leavesChan, missingNodesChan, ctx, stats, idleProvider, depthLevel+1) + err := commitSnapshot( + db, + maxEpochToSearchFrom, + bn.marsh, + bn.hasher, + leavesChan, + missingNodesChan, + ctx, + stats, + idleProvider, + depthLevel, + bn.EncodedChildren[i], + ) if err != nil { return err } } - return bn.saveToStorage(db, stats, depthLevel) -} - -func (bn *branchNode) saveToStorage(targetDb common.BaseStorer, stats common.TrieStatisticsHandler, depthLevel int) error { - nodeSize, err := encodeNodeAndCommitToDB(bn, targetDb) - if err != nil { - return err - } - - stats.AddBranchNode(depthLevel, uint64(nodeSize)) - - bn.removeChildrenPointers() + stats.AddBranchNode(depthLevel, uint64(len(nodeBytes))) return nil } -func (bn *branchNode) removeChildrenPointers() { - for i := range bn.children { - bn.children[i] = nil - } -} - func (bn *branchNode) getEncodedNode() ([]byte, error) { err := bn.isEmptyOrNil() if err != nil { diff --git a/trie/branchNode_test.go b/trie/branchNode_test.go index 687a0d01023..4cea8910bad 100644 --- a/trie/branchNode_test.go +++ b/trie/branchNode_test.go @@ -17,6 +17,7 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" "github.com/multiversx/mx-chain-go/trie/keyBuilder" + "github.com/multiversx/mx-chain-go/trie/mock" "github.com/multiversx/mx-chain-go/trie/statistics" "github.com/stretchr/testify/assert" ) @@ -1024,7 +1025,7 @@ func TestBranchNode_getChildrenCollapsedBn(t *testing.T) { db := testscommon.NewMemDbMock() bn, collapsedBn := getBnAndCollapsedBn(getTestMarshalizerAndHasher()) - _ = bn.commitSnapshot(db, nil, nil, context.Background(), statistics.NewTrieStatistics(), &testscommon.ProcessStatusHandlerStub{}, 0) + _ = bn.commitDirty(0, 5, db, db) children, err := collapsedBn.getChildren(db) assert.Nil(t, err) @@ -1224,8 +1225,8 @@ func TestBranchNode_printShouldNotPanicEvenIfNodeIsCollapsed(t *testing.T) { db := testscommon.NewMemDbMock() bn, collapsedBn := getBnAndCollapsedBn(getTestMarshalizerAndHasher()) - _ = bn.commitSnapshot(db, nil, nil, context.Background(), statistics.NewTrieStatistics(), &testscommon.ProcessStatusHandlerStub{}, 0) - _ = collapsedBn.commitSnapshot(db, nil, nil, context.Background(), statistics.NewTrieStatistics(), &testscommon.ProcessStatusHandlerStub{}, 0) + _ = bn.commitDirty(0, 5, db, db) + _ = collapsedBn.commitDirty(0, 5, db, db) bn.print(bnWriter, 0, db) collapsedBn.print(collapsedBnWriter, 0, db) @@ -1262,7 +1263,7 @@ func TestBranchNode_getAllHashesResolvesCollapsed(t *testing.T) { db := testscommon.NewMemDbMock() bn, collapsedBn := getBnAndCollapsedBn(getTestMarshalizerAndHasher()) - _ = bn.commitSnapshot(db, nil, nil, context.Background(), statistics.NewTrieStatistics(), &testscommon.ProcessStatusHandlerStub{}, 0) + _ = bn.commitDirty(0, 5, db, db) hashes, err := collapsedBn.getAllHashes(db) assert.Nil(t, err) @@ -1329,26 +1330,26 @@ func TestBranchNode_SizeInBytes(t *testing.T) { func TestBranchNode_commitContextDone(t *testing.T) { t.Parallel() - db := testscommon.NewMemDbMock() + db := mock.NewSnapshotDbMock() bn, _ := getBnAndCollapsedBn(getTestMarshalizerAndHasher()) ctx, cancel := context.WithCancel(context.Background()) cancel() - err := bn.commitSnapshot(db, nil, nil, ctx, statistics.NewTrieStatistics(), &testscommon.ProcessStatusHandlerStub{}, 0) + err := bn.commitSnapshot(db, 0, nil, nil, ctx, statistics.NewTrieStatistics(), &testscommon.ProcessStatusHandlerStub{}, []byte{}, 0) assert.Equal(t, core.ErrContextClosing, err) } func TestBranchNode_commitSnapshotDbIsClosing(t *testing.T) { t.Parallel() - db := testscommon.NewMemDbMock() + db := mock.NewSnapshotDbMock() db.GetCalled = func(key []byte) ([]byte, error) { return nil, core.ErrContextClosing } _, collapsedBn := getBnAndCollapsedBn(getTestMarshalizerAndHasher()) missingNodesChan := make(chan []byte, 10) - err := collapsedBn.commitSnapshot(db, nil, missingNodesChan, context.Background(), statistics.NewTrieStatistics(), &testscommon.ProcessStatusHandlerStub{}, 0) + err := collapsedBn.commitSnapshot(db, 0, nil, missingNodesChan, context.Background(), statistics.NewTrieStatistics(), &testscommon.ProcessStatusHandlerStub{}, []byte{}, 0) assert.True(t, core.IsClosingError(err)) assert.Equal(t, 0, len(missingNodesChan)) } @@ -1356,14 +1357,14 @@ func TestBranchNode_commitSnapshotDbIsClosing(t *testing.T) { func TestBranchNode_commitSnapshotChildIsMissingErr(t *testing.T) { t.Parallel() - db := testscommon.NewMemDbMock() + db := mock.NewSnapshotDbMock() db.GetCalled = func(key []byte) ([]byte, error) { return nil, core.NewGetNodeFromDBErrWithKey(key, ErrKeyNotFound, "test") } _, collapsedBn := getBnAndCollapsedBn(getTestMarshalizerAndHasher()) missingNodesChan := make(chan []byte, 10) - err := collapsedBn.commitSnapshot(db, nil, missingNodesChan, context.Background(), statistics.NewTrieStatistics(), &testscommon.ProcessStatusHandlerStub{}, 0) + err := collapsedBn.commitSnapshot(db, 0, nil, missingNodesChan, context.Background(), statistics.NewTrieStatistics(), &testscommon.ProcessStatusHandlerStub{}, []byte{}, 0) assert.Nil(t, err) assert.Equal(t, 3, len(missingNodesChan)) } diff --git a/trie/export_test.go b/trie/export_test.go index 06d7896f3c5..dea9315ffab 100644 --- a/trie/export_test.go +++ b/trie/export_test.go @@ -82,7 +82,7 @@ func IsBaseTrieStorageManager(tsm common.StorageManager) bool { return ok } -// IsInEpochTrieStorageManager - +// IsTrieStorageManagerInEpoch - func IsTrieStorageManagerInEpoch(tsm common.StorageManager) bool { _, ok := tsm.(*trieStorageManagerInEpoch) return ok diff --git a/trie/extensionNode.go b/trie/extensionNode.go index 9ff667a7b63..0c1a657665b 100644 --- a/trie/extensionNode.go +++ b/trie/extensionNode.go @@ -211,48 +211,38 @@ func (en *extensionNode) commitDirty(level byte, maxTrieLevelInMemory uint, orig } func (en *extensionNode) commitSnapshot( - db common.TrieStorageInteractor, + db snapshotDb, + maxEpochToSearchFrom uint32, leavesChan chan core.KeyValueHolder, missingNodesChan chan []byte, ctx context.Context, stats common.TrieStatisticsHandler, idleProvider IdleNodeProvider, + nodeBytes []byte, depthLevel int, ) error { if shouldStopIfContextDoneBlockingIfBusy(ctx, idleProvider) { return core.ErrContextClosing } - err := en.isEmptyOrNil() - if err != nil { - return fmt.Errorf("commit snapshot error %w", err) - } - - err = resolveIfCollapsed(en, 0, db) - childIsMissing, err := treatCommitSnapshotError(err, en.EncodedChild, missingNodesChan) - if err != nil { - return err - } - - if !childIsMissing { - err = en.child.commitSnapshot(db, leavesChan, missingNodesChan, ctx, stats, idleProvider, depthLevel+1) - if err != nil { - return err - } - } - - return en.saveToStorage(db, stats, depthLevel) -} - -func (en *extensionNode) saveToStorage(targetDb common.BaseStorer, stats common.TrieStatisticsHandler, depthLevel int) error { - nodeSize, err := encodeNodeAndCommitToDB(en, targetDb) + err := commitSnapshot( + db, + maxEpochToSearchFrom, + en.marsh, + en.hasher, + leavesChan, + missingNodesChan, + ctx, + stats, + idleProvider, + depthLevel, + en.EncodedChild, + ) if err != nil { return err } - stats.AddExtensionNode(depthLevel, uint64(nodeSize)) - - en.child = nil + stats.AddExtensionNode(depthLevel, uint64(len(nodeBytes))) return nil } diff --git a/trie/extensionNode_test.go b/trie/extensionNode_test.go index e1c099e77a4..b68d9f14d79 100644 --- a/trie/extensionNode_test.go +++ b/trie/extensionNode_test.go @@ -15,6 +15,7 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" "github.com/multiversx/mx-chain-go/trie/keyBuilder" + "github.com/multiversx/mx-chain-go/trie/mock" "github.com/multiversx/mx-chain-go/trie/statistics" "github.com/stretchr/testify/assert" ) @@ -908,7 +909,7 @@ func TestExtensionNode_printShouldNotPanicEvenIfNodeIsCollapsed(t *testing.T) { db := testscommon.NewMemDbMock() en, collapsedEn := getEnAndCollapsedEn() _ = en.commitDirty(0, 5, db, db) - _ = collapsedEn.commitSnapshot(db, nil, nil, context.Background(), statistics.NewTrieStatistics(), &testscommon.ProcessStatusHandlerStub{}, 0) + _ = collapsedEn.setHash() en.print(enWriter, 0, db) collapsedEn.print(collapsedEnWriter, 0, db) @@ -921,7 +922,7 @@ func TestExtensionNode_getDirtyHashesFromCleanNode(t *testing.T) { db := testscommon.NewMemDbMock() en, _ := getEnAndCollapsedEn() - _ = en.commitSnapshot(db, nil, nil, context.Background(), statistics.NewTrieStatistics(), &testscommon.ProcessStatusHandlerStub{}, 0) + _ = en.commitDirty(0, 5, db, db) dirtyHashes := make(common.ModifiedHashes) err := en.getDirtyHashes(dirtyHashes) @@ -946,7 +947,7 @@ func TestExtensionNode_getAllHashesResolvesCollapsed(t *testing.T) { trieNodes := 5 db := testscommon.NewMemDbMock() en, collapsedEn := getEnAndCollapsedEn() - _ = en.commitSnapshot(db, nil, nil, context.Background(), statistics.NewTrieStatistics(), &testscommon.ProcessStatusHandlerStub{}, 0) + _ = en.commitDirty(0, 5, db, db) hashes, err := collapsedEn.getAllHashes(db) assert.Nil(t, err) @@ -1014,12 +1015,12 @@ func TestExtensionNode_SizeInBytes(t *testing.T) { func TestExtensionNode_commitContextDone(t *testing.T) { t.Parallel() - db := testscommon.NewMemDbMock() + db := mock.NewSnapshotDbMock() en, _ := getEnAndCollapsedEn() ctx, cancel := context.WithCancel(context.Background()) cancel() - err := en.commitSnapshot(db, nil, nil, ctx, statistics.NewTrieStatistics(), &testscommon.ProcessStatusHandlerStub{}, 0) + err := en.commitSnapshot(db, 0, nil, nil, ctx, statistics.NewTrieStatistics(), &testscommon.ProcessStatusHandlerStub{}, []byte{}, 0) assert.Equal(t, core.ErrContextClosing, err) } @@ -1033,14 +1034,14 @@ func TestExtensionNode_getValueReturnsEmptyByteSlice(t *testing.T) { func TestExtensionNode_commitSnapshotDbIsClosing(t *testing.T) { t.Parallel() - db := testscommon.NewMemDbMock() + db := mock.NewSnapshotDbMock() db.GetCalled = func(key []byte) ([]byte, error) { return nil, core.ErrContextClosing } _, collapsedEn := getEnAndCollapsedEn() missingNodesChan := make(chan []byte, 10) - err := collapsedEn.commitSnapshot(db, nil, missingNodesChan, context.Background(), statistics.NewTrieStatistics(), &testscommon.ProcessStatusHandlerStub{}, 0) + err := collapsedEn.commitSnapshot(db, 0, nil, missingNodesChan, context.Background(), statistics.NewTrieStatistics(), &testscommon.ProcessStatusHandlerStub{}, []byte{}, 0) assert.True(t, core.IsClosingError(err)) assert.Equal(t, 0, len(missingNodesChan)) } diff --git a/trie/interceptedNode.go b/trie/interceptedNode.go index 0757c0e6b0d..a732342c547 100644 --- a/trie/interceptedNode.go +++ b/trie/interceptedNode.go @@ -46,6 +46,11 @@ func (inTn *InterceptedTrieNode) CheckValidity() error { return nil } +// ShouldAllowDuplicates returns if this type of intercepted data should allow duplicates +func (inTn *InterceptedTrieNode) ShouldAllowDuplicates() bool { + return true +} + // IsForCurrentShard checks if the intercepted data is for the current shard func (inTn *InterceptedTrieNode) IsForCurrentShard() bool { return true diff --git a/trie/interceptedNode_test.go b/trie/interceptedNode_test.go index eae6b884ad0..06faef3fef4 100644 --- a/trie/interceptedNode_test.go +++ b/trie/interceptedNode_test.go @@ -10,6 +10,7 @@ import ( "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/trie" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func getDefaultInterceptedTrieNodeParameters() ([]byte, hashing.Hasher) { @@ -178,3 +179,10 @@ func TestInterceptedTrieNode_SizeInBytes(t *testing.T) { interceptedNode, _ := trie.NewInterceptedTrieNode(getDefaultInterceptedTrieNodeParameters()) assert.Equal(t, 131, interceptedNode.SizeInBytes()) } + +func TestInterceptedTrieNode_ShouldAllowDuplicates(t *testing.T) { + t.Parallel() + + interceptedNode, _ := trie.NewInterceptedTrieNode(getDefaultInterceptedTrieNodeParameters()) + require.True(t, interceptedNode.ShouldAllowDuplicates()) +} diff --git a/trie/interface.go b/trie/interface.go index d1d8fab1502..af1eb4c1eef 100644 --- a/trie/interface.go +++ b/trie/interface.go @@ -48,7 +48,7 @@ type node interface { collectLeavesForMigration(migrationArgs vmcommon.ArgsMigrateDataTrieLeaves, db common.TrieStorageInteractor, keyBuilder common.KeyBuilder) (bool, error) commitDirty(level byte, maxTrieLevelInMemory uint, originDb common.TrieStorageInteractor, targetDb common.BaseStorer) error - commitSnapshot(originDb common.TrieStorageInteractor, leavesChan chan core.KeyValueHolder, missingNodesChan chan []byte, ctx context.Context, stats common.TrieStatisticsHandler, idleProvider IdleNodeProvider, depthLevel int) error + commitSnapshot(originDb snapshotDb, maxEpochToSearchFrom uint32, leavesChan chan core.KeyValueHolder, missingNodesChan chan []byte, ctx context.Context, stats common.TrieStatisticsHandler, idleProvider IdleNodeProvider, encodedRoot []byte, depthLevel int) error getMarshalizer() marshal.Marshalizer setMarshalizer(marshal.Marshalizer) @@ -65,7 +65,7 @@ type dbWithGetFromEpoch interface { } type snapshotNode interface { - commitSnapshot(originDb common.TrieStorageInteractor, leavesChan chan core.KeyValueHolder, missingNodesChan chan []byte, ctx context.Context, stats common.TrieStatisticsHandler, idleProvider IdleNodeProvider, depthLevel int) error + commitSnapshot(originDb snapshotDb, maxEpochToSearchFrom uint32, leavesChan chan core.KeyValueHolder, missingNodesChan chan []byte, ctx context.Context, stats common.TrieStatisticsHandler, idleProvider IdleNodeProvider, encodedRoot []byte, depthLevel int) error } // RequestHandler defines the methods through which request to data can be made @@ -89,7 +89,7 @@ type epochStorer interface { type snapshotPruningStorer interface { common.BaseStorer - GetFromOldEpochsWithoutAddingToCache(key []byte, maxEpochToSearchFrom uint32) ([]byte, core.OptionalUint32, error) + GetWithoutAddingToCache(key []byte, maxEpochToSearchFrom uint32) ([]byte, core.OptionalUint32, error) GetFromLastEpoch(key []byte) ([]byte, error) PutInEpoch(key []byte, data []byte, epoch uint32) error PutInEpochWithoutCache(key []byte, data []byte, epoch uint32) error @@ -111,3 +111,10 @@ type IdleNodeProvider interface { IsIdle() bool IsInterfaceNil() bool } + +type snapshotDb interface { + GetWithoutAddingToCache(key []byte, maxEpochToSearchFrom uint32) ([]byte, uint32, error) + GetFromLastEpoch(key []byte) ([]byte, error) + PutInEpochWithoutCache(key []byte, data []byte) error + GetIdentifier() string +} diff --git a/trie/leafNode.go b/trie/leafNode.go index 5cefe3754ff..4f82059518d 100644 --- a/trie/leafNode.go +++ b/trie/leafNode.go @@ -136,29 +136,21 @@ func (ln *leafNode) commitDirty(_ byte, _ uint, _ common.TrieStorageInteractor, } func (ln *leafNode) commitSnapshot( - db common.TrieStorageInteractor, + _ snapshotDb, + _ uint32, leavesChan chan core.KeyValueHolder, _ chan []byte, ctx context.Context, stats common.TrieStatisticsHandler, idleProvider IdleNodeProvider, + nodeBytes []byte, depthLevel int, ) error { if shouldStopIfContextDoneBlockingIfBusy(ctx, idleProvider) { return core.ErrContextClosing } - err := ln.isEmptyOrNil() - if err != nil { - return fmt.Errorf("commit snapshot error %w", err) - } - - err = writeNodeOnChannel(ln, leavesChan) - if err != nil { - return err - } - - nodeSize, err := encodeNodeAndCommitToDB(ln, db) + err := writeNodeOnChannel(ln, leavesChan) if err != nil { return err } @@ -168,7 +160,7 @@ func (ln *leafNode) commitSnapshot( return err } - stats.AddLeafNode(depthLevel, uint64(nodeSize), version) + stats.AddLeafNode(depthLevel, uint64(len(nodeBytes)), version) return nil } diff --git a/trie/leafNode_test.go b/trie/leafNode_test.go index 297d093ce94..bbcdbbf9fce 100644 --- a/trie/leafNode_test.go +++ b/trie/leafNode_test.go @@ -14,6 +14,7 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" "github.com/multiversx/mx-chain-go/trie/keyBuilder" + "github.com/multiversx/mx-chain-go/trie/mock" "github.com/multiversx/mx-chain-go/trie/statistics" "github.com/stretchr/testify/assert" ) @@ -723,12 +724,12 @@ func TestLeafNode_writeNodeOnChannel(t *testing.T) { func TestLeafNode_commitContextDone(t *testing.T) { t.Parallel() - db := testscommon.NewMemDbMock() + db := mock.NewSnapshotDbMock() ln := getLn(marshalizer, hasherMock) ctx, cancel := context.WithCancel(context.Background()) cancel() - err := ln.commitSnapshot(db, nil, nil, ctx, statistics.NewTrieStatistics(), &testscommon.ProcessStatusHandlerStub{}, 0) + err := ln.commitSnapshot(db, 0, nil, nil, ctx, statistics.NewTrieStatistics(), &testscommon.ProcessStatusHandlerStub{}, []byte{}, 0) assert.Equal(t, core.ErrContextClosing, err) } diff --git a/trie/mock/snapshotDbMock.go b/trie/mock/snapshotDbMock.go new file mode 100644 index 00000000000..7a514b362f9 --- /dev/null +++ b/trie/mock/snapshotDbMock.go @@ -0,0 +1,38 @@ +package mock + +import ( + "github.com/multiversx/mx-chain-go/testscommon" +) + +// SnapshotDbMock represents the memory database storage. It holds a map of key value pairs +type SnapshotDbMock struct { + testscommon.MemDbMock +} + +// NewSnapshotDbMock creates a new memorydb object +func NewSnapshotDbMock() *SnapshotDbMock { + return &SnapshotDbMock{ + MemDbMock: *testscommon.NewMemDbMock(), + } +} + +// GetWithoutAddingToCache gets the value associated to the key from old epochs, or reports an error +func (s *SnapshotDbMock) GetWithoutAddingToCache(key []byte, _ uint32) ([]byte, uint32, error) { + val, err := s.MemDbMock.Get(key) + return val, 0, err +} + +// GetFromLastEpoch gets the value associated to the key from the last epoch, or reports an error +func (s *SnapshotDbMock) GetFromLastEpoch(key []byte) ([]byte, error) { + return s.MemDbMock.Get(key) +} + +// PutInEpochWithoutCache adds the given (key, data) in the current epoch without adding it to cache +func (s *SnapshotDbMock) PutInEpochWithoutCache(key, data []byte) error { + return s.MemDbMock.Put(key, data) +} + +// GetIdentifier returns the identifier of the storage medium +func (s *SnapshotDbMock) GetIdentifier() string { + return s.MemDbMock.GetIdentifier() +} diff --git a/trie/node.go b/trie/node.go index 63f7a69c1e1..f15f182c1cb 100644 --- a/trie/node.go +++ b/trie/node.go @@ -308,3 +308,42 @@ func shouldMigrateCurrentNode( return true, nil } + +func commitSnapshot( + db snapshotDb, + maxEpochToSearchFrom uint32, + marshaller marshal.Marshalizer, + hasher hashing.Hasher, + leavesChan chan core.KeyValueHolder, + missingNodesChan chan []byte, + ctx context.Context, + stats common.TrieStatisticsHandler, + idleProvider IdleNodeProvider, + depthLevel int, + hash []byte, +) error { + encChild, foundInEpoch, err := db.GetWithoutAddingToCache(hash, maxEpochToSearchFrom) + if err != nil { + treatLogError(log, err, hash) + + if core.IsClosingError(err) { + return err + } + + log.Error("error during trie snapshot", "err", err.Error(), "hash", hash, "maxEpochToSearchFrom", maxEpochToSearchFrom) + missingNodesChan <- hash + return nil + } + + child, err := decodeNode(encChild, marshaller, hasher) + if err != nil { + return err + } + + err = child.commitSnapshot(db, foundInEpoch, leavesChan, missingNodesChan, ctx, stats, idleProvider, encChild, depthLevel+1) + if err != nil { + return err + } + + return db.PutInEpochWithoutCache(hash, encChild) +} diff --git a/trie/snapshotTrieStorageManager.go b/trie/snapshotTrieStorageManager.go index 8ad213003d5..8c80aaf00b4 100644 --- a/trie/snapshotTrieStorageManager.go +++ b/trie/snapshotTrieStorageManager.go @@ -5,11 +5,12 @@ import ( "fmt" "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-go/common" ) type snapshotTrieStorageManager struct { - *trieStorageManager + tsm *trieStorageManager mainSnapshotStorer snapshotPruningStorer epoch uint32 } @@ -21,34 +22,31 @@ func newSnapshotTrieStorageManager(tsm *trieStorageManager, epoch uint32) (*snap } return &snapshotTrieStorageManager{ - trieStorageManager: tsm, + tsm: tsm, mainSnapshotStorer: storer, epoch: epoch, }, nil } -// Get checks all the storers for the given key, and returns it if it is found -func (stsm *snapshotTrieStorageManager) Get(key []byte) ([]byte, error) { - stsm.storageOperationMutex.Lock() - defer stsm.storageOperationMutex.Unlock() - - if stsm.closed { - log.Debug("snapshotTrieStorageManager get context closing", "key", key) - return nil, core.ErrContextClosing - } - +// GetWithoutAddingToCache tries to get the value for the given key from old epochs without adding it to cache +func (stsm *snapshotTrieStorageManager) GetWithoutAddingToCache(key []byte, maxEpochToSearchFrom uint32) ([]byte, uint32, error) { // test point get during snapshot - val, epoch, err := stsm.mainSnapshotStorer.GetFromOldEpochsWithoutAddingToCache(key, stsm.epoch) + val, epoch, err := stsm.mainSnapshotStorer.GetWithoutAddingToCache(key, maxEpochToSearchFrom) if core.IsClosingError(err) { - return nil, err + return nil, 0, err } if len(val) == 0 { - return nil, ErrKeyNotFound + return nil, 0, ErrKeyNotFound } stsm.putInPreviousStorerIfAbsent(key, val, epoch) - return val, nil + foundInEpoch := maxEpochToSearchFrom + if epoch.HasValue { + foundInEpoch = epoch.Value + } + + return val, foundInEpoch, nil } func (stsm *snapshotTrieStorageManager) putInPreviousStorerIfAbsent(key []byte, val []byte, epoch core.OptionalUint32) { @@ -78,31 +76,25 @@ func (stsm *snapshotTrieStorageManager) putInPreviousStorerIfAbsent(key []byte, } } -// Put adds the given value to the main storer -func (stsm *snapshotTrieStorageManager) Put(key, data []byte) error { - stsm.storageOperationMutex.Lock() - defer stsm.storageOperationMutex.Unlock() - - if stsm.closed { - log.Debug("snapshotTrieStorageManager put context closing", "key", key, "data", data) - return core.ErrContextClosing - } - +// PutInEpochWithoutCache adds the given (key, data) in the current epoch without adding it to cache +func (stsm *snapshotTrieStorageManager) PutInEpochWithoutCache(key, data []byte) error { log.Trace("put hash in snapshot storer", "hash", key, "epoch", stsm.epoch) return stsm.mainSnapshotStorer.PutInEpochWithoutCache(key, data, stsm.epoch) } // GetFromLastEpoch searches only the last epoch storer for the given key func (stsm *snapshotTrieStorageManager) GetFromLastEpoch(key []byte) ([]byte, error) { - stsm.storageOperationMutex.Lock() - defer stsm.storageOperationMutex.Unlock() + return stsm.mainSnapshotStorer.GetFromLastEpoch(key) +} - if stsm.closed { - log.Debug("snapshotTrieStorageManager getFromLastEpoch context closing", "key", key) - return nil, core.ErrContextClosing - } +// GetIdentifier returns the identifier of the storage manager +func (stsm *snapshotTrieStorageManager) GetIdentifier() string { + return stsm.tsm.GetIdentifier() +} - return stsm.mainSnapshotStorer.GetFromLastEpoch(key) +// GetFromCurrentEpoch tries to get the value for the given key from the current epoch +func (stsm *snapshotTrieStorageManager) GetFromCurrentEpoch(key []byte) ([]byte, error) { + return stsm.mainSnapshotStorer.GetFromCurrentEpoch(key) } // IsInterfaceNil returns true if there is no value under the interface diff --git a/trie/snapshotTrieStorageManager_test.go b/trie/snapshotTrieStorageManager_test.go index 9cf0ec76b20..c8cc2df3ce2 100644 --- a/trie/snapshotTrieStorageManager_test.go +++ b/trie/snapshotTrieStorageManager_test.go @@ -36,33 +36,37 @@ func TestNewSnapshotTrieStorageManager(t *testing.T) { assert.False(t, check.IfNil(stsm)) } -func TestSnapshotTrieStorageManager_Get(t *testing.T) { +func TestSnapshotTrieStorageManager_GetWithoutAddingToCache(t *testing.T) { t.Parallel() t.Run("closed storage manager should error", func(t *testing.T) { t.Parallel() _, trieStorage := newEmptyTrie() - trieStorage.mainStorer = &trie.SnapshotPruningStorerStub{} + trieStorage.mainStorer = &trie.SnapshotPruningStorerStub{ + GetWithoutAddingToCacheCalled: func(key []byte, maxEpochToSearchFrom uint32) ([]byte, core.OptionalUint32, error) { + return nil, core.OptionalUint32{}, core.ErrContextClosing + }, + } stsm, _ := newSnapshotTrieStorageManager(trieStorage, 0) - _ = stsm.Close() + _ = trieStorage.Close() - val, err := stsm.Get([]byte("key")) + val, _, err := stsm.GetWithoutAddingToCache([]byte("key"), 0) assert.Equal(t, core.ErrContextClosing, err) assert.Nil(t, val) }) - t.Run("GetFromOldEpochsWithoutAddingToCache returns db closed should error", func(t *testing.T) { + t.Run("GetWithoutAddingToCache returns db closed should error", func(t *testing.T) { t.Parallel() _, trieStorage := newEmptyTrie() trieStorage.mainStorer = &trie.SnapshotPruningStorerStub{ - GetFromOldEpochsWithoutAddingToCacheCalled: func(_ []byte, _ uint32) ([]byte, core.OptionalUint32, error) { + GetWithoutAddingToCacheCalled: func(_ []byte, _ uint32) ([]byte, core.OptionalUint32, error) { return nil, core.OptionalUint32{}, storage.ErrDBIsClosed }, } stsm, _ := newSnapshotTrieStorageManager(trieStorage, 0) - val, err := stsm.Get([]byte("key")) + val, _, err := stsm.GetWithoutAddingToCache([]byte("key"), 0) assert.Equal(t, storage.ErrDBIsClosed, err) assert.Nil(t, val) }) @@ -72,30 +76,34 @@ func TestSnapshotTrieStorageManager_Get(t *testing.T) { _, trieStorage := newEmptyTrie() getFromOldEpochsWithoutCacheCalled := false trieStorage.mainStorer = &trie.SnapshotPruningStorerStub{ - GetFromOldEpochsWithoutAddingToCacheCalled: func(_ []byte, _ uint32) ([]byte, core.OptionalUint32, error) { + GetWithoutAddingToCacheCalled: func(_ []byte, _ uint32) ([]byte, core.OptionalUint32, error) { getFromOldEpochsWithoutCacheCalled = true return nil, core.OptionalUint32{}, nil }, } stsm, _ := newSnapshotTrieStorageManager(trieStorage, 0) - _, _ = stsm.Get([]byte("key")) + _, _, _ = stsm.GetWithoutAddingToCache([]byte("key"), 0) assert.True(t, getFromOldEpochsWithoutCacheCalled) }) } -func TestSnapshotTrieStorageManager_Put(t *testing.T) { +func TestSnapshotTrieStorageManager_PutInEpochWithoutCache(t *testing.T) { t.Parallel() t.Run("closed storage manager should error", func(t *testing.T) { t.Parallel() _, trieStorage := newEmptyTrie() - trieStorage.mainStorer = &trie.SnapshotPruningStorerStub{} + trieStorage.mainStorer = &trie.SnapshotPruningStorerStub{ + PutInEpochWithoutCacheCalled: func(_ []byte, _ []byte, _ uint32) error { + return core.ErrContextClosing + }, + } stsm, _ := newSnapshotTrieStorageManager(trieStorage, 0) - _ = stsm.Close() + _ = trieStorage.Close() - err := stsm.Put([]byte("key"), []byte("data")) + err := stsm.PutInEpochWithoutCache([]byte("key"), []byte("data")) assert.Equal(t, core.ErrContextClosing, err) }) t.Run("should work", func(t *testing.T) { @@ -111,7 +119,7 @@ func TestSnapshotTrieStorageManager_Put(t *testing.T) { } stsm, _ := newSnapshotTrieStorageManager(trieStorage, 0) - _ = stsm.Put([]byte("key"), []byte("data")) + _ = stsm.PutInEpochWithoutCache([]byte("key"), []byte("data")) assert.True(t, putWithoutCacheCalled) }) } @@ -123,9 +131,13 @@ func TestSnapshotTrieStorageManager_GetFromLastEpoch(t *testing.T) { t.Parallel() _, trieStorage := newEmptyTrie() - trieStorage.mainStorer = &trie.SnapshotPruningStorerStub{} + trieStorage.mainStorer = &trie.SnapshotPruningStorerStub{ + GetFromLastEpochCalled: func(_ []byte) ([]byte, error) { + return nil, core.ErrContextClosing + }, + } stsm, _ := newSnapshotTrieStorageManager(trieStorage, 0) - _ = stsm.Close() + _ = trieStorage.Close() val, err := stsm.GetFromLastEpoch([]byte("key")) assert.Equal(t, core.ErrContextClosing, err) @@ -156,7 +168,7 @@ func TestSnapshotTrieStorageManager_AlsoAddInPreviousEpoch(t *testing.T) { val := []byte("val") _, trieStorage := newEmptyTrie() trieStorage.mainStorer = &trie.SnapshotPruningStorerStub{ - GetFromOldEpochsWithoutAddingToCacheCalled: func(_ []byte, _ uint32) ([]byte, core.OptionalUint32, error) { + GetWithoutAddingToCacheCalled: func(_ []byte, _ uint32) ([]byte, core.OptionalUint32, error) { return val, core.OptionalUint32{}, nil }, PutInEpochCalled: func(_ []byte, _ []byte, _ uint32) error { @@ -166,14 +178,14 @@ func TestSnapshotTrieStorageManager_AlsoAddInPreviousEpoch(t *testing.T) { } stsm, _ := newSnapshotTrieStorageManager(trieStorage, 5) - returnedVal, _ := stsm.Get([]byte("key")) + returnedVal, _, _ := stsm.GetWithoutAddingToCache([]byte("key"), 5) assert.Equal(t, val, returnedVal) }) t.Run("epoch is previous epoch", func(t *testing.T) { val := []byte("val") _, trieStorage := newEmptyTrie() trieStorage.mainStorer = &trie.SnapshotPruningStorerStub{ - GetFromOldEpochsWithoutAddingToCacheCalled: func(_ []byte, _ uint32) ([]byte, core.OptionalUint32, error) { + GetWithoutAddingToCacheCalled: func(_ []byte, _ uint32) ([]byte, core.OptionalUint32, error) { epoch := core.OptionalUint32{ Value: 4, HasValue: true, @@ -187,14 +199,14 @@ func TestSnapshotTrieStorageManager_AlsoAddInPreviousEpoch(t *testing.T) { } stsm, _ := newSnapshotTrieStorageManager(trieStorage, 5) - returnedVal, _ := stsm.Get([]byte("key")) + returnedVal, _, _ := stsm.GetWithoutAddingToCache([]byte("key"), 5) assert.Equal(t, val, returnedVal) }) t.Run("epoch is 0", func(t *testing.T) { val := []byte("val") _, trieStorage := newEmptyTrie() trieStorage.mainStorer = &trie.SnapshotPruningStorerStub{ - GetFromOldEpochsWithoutAddingToCacheCalled: func(_ []byte, _ uint32) ([]byte, core.OptionalUint32, error) { + GetWithoutAddingToCacheCalled: func(_ []byte, _ uint32) ([]byte, core.OptionalUint32, error) { epoch := core.OptionalUint32{ Value: 4, HasValue: true, @@ -208,14 +220,14 @@ func TestSnapshotTrieStorageManager_AlsoAddInPreviousEpoch(t *testing.T) { } stsm, _ := newSnapshotTrieStorageManager(trieStorage, 0) - returnedVal, _ := stsm.Get([]byte("key")) + returnedVal, _, _ := stsm.GetWithoutAddingToCache([]byte("key"), 0) assert.Equal(t, val, returnedVal) }) t.Run("key is ActiveDBKey", func(t *testing.T) { val := []byte("val") _, trieStorage := newEmptyTrie() trieStorage.mainStorer = &trie.SnapshotPruningStorerStub{ - GetFromOldEpochsWithoutAddingToCacheCalled: func(_ []byte, _ uint32) ([]byte, core.OptionalUint32, error) { + GetWithoutAddingToCacheCalled: func(_ []byte, _ uint32) ([]byte, core.OptionalUint32, error) { epoch := core.OptionalUint32{ Value: 3, HasValue: true, @@ -229,14 +241,14 @@ func TestSnapshotTrieStorageManager_AlsoAddInPreviousEpoch(t *testing.T) { } stsm, _ := newSnapshotTrieStorageManager(trieStorage, 5) - returnedVal, _ := stsm.Get([]byte(common.ActiveDBKey)) + returnedVal, _, _ := stsm.GetWithoutAddingToCache([]byte(common.ActiveDBKey), 5) assert.Equal(t, val, returnedVal) }) t.Run("key is TrieSyncedKey", func(t *testing.T) { val := []byte("val") _, trieStorage := newEmptyTrie() trieStorage.mainStorer = &trie.SnapshotPruningStorerStub{ - GetFromOldEpochsWithoutAddingToCacheCalled: func(_ []byte, _ uint32) ([]byte, core.OptionalUint32, error) { + GetWithoutAddingToCacheCalled: func(_ []byte, _ uint32) ([]byte, core.OptionalUint32, error) { epoch := core.OptionalUint32{ Value: 3, HasValue: true, @@ -250,7 +262,7 @@ func TestSnapshotTrieStorageManager_AlsoAddInPreviousEpoch(t *testing.T) { } stsm, _ := newSnapshotTrieStorageManager(trieStorage, 5) - returnedVal, _ := stsm.Get([]byte(common.TrieSyncedKey)) + returnedVal, _, _ := stsm.GetWithoutAddingToCache([]byte(common.TrieSyncedKey), 5) assert.Equal(t, val, returnedVal) }) t.Run("add in previous epoch", func(t *testing.T) { @@ -258,7 +270,7 @@ func TestSnapshotTrieStorageManager_AlsoAddInPreviousEpoch(t *testing.T) { putInEpochCalled := false _, trieStorage := newEmptyTrie() trieStorage.mainStorer = &trie.SnapshotPruningStorerStub{ - GetFromOldEpochsWithoutAddingToCacheCalled: func(_ []byte, _ uint32) ([]byte, core.OptionalUint32, error) { + GetWithoutAddingToCacheCalled: func(_ []byte, _ uint32) ([]byte, core.OptionalUint32, error) { epoch := core.OptionalUint32{ Value: 3, HasValue: true, @@ -272,7 +284,7 @@ func TestSnapshotTrieStorageManager_AlsoAddInPreviousEpoch(t *testing.T) { } stsm, _ := newSnapshotTrieStorageManager(trieStorage, 5) - returnedVal, _ := stsm.Get([]byte("key")) + returnedVal, _, _ := stsm.GetWithoutAddingToCache([]byte("key"), 5) assert.Equal(t, val, returnedVal) assert.True(t, putInEpochCalled) }) diff --git a/trie/sync_test.go b/trie/sync_test.go index 7d6c26b3ba5..77fe8a6c75b 100644 --- a/trie/sync_test.go +++ b/trie/sync_test.go @@ -225,7 +225,7 @@ func TestTrieSync_FoundInStorageShouldNotRequest(t *testing.T) { }, } - err = bn.commitSnapshot(db, nil, nil, context.Background(), statistics.NewTrieStatistics(), &testscommon.ProcessStatusHandlerStub{}, 0) + err = bn.commitDirty(0, 5, db, db) require.Nil(t, err) leaves, err := bn.getChildren(db) diff --git a/trie/trieStorageManager.go b/trie/trieStorageManager.go index 669c06724bc..796eace218a 100644 --- a/trie/trieStorageManager.go +++ b/trie/trieStorageManager.go @@ -13,6 +13,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core/throttler" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/storage" @@ -129,7 +130,7 @@ func (tsm *trieStorageManager) checkGoRoutinesThrottler( if goRoutinesThrottler.CanProcess() { break } - + snapshotRequest.stats.IncrementThrottlerWaits() select { case <-time.After(time.Millisecond * 100): continue @@ -158,14 +159,6 @@ func (tsm *trieStorageManager) cleanupChans() { // Get checks all the storers for the given key, and returns it if it is found func (tsm *trieStorageManager) Get(key []byte) ([]byte, error) { - tsm.storageOperationMutex.Lock() - defer tsm.storageOperationMutex.Unlock() - - if tsm.closed { - log.Trace("trieStorageManager get context closing", "key", key) - return nil, core.ErrContextClosing - } - val, err := tsm.mainStorer.Get(key) if core.IsClosingError(err) { return nil, err @@ -184,51 +177,22 @@ func (tsm *trieStorageManager) GetStateStatsHandler() common.StateStatisticsHand // GetFromCurrentEpoch checks only the current storer for the given key, and returns it if it is found func (tsm *trieStorageManager) GetFromCurrentEpoch(key []byte) ([]byte, error) { - tsm.storageOperationMutex.Lock() - - if tsm.closed { - log.Trace("trieStorageManager get context closing", "key", key) - tsm.storageOperationMutex.Unlock() - return nil, core.ErrContextClosing - } - storer, ok := tsm.mainStorer.(snapshotPruningStorer) if !ok { storerType := fmt.Sprintf("%T", tsm.mainStorer) - tsm.storageOperationMutex.Unlock() return nil, fmt.Errorf("invalid storer, type is %s", storerType) } - tsm.storageOperationMutex.Unlock() - return storer.GetFromCurrentEpoch(key) } // Put adds the given value to the main storer func (tsm *trieStorageManager) Put(key []byte, val []byte) error { - tsm.storageOperationMutex.Lock() - defer tsm.storageOperationMutex.Unlock() - log.Trace("put hash in tsm", "hash", key) - - if tsm.closed { - log.Trace("trieStorageManager put context closing", "key", key, "value", val) - return core.ErrContextClosing - } - return tsm.mainStorer.Put(key, val) } // PutInEpoch adds the given value to the main storer in the specified epoch func (tsm *trieStorageManager) PutInEpoch(key []byte, val []byte, epoch uint32) error { - tsm.storageOperationMutex.Lock() - defer tsm.storageOperationMutex.Unlock() - log.Trace("put hash in tsm in epoch", "hash", key, "epoch", epoch) - - if tsm.closed { - log.Trace("trieStorageManager putInEpoch context closing", "key", key, "value", val, "epoch", epoch) - return core.ErrContextClosing - } - storer, ok := tsm.mainStorer.(snapshotPruningStorer) if !ok { return fmt.Errorf("invalid storer type for PutInEpoch") @@ -239,15 +203,6 @@ func (tsm *trieStorageManager) PutInEpoch(key []byte, val []byte, epoch uint32) // PutInEpochWithoutCache adds the given value to the main storer in the specified epoch without saving it to cache func (tsm *trieStorageManager) PutInEpochWithoutCache(key []byte, val []byte, epoch uint32) error { - tsm.storageOperationMutex.Lock() - defer tsm.storageOperationMutex.Unlock() - log.Trace("put hash in tsm in epoch without cache", "hash", key, "epoch", epoch) - - if tsm.closed { - log.Trace("trieStorageManager putInEpochWithoutCache context closing", "key", key, "value", val, "epoch", epoch) - return core.ErrContextClosing - } - storer, ok := tsm.mainStorer.(snapshotPruningStorer) if !ok { return fmt.Errorf("invalid storer type for PutInEpoch") @@ -285,9 +240,6 @@ func (tsm *trieStorageManager) ExitPruningBufferingMode() { // GetLatestStorageEpoch returns the epoch for the latest opened persister func (tsm *trieStorageManager) GetLatestStorageEpoch() (uint32, error) { - tsm.storageOperationMutex.Lock() - defer tsm.storageOperationMutex.Unlock() - storer, ok := tsm.mainStorer.(snapshotPruningStorer) if !ok { log.Debug("GetLatestStorageEpoch", "error", fmt.Sprintf("%T", tsm.mainStorer)) @@ -372,7 +324,7 @@ func (tsm *trieStorageManager) takeSnapshot(snapshotEntry *snapshotsQueueEntry, return } - newRoot, err := newSnapshotNode(stsm, msh, hsh, snapshotEntry.rootHash, snapshotEntry.missingNodesChan) + newRoot, encodedRoot, foundInEpoch, err := newSnapshotNode(stsm, msh, hsh, snapshotEntry.rootHash, snapshotEntry.epoch, snapshotEntry.missingNodesChan) if err != nil { snapshotEntry.iteratorChannels.ErrChan.WriteInChanNonBlocking(err) treatSnapshotError(err, @@ -384,7 +336,7 @@ func (tsm *trieStorageManager) takeSnapshot(snapshotEntry *snapshotsQueueEntry, } stats := statistics.NewTrieStatistics() - err = newRoot.commitSnapshot(stsm, snapshotEntry.iteratorChannels.LeavesChan, snapshotEntry.missingNodesChan, ctx, stats, tsm.idleProvider, rootDepthLevel) + err = newRoot.commitSnapshot(stsm, foundInEpoch, snapshotEntry.iteratorChannels.LeavesChan, snapshotEntry.missingNodesChan, ctx, stats, tsm.idleProvider, encodedRoot, rootDepthLevel) if err != nil { snapshotEntry.iteratorChannels.ErrChan.WriteInChanNonBlocking(err) treatSnapshotError(err, @@ -417,19 +369,38 @@ func treatSnapshotError(err error, message string, rootHash []byte, mainTrieRoot } func newSnapshotNode( - db common.TrieStorageInteractor, + db snapshotDb, msh marshal.Marshalizer, hsh hashing.Hasher, rootHash []byte, + epoch uint32, missingNodesCh chan []byte, -) (snapshotNode, error) { - newRoot, err := getNodeFromDBAndDecode(rootHash, db, msh, hsh) - _, _ = treatCommitSnapshotError(err, rootHash, missingNodesCh) +) (snapshotNode, []byte, uint32, error) { + encodedNode, foundInEpoch, err := db.GetWithoutAddingToCache(rootHash, epoch) if err != nil { - return nil, err + treatLogError(log, err, rootHash) + + if core.IsClosingError(err) { + return nil, nil, 0, err + } + + err = core.NewGetNodeFromDBErrWithKey(rootHash, err, db.GetIdentifier()) + log.Error("error during trie snapshot", "err", err.Error(), "hash", rootHash, "maxEpochToSearchFrom", epoch) + missingNodesCh <- rootHash + return nil, nil, 0, err } - return newRoot, nil + n, err := decodeNode(encodedNode, msh, hsh) + if err != nil { + return nil, nil, 0, err + } + + err = db.PutInEpochWithoutCache(rootHash, encodedNode) + if err != nil { + return nil, nil, 0, err + } + + return n, encodedNode, foundInEpoch, nil } // IsPruningEnabled returns true if the trie pruning is enabled @@ -447,9 +418,6 @@ func (tsm *trieStorageManager) IsPruningBlocked() bool { // Remove removes the given hash form the storage func (tsm *trieStorageManager) Remove(hash []byte) error { - tsm.storageOperationMutex.Lock() - defer tsm.storageOperationMutex.Unlock() - storer, ok := tsm.mainStorer.(snapshotPruningStorer) if !ok { return tsm.mainStorer.Remove(hash) @@ -460,9 +428,6 @@ func (tsm *trieStorageManager) Remove(hash []byte) error { // RemoveFromAllActiveEpochs removes the given hash from all epochs func (tsm *trieStorageManager) RemoveFromAllActiveEpochs(hash []byte) error { - tsm.storageOperationMutex.Lock() - defer tsm.storageOperationMutex.Unlock() - storer, ok := tsm.mainStorer.(snapshotPruningStorer) if !ok { return fmt.Errorf("trie storage manager: main storer does not implement snapshotPruningStorer interface: %T", tsm.mainStorer) diff --git a/trie/trieStorageManagerInEpoch.go b/trie/trieStorageManagerInEpoch.go index 5726290b3fd..02d95e529d5 100644 --- a/trie/trieStorageManagerInEpoch.go +++ b/trie/trieStorageManagerInEpoch.go @@ -44,14 +44,6 @@ func newTrieStorageManagerInEpoch(storageManager common.StorageManager, epoch ui // Get checks all the storers for the given key, and returns it if it is found func (tsmie *trieStorageManagerInEpoch) Get(key []byte) ([]byte, error) { - tsmie.storageOperationMutex.Lock() - defer tsmie.storageOperationMutex.Unlock() - - if tsmie.closed { - log.Debug("trieStorageManagerInEpoch get context closing", "key", key) - return nil, core.ErrContextClosing - } - for i := uint32(0); i < numEpochsToVerify; i++ { if i > tsmie.epoch { break diff --git a/trie/trieStorageManagerInEpoch_test.go b/trie/trieStorageManagerInEpoch_test.go index 735af7571cb..bedf3734529 100644 --- a/trie/trieStorageManagerInEpoch_test.go +++ b/trie/trieStorageManagerInEpoch_test.go @@ -5,13 +5,11 @@ import ( "strings" "testing" - "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-go/storage" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/storageManager" "github.com/multiversx/mx-chain-go/testscommon/trie" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestNewTrieStorageManagerInEpochNilStorageManager(t *testing.T) { @@ -69,23 +67,6 @@ func TestTrieStorageManagerInEpoch_IsInterfaceNil(t *testing.T) { func TestTrieStorageManagerInEpoch_GetFromEpoch(t *testing.T) { t.Parallel() - t.Run("closed storage manager should error", func(t *testing.T) { - t.Parallel() - - _, trieStorage := newEmptyTrie() - trieStorage.mainStorer = &trie.SnapshotPruningStorerStub{ - GetFromEpochCalled: func(_ []byte, _ uint32) ([]byte, error) { - require.Fail(t, "should have not been called") - return nil, nil - }, - } - tsmie, _ := newTrieStorageManagerInEpoch(trieStorage, 0) - _ = tsmie.Close() - - _, err := tsmie.Get([]byte("key")) - require.Equal(t, core.ErrContextClosing, err) - }) - t.Run("epoch 0 does not panic", func(t *testing.T) { t.Parallel() diff --git a/trie/trieStorageManager_test.go b/trie/trieStorageManager_test.go index 670119c1b19..104c6e2578c 100644 --- a/trie/trieStorageManager_test.go +++ b/trie/trieStorageManager_test.go @@ -221,17 +221,6 @@ func TestTrieStorageManager_RemoveFromAllActiveEpochs(t *testing.T) { assert.True(t, RemoveFromAllActiveEpochsCalled) } -func TestTrieStorageManager_PutInEpochClosedDb(t *testing.T) { - t.Parallel() - - args := trie.GetDefaultTrieStorageManagerParameters() - ts, _ := trie.NewTrieStorageManager(args) - _ = ts.Close() - - err := ts.PutInEpoch(providedKey, providedVal, 0) - assert.Equal(t, core.ErrContextClosing, err) -} - func TestTrieStorageManager_PutInEpochInvalidStorer(t *testing.T) { t.Parallel() @@ -419,7 +408,7 @@ func TestTrieStorageManager_ShouldTakeSnapshot(t *testing.T) { assert.True(t, ts.ShouldTakeSnapshot()) }) - t.Run("GetFromOldEpochsWithoutAddingToCacheCalled returns ActiveDBVal should return true", func(t *testing.T) { + t.Run("GetWithoutAddingToCacheCalled returns ActiveDBVal should return true", func(t *testing.T) { t.Parallel() args := trie.GetDefaultTrieStorageManagerParameters() @@ -427,7 +416,7 @@ func TestTrieStorageManager_ShouldTakeSnapshot(t *testing.T) { GetFromCurrentEpochCalled: func(key []byte) ([]byte, error) { return nil, expectedErr // isTrieSynced returns false }, - GetFromOldEpochsWithoutAddingToCacheCalled: func(key []byte, _ uint32) ([]byte, core.OptionalUint32, error) { + GetWithoutAddingToCacheCalled: func(key []byte, _ uint32) ([]byte, core.OptionalUint32, error) { return []byte(common.ActiveDBVal), core.OptionalUint32{}, nil }, MemDbMock: testscommon.NewMemDbMock(), @@ -441,16 +430,6 @@ func TestTrieStorageManager_ShouldTakeSnapshot(t *testing.T) { func TestTrieStorageManager_Get(t *testing.T) { t.Parallel() - t.Run("closed storage manager should error", func(t *testing.T) { - t.Parallel() - - ts, _ := trie.NewTrieStorageManager(trie.GetDefaultTrieStorageManagerParameters()) - _ = ts.Close() - - val, err := ts.Get(providedKey) - assert.Equal(t, core.ErrContextClosing, err) - assert.Nil(t, val) - }) t.Run("main storer closing should error", func(t *testing.T) { t.Parallel() @@ -482,16 +461,6 @@ func TestTrieStorageManager_Get(t *testing.T) { func TestNewSnapshotTrieStorageManager_GetFromCurrentEpoch(t *testing.T) { t.Parallel() - t.Run("closed storage manager should error", func(t *testing.T) { - t.Parallel() - - ts, _ := trie.NewTrieStorageManager(trie.GetDefaultTrieStorageManagerParameters()) - _ = ts.Close() - - val, err := ts.GetFromCurrentEpoch(providedKey) - assert.Equal(t, core.ErrContextClosing, err) - assert.Nil(t, val) - }) t.Run("main storer not snapshotPruningStorer should error", func(t *testing.T) { t.Parallel() @@ -526,15 +495,6 @@ func TestNewSnapshotTrieStorageManager_GetFromCurrentEpoch(t *testing.T) { func TestTrieStorageManager_Put(t *testing.T) { t.Parallel() - t.Run("closed storage manager should error", func(t *testing.T) { - t.Parallel() - - ts, _ := trie.NewTrieStorageManager(trie.GetDefaultTrieStorageManagerParameters()) - _ = ts.Close() - - err := ts.Put(providedKey, providedVal) - assert.Equal(t, core.ErrContextClosing, err) - }) t.Run("should work", func(t *testing.T) { t.Parallel() @@ -550,15 +510,6 @@ func TestTrieStorageManager_Put(t *testing.T) { func TestTrieStorageManager_PutInEpochWithoutCache(t *testing.T) { t.Parallel() - t.Run("closed storage manager should error", func(t *testing.T) { - t.Parallel() - - ts, _ := trie.NewTrieStorageManager(trie.GetDefaultTrieStorageManagerParameters()) - _ = ts.Close() - - err := ts.PutInEpochWithoutCache(providedKey, providedVal, 0) - assert.Equal(t, core.ErrContextClosing, err) - }) t.Run("main storer not snapshotPruningStorer should error", func(t *testing.T) { t.Parallel() diff --git a/txcache/README.md b/txcache/README.md new file mode 100644 index 00000000000..cb6a564ce2d --- /dev/null +++ b/txcache/README.md @@ -0,0 +1,210 @@ +## Mempool + +### Glossary + +1. **selection session:** an ephemeral session during which the mempool selects transactions for a proposer. A session starts when a proposer asks the mempool for transactions and ends when the mempool returns the transactions. The most important part of a session is the _selection loop_. +2. **transaction PPU:** the price per unit of computation, for a transaction. It's computed as `initiallyPaidFee / gasLimit`. +3. **initially paid transaction fee:** the fee for processing a transaction, as known before its actual processing. That is, without knowing the _refund_ component. + +### Configuration + +1. **SelectTransactions::gasRequested:** `10_000_000_000`, the maximum total gas limit of the transactions to be returned to a proposer (one _selection session_). This value is provided by the Protocol. +2. **SelectTransactions::maxNum:** `30_000`, the maximum number of transactions to be returned to a proposer (one _selection session_). This value is provided by the Protocol. + +### Transactions selection + +### Paragraph 1 + +When a proposer asks the mempool for transactions, it provides the following parameters: + + - `gasRequested`: the maximum total gas limit of the transactions to be returned + - `maxNum`: the maximum number of transactions to be returned + +### Paragraph 2 + +The PPU (price per gas unit) of a transaction, is computed (once it enters the mempool) as follows: + +``` +ppu = initiallyPaidFee / gasLimit +``` + +In the formula above, + +``` +initiallyPaidFee = + dataCost * gasPrice + + executionCost * gasPrice * network.gasPriceModifier + +dataCost = network.minGasLimit + len(data) * network.gasPerDataByte + +executionCost = gasLimit - dataCost +``` + +Network parameters (as of November of 2024): + +``` +gasPriceModifier = 0.01 +minGasLimit = 50_000 +gasPerDataByte = 1_500 +``` + +#### Examples + +**(a)** A simple native transfer with `gasLimit = 50_000` and `gasPrice = 1_000_000_000`: + +``` +initiallyPaidFee = 50_000_000_000 atoms +ppu = 1_000_000_000 atoms +``` + +**(b)** A simple native transfer with `gasLimit = 50_000` and `gasPrice = 1_500_000_000`: + +``` +initiallyPaidFee = gasLimit * gasPrice = 75_000_000_000 atoms +ppu = 75_000_000_000 / 50_000 = 1_500_000_000 atoms +``` + +**(c)** A simple native transfer with a data payload of 7 bytes, with `gasLimit = 50_000 + 7 * 1500` and `gasPrice = 1_000_000_000`: + +``` +initiallyPaidFee = 60_500_000_000_000 atoms +ppu = 60_500_000_000_000 / 60_500 = 1_000_000_000 atoms +``` + +That is, for simple native transfers (whether they hold a data payload or not), the PPU is equal to the gas price. + +**(d)** A contract call with `gasLimit = 75_000_000` and `gasPrice = 1_000_000_000`, with a data payload of `42` bytes: + +``` +initiallyPaidFee = 861_870_000_000_000 atoms +ppu = 11_491_600 atoms +``` + +**(e)** Similar to **(d)**, but with `gasPrice = 2_000_000_000`: + +``` +initiallyPaidFee = 1_723_740_000_000_000 atoms +ppu = 22_983_200 atoms +``` + +That is, for contract calls, the PPU is not equal to the gas price, but much lower, due to the contract call _cost subsidy_. **A higher gas price will result in a higher PPU.** + +### Paragraph 3 + +Transaction **A** is considered **more valuable (for the Network)** than transaction **B** if **it has a higher PPU**. + +If two transactions have the same PPU, they are ordered by gas limit (higher is better, promoting less "execution fragmentation"). In the end, they are ordered using an arbitrary, but deterministic rule: the transaction with the "lower" transaction hash "wins" the comparison. + +Pseudo-code: + +``` +func isTransactionMoreValuableForNetwork(A, B): + if A.ppu > B.ppu: + return true + if A.ppu < B.ppu: + return false + + if A.gasLimit > B.gasLimit: + return true + if A.gasLimit < B.gasLimit: + return false + + return A.hash < B.hash +``` + +### Paragraph 4 + +The mempool selects transactions as follows (pseudo-code): + +``` +func selectTransactions(gasRequested, maxNum): + // Setup phase + senders := list of all current senders in the mempool, in an arbitrary order + bunchesOfTransactions := sourced from senders, nicely sorted by nonce + + // Holds selected transactions + selectedTransactions := empty + + // Holds not-yet-selected transactions, ordered by PPU + competitionHeap := empty + + for each bunch in bunchesOfTransactions: + competitionHeap.push(next available transaction from bunch) + + // Selection loop + while competitionHeap is not empty: + mostValuableTransaction := competitionHeap.pop() + + // Check if adding the next transaction exceeds limits + if selectedTransactions.totalGasLimit + mostValuableTransaction.gasLimit > gasRequested: + break + if selectedTransactions.length + 1 > maxNum: + break + + selectedTransactions.append(mostValuableTransaction) + + nextTransaction := next available transaction from the bunch of mostValuableTransaction + if nextTransaction exists: + competitionHeap.push(nextTransaction) + + return selectedTransactions +``` + +Thus, the mempool selects transactions using an efficient and value-driven algorithm that ensures the most valuable transactions (in terms of PPU) are prioritized while maintaining correct nonce sequencing per sender. The selection process is as follows: + +**Setup phase:** + + - **Snapshot of senders:** + - Before starting the selection loop, obtain a snapshot of all current senders in the mempool in an arbitrary order. + + - **Organize transactions into bunches:** + - For each sender, collect all their pending transactions and organize them into a "bunch." + - Each bunch is: + - **Sorted by nonce:** Transactions are ordered in ascending order based on their nonce values. + + - **Prepare the heap:** + - Extract the first transaction (lowest nonce) from each sender's bunch. + - Place these transactions onto a max heap, which is ordered based on the transaction's PPU. + +**Selection loop:** + + - **Iterative selection:** + - Continue the loop until either the total gas of selected transactions meets or exceeds `gasRequested`, or the number of selected transactions reaches `maxNum`. + - In each iteration: + - **Select the most valuable transaction:** + - Pop the transaction with the highest PPU from the heap. + - Append this transaction to the list of `selectedTransactions`. + - **Update the sender's bunch:** + - If the sender of the selected transaction has more transactions in their bunch: + - Take the next transaction (next higher nonce) from the bunch. + - Push this transaction onto the heap to compete in subsequent iterations. + - This process ensures that at each step, the most valuable transaction across all senders is selected while maintaining proper nonce order for each sender. + + - **Early termination:** + - The selection loop can terminate early if either of the following conditions is satisfied before all transactions are processed: + - The accumulated gas of selected transactions meets or exceeds `gasRequested`. + - The number of selected transactions reaches `maxNum`. + +**Additional notes:** + - Within the selection loop, the current nonce of the sender is queried from the blockchain, lazily (when needed). + - If an initial nonce gap is detected, the sender is (completely) skipped in the current selection session. + - If a middle nonce gap is detected, the sender is skipped (from now on) in the current selection session. + - Transactions with nonces lower than the current nonce of the sender are skipped. + - Transactions having the same nonce as a previously selected one (in the scope of a sender) are skipped. Also see paragraph 5. + - Incorrectly guarded transactions are skipped. + - Once the accumulated fees of selected transactions of a given sender exceed the sender's balance, the sender is skipped (from now one). + + +### Paragraph 5 + +On the node's side, the selected transactions are shuffled using a deterministic algorithm. This shuffling ensures that the transaction order remains unpredictable to the proposer, effectively preventing _front-running attacks_. Therefore, being selected first by the mempool does not guarantee that a transaction will be included first in the block. Additionally, selection by the mempool does not ensure inclusion in the very next block, as the proposer has the final authority on which transactions to include, based on **the remaining space available** in the block. + +### Order of transactions of the same sender + +Transactions from the same sender are organized based on specific rules to ensure proper sequencing for the selection flow: + +1. **Nonce ascending**: transactions are primarily sorted by their nonce values in ascending order. This sequence ensures that the transactions are processed in the order intended by the sender, as the nonce represents the transaction number in the sender's sequence. + +2. **Gas price descending (same nonce)**: if multiple transactions share the same nonce, they are sorted by their gas prices in descending order - transactions offering higher gas prices are prioritized. This mechanism allows one to easily override a pending transaction with a higher gas price. + +3. **Hash ascending (same nonce and gas price)**: for transactions that have identical nonce and gas price, the tie is broken by sorting them based on their transaction hash in ascending order. This provides a consistent and deterministic ordering when other factors are equal. While this ordering isn't a critical aspect of the mempool's operation, it ensures logical consistency. diff --git a/txcache/accountBreadcrumb.go b/txcache/accountBreadcrumb.go new file mode 100644 index 00000000000..b29a73766a5 --- /dev/null +++ b/txcache/accountBreadcrumb.go @@ -0,0 +1,68 @@ +package txcache + +import ( + "math/big" + + "github.com/multiversx/mx-chain-core-go/core" +) + +type accountBreadcrumb struct { + firstNonce core.OptionalUint64 + lastNonce core.OptionalUint64 + consumedBalance *big.Int +} + +func newAccountBreadcrumb( + initialNonce core.OptionalUint64, +) *accountBreadcrumb { + return &accountBreadcrumb{ + firstNonce: initialNonce, + lastNonce: core.OptionalUint64{HasValue: false}, + consumedBalance: big.NewInt(0), + } +} + +func (breadcrumb *accountBreadcrumb) accumulateConsumedBalance(transferredValue *big.Int) { + if transferredValue != nil { + _ = breadcrumb.consumedBalance.Add(breadcrumb.consumedBalance, transferredValue) + } +} + +// updateFirstNonce is called only in the updateNonceRange method +func (breadcrumb *accountBreadcrumb) updateFirstNonce(firstNonce core.OptionalUint64) { + breadcrumb.firstNonce = firstNonce +} + +// updateNonceRange updates: +// the last nonce of a sender breadcrumb. +// the first nonce, in case the sender was previously only a relayer. +func (breadcrumb *accountBreadcrumb) updateNonceRange(lastNonce core.OptionalUint64) error { + if !lastNonce.HasValue { + return errReceivedLastNonceNotSet + } + + if breadcrumb.lastNonce.HasValue && breadcrumb.lastNonce.Value+1 != lastNonce.Value { + // validate that we have txs with sequential nonces inside the tracked block used for breadcrumbs + return errNonceGap + } + + // if the account was previously a relayer, the first nonce should not remain unset + if !breadcrumb.firstNonce.HasValue { + breadcrumb.updateFirstNonce(lastNonce) + } + + breadcrumb.lastNonce = lastNonce + return nil +} + +func (breadcrumb *accountBreadcrumb) verifyContinuityBetweenAccountBreadcrumbs(previousBreadcrumbOfSender *accountBreadcrumb) bool { + return previousBreadcrumbOfSender == nil || previousBreadcrumbOfSender.lastNonce.Value+1 == breadcrumb.firstNonce.Value +} + +func (breadcrumb *accountBreadcrumb) verifyContinuityWithSessionNonce(sessionNonce uint64) bool { + return breadcrumb.firstNonce.Value == sessionNonce +} + +func (breadcrumb *accountBreadcrumb) hasUnknownNonce() bool { + return !breadcrumb.firstNonce.HasValue && !breadcrumb.lastNonce.HasValue +} diff --git a/txcache/accountBreadcrumb_test.go b/txcache/accountBreadcrumb_test.go new file mode 100644 index 00000000000..aa7ca3117e3 --- /dev/null +++ b/txcache/accountBreadcrumb_test.go @@ -0,0 +1,293 @@ +package txcache + +import ( + "math/big" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/stretchr/testify/require" +) + +func Test_isRelayer(t *testing.T) { + t.Parallel() + + t.Run("should return true", func(t *testing.T) { + t.Parallel() + + breadcrumb := accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 0, + HasValue: false, + }, + lastNonce: core.OptionalUint64{ + Value: 0, + HasValue: false, + }, + consumedBalance: nil, + } + + actualRes := breadcrumb.hasUnknownNonce() + require.True(t, actualRes) + }) + + t.Run("should return false", func(t *testing.T) { + t.Parallel() + + breadcrumb := accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 0, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 0, + HasValue: true, + }, + consumedBalance: nil, + } + + actualRes := breadcrumb.hasUnknownNonce() + require.False(t, actualRes) + }) +} + +func Test_updateNonceRange(t *testing.T) { + t.Parallel() + + t.Run("should return receivedLastNonceNotSet the received lastNonce does not have value", func(t *testing.T) { + t.Parallel() + + breadcrumb := accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 0, + HasValue: false, + }, + lastNonce: core.OptionalUint64{ + Value: 1, + HasValue: true, + }, + consumedBalance: nil, + } + + receivedLastNonce := core.OptionalUint64{ + Value: 3, + HasValue: false, + } + + err := breadcrumb.updateNonceRange(receivedLastNonce) + require.Equal(t, errReceivedLastNonceNotSet, err) + require.Equal(t, uint64(1), breadcrumb.lastNonce.Value) + }) + + t.Run("should return nonce gap", func(t *testing.T) { + t.Parallel() + + breadcrumb := accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 0, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 3, + HasValue: true, + }, + consumedBalance: nil, + } + + receivedLastNonce := core.OptionalUint64{ + Value: 5, + HasValue: true, + } + + err := breadcrumb.updateNonceRange(receivedLastNonce) + require.Equal(t, errNonceGap, err) + }) + + t.Run("should return no err", func(t *testing.T) { + t.Parallel() + + breadcrumb := accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 0, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 3, + HasValue: true, + }, + consumedBalance: nil, + } + + receivedLastNonce := core.OptionalUint64{ + Value: 4, + HasValue: true, + } + + err := breadcrumb.updateNonceRange(receivedLastNonce) + require.Nil(t, err) + require.Equal(t, uint64(4), breadcrumb.lastNonce.Value) + }) + + t.Run("should update the first nonce in case of previously relayer", func(t *testing.T) { + t.Parallel() + + feePayerBreadcrumb := accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 0, + HasValue: false, + }, + lastNonce: core.OptionalUint64{ + Value: 0, + HasValue: false, + }, + consumedBalance: big.NewInt(10), + } + + err := feePayerBreadcrumb.updateNonceRange(core.OptionalUint64{ + Value: 3, + HasValue: true, + }) + require.Nil(t, err) + + require.Equal(t, uint64(3), feePayerBreadcrumb.firstNonce.Value) + require.Equal(t, uint64(3), feePayerBreadcrumb.lastNonce.Value) + }) +} + +func Test_verifyContinuityBetweenAccountBreadcrumbs(t *testing.T) { + t.Parallel() + + t.Run("should return true", func(t *testing.T) { + t.Parallel() + + breadcrumbAlice := accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 1, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 2, + HasValue: true, + }, + consumedBalance: nil, + } + + breadcrumbBob := accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 3, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 4, + HasValue: true, + }, + consumedBalance: nil, + } + + actualRes := breadcrumbBob.verifyContinuityBetweenAccountBreadcrumbs(&breadcrumbAlice) + require.True(t, actualRes) + }) + + t.Run("should return false", func(t *testing.T) { + t.Parallel() + + breadcrumbAlice := accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 1, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 2, + HasValue: true, + }, + consumedBalance: nil, + } + + breadcrumbBob := accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 2, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 4, + HasValue: true, + }, + consumedBalance: nil, + } + + actualRes := breadcrumbBob.verifyContinuityBetweenAccountBreadcrumbs(&breadcrumbAlice) + require.False(t, actualRes) + }) + + t.Run("should return false", func(t *testing.T) { + t.Parallel() + + breadcrumbAlice := accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 1, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 2, + HasValue: true, + }, + consumedBalance: nil, + } + + breadcrumbBob := accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 4, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 5, + HasValue: true, + }, + consumedBalance: nil, + } + + actualRes := breadcrumbBob.verifyContinuityBetweenAccountBreadcrumbs(&breadcrumbAlice) + require.False(t, actualRes) + }) +} + +func Test_verifyContinuityWithSessionNonce(t *testing.T) { + t.Parallel() + + t.Run("should return true", func(t *testing.T) { + t.Parallel() + + breadcrumb := accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 1, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 2, + HasValue: true, + }, + consumedBalance: nil, + } + + actualRes := breadcrumb.verifyContinuityWithSessionNonce(1) + require.True(t, actualRes) + }) + + t.Run("should return false", func(t *testing.T) { + t.Parallel() + + breadcrumb := accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 1, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 2, + HasValue: true, + }, + consumedBalance: nil, + } + + actualRes := breadcrumb.verifyContinuityWithSessionNonce(2) + require.False(t, actualRes) + }) +} diff --git a/txcache/accountBreadcrumbsValidator.go b/txcache/accountBreadcrumbsValidator.go new file mode 100644 index 00000000000..8abd7d19aa7 --- /dev/null +++ b/txcache/accountBreadcrumbsValidator.go @@ -0,0 +1,130 @@ +package txcache + +import ( + "math/big" +) + +// breadcrumbsValidator checks that breadcrumbs are continuous: +// With the session nonce. +// With the previous breadcrumbs. +type breadcrumbsValidator struct { + sendersInContinuityWithSessionNonce map[string]struct{} + accountPreviousBreadcrumb map[string]*accountBreadcrumb + virtualBalancesByAddress map[string]*virtualAccountBalance + discontinuousAddresses map[string]struct{} +} + +// newBreadcrumbValidator is used when the validation for a proposed block is called (when receiving the OnProposedBlock notification). +// At the end of the method it becomes useless. +func newBreadcrumbValidator() *breadcrumbsValidator { + return &breadcrumbsValidator{ + sendersInContinuityWithSessionNonce: make(map[string]struct{}), + accountPreviousBreadcrumb: make(map[string]*accountBreadcrumb), + virtualBalancesByAddress: make(map[string]*virtualAccountBalance), + discontinuousAddresses: make(map[string]struct{}), + } +} + +// validateNonceContinuityOfBreadcrumb is used when a block is proposed. +func (validator *breadcrumbsValidator) validateNonceContinuityOfBreadcrumb( + address string, + accountSessionNonce uint64, + breadcrumb *accountBreadcrumb, +) bool { + if breadcrumb.hasUnknownNonce() { + // this might occur when an account only acts as a relayer (never sender) in a specific tracked block. + // in that case, we don't have any nonce info for the relayer. + // as a result, its breadcrumb is treated as continuous. + log.Trace("breadcrumbsValidator.validateNonceContinuityOfBreadcrumb breadcrumb has unknown nonce") + return true + } + + if !validator.validateContinuityWithSessionNonce(address, accountSessionNonce, breadcrumb) { + return false + } + + if !validator.validateContinuityWithPreviousBreadcrumb(address, breadcrumb) { + return false + } + + return true +} + +// validateContinuityWithSessionNonce checks that the given breadcrumb is continuous with the session nonce +func (validator *breadcrumbsValidator) validateContinuityWithSessionNonce( + address string, + accountNonce uint64, + breadcrumb *accountBreadcrumb, +) bool { + _, ok := validator.sendersInContinuityWithSessionNonce[address] + if ok { + return true + } + + continuousWithSessionNonce := breadcrumb.verifyContinuityWithSessionNonce(accountNonce) + if !continuousWithSessionNonce { + log.Debug("virtualSessionComputer.validateNonceContinuityOfBreadcrumb breadcrumb not continuous with session nonce", + "address", address, + "accountNonce", accountNonce, + "breadcrumb nonce", breadcrumb.firstNonce) + return false + } + + // mark this sender as continuous with the session nonce + validator.sendersInContinuityWithSessionNonce[address] = struct{}{} + return true +} + +// validateContinuityWithPreviousBreadcrumb checks that the given breadcrumb of the address is continuous with the previous one, saved in the internal state of the validator. +func (validator *breadcrumbsValidator) validateContinuityWithPreviousBreadcrumb( + address string, + breadcrumb *accountBreadcrumb) bool { + + previousBreadcrumb := validator.accountPreviousBreadcrumb[address] + continuousBreadcrumbs := breadcrumb.verifyContinuityBetweenAccountBreadcrumbs(previousBreadcrumb) + if !continuousBreadcrumbs { + log.Debug("virtualSessionComputer.validateNonceContinuityOfBreadcrumb breadcrumb not continuous with previous breadcrumb", + "address", address, + "current breadcrumb nonce", breadcrumb.firstNonce, + "previous breadcrumb nonce", previousBreadcrumb.lastNonce) + return false + } + + // update the previous breadcrumb of this address + validator.accountPreviousBreadcrumb[address] = breadcrumb + + return true +} + +// markAddressAsDiscontinuous marks an address as having discontinuous breadcrumbs. +// This is used during predecessor block validation to tolerate stale breadcrumbs. +func (validator *breadcrumbsValidator) markAddressAsDiscontinuous(address string) { + validator.discontinuousAddresses[address] = struct{}{} +} + +// isAddressDiscontinuous checks if an address has been marked as having discontinuous breadcrumbs. +func (validator *breadcrumbsValidator) isAddressDiscontinuous(address string) bool { + _, ok := validator.discontinuousAddresses[address] + return ok +} + +// validateBalance is used for the OnProposedBlock flow, when validating the compiled breadcrumbs. +func (validator *breadcrumbsValidator) validateBalance( + address string, + initialBalance *big.Int, + breadcrumb *accountBreadcrumb, +) error { + virtualBalance, ok := validator.virtualBalancesByAddress[address] + if !ok { + balance, err := newVirtualAccountBalance(initialBalance) + if err != nil { + return err + } + + virtualBalance = balance + validator.virtualBalancesByAddress[address] = balance + } + + virtualBalance.accumulateConsumedBalance(breadcrumb.consumedBalance) + return virtualBalance.validateBalance() +} diff --git a/txcache/accountBreadcrumbsValidator_test.go b/txcache/accountBreadcrumbsValidator_test.go new file mode 100644 index 00000000000..5ad93a98c72 --- /dev/null +++ b/txcache/accountBreadcrumbsValidator_test.go @@ -0,0 +1,165 @@ +package txcache + +import ( + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/stretchr/testify/require" +) + +func Test_continuousBreadcrumbs(t *testing.T) { + t.Parallel() + + // when breadcrumb is relayer + t.Run("relayer should be continuous", func(t *testing.T) { + t.Parallel() + + breadcrumb := accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 1, + HasValue: false, + }, + lastNonce: core.OptionalUint64{ + Value: 2, + HasValue: false, + }, + consumedBalance: nil, + } + + validator := newBreadcrumbValidator() + + actualRes := validator.validateNonceContinuityOfBreadcrumb("bob", 0, &breadcrumb) + require.True(t, actualRes) + }) + + // when certain account is sender for the first time in the chain of tracked blocks + t.Run("sender not continuous with session nonce", func(t *testing.T) { + t.Parallel() + + breadcrumbAlice := accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 1, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 2, + HasValue: true, + }, + consumedBalance: nil, + } + + validator := newBreadcrumbValidator() + actualRes := validator.validateContinuityWithSessionNonce("alice", 3, &breadcrumbAlice) + require.False(t, actualRes) + }) + + t.Run("sender continuous with session nonce", func(t *testing.T) { + t.Parallel() + + breadcrumbAlice := accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 1, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 2, + HasValue: true, + }, + consumedBalance: nil, + } + + validator := newBreadcrumbValidator() + + actualRes := validator.validateNonceContinuityOfBreadcrumb("alice", 1, &breadcrumbAlice) + require.True(t, actualRes) + + _, ok := validator.sendersInContinuityWithSessionNonce["alice"] + require.True(t, ok) + + actualBreadcrumb, ok := validator.accountPreviousBreadcrumb["alice"] + require.True(t, ok) + require.Equal(t, &breadcrumbAlice, actualBreadcrumb) + }) + + // when address was already a sender in the chain of tracked blocks + t.Run("sender continuous with previous account breadcrumb ", func(t *testing.T) { + t.Parallel() + + breadcrumbAlice1 := accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 1, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 2, + HasValue: true, + }, + consumedBalance: nil, + } + + breadcrumbAlice2 := accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 3, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 4, + HasValue: true, + }, + consumedBalance: nil, + } + + validator := newBreadcrumbValidator() + + validator.accountPreviousBreadcrumb = map[string]*accountBreadcrumb{ + "alice": &breadcrumbAlice1, + } + + actualRes := validator.validateContinuityWithPreviousBreadcrumb("alice", &breadcrumbAlice2) + require.True(t, actualRes) + + actualBreadcrumb, ok := validator.accountPreviousBreadcrumb["alice"] + require.True(t, ok) + require.Equal(t, &breadcrumbAlice2, actualBreadcrumb) + }) + + t.Run("sender is not continuous with previous account breadcrumb ", func(t *testing.T) { + t.Parallel() + + breadcrumbAlice1 := accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 1, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 2, + HasValue: true, + }, + consumedBalance: nil, + } + + breadcrumbAlice2 := accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 4, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 4, + HasValue: true, + }, + consumedBalance: nil, + } + + validator := newBreadcrumbValidator() + + validator.accountPreviousBreadcrumb = map[string]*accountBreadcrumb{ + "alice": &breadcrumbAlice1, + } + validator.sendersInContinuityWithSessionNonce = map[string]struct{}{ + "alice": {}, + } + + actualRes := validator.validateContinuityWithPreviousBreadcrumb("alice", &breadcrumbAlice2) + require.False(t, actualRes) + }) +} diff --git a/txcache/autoClean.go b/txcache/autoClean.go new file mode 100644 index 00000000000..26ead823ba0 --- /dev/null +++ b/txcache/autoClean.go @@ -0,0 +1,188 @@ +package txcache + +import ( + "bytes" + "encoding/binary" + "sort" + "time" + + "github.com/multiversx/mx-chain-core-go/hashing/sha256" + "github.com/multiversx/mx-chain-go/common" +) + +// Cleanup simulates a selection and removes not-executable transactions. Initial implementation: lower nonces +func (cache *TxCache) Cleanup(accountsProvider common.AccountNonceProvider, randomness uint64, maxNum int, cleanupLoopMaximumDurationMs time.Duration) uint64 { + logRemove.Debug( + "TxCache.Cleanup: begin", + "randomness", randomness, + "maxNum", maxNum, + "cleanupLoopMaximumDuration", cleanupLoopMaximumDurationMs, + "num bytes", cache.NumBytes(), + "num txs", cache.CountTx(), + "num senders", cache.CountSenders(), + ) + return cache.RemoveSweepableTxs(accountsProvider, randomness, maxNum, cleanupLoopMaximumDurationMs) +} + +func (cache *TxCache) RemoveSweepableTxs(accountsProvider common.AccountNonceProvider, randomness uint64, maxNum int, cleanupLoopMaximumDurationMs time.Duration) uint64 { + cache.mutTxOperation.Lock() + defer cache.mutTxOperation.Unlock() + + rootHash, err := accountsProvider.GetRootHash() + if err != nil { + log.Debug("TxCache.RemoveSweepableTxs: failed to get root hash", "err", err) + } + + cleanupLoopStartTime := time.Now() + + logRemove.Debug("TxCache.RemoveSweepableTxs start", + "randomness", randomness, + "maxNum", maxNum, + "cleanupLoopMaximumDuration", cleanupLoopMaximumDurationMs, + "rootHash", rootHash, + ) + + evicted := make([][]byte, 0, cache.txByHash.counter.Get()) + + senders := cache.getDeterministicallyShuffledSenders(randomness) + + for _, sender := range senders { + senderAddress := []byte(sender.sender) + + accountNonce, _, err := accountsProvider.GetAccountNonce(senderAddress) + if err != nil { + log.Debug("TxCache.RemoveSweepableTxs", + "address", senderAddress, + "err", err, + ) + continue + } + + // nothing to do + if accountNonce == 0 { + continue + } + + // stop if we reached the max number of evicted transactions for this cleanup loop + if len(evicted) >= maxNum { + logRemove.Debug("TxCache.RemoveSweepableTxs reached maxNum", + "len(evicted)", len(evicted), + "maxNum", maxNum, + ) + break + } + + // stop if we reached the maximum duration for this cleanup loop + if time.Since(cleanupLoopStartTime) > cleanupLoopMaximumDurationMs { + logRemove.Debug("TxCache.RemoveSweepableTxs reached cleanupLoopMaximumDuration", + "len(evicted)", len(evicted), + "duration", time.Since(cleanupLoopStartTime), + "cleanupLoopMaximumDuration", cleanupLoopMaximumDurationMs, + ) + break + } + + // we want to remove transactions with nonces < lastCommittedNonce + targetNonce := accountNonce - 1 + + evicted = append(evicted, sender.removeSweepableTransactionsReturnHashes(targetNonce, cache.tracker)...) + } + + if len(evicted) > 0 { + cache.txByHash.RemoveTxsBulk(evicted) + } + + logRemove.Debug("TxCache.RemoveSweepableTxs end", + "randomness", randomness, + "len(evicted)", len(evicted), + "duration", time.Since(cleanupLoopStartTime), + ) + + return uint64(len(evicted)) +} + +func (cache *TxCache) getDeterministicallyShuffledSenders(randomness uint64) []*txListForSender { + senders := make([]*txListForSender, 0, cache.txListBySender.counter.Get()) + senderAddresses := cache.txListBySender.backingMap.Keys() + + shuffleSendersAddresses(senderAddresses, randomness) + for _, sender := range senderAddresses { + listForSender, ok := cache.txListBySender.backingMap.Get(sender) + if ok { + senders = append(senders, listForSender.(*txListForSender)) + } + } + + return senders +} + +func shuffleSendersAddresses(senders []string, randomness uint64) { + randomnessAsBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(randomnessAsBytes, randomness) + + hasher := sha256.NewSha256() + keys := make([][]byte, len(senders)) + + for i, addr := range senders { + addrWithRand := append([]byte(addr), randomnessAsBytes...) + keys[i] = hasher.Compute(string(addrWithRand)) + } + + sort.Slice(senders, func(i, j int) bool { + cmp := bytes.Compare(keys[i], keys[j]) + if cmp != 0 { + return cmp > 0 + } + return senders[i] > senders[j] + }) +} + +func (listForSender *txListForSender) removeSweepableTransactionsReturnHashes(targetNonce uint64, tracker *selectionTracker) [][]byte { + txHashesToEvict := make([][]byte, 0) + + // We don't allow concurrent goroutines to mutate a given sender's list + listForSender.mutex.Lock() + defer listForSender.mutex.Unlock() + + // Filter approach: we need to remove untracked transactions <= targetNonce + // Since we are operating on the underlying slice, we can iterate. + + cutoffIndex := listForSender.list.len() + items := listForSender.list.items + + for i, tx := range items { + // Nonces are sorted ascending, so we can stop as soon as we find a nonce that is higher + if tx.Tx.GetNonce() > targetNonce { + cutoffIndex = i + break + } + } + + // Candidates in [0...cutoffIndex-1] + // Rebuild the prefix + + keptItems := make([]*WrappedTransaction, 0, cutoffIndex) + + for i := 0; i < cutoffIndex; i++ { + tx := items[i] + shouldRemove := !tracker.IsTransactionTracked(tx) + + if shouldRemove { + logRemove.Debug("TxCache.removeSweepableTransactionsReturnHashes", + "txHash", tx.TxHash, + "txNonce", tx.Tx.GetNonce(), + "targetNonce", targetNonce, + ) + txHashesToEvict = append(txHashesToEvict, tx.TxHash) + listForSender.onRemovedTransaction(tx) + continue + } + keptItems = append(keptItems, tx) + } + + // Reassemble + keptItems = append(keptItems, items[cutoffIndex:]...) + listForSender.list.items = keptItems + + return txHashesToEvict +} diff --git a/txcache/autoClean_test.go b/txcache/autoClean_test.go new file mode 100644 index 00000000000..8c7b05aed76 --- /dev/null +++ b/txcache/autoClean_test.go @@ -0,0 +1,371 @@ +package txcache + +import ( + "fmt" + "math" + "testing" + + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/testscommon/txcachemocks" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTxCache_ShuffleSendersAddresses_Dummy(t *testing.T) { + + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + // add data into the cache + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1)) + cache.AddTx(createTx([]byte("hash-bob-40"), "bob", 40)) + cache.AddTx(createTx([]byte("hash-carol-7"), "carol", 7)) + cache.AddTx(createTx([]byte("hash-dave-2"), "dave", 2)) + cache.AddTx(createTx([]byte("hash-eve-3"), "eve", 3)) + cache.AddTx(createTx([]byte("hash-frank-5"), "frank", 5)) + cache.AddTx(createTx([]byte("hash-grace-6"), "grace", 6)) + cache.AddTx(createTx([]byte("hash-helen-4"), "helen", 4)) + + t.Run("with same randomness", func(t *testing.T) { + senderAddresses := cache.txListBySender.backingMap.Keys() + senderAddressesCopy := make([]string, len(senderAddresses)) + copy(senderAddressesCopy, senderAddresses) + senderAddressesReference := make([]string, len(senderAddresses)) + copy(senderAddressesReference, senderAddresses) + + shuffleSendersAddresses(senderAddresses, 108) + assert.NotEqual(t, senderAddresses, senderAddressesReference) + + shuffleSendersAddresses(senderAddressesCopy, 108) + assert.NotEqual(t, senderAddressesCopy, senderAddressesReference) + assert.Equal(t, senderAddressesCopy, senderAddresses) + }) + + t.Run("with different randomness", func(t *testing.T) { + senderAddresses := cache.txListBySender.backingMap.Keys() + senderAddressesCopy := make([]string, len(senderAddresses)) + copy(senderAddressesCopy, senderAddresses) + senderAddressesReference := make([]string, len(senderAddresses)) + copy(senderAddressesReference, senderAddresses) + + shuffleSendersAddresses(senderAddresses, 108) + assert.NotEqual(t, senderAddresses, senderAddressesReference) + + shuffleSendersAddresses(senderAddressesCopy, 10832) + assert.NotEqual(t, senderAddressesCopy, senderAddressesReference) + assert.NotEqual(t, senderAddressesCopy, senderAddresses) + }) +} + +func TestTxCache_GetDeterministicallyShuffledSenders_Dummy(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + // add data into the cache + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1)) + cache.AddTx(createTx([]byte("hash-bob-40"), "bob", 40)) + cache.AddTx(createTx([]byte("hash-carol-7"), "carol", 7)) + + t.Run("with same randomness", func(t *testing.T) { + sendersList := cache.txListBySender.backingMap.Keys() + randomnessSendersList := cache.getDeterministicallyShuffledSenders(uint64(100)) + sameRandomnessSendersList := cache.getDeterministicallyShuffledSenders(uint64(100)) + + assert.NotEqual(t, sendersList, sameRandomnessSendersList) + assert.NotEqual(t, sendersList, randomnessSendersList) + assert.Equal(t, randomnessSendersList, sameRandomnessSendersList) + }) + + t.Run("with different randomness", func(t *testing.T) { + sendersList := cache.txListBySender.backingMap.Keys() + randomnessSendersList := cache.getDeterministicallyShuffledSenders(uint64(100)) + otherRandomnessSendersList := cache.getDeterministicallyShuffledSenders(uint64(127)) + + assert.NotEqual(t, sendersList, otherRandomnessSendersList) + assert.NotEqual(t, sendersList, randomnessSendersList) + assert.NotEqual(t, randomnessSendersList, otherRandomnessSendersList) + }) +} + +func Test_RemoveSweepableTransactionsReturnHashes_Dummy(t *testing.T) { + t.Run("with lower nonces", func(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + list := newUnconstrainedListToTest() + list.AddTx(createTx([]byte("a"), ".", 1), txCache.tracker) + list.AddTx(createTx([]byte("b"), ".", 3), txCache.tracker) + list.AddTx(createTx([]byte("c"), ".", 4), txCache.tracker) + list.AddTx(createTx([]byte("d"), ".", 2), txCache.tracker) + list.AddTx(createTx([]byte("e"), ".", 5), txCache.tracker) + + hashesBeforeEviction := list.getTxHashesAsStrings() + + list.removeSweepableTransactionsReturnHashes(uint64(3), txCache.tracker) + hashesAfterEviction := list.getTxHashesAsStrings() + require.Equal(t, []string{"c", "e"}, hashesAfterEviction) + + expectedEvicted := 3 // nonce 1, 2, 3 + require.Equal(t, len(hashesBeforeEviction), expectedEvicted+len(hashesAfterEviction)) + }) +} + +func TestTxCache_Cleanup(t *testing.T) { + t.Run("with GetAccountNonce errors", func(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMock() + accountsProvider.GetAccountNonceCalled = func(address []byte) (uint64, bool, error) { + switch string(address) { + case "alice": + return 3, true, nil + case "bob": + return 42, true, fmt.Errorf("forced error for address %s", address) + case "carol": + return 7, true, nil + default: + return 0, false, nil + } + } + + // Two with lower nonce for alice + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1)) + cache.AddTx(createTx([]byte("hash-alice-2"), "alice", 2)) + cache.AddTx(createTx([]byte("hash-alice-3"), "alice", 3)) + // Two with lower nonce for bob (all will error out) + cache.AddTx(createTx([]byte("hash-bob-40"), "bob", 40)) + cache.AddTx(createTx([]byte("hash-bob-41"), "bob", 41)) + cache.AddTx(createTx([]byte("hash-bob-42"), "bob", 42)) + // Good for carol + cache.AddTx(createTx([]byte("hash-carol-7"), "carol", 7)) + cache.AddTx(createTx([]byte("hash-carol-8"), "carol", 8)) + expectedNumEvicted := 2 // only alice + evicted := cache.Cleanup(accountsProvider, 7, math.MaxInt, 1000*cleanupLoopMaximumDuration) + + require.Equal(t, uint64(expectedNumEvicted), evicted) + require.True(t, cache.areInternalMapsConsistent()) + }) + + t.Run("with nonce equal 0", func(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMock() + accountsProvider.SetNonce([]byte("alice"), 0) + + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1)) + cache.AddTx(createTx([]byte("hash-alice-2"), "alice", 2)) + + expectedNumEvicted := 0 + evicted := cache.Cleanup(accountsProvider, 7, math.MaxInt, 1000*cleanupLoopMaximumDuration) + + require.Equal(t, uint64(expectedNumEvicted), evicted) + }) + + t.Run("with number of evicted transactions cap reached", func(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMock() + accountsProvider.SetNonce([]byte("alice"), 3) + accountsProvider.SetNonce([]byte("bob"), 42) + + // Two with lower nonce for alice + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1)) + cache.AddTx(createTx([]byte("hash-alice-2"), "alice", 2)) + cache.AddTx(createTx([]byte("hash-alice-3"), "alice", 3)) + + // A few with lower nonce for bob + cache.AddTx(createTx([]byte("hash-bob-40"), "bob", 40)) + cache.AddTx(createTx([]byte("hash-bob-41"), "bob", 41)) + cache.AddTx(createTx([]byte("hash-bob-42"), "bob", 42)) + + expectedNumEvicted := 2 // only alice, because maxNum is 2 + evicted := cache.Cleanup(accountsProvider, 7, 2, 1000*cleanupLoopMaximumDuration) + require.Equal(t, uint64(expectedNumEvicted), evicted) + require.True(t, cache.areInternalMapsConsistent()) + }) + + t.Run("with cleanupLoopMaximumDuration cap reached", func(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMock() + accountsProvider.SetNonce([]byte("alice"), 4) + accountsProvider.SetNonce([]byte("bob"), 43) + accountsProvider.SetNonce([]byte("carol"), 9) + // Two with lower nonce for alice + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1)) + cache.AddTx(createTx([]byte("hash-alice-2"), "alice", 2)) + cache.AddTx(createTx([]byte("hash-alice-3"), "alice", 3)) + // A few with lower nonce for bob + cache.AddTx(createTx([]byte("hash-bob-40"), "bob", 40)) + cache.AddTx(createTx([]byte("hash-bob-41"), "bob", 41)) + cache.AddTx(createTx([]byte("hash-bob-42"), "bob", 42)) + // Good for carol + cache.AddTx(createTx([]byte("hash-carol-7"), "carol", 7)) + cache.AddTx(createTx([]byte("hash-carol-8"), "carol", 8)) + evictable := 8 + evicted := cache.Cleanup(accountsProvider, 7, math.MaxInt, 1000) + require.Less(t, evicted, uint64(evictable)) + require.True(t, cache.areInternalMapsConsistent()) + }) + + t.Run("with lower nonces", func(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMock() + accountsProvider.SetNonce([]byte("alice"), 2) + accountsProvider.SetNonce([]byte("bob"), 42) + accountsProvider.SetNonce([]byte("carol"), 7) + + // One with lower nonce + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1)) + cache.AddTx(createTx([]byte("hash-alice-2"), "alice", 2)) + cache.AddTx(createTx([]byte("hash-alice-3"), "alice", 3)) + + // A few with lower nonce + cache.AddTx(createTx([]byte("hash-bob-40"), "bob", 40)) + cache.AddTx(createTx([]byte("hash-bob-41"), "bob", 41)) + cache.AddTx(createTx([]byte("hash-bob-42"), "bob", 42)) + + // Good + cache.AddTx(createTx([]byte("hash-carol-7"), "carol", 7)) + cache.AddTx(createTx([]byte("hash-carol-8"), "carol", 8)) + + expectedNumEvicted := 3 // 2 bob, 1 alice + evicted := cache.Cleanup(accountsProvider, 7, math.MaxInt, 1000*cleanupLoopMaximumDuration) + require.Equal(t, uint64(expectedNumEvicted), evicted) + require.True(t, cache.areInternalMapsConsistent()) + }) + + t.Run("with tracked txs", func(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMock() + accountsProvider.SetNonce([]byte("alice"), 2) + accountsProvider.SetNonce([]byte("bob"), 42) + accountsProvider.SetNonce([]byte("carol"), 7) + + // add txs into the cache + cache.AddTx(createTx([]byte("hash-alice-2"), "alice", 2)) + cache.AddTx(createTx([]byte("hash-alice-3"), "alice", 3)) + cache.AddTx(createTx([]byte("hash-bob-42"), "bob", 42)) + cache.AddTx(createTx([]byte("hash-carol-7"), "carol", 7)) + cache.AddTx(createTx([]byte("hash-carol-8"), "carol", 8)) + + // propose those txs + err := cache.OnProposedBlock( + []byte("blockHash1"), + &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("hash-alice-2"), + []byte("hash-alice-3"), + []byte("hash-bob-42"), + []byte("hash-carol-7"), + []byte("hash-carol-8"), + }, + }, + }, + }, + &block.Header{ + Nonce: 1, + PrevHash: []byte("blockHash0"), + }, + accountsProvider, + []byte("blockHash0"), + ) + require.Nil(t, err) + + expectedNumEvicted := 0 + evicted := cache.Cleanup(accountsProvider, 7, math.MaxInt, 1000*cleanupLoopMaximumDuration) + require.Equal(t, uint64(expectedNumEvicted), evicted) + require.True(t, cache.areInternalMapsConsistent()) + + // add a transaction that has a tracked nonce + cache.AddTx(createTx([]byte("hash-carol-X"), "carol", 8)) + evicted = cache.Cleanup(accountsProvider, 7, math.MaxInt, 1000*cleanupLoopMaximumDuration) + require.Equal(t, uint64(expectedNumEvicted), evicted) + require.True(t, cache.areInternalMapsConsistent()) + + _, ok := cache.GetByTxHash([]byte("hash-carol-X")) + require.True(t, ok) + + // add a transaction with lower nonce + cache.AddTx(createTx([]byte("hash-alice-X"), "alice", 1)) + expectedNumEvicted = 1 + evicted = cache.Cleanup(accountsProvider, 7, math.MaxInt, 1000*cleanupLoopMaximumDuration) + require.Equal(t, uint64(expectedNumEvicted), evicted) + require.True(t, cache.areInternalMapsConsistent()) + + _, ok = cache.GetByTxHash([]byte("hash-alice-X")) + require.False(t, ok) + + err = cache.OnExecutedBlock( + &block.Header{ + Nonce: 1, + PrevHash: []byte("blockHash0"), + }, + []byte("rootHAsh"), + ) + require.Nil(t, err) + + cache.Remove([]byte("hash-alice-2")) + cache.Remove([]byte("hash-alice-3")) + cache.Remove([]byte("hash-bob-42")) + cache.Remove([]byte("hash-carol-7")) + // when this transaction is removed, the hash-carol-X transaction is also removed + cache.Remove([]byte("hash-carol-8")) + + expectedNumEvicted = 0 + evicted = cache.Cleanup(accountsProvider, 7, math.MaxInt, 1000*cleanupLoopMaximumDuration) + require.Equal(t, uint64(expectedNumEvicted), evicted) + require.True(t, cache.areInternalMapsConsistent()) + }) +} + +// helper function for creating a new unconstrained cache with a given size +func newTxPoolWithN(size int, accountsProvider *txcachemocks.AccountNonceAndBalanceProviderMock) *TxCache { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + for i := 0; i < size; i++ { + cache.AddTx(createTx([]byte(fmt.Sprintf("hash-%d", i)), fmt.Sprintf("sender-%d", i), uint64(i))) + accountsProvider.SetNonce([]byte(fmt.Sprintf("sender-%d", i)), uint64(i+10)) + } + return cache +} + +func BenchmarkAddressShuffling(b *testing.B) { + sizes := []int{1000, 10000, 50000, 100000} + for _, size := range sizes { + b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) { + // prepare pool + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMock() + cache := newTxPoolWithN(size, accountsProvider) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + senderAddresses := cache.txListBySender.backingMap.Keys() + shuffleSendersAddresses(senderAddresses, uint64(i)) + } + }) + } +} + +func BenchmarkCleanup(b *testing.B) { + sizes := []int{1000, 10000, 50000, 100000} + for _, size := range sizes { + b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StopTimer() + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMock() + cache := newTxPoolWithN(size, accountsProvider) + b.StartTimer() + + _ = cache.Cleanup(accountsProvider, uint64(i), math.MaxInt, 1000*cleanupLoopMaximumDuration) + } + + }) + } +} diff --git a/txcache/blocks.go b/txcache/blocks.go new file mode 100644 index 00000000000..f1580b62500 --- /dev/null +++ b/txcache/blocks.go @@ -0,0 +1,54 @@ +package txcache + +import ( + "encoding/hex" + "fmt" + + "github.com/multiversx/mx-chain-core-go/data/block" +) + +// getTransactionsInBlock returns the transactions from a block body +func getTransactionsInBlock( + blockBody *block.Body, + txCache txCacheForSelectionTracker, + selfShardId uint32, +) ([]*WrappedTransaction, error) { + miniBlocks := blockBody.GetMiniBlocks() + numberOfTxs := computeNumberOfTxsInMiniBlocks(miniBlocks) + txs := make([]*WrappedTransaction, 0, numberOfTxs) + + for _, miniBlock := range miniBlocks { + isTxBlock := miniBlock.Type == block.TxBlock + isInvalidBlock := miniBlock.Type == block.InvalidBlock + if !isTxBlock && !isInvalidBlock { + continue + } + + if miniBlock.SenderShardID != selfShardId { + continue + } + + txHashes := miniBlock.GetTxHashes() + + for _, txHash := range txHashes { + tx, ok := txCache.GetByTxHash(txHash) + if !ok { + return nil, fmt.Errorf("%w for txHash: %s", errNotFoundTx, hex.EncodeToString(txHash)) + } + + txs = append(txs, tx) + } + } + + return txs, nil +} + +// computeNumberOfTxsInMiniBlocks returns the number of transactions in mini blocks +func computeNumberOfTxsInMiniBlocks(miniBlocks []*block.MiniBlock) int { + numberOfTxs := 0 + for _, miniBlock := range miniBlocks { + numberOfTxs += len(miniBlock.GetTxHashes()) + } + + return numberOfTxs +} diff --git a/txcache/blocks_test.go b/txcache/blocks_test.go new file mode 100644 index 00000000000..61b5116dc68 --- /dev/null +++ b/txcache/blocks_test.go @@ -0,0 +1,95 @@ +package txcache + +import ( + "testing" + + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/stretchr/testify/require" +) + +func Test_computeNumberOfTxsInMiniBlocks(t *testing.T) { + t.Parallel() + + t.Run("should return the right number of txs", func(t *testing.T) { + blockBody := block.Body{MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("txHash1"), + []byte("txHash2"), + }, + }, + { + TxHashes: [][]byte{ + []byte("txHash3"), + }, + }, + { + TxHashes: [][]byte{ + []byte("txHash4"), + []byte("txHash5"), + []byte("txHash6"), + }, + }, + }} + + actualResult := computeNumberOfTxsInMiniBlocks(blockBody.MiniBlocks) + require.Equal(t, 6, actualResult) + }) +} + +func Test_getTransactionsInBlock(t *testing.T) { + t.Parallel() + + t.Run("should work", func(t *testing.T) { + blockBody := block.Body{MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("txHash1"), + []byte("txHash2"), + }, + }, + { + TxHashes: [][]byte{ + []byte("txHash3"), + }, + }, + }} + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + txCache.txByHash = newTxByHashMap(1) + + txCache.txByHash.addTx(createTx([]byte("txHash1"), "alice", 1)) + txCache.txByHash.addTx(createTx([]byte("txHash2"), "alice", 2)) + txCache.txByHash.addTx(createTx([]byte("txHash3"), "alice", 3)) + + txs, err := getTransactionsInBlock(&blockBody, txCache, 0) + require.Nil(t, err) + require.Equal(t, 3, len(txs)) + }) + + t.Run("should fail", func(t *testing.T) { + blockBody := block.Body{MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("txHash1"), + []byte("txHash2"), + }, + }, + { + TxHashes: [][]byte{ + []byte("txHash3"), + }, + }, + }} + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + txCache.txByHash = newTxByHashMap(1) + + txCache.txByHash.addTx(createTx([]byte("txHash1"), "alice", 1)) + txCache.txByHash.addTx(createTx([]byte("txHash2"), "alice", 2)) + + txs, err := getTransactionsInBlock(&blockBody, txCache, 0) + require.Nil(t, txs) + require.ErrorIs(t, err, errNotFoundTx) + }) +} diff --git a/txcache/config.go b/txcache/config.go new file mode 100644 index 00000000000..3ae721163bd --- /dev/null +++ b/txcache/config.go @@ -0,0 +1,119 @@ +package txcache + +import ( + "encoding/json" + "fmt" + + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-storage-go/common" +) + +const numChunksLowerBound = 1 +const numChunksUpperBound = 128 +const maxNumItemsLowerBound = 4 +const maxNumBytesLowerBound = maxNumItemsLowerBound * 1 +const maxNumBytesUpperBound = 1_073_741_824 // one GB +const maxNumItemsPerSenderLowerBound = 1 +const maxNumBytesPerSenderLowerBound = maxNumItemsPerSenderLowerBound * 1 +const numItemsToPreemptivelyEvictLowerBound = uint32(1) + +// ConfigSourceMe holds cache configuration +type ConfigSourceMe struct { + Name string + NumChunks uint32 + EvictionEnabled bool + NumBytesThreshold uint32 + NumBytesPerSenderThreshold uint32 + CountThreshold uint32 + CountPerSenderThreshold uint32 + NumItemsToPreemptivelyEvict uint32 + TxCacheBoundsConfig config.TxCacheBoundsConfig +} + +type senderConstraints struct { + maxNumTxs uint32 + maxNumBytes uint32 +} + +func (config *ConfigSourceMe) verify() error { + if len(config.Name) == 0 { + return fmt.Errorf("%w: config.Name is invalid", common.ErrInvalidConfig) + } + if config.NumChunks < numChunksLowerBound || config.NumChunks > numChunksUpperBound { + return fmt.Errorf("%w: config.NumChunks is invalid", common.ErrInvalidConfig) + } + if config.NumBytesPerSenderThreshold < maxNumBytesPerSenderLowerBound || config.NumBytesPerSenderThreshold > config.TxCacheBoundsConfig.MaxNumBytesPerSenderUpperBound { + return fmt.Errorf("%w: config.NumBytesPerSenderThreshold is invalid", common.ErrInvalidConfig) + } + if config.CountPerSenderThreshold < maxNumItemsPerSenderLowerBound { + return fmt.Errorf("%w: config.CountPerSenderThreshold is invalid", common.ErrInvalidConfig) + } + + if config.NumBytesThreshold < maxNumBytesLowerBound || config.NumBytesThreshold > maxNumBytesUpperBound { + return fmt.Errorf("%w: config.NumBytesThreshold is invalid", common.ErrInvalidConfig) + } + if config.CountThreshold < maxNumItemsLowerBound { + return fmt.Errorf("%w: config.CountThreshold is invalid", common.ErrInvalidConfig) + } + if config.NumItemsToPreemptivelyEvict < numItemsToPreemptivelyEvictLowerBound { + return fmt.Errorf("%w: config.NumItemsToPreemptivelyEvict is invalid", common.ErrInvalidConfig) + } + + return nil +} + +func (config *ConfigSourceMe) getSenderConstraints() senderConstraints { + return senderConstraints{ + maxNumBytes: config.NumBytesPerSenderThreshold, + maxNumTxs: config.CountPerSenderThreshold, + } +} + +// String returns a readable representation of the object +func (config *ConfigSourceMe) String() string { + bytes, err := json.Marshal(config) + if err != nil { + log.Error("ConfigSourceMe.String", "err", err) + } + + return string(bytes) +} + +// ConfigDestinationMe holds cache configuration +type ConfigDestinationMe struct { + Name string + NumChunks uint32 + MaxNumItems uint32 + MaxNumBytes uint32 + NumItemsToPreemptivelyEvict uint32 +} + +func (config *ConfigDestinationMe) verify() error { + if len(config.Name) == 0 { + return fmt.Errorf("%w: config.Name is invalid", common.ErrInvalidConfig) + } + if config.NumChunks < numChunksLowerBound || config.NumChunks > numChunksUpperBound { + return fmt.Errorf("%w: config.NumChunks is invalid", common.ErrInvalidConfig) + } + if config.MaxNumItems < maxNumItemsLowerBound { + return fmt.Errorf("%w: config.MaxNumItems is invalid", common.ErrInvalidConfig) + } + if config.MaxNumBytes < maxNumBytesLowerBound || config.MaxNumBytes > maxNumBytesUpperBound { + return fmt.Errorf("%w: config.MaxNumBytes is invalid", common.ErrInvalidConfig) + } + if config.NumItemsToPreemptivelyEvict < numItemsToPreemptivelyEvictLowerBound { + return fmt.Errorf("%w: config.NumItemsToPreemptivelyEvict is invalid", common.ErrInvalidConfig) + } + + return nil +} + +// String returns a readable representation of the object +func (config *ConfigDestinationMe) String() string { + bytes, err := json.Marshal(config) + if err != nil { + log.Error("ConfigDestinationMe.String", "err", err) + } + + return string(bytes) +} diff --git a/txcache/constants.go b/txcache/constants.go new file mode 100644 index 00000000000..6faad0bb4f3 --- /dev/null +++ b/txcache/constants.go @@ -0,0 +1,4 @@ +package txcache + +const diagnosisMaxTransactionsToDisplay = 10000 +const initialCapacityOfSelectionSlice = 30000 diff --git a/txcache/crossTxCache.go b/txcache/crossTxCache.go new file mode 100644 index 00000000000..eef9622bfbf --- /dev/null +++ b/txcache/crossTxCache.go @@ -0,0 +1,156 @@ +package txcache + +import ( + "time" + + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-storage-go/immunitycache" + "github.com/multiversx/mx-chain-storage-go/types" + + "github.com/multiversx/mx-chain-go/common" +) + +var _ types.Cacher = (*CrossTxCache)(nil) + +// CrossTxCache holds cross-shard transactions (where destination == me) +type CrossTxCache struct { + *immunitycache.ImmunityCache + config ConfigDestinationMe +} + +// NewCrossTxCache creates a new transactions cache +func NewCrossTxCache(config ConfigDestinationMe) (*CrossTxCache, error) { + log.Debug("NewCrossTxCache", "config", config.String()) + + err := config.verify() + if err != nil { + return nil, err + } + + immunityCacheConfig := immunitycache.CacheConfig{ + Name: config.Name, + NumChunks: config.NumChunks, + MaxNumBytes: config.MaxNumBytes, + MaxNumItems: config.MaxNumItems, + NumItemsToPreemptivelyEvict: config.NumItemsToPreemptivelyEvict, + } + + immunityCache, err := immunitycache.NewImmunityCache(immunityCacheConfig) + if err != nil { + return nil, err + } + + cache := CrossTxCache{ + ImmunityCache: immunityCache, + config: config, + } + + return &cache, nil +} + +// ImmunizeTxsAgainstEviction marks items as non-evictable +func (cache *CrossTxCache) ImmunizeTxsAgainstEviction(keys [][]byte) { + numNow, numFuture := cache.ImmunityCache.ImmunizeKeys(keys) + log.Trace("CrossTxCache.ImmunizeTxsAgainstEviction", + "name", cache.config.Name, + "len(keys)", len(keys), + "numNow", numNow, + "numFuture", numFuture, + ) + cache.Diagnose(false) +} + +// AddTx adds a transaction in the cache +func (cache *CrossTxCache) AddTx(tx *WrappedTransaction) (has, added bool) { + log.Trace("CrossTxCache.AddTx", "name", cache.config.Name, "txHash", tx.TxHash) + return cache.HasOrAdd(tx.TxHash, tx, int(tx.Size)) +} + +// GetByTxHash gets the transaction by hash +func (cache *CrossTxCache) GetByTxHash(txHash []byte) (*WrappedTransaction, bool) { + item, ok := cache.ImmunityCache.Get(txHash) + if !ok { + return nil, false + } + tx, ok := item.(*WrappedTransaction) + if !ok { + return nil, false + } + + return tx, true +} + +// Get returns the unwrapped payload of a TransactionWrapper +// Implemented for compatibility reasons (see txPoolsCleaner.go). +func (cache *CrossTxCache) Get(key []byte) (value interface{}, ok bool) { + wrapped, ok := cache.GetByTxHash(key) + if !ok { + return nil, false + } + + return wrapped.Tx, true +} + +// Peek returns the unwrapped payload of a TransactionWrapper +// Implemented for compatibility reasons (see transactions.go, common.go). +func (cache *CrossTxCache) Peek(key []byte) (value interface{}, ok bool) { + return cache.Get(key) +} + +// RemoveTxByHash removes tx by hash +func (cache *CrossTxCache) RemoveTxByHash(txHash []byte) bool { + log.Trace("CrossTxCache.RemoveTxByHash", "name", cache.config.Name, "txHash", txHash) + return cache.RemoveWithResult(txHash) +} + +// ForEachTransaction iterates over the transactions in the cache +func (cache *CrossTxCache) ForEachTransaction(function ForEachTransaction) { + cache.ForEachItem(func(key []byte, item interface{}) { + tx, ok := item.(*WrappedTransaction) + if !ok { + return + } + + function(key, tx) + }) +} + +// GetTransactionsPoolForSender returns an empty slice, only to respect the interface +// CrossTxCache does not support transaction selection (not applicable, since transactions are already half-executed), +// thus does not handle nonces, nonce gaps etc. +func (cache *CrossTxCache) GetTransactionsPoolForSender(_ string) []*WrappedTransaction { + return make([]*WrappedTransaction, 0) +} + +// OnProposedBlock does nothing (only to satisfy the interface) +func (cache *CrossTxCache) OnProposedBlock(_ []byte, _ data.BodyHandler, _ data.HeaderHandler, _ common.AccountNonceAndBalanceProvider, _ []byte) error { + return nil +} + +// OnBackfilledBlock does nothing (only to satisfy the interface) +func (cache *CrossTxCache) OnBackfilledBlock(_ []byte, _ data.BodyHandler, _ data.HeaderHandler) error { + return nil +} + +// OnExecutedBlock does nothing (only to satisfy the interface) +func (cache *CrossTxCache) OnExecutedBlock(data.HeaderHandler, []byte) error { + return nil +} + +// ResetTracker does nothing (only to satisfy the interface) +func (cache *CrossTxCache) ResetTracker() {} + +// GetTrackerDiagnosis returns nil (only to satisfy the interface) +func (cache *CrossTxCache) GetTrackerDiagnosis() TrackerDiagnosis { + return nil +} + +// Cleanup does nothing (only to satisfy the interface) +func (cache *CrossTxCache) Cleanup(_ common.AccountNonceProvider, _ uint64, _ int, _ time.Duration) uint64 { + return 0 +} + +// IsInterfaceNil returns true if there is no value under the interface +func (cache *CrossTxCache) IsInterfaceNil() bool { + return cache == nil +} diff --git a/txcache/crossTxCache_test.go b/txcache/crossTxCache_test.go new file mode 100644 index 00000000000..d657e5684fb --- /dev/null +++ b/txcache/crossTxCache_test.go @@ -0,0 +1,135 @@ +package txcache + +import ( + "fmt" + "math" + "strings" + "testing" + + "github.com/multiversx/mx-chain-storage-go/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewCrossTxCache(t *testing.T) { + t.Parallel() + + t.Run("invalid config should error", func(t *testing.T) { + t.Parallel() + + cfg := ConfigDestinationMe{ + Name: "", + NumChunks: 1, + MaxNumItems: 100, + MaxNumBytes: 1000, + NumItemsToPreemptivelyEvict: 1, + } + + cache, err := NewCrossTxCache(cfg) + assert.Nil(t, cache) + assert.ErrorIs(t, err, common.ErrInvalidConfig) + assert.True(t, strings.Contains(err.Error(), "config.Name is invalid")) + }) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + cfg := ConfigDestinationMe{ + Name: "test", + NumChunks: 1, + MaxNumItems: 100, + MaxNumBytes: 1000, + NumItemsToPreemptivelyEvict: 1, + } + + cache, err := NewCrossTxCache(cfg) + assert.NotNil(t, cache) + assert.Nil(t, err) + }) +} + +func TestCrossTxCache_DoImmunizeTxsAgainstEviction(t *testing.T) { + cache := newCrossTxCacheToTest(1, 8, math.MaxUint16) + + cache.addTestTxs("a", "b", "c", "d") + numNow, numFuture := cache.ImmunizeKeys(hashesAsBytes([]string{"a", "b", "e", "f"})) + require.Equal(t, 2, numNow) + require.Equal(t, 2, numFuture) + require.Equal(t, 4, cache.Len()) + + cache.addTestTxs("e", "f", "g", "h") + require.ElementsMatch(t, []string{"a", "b", "c", "d", "e", "f", "g", "h"}, hashesAsStrings(cache.Keys())) + + cache.addTestTxs("i", "j", "k", "l") + require.ElementsMatch(t, []string{"a", "b", "e", "f", "i", "j", "k", "l"}, hashesAsStrings(cache.Keys())) +} + +func TestCrossTxCache_Get(t *testing.T) { + cache := newCrossTxCacheToTest(1, 8, math.MaxUint16) + + cache.addTestTxs("a", "b", "c", "d") + a, ok := cache.GetByTxHash([]byte("a")) + require.True(t, ok) + require.NotNil(t, a) + + x, ok := cache.GetByTxHash([]byte("x")) + require.False(t, ok) + require.Nil(t, x) + + aTx, ok := cache.Get([]byte("a")) + require.True(t, ok) + require.NotNil(t, aTx) + require.Equal(t, a.Tx, aTx) + + xTx, ok := cache.Get([]byte("x")) + require.False(t, ok) + require.Nil(t, xTx) + + aTx, ok = cache.Peek([]byte("a")) + require.True(t, ok) + require.NotNil(t, aTx) + require.Equal(t, a.Tx, aTx) + + xTx, ok = cache.Peek([]byte("x")) + require.False(t, ok) + require.Nil(t, xTx) + + require.Equal(t, make([]*WrappedTransaction, 0), cache.GetTransactionsPoolForSender("")) +} + +func TestCrossTxCache_NotImplemented(t *testing.T) { + cache := newCrossTxCacheToTest(1, 8, math.MaxUint16) + + err := cache.OnProposedBlock(nil, nil, nil, nil, nil) + require.Nil(t, err) + + err = cache.OnExecutedBlock(nil, nil) + require.Nil(t, err) + + diagnosis := cache.GetTrackerDiagnosis() + require.Nil(t, diagnosis) +} + +func newCrossTxCacheToTest(numChunks uint32, maxNumItems uint32, numMaxBytes uint32) *CrossTxCache { + cache, err := NewCrossTxCache(ConfigDestinationMe{ + Name: "test", + NumChunks: numChunks, + MaxNumItems: maxNumItems, + MaxNumBytes: numMaxBytes, + NumItemsToPreemptivelyEvict: numChunks * 1, + }) + if err != nil { + panic(fmt.Sprintf("newCrossTxCacheToTest(): %s", err)) + } + + return cache +} + +func (cache *CrossTxCache) addTestTxs(hashes ...string) { + for _, hash := range hashes { + _, _ = cache.addTestTx(hash) + } +} + +func (cache *CrossTxCache) addTestTx(hash string) (ok, added bool) { + return cache.AddTx(createTx([]byte(hash), ".", uint64(42))) +} diff --git a/txcache/diagnosis.go b/txcache/diagnosis.go new file mode 100644 index 00000000000..38f54a4cf57 --- /dev/null +++ b/txcache/diagnosis.go @@ -0,0 +1,130 @@ +package txcache + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "strings" + + "github.com/multiversx/mx-chain-core-go/core" + logger "github.com/multiversx/mx-chain-logger-go" +) + +type printedTransaction struct { + Hash string `json:"hash"` + PPU uint64 `json:"ppu"` + Nonce uint64 `json:"nonce"` + Sender string `json:"sender"` + GasPrice uint64 `json:"gasPrice"` + GasLimit uint64 `json:"gasLimit"` + Receiver string `json:"receiver"` + DataLength int `json:"dataLength"` +} + +type printedTrackedBlock struct { + Hash string `json:"hash"` + PreviousHash string `json:"previousHash"` + Nonce uint64 `json:"nonce"` +} + +// Diagnose checks the state of the cache for inconsistencies and displays a summary, senders and transactions. +func (cache *TxCache) Diagnose(_ bool) { + cache.diagnoseTransactions() +} + +// GetTrackerDiagnosis returns the dimension of the tracked blocks +func (cache *TxCache) GetTrackerDiagnosis() TrackerDiagnosis { + return cache.tracker.getTrackerDiagnosis() +} + +func (cache *TxCache) diagnoseTransactions() { + if logDiagnoseTransactions.GetLevel() > logger.LogTrace { + return + } + + transactions := cache.getAllTransactions() + if len(transactions) == 0 { + return + } + + numToDisplay := core.MinInt(diagnosisMaxTransactionsToDisplay, len(transactions)) + logDiagnoseTransactions.Trace("diagnoseTransactions", "numTransactions", len(transactions), "numToDisplay", numToDisplay) + logDiagnoseTransactions.Trace(marshalTransactionsToNewlineDelimitedJSON(transactions[:numToDisplay], "diagnoseTransactions")) +} + +// marshalTransactionsToNewlineDelimitedJSON converts a list of transactions to a newline-delimited JSON string. +// Note: each line is indexed, to improve readability. The index is easily removable if separate analysis is needed. +func marshalTransactionsToNewlineDelimitedJSON(transactions []*WrappedTransaction, linePrefix string) string { + builder := strings.Builder{} + builder.WriteString("\n") + + for i, wrappedTx := range transactions { + printedTx := convertWrappedTransactionToPrintedTransaction(wrappedTx) + printedTxJSON, _ := json.Marshal(printedTx) + + builder.WriteString(fmt.Sprintf("%s#%d: ", linePrefix, i)) + builder.WriteString(string(printedTxJSON)) + builder.WriteString("\n") + } + + builder.WriteString("\n") + return builder.String() +} + +func convertWrappedTransactionToPrintedTransaction(wrappedTx *WrappedTransaction) *printedTransaction { + transaction := wrappedTx.Tx + + return &printedTransaction{ + Hash: hex.EncodeToString(wrappedTx.TxHash), + Nonce: transaction.GetNonce(), + Receiver: hex.EncodeToString(transaction.GetRcvAddr()), + Sender: hex.EncodeToString(transaction.GetSndAddr()), + GasPrice: transaction.GetGasPrice(), + GasLimit: transaction.GetGasLimit(), + DataLength: len(transaction.GetData()), + PPU: wrappedTx.PricePerUnit, + } +} + +// marshalTrackedBlockToNewlineDelimitedJSON converts a list of tracked blocks to a newline-delimited JSON string. +// Note: each line is indexed, to improve readability. The index is easily removable if separate analysis is needed. +func marshalTrackedBlockToNewlineDelimitedJSON(trackedBlocks map[string]*trackedBlock, linePrefix string) string { + builder := strings.Builder{} + builder.WriteString("\n") + + i := 0 + for _, block := range trackedBlocks { + printedBlock := convertTrackedBlockToPrintedBlock(block) + printedBlockJSON, _ := json.Marshal(printedBlock) + + builder.WriteString(fmt.Sprintf("%s#%d: ", linePrefix, i)) + builder.WriteString(string(printedBlockJSON)) + builder.WriteString("\n") + + i += 1 + } + + builder.WriteString("\n") + return builder.String() +} + +func convertTrackedBlockToPrintedBlock(block *trackedBlock) *printedTrackedBlock { + return &printedTrackedBlock{ + Hash: hex.EncodeToString(block.hash), + PreviousHash: hex.EncodeToString(block.prevHash), + Nonce: block.nonce, + } +} + +func displaySelectionOutcome(contextualLogger logger.Logger, linePrefix string, transactions []*WrappedTransaction) { + if contextualLogger.GetLevel() > logger.LogTrace { + return + } + + if len(transactions) > 0 { + contextualLogger.Trace("displaySelectionOutcome - transactions (as newline-separated JSON):") + contextualLogger.Trace(marshalTransactionsToNewlineDelimitedJSON(transactions, linePrefix)) + } else { + contextualLogger.Trace("displaySelectionOutcome - transactions: none") + } +} diff --git a/txcache/disabledCache.go b/txcache/disabledCache.go new file mode 100644 index 00000000000..874cbb0d5f2 --- /dev/null +++ b/txcache/disabledCache.go @@ -0,0 +1,162 @@ +package txcache + +import ( + "time" + + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-storage-go/types" + + "github.com/multiversx/mx-chain-go/common" +) + +var _ types.Cacher = (*DisabledCache)(nil) + +// DisabledCache represents a disabled cache +type DisabledCache struct { +} + +// NewDisabledCache creates a new disabled cache +func NewDisabledCache() *DisabledCache { + return &DisabledCache{} +} + +// AddTx does nothing +func (cache *DisabledCache) AddTx(_ *WrappedTransaction) (ok bool, added bool) { + return false, false +} + +// GetByTxHash returns no transaction +func (cache *DisabledCache) GetByTxHash(_ []byte) (*WrappedTransaction, bool) { + return nil, false +} + +// SelectTransactions returns an empty slice +func (cache *DisabledCache) SelectTransactions(uint64, int) ([]*WrappedTransaction, uint64) { + return make([]*WrappedTransaction, 0), 0 +} + +// RemoveTxByHash does nothing +func (cache *DisabledCache) RemoveTxByHash(_ []byte) bool { + return false +} + +// Len returns zero +func (cache *DisabledCache) Len() int { + return 0 +} + +// SizeInBytesContained returns 0 +func (cache *DisabledCache) SizeInBytesContained() uint64 { + return 0 +} + +// NumBytes returns zero +func (cache *DisabledCache) NumBytes() int { + return 0 +} + +// ForEachTransaction does nothing +func (cache *DisabledCache) ForEachTransaction(_ ForEachTransaction) { +} + +// Clear does nothing +func (cache *DisabledCache) Clear() { +} + +// Put does nothing +func (cache *DisabledCache) Put(_ []byte, _ interface{}, _ int) (evicted bool) { + return false +} + +// Get returns no transaction +func (cache *DisabledCache) Get(_ []byte) (value interface{}, ok bool) { + return nil, false +} + +// Has returns false +func (cache *DisabledCache) Has(_ []byte) bool { + return false +} + +// Peek returns no transaction +func (cache *DisabledCache) Peek(_ []byte) (value interface{}, ok bool) { + return nil, false +} + +// HasOrAdd returns false, does nothing +func (cache *DisabledCache) HasOrAdd(_ []byte, _ interface{}, _ int) (has, added bool) { + return false, false +} + +// Remove does nothing +func (cache *DisabledCache) Remove(_ []byte) { +} + +// Keys returns an empty slice +func (cache *DisabledCache) Keys() [][]byte { + return make([][]byte, 0) +} + +// MaxSize returns zero +func (cache *DisabledCache) MaxSize() int { + return 0 +} + +// RegisterHandler does nothing +func (cache *DisabledCache) RegisterHandler(func(key []byte, value interface{}), string) { +} + +// UnRegisterHandler does nothing +func (cache *DisabledCache) UnRegisterHandler(string) { +} + +// ImmunizeTxsAgainstEviction does nothing +func (cache *DisabledCache) ImmunizeTxsAgainstEviction(_ [][]byte) { +} + +// Diagnose does nothing +func (cache *DisabledCache) Diagnose(_ bool) { +} + +// GetTrackerDiagnosis returns nil +func (cache *DisabledCache) GetTrackerDiagnosis() TrackerDiagnosis { + return nil +} + +// GetTransactionsPoolForSender returns an empty slice +func (cache *DisabledCache) GetTransactionsPoolForSender(_ string) []*WrappedTransaction { + return make([]*WrappedTransaction, 0) +} + +// Close does nothing +func (cache *DisabledCache) Close() error { + return nil +} + +// OnProposedBlock does nothing +func (cache *DisabledCache) OnProposedBlock(_ []byte, _ data.BodyHandler, _ data.HeaderHandler, _ common.AccountNonceAndBalanceProvider, _ []byte) error { + return nil +} + +// OnBackfilledBlock does nothing +func (cache *DisabledCache) OnBackfilledBlock(_ []byte, _ data.BodyHandler, _ data.HeaderHandler) error { + return nil +} + +// OnExecutedBlock does nothing +func (cache *DisabledCache) OnExecutedBlock(_ data.HeaderHandler, _ []byte) error { + return nil +} + +// ResetTracker does nothing +func (cache *DisabledCache) ResetTracker() {} + +// Cleanup does nothing +func (cache *DisabledCache) Cleanup(_ common.AccountNonceProvider, _ uint64, _ int, _ time.Duration) uint64 { + return 0 +} + +// IsInterfaceNil returns true if there is no value under the interface +func (cache *DisabledCache) IsInterfaceNil() bool { + return cache == nil +} diff --git a/txcache/disabledCache_test.go b/txcache/disabledCache_test.go new file mode 100644 index 00000000000..0404371be71 --- /dev/null +++ b/txcache/disabledCache_test.go @@ -0,0 +1,77 @@ +package txcache + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDisabledCache_DoesNothing(t *testing.T) { + cache := NewDisabledCache() + + ok, added := cache.AddTx(nil) + require.False(t, ok) + require.False(t, added) + + tx, ok := cache.GetByTxHash([]byte{}) + require.Nil(t, tx) + require.False(t, ok) + + selection, accumulatedGas := cache.SelectTransactions(42, 42) + require.Equal(t, 0, len(selection)) + require.Equal(t, uint64(0), accumulatedGas) + + removed := cache.RemoveTxByHash([]byte{}) + require.False(t, removed) + + length := cache.Len() + require.Equal(t, 0, length) + + require.NotPanics(t, func() { cache.ForEachTransaction(func(_ []byte, _ *WrappedTransaction) {}) }) + + txs := cache.GetTransactionsPoolForSender("") + require.Equal(t, make([]*WrappedTransaction, 0), txs) + + cache.Clear() + + evicted := cache.Put(nil, nil, 0) + require.False(t, evicted) + + value, ok := cache.Get([]byte{}) + require.Nil(t, value) + require.False(t, ok) + + value, ok = cache.Peek([]byte{}) + require.Nil(t, value) + require.False(t, ok) + + has := cache.Has([]byte{}) + require.False(t, has) + + has, added = cache.HasOrAdd([]byte{}, nil, 0) + require.False(t, has) + require.False(t, added) + + cache.Remove([]byte{}) + + keys := cache.Keys() + require.Equal(t, 0, len(keys)) + + maxSize := cache.MaxSize() + require.Equal(t, 0, maxSize) + + require.NotPanics(t, func() { cache.RegisterHandler(func(_ []byte, _ interface{}) {}, "") }) + require.False(t, cache.IsInterfaceNil()) + + err := cache.Close() + require.Nil(t, err) + + err = cache.OnProposedBlock(nil, nil, nil, nil, nil) + require.Nil(t, err) + + err = cache.OnExecutedBlock(nil, nil) + require.Nil(t, err) + + diagnosis := cache.GetTrackerDiagnosis() + require.Nil(t, diagnosis) +} diff --git a/txcache/errors.go b/txcache/errors.go new file mode 100644 index 00000000000..455c50c77db --- /dev/null +++ b/txcache/errors.go @@ -0,0 +1,33 @@ +package txcache + +import ( + "errors" +) + +var errNilMempoolHost = errors.New("nil mempool host") +var errNilSelectionSession = errors.New("nil selection session") +var errNilAccountNonceAndBalanceProvider = errors.New("nil account nonce and balance provider") +var errEmptyBunchOfTransactions = errors.New("empty bunch of transactions") +var errNilBlockBody = errors.New("nil block body") +var errNilBlockHeader = errors.New("nil block header") +var errNilBlockHash = errors.New("nil block hash") +var errBlockNotFound = errors.New("block not found") +var errDiscontinuousSequenceOfBlocks = errors.New("discontinuous sequence of blocks") +var errNilTxCache = errors.New("nil tx cache") +var errNotFoundTx = errors.New("tx not found") +var errDiscontinuousBreadcrumbs = errors.New("discontinuous breadcrumbs") +var errNegativeBalanceForBreadcrumb = errors.New("breadcrumb should not have negative balance") +var errLastNonceNotFound = errors.New("last nonce not found") +var errNonceGap = errors.New("nonce gap") +var errExceededBalance = errors.New("exceeded balance") +var errNonceNotSet = errors.New("nonce not set") +var errReceivedLastNonceNotSet = errors.New("received last nonce not set") +var errInvalidMaxTrackedBlocks = errors.New("bad max tracked blocks") +var errBadBlockWhileMaxTrackedBlocksReached = errors.New("bad block received while max tracked blocks is reached") +var errNilBalance = errors.New("nil balance") +var errGlobalBreadcrumbDoesNotExist = errors.New("global breadcrumb does not exist") +var errRootHashMismatch = errors.New("root hash mismatch") +var errWrongTypeAssertion = errors.New("wrong type assertion") +var errToManyUniqueAccountsInBlock = errors.New("too many unique accounts in block") +var errSimulateSelectionContextInvalid = errors.New("simulate selection context invalid") +var errNilTrackedBlock = errors.New("nil tracked block") diff --git a/txcache/eviction.go b/txcache/eviction.go new file mode 100644 index 00000000000..746c95085b9 --- /dev/null +++ b/txcache/eviction.go @@ -0,0 +1,171 @@ +package txcache + +import ( + "container/heap" + + "github.com/multiversx/mx-chain-core-go/core" +) + +// evictionJournal keeps a short journal about the eviction process +// This is useful for debugging and reasoning about the eviction +type evictionJournal struct { + numEvicted int + numEvictedByPass []int +} + +// doEviction does cache eviction. +// We do not allow more evictions to start concurrently. +func (cache *TxCache) doEviction() *evictionJournal { + if cache.isEvictionInProgress.IsSet() { + return nil + } + + if !cache.isCapacityExceeded() { + return nil + } + + cache.evictionMutex.Lock() + defer cache.evictionMutex.Unlock() + + _ = cache.isEvictionInProgress.SetReturningPrevious() + defer cache.isEvictionInProgress.Reset() + + if !cache.isCapacityExceeded() { + return nil + } + + logRemove.Debug("doEviction: before eviction", + "num bytes", cache.NumBytes(), + "num txs", cache.CountTx(), + "num senders", cache.CountSenders(), + ) + + stopWatch := core.NewStopWatch() + stopWatch.Start("eviction") + + evictionJournal := cache.evictLeastLikelyToSelectTransactions() + + stopWatch.Stop("eviction") + + logRemove.Debug( + "doEviction: after eviction", + "num bytes", cache.NumBytes(), + "num now", cache.CountTx(), + "num senders", cache.CountSenders(), + "duration", stopWatch.GetMeasurement("eviction"), + "evicted txs", evictionJournal.numEvicted, + ) + + return evictionJournal +} + +func (cache *TxCache) isCapacityExceeded() bool { + exceeded := cache.areThereTooManyBytes() || cache.areThereTooManySenders() || cache.areThereTooManyTxs() + return exceeded +} + +func (cache *TxCache) areThereTooManyBytes() bool { + numBytes := cache.NumBytes() + tooManyBytes := numBytes > int(cache.config.NumBytesThreshold) + return tooManyBytes +} + +func (cache *TxCache) areThereTooManySenders() bool { + numSenders := cache.CountSenders() + tooManySenders := numSenders > uint64(cache.config.CountThreshold) + return tooManySenders +} + +func (cache *TxCache) areThereTooManyTxs() bool { + numTxs := cache.CountTx() + tooManyTxs := numTxs > uint64(cache.config.CountThreshold) + return tooManyTxs +} + +// Eviction tolerates concurrent transaction additions / removals. +func (cache *TxCache) evictLeastLikelyToSelectTransactions() *evictionJournal { + senders := cache.getSenders() + bunches := make([]bunchOfTransactions, 0, len(senders)) + + for _, sender := range senders { + // Include transactions after gaps, as well (important), unlike when selecting transactions for processing. + // Reverse the order of transactions (will come in handy later, when creating the min-heap). + bunch := sender.getTxsReversed() + bunches = append(bunches, bunch) + } + + journal := &evictionJournal{} + + // Heap is reused among passes. + // Items popped from the heap are added to "transactionsToEvict" (slice is re-created in each pass). + transactionsHeap := newMinTransactionsHeap(len(bunches)) + heap.Init(transactionsHeap) + + // Initialize the heap with the first transaction of each bunch + for _, bunch := range bunches { + item, err := newTransactionsHeapItem(bunch) + if err != nil { + continue + } + + // Items will be reused (see below). Each sender gets one (and only one) item in the heap. + heap.Push(transactionsHeap, item) + } + + for pass := 0; cache.isCapacityExceeded(); pass++ { + transactionsToEvict := make(bunchOfTransactions, 0, cache.config.NumItemsToPreemptivelyEvict) + transactionsToEvictHashes := make([][]byte, 0, cache.config.NumItemsToPreemptivelyEvict) + + // Select transactions (sorted). + for transactionsHeap.Len() > 0 { + // Always pick the "worst" transaction. + item := heap.Pop(transactionsHeap).(*transactionsHeapItem) + + if len(transactionsToEvict) >= int(cache.config.NumItemsToPreemptivelyEvict) { + // We have enough transactions to evict in this pass. + break + } + + if !cache.tracker.IsTransactionTracked(item.currentTransaction) { + transactionsToEvict = append(transactionsToEvict, item.currentTransaction) + transactionsToEvictHashes = append(transactionsToEvictHashes, item.currentTransaction.TxHash) + } + + // If there are more transactions in the same bunch (same sender as the popped item), + // add the next one to the heap (to compete with the others in being "the worst"). + // Item is reused (same originating sender), pushed back on the heap. + if item.gotoNextTransaction() { + heap.Push(transactionsHeap, item) + } + } + + if len(transactionsToEvict) == 0 { + // No more transactions to evict. + break + } + + // For each sender, find the "lowest" (in nonce) transaction to evict, + // so that we can remove all transactions with higher or equal nonces (of a sender) in one go (see below). + lowestToEvictBySender := make(map[string]uint64) + + for _, tx := range transactionsToEvict { + sender := string(tx.Tx.GetSndAddr()) + lowestToEvictBySender[sender] = tx.Tx.GetNonce() + } + + // Remove those transactions from "txListBySender". + for sender, nonce := range lowestToEvictBySender { + cache.txListBySender.removeTransactionsWithHigherOrEqualNonce([]byte(sender), nonce) + } + + // Remove those transactions from "txByHash". + _ = cache.txByHash.RemoveTxsBulk(transactionsToEvictHashes) + + journal.numEvictedByPass = append(journal.numEvictedByPass, len(transactionsToEvict)) + journal.numEvicted += len(transactionsToEvict) + + logRemove.Debug("evictLeastLikelyToSelectTransactions", "pass", pass, "num evicted", len(transactionsToEvict)) + } + + return journal +} diff --git a/txcache/eviction_test.go b/txcache/eviction_test.go new file mode 100644 index 00000000000..00d7ea447dc --- /dev/null +++ b/txcache/eviction_test.go @@ -0,0 +1,300 @@ +package txcache + +import ( + "fmt" + "math" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/testscommon/txcachemocks" + "github.com/stretchr/testify/require" +) + +// maxNumBytesPerSenderUpperBoundTest is used for setting the MaxNumBytesPerSenderUpperBoundTest from ConfigSourceMe in tests +const maxNumBytesPerSenderUpperBoundTest = 33_554_432 // 32 MB +const maxTrackedBlocks = 100 + +func TestTxCache_DoEviction_BecauseOfCount(t *testing.T) { + config := ConfigSourceMe{ + Name: "untitled", + NumChunks: 16, + NumBytesThreshold: maxNumBytesUpperBound, + NumBytesPerSenderThreshold: maxNumBytesPerSenderUpperBoundTest, + CountThreshold: 4, + CountPerSenderThreshold: math.MaxUint32, + EvictionEnabled: true, + NumItemsToPreemptivelyEvict: 1, + TxCacheBoundsConfig: createMockTxBoundsConfig(), + } + + host := txcachemocks.NewMempoolHostMock() + + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + require.NotNil(t, cache) + + cache.AddTx(createTx([]byte("hash-alice"), "alice", 1).withGasPrice(1 * oneBillion)) + cache.AddTx(createTx([]byte("hash-bob"), "bob", 1).withGasPrice(2 * oneBillion)) + cache.AddTx(createTx([]byte("hash-carol"), "carol", 1).withGasPrice(3 * oneBillion)) + cache.AddTx(createTx([]byte("hash-eve"), "eve", 1).withGasPrice(4 * oneBillion)) + cache.AddTx(createTx([]byte("hash-dan"), "dan", 1).withGasPrice(5 * oneBillion)) + + journal := cache.doEviction() + require.Equal(t, 1, journal.numEvicted) + require.Equal(t, []int{1}, journal.numEvictedByPass) + + // Alice and Bob evicted. Carol still there (better score). + _, ok := cache.GetByTxHash([]byte("hash-carol")) + require.True(t, ok) + require.Equal(t, uint64(4), cache.CountSenders()) + require.Equal(t, uint64(4), cache.CountTx()) +} + +func TestTxCache_DoEviction_BecauseOfSize(t *testing.T) { + config := ConfigSourceMe{ + Name: "untitled", + NumChunks: 16, + NumBytesThreshold: 1000, + NumBytesPerSenderThreshold: maxNumBytesPerSenderUpperBoundTest, + CountThreshold: math.MaxUint32, + CountPerSenderThreshold: math.MaxUint32, + EvictionEnabled: true, + NumItemsToPreemptivelyEvict: 1, + TxCacheBoundsConfig: createMockTxBoundsConfig(), + } + + host := txcachemocks.NewMempoolHostMock() + + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + require.NotNil(t, cache) + + cache.AddTx(createTx([]byte("hash-alice"), "alice", 1).withSize(256).withGasLimit(500000)) + cache.AddTx(createTx([]byte("hash-bob"), "bob", 1).withSize(256).withGasLimit(500000)) + cache.AddTx(createTx([]byte("hash-carol"), "carol", 1).withSize(256).withGasLimit(500000).withGasPrice(1.5 * oneBillion)) + cache.AddTx(createTx([]byte("hash-eve"), "eve", 1).withSize(256).withGasLimit(500000).withGasPrice(3 * oneBillion)) + + journal := cache.doEviction() + require.Equal(t, 1, journal.numEvicted) + require.Equal(t, []int{1}, journal.numEvictedByPass) + + // Alice and Bob evicted (lower score). Carol and Eve still there. + _, ok := cache.GetByTxHash([]byte("hash-carol")) + require.True(t, ok) + _, ok = cache.GetByTxHash([]byte("hash-eve")) + require.True(t, ok) + require.Equal(t, uint64(3), cache.CountSenders()) + require.Equal(t, uint64(3), cache.CountTx()) +} + +func TestTxCache_DoEviction_WithTrackedTxs(t *testing.T) { + config := ConfigSourceMe{ + Name: "untitled", + NumChunks: 16, + NumBytesThreshold: maxNumBytesUpperBound, + NumBytesPerSenderThreshold: maxNumBytesPerSenderUpperBoundTest, + CountThreshold: 4, + CountPerSenderThreshold: math.MaxUint32, + EvictionEnabled: true, + NumItemsToPreemptivelyEvict: 1, + TxCacheBoundsConfig: createMockTxBoundsConfig(), + } + + host := txcachemocks.NewMempoolHostMock() + + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + require.NotNil(t, cache) + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMock() + accountsProvider.SetNonce([]byte("alice"), 1) + accountsProvider.SetNonce([]byte("bob"), 1) + accountsProvider.SetNonce([]byte("carol"), 1) + accountsProvider.SetNonce([]byte("eve"), 1) + accountsProvider.SetNonce([]byte("dan"), 1) + + cache.AddTx(createTx([]byte("hash-alice"), "alice", 1).withGasPrice(1 * oneBillion)) + cache.AddTx(createTx([]byte("hash-bob"), "bob", 1).withGasPrice(2 * oneBillion)) + cache.AddTx(createTx([]byte("hash-carol"), "carol", 1).withGasPrice(3 * oneBillion)) + cache.AddTx(createTx([]byte("hash-eve"), "eve", 1).withGasPrice(4 * oneBillion)) + cache.AddTx(createTx([]byte("hash-dan"), "dan", 1).withGasPrice(5 * oneBillion)) + + // propose those txs + err = cache.OnProposedBlock( + []byte("blockHash1"), + &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("hash-alice"), + []byte("hash-bob"), + []byte("hash-carol"), + []byte("hash-dan"), + []byte("hash-eve"), + }, + }, + }, + }, + &block.Header{ + Nonce: 1, + PrevHash: []byte("blockHash0"), + }, + accountsProvider, + []byte("blockHash0"), + ) + require.Nil(t, err) + + // Because all txs are tracked, nothing is evicted. + journal := cache.doEviction() + require.Equal(t, 0, journal.numEvicted) + require.Nil(t, journal.numEvictedByPass) + require.True(t, cache.areInternalMapsConsistent()) +} + +func TestTxCache_DoEviction_DoesNothingWhenAlreadyInProgress(t *testing.T) { + config := ConfigSourceMe{ + Name: "untitled", + NumChunks: 1, + NumBytesThreshold: maxNumBytesUpperBound, + NumBytesPerSenderThreshold: maxNumBytesPerSenderUpperBoundTest, + CountThreshold: 4, + CountPerSenderThreshold: math.MaxUint32, + EvictionEnabled: true, + NumItemsToPreemptivelyEvict: 1, + TxCacheBoundsConfig: createMockTxBoundsConfig(), + } + + host := txcachemocks.NewMempoolHostMock() + + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + require.NotNil(t, cache) + + _ = cache.isEvictionInProgress.SetReturningPrevious() + + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", uint64(1))) + cache.AddTx(createTx([]byte("hash-alice-2"), "alice", uint64(2))) + cache.AddTx(createTx([]byte("hash-alice-3"), "alice", uint64(3))) + cache.AddTx(createTx([]byte("hash-alice-4"), "alice", uint64(4))) + cache.AddTx(createTx([]byte("hash-alice-5"), "alice", uint64(5))) + + // Nothing is evicted because eviction is already in progress. + journal := cache.doEviction() + require.Nil(t, journal) + require.Equal(t, uint64(5), cache.CountTx()) + + cache.isEvictionInProgress.Reset() + + // Now eviction can happen. + journal = cache.doEviction() + require.NotNil(t, journal) + require.Equal(t, 1, journal.numEvicted) + require.Equal(t, 4, int(cache.CountTx())) +} + +func TestBenchmarkTxCache_DoEviction(t *testing.T) { + config := ConfigSourceMe{ + Name: "untitled", + NumChunks: 16, + NumBytesThreshold: 1000000000, + NumBytesPerSenderThreshold: maxNumBytesPerSenderUpperBoundTest, + CountThreshold: 300000, + CountPerSenderThreshold: math.MaxUint32, + NumItemsToPreemptivelyEvict: 50000, + TxCacheBoundsConfig: createMockTxBoundsConfig(), + } + + host := txcachemocks.NewMempoolHostMock() + + sw := core.NewStopWatch() + + t.Run("numSenders = 35000, numTransactions = 10", func(t *testing.T) { + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + + cache.config.EvictionEnabled = false + addManyTransactionsWithUniformDistribution(cache, 35000, 10) + cache.config.EvictionEnabled = true + + require.Equal(t, uint64(350000), cache.CountTx()) + + sw.Start(t.Name()) + journal := cache.doEviction() + sw.Stop(t.Name()) + + require.Equal(t, 50000, journal.numEvicted) + require.Equal(t, 1, len(journal.numEvictedByPass)) + }) + + t.Run("numSenders = 100000, numTransactions = 5", func(t *testing.T) { + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + + cache.config.EvictionEnabled = false + addManyTransactionsWithUniformDistribution(cache, 100000, 5) + cache.config.EvictionEnabled = true + + require.Equal(t, uint64(500000), cache.CountTx()) + + sw.Start(t.Name()) + journal := cache.doEviction() + sw.Stop(t.Name()) + + require.Equal(t, 200000, journal.numEvicted) + require.Equal(t, 4, len(journal.numEvictedByPass)) + }) + + t.Run("numSenders = 400000, numTransactions = 1", func(t *testing.T) { + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + + cache.config.EvictionEnabled = false + addManyTransactionsWithUniformDistribution(cache, 400000, 1) + cache.config.EvictionEnabled = true + + require.Equal(t, uint64(400000), cache.CountTx()) + + sw.Start(t.Name()) + journal := cache.doEviction() + sw.Stop(t.Name()) + + require.Equal(t, 100000, journal.numEvicted) + require.Equal(t, 2, len(journal.numEvictedByPass)) + }) + + t.Run("numSenders = 10000, numTransactions = 100", func(t *testing.T) { + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + + cache.config.EvictionEnabled = false + addManyTransactionsWithUniformDistribution(cache, 10000, 100) + cache.config.EvictionEnabled = true + + require.Equal(t, uint64(1000000), cache.CountTx()) + + sw.Start(t.Name()) + journal := cache.doEviction() + sw.Stop(t.Name()) + + require.Equal(t, 700000, journal.numEvicted) + require.Equal(t, 14, len(journal.numEvictedByPass)) + }) + + for name, measurement := range sw.GetMeasurementsMap() { + fmt.Printf("%fs (%s)\n", measurement, name) + } + + // (1) + // Vendor ID: GenuineIntel + // Model name: 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz + // CPU family: 6 + // Model: 140 + // Thread(s) per core: 2 + // Core(s) per socket: 4 + // + // 0.092625s (TestBenchmarkTxCache_DoEviction/numSenders_=_35000,_numTransactions_=_10) + // 0.426718s (TestBenchmarkTxCache_DoEviction/numSenders_=_100000,_numTransactions_=_5) + // 0.546757s (TestBenchmarkTxCache_DoEviction/numSenders_=_10000,_numTransactions_=_100) + // 0.542678s (TestBenchmarkTxCache_DoEviction/numSenders_=_400000,_numTransactions_=_1) +} diff --git a/txcache/globalAccountBreadcrumb.go b/txcache/globalAccountBreadcrumb.go new file mode 100644 index 00000000000..768f7e4f4af --- /dev/null +++ b/txcache/globalAccountBreadcrumb.go @@ -0,0 +1,180 @@ +package txcache + +import ( + "math" + "math/big" + + "github.com/multiversx/mx-chain-core-go/core" +) + +// globalAccountBreadcrumb will be used for the elements stored in the globalAccountBreadcrumbs from the SelectionTracker. +// A globalAccountBreadcrumb should be affected each time a tracked block is added or removed. +type globalAccountBreadcrumb struct { + firstNonce core.OptionalUint64 + lastNonce core.OptionalUint64 + consumedBalance *big.Int +} + +// newGlobalAccountBreadcrumb creates a new global account breadcrumb +func newGlobalAccountBreadcrumb() *globalAccountBreadcrumb { + gab := &globalAccountBreadcrumb{ + consumedBalance: big.NewInt(0), + } + gab.resetNonces() + + return gab +} + +// resetNonces sets a global account breadcrumb for a fee payer +func (gab *globalAccountBreadcrumb) resetNonces() { + gab.firstNonce = core.OptionalUint64{ + Value: math.MaxUint64, + HasValue: false, + } + gab.lastNonce = core.OptionalUint64{ + Value: 0, + HasValue: false, + } +} + +// updateOnAddedBreadcrumb updates a global breadcrumb when a tracked block is added, +// but should be used only after the validation of the proposed block passed. +func (gab *globalAccountBreadcrumb) updateOnAddedBreadcrumb(receivedBreadcrumb *accountBreadcrumb) { + gab.extendConsumedBalance(receivedBreadcrumb) + + // when a tracked block is added, we should only extend the nonce range of the global account breadcrumb + if receivedBreadcrumb.hasUnknownNonce() { + // we do not update with nonce info from breadcrumb of relayer + return + } + + // check if it is the first time as sender + if !gab.firstNonce.HasValue { + gab.firstNonce = receivedBreadcrumb.firstNonce + } + + gab.extendRightNonceRange(receivedBreadcrumb) +} + +// extendRightNonceRange extends the nonce range by maximizing the last nonce +func (gab *globalAccountBreadcrumb) extendRightNonceRange(receivedBreadcrumb *accountBreadcrumb) { + gab.lastNonce.Value = max(gab.lastNonce.Value, receivedBreadcrumb.lastNonce.Value) + gab.lastNonce.HasValue = true +} + +// updateOnRemovedBreadcrumbWithSameNonceOrBelow updates the global account breadcrumb when a block is removed on the OnExecutedBlock notification +func (gab *globalAccountBreadcrumb) updateOnRemovedBreadcrumbWithSameNonceOrBelow(receivedBreadcrumb *accountBreadcrumb) (bool, error) { + err := gab.reduceConsumedBalance(receivedBreadcrumb) + if err != nil { + return false, err + } + + hasSameLastNonce := gab.lastNonce == receivedBreadcrumb.lastNonce + + // if our global breadcrumb has same last nonce with the received one it means we can mark it as a fee payer + if gab.isUser() && hasSameLastNonce { + gab.resetNonces() + } + + if gab.isUser() { + if receivedBreadcrumb.hasUnknownNonce() { + // should not update with nonce info from breadcrumb of relayer + return false, nil + } + + gab.reduceLeftNonceRange(receivedBreadcrumb) + } + + return gab.canBeDeleted(), nil +} + +// reduceLeftNonceRange reduces the left range by maximizing the first nonce +func (gab *globalAccountBreadcrumb) reduceLeftNonceRange(receivedBreadcrumb *accountBreadcrumb) { + gab.firstNonce.Value = max(gab.firstNonce.Value, receivedBreadcrumb.lastNonce.Value+1) + gab.firstNonce.HasValue = true +} + +// updateOnRemoveBreadcrumbWithSameNonceOrAbove updates the global account breadcrumb when a block is removed on the OnProposedBlock notification, +// but should be used only after the validation of the proposed block passed. +func (gab *globalAccountBreadcrumb) updateOnRemoveBreadcrumbWithSameNonceOrAbove(receivedBreadcrumb *accountBreadcrumb) (bool, error) { + err := gab.reduceConsumedBalance(receivedBreadcrumb) + if err != nil { + return false, err + } + + hasSameFirstNonce := gab.firstNonce == receivedBreadcrumb.firstNonce + + // if our global breadcrumb has same first nonce with the received one it means we can mark it as a fee payer + if gab.isUser() && hasSameFirstNonce { + gab.resetNonces() + } + + if gab.isUser() { + if receivedBreadcrumb.hasUnknownNonce() { + // should not update with nonce info from breadcrumb of relayer + return false, nil + } + + gab.reduceRightNonceRange(receivedBreadcrumb) + } + + return gab.canBeDeleted(), nil +} + +// reduceRightNonceRange reduces the nonce range by minimizing the last nonce +func (gab *globalAccountBreadcrumb) reduceRightNonceRange(receivedBreadcrumb *accountBreadcrumb) { + gab.lastNonce.Value = min(gab.lastNonce.Value, receivedBreadcrumb.firstNonce.Value-1) + gab.lastNonce.HasValue = true +} + +// reduceConsumedBalance reduces the consumed balance of the global account breadcrumb by subtracting the received balance +func (gab *globalAccountBreadcrumb) reduceConsumedBalance(receivedBreadcrumb *accountBreadcrumb) error { + _ = gab.consumedBalance.Sub(gab.consumedBalance, receivedBreadcrumb.consumedBalance) + if gab.consumedBalance.Sign() == -1 { + return errNegativeBalanceForBreadcrumb + } + + return nil +} + +// extendConsumedBalance accumulates the consumed balance of the global account breadcrumb by adding the received balance +func (gab *globalAccountBreadcrumb) extendConsumedBalance(receivedBreadcrumb *accountBreadcrumb) { + // the consumed balance has to be updated despite the type of the breadcrumb (relayer or sender) + _ = gab.consumedBalance.Add(gab.consumedBalance, receivedBreadcrumb.consumedBalance) +} + +func (gab *globalAccountBreadcrumb) isUser() bool { + return gab.firstNonce.HasValue || gab.lastNonce.HasValue +} + +func (gab *globalAccountBreadcrumb) canBeDeleted() bool { + hasConsumedBalance := gab.consumedBalance.Sign() == 1 + // it might be possible to delete the address of the breadcrumb from the global map, + // but only if it is a relayer breadcrumb and its consumed balance is equal to 0. + if !gab.isUser() && !hasConsumedBalance { + return true + } + + return false +} + +// isContinuousWithSessionNonce verifies if a global account breadcrumb is continuous with a given session nonce +func (gab *globalAccountBreadcrumb) isContinuousWithSessionNonce(sessionNonce uint64) bool { + if !gab.isUser() { + // a global account breadcrumb of a relayer it is continuous with the session nonce + return true + } + + return gab.firstNonce.Value == sessionNonce +} + +// getSnapshotOfGlobalBreadcrumb creates a deep copy of a global account breadcrumb +func (gab *globalAccountBreadcrumb) getSnapshotOfGlobalBreadcrumb() *globalAccountBreadcrumb { + gabCopy := newGlobalAccountBreadcrumb() + + gabCopy.firstNonce = gab.firstNonce + gabCopy.lastNonce = gab.lastNonce + gabCopy.consumedBalance = big.NewInt(0).Set(gab.consumedBalance) + + return gabCopy +} diff --git a/txcache/globalAccountBreadcrumb_test.go b/txcache/globalAccountBreadcrumb_test.go new file mode 100644 index 00000000000..bab2d568ce6 --- /dev/null +++ b/txcache/globalAccountBreadcrumb_test.go @@ -0,0 +1,706 @@ +package txcache + +import ( + "math" + "math/big" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/stretchr/testify/require" +) + +func Test_newGlobalAccountBreadcrumb(t *testing.T) { + t.Parallel() + + gab := newGlobalAccountBreadcrumb() + require.NotNil(t, gab) + require.Equal(t, big.NewInt(0), gab.consumedBalance) + + require.Equal(t, uint64(0), gab.lastNonce.Value) + require.False(t, gab.lastNonce.HasValue) + + require.Equal(t, uint64(math.MaxUint64), gab.firstNonce.Value) + require.False(t, gab.firstNonce.HasValue) +} + +func Test_updateOnAddedAccountBreadcrumb(t *testing.T) { + t.Parallel() + + t.Run("should work when each breadcrumb is a sender breadcrumb", func(t *testing.T) { + t.Parallel() + + accountBreadcrumbsForAlice := []*accountBreadcrumb{ + { + firstNonce: core.OptionalUint64{ + Value: 10, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 16, + HasValue: true, + }, + consumedBalance: big.NewInt(10), + }, + { + firstNonce: core.OptionalUint64{ + Value: 17, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 23, + HasValue: true, + }, + consumedBalance: big.NewInt(5), + }, + { + firstNonce: core.OptionalUint64{ + Value: 24, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 31, + HasValue: true, + }, + consumedBalance: big.NewInt(5), + }, + } + + gab := newGlobalAccountBreadcrumb() + for _, breadcrumb := range accountBreadcrumbsForAlice { + gab.updateOnAddedBreadcrumb(breadcrumb) + } + + require.Equal(t, big.NewInt(20), gab.consumedBalance) + + require.True(t, gab.firstNonce.HasValue) + require.Equal(t, uint64(10), gab.firstNonce.Value) + + require.True(t, gab.lastNonce.HasValue) + require.Equal(t, uint64(31), gab.lastNonce.Value) + }) + + t.Run("should work when some breadcrumbs are fee payer breadcrumbs", func(t *testing.T) { + t.Parallel() + + accountBreadcrumbsForAlice := []*accountBreadcrumb{ + { + firstNonce: core.OptionalUint64{ + Value: 0, + HasValue: false, + }, + lastNonce: core.OptionalUint64{ + Value: 0, + HasValue: false, + }, + consumedBalance: big.NewInt(10), + }, + { + firstNonce: core.OptionalUint64{ + Value: 0, + HasValue: false, + }, + lastNonce: core.OptionalUint64{ + Value: 0, + HasValue: false, + }, + consumedBalance: big.NewInt(10), + }, + { + firstNonce: core.OptionalUint64{ + Value: 17, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 23, + HasValue: true, + }, + consumedBalance: big.NewInt(5), + }, + { + firstNonce: core.OptionalUint64{ + Value: 0, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 0, + HasValue: true, + }, + consumedBalance: big.NewInt(5), + }, + } + + gab := newGlobalAccountBreadcrumb() + for _, breadcrumb := range accountBreadcrumbsForAlice { + gab.updateOnAddedBreadcrumb(breadcrumb) + } + + require.Equal(t, big.NewInt(30), gab.consumedBalance) + + require.True(t, gab.firstNonce.HasValue) + require.Equal(t, uint64(17), gab.firstNonce.Value) + + require.True(t, gab.lastNonce.HasValue) + require.Equal(t, uint64(23), gab.lastNonce.Value) + }) +} + +func Test_updateOnRemoveAccountBreadcrumbOnExecutedBlock(t *testing.T) { + t.Parallel() + + t.Run("should work when each breadcrumb is a sender breadcrumb", func(t *testing.T) { + t.Parallel() + + breadcrumb1 := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 10, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 16, + HasValue: true, + }, + consumedBalance: big.NewInt(10), + } + breadcrumb2 := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 17, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 23, + HasValue: true, + }, + consumedBalance: big.NewInt(5), + } + breadcrumb3 := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 24, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 31, + HasValue: true, + }, + consumedBalance: big.NewInt(5), + } + + accountBreadcrumbsForAlice := []*accountBreadcrumb{ + breadcrumb1, + breadcrumb2, + breadcrumb3, + } + + gab := newGlobalAccountBreadcrumb() + for _, breadcrumb := range accountBreadcrumbsForAlice { + gab.updateOnAddedBreadcrumb(breadcrumb) + } + + require.Equal(t, big.NewInt(20), gab.consumedBalance) + + require.True(t, gab.firstNonce.HasValue) + require.Equal(t, uint64(10), gab.firstNonce.Value) + + require.True(t, gab.lastNonce.HasValue) + require.Equal(t, uint64(31), gab.lastNonce.Value) + + shouldBeDeleted, err := gab.updateOnRemovedBreadcrumbWithSameNonceOrBelow(breadcrumb1) + require.NoError(t, err) + require.False(t, shouldBeDeleted) + + require.Equal(t, big.NewInt(10), gab.consumedBalance) + require.True(t, gab.firstNonce.HasValue) + require.Equal(t, uint64(17), gab.firstNonce.Value) + + shouldBeDeleted, err = gab.updateOnRemovedBreadcrumbWithSameNonceOrBelow(breadcrumb3) + require.NoError(t, err) + require.False(t, shouldBeDeleted) + + require.Equal(t, big.NewInt(5), gab.consumedBalance) + require.False(t, gab.firstNonce.HasValue) + require.Equal(t, uint64(math.MaxUint64), gab.firstNonce.Value) + require.Equal(t, uint64(0), gab.lastNonce.Value) + + shouldBeDeleted, err = gab.updateOnRemovedBreadcrumbWithSameNonceOrBelow(breadcrumb2) + require.NoError(t, err) + require.True(t, shouldBeDeleted) + require.Equal(t, uint64(math.MaxUint64), gab.firstNonce.Value) + require.Equal(t, uint64(0), gab.lastNonce.Value) + }) + + t.Run("should work when there are some fee payer breadcrumbs", func(t *testing.T) { + t.Parallel() + + breadcrumb0 := &accountBreadcrumb{ + consumedBalance: big.NewInt(10), + } + breadcrumb1 := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 10, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 16, + HasValue: true, + }, + consumedBalance: big.NewInt(10), + } + breadcrumb2 := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 17, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 23, + HasValue: true, + }, + consumedBalance: big.NewInt(5), + } + breadcrumb3 := &accountBreadcrumb{ + consumedBalance: big.NewInt(10), + } + breadcrumb4 := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 24, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 31, + HasValue: true, + }, + consumedBalance: big.NewInt(5), + } + + accountBreadcrumbsForAlice := []*accountBreadcrumb{ + breadcrumb0, + breadcrumb1, + breadcrumb2, + breadcrumb3, + breadcrumb4, + } + + gab := newGlobalAccountBreadcrumb() + for _, breadcrumb := range accountBreadcrumbsForAlice { + gab.updateOnAddedBreadcrumb(breadcrumb) + } + + require.Equal(t, big.NewInt(40), gab.consumedBalance) + + require.True(t, gab.firstNonce.HasValue) + require.Equal(t, uint64(10), gab.firstNonce.Value) + + require.True(t, gab.lastNonce.HasValue) + require.Equal(t, uint64(31), gab.lastNonce.Value) + + shouldBeDeleted, err := gab.updateOnRemovedBreadcrumbWithSameNonceOrBelow(breadcrumb0) + require.NoError(t, err) + require.False(t, shouldBeDeleted) + + require.Equal(t, big.NewInt(30), gab.consumedBalance) + require.True(t, gab.firstNonce.HasValue) + require.Equal(t, uint64(10), gab.firstNonce.Value) + require.Equal(t, uint64(31), gab.lastNonce.Value) + + shouldBeDeleted, err = gab.updateOnRemovedBreadcrumbWithSameNonceOrBelow(breadcrumb1) + require.NoError(t, err) + require.False(t, shouldBeDeleted) + + require.Equal(t, big.NewInt(20), gab.consumedBalance) + require.True(t, gab.firstNonce.HasValue) + require.Equal(t, uint64(17), gab.firstNonce.Value) + + shouldBeDeleted, err = gab.updateOnRemovedBreadcrumbWithSameNonceOrBelow(breadcrumb4) + require.NoError(t, err) + require.False(t, shouldBeDeleted) + + require.Equal(t, big.NewInt(15), gab.consumedBalance) + require.False(t, gab.firstNonce.HasValue) + require.Equal(t, uint64(math.MaxUint64), gab.firstNonce.Value) + require.Equal(t, uint64(0), gab.lastNonce.Value) + + shouldBeDeleted, err = gab.updateOnRemovedBreadcrumbWithSameNonceOrBelow(breadcrumb2) + require.NoError(t, err) + require.False(t, shouldBeDeleted) + + require.Equal(t, uint64(math.MaxUint64), gab.firstNonce.Value) + require.Equal(t, uint64(0), gab.lastNonce.Value) + + shouldBeDeleted, err = gab.updateOnRemovedBreadcrumbWithSameNonceOrBelow(breadcrumb3) + require.NoError(t, err) + require.True(t, shouldBeDeleted) + }) + + t.Run("should error", func(t *testing.T) { + t.Parallel() + + gabc := newGlobalAccountBreadcrumb() + breadcrumb := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 10, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 16, + HasValue: true, + }, + consumedBalance: big.NewInt(10), + } + + _, err := gabc.updateOnRemovedBreadcrumbWithSameNonceOrBelow(breadcrumb) + require.Equal(t, errNegativeBalanceForBreadcrumb, err) + }) +} + +func Test_updateOnRemoveAccountBreadcrumbOnProposedBlock(t *testing.T) { + t.Parallel() + + t.Run("should work in the following order: replacing and then deleting the ones with greater nonce", func(t *testing.T) { + t.Parallel() + + breadcrumb0 := &accountBreadcrumb{ + consumedBalance: big.NewInt(10), + } + breadcrumb1 := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 10, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 16, + HasValue: true, + }, + consumedBalance: big.NewInt(10), + } + breadcrumb2 := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 17, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 23, + HasValue: true, + }, + consumedBalance: big.NewInt(5), + } + breadcrumb3 := &accountBreadcrumb{ + consumedBalance: big.NewInt(10), + } + breadcrumb4 := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 24, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 31, + HasValue: true, + }, + consumedBalance: big.NewInt(5), + } + breadcrumb5 := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 32, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 40, + HasValue: true, + }, + consumedBalance: big.NewInt(10), + } + + accountBreadcrumbsForAlice := []*accountBreadcrumb{ + breadcrumb0, + breadcrumb1, + breadcrumb2, + breadcrumb3, + breadcrumb4, + breadcrumb5, + } + + gab := newGlobalAccountBreadcrumb() + for _, breadcrumb := range accountBreadcrumbsForAlice { + gab.updateOnAddedBreadcrumb(breadcrumb) + } + + require.Equal(t, big.NewInt(50), gab.consumedBalance) + require.Equal(t, uint64(10), gab.firstNonce.Value) + require.Equal(t, uint64(40), gab.lastNonce.Value) + + // now, remove breadcrumb corresponding to the block with nonce 4 + shouldDelete, err := gab.updateOnRemoveBreadcrumbWithSameNonceOrAbove(breadcrumb4) + require.NoError(t, err) + require.False(t, shouldDelete) + + require.Equal(t, big.NewInt(45), gab.consumedBalance) + require.True(t, gab.firstNonce.HasValue) + require.Equal(t, uint64(10), gab.firstNonce.Value) + require.True(t, gab.lastNonce.HasValue) + require.Equal(t, uint64(23), gab.lastNonce.Value) + + // now, remove the ones with greater nonce + shouldDelete, err = gab.updateOnRemoveBreadcrumbWithSameNonceOrAbove(breadcrumb5) + require.NoError(t, err) + require.False(t, shouldDelete) + + require.Equal(t, big.NewInt(35), gab.consumedBalance) + require.True(t, gab.firstNonce.HasValue) + require.Equal(t, uint64(10), gab.firstNonce.Value) + require.True(t, gab.lastNonce.HasValue) + require.Equal(t, uint64(23), gab.lastNonce.Value) + + // now, we create a new breadcrumb which will replace the one with nonce 4 (breadcrumb4) + breadcrumbToReplaceNonce4 := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 24, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 31, + HasValue: true, + }, + consumedBalance: big.NewInt(5), + } + gab.updateOnAddedBreadcrumb(breadcrumbToReplaceNonce4) + require.Equal(t, big.NewInt(40), gab.consumedBalance) + require.True(t, gab.firstNonce.HasValue) + require.Equal(t, uint64(10), gab.firstNonce.Value) + require.True(t, gab.lastNonce.HasValue) + require.Equal(t, uint64(31), gab.lastNonce.Value) + }) + + t.Run("should work without specific order", func(t *testing.T) { + t.Parallel() + + breadcrumb0 := &accountBreadcrumb{ + consumedBalance: big.NewInt(10), + } + breadcrumb1 := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 10, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 16, + HasValue: true, + }, + consumedBalance: big.NewInt(10), + } + breadcrumb2 := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 17, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 23, + HasValue: true, + }, + consumedBalance: big.NewInt(5), + } + breadcrumb3 := &accountBreadcrumb{ + consumedBalance: big.NewInt(10), + } + breadcrumb4 := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 24, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 31, + HasValue: true, + }, + consumedBalance: big.NewInt(5), + } + breadcrumb5 := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 32, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 40, + HasValue: true, + }, + consumedBalance: big.NewInt(10), + } + + accountBreadcrumbsForAlice := []*accountBreadcrumb{ + breadcrumb0, + breadcrumb1, + breadcrumb2, + breadcrumb3, + breadcrumb4, + breadcrumb5, + } + + gab := newGlobalAccountBreadcrumb() + for _, breadcrumb := range accountBreadcrumbsForAlice { + gab.updateOnAddedBreadcrumb(breadcrumb) + } + + require.Equal(t, big.NewInt(50), gab.consumedBalance) + require.Equal(t, uint64(10), gab.firstNonce.Value) + require.Equal(t, uint64(40), gab.lastNonce.Value) + + // in this scenario, we want to replace breadcrumb1 + + // remove breadcrumb with greater nonce + shouldDelete, err := gab.updateOnRemoveBreadcrumbWithSameNonceOrAbove(breadcrumb4) + require.NoError(t, err) + require.False(t, shouldDelete) + + require.Equal(t, big.NewInt(45), gab.consumedBalance) + require.True(t, gab.firstNonce.HasValue) + require.Equal(t, uint64(10), gab.firstNonce.Value) + require.True(t, gab.lastNonce.HasValue) + require.Equal(t, uint64(23), gab.lastNonce.Value) + + // remove breadcrumb with greater nonce + shouldDelete, err = gab.updateOnRemoveBreadcrumbWithSameNonceOrAbove(breadcrumb5) + require.NoError(t, err) + require.False(t, shouldDelete) + + require.Equal(t, big.NewInt(35), gab.consumedBalance) + require.True(t, gab.firstNonce.HasValue) + require.Equal(t, uint64(10), gab.firstNonce.Value) + require.True(t, gab.lastNonce.HasValue) + require.Equal(t, uint64(23), gab.lastNonce.Value) + + // now, remove the breadcrumb with nonce 1 + shouldDelete, err = gab.updateOnRemoveBreadcrumbWithSameNonceOrAbove(breadcrumb1) + require.NoError(t, err) + require.False(t, shouldDelete) + + require.Equal(t, big.NewInt(25), gab.consumedBalance) + require.False(t, gab.firstNonce.HasValue) + require.False(t, gab.lastNonce.HasValue) + + shouldDelete, err = gab.updateOnRemoveBreadcrumbWithSameNonceOrAbove(breadcrumb2) + require.NoError(t, err) + require.False(t, shouldDelete) + + require.Equal(t, big.NewInt(20), gab.consumedBalance) + require.False(t, gab.firstNonce.HasValue) + require.False(t, gab.lastNonce.HasValue) + + shouldDelete, err = gab.updateOnRemoveBreadcrumbWithSameNonceOrAbove(breadcrumb3) + require.NoError(t, err) + require.False(t, shouldDelete) + + require.Equal(t, big.NewInt(10), gab.consumedBalance) + require.False(t, gab.firstNonce.HasValue) + require.False(t, gab.lastNonce.HasValue) + + // now, if we would delete the breadcrumb 0, we should receive that the account can be deleted from the global map + shouldDelete, err = gab.updateOnRemoveBreadcrumbWithSameNonceOrAbove(breadcrumb0) + require.NoError(t, err) + require.True(t, shouldDelete) + }) + + t.Run("should not update with unknown nonces", func(t *testing.T) { + t.Parallel() + + gabc := newGlobalAccountBreadcrumb() + gabc.firstNonce = core.OptionalUint64{ + Value: 10, + HasValue: true, + } + gabc.lastNonce = core.OptionalUint64{ + Value: 12, + HasValue: true, + } + gabc.consumedBalance = big.NewInt(12) + + breadcrumb := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 0, + HasValue: false, + }, + lastNonce: core.OptionalUint64{ + Value: 0, + HasValue: false, + }, + consumedBalance: big.NewInt(10), + } + + shouldDelete, err := gabc.updateOnRemoveBreadcrumbWithSameNonceOrAbove(breadcrumb) + require.NoError(t, err) + require.False(t, shouldDelete) + }) + + t.Run("should error", func(t *testing.T) { + t.Parallel() + + gabc := newGlobalAccountBreadcrumb() + breadcrumb := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 10, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 16, + HasValue: true, + }, + consumedBalance: big.NewInt(10), + } + + _, err := gabc.updateOnRemoveBreadcrumbWithSameNonceOrAbove(breadcrumb) + require.Equal(t, errNegativeBalanceForBreadcrumb, err) + }) +} + +func Test_reduceConsumedBalance(t *testing.T) { + t.Parallel() + + t.Run("should fail", func(t *testing.T) { + t.Parallel() + + gab := newGlobalAccountBreadcrumb() + breadcrumb := accountBreadcrumb{ + consumedBalance: big.NewInt(10), + } + + err := gab.reduceConsumedBalance(&breadcrumb) + require.Equal(t, errNegativeBalanceForBreadcrumb, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + gab := newGlobalAccountBreadcrumb() + gab.consumedBalance = big.NewInt(10) + breadcrumb := accountBreadcrumb{ + consumedBalance: big.NewInt(5), + } + + err := gab.reduceConsumedBalance(&breadcrumb) + require.Nil(t, err) + + err = gab.reduceConsumedBalance(&breadcrumb) + require.Nil(t, err) + require.Equal(t, 0, gab.consumedBalance.Sign()) + + gab.extendConsumedBalance(&breadcrumb) + require.Equal(t, big.NewInt(5), gab.consumedBalance) + }) +} + +func Test_createCopy(t *testing.T) { + t.Parallel() + + t.Run("should fail", func(t *testing.T) { + t.Parallel() + + globalBreadcrumb := &globalAccountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 10, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 12, + HasValue: true, + }, + consumedBalance: big.NewInt(10), + } + + copyOfBreadcrumb := globalBreadcrumb.getSnapshotOfGlobalBreadcrumb() + require.Equal(t, globalBreadcrumb, copyOfBreadcrumb) + }) +} diff --git a/txcache/globalAccountBreadcrumbsCompiler.go b/txcache/globalAccountBreadcrumbsCompiler.go new file mode 100644 index 00000000000..48d58e203c1 --- /dev/null +++ b/txcache/globalAccountBreadcrumbsCompiler.go @@ -0,0 +1,129 @@ +package txcache + +import ( + "sync" +) + +// globalAccountBreadcrumbsCompiler represents the global account breadcrumbs compiler used in the Selection Tracker. +// A globalAccountBreadcrumbsCompiler holds a globalAccountBreadcrumb for each account. +type globalAccountBreadcrumbsCompiler struct { + mutCompiler sync.RWMutex + globalAccountBreadcrumbs map[string]*globalAccountBreadcrumb +} + +// newGlobalAccountBreadcrumbsCompiler creates a new global account breadcrumb compiler +func newGlobalAccountBreadcrumbsCompiler() *globalAccountBreadcrumbsCompiler { + return &globalAccountBreadcrumbsCompiler{ + mutCompiler: sync.RWMutex{}, + globalAccountBreadcrumbs: make(map[string]*globalAccountBreadcrumb), + } +} + +// updateOnAddedBlock updates the global state of the account when a block is added on the OnProposedBlock flow +func (gabc *globalAccountBreadcrumbsCompiler) updateOnAddedBlock(tb *trackedBlock) { + gabc.mutCompiler.Lock() + defer gabc.mutCompiler.Unlock() + + breadcrumbsOfTrackedBlock := tb.breadcrumbsByAddress + for account, breadcrumb := range breadcrumbsOfTrackedBlock { + globalBreadcrumb, ok := gabc.globalAccountBreadcrumbs[account] + if !ok { + globalBreadcrumb = newGlobalAccountBreadcrumb() + gabc.globalAccountBreadcrumbs[account] = globalBreadcrumb + } + + globalBreadcrumb.updateOnAddedBreadcrumb(breadcrumb) + } +} + +// updateOnRemovedBlockWithSameNonceOrAbove updates the global state of the account when a block is removed on the OnProposedBlock flow +func (gabc *globalAccountBreadcrumbsCompiler) updateOnRemovedBlockWithSameNonceOrAbove(tb *trackedBlock) error { + gabc.mutCompiler.Lock() + defer gabc.mutCompiler.Unlock() + + breadcrumbsOfTrackedBlock := tb.breadcrumbsByAddress + for account, breadcrumb := range breadcrumbsOfTrackedBlock { + globalBreadcrumb, ok := gabc.globalAccountBreadcrumbs[account] + if !ok { + return errGlobalBreadcrumbDoesNotExist + } + + shouldBeDeleted, err := globalBreadcrumb.updateOnRemoveBreadcrumbWithSameNonceOrAbove(breadcrumb) + if err != nil { + return err + } + + if shouldBeDeleted { + delete(gabc.globalAccountBreadcrumbs, account) + } + } + + return nil +} + +// updateOnRemovedBlockWithSameNonceOrBelow updates the global state of the account when a block is removed on the OnExecutedBlock flow +func (gabc *globalAccountBreadcrumbsCompiler) updateOnRemovedBlockWithSameNonceOrBelow(tb *trackedBlock) error { + gabc.mutCompiler.Lock() + defer gabc.mutCompiler.Unlock() + + breadcrumbsOfTrackedBlock := tb.breadcrumbsByAddress + for account, breadcrumb := range breadcrumbsOfTrackedBlock { + globalBreadcrumb, ok := gabc.globalAccountBreadcrumbs[account] + if !ok { + return errGlobalBreadcrumbDoesNotExist + } + + shouldBeDeleted, err := globalBreadcrumb.updateOnRemovedBreadcrumbWithSameNonceOrBelow(breadcrumb) + if err != nil { + return err + } + + if shouldBeDeleted { + delete(gabc.globalAccountBreadcrumbs, account) + } + } + + return nil +} + +// getGlobalBreadcrumbByAddress returns a deep copy of the global breadcrumb of a certain address +func (gabc *globalAccountBreadcrumbsCompiler) getGlobalBreadcrumbByAddress(address string) (*globalAccountBreadcrumb, error) { + gabc.mutCompiler.RLock() + defer gabc.mutCompiler.RUnlock() + + _, ok := gabc.globalAccountBreadcrumbs[address] + if !ok { + return nil, errGlobalBreadcrumbDoesNotExist + } + + return gabc.globalAccountBreadcrumbs[address].getSnapshotOfGlobalBreadcrumb(), nil +} + +// getGlobalBreadcrumbs returns a deep copy of the map of global accounts breadcrumbs +func (gabc *globalAccountBreadcrumbsCompiler) getGlobalBreadcrumbs() map[string]*globalAccountBreadcrumb { + gabc.mutCompiler.RLock() + defer gabc.mutCompiler.RUnlock() + + globalBreadcrumbsCopy := make(map[string]*globalAccountBreadcrumb) + for account, globalBreadcrumb := range gabc.globalAccountBreadcrumbs { + globalBreadcrumbsCopy[account] = globalBreadcrumb.getSnapshotOfGlobalBreadcrumb() + } + + return globalBreadcrumbsCopy +} + +// getNumGlobalBreadcrumbs returns the number of global breadcrumbs +func (gabc *globalAccountBreadcrumbsCompiler) getNumGlobalBreadcrumbs() uint64 { + gabc.mutCompiler.RLock() + defer gabc.mutCompiler.RUnlock() + + return uint64(len(gabc.globalAccountBreadcrumbs)) +} + +// cleanGlobalBreadcrumbs resets the global accounts breadcrumbs +func (gabc *globalAccountBreadcrumbsCompiler) cleanGlobalBreadcrumbs() { + gabc.mutCompiler.Lock() + defer gabc.mutCompiler.Unlock() + + gabc.globalAccountBreadcrumbs = make(map[string]*globalAccountBreadcrumb) +} diff --git a/txcache/globalAccountBreadcrumbsCompiler_test.go b/txcache/globalAccountBreadcrumbsCompiler_test.go new file mode 100644 index 00000000000..02e18bca32a --- /dev/null +++ b/txcache/globalAccountBreadcrumbsCompiler_test.go @@ -0,0 +1,701 @@ +package txcache + +import ( + "fmt" + "math" + "math/big" + "sync" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/stretchr/testify/require" +) + +func requireEqualGlobalAccountsBreadcrumbs( + t *testing.T, + expected map[string]*globalAccountBreadcrumb, + actual map[string]*globalAccountBreadcrumb, +) { + require.Equal(t, len(expected), len(actual)) + for account, globalBreadcrumb := range expected { + _, ok := actual[account] + require.True(t, ok) + require.Equal(t, globalBreadcrumb.lastNonce, actual[account].lastNonce) + require.Equal(t, globalBreadcrumb.firstNonce, actual[account].firstNonce) + require.Equal(t, 0, globalBreadcrumb.consumedBalance.Cmp(actual[account].consumedBalance)) + } +} + +func Test_newGlobalAccountBreadcrumbsCompiler(t *testing.T) { + t.Parallel() + + gabc := newGlobalAccountBreadcrumbsCompiler() + require.NotNil(t, gabc) +} + +func Test_shouldWorkConcurrently(t *testing.T) { + t.Parallel() + + defer func() { + r := recover() + if r != nil { + require.Fail(t, "should not panic") + } + }() + + gabc := newGlobalAccountBreadcrumbsCompiler() + numOfBlocks := 10 + + wg := sync.WaitGroup{} + wg.Add(2 * numOfBlocks) + + for i := 1; i <= numOfBlocks; i++ { + tb := newTrackedBlock(uint64(i), []byte(fmt.Sprintf("hash%d", i-1)), []byte(fmt.Sprintf("prevHash%d", i-1))) + + go func(tb *trackedBlock) { + defer wg.Done() + + gabc.updateOnAddedBlock(tb) + }(tb) + + go func(tb *trackedBlock) { + defer wg.Done() + + err := gabc.updateOnRemovedBlockWithSameNonceOrBelow(tb) + require.NoError(t, err) + }(tb) + } + + wg.Wait() +} + +func Test_shouldWorkOnDifferentScenarios(t *testing.T) { + t.Parallel() + t.Run("should work on most common scenario", func(t *testing.T) { + t.Parallel() + + gabc := newGlobalAccountBreadcrumbsCompiler() + + // create a first proposed block + tb1 := newTrackedBlock(0, []byte("hash0"), []byte("prevHash")) + + tx1 := createTx([]byte("hash1"), "alice", 0).withRelayer([]byte("bob")).withTransferredValue(big.NewInt(1)).withFee(big.NewInt(1)) + tx2 := createTx([]byte("hash2"), "carol", 0).withTransferredValue(big.NewInt(2)).withFee(big.NewInt(1)) + + txs := []*WrappedTransaction{ + tx1, tx2, + } + + // compile its breadcrumbs + _, err := tb1.compileBreadcrumbs(txs) + require.NoError(t, err) + + // update the global state + gabc.updateOnAddedBlock(tb1) + + expectedGlobalBreadcrumbs := map[string]*globalAccountBreadcrumb{ + "alice": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 0, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 0, + }, + consumedBalance: big.NewInt(1), + }, + "carol": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 0, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 0, + }, + consumedBalance: big.NewInt(2), + }, + "bob": { + firstNonce: core.OptionalUint64{ + HasValue: false, + Value: math.MaxUint64, + }, + lastNonce: core.OptionalUint64{ + HasValue: false, + Value: 0, + }, + consumedBalance: big.NewInt(1), + }, + } + + requireEqualGlobalAccountsBreadcrumbs(t, expectedGlobalBreadcrumbs, gabc.globalAccountBreadcrumbs) + + // create the second proposed block + tb2 := newTrackedBlock(0, []byte("hash0"), []byte("prevHash")) + + tx3 := createTx([]byte("hash3"), "alice", 1).withTransferredValue(big.NewInt(1)).withFee(big.NewInt(1)) + tx4 := createTx([]byte("hash4"), "carol", 1).withTransferredValue(big.NewInt(2)).withFee(big.NewInt(1)) + + txs = []*WrappedTransaction{ + tx3, tx4, + } + + // compile its breadcrumbs + _, err = tb2.compileBreadcrumbs(txs) + require.NoError(t, err) + + // update the global state + gabc.updateOnAddedBlock(tb2) + + expectedGlobalBreadcrumbs = map[string]*globalAccountBreadcrumb{ + "alice": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 0, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 1, + }, + consumedBalance: big.NewInt(2), + }, + "carol": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 0, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 1, + }, + consumedBalance: big.NewInt(4), + }, + "bob": { + firstNonce: core.OptionalUint64{ + HasValue: false, + Value: math.MaxUint64, + }, + lastNonce: core.OptionalUint64{ + HasValue: false, + Value: 0, + }, + consumedBalance: big.NewInt(1), + }, + } + + requireEqualGlobalAccountsBreadcrumbs(t, expectedGlobalBreadcrumbs, gabc.globalAccountBreadcrumbs) + + // remove the first proposed block and update the global state + err = gabc.updateOnRemovedBlockWithSameNonceOrBelow(tb1) + require.NoError(t, err) + + expectedGlobalBreadcrumbs = map[string]*globalAccountBreadcrumb{ + "alice": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 1, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 1, + }, + consumedBalance: big.NewInt(1), + }, + "carol": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 1, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 1, + }, + consumedBalance: big.NewInt(2), + }, + } + + requireEqualGlobalAccountsBreadcrumbs(t, expectedGlobalBreadcrumbs, gabc.globalAccountBreadcrumbs) + + // remove the second proposed block and update the global state + err = gabc.updateOnRemovedBlockWithSameNonceOrBelow(tb2) + require.NoError(t, err) + + expectedGlobalBreadcrumbs = map[string]*globalAccountBreadcrumb{} + requireEqualGlobalAccountsBreadcrumbs(t, expectedGlobalBreadcrumbs, gabc.globalAccountBreadcrumbs) + + }) + + t.Run("should work for forks scenarios", func(t *testing.T) { + t.Parallel() + + gabc := newGlobalAccountBreadcrumbsCompiler() + // propose another block + tb3 := newTrackedBlock(0, []byte("hash0"), []byte("prevHash")) + + tx5 := createTx([]byte("hash5"), "eve", 5).withRelayer([]byte("bob")).withFee(big.NewInt(1)) + tx6 := createTx([]byte("hash6"), "dave", 10).withRelayer([]byte("bob")).withFee(big.NewInt(1)) + tx7 := createTx([]byte("hash7"), "eve", 6).withRelayer([]byte("bob")).withFee(big.NewInt(1)) + tx8 := createTx([]byte("hash8"), "dave", 11).withRelayer([]byte("bob")).withFee(big.NewInt(1)) + tx9 := createTx([]byte("hash9"), "eve", 7).withRelayer([]byte("bob")).withFee(big.NewInt(1)) + + txs := []*WrappedTransaction{ + tx5, tx6, tx7, tx8, tx9, + } + + // compile its breadcrumbs + _, err := tb3.compileBreadcrumbs(txs) + require.NoError(t, err) + + // update the global state + gabc.updateOnAddedBlock(tb3) + + expectedGlobalBreadcrumbs := map[string]*globalAccountBreadcrumb{ + "eve": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 5, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 7, + }, + consumedBalance: big.NewInt(0), + }, + "dave": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 10, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 11, + }, + consumedBalance: big.NewInt(0), + }, + "bob": { + firstNonce: core.OptionalUint64{ + HasValue: false, + Value: math.MaxUint64, + }, + lastNonce: core.OptionalUint64{ + HasValue: false, + Value: 0, + }, + consumedBalance: big.NewInt(5), + }, + } + + requireEqualGlobalAccountsBreadcrumbs(t, expectedGlobalBreadcrumbs, gabc.globalAccountBreadcrumbs) + + // start a non-canonical chain + // propose another block + tb4 := newTrackedBlock(0, []byte("hash0"), []byte("prevHash")) + + tx10 := createTx([]byte("hash10"), "dave", 12).withRelayer([]byte("eve")).withFee(big.NewInt(1)) + tx11 := createTx([]byte("hash11"), "dave", 13).withRelayer([]byte("eve")).withFee(big.NewInt(1)) + + txs = []*WrappedTransaction{ + tx10, tx11, + } + + // compile its breadcrumbs + _, err = tb4.compileBreadcrumbs(txs) + require.NoError(t, err) + + // update the global state + gabc.updateOnAddedBlock(tb4) + + expectedGlobalBreadcrumbs = map[string]*globalAccountBreadcrumb{ + "eve": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 5, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 7, + }, + consumedBalance: big.NewInt(2), + }, + "dave": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 10, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 13, + }, + consumedBalance: big.NewInt(0), + }, + "bob": { + firstNonce: core.OptionalUint64{ + HasValue: false, + Value: math.MaxUint64, + }, + lastNonce: core.OptionalUint64{ + HasValue: false, + Value: 0, + }, + consumedBalance: big.NewInt(5), + }, + } + + requireEqualGlobalAccountsBreadcrumbs(t, expectedGlobalBreadcrumbs, gabc.globalAccountBreadcrumbs) + + // propose another block to the non-canonical chain + tb5 := newTrackedBlock(0, []byte("hash0"), []byte("prevHash")) + + tx12 := createTx([]byte("hash12"), "bob", 5).withTransferredValue(big.NewInt(1)).withFee(big.NewInt(1)) + tx13 := createTx([]byte("hash13"), "alice", 20).withTransferredValue(big.NewInt(3)).withFee(big.NewInt(1)) + + txs = []*WrappedTransaction{ + tx12, tx13, + } + + // compile its breadcrumbs + _, err = tb5.compileBreadcrumbs(txs) + require.NoError(t, err) + + // propose + gabc.updateOnAddedBlock(tb5) + + expectedGlobalBreadcrumbs = map[string]*globalAccountBreadcrumb{ + "eve": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 5, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 7, + }, + consumedBalance: big.NewInt(2), + }, + "dave": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 10, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 13, + }, + consumedBalance: big.NewInt(0), + }, + "bob": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 5, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 5, + }, + consumedBalance: big.NewInt(6), + }, + "alice": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 20, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 20, + }, + consumedBalance: big.NewInt(3), + }, + } + + requireEqualGlobalAccountsBreadcrumbs(t, expectedGlobalBreadcrumbs, gabc.globalAccountBreadcrumbs) + + // now, replace the first block in the non-canonical chain + // first, remove all the once greater or equal to its nonce + + err = gabc.updateOnRemovedBlockWithSameNonceOrAbove(tb4) + require.NoError(t, err) + + expectedGlobalBreadcrumbs = map[string]*globalAccountBreadcrumb{ + "eve": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 5, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 7, + }, + consumedBalance: big.NewInt(0), + }, + "dave": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 10, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 11, + }, + consumedBalance: big.NewInt(0), + }, + "bob": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 5, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 5, + }, + consumedBalance: big.NewInt(6), + }, + "alice": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 20, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 20, + }, + consumedBalance: big.NewInt(3), + }, + } + + requireEqualGlobalAccountsBreadcrumbs(t, expectedGlobalBreadcrumbs, gabc.globalAccountBreadcrumbs) + + err = gabc.updateOnRemovedBlockWithSameNonceOrAbove(tb5) + require.NoError(t, err) + + expectedGlobalBreadcrumbs = map[string]*globalAccountBreadcrumb{ + "eve": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 5, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 7, + }, + consumedBalance: big.NewInt(0), + }, + "dave": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 10, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 11, + }, + consumedBalance: big.NewInt(0), + }, + "bob": { + firstNonce: core.OptionalUint64{ + HasValue: false, + Value: math.MaxUint64, + }, + lastNonce: core.OptionalUint64{ + HasValue: false, + Value: 0, + }, + consumedBalance: big.NewInt(5), + }, + } + + requireEqualGlobalAccountsBreadcrumbs(t, expectedGlobalBreadcrumbs, gabc.globalAccountBreadcrumbs) + + // now, add the new block, the one that replaces the first of the non-canonical chain + + tb6 := newTrackedBlock(0, []byte("hash0"), []byte("prevHash")) + + tx14 := createTx([]byte("hash14"), "frank", 0).withRelayer([]byte("eve")).withFee(big.NewInt(1)) + tx15 := createTx([]byte("hash15"), "frank", 1).withRelayer([]byte("eve")).withFee(big.NewInt(1)) + + txs = []*WrappedTransaction{ + tx14, tx15, + } + + // compile its breadcrumbs + _, err = tb6.compileBreadcrumbs(txs) + require.NoError(t, err) + + // update the global state + gabc.updateOnAddedBlock(tb6) + + expectedGlobalBreadcrumbs = map[string]*globalAccountBreadcrumb{ + "eve": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 5, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 7, + }, + consumedBalance: big.NewInt(2), + }, + "dave": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 10, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 11, + }, + consumedBalance: big.NewInt(0), + }, + "bob": { + firstNonce: core.OptionalUint64{ + HasValue: false, + Value: math.MaxUint64, + }, + lastNonce: core.OptionalUint64{ + HasValue: false, + Value: 0, + }, + consumedBalance: big.NewInt(5), + }, + "frank": { + firstNonce: core.OptionalUint64{ + HasValue: true, + Value: 0, + }, + lastNonce: core.OptionalUint64{ + HasValue: true, + Value: 1, + }, + consumedBalance: big.NewInt(0), + }, + } + + requireEqualGlobalAccountsBreadcrumbs(t, expectedGlobalBreadcrumbs, gabc.globalAccountBreadcrumbs) + }) +} + +func Test_updateGlobalBreadcrumbsOnRemovedBlockOnProposed(t *testing.T) { + t.Parallel() + + t.Run("should return errGlobalBreadcrumbDoesNotExist error", func(t *testing.T) { + t.Parallel() + + gabc := newGlobalAccountBreadcrumbsCompiler() + + tb1 := newTrackedBlock(0, []byte("hash0"), []byte("prevHash")) + tx1 := createTx([]byte("hash14"), "frank", 0).withRelayer([]byte("eve")).withFee(big.NewInt(1)) + tx2 := createTx([]byte("hash15"), "frank", 1).withRelayer([]byte("eve")).withFee(big.NewInt(1)) + + txs := []*WrappedTransaction{ + tx1, tx2, + } + + _, err := tb1.compileBreadcrumbs(txs) + require.NoError(t, err) + + err = gabc.updateOnRemovedBlockWithSameNonceOrAbove(tb1) + require.Equal(t, errGlobalBreadcrumbDoesNotExist, err) + }) + + t.Run("should return errNegativeBalanceForBreadcrumb error", func(t *testing.T) { + t.Parallel() + + gabc := newGlobalAccountBreadcrumbsCompiler() + gabc.globalAccountBreadcrumbs = map[string]*globalAccountBreadcrumb{ + "frank": { + firstNonce: core.OptionalUint64{}, + lastNonce: core.OptionalUint64{}, + consumedBalance: big.NewInt(0), + }, + "eve": { + firstNonce: core.OptionalUint64{}, + lastNonce: core.OptionalUint64{}, + consumedBalance: big.NewInt(0), + }, + } + tb1 := newTrackedBlock(0, []byte("hash0"), []byte("prevHash")) + tx1 := createTx([]byte("hash14"), "frank", 0).withRelayer([]byte("eve")).withFee(big.NewInt(1)) + tx2 := createTx([]byte("hash15"), "frank", 1).withRelayer([]byte("eve")).withFee(big.NewInt(1)) + + txs := []*WrappedTransaction{ + tx1, tx2, + } + + _, err := tb1.compileBreadcrumbs(txs) + require.NoError(t, err) + + err = gabc.updateOnRemovedBlockWithSameNonceOrAbove(tb1) + require.Equal(t, errNegativeBalanceForBreadcrumb, err) + }) +} + +func Test_updateGlobalBreadcrumbsOnRemovedBlockOnExecuted(t *testing.T) { + t.Parallel() + + t.Run("should return errGlobalBreadcrumbDoesNotExist error", func(t *testing.T) { + t.Parallel() + + gabc := newGlobalAccountBreadcrumbsCompiler() + + tb1 := newTrackedBlock(0, []byte("hash0"), []byte("prevHash")) + tx1 := createTx([]byte("hash14"), "frank", 0).withRelayer([]byte("eve")).withFee(big.NewInt(1)) + tx2 := createTx([]byte("hash15"), "frank", 1).withRelayer([]byte("eve")).withFee(big.NewInt(1)) + + txs := []*WrappedTransaction{ + tx1, tx2, + } + + _, err := tb1.compileBreadcrumbs(txs) + require.NoError(t, err) + + err = gabc.updateOnRemovedBlockWithSameNonceOrBelow(tb1) + require.Equal(t, errGlobalBreadcrumbDoesNotExist, err) + }) + + t.Run("should return errNegativeBalanceForBreadcrumb error", func(t *testing.T) { + t.Parallel() + + gabc := newGlobalAccountBreadcrumbsCompiler() + gabc.globalAccountBreadcrumbs = map[string]*globalAccountBreadcrumb{ + "frank": { + firstNonce: core.OptionalUint64{}, + lastNonce: core.OptionalUint64{}, + consumedBalance: big.NewInt(0), + }, + "eve": { + firstNonce: core.OptionalUint64{}, + lastNonce: core.OptionalUint64{}, + consumedBalance: big.NewInt(0), + }, + } + tb1 := newTrackedBlock(0, []byte("hash0"), []byte("prevHash")) + tx1 := createTx([]byte("hash14"), "frank", 0).withRelayer([]byte("eve")).withFee(big.NewInt(1)) + tx2 := createTx([]byte("hash15"), "frank", 1).withRelayer([]byte("eve")).withFee(big.NewInt(1)) + + txs := []*WrappedTransaction{ + tx1, tx2, + } + + _, err := tb1.compileBreadcrumbs(txs) + require.NoError(t, err) + + err = gabc.updateOnRemovedBlockWithSameNonceOrBelow(tb1) + require.Equal(t, errNegativeBalanceForBreadcrumb, err) + }) +} + +func Test_cleanGlobalBreadcrumbs(t *testing.T) { + t.Parallel() + + gabc := newGlobalAccountBreadcrumbsCompiler() + gabc.globalAccountBreadcrumbs = map[string]*globalAccountBreadcrumb{ + "alice": {}, + "bob": {}, + } + + require.Equal(t, 2, len(gabc.globalAccountBreadcrumbs)) + + gabc.cleanGlobalBreadcrumbs() + require.Equal(t, 0, len(gabc.globalAccountBreadcrumbs)) +} diff --git a/txcache/interface.go b/txcache/interface.go new file mode 100644 index 00000000000..555f36b84dc --- /dev/null +++ b/txcache/interface.go @@ -0,0 +1,41 @@ +package txcache + +import ( + "math/big" + + "github.com/multiversx/mx-chain-core-go/data" +) + +// MempoolHost provides blockchain information for mempool operations +type MempoolHost interface { + ComputeTxFee(tx data.TransactionWithFeeHandler) *big.Int + GetTransferredValue(tx data.TransactionHandler) *big.Int + IsInterfaceNil() bool +} + +// SelectionSession provides blockchain information for transaction selection +type SelectionSession interface { + GetAccountNonceAndBalance(accountKey []byte) (uint64, *big.Int, bool, error) + GetRootHash() ([]byte, error) + IsIncorrectlyGuarded(tx data.TransactionHandler) bool + IsGuarded(tx data.TransactionHandler) bool + IsInterfaceNil() bool +} + +// ForEachTransaction is an iterator callback +type ForEachTransaction func(txHash []byte, value *WrappedTransaction) + +// txCacheForSelectionTracker provides the TxCache methods used in SelectionTracker +type txCacheForSelectionTracker interface { + GetByTxHash(txHash []byte) (*WrappedTransaction, bool) + SetSelectionOffsetsByLastNonce(lastNoncePerSender map[string]uint64) + ResetSelectionOffsetsToNonce(sendersWithFirstNonce map[string]uint64) + IsInterfaceNil() bool +} + +// TrackerDiagnosis provides the methods for a tracker diagnosis +type TrackerDiagnosis interface { + GetNumTrackedBlocks() uint64 + GetNumTrackedAccounts() uint64 + IsInterfaceNil() bool +} diff --git a/txcache/loggers.go b/txcache/loggers.go new file mode 100644 index 00000000000..ecedbfec7b5 --- /dev/null +++ b/txcache/loggers.go @@ -0,0 +1,9 @@ +package txcache + +import logger "github.com/multiversx/mx-chain-logger-go" + +var log = logger.GetOrCreate("txcache/main") +var logAdd = logger.GetOrCreate("txcache/add") +var logRemove = logger.GetOrCreate("txcache/remove") +var logSelect = logger.GetOrCreate("txcache/select") +var logDiagnoseTransactions = logger.GetOrCreate("txcache/diagnose/transactions") diff --git a/txcache/maps/concurrentMap.go b/txcache/maps/concurrentMap.go new file mode 100644 index 00000000000..cb24048b432 --- /dev/null +++ b/txcache/maps/concurrentMap.go @@ -0,0 +1,180 @@ +package maps + +import ( + "sync" +) + +// This implementation is a simplified version of: +// https://github.com/multiversx/concurrent-map, which is based on: +// https://github.com/orcaman/concurrent-map + +// ConcurrentMap is a thread safe map of type string:Anything. +// To avoid lock bottlenecks this map is divided to several map chunks. +type ConcurrentMap struct { + mutex sync.RWMutex + nChunks uint32 + chunks []*concurrentMapChunk +} + +// concurrentMapChunk is a thread safe string to anything map. +type concurrentMapChunk struct { + items map[string]interface{} + mutex sync.RWMutex +} + +// NewConcurrentMap creates a new concurrent map. +func NewConcurrentMap(nChunks uint32) *ConcurrentMap { + // We cannot have a map with no chunks + if nChunks == 0 { + nChunks = 1 + } + + m := ConcurrentMap{ + nChunks: nChunks, + } + + m.initializeChunks() + + return &m +} + +func (m *ConcurrentMap) initializeChunks() { + // Assignment is not an atomic operation, so we have to wrap this in a critical section + m.mutex.Lock() + defer m.mutex.Unlock() + + m.chunks = make([]*concurrentMapChunk, m.nChunks) + + for i := uint32(0); i < m.nChunks; i++ { + m.chunks[i] = &concurrentMapChunk{ + items: make(map[string]interface{}), + } + } +} + +// Set sets the given value under the specified key. +func (m *ConcurrentMap) Set(key string, value interface{}) { + chunk := m.getChunk(key) + chunk.mutex.Lock() + chunk.items[key] = value + chunk.mutex.Unlock() +} + +// SetIfAbsent sets the given value under the specified key if no value was associated with it. +func (m *ConcurrentMap) SetIfAbsent(key string, value interface{}) bool { + chunk := m.getChunk(key) + chunk.mutex.Lock() + _, ok := chunk.items[key] + if !ok { + chunk.items[key] = value + } + chunk.mutex.Unlock() + return !ok +} + +// Get retrieves an element from map under given key. +func (m *ConcurrentMap) Get(key string) (interface{}, bool) { + chunk := m.getChunk(key) + chunk.mutex.RLock() + val, ok := chunk.items[key] + chunk.mutex.RUnlock() + return val, ok +} + +// Has looks up an item under specified key. +func (m *ConcurrentMap) Has(key string) bool { + chunk := m.getChunk(key) + chunk.mutex.RLock() + _, ok := chunk.items[key] + chunk.mutex.RUnlock() + return ok +} + +// Remove removes an element from the map. +func (m *ConcurrentMap) Remove(key string) (interface{}, bool) { + chunk := m.getChunk(key) + chunk.mutex.Lock() + defer chunk.mutex.Unlock() + + item := chunk.items[key] + delete(chunk.items, key) + return item, item != nil +} + +func (m *ConcurrentMap) getChunk(key string) *concurrentMapChunk { + m.mutex.RLock() + defer m.mutex.RUnlock() + return m.chunks[fnv32(key)%m.nChunks] +} + +// fnv32 implements https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function for 32 bits +func fnv32(key string) uint32 { + hash := uint32(2166136261) + const prime32 = uint32(16777619) + for i := 0; i < len(key); i++ { + hash *= prime32 + hash ^= uint32(key[i]) + } + return hash +} + +// Clear clears the map +func (m *ConcurrentMap) Clear() { + // There is no need to explicitly remove each item for each chunk + // The garbage collector will remove the data from memory + m.initializeChunks() +} + +// Count returns the number of elements within the map +func (m *ConcurrentMap) Count() int { + count := 0 + chunks := m.getChunks() + + for _, chunk := range chunks { + chunk.mutex.RLock() + count += len(chunk.items) + chunk.mutex.RUnlock() + } + return count +} + +// Keys returns all keys as []string +func (m *ConcurrentMap) Keys() []string { + count := m.Count() + chunks := m.getChunks() + + // count is not exact anymore, since we are in a different lock than the one aquired by Count() (but is a good approximation) + keys := make([]string, 0, count) + + for _, chunk := range chunks { + chunk.mutex.RLock() + for key := range chunk.items { + keys = append(keys, key) + } + chunk.mutex.RUnlock() + } + + return keys +} + +// IterCb is an iterator callback +type IterCb func(key string, v interface{}) + +// IterCb iterates over the map (cheapest way to read all elements in a map) +func (m *ConcurrentMap) IterCb(fn IterCb) { + chunks := m.getChunks() + + for _, chunk := range chunks { + chunk.mutex.RLock() + for key, value := range chunk.items { + fn(key, value) + } + chunk.mutex.RUnlock() + } +} + +func (m *ConcurrentMap) getChunks() []*concurrentMapChunk { + m.mutex.RLock() + defer m.mutex.RUnlock() + return m.chunks +} diff --git a/txcache/maps/concurrentMap_test.go b/txcache/maps/concurrentMap_test.go new file mode 100644 index 00000000000..705b87791ea --- /dev/null +++ b/txcache/maps/concurrentMap_test.go @@ -0,0 +1,160 @@ +package maps + +import ( + "sync" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewConcurrentMap(t *testing.T) { + myMap := NewConcurrentMap(4) + require.Equal(t, uint32(4), myMap.nChunks) + require.Equal(t, 4, len(myMap.chunks)) + + // 1 is minimum number of chunks + myMap = NewConcurrentMap(0) + require.Equal(t, uint32(1), myMap.nChunks) + require.Equal(t, 1, len(myMap.chunks)) +} + +func TestConcurrentMap_Get(t *testing.T) { + myMap := NewConcurrentMap(4) + myMap.Set("a", "foo") + myMap.Set("b", 42) + + a, ok := myMap.Get("a") + require.True(t, ok) + require.Equal(t, "foo", a) + + b, ok := myMap.Get("b") + require.True(t, ok) + require.Equal(t, 42, b) +} + +func TestConcurrentMap_Count(t *testing.T) { + myMap := NewConcurrentMap(4) + myMap.Set("a", "a") + myMap.Set("b", "b") + myMap.Set("c", "c") + + require.Equal(t, 3, myMap.Count()) +} + +func TestConcurrentMap_Keys(t *testing.T) { + myMap := NewConcurrentMap(4) + myMap.Set("1", 0) + myMap.Set("2", 0) + myMap.Set("3", 0) + myMap.Set("4", 0) + + require.Equal(t, 4, len(myMap.Keys())) +} + +func TestConcurrentMap_Has(t *testing.T) { + myMap := NewConcurrentMap(4) + myMap.SetIfAbsent("a", "a") + myMap.SetIfAbsent("b", "b") + + require.True(t, myMap.Has("a")) + require.True(t, myMap.Has("b")) + require.False(t, myMap.Has("c")) +} + +func TestConcurrentMap_Remove(t *testing.T) { + myMap := NewConcurrentMap(4) + myMap.SetIfAbsent("a", "a") + myMap.SetIfAbsent("b", "b") + + _, ok := myMap.Remove("b") + require.True(t, ok) + _, ok = myMap.Remove("x") + require.False(t, ok) + + require.True(t, myMap.Has("a")) + require.False(t, myMap.Has("b")) +} + +func TestConcurrentMap_Clear(t *testing.T) { + myMap := NewConcurrentMap(4) + myMap.SetIfAbsent("a", "a") + myMap.SetIfAbsent("b", "b") + + myMap.Clear() + + require.Equal(t, 0, myMap.Count()) +} + +func TestConcurrentMap_ClearConcurrentWithRead(t *testing.T) { + myMap := NewConcurrentMap(4) + + var wg sync.WaitGroup + wg.Add(2) + + go func() { + for j := 0; j < 1000; j++ { + myMap.Clear() + } + + wg.Done() + }() + + go func() { + for j := 0; j < 1000; j++ { + require.Equal(t, 0, myMap.Count()) + require.Len(t, myMap.Keys(), 0) + require.Equal(t, false, myMap.Has("foobar")) + item, ok := myMap.Get("foobar") + require.Nil(t, item) + require.False(t, ok) + myMap.IterCb(func(key string, item interface{}) { + }) + } + + wg.Done() + }() + + wg.Wait() +} + +func TestConcurrentMap_ClearConcurrentWithWrite(t *testing.T) { + myMap := NewConcurrentMap(4) + + var wg sync.WaitGroup + wg.Add(2) + + go func() { + for j := 0; j < 10000; j++ { + myMap.Clear() + } + + wg.Done() + }() + + go func() { + for j := 0; j < 10000; j++ { + myMap.Set("foobar", "foobar") + myMap.SetIfAbsent("foobar", "foobar") + _, _ = myMap.Remove("foobar") + } + + wg.Done() + }() + + wg.Wait() +} + +func TestConcurrentMap_IterCb(t *testing.T) { + myMap := NewConcurrentMap(4) + + myMap.Set("a", "a") + myMap.Set("b", "b") + myMap.Set("c", "c") + + i := 0 + myMap.IterCb(func(key string, value interface{}) { + i++ + }) + + require.Equal(t, 3, i) +} diff --git a/txcache/orderedTransactionsList.go b/txcache/orderedTransactionsList.go new file mode 100644 index 00000000000..e48e242a617 --- /dev/null +++ b/txcache/orderedTransactionsList.go @@ -0,0 +1,189 @@ +package txcache + +import ( + "bytes" + "sort" +) + +// orderedTransactionsList manages a slice of transactions sorted by execution order +type orderedTransactionsList struct { + items []*WrappedTransaction +} + +func newOrderedTransactionsList() *orderedTransactionsList { + return &orderedTransactionsList{ + items: make([]*WrappedTransaction, 0), + } +} + +func (otl *orderedTransactionsList) removeAt(index int) *WrappedTransaction { + if index < 0 || index >= len(otl.items) { + return nil + } + tx := otl.items[index] + copy(otl.items[index:], otl.items[index+1:]) + otl.items[len(otl.items)-1] = nil // Avoid memory leak + otl.items = otl.items[:len(otl.items)-1] + return tx +} + +// removeAfterNonce removes all transactions with nonce >= givenNonce +func (otl *orderedTransactionsList) removeAfterNonce(givenNonce uint64) []*WrappedTransaction { + cutoffIndex := -1 + for i, tx := range otl.items { + if tx.Tx.GetNonce() >= givenNonce { + cutoffIndex = i + break + } + } + + if cutoffIndex == -1 { + return nil + } + + removed := make([]*WrappedTransaction, 0, len(otl.items)-cutoffIndex) + for i := cutoffIndex; i < len(otl.items); i++ { + removed = append(removed, otl.items[i]) + otl.items[i] = nil // Help GC + } + otl.items = otl.items[:cutoffIndex] + return removed +} + +// removeBeforeNonce removes all transactions with nonce <= targetNonce +// Returns the removed transactions +func (otl *orderedTransactionsList) removeBeforeNonce(targetNonce uint64) []*WrappedTransaction { + cutoffIndex := 0 + for i, tx := range otl.items { + if tx.Tx.GetNonce() > targetNonce { + cutoffIndex = i + break + } + cutoffIndex = i + 1 + } + + if cutoffIndex == 0 { + return nil + } + + removed := make([]*WrappedTransaction, cutoffIndex) + copy(removed, otl.items[:cutoffIndex]) + + remain := len(otl.items) - cutoffIndex + copy(otl.items, otl.items[cutoffIndex:]) + + for i := remain; i < len(otl.items); i++ { + otl.items[i] = nil + } + otl.items = otl.items[:remain] + + return removed +} + +func (otl *orderedTransactionsList) get(index int) *WrappedTransaction { + if index < 0 || index >= len(otl.items) { + return nil + } + return otl.items[index] +} + +func (otl *orderedTransactionsList) len() int { + return len(otl.items) +} + +func (otl *orderedTransactionsList) getAll() []*WrappedTransaction { + result := make([]*WrappedTransaction, len(otl.items)) + copy(result, otl.items) + return result +} + +// getAllFromIndex returns a copy of all transactions starting from the given index. +// This is used for selection to skip already-proposed transactions. +func (otl *orderedTransactionsList) getAllFromIndex(startIndex int) []*WrappedTransaction { + if startIndex < 0 { + startIndex = 0 + } + if startIndex >= len(otl.items) { + return make([]*WrappedTransaction, 0) + } + + result := make([]*WrappedTransaction, len(otl.items)-startIndex) + copy(result, otl.items[startIndex:]) + return result +} + +// findIndexByNonce returns the index of the first transaction with nonce >= targetNonce. +// Uses binary search for efficiency. Returns len(items) if no such transaction exists. +// This is used for block replacement to reset the selection offset. +func (otl *orderedTransactionsList) findIndexByNonce(targetNonce uint64) int { + return sort.Search(len(otl.items), func(i int) bool { + return otl.items[i].Tx.GetNonce() >= targetNonce + }) +} + +// findInsertionIndex returns the index where a transaction would be inserted. +// This function should only be used in critical section (listForSender.mutex). +// When searching for the insertion place, we consider the following rules: +// - transactions are sorted by nonce in ascending order. +// - transactions with the same nonce are sorted by gas price in descending order. +// - transactions with the same nonce and gas price are sorted by hash in ascending order. +// - "PPU" measurement is not relevant in this context. Competition among transactions of the same sender (and nonce) is based on gas price. +func (otl *orderedTransactionsList) findInsertionIndex(tx *WrappedTransaction) int { + if len(otl.items) == 0 { + return 0 + } + + lastItem := otl.items[len(otl.items)-1] + if isGreater(tx, lastItem) { + return len(otl.items) + } + + return sort.Search(len(otl.items), func(i int) bool { + return compareTxs(otl.items[i], tx) >= 0 + }) +} + +// insertAt inserts a transaction at the given index (pre-computed by findInsertionIndex). +// This function should only be used in critical section (listForSender.mutex). +// Returns false if the transaction is a duplicate (item at index has same nonce, gas price, and hash). +// Duplicates are not allowed. +func (otl *orderedTransactionsList) insertAt(tx *WrappedTransaction, index int) bool { + // Check for duplicate at the insertion index + if index < len(otl.items) && compareTxs(otl.items[index], tx) == 0 { + return false // Duplicate + } + + // Insert at index + otl.items = append(otl.items, nil) // grow + copy(otl.items[index+1:], otl.items[index:]) + otl.items[index] = tx + return true +} + +// isGreater returns true if tx1 > tx2 +func isGreater(tx1, tx2 *WrappedTransaction) bool { + return compareTxs(tx1, tx2) > 0 +} + +// compareTxs returns: +// -1 if tx1 < tx2, 0 if tx1 == tx2, 1 if tx1 > tx2 +func compareTxs(tx1 *WrappedTransaction, tx2 *WrappedTransaction) int { + // Nonce ASC + if tx1.Tx.GetNonce() < tx2.Tx.GetNonce() { + return -1 + } + if tx1.Tx.GetNonce() > tx2.Tx.GetNonce() { + return 1 + } + + // Gas Price DESC (Secondary sort key) + if tx1.Tx.GetGasPrice() > tx2.Tx.GetGasPrice() { + return -1 + } + if tx1.Tx.GetGasPrice() < tx2.Tx.GetGasPrice() { + return 1 + } + + // Hash ASC (Tie-breaker) + return bytes.Compare(tx1.TxHash, tx2.TxHash) +} diff --git a/txcache/orderedTransactionsList_test.go b/txcache/orderedTransactionsList_test.go new file mode 100644 index 00000000000..79c07062dc2 --- /dev/null +++ b/txcache/orderedTransactionsList_test.go @@ -0,0 +1,535 @@ +package txcache + +import ( + "fmt" + "math/rand" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +// insertTx is a test helper that inserts a transaction using findInsertionIndex + insertAt +func (otl *orderedTransactionsList) insertTx(tx *WrappedTransaction) bool { + index := otl.findInsertionIndex(tx) + return otl.insertAt(tx, index) +} + +func TestNewOrderedTransactionsList(t *testing.T) { + otl := newOrderedTransactionsList() + require.NotNil(t, otl) + require.Equal(t, 0, otl.len()) + require.Equal(t, 0, len(otl.getAll())) +} + +func TestOrderedTransactionsList_InsertTx(t *testing.T) { + t.Run("ordered by nonce ascending", func(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 1) + tx2 := createTx([]byte("tx2"), "sender", 2) + tx3 := createTx([]byte("tx3"), "sender", 3) + + require.True(t, otl.insertTx(tx1)) + require.True(t, otl.insertTx(tx2)) + require.True(t, otl.insertTx(tx3)) + + require.Equal(t, 3, otl.len()) + require.Equal(t, []*WrappedTransaction{tx1, tx2, tx3}, otl.getAll()) + }) + + t.Run("ordered by nonce descending (insert reverse)", func(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 1) + tx2 := createTx([]byte("tx2"), "sender", 2) + tx3 := createTx([]byte("tx3"), "sender", 3) + + require.True(t, otl.insertTx(tx3)) + require.True(t, otl.insertTx(tx2)) + require.True(t, otl.insertTx(tx1)) + + require.Equal(t, 3, otl.len()) + require.Equal(t, []*WrappedTransaction{tx1, tx2, tx3}, otl.getAll()) + }) + + t.Run("mixed nonce and gas price", func(t *testing.T) { + otl := newOrderedTransactionsList() + + // Expected Order: + // 1. Nonce 1, Gas 2000 + // 2. Nonce 1, Gas 1000 + // 3. Nonce 2, Gas 500 + // 4. Nonce 3, Gas 3000 + + tx1HighGas := createTx([]byte("tx1H"), "sender", 1).withGasPrice(2000) + tx1LowGas := createTx([]byte("tx1L"), "sender", 1).withGasPrice(1000) + tx2 := createTx([]byte("tx2"), "sender", 2).withGasPrice(500) + tx3 := createTx([]byte("tx3"), "sender", 3).withGasPrice(3000) + + // Insert in random order + require.True(t, otl.insertTx(tx2)) + require.True(t, otl.insertTx(tx1LowGas)) + require.True(t, otl.insertTx(tx3)) + require.True(t, otl.insertTx(tx1HighGas)) + + expected := []*WrappedTransaction{tx1HighGas, tx1LowGas, tx2, tx3} + require.Equal(t, expected, otl.getAll()) + }) + + t.Run("ordered by gas price descending (same nonce)", func(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 10).withGasPrice(100) + tx2 := createTx([]byte("tx2"), "sender", 10).withGasPrice(200) + tx3 := createTx([]byte("tx3"), "sender", 10).withGasPrice(300) + + // Insert low to high (should end up high to low) + otl.insertTx(tx1) + otl.insertTx(tx2) + otl.insertTx(tx3) + + require.Equal(t, []*WrappedTransaction{tx3, tx2, tx1}, otl.getAll()) + }) + + t.Run("random insertion mixed", func(t *testing.T) { + otl := newOrderedTransactionsList() + + tx1 := createTx([]byte("tx1"), "sender", 1) + tx2 := createTx([]byte("tx2"), "sender", 2) + tx3 := createTx([]byte("tx3"), "sender", 3) + + require.True(t, otl.insertTx(tx2)) + require.True(t, otl.insertTx(tx3)) + require.True(t, otl.insertTx(tx1)) + + require.Equal(t, 3, otl.len()) + require.Equal(t, []*WrappedTransaction{tx1, tx2, tx3}, otl.getAll()) + }) + + t.Run("duplicates are rejected", func(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 1) + + require.True(t, otl.insertTx(tx1)) + require.False(t, otl.insertTx(tx1)) + + tx1Copy := createTx([]byte("tx1"), "sender", 1) + require.False(t, otl.insertTx(tx1Copy)) + + require.Equal(t, 1, otl.len()) + }) +} + +func TestOrderedTransactionsList_RemoveAt(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 1) + tx2 := createTx([]byte("tx2"), "sender", 2) + tx3 := createTx([]byte("tx3"), "sender", 3) + + otl.insertTx(tx1) + otl.insertTx(tx2) + otl.insertTx(tx3) + + // Remove middle + removed := otl.removeAt(1) + require.Equal(t, tx2, removed) + require.Equal(t, []*WrappedTransaction{tx1, tx3}, otl.getAll()) + + // Remove start + removed = otl.removeAt(0) + require.Equal(t, tx1, removed) + require.Equal(t, []*WrappedTransaction{tx3}, otl.getAll()) + + // Remove end + removed = otl.removeAt(0) + require.Equal(t, tx3, removed) + require.Equal(t, 0, otl.len()) + + // Out of bounds + require.Nil(t, otl.removeAt(0)) + require.Nil(t, otl.removeAt(-1)) +} + +func TestOrderedTransactionsList_RemoveAfterNonce(t *testing.T) { + t.Run("remove some from end", func(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 1) + tx2 := createTx([]byte("tx2"), "sender", 2) + tx3 := createTx([]byte("tx3"), "sender", 3) + tx4 := createTx([]byte("tx4"), "sender", 4) + otl.insertTx(tx1) + otl.insertTx(tx2) + otl.insertTx(tx3) + otl.insertTx(tx4) + + removed := otl.removeAfterNonce(3) + require.Equal(t, []*WrappedTransaction{tx3, tx4}, removed) + require.Equal(t, []*WrappedTransaction{tx1, tx2}, otl.getAll()) + }) + + t.Run("remove all", func(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 1) + otl.insertTx(tx1) + + removed := otl.removeAfterNonce(0) + require.Equal(t, []*WrappedTransaction{tx1}, removed) + require.Equal(t, 0, otl.len()) + }) + + t.Run("remove none (too high)", func(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 1) + otl.insertTx(tx1) + + removed := otl.removeAfterNonce(2) + require.Nil(t, removed) + require.Equal(t, 1, otl.len()) + }) + + t.Run("remove empty list", func(t *testing.T) { + otl := newOrderedTransactionsList() + removed := otl.removeAfterNonce(0) + require.Nil(t, removed) + }) +} + +func TestOrderedTransactionsList_RemoveBeforeNonce(t *testing.T) { + t.Run("remove some from start", func(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 1) + tx2 := createTx([]byte("tx2"), "sender", 2) + tx3 := createTx([]byte("tx3"), "sender", 3) + tx4 := createTx([]byte("tx4"), "sender", 4) + otl.insertTx(tx1) + otl.insertTx(tx2) + otl.insertTx(tx3) + otl.insertTx(tx4) + + // Remove <= 2 + removed := otl.removeBeforeNonce(2) + require.Equal(t, []*WrappedTransaction{tx1, tx2}, removed) + require.Equal(t, []*WrappedTransaction{tx3, tx4}, otl.getAll()) + }) + + t.Run("remove all", func(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 1) + otl.insertTx(tx1) + + removed := otl.removeBeforeNonce(10) + require.Equal(t, []*WrappedTransaction{tx1}, removed) + require.Equal(t, 0, otl.len()) + }) + + t.Run("remove none (too low)", func(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 10) + otl.insertTx(tx1) + + removed := otl.removeBeforeNonce(5) + require.Nil(t, removed) + require.Equal(t, 1, otl.len()) + }) + + t.Run("remove matches exactly", func(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 10) + otl.insertTx(tx1) + + removed := otl.removeBeforeNonce(10) + require.Equal(t, []*WrappedTransaction{tx1}, removed) + require.Equal(t, 0, otl.len()) + }) +} + +func TestOrderedTransactionsList_Get(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 1) + tx2 := createTx([]byte("tx2"), "sender", 2) + tx3 := createTx([]byte("tx3"), "sender", 3) + otl.insertTx(tx1) + otl.insertTx(tx2) + otl.insertTx(tx3) + + require.Equal(t, tx1, otl.get(0)) + require.Equal(t, tx2, otl.get(1)) + require.Equal(t, tx3, otl.get(2)) + + require.Nil(t, otl.get(3)) + require.Nil(t, otl.get(-1)) +} + +func TestIsGreater(t *testing.T) { + txHighPrice := createTx([]byte("high"), "sender", 1).withGasPrice(2000) + txLowPrice := createTx([]byte("low"), "sender", 1).withGasPrice(1000) + + require.Equal(t, -1, compareTxs(txHighPrice, txLowPrice)) + require.False(t, isGreater(txHighPrice, txLowPrice)) + require.True(t, isGreater(txLowPrice, txHighPrice)) +} + +func TestOrderedTransactionsList_MemorySafety_Fuzz(t *testing.T) { + otl := newOrderedTransactionsList() + + // Fuzz-like scenario: + // Insert 1000 items with random nonces/prices + // Remove random chunks + // Verify sorting is maintained + + rnd := rand.New(rand.NewSource(time.Now().UnixNano())) + + numItems := 1000 + for i := 0; i < numItems; i++ { + nonce := uint64(rnd.Intn(100)) + gas := uint64(rnd.Intn(1000) + 1) + tx := createTx([]byte(fmt.Sprintf("tx-%d", i)), "sender", nonce).withGasPrice(gas) + otl.insertTx(tx) + } + + // Check integrity + items := otl.getAll() + for i := 0; i < len(items)-1; i++ { + // items[i] should be <= items[i+1] + // compareTxs(items[i], items[i+1]) should be <= 0 + cmp := compareTxs(items[i], items[i+1]) + require.True(t, cmp <= 0, "List validation failed at index %d: nonce %d vs %d", i, items[i].Tx.GetNonce(), items[i+1].Tx.GetNonce()) + } + + // Remove some + otl.removeBeforeNonce(50) + + // Check again + items = otl.getAll() + for i := 0; i < len(items)-1; i++ { + cmp := compareTxs(items[i], items[i+1]) + require.True(t, cmp <= 0, "List validation failed after remove at index %d", i) + } + + // Check that we don't have dangling low nonces + if len(items) > 0 { + require.True(t, items[0].Tx.GetNonce() > 50, "Expected all nonces > 50, got %d", items[0].Tx.GetNonce()) + } +} + +func TestOrderedTransactionsList_GetAllFromIndex(t *testing.T) { + t.Run("empty list", func(t *testing.T) { + otl := newOrderedTransactionsList() + result := otl.getAllFromIndex(0) + require.Equal(t, 0, len(result)) + }) + + t.Run("start index 0", func(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 1) + tx2 := createTx([]byte("tx2"), "sender", 2) + tx3 := createTx([]byte("tx3"), "sender", 3) + + otl.insertTx(tx1) + otl.insertTx(tx2) + otl.insertTx(tx3) + + result := otl.getAllFromIndex(0) + require.Equal(t, 3, len(result)) + require.Equal(t, tx1, result[0]) + require.Equal(t, tx2, result[1]) + require.Equal(t, tx3, result[2]) + }) + + t.Run("start index 1", func(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 1) + tx2 := createTx([]byte("tx2"), "sender", 2) + tx3 := createTx([]byte("tx3"), "sender", 3) + + otl.insertTx(tx1) + otl.insertTx(tx2) + otl.insertTx(tx3) + + result := otl.getAllFromIndex(1) + require.Equal(t, 2, len(result)) + require.Equal(t, tx2, result[0]) + require.Equal(t, tx3, result[1]) + }) + + t.Run("start index beyond list", func(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 1) + otl.insertTx(tx1) + + result := otl.getAllFromIndex(5) + require.Equal(t, 0, len(result)) + }) + + t.Run("negative start index", func(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 1) + tx2 := createTx([]byte("tx2"), "sender", 2) + + otl.insertTx(tx1) + otl.insertTx(tx2) + + result := otl.getAllFromIndex(-1) + require.Equal(t, 2, len(result)) + }) +} + +func TestOrderedTransactionsList_FindIndexByNonce(t *testing.T) { + t.Run("empty list", func(t *testing.T) { + otl := newOrderedTransactionsList() + require.Equal(t, 0, otl.findIndexByNonce(10)) + }) + + t.Run("exact match", func(t *testing.T) { + otl := newOrderedTransactionsList() + otl.insertTx(createTx([]byte("tx1"), "sender", 10)) + otl.insertTx(createTx([]byte("tx2"), "sender", 20)) + otl.insertTx(createTx([]byte("tx3"), "sender", 30)) + + require.Equal(t, 0, otl.findIndexByNonce(10)) + require.Equal(t, 1, otl.findIndexByNonce(20)) + require.Equal(t, 2, otl.findIndexByNonce(30)) + }) + + t.Run("nonce between existing", func(t *testing.T) { + otl := newOrderedTransactionsList() + otl.insertTx(createTx([]byte("tx1"), "sender", 10)) + otl.insertTx(createTx([]byte("tx2"), "sender", 20)) + otl.insertTx(createTx([]byte("tx3"), "sender", 30)) + + // Should return index of first tx with nonce >= 15 (which is tx2 at index 1) + require.Equal(t, 1, otl.findIndexByNonce(15)) + // Should return index of first tx with nonce >= 25 (which is tx3 at index 2) + require.Equal(t, 2, otl.findIndexByNonce(25)) + }) + + t.Run("nonce below all", func(t *testing.T) { + otl := newOrderedTransactionsList() + otl.insertTx(createTx([]byte("tx1"), "sender", 10)) + otl.insertTx(createTx([]byte("tx2"), "sender", 20)) + + require.Equal(t, 0, otl.findIndexByNonce(5)) + }) + + t.Run("nonce above all", func(t *testing.T) { + otl := newOrderedTransactionsList() + otl.insertTx(createTx([]byte("tx1"), "sender", 10)) + otl.insertTx(createTx([]byte("tx2"), "sender", 20)) + + require.Equal(t, 2, otl.findIndexByNonce(100)) + }) +} + +func TestOrderedTransactionsList_FindInsertionIndex(t *testing.T) { + t.Run("empty list", func(t *testing.T) { + otl := newOrderedTransactionsList() + tx := createTx([]byte("tx"), "sender", 10) + require.Equal(t, 0, otl.findInsertionIndex(tx)) + }) + + t.Run("insert at end", func(t *testing.T) { + otl := newOrderedTransactionsList() + otl.insertTx(createTx([]byte("tx1"), "sender", 10)) + otl.insertTx(createTx([]byte("tx2"), "sender", 20)) + + tx := createTx([]byte("tx3"), "sender", 30) + require.Equal(t, 2, otl.findInsertionIndex(tx)) + }) + + t.Run("insert at beginning", func(t *testing.T) { + otl := newOrderedTransactionsList() + otl.insertTx(createTx([]byte("tx1"), "sender", 20)) + otl.insertTx(createTx([]byte("tx2"), "sender", 30)) + + tx := createTx([]byte("tx3"), "sender", 10) + require.Equal(t, 0, otl.findInsertionIndex(tx)) + }) + + t.Run("insert in middle", func(t *testing.T) { + otl := newOrderedTransactionsList() + otl.insertTx(createTx([]byte("tx1"), "sender", 10)) + otl.insertTx(createTx([]byte("tx2"), "sender", 30)) + + tx := createTx([]byte("tx3"), "sender", 20) + require.Equal(t, 1, otl.findInsertionIndex(tx)) + }) +} + +func TestOrderedTransactionsList_InsertAt(t *testing.T) { + t.Run("insert at beginning", func(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 20) + otl.insertTx(tx1) + + tx2 := createTx([]byte("tx2"), "sender", 10) + index := otl.findInsertionIndex(tx2) + require.Equal(t, 0, index) + + added := otl.insertAt(tx2, index) + require.True(t, added) + require.Equal(t, 2, otl.len()) + require.Equal(t, tx2, otl.get(0)) + require.Equal(t, tx1, otl.get(1)) + }) + + t.Run("insert at end", func(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 10) + otl.insertTx(tx1) + + tx2 := createTx([]byte("tx2"), "sender", 20) + index := otl.findInsertionIndex(tx2) + require.Equal(t, 1, index) + + added := otl.insertAt(tx2, index) + require.True(t, added) + require.Equal(t, 2, otl.len()) + require.Equal(t, tx1, otl.get(0)) + require.Equal(t, tx2, otl.get(1)) + }) + + t.Run("insert in middle", func(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 10) + tx3 := createTx([]byte("tx3"), "sender", 30) + otl.insertTx(tx1) + otl.insertTx(tx3) + + tx2 := createTx([]byte("tx2"), "sender", 20) + index := otl.findInsertionIndex(tx2) + require.Equal(t, 1, index) + + added := otl.insertAt(tx2, index) + require.True(t, added) + require.Equal(t, 3, otl.len()) + require.Equal(t, tx1, otl.get(0)) + require.Equal(t, tx2, otl.get(1)) + require.Equal(t, tx3, otl.get(2)) + }) + + t.Run("reject duplicate", func(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 10) + otl.insertTx(tx1) + + // Same transaction (duplicate) + tx1Dup := createTx([]byte("tx1"), "sender", 10) + index := otl.findInsertionIndex(tx1Dup) + require.Equal(t, 0, index) + + added := otl.insertAt(tx1Dup, index) + require.False(t, added) + require.Equal(t, 1, otl.len()) + }) + + t.Run("insert into empty list", func(t *testing.T) { + otl := newOrderedTransactionsList() + tx1 := createTx([]byte("tx1"), "sender", 10) + + index := otl.findInsertionIndex(tx1) + require.Equal(t, 0, index) + + added := otl.insertAt(tx1, index) + require.True(t, added) + require.Equal(t, 1, otl.len()) + require.Equal(t, tx1, otl.get(0)) + }) +} diff --git a/txcache/selection.go b/txcache/selection.go new file mode 100644 index 00000000000..cf212b673cc --- /dev/null +++ b/txcache/selection.go @@ -0,0 +1,210 @@ +package txcache + +import ( + "container/heap" + "time" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/process" +) + +func (cache *TxCache) doSelectTransactions(virtualSession *virtualSelectionSession, options common.TxSelectionOptions) (bunchOfTransactions, uint64) { + bunches := cache.acquireBunchesOfTransactions() + + return selectTransactionsFromBunches(virtualSession, bunches, options, cache.propagationGracePeriod) +} + +func (cache *TxCache) acquireBunchesOfTransactions() []bunchOfTransactions { + senders := cache.getSenders() + bunches := make([]bunchOfTransactions, 0, len(senders)) + + for _, sender := range senders { + bunch := sender.getTxsForSelection() + if len(bunch) > 0 { + bunches = append(bunches, bunch) + } + } + + return bunches +} + +// Selection tolerates concurrent transaction additions / removals. +func selectTransactionsFromBunches( + virtualSession *virtualSelectionSession, + bunches []bunchOfTransactions, + options common.TxSelectionOptions, + propagationGracePeriod time.Duration, +) (bunchOfTransactions, uint64) { + gasRequested := options.GetGasRequested() + maxNumTxs := options.GetMaxNumTxs() + loopDurationCheckInterval := options.GetLoopDurationCheckInterval() + + logSelect.Debug("TxCache.selectTransactionsFromBunches", + "len(bunches)", len(bunches), + "gasRequested", gasRequested, + "maxNumTxs", maxNumTxs, + "loopDurationCheckInterval", loopDurationCheckInterval, + ) + + selectedTransactions := make(bunchOfTransactions, 0, initialCapacityOfSelectionSlice) + + // Items popped from the heap are added to "selectedTransactions". + transactionsHeap := newMaxTransactionsHeap(len(bunches)) + heap.Init(transactionsHeap) + + // Initialize the heap with the first transaction of each bunch + for _, bunch := range bunches { + item, err := newTransactionsHeapItem(bunch) + if err != nil { + continue + } + + // Items will be reused (see below). Each sender gets one (and only one) item in the heap. + heap.Push(transactionsHeap, item) + } + + accumulatedGas := uint64(0) + selectionLoopStartTime := time.Now() + selectionCutoff := selectionLoopStartTime.Add(-propagationGracePeriod) + uniqueSenderCount := 0 + + var currentTransaction *WrappedTransaction + var processedTxs int + // Select transactions (sorted). + for transactionsHeap.Len() > 0 { + processedTxs++ + // Always pick the best transaction. + item := heap.Pop(transactionsHeap).(*transactionsHeapItem) + gasLimit := item.currentTransaction.Tx.GetGasLimit() + + if accumulatedGas+gasLimit > gasRequested { + break + } + if len(selectedTransactions) >= maxNumTxs { + break + } + if processedTxs%loopDurationCheckInterval == 0 { + if !options.HaveTimeForSelection() { + logSelect.Debug("TxCache.selectTransactionsFromBunches, selection loop timeout", "duration", time.Since(selectionLoopStartTime)) + break + } + } + + // Skip transactions that haven't had enough time to propagate + if propagationGracePeriod > 0 && item.currentTransaction.ReceivedAt.After(selectionCutoff) { + continue + } + + senderRecord, err := virtualSession.getRecord(item.sender) + if err != nil { + log.Debug("TxCache.selectTransactionsFromBunches when getting the virtual record of sender", "err", err, + "address", item.sender) + continue + } + + shouldSkipSender := detectSkippableSender(virtualSession, item, senderRecord) + if shouldSkipSender { + // Item was popped from the heap, but not used downstream. + // Therefore, the sender is completely ignored (from now on) in the current selection session. + continue + } + + shouldSkipTransaction := detectSkippableTransaction(virtualSession, item, senderRecord) + if !shouldSkipTransaction { + // first, we get the transaction that might be selected + currentTransaction = item.getCurrentTransaction() + err = virtualSession.accumulateConsumedBalance(currentTransaction, senderRecord) + if err != nil { + // This error is unlikely to occur, as it would have been raised earlier during the detectSkippableSender call. + // However, we should not select the transaction if anything fails here. + log.Warn("TxCache.selectTransactionsFromBunches error when accumulating consumed balance", + "err", err, + "txHash", currentTransaction.TxHash) + } else { + // Track unique senders to match OnProposedBlock's maxAccountsPerBlock limit. + isNewSender := item.latestSelectedTransaction == nil + if isNewSender { + uniqueSenderCount++ + if uniqueSenderCount > maxAccountsPerBlock { + logSelect.Debug("TxCache.selectTransactionsFromBunches, unique sender limit reached", + "limit", maxAccountsPerBlock) + break + } + } + + // only if there isn't any error, we select the transaction + accumulatedGas += gasLimit + item.selectCurrentTransaction() + selectedTransactions = append(selectedTransactions, currentTransaction) + } + } + + // If there are more transactions in the same bunch (same sender as the popped item), + // add the next one to the heap (to compete with the others). + // Heap item is reused (same originating sender), pushed back on the heap. + if item.gotoNextTransaction() { + heap.Push(transactionsHeap, item) + } + } + + return selectedTransactions, accumulatedGas +} + +// Note (future micro-optimization): we can merge "detectSkippableSender()" and "detectSkippableTransaction()" into a single function, +// any share the result of "sessionWrapper.getNonceForAccountRecord()". +func detectSkippableSender(virtualSession *virtualSelectionSession, item *transactionsHeapItem, virtualRecord *virtualAccountRecord) bool { + nonce, err := virtualRecord.getInitialNonce() + if err != nil { + // This error is expected for accounts with discontinuous global breadcrumbs, + // which get a blocked virtual record (initialNonce.HasValue=false). + // In this case, the sender is correctly skipped to avoid selecting transactions + // that would fail OnProposedBlock validation. + log.Debug("detectSkippableSender", "err", err) + return true + } + + if virtualRecord.hasPendingChangeGuardian() { + return true + } + if item.detectInitialGap(nonce) { + return true + } + if item.detectMiddleGap() { + return true + } + if virtualSession.detectWillBalanceBeExceeded(item.currentTransaction) { + return true + } + + isSetGuardianCall := process.IsSetGuardianCall(item.currentTransaction.Tx.GetData()) + if !isSetGuardianCall { + return false + } + + // if an instant change guardian is detected, further transactions for this sender should be skipped as they are probably guarded by the old one + // allow this one though + virtualSession.setChangeGuardianIfNeeded(item.currentTransaction.Tx) + + return false +} + +func detectSkippableTransaction(virtualSession *virtualSelectionSession, item *transactionsHeapItem, virtualRecord *virtualAccountRecord) bool { + nonce, err := virtualRecord.getInitialNonce() + if err != nil { + // This error is expected for accounts with discontinuous global breadcrumbs, + // which get a blocked virtual record (initialNonce.HasValue=false). + log.Debug("detectSkippableTransaction", "err", err) + return true + } + if item.detectLowerNonce(nonce) { + return true + } + if item.detectIncorrectlyGuarded(virtualSession) { + return true + } + if item.detectNonceDuplicate() { + return true + } + + return false +} diff --git a/txcache/selectionTracker.go b/txcache/selectionTracker.go new file mode 100644 index 00000000000..179f23bb745 --- /dev/null +++ b/txcache/selectionTracker.go @@ -0,0 +1,810 @@ +package txcache + +import ( + "bytes" + "sort" + "sync" + + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/block" + logger "github.com/multiversx/mx-chain-logger-go" + "golang.org/x/exp/slices" + + "github.com/multiversx/mx-chain-go/common" +) + +const ( + maxAccountsPerBlock = 12000 +) + +type blockWithHash struct { + hash string + block *trackedBlock +} + +type selectionTracker struct { + mutTracker sync.RWMutex + latestNonce uint64 // last OnExecuted nonce + latestRootHash []byte // last OnExecuted rootHash + blocks map[string]*trackedBlock + globalBreadcrumbsCompiler *globalAccountBreadcrumbsCompiler + txCache txCacheForSelectionTracker + selfShardId uint32 + maxTrackedBlocks uint32 + aotSelectionPreempter common.AOTSelectionPreempter +} + +// NewSelectionTracker creates a new selectionTracker +func NewSelectionTracker( + txCache txCacheForSelectionTracker, + selfShardId uint32, + maxTrackedBlocks uint32, +) (*selectionTracker, error) { + if check.IfNil(txCache) { + return nil, errNilTxCache + } + if maxTrackedBlocks == 0 { + return nil, errInvalidMaxTrackedBlocks + } + return &selectionTracker{ + mutTracker: sync.RWMutex{}, + blocks: make(map[string]*trackedBlock), + globalBreadcrumbsCompiler: newGlobalAccountBreadcrumbsCompiler(), + txCache: txCache, + selfShardId: selfShardId, + maxTrackedBlocks: maxTrackedBlocks, + }, nil +} + +// OnProposedBlock notifies when a block is proposed and updates the state of the selectionTracker. +// blockHash is the hash of the new proposed block. +// blockBody contains the transactions of the new block (required for creating the breadcrumbs and validating the block). +// blockHeader contains the nonce, the rootHash and the previousHash of the new proposed block. +// accountsProvider is a wrapper over the current blockchain state. +// latestExecutedHash represents the hash of the last executed block. +func (st *selectionTracker) OnProposedBlock( + blockHash []byte, + bodyHandler data.BodyHandler, + blockHeader data.HeaderHandler, + accountsProvider common.AccountNonceAndBalanceProvider, + latestExecutedHash []byte, +) error { + blockBody, ok := bodyHandler.(*block.Body) + if !ok { + return errWrongTypeAssertion + } + + err := st.verifyBlockArgs(blockHash, blockBody, blockHeader) + if err != nil { + return err + } + if check.IfNil(accountsProvider) { + return errNilAccountNonceAndBalanceProvider + } + + accountsRootHash, err := accountsProvider.GetRootHash() + if err != nil { + return err + } + + // Preempt any ongoing AOT selection before acquiring the lock + st.cancelAOTOngoingSelection() + + st.mutTracker.Lock() + defer st.mutTracker.Unlock() + + err = st.checkUniqueAccountsLimit(blockBody) + if err != nil { + return err + } + + if !bytes.Equal(st.latestRootHash, accountsRootHash) { + log.Error("selectionTracker.OnProposedBlock", + "err", errRootHashMismatch, + "latestRootHash", st.latestRootHash, + "accountsRootHash", accountsRootHash, + ) + return errRootHashMismatch + } + + nonce := blockHeader.GetNonce() + prevHash := blockHeader.GetPrevHash() + + tBlock := newTrackedBlock(nonce, blockHash, prevHash) + + log.Debug("selectionTracker.OnProposedBlock", + "nonce", nonce, + "blockHash", blockHash, + "prevHash", prevHash, + "latestRootHash", st.latestRootHash, + ) + + err = st.checkReceivedBlockNoLock(blockBody, blockHeader) + if err != nil { + log.Debug("selectionTracker.OnProposedBlock: error checking the received block", "err", err) + return err + } + + lastNoncePerSender, err := st.validateTrackedBlocksAndCompileBreadcrumbsNoLock(blockBody, tBlock, accountsProvider, latestExecutedHash) + if err != nil { + log.Debug("selectionTracker.OnProposedBlock: error validating the tracked blocks", "err", err) + return err + } + + err = st.addNewTrackedBlockNoLock(blockHash, tBlock) + if err != nil { + log.Debug("selectionTracker.OnProposedBlock: error adding the new tracked block", "err", err) + return err + } + + // Set selection offsets to skip transactions up to and including the last proposed nonce per sender + // This skips already-proposed transactions during future selections + st.txCache.SetSelectionOffsetsByLastNonce(lastNoncePerSender) + + return nil +} + +// OnBackfilledBlock adds a previously consensus-agreed block as tracked without breadcrumb validation. +// This is used during sync start to backfill blocks between the last execution result and the current +// committed header. Since these blocks were already validated during consensus, breadcrumb continuity +// and balance checks are skipped. The block's breadcrumbs are still compiled and tracked. +func (st *selectionTracker) OnBackfilledBlock( + blockHash []byte, + bodyHandler data.BodyHandler, + blockHeader data.HeaderHandler, +) error { + blockBody, ok := bodyHandler.(*block.Body) + if !ok { + return errWrongTypeAssertion + } + + err := st.verifyBlockArgs(blockHash, blockBody, blockHeader) + if err != nil { + return err + } + + // Preempt any ongoing AOT selection before acquiring the lock + st.cancelAOTOngoingSelection() + + st.mutTracker.Lock() + defer st.mutTracker.Unlock() + + nonce := blockHeader.GetNonce() + prevHash := blockHeader.GetPrevHash() + + tBlock := newTrackedBlock(nonce, blockHash, prevHash) + + log.Debug("selectionTracker.OnBackfilledBlock", + "nonce", nonce, + "blockHash", blockHash, + "prevHash", prevHash, + ) + + txs, err := getTransactionsInBlock(blockBody, st.txCache, st.selfShardId) + if err != nil { + return err + } + + lastNoncePerSender, err := tBlock.compileBreadcrumbs(txs) + if err != nil { + return err + } + + err = st.addNewTrackedBlockNoLock(blockHash, tBlock) + if err != nil { + return err + } + + st.txCache.SetSelectionOffsetsByLastNonce(lastNoncePerSender) + + return nil +} + +func (st *selectionTracker) verifyBlockArgs( + blockHash []byte, + blockBody *block.Body, + blockHeader data.HeaderHandler, +) error { + if len(blockHash) == 0 { + return errNilBlockHash + } + if check.IfNil(blockBody) { + return errNilBlockBody + } + if check.IfNil(blockHeader) { + return errNilBlockHeader + } + + return nil +} + +// checkReceivedBlockNoLock first checks if MaxTrackedBlocks is reached. +// If MaxTrackedBlocks is reached, the received block must either have an empty body or contain new execution results. +func (st *selectionTracker) checkReceivedBlockNoLock(blockBody *block.Body, blockHeader data.HeaderHandler) error { + if len(st.blocks) < int(st.maxTrackedBlocks) { + return nil + } + + hasNewTransactions := len(blockBody.MiniBlocks) != 0 + hasNoNewExecutionResults := len(blockHeader.GetExecutionResultsHandlers()) == 0 + + // should receive empty block or a block with new execution results + if hasNewTransactions && hasNoNewExecutionResults { + log.Warn("selectionTracker.checkReceivedBlockNoLock: received non-tolerated block while max tracked blocks is reached. "+ + "len(st.blocks)", len(st.blocks), + ) + + return errBadBlockWhileMaxTrackedBlocksReached + } + + // received an empty block or a block with new execution results + log.Warn("selectionTracker.checkReceivedBlockNoLock: max tracked blocks reached "+ + "but received a tolerated block", + "len(st.blocks)", len(st.blocks), + "nonce", blockHeader.GetNonce()) + + return nil +} + +// validateTrackedBlocksAndCompileBreadcrumbsNoLock is used when a new block is proposed. +// Firstly, the method finds the chain of tracked blocks. +// Secondly, the method extracts the transaction of the new block, compiles its breadcrumbs and adds the new block to the previous returned chain. +// Then, it validates the entire chain (by nonce and balance of each breadcrumb). +// Returns lastNoncePerSender map for updating selection offsets. +func (st *selectionTracker) validateTrackedBlocksAndCompileBreadcrumbsNoLock( + blockBody *block.Body, + blockToTrack *trackedBlock, + accountsProvider common.AccountNonceAndBalanceProvider, + latestExecutedHash []byte, +) (map[string]uint64, error) { + blocksToBeValidated, err := st.getChainOfTrackedPendingBlocks( + latestExecutedHash, + blockToTrack.prevHash, + blockToTrack.nonce, + ) + if err != nil { + log.Debug("selectionTracker.validateTrackedBlocksAndCompileBreadcrumbsNoLock: error creating chain of tracked blocks", "err", err) + return nil, err + } + + // if we pass the first validation, only then we extract the txs to compile the breadcrumbs + txs, err := getTransactionsInBlock(blockBody, st.txCache, st.selfShardId) + if err != nil { + log.Debug("selectionTracker.validateTrackedBlocksAndCompileBreadcrumbsNoLock: error getting transactions from block", "err", err) + return nil, err + } + + lastNoncePerSender, err := blockToTrack.compileBreadcrumbs(txs) + if err != nil { + log.Debug("selectionTracker.validateTrackedBlocksAndCompileBreadcrumbsNoLock: error compiling breadcrumbs", + "error", err) + return nil, err + } + + // add the new block in the returned chain + blocksToBeValidated = append(blocksToBeValidated, blockToTrack) + + // make sure that the breadcrumbs of the proposed block are valid + // i.e. continuous with the other proposed blocks and no balance issues + err = st.validateBreadcrumbsOfTrackedBlocks(blocksToBeValidated, accountsProvider) + if err != nil { + log.Debug("selectionTracker.validateTrackedBlocksAndCompileBreadcrumbsNoLock: error validating tracked blocks", "err", err) + return nil, err + } + + return lastNoncePerSender, nil +} + +// validateBreadcrumbsOfTrackedBlocks validates the breadcrumbs of each tracked block. +// For predecessor blocks (all except the last), discontinuous breadcrumbs are tolerated: +// the address is marked as discontinuous and its nonce/balance validation is skipped. +// For the new block (the last one), if it has breadcrumbs for an address that was marked +// as discontinuous, validation fails - the new block must not include transactions +// for accounts with stale/discontinuous history. +func (st *selectionTracker) validateBreadcrumbsOfTrackedBlocks( + chainOfTrackedBlocks []*trackedBlock, + accountsProvider common.AccountNonceAndBalanceProvider, +) error { + validator := newBreadcrumbValidator() + + numBlocks := len(chainOfTrackedBlocks) + + for i, tb := range chainOfTrackedBlocks { + isNewBlock := i == numBlocks-1 + + for address, breadcrumb := range tb.breadcrumbsByAddress { + if isNewBlock && validator.isAddressDiscontinuous(address) { + // The new block has transactions for an account with discontinuous breadcrumbs + // in predecessor blocks. This must be rejected. + log.Debug("selectionTracker.validateBreadcrumbsOfTrackedBlocks: new block has breadcrumb for discontinuous address", + "err", errDiscontinuousBreadcrumbs, + "address", address, + "tracked block hash", tb.hash, + "tracked block nonce", tb.nonce) + return errDiscontinuousBreadcrumbs + } + + // NOTE: the initial balance should never change during this validation, because we use an account provider which is not affected by the actual execution. + initialNonce, initialBalance, _, err := accountsProvider.GetAccountNonceAndBalance([]byte(address)) + if err != nil { + log.Debug("selectionTracker.validateBreadcrumbsOfTrackedBlocks", + "err", err, + "address", address, + "tracked block hash", tb.hash, + "tracked block nonce", tb.nonce) + return err + } + + if !validator.validateNonceContinuityOfBreadcrumb(address, initialNonce, breadcrumb) { + if isNewBlock { + // The new block itself has discontinuous breadcrumbs - reject + log.Debug("selectionTracker.validateBreadcrumbsOfTrackedBlocks: new block has discontinuous breadcrumbs", + "err", errDiscontinuousBreadcrumbs, + "address", address, + "tracked block hash", tb.hash, + "tracked block nonce", tb.nonce) + return errDiscontinuousBreadcrumbs + } + + // Predecessor block has discontinuous breadcrumbs - tolerate, mark address + log.Debug("selectionTracker.validateBreadcrumbsOfTrackedBlocks: tolerating discontinuous breadcrumbs in predecessor block", + "address", address, + "tracked block hash", tb.hash, + "tracked block nonce", tb.nonce) + validator.markAddressAsDiscontinuous(address) + continue + } + + // Skip balance validation for addresses already marked as discontinuous. + // This is safe because predecessor blocks were already validated at their original proposal time. + if validator.isAddressDiscontinuous(address) { + continue + } + + // use its balance to accumulate and validate (make sure is < than initialBalance from the session) + err = validator.validateBalance(address, initialBalance, breadcrumb) + if err != nil { + // exit at the first failure + log.Debug("selectionTracker.validateBreadcrumbsOfTrackedBlocks validation failed", + "err", err, + "address", address, + "tracked block hash", tb.hash, + "tracked block nonce", tb.nonce) + return err + } + } + } + + return nil +} + +// addNewTrackedBlockNoLock adds a new tracked block into the map of tracked blocks, +// replaces an existing block which has the same nonce with the one received. +func (st *selectionTracker) addNewTrackedBlockNoLock(blockToBeAddedHash []byte, blockToBeAdded *trackedBlock) error { + if blockToBeAdded == nil { + return errNilTrackedBlock + } + + // remove all the blocks with nonce equal or above the given nonce + err := st.removeBlocksAboveOrEqualToNonceNoLock(blockToBeAdded.nonce) + if err != nil { + return err + } + + // add the new block + st.blocks[string(blockToBeAddedHash)] = blockToBeAdded + st.globalBreadcrumbsCompiler.updateOnAddedBlock(blockToBeAdded) + + return nil +} + +// OnExecutedBlock notifies when a block is executed and updates the state of the selectionTracker +// by removing each tracked block with nonce equal or lower than the one received in the blockHeader. +func (st *selectionTracker) OnExecutedBlock(blockHeader data.HeaderHandler, rootHash []byte) error { + if check.IfNil(blockHeader) { + return errNilBlockHeader + } + + nonce := blockHeader.GetNonce() + prevHash := blockHeader.GetPrevHash() + + log.Debug("selectionTracker.OnExecutedBlock", + "nonce", nonce, + "rootHash", rootHash, + "prevHash", prevHash, + ) + + tempTrackedBlock := newTrackedBlock(nonce, nil, prevHash) + + // Preempt any ongoing AOT selection before acquiring the lock + st.cancelAOTOngoingSelection() + + st.mutTracker.Lock() + defer st.mutTracker.Unlock() + + err := st.removeUpToBlockNoLock(tempTrackedBlock) + if err != nil { + return err + } + + st.updateLatestRootHashNoLock(nonce, rootHash) + return nil +} + +// removeUpToBlockNoLock removes all the tracked blocks with nonce equal or lower than the given nonce. +// The removeUpToBlockNoLock is called on the OnExecutedBlock flow. +// Blocks are sorted by ascending nonce before processing to ensure correct global breadcrumb updates. +func (st *selectionTracker) removeUpToBlockNoLock(searchedBlock *trackedBlock) error { + // Collect matching blocks first + var matchingBlocks []blockWithHash + for blockHash, b := range st.blocks { + if b.hasSameNonceOrLower(searchedBlock) { + matchingBlocks = append(matchingBlocks, blockWithHash{hash: blockHash, block: b}) + } + } + + // Sort by ascending nonce to ensure correct global breadcrumb updates + sort.Slice(matchingBlocks, func(i, j int) bool { + return matchingBlocks[i].block.nonce < matchingBlocks[j].block.nonce + }) + + // Process in sorted order + for _, mb := range matchingBlocks { + // first delete, then update the global breadcrumbs + delete(st.blocks, mb.hash) + + err := st.globalBreadcrumbsCompiler.updateOnRemovedBlockWithSameNonceOrBelow(mb.block) + if err != nil { + return err + } + } + + log.Trace("selectionTracker.removeUpToBlockNoLock", + "searched block nonce", searchedBlock.nonce, + "searched block hash", searchedBlock.hash, + "searched block prevHash", searchedBlock.prevHash, + "removed blocks", len(matchingBlocks), + ) + + return nil +} + +func (st *selectionTracker) updateLatestRootHashNoLock(receivedNonce uint64, receivedRootHash []byte) { + log.Debug("selectionTracker.updateLatestRootHashNoLock", + "latest root hash", st.latestRootHash, + "received root hash", receivedRootHash, + "latest nonce", st.latestNonce, + "received nonce", receivedNonce, + ) + + if st.latestRootHash == nil { + st.latestRootHash = receivedRootHash + st.latestNonce = receivedNonce + return + } + + if st.latestNonce >= receivedNonce { + log.Debug("selectionTracker.updateLatestRootHashNoLock received a lower or equal nonce than the latest nonce") + return + } + + st.latestRootHash = receivedRootHash + st.latestNonce = receivedNonce +} + +func (st *selectionTracker) checkUniqueAccountsLimit(blockBody *block.Body) error { + txsInBlock, err := getTransactionsInBlock(blockBody, st.txCache, st.selfShardId) + if err != nil { + return nil + } + + uniqueAccounts := make(map[string]struct{}) + for _, tx := range txsInBlock { + uniqueAccounts[string(tx.Tx.GetSndAddr())] = struct{}{} + if len(uniqueAccounts) > maxAccountsPerBlock { + log.Warn("selectionTracker.OnProposedBlock: too many unique accounts in block", + "count", len(uniqueAccounts), + "limit", maxAccountsPerBlock) + return errToManyUniqueAccountsInBlock + } + } + + return nil +} + +// ResetTrackedBlocks resets the tracked blocks, the global account breadcrumbs and the state saved on the OnExecutedBlock. +func (st *selectionTracker) ResetTrackedBlocks() { + st.mutTracker.Lock() + defer st.mutTracker.Unlock() + + log.Debug("selectionTracker.ResetTrackedBlocks removing all tracked blocks", + "len(trackedBlocks)", len(st.blocks), + ) + + st.latestRootHash = nil + st.latestNonce = 0 + + st.blocks = make(map[string]*trackedBlock) + st.globalBreadcrumbsCompiler.cleanGlobalBreadcrumbs() +} + +func (st *selectionTracker) canDoSimulateSelection(nonce uint64) bool { + // nonce 0 will select over current tracker state + if nonce == 0 { + return true + } + + lastTrackedBlockNonce := st.latestNonce + // drop the selection if not matching the tracker state + for _, tb := range st.blocks { + if tb.nonce > lastTrackedBlockNonce { + lastTrackedBlockNonce = tb.nonce + } + } + + return nonce == lastTrackedBlockNonce+1 +} + +// deriveVirtualSelectionSession creates a virtual selection session by transforming the global accounts breadcrumbs into virtual records +// The deriveVirtualSelectionSession methods needs a SelectionSession and the nonce of the block for which the selection is built. +// Before the actual selection, all tracked blocks with greater or equal nonce are removed from the tracker. +func (st *selectionTracker) deriveVirtualSelectionSession( + session SelectionSession, + nonce uint64, + isSimulation bool, +) (*virtualSelectionSession, error) { + st.mutTracker.Lock() + defer st.mutTracker.Unlock() + + if !isSimulation { + err := st.removeBlocksAboveOrEqualToNonceNoLock(nonce) + if err != nil { + return nil, err + } + } else if !st.canDoSimulateSelection(nonce) { + return nil, errSimulateSelectionContextInvalid + } + + rootHash, err := session.GetRootHash() + if err != nil { + log.Debug("selectionTracker.deriveVirtualSelectionSession", + "err", err) + return nil, err + } + + if !bytes.Equal(st.latestRootHash, rootHash) { + log.Error("selectionTracker.deriveVirtualSelectionSession", + "err", errRootHashMismatch, + "latestRootHash", st.latestRootHash, + "session rootHash", rootHash, + ) + return nil, errRootHashMismatch + } + + log.Debug("selectionTracker.deriveVirtualSelectionSession", + "rootHash", rootHash, + "nonce", nonce, + ) + + st.displayTrackedBlocks(log, "trackedBlocks") + + computer := newVirtualSessionComputer(session) + + globalAccountsBreadcrumbs := st.getGlobalAccountsBreadcrumbs() + return computer.createVirtualSelectionSession(globalAccountsBreadcrumbs) +} + +// removeBlocksAboveOrEqualToNonceNoLock removes blocks with nonce higher or equal than the given nonce. +// The removeBlocksAboveOrEqualToNonceNoLock is used on the deriveVirtualSelectionSession flow. +// Blocks are sorted by descending nonce before processing to ensure correct global breadcrumb updates. +func (st *selectionTracker) removeBlocksAboveOrEqualToNonceNoLock(nonce uint64) error { + // Collect matching blocks first + var matchingBlocks []blockWithHash + for blockHash, tb := range st.blocks { + if tb.hasSameNonceOrHigherThanGivenNonce(nonce) { + matchingBlocks = append(matchingBlocks, blockWithHash{hash: blockHash, block: tb}) + } + } + + log.Trace("selectionTracker.removeBlocksAboveOrEqualToNonceNoLock", + "nonce", nonce, + "num blocks to remove", len(matchingBlocks), + ) + + return st.deleteMatchedBlocksWithSameNonceOrAboveNoLock(matchingBlocks) +} + +// deleteMatchedBlocksWithSameNonceOrAboveNoLock sorts the matched blocks by descending nonce, +// deletes them from the tracked blocks map, updates the global breadcrumbs, and resets selection offsets. +// The descending sort order ensures global breadcrumbs are reduced correctly (highest nonce first). +func (st *selectionTracker) deleteMatchedBlocksWithSameNonceOrAboveNoLock(matchingBlocks []blockWithHash) error { + // Sort by descending nonce to ensure correct global breadcrumb updates + sort.Slice(matchingBlocks, func(i, j int) bool { + return matchingBlocks[i].block.nonce > matchingBlocks[j].block.nonce + }) + + sendersWithFirstNonce := make(map[string]uint64) + + for _, mb := range matchingBlocks { + // Collect senders and their first nonce from removed blocks for offset reset + for address, breadcrumb := range mb.block.breadcrumbsByAddress { + if breadcrumb.firstNonce.HasValue { + // Keep the lowest first nonce for each sender across all removed blocks + if existingNonce, exists := sendersWithFirstNonce[address]; !exists || breadcrumb.firstNonce.Value < existingNonce { + sendersWithFirstNonce[address] = breadcrumb.firstNonce.Value + } + } + } + + // first delete, then update the global breadcrumbs + delete(st.blocks, mb.hash) + + err := st.globalBreadcrumbsCompiler.updateOnRemovedBlockWithSameNonceOrAbove(mb.block) + if err != nil { + return err + } + + log.Trace("selectionTracker.deleteMatchedBlocksWithSameNonceOrAboveNoLock", + "nonce of deleted block", mb.block.nonce, + "hash of deleted block", mb.hash, + ) + } + + // Reset selection offsets for affected senders so their transactions can be re-selected + if len(sendersWithFirstNonce) > 0 { + st.txCache.ResetSelectionOffsetsToNonce(sendersWithFirstNonce) + } + + return nil +} + +// getChainOfTrackedPendingBlocks finds the chain of tracked blocks, iterating from tail to head, +// following the previous hash of each block, in order to avoid fork scenarios. +// The iteration stops when the previous hash of a block is equal to latestExecutedBlockHash. +func (st *selectionTracker) getChainOfTrackedPendingBlocks( + latestExecutedBlockHash []byte, + previousHashToBeFound []byte, + nonceOfNextBlock uint64, +) ([]*trackedBlock, error) { + chain := make([]*trackedBlock, 0) + + // If the previous hash to be found is equal to the latest executed hash, + // it means that we do not have any tracked proposed block on top. + // The block found would be the actual executed block, but that one is not tracked anymore. + if bytes.Equal(latestExecutedBlockHash, previousHashToBeFound) { + return chain, nil + } + + // search for the block with the hash equal to the previous hash. + // NOTE: we expect a nil value for a key (block hash) which is not in the map of tracked blocks. + previousBlock := st.blocks[string(previousHashToBeFound)] + + for { + if nonceOfNextBlock == 0 { + // should never actually happen (e.g. genesis) + break + } + + // if no block was found, it means there is a gap and we have to return an error + if previousBlock == nil { + log.Debug("getChainOfTrackedPendingBlocks: hash not found", + "previousHashToBeFound", previousHashToBeFound, + ) + return nil, errBlockNotFound + } + + // extra check for a block gap, to assure there are no missing tracked blocks + hasDiscontinuousBlockNonce := previousBlock.nonce != nonceOfNextBlock-1 + if hasDiscontinuousBlockNonce { + return nil, errDiscontinuousSequenceOfBlocks + } + + // if the block passes the validation, add it to the returned chain + chain = append(chain, previousBlock) + + // move backwards in the chain and check if the head was reached + previousBlockHash := previousBlock.prevHash + if bytes.Equal(latestExecutedBlockHash, previousBlockHash) { + break + } + + // update also the nonce + nonceOfNextBlock -= 1 + + // find the previous block + previousBlock = st.blocks[string(previousBlockHash)] + } + + // return the blocks in their natural order (from head to tail) + slices.Reverse(chain) + return chain, nil +} + +func (st *selectionTracker) getGlobalAccountsBreadcrumbs() map[string]*globalAccountBreadcrumb { + return st.globalBreadcrumbsCompiler.getGlobalBreadcrumbs() +} + +// getVirtualNonceOfAccountWithRootHash searches the global breadcrumb of the given address and return its nonce +func (st *selectionTracker) getVirtualNonceOfAccountWithRootHash( + address []byte, +) (uint64, []byte, error) { + breadcrumb, err := st.globalBreadcrumbsCompiler.getGlobalBreadcrumbByAddress(string(address)) + if err != nil { + return 0, nil, errGlobalBreadcrumbDoesNotExist + } + + if !breadcrumb.lastNonce.HasValue { + return 0, nil, errLastNonceNotFound + } + + return breadcrumb.lastNonce.Value + 1, st.latestRootHash, nil +} + +// IsTransactionTracked checks if a transaction is still in the tracked blocks of the SelectionTracker. +// However, in the case of forks, IsTransactionTracked might return inaccurate results. +func (st *selectionTracker) IsTransactionTracked(transaction *WrappedTransaction) bool { + if transaction == nil || transaction.Tx == nil { + return false + } + + sender := transaction.Tx.GetSndAddr() + txNonce := transaction.Tx.GetNonce() + + senderGlobalBreadcrumb, err := st.globalBreadcrumbsCompiler.getGlobalBreadcrumbByAddress(string(sender)) + if err != nil { + return false + } + + minNonce := senderGlobalBreadcrumb.firstNonce + maxNonce := senderGlobalBreadcrumb.lastNonce + + if !minNonce.HasValue || !maxNonce.HasValue { + // we consider the transaction as not tracked because the account is tracked only as a relayer + return false + } + + if txNonce < minNonce.Value || txNonce > maxNonce.Value { + // we consider the transaction as not tracked because it's outside the tracked range + return false + } + + return true +} + +// should be called under mutex protection +func (st *selectionTracker) displayTrackedBlocks(contextualLogger logger.Logger, linePrefix string) { + if contextualLogger.GetLevel() > logger.LogTrace { + return + } + + log.Debug("selectionTracker.deriveVirtualSelectionSession", + "len(trackedBlocks)", len(st.blocks)) + + if len(st.blocks) > 0 { + contextualLogger.Trace("displayTrackedBlocks - trackedBlocks (as newline-separated JSON):") + contextualLogger.Trace(marshalTrackedBlockToNewlineDelimitedJSON(st.blocks, linePrefix)) + } else { + contextualLogger.Trace("displayTrackedBlocks - trackedBlocks: none") + } +} + +// getTrackerDiagnosis returns the dimension of tracked blocks and the number of global account breadcrumbs +func (st *selectionTracker) getTrackerDiagnosis() TrackerDiagnosis { + st.mutTracker.RLock() + defer st.mutTracker.RUnlock() + + return NewTrackerDiagnosis(uint64(len(st.blocks)), st.globalBreadcrumbsCompiler.getNumGlobalBreadcrumbs()) +} + +// SetAOTSelectionPreempter sets the AOT selection preempter for preemption support +func (st *selectionTracker) SetAOTSelectionPreempter(preempter common.AOTSelectionPreempter) { + st.aotSelectionPreempter = preempter +} + +// cancelAOTOngoingSelection cancels any ongoing AOT selection before critical operations +func (st *selectionTracker) cancelAOTOngoingSelection() { + if !check.IfNil(st.aotSelectionPreempter) { + st.aotSelectionPreempter.CancelOngoingSelection() + } +} diff --git a/txcache/selectionTracker_test.go b/txcache/selectionTracker_test.go new file mode 100644 index 00000000000..f1f68953eac --- /dev/null +++ b/txcache/selectionTracker_test.go @@ -0,0 +1,2378 @@ +package txcache + +import ( + "errors" + "fmt" + "math" + "math/big" + "sync" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/common/holders" + "github.com/multiversx/mx-chain-go/testscommon/txcachemocks" +) + +func proposeBlocks(t *testing.T, numOfBlocks int, selectionTracker *selectionTracker, accountsProvider common.AccountNonceAndBalanceProvider) { + + for i := 1; i < numOfBlocks+1; i++ { + err := selectionTracker.OnProposedBlock( + []byte(fmt.Sprintf("hash%d", i)), + &block.Body{}, + &block.Header{ + Nonce: uint64(i), + PrevHash: []byte(fmt.Sprintf("hash%d", i-1)), + RootHash: []byte("rootHash0"), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + } +} + +func executeBlocksConcurrently(t *testing.T, numOfBlocks int, selectionTracker *selectionTracker) { + wg := sync.WaitGroup{} + wg.Add(numOfBlocks) + + for i := 1; i <= numOfBlocks; i++ { + go func(index int) { + defer wg.Done() + + rootHash := []byte("rootHash0") + + blockHeader := &block.Header{ + Nonce: uint64(index), + PrevHash: []byte(fmt.Sprintf("prevHash%d", index-1)), + } + err := selectionTracker.OnExecutedBlock(blockHeader, rootHash) + require.Nil(t, err) + }(i) + } + + wg.Wait() +} + +func TestNewSelectionTracker(t *testing.T) { + t.Parallel() + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + _, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + }) + + t.Run("should fail because of nil TxCache", func(t *testing.T) { + t.Parallel() + + tracker, err := NewSelectionTracker(nil, 0, maxTrackedBlocks) + require.Equal(t, errNilTxCache, err) + require.Nil(t, tracker) + }) + + t.Run("should fail because of maxTrackedBlocks", func(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, 0) + require.Equal(t, errInvalidMaxTrackedBlocks, err) + require.Nil(t, tracker) + }) +} + +func TestSelectionTracker_OnProposedBlockShouldErr(t *testing.T) { + t.Parallel() + + t.Run("should err nil block hash", func(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + err = tracker.OnProposedBlock(nil, &block.Body{}, nil, nil, nil) + require.Equal(t, errNilBlockHash, err) + }) + + t.Run("should err nil block header", func(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + blockBody := block.Body{} + err = tracker.OnProposedBlock([]byte("hash1"), &blockBody, nil, nil, nil) + require.Equal(t, errNilBlockHeader, err) + }) + + t.Run("should return errWrongTypeAssertion", func(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + err = tracker.OnProposedBlock([]byte("hash1"), nil, &block.Header{}, nil, defaultLatestExecutedHash) + + require.Equal(t, errWrongTypeAssertion, err) + }) + + t.Run("should err nil accounts provider", func(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + err = tracker.OnProposedBlock([]byte("hash1"), &block.Body{}, &block.Header{}, nil, nil) + require.Equal(t, errNilAccountNonceAndBalanceProvider, err) + }) + + t.Run("should return errNonceGap", func(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + txCache.txByHash.addTx(createTx([]byte("txHash1"), "alice", 1)) + txCache.txByHash.addTx(createTx([]byte("txHash2"), "alice", 5)) + + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + blockBody := block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("txHash1"), + []byte("txHash2"), + }, + }, + }, + } + + accountsProvider := &txcachemocks.AccountNonceAndBalanceProviderMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 1, big.NewInt(20), true, nil + }, + } + + err = tracker.OnProposedBlock([]byte("hash1"), &blockBody, &block.Header{ + Nonce: uint64(0), + PrevHash: []byte(fmt.Sprintf("prevHash%d", 0)), + RootHash: []byte(fmt.Sprintf("rootHash%d", 0)), + }, accountsProvider, defaultLatestExecutedHash) + + require.Equal(t, errNonceGap, err) + }) + + t.Run("should return errDiscontinuousBreadcrumbs", func(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + txCache.txByHash.addTx(createTx([]byte("txHash1"), "alice", 1)) + txCache.txByHash.addTx(createTx([]byte("txHash2"), "alice", 2)) + txCache.txByHash.addTx(createTx([]byte("txHash3"), "alice", 4)) + txCache.txByHash.addTx(createTx([]byte("txHash4"), "alice", 5)) + + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + blockBody1 := block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("txHash1"), + []byte("txHash2"), + }, + }, + }, + } + + blockBody2 := block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("txHash3"), + []byte("txHash4"), + }, + }, + }, + } + + accountsProvider := &txcachemocks.AccountNonceAndBalanceProviderMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 1, big.NewInt(20), true, nil + }, + } + + err = tracker.OnProposedBlock([]byte("hash1"), &blockBody1, &block.Header{ + Nonce: uint64(0), + PrevHash: []byte(fmt.Sprintf("prevHash%d", 0)), + RootHash: []byte(fmt.Sprintf("rootHash%d", 0)), + }, accountsProvider, defaultLatestExecutedHash) + require.Nil(t, err) + + err = tracker.OnProposedBlock([]byte("hash2"), &blockBody2, &block.Header{ + Nonce: uint64(1), + PrevHash: []byte(fmt.Sprintf("hash%d", 1)), + RootHash: []byte(fmt.Sprintf("rootHash%d", 0)), + }, accountsProvider, []byte("prevHash0")) + require.Equal(t, errDiscontinuousBreadcrumbs, err) + }) + + t.Run("should return errExceededBalance because of fees", func(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + txCache.txByHash.addTx(createTx([]byte("txHash1"), "alice", 1).withTransferredValue(big.NewInt(5))) + txCache.txByHash.addTx(createTx([]byte("txHash2"), "alice", 2).withTransferredValue(big.NewInt(5))) + txCache.txByHash.addTx(createTx([]byte("txHash3"), "alice", 3).withTransferredValue(big.NewInt(5))) + txCache.txByHash.addTx(createTx([]byte("txHash4"), "alice", 4).withTransferredValue(big.NewInt(6))) + + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + blockBody1 := block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("txHash1"), + []byte("txHash2"), + }, + }, + }, + } + + blockBody2 := block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("txHash3"), + []byte("txHash4"), + }, + }, + }, + } + + accountsProvider := &txcachemocks.AccountNonceAndBalanceProviderMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 1, big.NewInt(20), true, nil + }, + } + + err = tracker.OnProposedBlock([]byte("hash1"), &blockBody1, &block.Header{ + Nonce: uint64(0), + PrevHash: []byte(fmt.Sprintf("prevHash%d", 0)), + RootHash: []byte(fmt.Sprintf("rootHash%d", 0)), + }, accountsProvider, []byte("prevHash0")) + require.Nil(t, err) + + err = tracker.OnProposedBlock([]byte("hash2"), &blockBody2, &block.Header{ + Nonce: uint64(1), + PrevHash: []byte(fmt.Sprintf("hash%d", 1)), + RootHash: []byte(fmt.Sprintf("rootHash%d", 0)), + }, accountsProvider, []byte("prevHash0")) + require.Equal(t, errExceededBalance, err) + }) + + t.Run("should return err from selection session", func(t *testing.T) { + t.Parallel() + + expectedErr := errors.New("default err") + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + txCache.txByHash.addTx(createTx([]byte("txHash1"), "alice", 1)) + + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + blockBody1 := block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("txHash1"), + }, + }, + }, + } + + accountsProvider := &txcachemocks.AccountNonceAndBalanceProviderMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 0, nil, false, expectedErr + }, + } + + err = tracker.OnProposedBlock([]byte("hash1"), &blockBody1, &block.Header{ + Nonce: uint64(0), + PrevHash: []byte(fmt.Sprintf("prevHash%d", 0)), + RootHash: []byte(fmt.Sprintf("rootHash%d", 0)), + }, accountsProvider, []byte("prevHash0")) + require.Equal(t, expectedErr, err) + }) + + t.Run("should return errRootHashMismatch in case of different root hashes", func(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + txCache.txByHash.addTx(createTx([]byte("txHash1"), "alice", 1)) + + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + blockBody1 := block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("txHash1"), + }, + }, + }, + } + + accountsProvider := &txcachemocks.AccountNonceAndBalanceProviderMock{ + GetRootHashCalled: func() ([]byte, error) { + return []byte("rootHash1"), nil + }, + } + + tracker.latestRootHash = []byte("rootHashX") + + err = tracker.OnProposedBlock([]byte("hash1"), &blockBody1, &block.Header{ + Nonce: uint64(0), + PrevHash: []byte(fmt.Sprintf("prevHash%d", 0)), + RootHash: []byte(fmt.Sprintf("rootHash%d", 0)), + }, accountsProvider, []byte("prevHash0")) + require.Equal(t, errRootHashMismatch, err) + }) +} + +func TestSelectionTracker_OnProposedBlockShouldWork(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + numOfBlocks := 20 + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMock() + + proposeBlocks(t, numOfBlocks, tracker, accountsProvider) + require.Equal(t, 20, len(tracker.blocks)) +} + +func TestSelectionTracker_OnProposedBlockWhenMaxTrackedBlocksIsReached(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, 3) + require.Nil(t, err) + + numOfBlocks := 3 + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMock() + + proposeBlocks(t, numOfBlocks, tracker, accountsProvider) + + // this one should not be added, it has new transactions, but it doesn't have execution results + err = tracker.OnProposedBlock( + []byte("hashX"), + &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("txHash"), + }, + }, + }, + }, + &block.Header{ + Nonce: uint64(4), + PrevHash: []byte(fmt.Sprintf("hash%d", 3)), + RootHash: []byte("rootHash0"), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Equal(t, errBadBlockWhileMaxTrackedBlocksReached, err) + require.Equal(t, 3, len(tracker.blocks)) + + // this one should be added because it has new execution results + err = tracker.OnProposedBlock( + []byte(fmt.Sprintf("hash%d", 4)), + &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{}, + }, + }, + }, + &block.HeaderV3{ + Nonce: uint64(4), + PrevHash: []byte(fmt.Sprintf("hash%d", 3)), + ExecutionResults: []*block.ExecutionResult{ + {}, + }, + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + require.Equal(t, 4, len(tracker.blocks)) + + // this one should be added because it's an empty block + err = tracker.OnProposedBlock( + []byte(fmt.Sprintf("hash%d", 5)), + &block.Body{}, + &block.HeaderV3{ + Nonce: uint64(5), + PrevHash: []byte(fmt.Sprintf("hash%d", 4)), + ExecutionResults: []*block.ExecutionResult{ + {}, + }, + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + require.Equal(t, 5, len(tracker.blocks)) +} + +func Test_CompleteFlowShouldWork(t *testing.T) { + t.Parallel() + + accountsProvider := &txcachemocks.AccountNonceAndBalanceProviderMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 11, big.NewInt(8 * 100000 * oneBillion), true, nil + }, + } + + config := ConfigSourceMe{ + Name: "test", + NumChunks: 16, + NumBytesThreshold: maxNumBytesUpperBound, + NumBytesPerSenderThreshold: maxNumBytesPerSenderUpperBoundTest, + CountThreshold: math.MaxUint32, + CountPerSenderThreshold: math.MaxUint32, + EvictionEnabled: true, + NumItemsToPreemptivelyEvict: 1, + TxCacheBoundsConfig: createMockTxBoundsConfig(), + } + + host := txcachemocks.NewMempoolHostMock() + + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + + txs := []*WrappedTransaction{ + // txs for first block + createTx([]byte("txHash1"), "alice", 11).withValue(big.NewInt(0)).withRelayer([]byte("bob")).withGasLimit(100_000), // the fee is 100000000000000 + createTx([]byte("txHash2"), "alice", 12).withValue(big.NewInt(0)), // the fee is 50000000000000 + createTx([]byte("txHash3"), "alice", 13).withValue(big.NewInt(0)), + createTx([]byte("txHash4"), "bob", 11).withValue(big.NewInt(0)), + createTx([]byte("txHash5"), "carol", 11).withValue(big.NewInt(0)), + createTx([]byte("txHash6"), "carol", 12).withValue(big.NewInt(0)).withRelayer([]byte("bob")).withGasLimit(100_000), + createTx([]byte("txHash7"), "carol", 13).withValue(big.NewInt(0)).withRelayer([]byte("alice")).withGasLimit(100_000), + createTx([]byte("txHash8"), "carol", 14).withValue(big.NewInt(0)).withRelayer([]byte("eve")).withGasLimit(100_000), + + // txs for second block + createTx([]byte("txHash9"), "carol", 15).withValue(big.NewInt(0)), + createTx([]byte("txHash10"), "eve", 11).withValue(big.NewInt(0)).withRelayer([]byte("bob")).withGasLimit(100_000), + + // tx to be selected + createTx([]byte("txHash11"), "bob", 12).withValue(big.NewInt(0)), + createTx([]byte("txHash12"), "carol", 13).withValue(big.NewInt(0)), // this one should not be selected + createTx([]byte("txHash13"), "eve", 14).withValue(big.NewInt(0)), // this one should not be selected + } + for _, tx := range txs { + cache.AddTx(tx) + } + + proposedBlock1 := [][]byte{ + []byte("txHash1"), + []byte("txHash2"), + []byte("txHash3"), + []byte("txHash4"), + []byte("txHash5"), + []byte("txHash6"), + []byte("txHash7"), + []byte("txHash8"), + } + + err = cache.OnProposedBlock( + []byte("hash1"), + &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: proposedBlock1, + }, + }, + }, + &block.Header{ + Nonce: uint64(0), + PrevHash: []byte("hash0"), + RootHash: []byte("rootHash0"), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + expectedBreadcrumbs := map[string]*accountBreadcrumb{ + "alice": createExpectedBreadcrumb(true, 11, 13, big.NewInt(200000000000000)), // feeOf(txHash2) + feeOf(txHash3) + feeOf(txHash7) + "bob": createExpectedBreadcrumb(true, 11, 11, big.NewInt(250000000000000)), // feeOf(txHash1) + feeOf(txHash4) + feeOf(txHash6) + "carol": createExpectedBreadcrumb(true, 11, 14, big.NewInt(50000000000000)), // feeOf(txHash5) + "eve": createExpectedBreadcrumb(false, 0, 0, big.NewInt(100000000000000)), // feeOf(txHash8) + } + + require.Equal(t, 1, len(cache.tracker.blocks)) + tb, ok := cache.tracker.blocks["hash1"] + require.True(t, ok) + require.Equal(t, expectedBreadcrumbs, tb.breadcrumbsByAddress) + + // propose another block + err = cache.OnProposedBlock( + []byte("hash2"), + &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("txHash9"), + []byte("txHash10"), + }, + }, + }, + }, + &block.Header{ + Nonce: uint64(1), + PrevHash: []byte("hash1"), + RootHash: []byte("rootHash0"), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + expectedBreadcrumbs = map[string]*accountBreadcrumb{ + "bob": createExpectedBreadcrumb(false, 0, 0, big.NewInt(100000000000000)), + "carol": createExpectedBreadcrumb(true, 15, 15, big.NewInt(50000000000000)), + "eve": createExpectedBreadcrumb(true, 11, 11, big.NewInt(0)), // feeOf(txHash8) + } + require.Equal(t, 2, len(cache.tracker.blocks)) + tb, ok = cache.tracker.blocks["hash2"] + require.True(t, ok) + require.Equal(t, expectedBreadcrumbs, tb.breadcrumbsByAddress) + + selectionSession := &txcachemocks.SelectionSessionMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 11, big.NewInt(8 * 100000 * oneBillion), true, nil + }, + } + + virtualSession, err := cache.tracker.deriveVirtualSelectionSession(selectionSession, 2, false) + + require.Nil(t, err) + require.NotNil(t, virtualSession) + + expectedVirtualRecords := map[string]*virtualAccountRecord{ + "alice": createExpectedVirtualRecord(true, 14, big.NewInt(8*100000*oneBillion), big.NewInt(200000000000000)), + "bob": createExpectedVirtualRecord(true, 12, big.NewInt(8*100000*oneBillion), big.NewInt(350000000000000)), + "carol": createExpectedVirtualRecord(true, 16, big.NewInt(8*100000*oneBillion), big.NewInt(100000000000000)), + "eve": createExpectedVirtualRecord(true, 12, big.NewInt(8*100000*oneBillion), big.NewInt(100000000000000)), + } + require.Equal(t, expectedVirtualRecords, virtualSession.virtualAccountsByAddress) + + // execute the first block + err = cache.OnExecutedBlock(&block.Header{ + Nonce: uint64(0), + PrevHash: []byte("hash0"), + }, []byte("rootHash0")) + require.Nil(t, err) + + for _, txHash := range proposedBlock1 { + cache.RemoveTxByHash(txHash) + } + + // update the session nonce + selectionSession = &txcachemocks.SelectionSessionMock{ + GetRootHashCalled: func() ([]byte, error) { + return []byte("rootHash0"), nil + }, + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + if string(address) == "alice" { + return 14, big.NewInt(8 * 100000 * oneBillion), true, nil + } + if string(address) == "bob" { + return 12, big.NewInt(8 * 100000 * oneBillion), true, nil + } + if string(address) == "carol" { + return 15, big.NewInt(8 * 100000 * oneBillion), true, nil + } + + return 11, big.NewInt(8 * 100000 * oneBillion), true, nil + }, + } + + virtualSession, err = cache.tracker.deriveVirtualSelectionSession(selectionSession, 2, false) + require.Nil(t, err) + + expectedVirtualRecords = map[string]*virtualAccountRecord{ + // bob was only relayer in the last proposed block (which is still tracked). + // However, its initialNonce shouldn't remain uninitialized, so it's initialized with the session nonce. + "bob": createExpectedVirtualRecord(true, 12, big.NewInt(8*100000*oneBillion), big.NewInt(100000000000000)), + "carol": createExpectedVirtualRecord(true, 16, big.NewInt(8*100000*oneBillion), big.NewInt(50000000000000)), + "eve": createExpectedVirtualRecord(true, 12, big.NewInt(8*100000*oneBillion), big.NewInt(0)), + } + require.Equal(t, expectedVirtualRecords, virtualSession.virtualAccountsByAddress) + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + 10, + 10, + haveTimeTrueForSelection, + ) + + selectedTxs, _, err := cache.SelectTransactions( + selectionSession, + options, + 2, + ) + require.Nil(t, err) + require.Len(t, selectedTxs, 1) + require.Equal(t, "txHash11", string(selectedTxs[0].TxHash)) +} + +func TestSelectionTracker_OnExecutedBlockShouldError(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + err = tracker.OnExecutedBlock(nil, []byte{}) + require.Equal(t, errNilBlockHeader, err) +} + +func TestSelectionTracker_OnExecutedBlockShouldWork(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + numOfBlocks := 20 + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMock() + + proposeBlocks(t, numOfBlocks, tracker, accountsProvider) + require.Equal(t, numOfBlocks, len(tracker.blocks)) + + executeBlocksConcurrently(t, numOfBlocks, tracker) + require.Equal(t, 0, len(tracker.blocks)) + require.Equal(t, uint64(20), tracker.latestNonce) + require.Equal(t, []byte("rootHash0"), tracker.latestRootHash) +} + +func TestSelectionTracker_OnExecutedBlockShouldDeleteAllBlocksBelowSpecificNonce(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMock() + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + err = tracker.OnProposedBlock( + []byte(fmt.Sprintf("blockHash%d", 0)), + &block.Body{}, + &block.Header{ + Nonce: 0, + PrevHash: nil, + RootHash: nil, + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + err = tracker.OnProposedBlock( + []byte(fmt.Sprintf("blockHash%d", 1)), + &block.Body{}, + &block.Header{ + Nonce: 1, + PrevHash: []byte(fmt.Sprintf("blockHash%d", 0)), + RootHash: []byte(fmt.Sprintf("rootHash%d", 0)), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + err = tracker.OnProposedBlock( + []byte(fmt.Sprintf("blockHash%d", 2)), + &block.Body{}, + &block.Header{ + Nonce: 2, + PrevHash: []byte(fmt.Sprintf("blockHash%d", 1)), + RootHash: []byte(fmt.Sprintf("rootHash%d", 0)), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + err = tracker.OnProposedBlock( + []byte(fmt.Sprintf("blockHash%d", 3)), + &block.Body{}, + &block.Header{ + Nonce: 3, + PrevHash: []byte(fmt.Sprintf("blockHash%d", 2)), + RootHash: []byte(fmt.Sprintf("rootHash%d", 0)), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + err = tracker.OnExecutedBlock(&block.Header{ + Nonce: 2, + PrevHash: []byte(fmt.Sprintf("blockHash%d", 1)), + }, []byte(fmt.Sprintf("rootHash%d", 0))) + require.Nil(t, err) + require.Equal(t, 1, len(tracker.blocks)) + + err = tracker.OnExecutedBlock(&block.Header{ + Nonce: 3, + PrevHash: []byte(fmt.Sprintf("blockHash%d", 2)), + }, []byte(fmt.Sprintf("rootHash%d", 0))) + require.Nil(t, err) + require.Equal(t, 0, len(tracker.blocks)) +} + +func TestSelectionTracker_updateLatestRoothash(t *testing.T) { + t.Parallel() + + t.Run("latest roothash is nil", func(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + tracker.updateLatestRootHashNoLock(1, []byte("rootHash1")) + require.Equal(t, uint64(1), tracker.latestNonce) + require.Equal(t, []byte("rootHash1"), tracker.latestRootHash) + }) + + t.Run("root hash of block N after root hash of block N+1", func(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + tracker.updateLatestRootHashNoLock(2, []byte("rootHash2")) + require.Equal(t, uint64(2), tracker.latestNonce) + require.Equal(t, []byte("rootHash2"), tracker.latestRootHash) + + tracker.updateLatestRootHashNoLock(1, []byte("rootHash1")) + require.Equal(t, uint64(2), tracker.latestNonce) + require.Equal(t, []byte("rootHash2"), tracker.latestRootHash) + }) + + t.Run("root hash of block N + 1 after root hash of block N", func(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + tracker.updateLatestRootHashNoLock(1, []byte("rootHash1")) + require.Equal(t, uint64(1), tracker.latestNonce) + require.Equal(t, []byte("rootHash1"), tracker.latestRootHash) + + tracker.updateLatestRootHashNoLock(2, []byte("rootHash2")) + require.Equal(t, uint64(2), tracker.latestNonce) + require.Equal(t, []byte("rootHash2"), tracker.latestRootHash) + }) +} + +func TestSelectionTracker_removeFromTrackedBlocks(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + expectedTrackedBlock := newTrackedBlock(1, []byte("blockHash2"), []byte("prevHash2")) + b1 := newTrackedBlock(0, []byte("blockHash1"), []byte("prevHash1")) + b2 := newTrackedBlock(0, []byte("blockHash3"), []byte("prevHash1")) + + tracker.blocks = map[string]*trackedBlock{ + string(b1.hash): b1, + string(expectedTrackedBlock.hash): expectedTrackedBlock, + string(b2.hash): b2, + } + + require.Equal(t, 3, len(tracker.blocks)) + + r := newTrackedBlock(0, nil, []byte("prevHash1")) + + err = tracker.removeUpToBlockNoLock(r) + require.Nil(t, err) + require.Equal(t, 1, len(tracker.blocks)) + + _, ok := tracker.blocks[string(expectedTrackedBlock.hash)] + require.True(t, ok) + + _, ok = tracker.blocks[string(b1.hash)] + require.False(t, ok) + + _, ok = tracker.blocks[string(b2.hash)] + require.False(t, ok) +} + +func TestSelectionTracker_getChainOfTrackedBlocks(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + // create a slice of tracked block which aren't ordered + tracker.blocks = make(map[string]*trackedBlock) + b := newTrackedBlock(7, []byte("blockHash8"), []byte("blockHash7")) + tracker.blocks[string(b.hash)] = b + + b = newTrackedBlock(5, []byte("blockHash6"), []byte("blockHash5")) + tracker.blocks[string(b.hash)] = b + + b = newTrackedBlock(1, []byte("blockHash2"), []byte("blockHash1")) + tracker.blocks[string(b.hash)] = b + + b = newTrackedBlock(0, []byte("blockHash1"), []byte("prevHash1")) + tracker.blocks[string(b.hash)] = b + + b = newTrackedBlock(3, []byte("blockHash4"), []byte("blockHash3")) + tracker.blocks[string(b.hash)] = b + + b = newTrackedBlock(2, []byte("blockHash3"), []byte("blockHash2")) + tracker.blocks[string(b.hash)] = b + + // create a block with a wrong previous hash + b = newTrackedBlock(4, []byte("blockHash5"), []byte("blockHashY")) + tracker.blocks[string(b.hash)] = b + + b = newTrackedBlock(6, []byte("blockHash7"), []byte("blockHash6")) + tracker.blocks[string(b.hash)] = b + + t.Run("check order to be from head to tail", func(t *testing.T) { + t.Parallel() + + expectedTrackedBlockHashes := [][]byte{ + []byte("blockHash2"), + []byte("blockHash3"), + []byte("blockHash4"), + } + + actualChain, err := tracker.getChainOfTrackedPendingBlocks([]byte("blockHash1"), []byte("blockHash4"), 4) + require.Nil(t, err) + for i, returnedBlock := range actualChain { + require.Equal(t, returnedBlock.hash, expectedTrackedBlockHashes[i]) + } + }) + + t.Run("should return expected tracked blocks and stop before nonce", func(t *testing.T) { + t.Parallel() + + expectedTrackedBlockHashes := [][]byte{ + []byte("blockHash6"), + []byte("blockHash7"), + } + + actualChain, err := tracker.getChainOfTrackedPendingBlocks([]byte("blockHash5"), []byte("blockHash7"), 7) + require.Nil(t, err) + for i, returnedBlock := range actualChain { + require.Equal(t, returnedBlock.hash, expectedTrackedBlockHashes[i]) + } + }) + + t.Run("should return errBlockNotFound because of prevHash not found", func(t *testing.T) { + t.Parallel() + + actualChain, err := tracker.getChainOfTrackedPendingBlocks([]byte("blockHash4"), []byte("blockHash7"), 7) + require.Equal(t, err, errBlockNotFound) + require.Nil(t, actualChain) + }) + + t.Run("should return errDiscontinuousSequenceOfBlocks because of nonce", func(t *testing.T) { + t.Parallel() + + actualChain, err := tracker.getChainOfTrackedPendingBlocks([]byte("blockHash5"), []byte("blockHash7"), 6) + require.Equal(t, errDiscontinuousSequenceOfBlocks, err) + require.Equal(t, 0, len(actualChain)) + }) + +} + +func TestSelectionTracker_deriveVirtualSelectionSessionShouldErr(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + // should do the tests sequentially + t.Run("get roothash returns error, should error", func(t *testing.T) { + expectedErr := errors.New("expected err") + + session := txcachemocks.SelectionSessionMock{} + session.GetRootHashCalled = func() ([]byte, error) { + return nil, expectedErr + } + virtualSession, actualErr := tracker.deriveVirtualSelectionSession(&session, 0, false) + require.Nil(t, virtualSession) + require.Equal(t, expectedErr, actualErr) + }) + t.Run("cannot do simulation error on wrong nonce, returns error", func(t *testing.T) { + session := txcachemocks.SelectionSessionMock{} + session.GetRootHashCalled = func() ([]byte, error) { + return []byte("root hash"), nil + } + virtualSession, actualErr := tracker.deriveVirtualSelectionSession(&session, 10, true) + require.Nil(t, virtualSession) + require.Equal(t, errSimulateSelectionContextInvalid, actualErr) + }) +} + +func TestSelectionTracker_canDoSimulateSelection(t *testing.T) { + t.Parallel() + + t.Run("OK - matching nonce", func(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + tracker.latestNonce = 9 + require.Nil(t, err) + require.True(t, tracker.canDoSimulateSelection(10)) + + // matching on top of tracked blocks + tracker.blocks["blockHash1"] = newTrackedBlock(10, []byte("blockHash1"), []byte("prevHash1")) + require.True(t, tracker.canDoSimulateSelection(11)) + }) + t.Run("simulation trial with lower nonce than last nonce should return false", func(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + tracker.latestNonce = 9 + require.False(t, tracker.canDoSimulateSelection(8)) + }) + t.Run("simulation trial when having tracked blocks", func(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + tracker.latestNonce = 9 + tracker.blocks["blockHash1"] = newTrackedBlock(10, []byte("blockHash1"), []byte("prevHash1")) + // same as last tracked block + require.False(t, tracker.canDoSimulateSelection(10)) + + // previous nonce to last tracked block + tracker.blocks["blockHash2"] = newTrackedBlock(11, []byte("blockHash2"), []byte("blockHash1")) + require.False(t, tracker.canDoSimulateSelection(10)) + + // above and with gap compared to last tracked block + require.False(t, tracker.canDoSimulateSelection(13)) + }) +} + +func TestSelectionTracker_deriveVirtualSelectionSessionShouldDeleteProposedBlocks(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + tracker.blocks = createDummyTrackedBlocks() + require.Equal(t, 3, len(tracker.blocks)) + + session := txcachemocks.SelectionSessionMock{} + session.GetRootHashCalled = func() ([]byte, error) { + return nil, nil + } + + _, err = tracker.deriveVirtualSelectionSession(&session, 0, false) + require.Nil(t, err) + require.Equal(t, 0, len(tracker.blocks)) +} + +func TestSelectionTracker_deriveVirtualSelectionSessionShouldNotDeleteProposedBlocks(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + tracker.blocks = createDummyTrackedBlocks() + require.Nil(t, err) + require.Equal(t, 3, len(tracker.blocks)) + + session := txcachemocks.SelectionSessionMock{} + session.GetRootHashCalled = func() ([]byte, error) { + return nil, nil + } + + _, err = tracker.deriveVirtualSelectionSession(&session, 0, true) + require.Nil(t, err) + require.Equal(t, 3, len(tracker.blocks)) +} + +func TestSelectionTracker_validateTrackedBlocks(t *testing.T) { + t.Parallel() + + t.Run("should return discontinuous breadcrumbs", func(t *testing.T) { + t.Parallel() + + breadcrumbAlice1 := newAccountBreadcrumb(core.OptionalUint64{ + Value: 0, + HasValue: true, + }) + breadcrumbAlice1.lastNonce = core.OptionalUint64{ + Value: 4, + HasValue: true, + } + + breadcrumbAlice2 := newAccountBreadcrumb(core.OptionalUint64{ + Value: 6, + HasValue: true, + }) + breadcrumbAlice2.lastNonce = core.OptionalUint64{ + Value: 7, + HasValue: true, + } + + trackedBlocks := []*trackedBlock{ + { + nonce: 0, + hash: []byte("hash1"), + prevHash: []byte("prevHash1"), + breadcrumbsByAddress: map[string]*accountBreadcrumb{ + "alice": breadcrumbAlice1, + }, + }, + { + nonce: 0, + hash: []byte("hash2"), + prevHash: []byte("prevHash2"), + breadcrumbsByAddress: map[string]*accountBreadcrumb{ + "alice": breadcrumbAlice2, + }, + }, + } + + mockSelectionSession := txcachemocks.SelectionSessionMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 0, big.NewInt(20), true, nil + }, + } + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + err = tracker.validateBreadcrumbsOfTrackedBlocks(trackedBlocks, &mockSelectionSession) + require.Equal(t, errDiscontinuousBreadcrumbs, err) + }) + + t.Run("should return balance exceeded", func(t *testing.T) { + t.Parallel() + + breadcrumbAlice1 := newAccountBreadcrumb(core.OptionalUint64{ + Value: 0, + HasValue: true, + }) + breadcrumbAlice1.accumulateConsumedBalance(big.NewInt(3)) + breadcrumbAlice1.lastNonce = core.OptionalUint64{ + Value: 4, + HasValue: true, + } + + breadcrumbAlice2 := newAccountBreadcrumb(core.OptionalUint64{ + Value: 5, + HasValue: true, + }) + breadcrumbAlice2.accumulateConsumedBalance(big.NewInt(3)) + breadcrumbAlice2.lastNonce = core.OptionalUint64{ + Value: 7, + HasValue: true, + } + + trackedBlocks := []*trackedBlock{ + { + nonce: 0, + hash: []byte("hash1"), + prevHash: []byte("prevHash1"), + breadcrumbsByAddress: map[string]*accountBreadcrumb{ + "alice": breadcrumbAlice1, + }, + }, + { + nonce: 0, + hash: []byte("hash2"), + prevHash: []byte("prevHash2"), + breadcrumbsByAddress: map[string]*accountBreadcrumb{ + "alice": breadcrumbAlice2, + }, + }, + } + + mockSelectionSession := txcachemocks.SelectionSessionMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 0, big.NewInt(5), true, nil + }, + } + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + err = tracker.validateBreadcrumbsOfTrackedBlocks(trackedBlocks, &mockSelectionSession) + require.Equal(t, errExceededBalance, err) + }) + + t.Run("should return nil", func(t *testing.T) { + t.Parallel() + + breadcrumbAlice1 := newAccountBreadcrumb(core.OptionalUint64{ + Value: 0, + HasValue: true, + }) + breadcrumbAlice1.lastNonce = core.OptionalUint64{ + Value: 4, + HasValue: true, + } + + breadcrumbAlice2 := newAccountBreadcrumb(core.OptionalUint64{ + Value: 5, + HasValue: true, + }) + breadcrumbAlice2.accumulateConsumedBalance(big.NewInt(1)) + + breadcrumbAlice2.lastNonce = core.OptionalUint64{ + Value: 7, + HasValue: true, + } + + trackedBlocks := []*trackedBlock{ + { + nonce: 0, + hash: []byte("hash1"), + prevHash: []byte("prevHash1"), + breadcrumbsByAddress: map[string]*accountBreadcrumb{ + "alice": breadcrumbAlice1, + }, + }, + { + nonce: 0, + hash: []byte("hash2"), + prevHash: []byte("prevHash2"), + breadcrumbsByAddress: map[string]*accountBreadcrumb{ + "alice": breadcrumbAlice2, + }, + }, + } + + mockSelectionSession := txcachemocks.SelectionSessionMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 0, big.NewInt(2), true, nil + }, + } + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + err = tracker.validateBreadcrumbsOfTrackedBlocks(trackedBlocks, &mockSelectionSession) + require.Nil(t, err) + }) +} + +func TestSelectionTracker_addNewBlockNoLock(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + tb0 := newTrackedBlock(0, []byte("blockHash0"), []byte("blockHash")) + tb1 := newTrackedBlock(1, []byte("blockHash1"), []byte("blockHash0")) + tb2 := newTrackedBlock(1, []byte("blockHash2"), []byte("blockHash0")) + + err = tracker.addNewTrackedBlockNoLock([]byte("blockHash0"), tb0) + require.Nil(t, err) + require.Equal(t, len(tracker.blocks), 1) + + err = tracker.addNewTrackedBlockNoLock([]byte("blockHash1"), tb1) + require.Nil(t, err) + require.Equal(t, len(tracker.blocks), 2) + + err = tracker.addNewTrackedBlockNoLock([]byte("blockHash2"), tb2) + require.Nil(t, err) + require.Equal(t, len(tracker.blocks), 2) +} + +func Test_getVirtualNonceOfAccount(t *testing.T) { + t.Parallel() + + t.Run("should return errGlobalBreadcrumbDoesNotExist error", func(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + tracker.blocks["hash2"] = newTrackedBlock(0, []byte("hash2"), []byte("hash1")) + + _, _, err = tracker.getVirtualNonceOfAccountWithRootHash([]byte("alice")) + require.Equal(t, errGlobalBreadcrumbDoesNotExist, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + breadcrumb := newAccountBreadcrumb(core.OptionalUint64{HasValue: true, Value: 10}) + err = breadcrumb.updateNonceRange(core.OptionalUint64{ + HasValue: true, + Value: 20, + }) + require.Nil(t, err) + + tb := newTrackedBlock(0, []byte("hash2"), []byte("hash1")) + tb.breadcrumbsByAddress["alice"] = breadcrumb + + tracker.globalBreadcrumbsCompiler.updateOnAddedBlock(tb) + + nonce, _, err := tracker.getVirtualNonceOfAccountWithRootHash([]byte("alice")) + require.Nil(t, err) + require.Equal(t, uint64(21), nonce) + }) +} + +func Test_isTransactionTracked(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 6) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + txCache.tracker = tracker + + accountsProvider := &txcachemocks.AccountNonceAndBalanceProviderMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 11, big.NewInt(6 * 100000 * oneBillion), true, nil + }, + } + + txs := []*WrappedTransaction{ + createTx([]byte("txHash1"), "alice", 11).withRelayer([]byte("bob")).withGasLimit(100_000), + createTx([]byte("txHash2"), "alice", 12), + createTx([]byte("txHash3"), "alice", 13), + createTx([]byte("txHash4"), "alice", 14), + createTx([]byte("txHash5"), "alice", 15).withRelayer([]byte("bob")).withGasLimit(100_000), + createTx([]byte("txHash6"), "eve", 11).withRelayer([]byte("alice")).withGasLimit(100_000), + // This one is not proposed. However, will be detected as "tracked" because it has the same nonce with as a tracked one. + // This is not critical. It is ok that a sender has a specific nonce "protected". + createTx([]byte("txHash7"), "eve", 11).withRelayer([]byte("alice")).withGasLimit(100_000), + } + + for _, tx := range txs { + txCache.AddTx(tx) + } + + err = txCache.OnProposedBlock( + []byte("hash1"), + &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("txHash1"), + []byte("txHash2"), + []byte("txHash3"), + }, + }, + }, + }, + &block.Header{ + Nonce: uint64(0), + PrevHash: []byte("hash0"), + RootHash: []byte("rootHash0"), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + err = txCache.OnProposedBlock( + []byte("hash2"), + &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("txHash4"), + []byte("txHash5"), + }, + }, + }, + }, + &block.Header{ + Nonce: uint64(1), + PrevHash: []byte("hash1"), + RootHash: []byte("rootHash0"), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + err = txCache.OnProposedBlock( + []byte("hash3"), + &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("txHash6"), + }, + }, + }, + }, + &block.Header{ + Nonce: uint64(2), + PrevHash: []byte("hash2"), + RootHash: []byte("rootHash0"), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + t.Run("should return true", func(t *testing.T) { + t.Parallel() + + tx1 := createTx([]byte("txHash1"), "alice", 11) + tx2 := createTx([]byte("txHash6"), "eve", 11) + + require.True(t, txCache.tracker.IsTransactionTracked(tx1)) + require.True(t, txCache.tracker.IsTransactionTracked(tx2)) + }) + + t.Run("should return false because out of range", func(t *testing.T) { + t.Parallel() + + tx1 := createTx([]byte("txHashX"), "alice", 16) + tx2 := createTx([]byte("txHashX"), "eve", 12) + + require.False(t, txCache.tracker.IsTransactionTracked(tx1)) + require.False(t, txCache.tracker.IsTransactionTracked(tx2)) + + }) + + t.Run("should return false because account is only relayer", func(t *testing.T) { + t.Parallel() + + tx1 := createTx([]byte("txHashX"), "alice", 16) + + require.False(t, txCache.tracker.IsTransactionTracked(tx1)) + }) + + t.Run("should return false because account is not tracked at all", func(t *testing.T) { + t.Parallel() + + tx1 := createTx([]byte("txHash2"), "carol", 12) + + require.False(t, txCache.tracker.IsTransactionTracked(tx1)) + }) + + t.Run("should return true for any transaction of sender with a tracked nonce", func(t *testing.T) { + t.Parallel() + + tx1 := createTx([]byte("txHash7"), "eve", 12) + + require.False(t, txCache.tracker.IsTransactionTracked(tx1)) + }) +} + +func TestSelectionTracker_IsTransactionTracked(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 6) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + txCache.tracker = tracker + + accountsProvider := &txcachemocks.AccountNonceAndBalanceProviderMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 11, big.NewInt(6 * 100000 * oneBillion), true, nil + }, + } + + txs := []*WrappedTransaction{ + createTx([]byte("txHash1"), "alice", 11).withRelayer([]byte("bob")).withGasLimit(100_000), + createTx([]byte("txHash2"), "alice", 12), + createTx([]byte("txHash3"), "alice", 13), + createTx([]byte("txHash4"), "alice", 14), + createTx([]byte("txHash5"), "alice", 15).withRelayer([]byte("bob")).withGasLimit(100_000), + createTx([]byte("txHash6"), "eve", 11).withRelayer([]byte("alice")).withGasLimit(100_000), + // This one is not proposed. However, will be detected as "tracked" because it has the same nonce with as a tracked one. + // This is not critical. It is ok that a sender has a specific nonce "protected". + createTx([]byte("txHash7"), "eve", 11).withRelayer([]byte("alice")).withGasLimit(100_000), + } + + for _, tx := range txs { + txCache.AddTx(tx) + } + + err = txCache.OnProposedBlock( + []byte("hash1"), + &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("txHash1"), + []byte("txHash2"), + []byte("txHash3"), + []byte("txHash6"), + }, + }, + }, + }, + &block.Header{ + Nonce: uint64(0), + PrevHash: []byte("hash0"), + RootHash: []byte("rootHash0"), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + require.True(t, txCache.tracker.IsTransactionTracked(txs[0])) + require.True(t, txCache.tracker.IsTransactionTracked(txs[1])) + require.True(t, txCache.tracker.IsTransactionTracked(txs[2])) + require.False(t, txCache.tracker.IsTransactionTracked(txs[3])) + require.False(t, txCache.tracker.IsTransactionTracked(txs[4])) + require.True(t, txCache.tracker.IsTransactionTracked(txs[5])) + require.True(t, txCache.tracker.IsTransactionTracked(txs[6])) +} + +func TestSelectionTracker_ResetTracker(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 6) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + txCache.tracker = tracker + + tracker.blocks = createDummyTrackedBlocks() + tracker.globalBreadcrumbsCompiler.globalAccountBreadcrumbs = map[string]*globalAccountBreadcrumb{ + "alice": {}, + "bob": {}, + "carol": {}, + } + + tracker.latestNonce = 10 + tracker.latestRootHash = []byte("rootHash0") + + require.Equal(t, []byte("rootHash0"), tracker.latestRootHash) + require.Equal(t, uint64(10), tracker.latestNonce) + + require.Equal(t, 3, len(tracker.blocks)) + require.Equal(t, 3, len(tracker.globalBreadcrumbsCompiler.globalAccountBreadcrumbs)) + + tracker.ResetTrackedBlocks() + require.Equal(t, 0, len(tracker.blocks)) + require.Equal(t, 0, len(tracker.globalBreadcrumbsCompiler.globalAccountBreadcrumbs)) + + require.Nil(t, tracker.latestRootHash) + require.Equal(t, uint64(0), tracker.latestNonce) +} + +func Test_getDimensionOfTrackedBlocks(t *testing.T) { + t.Parallel() + + t.Run("should return the number of tracked blocks", func(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + txCache.tracker = tracker + + tracker.blocks = map[string]*trackedBlock{} + trackerDiagnosis := tracker.getTrackerDiagnosis() + require.Equal(t, uint64(0), trackerDiagnosis.GetNumTrackedBlocks()) + require.Equal(t, uint64(0), trackerDiagnosis.GetNumTrackedAccounts()) + + tracker.blocks = createDummyTrackedBlocks() + tracker.globalBreadcrumbsCompiler.globalAccountBreadcrumbs = map[string]*globalAccountBreadcrumb{ + "alice": {}, + "bob": {}, + } + + trackerDiagnosis = tracker.getTrackerDiagnosis() + require.Equal(t, uint64(3), trackerDiagnosis.GetNumTrackedBlocks()) + require.Equal(t, uint64(2), trackerDiagnosis.GetNumTrackedAccounts()) + }) +} + +func TestSelectionTracker_addNewTrackedBlockNoLock(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 6) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + txCache.tracker = tracker + tracker.blocks = createDummyTrackedBlocks() + + require.Equal(t, 3, len(txCache.tracker.blocks)) + err = tracker.addNewTrackedBlockNoLock([]byte("hashX"), &trackedBlock{ + nonce: 1, + hash: []byte("hashX"), + }) + require.Nil(t, err) + + require.Equal(t, 1, len(txCache.tracker.blocks)) + _, ok := txCache.tracker.blocks["hashX"] + require.True(t, ok) +} + +func TestSelectionTracker_removeBlocksAboveNonce(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 6) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + txCache.tracker = tracker + + tracker.blocks = createDummyTrackedBlocks() + + require.Equal(t, 3, len(txCache.tracker.blocks)) + err = tracker.removeBlocksAboveOrEqualToNonceNoLock(1) + require.Nil(t, err) + + require.Equal(t, 0, len(txCache.tracker.blocks)) +} + +func TestSelectionTracker_MaxUniqueAccounts(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + st, err := NewSelectionTracker(txCache, 0, 100) + require.Nil(t, err) + + count := maxAccountsPerBlock + 1 + txs := make([][]byte, count) + + for i := 0; i < count; i++ { + sender := fmt.Sprintf("sender%d", i) + hash := []byte(fmt.Sprintf("tx%d", i)) + + wTx := &WrappedTransaction{ + Tx: &transaction.Transaction{ + SndAddr: []byte(sender), + RcvAddr: []byte("receiver"), + Nonce: uint64(i), + }, + TxHash: hash, + } + txCache.txByHash.addTx(wTx) + txs[i] = hash + } + + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + {TxHashes: txs, Type: block.TxBlock}, + }, + } + header := &block.Header{ + Nonce: 10, + } + + accProvider := &txcachemocks.AccountNonceAndBalanceProviderMock{ + GetRootHashCalled: func() ([]byte, error) { + return defaultLatestExecutedHash, nil + }, + } + + err = st.OnProposedBlock([]byte("blockHash"), body, header, accProvider, defaultLatestExecutedHash) + require.Equal(t, errToManyUniqueAccountsInBlock, err) +} + +type twoBlockTrackerSetup struct { + tracker *selectionTracker + blockNonce1 uint64 + blockNonce2 uint64 + blockHashPrev string +} + +// buildTrackerWithTwoSharedSenderBlocks creates a tracker with two consecutive tracked blocks +// that share a common sender (alice) with contiguous nonce ranges and breadcrumbs. +// Block1 (nonce=100): alice nonces 5-7, balance=10 +// Block2 (nonce=101): alice nonces 8-10, balance=10 +func buildTrackerWithTwoSharedSenderBlocks(t *testing.T) *twoBlockTrackerSetup { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + aliceFirstNonceBlock1 := uint64(5) + aliceLastNonceBlock1 := uint64(7) + aliceFirstNonceBlock2 := uint64(8) + aliceLastNonceBlock2 := uint64(10) + balancePerBlock := big.NewInt(10) + + blockNonce1 := uint64(100) + blockNonce2 := uint64(101) + blockHash1 := "blockHash100" + blockHash2 := "blockHash101" + blockHashPrev := "blockHash99" + alice := "alice" + + breadcrumbAlice1 := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{Value: aliceFirstNonceBlock1, HasValue: true}, + lastNonce: core.OptionalUint64{Value: aliceLastNonceBlock1, HasValue: true}, + consumedBalance: new(big.Int).Set(balancePerBlock), + } + breadcrumbAlice2 := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{Value: aliceFirstNonceBlock2, HasValue: true}, + lastNonce: core.OptionalUint64{Value: aliceLastNonceBlock2, HasValue: true}, + consumedBalance: new(big.Int).Set(balancePerBlock), + } + + block1 := &trackedBlock{ + nonce: blockNonce1, + hash: []byte(blockHash1), + prevHash: []byte(blockHashPrev), + breadcrumbsByAddress: map[string]*accountBreadcrumb{ + alice: breadcrumbAlice1, + }, + } + block2 := &trackedBlock{ + nonce: blockNonce2, + hash: []byte(blockHash2), + prevHash: []byte(blockHash1), + breadcrumbsByAddress: map[string]*accountBreadcrumb{ + alice: breadcrumbAlice2, + }, + } + + tracker.blocks[blockHash1] = block1 + tracker.blocks[blockHash2] = block2 + + tracker.globalBreadcrumbsCompiler.updateOnAddedBlock(block1) + tracker.globalBreadcrumbsCompiler.updateOnAddedBlock(block2) + + // Verify the setup is correct + globalBreadcrumbs := tracker.globalBreadcrumbsCompiler.getGlobalBreadcrumbs() + require.Equal(t, aliceFirstNonceBlock1, globalBreadcrumbs[alice].firstNonce.Value) + require.Equal(t, aliceLastNonceBlock2, globalBreadcrumbs[alice].lastNonce.Value) + expectedTotalBalance := new(big.Int).Mul(balancePerBlock, big.NewInt(2)) + require.Equal(t, expectedTotalBalance, globalBreadcrumbs[alice].consumedBalance) + require.Equal(t, 2, len(tracker.blocks)) + require.Equal(t, uint64(1), tracker.globalBreadcrumbsCompiler.getNumGlobalBreadcrumbs()) + + return &twoBlockTrackerSetup{ + tracker: tracker, + blockNonce1: blockNonce1, + blockNonce2: blockNonce2, + blockHashPrev: blockHashPrev, + } +} + +func TestSelectionTracker_removeUpToBlockNoLock_orderedRemoval(t *testing.T) { + t.Parallel() + + setup := buildTrackerWithTwoSharedSenderBlocks(t) + + // Remove both blocks at once (nonce <= blockNonce2) + searchedBlock := newTrackedBlock(setup.blockNonce2, nil, nil) + err := setup.tracker.removeUpToBlockNoLock(searchedBlock) + require.Nil(t, err) + + require.Equal(t, 0, len(setup.tracker.blocks)) + require.Equal(t, uint64(0), setup.tracker.globalBreadcrumbsCompiler.getNumGlobalBreadcrumbs()) +} + +func TestSelectionTracker_removeBlocksAboveOrEqualToNonceNoLock_orderedRemoval(t *testing.T) { + t.Parallel() + + setup := buildTrackerWithTwoSharedSenderBlocks(t) + + // Same scenario as removeBlockEqualOrAboveNoLock but triggered from deriveVirtualSelectionSession path + err := setup.tracker.removeBlocksAboveOrEqualToNonceNoLock(setup.blockNonce1) + require.Nil(t, err) + + require.Equal(t, 0, len(setup.tracker.blocks)) + require.Equal(t, uint64(0), setup.tracker.globalBreadcrumbsCompiler.getNumGlobalBreadcrumbs()) +} + +func TestSelectionTracker_OnExecutedBlock_multipleBlocksWithSharedSender(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 6) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + txCache.tracker = tracker + + aliceInitialNonce := uint64(1) + accountsProvider := &txcachemocks.AccountNonceAndBalanceProviderMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return aliceInitialNonce, big.NewInt(8 * 100000 * oneBillion), true, nil + }, + } + + txHash1 := []byte("txHash1") + txHash2 := []byte("txHash2") + txHash3 := []byte("txHash3") + txHash4 := []byte("txHash4") + blockHash1 := []byte("hash1") + blockHash2 := []byte("hash2") + rootHash0 := []byte("rootHash0") + rootHash1 := []byte("rootHash1") + alice := "alice" + + // Add transactions for alice across two blocks + txCache.txByHash.addTx(createTx(txHash1, alice, 1)) + txCache.txByHash.addTx(createTx(txHash2, alice, 2)) + txCache.txByHash.addTx(createTx(txHash3, alice, 3)) + txCache.txByHash.addTx(createTx(txHash4, alice, 4)) + + // Propose block 1 with alice's tx nonces 1-2 + err = tracker.OnProposedBlock( + blockHash1, + &block.Body{ + MiniBlocks: []*block.MiniBlock{ + {TxHashes: [][]byte{txHash1, txHash2}}, + }, + }, + &block.Header{ + Nonce: 1, + PrevHash: defaultLatestExecutedHash, + RootHash: rootHash0, + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + // Propose block 2 with alice's tx nonces 3-4 + err = tracker.OnProposedBlock( + blockHash2, + &block.Body{ + MiniBlocks: []*block.MiniBlock{ + {TxHashes: [][]byte{txHash3, txHash4}}, + }, + }, + &block.Header{ + Nonce: 2, + PrevHash: blockHash1, + RootHash: rootHash0, + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + require.Equal(t, 2, len(tracker.blocks)) + require.Equal(t, uint64(1), tracker.globalBreadcrumbsCompiler.getNumGlobalBreadcrumbs()) + + // Execute block 2, which removes both blocks at once (nonce <= 2) + err = tracker.OnExecutedBlock(&block.Header{ + Nonce: 2, + PrevHash: blockHash1, + }, rootHash1) + require.Nil(t, err) + + require.Equal(t, 0, len(tracker.blocks)) + require.Equal(t, uint64(0), tracker.globalBreadcrumbsCompiler.getNumGlobalBreadcrumbs()) +} + +func TestSelectionTracker_removeUpToBlockNoLock_orderedRemovalWithRelayer(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + // Test with an account that appears as both sender and relayer across blocks. + // Bob is a sender in block1 and a relayer in block2. + bobFirstNonce := uint64(5) + bobLastNonce := uint64(7) + bobSenderBalance := big.NewInt(10) + bobRelayerBalance := big.NewInt(5) + aliceFirstNonce := uint64(10) + aliceLastNonce := uint64(12) + aliceBalance := big.NewInt(10) + + blockNonce1 := uint64(100) + blockNonce2 := uint64(101) + blockHash1 := "blockHash100" + blockHash2 := "blockHash101" + blockHashPrev := "blockHash99" + alice := "alice" + bob := "bob" + + breadcrumbBobAsSender := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{Value: bobFirstNonce, HasValue: true}, + lastNonce: core.OptionalUint64{Value: bobLastNonce, HasValue: true}, + consumedBalance: new(big.Int).Set(bobSenderBalance), + } + breadcrumbBobAsRelayer := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{Value: 0, HasValue: false}, + lastNonce: core.OptionalUint64{Value: 0, HasValue: false}, + consumedBalance: new(big.Int).Set(bobRelayerBalance), + } + breadcrumbAlice := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{Value: aliceFirstNonce, HasValue: true}, + lastNonce: core.OptionalUint64{Value: aliceLastNonce, HasValue: true}, + consumedBalance: new(big.Int).Set(aliceBalance), + } + + block1 := &trackedBlock{ + nonce: blockNonce1, + hash: []byte(blockHash1), + prevHash: []byte(blockHashPrev), + breadcrumbsByAddress: map[string]*accountBreadcrumb{ + bob: breadcrumbBobAsSender, + }, + } + block2 := &trackedBlock{ + nonce: blockNonce2, + hash: []byte(blockHash2), + prevHash: []byte(blockHash1), + breadcrumbsByAddress: map[string]*accountBreadcrumb{ + bob: breadcrumbBobAsRelayer, + alice: breadcrumbAlice, + }, + } + + tracker.blocks[blockHash1] = block1 + tracker.blocks[blockHash2] = block2 + + tracker.globalBreadcrumbsCompiler.updateOnAddedBlock(block1) + tracker.globalBreadcrumbsCompiler.updateOnAddedBlock(block2) + + // Remove both blocks + searchedBlock := newTrackedBlock(blockNonce2, nil, nil) + err = tracker.removeUpToBlockNoLock(searchedBlock) + require.Nil(t, err) + + require.Equal(t, 0, len(tracker.blocks)) + require.Equal(t, uint64(0), tracker.globalBreadcrumbsCompiler.getNumGlobalBreadcrumbs()) +} + +func TestSelectionTracker_validateBreadcrumbsToleratesPredecessorDiscontinuity(t *testing.T) { + t.Parallel() + + t.Run("should tolerate discontinuous breadcrumbs in predecessor block when new block has no txs for that account", func(t *testing.T) { + t.Parallel() + + // Predecessor block has discontinuous breadcrumbs for alice (firstNonce=52, but sessionNonce=0) + // New block has no breadcrumbs for alice - should be tolerated + breadcrumbAlicePredecessor := newAccountBreadcrumb(core.OptionalUint64{ + Value: 52, + HasValue: true, + }) + breadcrumbAlicePredecessor.lastNonce = core.OptionalUint64{ + Value: 55, + HasValue: true, + } + + // Bob is continuous in both blocks + breadcrumbBobPredecessor := newAccountBreadcrumb(core.OptionalUint64{ + Value: 0, + HasValue: true, + }) + breadcrumbBobPredecessor.lastNonce = core.OptionalUint64{ + Value: 2, + HasValue: true, + } + + breadcrumbBobNewBlock := newAccountBreadcrumb(core.OptionalUint64{ + Value: 3, + HasValue: true, + }) + breadcrumbBobNewBlock.lastNonce = core.OptionalUint64{ + Value: 5, + HasValue: true, + } + + trackedBlocks := []*trackedBlock{ + { + nonce: 100, + hash: []byte("predecessorHash"), + prevHash: []byte("prevHash"), + breadcrumbsByAddress: map[string]*accountBreadcrumb{ + "alice": breadcrumbAlicePredecessor, + "bob": breadcrumbBobPredecessor, + }, + }, + { + nonce: 101, + hash: []byte("newBlockHash"), + prevHash: []byte("predecessorHash"), + breadcrumbsByAddress: map[string]*accountBreadcrumb{ + "bob": breadcrumbBobNewBlock, + }, + }, + } + + accountsProvider := &txcachemocks.AccountNonceAndBalanceProviderMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 0, big.NewInt(1000), true, nil + }, + } + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + err = tracker.validateBreadcrumbsOfTrackedBlocks(trackedBlocks, accountsProvider) + require.Nil(t, err) + }) + + t.Run("should reject when new block has breadcrumbs for an address that is discontinuous in predecessor", func(t *testing.T) { + t.Parallel() + + // Predecessor block has discontinuous breadcrumbs for alice (firstNonce=52, but sessionNonce=0) + breadcrumbAlicePredecessor := newAccountBreadcrumb(core.OptionalUint64{ + Value: 52, + HasValue: true, + }) + breadcrumbAlicePredecessor.lastNonce = core.OptionalUint64{ + Value: 55, + HasValue: true, + } + + // New block also has breadcrumbs for alice - should be rejected + breadcrumbAliceNewBlock := newAccountBreadcrumb(core.OptionalUint64{ + Value: 56, + HasValue: true, + }) + breadcrumbAliceNewBlock.lastNonce = core.OptionalUint64{ + Value: 58, + HasValue: true, + } + + trackedBlocks := []*trackedBlock{ + { + nonce: 100, + hash: []byte("predecessorHash"), + prevHash: []byte("prevHash"), + breadcrumbsByAddress: map[string]*accountBreadcrumb{ + "alice": breadcrumbAlicePredecessor, + }, + }, + { + nonce: 101, + hash: []byte("newBlockHash"), + prevHash: []byte("predecessorHash"), + breadcrumbsByAddress: map[string]*accountBreadcrumb{ + "alice": breadcrumbAliceNewBlock, + }, + }, + } + + accountsProvider := &txcachemocks.AccountNonceAndBalanceProviderMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 0, big.NewInt(1000), true, nil + }, + } + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + err = tracker.validateBreadcrumbsOfTrackedBlocks(trackedBlocks, accountsProvider) + require.Equal(t, errDiscontinuousBreadcrumbs, err) + }) + + t.Run("should still reject when new block itself has discontinuous breadcrumbs (no predecessor)", func(t *testing.T) { + t.Parallel() + + // Single block (the new block) has discontinuous breadcrumbs for alice + breadcrumbAlice := newAccountBreadcrumb(core.OptionalUint64{ + Value: 52, + HasValue: true, + }) + breadcrumbAlice.lastNonce = core.OptionalUint64{ + Value: 55, + HasValue: true, + } + + trackedBlocks := []*trackedBlock{ + { + nonce: 100, + hash: []byte("newBlockHash"), + prevHash: []byte("prevHash"), + breadcrumbsByAddress: map[string]*accountBreadcrumb{ + "alice": breadcrumbAlice, + }, + }, + } + + accountsProvider := &txcachemocks.AccountNonceAndBalanceProviderMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 0, big.NewInt(1000), true, nil + }, + } + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + err = tracker.validateBreadcrumbsOfTrackedBlocks(trackedBlocks, accountsProvider) + require.Equal(t, errDiscontinuousBreadcrumbs, err) + }) + + t.Run("should tolerate discontinuous breadcrumbs in multiple predecessors", func(t *testing.T) { + t.Parallel() + + // Two predecessor blocks with discontinuous breadcrumbs for alice + // New block has no breadcrumbs for alice - should be tolerated + breadcrumbAlice1 := newAccountBreadcrumb(core.OptionalUint64{ + Value: 52, + HasValue: true, + }) + breadcrumbAlice1.lastNonce = core.OptionalUint64{ + Value: 55, + HasValue: true, + } + + breadcrumbAlice2 := newAccountBreadcrumb(core.OptionalUint64{ + Value: 56, + HasValue: true, + }) + breadcrumbAlice2.lastNonce = core.OptionalUint64{ + Value: 58, + HasValue: true, + } + + // Bob is continuous everywhere + breadcrumbBob := newAccountBreadcrumb(core.OptionalUint64{ + Value: 0, + HasValue: true, + }) + breadcrumbBob.lastNonce = core.OptionalUint64{ + Value: 1, + HasValue: true, + } + + trackedBlocks := []*trackedBlock{ + { + nonce: 100, + hash: []byte("predecessorHash1"), + prevHash: []byte("prevHash"), + breadcrumbsByAddress: map[string]*accountBreadcrumb{ + "alice": breadcrumbAlice1, + "bob": breadcrumbBob, + }, + }, + { + nonce: 101, + hash: []byte("predecessorHash2"), + prevHash: []byte("predecessorHash1"), + breadcrumbsByAddress: map[string]*accountBreadcrumb{ + "alice": breadcrumbAlice2, + }, + }, + { + nonce: 102, + hash: []byte("newBlockHash"), + prevHash: []byte("predecessorHash2"), + breadcrumbsByAddress: map[string]*accountBreadcrumb{}, + }, + } + + accountsProvider := &txcachemocks.AccountNonceAndBalanceProviderMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 0, big.NewInt(1000), true, nil + }, + } + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + + err = tracker.validateBreadcrumbsOfTrackedBlocks(trackedBlocks, accountsProvider) + require.Nil(t, err) + }) +} + +func TestSelectionTracker_SelectionSkipsDiscontinuousAccounts(t *testing.T) { + t.Parallel() + + t.Run("selection should skip sender with discontinuous breadcrumbs and select other senders", func(t *testing.T) { + t.Parallel() + + // Setup: + // - alice has discontinuous breadcrumbs in a tracked block (firstNonce=52, sessionNonce=0) + // - bob has continuous breadcrumbs (firstNonce=0, sessionNonce=0) + // - Both have transactions in the pool + // Expected: selection should skip alice's transactions, select bob's + + config := ConfigSourceMe{ + Name: "test", + NumChunks: 16, + NumBytesThreshold: maxNumBytesUpperBound, + NumBytesPerSenderThreshold: maxNumBytesPerSenderUpperBoundTest, + CountThreshold: math.MaxUint32, + CountPerSenderThreshold: math.MaxUint32, + EvictionEnabled: false, + NumItemsToPreemptivelyEvict: 1, + TxCacheBoundsConfig: createMockTxBoundsConfig(), + } + + host := txcachemocks.NewMempoolHostMock() + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + + // Add transactions for both alice and bob + // Alice: nonces 0, 1, 2 (these would be selected if not blocked) + // Bob: nonces 0, 1, 2 + txs := []*WrappedTransaction{ + createTx([]byte("txAlice0"), "alice", 0).withValue(big.NewInt(0)), + createTx([]byte("txAlice1"), "alice", 1).withValue(big.NewInt(0)), + createTx([]byte("txAlice2"), "alice", 2).withValue(big.NewInt(0)), + createTx([]byte("txBob0"), "bob", 0).withValue(big.NewInt(0)), + createTx([]byte("txBob1"), "bob", 1).withValue(big.NewInt(0)), + createTx([]byte("txBob2"), "bob", 2).withValue(big.NewInt(0)), + } + + for _, tx := range txs { + cache.AddTx(tx) + } + + // Manually set up a tracked block with discontinuous breadcrumbs for alice + // alice: firstNonce=52 (but sessionNonce=0, so discontinuous) + // bob: firstNonce=0 (continuous) + breadcrumbAlice := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{Value: 52, HasValue: true}, + lastNonce: core.OptionalUint64{Value: 55, HasValue: true}, + consumedBalance: big.NewInt(0), + } + breadcrumbBob := &accountBreadcrumb{ + firstNonce: core.OptionalUint64{Value: 0, HasValue: true}, + lastNonce: core.OptionalUint64{Value: 2, HasValue: true}, + consumedBalance: big.NewInt(0), + } + + tb := &trackedBlock{ + nonce: 100, + hash: []byte("trackedBlockHash"), + prevHash: []byte("prevBlockHash"), + breadcrumbsByAddress: map[string]*accountBreadcrumb{ + "alice": breadcrumbAlice, + "bob": breadcrumbBob, + }, + } + + cache.tracker.blocks["trackedBlockHash"] = tb + cache.tracker.globalBreadcrumbsCompiler.updateOnAddedBlock(tb) + cache.tracker.latestRootHash = []byte("rootHash0") + cache.tracker.latestNonce = 99 + + selectionSession := &txcachemocks.SelectionSessionMock{ + GetRootHashCalled: func() ([]byte, error) { + return []byte("rootHash0"), nil + }, + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 0, big.NewInt(8 * 100000 * oneBillion), true, nil + }, + } + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + 100, + 10, + haveTimeTrueForSelection, + ) + + selectedTxs, _, err := cache.SelectTransactions(selectionSession, options, 101) + require.Nil(t, err) + + // Alice should be skipped entirely (discontinuous breadcrumbs \u2192 blocked virtual record) + // Bob's nonces 0-2 are in the tracked block, so selection starts from nonce 3+, but bob has no nonce 3 + // So no transactions should be selected for bob either (they're already proposed) + // Let's verify alice's txs are NOT selected + for _, tx := range selectedTxs { + sender := string(tx.Tx.GetSndAddr()) + require.NotEqual(t, "alice", sender, "alice's transactions should not be selected due to discontinuous breadcrumbs") + } + }) +} + +func TestSelectionTracker_RecoveryFromDiscontinuousBreadcrumbs(t *testing.T) { + t.Parallel() + + // End-to-end scenario: + // 1. A tracked block has discontinuous breadcrumbs for alice + // 2. Selection skips alice (creates blocked virtual record) + // 3. OnProposedBlock succeeds (new block has no txs for alice, predecessor discontinuity is tolerated) + // 4. OnExecutedBlock removes the stale predecessor block + // 5. After cleanup, alice's transactions can be selected again + + accountNonces := map[string]uint64{ + "alice": 0, + "bob": 0, + } + + accountsProvider := &txcachemocks.AccountNonceAndBalanceProviderMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + nonce := accountNonces[string(address)] + return nonce, big.NewInt(8 * 100000 * oneBillion), true, nil + }, + GetRootHashCalled: func() ([]byte, error) { + return []byte("rootHash0"), nil + }, + } + + config := ConfigSourceMe{ + Name: "test", + NumChunks: 16, + NumBytesThreshold: maxNumBytesUpperBound, + NumBytesPerSenderThreshold: maxNumBytesPerSenderUpperBoundTest, + CountThreshold: math.MaxUint32, + CountPerSenderThreshold: math.MaxUint32, + EvictionEnabled: false, + NumItemsToPreemptivelyEvict: 1, + TxCacheBoundsConfig: createMockTxBoundsConfig(), + } + + host := txcachemocks.NewMempoolHostMock() + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + + // Add alice's and bob's transactions + txs := []*WrappedTransaction{ + createTx([]byte("txAlice0"), "alice", 0).withValue(big.NewInt(0)), + createTx([]byte("txAlice1"), "alice", 1).withValue(big.NewInt(0)), + createTx([]byte("txBob0"), "bob", 0).withValue(big.NewInt(0)), + createTx([]byte("txBob1"), "bob", 1).withValue(big.NewInt(0)), + } + for _, tx := range txs { + cache.AddTx(tx) + } + + // Step 1: Manually create a stale tracked block with discontinuous breadcrumbs for alice + // (simulates the effect of a block replacement that made alice's breadcrumbs stale) + staleBlock := &trackedBlock{ + nonce: 100, + hash: []byte("staleBlockHash"), + prevHash: []byte("prevBlockHash"), + breadcrumbsByAddress: map[string]*accountBreadcrumb{ + "alice": { + firstNonce: core.OptionalUint64{Value: 52, HasValue: true}, + lastNonce: core.OptionalUint64{Value: 55, HasValue: true}, + consumedBalance: big.NewInt(0), + }, + }, + } + + cache.tracker.blocks["staleBlockHash"] = staleBlock + cache.tracker.globalBreadcrumbsCompiler.updateOnAddedBlock(staleBlock) + cache.tracker.latestRootHash = []byte("rootHash0") + cache.tracker.latestNonce = 99 + + // Step 2: Propose a new block with only bob's transactions + // This should succeed because: + // - alice's discontinuous breadcrumbs in predecessor are tolerated + // - new block has no txs for alice + cache.txByHash.addTx(createTx([]byte("txBob0_proposed"), "bob", 0).withValue(big.NewInt(0))) + + err = cache.OnProposedBlock( + []byte("newBlockHash"), + &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("txBob0_proposed"), + }, + }, + }, + }, + &block.Header{ + Nonce: 101, + PrevHash: []byte("staleBlockHash"), + RootHash: []byte("rootHash0"), + }, + accountsProvider, + []byte("prevBlockHash"), + ) + require.Nil(t, err) + require.Equal(t, 2, len(cache.tracker.blocks)) + + // Step 3: Execute the stale block (simulates OnExecutedBlock removing it) + err = cache.OnExecutedBlock(&block.Header{ + Nonce: 100, + PrevHash: []byte("prevBlockHash"), + }, []byte("rootHash1")) + require.Nil(t, err) + require.Equal(t, 1, len(cache.tracker.blocks)) + + // Step 4: After the stale block is removed, alice's breadcrumbs are no longer in any tracked block + // Now verify alice can be selected in the next selection + selectionSession := &txcachemocks.SelectionSessionMock{ + GetRootHashCalled: func() ([]byte, error) { + return []byte("rootHash1"), nil + }, + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + nonce := accountNonces[string(address)] + return nonce, big.NewInt(8 * 100000 * oneBillion), true, nil + }, + } + + options, _ := holders.NewTxSelectionOptions( + 10_000_000_000, + 100, + 10, + haveTimeTrueForSelection, + ) + + selectedTxs, _, err := cache.SelectTransactions(selectionSession, options, 102) + require.Nil(t, err) + + // Alice should now be selectable since the stale block was removed + hasAliceTx := false + for _, tx := range selectedTxs { + if string(tx.Tx.GetSndAddr()) == "alice" { + hasAliceTx = true + break + } + } + require.True(t, hasAliceTx, "alice should be selectable after stale tracked block is removed") +} + +func createDummyTrackedBlocks() map[string]*trackedBlock { + return map[string]*trackedBlock{ + "hash1": { + nonce: 1, + hash: []byte("hash1"), + }, + "hash2": { + nonce: 2, + hash: []byte("hash2"), + }, + "hash3": { + nonce: 3, + hash: []byte("hash3"), + }, + } +} diff --git a/txcache/selection_test.go b/txcache/selection_test.go new file mode 100644 index 00000000000..ca11769fb77 --- /dev/null +++ b/txcache/selection_test.go @@ -0,0 +1,1049 @@ +package txcache + +import ( + "bytes" + "errors" + "fmt" + "math" + "math/big" + "strings" + "testing" + "time" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/common/holders" + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/testscommon/txcachemocks" +) + +var expectedError = errors.New("expected error") + +func createMockTxSelectionOptions(gasRequested uint64, maxNumTxs int) common.TxSelectionOptions { + options, _ := holders.NewTxSelectionOptions( + gasRequested, + maxNumTxs, + 10, + haveTimeTrueForSelection, + ) + return options +} + +func createMockTxSelectionOptionsWithTimeFunc(gasRequested uint64, maxNumTxs int, haveTimeFunc func() bool) common.TxSelectionOptions { + options, _ := holders.NewTxSelectionOptions( + gasRequested, + maxNumTxs, + 10, + haveTimeFunc, + ) + return options +} + +func createMockTxBoundsConfig() config.TxCacheBoundsConfig { + return config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: maxNumBytesPerSenderUpperBoundTest, + MaxTrackedBlocks: maxTrackedBlocks, + } +} + +func TestTxCache_SelectTransactions(t *testing.T) { + t.Parallel() + + t.Run("should return errNilSelectionSession error", func(t *testing.T) { + t.Parallel() + + options := createMockTxSelectionOptions(math.MaxUint64, math.MaxInt) + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + _, _, err := cache.SelectTransactions(nil, options, 0) + require.Equal(t, errNilSelectionSession, err) + }) + + t.Run("should return the error from GetRootHash", func(t *testing.T) { + t.Parallel() + + options := createMockTxSelectionOptions(math.MaxUint64, math.MaxInt) + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + session := &txcachemocks.SelectionSessionMock{ + GetRootHashCalled: func() ([]byte, error) { + return nil, expectedError + }, + } + + _, _, err := cache.SelectTransactions(session, options, 0) + require.Equal(t, expectedError, err) + }) +} + +func TestTxCache_SelectTransactions_Dummy(t *testing.T) { + t.Run("all having same PPU", func(t *testing.T) { + options := createMockTxSelectionOptions(math.MaxUint64, math.MaxInt) + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + session := txcachemocks.NewSelectionSessionMock() + session.SetNonce([]byte("alice"), 1) + session.SetNonce([]byte("bob"), 5) + session.SetNonce([]byte("carol"), 1) + + cache.AddTx(createRelayedTx([]byte("hash-alice-4"), "alice", "relayer", 4)) + cache.AddTx(createRelayedTx([]byte("hash-alice-3"), "alice", "relayer", 3)) + cache.AddTx(createRelayedTx([]byte("hash-alice-2"), "alice", "relayer", 2)) + cache.AddTx(createRelayedTx([]byte("hash-alice-1"), "alice", "relayer", 1)) + cache.AddTx(createRelayedTx([]byte("hash-bob-7"), "bob", "relayer", 7)) + cache.AddTx(createRelayedTx([]byte("hash-bob-6"), "bob", "relayer", 6)) + cache.AddTx(createRelayedTx([]byte("hash-bob-5"), "bob", "relayer", 5)) + cache.AddTx(createRelayedTx([]byte("hash-carol-1"), "carol", "relayer", 1)) + + selected, accumulatedGas, err := cache.SelectTransactions(session, options, 0) + require.NoError(t, err) + require.Len(t, selected, 8) + require.Equal(t, 400000, int(accumulatedGas)) + + // Check order + require.Equal(t, "hash-alice-1", string(selected[0].TxHash)) + require.Equal(t, "hash-alice-2", string(selected[1].TxHash)) + require.Equal(t, "hash-alice-3", string(selected[2].TxHash)) + require.Equal(t, "hash-alice-4", string(selected[3].TxHash)) + require.Equal(t, "hash-bob-5", string(selected[4].TxHash)) + require.Equal(t, "hash-bob-6", string(selected[5].TxHash)) + require.Equal(t, "hash-bob-7", string(selected[6].TxHash)) + require.Equal(t, "hash-carol-1", string(selected[7].TxHash)) + }) + + t.Run("alice > carol > bob", func(t *testing.T) { + options := createMockTxSelectionOptions(math.MaxUint64, math.MaxInt) + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + session := txcachemocks.NewSelectionSessionMock() + session.SetNonce([]byte("alice"), 1) + session.SetNonce([]byte("bob"), 5) + session.SetNonce([]byte("carol"), 3) + + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1).withGasPrice(100).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-bob-5"), "bob", 5).withGasPrice(50).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-carol-3"), "carol", 3).withGasPrice(75).withValue(big.NewInt(0))) + + selected, accumulatedGas, err := cache.SelectTransactions(session, options, 0) + require.NoError(t, err) + require.Len(t, selected, 3) + require.Equal(t, 150000, int(accumulatedGas)) + + // Check order + require.Equal(t, "hash-alice-1", string(selected[0].TxHash)) + require.Equal(t, "hash-carol-3", string(selected[1].TxHash)) + require.Equal(t, "hash-bob-5", string(selected[2].TxHash)) + }) +} + +func TestTxCache_SelectTransactionsWithBandwidth_Dummy(t *testing.T) { + t.Run("transactions with no data field", func(t *testing.T) { + options := createMockTxSelectionOptions(760000, math.MaxInt) + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + session := txcachemocks.NewSelectionSessionMock() + session.SetNonce([]byte("alice"), 1) + session.SetNonce([]byte("bob"), 5) + session.SetNonce([]byte("carol"), 1) + + cache.AddTx(createTx([]byte("hash-alice-4"), "alice", 4).withGasLimit(100000).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-3"), "alice", 3).withGasLimit(100000).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-2"), "alice", 2).withGasLimit(500000).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1).withGasLimit(200000).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-bob-7"), "bob", 7).withGasLimit(400000).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-bob-6"), "bob", 6).withGasLimit(50000).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-bob-5"), "bob", 5).withGasLimit(50000).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-carol-1"), "carol", 1).withGasLimit(50000).withValue(big.NewInt(0))) + + selected, accumulatedGas, err := cache.SelectTransactions(session, options, 0) + require.NoError(t, err) + require.Len(t, selected, 5) + require.Equal(t, 750000, int(accumulatedGas)) + + // Check order + require.Equal(t, "hash-bob-5", string(selected[0].TxHash)) + require.Equal(t, "hash-bob-6", string(selected[1].TxHash)) + require.Equal(t, "hash-carol-1", string(selected[2].TxHash)) + require.Equal(t, "hash-alice-1", string(selected[3].TxHash)) + require.Equal(t, "hash-bob-7", string(selected[4].TxHash)) + }) +} + +func TestTxCache_SelectTransactions_HandlesNotExecutableTransactions(t *testing.T) { + t.Run("with middle gaps", func(t *testing.T) { + options := createMockTxSelectionOptions(math.MaxUint64, math.MaxInt) + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + session := txcachemocks.NewSelectionSessionMock() + session.SetNonce([]byte("alice"), 1) + session.SetNonce([]byte("bob"), 42) + session.SetNonce([]byte("carol"), 7) + + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-2"), "alice", 2).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-3"), "alice", 3).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-5"), "alice", 5).withValue(big.NewInt(0))) // gap + cache.AddTx(createTx([]byte("hash-bob-42"), "bob", 42).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-bob-44"), "bob", 44).withValue(big.NewInt(0))) // gap + cache.AddTx(createTx([]byte("hash-bob-45"), "bob", 45).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-carol-7"), "carol", 7).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-carol-8"), "carol", 8).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-carol-10"), "carol", 10).withValue(big.NewInt(0))) // gap + cache.AddTx(createTx([]byte("hash-carol-11"), "carol", 11).withValue(big.NewInt(0))) + + sorted, accumulatedGas, err := cache.SelectTransactions(session, options, 0) + require.NoError(t, err) + + expectedNumSelected := 3 + 1 + 2 // 3 alice + 1 bob + 2 carol + require.Len(t, sorted, expectedNumSelected) + require.Equal(t, 300000, int(accumulatedGas)) + }) + + t.Run("with initial gaps", func(t *testing.T) { + options := createMockTxSelectionOptions(math.MaxUint64, math.MaxInt) + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + session := txcachemocks.NewSelectionSessionMock() + session.SetNonce([]byte("alice"), 1) + session.SetNonce([]byte("bob"), 42) + session.SetNonce([]byte("carol"), 7) + + // Good + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-2"), "alice", 2).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-3"), "alice", 3).withValue(big.NewInt(0))) + + // Initial gap + cache.AddTx(createTx([]byte("hash-bob-42"), "bob", 44).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-bob-43"), "bob", 45).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-bob-44"), "bob", 46).withValue(big.NewInt(0))) + + // Good + cache.AddTx(createTx([]byte("hash-carol-7"), "carol", 7).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-carol-8"), "carol", 8).withValue(big.NewInt(0))) + + sorted, accumulatedGas, err := cache.SelectTransactions(session, options, 0) + require.NoError(t, err) + + expectedNumSelected := 3 + 0 + 2 // 3 alice + 0 bob + 2 carol + require.Len(t, sorted, expectedNumSelected) + require.Equal(t, 250000, int(accumulatedGas)) + }) + + t.Run("with lower nonces", func(t *testing.T) { + options := createMockTxSelectionOptions(math.MaxUint64, math.MaxInt) + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + session := txcachemocks.NewSelectionSessionMock() + session.SetNonce([]byte("alice"), 1) + session.SetNonce([]byte("bob"), 42) + session.SetNonce([]byte("carol"), 7) + + // Good + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-2"), "alice", 2).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-3"), "alice", 3).withValue(big.NewInt(0))) + + // A few with lower nonce + cache.AddTx(createTx([]byte("hash-bob-42"), "bob", 40).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-bob-43"), "bob", 41).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-bob-44"), "bob", 42).withValue(big.NewInt(0))) + + // Good + cache.AddTx(createTx([]byte("hash-carol-7"), "carol", 7).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-carol-8"), "carol", 8).withValue(big.NewInt(0))) + + sorted, accumulatedGas, err := cache.SelectTransactions(session, options, 0) + require.NoError(t, err) + + expectedNumSelected := 3 + 1 + 2 // 3 alice + 1 bob + 2 carol + require.Len(t, sorted, expectedNumSelected) + require.Equal(t, 300000, int(accumulatedGas)) + }) + + t.Run("with duplicated nonces", func(t *testing.T) { + options := createMockTxSelectionOptions(math.MaxUint64, math.MaxInt) + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + session := txcachemocks.NewSelectionSessionMock() + session.SetNonce([]byte("alice"), 1) + + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-2"), "alice", 2).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-3a"), "alice", 3).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-3b"), "alice", 3).withGasPrice(oneBillion * 2).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-3c"), "alice", 3).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-4"), "alice", 4).withValue(big.NewInt(0))) + + sorted, accumulatedGas, err := cache.SelectTransactions(session, options, 0) + require.NoError(t, err) + require.Len(t, sorted, 4) + require.Equal(t, 200000, int(accumulatedGas)) + + require.Equal(t, "hash-alice-1", string(sorted[0].TxHash)) + require.Equal(t, "hash-alice-2", string(sorted[1].TxHash)) + require.Equal(t, "hash-alice-3b", string(sorted[2].TxHash)) + require.Equal(t, "hash-alice-4", string(sorted[3].TxHash)) + }) + + t.Run("with fee exceeding balance", func(t *testing.T) { + options := createMockTxSelectionOptions(math.MaxUint64, math.MaxInt) + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + session := txcachemocks.NewSelectionSessionMock() + session.SetNonce([]byte("alice"), 1) + session.SetBalance([]byte("alice"), big.NewInt(150000000000000)) + session.SetNonce([]byte("bob"), 42) + session.SetBalance([]byte("bob"), big.NewInt(70000000000000)) + + // Enough balance + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-2"), "alice", 2).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-3"), "alice", 3).withValue(big.NewInt(0))) + + // Not enough balance + cache.AddTx(createTx([]byte("hash-bob-42"), "bob", 40).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-bob-43"), "bob", 41).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-bob-44"), "bob", 42).withValue(big.NewInt(0))) + + sorted, accumulatedGas, err := cache.SelectTransactions(session, options, 0) + require.NoError(t, err) + + expectedNumSelected := 3 + 1 // 3 alice + 1 bob + require.Len(t, sorted, expectedNumSelected) + require.Equal(t, 200000, int(accumulatedGas)) + }) + + t.Run("with incorrectly guarded", func(t *testing.T) { + options := createMockTxSelectionOptions(math.MaxUint64, math.MaxInt) + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + session := txcachemocks.NewSelectionSessionMock() + session.SetNonce([]byte("alice"), 1) + session.SetNonce([]byte("bob"), 42) + + session.IsIncorrectlyGuardedCalled = func(tx data.TransactionHandler) bool { + return bytes.Equal(tx.GetData(), []byte("t")) + } + + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1).withData([]byte("x")).withGasLimit(100000).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-bob-42a"), "bob", 42).withData([]byte("y")).withGasLimit(100000).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-bob-43a"), "bob", 43).withData([]byte("z")).withGasLimit(100000).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-bob-43b"), "bob", 43).withData([]byte("t")).withGasLimit(100000).withValue(big.NewInt(0))) + + sorted, accumulatedGas, err := cache.SelectTransactions(session, options, 0) + require.NoError(t, err) + require.Len(t, sorted, 3) + require.Equal(t, 300000, int(accumulatedGas)) + + require.Equal(t, "hash-alice-1", string(sorted[0].TxHash)) + require.Equal(t, "hash-bob-42a", string(sorted[1].TxHash)) + require.Equal(t, "hash-bob-43a", string(sorted[2].TxHash)) + }) + + t.Run("with change guardian", func(t *testing.T) { + options := createMockTxSelectionOptions(math.MaxUint64, math.MaxInt) + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + session := txcachemocks.NewSelectionSessionMock() + session.SetNonce([]byte("alice"), 1) + + session.IsIncorrectlyGuardedCalled = func(tx data.TransactionHandler) bool { + return false + } + session.IsGuardedCalled = func(tx data.TransactionHandler) bool { + return strings.Contains(string(tx.GetData()), "SetGuardian") + } + + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-2"), "alice", 2).withValue(big.NewInt(0)).withData([]byte("SetGuardian@newGuardian")).withGasLimit(100000)) + cache.AddTx(createTx([]byte("hash-alice-3"), "alice", 3).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-4"), "alice", 4).withValue(big.NewInt(0))) + + sorted, accumulatedGas, err := cache.SelectTransactions(session, options, 0) + require.NoError(t, err) + require.Len(t, sorted, 2) + require.Equal(t, 150000, int(accumulatedGas)) + + require.Equal(t, "hash-alice-1", string(sorted[0].TxHash)) + require.Equal(t, "hash-alice-2", string(sorted[1].TxHash)) + }) +} + +func TestTxCache_SelectTransactions_WhenTransactionsAddedInReversedNonceOrder(t *testing.T) { + options := createMockTxSelectionOptions(math.MaxUint64, math.MaxInt) + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + session := txcachemocks.NewSelectionSessionMock() + + // Add "nSenders" * "nTransactionsPerSender" transactions in the cache (in reversed nonce order) + nSenders := 1000 + nTransactionsPerSender := 100 + nTotalTransactions := nSenders * nTransactionsPerSender + + for senderTag := 0; senderTag < nSenders; senderTag++ { + sender := fmt.Sprintf("sender:%d", senderTag) + + for txNonce := nTransactionsPerSender - 1; txNonce >= 0; txNonce-- { + txHash := fmt.Sprintf("hash:%d:%d", senderTag, txNonce) + tx := createTx([]byte(txHash), sender, uint64(txNonce)).withValue(big.NewInt(0)) + cache.AddTx(tx) + } + } + + require.Equal(t, uint64(nTotalTransactions), cache.CountTx()) + + sorted, accumulatedGas, err := cache.SelectTransactions(session, options, 0) + require.NoError(t, err) + require.Len(t, sorted, nTotalTransactions) + require.Equal(t, 5_000_000_000, int(accumulatedGas)) + + // Check order + nonces := make(map[string]uint64, nSenders) + + for _, tx := range sorted { + nonce := tx.Tx.GetNonce() + sender := string(tx.Tx.GetSndAddr()) + previousNonce := nonces[sender] + + require.LessOrEqual(t, previousNonce, nonce) + nonces[sender] = nonce + } +} + +func TestTxCache_selectTransactionsFromBunches(t *testing.T) { + t.Run("empty cache", func(t *testing.T) { + session := txcachemocks.NewSelectionSessionMock() + virtualSession := newVirtualSelectionSession(session, make(map[string]*virtualAccountRecord)) + options := createMockTxSelectionOptions(10_000_000_000, math.MaxInt) + selected, accumulatedGas := selectTransactionsFromBunches(virtualSession, []bunchOfTransactions{}, options, 0) + + require.Equal(t, 0, len(selected)) + require.Equal(t, uint64(0), accumulatedGas) + }) +} + +func TestBenchmarkTxCache_acquireBunchesOfTransactions(t *testing.T) { + cfg := ConfigSourceMe{ + Name: "untitled", + NumChunks: 16, + NumBytesThreshold: 1000000000, + NumBytesPerSenderThreshold: maxNumBytesPerSenderUpperBoundTest, + CountThreshold: 300001, + CountPerSenderThreshold: math.MaxUint32, + EvictionEnabled: false, + NumItemsToPreemptivelyEvict: 1, + TxCacheBoundsConfig: createMockTxBoundsConfig(), + } + + host := txcachemocks.NewMempoolHostMock() + + sw := core.NewStopWatch() + + t.Run("numSenders = 10000, numTransactions = 100", func(t *testing.T) { + cache, err := NewTxCache(cfg, host, 0) + require.Nil(t, err) + + addManyTransactionsWithUniformDistribution(cache, 10000, 100) + + require.Equal(t, 1000000, int(cache.CountTx())) + + sw.Start(t.Name()) + bunches := cache.acquireBunchesOfTransactions() + sw.Stop(t.Name()) + + require.Len(t, bunches, 10000) + require.Len(t, bunches[0], 100) + require.Len(t, bunches[len(bunches)-1], 100) + }) + + t.Run("numSenders = 50000, numTransactions = 2", func(t *testing.T) { + cache, err := NewTxCache(cfg, host, 0) + require.Nil(t, err) + + addManyTransactionsWithUniformDistribution(cache, 50000, 2) + + require.Equal(t, 100000, int(cache.CountTx())) + + sw.Start(t.Name()) + bunches := cache.acquireBunchesOfTransactions() + sw.Stop(t.Name()) + + require.Len(t, bunches, 50000) + require.Len(t, bunches[0], 2) + require.Len(t, bunches[len(bunches)-1], 2) + }) + + t.Run("numSenders = 100000, numTransactions = 1", func(t *testing.T) { + cache, err := NewTxCache(cfg, host, 0) + require.Nil(t, err) + + addManyTransactionsWithUniformDistribution(cache, 100000, 1) + + require.Equal(t, 100000, int(cache.CountTx())) + + sw.Start(t.Name()) + bunches := cache.acquireBunchesOfTransactions() + sw.Stop(t.Name()) + + require.Len(t, bunches, 100000) + require.Len(t, bunches[0], 1) + require.Len(t, bunches[len(bunches)-1], 1) + }) + + t.Run("numSenders = 300000, numTransactions = 1", func(t *testing.T) { + cache, err := NewTxCache(cfg, host, 0) + require.Nil(t, err) + + addManyTransactionsWithUniformDistribution(cache, 300000, 1) + + require.Equal(t, 300000, int(cache.CountTx())) + + sw.Start(t.Name()) + bunches := cache.acquireBunchesOfTransactions() + sw.Stop(t.Name()) + + require.Len(t, bunches, 300000) + require.Len(t, bunches[0], 1) + require.Len(t, bunches[len(bunches)-1], 1) + }) + + for name, measurement := range sw.GetMeasurementsMap() { + fmt.Printf("%fs (%s)\n", measurement, name) + } + + // (1) + // Vendor ID: GenuineIntel + // Model name: 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz + // CPU family: 6 + // Model: 140 + // Thread(s) per core: 2 + // Core(s) per socket: 4 + // + // 0.014468s (TestBenchmarkTxCache_acquireBunchesOfTransactions/numSenders_=_10000,_numTransactions_=_100) + // 0.019183s (TestBenchmarkTxCache_acquireBunchesOfTransactions/numSenders_=_50000,_numTransactions_=_2) + // 0.013876s (TestBenchmarkTxCache_acquireBunchesOfTransactions/numSenders_=_100000,_numTransactions_=_1) + // 0.056631s (TestBenchmarkTxCache_acquireBunchesOfTransactions/numSenders_=_300000,_numTransactions_=_1) +} + +func TestBenchmarkTxCache_selectTransactionsFromBunches(t *testing.T) { + sw := core.NewStopWatch() + + t.Run("numSenders = 1000, numTransactions = 1000", func(t *testing.T) { + options := createMockTxSelectionOptions(10_000_000_000, math.MaxInt) + session := txcachemocks.NewSelectionSessionMock() + virtualSession := newVirtualSelectionSession(session, make(map[string]*virtualAccountRecord)) + bunches := createBunchesOfTransactionsWithUniformDistribution(1000, 1000) + + sw.Start(t.Name()) + selected, accumulatedGas := selectTransactionsFromBunches(virtualSession, bunches, options, 0) + sw.Stop(t.Name()) + + require.Equal(t, 200000, len(selected)) + require.Equal(t, uint64(10_000_000_000), accumulatedGas) + }) + + t.Run("numSenders = 10000, numTransactions = 100", func(t *testing.T) { + options := createMockTxSelectionOptions(10_000_000_000, math.MaxInt) + session := txcachemocks.NewSelectionSessionMock() + virtualSession := newVirtualSelectionSession(session, make(map[string]*virtualAccountRecord)) + bunches := createBunchesOfTransactionsWithUniformDistribution(1000, 1000) + + sw.Start(t.Name()) + selected, accumulatedGas := selectTransactionsFromBunches(virtualSession, bunches, options, 0) + sw.Stop(t.Name()) + + require.Equal(t, 200000, len(selected)) + require.Equal(t, uint64(10_000_000_000), accumulatedGas) + }) + + t.Run("numSenders = 100000, numTransactions = 3", func(t *testing.T) { + options := createMockTxSelectionOptions(10_000_000_000, math.MaxInt) + session := txcachemocks.NewSelectionSessionMock() + virtualSession := newVirtualSelectionSession(session, make(map[string]*virtualAccountRecord)) + bunches := createBunchesOfTransactionsWithUniformDistribution(100000, 3) + + sw.Start(t.Name()) + selected, accumulatedGas := selectTransactionsFromBunches(virtualSession, bunches, options, 0) + sw.Stop(t.Name()) + + // Limited by maxAccountsPerBlock (maxAccountsPerBlock unique senders); exact count depends on heap interleaving + require.Greater(t, len(selected), maxAccountsPerBlock) + require.LessOrEqual(t, len(selected), 30000) + require.Greater(t, accumulatedGas, uint64(maxAccountsPerBlock*50000)) + }) + + t.Run("numSenders = 300000, numTransactions = 1", func(t *testing.T) { + options := createMockTxSelectionOptions(10_000_000_000, math.MaxInt) + session := txcachemocks.NewSelectionSessionMock() + virtualSession := newVirtualSelectionSession(session, make(map[string]*virtualAccountRecord)) + + bunches := createBunchesOfTransactionsWithUniformDistribution(300000, 1) + + sw.Start(t.Name()) + selected, accumulatedGas := selectTransactionsFromBunches(virtualSession, bunches, options, 0) + sw.Stop(t.Name()) + + // Limited by maxAccountsPerBlock (maxAccountsPerBlock unique senders) + require.Equal(t, maxAccountsPerBlock, len(selected)) + require.Equal(t, uint64(maxAccountsPerBlock*50000), accumulatedGas) + }) + + for name, measurement := range sw.GetMeasurementsMap() { + fmt.Printf("%fs (%s)\n", measurement, name) + } + + // (1) + // Vendor ID: GenuineIntel + // Model name: 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz + // CPU family: 6 + // Model: 140 + // Thread(s) per core: 2 + // Core(s) per socket: 4 + // + // 0.074999s (TestBenchmarkTxCache_selectTransactionsFromBunches/numSenders_=_1000,_numTransactions_=_1000) + // 0.059256s (TestBenchmarkTxCache_selectTransactionsFromBunches/numSenders_=_10000,_numTransactions_=_100) + // 0.389317s (TestBenchmarkTxCache_selectTransactionsFromBunches/numSenders_=_100000,_numTransactions_=_3) + // 0.498457s (TestBenchmarkTxCache_selectTransactionsFromBunches/numSenders_=_300000,_numTransactions_=_1) +} + +func TestTxCache_selectTransactionsFromBunches_loopBreaks_whenTakesTooLong(t *testing.T) { + t.Run("numSenders = 300000, numTransactions = 1", func(t *testing.T) { + session := txcachemocks.NewSelectionSessionMock() + virtualSession := newVirtualSelectionSession(session, make(map[string]*virtualAccountRecord)) + options := createMockTxSelectionOptionsWithTimeFunc(10_000_000_000, 50_000, haveTimeFalseForSelection) + bunches := createBunchesOfTransactionsWithUniformDistribution(300000, 1) + selected, accumulatedGas := selectTransactionsFromBunches(virtualSession, bunches, options, 0) + + require.Less(t, len(selected), 50_000) + require.Less(t, int(accumulatedGas), 10_000_000_000) + }) +} + +func TestBenchmarkTxCache_doSelectTransactions(t *testing.T) { + options := createMockTxSelectionOptions(10_000_000_000, 30_000) + cfg := ConfigSourceMe{ + Name: "untitled", + NumChunks: 16, + NumBytesThreshold: 1000000000, + NumBytesPerSenderThreshold: maxNumBytesPerSenderUpperBoundTest, + CountThreshold: 300001, + CountPerSenderThreshold: math.MaxUint32, + EvictionEnabled: false, + NumItemsToPreemptivelyEvict: 1, + TxCacheBoundsConfig: createMockTxBoundsConfig(), + } + + host := txcachemocks.NewMempoolHostMock() + session := txcachemocks.NewSelectionSessionMock() + + sw := core.NewStopWatch() + + t.Run("numSenders = 10000, numTransactions = 100, maxNum = 30_000", func(t *testing.T) { + cache, err := NewTxCache(cfg, host, 0) + require.Nil(t, err) + + addManyTransactionsWithUniformDistribution(cache, 10000, 100) + + require.Equal(t, 1000000, int(cache.CountTx())) + + sw.Start(t.Name()) + selected, accumulatedGas, err := cache.SelectTransactions(session, options, 0) + sw.Stop(t.Name()) + + require.NoError(t, err) + require.Equal(t, 30_000, len(selected)) + require.Equal(t, uint64(1_500_000_000), accumulatedGas) + }) + + t.Run("numSenders = 50000, numTransactions = 2, maxNum = 30_000", func(t *testing.T) { + cache, err := NewTxCache(cfg, host, 0) + require.Nil(t, err) + + addManyTransactionsWithUniformDistribution(cache, 50000, 2) + + require.Equal(t, 100000, int(cache.CountTx())) + + sw.Start(t.Name()) + selected, accumulatedGas, err := cache.SelectTransactions(session, options, 0) + sw.Stop(t.Name()) + + require.NoError(t, err) + // Limited by maxAccountsPerBlock (maxAccountsPerBlock unique senders); exact count depends on heap interleaving + require.Greater(t, len(selected), maxAccountsPerBlock) + require.LessOrEqual(t, len(selected), 20_000) + require.Greater(t, accumulatedGas, uint64(500_000_000)) + }) + + t.Run("numSenders = 100000, numTransactions = 1, maxNum = 30_000", func(t *testing.T) { + cache, err := NewTxCache(cfg, host, 0) + require.Nil(t, err) + + addManyTransactionsWithUniformDistribution(cache, 100000, 1) + + require.Equal(t, 100000, int(cache.CountTx())) + + sw.Start(t.Name()) + selected, accumulatedGas, err := cache.SelectTransactions(session, options, 0) + sw.Stop(t.Name()) + + require.NoError(t, err) + // Limited by maxAccountsPerBlock (10000 unique senders with 1 tx each) + require.Equal(t, maxAccountsPerBlock, len(selected)) + require.Equal(t, uint64(maxAccountsPerBlock*50000), accumulatedGas) + }) + + t.Run("numSenders = 300000, numTransactions = 1, maxNum = 30_000", func(t *testing.T) { + cache, err := NewTxCache(cfg, host, 0) + require.Nil(t, err) + + addManyTransactionsWithUniformDistribution(cache, 300000, 1) + + require.Equal(t, 300000, int(cache.CountTx())) + + sw.Start(t.Name()) + selected, accumulatedGas, err := cache.SelectTransactions(session, options, 0) + sw.Stop(t.Name()) + + require.NoError(t, err) + // Limited by maxAccountsPerBlock (maxAccountsPerBlock unique senders with 1 tx each) + require.Equal(t, maxAccountsPerBlock, len(selected)) + require.Equal(t, uint64(maxAccountsPerBlock*50000), accumulatedGas) + }) + + for name, measurement := range sw.GetMeasurementsMap() { + fmt.Printf("%fs (%s)\n", measurement, name) + } + + // (1) + // Vendor ID: GenuineIntel + // Model name: 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz + // CPU family: 6 + // Model: 140 + // Thread(s) per core: 2 + // Core(s) per socket: 4 + // + // 0.048709s (TestBenchmarkTxCache_doSelectTransactions/numSenders_=_10000,_numTransactions_=_100,_maxNum_=_30_000) + // 0.076177s (TestBenchmarkTxCache_doSelectTransactions/numSenders_=_50000,_numTransactions_=_2,_maxNum_=_30_000) + // 0.104399s (TestBenchmarkTxCache_doSelectTransactions/numSenders_=_100000,_numTransactions_=_1,_maxNum_=_30_000) + // 0.319060s (TestBenchmarkTxCache_doSelectTransactions/numSenders_=_300000,_numTransactions_=_1,_maxNum_=_30_000) +} + +func TestTxCache_SelectionWithOffset(t *testing.T) { + t.Run("selection skips transactions based on offset", func(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + // Add transactions for alice + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-2"), "alice", 2).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-3"), "alice", 3).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-4"), "alice", 4).withValue(big.NewInt(0))) + + // Get the sender list and set offset to 2 (skip first 2 transactions) + senderList := cache.getListForSender("alice") + senderList.mutex.Lock() + senderList.incrementSelectionOffset(2) + senderList.mutex.Unlock() + + // Selection should only return transactions starting from offset + bunches := cache.acquireBunchesOfTransactions() + require.Len(t, bunches, 1) + require.Len(t, bunches[0], 2) + require.Equal(t, "hash-alice-3", string(bunches[0][0].TxHash)) + require.Equal(t, "hash-alice-4", string(bunches[0][1].TxHash)) + }) + + t.Run("selection returns empty bunch when all transactions are skipped", func(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-2"), "alice", 2).withValue(big.NewInt(0))) + + // Set offset beyond all transactions + senderList := cache.getListForSender("alice") + senderList.mutex.Lock() + senderList.incrementSelectionOffset(2) + senderList.mutex.Unlock() + + // Selection should return no bunches since the only sender has empty selection + bunches := cache.acquireBunchesOfTransactions() + require.Len(t, bunches, 0) + }) + + t.Run("getTxs returns all transactions regardless of offset", func(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-2"), "alice", 2).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-3"), "alice", 3).withValue(big.NewInt(0))) + + senderList := cache.getListForSender("alice") + senderList.mutex.Lock() + senderList.incrementSelectionOffset(2) + senderList.mutex.Unlock() + + // getTxs should still return all transactions (for API compatibility) + allTxs := senderList.getTxs() + require.Len(t, allTxs, 3) + + // getTxsForSelection should return only unselected transactions + selectableTxs := senderList.getTxsForSelection() + require.Len(t, selectableTxs, 1) + }) +} + +func TestTxCache_SetSelectionOffsetsByLastNonce(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-2"), "alice", 2).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-bob-1"), "bob", 1).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-bob-2"), "bob", 2).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-bob-3"), "bob", 3).withValue(big.NewInt(0))) + + // Simulate setting offsets by last nonce (as would happen after OnProposed) + // alice's last nonce in block is 1, bob's last nonce is 2 + lastNoncePerSender := map[string]uint64{ + "alice": 1, + "bob": 2, + } + cache.SetSelectionOffsetsByLastNonce(lastNoncePerSender) + + // Check offsets - should point to first tx with nonce > lastNonce + aliceList := cache.getListForSender("alice") + require.Equal(t, 1, aliceList.getSelectionOffset()) // points to nonce 2 (index 1) + + bobList := cache.getListForSender("bob") + require.Equal(t, 2, bobList.getSelectionOffset()) // points to nonce 3 (index 2) + + // Selection should skip the proposed transactions + bunches := cache.acquireBunchesOfTransactions() + require.Len(t, bunches, 2) + + // Find alice's and bob's bunches + var aliceBunch, bobBunch bunchOfTransactions + for _, bunch := range bunches { + if string(bunch[0].Tx.GetSndAddr()) == "alice" { + aliceBunch = bunch + } else { + bobBunch = bunch + } + } + + require.Len(t, aliceBunch, 1) + require.Equal(t, "hash-alice-2", string(aliceBunch[0].TxHash)) + + require.Len(t, bobBunch, 1) + require.Equal(t, "hash-bob-3", string(bobBunch[0].TxHash)) +} + +func TestTxCache_SetSelectionOffsetsByLastNonce_WithGaps(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + // Sender has transactions with nonces 10, 20, 30, 40, 50 + cache.AddTx(createTx([]byte("hash-alice-10"), "alice", 10).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-20"), "alice", 20).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-30"), "alice", 30).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-40"), "alice", 40).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-50"), "alice", 50).withValue(big.NewInt(0))) + + // Block contains transactions with nonces 30 and 40 (account nonce was 30) + // The lastNonce in the block is 40 + lastNoncePerSender := map[string]uint64{ + "alice": 40, + } + cache.SetSelectionOffsetsByLastNonce(lastNoncePerSender) + + // Offset should point to first tx with nonce > 40, which is nonce 50 at index 4 + aliceList := cache.getListForSender("alice") + require.Equal(t, 4, aliceList.getSelectionOffset()) + + // Selection should return only nonce 50 + bunches := cache.acquireBunchesOfTransactions() + require.Len(t, bunches, 1) + require.Len(t, bunches[0], 1) + require.Equal(t, "hash-alice-50", string(bunches[0][0].TxHash)) +} + +func TestTxCache_ResetSelectionOffsetsToNonce(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + cache.AddTx(createTx([]byte("hash-alice-10"), "alice", 10).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-20"), "alice", 20).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-30"), "alice", 30).withValue(big.NewInt(0))) + cache.AddTx(createTx([]byte("hash-alice-40"), "alice", 40).withValue(big.NewInt(0))) + + // Set offset to skip all transactions + aliceList := cache.getListForSender("alice") + aliceList.mutex.Lock() + aliceList.incrementSelectionOffset(4) + aliceList.mutex.Unlock() + require.Equal(t, 4, aliceList.getSelectionOffset()) + + // Reset offset to nonce 20 (should point to index 1) + sendersWithFirstNonce := map[string]uint64{ + "alice": 20, + } + cache.ResetSelectionOffsetsToNonce(sendersWithFirstNonce) + + require.Equal(t, 1, aliceList.getSelectionOffset()) + + // Selection should now return transactions starting from nonce 20 + bunches := cache.acquireBunchesOfTransactions() + require.Len(t, bunches, 1) + require.Len(t, bunches[0], 3) + require.Equal(t, "hash-alice-20", string(bunches[0][0].TxHash)) +} + +func TestTxCache_PropagationGracePeriod(t *testing.T) { + t.Parallel() + + t.Run("grace period zero disables filtering", func(t *testing.T) { + t.Parallel() + + options := createMockTxSelectionOptions(math.MaxUint64, math.MaxInt) + boundsConfig := config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: maxNumBytesPerSenderUpperBoundTest, + MaxTrackedBlocks: maxTrackedBlocks, + PropagationGracePeriodMs: 0, + } + cache := newUnconstrainedCacheToTest(boundsConfig) + session := txcachemocks.NewSelectionSessionMock() + session.SetNonce([]byte("alice"), 1) + session.SetNonce([]byte("bob"), 5) + + // Even freshly added transactions are selected when grace period is disabled + cache.AddTx(createRelayedTx([]byte("hash-alice-1"), "alice", "relayer", 1)) + cache.AddTx(createRelayedTx([]byte("hash-bob-5"), "bob", "relayer", 5)) + + selected, _, err := cache.SelectTransactions(session, options, 0) + require.Nil(t, err) + require.Equal(t, 2, len(selected)) + }) + + t.Run("all transactions too young", func(t *testing.T) { + t.Parallel() + + options := createMockTxSelectionOptions(math.MaxUint64, math.MaxInt) + boundsConfig := config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: maxNumBytesPerSenderUpperBoundTest, + MaxTrackedBlocks: maxTrackedBlocks, + PropagationGracePeriodMs: 500, + } + cache := newUnconstrainedCacheToTest(boundsConfig) + session := txcachemocks.NewSelectionSessionMock() + session.SetNonce([]byte("alice"), 1) + session.SetNonce([]byte("bob"), 5) + + // Transactions just added (ReceivedAt ~ now), grace period is 500ms + cache.AddTx(createRelayedTx([]byte("hash-alice-1"), "alice", "relayer", 1)) + cache.AddTx(createRelayedTx([]byte("hash-bob-5"), "bob", "relayer", 5)) + + selected, _, err := cache.SelectTransactions(session, options, 0) + require.Nil(t, err) + require.Equal(t, 0, len(selected)) + }) + + t.Run("mixed senders: old sender selected, young sender skipped", func(t *testing.T) { + t.Parallel() + + options := createMockTxSelectionOptions(math.MaxUint64, math.MaxInt) + boundsConfig := config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: maxNumBytesPerSenderUpperBoundTest, + MaxTrackedBlocks: maxTrackedBlocks, + PropagationGracePeriodMs: 500, + } + cache := newUnconstrainedCacheToTest(boundsConfig) + session := txcachemocks.NewSelectionSessionMock() + session.SetNonce([]byte("alice"), 1) + session.SetNonce([]byte("bob"), 5) + + cache.AddTx(createRelayedTx([]byte("hash-alice-1"), "alice", "relayer", 1)) + cache.AddTx(createRelayedTx([]byte("hash-bob-5"), "bob", "relayer", 5)) + + // Make alice's transaction old enough + aliceTx, _ := cache.GetByTxHash([]byte("hash-alice-1")) + aliceTx.ReceivedAt = time.Now().Add(-1 * time.Second) + + selected, _, err := cache.SelectTransactions(session, options, 0) + require.Nil(t, err) + require.Equal(t, 1, len(selected)) + require.Equal(t, "hash-alice-1", string(selected[0].TxHash)) + }) + + t.Run("same sender mixed age: old nonce selected, young nonce stops sender", func(t *testing.T) { + t.Parallel() + + options := createMockTxSelectionOptions(math.MaxUint64, math.MaxInt) + boundsConfig := config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: maxNumBytesPerSenderUpperBoundTest, + MaxTrackedBlocks: maxTrackedBlocks, + PropagationGracePeriodMs: 500, + } + cache := newUnconstrainedCacheToTest(boundsConfig) + session := txcachemocks.NewSelectionSessionMock() + session.SetNonce([]byte("alice"), 1) + + cache.AddTx(createRelayedTx([]byte("hash-alice-1"), "alice", "relayer", 1)) + cache.AddTx(createRelayedTx([]byte("hash-alice-2"), "alice", "relayer", 2)) + cache.AddTx(createRelayedTx([]byte("hash-alice-3"), "alice", "relayer", 3)) + + // Make nonce 1 old enough, but nonce 2 and 3 are still young + tx1, _ := cache.GetByTxHash([]byte("hash-alice-1")) + tx1.ReceivedAt = time.Now().Add(-1 * time.Second) + + selected, _, err := cache.SelectTransactions(session, options, 0) + require.Nil(t, err) + // Only nonce 1 selected; nonce 2 is too young, and nonce 3 depends on nonce 2 + require.Equal(t, 1, len(selected)) + require.Equal(t, "hash-alice-1", string(selected[0].TxHash)) + }) + + t.Run("all transactions old enough", func(t *testing.T) { + t.Parallel() + + options := createMockTxSelectionOptions(math.MaxUint64, math.MaxInt) + boundsConfig := config.TxCacheBoundsConfig{ + MaxNumBytesPerSenderUpperBound: maxNumBytesPerSenderUpperBoundTest, + MaxTrackedBlocks: maxTrackedBlocks, + PropagationGracePeriodMs: 200, + } + cache := newUnconstrainedCacheToTest(boundsConfig) + session := txcachemocks.NewSelectionSessionMock() + session.SetNonce([]byte("alice"), 1) + session.SetNonce([]byte("bob"), 5) + + cache.AddTx(createRelayedTx([]byte("hash-alice-1"), "alice", "relayer", 1)) + cache.AddTx(createRelayedTx([]byte("hash-bob-5"), "bob", "relayer", 5)) + + // Make all transactions old enough + aliceTx, _ := cache.GetByTxHash([]byte("hash-alice-1")) + aliceTx.ReceivedAt = time.Now().Add(-1 * time.Second) + bobTx, _ := cache.GetByTxHash([]byte("hash-bob-5")) + bobTx.ReceivedAt = time.Now().Add(-1 * time.Second) + + selected, _, err := cache.SelectTransactions(session, options, 0) + require.Nil(t, err) + require.Equal(t, 2, len(selected)) + }) +} diff --git a/txcache/testutils_test.go b/txcache/testutils_test.go new file mode 100644 index 00000000000..645eb5fc1c5 --- /dev/null +++ b/txcache/testutils_test.go @@ -0,0 +1,330 @@ +package txcache + +import ( + cryptoRand "crypto/rand" + "encoding/binary" + "math" + "math/big" + "math/rand" + "strconv" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-go/testscommon/txcachemocks" +) + +const oneMilion = 1000000 +const oneBillion = oneMilion * 1000 +const oneQuintillion = 1_000_000_000_000_000_000 +const estimatedSizeOfBoundedTxFields = uint64(128) +const hashLength = 32 +const addressLength = 32 + +var oneQuintillionBig = big.NewInt(oneQuintillion) + +// The GitHub Actions runners are (extremely) slow. The variable is expressed in milliseconds. +const cleanupLoopMaximumDuration = 30_000 + +func haveTimeTrueForSelection() bool { + return true +} + +func haveTimeFalseForSelection() bool { + return false +} + +var randomHashes = newRandomData(math.MaxUint16, hashLength) +var randomAddresses = newRandomData(math.MaxUint16, addressLength) + +var defaultLatestExecutedHash = []byte("hash0") + +type randomData struct { + randomBytes []byte + numItems int + itemSize int +} + +func newRandomData(numItems int, itemSize int) *randomData { + randomBytes := make([]byte, numItems*itemSize) + + _, err := cryptoRand.Read(randomBytes) + if err != nil { + panic(err) + } + + return &randomData{ + randomBytes: randomBytes, + numItems: numItems, + itemSize: itemSize, + } +} + +func (data *randomData) getItem(index int) []byte { + start := index * data.itemSize + end := start + data.itemSize + return data.randomBytes[start:end] +} + +func (cache *TxCache) areInternalMapsConsistent() bool { + internalMapByHash := cache.txByHash + internalMapBySender := cache.txListBySender + + senders := internalMapBySender.getSenders() + numInMapByHash := len(internalMapByHash.keys()) + numInMapBySender := 0 + numMissingInMapByHash := 0 + + for _, sender := range senders { + numInMapBySender += int(sender.countTx()) + + for _, hash := range sender.getTxsHashes() { + _, ok := internalMapByHash.getTx(string(hash)) + if !ok { + numMissingInMapByHash++ + } + } + } + + isFine := (numInMapByHash == numInMapBySender) && (numMissingInMapByHash == 0) + return isFine +} + +func (cache *TxCache) getHashesForSender(sender string) []string { + return cache.getListForSender(sender).getTxHashesAsStrings() +} + +func (cache *TxCache) getListForSender(sender string) *txListForSender { + return cache.txListBySender.testGetListForSender(sender) +} + +func (txMap *txListBySenderMap) testGetListForSender(sender string) *txListForSender { + list, ok := txMap.getListForSender(sender) + if !ok { + panic("sender not in cache") + } + + return list +} + +func (listForSender *txListForSender) getTxHashesAsStrings() []string { + hashes := listForSender.getTxsHashes() + return hashesAsStrings(hashes) +} + +func (listForSender *txListForSender) getTxsHashes() [][]byte { + listForSender.mutex.RLock() + defer listForSender.mutex.RUnlock() + + result := make([][]byte, 0, listForSender.countTx()) + + for _, tx := range listForSender.list.items { + result = append(result, tx.TxHash) + } + + return result +} + +func hashesAsStrings(hashes [][]byte) []string { + result := make([]string, len(hashes)) + for i := 0; i < len(hashes); i++ { + result[i] = string(hashes[i]) + } + + return result +} + +func hashesAsBytes(hashes []string) [][]byte { + result := make([][]byte, len(hashes)) + for i := 0; i < len(hashes); i++ { + result[i] = []byte(hashes[i]) + } + + return result +} + +func addManyTransactionsWithUniformDistribution(cache *TxCache, nSenders int, nTransactionsPerSender int) { + for senderTag := 0; senderTag < nSenders; senderTag++ { + sender := createFakeSenderAddress(senderTag) + + for nonce := nTransactionsPerSender - 1; nonce >= 0; nonce-- { + transactionHash := createFakeTxHash(sender, nonce) + gasPrice := oneBillion + rand.Intn(3*oneBillion) + transaction := createTx(transactionHash, string(sender), uint64(nonce)).withGasPrice(uint64(gasPrice)).withValue(big.NewInt(0)) + + cache.AddTx(transaction) + } + } +} + +func createBunchesOfTransactionsWithUniformDistribution(nSenders int, nTransactionsPerSender int) []bunchOfTransactions { + bunches := make([]bunchOfTransactions, 0, nSenders) + host := txcachemocks.NewMempoolHostMock() + + for senderTag := 0; senderTag < nSenders; senderTag++ { + bunch := make(bunchOfTransactions, 0, nTransactionsPerSender) + sender := createFakeSenderAddress(senderTag) + + for nonce := 0; nonce < nTransactionsPerSender; nonce++ { + transactionHash := createFakeTxHash(sender, nonce) + gasPrice := oneBillion + rand.Intn(3*oneBillion) + transaction := createTx(transactionHash, string(sender), uint64(nonce)).withGasPrice(uint64(gasPrice)).withValue(big.NewInt(0)) + transaction.precomputeFields(host) + + bunch = append(bunch, transaction) + } + + bunches = append(bunches, bunch) + } + + return bunches +} + +func createTx(hash []byte, sender string, nonce uint64) *WrappedTransaction { + tx := &transaction.Transaction{ + SndAddr: []byte(sender), + Nonce: nonce, + GasLimit: 50000, + GasPrice: oneBillion, + } + + return &WrappedTransaction{ + Tx: tx, + TxHash: hash, + Size: int64(estimatedSizeOfBoundedTxFields), + } +} + +func createRelayedTx(hash []byte, sender string, relayer string, nonce uint64) *WrappedTransaction { + tx := &transaction.Transaction{ + SndAddr: []byte(sender), + Nonce: nonce, + GasLimit: 50000, + GasPrice: oneBillion, + Value: big.NewInt(0), + } + + return &WrappedTransaction{ + Tx: tx, + TxHash: hash, + Size: int64(estimatedSizeOfBoundedTxFields), + FeePayer: []byte(relayer), + Fee: big.NewInt(0), + } +} + +func createSliceMockWrappedTxs(txHashes [][]byte) []*WrappedTransaction { + wrappedTxs := make([]*WrappedTransaction, len(txHashes)) + for i, txHash := range txHashes { + wrappedTxs[i] = createTx(txHash, "sender"+strconv.Itoa(i), uint64(i)) + } + + return wrappedTxs +} + +func createMockTxHashes(numberOfTxs int) [][]byte { + txHashes := make([][]byte, numberOfTxs) + for i := 0; i < numberOfTxs; i++ { + txHashes[i] = []byte("txHash" + strconv.Itoa(i)) + } + + return txHashes +} + +func (wrappedTx *WrappedTransaction) withSize(size uint64) *WrappedTransaction { + dataLength := size - estimatedSizeOfBoundedTxFields + tx := wrappedTx.Tx.(*transaction.Transaction) + tx.Data = make([]byte, dataLength) + wrappedTx.Size = int64(size) + return wrappedTx +} + +func (wrappedTx *WrappedTransaction) withData(data []byte) *WrappedTransaction { + tx := wrappedTx.Tx.(*transaction.Transaction) + tx.Data = data + wrappedTx.Size = int64(len(data)) + int64(estimatedSizeOfBoundedTxFields) + return wrappedTx +} + +func (wrappedTx *WrappedTransaction) withDataLength(dataLength int) *WrappedTransaction { + tx := wrappedTx.Tx.(*transaction.Transaction) + tx.Data = make([]byte, dataLength) + wrappedTx.Size = int64(dataLength) + int64(estimatedSizeOfBoundedTxFields) + return wrappedTx +} + +func (wrappedTx *WrappedTransaction) withGasPrice(gasPrice uint64) *WrappedTransaction { + tx := wrappedTx.Tx.(*transaction.Transaction) + tx.GasPrice = gasPrice + return wrappedTx +} + +func (wrappedTx *WrappedTransaction) withGasLimit(gasLimit uint64) *WrappedTransaction { + tx := wrappedTx.Tx.(*transaction.Transaction) + tx.GasLimit = gasLimit + return wrappedTx +} + +func (wrappedTx *WrappedTransaction) withValue(value *big.Int) *WrappedTransaction { + tx := wrappedTx.Tx.(*transaction.Transaction) + tx.Value = value + return wrappedTx +} + +func (wrappedTx *WrappedTransaction) withTransferredValue(value *big.Int) *WrappedTransaction { + wrappedTx.TransferredValue = value + return wrappedTx +} + +func (wrappedTx *WrappedTransaction) withFee(value *big.Int) *WrappedTransaction { + wrappedTx.Fee = value + return wrappedTx +} + +func (wrappedTx *WrappedTransaction) withRelayer(relayer []byte) *WrappedTransaction { + wrappedTx.FeePayer = relayer + tx := wrappedTx.Tx.(*transaction.Transaction) + tx.RelayerAddr = relayer + return wrappedTx +} + +func createFakeSenderAddress(senderTag int) []byte { + bytes := make([]byte, 32) + binary.LittleEndian.PutUint64(bytes, uint64(senderTag)) + binary.LittleEndian.PutUint64(bytes[24:], uint64(senderTag)) + return bytes +} + +func createFakeTxHash(fakeSenderAddress []byte, nonce int) []byte { + bytes := make([]byte, 32) + copy(bytes, fakeSenderAddress) + binary.LittleEndian.PutUint64(bytes[8:], uint64(nonce)) + binary.LittleEndian.PutUint64(bytes[16:], uint64(nonce)) + return bytes +} + +func createExpectedBreadcrumb(isSender bool, firstNonce uint64, lastNonce uint64, consumedBalance *big.Int) *accountBreadcrumb { + return &accountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: firstNonce, + HasValue: isSender, + }, + lastNonce: core.OptionalUint64{ + Value: lastNonce, + HasValue: isSender, + }, + consumedBalance: consumedBalance, + } +} + +func createExpectedVirtualRecord(isSender bool, initialNonce uint64, initialBalance *big.Int, consumedBalance *big.Int) *virtualAccountRecord { + return &virtualAccountRecord{ + initialNonce: core.OptionalUint64{ + Value: initialNonce, + HasValue: isSender, + }, + virtualBalance: &virtualAccountBalance{ + initialBalance: initialBalance, + consumedBalance: consumedBalance, + }, + } +} diff --git a/txcache/trackedBlock.go b/txcache/trackedBlock.go new file mode 100644 index 00000000000..ca3a868a384 --- /dev/null +++ b/txcache/trackedBlock.go @@ -0,0 +1,136 @@ +package txcache + +import ( + "bytes" + + "github.com/multiversx/mx-chain-core-go/core" +) + +type trackedBlock struct { + nonce uint64 + hash []byte + prevHash []byte + breadcrumbsByAddress map[string]*accountBreadcrumb +} + +func newTrackedBlock( + nonce uint64, + blockHash []byte, + prevHash []byte, +) *trackedBlock { + return &trackedBlock{ + nonce: nonce, + hash: blockHash, + prevHash: prevHash, + breadcrumbsByAddress: make(map[string]*accountBreadcrumb), + } +} + +func (tb *trackedBlock) hasSameNonceOrLower(otherBlock *trackedBlock) bool { + return tb.nonce <= otherBlock.nonce +} + +func (tb *trackedBlock) hasSameNonceOrHigherThanGivenNonce(nonce uint64) bool { + return tb.nonce >= nonce +} + +// compileBreadcrumbs compiles breadcrumbs for all transactions and returns a map of last nonce per sender. +// The lastNoncePerSender map is used to update selection offsets after the block is tracked. +func (tb *trackedBlock) compileBreadcrumbs(txs []*WrappedTransaction) (map[string]uint64, error) { + lastNoncePerSender := make(map[string]uint64) + + for _, tx := range txs { + err := tb.compileBreadcrumb(tx) + if err != nil { + log.Debug("trackedBlock.compileBreadcrumbs failed", + "err", err, + "txHash", tx.TxHash, + "sender", tx.Tx.GetSndAddr(), + "nonce", tx.Tx.GetNonce(), + ) + return nil, err + } + + // Track the highest nonce per sender (to update selection offset) + sender := string(tx.Tx.GetSndAddr()) + nonce := tx.Tx.GetNonce() + if existingNonce, exists := lastNoncePerSender[sender]; !exists || nonce > existingNonce { + lastNoncePerSender[sender] = nonce + } + } + + return lastNoncePerSender, nil +} + +func (tb *trackedBlock) compileBreadcrumb(tx *WrappedTransaction) error { + sender := tx.Tx.GetSndAddr() + feePayer := tx.FeePayer + initialNonce := tx.Tx.GetNonce() + latestNonce := initialNonce + + // compile for sender + senderBreadcrumb := tb.getOrCreateBreadcrumbWithNonce(string(sender), core.OptionalUint64{ + Value: initialNonce, + HasValue: true, + }) + + transferredValue := tx.TransferredValue + senderBreadcrumb.accumulateConsumedBalance(transferredValue) + + err := senderBreadcrumb.updateNonceRange(core.OptionalUint64{ + Value: latestNonce, + HasValue: true, + }) + if err != nil { + return err + } + + // compile for fee payer + if feePayer == nil { + return nil + } + + isSenderTheFeePayer := bytes.Equal(sender, feePayer) + if isSenderTheFeePayer { + fee := tx.Fee + senderBreadcrumb.accumulateConsumedBalance(fee) + return nil + } + + feePayerBreadcrumb := tb.getOrCreateBreadcrumb(string(feePayer)) + fee := tx.Fee + feePayerBreadcrumb.accumulateConsumedBalance(fee) + return nil +} + +// getOrCreateBreadcrumbWithNonce is used on the flow of senders +func (tb *trackedBlock) getOrCreateBreadcrumbWithNonce( + address string, + nonce core.OptionalUint64, +) *accountBreadcrumb { + breadCrumb, ok := tb.breadcrumbsByAddress[address] + if ok { + return breadCrumb + } + + breadcrumb := newAccountBreadcrumb(nonce) + tb.breadcrumbsByAddress[address] = breadcrumb + + return breadcrumb +} + +// getOrCreateBreadcrumb is used on the flow of relayers +func (tb *trackedBlock) getOrCreateBreadcrumb(address string) *accountBreadcrumb { + breadCrumb, ok := tb.breadcrumbsByAddress[address] + if ok { + return breadCrumb + } + + breadcrumb := newAccountBreadcrumb(core.OptionalUint64{ + Value: 0, + HasValue: false, + }) + tb.breadcrumbsByAddress[address] = breadcrumb + + return breadcrumb +} diff --git a/txcache/trackedBlock_test.go b/txcache/trackedBlock_test.go new file mode 100644 index 00000000000..1d8d271999d --- /dev/null +++ b/txcache/trackedBlock_test.go @@ -0,0 +1,388 @@ +package txcache + +import ( + "math/big" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/stretchr/testify/require" +) + +func requireEqualBreadcrumbs(t *testing.T, breadCrumb1 *accountBreadcrumb, breadCrumb2 *accountBreadcrumb) { + require.Equal(t, breadCrumb1.firstNonce, breadCrumb2.firstNonce) + require.Equal(t, breadCrumb1.lastNonce, breadCrumb2.lastNonce) + require.Equal(t, breadCrumb1.consumedBalance, breadCrumb2.consumedBalance) +} + +func TestTrackedBlock_sameNonceOrBelow(t *testing.T) { + t.Parallel() + + t.Run("same nonce and same prev hash", func(t *testing.T) { + t.Parallel() + + trackedBlock1 := newTrackedBlock(0, []byte("blockHash1"), []byte("blockPrevHash1")) + trackedBlock2 := newTrackedBlock(0, []byte("blockHash1"), []byte("blockPrevHash1")) + + shouldRemoveBlock := trackedBlock1.hasSameNonceOrLower(trackedBlock2) + require.True(t, shouldRemoveBlock) + }) + + t.Run("lower nonce", func(t *testing.T) { + t.Parallel() + + trackedBlock1 := newTrackedBlock(0, []byte("blockHash1"), []byte("blockPrevHash1")) + trackedBlock2 := newTrackedBlock(1, []byte("blockHash1"), []byte("blockPrevHash1")) + + shouldRemoveBlock := trackedBlock1.hasSameNonceOrLower(trackedBlock2) + require.True(t, shouldRemoveBlock) + }) + + t.Run("greater nonce", func(t *testing.T) { + t.Parallel() + + trackedBlock1 := newTrackedBlock(2, []byte("blockHash1"), []byte("blockPrevHash1")) + trackedBlock2 := newTrackedBlock(1, []byte("blockHash1"), []byte("blockPrevHash1")) + + shouldRemoveBlock := trackedBlock1.hasSameNonceOrLower(trackedBlock2) + require.False(t, shouldRemoveBlock) + }) +} + +func TestTrackedBlock_getBreadcrumb(t *testing.T) { + t.Parallel() + + t.Run("should return new breadcrumb", func(t *testing.T) { + t.Parallel() + + block := newTrackedBlock(0, []byte("blockHash1"), []byte("blockPrevHash1")) + block.breadcrumbsByAddress = map[string]*accountBreadcrumb{ + "alice": newAccountBreadcrumb(core.OptionalUint64{ + Value: 0, + HasValue: true, + }), + } + + nonce := core.OptionalUint64{ + Value: 1, + HasValue: true, + } + expectedBreadcrumb := newAccountBreadcrumb(nonce) + + breadcrumb := block.getOrCreateBreadcrumbWithNonce("bob", nonce) + require.Equal(t, expectedBreadcrumb, breadcrumb) + }) + + t.Run("should return existing breadcrumb", func(t *testing.T) { + t.Parallel() + + nonce := core.OptionalUint64{ + Value: 1, + HasValue: true, + } + expectedBreadcrumb := newAccountBreadcrumb(nonce) + expectedBreadcrumb.accumulateConsumedBalance(big.NewInt(1)) + + block := newTrackedBlock(0, []byte("blockHash1"), []byte("blockPrevHash1")) + + block.breadcrumbsByAddress = map[string]*accountBreadcrumb{ + "alice": expectedBreadcrumb, + } + + breadcrumb := block.getOrCreateBreadcrumbWithNonce("alice", nonce) + require.Equal(t, expectedBreadcrumb, breadcrumb) + }) +} + +func TestTrackedBlock_compileBreadcrumbs(t *testing.T) { + t.Parallel() + + t.Run("sender does not exist in map", func(t *testing.T) { + t.Parallel() + + block := newTrackedBlock(0, []byte("blockHash1"), []byte("blockPrevHash1")) + + txs := []*WrappedTransaction{ + { + Tx: &transaction.Transaction{ + SndAddr: []byte("alice"), + Nonce: 1, + }, + TransferredValue: big.NewInt(5), + }, + } + + _, err := block.compileBreadcrumbs(txs) + require.NoError(t, err) + + aliceBreadcrumb := newAccountBreadcrumb(core.OptionalUint64{Value: 1, HasValue: true}) + aliceBreadcrumb.accumulateConsumedBalance(big.NewInt(5)) + aliceBreadcrumb.lastNonce = core.OptionalUint64{Value: 1, HasValue: true} + expectedBreadcrumbs := map[string]*accountBreadcrumb{ + "alice": aliceBreadcrumb, + } + + for key := range expectedBreadcrumbs { + _, ok := block.breadcrumbsByAddress[key] + require.True(t, ok) + requireEqualBreadcrumbs(t, expectedBreadcrumbs[key], block.breadcrumbsByAddress[key]) + } + }) + + t.Run("sender exists in map, nil fee payer", func(t *testing.T) { + t.Parallel() + + block := newTrackedBlock(0, []byte("blockHash1"), []byte("blockPrevHash1")) + aliceBreadcrumb := newAccountBreadcrumb(core.OptionalUint64{ + Value: 1, + HasValue: true}) + + aliceBreadcrumb.accumulateConsumedBalance(big.NewInt(5)) + block.breadcrumbsByAddress = map[string]*accountBreadcrumb{ + "alice": aliceBreadcrumb, + } + + txs := []*WrappedTransaction{ + { + Tx: &transaction.Transaction{ + SndAddr: []byte("alice"), + Nonce: 4, + }, + TransferredValue: big.NewInt(5), + }, + } + + _, err := block.compileBreadcrumbs(txs) + require.NoError(t, err) + + aliceBreadcrumb = newAccountBreadcrumb(core.OptionalUint64{Value: 1, HasValue: true}) + aliceBreadcrumb.accumulateConsumedBalance(big.NewInt(10)) + aliceBreadcrumb.lastNonce = core.OptionalUint64{Value: 4, HasValue: true} + expectedBreadcrumbs := map[string]*accountBreadcrumb{ + "alice": aliceBreadcrumb, + } + + for key := range expectedBreadcrumbs { + _, ok := block.breadcrumbsByAddress[key] + require.True(t, ok) + requireEqualBreadcrumbs(t, expectedBreadcrumbs[key], block.breadcrumbsByAddress[key]) + } + }) + + t.Run("sender exists in map, fee payer does not", func(t *testing.T) { + t.Parallel() + + block := newTrackedBlock(0, []byte("blockHash1"), []byte("blockPrevHash1")) + aliceBreadcrumb := newAccountBreadcrumb(core.OptionalUint64{ + Value: 1, + HasValue: true, + }) + aliceBreadcrumb.accumulateConsumedBalance(big.NewInt(5)) + block.breadcrumbsByAddress = map[string]*accountBreadcrumb{ + "alice": aliceBreadcrumb, + } + + txs := []*WrappedTransaction{ + { + Tx: &transaction.Transaction{ + SndAddr: []byte("alice"), + Nonce: 4, + }, + TransferredValue: big.NewInt(5), + Fee: big.NewInt(5), + FeePayer: []byte("bob"), + }, + } + + _, err := block.compileBreadcrumbs(txs) + require.NoError(t, err) + + aliceBreadcrumb = newAccountBreadcrumb(core.OptionalUint64{Value: 1, HasValue: true}) + aliceBreadcrumb.accumulateConsumedBalance(big.NewInt(10)) + aliceBreadcrumb.lastNonce = core.OptionalUint64{Value: 4, HasValue: true} + + bobBreadcrumb := newAccountBreadcrumb(core.OptionalUint64{Value: 0, HasValue: false}) + bobBreadcrumb.accumulateConsumedBalance(big.NewInt(5)) + + expectedBreadcrumbs := map[string]*accountBreadcrumb{ + "alice": aliceBreadcrumb, + "bob": bobBreadcrumb, + } + + for key := range expectedBreadcrumbs { + _, ok := block.breadcrumbsByAddress[key] + require.True(t, ok) + requireEqualBreadcrumbs(t, expectedBreadcrumbs[key], block.breadcrumbsByAddress[key]) + } + }) + + t.Run("sender and fee payer existing in map", func(t *testing.T) { + t.Parallel() + + block := newTrackedBlock(0, []byte("blockHash1"), []byte("blockPrevHash1")) + aliceBreadcrumb := newAccountBreadcrumb(core.OptionalUint64{ + Value: 1, + HasValue: true, + }) + aliceBreadcrumb.accumulateConsumedBalance(big.NewInt(5)) + + bobBreadcrumb := newAccountBreadcrumb(core.OptionalUint64{ + Value: 0, + HasValue: true, + }) + bobBreadcrumb.accumulateConsumedBalance(big.NewInt(3)) + + block.breadcrumbsByAddress = map[string]*accountBreadcrumb{ + "alice": aliceBreadcrumb, + "bob": bobBreadcrumb, + } + txs := []*WrappedTransaction{ + { + Tx: &transaction.Transaction{ + SndAddr: []byte("alice"), + Nonce: 3, + }, + TransferredValue: big.NewInt(5), + FeePayer: []byte("bob"), + }, + { + Tx: &transaction.Transaction{ + SndAddr: []byte("alice"), + Nonce: 4, + }, + TransferredValue: big.NewInt(5), + FeePayer: []byte("bob"), + Fee: big.NewInt(3), + }, + } + + _, err := block.compileBreadcrumbs(txs) + require.NoError(t, err) + + aliceBreadcrumb = newAccountBreadcrumb(core.OptionalUint64{Value: 1, HasValue: true}) + aliceBreadcrumb.accumulateConsumedBalance(big.NewInt(15)) + aliceBreadcrumb.lastNonce = core.OptionalUint64{Value: 4, HasValue: true} + + bobBreadcrumb = newAccountBreadcrumb(core.OptionalUint64{Value: 0, HasValue: true}) + bobBreadcrumb.accumulateConsumedBalance(big.NewInt(6)) + + expectedBreadcrumbs := map[string]*accountBreadcrumb{ + "alice": aliceBreadcrumb, + "bob": bobBreadcrumb, + } + + for key := range expectedBreadcrumbs { + _, ok := block.breadcrumbsByAddress[key] + require.True(t, ok) + requireEqualBreadcrumbs(t, expectedBreadcrumbs[key], block.breadcrumbsByAddress[key]) + } + }) + + t.Run("sender and fee payer are equal", func(t *testing.T) { + t.Parallel() + + block := newTrackedBlock(0, []byte("blockHash1"), []byte("blockPrevHash1")) + aliceBreadcrumb := newAccountBreadcrumb(core.OptionalUint64{ + Value: 1, + HasValue: true, + }) + aliceBreadcrumb.accumulateConsumedBalance(big.NewInt(5)) + block.breadcrumbsByAddress = map[string]*accountBreadcrumb{ + "alice": aliceBreadcrumb, + } + txs := []*WrappedTransaction{ + { + Tx: &transaction.Transaction{ + SndAddr: []byte("alice"), + Nonce: 3, + }, + TransferredValue: big.NewInt(5), + FeePayer: []byte("alice"), + Fee: big.NewInt(2), + }, + } + + _, err := block.compileBreadcrumbs(txs) + require.NoError(t, err) + + aliceBreadcrumb = newAccountBreadcrumb(core.OptionalUint64{ + Value: 1, + HasValue: true, + }) + aliceBreadcrumb.accumulateConsumedBalance(big.NewInt(12)) // initial value in breadcrumb + transferredValue + fee + aliceBreadcrumb.lastNonce = core.OptionalUint64{Value: 3, HasValue: true} + + expectedBreadcrumbs := map[string]*accountBreadcrumb{ + "alice": aliceBreadcrumb, + } + + for key := range expectedBreadcrumbs { + _, ok := block.breadcrumbsByAddress[key] + require.True(t, ok) + requireEqualBreadcrumbs(t, expectedBreadcrumbs[key], block.breadcrumbsByAddress[key]) + } + }) + + t.Run("sender becomes fee payer, fee payer becomes sender", func(t *testing.T) { + t.Parallel() + + block := newTrackedBlock(0, []byte("blockHash1"), []byte("blockPrevHash1")) + + txs := []*WrappedTransaction{ + { + Tx: &transaction.Transaction{ + SndAddr: []byte("alice"), + Nonce: 1, + }, + FeePayer: []byte("bob"), + Fee: big.NewInt(2), + TransferredValue: big.NewInt(5), + }, + { + Tx: &transaction.Transaction{ + SndAddr: []byte("bob"), + Nonce: 4, + }, + FeePayer: []byte("alice"), + Fee: big.NewInt(3), + TransferredValue: big.NewInt(10), + }, + { + Tx: &transaction.Transaction{ + SndAddr: []byte("alice"), + Nonce: 2, + }, + TransferredValue: big.NewInt(10), + }, + { + Tx: &transaction.Transaction{ + SndAddr: []byte("bob"), + Nonce: 5, + }, + TransferredValue: big.NewInt(10), + }, + } + + _, err := block.compileBreadcrumbs(txs) + require.NoError(t, err) + + aliceBreadcrumb := newAccountBreadcrumb(core.OptionalUint64{Value: 1, HasValue: true}) + aliceBreadcrumb.accumulateConsumedBalance(big.NewInt(18)) + aliceBreadcrumb.lastNonce = core.OptionalUint64{Value: 2, HasValue: true} + + bobBreadcrumb := newAccountBreadcrumb(core.OptionalUint64{Value: 4, HasValue: true}) + bobBreadcrumb.accumulateConsumedBalance(big.NewInt(22)) + bobBreadcrumb.lastNonce = core.OptionalUint64{Value: 5, HasValue: true} + + expectedBreadcrumbs := map[string]*accountBreadcrumb{ + "alice": aliceBreadcrumb, + "bob": bobBreadcrumb, + } + + for key := range expectedBreadcrumbs { + _, ok := block.breadcrumbsByAddress[key] + require.True(t, ok) + requireEqualBreadcrumbs(t, expectedBreadcrumbs[key], block.breadcrumbsByAddress[key]) + } + }) +} diff --git a/txcache/trackerInfo.go b/txcache/trackerInfo.go new file mode 100644 index 00000000000..0ed709bc0e5 --- /dev/null +++ b/txcache/trackerInfo.go @@ -0,0 +1,29 @@ +package txcache + +type trackerDiagnosis struct { + numTrackedBlocks uint64 + numTrackedAccounts uint64 +} + +// NewTrackerDiagnosis creates a new trackerDiagnosis +func NewTrackerDiagnosis(numTrackedBlocks uint64, numTrackedAccounts uint64) *trackerDiagnosis { + return &trackerDiagnosis{ + numTrackedBlocks: numTrackedBlocks, + numTrackedAccounts: numTrackedAccounts, + } +} + +// GetNumTrackedBlocks returns the number of tracked blocks +func (t *trackerDiagnosis) GetNumTrackedBlocks() uint64 { + return t.numTrackedBlocks +} + +// GetNumTrackedAccounts returns the number of tracked accounts +func (t *trackerDiagnosis) GetNumTrackedAccounts() uint64 { + return t.numTrackedAccounts +} + +// IsInterfaceNil returns true if there is no value under the interface +func (t *trackerDiagnosis) IsInterfaceNil() bool { + return t == nil +} diff --git a/txcache/trackerInfo_test.go b/txcache/trackerInfo_test.go new file mode 100644 index 00000000000..f237b96fd18 --- /dev/null +++ b/txcache/trackerInfo_test.go @@ -0,0 +1,38 @@ +package txcache + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestTrackerInfo_NewTrackerInfo(t *testing.T) { + t.Parallel() + + diagnosis := NewTrackerDiagnosis(0, 0) + require.NotNil(t, diagnosis) +} + +func TestTrackerInfo_GetNumTrackedBlocks(t *testing.T) { + t.Parallel() + + diagnosis := NewTrackerDiagnosis(16, 8) + require.Equal(t, uint64(16), diagnosis.GetNumTrackedBlocks()) +} + +func TestTrackerInfo_GetNumTrackedAccounts(t *testing.T) { + t.Parallel() + + diagnosis := NewTrackerDiagnosis(16, 8) + require.Equal(t, uint64(8), diagnosis.GetNumTrackedAccounts()) +} + +func TestTrackerInfo_IsInterfaceNil(t *testing.T) { + t.Parallel() + + var td *trackerDiagnosis + require.True(t, td.IsInterfaceNil()) + + td = NewTrackerDiagnosis(0, 0) + require.False(t, td.IsInterfaceNil()) +} diff --git a/txcache/transactionsHeap.go b/txcache/transactionsHeap.go new file mode 100644 index 00000000000..28b4e0724a5 --- /dev/null +++ b/txcache/transactionsHeap.go @@ -0,0 +1,59 @@ +package txcache + +type transactionsHeap struct { + items []*transactionsHeapItem + less func(i, j int) bool +} + +func newMinTransactionsHeap(capacity int) *transactionsHeap { + h := transactionsHeap{ + items: make([]*transactionsHeapItem, 0, capacity), + } + + h.less = func(i, j int) bool { + return h.items[j].isCurrentTransactionMoreValuableForNetwork(h.items[i]) + } + + return &h +} + +func newMaxTransactionsHeap(capacity int) *transactionsHeap { + h := transactionsHeap{ + items: make([]*transactionsHeapItem, 0, capacity), + } + + h.less = func(i, j int) bool { + return h.items[i].isCurrentTransactionMoreValuableForNetwork(h.items[j]) + } + + return &h +} + +// Len returns the number of elements in the heap. +func (h *transactionsHeap) Len() int { return len(h.items) } + +// Less reports whether the element with index i should sort before the element with index j. +func (h *transactionsHeap) Less(i, j int) bool { + return h.less(i, j) +} + +// Swap swaps the elements with indexes i and j. +func (h *transactionsHeap) Swap(i, j int) { + h.items[i], h.items[j] = h.items[j], h.items[i] +} + +// Push pushes the element x onto the heap. +func (h *transactionsHeap) Push(x interface{}) { + h.items = append(h.items, x.(*transactionsHeapItem)) +} + +// Pop removes and returns the minimum element (according to "h.less") from the heap. +func (h *transactionsHeap) Pop() interface{} { + // Standard code when storing the heap in a slice: + // https://pkg.go.dev/container/heap + old := h.items + n := len(old) + item := old[n-1] + h.items = old[0 : n-1] + return item +} diff --git a/txcache/transactionsHeapItem.go b/txcache/transactionsHeapItem.go new file mode 100644 index 00000000000..ab99ee4b45a --- /dev/null +++ b/txcache/transactionsHeapItem.go @@ -0,0 +1,134 @@ +package txcache + +type transactionsHeapItem struct { + sender []byte + bunch bunchOfTransactions + + currentTransactionIndex int + currentTransaction *WrappedTransaction + currentTransactionNonce uint64 + latestSelectedTransaction *WrappedTransaction + latestSelectedTransactionNonce uint64 +} + +func newTransactionsHeapItem(bunch bunchOfTransactions) (*transactionsHeapItem, error) { + if len(bunch) == 0 { + return nil, errEmptyBunchOfTransactions + } + + firstTransaction := bunch[0] + + return &transactionsHeapItem{ + sender: firstTransaction.Tx.GetSndAddr(), + bunch: bunch, + + currentTransactionIndex: 0, + currentTransaction: firstTransaction, + currentTransactionNonce: firstTransaction.Tx.GetNonce(), + latestSelectedTransaction: nil, + }, nil +} + +func (item *transactionsHeapItem) getCurrentTransaction() *WrappedTransaction { + return item.currentTransaction +} + +func (item *transactionsHeapItem) selectCurrentTransaction() { + item.latestSelectedTransaction = item.currentTransaction + item.latestSelectedTransactionNonce = item.currentTransactionNonce +} + +func (item *transactionsHeapItem) gotoNextTransaction() bool { + if item.currentTransactionIndex+1 >= len(item.bunch) { + return false + } + + item.currentTransactionIndex++ + item.currentTransaction = item.bunch[item.currentTransactionIndex] + item.currentTransactionNonce = item.currentTransaction.Tx.GetNonce() + return true +} + +func (item *transactionsHeapItem) detectInitialGap(senderNonce uint64) bool { + if item.latestSelectedTransaction != nil { + return false + } + + hasInitialGap := item.currentTransactionNonce > senderNonce + if hasInitialGap { + logSelect.Trace("transactionsHeapItem.detectInitialGap, initial gap", + "tx", item.currentTransaction.TxHash, + "nonce", item.currentTransactionNonce, + "sender", item.sender, + "senderNonce", senderNonce, + ) + } + + return hasInitialGap +} + +func (item *transactionsHeapItem) detectMiddleGap() bool { + if item.latestSelectedTransaction == nil { + return false + } + + // Detect middle gap. + hasMiddleGap := item.currentTransactionNonce > item.latestSelectedTransactionNonce+1 + if hasMiddleGap { + logSelect.Trace("transactionsHeapItem.detectMiddleGap, middle gap", + "tx", item.currentTransaction.TxHash, + "nonce", item.currentTransactionNonce, + "sender", item.sender, + "previousSelectedNonce", item.latestSelectedTransactionNonce, + ) + } + + return hasMiddleGap +} + +func (item *transactionsHeapItem) detectLowerNonce(senderNonce uint64) bool { + isLowerNonce := item.currentTransactionNonce < senderNonce + if isLowerNonce { + logSelect.Trace("transactionsHeapItem.detectLowerNonce", + "tx", item.currentTransaction.TxHash, + "nonce", item.currentTransactionNonce, + "sender", item.sender, + "senderNonce", senderNonce, + ) + } + + return isLowerNonce +} + +func (item *transactionsHeapItem) detectIncorrectlyGuarded(virtualSession *virtualSelectionSession) bool { + isIncorrectlyGuarded := virtualSession.isIncorrectlyGuarded(item.currentTransaction.Tx) + if isIncorrectlyGuarded { + logSelect.Trace("transactionsHeapItem.detectIncorrectlyGuarded", + "tx", item.currentTransaction.TxHash, + "sender", item.sender, + ) + } + + return isIncorrectlyGuarded +} + +func (item *transactionsHeapItem) detectNonceDuplicate() bool { + if item.latestSelectedTransaction == nil { + return false + } + + isDuplicate := item.currentTransactionNonce == item.latestSelectedTransactionNonce + if isDuplicate { + logSelect.Trace("transactionsHeapItem.detectNonceDuplicate", + "tx", item.currentTransaction.TxHash, + "sender", item.sender, + "nonce", item.currentTransactionNonce, + ) + } + + return isDuplicate +} + +func (item *transactionsHeapItem) isCurrentTransactionMoreValuableForNetwork(other *transactionsHeapItem) bool { + return item.currentTransaction.isTransactionMoreValuableForNetwork(other.currentTransaction) +} diff --git a/txcache/transactionsHeapItem_test.go b/txcache/transactionsHeapItem_test.go new file mode 100644 index 00000000000..267d76a85b2 --- /dev/null +++ b/txcache/transactionsHeapItem_test.go @@ -0,0 +1,185 @@ +package txcache + +import ( + "testing" + + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/testscommon/txcachemocks" + "github.com/stretchr/testify/require" +) + +func TestNewTransactionsHeapItem(t *testing.T) { + t.Run("empty bunch", func(t *testing.T) { + item, err := newTransactionsHeapItem(nil) + require.Nil(t, item) + require.Equal(t, errEmptyBunchOfTransactions, err) + }) + + t.Run("non-empty bunch", func(t *testing.T) { + bunch := bunchOfTransactions{ + createTx([]byte("tx-1"), "alice", 42), + } + + item, err := newTransactionsHeapItem(bunch) + require.NotNil(t, item) + require.Nil(t, err) + + require.Equal(t, []byte("alice"), item.sender) + require.Equal(t, bunch, item.bunch) + require.Equal(t, 0, item.currentTransactionIndex) + require.Equal(t, bunch[0], item.currentTransaction) + require.Equal(t, uint64(42), item.currentTransactionNonce) + require.Nil(t, item.latestSelectedTransaction) + }) +} + +func TestTransactionsHeapItem_selectTransaction(t *testing.T) { + host := txcachemocks.NewMempoolHostMock() + + a := createTx([]byte("tx-1"), "alice", 42) + b := createTx([]byte("tx-2"), "alice", 43) + a.precomputeFields(host) + b.precomputeFields(host) + + item, err := newTransactionsHeapItem(bunchOfTransactions{a, b}) + require.NoError(t, err) + + selected := item.getCurrentTransaction() + item.selectCurrentTransaction() + require.Equal(t, a, selected) + require.Equal(t, a, item.latestSelectedTransaction) + require.Equal(t, 42, int(item.latestSelectedTransactionNonce)) + + ok := item.gotoNextTransaction() + require.True(t, ok) + + selected = item.getCurrentTransaction() + item.selectCurrentTransaction() + require.Equal(t, b, selected) + require.Equal(t, b, item.latestSelectedTransaction) + require.Equal(t, 43, int(item.latestSelectedTransactionNonce)) + + ok = item.gotoNextTransaction() + require.False(t, ok) +} + +func TestTransactionsHeapItem_detectInitialGap(t *testing.T) { + a := createTx([]byte("tx-1"), "alice", 42) + b := createTx([]byte("tx-2"), "alice", 43) + + t.Run("known, without gap", func(t *testing.T) { + item, err := newTransactionsHeapItem(bunchOfTransactions{a, b}) + require.NoError(t, err) + require.False(t, item.detectInitialGap(42)) + }) + + t.Run("known, without gap", func(t *testing.T) { + item, err := newTransactionsHeapItem(bunchOfTransactions{a, b}) + require.NoError(t, err) + require.True(t, item.detectInitialGap(41)) + }) +} + +func TestTransactionsHeapItem_detectMiddleGap(t *testing.T) { + a := createTx([]byte("tx-1"), "alice", 42) + b := createTx([]byte("tx-2"), "alice", 43) + c := createTx([]byte("tx-3"), "alice", 44) + + t.Run("known, without gap", func(t *testing.T) { + item := &transactionsHeapItem{} + item.latestSelectedTransaction = a + item.latestSelectedTransactionNonce = 42 + item.currentTransaction = b + item.currentTransactionNonce = 43 + + require.False(t, item.detectMiddleGap()) + }) + + t.Run("known, without gap", func(t *testing.T) { + item := &transactionsHeapItem{} + item.latestSelectedTransaction = a + item.latestSelectedTransactionNonce = 42 + item.currentTransaction = c + item.currentTransactionNonce = 44 + + require.True(t, item.detectMiddleGap()) + }) +} + +func TestTransactionsHeapItem_detectLowerNonce(t *testing.T) { + a := createTx([]byte("tx-1"), "alice", 42) + b := createTx([]byte("tx-2"), "alice", 43) + + t.Run("known, good", func(t *testing.T) { + item, err := newTransactionsHeapItem(bunchOfTransactions{a, b}) + require.NoError(t, err) + require.False(t, item.detectLowerNonce(42)) + }) + + t.Run("known, lower", func(t *testing.T) { + item, err := newTransactionsHeapItem(bunchOfTransactions{a, b}) + require.NoError(t, err) + require.True(t, item.detectLowerNonce(44)) + }) +} + +func TestTransactionsHeapItem_detectNonceDuplicate(t *testing.T) { + a := createTx([]byte("tx-1"), "alice", 42) + b := createTx([]byte("tx-2"), "alice", 43) + c := createTx([]byte("tx-3"), "alice", 42) + + t.Run("unknown", func(t *testing.T) { + item := &transactionsHeapItem{} + item.latestSelectedTransaction = nil + require.False(t, item.detectNonceDuplicate()) + }) + + t.Run("no duplicates", func(t *testing.T) { + item := &transactionsHeapItem{} + item.latestSelectedTransaction = a + item.latestSelectedTransactionNonce = 42 + item.currentTransaction = b + item.currentTransactionNonce = 43 + + require.False(t, item.detectNonceDuplicate()) + }) + + t.Run("duplicates", func(t *testing.T) { + item := &transactionsHeapItem{} + item.latestSelectedTransaction = a + item.latestSelectedTransactionNonce = 42 + item.currentTransaction = c + item.currentTransactionNonce = 42 + + require.True(t, item.detectNonceDuplicate()) + }) +} + +func TestTransactionsHeapItem_detectIncorrectlyGuarded(t *testing.T) { + t.Run("is correctly guarded", func(t *testing.T) { + session := txcachemocks.NewSelectionSessionMock() + virtualSession := newVirtualSelectionSession(session, make(map[string]*virtualAccountRecord)) + + session.IsIncorrectlyGuardedCalled = func(tx data.TransactionHandler) bool { + return false + } + + item, err := newTransactionsHeapItem(bunchOfTransactions{createTx([]byte("tx-1"), "alice", 42)}) + require.NoError(t, err) + + require.False(t, item.detectIncorrectlyGuarded(virtualSession)) + }) + + t.Run("is incorrectly guarded", func(t *testing.T) { + session := txcachemocks.NewSelectionSessionMock() + session.IsIncorrectlyGuardedCalled = func(tx data.TransactionHandler) bool { + return true + } + virtualSession := newVirtualSelectionSession(session, make(map[string]*virtualAccountRecord)) + + item, err := newTransactionsHeapItem(bunchOfTransactions{createTx([]byte("tx-1"), "alice", 42)}) + require.NoError(t, err) + + require.True(t, item.detectIncorrectlyGuarded(virtualSession)) + }) +} diff --git a/txcache/transactionsHeap_test.go b/txcache/transactionsHeap_test.go new file mode 100644 index 00000000000..d5c36e1cf0d --- /dev/null +++ b/txcache/transactionsHeap_test.go @@ -0,0 +1,285 @@ +package txcache + +import ( + "container/heap" + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestTransactionsHeap_Len(t *testing.T) { + t.Parallel() + + txHeap := newMinTransactionsHeap(10) + require.Equal(t, 0, txHeap.Len()) + + mockBunchOfTxs := createBunchesOfTransactionsWithUniformDistribution(2, 5) + item, err := newTransactionsHeapItem(mockBunchOfTxs[0]) + require.NoError(t, err) + + txHeap.Push(item) + require.Equal(t, txHeap.Len(), 1) + + item, err = newTransactionsHeapItem(mockBunchOfTxs[1]) + require.NoError(t, err) + txHeap.Push(item) + require.Equal(t, 2, txHeap.Len()) + + _ = txHeap.Pop() + require.Equal(t, 1, txHeap.Len()) + + _ = txHeap.Pop() + require.Equal(t, 0, txHeap.Len()) +} + +func createMockBunchOfTxsWithSpecificTxHashes(numOfTxsPerBunch int, numOfBunches int) []bunchOfTransactions { + bunchesOfTxs := make([]bunchOfTransactions, numOfBunches) + for i := range bunchesOfTxs { + bunchesOfTxs[i] = make(bunchOfTransactions, 0, numOfTxsPerBunch) + for j := 0; j < numOfTxsPerBunch; j++ { + tx := createTx([]byte(fmt.Sprintf("txHash%d", i*numOfTxsPerBunch+j)), fmt.Sprintf("sender%d", i), uint64(i)) + bunchesOfTxs[i] = append(bunchesOfTxs[i], tx) + } + } + + return bunchesOfTxs +} + +func createMockBunchOfTxsWithSpecificTxHashesAndGasLimit(numOfBunches int, numOfTxsPerBunch int) []bunchOfTransactions { + bunchesOfTxs := make([]bunchOfTransactions, numOfBunches) + for i := range bunchesOfTxs { + bunchesOfTxs[i] = make(bunchOfTransactions, 0, numOfTxsPerBunch) + + for j := 0; j < numOfTxsPerBunch; j++ { + tx := createTx([]byte(fmt.Sprintf("txHash%d", i*numOfTxsPerBunch+j)), fmt.Sprintf("sender%d", i), 0).withGasLimit(uint64(i)) + bunchesOfTxs[i] = append(bunchesOfTxs[i], tx) + } + } + + return bunchesOfTxs +} + +func getSortedTxsFromHeap(txHeap *transactionsHeap) []string { + sortedTxs := make([]string, 0, 15) + + for txHeap.Len() > 0 { + tx := heap.Pop(txHeap).(*transactionsHeapItem) + sortedTxs = append(sortedTxs, string(tx.currentTransaction.TxHash)) + if tx.gotoNextTransaction() { + heap.Push(txHeap, tx) + } + } + + return sortedTxs +} + +func TestTransactionsHeap_SortingByTxHashShouldWork(t *testing.T) { + t.Parallel() + + t.Run("should work for minHeap", func(t *testing.T) { + t.Parallel() + + bunchesOfTxs := createMockBunchOfTxsWithSpecificTxHashes(5, 3) + + minHeap := newMinTransactionsHeap(3) + heap.Init(minHeap) + + for _, bunch := range bunchesOfTxs { + item, err := newTransactionsHeapItem(bunch) + require.NoError(t, err) + + heap.Push(minHeap, item) + } + + expectedOrderOfTxs := []string{ + "txHash5", "txHash6", "txHash7", "txHash8", "txHash9", + "txHash10", "txHash11", "txHash12", "txHash13", "txHash14", + "txHash0", "txHash1", "txHash2", "txHash3", "txHash4", + } + + sortedTxs := getSortedTxsFromHeap(minHeap) + currentExpectedTx := 0 + for _, tx := range sortedTxs { + require.Equal(t, expectedOrderOfTxs[currentExpectedTx], tx) + currentExpectedTx += 1 + } + }) + + t.Run("should work for maxHeap", func(t *testing.T) { + t.Parallel() + + bunchesOfTxs := createMockBunchOfTxsWithSpecificTxHashes(3, 3) + + maxHeap := newMaxTransactionsHeap(3) + heap.Init(maxHeap) + + for _, bunch := range bunchesOfTxs { + item, err := newTransactionsHeapItem(bunch) + require.NoError(t, err) + + heap.Push(maxHeap, item) + } + + expectedOrderOfTxs := []string{ + "txHash0", "txHash1", "txHash2", "txHash3", "txHash4", + "txHash5", "txHash6", "txHash7", "txHash8", "txHash9", + } + + sortedTxs := getSortedTxsFromHeap(maxHeap) + currentExpectedTx := 0 + for _, tx := range sortedTxs { + require.Equal(t, expectedOrderOfTxs[currentExpectedTx], tx) + currentExpectedTx += 1 + } + }) +} + +func TestTransactionsHeap_SortingByGasLimitShouldWork(t *testing.T) { + t.Parallel() + + bunchesOfTxs := createMockBunchOfTxsWithSpecificTxHashesAndGasLimit(3, 5) + + t.Run("should work for minHeap", func(t *testing.T) { + t.Parallel() + + minHeap := newMinTransactionsHeap(3) + heap.Init(minHeap) + + for _, bunch := range bunchesOfTxs { + item, err := newTransactionsHeapItem(bunch) + require.NoError(t, err) + + heap.Push(minHeap, item) + } + + expectedOrderOfTxs := []string{ + "txHash0", "txHash1", "txHash2", "txHash3", "txHash4", + "txHash5", "txHash6", "txHash7", "txHash8", "txHash9", + "txHash10", "txHash11", "txHash12", "txHash13", "txHash14", + } + + sortedTxs := getSortedTxsFromHeap(minHeap) + currentExpectedTx := 0 + for _, tx := range sortedTxs { + require.Equal(t, expectedOrderOfTxs[currentExpectedTx], tx) + currentExpectedTx += 1 + } + }) + + t.Run("should work for maxHeap", func(t *testing.T) { + t.Parallel() + + maxHeap := newMaxTransactionsHeap(3) + heap.Init(maxHeap) + + for _, bunch := range bunchesOfTxs { + item, err := newTransactionsHeapItem(bunch) + require.NoError(t, err) + + heap.Push(maxHeap, item) + } + + expectedOrderOfTxs := []string{ + "txHash10", "txHash11", "txHash12", "txHash13", "txHash14", // bunch 2 + "txHash5", "txHash6", "txHash7", "txHash8", "txHash9", + "txHash0", "txHash1", "txHash2", "txHash3", "txHash4", + } + + sortedTxs := getSortedTxsFromHeap(maxHeap) + currentExpectedTx := 0 + for _, tx := range sortedTxs { + require.Equal(t, expectedOrderOfTxs[currentExpectedTx], tx) + currentExpectedTx += 1 + } + }) + + t.Run("minHeap should return reverse of maxHeap result", func(t *testing.T) { + t.Parallel() + + bunchesOfTransactionsMinHeap := make([]bunchOfTransactions, 2) + bunchesOfTransactionsMinHeap[0] = []*WrappedTransaction{ + createTx([]byte(fmt.Sprintf("txHash%d", 0)), fmt.Sprintf("sender%d", 0), 0).withGasLimit(uint64(0)), + createTx([]byte(fmt.Sprintf("txHash%d", 2)), fmt.Sprintf("sender%d", 2), 2).withGasLimit(uint64(2)), + createTx([]byte(fmt.Sprintf("txHash%d", 4)), fmt.Sprintf("sender%d", 4), 4).withGasLimit(uint64(4)), + createTx([]byte(fmt.Sprintf("txHash%d", 6)), fmt.Sprintf("sender%d", 6), 6).withGasLimit(uint64(6)), + createTx([]byte(fmt.Sprintf("txHash%d", 8)), fmt.Sprintf("sender%d", 8), 8).withGasLimit(uint64(8)), + } + bunchesOfTransactionsMinHeap[1] = []*WrappedTransaction{ + createTx([]byte(fmt.Sprintf("txHash%d", 1)), fmt.Sprintf("sender%d", 1), 1).withGasLimit(uint64(1)), + createTx([]byte(fmt.Sprintf("txHash%d", 3)), fmt.Sprintf("sender%d", 3), 3).withGasLimit(uint64(3)), + createTx([]byte(fmt.Sprintf("txHash%d", 5)), fmt.Sprintf("sender%d", 5), 5).withGasLimit(uint64(5)), + createTx([]byte(fmt.Sprintf("txHash%d", 7)), fmt.Sprintf("sender%d", 7), 7).withGasLimit(uint64(7)), + createTx([]byte(fmt.Sprintf("txHash%d", 9)), fmt.Sprintf("sender%d", 9), 9).withGasLimit(uint64(9)), + } + + minHeap := newMinTransactionsHeap(2) + heap.Init(minHeap) + + for _, bunch := range bunchesOfTransactionsMinHeap { + item, err := newTransactionsHeapItem(bunch) + require.NoError(t, err) + + heap.Push(minHeap, item) + } + sortedTxsMinHeap := getSortedTxsFromHeap(minHeap) + + bunchesOfTransactionsMaxHeap := make([]bunchOfTransactions, 2) + bunchesOfTransactionsMaxHeap[0] = []*WrappedTransaction{ + createTx([]byte(fmt.Sprintf("txHash%d", 8)), fmt.Sprintf("sender%d", 8), 8).withGasLimit(uint64(8)), + createTx([]byte(fmt.Sprintf("txHash%d", 6)), fmt.Sprintf("sender%d", 6), 6).withGasLimit(uint64(6)), + createTx([]byte(fmt.Sprintf("txHash%d", 4)), fmt.Sprintf("sender%d", 4), 4).withGasLimit(uint64(4)), + createTx([]byte(fmt.Sprintf("txHash%d", 2)), fmt.Sprintf("sender%d", 2), 2).withGasLimit(uint64(2)), + createTx([]byte(fmt.Sprintf("txHash%d", 0)), fmt.Sprintf("sender%d", 0), 0).withGasLimit(uint64(0)), + } + bunchesOfTransactionsMaxHeap[1] = []*WrappedTransaction{ + createTx([]byte(fmt.Sprintf("txHash%d", 9)), fmt.Sprintf("sender%d", 9), 9).withGasLimit(uint64(9)), + createTx([]byte(fmt.Sprintf("txHash%d", 7)), fmt.Sprintf("sender%d", 7), 7).withGasLimit(uint64(7)), + createTx([]byte(fmt.Sprintf("txHash%d", 5)), fmt.Sprintf("sender%d", 5), 5).withGasLimit(uint64(5)), + createTx([]byte(fmt.Sprintf("txHash%d", 3)), fmt.Sprintf("sender%d", 3), 3).withGasLimit(uint64(3)), + createTx([]byte(fmt.Sprintf("txHash%d", 1)), fmt.Sprintf("sender%d", 1), 1).withGasLimit(uint64(1)), + } + + maxHeap := newMaxTransactionsHeap(2) + heap.Init(maxHeap) + + for _, bunch := range bunchesOfTransactionsMaxHeap { + item, err := newTransactionsHeapItem(bunch) + require.NoError(t, err) + + heap.Push(maxHeap, item) + } + + sortedTxsMaxHeap := getSortedTxsFromHeap(maxHeap) + + for i, tx := range sortedTxsMinHeap { + require.Equal(t, sortedTxsMaxHeap[len(sortedTxsMaxHeap)-i-1], tx) + } + }) +} + +func TestTransactionsHeap_Swap(t *testing.T) { + t.Parallel() + + bunchOfTx1 := bunchOfTransactions{ + createTx([]byte("txHashA"), "sender1", 0), + } + bunchOfTx2 := bunchOfTransactions{ + createTx([]byte("txHashB"), "sender2", 1), + } + + minHeap := newMaxTransactionsHeap(3) + heap.Init(minHeap) + + item, err := newTransactionsHeapItem(bunchOfTx1) + require.NoError(t, err) + minHeap.Push(item) + + item, err = newTransactionsHeapItem(bunchOfTx2) + require.NoError(t, err) + minHeap.Push(item) + + require.Equal(t, true, minHeap.Less(0, 1)) + minHeap.Swap(0, 1) + require.Equal(t, false, minHeap.Less(0, 1)) +} diff --git a/txcache/txByHashMap.go b/txcache/txByHashMap.go new file mode 100644 index 00000000000..c2e0d8e512f --- /dev/null +++ b/txcache/txByHashMap.go @@ -0,0 +1,123 @@ +package txcache + +import ( + "github.com/multiversx/mx-chain-core-go/core/atomic" + "github.com/multiversx/mx-chain-go/txcache/maps" +) + +// txByHashMap is a new map-like structure for holding and accessing transactions by txHash +type txByHashMap struct { + backingMap *maps.ConcurrentMap + counter atomic.Counter + numBytes atomic.Counter +} + +// newTxByHashMap creates a new TxByHashMap instance +func newTxByHashMap(nChunksHint uint32) *txByHashMap { + backingMap := maps.NewConcurrentMap(nChunksHint) + + return &txByHashMap{ + backingMap: backingMap, + } +} + +// addTx adds a transaction to the map +func (txMap *txByHashMap) addTx(tx *WrappedTransaction) bool { + added := txMap.backingMap.SetIfAbsent(string(tx.TxHash), tx) + if added { + txMap.counter.Increment() + txMap.numBytes.Add(tx.Size) + } + + return added +} + +// removeTx removes a transaction from the map +func (txMap *txByHashMap) removeTx(txHash string) (*WrappedTransaction, bool) { + item, removed := txMap.backingMap.Remove(txHash) + if !removed { + return nil, false + } + + tx, ok := item.(*WrappedTransaction) + if !ok { + return nil, false + } + + if removed { + txMap.counter.Decrement() + txMap.numBytes.Subtract(tx.Size) + } + + return tx, true +} + +// hasTx checks if a transaction exists in the map (without retrieving it) +func (txMap *txByHashMap) hasTx(txHash string) bool { + return txMap.backingMap.Has(txHash) +} + +// getTx gets a transaction from the map +func (txMap *txByHashMap) getTx(txHash string) (*WrappedTransaction, bool) { + txUntyped, ok := txMap.backingMap.Get(txHash) + if !ok { + return nil, false + } + + tx := txUntyped.(*WrappedTransaction) + return tx, true +} + +// GetTxsBulk gets a bulk of transactions from map +func (txMap *txByHashMap) GetTxsBulk(txHashes [][]byte) []*WrappedTransaction { + txs := make([]*WrappedTransaction, 0, len(txHashes)) + for _, txHash := range txHashes { + txUntyped, ok := txMap.backingMap.Get(string(txHash)) + if !ok { + continue + } + + tx := txUntyped.(*WrappedTransaction) + + txs = append(txs, tx) + } + + return txs +} + +// RemoveTxsBulk removes transactions, in bulk +func (txMap *txByHashMap) RemoveTxsBulk(txHashes [][]byte) uint32 { + numRemoved := uint32(0) + + for _, txHash := range txHashes { + _, removed := txMap.removeTx(string(txHash)) + if removed { + numRemoved++ + } + } + + return numRemoved +} + +// forEach iterates over the senders +func (txMap *txByHashMap) forEach(function ForEachTransaction) { + txMap.backingMap.IterCb(func(key string, item interface{}) { + tx := item.(*WrappedTransaction) + function([]byte(key), tx) + }) +} + +func (txMap *txByHashMap) clear() { + txMap.backingMap.Clear() + txMap.counter.Set(0) +} + +func (txMap *txByHashMap) keys() [][]byte { + keys := txMap.backingMap.Keys() + keysAsBytes := make([][]byte, len(keys)) + for i := 0; i < len(keys); i++ { + keysAsBytes[i] = []byte(keys[i]) + } + + return keysAsBytes +} diff --git a/txcache/txByHashMap_test.go b/txcache/txByHashMap_test.go new file mode 100644 index 00000000000..3d404d7188a --- /dev/null +++ b/txcache/txByHashMap_test.go @@ -0,0 +1,138 @@ +package txcache + +import ( + "math/big" + "sync" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_newTxByHashMap(t *testing.T) { + txByHashmap := newTxByHashMap(1) + require.NotNil(t, txByHashmap) +} + +func addWrappedTxsConcurrently(txByHash *txByHashMap, wrappedTxs []*WrappedTransaction, numberOfTxs int) { + var wg sync.WaitGroup + wg.Add(numberOfTxs) + + for _, wrappedTx := range wrappedTxs { + go func(wrappedTx *WrappedTransaction) { + defer wg.Done() + txByHash.addTx(wrappedTx) + }(wrappedTx) + } + + wg.Wait() +} + +func removeWrappedTxsConcurrently(txByHash *txByHashMap, wrappedTxs []*WrappedTransaction, numberOfTxs int) { + var wg sync.WaitGroup + wg.Add(numberOfTxs) + + for _, wrappedTx := range wrappedTxs { + go func(wrappedTx *WrappedTransaction) { + defer wg.Done() + txByHash.removeTx(string(wrappedTx.TxHash)) + }(wrappedTx) + } + + wg.Wait() +} + +func checkExistenceOfTxs(t *testing.T, txByHash *txByHashMap, wrappedTxs []*WrappedTransaction, shouldExist bool) { + for _, wrappedTx := range wrappedTxs { + _, found := txByHash.getTx(string(wrappedTx.TxHash)) + require.Equal(t, shouldExist, found) + } +} + +func Test_addTx(t *testing.T) { + t.Parallel() + + numberOfTxs := 20 + txHashes := createMockTxHashes(numberOfTxs) + wrappedTxs := createSliceMockWrappedTxs(txHashes) + txByHash := newTxByHashMap(1) + + checkExistenceOfTxs(t, txByHash, wrappedTxs, false) + addWrappedTxsConcurrently(txByHash, wrappedTxs, numberOfTxs) + checkExistenceOfTxs(t, txByHash, wrappedTxs, true) +} + +func Test_removeTx(t *testing.T) { + t.Parallel() + + numberOfTxs := 20 + txHashes := createMockTxHashes(numberOfTxs) + wrappedTxs := createSliceMockWrappedTxs(txHashes) + txByHash := newTxByHashMap(1) + + addWrappedTxsConcurrently(txByHash, wrappedTxs, numberOfTxs) + checkExistenceOfTxs(t, txByHash, wrappedTxs, true) + + removeWrappedTxsConcurrently(txByHash, wrappedTxs, numberOfTxs) + checkExistenceOfTxs(t, txByHash, wrappedTxs, false) +} + +func Test_RemoveTxsBulk(t *testing.T) { + t.Parallel() + + numberOfTxs := 20 + txHashes := createMockTxHashes(numberOfTxs) + wrappedTxs := createSliceMockWrappedTxs(txHashes) + txByHash := newTxByHashMap(1) + + addWrappedTxsConcurrently(txByHash, wrappedTxs, numberOfTxs) + checkExistenceOfTxs(t, txByHash, wrappedTxs, true) + + removed := txByHash.RemoveTxsBulk(txHashes) + + checkExistenceOfTxs(t, txByHash, wrappedTxs, false) + require.Equal(t, uint32(len(wrappedTxs)), removed) +} + +func Test_clear(t *testing.T) { + t.Parallel() + + numberOfTxs := 20 + txHashes := createMockTxHashes(numberOfTxs) + wrappedTxs := createSliceMockWrappedTxs(txHashes) + txByHash := newTxByHashMap(1) + + addWrappedTxsConcurrently(txByHash, wrappedTxs, numberOfTxs) + checkExistenceOfTxs(t, txByHash, wrappedTxs, true) + + txByHash.clear() + + checkExistenceOfTxs(t, txByHash, wrappedTxs, false) +} + +func Test_forEach(t *testing.T) { + t.Parallel() + + numberOfTxs := 20 + txHashes := createMockTxHashes(numberOfTxs) + wrappedTxs := createSliceMockWrappedTxs(txHashes) + txByHash := newTxByHashMap(1) + + addWrappedTxsConcurrently(txByHash, wrappedTxs, numberOfTxs) + checkExistenceOfTxs(t, txByHash, wrappedTxs, true) + + var expectedFee *big.Int + + expectedFee = nil + txByHash.forEach(func(txHash []byte, value *WrappedTransaction) { + require.Equal(t, value.Fee, expectedFee) + }) + + txByHash.forEach(func(txHash []byte, value *WrappedTransaction) { + value.Fee = big.NewInt(20) + }) + + expectedFee = big.NewInt(20) + txByHash.forEach(func(txHash []byte, value *WrappedTransaction) { + require.Equal(t, value.Fee, expectedFee) + }) +} diff --git a/txcache/txCache.go b/txcache/txCache.go new file mode 100644 index 00000000000..73aa1ee5078 --- /dev/null +++ b/txcache/txCache.go @@ -0,0 +1,421 @@ +package txcache + +import ( + "sync" + "time" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/core/atomic" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-storage-go/monitoring" + "github.com/multiversx/mx-chain-storage-go/types" + + "github.com/multiversx/mx-chain-go/common" +) + +var _ types.Cacher = (*TxCache)(nil) + +// TxCache represents a cache-like structure (it has a fixed capacity and implements an eviction mechanism) for holding transactions +type TxCache struct { + name string + txListBySender *txListBySenderMap + txByHash *txByHashMap + config ConfigSourceMe + host MempoolHost + evictionMutex sync.Mutex + isEvictionInProgress atomic.Flag + mutTxOperation sync.Mutex + tracker *selectionTracker + propagationGracePeriod time.Duration +} + +// NewTxCache creates a new transaction cache +func NewTxCache(config ConfigSourceMe, host MempoolHost, selfShardId uint32) (*TxCache, error) { + log.Debug("NewTxCache", "config", config.String()) + monitoring.MonitorNewCache(config.Name, uint64(config.NumBytesThreshold)) + + err := config.verify() + if err != nil { + return nil, err + } + if check.IfNil(host) { + return nil, errNilMempoolHost + } + + // Note: for simplicity, we use the same "numChunks" for both internal concurrent maps + numChunks := config.NumChunks + senderConstraintsObj := config.getSenderConstraints() + gracePeriodMs := config.TxCacheBoundsConfig.PropagationGracePeriodMs + propagationGracePeriod := time.Duration(gracePeriodMs) * time.Millisecond + + txCache := &TxCache{ + name: config.Name, + txListBySender: newTxListBySenderMap(numChunks, senderConstraintsObj), + txByHash: newTxByHashMap(numChunks), + config: config, + host: host, + propagationGracePeriod: propagationGracePeriod, + } + + tracker, err := NewSelectionTracker( + txCache, + selfShardId, + config.TxCacheBoundsConfig.MaxTrackedBlocks, + ) + if err != nil { + return nil, err + } + txCache.tracker = tracker + + return txCache, nil +} + +// AddTx adds a transaction in the cache +// Eviction happens if maximum capacity is reached +func (cache *TxCache) AddTx(tx *WrappedTransaction) (ok bool, added bool) { + if tx == nil || check.IfNil(tx.Tx) { + return false, false + } + + logAdd.Trace("TxCache.AddTx", "tx", tx.TxHash, "nonce", tx.Tx.GetNonce(), "sender", tx.Tx.GetSndAddr()) + + // Early duplicate check before expensive precomputeFields (DoS protection) + if cache.txByHash.hasTx(string(tx.TxHash)) { + return true, false + } + + tx.precomputeFields(cache.host) + tx.ReceivedAt = time.Now() + + if cache.config.EvictionEnabled { + _ = cache.doEviction() + } + + cache.mutTxOperation.Lock() + addedInByHash := cache.txByHash.addTx(tx) + addedInBySender, evicted := cache.txListBySender.addTxReturnEvicted(tx, cache.tracker) + cache.mutTxOperation.Unlock() + if addedInByHash != addedInBySender { + // This can happen when two go-routines concur to add the same transaction: + // - A adds to "txByHash" + // - B won't add to "txByHash" (duplicate) + // - B adds to "txListBySender" + // - A won't add to "txListBySender" (duplicate) + logAdd.Debug("TxCache.AddTx: slight inconsistency detected:", "tx", tx.TxHash, "sender", tx.Tx.GetSndAddr(), "addedInByHash", addedInByHash, "addedInBySender", addedInBySender) + } + + if len(evicted) > 0 { + logRemove.Trace("TxCache.AddTx with eviction", "sender", tx.Tx.GetSndAddr(), "num evicted txs", len(evicted)) + _ = cache.txByHash.RemoveTxsBulk(evicted) + } + + // The return value "added" is true even if transaction added, but then removed due to limits be sender. + // This it to ensure that onAdded() notification is triggered. + return true, addedInByHash || addedInBySender +} + +// GetByTxHash gets the transaction by hash +func (cache *TxCache) GetByTxHash(txHash []byte) (*WrappedTransaction, bool) { + tx, ok := cache.txByHash.getTx(string(txHash)) + return tx, ok +} + +// SelectTransactions selects the best transactions to be included in the next miniblock. +// It returns up to "options.maxNumTxs" transactions, with total gas <= "options.gasRequested". +// The selection takes into consideration the proposed blocks which were not yet executed. +// The SelectTransactions should receive the nonce of the block for which the selection is built. +// The blocks with a nonce equal or greater than the given one will be removed. +func (cache *TxCache) SelectTransactions( + session SelectionSession, + options common.TxSelectionOptions, + blockNonce uint64, +) ([]*WrappedTransaction, uint64, error) { + return cache.selectTransactions(session, options, blockNonce, false) +} + +// SimulateSelectTransactions simulates a selection of transaction and does not affect the internal state of the tracker +func (cache *TxCache) SimulateSelectTransactions( + session SelectionSession, + options common.TxSelectionOptions, + currentBlockNonce uint64, +) ([]*WrappedTransaction, uint64, error) { + return cache.selectTransactions(session, options, currentBlockNonce, true) +} + +// selectTransactions executes a real / simulated selection +func (cache *TxCache) selectTransactions( + session SelectionSession, + options common.TxSelectionOptions, + nonce uint64, + isSimulation bool, +) ([]*WrappedTransaction, uint64, error) { + if check.IfNil(session) { + log.Error("TxCache.SelectTransactions", "err", errNilSelectionSession) + return nil, 0, errNilSelectionSession + } + + stopWatch := core.NewStopWatch() + stopWatch.Start("selection") + + logSelect.Debug( + "TxCache.SelectTransactions: begin", + "num txs", cache.CountTx(), + "num senders", cache.CountSenders(), + "num bytes", cache.NumBytes(), + ) + + virtualSession, err := cache.tracker.deriveVirtualSelectionSession(session, nonce, isSimulation) + if err != nil { + log.Error("TxCache.SelectTransactions: could not derive virtual selection session", "err", err) + return nil, 0, err + } + transactions, accumulatedGas := cache.doSelectTransactions(virtualSession, options) + + stopWatch.Stop("selection") + + logSelect.Debug( + "TxCache.SelectTransactions: end", + "duration", stopWatch.GetMeasurement("selection"), + "num txs selected", len(transactions), + "gas", accumulatedGas, + ) + + go displaySelectionOutcome(logSelect, "selection", transactions) + + return transactions, accumulatedGas, nil +} + +// GetVirtualNonceAndRootHash returns the nonce of the virtual record of an account and the corresponding rootHash. +func (cache *TxCache) GetVirtualNonceAndRootHash( + address []byte, +) (uint64, []byte, error) { + virtualNonce, rootHash, err := cache.tracker.getVirtualNonceOfAccountWithRootHash(address) + if err != nil { + return 0, nil, err + } + + return virtualNonce, rootHash, nil +} + +// OnProposedBlock calls the OnProposedBlock method from SelectionTracker +func (cache *TxCache) OnProposedBlock( + blockHash []byte, + blockBody data.BodyHandler, + blockHeader data.HeaderHandler, + accountsProvider common.AccountNonceAndBalanceProvider, + latestExecutedHash []byte) error { + return cache.tracker.OnProposedBlock(blockHash, blockBody, blockHeader, accountsProvider, latestExecutedHash) +} + +// OnBackfilledBlock calls the OnBackfilledBlock method from SelectionTracker +func (cache *TxCache) OnBackfilledBlock( + blockHash []byte, + blockBody data.BodyHandler, + blockHeader data.HeaderHandler) error { + return cache.tracker.OnBackfilledBlock(blockHash, blockBody, blockHeader) +} + +// OnExecutedBlock calls the OnExecutedBlock method from SelectionTracker +func (cache *TxCache) OnExecutedBlock(blockHeader data.HeaderHandler, rootHash []byte) error { + return cache.tracker.OnExecutedBlock(blockHeader, rootHash) +} + +// ResetTracker resets the SelectionTracker +func (cache *TxCache) ResetTracker() { + cache.tracker.ResetTrackedBlocks() +} + +func (cache *TxCache) getSenders() []*txListForSender { + return cache.txListBySender.getSenders() +} + +// SetSelectionOffsetsByLastNonce sets the selection offset for each sender to skip all transactions +// up to and including the given last nonce. This is called after a block is proposed. +func (cache *TxCache) SetSelectionOffsetsByLastNonce(lastNoncePerSender map[string]uint64) { + for sender, lastNonce := range lastNoncePerSender { + listForSender, ok := cache.txListBySender.getListForSender(sender) + if !ok { + continue + } + + // Set offset to first transaction with nonce > lastNonce (i.e., nonce >= lastNonce + 1) + listForSender.resetSelectionOffsetByNonce(lastNonce + 1) + } +} + +// ResetSelectionOffsetsToNonce resets the selection offset for each sender to point to the first +// transaction with nonce >= the given nonce. This is called during block replacement. +func (cache *TxCache) ResetSelectionOffsetsToNonce(sendersWithFirstNonce map[string]uint64) { + for sender, firstNonce := range sendersWithFirstNonce { + listForSender, ok := cache.txListBySender.getListForSender(sender) + if !ok { + continue + } + + listForSender.resetSelectionOffsetByNonce(firstNonce) + } +} + +// RemoveTxByHash removes transactions with nonces lower or equal to the given transaction's nonce +func (cache *TxCache) RemoveTxByHash(txHash []byte) bool { + cache.mutTxOperation.Lock() + defer cache.mutTxOperation.Unlock() + + tx, foundInByHash := cache.txByHash.removeTx(string(txHash)) + if !foundInByHash { + // Transaction might have been removed in the meantime. + return false + } + + evicted := cache.txListBySender.removeTransactionsWithLowerOrEqualNonceReturnHashes(tx) + if len(evicted) > 0 { + cache.txByHash.RemoveTxsBulk(evicted) + } + + logRemove.Trace("TxCache.RemoveTxByHash", "tx", txHash, "len(evicted)", len(evicted)) + return true +} + +// NumBytes gets the approximate number of bytes stored in the cache +func (cache *TxCache) NumBytes() int { + return int(cache.txByHash.numBytes.GetUint64()) +} + +// CountTx gets the number of transactions in the cache +func (cache *TxCache) CountTx() uint64 { + return cache.txByHash.counter.GetUint64() +} + +// Len is an alias for CountTx +func (cache *TxCache) Len() int { + return int(cache.CountTx()) +} + +// SizeInBytesContained returns 0 +func (cache *TxCache) SizeInBytesContained() uint64 { + return 0 +} + +// CountSenders gets the number of senders in the cache +func (cache *TxCache) CountSenders() uint64 { + return cache.txListBySender.counter.GetUint64() +} + +// ForEachTransaction iterates over the transactions in the cache +func (cache *TxCache) ForEachTransaction(function ForEachTransaction) { + cache.txByHash.forEach(function) +} + +// getAllTransactions returns all transactions in the cache +func (cache *TxCache) getAllTransactions() []*WrappedTransaction { + transactions := make([]*WrappedTransaction, 0, cache.Len()) + + cache.ForEachTransaction(func(_ []byte, tx *WrappedTransaction) { + transactions = append(transactions, tx) + }) + + return transactions +} + +// GetTransactionsPoolForSender returns the list of transaction hashes for the sender +func (cache *TxCache) GetTransactionsPoolForSender(sender string) []*WrappedTransaction { + listForSender, ok := cache.txListBySender.getListForSender(sender) + if !ok { + return nil + } + + return listForSender.getTxs() +} + +// Clear clears the cache +func (cache *TxCache) Clear() { + cache.mutTxOperation.Lock() + cache.txListBySender.clear() + cache.txByHash.clear() + cache.mutTxOperation.Unlock() +} + +// Put is not implemented +func (cache *TxCache) Put(_ []byte, _ interface{}, _ int) (evicted bool) { + log.Error("TxCache.Put is not implemented") + return false +} + +// Get gets a transaction (unwrapped) by hash +// Implemented for compatibility reasons (see txPoolsCleaner.go). +func (cache *TxCache) Get(key []byte) (value interface{}, ok bool) { + tx, ok := cache.GetByTxHash(key) + if ok { + return tx.Tx, true + } + return nil, false +} + +// Has checks if a transaction exists +func (cache *TxCache) Has(key []byte) bool { + _, ok := cache.GetByTxHash(key) + return ok +} + +// Peek gets a transaction (unwrapped) by hash +// Implemented for compatibility reasons (see transactions.go, common.go). +func (cache *TxCache) Peek(key []byte) (value interface{}, ok bool) { + tx, ok := cache.GetByTxHash(key) + if ok { + return tx.Tx, true + } + return nil, false +} + +// HasOrAdd is not implemented +func (cache *TxCache) HasOrAdd(_ []byte, _ interface{}, _ int) (has, added bool) { + log.Error("TxCache.HasOrAdd is not implemented") + return false, false +} + +// Remove removes tx by hash +func (cache *TxCache) Remove(key []byte) { + _ = cache.RemoveTxByHash(key) +} + +// Keys returns the tx hashes in the cache +func (cache *TxCache) Keys() [][]byte { + return cache.txByHash.keys() +} + +// MaxSize returns the maximum number of transactions that can be stored in the cache. +// See: https://github.com/multiversx/mx-chain-go/blob/v1.10.6/dataRetriever/txpool/shardedTxPool.go#L63 +func (cache *TxCache) MaxSize() int { + return int(cache.config.CountThreshold) +} + +// RegisterHandler is not implemented +func (cache *TxCache) RegisterHandler(func(key []byte, value interface{}), string) { + log.Error("TxCache.RegisterHandler is not implemented") +} + +// UnRegisterHandler is not implemented +func (cache *TxCache) UnRegisterHandler(string) { + log.Error("TxCache.UnRegisterHandler is not implemented") +} + +// ImmunizeTxsAgainstEviction does nothing for this type of cache +func (cache *TxCache) ImmunizeTxsAgainstEviction(_ [][]byte) { +} + +// Close does nothing for this cacher implementation +func (cache *TxCache) Close() error { + return nil +} + +// IsInterfaceNil returns true if there is no value under the interface +func (cache *TxCache) IsInterfaceNil() bool { + return cache == nil +} + +// SetAOTSelectionPreempter sets the AOT selection preempter for preemption support +// This allows the selectionTracker to preempt ongoing AOT selections when needed +func (cache *TxCache) SetAOTSelectionPreempter(preempter common.AOTSelectionPreempter) { + cache.tracker.SetAOTSelectionPreempter(preempter) +} diff --git a/txcache/txCache_test.go b/txcache/txCache_test.go new file mode 100644 index 00000000000..bd162c046cc --- /dev/null +++ b/txcache/txCache_test.go @@ -0,0 +1,933 @@ +package txcache + +import ( + "errors" + "fmt" + "math" + "math/big" + "math/rand/v2" + "sort" + "sync" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/testscommon/txcachemocks" + "github.com/multiversx/mx-chain-storage-go/common" + "github.com/multiversx/mx-chain-storage-go/types" + "github.com/stretchr/testify/require" +) + +func Test_NewTxCache(t *testing.T) { + config := ConfigSourceMe{ + Name: "test", + NumChunks: 16, + NumBytesThreshold: maxNumBytesUpperBound, + NumBytesPerSenderThreshold: maxNumBytesPerSenderUpperBoundTest, + CountThreshold: math.MaxUint32, + CountPerSenderThreshold: math.MaxUint32, + EvictionEnabled: true, + NumItemsToPreemptivelyEvict: 1, + TxCacheBoundsConfig: createMockTxBoundsConfig(), + } + + host := txcachemocks.NewMempoolHostMock() + + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + require.NotNil(t, cache) + + badConfig := config + badConfig.Name = "" + requireErrorOnNewTxCache(t, badConfig, common.ErrInvalidConfig, "config.Name", host) + + badConfig = config + badConfig.NumChunks = 0 + requireErrorOnNewTxCache(t, badConfig, common.ErrInvalidConfig, "config.NumChunks", host) + + badConfig = config + badConfig.NumBytesPerSenderThreshold = 0 + requireErrorOnNewTxCache(t, badConfig, common.ErrInvalidConfig, "config.NumBytesPerSenderThreshold", host) + + badConfig = config + badConfig.CountPerSenderThreshold = 0 + requireErrorOnNewTxCache(t, badConfig, common.ErrInvalidConfig, "config.CountPerSenderThreshold", host) + + badConfig = config + cache, err = NewTxCache(config, nil, 0) + require.Nil(t, cache) + require.Equal(t, errNilMempoolHost, err) + + badConfig = config + badConfig.NumBytesThreshold = 0 + requireErrorOnNewTxCache(t, badConfig, common.ErrInvalidConfig, "config.NumBytesThreshold", host) + + badConfig = config + badConfig.CountThreshold = 0 + requireErrorOnNewTxCache(t, badConfig, common.ErrInvalidConfig, "config.CountThreshold", host) + + badConfig = config + badConfig.TxCacheBoundsConfig.MaxTrackedBlocks = 0 + requireErrorOnNewTxCache(t, badConfig, errInvalidMaxTrackedBlocks, "bad max tracked blocks", host) +} + +func requireErrorOnNewTxCache(t *testing.T, config ConfigSourceMe, errExpected error, errPartialMessage string, host MempoolHost) { + cache, errReceived := NewTxCache(config, host, 0) + require.Nil(t, cache) + require.True(t, errors.Is(errReceived, errExpected)) + require.Contains(t, errReceived.Error(), errPartialMessage) +} + +func Test_AddTx(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + tx := createTx([]byte("hash-1"), "alice", 1) + + ok, added := cache.AddTx(tx) + require.True(t, ok) + require.True(t, added) + require.True(t, cache.Has([]byte("hash-1"))) + + // Add it again (no-operation) + ok, added = cache.AddTx(tx) + require.True(t, ok) + require.False(t, added) + require.True(t, cache.Has([]byte("hash-1"))) + + foundTx, ok := cache.GetByTxHash([]byte("hash-1")) + require.True(t, ok) + require.Equal(t, tx, foundTx) +} + +func Test_AddNilTx_DoesNothing(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + txHash := []byte("hash-1") + + ok, added := cache.AddTx(&WrappedTransaction{Tx: nil, TxHash: txHash}) + require.False(t, ok) + require.False(t, added) + + foundTx, ok := cache.GetByTxHash(txHash) + require.False(t, ok) + require.Nil(t, foundTx) +} + +func Test_AddTx_AppliesSizeConstraintsPerSenderForNumTransactions(t *testing.T) { + t.Run("with untracked txs", func(t *testing.T) { + t.Parallel() + + cache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + + cache.AddTx(createTx([]byte("tx-alice-1"), "alice", 1)) + cache.AddTx(createTx([]byte("tx-alice-2"), "alice", 2)) + cache.AddTx(createTx([]byte("tx-alice-4"), "alice", 4)) + cache.AddTx(createTx([]byte("tx-bob-1"), "bob", 1)) + cache.AddTx(createTx([]byte("tx-bob-2"), "bob", 2)) + require.Equal(t, []string{"tx-alice-1", "tx-alice-2", "tx-alice-4"}, cache.getHashesForSender("alice")) + require.Equal(t, []string{"tx-bob-1", "tx-bob-2"}, cache.getHashesForSender("bob")) + require.True(t, cache.areInternalMapsConsistent()) + + cache.AddTx(createTx([]byte("tx-alice-3"), "alice", 3)) + require.Equal(t, []string{"tx-alice-1", "tx-alice-2", "tx-alice-3"}, cache.getHashesForSender("alice")) + require.Equal(t, []string{"tx-bob-1", "tx-bob-2"}, cache.getHashesForSender("bob")) + require.True(t, cache.areInternalMapsConsistent()) + }) + + t.Run("with tracked txs", func(t *testing.T) { + t.Parallel() + + cache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + + accountsProvider := &txcachemocks.AccountNonceAndBalanceProviderMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 1, big.NewInt(3 * 1500000 * oneBillion), true, nil + }, + } + + cache.AddTx(createTx([]byte("tx-alice-1"), "alice", 1).withGasLimit(50000)) + cache.AddTx(createTx([]byte("tx-alice-2"), "alice", 2).withGasLimit(1500000)) + cache.AddTx(createTx([]byte("tx-alice-4"), "alice", 3).withGasLimit(1500000)) + require.Equal(t, []string{"tx-alice-1", "tx-alice-2", "tx-alice-4"}, cache.getHashesForSender("alice")) + require.True(t, cache.areInternalMapsConsistent()) + + err := cache.OnProposedBlock( + []byte("blockHash1"), + &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("tx-alice-1"), + []byte("tx-alice-2"), + []byte("tx-alice-4"), + }, + }, + }, + }, + &block.Header{ + Nonce: 1, + PrevHash: []byte("blockHash0"), + }, + accountsProvider, + []byte("blockHash0"), + ) + require.Nil(t, err) + + // TODO analyze if this behaviour is ok. Even if the limit of txs per sender is exceeded, no tx is removed because the specific nonce is tracked. + cache.AddTx(createTx([]byte("tx-alice-3"), "alice", 3).withGasLimit(1500000)) + require.Equal(t, []string{"tx-alice-1", "tx-alice-2", "tx-alice-3", "tx-alice-4"}, cache.getHashesForSender("alice")) + require.True(t, cache.areInternalMapsConsistent()) + + err = cache.OnExecutedBlock( + &block.Header{ + Nonce: 1, + PrevHash: []byte("blockHash0"), + }, + []byte("rootHAsh"), + ) + require.Nil(t, err) + + cache.AddTx(createTx([]byte("tx-alice-5"), "alice", 3).withGasLimit(1500000)) + require.Equal(t, []string{"tx-alice-1", "tx-alice-2", "tx-alice-3"}, cache.getHashesForSender("alice")) + require.True(t, cache.areInternalMapsConsistent()) + }) +} + +func Test_AddTx_AppliesSizeConstraintsPerSenderForNumBytes(t *testing.T) { + cache := newCacheToTest(1024, math.MaxUint32) + + cache.AddTx(createTx([]byte("tx-alice-1"), "alice", 1).withSize(128).withGasLimit(50000)) + cache.AddTx(createTx([]byte("tx-alice-2"), "alice", 2).withSize(512).withGasLimit(1500000)) + cache.AddTx(createTx([]byte("tx-alice-4"), "alice", 3).withSize(256).withGasLimit(1500000)) + cache.AddTx(createTx([]byte("tx-bob-1"), "bob", 1).withSize(512).withGasLimit(1500000)) + cache.AddTx(createTx([]byte("tx-bob-2"), "bob", 2).withSize(513).withGasLimit(1500000)) + + require.Equal(t, []string{"tx-alice-1", "tx-alice-2", "tx-alice-4"}, cache.getHashesForSender("alice")) + require.Equal(t, []string{"tx-bob-1"}, cache.getHashesForSender("bob")) + require.True(t, cache.areInternalMapsConsistent()) + + cache.AddTx(createTx([]byte("tx-alice-3"), "alice", 3).withSize(256).withGasLimit(1500000)) + cache.AddTx(createTx([]byte("tx-bob-2"), "bob", 3).withSize(512).withGasLimit(1500000)) + require.Equal(t, []string{"tx-alice-1", "tx-alice-2", "tx-alice-3"}, cache.getHashesForSender("alice")) + require.Equal(t, []string{"tx-bob-1", "tx-bob-2"}, cache.getHashesForSender("bob")) + require.True(t, cache.areInternalMapsConsistent()) +} + +func Test_RemoveByTxHash(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + cache.AddTx(createTx([]byte("hash-1"), "alice", 1)) + cache.AddTx(createTx([]byte("hash-2"), "alice", 2)) + + removed := cache.RemoveTxByHash([]byte("hash-1")) + require.True(t, removed) + + removed = cache.RemoveTxByHash([]byte("hash-2")) + require.True(t, removed) + + removed = cache.RemoveTxByHash([]byte("hash-3")) + require.False(t, removed) + + foundTx, ok := cache.GetByTxHash([]byte("hash-1")) + require.False(t, ok) + require.Nil(t, foundTx) + + foundTx, ok = cache.GetByTxHash([]byte("hash-2")) + require.False(t, ok) + require.Nil(t, foundTx) + + require.Equal(t, uint64(0), cache.CountTx()) +} + +func Test_CountTx_And_Len(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + cache.AddTx(createTx([]byte("hash-1"), "alice", 1)) + cache.AddTx(createTx([]byte("hash-2"), "alice", 2)) + cache.AddTx(createTx([]byte("hash-3"), "alice", 3)) + + require.Equal(t, uint64(3), cache.CountTx()) + require.Equal(t, 3, cache.Len()) +} + +func Test_GetByTxHash_And_Peek_And_Get(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + txHash := []byte("hash-1") + tx := createTx(txHash, "alice", 1) + cache.AddTx(tx) + + foundTx, ok := cache.GetByTxHash(txHash) + require.True(t, ok) + require.Equal(t, tx, foundTx) + + foundTxPeek, okPeek := cache.Peek(txHash) + require.True(t, okPeek) + require.Equal(t, tx.Tx, foundTxPeek) + + foundTxPeek, okPeek = cache.Peek([]byte("missing")) + require.False(t, okPeek) + require.Nil(t, foundTxPeek) + + foundTxGet, okGet := cache.Get(txHash) + require.True(t, okGet) + require.Equal(t, tx.Tx, foundTxGet) + + foundTxGet, okGet = cache.Get([]byte("missing")) + require.False(t, okGet) + require.Nil(t, foundTxGet) +} + +func Test_RemoveByTxHash_WhenMissing(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + removed := cache.RemoveTxByHash([]byte("missing")) + require.False(t, removed) +} + +func Test_RemoveByTxHash_RemovesFromByHash_WhenMapsInconsistency(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + txHash := []byte("hash-1") + tx := createTx(txHash, "alice", 1) + cache.AddTx(tx) + + // Cause an inconsistency between the two internal maps (theoretically possible in case of misbehaving eviction) + _ = cache.txListBySender.removeTransactionsWithLowerOrEqualNonceReturnHashes(tx) + + _ = cache.RemoveTxByHash(txHash) + require.Equal(t, 0, cache.txByHash.backingMap.Count()) +} + +func Test_Clear(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1)) + cache.AddTx(createTx([]byte("hash-bob-7"), "bob", 7)) + cache.AddTx(createTx([]byte("hash-alice-42"), "alice", 42)) + require.Equal(t, uint64(3), cache.CountTx()) + + cache.Clear() + require.Equal(t, uint64(0), cache.CountTx()) +} + +func Test_ForEachTransaction(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + cache.AddTx(createTx([]byte("hash-alice-1"), "alice", 1)) + cache.AddTx(createTx([]byte("hash-bob-7"), "bob", 7)) + + counter := 0 + cache.ForEachTransaction(func(txHash []byte, value *WrappedTransaction) { + counter++ + }) + require.Equal(t, 2, counter) +} + +func Test_GetTransactionsPoolForSender(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + txHashes1 := [][]byte{[]byte("hash-1"), []byte("hash-2")} + txSender1 := "alice" + wrappedTxs1 := []*WrappedTransaction{ + createTx(txHashes1[1], txSender1, 2), + createTx(txHashes1[0], txSender1, 1), + } + txHashes2 := [][]byte{[]byte("hash-3"), []byte("hash-4"), []byte("hash-5")} + txSender2 := "bob" + wrappedTxs2 := []*WrappedTransaction{ + createTx(txHashes2[1], txSender2, 4), + createTx(txHashes2[0], txSender2, 3), + createTx(txHashes2[2], txSender2, 5), + } + cache.AddTx(wrappedTxs1[0]) + cache.AddTx(wrappedTxs1[1]) + cache.AddTx(wrappedTxs2[0]) + cache.AddTx(wrappedTxs2[1]) + cache.AddTx(wrappedTxs2[2]) + + sort.Slice(wrappedTxs1, func(i, j int) bool { + return wrappedTxs1[i].Tx.GetNonce() < wrappedTxs1[j].Tx.GetNonce() + }) + txs := cache.GetTransactionsPoolForSender(txSender1) + require.Equal(t, wrappedTxs1, txs) + + sort.Slice(wrappedTxs2, func(i, j int) bool { + return wrappedTxs2[i].Tx.GetNonce() < wrappedTxs2[j].Tx.GetNonce() + }) + txs = cache.GetTransactionsPoolForSender(txSender2) + require.Equal(t, wrappedTxs2, txs) + + _ = cache.RemoveTxByHash(txHashes2[0]) + expectedTxs := wrappedTxs2[1:] + txs = cache.GetTransactionsPoolForSender(txSender2) + require.Equal(t, expectedTxs, txs) +} + +func Test_Keys(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + cache.AddTx(createTx([]byte("alice-x"), "alice", 42)) + cache.AddTx(createTx([]byte("alice-y"), "alice", 43)) + cache.AddTx(createTx([]byte("bob-x"), "bob", 42)) + cache.AddTx(createTx([]byte("bob-y"), "bob", 43)) + + keys := cache.Keys() + require.Equal(t, 4, len(keys)) + require.Contains(t, keys, []byte("alice-x")) + require.Contains(t, keys, []byte("alice-y")) + require.Contains(t, keys, []byte("bob-x")) + require.Contains(t, keys, []byte("bob-y")) +} + +func Test_AddWithEviction_UniformDistributionOfTxsPerSender(t *testing.T) { + host := txcachemocks.NewMempoolHostMock() + + t.Run("numSenders = 11, numTransactions = 10, countThreshold = 100, numItemsToPreemptivelyEvict = 1", func(t *testing.T) { + config := ConfigSourceMe{ + Name: "untitled", + NumChunks: 16, + EvictionEnabled: true, + NumBytesThreshold: maxNumBytesUpperBound, + NumBytesPerSenderThreshold: maxNumBytesPerSenderUpperBoundTest, + CountThreshold: 100, + CountPerSenderThreshold: math.MaxUint32, + NumItemsToPreemptivelyEvict: 1, + TxCacheBoundsConfig: createMockTxBoundsConfig(), + } + + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + require.NotNil(t, cache) + + addManyTransactionsWithUniformDistribution(cache, 11, 10) + + // Eviction happens if the cache capacity is already exceeded, + // but not if the capacity will be exceeded after the addition. + // Thus, for the given value of "NumItemsToPreemptivelyEvict", there will be "countThreshold" + 1 transactions in the cache. + require.Equal(t, 101, int(cache.CountTx())) + }) + + t.Run("numSenders = 3, numTransactions = 5, countThreshold = 4, numItemsToPreemptivelyEvict = 3", func(t *testing.T) { + config := ConfigSourceMe{ + Name: "untitled", + NumChunks: 16, + EvictionEnabled: true, + NumBytesThreshold: maxNumBytesUpperBound, + NumBytesPerSenderThreshold: maxNumBytesPerSenderUpperBoundTest, + CountThreshold: 4, + CountPerSenderThreshold: math.MaxUint32, + NumItemsToPreemptivelyEvict: 3, + TxCacheBoundsConfig: createMockTxBoundsConfig(), + } + + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + require.NotNil(t, cache) + + addManyTransactionsWithUniformDistribution(cache, 3, 5) + require.Equal(t, 3, int(cache.CountTx())) + }) + + t.Run("numSenders = 11, numTransactions = 10, countThreshold = 100, numItemsToPreemptivelyEvict = 2", func(t *testing.T) { + config := ConfigSourceMe{ + Name: "untitled", + NumChunks: 16, + EvictionEnabled: true, + NumBytesThreshold: maxNumBytesUpperBound, + NumBytesPerSenderThreshold: maxNumBytesPerSenderUpperBoundTest, + CountThreshold: 100, + CountPerSenderThreshold: math.MaxUint32, + NumItemsToPreemptivelyEvict: 2, + TxCacheBoundsConfig: createMockTxBoundsConfig(), + } + + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + require.NotNil(t, cache) + + addManyTransactionsWithUniformDistribution(cache, 11, 10) + require.Equal(t, 100, int(cache.CountTx())) + }) + + t.Run("numSenders = 100, numTransactions = 1000, countThreshold = 250000 (no eviction)", func(t *testing.T) { + config := ConfigSourceMe{ + Name: "untitled", + NumChunks: 16, + EvictionEnabled: true, + NumBytesThreshold: maxNumBytesUpperBound, + NumBytesPerSenderThreshold: maxNumBytesPerSenderUpperBoundTest, + CountThreshold: 250000, + CountPerSenderThreshold: math.MaxUint32, + NumItemsToPreemptivelyEvict: 1, + TxCacheBoundsConfig: createMockTxBoundsConfig(), + } + + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + require.NotNil(t, cache) + + addManyTransactionsWithUniformDistribution(cache, 100, 1000) + require.Equal(t, 100000, int(cache.CountTx())) + }) + + t.Run("numSenders = 1000, numTransactions = 500, countThreshold = 250000, NumItemsToPreemptivelyEvict = 50000", func(t *testing.T) { + config := ConfigSourceMe{ + Name: "untitled", + NumChunks: 16, + EvictionEnabled: true, + NumBytesThreshold: maxNumBytesUpperBound, + NumBytesPerSenderThreshold: maxNumBytesPerSenderUpperBoundTest, + CountThreshold: 250000, + CountPerSenderThreshold: math.MaxUint32, + NumItemsToPreemptivelyEvict: 10000, + TxCacheBoundsConfig: createMockTxBoundsConfig(), + } + + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + require.NotNil(t, cache) + + addManyTransactionsWithUniformDistribution(cache, 1000, 500) + require.Equal(t, 250000, int(cache.CountTx())) + }) +} + +func Test_NotImplementedFunctions(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + evicted := cache.Put(nil, nil, 0) + require.False(t, evicted) + + has, added := cache.HasOrAdd(nil, nil, 0) + require.False(t, has) + require.False(t, added) + + require.NotPanics(t, func() { cache.RegisterHandler(nil, "") }) + + err := cache.Close() + require.Nil(t, err) +} + +func Test_IsInterfaceNil(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + require.False(t, check.IfNil(cache)) + + makeNil := func() types.Cacher { + return nil + } + + thisIsNil := makeNil() + require.True(t, check.IfNil(thisIsNil)) +} + +func TestTxCache_TransactionIsAdded_EvenWhenInternalMapsAreInconsistent(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + // Setup inconsistency: transaction already exists in map by hash, but not in map by sender + // With early duplicate check (DoS protection), this now returns (true, false) - duplicate detected early + cache.txByHash.addTx(createTx([]byte("alice-x"), "alice", 42)) + + require.Equal(t, 1, cache.txByHash.backingMap.Count()) + require.True(t, cache.Has([]byte("alice-x"))) + ok, added := cache.AddTx(createTx([]byte("alice-x"), "alice", 42)) + require.True(t, ok) + require.False(t, added) // Changed: now returns false due to early duplicate check (DoS protection) + cache.Clear() + + // Setup inconsistency: transaction already exists in map by sender, but not in map by hash + // This case still works as before - tx not in hash map, so it gets added + cache.txListBySender.addTxReturnEvicted(createTx([]byte("alice-x"), "alice", 42), cache.tracker) + + require.False(t, cache.Has([]byte("alice-x"))) + ok, added = cache.AddTx(createTx([]byte("alice-x"), "alice", 42)) + require.True(t, ok) + require.True(t, added) + require.Equal(t, uint64(1), cache.CountSenders()) + require.Equal(t, []string{"alice-x"}, cache.getHashesForSender("alice")) + cache.Clear() +} + +func TestTxCache_GetDimensionOfTrackedBlocks(t *testing.T) { + t.Parallel() + + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, 3) + tracker, err := NewSelectionTracker(txCache, 0, maxTrackedBlocks) + require.Nil(t, err) + txCache.tracker = tracker + + accountsProvider := txcachemocks.NewAccountNonceAndBalanceProviderMock() + + err = txCache.OnProposedBlock( + []byte("hash1"), + &block.Body{}, + &block.Header{ + Nonce: uint64(1), + PrevHash: []byte("hash0"), + RootHash: []byte("rootHash0"), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + require.Equal(t, len(tracker.blocks), 1) + + // replacing the block with nonce 1 + err = txCache.OnProposedBlock( + []byte("hash2"), + &block.Body{}, + &block.Header{ + Nonce: uint64(1), + PrevHash: []byte("hash0"), + RootHash: []byte("rootHash0"), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + require.Equal(t, len(tracker.blocks), 1) +} + +func TestTxCache_NoCriticalInconsistency_WhenConcurrentAdditionsAndRemovals(t *testing.T) { + boundsConfig := createMockTxBoundsConfig() + cache := newUnconstrainedCacheToTest(boundsConfig) + + // A lot of routines concur to add & remove a transaction + for try := 0; try < 100; try++ { + var wg sync.WaitGroup + + for i := 0; i < 50; i++ { + wg.Add(1) + go func() { + cache.AddTx(createTx([]byte("alice-x"), "alice", 42)) + _ = cache.RemoveTxByHash([]byte("alice-x")) + wg.Done() + }() + } + + wg.Wait() + // In this case, there is the slight chance that: + // go A: add to map by hash + // go B: won't add in map by hash, already there + // go A: add to map by sender + // go A: remove from map by hash + // go A: remove from map by sender and delete empty sender + // go B: add to map by sender + // go B: can't remove from map by hash, not found + // go B: won't remove from map by sender (sender unknown) + + // Therefore, the number of senders could be 0 or 1 + require.Equal(t, 0, cache.txByHash.backingMap.Count()) + expectedCountConsistent := 0 + expectedCountSlightlyInconsistent := 1 + actualCount := int(cache.txListBySender.backingMap.Count()) + require.True(t, actualCount == expectedCountConsistent || actualCount == expectedCountSlightlyInconsistent) + + // A further addition works: + cache.AddTx(createTx([]byte("alice-x"), "alice", 42)) + require.True(t, cache.Has([]byte("alice-x"))) + require.Equal(t, []string{"alice-x"}, cache.getHashesForSender("alice")) + } +} + +func TestBenchmarkTxCache_addManyTransactionsWithSameNonce(t *testing.T) { + config := ConfigSourceMe{ + Name: "untitled", + NumChunks: 16, + NumBytesThreshold: 419_430_400, + NumBytesPerSenderThreshold: 12_288_000, + CountThreshold: 300_000, + CountPerSenderThreshold: 5_000, + EvictionEnabled: true, + NumItemsToPreemptivelyEvict: 50_000, + TxCacheBoundsConfig: createMockTxBoundsConfig(), + } + + host := txcachemocks.NewMempoolHostMock() + + sw := core.NewStopWatch() + + t.Run("numTransactions = 100 (worst case)", func(t *testing.T) { + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + + numTransactions := 100 + + sw.Start(t.Name()) + + for i := 0; i < numTransactions; i++ { + cache.AddTx(createTx(randomHashes.getItem(i), "alice", 42).withGasPrice(oneBillion + uint64(i))) + } + + sw.Stop(t.Name()) + + require.Equal(t, numTransactions, int(cache.CountTx())) + }) + + t.Run("numTransactions = 1000 (worst case)", func(t *testing.T) { + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + + numTransactions := 1000 + + sw.Start(t.Name()) + + for i := 0; i < numTransactions; i++ { + cache.AddTx(createTx(randomHashes.getItem(i), "alice", 42).withGasPrice(oneBillion + uint64(i))) + } + + sw.Stop(t.Name()) + + require.Equal(t, numTransactions, int(cache.CountTx())) + }) + + t.Run("numTransactions = 5_000 (worst case)", func(t *testing.T) { + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + + numTransactions := 5_000 + + sw.Start(t.Name()) + + for i := 0; i < numTransactions; i++ { + cache.AddTx(createTx(randomHashes.getItem(i), "alice", 42).withGasPrice(oneBillion + uint64(i))) + } + + sw.Stop(t.Name()) + + require.Equal(t, numTransactions, int(cache.CountTx())) + }) + + for name, measurement := range sw.GetMeasurementsMap() { + fmt.Printf("%fs (%s)\n", measurement, name) + } + + // (1) + // Vendor ID: GenuineIntel + // Model name: 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz + // CPU family: 6 + // Model: 140 + // Thread(s) per core: 2 + // Core(s) per socket: 4 + // + // 0.000120s (TestBenchmarkTxCache_addManyTransactionsWithSameNonce/numTransactions_=_100_(worst_case)) + // 0.002821s (TestBenchmarkTxCache_addManyTransactionsWithSameNonce/numTransactions_=_1000_(worst_case)) + // 0.062260s (TestBenchmarkTxCache_addManyTransactionsWithSameNonce/numTransactions_=_5_000_(worst_case)) +} + +func TestBenchmarkTxCache_addManyTransactionsInDifferentScenarios(t *testing.T) { + config := ConfigSourceMe{ + Name: "untitled", + NumChunks: 16, + NumBytesThreshold: 419_430_400, + NumBytesPerSenderThreshold: 12_288_000, + CountThreshold: 300_000, + CountPerSenderThreshold: 5_000, + EvictionEnabled: true, + NumItemsToPreemptivelyEvict: 50_000, + TxCacheBoundsConfig: createMockTxBoundsConfig(), + } + + host := txcachemocks.NewMempoolHostMock() + sw := core.NewStopWatch() + + t.Run("numTransactions = 5_000 with decreasing nonce (worst case)", func(t *testing.T) { + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + + numTransactions := 5_000 + + sw.Start(t.Name()) + + for i := numTransactions - 1; i >= 0; i-- { + cache.AddTx(createTx(randomHashes.getItem(i), "alice", uint64(i)).withGasPrice(oneBillion + uint64(i))) + } + + sw.Stop(t.Name()) + + require.Equal(t, numTransactions, int(cache.CountTx())) + }) + + t.Run("numTransactions = 5_000 with unordered nonce", func(t *testing.T) { + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + + numTransactions := 5_000 + noncesMap := map[int]struct{}{} + + s3 := rand.NewPCG(42, 1024) + r3 := rand.New(s3) + + nonces := make([]int, 0, numTransactions) + for len(nonces) < numTransactions { + num := r3.IntN(numTransactions) + _, ok := noncesMap[num] + for ok { + num = r3.IntN(numTransactions) + _, ok = noncesMap[num] + } + + nonces = append(nonces, num) + noncesMap[num] = struct{}{} + } + + sw.Start(t.Name()) + + for i := 0; i < len(nonces); i++ { + cache.AddTx(createTx(randomHashes.getItem(nonces[i]), "alice", uint64(nonces[i])).withGasPrice(oneBillion + uint64(nonces[i]))) + } + + sw.Stop(t.Name()) + + require.Equal(t, numTransactions, int(cache.CountTx())) + }) + + t.Run("numTransactions = 5_000 with increasing nonce (best case)", func(t *testing.T) { + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + + numTransactions := 5_000 + + sw.Start(t.Name()) + + for i := 0; i < numTransactions; i++ { + cache.AddTx(createTx(randomHashes.getItem(i), "alice", uint64(i)).withGasPrice(oneBillion + uint64(i))) + } + + sw.Stop(t.Name()) + + require.Equal(t, numTransactions, int(cache.CountTx())) + }) + + for name, measurement := range sw.GetMeasurementsMap() { + fmt.Printf("%fs (%s)\n", measurement, name) + } +} + +func Test_ResetTracker(t *testing.T) { + t.Parallel() + + accountsProvider := &txcachemocks.AccountNonceAndBalanceProviderMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 11, big.NewInt(6 * 100000 * oneBillion), true, nil + }, + } + + config := ConfigSourceMe{ + Name: "test", + NumChunks: 16, + NumBytesThreshold: maxNumBytesUpperBound, + NumBytesPerSenderThreshold: maxNumBytesPerSenderUpperBoundTest, + CountThreshold: math.MaxUint32, + CountPerSenderThreshold: math.MaxUint32, + EvictionEnabled: true, + NumItemsToPreemptivelyEvict: 1, + TxCacheBoundsConfig: createMockTxBoundsConfig(), + } + + host := txcachemocks.NewMempoolHostMock() + + cache, err := NewTxCache(config, host, 0) + require.Nil(t, err) + + txs := []*WrappedTransaction{ + createTx([]byte("txHash1"), "bob", 11), + createTx([]byte("txHash2"), "alice", 11).withRelayer([]byte("bob")).withGasLimit(100_000), + createTx([]byte("txHash3"), "alice", 12), + createTx([]byte("txHash4"), "alice", 13), + } + for _, tx := range txs { + cache.AddTx(tx) + } + + err = cache.OnProposedBlock( + []byte("hash1"), + &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + TxHashes: [][]byte{ + []byte("txHash1"), + []byte("txHash2"), + []byte("txHash3"), + []byte("txHash4"), + }, + }, + }, + }, + &block.Header{ + Nonce: uint64(0), + PrevHash: []byte("hash0"), + RootHash: []byte("rootHash0"), + }, + accountsProvider, + defaultLatestExecutedHash, + ) + require.Nil(t, err) + + require.Equal(t, 1, len(cache.tracker.blocks)) + require.Equal(t, 2, len(cache.tracker.getGlobalAccountsBreadcrumbs())) + + cache.ResetTracker() + require.Equal(t, 0, len(cache.tracker.blocks)) + require.Equal(t, 0, len(cache.tracker.getGlobalAccountsBreadcrumbs())) +} + +func newUnconstrainedCacheToTest(boundsConfig config.TxCacheBoundsConfig) *TxCache { + host := txcachemocks.NewMempoolHostMock() + + cache, err := NewTxCache(ConfigSourceMe{ + Name: "test", + NumChunks: 16, + NumBytesThreshold: maxNumBytesUpperBound, + NumBytesPerSenderThreshold: maxNumBytesPerSenderUpperBoundTest, + CountThreshold: math.MaxUint32, + CountPerSenderThreshold: math.MaxUint32, + EvictionEnabled: false, + NumItemsToPreemptivelyEvict: 1, + TxCacheBoundsConfig: boundsConfig, + }, host, 0) + if err != nil { + panic(fmt.Sprintf("newUnconstrainedCacheToTest(): %s", err)) + } + + return cache +} + +func newCacheToTest(numBytesPerSenderThreshold uint32, countPerSenderThreshold uint32) *TxCache { + host := txcachemocks.NewMempoolHostMock() + + cache, err := NewTxCache(ConfigSourceMe{ + Name: "test", + NumChunks: 16, + NumBytesThreshold: maxNumBytesUpperBound, + NumBytesPerSenderThreshold: numBytesPerSenderThreshold, + CountThreshold: math.MaxUint32, + CountPerSenderThreshold: countPerSenderThreshold, + EvictionEnabled: false, + NumItemsToPreemptivelyEvict: 1, + TxCacheBoundsConfig: createMockTxBoundsConfig(), + }, host, 0) + if err != nil { + panic(fmt.Sprintf("newCacheToTest(): %s", err)) + } + + return cache +} diff --git a/txcache/txListBySenderMap.go b/txcache/txListBySenderMap.go new file mode 100644 index 00000000000..f4185ac6670 --- /dev/null +++ b/txcache/txListBySenderMap.go @@ -0,0 +1,153 @@ +package txcache + +import ( + "sync" + + "github.com/multiversx/mx-chain-core-go/core/atomic" + "github.com/multiversx/mx-chain-go/txcache/maps" +) + +// txListBySenderMap is a map-like structure for holding and accessing transactions by sender +type txListBySenderMap struct { + backingMap *maps.ConcurrentMap + senderConstraints senderConstraints + counter atomic.Counter + mutex sync.Mutex +} + +// newTxListBySenderMap creates a new instance of TxListBySenderMap +func newTxListBySenderMap( + nChunksHint uint32, + senderConstraints senderConstraints, +) *txListBySenderMap { + backingMap := maps.NewConcurrentMap(nChunksHint) + + return &txListBySenderMap{ + backingMap: backingMap, + senderConstraints: senderConstraints, + } +} + +// addTxReturnEvicted adds a transaction in the map, in the corresponding list (selected by its sender). +// This function returns a boolean indicating whether the transaction was added, and a slice of evicted transaction hashes (upon applying sender-level constraints). +func (txMap *txListBySenderMap) addTxReturnEvicted(tx *WrappedTransaction, tracker *selectionTracker) (bool, [][]byte) { + sender := string(tx.Tx.GetSndAddr()) + listForSender := txMap.getOrAddListForSender(sender) + + added, evictedHashes := listForSender.AddTx(tx, tracker) + return added, evictedHashes +} + +// getOrAddListForSender gets or lazily creates a list (using double-checked locking pattern) +func (txMap *txListBySenderMap) getOrAddListForSender(sender string) *txListForSender { + listForSender, ok := txMap.getListForSender(sender) + if ok { + return listForSender + } + + txMap.mutex.Lock() + defer txMap.mutex.Unlock() + + listForSender, ok = txMap.getListForSender(sender) + if ok { + return listForSender + } + + return txMap.addSender(sender) +} + +func (txMap *txListBySenderMap) getListForSender(sender string) (*txListForSender, bool) { + listForSenderUntyped, ok := txMap.backingMap.Get(sender) + if !ok { + return nil, false + } + + listForSender := listForSenderUntyped.(*txListForSender) + return listForSender, true +} + +func (txMap *txListBySenderMap) addSender(sender string) *txListForSender { + listForSender := newTxListForSender(sender, &txMap.senderConstraints) + + txMap.backingMap.Set(sender, listForSender) + txMap.counter.Increment() + + return listForSender +} + +// removeTransactionsWithLowerOrEqualNonceReturnHashes removes transactions with nonces lower or equal to the given transaction's nonce. +func (txMap *txListBySenderMap) removeTransactionsWithLowerOrEqualNonceReturnHashes(tx *WrappedTransaction) [][]byte { + sender := string(tx.Tx.GetSndAddr()) + + listForSender, ok := txMap.getListForSender(sender) + if !ok { + // This happens when a sender whose transactions were selected for processing is removed from cache in the meantime. + // When it comes to remove one if its transactions due to processing (commited / finalized block), they don't exist in cache anymore. + log.Trace("txListBySenderMap.removeTxReturnEvicted detected slight inconsistency: sender of tx not in cache", "tx", tx.TxHash, "sender", []byte(sender)) + return nil + } + + evicted := listForSender.removeTransactionsWithLowerOrEqualNonceReturnHashes(tx.Tx.GetNonce()) + txMap.removeSenderIfEmpty(listForSender) + return evicted +} + +func (txMap *txListBySenderMap) removeSenderIfEmpty(listForSender *txListForSender) { + if listForSender.IsEmpty() { + txMap.removeSender(listForSender.sender) + } +} + +// Important note: this doesn't remove the transactions from txCache.txByHash. That is the responsibility of the caller (of this function). +func (txMap *txListBySenderMap) removeSender(sender string) bool { + logRemove.Trace("txListBySenderMap.removeSender", "sender", sender) + + _, removed := txMap.backingMap.Remove(sender) + if removed { + txMap.counter.Decrement() + } + + return removed +} + +// RemoveSendersBulk removes senders, in bulk +func (txMap *txListBySenderMap) RemoveSendersBulk(senders []string) uint32 { + numRemoved := uint32(0) + + for _, senderKey := range senders { + if txMap.removeSender(senderKey) { + numRemoved++ + } + } + + return numRemoved +} + +// removeTransactionsWithHigherOrEqualNonce removes transactions with nonces higher or equal to the given nonce. +// Useful for the eviction flow. +func (txMap *txListBySenderMap) removeTransactionsWithHigherOrEqualNonce(accountKey []byte, nonce uint64) { + sender := string(accountKey) + listForSender, ok := txMap.getListForSender(sender) + if !ok { + return + } + + listForSender.removeTransactionsWithHigherOrEqualNonce(nonce) + txMap.removeSenderIfEmpty(listForSender) +} + +func (txMap *txListBySenderMap) getSenders() []*txListForSender { + senders := make([]*txListForSender, 0, txMap.counter.Get()) + + txMap.backingMap.IterCb(func(key string, item interface{}) { + listForSender := item.(*txListForSender) + senders = append(senders, listForSender) + }) + + return senders +} + +func (txMap *txListBySenderMap) clear() { + txMap.backingMap.Clear() + txMap.counter.Set(0) +} diff --git a/txcache/txListBySenderMap_test.go b/txcache/txListBySenderMap_test.go new file mode 100644 index 00000000000..030b984384d --- /dev/null +++ b/txcache/txListBySenderMap_test.go @@ -0,0 +1,111 @@ +package txcache + +import ( + "math" + "sync" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSendersMap_AddTx_IncrementsCounter(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + + myMap := newSendersMapToTest() + + myMap.addTxReturnEvicted(createTx([]byte("a"), "alice", 1), txCache.tracker) + myMap.addTxReturnEvicted(createTx([]byte("aa"), "alice", 2), txCache.tracker) + myMap.addTxReturnEvicted(createTx([]byte("b"), "bob", 1), txCache.tracker) + + // There are 2 senders + require.Equal(t, int64(2), myMap.counter.Get()) +} + +func TestSendersMap_removeTransactionsWithLowerOrEqualNonceReturnHashes_alsoRemovesSenderWhenNoTransactionLeft(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + + myMap := newSendersMapToTest() + + txAlice1 := createTx([]byte("a1"), "alice", 1) + txAlice2 := createTx([]byte("a2"), "alice", 2) + txBob := createTx([]byte("b"), "bob", 1) + + myMap.addTxReturnEvicted(txAlice1, txCache.tracker) + myMap.addTxReturnEvicted(txAlice2, txCache.tracker) + myMap.addTxReturnEvicted(txBob, txCache.tracker) + require.Equal(t, int64(2), myMap.counter.Get()) + require.Equal(t, uint64(2), myMap.testGetListForSender("alice").countTx()) + require.Equal(t, uint64(1), myMap.testGetListForSender("bob").countTx()) + + _ = myMap.removeTransactionsWithLowerOrEqualNonceReturnHashes(txAlice1) + require.Equal(t, int64(2), myMap.counter.Get()) + require.Equal(t, uint64(1), myMap.testGetListForSender("alice").countTx()) + require.Equal(t, uint64(1), myMap.testGetListForSender("bob").countTx()) + + _ = myMap.removeTransactionsWithLowerOrEqualNonceReturnHashes(txAlice2) + // All alice's transactions have been removed now + require.Equal(t, int64(1), myMap.counter.Get()) + + _ = myMap.removeTransactionsWithLowerOrEqualNonceReturnHashes(txBob) + // Also Bob has no more transactions + require.Equal(t, int64(0), myMap.counter.Get()) +} + +func TestSendersMap_RemoveSender(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + + myMap := newSendersMapToTest() + + myMap.addTxReturnEvicted(createTx([]byte("a"), "alice", 1), txCache.tracker) + require.Equal(t, int64(1), myMap.counter.Get()) + + // Bob is unknown + myMap.removeSender("bob") + require.Equal(t, int64(1), myMap.counter.Get()) + + myMap.removeSender("alice") + require.Equal(t, int64(0), myMap.counter.Get()) +} + +func TestSendersMap_RemoveSendersBulk_ConcurrentWithAddition(t *testing.T) { + myMap := newSendersMapToTest() + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + + var wg sync.WaitGroup + + wg.Add(1) + go func() { + defer wg.Done() + + for i := 0; i < 100; i++ { + numRemoved := myMap.RemoveSendersBulk([]string{"alice"}) + require.LessOrEqual(t, numRemoved, uint32(1)) + + numRemoved = myMap.RemoveSendersBulk([]string{"bob"}) + require.LessOrEqual(t, numRemoved, uint32(1)) + + numRemoved = myMap.RemoveSendersBulk([]string{"carol"}) + require.LessOrEqual(t, numRemoved, uint32(1)) + } + }() + + wg.Add(100) + for i := 0; i < 100; i++ { + go func(i int) { + myMap.addTxReturnEvicted(createTx([]byte("a"), "alice", uint64(i)), txCache.tracker) + myMap.addTxReturnEvicted(createTx([]byte("b"), "bob", uint64(i)), txCache.tracker) + myMap.addTxReturnEvicted(createTx([]byte("c"), "carol", uint64(i)), txCache.tracker) + + wg.Done() + }(i) + } + + wg.Wait() +} + +func newSendersMapToTest() *txListBySenderMap { + return newTxListBySenderMap(4, senderConstraints{ + maxNumBytes: math.MaxUint32, + maxNumTxs: math.MaxUint32, + }) +} diff --git a/txcache/txListForSender.go b/txcache/txListForSender.go new file mode 100644 index 00000000000..7ecd1c5b812 --- /dev/null +++ b/txcache/txListForSender.go @@ -0,0 +1,216 @@ +package txcache + +import ( + "sync" + + "github.com/multiversx/mx-chain-core-go/core/atomic" +) + +// txListForSender represents a sorted list of transactions of a particular sender +type txListForSender struct { + sender string + list *orderedTransactionsList + totalBytes atomic.Counter + constraints *senderConstraints + selectionOffset int // Index from which selection should start (transactions before are in proposed blocks) + + mutex sync.RWMutex +} + +// newTxListForSender creates a new (sorted) list of transactions +func newTxListForSender(sender string, constraints *senderConstraints) *txListForSender { + return &txListForSender{ + list: newOrderedTransactionsList(), + sender: sender, + constraints: constraints, + } +} + +// AddTx adds a transaction in sender's list +// This is a "sorted" insert +func (listForSender *txListForSender) AddTx(tx *WrappedTransaction, tracker *selectionTracker) (bool, [][]byte) { + listForSender.mutex.Lock() + defer listForSender.mutex.Unlock() + + if tracker == nil { + return false, nil + } + + insertionIndex := listForSender.list.findInsertionIndex(tx) + added := listForSender.list.insertAt(tx, insertionIndex) + if !added { + return false, nil + } + + // If transaction was inserted before the selection offset, increment offset to maintain position + if insertionIndex < listForSender.selectionOffset { + listForSender.selectionOffset++ + } + + listForSender.onAddedTransaction(tx) + + evicted := listForSender.applySizeConstraints(tracker) + return true, evicted +} + +// This function should only be used in critical section (listForSender.mutex) +func (listForSender *txListForSender) applySizeConstraints(tracker *selectionTracker) [][]byte { + evictedTxHashes := make([][]byte, 0) + + // Iterate back to front + for i := listForSender.list.len() - 1; i >= 0; i-- { + if !listForSender.isCapacityExceeded() { + break + } + + tx := listForSender.list.get(i) + if !tracker.IsTransactionTracked(tx) { + _ = listForSender.list.removeAt(i) + listForSender.onRemovedTransaction(tx) + evictedTxHashes = append(evictedTxHashes, tx.TxHash) + + // If removal is at index < offset, decrement offset + if i < listForSender.selectionOffset { + listForSender.selectionOffset-- + } + } + } + + return evictedTxHashes +} + +func (listForSender *txListForSender) isCapacityExceeded() bool { + maxBytes := int64(listForSender.constraints.maxNumBytes) + maxNumTxs := uint64(listForSender.constraints.maxNumTxs) + tooManyBytes := listForSender.totalBytes.Get() > maxBytes + tooManyTxs := listForSender.countTx() > maxNumTxs + + return tooManyBytes || tooManyTxs +} + +func (listForSender *txListForSender) onAddedTransaction(tx *WrappedTransaction) { + listForSender.totalBytes.Add(tx.Size) +} + +func (listForSender *txListForSender) onRemovedTransaction(tx *WrappedTransaction) { + listForSender.totalBytes.Subtract(tx.Size) +} + +// IsEmpty checks whether the list is empty +func (listForSender *txListForSender) IsEmpty() bool { + return listForSender.countTxWithLock() == 0 +} + +// getTxs returns the transactions of the sender +func (listForSender *txListForSender) getTxs() []*WrappedTransaction { + listForSender.mutex.RLock() + defer listForSender.mutex.RUnlock() + + return listForSender.list.getAll() +} + +// getTxsReversed returns the transactions of the sender, in reverse nonce order +func (listForSender *txListForSender) getTxsReversed() []*WrappedTransaction { + listForSender.mutex.RLock() + defer listForSender.mutex.RUnlock() + + items := listForSender.list.items + result := make([]*WrappedTransaction, 0, len(items)) + for i := len(items) - 1; i >= 0; i-- { + result = append(result, items[i]) + } + return result +} + +// This function should only be used in critical section (listForSender.mutex) +func (listForSender *txListForSender) countTx() uint64 { + return uint64(listForSender.list.len()) +} + +func (listForSender *txListForSender) countTxWithLock() uint64 { + listForSender.mutex.RLock() + defer listForSender.mutex.RUnlock() + return uint64(listForSender.list.len()) +} + +// removeTransactionsWithLowerOrEqualNonceReturnHashes removes transactions with nonces lower or equal to the given nonce +func (listForSender *txListForSender) removeTransactionsWithLowerOrEqualNonceReturnHashes(targetNonce uint64) [][]byte { + listForSender.mutex.Lock() + defer listForSender.mutex.Unlock() + + removed := listForSender.list.removeBeforeNonce(targetNonce) + evictedTxHashes := make([][]byte, len(removed)) + + for i, tx := range removed { + listForSender.onRemovedTransaction(tx) + evictedTxHashes[i] = tx.TxHash + } + + // Decrement offset by number of removed transactions (clamped to 0) + listForSender.decrementSelectionOffset(len(removed)) + + return evictedTxHashes +} + +func (listForSender *txListForSender) removeTransactionsWithHigherOrEqualNonce(givenNonce uint64) { + listForSender.mutex.Lock() + defer listForSender.mutex.Unlock() + + removed := listForSender.list.removeAfterNonce(givenNonce) + for _, tx := range removed { + listForSender.onRemovedTransaction(tx) + } + if listForSender.selectionOffset > listForSender.list.len() { + listForSender.selectionOffset = listForSender.list.len() + } +} + +// getTxsForSelection returns the transactions of the sender starting from the selection offset. +// Transactions before the offset are already in proposed blocks and should be skipped during selection. +func (listForSender *txListForSender) getTxsForSelection() []*WrappedTransaction { + listForSender.mutex.RLock() + defer listForSender.mutex.RUnlock() + + return listForSender.list.getAllFromIndex(listForSender.selectionOffset) +} + +// incrementSelectionOffset increases the selection offset by the given count. +// This is called during OnProposed to skip transactions that are now in a proposed block. +// This function should only be used in critical section (listForSender.mutex) +func (listForSender *txListForSender) incrementSelectionOffset(count int) { + listForSender.selectionOffset += count + // Clamp to list length to avoid going beyond available transactions + if listForSender.selectionOffset > listForSender.list.len() { + listForSender.selectionOffset = listForSender.list.len() + } +} + +// decrementSelectionOffset decreases the selection offset by the given count. +// This is called when transactions are removed from the front of the list (executed). +// This function should only be used in critical section (listForSender.mutex) +func (listForSender *txListForSender) decrementSelectionOffset(count int) { + listForSender.selectionOffset -= count + // Clamp to 0 to avoid negative offset + if listForSender.selectionOffset < 0 { + listForSender.selectionOffset = 0 + } +} + +// resetSelectionOffsetByNonce resets the selection offset to point to the first transaction +// with nonce >= startNonce. Uses binary search for efficiency. +// This is called during block replacement to re-enable transactions for selection. +func (listForSender *txListForSender) resetSelectionOffsetByNonce(startNonce uint64) { + listForSender.mutex.Lock() + defer listForSender.mutex.Unlock() + + listForSender.selectionOffset = listForSender.list.findIndexByNonce(startNonce) +} + +// getSelectionOffset returns the current selection offset. +// This is primarily used for testing. +func (listForSender *txListForSender) getSelectionOffset() int { + listForSender.mutex.RLock() + defer listForSender.mutex.RUnlock() + + return listForSender.selectionOffset +} diff --git a/txcache/txListForSender_test.go b/txcache/txListForSender_test.go new file mode 100644 index 00000000000..95557aeb722 --- /dev/null +++ b/txcache/txListForSender_test.go @@ -0,0 +1,524 @@ +package txcache + +import ( + "fmt" + "math" + "math/rand" + "sync" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestListForSender_AddTx_Sorts(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + list := newUnconstrainedListToTest() + + list.AddTx(createTx([]byte("a"), ".", 1), txCache.tracker) + list.AddTx(createTx([]byte("c"), ".", 3), txCache.tracker) + list.AddTx(createTx([]byte("d"), ".", 4), txCache.tracker) + list.AddTx(createTx([]byte("b"), ".", 2), txCache.tracker) + + require.Equal(t, []string{"a", "b", "c", "d"}, list.getTxHashesAsStrings()) +} + +func TestListForSender_AddTx_GivesPriorityToHigherGas(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + list := newUnconstrainedListToTest() + + list.AddTx(createTx([]byte("a"), ".", 1), txCache.tracker) + list.AddTx(createTx([]byte("b"), ".", 3).withGasPrice(1.2*oneBillion), txCache.tracker) + list.AddTx(createTx([]byte("c"), ".", 3).withGasPrice(1.1*oneBillion), txCache.tracker) + list.AddTx(createTx([]byte("d"), ".", 2), txCache.tracker) + list.AddTx(createTx([]byte("e"), ".", 3).withGasPrice(1.3*oneBillion), txCache.tracker) + + require.Equal(t, []string{"a", "d", "e", "b", "c"}, list.getTxHashesAsStrings()) +} + +func TestListForSender_AddTx_SortsCorrectlyWhenSameNonceSamePrice(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + list := newUnconstrainedListToTest() + + list.AddTx(createTx([]byte("a"), ".", 1).withGasPrice(oneBillion), txCache.tracker) + list.AddTx(createTx([]byte("b"), ".", 3).withGasPrice(3*oneBillion), txCache.tracker) + list.AddTx(createTx([]byte("c"), ".", 3).withGasPrice(3*oneBillion), txCache.tracker) + list.AddTx(createTx([]byte("d"), ".", 3).withGasPrice(2*oneBillion), txCache.tracker) + list.AddTx(createTx([]byte("e"), ".", 3).withGasPrice(3.5*oneBillion), txCache.tracker) + list.AddTx(createTx([]byte("f"), ".", 2).withGasPrice(oneBillion), txCache.tracker) + list.AddTx(createTx([]byte("g"), ".", 3).withGasPrice(2.5*oneBillion), txCache.tracker) + + // In case of same-nonce, same-price transactions, the newer one has priority + require.Equal(t, []string{"a", "f", "e", "b", "c", "g", "d"}, list.getTxHashesAsStrings()) +} + +func TestListForSender_AddTx_IgnoresDuplicates(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + list := newUnconstrainedListToTest() + + added, _ := list.AddTx(createTx([]byte("tx1"), ".", 1), txCache.tracker) + require.True(t, added) + added, _ = list.AddTx(createTx([]byte("tx2"), ".", 2), txCache.tracker) + require.True(t, added) + added, _ = list.AddTx(createTx([]byte("tx3"), ".", 3), txCache.tracker) + require.True(t, added) + added, _ = list.AddTx(createTx([]byte("tx2"), ".", 2), txCache.tracker) + require.False(t, added) +} + +func TestListForSender_AddTx_AppliesSizeConstraintsForNumTransactions(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + list := newListToTest(math.MaxUint32, 3) + + list.AddTx(createTx([]byte("tx1"), ".", 1), txCache.tracker) + list.AddTx(createTx([]byte("tx5"), ".", 5), txCache.tracker) + list.AddTx(createTx([]byte("tx4"), ".", 4), txCache.tracker) + list.AddTx(createTx([]byte("tx2"), ".", 2), txCache.tracker) + require.Equal(t, []string{"tx1", "tx2", "tx4"}, list.getTxHashesAsStrings()) + + _, evicted := list.AddTx(createTx([]byte("tx3"), ".", 3), txCache.tracker) + require.Equal(t, []string{"tx1", "tx2", "tx3"}, list.getTxHashesAsStrings()) + require.Equal(t, []string{"tx4"}, hashesAsStrings(evicted)) + + // Gives priority to higher gas - though undesirable to some extent, "tx3" is evicted + _, evicted = list.AddTx(createTx([]byte("tx2++"), ".", 2).withGasPrice(1.5*oneBillion), txCache.tracker) + require.Equal(t, []string{"tx1", "tx2++", "tx2"}, list.getTxHashesAsStrings()) + require.Equal(t, []string{"tx3"}, hashesAsStrings(evicted)) + + // Though undesirable to some extent, "tx3++"" is added, then evicted + _, evicted = list.AddTx(createTx([]byte("tx3++"), ".", 3).withGasPrice(1.5*oneBillion), txCache.tracker) + require.Equal(t, []string{"tx1", "tx2++", "tx2"}, list.getTxHashesAsStrings()) + require.Equal(t, []string{"tx3++"}, hashesAsStrings(evicted)) +} + +func TestListForSender_AddTx_AppliesSizeConstraintsForNumBytes(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + list := newListToTest(1024, math.MaxUint32) + + list.AddTx(createTx([]byte("tx1"), ".", 1).withSize(128).withGasLimit(50000), txCache.tracker) + list.AddTx(createTx([]byte("tx2"), ".", 2).withSize(512).withGasLimit(1500000), txCache.tracker) + list.AddTx(createTx([]byte("tx3"), ".", 3).withSize(256).withGasLimit(1500000), txCache.tracker) + _, evicted := list.AddTx(createTx([]byte("tx5"), ".", 4).withSize(256).withGasLimit(1500000), txCache.tracker) + require.Equal(t, []string{"tx1", "tx2", "tx3"}, list.getTxHashesAsStrings()) + require.Equal(t, []string{"tx5"}, hashesAsStrings(evicted)) + + _, evicted = list.AddTx(createTx([]byte("tx5--"), ".", 4).withSize(128).withGasLimit(50000), txCache.tracker) + require.Equal(t, []string{"tx1", "tx2", "tx3", "tx5--"}, list.getTxHashesAsStrings()) + require.Equal(t, []string{}, hashesAsStrings(evicted)) + + _, evicted = list.AddTx(createTx([]byte("tx4"), ".", 4).withSize(128).withGasLimit(50000), txCache.tracker) + require.Equal(t, []string{"tx1", "tx2", "tx3", "tx4"}, list.getTxHashesAsStrings()) + require.Equal(t, []string{"tx5--"}, hashesAsStrings(evicted)) + + // Gives priority to higher gas - though undesirably to some extent, "tx4" is evicted + _, evicted = list.AddTx(createTx([]byte("tx3++"), ".", 3).withSize(256).withGasLimit(1500000).withGasPrice(1.5*oneBillion), txCache.tracker) + require.Equal(t, []string{"tx1", "tx2", "tx3++"}, list.getTxHashesAsStrings()) + require.Equal(t, []string{"tx4", "tx3"}, hashesAsStrings(evicted)) +} + +func TestListForSender_removeTransactionsWithLowerOrEqualNonceReturnHashes(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + list := newUnconstrainedListToTest() + + list.AddTx(createTx([]byte("tx-42"), ".", 42), txCache.tracker) + list.AddTx(createTx([]byte("tx-43"), ".", 43), txCache.tracker) + list.AddTx(createTx([]byte("tx-44"), ".", 44), txCache.tracker) + list.AddTx(createTx([]byte("tx-45"), ".", 45), txCache.tracker) + + require.Equal(t, 4, list.list.len()) + + _ = list.removeTransactionsWithLowerOrEqualNonceReturnHashes(43) + require.Equal(t, 2, list.list.len()) + + _ = list.removeTransactionsWithLowerOrEqualNonceReturnHashes(44) + require.Equal(t, 1, list.list.len()) + + _ = list.removeTransactionsWithLowerOrEqualNonceReturnHashes(99) + require.Equal(t, 0, list.list.len()) +} + +func TestListForSender_getTxs(t *testing.T) { + t.Run("without transactions", func(t *testing.T) { + list := newUnconstrainedListToTest() + + require.Len(t, list.getTxs(), 0) + require.Len(t, list.getTxsReversed(), 0) + }) + + t.Run("with transactions", func(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + list := newUnconstrainedListToTest() + + list.AddTx(createTx([]byte("tx-42"), ".", 42), txCache.tracker) + require.Len(t, list.getTxs(), 1) + require.Len(t, list.getTxsReversed(), 1) + + list.AddTx(createTx([]byte("tx-44"), ".", 44), txCache.tracker) + require.Len(t, list.getTxs(), 2) + require.Len(t, list.getTxsReversed(), 2) + + list.AddTx(createTx([]byte("tx-43"), ".", 43), txCache.tracker) + require.Len(t, list.getTxs(), 3) + require.Len(t, list.getTxsReversed(), 3) + + require.Equal(t, []byte("tx-42"), list.getTxs()[0].TxHash) + require.Equal(t, []byte("tx-43"), list.getTxs()[1].TxHash) + require.Equal(t, []byte("tx-44"), list.getTxs()[2].TxHash) + require.Equal(t, []byte("tx-44"), list.getTxsReversed()[0].TxHash) + require.Equal(t, []byte("tx-43"), list.getTxsReversed()[1].TxHash) + require.Equal(t, []byte("tx-42"), list.getTxsReversed()[2].TxHash) + }) +} + +func TestListForSender_DetectRaceConditions(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + list := newUnconstrainedListToTest() + + wg := sync.WaitGroup{} + + doOperations := func() { + // These might be called concurrently: + _ = list.IsEmpty() + _ = list.getTxs() + _ = list.getTxsReversed() + _ = list.countTxWithLock() + _, _ = list.AddTx(createTx([]byte("test"), ".", 42), txCache.tracker) + + wg.Done() + } + + for i := 0; i < 100; i++ { + wg.Add(1) + go doOperations() + } + + wg.Wait() +} + +const numAccountsForBenchmark = 1_000 + +type testTxListFunc func(txCache *TxCache, list *txListForSender, numNoncesPerAccount uint64) +type createTxsFunc func(size int, senderIdx int) []*WrappedTransaction + +func funcRemoveTransactionsWithHigherOrEqualNonce(_ *TxCache, list *txListForSender, _ uint64) { + list.removeTransactionsWithHigherOrEqualNonce(0) +} + +func funcRemoveTransactionsWithLowerOrEqualNonceReturnHashes(_ *TxCache, list *txListForSender, numNoncesPerAccount uint64) { + list.removeTransactionsWithLowerOrEqualNonceReturnHashes(numNoncesPerAccount) +} + +func funcApplySizeConstraints(txCache *TxCache, list *txListForSender, numNoncesPerAccount uint64) { + list.constraints.maxNumTxs = uint32(numNoncesPerAccount * numAccountsForBenchmark / 2) + list.applySizeConstraints(txCache.tracker) +} + +func createTxsOrdered(size int, senderIdx int) []*WrappedTransaction { + txs := make([]*WrappedTransaction, 0, size) + + for i := 0; i < size; i++ { + txs = append(txs, + createTx( + []byte(fmt.Sprintf("txHash%d", i)), + fmt.Sprintf("sender-%d", senderIdx), + uint64(i), + ), + ) + } + + return txs +} + +func createTxsReversed(size int, senderIdx int) []*WrappedTransaction { + txs := make([]*WrappedTransaction, 0, size) + + for i := size - 1; i >= 0; i-- { + txs = append(txs, + createTx( + []byte(fmt.Sprintf("txHash%d", i)), + fmt.Sprintf("sender-%d", senderIdx), + uint64(i), + ), + ) + } + + return txs +} + +func createTxsRandom(size int, senderIdx int) []*WrappedTransaction { + txs := createTxsOrdered(size, senderIdx) + rand.Shuffle(size, func(i, j int) { txs[i], txs[j] = txs[j], txs[i] }) + return txs +} + +func BenchmarkTxList_removeTransactionsWithHigherOrEqualNonce(b *testing.B) { + benchmarkTxList(b, funcRemoveTransactionsWithHigherOrEqualNonce) +} + +func BenchmarkTxList_removeTransactionsWithLowerOrEqualNonceReturnHashes(b *testing.B) { + benchmarkTxList(b, funcRemoveTransactionsWithLowerOrEqualNonceReturnHashes) +} + +func BenchmarkTxList_applySizeConstraints(b *testing.B) { + benchmarkTxList(b, funcApplySizeConstraints) +} + +func benchmarkTxList(b *testing.B, testFunc testTxListFunc) { + b.Run("ordered", func(b *testing.B) { + benchmarkTxListForTxOrder(b, testFunc, createTxsOrdered) + }) + b.Run("reversed", func(b *testing.B) { + benchmarkTxListForTxOrder(b, testFunc, createTxsReversed) + }) + b.Run("random", func(b *testing.B) { + benchmarkTxListForTxOrder(b, testFunc, createTxsRandom) + }) +} + +func benchmarkTxListForTxOrder(b *testing.B, testFunc testTxListFunc, createTxFunc createTxsFunc) { + numNoncesPerAccount := []int{10, 100, 1_000} + + for _, numNonces := range numNoncesPerAccount { + b.Run(fmt.Sprintf("noncesPerAccount=%d", numNonces), func(b *testing.B) { + txCache := newCacheToTest( + maxNumBytesPerSenderUpperBoundTest, + math.MaxUint32, + ) + + txs := make([]*WrappedTransaction, 0, numNonces*numAccountsForBenchmark) + for accIdx := 0; accIdx < numAccountsForBenchmark; accIdx++ { + txs = append(txs, createTxFunc(numNonces, accIdx)...) + } + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + list := newUnconstrainedListToTest() + + for _, tx := range txs { + list.AddTx(tx, txCache.tracker) + } + + testFunc(txCache, list, uint64(numNonces)) + } + }) + } +} + +func TestListForSender_getTxsForSelection(t *testing.T) { + t.Run("without transactions", func(t *testing.T) { + list := newUnconstrainedListToTest() + require.Len(t, list.getTxsForSelection(), 0) + }) + + t.Run("with zero offset", func(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + list := newUnconstrainedListToTest() + + list.AddTx(createTx([]byte("tx-42"), ".", 42), txCache.tracker) + list.AddTx(createTx([]byte("tx-43"), ".", 43), txCache.tracker) + list.AddTx(createTx([]byte("tx-44"), ".", 44), txCache.tracker) + + txs := list.getTxsForSelection() + require.Len(t, txs, 3) + require.Equal(t, []byte("tx-42"), txs[0].TxHash) + require.Equal(t, []byte("tx-43"), txs[1].TxHash) + require.Equal(t, []byte("tx-44"), txs[2].TxHash) + }) + + t.Run("with offset = 1", func(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + list := newUnconstrainedListToTest() + + list.AddTx(createTx([]byte("tx-42"), ".", 42), txCache.tracker) + list.AddTx(createTx([]byte("tx-43"), ".", 43), txCache.tracker) + list.AddTx(createTx([]byte("tx-44"), ".", 44), txCache.tracker) + + list.mutex.Lock() + list.incrementSelectionOffset(1) + list.mutex.Unlock() + + txs := list.getTxsForSelection() + require.Len(t, txs, 2) + require.Equal(t, []byte("tx-43"), txs[0].TxHash) + require.Equal(t, []byte("tx-44"), txs[1].TxHash) + }) + + t.Run("with offset = all transactions", func(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + list := newUnconstrainedListToTest() + + list.AddTx(createTx([]byte("tx-42"), ".", 42), txCache.tracker) + list.AddTx(createTx([]byte("tx-43"), ".", 43), txCache.tracker) + + list.mutex.Lock() + list.incrementSelectionOffset(2) + list.mutex.Unlock() + + txs := list.getTxsForSelection() + require.Len(t, txs, 0) + }) + + t.Run("with offset exceeds list length", func(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + list := newUnconstrainedListToTest() + + list.AddTx(createTx([]byte("tx-42"), ".", 42), txCache.tracker) + + list.mutex.Lock() + list.incrementSelectionOffset(10) + list.mutex.Unlock() + + // Offset should be clamped to list length + require.Equal(t, 1, list.getSelectionOffset()) + txs := list.getTxsForSelection() + require.Len(t, txs, 0) + }) +} + +func TestListForSender_incrementSelectionOffset(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + list := newUnconstrainedListToTest() + + list.AddTx(createTx([]byte("tx-1"), ".", 1), txCache.tracker) + list.AddTx(createTx([]byte("tx-2"), ".", 2), txCache.tracker) + list.AddTx(createTx([]byte("tx-3"), ".", 3), txCache.tracker) + + require.Equal(t, 0, list.getSelectionOffset()) + + list.mutex.Lock() + list.incrementSelectionOffset(1) + list.mutex.Unlock() + require.Equal(t, 1, list.getSelectionOffset()) + + list.mutex.Lock() + list.incrementSelectionOffset(2) + list.mutex.Unlock() + require.Equal(t, 3, list.getSelectionOffset()) + + // Should be clamped to list length + list.mutex.Lock() + list.incrementSelectionOffset(5) + list.mutex.Unlock() + require.Equal(t, 3, list.getSelectionOffset()) +} + +func TestListForSender_decrementSelectionOffset(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + list := newUnconstrainedListToTest() + + list.AddTx(createTx([]byte("tx-1"), ".", 1), txCache.tracker) + list.AddTx(createTx([]byte("tx-2"), ".", 2), txCache.tracker) + list.AddTx(createTx([]byte("tx-3"), ".", 3), txCache.tracker) + + list.mutex.Lock() + list.incrementSelectionOffset(3) + list.mutex.Unlock() + require.Equal(t, 3, list.getSelectionOffset()) + + list.mutex.Lock() + list.decrementSelectionOffset(1) + list.mutex.Unlock() + require.Equal(t, 2, list.getSelectionOffset()) + + list.mutex.Lock() + list.decrementSelectionOffset(2) + list.mutex.Unlock() + require.Equal(t, 0, list.getSelectionOffset()) + + // Should be clamped to 0 + list.mutex.Lock() + list.decrementSelectionOffset(5) + list.mutex.Unlock() + require.Equal(t, 0, list.getSelectionOffset()) +} + +func TestListForSender_resetSelectionOffsetByNonce(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + list := newUnconstrainedListToTest() + + list.AddTx(createTx([]byte("tx-10"), ".", 10), txCache.tracker) + list.AddTx(createTx([]byte("tx-20"), ".", 20), txCache.tracker) + list.AddTx(createTx([]byte("tx-30"), ".", 30), txCache.tracker) + list.AddTx(createTx([]byte("tx-40"), ".", 40), txCache.tracker) + + // Reset to first transaction with nonce >= 20 + list.resetSelectionOffsetByNonce(20) + require.Equal(t, 1, list.getSelectionOffset()) + + // Reset to first transaction with nonce >= 35 (should be tx-40 at index 3) + list.resetSelectionOffsetByNonce(35) + require.Equal(t, 3, list.getSelectionOffset()) + + // Reset to first transaction with nonce >= 10 (should be tx-10 at index 0) + list.resetSelectionOffsetByNonce(10) + require.Equal(t, 0, list.getSelectionOffset()) + + // Reset with nonce beyond all transactions + list.resetSelectionOffsetByNonce(100) + require.Equal(t, 4, list.getSelectionOffset()) +} + +func TestListForSender_OffsetAdjustsOnAddTx(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + list := newUnconstrainedListToTest() + + list.AddTx(createTx([]byte("tx-10"), ".", 10), txCache.tracker) + list.AddTx(createTx([]byte("tx-20"), ".", 20), txCache.tracker) + list.AddTx(createTx([]byte("tx-30"), ".", 30), txCache.tracker) + + // Set offset to 2 (pointing to tx-30) + list.mutex.Lock() + list.incrementSelectionOffset(2) + list.mutex.Unlock() + require.Equal(t, 2, list.getSelectionOffset()) + + // Add transaction with nonce 15 (inserts before offset) + list.AddTx(createTx([]byte("tx-15"), ".", 15), txCache.tracker) + // Offset should be incremented to 3 + require.Equal(t, 3, list.getSelectionOffset()) + + // Add transaction with nonce 35 (inserts after offset) + list.AddTx(createTx([]byte("tx-35"), ".", 35), txCache.tracker) + // Offset should remain 3 + require.Equal(t, 3, list.getSelectionOffset()) +} + +func TestListForSender_OffsetAdjustsOnRemove(t *testing.T) { + txCache := newCacheToTest(maxNumBytesPerSenderUpperBoundTest, math.MaxUint32) + list := newUnconstrainedListToTest() + + list.AddTx(createTx([]byte("tx-10"), ".", 10), txCache.tracker) + list.AddTx(createTx([]byte("tx-20"), ".", 20), txCache.tracker) + list.AddTx(createTx([]byte("tx-30"), ".", 30), txCache.tracker) + list.AddTx(createTx([]byte("tx-40"), ".", 40), txCache.tracker) + + // Set offset to 2 + list.mutex.Lock() + list.incrementSelectionOffset(2) + list.mutex.Unlock() + require.Equal(t, 2, list.getSelectionOffset()) + + // Remove transactions with nonce <= 10 (removes 1 tx before offset) + list.removeTransactionsWithLowerOrEqualNonceReturnHashes(10) + // Offset should be decremented to 1 + require.Equal(t, 1, list.getSelectionOffset()) + + // Remove transactions with nonce <= 20 (removes 1 tx before offset) + list.removeTransactionsWithLowerOrEqualNonceReturnHashes(20) + // Offset should be decremented to 0 + require.Equal(t, 0, list.getSelectionOffset()) +} + +func newUnconstrainedListToTest() *txListForSender { + return newListToTest(math.MaxUint32, math.MaxUint32) +} + +func newListToTest(maxNumBytes uint32, maxNumTxs uint32) *txListForSender { + senderConstraints := &senderConstraints{ + maxNumBytes: maxNumBytes, + maxNumTxs: maxNumTxs, + } + + return newTxListForSender(".", senderConstraints) +} diff --git a/txcache/virtualAccountBalance.go b/txcache/virtualAccountBalance.go new file mode 100644 index 00000000000..6b09bd14298 --- /dev/null +++ b/txcache/virtualAccountBalance.go @@ -0,0 +1,66 @@ +package txcache + +import ( + "math/big" + "sync" +) + +// virtualAccountBalance contains: +// the initialBalance from the non-virtual session, +// the consumedBalance accumulated from breadcrumbs. +type virtualAccountBalance struct { + initialBalance *big.Int + consumedBalance *big.Int + mutex sync.Mutex +} + +// virtualAccountBalance is used in two scenarios: +// when validating a proposed block (on the OnProposedBlock notification), where we create a virtualBalance for each account; +// inside a virtual record +func newVirtualAccountBalance(initialBalance *big.Int) (*virtualAccountBalance, error) { + if initialBalance == nil { + return nil, errNilBalance + } + return &virtualAccountBalance{ + initialBalance: initialBalance, + consumedBalance: big.NewInt(0), + }, nil +} + +// accumulateConsumedBalance is used in two places: +// accumulating for the validation of a proposed block +// accumulating for a virtual record +func (virtualBalance *virtualAccountBalance) accumulateConsumedBalance(consumedBalance *big.Int) { + virtualBalance.mutex.Lock() + defer virtualBalance.mutex.Unlock() + + // defensive copy to prevent aliasing issues if consumedBalance is modified externally later + toAdd := new(big.Int).Set(consumedBalance) + _ = virtualBalance.consumedBalance.Add(virtualBalance.consumedBalance, toAdd) +} + +// validateBalance is used in ONLY one place: the validation of a proposed block +// this method is NOT used for the virtual records (in deriveVirtualSelectionSession) +func (virtualBalance *virtualAccountBalance) validateBalance() error { + virtualBalance.mutex.Lock() + defer virtualBalance.mutex.Unlock() + + if virtualBalance.consumedBalance.Cmp(virtualBalance.initialBalance) > 0 { + return errExceededBalance + } + + return nil +} + +func (virtualBalance *virtualAccountBalance) getInitialBalance() *big.Int { + // initialBalance is immutable after creation, no lock needed + return virtualBalance.initialBalance +} + +func (virtualBalance *virtualAccountBalance) getConsumedBalance() *big.Int { + virtualBalance.mutex.Lock() + defer virtualBalance.mutex.Unlock() + + // Return a copy to prevent external mutation + return new(big.Int).Set(virtualBalance.consumedBalance) +} diff --git a/txcache/virtualAccountBalance_test.go b/txcache/virtualAccountBalance_test.go new file mode 100644 index 00000000000..797f3591a1c --- /dev/null +++ b/txcache/virtualAccountBalance_test.go @@ -0,0 +1,85 @@ +package txcache + +import ( + "math/big" + "sync" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_virtualAccountBalance_ConcurrentAccumulate(t *testing.T) { + t.Parallel() + + vb, err := newVirtualAccountBalance(big.NewInt(1000)) + require.Nil(t, err) + + var wg sync.WaitGroup + count := 100 + addVal := big.NewInt(1) + + wg.Add(count) + for i := 0; i < count; i++ { + go func() { + defer wg.Done() + vb.accumulateConsumedBalance(addVal) + }() + } + wg.Wait() + + // Expected consumed: 100 * 1 = 100 + require.Equal(t, big.NewInt(100), vb.getConsumedBalance()) + + // Initial should be unchanged + require.Equal(t, big.NewInt(1000), vb.getInitialBalance()) +} + +func Test_virtualAccountBalance_ConcurrentReadWrite(t *testing.T) { + t.Parallel() + + vb, err := newVirtualAccountBalance(big.NewInt(1000)) + require.Nil(t, err) + + var wg sync.WaitGroup + count := 100 + + wg.Add(2 * count) + + // Writers + for i := 0; i < count; i++ { + go func() { + defer wg.Done() + vb.accumulateConsumedBalance(big.NewInt(1)) + }() + } + + // Readers + for i := 0; i < count; i++ { + go func() { + defer wg.Done() + _ = vb.getConsumedBalance() + _ = vb.validateBalance() + }() + } + + wg.Wait() + + require.Equal(t, big.NewInt(int64(count)), vb.getConsumedBalance()) +} + +func Test_virtualAccountBalance_ValidateBalance(t *testing.T) { + t.Parallel() + + // 1. Valid case + vb, _ := newVirtualAccountBalance(big.NewInt(100)) + vb.accumulateConsumedBalance(big.NewInt(50)) + require.Nil(t, vb.validateBalance()) + + // 2. Exact match + vb.accumulateConsumedBalance(big.NewInt(50)) + require.Nil(t, vb.validateBalance()) + + // 3. Exceeded + vb.accumulateConsumedBalance(big.NewInt(1)) + require.Equal(t, errExceededBalance, vb.validateBalance()) +} diff --git a/txcache/virtualAccountRecord.go b/txcache/virtualAccountRecord.go new file mode 100644 index 00000000000..cd3d935004c --- /dev/null +++ b/txcache/virtualAccountRecord.go @@ -0,0 +1,51 @@ +package txcache + +import ( + "math/big" + + "github.com/multiversx/mx-chain-core-go/core" +) + +type virtualAccountRecord struct { + initialNonce core.OptionalUint64 + virtualBalance *virtualAccountBalance + hasPendingChangeGuardianTransaction bool +} + +func newVirtualAccountRecord(initialNonce core.OptionalUint64, initialBalance *big.Int) (*virtualAccountRecord, error) { + virtualBalance, err := newVirtualAccountBalance(initialBalance) + if err != nil { + return nil, err + } + + return &virtualAccountRecord{ + initialNonce: initialNonce, + virtualBalance: virtualBalance, + }, nil +} + +func (virtualRecord *virtualAccountRecord) getInitialNonce() (uint64, error) { + if !virtualRecord.initialNonce.HasValue { + log.Debug("virtualAccountRecord.getInitialNonce", + "err", errNonceNotSet) + return 0, errNonceNotSet + } + + return virtualRecord.initialNonce.Value, nil +} + +func (virtualRecord *virtualAccountRecord) getInitialBalance() *big.Int { + return virtualRecord.virtualBalance.getInitialBalance() +} + +func (virtualRecord *virtualAccountRecord) getConsumedBalance() *big.Int { + return virtualRecord.virtualBalance.getConsumedBalance() +} + +func (virtualRecord *virtualAccountRecord) accumulateConsumedBalance(consumedBalance *big.Int) { + virtualRecord.virtualBalance.accumulateConsumedBalance(consumedBalance) +} + +func (virtualRecord *virtualAccountRecord) hasPendingChangeGuardian() bool { + return virtualRecord.hasPendingChangeGuardianTransaction +} diff --git a/txcache/virtualAccountRecord_test.go b/txcache/virtualAccountRecord_test.go new file mode 100644 index 00000000000..e330998ead0 --- /dev/null +++ b/txcache/virtualAccountRecord_test.go @@ -0,0 +1,56 @@ +package txcache + +import ( + "math/big" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/stretchr/testify/require" +) + +func Test_newVirtualAccountRecord(t *testing.T) { + t.Parallel() + + initialNonce := core.OptionalUint64{ + Value: uint64(1), + HasValue: true, + } + + record, err := newVirtualAccountRecord(initialNonce, big.NewInt(0)) + require.NoError(t, err) + + require.Equal(t, record.initialNonce, initialNonce) + require.Equal(t, record.virtualBalance.initialBalance, big.NewInt(0)) + require.Equal(t, record.virtualBalance.consumedBalance, big.NewInt(0)) +} + +func Test_getInitialNonce(t *testing.T) { + t.Parallel() + + expectedInitialNonce := core.OptionalUint64{ + Value: uint64(1), + HasValue: true, + } + + record, err := newVirtualAccountRecord(expectedInitialNonce, big.NewInt(0)) + require.NoError(t, err) + + initialNonce, err := record.getInitialNonce() + require.NoError(t, err) + require.Equal(t, expectedInitialNonce.Value, initialNonce) +} + +func Test_getConsumedBalance(t *testing.T) { + t.Parallel() + + initialNonce := core.OptionalUint64{ + Value: uint64(1), + HasValue: true, + } + + record, err := newVirtualAccountRecord(initialNonce, big.NewInt(0)) + require.NoError(t, err) + + balance := record.getConsumedBalance() + require.Equal(t, balance, big.NewInt(0)) +} diff --git a/txcache/virtualSelectionSession.go b/txcache/virtualSelectionSession.go new file mode 100644 index 00000000000..514ecea25e6 --- /dev/null +++ b/txcache/virtualSelectionSession.go @@ -0,0 +1,197 @@ +package txcache + +import ( + "bytes" + "math/big" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data" +) + +type virtualSelectionSession struct { + session SelectionSession + virtualAccountsByAddress map[string]*virtualAccountRecord +} + +func newVirtualSelectionSession(session SelectionSession, virtualAccountsByAddress map[string]*virtualAccountRecord) *virtualSelectionSession { + return &virtualSelectionSession{ + session: session, + virtualAccountsByAddress: virtualAccountsByAddress, + } +} + +func (virtualSession *virtualSelectionSession) getRecord(address []byte) (*virtualAccountRecord, error) { + virtualRecord, ok := virtualSession.virtualAccountsByAddress[string(address)] + if ok { + return virtualRecord, nil + } + + virtualRecord, err := virtualSession.createAccountRecord(address) + if err != nil { + log.Warn("virtualSelectionSession.getRecord: error when creating virtual account record", + "address", address, + "err", err) + return nil, err + } + + // We handle records corresponding to new (missing) accounts, as well (see "createAccountRecord"). + virtualSession.virtualAccountsByAddress[string(address)] = virtualRecord + return virtualRecord, nil +} + +func (virtualSession *virtualSelectionSession) createAccountRecord(address []byte) (*virtualAccountRecord, error) { + initialNonce, initialBalance, _, err := virtualSession.session.GetAccountNonceAndBalance(address) + if err != nil { + return nil, err + } + + return newVirtualAccountRecord( + core.OptionalUint64{ + Value: initialNonce, + HasValue: true, + }, + initialBalance, + ) +} + +func (virtualSession *virtualSelectionSession) getNonceForAccountRecord(accountRecord *virtualAccountRecord) (uint64, error) { + return accountRecord.getInitialNonce() +} + +func (virtualSession *virtualSelectionSession) accumulateConsumedBalance(tx *WrappedTransaction, senderRecord *virtualAccountRecord) error { + var feePayerRecord *virtualAccountRecord + + // check if there's a need to search for another record + if tx.isFeePayerSameAsSender() { + feePayerRecord = senderRecord + } else { + feePayer := tx.FeePayer + record, err := virtualSession.getRecord(feePayer) + if err != nil { + log.Warn("accumulateConsumedBalance.getRecord feePayer", + "feePayer", feePayer, + "err", err) + return err + } + + // should affect the record of fee payer + feePayerRecord = record + } + + fee := tx.Fee + if fee != nil { + feePayerRecord.accumulateConsumedBalance(fee) + } + + // getting the record of the fee payer might generate an unexpected failure. + // this means that the transaction will not be selected. + // accumulate the transferred value only if there isn't any error until here. + transferredValue := tx.TransferredValue + if transferredValue != nil { + senderRecord.accumulateConsumedBalance(transferredValue) + } + + return nil +} + +func (virtualSession *virtualSelectionSession) consumedBalanceExceedsInitialBalance(address []byte, value *big.Int) bool { + record, err := virtualSession.getRecord(address) + if err != nil { + log.Debug("virtualSelectionSession.consumedBalanceExceedsInitialBalance", + "err", err) + return true + } + + consumedBalance := record.getConsumedBalance() + futureConsumedBalance := new(big.Int).Add(consumedBalance, value) + initialBalance := record.getInitialBalance() + + willBalanceBeExceeded := futureConsumedBalance.Cmp(initialBalance) > 0 + if willBalanceBeExceeded { + logSelect.Trace("virtualSelectionSession.consumedBalanceExceedsInitialBalance", + "initialBalance", initialBalance, + "consumedBalance", consumedBalance, + ) + } + return willBalanceBeExceeded +} + +// the selection of transactions has to be as restrictive as proposing the block. +// this means that we should check not only if fee exceeds the balance of relayer, but also if the transferred value exceeds it +func (virtualSession *virtualSelectionSession) detectWillBalanceBeExceeded(tx *WrappedTransaction) bool { + if tx == nil { + log.Debug("virtualSelectionSession.detectWillBalanceBeExceeded nil wrapped transaction") + return true + } + + if tx.Tx == nil { + log.Debug("virtualSelectionSession.detectWillBalanceBeExceeded nil tx") + return true + } + + sender := tx.Tx.GetSndAddr() + transferredValue := tx.TransferredValue + if transferredValue == nil { + log.Trace("virtualSelectionSession.detectWillBalanceBeExceeded nil transferredValue") + return true + } + + if virtualSession.consumedBalanceExceedsInitialBalance(sender, transferredValue) { + logSelect.Debug("virtualSelectionSession.detectWillBalanceBeExceeded balance exceeded by transferred value", + "txHash", tx.TxHash, + "sender", sender, + "transferredValue", transferredValue, + ) + return true + } + + feePayer := tx.FeePayer + fee := tx.Fee + if fee == nil { + log.Debug("virtualSelectionSession.detectWillBalanceBeExceeded nil fee") + return true + } + + if virtualSession.consumedBalanceExceedsInitialBalance(feePayer, fee) { + logSelect.Trace("virtualSelectionSession.detectWillBalanceBeExceeded balance exceeded by fee", + "txHash", tx.TxHash, + "feePayer", feePayer, + "fee", fee, + ) + return true + } + + accumulatedBalance := big.NewInt(0).Add(transferredValue, fee) + if bytes.Equal(sender, feePayer) && virtualSession.consumedBalanceExceedsInitialBalance(sender, accumulatedBalance) { + logSelect.Trace("virtualSelectionSession.detectWillBalanceBeExceeded balance exceeded by sum of transferred value and fee", + "txHash", tx.TxHash, + "sender", sender, + "transferredValue", transferredValue, + "fee", fee, + ) + return true + } + + return false +} + +func (virtualSession *virtualSelectionSession) isIncorrectlyGuarded(tx data.TransactionHandler) bool { + return virtualSession.session.IsIncorrectlyGuarded(tx) +} + +func (virtualSession *virtualSelectionSession) setChangeGuardianIfNeeded(tx data.TransactionHandler) { + if !virtualSession.session.IsGuarded(tx) { + return + } + + if virtualSession.isIncorrectlyGuarded(tx) { + return + } + + record, err := virtualSession.getRecord(tx.GetSndAddr()) + if err != nil { + return + } + + record.hasPendingChangeGuardianTransaction = true +} diff --git a/txcache/virtualSelectionSession_test.go b/txcache/virtualSelectionSession_test.go new file mode 100644 index 00000000000..7c50ad67e77 --- /dev/null +++ b/txcache/virtualSelectionSession_test.go @@ -0,0 +1,511 @@ +package txcache + +import ( + "errors" + "fmt" + "math/big" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-go/testscommon/txcachemocks" + "github.com/stretchr/testify/require" +) + +func Test_newVirtualSelectionSession(t *testing.T) { + t.Parallel() + + session := txcachemocks.NewSelectionSessionMock() + virtualSession := newVirtualSelectionSession(session, make(map[string]*virtualAccountRecord)) + require.NotNil(t, virtualSession) +} + +func Test_getVirtualRecord(t *testing.T) { + t.Parallel() + + t.Run("should return virtual record", func(t *testing.T) { + t.Parallel() + + sessionMock := txcachemocks.SelectionSessionMock{} + virtualSession := newVirtualSelectionSession(&sessionMock, make(map[string]*virtualAccountRecord)) + + expectedRecord := virtualAccountRecord{ + initialNonce: core.OptionalUint64{ + Value: 3, + HasValue: true, + }, + virtualBalance: &virtualAccountBalance{ + initialBalance: big.NewInt(2), + consumedBalance: big.NewInt(3), + }, + } + virtualSession.virtualAccountsByAddress = map[string]*virtualAccountRecord{ + "alice": &expectedRecord, + } + + actualRecord, err := virtualSession.getRecord([]byte("alice")) + require.NoError(t, err) + require.Equal(t, &expectedRecord, actualRecord) + }) + + t.Run("should return account from real session", func(t *testing.T) { + t.Parallel() + + sessionMock := txcachemocks.SelectionSessionMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 2, big.NewInt(2), true, nil + }, + } + virtualSession := newVirtualSelectionSession(&sessionMock, make(map[string]*virtualAccountRecord)) + + expectedRecord := virtualAccountRecord{ + initialNonce: core.OptionalUint64{ + Value: 2, + HasValue: true, + }, + virtualBalance: &virtualAccountBalance{ + initialBalance: big.NewInt(2), + consumedBalance: big.NewInt(0), + }, + } + actualRecord, err := virtualSession.getRecord([]byte("alice")) + + require.NoError(t, err) + require.Equal(t, expectedRecord.initialNonce, actualRecord.initialNonce) + require.Equal(t, expectedRecord.getInitialBalance(), actualRecord.getInitialBalance()) + require.Equal(t, expectedRecord.getConsumedBalance(), actualRecord.getConsumedBalance()) + }) + + t.Run("should create empty record when account does not exist", func(t *testing.T) { + t.Parallel() + + sessionMock := txcachemocks.SelectionSessionMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 0, big.NewInt(0), false, nil + }, + } + virtualSession := newVirtualSelectionSession(&sessionMock, make(map[string]*virtualAccountRecord)) + + actualRecord, err := virtualSession.getRecord([]byte("alice")) + require.NoError(t, err) + require.Equal(t, core.OptionalUint64{Value: 0, HasValue: true}, actualRecord.initialNonce) + require.Equal(t, big.NewInt(0), actualRecord.getInitialBalance()) + require.Equal(t, big.NewInt(0), actualRecord.getConsumedBalance()) + }) + + t.Run("should err", func(t *testing.T) { + t.Parallel() + + expErr := errors.New("error") + sessionMock := txcachemocks.SelectionSessionMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 0, nil, false, expErr + }, + } + virtualSession := newVirtualSelectionSession(&sessionMock, make(map[string]*virtualAccountRecord)) + + actualRecord, err := virtualSession.getRecord([]byte("alice")) + require.Nil(t, actualRecord) + require.Equal(t, expErr, err) + }) +} + +func Test_getNonce(t *testing.T) { + t.Parallel() + + t.Run("should return nonce from real session", func(t *testing.T) { + t.Parallel() + + sessionMock := txcachemocks.SelectionSessionMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 2, big.NewInt(2), true, nil + }, + } + virtualSession := newVirtualSelectionSession(&sessionMock, make(map[string]*virtualAccountRecord)) + + virtualRecord, err := virtualSession.getRecord([]byte("alice")) + require.NoError(t, err) + + actualNonce, err := virtualSession.getNonceForAccountRecord(virtualRecord) + + require.NoError(t, err) + require.Equal(t, uint64(2), actualNonce) + }) + + t.Run("should return nonce from account record", func(t *testing.T) { + t.Parallel() + + sessionMock := txcachemocks.SelectionSessionMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 2, big.NewInt(2), true, nil + }, + } + + virtualSession := newVirtualSelectionSession(&sessionMock, make(map[string]*virtualAccountRecord)) + + expectedRecord := virtualAccountRecord{ + initialNonce: core.OptionalUint64{ + Value: 3, + HasValue: true, + }, + virtualBalance: &virtualAccountBalance{ + initialBalance: big.NewInt(2), + consumedBalance: big.NewInt(3), + }, + } + virtualSession.virtualAccountsByAddress = map[string]*virtualAccountRecord{ + "alice": &expectedRecord, + } + + aliceRecord, err := virtualSession.getRecord([]byte("alice")) + require.NoError(t, err) + + actualNonce, err := virtualSession.getNonceForAccountRecord(aliceRecord) + + require.NoError(t, err) + require.Equal(t, uint64(3), actualNonce) + }) + + t.Run("should err", func(t *testing.T) { + t.Parallel() + + sessionMock := txcachemocks.SelectionSessionMock{} + virtualSession := newVirtualSelectionSession(&sessionMock, make(map[string]*virtualAccountRecord)) + + aliceRecord, err := newVirtualAccountRecord(core.OptionalUint64{Value: 0, HasValue: false}, big.NewInt(1)) + require.NoError(t, err) + + _, err = virtualSession.getNonceForAccountRecord(aliceRecord) + require.Equal(t, errNonceNotSet, err) + }) + + t.Run("should return errNonceNotSet", func(t *testing.T) { + t.Parallel() + + sessionMock := txcachemocks.SelectionSessionMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 2, big.NewInt(2), true, nil + }, + } + + virtualSession := newVirtualSelectionSession(&sessionMock, make(map[string]*virtualAccountRecord)) + + expectedRecord := virtualAccountRecord{ + initialNonce: core.OptionalUint64{ + Value: 0, + HasValue: false, + }, + virtualBalance: &virtualAccountBalance{ + initialBalance: big.NewInt(2), + consumedBalance: big.NewInt(3), + }, + } + virtualSession.virtualAccountsByAddress = map[string]*virtualAccountRecord{ + "alice": &expectedRecord, + } + + aliceRecord, err := virtualSession.getRecord([]byte("alice")) + require.NoError(t, err) + + _, err = virtualSession.getNonceForAccountRecord(aliceRecord) + require.Equal(t, errNonceNotSet, err) + }) +} + +func Test_accumulateConsumedBalance(t *testing.T) { + host := txcachemocks.NewMempoolHostMock() + + t.Run("when sender is fee payer", func(t *testing.T) { + session := txcachemocks.NewSelectionSessionMock() + virtualSession := newVirtualSelectionSession(session, make(map[string]*virtualAccountRecord)) + + a := createTx([]byte("a-7"), "a", 7) + b := createTx([]byte("a-8"), "a", 8).withValue(oneQuintillionBig) + + a.precomputeFields(host) + b.precomputeFields(host) + + virtualRecord1, err := virtualSession.getRecord([]byte("a")) + require.NoError(t, err) + + err = virtualSession.accumulateConsumedBalance(a, virtualRecord1) + require.NoError(t, err) + + virtualRecord1, err = virtualSession.getRecord([]byte("a")) + require.NoError(t, err) + require.Equal(t, "50000000000000", virtualRecord1.getConsumedBalance().String()) + + err = virtualSession.accumulateConsumedBalance(b, virtualRecord1) + require.NoError(t, err) + + virtualRecord2, err := virtualSession.getRecord([]byte("a")) + require.NoError(t, err) + require.Equal(t, "1000100000000000000", virtualRecord2.getConsumedBalance().String()) + + }) + + t.Run("when relayer is fee payer", func(t *testing.T) { + session := txcachemocks.NewSelectionSessionMock() + virtualSession := newVirtualSelectionSession(session, make(map[string]*virtualAccountRecord)) + + a := createTx([]byte("a-7"), "a", 7).withRelayer([]byte("b")).withGasLimit(100_000) + b := createTx([]byte("a-8"), "a", 8).withValue(oneQuintillionBig).withRelayer([]byte("b")).withGasLimit(100_000) + + a.precomputeFields(host) + b.precomputeFields(host) + + virtualRecord1, err := virtualSession.getRecord([]byte("a")) + require.NoError(t, err) + + err = virtualSession.accumulateConsumedBalance(a, virtualRecord1) + require.NoError(t, err) + + virtualRecord1, err = virtualSession.getRecord([]byte("a")) + require.NoError(t, err) + require.Equal(t, "0", virtualRecord1.getConsumedBalance().String()) + + virtualRecord2, err := virtualSession.getRecord([]byte("b")) + require.NoError(t, err) + require.Equal(t, "100000000000000", virtualRecord2.getConsumedBalance().String()) + + err = virtualSession.accumulateConsumedBalance(b, virtualRecord1) + require.NoError(t, err) + + virtualRecord1, err = virtualSession.getRecord([]byte("a")) + require.NoError(t, err) + require.Equal(t, "1000000000000000000", virtualRecord1.getConsumedBalance().String()) + + virtualRecord2, err = virtualSession.getRecord([]byte("b")) + require.NoError(t, err) + require.Equal(t, "200000000000000", virtualRecord2.getConsumedBalance().String()) + }) +} + +func Test_detectWillBalanceBeExceeded(t *testing.T) { + t.Parallel() + + t.Run("should exceed balance", func(t *testing.T) { + t.Parallel() + + sessionMock := txcachemocks.SelectionSessionMock{} + virtualSession := newVirtualSelectionSession(&sessionMock, make(map[string]*virtualAccountRecord)) + + aliceRecord := virtualAccountRecord{ + initialNonce: core.OptionalUint64{ + Value: 3, + HasValue: true, + }, + virtualBalance: &virtualAccountBalance{ + initialBalance: big.NewInt(2), + consumedBalance: big.NewInt(1), + }, + } + virtualSession.virtualAccountsByAddress = map[string]*virtualAccountRecord{ + "alice": &aliceRecord, + } + + tx := WrappedTransaction{ + TransferredValue: big.NewInt(0), + Tx: &transaction.Transaction{ + SndAddr: []byte("alice"), + }, + Fee: big.NewInt(2), + FeePayer: []byte("alice"), + } + + actualRes := virtualSession.detectWillBalanceBeExceeded(&tx) + require.True(t, actualRes) + }) + + t.Run("should not exceed balance", func(t *testing.T) { + t.Parallel() + + sessionMock := txcachemocks.SelectionSessionMock{} + virtualSession := newVirtualSelectionSession(&sessionMock, make(map[string]*virtualAccountRecord)) + + aliceRecord := virtualAccountRecord{ + initialNonce: core.OptionalUint64{ + Value: 3, + HasValue: true, + }, + virtualBalance: &virtualAccountBalance{ + initialBalance: big.NewInt(5), + consumedBalance: big.NewInt(1), + }, + } + virtualSession.virtualAccountsByAddress = map[string]*virtualAccountRecord{ + "alice": &aliceRecord, + } + + tx := WrappedTransaction{ + Tx: &transaction.Transaction{ + SndAddr: []byte("bob"), + }, + TransferredValue: big.NewInt(0), + Fee: big.NewInt(2), + FeePayer: []byte("alice"), + } + + actualRes := virtualSession.detectWillBalanceBeExceeded(&tx) + require.False(t, actualRes) + }) +} + +func Test_isIncorrectlyGuarded(t *testing.T) { + t.Parallel() + + t.Run("should return not correctly guarded", func(t *testing.T) { + t.Parallel() + + sessionMock := txcachemocks.SelectionSessionMock{ + IsIncorrectlyGuardedCalled: func(tx data.TransactionHandler) bool { + return true + }, + } + + virtualSession := newVirtualSelectionSession(&sessionMock, make(map[string]*virtualAccountRecord)) + + actualRes := virtualSession.isIncorrectlyGuarded(nil) + require.True(t, actualRes) + }) +} + +func TestBenchmarkVirtualSelectionSession_getRecord(t *testing.T) { + sw := core.NewStopWatch() + + t.Run("numAccounts = 300, numTransactionsPerAccount = 100", func(t *testing.T) { + session := txcachemocks.NewSelectionSessionMock() + virtualSession := newVirtualSelectionSession(session, make(map[string]*virtualAccountRecord)) + + numAccounts := 300 + numTransactionsPerAccount := 100 + // See "detectSkippableSender()" and "detectSkippableTransaction()". + numCallsGetNoncePerTransaction := 2 + numCallsGetNoncePerAccount := numTransactionsPerAccount * numCallsGetNoncePerTransaction + + for i := 0; i < numAccounts; i++ { + session.SetNonce(randomAddresses.getItem(i), uint64(i)) + } + + sw.Start(t.Name()) + + for i := 0; i < numAccounts; i++ { + for j := 0; j < numCallsGetNoncePerAccount; j++ { + _, err := virtualSession.getRecord(randomAddresses.getItem(i)) + require.NoError(t, err) + } + } + + sw.Stop(t.Name()) + + require.Equal(t, numAccounts, session.NumCallsGetAccountNonceAndBalance) + }) + + t.Run("numAccounts = 10_000, numTransactionsPerAccount = 3", func(t *testing.T) { + session := txcachemocks.NewSelectionSessionMock() + sessionWrapper := newVirtualSelectionSession(session, make(map[string]*virtualAccountRecord)) + + numAccounts := 10_000 + numTransactionsPerAccount := 3 + // See "detectSkippableSender()" and "detectSkippableTransaction()". + numCallsGetNoncePerTransaction := 2 + numCallsGetNoncePerAccount := numTransactionsPerAccount * numCallsGetNoncePerTransaction + + for i := 0; i < numAccounts; i++ { + session.SetNonce(randomAddresses.getItem(i), uint64(i)) + } + + sw.Start(t.Name()) + + for i := 0; i < numAccounts; i++ { + for j := 0; j < numCallsGetNoncePerAccount; j++ { + _, err := sessionWrapper.getRecord(randomAddresses.getItem(i)) + require.NoError(t, err) + } + } + + sw.Stop(t.Name()) + + require.Equal(t, numAccounts, session.NumCallsGetAccountNonceAndBalance) + }) + + t.Run("numAccounts = 30_000, numTransactionsPerAccount = 1", func(t *testing.T) { + session := txcachemocks.NewSelectionSessionMock() + sessionWrapper := newVirtualSelectionSession(session, make(map[string]*virtualAccountRecord)) + + numAccounts := 30_000 + numTransactionsPerAccount := 1 + // See "detectSkippableSender()" and "detectSkippableTransaction()". + numCallsGetNoncePerTransaction := 2 + numCallsGetNoncePerAccount := numTransactionsPerAccount * numCallsGetNoncePerTransaction + + for i := 0; i < numAccounts; i++ { + session.SetNonce(randomAddresses.getItem(i), uint64(i)) + } + + sw.Start(t.Name()) + + for i := 0; i < numAccounts; i++ { + for j := 0; j < numCallsGetNoncePerAccount; j++ { + _, err := sessionWrapper.getRecord(randomAddresses.getItem(i)) + require.NoError(t, err) + } + } + + sw.Stop(t.Name()) + + require.Equal(t, numAccounts, session.NumCallsGetAccountNonceAndBalance) + }) + + for name, measurement := range sw.GetMeasurementsMap() { + fmt.Printf("%fs (%s)\n", measurement, name) + } + + // (1) + // Vendor ID: GenuineIntel + // Model name: 13th Gen Intel(R) Core(TM) i7-13700H + // CPU family: 6 + // Model: 186 + // Thread(s) per core: 2 + // Core(s) per socket: 14 + // + // VirtualSelectionSession operations should have a negligible (or small) impact on the performance! + // 0.011677s (TestBenchmarkVirtualSelectionSession_getNonce/_numAccounts_=_300,_numTransactionsPerAccount=_100) + // 0.016253s (TestBenchmarkVirtualSelectionSession_getNonce/_numAccounts_=_10_000,_numTransactionsPerAccount=_3) + // 0.028861s (TestBenchmarkVirtualSelectionSession_getNonce/_numAccounts_=_30_000,_numTransactionsPerAccount=_1) +} + +func Test_setChangeGuardianIfNeeded(t *testing.T) { + a := createTx([]byte("tx-1"), "alice", 42) + b := createTx([]byte("tx-2"), "alice", 43) + c := createTx([]byte("tx-3"), "alice", 44).withData([]byte("SetGuardian@newGuardian")).withGasLimit(100000) + + session := txcachemocks.NewSelectionSessionMock() + session.IsIncorrectlyGuardedCalled = func(tx data.TransactionHandler) bool { + return tx.GetNonce() == b.Tx.GetNonce() // for coverage + } + session.IsGuardedCalled = func(tx data.TransactionHandler) bool { + return tx.GetNonce() == b.Tx.GetNonce() || + tx.GetNonce() == c.Tx.GetNonce() + } + virtualSession := newVirtualSelectionSession(session, make(map[string]*virtualAccountRecord)) + + // regular tx, not guarded + virtualSession.setChangeGuardianIfNeeded(a.Tx) + virtualRecord1, err := virtualSession.getRecord([]byte("alice")) + require.NoError(t, err) + require.False(t, virtualRecord1.hasPendingChangeGuardian()) + + // incorrectly guarded + virtualSession.setChangeGuardianIfNeeded(b.Tx) + virtualRecord1, err = virtualSession.getRecord([]byte("alice")) + require.NoError(t, err) + require.False(t, virtualRecord1.hasPendingChangeGuardian()) + + // correctly guarded + virtualSession.setChangeGuardianIfNeeded(c.Tx) + virtualRecord1, err = virtualSession.getRecord([]byte("alice")) + require.NoError(t, err) + require.True(t, virtualRecord1.hasPendingChangeGuardian()) +} diff --git a/txcache/virtualSessionComputer.go b/txcache/virtualSessionComputer.go new file mode 100644 index 00000000000..6afd726e662 --- /dev/null +++ b/txcache/virtualSessionComputer.go @@ -0,0 +1,159 @@ +package txcache + +import ( + "math/big" + + "github.com/multiversx/mx-chain-core-go/core" +) + +type virtualSessionComputer struct { + session SelectionSession + virtualAccountsByAddress map[string]*virtualAccountRecord +} + +func newVirtualSessionComputer(session SelectionSession) *virtualSessionComputer { + return &virtualSessionComputer{ + session: session, + virtualAccountsByAddress: make(map[string]*virtualAccountRecord), + } +} + +// createVirtualSelectionSession iterates over the global breadcrumbs of the selection tracker. +// If the global breadcrumb of an account is continuous with the session nonce, +// the virtual record of that account is created or updated. +// NOTE: The createVirtualSelectionSession method should receive a deep copy of the globalAccountBreadcrumbs because it mutates them. +func (computer *virtualSessionComputer) createVirtualSelectionSession( + globalAccountBreadcrumbs map[string]*globalAccountBreadcrumb, +) (*virtualSelectionSession, error) { + err := computer.handleGlobalAccountBreadcrumbs(globalAccountBreadcrumbs) + if err != nil { + return nil, err + } + + virtualSession := newVirtualSelectionSession(computer.session, computer.virtualAccountsByAddress) + log.Debug("virtualSessionComputer.createVirtualSelectionSession", + "num of global accounts breadcrumbs", len(globalAccountBreadcrumbs), + "num of actual virtual records", len(virtualSession.virtualAccountsByAddress), + ) + return virtualSession, nil +} + +// handleGlobalAccountBreadcrumbs iterates over each global account breadcrumb, verifies the continuity with the session nonce +// and transforms each global account breadcrumb into a virtual record. +func (computer *virtualSessionComputer) handleGlobalAccountBreadcrumbs( + globalAccountBreadcrumbs map[string]*globalAccountBreadcrumb, +) error { + for address, globalBreadcrumb := range globalAccountBreadcrumbs { + accountNonce, accountBalance, _, err := computer.session.GetAccountNonceAndBalance([]byte(address)) + if err != nil { + log.Debug("virtualSessionComputer.handleGlobalAccountBreadcrumbs", + "err", err, + "address", address) + return err + } + + if !globalBreadcrumb.isContinuousWithSessionNonce(accountNonce) { + log.Debug("virtualSessionComputer.handleGlobalAccountBreadcrumbs global breadcrumb not continuous with session nonce, creating blocked record", + "address", address, + "accountNonce", accountNonce, + "breadcrumb nonce", globalBreadcrumb.firstNonce, + ) + + err = computer.createBlockedVirtualRecord(address, accountBalance, globalBreadcrumb) + if err != nil { + return err + } + continue + } + + err = computer.fromGlobalBreadcrumbToVirtualRecord(address, accountNonce, accountBalance, globalBreadcrumb) + if err != nil { + return err + } + } + + return nil +} + +// fromGlobalBreadcrumbToVirtualRecord transforms a global account breadcrumb simply by: +// initializing the initialNonce of the virtual record with the latestNonce + 1 +// copying the consumed balance in the initialBalance of the virtual record. +// It also saves the created virtual record into the map of the virtualSessionComputer. +// Each sender has a unique global breadcrumb which contains the necessary information to create a virtual record. +// If an account already exists in the map of virtual records, its virtual record doesn't need to be updated. +func (computer *virtualSessionComputer) fromGlobalBreadcrumbToVirtualRecord( + address string, + accountNonce uint64, + accountBalance *big.Int, + globalBreadcrumb *globalAccountBreadcrumb, +) error { + _, ok := computer.virtualAccountsByAddress[address] + if ok { + return nil + } + + initialBalance := accountBalance + initialNonce := core.OptionalUint64{ + Value: accountNonce, + HasValue: true, + } + + if globalBreadcrumb.isUser() { + initialNonce = core.OptionalUint64{ + Value: globalBreadcrumb.lastNonce.Value + 1, + HasValue: true, + } + } + + // We initialize the virtual record with the session nonce because an account might be only a relayer in the proposed blocks. + // Without this initialization, the initialNonce remains without a value. + // On the selection side, a virtual account record that has an initial nonce without a value + // will lead to an incorrect skip of a specific tx where the account is a sender. + record, err := newVirtualAccountRecord(initialNonce, initialBalance) + if err != nil { + log.Debug("virtualSessionComputer.fromGlobalBreadcrumbToVirtualRecord", + "err", err, + "address", address, + "accountBalance", accountBalance, + ) + return err + } + + record.accumulateConsumedBalance(globalBreadcrumb.consumedBalance) + computer.virtualAccountsByAddress[address] = record + return nil +} + +// createBlockedVirtualRecord creates a virtual record with an unset nonce (blocked) for an account +// whose global breadcrumb is discontinuous with the session nonce. +// This prevents the selection logic from selecting transactions for this sender, +// because detectSkippableSender will see errNonceNotSet and skip the sender entirely. +// The consumed balance from the global breadcrumb is still preserved for correct relayer balance tracking. +func (computer *virtualSessionComputer) createBlockedVirtualRecord( + address string, + accountBalance *big.Int, + globalBreadcrumb *globalAccountBreadcrumb, +) error { + _, ok := computer.virtualAccountsByAddress[address] + if ok { + return nil + } + + blockedNonce := core.OptionalUint64{ + HasValue: false, + } + + record, err := newVirtualAccountRecord(blockedNonce, accountBalance) + if err != nil { + log.Debug("virtualSessionComputer.createBlockedVirtualRecord", + "err", err, + "address", address, + "accountBalance", accountBalance, + ) + return err + } + + record.accumulateConsumedBalance(globalBreadcrumb.consumedBalance) + computer.virtualAccountsByAddress[address] = record + return nil +} diff --git a/txcache/virtualSessionComputer_test.go b/txcache/virtualSessionComputer_test.go new file mode 100644 index 00000000000..acdcb778b6a --- /dev/null +++ b/txcache/virtualSessionComputer_test.go @@ -0,0 +1,356 @@ +package txcache + +import ( + "errors" + "math/big" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/testscommon/txcachemocks" +) + +func Test_fromBreadcrumbToVirtualRecord(t *testing.T) { + t.Parallel() + + t.Run("virtual record of sender breadcrumb", func(t *testing.T) { + t.Parallel() + + address := "bob" + sessionNonce := uint64(1) + accountBalance := big.NewInt(2) + + breadcrumbBob := globalAccountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 1, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 2, + HasValue: true, + }, + consumedBalance: big.NewInt(3), + } + + expectedVirtualRecord := &virtualAccountRecord{ + initialNonce: core.OptionalUint64{ + Value: 3, + HasValue: true, + }, + virtualBalance: &virtualAccountBalance{ + initialBalance: big.NewInt(2), + consumedBalance: big.NewInt(3), + }, + } + + computer := newVirtualSessionComputer(nil) + err := computer.fromGlobalBreadcrumbToVirtualRecord(address, sessionNonce, accountBalance, &breadcrumbBob) + require.Nil(t, err) + + actualVirtualRecord, ok := computer.virtualAccountsByAddress[address] + require.True(t, ok) + require.Equal(t, expectedVirtualRecord, actualVirtualRecord) + }) + + t.Run("virtual record of bob relayer", func(t *testing.T) { + t.Parallel() + + address := "bob" + sessionNonce := uint64(1) + accountBalance := big.NewInt(2) + + breadcrumbBob := globalAccountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 0, + HasValue: false, + }, + lastNonce: core.OptionalUint64{ + Value: 0, + HasValue: false, + }, + consumedBalance: big.NewInt(5), + } + + expectedVirtualRecord := &virtualAccountRecord{ + initialNonce: core.OptionalUint64{ + Value: 1, + HasValue: true, + }, + virtualBalance: &virtualAccountBalance{ + initialBalance: big.NewInt(2), + consumedBalance: big.NewInt(5), + }, + } + + computer := newVirtualSessionComputer(nil) + err := computer.fromGlobalBreadcrumbToVirtualRecord(address, sessionNonce, accountBalance, &breadcrumbBob) + require.Nil(t, err) + + actualVirtualRecord, ok := computer.virtualAccountsByAddress[address] + require.True(t, ok) + require.Equal(t, expectedVirtualRecord, actualVirtualRecord) + }) +} + +func Test_createVirtualSelectionSession(t *testing.T) { + t.Parallel() + + t.Run("should create blocked record for carol because it has discontinuous nonce with session nonce", func(t *testing.T) { + sessionMock := txcachemocks.SelectionSessionMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 2, big.NewInt(2), true, nil + }, + } + + gabc := newGlobalAccountBreadcrumbsCompiler() + + breadcrumbs1 := map[string]*accountBreadcrumb{ + "alice": { + firstNonce: core.OptionalUint64{ + Value: 2, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 2, + HasValue: true, + }, + consumedBalance: big.NewInt(2), + }, + "bob": { + firstNonce: core.OptionalUint64{ + Value: 2, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 3, + HasValue: true, + }, + consumedBalance: big.NewInt(3), + }, + } + + breadcrumb2 := map[string]*accountBreadcrumb{ + // carol has discontinuous nonce (firstNonce=10 != sessionNonce=2), + // so a blocked virtual record is created (initialNonce.HasValue=false) + "carol": { + firstNonce: core.OptionalUint64{ + Value: 10, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 11, + HasValue: true, + }, + consumedBalance: big.NewInt(2), + }, + "bob": { + firstNonce: core.OptionalUint64{ + Value: 4, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 5, + HasValue: true, + }, + consumedBalance: big.NewInt(3), + }, + } + + trackedBlocks := []*trackedBlock{ + { + breadcrumbsByAddress: breadcrumbs1, + }, + { + breadcrumbsByAddress: breadcrumb2, + }, + } + + expectedVirtualAccounts := map[string]*virtualAccountRecord{ + "alice": { + initialNonce: core.OptionalUint64{ + Value: 3, + HasValue: true, + }, + virtualBalance: &virtualAccountBalance{ + initialBalance: big.NewInt(2), + consumedBalance: big.NewInt(2), + }, + }, + "bob": { + initialNonce: core.OptionalUint64{ + Value: 6, + HasValue: true, + }, + virtualBalance: &virtualAccountBalance{ + initialBalance: big.NewInt(2), + consumedBalance: big.NewInt(6), + }, + }, + // carol gets a blocked virtual record: nonce not set, but consumed balance preserved + "carol": { + initialNonce: core.OptionalUint64{ + Value: 0, + HasValue: false, + }, + virtualBalance: &virtualAccountBalance{ + initialBalance: big.NewInt(2), + consumedBalance: big.NewInt(2), + }, + }, + } + + gabc.updateOnAddedBlock(trackedBlocks[0]) + gabc.updateOnAddedBlock(trackedBlocks[1]) + + computer := newVirtualSessionComputer(&sessionMock) + _, err := computer.createVirtualSelectionSession(gabc.getGlobalBreadcrumbs()) + require.Nil(t, err) + require.Equal(t, expectedVirtualAccounts, computer.virtualAccountsByAddress) + }) + + t.Run("should return error from selection session", func(t *testing.T) { + var expectedErr = errors.New("expected err") + sessionMock := txcachemocks.SelectionSessionMock{ + GetAccountNonceAndBalanceCalled: func(address []byte) (uint64, *big.Int, bool, error) { + return 0, big.NewInt(0), true, expectedErr + }, + } + + gabc := newGlobalAccountBreadcrumbsCompiler() + + breadcrumbs1 := map[string]*accountBreadcrumb{ + "alice": { + firstNonce: core.OptionalUint64{ + Value: 2, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 2, + HasValue: true, + }, + consumedBalance: big.NewInt(2), + }, + "bob": { + firstNonce: core.OptionalUint64{ + Value: 2, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 3, + HasValue: true, + }, + consumedBalance: big.NewInt(3), + }, + } + + breadcrumb2 := map[string]*accountBreadcrumb{ + // carol's virtual record will not be saved because the firstNonce is != session nonce + "carol": { + firstNonce: core.OptionalUint64{ + Value: 10, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 11, + HasValue: true, + }, + consumedBalance: big.NewInt(2), + }, + "bob": { + firstNonce: core.OptionalUint64{ + Value: 4, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 5, + HasValue: true, + }, + consumedBalance: big.NewInt(3), + }, + } + + trackedBlocks := []*trackedBlock{ + { + breadcrumbsByAddress: breadcrumbs1, + }, + { + breadcrumbsByAddress: breadcrumb2, + }, + } + + gabc.updateOnAddedBlock(trackedBlocks[0]) + gabc.updateOnAddedBlock(trackedBlocks[1]) + + computer := newVirtualSessionComputer(&sessionMock) + _, err := computer.createVirtualSelectionSession(gabc.getGlobalBreadcrumbs()) + require.Equal(t, expectedErr, err) + }) +} + +func Test_createBlockedVirtualRecord(t *testing.T) { + t.Parallel() + + t.Run("should create blocked record with unset nonce", func(t *testing.T) { + t.Parallel() + + address := "alice" + accountBalance := big.NewInt(100) + globalBreadcrumb := &globalAccountBreadcrumb{ + firstNonce: core.OptionalUint64{ + Value: 52, + HasValue: true, + }, + lastNonce: core.OptionalUint64{ + Value: 55, + HasValue: true, + }, + consumedBalance: big.NewInt(30), + } + + computer := newVirtualSessionComputer(nil) + err := computer.createBlockedVirtualRecord(address, accountBalance, globalBreadcrumb) + require.Nil(t, err) + + record, ok := computer.virtualAccountsByAddress[address] + require.True(t, ok) + + // Nonce should NOT be set (blocked) + require.False(t, record.initialNonce.HasValue) + + // getInitialNonce should return error + _, err = record.getInitialNonce() + require.Equal(t, errNonceNotSet, err) + + // Balance should be preserved + require.Equal(t, big.NewInt(100), record.getInitialBalance()) + require.Equal(t, big.NewInt(30), record.getConsumedBalance()) + }) + + t.Run("should not overwrite existing record", func(t *testing.T) { + t.Parallel() + + address := "alice" + accountBalance := big.NewInt(100) + globalBreadcrumb := &globalAccountBreadcrumb{ + firstNonce: core.OptionalUint64{Value: 52, HasValue: true}, + lastNonce: core.OptionalUint64{Value: 55, HasValue: true}, + consumedBalance: big.NewInt(30), + } + + computer := newVirtualSessionComputer(nil) + + // Add an existing record first + existingRecord, err := newVirtualAccountRecord(core.OptionalUint64{Value: 10, HasValue: true}, big.NewInt(50)) + require.Nil(t, err) + computer.virtualAccountsByAddress[address] = existingRecord + + // Try to create a blocked record - should not overwrite + err = computer.createBlockedVirtualRecord(address, accountBalance, globalBreadcrumb) + require.Nil(t, err) + + record := computer.virtualAccountsByAddress[address] + require.True(t, record.initialNonce.HasValue) + require.Equal(t, uint64(10), record.initialNonce.Value) + }) +} diff --git a/txcache/wrappedTransaction.go b/txcache/wrappedTransaction.go new file mode 100644 index 00000000000..9d84d4fd308 --- /dev/null +++ b/txcache/wrappedTransaction.go @@ -0,0 +1,80 @@ +package txcache + +import ( + "bytes" + "math/big" + "time" + + "github.com/multiversx/mx-chain-core-go/data" +) + +// bunchOfTransactions is a slice of WrappedTransaction pointers +type bunchOfTransactions []*WrappedTransaction + +// WrappedTransaction contains a transaction, its hash and extra information +type WrappedTransaction struct { + Tx data.TransactionHandler + TxHash []byte + SenderShardID uint32 + ReceiverShardID uint32 + Size int64 + ReceivedAt time.Time + + // These fields are only set within "precomputeFields". + // We don't need to protect them with a mutex, since "precomputeFields" is called only once for each transaction. + // Additional note: "WrappedTransaction" objects are created by the Node, in dataRetriever/txpool/shardedTxPool.go. + Fee *big.Int + PricePerUnit uint64 + TransferredValue *big.Int + FeePayer []byte +} + +// precomputeFields computes (and caches) the (average) price per gas unit. +func (wrappedTx *WrappedTransaction) precomputeFields(host MempoolHost) { + wrappedTx.Fee = host.ComputeTxFee(wrappedTx.Tx) + + gasLimit := wrappedTx.Tx.GetGasLimit() + if gasLimit != 0 { + pricePerUnit := big.NewInt(0) + _ = pricePerUnit.Div(wrappedTx.Fee, big.NewInt(int64(gasLimit))) + // The operation below can't result in an overflow. + // PricePerUnit will always be uint64 because of how the fee is computed: + // Two uint64s which are multiplied and one of them being the actual gasLimit which is used later for the div. + wrappedTx.PricePerUnit = pricePerUnit.Uint64() + } + + wrappedTx.TransferredValue = host.GetTransferredValue(wrappedTx.Tx) + wrappedTx.FeePayer = wrappedTx.decideFeePayer() +} + +func (wrappedTx *WrappedTransaction) decideFeePayer() []byte { + asRelayed, ok := wrappedTx.Tx.(data.RelayedTransactionHandler) + if ok && len(asRelayed.GetRelayerAddr()) > 0 { + return asRelayed.GetRelayerAddr() + } + + return wrappedTx.Tx.GetSndAddr() +} + +// Equality is out of scope (not possible in our case). +func (wrappedTx *WrappedTransaction) isTransactionMoreValuableForNetwork(otherTransaction *WrappedTransaction) bool { + // First, compare by PPU (higher PPU is better). + if wrappedTx.PricePerUnit != otherTransaction.PricePerUnit { + return wrappedTx.PricePerUnit > otherTransaction.PricePerUnit + } + + // If PPU is the same, compare by gas limit (higher gas limit is better, promoting less "execution fragmentation"). + gasLimit := wrappedTx.Tx.GetGasLimit() + gasLimitOther := otherTransaction.Tx.GetGasLimit() + + if gasLimit != gasLimitOther { + return gasLimit > gasLimitOther + } + + // In the end, compare by transaction hash + return bytes.Compare(wrappedTx.TxHash, otherTransaction.TxHash) < 0 +} + +func (wrappedTx *WrappedTransaction) isFeePayerSameAsSender() bool { + return bytes.Equal(wrappedTx.FeePayer, wrappedTx.Tx.GetSndAddr()) +} diff --git a/txcache/wrappedTransaction_test.go b/txcache/wrappedTransaction_test.go new file mode 100644 index 00000000000..12a8c517dd2 --- /dev/null +++ b/txcache/wrappedTransaction_test.go @@ -0,0 +1,142 @@ +package txcache + +import ( + "math/big" + "testing" + + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/testscommon/txcachemocks" + "github.com/stretchr/testify/require" +) + +func TestWrappedTransaction_precomputeFields(t *testing.T) { + t.Run("only move balance gas limit", func(t *testing.T) { + host := txcachemocks.NewMempoolHostMock() + + tx := createTx([]byte("a"), "a", 1).withValue(oneQuintillionBig).withDataLength(1).withGasLimit(51500).withGasPrice(oneBillion) + tx.precomputeFields(host) + + require.Equal(t, "51500000000000", tx.Fee.String()) + require.Equal(t, oneBillion, int(tx.PricePerUnit)) + require.Equal(t, "1000000000000000000", tx.TransferredValue.String()) + require.Equal(t, []byte("a"), tx.FeePayer) + }) + + t.Run("move balance gas limit and execution gas limit (a)", func(t *testing.T) { + host := txcachemocks.NewMempoolHostMock() + + tx := createTx([]byte("b"), "b", 1).withDataLength(1).withGasLimit(51501).withGasPrice(oneBillion) + tx.precomputeFields(host) + + require.Equal(t, "51500010000000", tx.Fee.String()) + require.Equal(t, 999_980_777, int(tx.PricePerUnit)) + require.Equal(t, []byte("b"), tx.FeePayer) + }) + + t.Run("move balance gas limit and execution gas limit (b)", func(t *testing.T) { + host := txcachemocks.NewMempoolHostMock() + + tx := createTx([]byte("c"), "c", 1).withDataLength(1).withGasLimit(oneMilion).withGasPrice(oneBillion) + tx.precomputeFields(host) + + actualFee := 51500*oneBillion + (oneMilion-51500)*oneBillion/100 + require.Equal(t, "60985000000000", tx.Fee.String()) + require.Equal(t, 60_985_000_000_000, actualFee) + require.Equal(t, actualFee/oneMilion, int(tx.PricePerUnit)) + require.Equal(t, []byte("c"), tx.FeePayer) + }) + + t.Run("with guardian", func(t *testing.T) { + host := txcachemocks.NewMempoolHostMock() + + tx := createTx([]byte("a"), "a", 1).withValue(oneQuintillionBig) + tx.precomputeFields(host) + + require.Equal(t, "50000000000000", tx.Fee.String()) + require.Equal(t, oneBillion, int(tx.PricePerUnit)) + require.Equal(t, "1000000000000000000", tx.TransferredValue.String()) + require.Equal(t, []byte("a"), tx.FeePayer) + }) + + t.Run("with nil transferred value", func(t *testing.T) { + host := txcachemocks.NewMempoolHostMock() + + tx := createTx([]byte("a"), "a", 1) + tx.precomputeFields(host) + + require.Nil(t, tx.TransferredValue) + require.Equal(t, []byte("a"), tx.FeePayer) + }) + + t.Run("queries host", func(t *testing.T) { + host := txcachemocks.NewMempoolHostMock() + host.ComputeTxFeeCalled = func(_ data.TransactionWithFeeHandler) *big.Int { + return big.NewInt(42) + } + host.GetTransferredValueCalled = func(_ data.TransactionHandler) *big.Int { + return big.NewInt(43) + } + + tx := createTx([]byte("a"), "a", 1).withGasLimit(50_000) + tx.precomputeFields(host) + + require.Equal(t, "42", tx.Fee.String()) + require.Equal(t, "43", tx.TransferredValue.String()) + }) +} + +func TestWrappedTransaction_decideFeePayer(t *testing.T) { + host := txcachemocks.NewMempoolHostMock() + + t.Run("when sender is fee payer", func(t *testing.T) { + tx := createTx([]byte("a"), "a", 1) + tx.precomputeFields(host) + + require.Nil(t, tx.TransferredValue) + require.Equal(t, []byte("a"), tx.FeePayer) + }) + + t.Run("when relayer is fee payer", func(t *testing.T) { + tx := createTx([]byte("a"), "a", 1).withRelayer([]byte("b")).withGasLimit(100_000) + tx.precomputeFields(host) + + require.Nil(t, tx.TransferredValue) + require.Equal(t, []byte("b"), tx.FeePayer) + }) +} + +func TestWrappedTransaction_isTransactionMoreValuableForNetwork(t *testing.T) { + host := txcachemocks.NewMempoolHostMock() + + t.Run("decide by price per unit", func(t *testing.T) { + a := createTx([]byte("a-1"), "a", 1).withDataLength(1).withGasLimit(51500).withGasPrice(oneBillion) + a.precomputeFields(host) + + b := createTx([]byte("b-1"), "b", 1).withDataLength(1).withGasLimit(51501).withGasPrice(oneBillion) + b.precomputeFields(host) + + require.True(t, a.isTransactionMoreValuableForNetwork(b)) + }) + + t.Run("decide by gas limit (set them up to have the same PPU)", func(t *testing.T) { + a := createTx([]byte("a-7"), "a", 7).withDataLength(30).withGasLimit(95_000).withGasPrice(oneBillion) + a.precomputeFields(host) + + b := createTx([]byte("b-7"), "b", 7).withDataLength(60).withGasLimit(140_000).withGasPrice(oneBillion) + b.precomputeFields(host) + + require.Equal(t, a.PricePerUnit, b.PricePerUnit) + require.True(t, b.isTransactionMoreValuableForNetwork(a)) + }) + + t.Run("decide by transaction hash (set them up to have the same PPU and gas limit)", func(t *testing.T) { + a := createTx([]byte("a-7"), "a", 7) + a.precomputeFields(host) + + b := createTx([]byte("b-7"), "b", 7) + b.precomputeFields(host) + + require.Equal(t, a.PricePerUnit, b.PricePerUnit) + require.True(t, a.isTransactionMoreValuableForNetwork(b)) + }) +} diff --git a/update/common.go b/update/common.go index 1880231b8cf..b0637684fd0 100644 --- a/update/common.go +++ b/update/common.go @@ -8,6 +8,8 @@ import ( "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-logger-go" + + "github.com/multiversx/mx-chain-go/common" ) var log = logger.GetOrCreate("update") @@ -131,7 +133,12 @@ func getAllMiniBlocksWithDst(metaBlock data.MetaHeaderHandler, destShardID uint3 } } - miniBlockHeaderHandlers := metaBlock.GetMiniBlockHeaderHandlers() + miniBlockHeaderHandlers, err := common.GetMiniBlockHeadersFromExecResult(metaBlock) + if err != nil { + log.Error("GetMiniBlockHeadersFromExecResult failed", "error", err) + return mbHdrs + } + for i, mbHdr := range miniBlockHeaderHandlers { if mbHdr.GetReceiverShardID() == destShardID && mbHdr.GetSenderShardID() != destShardID { mbHdrs = append(mbHdrs, miniBlockHeaderHandlers[i]) diff --git a/update/factory/dataTrieFactory.go b/update/factory/dataTrieFactory.go index dcd83da1bd7..10483099780 100644 --- a/update/factory/dataTrieFactory.go +++ b/update/factory/dataTrieFactory.go @@ -14,7 +14,6 @@ import ( "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/sharding" "github.com/multiversx/mx-chain-go/state" - "github.com/multiversx/mx-chain-go/storage/factory" storageFactory "github.com/multiversx/mx-chain-go/storage/factory" "github.com/multiversx/mx-chain-go/storage/storageunit" "github.com/multiversx/mx-chain-go/trie" @@ -67,7 +66,7 @@ func NewDataTrieFactory(args ArgsNewDataTrieFactory) (*dataTrieFactory, error) { dbConfig := storageFactory.GetDBFromConfig(args.StorageConfig.DB) dbConfig.FilePath = path.Join(args.SyncFolder, args.StorageConfig.DB.FilePath) - persisterFactory, err := factory.NewPersisterFactory(args.StorageConfig.DB) + persisterFactory, err := storageFactory.NewPersisterFactory(args.StorageConfig.DB) if err != nil { return nil, err } diff --git a/update/factory/exportHandlerFactory.go b/update/factory/exportHandlerFactory.go index 0cda7a5d2e0..44bb0904c86 100644 --- a/update/factory/exportHandlerFactory.go +++ b/update/factory/exportHandlerFactory.go @@ -64,13 +64,13 @@ type ArgsExporter struct { HeaderIntegrityVerifier process.HeaderIntegrityVerifier ValidityAttester process.ValidityAttester RoundHandler process.RoundHandler - InterceptorDebugConfig config.InterceptorResolverDebugConfig MaxHardCapForMissingNodes int NumConcurrentTrieSyncers int TrieSyncerVersion int CheckNodesOnDisk bool NodeOperationMode common.NodeOperation InterceptedDataVerifierFactory process.InterceptedDataVerifierFactory + Config config.Config } type exportHandlerFactory struct { @@ -104,13 +104,13 @@ type exportHandlerFactory struct { resolverContainer dataRetriever.ResolversContainer requestersContainer dataRetriever.RequestersContainer roundHandler process.RoundHandler - interceptorDebugConfig config.InterceptorResolverDebugConfig maxHardCapForMissingNodes int numConcurrentTrieSyncers int trieSyncerVersion int checkNodesOnDisk bool nodeOperationMode common.NodeOperation interceptedDataVerifierFactory process.InterceptedDataVerifierFactory + config config.Config } // NewExportHandlerFactory creates an exporter factory @@ -262,7 +262,6 @@ func NewExportHandlerFactory(args ArgsExporter) (*exportHandlerFactory, error) { validityAttester: args.ValidityAttester, maxTrieLevelInMemory: args.MaxTrieLevelInMemory, roundHandler: args.RoundHandler, - interceptorDebugConfig: args.InterceptorDebugConfig, maxHardCapForMissingNodes: args.MaxHardCapForMissingNodes, numConcurrentTrieSyncers: args.NumConcurrentTrieSyncers, trieSyncerVersion: args.TrieSyncerVersion, @@ -270,6 +269,7 @@ func NewExportHandlerFactory(args ArgsExporter) (*exportHandlerFactory, error) { statusCoreComponents: args.StatusCoreComponents, nodeOperationMode: args.NodeOperationMode, interceptedDataVerifierFactory: args.InterceptedDataVerifierFactory, + config: args.Config, } return e, nil @@ -283,7 +283,7 @@ func (e *exportHandlerFactory) Create() (update.ExportHandler, error) { } // TODO reuse the debugger when the one used for regular resolvers & interceptors will be moved inside the status components - debugger, errNotCritical := factory.NewInterceptorDebuggerFactory(e.interceptorDebugConfig) + debugger, errNotCritical := factory.NewInterceptorDebuggerFactory(e.config.Debug.InterceptorResolver, e.coreComponents.SyncTimer()) if errNotCritical != nil { log.Warn("error creating hardfork debugger", "error", errNotCritical) } @@ -313,6 +313,7 @@ func (e *exportHandlerFactory) Create() (update.ExportHandler, error) { RoundHandler: e.roundHandler, AppStatusHandler: e.statusCoreComponents.AppStatusHandler(), EnableEpochsHandler: e.coreComponents.EnableEpochsHandler(), + CommonConfigsHandler: e.coreComponents.CommonConfigsHandler(), } epochHandler, err := shardchain.NewEpochStartTrigger(&argsEpochTrigger) if err != nil { @@ -593,6 +594,7 @@ func (e *exportHandlerFactory) createInterceptors() error { AntifloodHandler: e.networkComponents.InputAntiFloodHandler(), NodeOperationMode: e.nodeOperationMode, InterceptedDataVerifierFactory: e.interceptedDataVerifierFactory, + Config: e.config, } fullSyncInterceptors, err := NewFullSyncInterceptorsContainerFactory(argsInterceptors) if err != nil { diff --git a/update/factory/fullSyncInterceptors.go b/update/factory/fullSyncInterceptors.go index c7d005e94fb..be428c6f79d 100644 --- a/update/factory/fullSyncInterceptors.go +++ b/update/factory/fullSyncInterceptors.go @@ -2,11 +2,14 @@ package factory import ( "fmt" + "time" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/core/throttler" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/storage/cache" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/dataRetriever" @@ -50,6 +53,7 @@ type fullSyncInterceptorsContainerFactory struct { preferredPeersHolder update.PreferredPeersHolderHandler nodeOperationMode common.NodeOperation interceptedDataVerifierFactory process.InterceptedDataVerifierFactory + config config.Config } // ArgsNewFullSyncInterceptorsContainerFactory holds the arguments needed for fullSyncInterceptorsContainerFactory @@ -78,6 +82,7 @@ type ArgsNewFullSyncInterceptorsContainerFactory struct { AntifloodHandler process.P2PAntifloodHandler NodeOperationMode common.NodeOperation InterceptedDataVerifierFactory process.InterceptedDataVerifierFactory + Config config.Config } // NewFullSyncInterceptorsContainerFactory is responsible for creating a new interceptors factory object @@ -372,6 +377,7 @@ func (ficf *fullSyncInterceptorsContainerFactory) createOneShardHeaderIntercepto WhiteListRequest: ficf.whiteListHandler, CurrentPeerId: ficf.mainMessenger.ID(), InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: ficf.argInterceptorFactory.CryptoComponents.ManagedPeersHolder(), }, ) if err != nil { @@ -395,7 +401,8 @@ func (ficf *fullSyncInterceptorsContainerFactory) generateUnsignedTxsInterceptor continue } - interceptor, err := ficf.createOneUnsignedTxInterceptor(identifierScr) + isCrossShard := idx != ficf.shardCoordinator.SelfId() + interceptor, err := ficf.createOneUnsignedTxInterceptor(identifierScr, isCrossShard) if err != nil { return err } @@ -406,7 +413,8 @@ func (ficf *fullSyncInterceptorsContainerFactory) generateUnsignedTxsInterceptor identifierScr := factory.UnsignedTransactionTopic + shardC.CommunicationIdentifier(core.MetachainShardId) if !ficf.checkIfInterceptorExists(identifierScr) { - interceptor, err := ficf.createOneUnsignedTxInterceptor(identifierScr) + isCrossShard := core.MetachainShardId != ficf.shardCoordinator.SelfId() + interceptor, err := ficf.createOneUnsignedTxInterceptor(identifierScr, isCrossShard) if err != nil { return err } @@ -514,7 +522,8 @@ func (ficf *fullSyncInterceptorsContainerFactory) generateTxInterceptors() error continue } - interceptor, err := ficf.createOneTxInterceptor(identifierTx) + isCrossShard := idx != ficf.shardCoordinator.SelfId() + interceptor, err := ficf.createOneTxInterceptor(identifierTx, isCrossShard) if err != nil { return err } @@ -526,7 +535,8 @@ func (ficf *fullSyncInterceptorsContainerFactory) generateTxInterceptors() error // tx interceptor for metachain topic identifierTx := factory.TransactionTopic + shardC.CommunicationIdentifier(core.MetachainShardId) if !ficf.checkIfInterceptorExists(identifierTx) { - interceptor, err := ficf.createOneTxInterceptor(identifierTx) + isCrossShard := core.MetachainShardId != ficf.shardCoordinator.SelfId() + interceptor, err := ficf.createOneTxInterceptor(identifierTx, isCrossShard) if err != nil { return err } @@ -538,13 +548,14 @@ func (ficf *fullSyncInterceptorsContainerFactory) generateTxInterceptors() error return ficf.addInterceptorsToContainers(keys, interceptorSlice) } -func (ficf *fullSyncInterceptorsContainerFactory) createOneTxInterceptor(topic string) (process.Interceptor, error) { +func (ficf *fullSyncInterceptorsContainerFactory) createOneTxInterceptor(topic string, isCrossShard bool) (process.Interceptor, error) { txValidator, err := dataValidators.NewTxValidator( ficf.accounts, ficf.shardCoordinator, ficf.whiteListHandler, ficf.addressPubkeyConv, ficf.argInterceptorFactory.CoreComponents.TxVersionChecker(), + ficf.argInterceptorFactory.CoreComponents.EnableEpochsHandler(), ficf.maxTxNonceDeltaAllowed, ) if err != nil { @@ -552,8 +563,9 @@ func (ficf *fullSyncInterceptorsContainerFactory) createOneTxInterceptor(topic s } argProcessor := &processor.ArgTxInterceptorProcessor{ - ShardedDataCache: ficf.dataPool.Transactions(), - TxValidator: txValidator, + ShardedDataCache: ficf.dataPool.Transactions(), + TxValidator: txValidator, + DirectSentTransactionsCache: ficf.dataPool.DirectSentTransactions(), } txProcessor, err := processor.NewTxInterceptorProcessor(argProcessor) if err != nil { @@ -583,19 +595,28 @@ func (ficf *fullSyncInterceptorsContainerFactory) createOneTxInterceptor(topic s CurrentPeerId: ficf.mainMessenger.ID(), PreferredPeersHolder: ficf.preferredPeersHolder, InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: ficf.argInterceptorFactory.CryptoComponents.ManagedPeersHolder(), }, ) if err != nil { return nil, err } + if isCrossShard { + err = ficf.setUniqueChunksProcessor(interceptor) + if err != nil { + return nil, err + } + } + return ficf.createTopicAndAssignHandler(topic, interceptor, true) } -func (ficf *fullSyncInterceptorsContainerFactory) createOneUnsignedTxInterceptor(topic string) (process.Interceptor, error) { +func (ficf *fullSyncInterceptorsContainerFactory) createOneUnsignedTxInterceptor(topic string, isCrossShard bool) (process.Interceptor, error) { argProcessor := &processor.ArgTxInterceptorProcessor{ - ShardedDataCache: ficf.dataPool.UnsignedTransactions(), - TxValidator: dataValidators.NewDisabledTxValidator(), + ShardedDataCache: ficf.dataPool.UnsignedTransactions(), + TxValidator: dataValidators.NewDisabledTxValidator(), + DirectSentTransactionsCache: ficf.dataPool.DirectSentTransactions(), } txProcessor, err := processor.NewTxInterceptorProcessor(argProcessor) if err != nil { @@ -625,19 +646,28 @@ func (ficf *fullSyncInterceptorsContainerFactory) createOneUnsignedTxInterceptor CurrentPeerId: ficf.mainMessenger.ID(), PreferredPeersHolder: ficf.preferredPeersHolder, InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: ficf.argInterceptorFactory.CryptoComponents.ManagedPeersHolder(), }, ) if err != nil { return nil, err } + if isCrossShard { + err = ficf.setUniqueChunksProcessor(interceptor) + if err != nil { + return nil, err + } + } + return ficf.createTopicAndAssignHandler(topic, interceptor, true) } -func (ficf *fullSyncInterceptorsContainerFactory) createOneRewardTxInterceptor(topic string) (process.Interceptor, error) { +func (ficf *fullSyncInterceptorsContainerFactory) createOneRewardTxInterceptor(topic string, isCrossShard bool) (process.Interceptor, error) { argProcessor := &processor.ArgTxInterceptorProcessor{ - ShardedDataCache: ficf.dataPool.RewardTransactions(), - TxValidator: dataValidators.NewDisabledTxValidator(), + ShardedDataCache: ficf.dataPool.RewardTransactions(), + TxValidator: dataValidators.NewDisabledTxValidator(), + DirectSentTransactionsCache: ficf.dataPool.DirectSentTransactions(), } txProcessor, err := processor.NewTxInterceptorProcessor(argProcessor) if err != nil { @@ -667,15 +697,41 @@ func (ficf *fullSyncInterceptorsContainerFactory) createOneRewardTxInterceptor(t CurrentPeerId: ficf.mainMessenger.ID(), PreferredPeersHolder: ficf.preferredPeersHolder, InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: ficf.argInterceptorFactory.CryptoComponents.ManagedPeersHolder(), }, ) if err != nil { return nil, err } + if isCrossShard { + err = ficf.setUniqueChunksProcessor(interceptor) + if err != nil { + return nil, err + } + } + return ficf.createTopicAndAssignHandler(topic, interceptor, true) } +func (ficf *fullSyncInterceptorsContainerFactory) setUniqueChunksProcessor(interceptor *interceptors.MultiDataInterceptor) error { + internalMarshaller := ficf.argInterceptorFactory.CoreComponents.InternalMarshalizer() + chunksCache, err := cache.NewTimeCacher(cache.ArgTimeCacher{ + DefaultSpan: time.Duration(ficf.config.InterceptedDataVerifier.CacheSpanInSec) * time.Second, + CacheExpiry: time.Duration(ficf.config.InterceptedDataVerifier.CacheExpiryInSec) * time.Second, + }) + if err != nil { + return err + } + + chunkProcessor, err := processor.NewUniqueChunksProcessor(chunksCache, internalMarshaller, ficf.argInterceptorFactory.CoreComponents.Hasher()) + if err != nil { + return err + } + + return interceptor.SetChunkProcessor(chunkProcessor) +} + func (ficf *fullSyncInterceptorsContainerFactory) generateMiniBlocksInterceptors() error { shardC := ficf.shardCoordinator numShards := shardC.NumberOfShards() @@ -745,6 +801,7 @@ func (ficf *fullSyncInterceptorsContainerFactory) createOneMiniBlocksInterceptor CurrentPeerId: ficf.mainMessenger.ID(), PreferredPeersHolder: ficf.preferredPeersHolder, InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: ficf.argInterceptorFactory.CryptoComponents.ManagedPeersHolder(), }, ) if err != nil { @@ -796,6 +853,7 @@ func (ficf *fullSyncInterceptorsContainerFactory) generateMetachainHeaderInterce CurrentPeerId: ficf.mainMessenger.ID(), PreferredPeersHolder: ficf.preferredPeersHolder, InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: ficf.argInterceptorFactory.CryptoComponents.ManagedPeersHolder(), }, ) if err != nil { @@ -839,6 +897,7 @@ func (ficf *fullSyncInterceptorsContainerFactory) createOneTrieNodesInterceptor( CurrentPeerId: ficf.mainMessenger.ID(), PreferredPeersHolder: ficf.preferredPeersHolder, InterceptedDataVerifier: interceptedDataVerifier, + ManagedPeersHolder: ficf.argInterceptorFactory.CryptoComponents.ManagedPeersHolder(), }, ) if err != nil { @@ -865,8 +924,10 @@ func (ficf *fullSyncInterceptorsContainerFactory) generateRewardTxInterceptors() return nil } + isCrossShard := idx != ficf.shardCoordinator.SelfId() + var interceptor process.Interceptor - interceptor, err = ficf.createOneRewardTxInterceptor(identifierScr) + interceptor, err = ficf.createOneRewardTxInterceptor(identifierScr, isCrossShard) if err != nil { return err } diff --git a/update/genesis/import.go b/update/genesis/import.go index c6d98b0bbdd..8e59e45b7f4 100644 --- a/update/genesis/import.go +++ b/update/genesis/import.go @@ -151,7 +151,7 @@ func (si *stateImport) importEpochStartMetaBlock(identifier string, keys [][]byt return err } - metaBlock, ok := object.(*block.MetaBlock) + metaBlock, ok := object.(data.MetaHeaderHandler) if !ok { return update.ErrWrongTypeAssertion } @@ -170,7 +170,7 @@ func (si *stateImport) importUnFinishedMetaBlocks(identifier string, keys [][]by break } - metaBlock, ok := object.(*block.MetaBlock) + metaBlock, ok := object.(data.MetaHeaderHandler) if !ok { return update.ErrWrongTypeAssertion } diff --git a/update/interface.go b/update/interface.go index 385e9b62e56..7ff6492c140 100644 --- a/update/interface.go +++ b/update/interface.go @@ -7,6 +7,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/process" @@ -68,7 +69,7 @@ type HistoryStorer interface { // RequestHandler defines the methods through which request to data can be made type RequestHandler interface { - RequestTransaction(shardId uint32, txHashes [][]byte) + RequestTransactions(shardId uint32, txHashes [][]byte) RequestUnsignedTransactions(destShardID uint32, scrHashes [][]byte) RequestRewardTransactions(destShardID uint32, txHashes [][]byte) RequestMiniBlock(shardId uint32, miniblockHash []byte) diff --git a/update/mock/transactionCoordinatorMock.go b/update/mock/transactionCoordinatorMock.go index c0bb061a713..141429341a1 100644 --- a/update/mock/transactionCoordinatorMock.go +++ b/update/mock/transactionCoordinatorMock.go @@ -5,6 +5,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/block" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/block/processedMb" @@ -19,12 +20,16 @@ type TransactionCoordinatorMock struct { SaveTxsToStorageCalled func(body *block.Body) RestoreBlockDataFromStorageCalled func(body *block.Body) (int, error) RemoveBlockDataFromPoolCalled func(body *block.Body) error - RemoveTxsFromPoolCalled func(body *block.Body) error + RemoveTxsFromPoolCalled func(body *block.Body, rootHashHolder common.RootHashHolder) error ProcessBlockTransactionCalled func(header data.HeaderHandler, body *block.Body, haveTime func() time.Duration) error + GetCreatedMiniBlocksFromMeCalled func() block.MiniBlockSlice CreateBlockStartedCalled func() + CreateMbsCrossShardDstMeCalled func(header data.HeaderHandler, processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo) ([]block.MiniblockAndHash, []block.MiniblockAndHash, uint32, bool, bool, error) CreateMbsAndProcessCrossShardTransactionsDstMeCalled func(header data.HeaderHandler, processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo, haveTime func() bool, haveAdditionalTime func() bool, scheduledMode bool) (block.MiniBlockSlice, uint32, bool, error) CreateMbsAndProcessTransactionsFromMeCalled func(haveTime func() bool) block.MiniBlockSlice + SelectOutgoingTransactionsCalled func(nonce uint64, haveTimeForSelection func() bool) ([][]byte, []data.MiniBlockHeaderHandler) CreateMarshalizedDataCalled func(body *block.Body) map[string][][]byte + CreateMarshalledDataForHeaderCalled func(header data.HeaderHandler, body *block.Body, miniBlocksMap map[string]block.MiniBlockSlice) map[string][][]byte GetCreatedInShardMiniBlocksCalled func() []*block.MiniBlock GetAllCurrentUsedTxsCalled func(blockType block.Type) map[string]data.TransactionHandler VerifyCreatedBlockTransactionsCalled func(hdr data.HeaderHandler, body *block.Body) error @@ -34,10 +39,12 @@ type TransactionCoordinatorMock struct { GetAllIntermediateTxsCalled func() map[block.Type]map[string]data.TransactionHandler AddTxsFromMiniBlocksCalled func(miniBlocks block.MiniBlockSlice) AddTransactionsCalled func(txHandlers []data.TransactionHandler, blockType block.Type) + GetUnExecutableTransactionsCalled func() map[string]struct{} + ProposedDirectSentTransactionsToBroadcastCalled func(proposedBody data.BodyHandler) map[string][][]byte } // GetAllCurrentLogs - -func (tcm *TransactionCoordinatorMock) GetAllCurrentLogs() []*data.LogData { +func (tcm *TransactionCoordinatorMock) GetAllCurrentLogs() []data.LogDataHandler { return nil } @@ -109,12 +116,12 @@ func (tcm *TransactionCoordinatorMock) RemoveBlockDataFromPool(body *block.Body) } // RemoveTxsFromPool - -func (tcm *TransactionCoordinatorMock) RemoveTxsFromPool(body *block.Body) error { +func (tcm *TransactionCoordinatorMock) RemoveTxsFromPool(body *block.Body, rootHashHolder common.RootHashHolder) error { if tcm.RemoveTxsFromPoolCalled == nil { return nil } - return tcm.RemoveTxsFromPoolCalled(body) + return tcm.RemoveTxsFromPoolCalled(body, rootHashHolder) } // ProcessBlockTransaction - @@ -126,6 +133,15 @@ func (tcm *TransactionCoordinatorMock) ProcessBlockTransaction(header data.Heade return tcm.ProcessBlockTransactionCalled(header, body, haveTime) } +// GetCreatedMiniBlocksFromMe - +func (tcm *TransactionCoordinatorMock) GetCreatedMiniBlocksFromMe() block.MiniBlockSlice { + if tcm.GetCreatedMiniBlocksFromMeCalled == nil { + return nil + } + + return tcm.GetCreatedMiniBlocksFromMeCalled() +} + // CreateBlockStarted - func (tcm *TransactionCoordinatorMock) CreateBlockStarted() { if tcm.CreateBlockStartedCalled == nil { @@ -135,6 +151,18 @@ func (tcm *TransactionCoordinatorMock) CreateBlockStarted() { tcm.CreateBlockStartedCalled() } +// CreateMbsCrossShardDstMe - +func (tcm *TransactionCoordinatorMock) CreateMbsCrossShardDstMe( + header data.HeaderHandler, + processedMiniBlocksInfo map[string]*processedMb.ProcessedMiniBlockInfo, +) ([]block.MiniblockAndHash, []block.MiniblockAndHash, uint32, bool, bool, error) { + if tcm.CreateMbsCrossShardDstMeCalled == nil { + return nil, nil, 0, false, false, nil + } + + return tcm.CreateMbsCrossShardDstMeCalled(header, processedMiniBlocksInfo) +} + // CreateMbsAndProcessCrossShardTransactionsDstMe - func (tcm *TransactionCoordinatorMock) CreateMbsAndProcessCrossShardTransactionsDstMe( header data.HeaderHandler, @@ -150,6 +178,24 @@ func (tcm *TransactionCoordinatorMock) CreateMbsAndProcessCrossShardTransactions return tcm.CreateMbsAndProcessCrossShardTransactionsDstMeCalled(header, processedMiniBlocksInfo, haveTime, haveAdditionalTime, scheduledMode) } +// SelectOutgoingTransactions - +func (tcm *TransactionCoordinatorMock) SelectOutgoingTransactions(nonce uint64, haveTimeForSelection func() bool) ([][]byte, []data.MiniBlockHeaderHandler) { + if tcm.SelectOutgoingTransactionsCalled == nil { + return make([][]byte, 0), make([]data.MiniBlockHeaderHandler, 0) + } + + return tcm.SelectOutgoingTransactionsCalled(nonce, haveTimeForSelection) +} + +// GetUnExecutableTransactions - +func (tcm *TransactionCoordinatorMock) GetUnExecutableTransactions() map[string]struct{} { + if tcm.GetUnExecutableTransactionsCalled != nil { + return tcm.GetUnExecutableTransactionsCalled() + } + + return nil +} + // CreateMbsAndProcessTransactionsFromMe - func (tcm *TransactionCoordinatorMock) CreateMbsAndProcessTransactionsFromMe(haveTime func() bool, _ []byte) block.MiniBlockSlice { if tcm.CreateMbsAndProcessTransactionsFromMeCalled == nil { @@ -168,6 +214,15 @@ func (tcm *TransactionCoordinatorMock) CreateMarshalizedData(body *block.Body) m return tcm.CreateMarshalizedDataCalled(body) } +// CreateMarshalledDataForHeader - +func (tcm *TransactionCoordinatorMock) CreateMarshalledDataForHeader(header data.HeaderHandler, body *block.Body, miniBlocksMap map[string]block.MiniBlockSlice) map[string][][]byte { + if tcm.CreateMarshalledDataForHeaderCalled == nil { + return make(map[string][][]byte) + } + + return tcm.CreateMarshalledDataForHeaderCalled(header, body, miniBlocksMap) +} + // GetAllCurrentUsedTxs - func (tcm *TransactionCoordinatorMock) GetAllCurrentUsedTxs(blockType block.Type) map[string]data.TransactionHandler { if tcm.GetAllCurrentUsedTxsCalled == nil { @@ -240,6 +295,15 @@ func (tcm *TransactionCoordinatorMock) AddTransactions(txHandlers []data.Transac tcm.AddTransactionsCalled(txHandlers, blockType) } +// ProposedDirectSentTransactionsToBroadcast - +func (tcm *TransactionCoordinatorMock) ProposedDirectSentTransactionsToBroadcast(proposedBody data.BodyHandler) map[string][][]byte { + if tcm.ProposedDirectSentTransactionsToBroadcastCalled == nil { + return nil + } + + return tcm.ProposedDirectSentTransactionsToBroadcastCalled(proposedBody) +} + // IsInterfaceNil returns true if there is no value under the interface func (tcm *TransactionCoordinatorMock) IsInterfaceNil() bool { return tcm == nil diff --git a/update/sync/syncEpochStartShardHeaders_test.go b/update/sync/syncEpochStartShardHeaders_test.go index a8ff71215f7..4dfabb6331e 100644 --- a/update/sync/syncEpochStartShardHeaders_test.go +++ b/update/sync/syncEpochStartShardHeaders_test.go @@ -498,7 +498,7 @@ func TestSyncEpochStartShardHeader_TwoConsecutiveProofsWithSameHeaderHash(t *tes syncer.receivedProof(nonEpochStartProof) }() - ctx, cancel := context.WithTimeout(context.Background(), time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() err = syncer.SyncEpochStartShardHeader(shardID, epoch, startNonce, ctx) @@ -562,7 +562,7 @@ func TestSyncEpochStartShardHeader_ProofsBeforeHeaderShouldWork(t *testing.T) { syncer.receivedHeader(epochStartHeader, headerHash) }() - ctx, cancel := context.WithTimeout(context.Background(), time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() err = syncer.SyncEpochStartShardHeader(shardID, epoch, startNonce, ctx) @@ -628,7 +628,7 @@ func TestSyncEpochStartShardHeader_ShouldWorkWithoutAndromedaActivated(t *testin syncer.receivedHeader(epochStartHeader, headerHash) }() - ctx, cancel := context.WithTimeout(context.Background(), time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() err = syncer.SyncEpochStartShardHeader(shardID, epoch, startNonce, ctx) diff --git a/update/sync/syncHeaders.go b/update/sync/syncHeaders.go index 2d3d2289914..206d1c3d5db 100644 --- a/update/sync/syncHeaders.go +++ b/update/sync/syncHeaders.go @@ -115,7 +115,7 @@ func (h *headersToSync) receivedMetaBlockFirstPending(headerHandler data.HeaderH return } - metaHeader, ok := headerHandler.(*block.MetaBlock) + metaHeader, ok := headerHandler.(data.MetaHeaderHandler) if !ok { h.mutMeta.Unlock() return @@ -145,7 +145,7 @@ func (h *headersToSync) receivedUnFinishedMetaBlocks(headerHandler data.HeaderHa return } - meta, ok := headerHandler.(*block.MetaBlock) + meta, ok := headerHandler.(data.MetaHeaderHandler) if !ok { h.mutMeta.Unlock() return @@ -201,7 +201,7 @@ func (h *headersToSync) SyncUnFinishedMetaHeaders(epoch uint32) error { return nil } -// SyncEpochStartMetaHeader syncs and validates an epoch start metaHeader +// syncEpochStartMetaHeader syncs and validates an epoch start metaHeader func (h *headersToSync) syncEpochStartMetaHeader(epoch uint32, waitTime time.Duration) error { defer func() { h.mutMeta.Lock() diff --git a/update/sync/syncTransactions.go b/update/sync/syncTransactions.go index 79918f0bcbd..e9097c43f3f 100644 --- a/update/sync/syncTransactions.go +++ b/update/sync/syncTransactions.go @@ -13,10 +13,11 @@ import ( "github.com/multiversx/mx-chain-core-go/data/smartContractResult" "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/dataRetriever" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/state" - "github.com/multiversx/mx-chain-go/storage/txcache" + "github.com/multiversx/mx-chain-go/txcache" "github.com/multiversx/mx-chain-go/update" ) @@ -200,8 +201,8 @@ func (ts *transactionsSync) requestTransactionsForNonPeerMiniBlock(miniBlock *bl switch mbType { case block.TxBlock: - ts.requestHandler.RequestTransaction(miniBlock.SenderShardID, missingTxs) - ts.requestHandler.RequestTransaction(miniBlock.ReceiverShardID, missingTxs) + ts.requestHandler.RequestTransactions(miniBlock.SenderShardID, missingTxs) + ts.requestHandler.RequestTransactions(miniBlock.ReceiverShardID, missingTxs) case block.SmartContractResultBlock: ts.requestHandler.RequestUnsignedTransactions(miniBlock.SenderShardID, missingTxs) ts.requestHandler.RequestUnsignedTransactions(miniBlock.ReceiverShardID, missingTxs) diff --git a/update/trigger/export_test.go b/update/trigger/export_test.go index f5d65d12679..d3db1d9dac4 100644 --- a/update/trigger/export_test.go +++ b/update/trigger/export_test.go @@ -9,6 +9,7 @@ const PayloadSeparator = dataSeparator const HardforkGracePeriod = hardforkGracePeriod const MinimumEpochForHarfork = minimumEpochForHarfork const DeltaRoundsForForcedEpoch = deltaRoundsForForcedEpoch +const SupernovaDeltaRoundsForForcedEpoch = supernovaDeltaRoundsForForcedEpoch func (t *trigger) SetTimeHandler(handler func() int64) { t.getTimestampHandler = handler diff --git a/update/trigger/trigger.go b/update/trigger/trigger.go index 604b74f680f..bccb7d92174 100644 --- a/update/trigger/trigger.go +++ b/update/trigger/trigger.go @@ -12,10 +12,13 @@ import ( "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data/endProcess" + logger "github.com/multiversx/mx-chain-logger-go" + + "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/errors" "github.com/multiversx/mx-chain-go/facade" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/update" - logger "github.com/multiversx/mx-chain-logger-go" ) const hardforkTriggerString = "hardfork trigger" @@ -24,7 +27,10 @@ const hardforkGracePeriod = time.Minute * 5 const epochGracePeriod = 4 const minTimeToWaitAfterHardforkInMinutes = 2 const minimumEpochForHarfork = 1 + +// TODO: add constants to config const deltaRoundsForForcedEpoch = uint64(10) +const supernovaDeltaRoundsForForcedEpoch = uint64(100) const disabledRoundForForceEpochStart = uint64(math.MaxUint64) var _ facade.HardforkTrigger = (*trigger)(nil) @@ -44,6 +50,8 @@ type ArgHardforkTrigger struct { EpochConfirmedNotifier update.EpochChangeConfirmedNotifier ImportStartHandler update.ImportStartHandler RoundHandler update.RoundHandler + EnableEpochsHandler common.EnableEpochsHandler + EnableRoundsHandler common.EnableRoundsHandler } // trigger implements a hardfork trigger that is able to notify a set list of handlers if this instance gets triggered @@ -73,6 +81,8 @@ type trigger struct { importStartHandler update.ImportStartHandler isWithEarlyEndOfEpoch bool roundHandler update.RoundHandler + enableEpochsHandler common.EnableEpochsHandler + enableRoundsHandler common.EnableRoundsHandler } // NewTrigger returns the trigger instance @@ -110,6 +120,12 @@ func NewTrigger(arg ArgHardforkTrigger) (*trigger, error) { if check.IfNil(arg.RoundHandler) { return nil, fmt.Errorf("%w in update.NewTrigger", update.ErrNilRoundHandler) } + if check.IfNil(arg.EnableEpochsHandler) { + return nil, errors.ErrNilEnableEpochsHandler + } + if check.IfNil(arg.EnableRoundsHandler) { + return nil, errors.ErrNilEnableRoundsHandler + } t := &trigger{ enabled: arg.Enabled, @@ -127,6 +143,8 @@ func NewTrigger(arg ArgHardforkTrigger) (*trigger, error) { chanTriggerReceivedV2: make(chan struct{}, 1), // buffer with one value as there might be async calls importStartHandler: arg.ImportStartHandler, roundHandler: arg.RoundHandler, + enableEpochsHandler: arg.EnableEpochsHandler, + enableRoundsHandler: arg.EnableRoundsHandler, } t.isTriggerSelf = bytes.Equal(arg.TriggerPubKeyBytes, arg.SelfPubKeyBytes) @@ -136,7 +154,19 @@ func NewTrigger(arg ArgHardforkTrigger) (*trigger, error) { return t, nil } +func (t *trigger) getHardforkGracePeriod() int64 { + if t.enableEpochsHandler.IsFlagEnabled(common.SupernovaFlag) { + return int64(hardforkGracePeriod.Milliseconds()) + } + + return int64(hardforkGracePeriod.Seconds()) +} + func (t *trigger) getCurrentUnixTime() int64 { + if t.enableEpochsHandler.IsFlagEnabled(common.SupernovaFlag) { + return time.Now().UnixMilli() + } + return time.Now().Unix() } @@ -205,7 +235,7 @@ func (t *trigger) Trigger(epoch uint32, withEarlyEndOfEpoch bool) error { return fmt.Errorf("%w, minimum epoch accepted is %d", update.ErrInvalidEpoch, minimumEpochForHarfork) } - shouldTrigger, err := t.computeAndSetTrigger(epoch, nil, withEarlyEndOfEpoch, round) //original payload is nil because this node is the originator + shouldTrigger, err := t.computeAndSetTrigger(epoch, nil, withEarlyEndOfEpoch, round) // original payload is nil because this node is the originator if err != nil { return err } @@ -227,11 +257,19 @@ func (t *trigger) computeHardforkRound(withEarlyEndOfEpoch bool) uint64 { currentRound := t.roundHandler.Index() if currentRound < 0 { - //do not overflow on uint64 when current round is negative + // do not overflow on uint64 when current round is negative return deltaRoundsForForcedEpoch } - return uint64(currentRound) + deltaRoundsForForcedEpoch + return uint64(currentRound) + t.getDeltaRoundsForForceEpoch(uint64(currentRound)) +} + +func (t *trigger) getDeltaRoundsForForceEpoch(round uint64) uint64 { + if t.enableRoundsHandler.IsFlagEnabledInRound(common.SupernovaRoundFlag, round) { + return supernovaDeltaRoundsForForcedEpoch + } + + return deltaRoundsForForcedEpoch } // computeAndSetTrigger needs to do 2 things atomically: set the original payload and epoch and determine if the trigger @@ -356,17 +394,18 @@ func (t *trigger) TriggerReceived(originalPayload []byte, data []byte, pkBytes [ } currentTimeStamp := t.getTimestampHandler() - if timestamp+int64(hardforkGracePeriod.Seconds()) < currentTimeStamp { + if timestamp+t.getHardforkGracePeriod() < currentTimeStamp { return true, fmt.Errorf("%w message timestamp out of grace period message", update.ErrIncorrectHardforkMessage) } - epoch, err := t.getIntFromArgument(string(arguments[1])) + epochUint64, err := strconv.ParseUint(string(arguments[1]), 10, 32) if err != nil { return true, err } - if epoch < minimumEpochForHarfork { + if epochUint64 < minimumEpochForHarfork { return true, fmt.Errorf("%w, minimum epoch accepted is %d", update.ErrInvalidEpoch, minimumEpochForHarfork) } + epoch := uint32(epochUint64) earlyEndOfEpochRound := disabledRoundForForceEpochStart withEarlyEndOfEpoch := false @@ -378,12 +417,12 @@ func (t *trigger) TriggerReceived(originalPayload []byte, data []byte, pkBytes [ } } - currentEpoch := int64(t.epochProvider.MetaEpoch()) - if currentEpoch-epoch > epochGracePeriod { + currentEpoch := t.epochProvider.MetaEpoch() + if currentEpoch > epoch && currentEpoch-epoch > epochGracePeriod { return true, fmt.Errorf("%w epoch out of grace period", update.ErrIncorrectHardforkMessage) } - shouldTrigger, err := t.computeAndSetTrigger(uint32(epoch), originalPayload, withEarlyEndOfEpoch, earlyEndOfEpochRound) + shouldTrigger, err := t.computeAndSetTrigger(epoch, originalPayload, withEarlyEndOfEpoch, earlyEndOfEpochRound) if err != nil { log.Debug("received trigger", "status", err) return true, nil diff --git a/update/trigger/trigger_test.go b/update/trigger/trigger_test.go index 5038bb4ab38..35ab262db73 100644 --- a/update/trigger/trigger_test.go +++ b/update/trigger/trigger_test.go @@ -8,9 +8,13 @@ import ( "testing" "time" + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/data/endProcess" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process/smartContract" + "github.com/multiversx/mx-chain-go/testscommon" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/update" "github.com/multiversx/mx-chain-go/update/mock" "github.com/multiversx/mx-chain-go/update/trigger" @@ -35,6 +39,8 @@ func createMockArgHardforkTrigger() trigger.ArgHardforkTrigger { EpochConfirmedNotifier: &mock.EpochStartNotifierStub{}, ImportStartHandler: &mock.ImportStartHandlerStub{}, RoundHandler: &mock.RoundHandlerStub{}, + EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, } } @@ -120,61 +126,140 @@ func TestTrigger_TriggerWrongEpochShouldErr(t *testing.T) { func TestTrigger_TriggerEnabledShouldWork(t *testing.T) { t.Parallel() - arg := createMockArgHardforkTrigger() - trig, _ := trigger.NewTrigger(arg) + t.Run("before supernova", func(t *testing.T) { + t.Parallel() - payload, wasTriggered := trig.RecordedTriggerMessage() - assert.Nil(t, payload) - assert.False(t, wasTriggered) + arg := createMockArgHardforkTrigger() + trig, _ := trigger.NewTrigger(arg) - err := trig.Trigger(trigger.MinimumEpochForHarfork, false) + payload, wasTriggered := trig.RecordedTriggerMessage() + assert.Nil(t, payload) + assert.False(t, wasTriggered) - // delay as to execute the async calls - time.Sleep(time.Second) + err := trig.Trigger(trigger.MinimumEpochForHarfork, false) - payload, wasTriggered = trig.RecordedTriggerMessage() + // delay as to execute the async calls + time.Sleep(time.Second) - assert.Nil(t, err) - assert.Nil(t, payload) - assert.True(t, wasTriggered) + payload, wasTriggered = trig.RecordedTriggerMessage() + + assert.Nil(t, err) + assert.Nil(t, payload) + assert.True(t, wasTriggered) + }) + + t.Run("after supernova", func(t *testing.T) { + t.Parallel() + + arg := createMockArgHardforkTrigger() + arg.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag + }, + } + + trig, _ := trigger.NewTrigger(arg) + + payload, wasTriggered := trig.RecordedTriggerMessage() + assert.Nil(t, payload) + assert.False(t, wasTriggered) + + err := trig.Trigger(trigger.MinimumEpochForHarfork, false) + + // delay as to execute the async calls + time.Sleep(time.Second) + + payload, wasTriggered = trig.RecordedTriggerMessage() + + assert.Nil(t, err) + assert.Nil(t, payload) + assert.True(t, wasTriggered) + }) } func TestTrigger_TriggerWithEarlyEndOfEpochEnabledShouldWork(t *testing.T) { t.Parallel() - forceEpochStartWasCalled := false - arg := createMockArgHardforkTrigger() - recoveredRound := uint64(0) - arg.EpochProvider = &mock.EpochHandlerStub{ - ForceEpochStartCalled: func(round uint64) { - forceEpochStartWasCalled = true - recoveredRound = round - }, - } - currentRound := uint64(4433) - arg.RoundHandler = &mock.RoundHandlerStub{ - IndexCalled: func() int64 { - return int64(currentRound) - }, - } - trig, _ := trigger.NewTrigger(arg) + t.Run("before supernova", func(t *testing.T) { + t.Parallel() - payload, wasTriggered := trig.RecordedTriggerMessage() - assert.Nil(t, payload) - assert.False(t, wasTriggered) + forceEpochStartWasCalled := false + arg := createMockArgHardforkTrigger() + recoveredRound := uint64(0) + arg.EpochProvider = &mock.EpochHandlerStub{ + ForceEpochStartCalled: func(round uint64) { + forceEpochStartWasCalled = true + recoveredRound = round + }, + } + currentRound := uint64(4433) + arg.RoundHandler = &mock.RoundHandlerStub{ + IndexCalled: func() int64 { + return int64(currentRound) + }, + } + trig, _ := trigger.NewTrigger(arg) - err := trig.Trigger(trigger.MinimumEpochForHarfork, true) + payload, wasTriggered := trig.RecordedTriggerMessage() + assert.Nil(t, payload) + assert.False(t, wasTriggered) - // delay as to execute the async calls - time.Sleep(time.Second) + err := trig.Trigger(trigger.MinimumEpochForHarfork, true) - payload, wasTriggered = trig.RecordedTriggerMessage() + // delay as to execute the async calls + time.Sleep(time.Second) - assert.Nil(t, err) - assert.Nil(t, payload) - assert.True(t, wasTriggered) - assert.True(t, forceEpochStartWasCalled) - assert.Equal(t, currentRound+trigger.DeltaRoundsForForcedEpoch, recoveredRound) + payload, wasTriggered = trig.RecordedTriggerMessage() + + assert.Nil(t, err) + assert.Nil(t, payload) + assert.True(t, wasTriggered) + assert.True(t, forceEpochStartWasCalled) + assert.Equal(t, currentRound+trigger.DeltaRoundsForForcedEpoch, recoveredRound) + }) + + t.Run("after supernova", func(t *testing.T) { + t.Parallel() + + forceEpochStartWasCalled := false + arg := createMockArgHardforkTrigger() + arg.EnableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return flag == common.SupernovaRoundFlag + }, + } + recoveredRound := uint64(0) + arg.EpochProvider = &mock.EpochHandlerStub{ + ForceEpochStartCalled: func(round uint64) { + forceEpochStartWasCalled = true + recoveredRound = round + }, + } + currentRound := uint64(4433) + arg.RoundHandler = &mock.RoundHandlerStub{ + IndexCalled: func() int64 { + return int64(currentRound) + }, + } + trig, _ := trigger.NewTrigger(arg) + + payload, wasTriggered := trig.RecordedTriggerMessage() + assert.Nil(t, payload) + assert.False(t, wasTriggered) + + err := trig.Trigger(trigger.MinimumEpochForHarfork, true) + + // delay as to execute the async calls + time.Sleep(time.Second) + + payload, wasTriggered = trig.RecordedTriggerMessage() + + assert.Nil(t, err) + assert.Nil(t, payload) + assert.True(t, wasTriggered) + assert.True(t, forceEpochStartWasCalled) + assert.Equal(t, currentRound+trigger.SupernovaDeltaRoundsForForcedEpoch, recoveredRound) + }) } func TestTrigger_TriggerCalledTwiceShouldErr(t *testing.T) { diff --git a/vm/errors.go b/vm/errors.go index 823621ce7dd..16b28c7a41a 100644 --- a/vm/errors.go +++ b/vm/errors.go @@ -250,6 +250,9 @@ var ErrProposalNotFound = errors.New("proposal was not found in storage") // ErrNilEnableEpochsHandler signals that a nil enable epochs handler has been provided var ErrNilEnableEpochsHandler = errors.New("nil enable epochs handler") +// ErrNilEnableRoundsHandler signals that a nil enable rounds handler has been provided +var ErrNilEnableRoundsHandler = errors.New("nil enable rounds handler") + // ErrNotEnoughStakeToVote signals that the stake/delegation is not enough to vote var ErrNotEnoughStakeToVote = errors.New("not enough stake/delegate to vote") @@ -282,3 +285,6 @@ var ErrCannotChangeToDynamic = errors.New("cannot change to dynamic because of d // ErrDivisionByZero signals that tokenID division by zero will happen var ErrDivisionByZero = errors.New("division by zero") + +// ErrInvalidValue signals that an invalid value was provided +var ErrInvalidValue = errors.New("invalid value") diff --git a/vm/factory/systemSCFactory.go b/vm/factory/systemSCFactory.go index 4ebd97a63b4..967db635ee8 100644 --- a/vm/factory/systemSCFactory.go +++ b/vm/factory/systemSCFactory.go @@ -31,6 +31,7 @@ type systemSCFactory struct { addressPubKeyConverter core.PubkeyConverter shardCoordinator sharding.Coordinator enableEpochsHandler common.EnableEpochsHandler + enableRoundsHandler common.EnableRoundsHandler nodesCoordinator vm.NodesCoordinator } @@ -47,6 +48,7 @@ type ArgsNewSystemSCFactory struct { AddressPubKeyConverter core.PubkeyConverter ShardCoordinator sharding.Coordinator EnableEpochsHandler common.EnableEpochsHandler + EnableRoundsHandler common.EnableRoundsHandler NodesCoordinator vm.NodesCoordinator } @@ -97,6 +99,7 @@ func NewSystemSCFactory(args ArgsNewSystemSCFactory) (*systemSCFactory, error) { addressPubKeyConverter: args.AddressPubKeyConverter, shardCoordinator: args.ShardCoordinator, enableEpochsHandler: args.EnableEpochsHandler, + enableRoundsHandler: args.EnableRoundsHandler, nodesCoordinator: args.NodesCoordinator, } @@ -182,6 +185,7 @@ func (scf *systemSCFactory) createStakingContract() (vm.SystemSmartContract, err GasCost: scf.gasCost, Marshalizer: scf.marshalizer, EnableEpochsHandler: scf.enableEpochsHandler, + EnableRoundsHandler: scf.enableRoundsHandler, } staking, err := systemSmartContracts.NewStakingSmartContract(argsStaking) return staking, err diff --git a/vm/factory/systemSCFactory_test.go b/vm/factory/systemSCFactory_test.go index 7e4cb7b814d..708a389c884 100644 --- a/vm/factory/systemSCFactory_test.go +++ b/vm/factory/systemSCFactory_test.go @@ -66,6 +66,7 @@ func createMockNewSystemScFactoryArgs() ArgsNewSystemSCFactory { MinStepValue: "10", MinStakeValue: "1", UnBondPeriod: 1, + UnBondPeriodSupernova: 2, NumRoundsWithoutBleed: 1, MaximumPercentageToBleed: 1, BleedPercentagePerRound: 1, @@ -94,6 +95,7 @@ func createMockNewSystemScFactoryArgs() ArgsNewSystemSCFactory { AddressPubKeyConverter: &testscommon.PubkeyConverterMock{}, ShardCoordinator: &mock.ShardCoordinatorStub{}, EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, NodesCoordinator: &mock.NodesCoordinatorStub{}, } } diff --git a/vm/systemSmartContracts/staking.go b/vm/systemSmartContracts/staking.go index 7acfb492d15..76c40e0def8 100644 --- a/vm/systemSmartContracts/staking.go +++ b/vm/systemSmartContracts/staking.go @@ -28,6 +28,7 @@ const nodesConfigKey = "nodesConfig" type stakingSC struct { eei vm.SystemEI unBondPeriod uint64 + unBondPeriodSupernova uint64 stakeAccessAddr []byte // TODO add a viewAddress field and use it on all system SC view functions jailAccessAddr []byte endOfEpochAccessAddr []byte @@ -43,6 +44,7 @@ type stakingSC struct { mutExecution sync.RWMutex minNodePrice *big.Int enableEpochsHandler common.EnableEpochsHandler + enableRoundsHandler common.EnableRoundsHandler } // ArgsNewStakingSmartContract holds the arguments needed to create a StakingSmartContract @@ -56,6 +58,7 @@ type ArgsNewStakingSmartContract struct { GasCost vm.GasCost Marshalizer marshal.Marshalizer EnableEpochsHandler common.EnableEpochsHandler + EnableRoundsHandler common.EnableRoundsHandler } // NewStakingSmartContract creates a staking smart contract @@ -86,9 +89,18 @@ func NewStakingSmartContract( if args.MinNumNodes > args.StakingSCConfig.MaxNumberOfNodesForStake { return nil, vm.ErrInvalidMaxNumberOfNodes } + if args.StakingSCConfig.UnBondPeriod == 0 { + return nil, fmt.Errorf("%w for UnBondPeriod", vm.ErrInvalidValue) + } + if args.StakingSCConfig.UnBondPeriodSupernova == 0 { + return nil, fmt.Errorf("%w for UnBondPeriodSupernova", vm.ErrInvalidValue) + } if check.IfNil(args.EnableEpochsHandler) { return nil, vm.ErrNilEnableEpochsHandler } + if check.IfNil(args.EnableRoundsHandler) { + return nil, vm.ErrNilEnableRoundsHandler + } err := core.CheckHandlerCompatibility(args.EnableEpochsHandler, []core.EnableEpochFlag{ common.CorrectFirstQueuedFlag, common.ValidatorToDelegationFlag, @@ -109,6 +121,7 @@ func NewStakingSmartContract( reg := &stakingSC{ eei: args.Eei, unBondPeriod: args.StakingSCConfig.UnBondPeriod, + unBondPeriodSupernova: args.StakingSCConfig.UnBondPeriodSupernova, stakeAccessAddr: args.StakingAccessAddr, jailAccessAddr: args.JailAccessAddr, numRoundsWithoutBleed: args.StakingSCConfig.NumRoundsWithoutBleed, @@ -122,6 +135,7 @@ func NewStakingSmartContract( walletAddressLen: len(args.StakingAccessAddr), minNodePrice: minStakeValue, enableEpochsHandler: args.EnableEpochsHandler, + enableRoundsHandler: args.EnableRoundsHandler, } var conversionOk bool @@ -133,6 +147,16 @@ func NewStakingSmartContract( return reg, nil } +func (s *stakingSC) getUnBondPeriod() uint64 { + round := s.eei.BlockChainHook().CurrentRound() + + if !s.enableRoundsHandler.IsFlagEnabledInRound(common.SupernovaRoundFlag, round) { + return s.unBondPeriod + } + + return s.unBondPeriodSupernova +} + // Execute calls one of the functions from the staking smart contract and runs the code according to the input func (s *stakingSC) Execute(args *vmcommon.ContractCallInput) vmcommon.ReturnCode { s.mutExecution.RLock() @@ -706,7 +730,7 @@ func (s *stakingSC) unBond(args *vmcommon.ContractCallInput) vmcommon.ReturnCode } currentNonce := s.eei.BlockChainHook().CurrentNonce() - if registrationData.UnStakedNonce > 0 && currentNonce-registrationData.UnStakedNonce < s.unBondPeriod { + if registrationData.UnStakedNonce > 0 && currentNonce-registrationData.UnStakedNonce < s.getUnBondPeriod() { s.eei.AddReturnMessage(fmt.Sprintf("unBond is not possible for key %s because unBond period did not pass", encodedBlsKey)) return vmcommon.UserError } @@ -952,14 +976,14 @@ func (s *stakingSC) getRemainingUnbondPeriod(args *vmcommon.ContractCallInput) v currentNonce := s.eei.BlockChainHook().CurrentNonce() passedNonce := currentNonce - stakedData.UnStakedNonce - if passedNonce >= s.unBondPeriod { + if passedNonce >= s.getUnBondPeriod() { if s.enableEpochsHandler.IsFlagEnabled(common.StakingV2Flag) { s.eei.Finish(zero.Bytes()) } else { s.eei.Finish([]byte("0")) } } else { - remaining := s.unBondPeriod - passedNonce + remaining := s.getUnBondPeriod() - passedNonce if s.enableEpochsHandler.IsFlagEnabled(common.StakingV2Flag) { s.eei.Finish(big.NewInt(0).SetUint64(remaining).Bytes()) } else { diff --git a/vm/systemSmartContracts/staking_test.go b/vm/systemSmartContracts/staking_test.go index fb92a574945..6a3f222f2fe 100644 --- a/vm/systemSmartContracts/staking_test.go +++ b/vm/systemSmartContracts/staking_test.go @@ -21,6 +21,7 @@ import ( "github.com/multiversx/mx-chain-go/process/smartContract/hooks" "github.com/multiversx/mx-chain-go/state" "github.com/multiversx/mx-chain-go/state/accounts" + "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" stateMock "github.com/multiversx/mx-chain-go/testscommon/state" "github.com/multiversx/mx-chain-go/vm" @@ -47,7 +48,8 @@ func createMockStakingScArgumentsWithSystemScAddresses( MinStakeValue: "1", UnJailValue: "1", MinStepValue: "1", - UnBondPeriod: 0, + UnBondPeriod: 1, + UnBondPeriodSupernova: 2, NumRoundsWithoutBleed: 0, MaximumPercentageToBleed: 0, BleedPercentagePerRound: 0, @@ -64,6 +66,7 @@ func createMockStakingScArgumentsWithSystemScAddresses( common.CorrectJailedNotUnStakedEmptyQueueFlag, common.ValidatorToDelegationFlag, ), + EnableRoundsHandler: &testscommon.EnableRoundsHandlerStub{}, } } @@ -164,6 +167,52 @@ func TestNewStakingSmartContract_NilEnableEpochsHandlerShouldErr(t *testing.T) { assert.Equal(t, vm.ErrNilEnableEpochsHandler, err) } +func TestNewStakingSmartContract_NilEnableRoundsHandlerShouldErr(t *testing.T) { + t.Parallel() + + args := createMockStakingScArguments() + args.EnableRoundsHandler = nil + stakingSmartContract, err := NewStakingSmartContract(args) + + assert.Nil(t, stakingSmartContract) + assert.Equal(t, vm.ErrNilEnableRoundsHandler, err) +} + +func TestNewStakingSmartContract_NilMarshallerShouldErr(t *testing.T) { + t.Parallel() + + args := createMockStakingScArguments() + args.Marshalizer = nil + stakingSmartContract, err := NewStakingSmartContract(args) + + assert.Nil(t, stakingSmartContract) + assert.Equal(t, vm.ErrNilMarshalizer, err) +} + +func TestNewStakingSmartContract_InvalidUnBondPeriodShouldErr(t *testing.T) { + t.Parallel() + + args := createMockStakingScArguments() + args.StakingSCConfig.UnBondPeriod = 0 + stakingSmartContract, err := NewStakingSmartContract(args) + + assert.Nil(t, stakingSmartContract) + require.True(t, errors.Is(err, vm.ErrInvalidValue)) + require.Contains(t, err.Error(), "UnBondPeriod") +} + +func TestNewStakingSmartContract_InvalidUnBondPeriodSupernovaShouldErr(t *testing.T) { + t.Parallel() + + args := createMockStakingScArguments() + args.StakingSCConfig.UnBondPeriodSupernova = 0 + stakingSmartContract, err := NewStakingSmartContract(args) + + assert.Nil(t, stakingSmartContract) + require.True(t, errors.Is(err, vm.ErrInvalidValue)) + require.Contains(t, err.Error(), "UnBondPeriodSupernova") +} + func TestNewStakingSmartContract_InvalidEnableEpochsHandlerShouldErr(t *testing.T) { t.Parallel() @@ -1007,7 +1056,7 @@ func TestStakingSc_ExecuteIsStaked(t *testing.T) { doStake(t, stakingSmartContract, stakingAccessAddress, stakerAddress, stakerPubKey) // check again isStaked should return vmcommon.Ok checkIsStaked(t, stakingSmartContract, callerAddress, stakerPubKey, vmcommon.Ok) - //do unStake + // do unStake doUnStake(t, stakingSmartContract, stakingAccessAddress, stakerAddress, stakerPubKey, vmcommon.Ok) // check if account is staked should return error code checkIsStaked(t, stakingSmartContract, callerAddress, stakerPubKey, vmcommon.UserError) @@ -1131,14 +1180,14 @@ func TestStakingSc_StakeWithV1ShouldWork(t *testing.T) { stakerAddress := []byte("stakerAddr") stakerPubKey := []byte("stakerPublicKey") - //do stake should work + // do stake should work doStake(t, stakingSmartContract, stakingAccessAddress, stakerAddress, stakerPubKey) blockChainHook.CurrentNonceCalled = func() uint64 { return 11 } - //do unStake with V2 should work + // do unStake with V2 should work doUnStake(t, stakingSmartContract, stakingAccessAddress, stakerAddress, stakerPubKey, vmcommon.Ok) } @@ -1182,7 +1231,7 @@ func TestStakingSc_StakeJailAndUnJail(t *testing.T) { doJail(t, stakingSmartContract, []byte("addr"), stakerPubKey, vmcommon.UserError) // cannot do jail if no stake should return userError doJail(t, stakingSmartContract, jailAccessAddr, stakerPubKey, vmcommon.UserError) - //do stake should work + // do stake should work doStake(t, stakingSmartContract, stakingAccessAddress, stakerAddress, stakerPubKey) // jail should work blockChainHook.CurrentRoundCalled = func() uint64 { @@ -1190,7 +1239,7 @@ func TestStakingSc_StakeJailAndUnJail(t *testing.T) { } doJail(t, stakingSmartContract, jailAccessAddr, stakerPubKey, vmcommon.Ok) - //do unStake should return error because validator is jail + // do unStake should return error because validator is jail doUnStake(t, stakingSmartContract, stakingAccessAddress, stakerAddress, stakerPubKey, vmcommon.UserError) // unJail wrong access address should not work @@ -1538,6 +1587,141 @@ func TestStakingSc_ExecuteStakeStakeStakeJailJailUnJailTwice(t *testing.T) { requireTotalNumberOfRegisteredNodes(t, stakingSmartContract, eei, big.NewInt(4)) } +func TestStakingSc_ExecuteStakeWithSupernova(t *testing.T) { + t.Parallel() + + stakeValue := big.NewInt(100) + blockChainHook := &mock.BlockChainHookStub{} + blockChainHook.GetStorageDataCalled = func(accountsAddress []byte, index []byte) ([]byte, uint32, error) { + return nil, 0, nil + } + + eei := createDefaultEei() + eei.blockChainHook = blockChainHook + eei.SetSCAddress([]byte("addr")) + + stakingAccessAddress := []byte("stakingAccessAddress") + args := createMockStakingScArguments() + args.StakingAccessAddr = stakingAccessAddress + args.StakingSCConfig.MinStakeValue = stakeValue.Text(10) + args.StakingSCConfig.MaxNumberOfNodesForStake = 2 + enableEpochsHandler, _ := args.EnableEpochsHandler.(*enableEpochsHandlerMock.EnableEpochsHandlerStub) + enableEpochsHandler.AddActiveFlags(common.StakingV2Flag) + + enableRoundsHandler, _ := args.EnableRoundsHandler.(*testscommon.EnableRoundsHandlerStub) + + enableRoundsHandler.IsFlagEnabledInRoundCalled = func(flag common.EnableRoundFlag, round uint64) bool { + return flag == common.SupernovaRoundFlag + } + + args.Eei = eei + stakingSmartContract, _ := NewStakingSmartContract(args) + + stakerAddress := []byte("stakerAddr") + callerAddress := []byte("data") + + // keys have to be the same len + pubKey1 := []byte("firsstKey") + pubKey2 := []byte("secondKey") + pubKey3 := []byte("thirddKey") + pubKey4 := []byte("fourthKey") + pubKey5 := []byte("fifthhKey") + pubKey6 := []byte("sixthhKey") + pubKey7 := []byte("seventKey") + + // do stake should work + doStake(t, stakingSmartContract, stakingAccessAddress, stakerAddress, pubKey1) + doStake(t, stakingSmartContract, stakingAccessAddress, stakerAddress, pubKey2) + doStake(t, stakingSmartContract, stakingAccessAddress, stakerAddress, pubKey3) + doStake(t, stakingSmartContract, stakingAccessAddress, stakerAddress, pubKey4) + + checkIsStaked(t, stakingSmartContract, callerAddress, pubKey1, vmcommon.Ok) + checkIsStaked(t, stakingSmartContract, callerAddress, pubKey2, vmcommon.Ok) + checkIsStaked(t, stakingSmartContract, callerAddress, pubKey3, vmcommon.UserError) + checkIsStaked(t, stakingSmartContract, callerAddress, pubKey4, vmcommon.UserError) + + arguments := CreateVmContractCallInput() + arguments.Function = "switchJailedWithWaiting" + arguments.CallerAddr = args.EndOfEpochAccessAddr + arguments.Arguments = [][]byte{pubKey1} + retCode := stakingSmartContract.Execute(arguments) + assert.Equal(t, retCode, vmcommon.Ok) + // check if account is staked should return error code + checkIsStaked(t, stakingSmartContract, callerAddress, pubKey1, vmcommon.UserError) + + arguments = CreateVmContractCallInput() + arguments.Function = "switchJailedWithWaiting" + arguments.CallerAddr = args.EndOfEpochAccessAddr + arguments.Arguments = [][]byte{pubKey2} + retCode = stakingSmartContract.Execute(arguments) + assert.Equal(t, retCode, vmcommon.Ok) + checkIsStaked(t, stakingSmartContract, callerAddress, pubKey4, vmcommon.Ok) + checkIsStaked(t, stakingSmartContract, callerAddress, pubKey2, vmcommon.UserError) + + doStake(t, stakingSmartContract, stakingAccessAddress, stakerAddress, pubKey5) + checkIsStaked(t, stakingSmartContract, callerAddress, pubKey5, vmcommon.UserError) + + doGetStatus(t, stakingSmartContract, eei, pubKey1, "jailed") + doUnJail(t, stakingSmartContract, stakingAccessAddress, pubKey1, vmcommon.Ok) + doGetStatus(t, stakingSmartContract, eei, pubKey1, "queued") + doUnJail(t, stakingSmartContract, stakingAccessAddress, pubKey2, vmcommon.Ok) + + waitingList, _ := stakingSmartContract.getWaitingListHead() + assert.Equal(t, uint32(3), waitingList.Length) + assert.Equal(t, []byte("w_"+string(pubKey2)), waitingList.LastJailedKey) + assert.Equal(t, []byte("w_"+string(pubKey1)), waitingList.FirstKey) + assert.Equal(t, []byte("w_"+string(pubKey5)), waitingList.LastKey) + + doStake(t, stakingSmartContract, stakingAccessAddress, stakerAddress, pubKey6) + doGetWaitingListIndex(t, stakingSmartContract, eei, pubKey1, vmcommon.Ok, 1) + doGetWaitingListIndex(t, stakingSmartContract, eei, pubKey2, vmcommon.Ok, 2) + doGetWaitingListIndex(t, stakingSmartContract, eei, pubKey5, vmcommon.Ok, 3) + doGetWaitingListIndex(t, stakingSmartContract, eei, pubKey6, vmcommon.Ok, 4) + + outPut := doGetWaitingListRegisterNonceAndRewardAddress(t, stakingSmartContract, eei) + assert.Equal(t, 12, len(outPut)) + + stakingSmartContract.unBondPeriod = 0 + doUnStake(t, stakingSmartContract, stakingAccessAddress, stakerAddress, pubKey2, vmcommon.Ok) + doUnBond(t, stakingSmartContract, stakingAccessAddress, pubKey2, vmcommon.Ok) + waitingList, _ = stakingSmartContract.getWaitingListHead() + assert.Equal(t, []byte("w_"+string(pubKey1)), waitingList.LastJailedKey) + + doUnStake(t, stakingSmartContract, stakingAccessAddress, stakerAddress, pubKey1, vmcommon.Ok) + doUnBond(t, stakingSmartContract, stakingAccessAddress, pubKey1, vmcommon.Ok) + waitingList, _ = stakingSmartContract.getWaitingListHead() + assert.Equal(t, 0, len(waitingList.LastJailedKey)) + + doGetWaitingListSize(t, stakingSmartContract, eei, 2) + doGetRewardAddress(t, stakingSmartContract, eei, pubKey5, string(stakerAddress)) + doGetStatus(t, stakingSmartContract, eei, pubKey5, "queued") + doGetStatus(t, stakingSmartContract, eei, pubKey4, "staked") + + stakingSmartContract.unBondPeriod = 10 + stakingSmartContract.unBondPeriodSupernova = 100 + blockChainHook.CurrentNonceCalled = func() uint64 { + return 1 + } + doUnStake(t, stakingSmartContract, stakingAccessAddress, stakerAddress, pubKey4, vmcommon.Ok) + doGetRemainingUnbondPeriod(t, stakingSmartContract, eei, pubKey4, 100) + + blockChainHook.CurrentNonceCalled = func() uint64 { + return 50 + } + doGetRemainingUnbondPeriod(t, stakingSmartContract, eei, pubKey4, 51) + + blockChainHook.CurrentNonceCalled = func() uint64 { + return 101 + } + doGetRemainingUnbondPeriod(t, stakingSmartContract, eei, pubKey4, 0) + + doStake(t, stakingSmartContract, stakingAccessAddress, stakerAddress, pubKey7) + doGetWaitingListSize(t, stakingSmartContract, eei, 2) + outPut = doGetWaitingListRegisterNonceAndRewardAddress(t, stakingSmartContract, eei) + assert.Equal(t, 6, len(outPut)) + requireTotalNumberOfRegisteredNodes(t, stakingSmartContract, eei, big.NewInt(4)) +} + func TestStakingSc_ExecuteStakeUnStakeJailCombinations(t *testing.T) { t.Parallel() @@ -3212,7 +3396,7 @@ func doGetRemainingUnbondPeriod(t *testing.T, sc *stakingSC, eei *vmContext, bls assert.Equal(t, vmcommon.Ok, retCode) lastOutput := eei.output[len(eei.output)-1] - assert.True(t, bytes.Equal(lastOutput, big.NewInt(int64(expected)).Bytes())) + require.Equal(t, lastOutput, big.NewInt(int64(expected)).Bytes()) } func doGetStatus(t *testing.T, sc *stakingSC, eei *vmContext, blsKey []byte, expectedStatus string) { diff --git a/vm/systemSmartContracts/validator_test.go b/vm/systemSmartContracts/validator_test.go index 758e0167a9d..ea2307e7346 100644 --- a/vm/systemSmartContracts/validator_test.go +++ b/vm/systemSmartContracts/validator_test.go @@ -44,6 +44,7 @@ func createMockArgumentsForValidatorSCWithSystemScAddresses( MinStepValue: "10", MinStakeValue: "1", UnBondPeriod: 1, + UnBondPeriodSupernova: 2, UnBondPeriodInEpochs: 1, NumRoundsWithoutBleed: 1, MaximumPercentageToBleed: 1, @@ -2656,7 +2657,7 @@ func TestValidatorStakingSC_ExecuteUnBondBeforePeriodEnds(t *testing.T) { }, }, BlsPubKeys: [][]byte{blsPubKey}, - TotalStakeValue: big.NewInt(1000), //in v1 this was still set to the unstaked value + TotalStakeValue: big.NewInt(1000), // in v1 this was still set to the unstaked value LockedStake: big.NewInt(0), TotalUnstaked: big.NewInt(1000), }, @@ -2681,7 +2682,7 @@ func TestValidatorStakingSC_ExecuteUnBondBeforePeriodEnds(t *testing.T) { assert.Equal(t, vmcommon.Ok, retCode) assert.True(t, strings.Contains(eei.returnMessage, "unBond is not possible")) assert.True(t, strings.Contains(eei.returnMessage, "unBond period did not pass")) - assert.True(t, len(eei.GetStorage(caller)) != 0) //should have not removed the account data + assert.True(t, len(eei.GetStorage(caller)) != 0) // should have not removed the account data } func TestValidatorSC_ExecuteUnBondBeforePeriodEndsForV2(t *testing.T) { @@ -2741,7 +2742,7 @@ func TestValidatorSC_ExecuteUnBondBeforePeriodEndsForV2(t *testing.T) { assert.Equal(t, vmcommon.Ok, retCode) assert.True(t, strings.Contains(eei.returnMessage, "unBond is not possible")) assert.True(t, strings.Contains(eei.returnMessage, "unBond period did not pass")) - assert.True(t, len(eei.GetStorage(caller)) != 0) //should have not removed the account data + assert.True(t, len(eei.GetStorage(caller)) != 0) // should have not removed the account data } func TestValidatorStakingSC_ExecuteUnBond(t *testing.T) { @@ -3018,14 +3019,14 @@ func TestValidatorStakingSC_Claim(t *testing.T) { sc, _ := NewValidatorSmartContract(args) - //do claim should ret error + // do claim should ret error doClaim(t, sc, stakerAddress, receiverAddr, vmcommon.UserError) - //do stake + // do stake nodePrice, _ := big.NewInt(0).SetString(args.StakingSCConfig.GenesisNodePrice, 10) stake(t, sc, nodePrice, receiverAddr, stakerAddress, stakerPubKey, nodesToRunBytes) - //do claim all stake is locked should return Ok + // do claim all stake is locked should return Ok doClaim(t, sc, stakerAddress, receiverAddr, vmcommon.Ok) // do stake to add more money but not lock the stake @@ -3353,7 +3354,7 @@ func TestValidatorStakingSC_ChangeRewardAddress(t *testing.T) { sc, _ := NewValidatorSmartContract(args) - //change reward address should error nil arguments + // change reward address should error nil arguments changeRewardAddress(t, sc, stakerAddress, nil, vmcommon.UserError) // change reward address should error wrong address eei.returnMessage = "" @@ -3362,7 +3363,7 @@ func TestValidatorStakingSC_ChangeRewardAddress(t *testing.T) { // change reward address should error because address is not belongs to any validator newRewardAddr := []byte("newAddr11") changeRewardAddress(t, sc, stakerAddress, newRewardAddr, vmcommon.UserError) - //do stake + // do stake nodePrice, _ := big.NewInt(0).SetString(args.StakingSCConfig.GenesisNodePrice, 10) stake(t, sc, nodePrice, receiverAddr, stakerAddress, stakerPubKey, nodesToRunBytes) @@ -4724,7 +4725,7 @@ func TestValidatorStakingSC_UnStakeUnBondPaused(t *testing.T) { sc, _ := NewValidatorSmartContract(args) - //do stake + // do stake nodePrice, _ := big.NewInt(0).SetString(args.StakingSCConfig.GenesisNodePrice, 10) stake(t, sc, nodePrice, receiverAddr, stakerAddress, stakerPubKey, nodesToRunBytes)