diff --git a/.github/workflows/tokenomics_ci.yml b/.github/workflows/tokenomics_ci.yml index de2819d052..0c5b78e779 100644 --- a/.github/workflows/tokenomics_ci.yml +++ b/.github/workflows/tokenomics_ci.yml @@ -25,6 +25,25 @@ jobs: with: github-token: ${{ github.token }} + - name: 'Setup jq' + uses: dcarbone/install-jq-action@v2.1.0 + with: + version: '1.7' + force: 'false' + + - name: "Create Tenderly virtual testnet" + run: | + echo "TENDERLY_CREATION_INFO=$(curl -X POST \ + -H "x-access-key: ${{ secrets.TENDERLY_SECRET }}" \ + -H "Content-Type: application/json" \ + -d '{"slug":"mainnet-dev-${{ env.RUNNER_NUMBER }}-${{ github.run_id }}","displayName":"mainnet-dev-${{ env.RUNNER_NUMBER }}-${{ github.run_id }}","description":"","visibility":"TEAM","tags":{"purpose":"development"},"networkConfig":{"networkId":"1","blockNumber":"18512782","chainConfig":{"chainId":"1"},"baseFeePerGas":"1"},"explorerConfig":{"enabled":false,"verificationVisibility":"bytecode"},"syncState":false}' \ + https://api.tenderly.co/api/v1/account/zus_network/project/project/testnet/container)" >> $GITHUB_ENV + + - name: "Parse Tenderly virtual testnet creation transaction result" + run: | + echo "TENDERLY_VIRTUAL_TESTNET_ID=$(echo '${{ env.TENDERLY_CREATION_INFO }}' | jq -r '.container.id')" >> $GITHUB_ENV + echo "TENDERLY_VIRTUAL_TESTNET_RPC_ID=$(echo '${{ env.TENDERLY_CREATION_INFO }}' | jq -r '.container.connectivityConfig.endpoints[0].id')" >> $GITHUB_ENV + - name: "Config: Run tests against existing 0Chain network" if: github.event_name == 'workflow_dispatch' && github.event.inputs.existing_network != '' run: | @@ -44,9 +63,134 @@ jobs: echo "TOKENOMICS_SLASH_REWARD_TESTS=$(echo $(([ -z 'TestBlobberSlashPenalty' ] && echo '') || echo 'TestBlobberSlashPenalty'))" >> $GITHUB_ENV echo "TOKENOMICS_BLOCK_REWARD_TESTS=$(echo $(([ -z 'TestBlockRewardsForBlobbers' ] && echo '') || echo 'TestBlockRewardsForBlobbers'))" >> $GITHUB_ENV echo "TOKENOMICS_MIN_STAKE_TESTS=$(echo $(([ -z 'TestMinStakeForProviders' ] && echo '') || echo 'TestMinStakeForProviders'))" >> $GITHUB_ENV + echo "ENTERPRISE_BLOBBER_CREATE_ALLOCATION_TESTS=$(echo $(([ -z 'TestCreateEnterpriseAllocation' ] && echo '') || echo 'TestCreateEnterpriseAllocation'))" >> $GITHUB_ENV + echo "ENTERPRISE_BLOBBER_UPDATE_ALLOCATION_TESTS=$(echo $(([ -z 'TestUpdateEnterpriseAllocation' ] && echo '') || echo 'TestUpdateEnterpriseAllocation'))" >> $GITHUB_ENV + echo "ENTERPRISE_BLOBBER_REPLACE_ALLOCATION_TESTS=$(echo $(([ -z 'TestReplaceEnterpriseBlobberAllocation' ] && echo '') || echo 'TestReplaceEnterpriseBlobberAllocation'))" >> $GITHUB_ENV + echo "ENTERPRISE_BLOBBER_CANCEL_ALLOCATION_TESTS=$(echo $(([ -z 'TestCancelEnterpriseAllocation' ] && echo '') || echo 'TestCancelEnterpriseAllocation'))" >> $GITHUB_ENV + echo "ENTERPRISE_BLOBBER_FINALIZE_ALLOCATION_TESTS=$(echo $(([ -z 'TestFinalizeEnterpriseAllocation' ] && echo '') || echo 'TestFinalizeEnterpriseAllocation'))" >> $GITHUB_ENV echo "CLIENT_THROTTLING=$(echo $(([ -z 'TestClientThrottling' ] && echo '') || echo 'TestClientThrottling'))" >> $GITHUB_ENV echo "REPO_SNAPSHOTS_BRANCH=current-sprint" >> $GITHUB_ENV + - name: "Deploy 0Chain with enterprise blobbers" + if: github.event_name == 'push' || github.event.inputs.existing_network == '' + uses: 0chain/actions/deploy-0chain@feature/enterprise-blobber + with: + repo_snapshots_branch: "${{ env.REPO_SNAPSHOTS_BRANCH }}" + kube_config: ${{ secrets[format('DEV{0}KC', env.RUNNER_NUMBER)] }} + teardown_condition: "TESTS_PASSED" + SUBGRAPH_API_URL: ${{ secrets.SUBGRAPH_API_URL }} + TENDERLY_VIRTUAL_TESTNET_RPC_ID: ${{ env.TENDERLY_VIRTUAL_TESTNET_RPC_ID }} + graphnode_sc: ${{ secrets.GRAPHNODE_SC }} + graphnode_network: ${{ secrets.GRAPHNODE_NETWORK }} + graphnode_ethereum_node_url: https://virtual.mainnet.rpc.tenderly.co/${{ env.TENDERLY_VIRTUAL_TESTNET_RPC_ID }} + svc_account_secret: ${{ secrets.SVC_ACCOUNT_SECRET }} + + - name: "Run Enterprise blobber create allocation tests" + uses: 0chain/actions/run-system-tests-tokenomics@master + with: + repo_snapshots_branch: "${{ env.REPO_SNAPSHOTS_BRANCH }}" + network: ${{ env.NETWORK_URL }} + svc_account_secret: ${{ secrets.SVC_ACCOUNT_SECRET }} + deploy_report_page: true + system_tests_branch: feat/enterprise-blobber-tests + archive_results: true + run_flaky_tests: false + run_api_system_tests: false + run_cli_system_tests: false + run_tokenomics_system_tests: true + tokenomics_test_filter: ${{ env.ENTERPRISE_BLOBBER_CREATE_ALLOCATION_TESTS }} + run_smoke_tests: ${{ inputs.run_smoke_tests }} + S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} + S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} + TENDERLY_VIRTUAL_TESTNET_RPC_ID: ${{ env.TENDERLY_VIRTUAL_TESTNET_RPC_ID }} + + - name: "Run Enterprise blobber update allocation tests" + uses: 0chain/actions/run-system-tests-tokenomics@master + with: + repo_snapshots_branch: "${{ env.REPO_SNAPSHOTS_BRANCH }}" + network: ${{ env.NETWORK_URL }} + svc_account_secret: ${{ secrets.SVC_ACCOUNT_SECRET }} + deploy_report_page: true + archive_results: true + run_flaky_tests: false + system_tests_branch: feat/enterprise-blobber-tests + run_api_system_tests: false + run_cli_system_tests: false + run_tokenomics_system_tests: true + tokenomics_test_filter: ${{ env.ENTERPRISE_BLOBBER_UPDATE_ALLOCATION_TESTS }} + TENDERLY_VIRTUAL_TESTNET_ID: "" + run_smoke_tests: ${{ inputs.run_smoke_tests }} + S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} + S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} + TENDERLY_VIRTUAL_TESTNET_RPC_ID: ${{ env.TENDERLY_VIRTUAL_TESTNET_RPC_ID }} + + - name: "Run Enterprise blobber replace blobber allocation tests" + uses: 0chain/actions/run-system-tests-tokenomics@master + with: + repo_snapshots_branch: "${{ env.REPO_SNAPSHOTS_BRANCH }}" + network: ${{ env.NETWORK_URL }} + svc_account_secret: ${{ secrets.SVC_ACCOUNT_SECRET }} + deploy_report_page: true + archive_results: true + system_tests_branch: feat/enterprise-blobber-tests + run_flaky_tests: false + run_api_system_tests: false + run_cli_system_tests: false + run_tokenomics_system_tests: true + tokenomics_test_filter: ${{ env.ENTERPRISE_BLOBBER_REPLACE_ALLOCATION_TESTS }} + TENDERLY_VIRTUAL_TESTNET_ID: "" + run_smoke_tests: ${{ inputs.run_smoke_tests }} + S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} + S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} + TENDERLY_VIRTUAL_TESTNET_RPC_ID: ${{ env.TENDERLY_VIRTUAL_TESTNET_RPC_ID }} + + - name: "Run Enterprise blobber cancel allocation tests" + uses: 0chain/actions/run-system-tests-tokenomics@master + with: + repo_snapshots_branch: "${{ env.REPO_SNAPSHOTS_BRANCH }}" + network: ${{ env.NETWORK_URL }} + svc_account_secret: ${{ secrets.SVC_ACCOUNT_SECRET }} + deploy_report_page: true + archive_results: true + run_flaky_tests: false + system_tests_branch: feat/enterprise-blobber-tests + run_api_system_tests: false + run_cli_system_tests: false + run_tokenomics_system_tests: true + tokenomics_test_filter: ${{ env.ENTERPRISE_BLOBBER_CANCEL_ALLOCATION_TESTS }} + TENDERLY_VIRTUAL_TESTNET_ID: "" + run_smoke_tests: ${{ inputs.run_smoke_tests }} + S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} + S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} + TENDERLY_VIRTUAL_TESTNET_RPC_ID: ${{ env.TENDERLY_VIRTUAL_TESTNET_RPC_ID }} + + - name: "Run Enterprise blobber finalize allocation tests" + uses: 0chain/actions/run-system-tests-tokenomics@master + with: + repo_snapshots_branch: "${{ env.REPO_SNAPSHOTS_BRANCH }}" + network: ${{ env.NETWORK_URL }} + svc_account_secret: ${{ secrets.SVC_ACCOUNT_SECRET }} + deploy_report_page: true + system_tests_branch: feat/enterprise-blobber-tests + archive_results: true + run_flaky_tests: false + run_api_system_tests: false + run_cli_system_tests: false + run_tokenomics_system_tests: true + tokenomics_test_filter: ${{ env.ENTERPRISE_BLOBBER_FINALIZE_ALLOCATION_TESTS }} + TENDERLY_VIRTUAL_TESTNET_ID: "" + run_smoke_tests: ${{ inputs.run_smoke_tests }} + S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} + S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} + TENDERLY_VIRTUAL_TESTNET_RPC_ID: ${{ env.TENDERLY_VIRTUAL_TESTNET_RPC_ID }} + + - name: "Remove Tenderly virtual testnet" + if: always() + run: | + curl -X DELETE \ + -H "x-access-key: ${{ secrets.TENDERLY_SECRET }}" \ + -H "Content-Type: application/json" \ + https://api.tenderly.co/api/v1/account/zus_network/project/project/testnet/container/${{ env.TENDERLY_VIRTUAL_TESTNET_ID }} - name: "Deploy 0Chain" if: github.event_name == 'push' || github.event.inputs.existing_network == '' @@ -261,8 +405,8 @@ jobs: notify_slack_on_failure: - runs-on: [self-hosted, arc-runner] - needs: [system-tests] + runs-on: [ self-hosted, arc-runner ] + needs: [ system-tests ] if: always() && (needs.system-tests.result == 'failure') steps: - name: "Notify Slack" @@ -279,8 +423,8 @@ jobs: curl -X POST -H 'Content-type: application/json' --data "${payload}" ${{ secrets.DEVOPS_CHANNEL_WEBHOOK_URL }} notify_slack_on_success: - runs-on: [self-hosted, arc-runner] - needs: [system-tests] + runs-on: [ self-hosted, arc-runner ] + needs: [ system-tests ] if: always() && (needs.system-tests.result == 'success') steps: - name: "Notify Slack" diff --git a/go.mod b/go.mod index 39c396728e..76f1be94e0 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.22.1 require ( github.com/0chain/errors v1.0.3 - github.com/0chain/gosdk v1.17.3 + github.com/0chain/gosdk v1.17.5 github.com/go-resty/resty/v2 v2.7.0 github.com/herumi/bls-go-binary v1.31.0 github.com/shopspring/decimal v1.3.1 diff --git a/go.sum b/go.sum index 42347945af..2e383915b9 100644 --- a/go.sum +++ b/go.sum @@ -40,8 +40,8 @@ github.com/0chain/common v0.0.6-0.20230127095721-8df4d1d72565 h1:z+DtCR8mBsjPnEs github.com/0chain/common v0.0.6-0.20230127095721-8df4d1d72565/go.mod h1:UyDC8Qyl5z9lGkCnf9RHJPMektnFX8XtCJZHXCCVj8E= github.com/0chain/errors v1.0.3 h1:QQZPFxTfnMcRdt32DXbzRQIfGWmBsKoEdszKQDb0rRM= github.com/0chain/errors v1.0.3/go.mod h1:xymD6nVgrbgttWwkpSCfLLEJbFO6iHGQwk/yeSuYkIc= -github.com/0chain/gosdk v1.17.3 h1:nL6x1saSwTku1UFDYoX/uKJO57VQTgowp6id+7UHb7g= -github.com/0chain/gosdk v1.17.3/go.mod h1:y7Ucdmv40VltqulZnncMNjNQ4piX5Dta5ujNmPmXnxg= +github.com/0chain/gosdk v1.17.5 h1:WusXPOj+lyK9XBUY1JPjBqPmkGV4A0J6bqsRluseusg= +github.com/0chain/gosdk v1.17.5/go.mod h1:y7Ucdmv40VltqulZnncMNjNQ4piX5Dta5ujNmPmXnxg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Luzifer/go-openssl/v3 v3.1.0 h1:QqKqo6kYXGGUsvtUoCpRZm8lHw+jDfhbzr36gVj+/gw= diff --git a/tests/api_tests/repair_allocation_test.go b/tests/api_tests/repair_allocation_test.go index 3995adbb6b..d27b8ec916 100644 --- a/tests/api_tests/repair_allocation_test.go +++ b/tests/api_tests/repair_allocation_test.go @@ -353,8 +353,8 @@ func TestRepairSize(testSetup *testing.T) { wallet := createWallet(t) sdkClient.SetWallet(t, wallet) apiClient.CreateReadPool(t, wallet, 0.5, client.TxSuccessfulStatus) - - t.RunSequentiallyWithTimeout("repair size in case of no blobber failure should be zero", 5 * time.Minute, func(t *test.SystemTest) { + + t.RunSequentiallyWithTimeout("repair size in case of no blobber failure should be zero", 5*time.Minute, func(t *test.SystemTest) { // create allocation with default blobber requirements blobberRequirements := model.DefaultBlobberRequirements(wallet.Id, wallet.PublicKey) allocationBlobbers := apiClient.GetAllocationBlobbers(t, wallet, &blobberRequirements, client.HttpOkStatus) @@ -362,9 +362,9 @@ func TestRepairSize(testSetup *testing.T) { t.Logf("allocationID: %v", allocationID) // create and upload a file of 2KB to allocation. - op := sdkClient.AddUploadOperation(t, "", "", int64(1024 * 2)) + op := sdkClient.AddUploadOperation(t, "", "", int64(1024*2)) sdkClient.MultiOperation(t, allocationID, []sdk.OperationRequest{op}) - + // assert both upload and download size should be zero alloc, err := sdk.GetAllocation(allocationID) require.NoErrorf(t, err, "allocation ID %v is not found", allocationID) @@ -374,8 +374,8 @@ func TestRepairSize(testSetup *testing.T) { require.Equal(t, uint64(0), rs.UploadSize, "upload size doesn't match") require.Equal(t, uint64(0), rs.DownloadSize, "download size doesn't match") }) - - t.RunSequentiallyWithTimeout("repair size on single blobber failure should match", 5 * time.Minute, func(t *test.SystemTest) { + + t.RunSequentiallyWithTimeout("repair size on single blobber failure should match", 5*time.Minute, func(t *test.SystemTest) { // create allocation with default blobber requirements blobberRequirements := model.DefaultBlobberRequirements(wallet.Id, wallet.PublicKey) blobberRequirements.DataShards = 2 @@ -390,18 +390,18 @@ func TestRepairSize(testSetup *testing.T) { alloc, err := sdk.GetAllocation(allocationID) require.NoErrorf(t, err, "allocation ID %v is not found", allocationID) alloc.Blobbers[0].Baseurl = "http://0zus.com/" - op := sdkClient.AddUploadOperation(t, "", "", int64(1024 * 2)) + op := sdkClient.AddUploadOperation(t, "", "", int64(1024*2)) sdkClient.MultiOperation(t, allocationID, []sdk.OperationRequest{op}, client.WithRepair(alloc.Blobbers)) - + // assert upload and download size should be 1KB and 2KB respectively rs, err := alloc.RepairSize("/") require.Nil(t, err) t.Logf("repair size: %v", rs) require.Equal(t, uint64(1024), rs.UploadSize, "upload size doesn't match") - require.Equal(t, uint64(1024 * 2), rs.DownloadSize, "download size doesn't match") + require.Equal(t, uint64(1024*2), rs.DownloadSize, "download size doesn't match") }) - t.RunSequentiallyWithTimeout("repair size with nested directories and two blobber failure should match", 5 * time.Minute, func(t *test.SystemTest) { + t.RunSequentiallyWithTimeout("repair size with nested directories and two blobber failure should match", 5*time.Minute, func(t *test.SystemTest) { // create allocation with default blobber requirements blobberRequirements := model.DefaultBlobberRequirements(wallet.Id, wallet.PublicKey) blobberRequirements.DataShards = 2 @@ -419,31 +419,31 @@ func TestRepairSize(testSetup *testing.T) { ops := []sdk.OperationRequest{ sdkClient.AddUploadOperationWithPath(t, allocationID, "/dir1/"), sdkClient.AddUploadOperationWithPath(t, allocationID, "/dir1/"), - sdkClient.AddUploadOperationWithPath(t, allocationID, "/"), + sdkClient.AddUploadOperationWithPath(t, allocationID, "/"), sdkClient.AddUploadOperationWithPath(t, allocationID, "/"), } sdkClient.MultiOperation(t, allocationID, ops, client.WithRepair(alloc.Blobbers)) - + // assert both upload and download size should be 2KB in /dir1 rs, err := alloc.RepairSize("/dir1") require.Nilf(t, err, "error getting repair size in /dir1: %v", err) t.Logf("repair size: %v", rs) - require.Equal(t, uint64(1024 * 2), rs.UploadSize, "upload size in directory /dir1 doesn't match") - require.Equal(t, uint64(1024 * 2), rs.DownloadSize, "download size in directory dir1 doesn't match") - + require.Equal(t, uint64(1024*2), rs.UploadSize, "upload size in directory /dir1 doesn't match") + require.Equal(t, uint64(1024*2), rs.DownloadSize, "download size in directory dir1 doesn't match") + // with trailing slash - // assert both upload and download size should be 2KB in /dir1/ + // assert both upload and download size should be 2KB in /dir1/ rs, err = alloc.RepairSize("/dir1/") require.Nilf(t, err, "error getting repair size in /dir1/: %v", err) t.Logf("repair size: %v", rs) - require.Equal(t, uint64(1024 * 2), rs.UploadSize, "upload size in directory /dir1/ doesn't match") - require.Equal(t, uint64(1024 * 2), rs.DownloadSize, "download size in directory /dir1/ doesn't match") - + require.Equal(t, uint64(1024*2), rs.UploadSize, "upload size in directory /dir1/ doesn't match") + require.Equal(t, uint64(1024*2), rs.DownloadSize, "download size in directory /dir1/ doesn't match") + // assert both upload and download size should be 4KB in root directory rs, err = alloc.RepairSize("/") require.Nilf(t, err, "error getting repair size in /: %v", err) t.Logf("repair size: %v", rs) - require.Equal(t, uint64(1024 * 4), rs.UploadSize, "upload size in root directory doesn't match") - require.Equal(t, uint64(1024 * 4), rs.DownloadSize, "download size in root directory doesn't match") + require.Equal(t, uint64(1024*4), rs.UploadSize, "upload size in root directory doesn't match") + require.Equal(t, uint64(1024*4), rs.DownloadSize, "download size in root directory doesn't match") }) -} \ No newline at end of file +} diff --git a/tests/cli_tests/sdk.go b/tests/cli_tests/sdk.go index f94a327106..c27bf92054 100644 --- a/tests/cli_tests/sdk.go +++ b/tests/cli_tests/sdk.go @@ -50,21 +50,30 @@ func InitSDK(wallet, configFile string) error { return err } -// GetBlobberNotPartOfAllocation returns a blobber not part of current allocation -func GetBlobberNotPartOfAllocation(walletname, configFile, allocationID string) (string, error) { - err := InitSDK(walletname, configFile) +// GetBlobberIDNotPartOfAllocation returns a blobber not part of current allocation +func GetBlobberIDNotPartOfAllocation(walletname, configFile, allocationID string) (string, error) { + blobber, err := getBlobberNotPartOfAllocation(walletname, configFile, allocationID) + if err != nil { return "", err } + return string(blobber.ID), err +} + +func getBlobberNotPartOfAllocation(walletname, configFile, allocationID string) (*sdk.Blobber, error) { + err := InitSDK(walletname, configFile) + if err != nil { + return nil, err + } a, err := sdk.GetAllocation(allocationID) if err != nil { - return "", err + return nil, err } blobbers, err := sdk.GetBlobbers(true, false) if err != nil { - return "", err + return nil, err } allocationBlobsMap := map[string]bool{} @@ -74,11 +83,20 @@ func GetBlobberNotPartOfAllocation(walletname, configFile, allocationID string) for _, blobber := range blobbers { if _, ok := allocationBlobsMap[string(blobber.ID)]; !ok { - return string(blobber.ID), nil + return blobber, nil } } - return "", fmt.Errorf("failed to get blobber not part of allocation") + return nil, fmt.Errorf("failed to get blobber not part of allocation") +} + +// GetBlobberIdAndUrlNotPartOfAllocation returns a blobber not part of current allocation +func GetBlobberIdAndUrlNotPartOfAllocation(walletName, configFile, allocationID string) (blobberId, blobberUrl string, err error) { + blobber, err := getBlobberNotPartOfAllocation(walletName, configFile, allocationID) + if err != nil || blobber == nil { + return "", "", err + } + return string(blobber.ID), blobber.BaseURL, err } func generateRandomIndex(sliceLen int64) (*big.Int, error) { diff --git a/tests/cli_tests/zboxcli_repair_test.go b/tests/cli_tests/zboxcli_repair_test.go index 9fa1123324..37cd32b417 100644 --- a/tests/cli_tests/zboxcli_repair_test.go +++ b/tests/cli_tests/zboxcli_repair_test.go @@ -16,8 +16,8 @@ import ( func TestRepairSize(testSetup *testing.T) { t := test.NewSystemTest(testSetup) - - t.RunSequentiallyWithTimeout("repair size should work", 5 * time.Minute, func(t *test.SystemTest) { + + t.RunSequentiallyWithTimeout("repair size should work", 5*time.Minute, func(t *test.SystemTest) { allocSize := int64(1 * MB) fileSize := int64(512 * KB) @@ -55,7 +55,7 @@ func TestRepairSize(testSetup *testing.T) { require.Nilf(t, err, "error unmarshal repair size: %v", err) require.Equal(t, uint64(0), rs.UploadSize, "upload size should be zero") require.Equal(t, uint64(0), rs.DownloadSize, "download size should be zero") - + // optional repairpath output, err = getRepairSize(t, configPath, map[string]interface{}{ "allocation": allocationID, @@ -67,7 +67,6 @@ func TestRepairSize(testSetup *testing.T) { require.Equal(t, uint64(0), rs2.UploadSize, "upload size should be zero") require.Equal(t, uint64(0), rs2.DownloadSize, "download size should be zero") }) - } func getRepairSize(t *test.SystemTest, cliConfigFilename string, param map[string]interface{}, retry bool) ([]string, error) { @@ -90,4 +89,4 @@ func getRepairSizeForWallet(t *test.SystemTest, wallet, cliConfigFilename string } else { return cliutils.RunCommandWithoutRetry(cmd) } -} \ No newline at end of file +} diff --git a/tests/cli_tests/zboxcli_restricted_blobber_test.go b/tests/cli_tests/zboxcli_restricted_blobber_test.go index 0941779c90..c9b04a8f65 100644 --- a/tests/cli_tests/zboxcli_restricted_blobber_test.go +++ b/tests/cli_tests/zboxcli_restricted_blobber_test.go @@ -202,7 +202,7 @@ func TestRestrictedBlobbers(testSetup *testing.T) { wd, _ := os.Getwd() walletFile := filepath.Join(wd, "config", escapedTestName(t)+"_wallet.json") configFile := filepath.Join(wd, "config", configPath) - blobberID, err := GetBlobberNotPartOfAllocation(walletFile, configFile, allocationID) + blobberID, err := GetBlobberIDNotPartOfAllocation(walletFile, configFile, allocationID) require.Nil(t, err) setupWallet(t, configPath) @@ -272,7 +272,7 @@ func TestRestrictedBlobbers(testSetup *testing.T) { walletFile := filepath.Join(wd, "config", escapedTestName(t)+"_wallet.json") configFile := filepath.Join(wd, "config", configPath) - blobberID, err := GetBlobberNotPartOfAllocation(walletFile, configFile, allocationID) + blobberID, err := GetBlobberIDNotPartOfAllocation(walletFile, configFile, allocationID) require.Nil(t, err) removeBlobber, err := GetRandomBlobber(walletFile, configFile, allocationID, blobberID) require.Nil(t, err) diff --git a/tests/cli_tests/zboxcli_update_allocation_test.go b/tests/cli_tests/zboxcli_update_allocation_test.go index 4b70ca551c..47b882816f 100644 --- a/tests/cli_tests/zboxcli_update_allocation_test.go +++ b/tests/cli_tests/zboxcli_update_allocation_test.go @@ -700,7 +700,7 @@ func TestUpdateAllocation(testSetup *testing.T) { wd, _ := os.Getwd() walletFile := filepath.Join(wd, "config", escapedTestName(t)+"_wallet.json") configFile := filepath.Join(wd, "config", configPath) - blobberID, err := GetBlobberNotPartOfAllocation(walletFile, configFile, allocationID) + blobberID, err := GetBlobberIDNotPartOfAllocation(walletFile, configFile, allocationID) require.Nil(t, err) params := createParams(map[string]interface{}{ @@ -746,7 +746,7 @@ func TestUpdateAllocation(testSetup *testing.T) { walletFile := filepath.Join(wd, "config", escapedTestName(t)+"_wallet.json") configFile := filepath.Join(wd, "config", configPath) - addBlobber, err := GetBlobberNotPartOfAllocation(walletFile, configFile, allocationID) + addBlobber, err := GetBlobberIDNotPartOfAllocation(walletFile, configFile, allocationID) require.Nil(t, err) removeBlobber, err := GetRandomBlobber(walletFile, configFile, allocationID, addBlobber) require.Nil(t, err) diff --git a/tests/tokenomics_tests/config/config.yaml b/tests/tokenomics_tests/config/config.yaml index 6607aaa42c..0df3e82b3f 100644 --- a/tests/tokenomics_tests/config/config.yaml +++ b/tests/tokenomics_tests/config/config.yaml @@ -1,4 +1,4 @@ -block_worker: https://test.zus.network/dns +block_worker: https://dev.zus.network/dns confirmation_chain_length: 3 ethereum_node_url: "https://rpc.tenderly.co/fork/05077a9d-3973-4409-a3bf-eee719ca2806" min_confirmation: 50 diff --git a/tests/tokenomics_tests/config/tokenomics_tests_config.yaml b/tests/tokenomics_tests/config/tokenomics_tests_config.yaml index 2d32d20d66..66f36bec98 100644 --- a/tests/tokenomics_tests/config/tokenomics_tests_config.yaml +++ b/tests/tokenomics_tests/config/tokenomics_tests_config.yaml @@ -1,6 +1,6 @@ -block_worker: https://dev.0chain.net -0box_url: https://0box.dev.0chain.net -zs3_server_url: https://dev.0chain.net/zs3server/ +block_worker: https://dev.zus.network/dns +0box_url: https://0box.dev.zus.network/dns +zs3_server_url: https://dev.zus.network/zs3server/ ethereum_node_url: "https://rpc.tenderly.co/fork/e753f650-9968-4f58-af77-5f02ace53cdc" confirmation_chain_length: 3 min_confirmation: 50 diff --git a/tests/tokenomics_tests/config/wallets/zbox_team_wallet.json b/tests/tokenomics_tests/config/wallets/zbox_team_wallet.json new file mode 100644 index 0000000000..e17142369b --- /dev/null +++ b/tests/tokenomics_tests/config/wallets/zbox_team_wallet.json @@ -0,0 +1 @@ +{"client_id":"65b32a635cffb6b6f3c73f09da617c29569a5f690662b5be57ed0d994f234335","client_key":"381fb2e8298680fc9c71e664821394adaa5db4537456aaa257ef4388ba8c090e476c89fbcd2c8a1b0871ba36b7001f778d178c8dfff1504fbafb43f7ee3b3c92","keys":[{"public_key":"381fb2e8298680fc9c71e664821394adaa5db4537456aaa257ef4388ba8c090e476c89fbcd2c8a1b0871ba36b7001f778d178c8dfff1504fbafb43f7ee3b3c92","private_key":"85e2119f494cd40ca524f6342e8bdb7bef2af03fe9a08c8d9c1d9f14d6c64f14"}],"mnemonics":"immense express entire board prize loop mushroom wild sunset stuff mixture analyst video that trouble soccer elder fall portion arrow eagle leaf enforce mesh","version":"1.0","date_created":"2023-05-01T18:40:02Z","nonce":0} \ No newline at end of file diff --git a/tests/tokenomics_tests/enterprise_blobber_cancel_allocation_test.go b/tests/tokenomics_tests/enterprise_blobber_cancel_allocation_test.go new file mode 100644 index 0000000000..0d9738e02f --- /dev/null +++ b/tests/tokenomics_tests/enterprise_blobber_cancel_allocation_test.go @@ -0,0 +1,378 @@ +package tokenomics_tests + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + "testing" + "time" + + "github.com/0chain/system_test/tests/cli_tests" + + "github.com/0chain/system_test/internal/api/util/test" + cliutils "github.com/0chain/system_test/internal/cli/util" + "github.com/0chain/system_test/tests/tokenomics_tests/utils" + "github.com/stretchr/testify/require" +) + +var ( + cancelAllocationRegex = regexp.MustCompile(`^Allocation canceled with txId : [a-f0-9]{64}$`) +) + +func TestCancelEnterpriseAllocation(testSetup *testing.T) { + t := test.NewSystemTest(testSetup) + + t.Parallel() + + t.TestSetup("set storage config to use time_unit as 10 minutes", func() { + output, err := utils.UpdateStorageSCConfig(t, scOwnerWallet, map[string]string{ + "time_unit": "10m", + }, true) + require.Nil(t, err, "Error updating sc config", strings.Join(output, "\n")) + }) + + t.Cleanup(func() { + output, err := utils.UpdateStorageSCConfig(t, scOwnerWallet, map[string]string{ + "time_unit": "1h", + }, true) + require.Nil(t, err, "Error updating sc config", strings.Join(output, "\n")) + }) + + t.RunWithTimeout("Cancel allocation after waiting for 7 minutes check refund amount.", time.Minute*15, func(t *test.SystemTest) { + utils.SetupWalletWithCustomTokens(t, configPath, 10) + + wallet, err := utils.GetWalletForName(t, configPath, utils.EscapedTestName(t)) + require.Nil(t, err, "Error fetching wallet") + require.Equal(t, wallet.ClientID, wallet.ClientID, "Error getting wallet with name %v") + + beforeBalance := utils.GetBalanceFromSharders(t, wallet.ClientID) + + // Create allocation + amountTotalLockedToAlloc := int64(5e10) + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + params := map[string]interface{}{"size": 1 * GB, "lock": amountTotalLockedToAlloc / 1e10, "enterprise": true, "blobber_auth_tickets": blobberAuthTickets, "preferred_blobbers": blobberIds} + allocOutput, err := utils.CreateNewEnterpriseAllocation(t, configPath, createParams(params)) + require.Nil(t, err, "Error creating allocation") + + allocationID, err := utils.GetAllocationID(strings.Join(allocOutput, "\n")) + require.Nil(t, err, "Error fetching allocation id %v", strings.Join(allocOutput, "\n")) + + beforeAlloc := utils.GetAllocation(t, allocationID) + + afterBalance := utils.GetBalanceFromSharders(t, wallet.ClientID) + require.Equal(t, beforeBalance-amountTotalLockedToAlloc-1e8, afterBalance, "Balance should be locked to allocation") // 1e8 is transaction fee + beforeBalance = afterBalance + + t.Log("Waiting for 7 minutes ....") + waitForTimeInMinutesWhileLogging(t, 7) + + // Cancel the allocation + output, err := cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Cancel allocation failed", strings.Join(output, "\n")) + + // Validate that the allocation is canceled and check refund + utils.AssertOutputMatchesAllocationRegex(t, cancelAllocationRegex, output[0]) + + afterAlloc := utils.GetAllocation(t, allocationID) + require.True(t, afterAlloc.Finalized, "Allocation should be expired") + require.Equal(t, int64(0), afterAlloc.WritePool, "Write pool balance should be 0") + + afterBalance = utils.GetBalanceFromSharders(t, wallet.ClientID) + + // Calculate expected refund + timeUnitInSeconds := int64(600) // 10 minutes + durationOfUsedInSeconds := afterAlloc.ExpirationDate - beforeAlloc.StartTime + realCostOfAlloc := costOfAlloc(&beforeAlloc) + expectedPaymentToBlobbers := realCostOfAlloc * durationOfUsedInSeconds / timeUnitInSeconds + expectedRefund := amountTotalLockedToAlloc - expectedPaymentToBlobbers + + t.Logf("Time unit in seconds: %v", timeUnitInSeconds) + t.Logf("Duration of used in seconds: %v", durationOfUsedInSeconds) + t.Logf("Real cost of allocation: %v", realCostOfAlloc) + t.Logf("Expected payment to blobbers: %v", expectedPaymentToBlobbers) + t.Logf("Expected refund: %v", expectedRefund) + t.Logf("Before balance: %v", beforeBalance) + t.Logf("After balance: %v", afterBalance) + + require.InEpsilon(t, beforeBalance+expectedRefund-1e8, afterBalance, 0.01, "Refund should be credited to client balance after cancel allocation") // 1e8 is transaction fee + + rewardQuery := fmt.Sprintf("allocation_id='%s' AND reward_type=%d", allocationID, EnterpriseBlobberReward) + enterpriseReward, err := getQueryRewards(t, rewardQuery) + require.Nil(t, err) + + require.InEpsilon(t, expectedPaymentToBlobbers, enterpriseReward.TotalReward, 0.01, "Enterprise blobber reward doesn't match") + }) + + t.RunWithTimeout("Cancel allocation after updating duration check refund amount.", time.Minute*15, func(t *test.SystemTest) { + // Setup, wallet creation, and initial balance retrieval + utils.SetupWalletWithCustomTokens(t, configPath, 10) + wallet, err := utils.GetWalletForName(t, configPath, utils.EscapedTestName(t)) + require.Nil(t, err, "Error getting wallet") + beforeBalance := utils.GetBalanceFromSharders(t, wallet.ClientID) + + // Create allocation + amountTotalLockedToAlloc := int64(5e10) + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + params := map[string]interface{}{"size": 1 * GB, "lock": amountTotalLockedToAlloc / 1e10, "enterprise": true, "blobber_auth_tickets": blobberAuthTickets, "preferred_blobbers": blobberIds} + allocOutput, err := utils.CreateNewEnterpriseAllocation(t, configPath, createParams(params)) + require.Nil(t, err, "Error creating allocation") + allocationID, err := utils.GetAllocationID(strings.Join(allocOutput, "\n")) + require.Nil(t, err, "Error fetching allocation id") + + beforeAlloc := utils.GetAllocation(t, allocationID) + afterBalance := utils.GetBalanceFromSharders(t, wallet.ClientID) + require.Equal(t, beforeBalance-amountTotalLockedToAlloc-1e8, afterBalance, "Balance should be locked to allocation") + beforeBalance = afterBalance + + // Update the allocation duration + updateAllocationParams := createParams(map[string]interface{}{ + "allocation": allocationID, + "extend": true, + }) + output, err := utils.UpdateAllocation(t, configPath, updateAllocationParams, true) + require.Nil(t, err, "Updating allocation duration failed", strings.Join(output, "\n")) + + // Cancel the allocation + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Cancel allocation failed", strings.Join(output, "\n")) + + // Validate and check refund + afterAlloc := utils.GetAllocation(t, allocationID) + require.True(t, afterAlloc.Finalized, "Allocation should be expired") + require.Equal(t, int64(0), afterAlloc.WritePool, "Write pool balance should be 0") + afterBalance = utils.GetBalanceFromSharders(t, wallet.ClientID) + + // Calculate expected refund + timeUnitInSeconds := int64(time.Minute * 10) + durationOfUsedInSeconds := afterAlloc.ExpirationDate - beforeAlloc.StartTime + realCostOfAlloc := costOfAlloc(&beforeAlloc) + expectedPaymentToBlobbers := realCostOfAlloc * durationOfUsedInSeconds / timeUnitInSeconds + expectedRefund := amountTotalLockedToAlloc - expectedPaymentToBlobbers + + t.Logf("Expected refund: %v", expectedRefund) + require.InEpsilon(t, beforeBalance+expectedRefund-1e8, afterBalance, 0.01, "Refund should be credited to client balance after cancel allocation") + }) + t.RunWithTimeout("Cancel allocation after adding blobber check refund amount.", time.Minute*15, func(t *test.SystemTest) { + // Setup, wallet creation, and initial balance retrieval + utils.SetupWalletWithCustomTokens(t, configPath, 10) + wallet, err := utils.GetWalletForName(t, configPath, utils.EscapedTestName(t)) + require.Nil(t, err, "Error getting wallet") + beforeBalance := utils.GetBalanceFromSharders(t, wallet.ClientID) + + // Create allocation + amountTotalLockedToAlloc := int64(5e10) + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + params := map[string]interface{}{"size": 1 * GB, "lock": amountTotalLockedToAlloc / 1e10, "enterprise": true, "blobber_auth_tickets": blobberAuthTickets, "preferred_blobbers": blobberIds} + allocOutput, err := utils.CreateNewEnterpriseAllocation(t, configPath, createParams(params)) + require.Nil(t, err, "Error creating allocation") + allocationID, err := utils.GetAllocationID(strings.Join(allocOutput, "\n")) + require.Nil(t, err, "Error fetching allocation id") + + beforeAlloc := utils.GetAllocation(t, allocationID) + afterBalance := utils.GetBalanceFromSharders(t, wallet.ClientID) + require.Equal(t, beforeBalance-amountTotalLockedToAlloc-1e8, afterBalance, "Balance should be locked to allocation") + beforeBalance = afterBalance + + // Add a blobber + wd, _ := os.Getwd() + walletFile := filepath.Join(wd, "config", utils.EscapedTestName(t)+"_wallet.json") + configFile := filepath.Join(wd, "config", configPath) + newBlobberID, newBlobberUrl, err := cli_tests.GetBlobberIdAndUrlNotPartOfAllocation(walletFile, configFile, allocationID) + require.Nil(t, err, "Unable to get blobber not part of allocation") + blobberAuthTicket, err := utils.GetBlobberAuthTicketWithId(t, configPath, newBlobberID, newBlobberUrl) + require.Nil(t, err, "Unable to generate auth ticket for adding blobber") + + updateAllocationParams := createParams(map[string]interface{}{ + "allocation": allocationID, + "add_blobber": newBlobberID, + "add_blobber_auth_ticket": blobberAuthTicket, + }) + output, err := utils.UpdateAllocation(t, configPath, updateAllocationParams, true) + require.Nil(t, err, "Adding blobber failed", strings.Join(output, "\n")) + + // Cancel the allocation + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Cancel allocation failed", strings.Join(output, "\n")) + + // Validate and check refund + afterAlloc := utils.GetAllocation(t, allocationID) + require.True(t, afterAlloc.Finalized, "Allocation should be expired") + require.Equal(t, int64(0), afterAlloc.WritePool, "Write pool balance should be 0") + afterBalance = utils.GetBalanceFromSharders(t, wallet.ClientID) + + // Calculate expected refund + timeUnitInSeconds := int64(time.Minute * 10) + durationOfUsedInSeconds := afterAlloc.ExpirationDate - beforeAlloc.StartTime + realCostOfAlloc := costOfAlloc(&beforeAlloc) + expectedPaymentToBlobbers := realCostOfAlloc * durationOfUsedInSeconds / timeUnitInSeconds + expectedRefund := amountTotalLockedToAlloc - expectedPaymentToBlobbers + + t.Logf("Expected refund: %v", expectedRefund) + require.InEpsilon(t, beforeBalance+expectedRefund-1e8, afterBalance, 0.01, "Refund should be credited to client balance after cancel allocation") + }) + + t.RunWithTimeout("Cancel allocation after adding a blobber with 2x amount check refund amount", time.Minute*15, func(t *test.SystemTest) { + utils.SetupWalletWithCustomTokens(t, configPath, 10) + + wallet, err := utils.GetWalletForName(t, configPath, utils.EscapedTestName(t)) + require.Nil(t, err, "Error getting wallet") + beforeBalance := utils.GetBalanceFromSharders(t, wallet.ClientID) + + // Create allocation + amountTotalLockedToAlloc := int64(5e10) + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + params := map[string]interface{}{"size": 1 * GB, "lock": amountTotalLockedToAlloc / 1e10, "enterprise": true, "blobber_auth_tickets": blobberAuthTickets, "preferred_blobbers": blobberIds} + allocOutput, err := utils.CreateNewEnterpriseAllocation(t, configPath, createParams(params)) + require.Nil(t, err, "Error creating allocation") + allocationID, err := utils.GetAllocationID(strings.Join(allocOutput, "\n")) + require.Nil(t, err, "Error fetching allocation id") + + beforeAlloc := utils.GetAllocation(t, allocationID) + afterBalance := utils.GetBalanceFromSharders(t, wallet.ClientID) + require.Equal(t, beforeBalance-amountTotalLockedToAlloc-1e8, afterBalance, "Balance should be locked to allocation") + beforeBalance = afterBalance + + // Replace a blobber with 2x price + wd, _ := os.Getwd() + walletFile := filepath.Join(wd, "config", utils.EscapedTestName(t)+"_wallet.json") + configFile := filepath.Join(wd, "config", configPath) + addBlobberID, addBlobberUrl, err := cli_tests.GetBlobberIdAndUrlNotPartOfAllocation(walletFile, configFile, allocationID) + require.Nil(t, err) + removeBlobber, err := cli_tests.GetRandomBlobber(walletFile, configFile, allocationID, addBlobberID) + require.Nil(t, err) + + blobberAuthTicket, err := utils.GetBlobberAuthTicketWithId(t, configPath, addBlobberID, addBlobberUrl) + require.Nil(t, err, "Unable to generate auth ticket for replace blobber") + + updateAllocationParams := createParams(map[string]interface{}{ + "allocation": allocationID, + "remove_blobber": removeBlobber, + "add_blobber": addBlobberID, + "add_blobber_auth_ticket": blobberAuthTicket, + }) + output, err := utils.UpdateAllocation(t, configPath, updateAllocationParams, true) + require.Nil(t, err, "Replacing blobber failed", strings.Join(output, "\n")) + + // Cancel the allocation + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Cancel allocation failed", strings.Join(output, "\n")) + + // Validate and check refund + afterAlloc := utils.GetAllocation(t, allocationID) + require.True(t, afterAlloc.Finalized, "Allocation should be expired") + require.Equal(t, int64(0), afterAlloc.WritePool, "Write pool balance should be 0") + afterBalance = utils.GetBalanceFromSharders(t, wallet.ClientID) + + // Calculate expected refund + timeUnitInSeconds := int64(time.Minute * 10) + durationOfUsedInSeconds := afterAlloc.ExpirationDate - beforeAlloc.StartTime + realCostOfAlloc := costOfAlloc(&beforeAlloc) + expectedPaymentToBlobbers := realCostOfAlloc * durationOfUsedInSeconds / timeUnitInSeconds + expectedRefund := amountTotalLockedToAlloc - expectedPaymentToBlobbers + + t.Logf("Expected refund: %v", expectedRefund) + require.InEpsilon(t, beforeBalance+expectedRefund-1e8, afterBalance, 0.01, "Refund should be credited to client balance after cancel allocation") + }) + + t.Run("Cancel allocation immediately should work", func(t *test.SystemTest) { + // Setup, wallet creation, and initial balance retrieval + utils.SetupWalletWithCustomTokens(t, configPath, 10) + wallet, err := utils.GetWalletForName(t, configPath, utils.EscapedTestName(t)) + require.Nil(t, err, "Error getting wallet") + beforeBalance := utils.GetBalanceFromSharders(t, wallet.ClientID) + + // Create allocation + amountTotalLockedToAlloc := int64(5e10) + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + params := map[string]interface{}{"size": 1 * GB, "lock": amountTotalLockedToAlloc / 1e10, "enterprise": true, "blobber_auth_tickets": blobberAuthTickets, "preferred_blobbers": blobberIds} + allocOutput, err := utils.CreateNewEnterpriseAllocation(t, configPath, createParams(params)) + require.Nil(t, err, "Error creating allocation") + allocationID, err := utils.GetAllocationID(strings.Join(allocOutput, "\n")) + require.Nil(t, err, "Error fetching allocation id") + + beforeAlloc := utils.GetAllocation(t, allocationID) + afterBalance := utils.GetBalanceFromSharders(t, wallet.ClientID) + require.Equal(t, beforeBalance-amountTotalLockedToAlloc-1e8, afterBalance, "Balance should be locked to allocation") + beforeBalance = afterBalance + + // Cancel the allocation immediately + output, err := cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Cancel allocation failed", strings.Join(output, "\n")) + + // Validate and check refund + afterAlloc := utils.GetAllocation(t, allocationID) + require.True(t, afterAlloc.Finalized, "Allocation should be expired") + require.Equal(t, int64(0), afterAlloc.WritePool, "Write pool balance should be 0") + afterBalance = utils.GetBalanceFromSharders(t, wallet.ClientID) + + // Calculate expected refund + timeUnitInSeconds := int64(time.Minute * 10) + durationOfUsedInSeconds := afterAlloc.ExpirationDate - beforeAlloc.StartTime + realCostOfAlloc := costOfAlloc(&beforeAlloc) + expectedPaymentToBlobbers := realCostOfAlloc * durationOfUsedInSeconds / timeUnitInSeconds + expectedRefund := amountTotalLockedToAlloc - expectedPaymentToBlobbers + + t.Logf("Expected refund: %v", expectedRefund) + require.InEpsilon(t, beforeBalance+expectedRefund-1e8, afterBalance, 0.01, "Refund should be credited to client balance after cancel allocation") + }) + + t.Run("Cancel Other's Allocation Should Fail", func(t *test.SystemTest) { + output, err := utils.CreateWalletForName(t, configPath, utils.EscapedTestName(t)+"_other") + require.Nil(t, err, "Unable to create the wallet", strings.Join(output, "\n")) + + otherAllocationID := utils.SetupEnterpriseAllocationWithWallet(t, utils.EscapedTestName(t)+"_other", configPath) + output, err = utils.ExecuteFaucetWithTokensForWallet(t, utils.EscapedTestName(t)+"_other_wallet.json", configPath, 1000) + require.Nil(t, err, "Error executing faucet", strings.Join(output, "\n")) + + output, err = utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error creating wallet", strings.Join(output, "\n")) + + output, err = utils.ExecuteFaucetWithTokens(t, configPath, 1000) + require.Nil(t, err, "Error executing faucet", strings.Join(output, "\n")) + + // otherAllocationID should not be cancelable from this level + output, err = cancelAllocation(t, configPath, otherAllocationID, false) + + require.Error(t, err, "expected error canceling allocation", strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1", strings.Join(output, "\n")) + require.Equal(t, "Error canceling allocation:alloc_cancel_failed: only owner can cancel an allocation", output[len(output)-1]) + }) + + t.Run("Cancel Non-existent Allocation Should Fail", func(t *test.SystemTest) { + _, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error craeting wallet") + + allocationID := "123abc" + + output, err := cancelAllocation(t, configPath, allocationID, false) + + require.Error(t, err, "expected error updating allocation", strings.Join(output, "\n")) + require.Equal(t, "Error canceling allocation:alloc_cancel_failed: value not present", output[0]) + }) +} + +func cancelAllocation(t *test.SystemTest, cliConfigFilename, allocationID string, retry bool) ([]string, error) { + t.Logf("Canceling allocation...") + cmd := fmt.Sprintf( + "./zbox alloc-cancel --allocation %s --silent "+ + "--wallet %s --configDir ./config --config %s", + allocationID, + utils.EscapedTestName(t)+"_wallet.json", + cliConfigFilename, + ) + + if retry { + return cliutils.RunCommand(t, cmd, 3, time.Second*2) + } else { + return cliutils.RunCommandWithoutRetry(cmd) + } +} + +func waitForTimeInMinutesWhileLogging(t *test.SystemTest, minutes int) { + for i := 0; i < minutes; i++ { + t.Log(fmt.Sprintf("Waiting for %d minutes...", minutes-i)) + time.Sleep(time.Minute) + } +} diff --git a/tests/tokenomics_tests/enterprise_blobber_create_allocation_test.go b/tests/tokenomics_tests/enterprise_blobber_create_allocation_test.go new file mode 100644 index 0000000000..546438dfdf --- /dev/null +++ b/tests/tokenomics_tests/enterprise_blobber_create_allocation_test.go @@ -0,0 +1,682 @@ +package tokenomics_tests + +import ( + "fmt" + "regexp" + "strings" + "testing" + "time" + + "github.com/0chain/system_test/tests/tokenomics_tests/utils" + + "github.com/0chain/system_test/internal/api/util/test" + + climodel "github.com/0chain/system_test/internal/cli/model" + cliutils "github.com/0chain/system_test/internal/cli/util" + "github.com/stretchr/testify/require" +) + +func TestCreateEnterpriseAllocation(testSetup *testing.T) { + t := test.NewSystemTest(testSetup) + + t.Run("Create enterprise allocation with blobber auth tickets should pass", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error registering wallet", strings.Join(output, "\n")) + + _, err = utils.ExecuteFaucetWithTokens(t, configPath, 10) + require.Nil(t, err, "Error executing faucet") + + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + + // Create enterprise allocation with blobber auth tickets + options := map[string]interface{}{ + "size": "1024", + "lock": "0.5", + "enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds, + } + output, err = utils.CreateNewEnterpriseAllocation(t, configPath, utils.CreateParams(options)) + require.Nil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Regexp(t, regexp.MustCompile("^Allocation created: [0-9a-fA-F]{64}$"), output[0], strings.Join(output, "\n")) + + allocationID, err := utils.GetAllocationID(output[0]) + require.Nil(t, err, "could not get allocation ID", strings.Join(output, "\n")) + createEnterpriseAllocationTestTeardown(t, allocationID) + }) + + t.Run("Create enterprise allocation for locking cost equal to the cost calculated should work", func(t *test.SystemTest) { + _, _ = utils.CreateWallet(t, configPath) + + blobberAuthTickets, blobbersIds := utils.GenerateBlobberAuthTickets(t, configPath) + options := map[string]interface{}{ + "cost": "", + "size": "10000", + "read_price": "0-1", + "write_price": "0-1", + "enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobbersIds, + } + output, err := createNewEnterpriseAllocation(t, configPath, utils.CreateParams(options)) + require.Nil(t, err, strings.Join(output, "\n")) + require.Len(t, output, 1) + + allocationCost, err := getAllocationCost(output[0]) + require.Nil(t, err, "could not get allocation cost", strings.Join(output, "\n")) + + options = map[string]interface{}{ + "lock": allocationCost, + "size": "10000", + "read_price": "0-1", + "write_price": "0-1", + "enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobbersIds, + } + output, err = createNewEnterpriseAllocation(t, configPath, utils.CreateParams(options)) + require.Nil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Regexp(t, regexp.MustCompile("^Allocation created: [0-9a-fA-F]{64}$"), output[0], strings.Join(output, "\n")) + + allocationID, err := utils.GetAllocationID(output[0]) + require.Nil(t, err, "could not get allocation ID", strings.Join(output, "\n")) + + createEnterpriseAllocationTestTeardown(t, allocationID) + }) + + t.Run("Create enterprise allocation with smallest possible size (1024) Should Work", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error registering wallet", strings.Join(output, "\n")) + + _, err = utils.ExecuteFaucetWithTokens(t, configPath, 10) + require.Nil(t, err, "Error executing faucet") + + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + + options := map[string]interface{}{ + "size": "1024", + "lock": "0.5", + "enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds, + } + output, err = createNewEnterpriseAllocation(t, configPath, utils.CreateParams(options)) + require.Nil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Regexp(t, regexp.MustCompile("^Allocation created: [0-9a-fA-F]{64}$"), output[0], strings.Join(output, "\n")) + + allocationID, err := utils.GetAllocationID(output[0]) + require.Nil(t, err, "could not get allocation ID", strings.Join(output, "\n")) + + createEnterpriseAllocationTestTeardown(t, allocationID) + }) + + t.Run("Create enterprise allocation for another owner should Work", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error registering wallet", strings.Join(output, "\n")) + + output, err = utils.CreateWalletForName(t, configPath, utils.EscapedTestName(t)+"_other") + require.Nil(t, err, "Error creating other wallet", strings.Join(output, "\n")) + + _, err = utils.ExecuteFaucetWithTokens(t, configPath, 10) + require.Nil(t, err, "Error executing facuet") + + targetWalletName := utils.EscapedTestName(t) + targetWallet, err := utils.GetWalletForName(t, configPath, targetWalletName) + require.Nil(t, err, "could not get target wallet") + + _, err = utils.ExecuteFaucetWithTokensForWallet(t, utils.EscapedTestName(t)+"_other", configPath, 1000) + require.Nil(t, err, "Error executing facuet") + + _, err = utils.GetWalletForName(t, configPath, utils.EscapedTestName(t)+"_other") + require.Nil(t, err, "could not get other wallet") + + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + + options := map[string]interface{}{ + "lock": "0.5", + "owner": targetWallet.ClientID, + "owner_public_key": targetWallet.ClientPublicKey, + "enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds, + } + output, err = createNewEnterpriseAllocation(t, configPath, utils.CreateParams(options)) + require.Nil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Regexp(t, regexp.MustCompile("^Allocation created: [0-9a-fA-F]{64}$"), output[0], strings.Join(output, "\n")) + + allocationID, err := utils.GetAllocationID(output[0]) + require.Nil(t, err, "could not get allocation ID", strings.Join(output, "\n")) + + file := utils.GenerateRandomTestFileName(t) + fileSize := int64(102400) + err = utils.CreateFileWithSize(file, fileSize) + require.Nil(t, err) + + uploadParams := map[string]interface{}{ + "allocation": allocationID, + "localpath": file, + "remotepath": "/", + "encrypt": "", + } + output, err = utils.UploadFile(t, configPath, uploadParams, true) + require.Nil(t, err, strings.Join(output, "\n")) + + output, err = utils.UploadFileForWallet(t, utils.EscapedTestName(t)+"_other", configPath, uploadParams, false) + require.NotNil(t, err, strings.Join(output, "\n")) + require.Contains(t, strings.Join(output, ""), "Operation needs to be performed by the owner or the payer of the allocation") + + createEnterpriseAllocationTestTeardown(t, allocationID) + }) + + t.Run("Create enterprise allocation with parity specified Should Work", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error registering wallet", strings.Join(output, "\n")) + + _, err = utils.ExecuteFaucetWithTokens(t, configPath, 10) + require.Nil(t, err, "Error executing faucets") + + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + + options := map[string]interface{}{ + "size": "1024", + "parity": "1", + "lock": "0.5", + "enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds, + } + output, err = createNewEnterpriseAllocation(t, configPath, utils.CreateParams(options)) + require.Nil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Regexp(t, regexp.MustCompile("^Allocation created: [0-9a-fA-F]{64}$"), output[0], strings.Join(output, "\n")) + + allocationID, err := utils.GetAllocationID(output[0]) + require.Nil(t, err, "could not get allocation ID", strings.Join(output, "\n")) + + createEnterpriseAllocationTestTeardown(t, allocationID) + }) + + t.Run("Create enterprise allocation with data specified Should Work", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error registering wallet", strings.Join(output, "\n")) + + _, err = utils.ExecuteFaucetWithTokens(t, configPath, 10) + require.Nil(t, err, "Error executing faucet") + + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + + options := map[string]interface{}{ + "size": "1024", + "data": "1", + "lock": "0.5", + "enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds, + } + output, err = createNewEnterpriseAllocation(t, configPath, utils.CreateParams(options)) + require.Nil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Regexp(t, regexp.MustCompile("^Allocation created: [0-9a-fA-F]{64}$"), output[0], strings.Join(output, "\n")) + + allocationID, err := utils.GetAllocationID(output[0]) + require.Nil(t, err, "could not get allocation ID", strings.Join(output, "\n")) + + createEnterpriseAllocationTestTeardown(t, allocationID) + }) + + t.Run("Create enterprise allocation with read price range Should Work", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error registering wallet", strings.Join(output, "\n")) + + _, err = utils.ExecuteFaucetWithTokens(t, configPath, 10) + require.Nil(t, err, "Error executing faucet") + + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + + options := map[string]interface{}{ + "size": "1024", + "read_price": "0-9999", + "lock": "0.5", + "enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds, + } + output, err = createNewEnterpriseAllocation(t, configPath, utils.CreateParams(options)) + require.Nil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Regexp(t, regexp.MustCompile("^Allocation created: [0-9a-fA-F]{64}$"), output[0], strings.Join(output, "\n")) + + allocationID, err := utils.GetAllocationID(output[0]) + require.Nil(t, err, "could not get allocation ID", strings.Join(output, "\n")) + + createEnterpriseAllocationTestTeardown(t, allocationID) + }) + + t.Run("Create enterprise allocation with write price range Should Work", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error registering wallet", strings.Join(output, "\n")) + + _, err = utils.ExecuteFaucetWithTokens(t, configPath, 10) + require.Nil(t, err, "Error executing faucet") + + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + + options := map[string]interface{}{ + "size": "1024", + "write_price": "0-9999", + "lock": "0.5", + "enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds, + } + + output, err = createNewEnterpriseAllocation(t, configPath, utils.CreateParams(options)) + require.Nil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Regexp(t, regexp.MustCompile("^Allocation created: [0-9a-fA-F]{64}$"), output[0], strings.Join(output, "\n")) + + allocationID, err := utils.GetAllocationID(output[0]) + require.Nil(t, err, "could not get allocation ID", strings.Join(output, "\n")) + createEnterpriseAllocationTestTeardown(t, allocationID) + }) + + t.Run("Create enterprise allocation should have all file options permitted by default", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error registering wallet", strings.Join(output, "\n")) + + _, err = utils.ExecuteFaucetWithTokens(t, configPath, 10) + require.Nil(t, err, "Error executing faucet") + + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + + options := map[string]interface{}{"lock": "0.5", "size": 1024, "enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds} + output, err = createNewEnterpriseAllocationWithoutRetry(t, configPath, utils.CreateParams(options)) + require.Nil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Contains(t, output[0], "Allocation created", strings.Join(output, "\n")) + + allocationID, err := utils.GetAllocationID(output[0]) + require.Nil(t, err) + + var alloc climodel.Allocation + + alloc = utils.GetAllocation(t, allocationID) + require.Equal(t, uint16(63), alloc.FileOptions) + createEnterpriseAllocationTestTeardown(t, allocationID) + }) + + t.Run("Create enterprise allocation with some forbidden file options flags should pass and show in allocation", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error registering wallet", strings.Join(output, "\n")) + + _, err = utils.ExecuteFaucetWithTokens(t, configPath, 10) + require.Nil(t, err, "Error executing faucet") + + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + + options := map[string]interface{}{"lock": "0.5", "size": 1024, "forbid_upload": nil, "enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds} + output, err = createNewEnterpriseAllocationWithoutRetry(t, configPath, utils.CreateParams(options)) + require.Nil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Contains(t, output[0], "Allocation created", strings.Join(output, "\n")) + + allocationID, err := utils.GetAllocationID(output[0]) + require.Nil(t, err) + alloc := utils.GetAllocation(t, allocationID) + require.Equal(t, uint16(62), alloc.FileOptions) + + createEnterpriseAllocationTestTeardown(t, allocationID) + + blobberAuthTickets, blobberIds = utils.GenerateBlobberAuthTickets(t, configPath) + + options = map[string]interface{}{"lock": "0.5", "size": 1024, "forbid_delete": nil, "enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds} + output, err = createNewEnterpriseAllocationWithoutRetry(t, configPath, utils.CreateParams(options)) + require.Nil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Contains(t, output[0], "Allocation created", strings.Join(output, "\n")) + + allocationID, err = utils.GetAllocationID(output[0]) + require.Nil(t, err) + alloc = utils.GetAllocation(t, allocationID) + require.Equal(t, uint16(61), alloc.FileOptions) + + createEnterpriseAllocationTestTeardown(t, allocationID) + + blobberAuthTickets, blobberIds = utils.GenerateBlobberAuthTickets(t, configPath) + + options = map[string]interface{}{"lock": "0.5", "size": 1024, "forbid_update": nil, "enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds} + output, err = createNewEnterpriseAllocationWithoutRetry(t, configPath, utils.CreateParams(options)) + require.Nil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Contains(t, output[0], "Allocation created", strings.Join(output, "\n")) + + allocationID, err = utils.GetAllocationID(output[0]) + require.Nil(t, err) + alloc = utils.GetAllocation(t, allocationID) + require.Equal(t, uint16(59), alloc.FileOptions) + + createEnterpriseAllocationTestTeardown(t, allocationID) + + blobberAuthTickets, blobberIds = utils.GenerateBlobberAuthTickets(t, configPath) + + options = map[string]interface{}{"lock": "0.5", "size": 1024, "forbid_move": nil, "enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds} + output, err = createNewEnterpriseAllocationWithoutRetry(t, configPath, utils.CreateParams(options)) + require.Nil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Contains(t, output[0], "Allocation created", strings.Join(output, "\n")) + + allocationID, err = utils.GetAllocationID(output[0]) + require.Nil(t, err) + alloc = utils.GetAllocation(t, allocationID) + require.Equal(t, uint16(55), alloc.FileOptions) + + createEnterpriseAllocationTestTeardown(t, allocationID) + + blobberAuthTickets, blobberIds = utils.GenerateBlobberAuthTickets(t, configPath) + + options = map[string]interface{}{"lock": "0.5", "size": 1024, "forbid_copy": nil, "enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds} + output, err = createNewEnterpriseAllocationWithoutRetry(t, configPath, utils.CreateParams(options)) + require.Nil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Contains(t, output[0], "Allocation created", strings.Join(output, "\n")) + + allocationID, err = utils.GetAllocationID(output[0]) + require.Nil(t, err) + alloc = utils.GetAllocation(t, allocationID) + require.Equal(t, uint16(47), alloc.FileOptions) + + createEnterpriseAllocationTestTeardown(t, allocationID) + + blobberAuthTickets, blobberIds = utils.GenerateBlobberAuthTickets(t, configPath) + + options = map[string]interface{}{"lock": "0.5", "size": 1024, "forbid_rename": nil, "enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds} + output, err = createNewEnterpriseAllocationWithoutRetry(t, configPath, utils.CreateParams(options)) + require.Nil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Contains(t, output[0], "Allocation created", strings.Join(output, "\n")) + + allocationID, err = utils.GetAllocationID(output[0]) + require.Nil(t, err) + alloc = utils.GetAllocation(t, allocationID) + require.Equal(t, uint16(31), alloc.FileOptions) + + createEnterpriseAllocationTestTeardown(t, allocationID) + }) + + t.Run("Create enterprise allocation third_party_extendable should be false by default and change with flags", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error registering wallet", strings.Join(output, "\n")) + + _, err = utils.ExecuteFaucetWithTokens(t, configPath, 10) + require.Nil(t, err, "Error executing faucet") + + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + + options := map[string]interface{}{"lock": "0.5", "size": 1024, "enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds} + output, err = createNewEnterpriseAllocationWithoutRetry(t, configPath, utils.CreateParams(options)) + require.Nil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Contains(t, output[0], "Allocation created", strings.Join(output, "\n")) + + allocationID, err := utils.GetAllocationID(output[0]) + require.Nil(t, err) + + var alloc climodel.Allocation + + alloc = utils.GetAllocation(t, allocationID) + require.Equal(t, false, alloc.ThirdPartyExtendable) + createEnterpriseAllocationTestTeardown(t, allocationID) + + blobberAuthTickets, blobberIds = utils.GenerateBlobberAuthTickets(t, configPath) + + options = map[string]interface{}{"lock": "0.5", "size": 1024, "third_party_extendable": nil, "enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds} + output, err = createNewEnterpriseAllocationWithoutRetry(t, configPath, utils.CreateParams(options)) + require.Nil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Contains(t, output[0], "Allocation created", strings.Join(output, "\n")) + + allocationID, err = utils.GetAllocationID(output[0]) + require.Nil(t, err) + + alloc = utils.GetAllocation(t, allocationID) + require.NotNil(t, alloc, "error fetching allocation") + require.Equal(t, true, alloc.ThirdPartyExtendable) + createEnterpriseAllocationTestTeardown(t, allocationID) + }) + t.Run("Create enterprise allocation for locking cost less than minimum cost should not work", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error registering wallet", strings.Join(output, "\n")) + + _, err = utils.ExecuteFaucetWithTokens(t, configPath, 10) + require.Nil(t, err, "Error executing faucet") + + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + + options := map[string]interface{}{ + "cost": "", + "read_price": "0-1", + "write_price": "0-1", + "size": 10000, + "enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds, + } + output, err = createNewEnterpriseAllocation(t, configPath, utils.CreateParams(options)) + require.Nil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + + allocationCost, err := getAllocationCost(output[0]) + require.Nil(t, err, "could not get allocation cost", strings.Join(output, "\n")) + + mustFailCost := allocationCost * 0.8 + options = map[string]interface{}{ + "lock": mustFailCost, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds, + "enterprise": true, + } + + output, err = createNewEnterpriseAllocation(t, configPath, utils.CreateParams(options)) + require.NotNil(t, err, strings.Join(output, "\n")) + require.Contains(t, strings.Join(output, "\n"), "not enough tokens to honor the allocation", strings.Join(output, "\n")) + }) + + t.Run("Create enterprise allocation for locking negative cost should not work", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error registering wallet", strings.Join(output, "\n")) + + _, err = utils.ExecuteFaucetWithTokens(t, configPath, 10) + require.Nil(t, err, "Error executing faucet") + + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + + options := map[string]interface{}{ + "cost": "", + "read_price": "0-1", + "write_price": "0-1", + "size": 10000, + "enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds, + } + mustFailCost := -1 + options = map[string]interface{}{"lock": mustFailCost} + output, err = createNewEnterpriseAllocationWithoutRetry(t, configPath, utils.CreateParams(options)) + require.NotNil(t, err, strings.Join(output, "\n")) + }) + + t.Run("Create enterprise allocation without blobber auth tickets should fail", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error registering wallet", strings.Join(output, "\n")) + + _, err = utils.ExecuteFaucetWithTokens(t, configPath, 10) + require.Nil(t, err, "Error executing faucet", strings.Join(output, "\n")) + + _, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + + options := map[string]interface{}{ + "size": "1024", + "lock": "0.5", + "enterprise": true, + "preferred_blobbers": blobberIds, + } + output, err = utils.CreateNewEnterpriseAllocation(t, configPath, utils.CreateParams(options)) + + require.NotNil(t, err, "expected an error when creating allocation without blobber auth tickets %v", err) + require.Len(t, output, 1, strings.Join(output, "\n")) + require.Contains(t, strings.Join(output, "\n"), "Not enough blobbers to honor the allocation", "expected error message to mention missing auth tickets %v", strings.Join(output, "\n")) + }) + t.Run("Create enterprise allocation with too large parity (Greater than the number of blobbers) Should Fail", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error registering wallet", strings.Join(output, "\n")) + + _, err = utils.ExecuteFaucetWithTokens(t, configPath, 10) + require.Nil(t, err, "Error executing faucet") + + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + + options := map[string]interface{}{"parity": "99", "lock": "0.5", "size": 1024, "enterprise": true, "blobber_auth_tickets": blobberAuthTickets, "preferred_blobbers": blobberIds} + output, err = createNewEnterpriseAllocationWithoutRetry(t, configPath, utils.CreateParams(options)) + require.NotNil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Contains(t, output[0], "blobbers provided are not enough to honour the allocation") //nolint + }) + + t.Run("Create enterprise allocation with too large data (Greater than the number of blobbers) Should Fail", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error registering wallet", strings.Join(output, "\n")) + + _, err = utils.ExecuteFaucetWithTokens(t, configPath, 10) + require.Nil(t, err, "Error executing faucet") + + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + + options := map[string]interface{}{"data": "99", "lock": "0.5", "size": 1024, "enterprise": true, "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds} + output, err = createNewEnterpriseAllocationWithoutRetry(t, configPath, utils.CreateParams(options)) + require.NotNil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Contains(t, output[0], "blobbers provided are not enough to honour the allocation") //nolint + }) + + t.Run("Create enterprise allocation with too large data and parity (Greater than the number of blobbers) Should Fail", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error registering wallet", strings.Join(output, "\n")) + + _, err = utils.ExecuteFaucetWithTokens(t, configPath, 10) + require.Nil(t, err, "Error executing faucet") + + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + + options := map[string]interface{}{"data": "30", "parity": "20", "lock": "0.5", "size": 1024, "enterprise": true, "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds} + output, err = createNewEnterpriseAllocationWithoutRetry(t, configPath, utils.CreateParams(options)) + require.NotNil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Contains(t, output[0], "blobbers provided are not enough to honour the allocation") //nolint + }) + + t.Run("Create enterprise allocation with read price range 0-0 Should Fail", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error registering wallet", strings.Join(output, "\n")) + + _, err = utils.ExecuteFaucetWithTokens(t, configPath, 10) + require.Nil(t, err, "Error executing faucet") + + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + + options := map[string]interface{}{"read_price": "0-0", "lock": "0.5", "size": 1024, "enterprise": true, "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds} + output, err = createNewEnterpriseAllocationWithoutRetry(t, configPath, utils.CreateParams(options)) + require.NotNil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Contains(t, output[0], "Not enough blobbers to honor the allocation", strings.Join(output, "\n")) + }) + + t.Run("Create enterprise allocation with size smaller than limit (size < 1024) Should Fail", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error registering wallet", strings.Join(output, "\n")) + + _, err = utils.ExecuteFaucetWithTokens(t, configPath, 10) + require.Nil(t, err, "Error executing faucet") + + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + + options := map[string]interface{}{"size": 256, "lock": "0.5", "enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds} + output, err = createNewEnterpriseAllocationWithoutRetry(t, configPath, utils.CreateParams(options)) + require.NotNil(t, err, strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1") + require.Equal(t, "Error creating allocation: allocation_creation_failed: invalid request: insufficient allocation size", output[0], strings.Join(output, "\n")) + }) + + t.Run("Create enterprise allocation with no parameter (missing lock) Should Fail", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error registering wallet", strings.Join(output, "\n")) + + _, err = utils.ExecuteFaucetWithTokens(t, configPath, 10) + require.Nil(t, err, "Error executing faucet") + + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + + options := map[string]interface{}{"enterprise": true, + "blobber_auth_tickets": blobberAuthTickets, + "preferred_blobbers": blobberIds} + output, err = createNewEnterpriseAllocationWithoutRetry(t, configPath, utils.CreateParams(options)) + require.NotNil(t, err) + require.True(t, len(output) > 0, "expected output length be at least 1", strings.Join(output, "\n")) + require.Equal(t, "missing required 'lock' argument", output[len(output)-1]) + }) +} + +func createNewEnterpriseAllocation(t *test.SystemTest, cliConfigFilename, params string) ([]string, error) { + return createNewEnterpriseAllocationForWallet(t, utils.EscapedTestName(t), cliConfigFilename, params) +} + +func createNewEnterpriseAllocationForWallet(t *test.SystemTest, wallet, cliConfigFilename, params string) ([]string, error) { + t.Logf("Creating new allocation...") + return cliutils.RunCommand(t, fmt.Sprintf( + "./zbox newallocation %s --silent --wallet %s --configDir ./config --config %s --allocationFileName %s", + params, + wallet+"_wallet.json", + cliConfigFilename, + wallet+"_allocation.txt"), 3, time.Second*5) +} + +func createNewEnterpriseAllocationWithoutRetry(t *test.SystemTest, cliConfigFilename, params string) ([]string, error) { + return cliutils.RunCommandWithoutRetry(fmt.Sprintf( + "./zbox newallocation %s --silent --wallet %s --configDir ./config --config %s --allocationFileName %s", + params, + utils.EscapedTestName(t)+"_wallet.json", + cliConfigFilename, + utils.EscapedTestName(t)+"_allocation.txt")) +} + +func createEnterpriseAllocationTestTeardown(t *test.SystemTest, allocationID string) { + t.Cleanup(func() { + _, _ = cancelAllocation(t, configPath, allocationID, false) + }) +} diff --git a/tests/tokenomics_tests/enterprise_blobber_finalize_allocation_test.go b/tests/tokenomics_tests/enterprise_blobber_finalize_allocation_test.go new file mode 100644 index 0000000000..a7f4978410 --- /dev/null +++ b/tests/tokenomics_tests/enterprise_blobber_finalize_allocation_test.go @@ -0,0 +1,245 @@ +package tokenomics_tests + +import ( + "fmt" + "regexp" + "strings" + "testing" + "time" + + "github.com/0chain/system_test/internal/api/util/test" + cliutils "github.com/0chain/system_test/internal/cli/util" + "github.com/0chain/system_test/tests/tokenomics_tests/utils" + "github.com/stretchr/testify/require" +) + +var ( + finalizeAllocationRegex = regexp.MustCompile(`^Allocation finalized with txId : [a-f0-9]{64}$`) +) + +func TestFinalizeEnterpriseAllocation(testSetup *testing.T) { + t := test.NewSystemTest(testSetup) + + t.Parallel() + + t.TestSetup("set storage config to use time_unit as 10 minutes", func() { + output, err := utils.UpdateStorageSCConfig(t, scOwnerWallet, map[string]string{ + "time_unit": "10m", + }, true) + require.Nil(t, err, "Error updating sc config", strings.Join(output, "\n")) + }) + + t.Cleanup(func() { + output, err := utils.UpdateStorageSCConfig(t, scOwnerWallet, map[string]string{ + "time_unit": "1h", + }, true) + require.Nil(t, err, "Error updating sc config", strings.Join(output, "\n")) + }) + + t.RunWithTimeout("Finalize allocation after waiting for 11 minutes check finalization and balance.", time.Minute*20, func(t *test.SystemTest) { + utils.SetupWalletWithCustomTokens(t, configPath, 10) + + wallet, err := utils.GetWalletForName(t, configPath, utils.EscapedTestName(t)) + require.Nil(t, err, "Error getting wallet") + require.Equal(t, wallet.ClientID, wallet.ClientID, "Error getting wallet with name %v") + + beforeBalance := utils.GetBalanceFromSharders(t, wallet.ClientID) + + // Create allocation + amountTotalLockedToAlloc := int64(2e9) + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + params := map[string]interface{}{"size": 1 * GB, "lock": "0.2", "enterprise": true, "blobber_auth_tickets": blobberAuthTickets, "preferred_blobbers": blobberIds} + allocOutput, err := utils.CreateNewEnterpriseAllocation(t, configPath, createParams(params)) + require.Nil(t, err, "Error creating allocation") + + allocationID, err := utils.GetAllocationID(strings.Join(allocOutput, "\n")) + require.Nil(t, err, "Error fetching allocation id %v", strings.Join(allocOutput, "\n")) + + beforeAlloc := utils.GetAllocation(t, allocationID) + + afterBalance := utils.GetBalanceFromSharders(t, wallet.ClientID) + require.Equal(t, beforeBalance-amountTotalLockedToAlloc-1e8, afterBalance, "Balance should be locked to allocation") // 1e8 is transaction fee + beforeBalance = afterBalance + + t.Log("Waiting for 11 minutes ....") + waitForTimeInMinutesWhileLogging(t, 11) + + // Finalize the allocation + output, err := finalizeAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Finalize allocation failed", strings.Join(output, "\n")) + + // Validate that the allocation is finalized and check balance + utils.AssertOutputMatchesAllocationRegex(t, finalizeAllocationRegex, output[0]) + + afterAlloc := utils.GetAllocation(t, allocationID) + require.True(t, afterAlloc.Finalized, "Allocation should be finalized") + require.Equal(t, int64(0), afterAlloc.WritePool, "Write pool balance should be 0") + + afterBalance = utils.GetBalanceFromSharders(t, wallet.ClientID) + + // Calculate expected finalization amount + timeUnitInSeconds := int64(600) // 10 minutes + durationOfUsedInSeconds := afterAlloc.ExpirationDate - beforeAlloc.StartTime + realCostOfAlloc := costOfAlloc(&beforeAlloc) + expectedPaymentToBlobbers := realCostOfAlloc * durationOfUsedInSeconds / timeUnitInSeconds + + t.Logf("Time unit in seconds: %v", timeUnitInSeconds) + t.Logf("Duration of used in seconds: %v", durationOfUsedInSeconds) + t.Logf("Real cost of allocation: %v", realCostOfAlloc) + t.Logf("Expected payment to blobbers: %v", expectedPaymentToBlobbers) + t.Logf("Before balance: %v", beforeBalance) + t.Logf("After balance: %v", afterBalance) + + require.InEpsilon(t, beforeBalance-expectedPaymentToBlobbers-1e8, afterBalance-beforeAlloc.WritePool, 0.01, "Finalization should correctly debit client balance") // 1e8 is transaction fee + + rewardQuery := fmt.Sprintf("allocation_id='%s' AND reward_type=%d", allocationID, EnterpriseBlobberReward) + enterpriseReward, err := getQueryRewards(t, rewardQuery) + require.Nil(t, err) + + require.InEpsilon(t, expectedPaymentToBlobbers, enterpriseReward.TotalReward, 0.01, "Enterprise blobber reward doesn't match") + }) + + t.RunWithTimeout("Finalize allocation after updating duration and check finalization and balance.", time.Minute*25, func(t *test.SystemTest) { + // Setup, wallet creation, and initial balance retrieval + utils.SetupWalletWithCustomTokens(t, configPath, 10) + + wallet, err := utils.GetWalletForName(t, configPath, utils.EscapedTestName(t)) + + require.Nil(t, err, "Error getting wallet") + beforeBalance := utils.GetBalanceFromSharders(t, wallet.ClientID) + + // Create allocation + amountTotalLockedToAlloc := int64(1e9 * 2) + + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + + params := map[string]interface{}{"data": 3, "parity": 3, "size": 1 * GB, "lock": "0.2", "enterprise": true, "blobber_auth_tickets": blobberAuthTickets, "preferred_blobbers": blobberIds} + + allocOutput, err := utils.CreateNewEnterpriseAllocation(t, configPath, createParams(params)) + require.Nil(t, err, "Error creating allocation") + + allocationID, err := utils.GetAllocationID(strings.Join(allocOutput, "\n")) + require.Nil(t, err, "Error fetching allocation id") + + beforeAlloc := utils.GetAllocation(t, allocationID) + + waitForTimeInMinutesWhileLogging(t, 5) + + afterBalance := utils.GetBalanceFromSharders(t, wallet.ClientID) + + require.Equal(t, beforeBalance-amountTotalLockedToAlloc-1e8, afterBalance, "Balance should be locked to allocation") + + requiredWpBalance := 1e9 * 2 + beforeBalance = afterBalance + + // Update the allocation duration + updateAllocationParams := createParams(map[string]interface{}{ + "allocation": allocationID, + "extend": true, + "lock": float64(requiredWpBalance) / 1e10, + }) + output, err := utils.UpdateAllocation(t, configPath, updateAllocationParams, true) + require.Nil(t, err, "Updating allocation duration failed", strings.Join(output, "\n")) + + waitForTimeInMinutesWhileLogging(t, 11) + + // Finalize the allocation + output, err = finalizeAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Finalize allocation failed", strings.Join(output, "\n")) + + // Validate that the allocation is finalized and check balance + utils.AssertOutputMatchesAllocationRegex(t, finalizeAllocationRegex, output[0]) + + afterAlloc := utils.GetAllocation(t, allocationID) + require.True(t, afterAlloc.Finalized, "Allocation should be finalized") + require.Equal(t, int64(0), afterAlloc.WritePool, "Write pool balance should be 0") + + afterBalance = utils.GetBalanceFromSharders(t, wallet.ClientID) + + // Calculate expected finalization amount + timeUnitInSeconds := int64(600) // 10 minutes + durationOfUsedInSeconds := afterAlloc.ExpirationDate - beforeAlloc.StartTime + realCostOfAlloc := costOfAlloc(&beforeAlloc) + expectedPaymentToBlobbers := realCostOfAlloc * durationOfUsedInSeconds / timeUnitInSeconds + + t.Logf("Time unit in seconds: %v", timeUnitInSeconds) + t.Logf("Duration of used in seconds: %v", durationOfUsedInSeconds) + t.Logf("Real cost of allocation: %v", realCostOfAlloc) + t.Logf("Expected payment to blobbers: %v", expectedPaymentToBlobbers) + t.Logf("Before balance: %v", beforeBalance) + t.Logf("After balance: %v", afterBalance) + + require.InEpsilon(t, beforeBalance-expectedPaymentToBlobbers-1e8, afterBalance-beforeAlloc.WritePool, 0.01, "Finalization should correctly debit client balance") // 1e8 is transaction fee + + rewardQuery := fmt.Sprintf("allocation_id='%s' AND reward_type=%d", allocationID, EnterpriseBlobberReward) + enterpriseReward, err := getQueryRewards(t, rewardQuery) + require.Nil(t, err) + + require.InEpsilon(t, expectedPaymentToBlobbers, enterpriseReward.TotalReward, 0.01, "Enterprise blobber reward doesn't match") + }) + + t.Run("Finalize Non-Expired Allocation Should Fail", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error creating wallet", strings.Join(output, "\n")) + + allocationID := utils.SetupEnterpriseAllocation(t, configPath) + + output, err = finalizeAllocation(t, configPath, allocationID, false) + require.NotNil(t, err, "expected error updating allocation", strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1", strings.Join(output, "\n")) + require.Equal(t, "Error finalizing allocation:fini_alloc_failed: allocation is not expired yet", output[0]) + }) + + t.Run("Finalize Other's Allocation Should Fail", func(t *test.SystemTest) { + output, err := utils.CreateWalletForName(t, configPath, utils.EscapedTestName(t)+"_other") + require.Nil(t, err, "Unable to create wallet", strings.Join(output, "\n")) + + var otherAllocationID = utils.SetupEnterpriseAllocationWithWallet(t, utils.EscapedTestName(t)+"_other", configPath) + + // create wallet + _, err = utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error creating wallet") + + // Then try updating with otherAllocationID: should not work + output, err = finalizeAllocation(t, configPath, otherAllocationID, false) + + // Error should not be nil since finalize is not working + require.NotNil(t, err, "expected error finalizing allocation", strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1", strings.Join(output, "\n")) + require.Equal(t, "Error finalizing allocation:fini_alloc_failed: not allowed, unknown finalization initiator", output[len(output)-1]) + }) + + t.Run("No allocation param should fail", func(t *test.SystemTest) { + _, err := utils.CreateWallet(t, configPath) + require.Nil(t, err) + + cmd := fmt.Sprintf( + "./zbox alloc-fini --silent "+ + "--wallet %s --configDir ./config --config %s", + utils.EscapedTestName(t)+"_wallet.json", + configPath, + ) + + output, err := cliutils.RunCommandWithoutRetry(cmd) + require.Error(t, err, "expected error finalizing allocation", strings.Join(output, "\n")) + require.Len(t, output, 1) + require.Equal(t, "Error: allocation flag is missing", output[len(output)-1]) + }) +} + +func finalizeAllocation(t *test.SystemTest, cliConfigFilename, allocationID string, retry bool) ([]string, error) { + t.Logf("Finalizing allocation...") + cmd := fmt.Sprintf( + "./zbox alloc-fini --allocation %s --silent "+ + "--wallet %s --configDir ./config --config %s", + allocationID, + utils.EscapedTestName(t)+"_wallet.json", + cliConfigFilename, + ) + + if retry { + return cliutils.RunCommand(t, cmd, 3, time.Second*2) + } else { + return cliutils.RunCommandWithoutRetry(cmd) + } +} diff --git a/tests/tokenomics_tests/enterprise_blobber_replace_allocation_test.go b/tests/tokenomics_tests/enterprise_blobber_replace_allocation_test.go new file mode 100644 index 0000000000..6161c214fd --- /dev/null +++ b/tests/tokenomics_tests/enterprise_blobber_replace_allocation_test.go @@ -0,0 +1,558 @@ +package tokenomics_tests + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/0chain/system_test/internal/api/util/test" + climodel "github.com/0chain/system_test/internal/cli/model" + "github.com/0chain/system_test/tests/cli_tests" + "github.com/0chain/system_test/tests/tokenomics_tests/utils" + "github.com/stretchr/testify/require" +) + +func TestReplaceEnterpriseBlobberAllocation(testSetup *testing.T) { + t := test.NewSystemTest(testSetup) + + var ( + replaceBlobberWithSameIdFailRegex = `^Error updating allocation:allocation_updating_failed: cannot add blobber [a-f0-9]{64}, already in allocation$` + replaceBlobberWithWrongIdFailRegex = `^Error updating allocation:allocation_updating_failed: can't get blobber (.+)$` + ) + + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error creating configuration wallet", strings.Join(output, "\n")) + + var blobbersList []climodel.Blobber + output, err = utils.ListBlobbers(t, configPath, "--json") + require.Nil(t, err, "Error fetching blobbers %v", strings.Join(output, "\n")) + require.Len(t, output, 1, "Error wrong json format", strings.Join(output, "\n")) + + err = json.NewDecoder(strings.NewReader(output[0])).Decode(&blobbersList) + + require.Nil(t, err, "Error decoding blobbers json") + + t.Parallel() + + t.TestSetup("set storage config to use time_unit as 10 minutes", func() { + output, err := utils.UpdateStorageSCConfig(t, scOwnerWallet, map[string]string{ + "time_unit": "10m", + }, true) + require.Nil(t, err, strings.Join(output, "\n")) + }) + + t.Cleanup(func() { + // Reset the storage config time unit + output, err := utils.UpdateStorageSCConfig(t, scOwnerWallet, map[string]string{ + "time_unit": "1h", + }, true) + require.Nil(t, err, strings.Join(output, "\n")) + + var blobbers []climodel.Blobber + output, err = utils.ListBlobbers(t, configPath, "--json") + require.Nil(t, err, "Error listing blobberes", strings.Join(output, "\n")) + require.Len(t, output, 1, "Error invalid json length", strings.Join(output, "\n")) + + err = json.NewDecoder(strings.NewReader(output[0])).Decode(&blobbers) + require.Nil(t, err, "Eerror decoding blobbers", strings.Join(output, "\n")) + + for _, blobber := range blobbers { + if blobber.Terms.WritePrice != 1e9 { + err := updateBlobberPrice(t, configPath, blobber.ID, 1e9) + require.Nil(t, err, "Error resetting blobber write prices") + } + } + }) + + t.RunSequentiallyWithTimeout("Check token accounting of a blobber replacing in allocation, should work", time.Minute*15, func(t *test.SystemTest) { + utils.SetupWalletWithCustomTokens(t, configPath, 10) + + // Set up allocation and get a random blobber to remove + params := map[string]interface{}{ + "size": 1 * GB, + "data": 2, + "parity": 2, + "lock": "0.2", + } + + allocationID := utils.SetupEnterpriseAllocation(t, configPath, params) + + // Fetch allocation details before replacement + beforeAlloc := utils.GetAllocation(t, allocationID) + require.NotNil(t, beforeAlloc, "Allocation should not be nil") + + // Initial amount locked to allocation (0.2 ZCN) + amountTotalLockedToAlloc := int64(2e9) // 0.2 ZCN + + waitForTimeInMinutesWhileLogging(t, 5) + + // Get the blobber to remove (first blobber in allocation) + blobberToRemove := beforeAlloc.BlobberDetails[0].BlobberID + + // Calculate the total stake of the blobber to remove + var prevReplaceeBlobberStake int64 + for _, blobber := range beforeAlloc.Blobbers { + if blobber.ID == blobberToRemove { + prevReplaceeBlobberStake = blobber.TotalStake + } + } + + wd, _ := os.Getwd() + walletFile := filepath.Join(wd, "config", utils.EscapedTestName(t)+"_wallet.json") + configFile := filepath.Join(wd, "config", configPath) + + // Get new blobber details and generate authorization ticket + addBlobberID, addBlobberUrl, err := cli_tests.GetBlobberIdAndUrlNotPartOfAllocation(walletFile, configFile, allocationID) + require.Nil(t, err) + + currentBlobberDetails, err := utils.GetBlobberDetails(t, configPath, addBlobberID) + require.Nil(t, err, "Error fetching blobber details") + require.NotNil(t, currentBlobberDetails, "Error no blobber details found") + + addBlobberAuthTicket, err := utils.GetBlobberAuthTicketWithId(t, configPath, addBlobberID, addBlobberUrl) + require.Nil(t, err, "Unable to generate auth ticket for add blobber") + + // Update allocation to replace blobber + updateParams := createParams(map[string]interface{}{ + "allocation": allocationID, + "add_blobber": addBlobberID, + "add_blobber_auth_ticket": addBlobberAuthTicket, + "remove_blobber": blobberToRemove, + }) + + output, err := updateAllocation(t, configPath, updateParams, true) + require.Nil(t, err, "Error updating allocation", strings.Join(output, "\n")) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + // Fetch updated allocation details + updatedAlloc := utils.GetAllocation(t, allocationID) + require.NotNil(t, updatedAlloc) + + // Validate that the write pool balance matches the expected value + require.InEpsilon(t, amountTotalLockedToAlloc, updatedAlloc.WritePool, 0.01, "Write pool balance doesn't match after blobber replacement") + + txn, err := getTransacionFromSingleSharder(t, updatedAlloc.Tx) + require.Nil(t, err, "Error getting update allocation txn from sharders") + + // Query blobber rewards + rewardQuery := fmt.Sprintf("allocation_id='%s' AND provider_id='%s' AND reward_type=%d", allocationID, blobberToRemove, EnterpriseBlobberReward) + enterpriseReward, err := getQueryRewards(t, rewardQuery) + require.Nil(t, err) + + // Calculate expected payment to replaced blobber + timeUnitInSeconds := int64(600) // 10 minutes + durationOfUsedInSeconds := int64(txn.CreationDate) - updatedAlloc.StartTime + expectedPaymentToReplacedBlobber := 5e8 * durationOfUsedInSeconds / timeUnitInSeconds + + t.Log("Enterprise reward: ", enterpriseReward.TotalReward, "Expected: ", expectedPaymentToReplacedBlobber) + + // Validate enterprise rewards and write pool balance + require.InEpsilon(t, expectedPaymentToReplacedBlobber, enterpriseReward.TotalReward, 0.01, "Enterprise blobber reward doesn't match after blobber replacement") + + // Validate that stake is transferred from the old blobber to the new blobber + var newReplaceeBlobberStake int64 + for _, blobber := range updatedAlloc.Blobbers { + if blobber.ID == addBlobberID { + newReplaceeBlobberStake = blobber.TotalStake + } + } + + require.Equal(t, prevReplaceeBlobberStake, newReplaceeBlobberStake, "Stake should be transferred from old blobber to new") + }) + + t.RunSequentiallyWithTimeout("Replace blobber with same price should work", time.Minute*15, func(t *test.SystemTest) { + utils.SetupWalletWithCustomTokens(t, configPath, 10) + + // Setup allocation with initial parameters + params := map[string]interface{}{ + "size": 1 * GB, + "data": 2, + "parity": 2, + "lock": "0.2", + } + + allocationID := utils.SetupEnterpriseAllocation(t, configPath, params) + + // Fetch allocation details before replacement + beforeAlloc := utils.GetAllocation(t, allocationID) + require.NotNil(t, beforeAlloc, "Allocation should not be nil") + + // Wait for 5 minutes to simulate usage + waitForTimeInMinutesWhileLogging(t, 5) + + // Initial amount locked to allocation (0.2 ZCN) + amountTotalLockedToAlloc := int64(2e9) // 0.2 ZCN + + // Get the blobber to remove (first blobber in allocation) + blobberToRemove := beforeAlloc.BlobberDetails[0].BlobberID + + wd, _ := os.Getwd() + walletFile := filepath.Join(wd, "config", utils.EscapedTestName(t)+"_wallet.json") + configFile := filepath.Join(wd, "config", configPath) + + // Get details of a new blobber not part of the current allocation + addBlobberID, addBlobberUrl, err := cli_tests.GetBlobberIdAndUrlNotPartOfAllocation(walletFile, configFile, allocationID) + require.Nil(t, err) + + currentBlobberDetails, err := utils.GetBlobberDetails(t, configPath, addBlobberID) + require.Nil(t, err, "Error fetching blobber details") + require.NotNil(t, currentBlobberDetails, "No blobber details found") + + // Get the original write price of the blobber + originalPrice := currentBlobberDetails.Terms.WritePrice + + // Update the blobber price to the same value (no change) + err = updateBlobberPrice(t, configPath, addBlobberID, originalPrice) + require.Nil(t, err, "Error updating blobber price") + + addBlobberAuthTicket, err := utils.GetBlobberAuthTicketWithId(t, configPath, addBlobberID, addBlobberUrl) + require.Nil(t, err, "Unable to generate auth ticket for add blobber") + + // Prepare parameters for allocation update + updateParams := createParams(map[string]interface{}{ + "allocation": allocationID, + "add_blobber": addBlobberID, + "add_blobber_auth_ticket": addBlobberAuthTicket, + "remove_blobber": blobberToRemove, + }) + + // Perform the allocation update to replace the blobber + output, err := updateAllocation(t, configPath, updateParams, true) + require.Nil(t, err, "Error updating allocation", strings.Join(output, "\n")) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + // Fetch the updated allocation details + afterAlloc := utils.GetAllocation(t, allocationID) + require.NotNil(t, afterAlloc, "Updated allocation should not be nil") + + // Log the expected and actual write pool balances + t.Logf("Expected write pool %d actual write pool %d", amountTotalLockedToAlloc, afterAlloc.WritePool) + + // Check that the write pool balance matches the expected value + require.InEpsilon(t, amountTotalLockedToAlloc, afterAlloc.WritePool, 0.01, "Write pool balance doesn't match after blobber replacement") + + // Retrieve the transaction details for further analysis + txn, err := getTransacionFromSingleSharder(t, afterAlloc.Tx) + require.Nil(t, err, "Error getting transaction from sharder") + + // Query blobber rewards to validate the enterprise blobber rewards calculation + rewardQuery := fmt.Sprintf("allocation_id='%s' AND provider_id='%s' AND reward_type=%d", allocationID, blobberToRemove, EnterpriseBlobberReward) + enterpriseReward, err := getQueryRewards(t, rewardQuery) + require.Nil(t, err) + + // Calculate the expected payment to the replaced blobber based on the time used and the same blobber price + timeUnitInSeconds := int64(600) // 10 minutes + durationOfUsedInSeconds := int64(txn.CreationDate) - afterAlloc.StartTime + expectedPaymentToReplacedBlobber := 5e8 * durationOfUsedInSeconds / timeUnitInSeconds + + t.Log("Enterprise reward: ", enterpriseReward.TotalReward, "Expected: ", expectedPaymentToReplacedBlobber) + + // Validate the enterprise rewards after blobber replacement + require.InEpsilon(t, expectedPaymentToReplacedBlobber, enterpriseReward.TotalReward, 0.01, "Enterprise blobber reward doesn't match after blobber replacement") + + // Reset the blobber price after the test and clean up + defer func() { + err = updateBlobberPrice(t, configPath, addBlobberID, originalPrice) + require.Nil(t, err, "Error resetting blobber price after test") + + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allocation fail", strings.Join(output, "\n")) + }() + }) + + t.RunSequentiallyWithTimeout("Replace blobber with 0.5x price should work", time.Minute*15, func(t *test.SystemTest) { + utils.SetupWalletWithCustomTokens(t, configPath, 10) + + params := map[string]interface{}{ + "size": 1 * GB, + "data": 2, + "parity": 2, + "lock": "0.2", + } + + allocationID := utils.SetupEnterpriseAllocation(t, configPath, params) + + // allocaiton + alloc := utils.GetAllocation(t, allocationID) + require.NotNil(t, alloc, "Error fetching allocation") + + blobberToRemove := alloc.BlobberDetails[0].BlobberID + + beforeAlloc := utils.GetAllocation(t, allocationID) + require.NotNil(t, beforeAlloc, "Allocation should not be nil") + + waitForTimeInMinutesWhileLogging(t, 5) + + amountTotalLockedToAlloc := int64(1e9) * 2 + + wd, _ := os.Getwd() + walletFile := filepath.Join(wd, "config", utils.EscapedTestName(t)+"_wallet.json") + configFile := filepath.Join(wd, "config", configPath) + + addBlobberID, addBlobberUrl, err := cli_tests.GetBlobberIdAndUrlNotPartOfAllocation(walletFile, configFile, allocationID) + require.Nil(t, err) + + currentBlobberDetails, err := utils.GetBlobberDetails(t, configPath, addBlobberID) + require.Nil(t, err, "Error fetching blobber details") + require.NotNil(t, currentBlobberDetails, "Error no blobber details found") + + halfPrice := currentBlobberDetails.Terms.WritePrice / 2 + amountTotalLockedToAlloc += 0.05 * 1e10 + + // Update blobber price + originalPrice := currentBlobberDetails.Terms.WritePrice + err = updateBlobberPrice(t, configPath, addBlobberID, halfPrice) + require.Nil(t, err, "Error updating blobber price") + + addBlobberAuthTicket, err := utils.GetBlobberAuthTicketWithId(t, configPath, addBlobberID, addBlobberUrl) + require.Nil(t, err, "Unable to generate auth ticket for add blobber") + + updateParams := createParams(map[string]interface{}{ + "allocation": allocationID, + "add_blobber": addBlobberID, + "add_blobber_auth_ticket": addBlobberAuthTicket, + "remove_blobber": blobberToRemove, + "lock": "0.05", + }) + + output, err := updateAllocation(t, configPath, updateParams, true) + require.Nil(t, err, "Error updating allocation", strings.Join(output, "\n")) + require.Len(t, output, 2) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + afterAlloc := utils.GetAllocation(t, allocationID) + require.NotNil(t, afterAlloc, "Updated allocation should not be nil") + + t.Logf("Expected write pool %d actual write pool %d", amountTotalLockedToAlloc, afterAlloc.WritePool) + + require.InEpsilon(t, amountTotalLockedToAlloc, afterAlloc.WritePool, 0.01, "Write pool balance doesn't match after blobber replacement") + + txn, err := getTransacionFromSingleSharder(t, afterAlloc.Tx) + require.Nil(t, err, "Error getting transaction from sharder") + + // Query blobber rewards + rewardQuery := fmt.Sprintf("allocation_id='%s' AND provider_id='%s' AND reward_type=%d", allocationID, blobberToRemove, EnterpriseBlobberReward) + enterpriseReward, err := getQueryRewards(t, rewardQuery) + require.Nil(t, err) + + // Verify blobber rewards calculation + timeUnitInSeconds := int64(600) // 10 minutes + durationOfUsedInSeconds := int64(txn.CreationDate) - afterAlloc.StartTime + expectedPaymentToReplacedBlobber := 5e8 * durationOfUsedInSeconds / timeUnitInSeconds + + t.Log("Enterprise reward: ", enterpriseReward.TotalReward, "Expected: ", expectedPaymentToReplacedBlobber) + + // Validate the enterprise rewards after blobber replacement + require.InEpsilon(t, expectedPaymentToReplacedBlobber, enterpriseReward.TotalReward, 0.01, "Enterprise blobber reward doesn't match after blobber replacement") + + // Reset blobber price + defer func() { + err = updateBlobberPrice(t, configPath, addBlobberID, originalPrice) + require.Nil(t, err, "Error resetting blobber price after test") + + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }() + }) + + t.RunSequentiallyWithTimeout("Replace blobber with 2x price should work", time.Minute*15, func(t *test.SystemTest) { + utils.SetupWalletWithCustomTokens(t, configPath, 10) + + // Setup allocation with initial parameters + params := map[string]interface{}{ + "size": 1 * GB, + "data": 2, + "parity": 2, + "lock": "0.2", + } + + allocationID := utils.SetupEnterpriseAllocation(t, configPath, params) + + // Fetch allocation details before replacement + beforeAlloc := utils.GetAllocation(t, allocationID) + require.NotNil(t, beforeAlloc, "Allocation should not be nil") + + // Wait for 5 minutes to simulate usage + waitForTimeInMinutesWhileLogging(t, 5) + + // Initial amount locked to allocation (0.2 ZCN) + amountTotalLockedToAlloc := int64(2e9) // 0.2 ZCN + + // Get the blobber to remove (first blobber in allocation) + alloc := utils.GetAllocation(t, allocationID) + require.NotNil(t, alloc, "Error fetching allocation") + blobberToRemove := alloc.BlobberDetails[0].BlobberID + + wd, _ := os.Getwd() + walletFile := filepath.Join(wd, "config", utils.EscapedTestName(t)+"_wallet.json") + configFile := filepath.Join(wd, "config", configPath) + + // Get details of a new blobber not part of the current allocation + addBlobberID, addBlobberUrl, err := cli_tests.GetBlobberIdAndUrlNotPartOfAllocation(walletFile, configFile, allocationID) + require.Nil(t, err) + + currentBlobberDetails, err := utils.GetBlobberDetails(t, configPath, addBlobberID) + require.Nil(t, err, "Error fetching blobber details") + require.NotNil(t, currentBlobberDetails, "No blobber details found") + + // Calculate double the current blobber's write price + doublePrice := currentBlobberDetails.Terms.WritePrice * 2 + + // Increment the amount locked by 0.1 ZCN + amountTotalLockedToAlloc += int64(0.1 * 1e10) // 0.1 ZCN + + // Update the blobber price to double of its original price + originalPrice := currentBlobberDetails.Terms.WritePrice + err = updateBlobberPrice(t, configPath, addBlobberID, doublePrice) + require.Nil(t, err, "Error updating blobber price") + + addBlobberAuthTicket, err := utils.GetBlobberAuthTicketWithId(t, configPath, addBlobberID, addBlobberUrl) + require.Nil(t, err, "Unable to generate auth ticket for add blobber") + + // Prepare parameters for allocation update + updateParams := createParams(map[string]interface{}{ + "allocation": allocationID, + "add_blobber": addBlobberID, + "add_blobber_auth_ticket": addBlobberAuthTicket, + "remove_blobber": blobberToRemove, + "lock": "0.1", + }) + + // Perform the allocation update to replace the blobber + output, err := updateAllocation(t, configPath, updateParams, true) + require.Nil(t, err, "Error updating allocation", strings.Join(output, "\n")) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + // Fetch the updated allocation details + afterAlloc := utils.GetAllocation(t, allocationID) + require.NotNil(t, afterAlloc, "Updated allocation should not be nil") + + // Log the expected and actual write pool balances + t.Logf("Expected write pool %d actual write pool %d", amountTotalLockedToAlloc, afterAlloc.WritePool) + + // Check that the write pool balance matches the expected value + require.InEpsilon(t, amountTotalLockedToAlloc, afterAlloc.WritePool, 0.01, "Write pool balance doesn't match after blobber replacement") + + // Retrieve the transaction details for further analysis + txn, err := getTransacionFromSingleSharder(t, afterAlloc.Tx) + require.Nil(t, err, "Error getting transaction from sharder") + + // Query blobber rewards to validate the enterprise blobber rewards calculation + rewardQuery := fmt.Sprintf("allocation_id='%s' AND provider_id='%s' AND reward_type=%d", allocationID, blobberToRemove, EnterpriseBlobberReward) + enterpriseReward, err := getQueryRewards(t, rewardQuery) + require.Nil(t, err) + + // Calculate the expected payment to the replaced blobber based on the time used and the double blobber price + timeUnitInSeconds := int64(600) // 10 minutes + durationOfUsedInSeconds := int64(txn.CreationDate) - afterAlloc.StartTime + expectedPaymentToReplacedBlobber := 5e8 * durationOfUsedInSeconds / timeUnitInSeconds + + t.Log("Enterprise reward: ", enterpriseReward.TotalReward, "Expected: ", expectedPaymentToReplacedBlobber) + + // Validate the enterprise rewards after blobber replacement + require.InEpsilon(t, expectedPaymentToReplacedBlobber, enterpriseReward.TotalReward, 0.01, "Enterprise blobber reward doesn't match after blobber replacement") + + // Reset the blobber price after the test and clean up + defer func() { + err = updateBlobberPrice(t, configPath, addBlobberID, originalPrice) + require.Nil(t, err, "Error resetting blobber price after test") + + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allocation fail", strings.Join(output, "\n")) + }() + }) + + t.Run("Replace blobber with the same one in allocation, shouldn't work", func(t *test.SystemTest) { + allocationID, blobberToRemove := setupAllocationAndGetRandomBlobber(t, configPath) + + blobberAuthTickets, _ := utils.GenerateBlobberAuthTickets(t, configPath) + addBlobberAuthTicket := blobberAuthTickets[0] + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "add_blobber": blobberToRemove, + "add_blobber_auth_ticket": addBlobberAuthTicket, + "remove_blobber": blobberToRemove, + }) + + output, err := updateAllocation(t, configPath, params, false) + require.NotNil(t, err, "Expected error updating allocation but got none", strings.Join(output, "\n")) + require.Regexp(t, replaceBlobberWithSameIdFailRegex, strings.Join(output, "\n"), + "Error regex match fail update allocation for replace blobber with the same one") + }) + + t.Run("Replace blobber with incorrect blobber ID of an old blobber, shouldn't work", func(t *test.SystemTest) { + allocationID, blobberToRemove := setupAllocationAndGetRandomBlobber(t, configPath) + + incorrectBlobberID := "1234abc" + + blobberAuthTickets, _ := utils.GenerateBlobberAuthTickets(t, configPath) + + addBlobberAuthTicket := blobberAuthTickets[0] + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "add_blobber": incorrectBlobberID, + "add_blobber_auth_ticket": addBlobberAuthTicket, + "remove_blobber": blobberToRemove, + }) + + output, err := updateAllocation(t, configPath, params, false) + require.NotNil(t, err, "Expected error updating allocation but got none", strings.Join(output, "\n")) + require.Regexp(t, replaceBlobberWithWrongIdFailRegex, strings.Join(output, "\n"), + "Error regex match update allocation for replace blobber with incorrect blobber") + }) +} + +func setupAllocationAndGetRandomBlobber(t *test.SystemTest, cliConfigFilename string, extraParams ...map[string]interface{}) (allocationId, randomBlobberId string) { + utils.SetupWalletWithCustomTokens(t, cliConfigFilename, 10) + + options := map[string]interface{}{ + "data": 2, + "parity": 2, + "size": 1 * GB, + "lock": "0.2", + } + + for _, params := range extraParams { + for k, v := range params { + options[k] = v + } + } + + allocationID := utils.SetupEnterpriseAllocation(t, cliConfigFilename, options) + + wd, _ := os.Getwd() + walletFile := filepath.Join(wd, "config", utils.EscapedTestName(t)+"_wallet.json") + configFile := filepath.Join(wd, "config", cliConfigFilename) + + randomBlobber, err := cli_tests.GetRandomBlobber(walletFile, configFile, allocationID, "") + require.Nil(t, err, "Error getting random blobber") + + return allocationID, randomBlobber +} + +func updateBlobberPrice(t *test.SystemTest, configPath, blobberID string, newPrice int64) error { + params := map[string]interface{}{ + "blobber_id": blobberID, + "write_price": utils.IntToZCN(newPrice), + } + output, err := utils.ExecuteFaucetWithTokensForWallet(t, "wallets/blobber_owner", configPath, 99) + require.Nil(t, err, "Error executing faucet", strings.Join(output, "\n")) + + output, err = utils.UpdateBlobberInfoForWallet(t, configPath, "wallets/blobber_owner", utils.CreateParams(params)) + if err != nil { + t.Log("Error updating blobber price: " + strings.Join(output, "\n")) + return err + } + + t.Log("Updated blobber price:", strings.Join(output, "\n")) + return nil +} diff --git a/tests/tokenomics_tests/enterprise_blobber_update_allocation_test.go b/tests/tokenomics_tests/enterprise_blobber_update_allocation_test.go new file mode 100644 index 0000000000..72681d6565 --- /dev/null +++ b/tests/tokenomics_tests/enterprise_blobber_update_allocation_test.go @@ -0,0 +1,1672 @@ +package tokenomics_tests + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "reflect" + "regexp" + "strconv" + "strings" + "testing" + "time" + + "github.com/0chain/system_test/tests/cli_tests" + "github.com/0chain/system_test/tests/tokenomics_tests/utils" + + "github.com/0chain/system_test/internal/api/util/test" + + "github.com/stretchr/testify/require" + + climodel "github.com/0chain/system_test/internal/cli/model" + cliutils "github.com/0chain/system_test/internal/cli/util" +) + +var ( + updateAllocationRegex = regexp.MustCompile(`^Allocation updated with txId : [a-f0-9]{64}$`) + repairCompletednRegex = regexp.MustCompile(`Repair file completed, Total files repaired: {2}1`) +) + +func costOfAlloc(alloc *climodel.Allocation) int64 { + cost := float64(0) + for _, blobber := range alloc.BlobberDetails { + cost += (sizeInGB(alloc.Size) / float64(alloc.DataShards)) * float64(blobber.Terms.WritePrice) + } + + return int64(cost) +} + +func TestUpdateEnterpriseAllocation(testSetup *testing.T) { + t := test.NewSystemTest(testSetup) + + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error creating configuration wallet", strings.Join(output, "\n")) + + var blobbersList []climodel.Blobber + output, err = utils.ListBlobbers(t, configPath, "--json") + require.Nil(t, err, "Error fetching blobbers %v", strings.Join(output, "\n")) + require.Len(t, output, 1, "Error wrong json format", strings.Join(output, "\n")) + + err = json.NewDecoder(strings.NewReader(output[0])).Decode(&blobbersList) + + require.Nil(t, err, "Error decoding blobbers json") + + t.TestSetup("set storage config to use time_unit as 10 minutes", func() { + output, err := utils.UpdateStorageSCConfig(t, scOwnerWallet, map[string]string{ + "time_unit": "10m", + }, true) + require.Nil(t, err, strings.Join(output, "\n")) + }) + + t.Cleanup(func() { + output, err := utils.UpdateStorageSCConfig(t, scOwnerWallet, map[string]string{ + "time_unit": "1h", + }, true) + require.Nil(t, err, strings.Join(output, "\n")) + + var blobbers []climodel.Blobber + output, err = utils.ListBlobbers(t, configPath, "--json") + require.Nil(t, err, "Error listing blobberes", strings.Join(output, "\n")) + require.Len(t, output, 1, "Error invalid json length", strings.Join(output, "\n")) + + err = json.NewDecoder(strings.NewReader(output[0])).Decode(&blobbers) + require.Nil(t, err, "Error decoding blobbers list", strings.Join(output, "\n")) + + for _, blobber := range blobbers { + if blobber.Terms.WritePrice != 1e9 { + err := updateBlobberPrice(t, configPath, blobber.ID, 1e9) + require.Nil(t, err, "Error resetting blobber write prices") + } + } + }) + + t.RunSequentiallyWithTimeout("Blobber price change extend size of allocation", 25*time.Minute, func(t *test.SystemTest) { + utils.SetupWalletWithCustomTokens(t, configPath, 10) + + amountTotalLockedToAlloc := int64(2e9) // 0.2ZCN + allocationID := utils.SetupEnterpriseAllocation(t, configPath, map[string]interface{}{ + "size": 1 * GB, + "data": 3, + "parity": 3, + "lock": 0.2, // 2GB total size where write price per blobber is 0.1ZCN + }) + + beforeAlloc := utils.GetAllocation(t, allocationID) + t.Logf("Update 1 Allocation %+v\n", beforeAlloc) + + blobber := beforeAlloc.BlobberDetails[0] + t.Logf("Blobber old write price %d", blobber.Terms.WritePrice) + t.Logf("Blobber new write price %d", 2*blobber.Terms.WritePrice) + + err := updateBlobberPrice(t, configPath, blobber.BlobberID, blobber.Terms.WritePrice*2) + require.Nil(t, err, "Error updating blobber price") + + waitForTimeInMinutesWhileLogging(t, 5) + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "lock": 0.5, + "size": 1 * GB, + }) + output, err := updateAllocation(t, configPath, params, true) + + amountTotalLockedToAlloc += 5e9 // 0.5ZCN + + require.Nil(t, err, "Could not update "+ + "allocation due to error", strings.Join(output, "\n")) + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + afterAlloc := utils.GetAllocation(t, allocationID) + t.Logf("Update 1 immediate Allocation %+v\n", afterAlloc) + + require.Less(t, beforeAlloc.ExpirationDate, afterAlloc.ExpirationDate, + fmt.Sprint("Expiration Time doesn't match: "+ + "Before:", beforeAlloc.ExpirationDate, "After:", afterAlloc.ExpirationDate), + ) + + // Verify write pool calculations + timeUnitInSeconds := int64(600) // 10 minutes + durationOfUsedInSeconds := afterAlloc.ExpirationDate - beforeAlloc.StartTime - timeUnitInSeconds + + realCostOfBeforeAlloc := costOfAlloc(&beforeAlloc) + expectedPaymentToBlobbers := realCostOfBeforeAlloc * durationOfUsedInSeconds / timeUnitInSeconds + expectedWritePoolBalance := amountTotalLockedToAlloc - expectedPaymentToBlobbers + amountTotalLockedToAlloc = expectedWritePoolBalance + // Log all values + t.Logf("Time unit in seconds: %d", timeUnitInSeconds) + t.Logf("Duration of used in seconds: %d", durationOfUsedInSeconds) + t.Logf("Expire time before: %d", beforeAlloc.ExpirationDate) + t.Logf("Expire time after: %d", afterAlloc.ExpirationDate) + t.Logf("Real cost of before alloc: %d", realCostOfBeforeAlloc) + t.Logf("Expected payment to blobbers: %d", expectedPaymentToBlobbers) + t.Logf("Expected write pool balance: %d", expectedWritePoolBalance) + t.Logf("Write pool balance: %d", afterAlloc.WritePool) + + require.InEpsilon(t, expectedWritePoolBalance, afterAlloc.WritePool, 0.01, "Write pool balance doesn't match") + + // Verify blobber rewards calculations + rewardQuery := fmt.Sprintf("allocation_id='%s' AND reward_type=%d", allocationID, EnterpriseBlobberReward) + enterpriseReward, err := getQueryRewards(t, rewardQuery) + require.Nil(t, err) + + t.Logf("Enterprise reward: %+v", enterpriseReward) + t.Log("Enterprise reward: ", enterpriseReward.TotalReward, "Expected: ", expectedPaymentToBlobbers) + + require.InEpsilon(t, expectedPaymentToBlobbers, enterpriseReward.TotalReward, 0.01, "Enterprise blobber reward doesn't match") + + afterUpdate1Alloc := utils.GetAllocation(t, allocationID) + t.Logf("Update 1 Allocation %+v\n", afterUpdate1Alloc) + + waitForTimeInMinutesWhileLogging(t, 5) + + // Upgrade 2 + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "lock": 0.5, + "size": 1 * GB, + }) + output, err = updateAllocation(t, configPath, params, true) + amountTotalLockedToAlloc += 5e9 // 0.5ZCN + + require.Nil(t, err, "Could not update "+ + "allocation due to error", strings.Join(output, "\n")) + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + afterAlloc = utils.GetAllocation(t, allocationID) + t.Logf("Update 2 Allocation %+v\n", afterAlloc) + + require.Less(t, afterUpdate1Alloc.ExpirationDate, afterAlloc.ExpirationDate, + fmt.Sprint("Expiration Time doesn't match: "+ + "Before:", afterUpdate1Alloc.ExpirationDate, "After:", afterAlloc.ExpirationDate), + ) + + // Verify write pool calculations + timeUnitInSeconds = int64(600) // 10 minutes + afterFirstUpdateAllocStartTime := afterUpdate1Alloc.ExpirationDate - timeUnitInSeconds + durationOfUsedInSeconds = afterAlloc.ExpirationDate - afterFirstUpdateAllocStartTime - timeUnitInSeconds // 50 + + realCostOfBeforeAlloc = costOfAlloc(&afterUpdate1Alloc) + expectedPaymentToBlobbersAfterFirstUpdate := realCostOfBeforeAlloc * durationOfUsedInSeconds / timeUnitInSeconds + expectedPaymentToBlobbers += expectedPaymentToBlobbersAfterFirstUpdate + expectedWritePoolBalance = amountTotalLockedToAlloc - expectedPaymentToBlobbersAfterFirstUpdate + + // Log all values + t.Logf("Time unit in seconds: %d", timeUnitInSeconds) + t.Logf("Duration of used in seconds: %d", durationOfUsedInSeconds) + t.Logf("Expire time before: %d", beforeAlloc.ExpirationDate) + t.Logf("Expire time after: %d", afterAlloc.ExpirationDate) + t.Logf("Real cost of before alloc: %d", realCostOfBeforeAlloc) + t.Logf("Expected payment to blobbers: %d", expectedPaymentToBlobbers) + t.Logf("Expected write pool balance: %d", expectedWritePoolBalance) + t.Logf("Write pool balance: %d", afterAlloc.WritePool) + + require.InEpsilon(t, expectedWritePoolBalance, afterAlloc.WritePool, 0.01, "Write pool balance doesn't match") + + // Verify blobber rewards calculations + rewardQuery = fmt.Sprintf("allocation_id='%s' AND reward_type=%d", allocationID, EnterpriseBlobberReward) + enterpriseReward, err = getQueryRewards(t, rewardQuery) + require.Nil(t, err) + + t.Logf("Enterprise reward: %+v", enterpriseReward) + t.Log("Enterprise reward: ", enterpriseReward.TotalReward, "Expected: ", expectedPaymentToBlobbers) + + require.InEpsilon(t, expectedPaymentToBlobbers, enterpriseReward.TotalReward, 0.01, "Enterprise blobber reward doesn't match") + + // reset blobber write price. + err = updateBlobberPrice(t, configPath, blobber.BlobberID, blobber.Terms.WritePrice) + require.Nil(t, err, "Error resting blobber prices to original") + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.RunSequentiallyWithTimeout("Blobber price change extend duration of allocation", 25*time.Minute, func(t *test.SystemTest) { + utils.SetupWalletWithCustomTokens(t, configPath, 10) + + amountTotalLockedToAlloc := int64(2e9) // 0.2ZCN + allocationID := utils.SetupEnterpriseAllocation(t, configPath, map[string]interface{}{ + "size": 1 * GB, + "data": 3, + "parity": 3, + "lock": 0.2, // 2GB total size where write price per blobber is 0.1ZCN + }) + + beforeAlloc := utils.GetAllocation(t, allocationID) + t.Logf("Before Allocation %+v\n", beforeAlloc) + blobber := beforeAlloc.BlobberDetails[0] + + err := updateBlobberPrice(t, configPath, blobber.BlobberID, blobber.Terms.WritePrice*2) + require.Nil(t, err, "Error updating blobber prices") + + waitForTimeInMinutesWhileLogging(t, 5) + + // First update + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "extend": true, + "lock": 0.5, + }) + output, err := updateAllocation(t, configPath, params, true) + amountTotalLockedToAlloc += 5e9 + + require.Nil(t, err, "Could not update "+ + "allocation due to error", strings.Join(output, "\n")) + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + afterAlloc := utils.GetAllocation(t, allocationID) + require.Less(t, beforeAlloc.ExpirationDate, afterAlloc.ExpirationDate, + fmt.Sprint("Expiration Time doesn't match: "+ + "Before:", beforeAlloc.ExpirationDate, "After:", afterAlloc.ExpirationDate), + ) + + // Verify write pool calculations + timeUnitInSeconds := int64(600) // 10 minutes + durationOfUsedInSeconds := afterAlloc.ExpirationDate - beforeAlloc.StartTime - timeUnitInSeconds + + realCostOfBeforeAlloc := costOfAlloc(&beforeAlloc) + expectedPaymentToBlobbers := realCostOfBeforeAlloc * durationOfUsedInSeconds / timeUnitInSeconds + expectedWritePoolBalance := amountTotalLockedToAlloc - expectedPaymentToBlobbers + amountTotalLockedToAlloc = expectedWritePoolBalance + + // Log all values + t.Logf("Time unit in seconds: %d", timeUnitInSeconds) + t.Logf("Duration of used in seconds: %d", durationOfUsedInSeconds) + t.Logf("Expire time before: %d", beforeAlloc.ExpirationDate) + t.Logf("Expire time after: %d", afterAlloc.ExpirationDate) + t.Logf("Real cost of before alloc: %d", realCostOfBeforeAlloc) + t.Logf("Expected payment to blobbers: %d", expectedPaymentToBlobbers) + t.Logf("Expected write pool balance: %d", expectedWritePoolBalance) + t.Logf("Write pool balance: %d", afterAlloc.WritePool) + + require.InEpsilon(t, expectedWritePoolBalance, afterAlloc.WritePool, 0.01, "Write pool balance doesn't match") + + // Verify blobber rewards calculations + rewardQuery := fmt.Sprintf("allocation_id='%s' AND reward_type=%d", allocationID, EnterpriseBlobberReward) + enterpriseReward, err := getQueryRewards(t, rewardQuery) + require.Nil(t, err) + + t.Logf("Enterprise reward: %+v", enterpriseReward) + t.Log("Enterprise reward: ", enterpriseReward.TotalReward, "Expected: ", expectedPaymentToBlobbers) + + require.InEpsilon(t, expectedPaymentToBlobbers, enterpriseReward.TotalReward, 0.01, "Enterprise blobber reward doesn't match") + + afterUpdate1Alloc := utils.GetAllocation(t, allocationID) + t.Logf("Update 1 Allocation %+v\n", afterUpdate1Alloc) + + waitForTimeInMinutesWhileLogging(t, 5) + + // Second update + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "extend": true, + "lock": 0.5, + }) + output, err = updateAllocation(t, configPath, params, true) + amountTotalLockedToAlloc += 5e9 + + require.Nil(t, err, "Could not update "+ + "allocation due to error", strings.Join(output, "\n")) + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + afterAlloc = utils.GetAllocation(t, allocationID) + t.Logf("Update 2 Allocation %+v\n", afterAlloc) + + require.Less(t, afterUpdate1Alloc.ExpirationDate, afterAlloc.ExpirationDate, + fmt.Sprint("Expiration Time doesn't match: "+ + "Before:", afterUpdate1Alloc.ExpirationDate, "After:", afterAlloc.ExpirationDate), + ) + + // Verify write pool calculations + timeUnitInSeconds = int64(600) // 10 minutes + afterFirstUpdateAllocStartTime := afterUpdate1Alloc.ExpirationDate - timeUnitInSeconds + durationOfUsedInSeconds = afterAlloc.ExpirationDate - afterFirstUpdateAllocStartTime - timeUnitInSeconds // 50 + + realCostOfBeforeAlloc = costOfAlloc(&afterUpdate1Alloc) + expectedPaymentToBlobbersAfterSecondUpdate := realCostOfBeforeAlloc * durationOfUsedInSeconds / timeUnitInSeconds + expectedPaymentToBlobbers += expectedPaymentToBlobbersAfterSecondUpdate + expectedWritePoolBalance = amountTotalLockedToAlloc - expectedPaymentToBlobbersAfterSecondUpdate + + // Log all values + t.Logf("Time unit in seconds: %d", timeUnitInSeconds) + t.Logf("Duration of used in seconds: %d", durationOfUsedInSeconds) + t.Logf("Expire time before: %d", afterUpdate1Alloc.ExpirationDate) + t.Logf("Expire time after: %d", afterAlloc.ExpirationDate) + t.Logf("Real cost of before alloc: %d", realCostOfBeforeAlloc) + t.Logf("Expected payment to blobbers: %d", expectedPaymentToBlobbers) + t.Logf("Expected write pool balance: %d", expectedWritePoolBalance) + t.Logf("Write pool balance: %d", afterAlloc.WritePool) + + require.InEpsilon(t, expectedWritePoolBalance, afterAlloc.WritePool, 0.01, "Write pool balance doesn't match") + + // Verify blobber rewards calculations + rewardQuery = fmt.Sprintf("allocation_id='%s' AND reward_type=%d", allocationID, EnterpriseBlobberReward) + enterpriseReward, err = getQueryRewards(t, rewardQuery) + require.Nil(t, err) + + t.Logf("Enterprise reward: %+v", enterpriseReward) + t.Log("Enterprise reward: ", enterpriseReward.TotalReward, "Expected: ", expectedPaymentToBlobbers) + + require.InEpsilon(t, expectedPaymentToBlobbers, enterpriseReward.TotalReward, 0.01, "Enterprise blobber reward doesn't match") + + // Cleanup + err = updateBlobberPrice(t, configPath, blobber.BlobberID, blobber.Terms.WritePrice) + require.Nil(t, err, "Error updating blobber price") + + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.RunWithTimeout("Extend duration cost calculation", 15*time.Minute, func(t *test.SystemTest) { + utils.SetupWalletWithCustomTokens(t, configPath, 10) + + amountTotalLockedToAlloc := int64(2e9) // 0.2ZCN + allocationID := utils.SetupEnterpriseAllocation(t, configPath, map[string]interface{}{ + "size": 1 * GB, + "data": 3, + "parity": 3, + "lock": 0.2, // 2GB total size where write price per blobber is 0.1ZCN + }) + + beforeAlloc := utils.GetAllocation(t, allocationID) + + realCostOfBeforeAlloc := costOfAlloc(&beforeAlloc) + + waitForTimeInMinutesWhileLogging(t, 5) + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "extend": true, + "lock": 0.2, // Locking 0.2 ZCN as we can't calculate the accurate time of update, will validate numbers after updating the alloc + }) + output, err := updateAllocation(t, configPath, params, true) + amountTotalLockedToAlloc += 2e9 // 0.2ZCN + + require.Nil(t, err, "Could not update "+ + "allocation due to error", strings.Join(output, "\n")) + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + afterAlloc := utils.GetAllocation(t, allocationID) + require.Less(t, beforeAlloc.ExpirationDate, afterAlloc.ExpirationDate, + fmt.Sprint("Expiration Time doesn't match: "+ + "Before:", beforeAlloc.ExpirationDate, "After:", afterAlloc.ExpirationDate), + ) + + // Verify write pool calculations + timeUnitInSeconds := int64(600) // 10 minutes + durationOfUsedInSeconds := afterAlloc.ExpirationDate - beforeAlloc.StartTime - timeUnitInSeconds + + expectedPaymentToBlobbers := realCostOfBeforeAlloc * durationOfUsedInSeconds / timeUnitInSeconds + expectedWritePoolBalance := amountTotalLockedToAlloc - expectedPaymentToBlobbers + + // Log all values + t.Logf("Time unit in seconds: %d", timeUnitInSeconds) + t.Logf("Duration of used in seconds: %d", durationOfUsedInSeconds) + t.Logf("Expire time before: %d", beforeAlloc.ExpirationDate) + t.Logf("Expire time after: %d", afterAlloc.ExpirationDate) + t.Logf("Real cost of before alloc: %d", realCostOfBeforeAlloc) + t.Logf("Expected payment to blobbers: %d", expectedPaymentToBlobbers) + t.Logf("Expected write pool balance: %d", expectedWritePoolBalance) + t.Logf("Write pool balance: %d", afterAlloc.WritePool) + + require.InEpsilon(t, expectedWritePoolBalance, afterAlloc.WritePool, 0.01, "Write pool balance doesn't match") + + // Verify blobber rewards calculations + rewardQuery := fmt.Sprintf("allocation_id='%s' AND reward_type=%d", allocationID, EnterpriseBlobberReward) + enterpriseReward, err := getQueryRewards(t, rewardQuery) + require.Nil(t, err) + + t.Log("Enterprise reward: ", enterpriseReward.TotalReward, "Expected: ", expectedPaymentToBlobbers) + + require.InEpsilon(t, expectedPaymentToBlobbers, enterpriseReward.TotalReward, 0.01, "Enterprise blobber reward doesn't match") + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.RunWithTimeout("Upgrade size cost calculation", 15*time.Minute, func(t *test.SystemTest) { + utils.SetupWalletWithCustomTokens(t, configPath, 10) + + amountTotalLockedToAlloc := int64(2e9) // 0.2ZCN + allocationID := utils.SetupEnterpriseAllocation(t, configPath, map[string]interface{}{ + "size": 1 * GB, + "data": 3, + "parity": 3, + "lock": 0.2, // 2GB total size where write price per blobber is 0.1ZCN + }) + + beforeAlloc := utils.GetAllocation(t, allocationID) + + waitForTimeInMinutesWhileLogging(t, 5) + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "extend": true, + "lock": 0.4, // Locking 0.4 ZCN (double as we have new size) as we can't calculate the accurate time of update, will validate numbers after updating the alloc + "size": 1 * GB, + }) + output, err := updateAllocation(t, configPath, params, true) + amountTotalLockedToAlloc += 4e9 // 0.4ZCN + + require.Nil(t, err, "Could not update "+ + "allocation due to error", strings.Join(output, "\n")) + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + afterAlloc := utils.GetAllocation(t, allocationID) + require.Less(t, beforeAlloc.ExpirationDate, afterAlloc.ExpirationDate, + fmt.Sprint("Expiration Time doesn't match: "+ + "Before:", beforeAlloc.ExpirationDate, "After:", afterAlloc.ExpirationDate), + ) + + // Verify write pool calculations + timeUnitInSeconds := int64(600) // 10 minutes + durationOfUsedInSeconds := afterAlloc.ExpirationDate - beforeAlloc.StartTime - timeUnitInSeconds + + realCostOfBeforeAlloc := costOfAlloc(&beforeAlloc) + expectedPaymentToBlobbers := realCostOfBeforeAlloc * durationOfUsedInSeconds / timeUnitInSeconds + expectedWritePoolBalance := amountTotalLockedToAlloc - expectedPaymentToBlobbers + + // Log all values + t.Logf("Time unit in seconds: %d", timeUnitInSeconds) + t.Logf("Duration of used in seconds: %d", durationOfUsedInSeconds) + t.Logf("Expire time before: %d", beforeAlloc.ExpirationDate) + t.Logf("Expire time after: %d", afterAlloc.ExpirationDate) + t.Logf("Real cost of before alloc: %d", realCostOfBeforeAlloc) + t.Logf("Expected payment to blobbers: %d", expectedPaymentToBlobbers) + t.Logf("Expected write pool balance: %d", expectedWritePoolBalance) + t.Logf("Write pool balance: %d", afterAlloc.WritePool) + + require.InEpsilon(t, expectedWritePoolBalance, afterAlloc.WritePool, 0.01, "Write pool balance doesn't match") + + // Verify blobber rewards calculations + rewardQuery := fmt.Sprintf("allocation_id='%s' AND reward_type=%d", allocationID, EnterpriseBlobberReward) + enterpriseReward, err := getQueryRewards(t, rewardQuery) + require.Nil(t, err) + + t.Log("Enterprise reward: ", enterpriseReward.TotalReward, "Expected: ", expectedPaymentToBlobbers) + + require.InEpsilon(t, expectedPaymentToBlobbers, enterpriseReward.TotalReward, 0.01, "Enterprise blobber reward doesn't match") + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.RunWithTimeout("Add blobber cost calculation", 15*time.Minute, func(t *test.SystemTest) { + utils.SetupWalletWithCustomTokens(t, configPath, 10) + + amountTotalLockedToAlloc := int64(2e9) // 0.2ZCN + allocationID := utils.SetupEnterpriseAllocation(t, configPath, map[string]interface{}{ + "size": 1 * GB, + "data": 2, + "parity": 2, + "lock": 0.2, // 2GB total size where write price per blobber is 0.1ZCN + }) + + waitForTimeInMinutesWhileLogging(t, 5) + + wd, _ := os.Getwd() + walletFile := filepath.Join(wd, "config", utils.EscapedTestName(t)+"_wallet.json") + configFile := filepath.Join(wd, "config", configPath) + addBlobberID, addBlobberUrl, err := utils.GetBlobberIdAndUrlNotPartOfAllocation(walletFile, configFile, allocationID) + require.Nil(t, err) + + addBlobberAuthTicket, err := utils.GetBlobberAuthTicketWithId(t, configPath, addBlobberID, addBlobberUrl) + require.Nil(t, err, "Unable to generate auth ticket for add blobber") + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "lock": 0.05, // Locking 0.05 ZCN (cost of single blobber) + "add_blobber": addBlobberID, + "add_blobber_auth_ticket": addBlobberAuthTicket, + }) + output, err := updateAllocation(t, configPath, params, true) + amountTotalLockedToAlloc += 0.05 * 1e10 // 0.25ZCN + + require.Nil(t, err, "Could not update "+ + "allocation due to error", strings.Join(output, "\n")) + require.Len(t, output, 2) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + afterAlloc := utils.GetAllocation(t, allocationID) + + require.InEpsilon(t, amountTotalLockedToAlloc, afterAlloc.WritePool, 0.01, "Write pool balance doesn't match") + + rewardQuery := fmt.Sprintf("allocation_id='%s' AND reward_type=%d", allocationID, EnterpriseBlobberReward) + enterpriseReward, err := getQueryRewards(t, rewardQuery) + require.Nil(t, err) + + require.Equal(t, float64(0), enterpriseReward.TotalReward, "Enterprise blobber reward should be 0") + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.RunWithTimeout("Replace blobber cost calculation", 15*time.Minute, func(t *test.SystemTest) { + utils.SetupWalletWithCustomTokens(t, configPath, 10) + + amountTotalLockedToAlloc := int64(2e9) // 0.2ZCN + allocationID := utils.SetupEnterpriseAllocation(t, configPath, map[string]interface{}{ + "size": 1 * GB, + "data": 2, + "parity": 2, + "lock": 0.2, // 2GB total size where write price per blobber is 0.1ZCN + }) + + beforeAlloc := utils.GetAllocation(t, allocationID) + + waitForTimeInMinutesWhileLogging(t, 5) + + wd, _ := os.Getwd() + walletFile := filepath.Join(wd, "config", utils.EscapedTestName(t)+"_wallet.json") + configFile := filepath.Join(wd, "config", configPath) + addBlobberID, addBlobberUrl, err := utils.GetBlobberIdAndUrlNotPartOfAllocation(walletFile, configFile, allocationID) + require.Nil(t, err) + + addBlobberAuthTicket, err := utils.GetBlobberAuthTicketWithId(t, configPath, addBlobberID, addBlobberUrl) + require.Nil(t, err, "Unable to generate auth ticket for add blobber") + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "add_blobber": addBlobberID, + "add_blobber_auth_ticket": addBlobberAuthTicket, + "remove_blobber": beforeAlloc.BlobberDetails[0].BlobberID, + }) + output, err := updateAllocation(t, configPath, params, true) + + require.Nil(t, err, "Could not update "+ + "allocation due to error", strings.Join(output, "\n")) + require.Len(t, output, 2) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + afterAlloc := utils.GetAllocation(t, allocationID) + + require.InEpsilon(t, amountTotalLockedToAlloc, afterAlloc.WritePool, 0.01, "Write pool balance doesn't match") + + txn, err := getTransacionFromSingleSharder(t, afterAlloc.Tx) + require.Nil(t, err) + + // Verify blobber rewards calculations + timeUnitInSeconds := int64(600) // 10 minutes + durationOfUsedInSeconds := int64(txn.CreationDate) - afterAlloc.StartTime + + expectedPaymentToReplacedBlobber := 5e8 * durationOfUsedInSeconds / timeUnitInSeconds + + rewardQuery := fmt.Sprintf("allocation_id='%s' AND provider_id='%s' AND reward_type=%d", allocationID, beforeAlloc.BlobberDetails[0].BlobberID, EnterpriseBlobberReward) + enterpriseReward, err := getQueryRewards(t, rewardQuery) + require.Nil(t, err) + + t.Log("Enterprise reward: ", enterpriseReward.TotalReward, "Expected: ", expectedPaymentToReplacedBlobber) + + require.InEpsilon(t, expectedPaymentToReplacedBlobber, enterpriseReward.TotalReward, 0.01, "Enterprise blobber reward doesn't match") + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.RunWithTimeout("Update Expiry Should Work", 15*time.Minute, func(t *test.SystemTest) { + allocationID, allocationBeforeUpdate := setupAndParseAllocation(t, configPath, map[string]interface{}{ + "lock": "0.1", + }) + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "extend": true, + }) + + time.Sleep(5 * time.Minute) + + output, err := updateAllocation(t, configPath, params, true) + + require.Nil(t, err, "Could not update "+ + "allocation due to error", strings.Join(output, "\n")) + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + ac := utils.GetAllocation(t, allocationID) + require.Less(t, allocationBeforeUpdate.ExpirationDate, ac.ExpirationDate, + fmt.Sprint("Expiration Time doesn't match: "+ + "Before:", allocationBeforeUpdate.ExpirationDate, "After:", ac.ExpirationDate), + ) + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.Run("Update Size Should Work", func(t *test.SystemTest) { + allocationID, allocationBeforeUpdate := setupAndParseAllocation(t, configPath) + size := int64(256) + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "size": size, + }) + output, err := updateAllocation(t, configPath, params, true) + + require.Nil(t, err, "Could not update allocation "+ + "due to error", strings.Join(output, "\n")) + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + allocations := parseListAllocations(t, configPath) + ac, ok := allocations[allocationID] + require.True(t, ok, "current allocation not found", allocationID, allocations) + require.Equal(t, allocationBeforeUpdate.Size+size, ac.Size, + fmt.Sprint("Size doesn't match: Before:", allocationBeforeUpdate.Size, "After:", ac.Size), + ) + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.Run("Update All Parameters Should Work", func(t *test.SystemTest) { + allocationID, allocationBeforeUpdate := setupAndParseAllocation(t, configPath) + size := int64(2048) + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "extend": true, + "size": size, + }) + output, err := updateAllocation(t, configPath, params, true) + + require.Nil(t, err, "Could not update allocation due to error", strings.Join(output, "\n")) + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + allocations := parseListAllocations(t, configPath) + ac, ok := allocations[allocationID] + require.True(t, ok, "current allocation not found", allocationID, allocations) + require.Less(t, allocationBeforeUpdate.ExpirationDate, ac.ExpirationDate) + require.Equal(t, allocationBeforeUpdate.Size+size, ac.Size) + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.RunWithTimeout("Update Allocation flags for forbid and allow file_options should succeed", 8*time.Minute, func(t *test.SystemTest) { + _, err := utils.CreateWallet(t, configPath) + require.Nil(t, err) + + allocationID := setupAllocation(t, configPath) + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "forbid_upload": nil, + }) + output, err := updateAllocation(t, configPath, params, true) + if err != nil { + require.Contains(t, err.Error(), "update allocation changes nothing") + } else { + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + } + + alloc := utils.GetAllocation(t, allocationID) + require.Equal(t, uint16(0), alloc.FileOptions&(1<<0)) + + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "forbid_delete": nil, + }) + t.Logf("forbidden delete") + output, err = updateAllocation(t, configPath, params, true) + if err != nil { + require.Contains(t, err.Error(), "update allocation changes nothing") + } else { + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + } + + alloc = utils.GetAllocation(t, allocationID) + require.Equal(t, uint16(0), alloc.FileOptions&(1<<1)) + + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "forbid_update": nil, + }) + t.Logf("forbidden update") + output, err = updateAllocation(t, configPath, params, true) + if err != nil { + require.Contains(t, err.Error(), "update allocation changes nothing") + } else { + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + } + + alloc = utils.GetAllocation(t, allocationID) + require.Equal(t, uint16(0), alloc.FileOptions&(1<<2)) + + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "forbid_move": nil, + }) + t.Logf("forbidden move") + output, err = updateAllocation(t, configPath, params, true) + if err != nil { + require.Contains(t, err.Error(), "update allocation changes nothing") + } else { + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + } + + alloc = utils.GetAllocation(t, allocationID) + require.Equal(t, uint16(0), alloc.FileOptions&(1<<3)) + + t.Logf("forbidden copy") + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "forbid_copy": nil, + }) + output, err = updateAllocation(t, configPath, params, true) + if err != nil { + require.Contains(t, err.Error(), "update allocation changes nothing") + } else { + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + } + + alloc = utils.GetAllocation(t, allocationID) + require.Equal(t, uint16(0), alloc.FileOptions&(1<<4)) + + t.Logf("forbidden rename") + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "forbid_rename": nil, + }) + output, err = updateAllocation(t, configPath, params, true) + if err != nil { + require.Contains(t, err.Error(), "update allocation changes nothing") + } else { + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + } + + alloc = utils.GetAllocation(t, allocationID) + require.Equal(t, uint16(0), alloc.FileOptions&(1<<5)) + + t.Logf("allow upload") + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "forbid_upload": false, + }) + + output, err = updateAllocation(t, configPath, params, true) + if err != nil { + require.Contains(t, err.Error(), "update allocation changes nothing") + } else { + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + } + + alloc = utils.GetAllocation(t, allocationID) + require.Equal(t, uint16(1), alloc.FileOptions) + + t.Logf("allow delete") + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "forbid_delete": false, + }) + output, err = updateAllocation(t, configPath, params, true) + if err != nil { + require.Contains(t, err.Error(), "update allocation changes nothing") + } else { + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + } + + alloc = utils.GetAllocation(t, allocationID) + require.Equal(t, uint16(2), alloc.FileOptions&(1<<1)) + + t.Logf("allow update") + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "forbid_update": false, + }) + output, err = updateAllocation(t, configPath, params, true) + if err != nil { + require.Contains(t, err.Error(), "update allocation changes nothing") + } else { + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + } + + alloc = utils.GetAllocation(t, allocationID) + require.Equal(t, uint16(4), alloc.FileOptions&(1<<2)) + + t.Logf("allow move") + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "forbid_move": false, + }) + output, err = updateAllocation(t, configPath, params, true) + if err != nil { + require.Contains(t, err.Error(), "update allocation changes nothing") + } else { + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + } + + alloc = utils.GetAllocation(t, allocationID) + require.Equal(t, uint16(8), alloc.FileOptions&(1<<3)) + + t.Logf("allow copy") + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "forbid_copy": false, + }) + output, err = updateAllocation(t, configPath, params, true) + if err != nil { + require.Contains(t, err.Error(), "update allocation changes nothing") + } else { + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + } + + alloc = utils.GetAllocation(t, allocationID) + require.Equal(t, uint16(16), alloc.FileOptions&(1<<4)) + + t.Logf("allow rename") + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "forbid_rename": false, + }) + output, err = updateAllocation(t, configPath, params, true) + if err != nil { + require.Contains(t, err.Error(), "update allocation changes nothing") + } else { + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + } + + alloc = utils.GetAllocation(t, allocationID) + require.Equal(t, uint16(32), alloc.FileOptions&(1<<5)) + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.Run("Update allocation set_third_party_extendable flag should work", func(t *test.SystemTest) { + allocationID, _ := setupAndParseAllocation(t, configPath) + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "set_third_party_extendable": nil, + }) + output, err := updateAllocation(t, configPath, params, true) + + require.Nil(t, err, "error updating allocation", strings.Join(output, "\n")) + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + alloc := utils.GetAllocation(t, allocationID) + require.True(t, alloc.ThirdPartyExtendable) + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.Run("Update allocation expand by third party if third_party_extendable = true should succeed", func(t *test.SystemTest) { + allocationID, _ := setupAndParseAllocation(t, configPath) + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "set_third_party_extendable": nil, + }) + + output, err := updateAllocation(t, configPath, params, true) + if err != nil { + require.Equal(t, output[0], "Error updating allocation:allocation_updating_failed: update allocation changes nothing") + } else { + require.Nil(t, err, "error updating allocation", strings.Join(output, "\n")) + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + } + + alloc := utils.GetAllocation(t, allocationID) + require.True(t, alloc.ThirdPartyExtendable) + + nonAllocOwnerWallet := utils.EscapedTestName(t) + "_NON_OWNER" + + _, err = utils.CreateWalletForName(t, configPath, nonAllocOwnerWallet) + require.Nil(t, err) + + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "size": 2, + "extend": true, + }) + output, err = updateAllocationWithWallet(t, nonAllocOwnerWallet, configPath, params, true) + + require.Nil(t, err, "error updating allocation", strings.Join(output, "\n")) + + allocUpdated := utils.GetAllocation(t, allocationID) + require.Equal(t, alloc.Size+2, allocUpdated.Size) + + require.Nil(t, err) + require.Less(t, alloc.ExpirationDate, allocUpdated.ExpirationDate) + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.Run("Update allocation with add blobber should succeed", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error creating wallet", strings.Join(output, "\n")) + + allocSize := int64(4096) + + allocationID := utils.SetupEnterpriseAllocation(t, configPath, map[string]interface{}{ + "size": allocSize, + "tokens": 9, + }) + + wd, _ := os.Getwd() + walletFile := filepath.Join(wd, "config", utils.EscapedTestName(t)+"_wallet.json") + configFile := filepath.Join(wd, "config", configPath) + blobberID, blobberUrl, err := cli_tests.GetBlobberIdAndUrlNotPartOfAllocation(walletFile, configFile, allocationID) + require.Nil(t, err) + + blobberAuthTicket, err := utils.GetBlobberAuthTicketWithId(t, configPath, blobberID, blobberUrl) + require.Nil(t, err, "Unable to generate add blobber auth ticket") + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "set_third_party_extendable": nil, + "add_blobber": blobberID, + "add_blobber_auth_ticket": blobberAuthTicket, + }) + + output, err = updateAllocation(t, configPath, params, true) + require.Nil(t, err, "error updating allocation", strings.Join(output, "\n")) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.Run("Update allocation with replace blobber should succeed", func(t *test.SystemTest) { + output, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error creating wallet %v", strings.Join(output, "\n")) + + allocSize := int64(4096) + fileSize := int64(1024) + + allocationID := utils.SetupEnterpriseAllocationAndReadLock(t, configPath, map[string]interface{}{ + "size": allocSize, + "tokens": 9, + }) + + filename := utils.GenerateRandomTestFileName(t) + err = utils.CreateFileWithSize(filename, fileSize) + require.Nil(t, err) + + remotePath := "/dir" + filename + output, err = utils.UploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "remotepath": remotePath, + "localpath": filename, + }, true) + require.Nil(t, err, strings.Join(output, "\n")) + + wd, _ := os.Getwd() + walletFile := filepath.Join(wd, "config", utils.EscapedTestName(t)+"_wallet.json") + configFile := filepath.Join(wd, "config", configPath) + + addBlobberID, addBlobberUrl, err := cli_tests.GetBlobberIdAndUrlNotPartOfAllocation(walletFile, configFile, allocationID) + require.Nil(t, err) + removeBlobber, err := cli_tests.GetRandomBlobber(walletFile, configFile, allocationID, addBlobberID) + require.Nil(t, err) + + addBlobberAuthTicket, err := utils.GetBlobberAuthTicketWithId(t, configPath, addBlobberID, addBlobberUrl) + require.Nil(t, err, "Unable to generate auth ticket for add blobber") + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "set_third_party_extendable": nil, + "add_blobber": addBlobberID, + "add_blobber_auth_ticket": addBlobberAuthTicket, + "remove_blobber": removeBlobber, + }) + + output, err = updateAllocation(t, configPath, params, true) + require.Nil(t, err, "error updating allocation", strings.Join(output, "\n")) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + utils.AssertOutputMatchesAllocationRegex(t, repairCompletednRegex, output[len(output)-1]) + fref, err := cli_tests.VerifyFileRefFromBlobber(walletFile, configFile, allocationID, addBlobberID, remotePath) + require.Nil(t, err) + require.NotNil(t, fref) + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.Run("Run all update operations one by one", func(t *test.SystemTest) { + allocationID, allocationBeforeUpdate := setupAndParseAllocation(t, configPath) + + // Extend Allocation + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "extend": true, + }) + output, err := updateAllocation(t, configPath, params, true) + require.Nil(t, err, "Error extending allocation", strings.Join(output, "\n")) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + // Increase Allocation Size + size := int64(2048) + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "size": size, + }) + output, err = updateAllocation(t, configPath, params, true) + require.Nil(t, err, "Error increasing allocation size", strings.Join(output, "\n")) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + // Set Third Party Extendable + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "set_third_party_extendable": nil, + }) + output, err = updateAllocation(t, configPath, params, true) + require.Nil(t, err, "Error setting third party extendable", strings.Join(output, "\n")) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + wd, _ := os.Getwd() + walletFile := filepath.Join(wd, "config", utils.EscapedTestName(t)+"_wallet.json") + configFile := filepath.Join(wd, "config", configPath) + + // Add Blobber + blobberID, blobberUrl, err := cli_tests.GetBlobberIdAndUrlNotPartOfAllocation(walletFile, configFile, allocationID) + require.Nil(t, err, "Unable to get blobber not part of allocaiton") + + blobberAuthTicket, err := utils.GetBlobberAuthTicketWithId(t, configPath, blobberID, blobberUrl) + require.Nil(t, err, "Unable to generate auth ticket for add blobber") + + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "add_blobber": blobberID, + "add_blobber_auth_ticket": blobberAuthTicket, + }) + output, err = updateAllocation(t, configPath, params, true) + require.Nil(t, err, "Error adding blobber", strings.Join(output, "\n")) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + // Validate Final Allocation State + alloc := utils.GetAllocation(t, allocationID) + require.Greater(t, alloc.Size, allocationBeforeUpdate.Size) + require.True(t, alloc.ThirdPartyExtendable) + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.Run("Run all update operations at once", func(t *test.SystemTest) { + allocationID, allocationBeforeUpdate := setupAndParseAllocation(t, configPath) + + size := int64(2048) + + wd, _ := os.Getwd() + walletFile := filepath.Join(wd, "config", utils.EscapedTestName(t)+"_wallet.json") + configFile := filepath.Join(wd, "config", configPath) + + // Add Blobber + blobberID, blobberUrl, err := cli_tests.GetBlobberIdAndUrlNotPartOfAllocation(walletFile, configFile, allocationID) + require.Nil(t, err, "Unable to get blobber not part of allocaiton") + + blobberAuthTicket, err := utils.GetBlobberAuthTicketWithId(t, configPath, blobberID, blobberUrl) + require.Nil(t, err, "Unable to generate auth ticket for add blobber") + + // Combine all update operations + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "extend": true, + "size": size, + "set_third_party_extendable": nil, + "add_blobber": blobberID, + "add_blobber_auth_ticket": blobberAuthTicket, + }) + + output, err := updateAllocation(t, configPath, params, true) + require.Nil(t, err, "Error updating allocation with all operations at once", strings.Join(output, "\n")) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + // Validate Final Allocation State + alloc := utils.GetAllocation(t, allocationID) + require.Greater(t, alloc.Size, allocationBeforeUpdate.Size) + require.True(t, alloc.ThirdPartyExtendable) + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.Run("Update Size beyond blobber capacity should fail", func(t *test.SystemTest) { + allocationID, _ := setupAndParseAllocation(t, configPath) + size := int64(1099511627776000) + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "size": size, + }) + output, err := updateAllocation(t, configPath, params, false) + + require.NotNil(t, err, "Could not update allocation "+ + "due to error", strings.Join(output, "\n")) + require.Len(t, output, 1) + require.Contains(t, output[0], "doesn't have enough free space") + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.Run("Update Negative Size Should Fail", func(t *test.SystemTest) { + allocationID, allocationBeforeUpdate := setupAndParseAllocation(t, configPath) + size := int64(-256) + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "size": size, + }) + output, err := updateAllocation(t, configPath, params, true) + + require.Error(t, err, "expected error updating allocation", strings.Join(output, "\n")) + require.Len(t, output, 1) + require.Equal(t, "Error updating allocation:allocation_updating_failed: allocation can't be reduced", output[0]) + + alloc := utils.GetAllocation(t, allocationID) + + require.Equal(t, allocationBeforeUpdate.Size, alloc.Size) + require.Equal(t, allocationBeforeUpdate.ExpirationDate, alloc.ExpirationDate) + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.Run("Update Nothing Should Fail", func(t *test.SystemTest) { + allocationID := setupAllocation(t, configPath) + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + }) + output, err := updateAllocation(t, configPath, params, false) + + require.NotNil(t, err, "expected error updating allocation", strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at least 1", strings.Join(output, "\n")) + require.Equal(t, "Error updating allocation:allocation_updating_failed: update allocation changes nothing", output[0]) + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.Run("Update Non-existent Allocation Should Fail", func(t *test.SystemTest) { + _, err := utils.CreateWallet(t, configPath) + require.Nil(t, err, "Error creating wallet") + + allocationID := "123abc" + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "extend": true, + }) + output, err := updateAllocation(t, configPath, params, false) + + require.NotNil(t, err, "expected error updating allocation", strings.Join(output, "\n")) + require.Equal(t, "Error updating allocation:couldnt_find_allocation: Couldn't find the allocation required for update", output[0]) + }) + + t.RunWithTimeout("Update Other's Allocation Should Fail", 5*time.Minute, func(t *test.SystemTest) { + myAllocationID := setupAllocation(t, configPath) + + targetWalletName := utils.EscapedTestName(t) + "_TARGET" + _, err := utils.CreateWalletForName(t, configPath, targetWalletName) + require.Nil(t, err) + + size := int64(2048) + + params := createParams(map[string]interface{}{ + "allocation": myAllocationID, + "size": size, + }) + + output, err := updateAllocation(t, configPath, params, true) + + require.Nil(t, err, "Could not update allocation due to error", strings.Join(output, "\n")) + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + params = createParams(map[string]interface{}{ + "allocation": myAllocationID, + "size": size, + }) + output, err = updateAllocationWithWallet(t, targetWalletName, configPath, params, false) + + require.NotNil(t, err, "expected error updating "+ + "allocation", strings.Join(output, "\n")) + require.Len(t, output, 1) + require.Equal(t, "Error updating allocation:allocation_updating_failed: only owner can update the allocation", output[0]) + }) + + t.Run("Update Mistake Size Parameter Should Fail", func(t *test.SystemTest) { + allocationID := setupAllocation(t, configPath) + size := "ab" + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "size": size, + }) + output, err := updateAllocation(t, configPath, params, false) + + require.NotNil(t, err, "expected error updating "+ + "allocation", strings.Join(output, "\n")) + require.True(t, len(output) > 0, "expected output length be at "+ + "least 1", strings.Join(output, "\n")) + expected := fmt.Sprintf( + `Error: invalid argument "%v" for "--size" flag: strconv.ParseInt: parsing "%v": invalid syntax`, + size, size, + ) + require.Equal(t, expected, output[0]) + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.Run("Updating same file options twice should fail", func(w *test.SystemTest) { + allocationID, _ := setupAndParseAllocation(t, configPath) + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "forbid_upload": nil, + "forbid_delete": nil, + "forbid_move": nil, + }) + output, err := updateAllocation(t, configPath, params, true) + + require.Nil(t, err, "error updating allocation", strings.Join(output, "\n")) + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "forbid_upload": nil, + "forbid_delete": nil, + "forbid_move": nil, + }) + output, err = updateAllocation(t, configPath, params, false) + + require.NotNil(t, err, "error updating allocation", strings.Join(output, "\n")) + require.Len(t, output, 1) + require.Contains(t, output[0], "changes nothing") + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.Run("Update allocation set_third_party_extendable flag should fail if third_party_extendable is already true", func(t *test.SystemTest) { + allocationID, _ := setupAndParseAllocation(t, configPath) + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "set_third_party_extendable": nil, + }) + output, err := updateAllocation(t, configPath, params, true) + + require.Nil(t, err, "error updating allocation", strings.Join(output, "\n")) + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + alloc := utils.GetAllocation(t, allocationID) + require.True(t, alloc.ThirdPartyExtendable) + + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "set_third_party_extendable": nil, + }) + output, err = updateAllocation(t, configPath, params, false) + + require.NotNil(t, err, "error updating allocation", strings.Join(output, "\n")) + require.Contains(t, strings.Join(output, "\n"), "changes nothing") + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + + t.Run("Update allocation expand by third party if third_party_extendable = false should fail", func(t *test.SystemTest) { + allocationID, _ := setupAndParseAllocation(t, configPath) + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "size": 1, + }) + output, err := updateAllocation(t, configPath, params, true) + + require.Nil(t, err, "error updating allocation", strings.Join(output, "\n")) + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + alloc := utils.GetAllocation(t, allocationID) + require.False(t, alloc.ThirdPartyExtendable) + + nonAllocOwnerWallet := utils.EscapedTestName(t) + "_NON_OWNER" + + _, err = utils.CreateWalletForName(t, configPath, nonAllocOwnerWallet) + require.Nil(t, err) + + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "size": 2, + }) + output, err = updateAllocationWithWallet(t, nonAllocOwnerWallet, configPath, params, true) + + require.NotNil(t, err, strings.Join(output, "\n")) + require.Contains(t, strings.Join(output, "\n"), "only owner can update the allocation") + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) + t.RunWithTimeout("Update allocation any other action than expand by third party regardless of third_party_extendable should fail", 7*time.Minute, func(t *test.SystemTest) { + allocationID, _ := setupAndParseAllocation(t, configPath) + + params := createParams(map[string]interface{}{ + "allocation": allocationID, + "set_third_party_extendable": nil, + }) + output, err := updateAllocation(t, configPath, params, true) + + require.Nil(t, err, "error updating allocation", strings.Join(output, "\n")) + require.Len(t, output, 1) + utils.AssertOutputMatchesAllocationRegex(t, updateAllocationRegex, output[0]) + + alloc := utils.GetAllocation(t, allocationID) + require.True(t, alloc.ThirdPartyExtendable) + + nonAllocOwnerWallet := utils.EscapedTestName(t) + "_NON_OWNER" + + _, err = utils.CreateWalletForName(t, configPath, nonAllocOwnerWallet) + require.Nil(t, err, "Error creating wallet for non allocaiton owner") + + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "size": -100, + }) + output, err = updateAllocationWithWallet(t, nonAllocOwnerWallet, configPath, params, false) + require.NotNil(t, err, "no error updating allocation by third party", strings.Join(output, "\n")) + require.Contains(t, strings.Join(output, "\n"), "only owner can update the allocation") + + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "forbid_upload": nil, + "forbid_update": nil, + "forbid_delete": nil, + "forbid_rename": nil, + "forbid_move": nil, + "forbid_copy": nil, + "set_third_party_extendable": nil, + }) + output, err = updateAllocationWithWallet(t, nonAllocOwnerWallet, configPath, params, false) + require.NotNil(t, err, "no error updating allocation by third party", strings.Join(output, "\n")) + require.Contains(t, strings.Join(output, "\n"), "only owner can update the allocation") + + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "add_blobber": "new_blobber_id", + "remove_blobber": "blobber_id", + }) + output, err = updateAllocationWithWallet(t, nonAllocOwnerWallet, configPath, params, false) + require.NotNil(t, err, "no error updating allocation by third party", strings.Join(output, "\n")) + require.Contains(t, strings.Join(output, "\n"), "only owner can update the allocation") + + params = createParams(map[string]interface{}{ + "allocation": allocationID, + "lock": 100, + }) + output, err = updateAllocationWithWallet(t, nonAllocOwnerWallet, configPath, params, false) + require.NotNil(t, err, "no error updating allocation by third party", strings.Join(output, "\n")) + require.Contains(t, strings.Join(output, "\n"), "only owner can update the allocation") + + updatedAlloc := utils.GetAllocation(t, allocationID) + + require.Equal(t, alloc.Size, updatedAlloc.Size) + + require.Equal(t, alloc.FileOptions, updatedAlloc.FileOptions) + + require.Equal(t, len(alloc.Blobbers), len(updatedAlloc.Blobbers)) + + // Cleanup + output, err = cancelAllocation(t, configPath, allocationID, true) + require.Nil(t, err, "Unable to cancel allocation", strings.Join(output, "\n")) + require.Regexp(t, cancelAllocationRegex, strings.Join(output, "\n"), "cancel allcoation fail", strings.Join(output, "\n")) + }) +} + +func setupAndParseAllocation(t *test.SystemTest, cliConfigFilename string, extraParams ...map[string]interface{}) (string, climodel.Allocation) { + allocationID := setupAllocation(t, cliConfigFilename, extraParams...) + + allocations := parseListAllocations(t, cliConfigFilename) + allocation, ok := allocations[allocationID] + require.True(t, ok, "current allocation not found", allocationID, allocations) + + return allocationID, allocation +} + +func parseListAllocations(t *test.SystemTest, cliConfigFilename string) map[string]climodel.Allocation { + output, err := listAllocations(t, cliConfigFilename) + require.Nil(t, err, "list allocations failed", err, strings.Join(output, "\n")) + require.Len(t, output, 1) + + var allocations []*climodel.Allocation + err = json.NewDecoder(strings.NewReader(output[0])).Decode(&allocations) + require.Nil(t, err, "error deserializing JSON", err) + + allocationMap := make(map[string]climodel.Allocation) + + for _, ac := range allocations { + allocationMap[ac.ID] = *ac + } + + return allocationMap +} + +func setupAllocation(t *test.SystemTest, cliConfigFilename string, extraParams ...map[string]interface{}) string { + return setupAllocationWithWallet(t, utils.EscapedTestName(t), cliConfigFilename, extraParams...) +} + +func setupAllocationWithWallet(t *test.SystemTest, walletName, cliConfigFilename string, extraParams ...map[string]interface{}) string { + output, err := utils.CreateWalletForName(t, configPath, walletName) + require.Nil(t, err, "Error creating wallet", strings.Join(output, "\n")) + + output, err = utils.ExecuteFaucetWithTokens(t, configPath, 1000) + require.Nil(t, err, "Error executing faucet", strings.Join(output, "\n")) + + blobberAuthTickets, blobberIds := utils.GenerateBlobberAuthTickets(t, configPath) + options := map[string]interface{}{"size": "10000000", "lock": "5", "enterprise": true, "blobber_auth_tickets": blobberAuthTickets, "preferred_blobbers": blobberIds} + + for _, params := range extraParams { + for k, v := range params { + options[k] = v + } + } + + output, err = utils.CreateNewEnterpriseAllocation(t, cliConfigFilename, utils.CreateParams(options)) + require.NoError(t, err, "create new allocation failed", strings.Join(output, "\n")) + require.Len(t, output, 1) + + allocationID, err := utils.GetAllocationID(output[0]) + require.Nil(t, err, "could not get allocation ID", strings.Join(output, "\n")) + + return allocationID +} +func getAllocationCost(str string) (float64, error) { + allocationCostInOutput, err := strconv.ParseFloat(strings.Fields(str)[5], 64) + if err != nil { + return 0.0, err + } + + unit := strings.Fields(str)[6] + allocationCostInZCN := utils.UnitToZCN(allocationCostInOutput, unit) + + return allocationCostInZCN, nil +} + +func createParams(params map[string]interface{}) string { + var builder strings.Builder + + for k, v := range params { + if v == nil { + _, _ = builder.WriteString(fmt.Sprintf("--%s ", k)) + } else if reflect.TypeOf(v).String() == "bool" { + _, _ = builder.WriteString(fmt.Sprintf("--%s=%v ", k, v)) + } else { + _, _ = builder.WriteString(fmt.Sprintf("--%s %v ", k, v)) + } + } + return strings.TrimSpace(builder.String()) +} + +func updateAllocation(t *test.SystemTest, cliConfigFilename, params string, retry bool) ([]string, error) { + return updateAllocationWithWallet(t, utils.EscapedTestName(t), cliConfigFilename, params, retry) +} + +func updateAllocationWithWallet(t *test.SystemTest, wallet, cliConfigFilename, params string, retry bool) ([]string, error) { + t.Logf("Updating allocation...") + cmd := fmt.Sprintf( + "./zbox updateallocation %s --silent --wallet %s "+ + "--configDir ./config --config %s", + params, + wallet+"_wallet.json", + cliConfigFilename, + ) + if retry { + return cliutils.RunCommand(t, cmd, 3, time.Second*2) + } else { + return cliutils.RunCommandWithoutRetry(cmd) + } +} + +func listAllocations(t *test.SystemTest, cliConfigFilename string) ([]string, error) { + cliutils.Wait(t, 5*time.Second) + t.Logf("Listing allocations...") + cmd := fmt.Sprintf( + "./zbox listallocations --json --silent "+ + "--wallet %s --configDir ./config --config %s", + utils.EscapedTestName(t)+"_wallet.json", + cliConfigFilename, + ) + return cliutils.RunCommand(t, cmd, 3, time.Second*2) +} + +func getTransacionFromSingleSharder(t *test.SystemTest, hash string) (TransactionVerify, error) { + sharderBaseUrl := utils.GetSharderUrl(t) + requestURL := fmt.Sprintf("%s/v1/transaction/get/confirmation?hash=%s", sharderBaseUrl, hash) + + var result TransactionVerify + + res, _ := http.Get(requestURL) //nolint:gosec + + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + return + } + }(res.Body) + + body, _ := io.ReadAll(res.Body) + + err := json.Unmarshal(body, &result) + if err != nil { + return TransactionVerify{}, err + } + + return result, nil +} + +type TransactionVerify struct { + Version string `json:"version"` + Hash string `json:"hash"` + BlockHash string `json:"block_hash"` + PreviousBlockHash string `json:"previous_block_hash"` + Txn struct { + Hash string `json:"hash"` + Version string `json:"version"` + ClientId string `json:"client_id"` + PublicKey string `json:"public_key"` + ToClientId string `json:"to_client_id"` + ChainId string `json:"chain_id"` + TransactionData string `json:"transaction_data"` + TransactionValue int `json:"transaction_value"` + Signature string `json:"signature"` + CreationDate int `json:"creation_date"` + TransactionFee int `json:"transaction_fee"` + TransactionNonce int `json:"transaction_nonce"` + TransactionType int `json:"transaction_type"` + TransactionOutput string `json:"transaction_output"` + TxnOutputHash string `json:"txn_output_hash"` + TransactionStatus int `json:"transaction_status"` + } `json:"txn"` + CreationDate int `json:"creation_date"` + MinerId string `json:"miner_id"` + Round int `json:"round"` + TransactionStatus int `json:"transaction_status"` + RoundRandomSeed int64 `json:"round_random_seed"` + StateChangesCount int `json:"state_changes_count"` + MerkleTreeRoot string `json:"merkle_tree_root"` + MerkleTreePath struct { + Nodes []string `json:"nodes"` + LeafIndex int `json:"leaf_index"` + } `json:"merkle_tree_path"` + ReceiptMerkleTreeRoot string `json:"receipt_merkle_tree_root"` + ReceiptMerkleTreePath struct { + Nodes []string `json:"nodes"` + LeafIndex int `json:"leaf_index"` + } `json:"receipt_merkle_tree_path"` +} diff --git a/tests/tokenomics_tests/main_test.go b/tests/tokenomics_tests/main_test.go index b624f58bc6..3bd275374f 100644 --- a/tests/tokenomics_tests/main_test.go +++ b/tests/tokenomics_tests/main_test.go @@ -47,7 +47,7 @@ func setupConfig() { log.Fatalln(fmt.Errorf("fatal error config file: %s", err)) } - parsedConfig := config.Parse(filepath.Join(".", path, "tokenomics_tests_config.yaml")) + parsedConfig = config.Parse(filepath.Join(".", path, "tokenomics_tests_config.yaml")) defaultTestTimeout, err := time.ParseDuration(parsedConfig.DefaultTestCaseTimeout) if err != nil { log.Printf("Default test case timeout could not be parsed so has defaulted to [%v]", test.DefaultTestTimeout) @@ -81,6 +81,7 @@ const ( validator1Delegate2Wallet = "wallets/validator1_delegate2" validator2Delegate1Wallet = "wallets/validator2_delegate1" validator2Delegate2Wallet = "wallets/validator2_delegate2" + zboxTeamWallet = "wallets/zbox_team" ) var ( @@ -88,6 +89,7 @@ var ( configDir string bridgeClientConfigFile string bridgeOwnerConfigFile string + parsedConfig *config.Config ) func TestMain(m *testing.M) { @@ -126,7 +128,8 @@ func TestMain(m *testing.M) { strings.HasSuffix(f, miner02NodeDelegateWalletName+"_wallet.json") || strings.HasSuffix(f, miner03NodeDelegateWalletName+"_wallet.json") || strings.HasSuffix(f, sharder01NodeDelegateWalletName+"_wallet.json") || - strings.HasSuffix(f, sharder02NodeDelegateWalletName+"_wallet.json") { + strings.HasSuffix(f, sharder02NodeDelegateWalletName+"_wallet.json") || + strings.HasSuffix(f, zboxTeamWallet+"_wallet.json") { continue } _ = os.Remove(f) diff --git a/tests/tokenomics_tests/min_stake_test.go b/tests/tokenomics_tests/min_stake_test.go index 8209f3750c..770f5d0f90 100644 --- a/tests/tokenomics_tests/min_stake_test.go +++ b/tests/tokenomics_tests/min_stake_test.go @@ -31,6 +31,7 @@ const ( ChallengePassReward ChallengeSlashPenalty CancellationChargeReward + EnterpriseBlobberReward NumOfRewards ) diff --git a/tests/tokenomics_tests/utils/allocation.go b/tests/tokenomics_tests/utils/allocation.go index d7a99470c3..2f6d2a8691 100644 --- a/tests/tokenomics_tests/utils/allocation.go +++ b/tests/tokenomics_tests/utils/allocation.go @@ -5,12 +5,17 @@ import ( "encoding/json" "errors" "fmt" + "io" "os" "regexp" "strconv" "strings" "time" + "github.com/0chain/gosdk/core/conf" + "github.com/0chain/gosdk/zboxcore/sdk" + "github.com/0chain/gosdk/zcncore" + "github.com/0chain/system_test/internal/api/util/test" climodel "github.com/0chain/system_test/internal/cli/model" cliutils "github.com/0chain/system_test/internal/cli/util" @@ -29,7 +34,29 @@ func SetupAllocationAndReadLock(t *test.SystemTest, cliConfigFilename string, ex tokens = token } - allocationID := setupAllocation(t, cliConfigFilename, extraParam) + allocationID := SetupAllocation(t, cliConfigFilename, extraParam) + + // Lock half the tokens for read pool + readPoolParams := CreateParams(map[string]interface{}{ + "tokens": tokens / 2, + }) + output, err := ReadPoolLock(t, cliConfigFilename, readPoolParams, true) + require.Nil(t, err, strings.Join(output, "\n")) + require.Len(t, output, 1) + require.Equal(t, "locked", output[0]) + + return allocationID +} + +func SetupEnterpriseAllocationAndReadLock(t *test.SystemTest, cliConfigFilename string, extraParam map[string]interface{}) string { + tokens := float64(1) + if tok, ok := extraParam["tokens"]; ok { + token, err := strconv.ParseFloat(fmt.Sprintf("%v", tok), 64) + require.Nil(t, err) + tokens = token + } + + allocationID := SetupEnterpriseAllocation(t, cliConfigFilename, extraParam) // Lock half the tokens for read pool readPoolParams := CreateParams(map[string]interface{}{ @@ -57,12 +84,13 @@ func readPoolLockWithWallet(t *test.SystemTest, wallet, cliConfigFilename, param } } -func setupAllocation(t *test.SystemTest, cliConfigFilename string, extraParams ...map[string]interface{}) string { - return setupAllocationWithWallet(t, EscapedTestName(t), cliConfigFilename, extraParams...) +func SetupAllocation(t *test.SystemTest, cliConfigFilename string, extraParams ...map[string]interface{}) string { + return SetupAllocationWithWallet(t, EscapedTestName(t), cliConfigFilename, extraParams...) } -func setupAllocationWithWallet(t *test.SystemTest, walletName, cliConfigFilename string, extraParams ...map[string]interface{}) string { +func SetupAllocationWithWallet(t *test.SystemTest, walletName, cliConfigFilename string, extraParams ...map[string]interface{}) string { faucetTokens := 2.0 + lockAmountPassed := false // Then create new allocation options := map[string]interface{}{"size": "10000", "lock": "0.5"} @@ -76,12 +104,19 @@ func setupAllocationWithWallet(t *test.SystemTest, walletName, cliConfigFilename faucetTokens = token delete(params, "tokens") } + + if _, lockPassed := params["lock"]; lockPassed { + lockAmountPassed = true + } + for k, v := range params { options[k] = v } } - options["lock"] = faucetTokens / 2 + if !lockAmountPassed { + options["lock"] = faucetTokens / 2 + } t.Log("Creating new allocation...", options) @@ -96,15 +131,33 @@ func setupAllocationWithWallet(t *test.SystemTest, walletName, cliConfigFilename output, err = CreateNewAllocationForWallet(t, walletName, cliConfigFilename, CreateParams(options)) require.Nil(t, err, "create new allocation failed", strings.Join(output, "\n")) - require.Len(t, output, 1) // Get the allocation ID and return it - allocationID, err := GetAllocationID(output[0]) + allocationID, err := GetAllocationID(output[len(output)-1]) require.Nil(t, err, "could not get allocation ID", strings.Join(output, "\n")) return allocationID } +func SetupEnterpriseAllocation(t *test.SystemTest, cliConfigFilename string, extraParams ...map[string]interface{}) string { + return SetupEnterpriseAllocationWithWallet(t, EscapedTestName(t), cliConfigFilename, extraParams...) +} + +func SetupEnterpriseAllocationWithWallet(t *test.SystemTest, walletName, cliConfigFilename string, extraParams ...map[string]interface{}) string { + if len(extraParams) == 0 { + extraParams = append(extraParams, map[string]interface{}{}) + } + + extraParams[0]["blobber_auth_tickets"], extraParams[0]["preferred_blobbers"] = GenerateBlobberAuthTicketsWithWallet(t, walletName, cliConfigFilename) + extraParams[0]["enterprise"] = true + + allocID := SetupAllocationWithWallet(t, walletName, cliConfigFilename, extraParams...) + + t.Logf("Enterprise allocation created with ID: %s", allocID) + + return allocID +} + func DownloadFile(t *test.SystemTest, cliConfigFilename, param string, retry bool) ([]string, error) { return downloadFileForWallet(t, EscapedTestName(t), cliConfigFilename, param, retry) } @@ -125,7 +178,19 @@ func downloadFileForWallet(t *test.SystemTest, wallet, cliConfigFilename, param return cliutils.RunCommandWithoutRetry(cmd) } } +func CreateNewEnterpriseAllocation(t *test.SystemTest, cliConfigFilename, params string) ([]string, error) { + return CreateNewEnterpriseAllocationForWallet(t, EscapedTestName(t), cliConfigFilename, params) +} +func CreateNewEnterpriseAllocationForWallet(t *test.SystemTest, wallet, cliConfigFilename, params string) ([]string, error) { + t.Logf("Creating new enterprise allocation...") + return cliutils.RunCommand(t, fmt.Sprintf( + "./zbox newallocation %s --silent --wallet %s --configDir ./config --config %s --allocationFileName %s --enterprise", + params, + wallet+"_wallet.json", + cliConfigFilename, + wallet+"_allocation.txt"), 3, time.Second*5) +} func CreateNewAllocation(t *test.SystemTest, cliConfigFilename, params string) ([]string, error) { return CreateNewAllocationForWallet(t, EscapedTestName(t), cliConfigFilename, params) } @@ -183,10 +248,10 @@ func GetAllocationID(str string) (string, error) { } func UploadFile(t *test.SystemTest, cliConfigFilename string, param map[string]interface{}, retry bool) ([]string, error) { - return uploadFileForWallet(t, EscapedTestName(t), cliConfigFilename, param, retry) + return UploadFileForWallet(t, EscapedTestName(t), cliConfigFilename, param, retry) } -func uploadFileForWallet(t *test.SystemTest, wallet, cliConfigFilename string, param map[string]interface{}, retry bool) ([]string, error) { +func UploadFileForWallet(t *test.SystemTest, wallet, cliConfigFilename string, param map[string]interface{}, retry bool) ([]string, error) { t.Logf("Uploading file...") p := CreateParams(param) @@ -258,3 +323,78 @@ func DeleteFile(t *test.SystemTest, walletName, params string, retry bool) ([]st return cliutils.RunCommandWithoutRetry(cmd) } } + +func getBlobberNotPartOfAllocation(walletname, configFile, allocationID string) (*sdk.Blobber, error) { + err := InitSDK(walletname, configFile) + if err != nil { + return nil, err + } + + a, err := sdk.GetAllocation(allocationID) + if err != nil { + return nil, err + } + + blobbers, err := sdk.GetBlobbers(true, false) + if err != nil { + return nil, err + } + + allocationBlobsMap := map[string]bool{} + for _, b := range a.BlobberDetails { + allocationBlobsMap[b.BlobberID] = true + } + + for _, blobber := range blobbers { + if _, ok := allocationBlobsMap[string(blobber.ID)]; !ok { + return blobber, nil + } + } + + return nil, fmt.Errorf("failed to get blobber not part of allocation") +} + +// GetBlobberNotPartOfAllocation returns a blobber not part of current allocation +func GetBlobberIdAndUrlNotPartOfAllocation(walletname, configFile, allocationID string) (blobberId, blobberUrl string, err error) { + blobber, err := getBlobberNotPartOfAllocation(walletname, configFile, allocationID) + if err != nil || blobber == nil { + return "", "", err + } + return string(blobber.ID), blobber.BaseURL, err +} + +func InitSDK(wallet, configFile string) error { + f, err := os.Open(wallet) + if err != nil { + return err + } + clientBytes, err := io.ReadAll(f) + if err != nil { + return err + } + walletJSON := string(clientBytes) + + parsed, err := conf.LoadConfigFile(configFile) + if err != nil { + return err + } + + marshal, err := json.Marshal(parsed) + if err != nil { + return err + } + err = zcncore.Init(string(marshal)) + if err != nil { + return err + } + + err = sdk.InitStorageSDK( + walletJSON, + parsed.BlockWorker, + parsed.ChainID, + parsed.SignatureScheme, + nil, + 0, + ) + return err +} diff --git a/tests/tokenomics_tests/utils/blobbers.go b/tests/tokenomics_tests/utils/blobbers.go index 753a7429b8..899a02d1fe 100644 --- a/tests/tokenomics_tests/utils/blobbers.go +++ b/tests/tokenomics_tests/utils/blobbers.go @@ -1,18 +1,58 @@ package utils import ( + "encoding/hex" + "encoding/json" "fmt" + "net/http" + "strings" "time" + "github.com/0chain/common/core/common" + "github.com/0chain/gosdk/core/zcncrypto" + climodel "github.com/0chain/system_test/internal/cli/model" + "github.com/stretchr/testify/require" + "github.com/0chain/system_test/internal/api/util/test" cliutils "github.com/0chain/system_test/internal/cli/util" ) +const zboxTeamWalletName = "wallets/zbox_team" + +var zboxTeamWallet *climodel.Wallet + func ListBlobbers(t *test.SystemTest, cliConfigFilename, params string) ([]string, error) { t.Log("Requesting blobber list...") return cliutils.RunCommand(t, fmt.Sprintf("./zbox ls-blobbers %s --silent --wallet %s_wallet.json --configDir ./config --config %s", params, EscapedTestName(t), cliConfigFilename), 3, time.Second*2) } +func ListBlobbersWithWallet(t *test.SystemTest, walletName, cliConfigFilename, params string) ([]string, error) { + t.Log("Requesting blobber list...") + return cliutils.RunCommand(t, fmt.Sprintf("./zbox ls-blobbers %s --silent --wallet %s_wallet.json --configDir ./config --config %s", params, walletName, cliConfigFilename), 3, time.Second*2) +} + +func GetBlobberDetails(t *test.SystemTest, cliConfigFilename, blobberId string) (*climodel.Blobber, error) { + return GetBlobberDetailsWithWallet(t, EscapedTestName(t), cliConfigFilename, blobberId) +} + +func GetBlobberDetailsWithWallet(t *test.SystemTest, walletName, cliConfigFilename, blobberId string) (*climodel.Blobber, error) { + output, err := ListBlobbersWithWallet(t, walletName, cliConfigFilename, "--json") + require.Nil(t, err, "Unable to get blobbers list", strings.Join(output, "\n")) + require.Len(t, output, 1, "Error invalid json data for list blobbers") + + var blobberList []climodel.Blobber + err = json.NewDecoder(strings.NewReader(output[0])).Decode(&blobberList) + require.Nil(t, err, "Error parsing blobbers list") + + for idx := range blobberList { + if blobberList[idx].ID == blobberId { + return &blobberList[idx], nil + } + } + + return nil, nil +} + func ListValidators(t *test.SystemTest, cliConfigFilename, params string) ([]string, error) { t.Log("Requesting validator list...") return cliutils.RunCommand(t, fmt.Sprintf("./zbox ls-validators %s --silent --wallet %s_wallet.json --configDir ./config --config %s", params, EscapedTestName(t), cliConfigFilename), 3, time.Second*2) @@ -32,3 +72,96 @@ func StakePoolInfo(t *test.SystemTest, cliConfigFilename, params string) ([]stri t.Log("Fetching stake pool info...") return cliutils.RunCommand(t, fmt.Sprintf("./zbox sp-info %s --silent --wallet %s_wallet.json --configDir ./config --config %s", params, EscapedTestName(t), cliConfigFilename), 3, time.Second*2) } +func GenerateBlobberAuthTickets(t *test.SystemTest, configFileName string) (blobberAuthTickets, blobberIds string) { + return GenerateBlobberAuthTicketsWithWallet(t, EscapedTestName(t), configFileName) +} + +func GenerateBlobberAuthTicketsWithWallet(t *test.SystemTest, walletName, configFileName string) (blobberAuthTicket, blobberIds string) { + var blobbersList []climodel.Blobber + output, err := ListBlobbersWithWallet(t, walletName, configFileName, "--json") + require.Nil(t, err, "Failed to get blobbers list", strings.Join(output, "\n")) + require.NotNil(t, output[0], "Empty list blobbers json response") + + err = json.NewDecoder(strings.NewReader(strings.Join(output, "\n"))).Decode(&blobbersList) + require.Nil(t, err, "Error parsing the blobbers list", strings.Join(output, "\n")) + require.NotNil(t, blobbersList, "Blobbers list is empty") + + // Get auth tickets for all blobbers + var blobberAuthTickets string + var blobbersIds string + wallet, err := GetWalletForName(t, configFileName, walletName) + require.Nil(t, err, "could not get wallet") + + for i := range blobbersList { + blobber := blobbersList[i] + authTicket, err := getBlobberAuthTicket(t, configFileName, blobber.ID, blobber.BaseURL, zboxTeamWalletName, wallet.ClientID) + require.Nil(t, err, "could not get auth ticket for blobber", blobber.ID) + require.NotNil(t, authTicket, "could not get auth ticket for blobber %v", blobber) + require.NotEqual(t, authTicket, "", "empty auth ticket for blobber %v", blobber) + + if i == len(blobbersList)-1 { + blobberAuthTickets += authTicket + blobbersIds += blobber.ID + break + } + blobberAuthTickets += authTicket + "," + blobbersIds += blobber.ID + "," + } + return blobberAuthTickets, blobbersIds +} + +func GetBlobberAuthTicketWithId(t *test.SystemTest, cliConfigFileName, blobberID, blobberUrl string) (string, error) { + return getBlobberAuthTicketForIdWithWallet(t, EscapedTestName(t), cliConfigFileName, blobberID, blobberUrl) +} +func getBlobberAuthTicketForIdWithWallet(t *test.SystemTest, walletName, cliConfigFileName, blobberId, blobberUrl string) (string, error) { + userWallet, err := GetWalletForName(t, cliConfigFileName, walletName) + if err != nil { + return "", err + } + + return getBlobberAuthTicket(t, cliConfigFileName, blobberId, blobberUrl, zboxTeamWalletName, userWallet.ClientID) +} + +func getBlobberAuthTicket(t *test.SystemTest, cliConfigFileName, blobberID, blobberUrl, zboxTeamWalletName, clientID string) (string, error) { + if zboxTeamWallet == nil { + zboxWallet, err := GetWalletForName(t, cliConfigFileName, zboxTeamWalletName) + require.Nil(t, err, "could not get zbox wallet") + + zboxTeamWallet = zboxWallet + } + + var authTicket string + signatureScheme := zcncrypto.NewSignatureScheme("bls0chain") + _ = signatureScheme.SetPrivateKey("85e2119f494cd40ca524f6342e8bdb7bef2af03fe9a08c8d9c1d9f14d6c64f14") + _ = signatureScheme.SetPublicKey(zboxTeamWallet.ClientPublicKey) + + signature, err := signatureScheme.Sign(hex.EncodeToString([]byte(zboxTeamWallet.ClientPublicKey))) + if err != nil { + return authTicket, err + } + + url := blobberUrl + "/v1/auth/generate?client_id=" + clientID + req, err := http.NewRequest("GET", url, http.NoBody) + if err != nil { + return authTicket, err + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Zbox-Signature", signature) + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return authTicket, err + } + defer resp.Body.Close() + var responseMap map[string]string + err = json.NewDecoder(resp.Body).Decode(&responseMap) + if err != nil { + return "", err + } + authTicket = responseMap["auth_ticket"] + if authTicket == "" { + return "", common.NewError("500", "Error getting auth ticket from blobber") + } + + return authTicket, nil +} diff --git a/tests/tokenomics_tests/utils/general.go b/tests/tokenomics_tests/utils/general.go index 5ef3d1685a..986158de87 100644 --- a/tests/tokenomics_tests/utils/general.go +++ b/tests/tokenomics_tests/utils/general.go @@ -3,8 +3,11 @@ package utils import ( "fmt" "reflect" + "regexp" "strings" + "github.com/stretchr/testify/require" + "github.com/0chain/system_test/internal/api/util/test" ) @@ -43,3 +46,23 @@ func CreateParams(params map[string]interface{}) string { func IntToZCN(balance int64) float64 { return float64(balance) / tokenUnit } + +func UnitToZCN(unitCost float64, unit string) float64 { + switch unit { + case "SAS", "sas": + unitCost /= 1e10 + return unitCost + case "uZCN", "uzcn": + unitCost /= 1e6 + return unitCost + case "mZCN", "mzcn": + unitCost /= 1e3 + return unitCost + } + return unitCost +} + +func AssertOutputMatchesAllocationRegex(t *test.SystemTest, re *regexp.Regexp, str string) { + match := re.FindStringSubmatch(str) + require.True(t, len(match) > 0, "expected allocation to match regex", re, str) +} diff --git a/tests/tokenomics_tests/utils/wallet.go b/tests/tokenomics_tests/utils/wallet.go index 33e4589033..aafc0569f1 100644 --- a/tests/tokenomics_tests/utils/wallet.go +++ b/tests/tokenomics_tests/utils/wallet.go @@ -9,6 +9,8 @@ import ( "strings" "time" + "github.com/0chain/system_test/internal/api/model" + "github.com/0chain/system_test/internal/api/util/test" climodel "github.com/0chain/system_test/internal/cli/model" cliutils "github.com/0chain/system_test/internal/cli/util" @@ -60,8 +62,13 @@ func CreateWalletForName(t *test.SystemTest, cliConfigFilename, name string, opt return cliutils.RunCommand(t, "./zwallet create-wallet "+ "--wallet "+name+"_wallet.json"+" --configDir ./config --config "+cliConfigFilename, 3, time.Second*2) } + output, err := cliutils.RunCommand(t, "./zwallet create-wallet --silent "+ + "--wallet "+name+"_wallet.json"+" --configDir ./config --config "+cliConfigFilename, 3, time.Second*2) + if err != nil { + return output, err + } - output, err := ExecuteFaucetWithTokensForWallet(t, name, cliConfigFilename, 5) + output, err = ExecuteFaucetWithTokensForWallet(t, name, cliConfigFilename, 5) t.Logf("faucet output: %v", output) return output, err } @@ -88,6 +95,28 @@ func GetWalletForName(t *test.SystemTest, cliConfigFilename, name string) (*clim return wallet, err } +func GetFullWalletForName(t *test.SystemTest, cliConfigFilename, name string) (*model.Wallet, error) { + t.Logf("Getting wallet...") + output, err := cliutils.RunCommand(t, "./zbox getwallet --json --silent "+ + "--wallet "+name+"_wallet.json"+" --configDir ./config --config "+cliConfigFilename, 3, time.Second*2) + + if err != nil { + return nil, err + } + + require.Len(t, output, 1) + + var wallet *model.Wallet + + err = json.Unmarshal([]byte(output[0]), &wallet) + if err != nil { + t.Errorf("failed to unmarshal the result into wallet") + return nil, err + } + + return wallet, err +} + func SetupWalletWithCustomTokens(t *test.SystemTest, configPath string, tokens float64) []string { output, err := CreateWallet(t, configPath) require.Nil(t, err, strings.Join(output, "\n"))