From ab2d303817e3e33dc67988df6bc2429959d5f4dd Mon Sep 17 00:00:00 2001 From: Logan Nguyen Date: Tue, 22 Oct 2024 21:51:43 -0500 Subject: [PATCH] test: added e2e test for privileged tier users Signed-off-by: Logan Nguyen --- .../tests/acceptance/hbarLimiter.spec.ts | 211 +++++++++++++++++- packages/server/tests/localAcceptance.env | 6 +- packages/server/tests/mainnetAcceptance.env | 5 +- .../server/tests/previewnetAcceptance.env | 5 +- .../server/tests/testSpendingPlansConfig.json | 28 +++ packages/server/tests/testnetAcceptance.env | 5 +- 6 files changed, 246 insertions(+), 14 deletions(-) create mode 100644 packages/server/tests/testSpendingPlansConfig.json diff --git a/packages/server/tests/acceptance/hbarLimiter.spec.ts b/packages/server/tests/acceptance/hbarLimiter.spec.ts index 4e988360d..29bcdd858 100644 --- a/packages/server/tests/acceptance/hbarLimiter.spec.ts +++ b/packages/server/tests/acceptance/hbarLimiter.spec.ts @@ -35,15 +35,13 @@ import RelayClient from '../clients/relayClient'; import MirrorClient from '../clients/mirrorClient'; import MetricsClient from '../clients/metricsClient'; import { AliasAccount } from '../types/AliasAccount'; -import { - estimateFileTransactionsFee, - overrideEnvsInMochaDescribe -} from '@hashgraph/json-rpc-relay/tests/helpers'; import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services'; import { ITransfer, RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; import { HbarLimitService } from '@hashgraph/json-rpc-relay/dist/lib/services/hbarLimitService'; import { CacheService } from '@hashgraph/json-rpc-relay/dist/lib/services/cacheService/cacheService'; import { SubscriptionTier } from '@hashgraph/json-rpc-relay/dist/lib/db/types/hbarLimiter/subscriptionTier'; +import { estimateFileTransactionsFee, overrideEnvsInMochaDescribe } from '@hashgraph/json-rpc-relay/tests/helpers'; +import { IDetailedHbarSpendingPlan } from '@hashgraph/json-rpc-relay/dist/lib/db/types/hbarLimiter/hbarSpendingPlan'; import { HbarSpendingPlanRepository } from '@hashgraph/json-rpc-relay/dist/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository'; import { IPAddressHbarSpendingPlanRepository } from '@hashgraph/json-rpc-relay/dist/lib/db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository'; import { EthAddressHbarSpendingPlanRepository } from '@hashgraph/json-rpc-relay/dist/lib/db/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository'; @@ -74,11 +72,14 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { metrics: MetricsClient; relayIsLocal: boolean; } = global; + const mockTTL = 60000; // 60 secs const operatorAccount = ConfigService.get('OPERATOR_ID_MAIN') || DOT_ENV.OPERATOR_ID_MAIN || ''; const fileAppendChunkSize = Number(ConfigService.get('FILE_APPEND_CHUNK_SIZE')) || 5120; const requestId = 'hbarLimiterTest'; const requestDetails = new RequestDetails({ requestId: requestId, ipAddress: '0.0.0.0' }); const cacheService = new CacheService(logger.child({ name: 'cache-service' }), new Registry()); + const maxBasicSpendingLimit = HbarLimitService.TIER_LIMITS.BASIC.toTinybars().toNumber(); + const maxPrivilegedSpendingLimit = HbarLimitService.TIER_LIMITS.PRIVILEGED.toTinybars().toNumber(); const ethAddressSpendingPlanRepository = new EthAddressHbarSpendingPlanRepository(cacheService, logger); const ipSpendingPlanRepository = new IPAddressHbarSpendingPlanRepository(cacheService, logger); @@ -196,7 +197,6 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { before(async function () { // Restart the relay to reset the limits await global.restartLocalRelay(); - await cacheService.clear(requestDetails); logger.info(`${requestDetails.formattedRequestId} Creating accounts`); logger.info( @@ -450,7 +450,6 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { it('should eventually exhaust the hbar limit for a BASIC user after multiple deployments of large contracts', async function () { const fileChunkSize = Number(ConfigService.get('FILE_APPEND_CHUNK_SIZE')) || 5120; - const maxBasicSpendingLimit = HbarLimitService.TIER_LIMITS.BASIC.toTinybars().toNumber(); const remainingHbarsBefore = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT)); const exchangeRateResult = (await mirrorNode.get(`/network/exchangerate`, requestId)).current_rate; const exchangeRateInCents = exchangeRateResult.cent_equivalent / exchangeRateResult.hbar_equivalent; @@ -503,6 +502,206 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { } }); }); + + describe('NON-BASIC Tiers', () => { + describe('Pre-configured spending plans', async () => { + const expectedPrivilegedEvmAddressAlpha = '0x7d102fe71af42790fe31b126c1f49766376ca2b5'; + const expectedPrivilegedEvmAddressBeta = '0x40183ec818c1826114767391989ff2eaebc2b91e'; + const expectedPrivilegedIpAddress = '127.0.0.1'; + const expectedPrivilegedPlans = { + ALPHA: { + id: 'c758c095-342c-4607-9db5-867d7e90ab9d', + name: 'PRIVILEGED_ALPHA', + ethAddresses: ['0x7d102fe71af42790fe31b126c1f49766376ca2b5'], + ipAddresses: ['127.0.0.1'], + subscriptionTier: 'PRIVILEGED', + }, + BETA: { + id: 'a68488b0-6f7d-44a0-87c1-774ad64615f2', + name: 'PRIVILEGED_BETA', + ethAddresses: ['0x40183ec818c1826114767391989ff2eaebc2b91e'], + subscriptionTier: 'PRIVILEGED', + }, + }; + it('Should successfully populate all pre-configured spending plans', async () => { + Object.values(expectedPrivilegedPlans).forEach(async (plan) => { + const hbarSpendingPlan = await hbarSpendingPlanRepository.findByIdWithDetails(plan.id, requestDetails); + expect(hbarSpendingPlan.id).to.eq(plan.id); + expect(hbarSpendingPlan.active).to.be.true; + expect(hbarSpendingPlan.subscriptionTier).to.eq(SubscriptionTier.PRIVILEGED); + }); + + const associatedPlanByEVMAddressAlpha = await ethAddressSpendingPlanRepository.findByAddress( + expectedPrivilegedEvmAddressAlpha, + requestDetails, + ); + expect(associatedPlanByEVMAddressAlpha.planId).to.eq(expectedPrivilegedPlans.ALPHA.id); + expect(associatedPlanByEVMAddressAlpha.ethAddress).to.eq(expectedPrivilegedEvmAddressAlpha); + + const associatedPlanByEVMAddressBeta = await ethAddressSpendingPlanRepository.findByAddress( + expectedPrivilegedEvmAddressBeta, + requestDetails, + ); + expect(associatedPlanByEVMAddressBeta.planId).to.eq(expectedPrivilegedPlans.BETA.id); + expect(associatedPlanByEVMAddressBeta.ethAddress).to.eq(expectedPrivilegedEvmAddressBeta); + + const associatedPlanByIpAddress = await ipSpendingPlanRepository.findByAddress( + expectedPrivilegedIpAddress, + requestDetails, + ); + expect(associatedPlanByIpAddress.planId).to.eq(expectedPrivilegedPlans.ALPHA.id); + expect(associatedPlanByIpAddress.ipAddress).to.eq(expectedPrivilegedIpAddress); + }); + }); + + describe('PRIVILEGED Tier', () => { + let priviledgedEvmAccount: AliasAccount; + let privilegedHbarSpendingPlan: IDetailedHbarSpendingPlan; + + beforeEach(async () => { + priviledgedEvmAccount = await Utils.createAliasAccount( + mirrorNode, + global.accounts[0], + requestId, + initialBalance, + ); + + privilegedHbarSpendingPlan = await hbarSpendingPlanRepository.create( + SubscriptionTier.PRIVILEGED, + requestDetails, + mockTTL, + ); + + await ethAddressSpendingPlanRepository.save( + { ethAddress: priviledgedEvmAccount.address, planId: privilegedHbarSpendingPlan.id }, + requestDetails, + mockTTL, + ); + }); + + afterEach(async () => { + await hbarSpendingPlanRepository.delete(privilegedHbarSpendingPlan.id, requestDetails); + await ethAddressSpendingPlanRepository.delete(priviledgedEvmAccount.address, requestDetails); + }); + + it('Should successfully add spending plans', async () => { + const plan = await ethAddressSpendingPlanRepository.findByAddress( + priviledgedEvmAccount.address, + requestDetails, + ); + + expect(plan.ethAddress).to.eq(priviledgedEvmAccount.address); + expect(plan.planId).to.eq(privilegedHbarSpendingPlan.id); + + const spendingPlan = await hbarSpendingPlanRepository.findByIdWithDetails(plan.planId, requestDetails); + expect(spendingPlan.active).to.be.true; + expect(spendingPlan.amountSpent).to.eq(0); + expect(spendingPlan.subscriptionTier).to.eq(SubscriptionTier.PRIVILEGED); + }); + + it('Should increase the amount spent by the spending plan', async () => { + const contract = await deployContract(largeContractJson, priviledgedEvmAccount.wallet); + + // awaiting for HBAR limiter to finish updating expenses in the background + await Utils.wait(6000); + + const spendingPlan = await hbarSpendingPlanRepository.findByIdWithDetails( + privilegedHbarSpendingPlan.id, + requestDetails, + ); + + const expectedCost = await getExpectedCostOfLastLargeTx(contract.deploymentTransaction()!.data); + const amountSpent = spendingPlan.amountSpent; + + expect(amountSpent).to.be.approximately(expectedCost, 0.009 * expectedCost); + }); + + it('Should eventually exhaust the hbar limit for BASIC user but PRIVILEDGED user is still able to make calls', async () => { + let contract: ethers.Contract; + try { + for (let i = 0; i < 50; i++) { + contract = await deployContract(largeContractJson, accounts[2].wallet); + } + expect.fail(`Expected an error but nothing was thrown`); + } catch (e: any) { + expect(e.message).to.contain(predefined.HBAR_RATE_LIMIT_EXCEEDED.message); + + // awaiting for HBAR limiter to finish updating expenses in the background + await Utils.wait(6000); + + // it should use a different BASIC plan for another user + const gasPrice = await relay.gasPrice(requestId); + const tx = { + ...defaultLondonTransactionData, + to: contract!.target, + nonce: await relay.getAccountNonce(priviledgedEvmAccount.address, requestId), + maxPriorityFeePerGas: gasPrice, + maxFeePerGas: gasPrice, + }; + const signedTxThird = await priviledgedEvmAccount.wallet.signTransaction(tx); + + await expect( + relay.call(testConstants.ETH_ENDPOINTS.ETH_SEND_RAW_TRANSACTION, [signedTxThird], requestId), + ).to.be.fulfilled; + + const spendingPlan = await hbarSpendingPlanRepository.findByIdWithDetails( + privilegedHbarSpendingPlan.id, + requestDetails, + ); + const amountSpent = spendingPlan.amountSpent; + expect(amountSpent).to.lt(maxPrivilegedSpendingLimit); + } + }); + + it('Should eventually exhaust the hbar limit for PRIVILEDGED user', async () => { + const fileChunkSize = Number(ConfigService.get('FILE_APPEND_CHUNK_SIZE')) || 5120; + const remainingHbarsBefore = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT)); + const exchangeRateResult = (await mirrorNode.get(`/network/exchangerate`, requestId)).current_rate; + const exchangeRateInCents = exchangeRateResult.cent_equivalent / exchangeRateResult.hbar_equivalent; + + const factory = new ethers.ContractFactory( + largeContractJson.abi, + largeContractJson.bytecode, + accounts[0].wallet, + ); + const deployedTransaction = await factory.getDeployTransaction(); + const estimatedTxFee = estimateFileTransactionsFee( + deployedTransaction.data.length, + fileChunkSize, + exchangeRateInCents, + ); + + try { + for (let i = 0; i < 50; i++) { + await deployContract(largeContractJson, priviledgedEvmAccount.wallet); + } + expect.fail(`Expected an error but nothing was thrown`); + } catch (e: any) { + expect(e.message).to.contain(predefined.HBAR_RATE_LIMIT_EXCEEDED.message); + + // awaiting for HBAR limiter to finish updating expenses in the background + await Utils.wait(6000); + + const spendingPlanAssociated = await hbarSpendingPlanRepository.findByIdWithDetails( + privilegedHbarSpendingPlan.id, + requestDetails, + ); + const amountSpent = spendingPlanAssociated.amountSpent; + const remainingHbarsAfter = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT)); + + // Explanation: + // An HBAR limit check triggers the HBAR_RATE_LIMIT_EXCEED error in two scenarios: + // a. if remainingHbarsBefore > maxPrivilegedSpendingLimit ===> (totalHbarSpentByPrivilegedPlan + estimatedTxFee) > maxPrivilegedSpendingLimit + // b. if remainingHbarsBefore <= maxPrivilegedSpendingLimit ===> (remainingBudget - estimatedTxFee) < 0 + if (remainingHbarsBefore > maxPrivilegedSpendingLimit) { + expect(amountSpent + estimatedTxFee).to.be.gt(maxPrivilegedSpendingLimit); + } else { + expect(remainingHbarsAfter).to.be.lt(estimatedTxFee); + } + } + }); + }); + }); }); }); } diff --git a/packages/server/tests/localAcceptance.env b/packages/server/tests/localAcceptance.env index e096ae317..734d3df81 100644 --- a/packages/server/tests/localAcceptance.env +++ b/packages/server/tests/localAcceptance.env @@ -27,6 +27,8 @@ LIMIT_DURATION=90000 SERVER_REQUEST_TIMEOUT_MS=60000 MEMWATCH_ENABLED=true WRITE_SNAPSHOT_ON_MEMORY_LEAK=false -HBAR_RATE_LIMIT_TINYBAR=5000000000# 50 HBARs -HBAR_RATE_LIMIT_DURATION=80000# 80 seconds +HBAR_RATE_LIMIT_TINYBAR=9000000000# 90 HBARs +HBAR_RATE_LIMIT_DURATION=180000# 180 seconds HBAR_RATE_LIMIT_BASIC=4000000000# 40 HBARs +HBAR_RATE_LIMIT_PRIVILEGED=5000000000# 50 HBARs +HBAR_SPENDING_PLANS_CONFIG_FILE=./packages/server/tests/testSpendingPlansConfig.json diff --git a/packages/server/tests/mainnetAcceptance.env b/packages/server/tests/mainnetAcceptance.env index f89a78b89..13604d7b7 100644 --- a/packages/server/tests/mainnetAcceptance.env +++ b/packages/server/tests/mainnetAcceptance.env @@ -5,6 +5,7 @@ MIRROR_NODE_URL_WEB3=https://mainnet.mirrornode.hedera.com E2E_RELAY_HOST=https://mainnet.hashio.io/api WS_RELAY_URL=wss://mainnet.hashio.io/ws TEST_INITIAL_ACCOUNT_STARTING_BALANCE=500 -HBAR_RATE_LIMIT_TINYBAR=5000000000# 50 HBARs -HBAR_RATE_LIMIT_DURATION=80000# 80 seconds +HBAR_RATE_LIMIT_TINYBAR=9000000000# 90 HBARs +HBAR_RATE_LIMIT_DURATION=180000# 180 seconds HBAR_RATE_LIMIT_BASIC=4000000000# 40 HBARs +HBAR_RATE_LIMIT_PRIVILEGED=5000000000# 50 HBARs diff --git a/packages/server/tests/previewnetAcceptance.env b/packages/server/tests/previewnetAcceptance.env index dcf0b8597..dbe07c5e9 100644 --- a/packages/server/tests/previewnetAcceptance.env +++ b/packages/server/tests/previewnetAcceptance.env @@ -20,6 +20,7 @@ SERVER_REQUEST_TIMEOUT_MS=60000 MEMWATCH_ENABLED=false WRITE_SNAPSHOT_ON_MEMORY_LEAK=false TEST_INITIAL_ACCOUNT_STARTING_BALANCE=500 -HBAR_RATE_LIMIT_TINYBAR=5000000000# 50 HBARs -HBAR_RATE_LIMIT_DURATION=80000# 80 seconds +HBAR_RATE_LIMIT_TINYBAR=9000000000# 90 HBARs +HBAR_RATE_LIMIT_DURATION=180000# 180 seconds HBAR_RATE_LIMIT_BASIC=4000000000# 40 HBARs +HBAR_RATE_LIMIT_PRIVILEGED=5000000000# 50 HBARs diff --git a/packages/server/tests/testSpendingPlansConfig.json b/packages/server/tests/testSpendingPlansConfig.json new file mode 100644 index 000000000..1dd7484c6 --- /dev/null +++ b/packages/server/tests/testSpendingPlansConfig.json @@ -0,0 +1,28 @@ +[ + { + "id": "c758c095-342c-4607-9db5-867d7e90ab9d", + "name": "PRIVILEGED_ALPHA", + "ethAddresses": ["0x7d102fe71af42790fe31b126c1f49766376ca2b5"], + "ipAddresses": ["127.0.0.1"], + "subscriptionTier": "PRIVILEGED" + }, + { + "id": "a68488b0-6f7d-44a0-87c1-774ad64615f2", + "name": "PRIVILEGED_BETA", + "ethAddresses": ["0x40183ec818c1826114767391989ff2eaebc2b91e"], + "subscriptionTier": "PRIVILEGED" + }, + { + "id": "af13d6ed-d676-4d33-8b9d-cf05d1ad7134", + "name": "EXTENDED_ALPHA", + "ethAddresses": ["0x149294f355f62827748988071de70ab195d0eb23"], + "ipAddresses": ["127.0.0.2"], + "subscriptionTier": "EXTENDED" + }, + { + "id": "7f665aa3-6b73-41d7-bf9b-92d04cdab96b", + "name": "EXTENDED_BETA", + "ipAddresses": ["127.0.0.3"], + "subscriptionTier": "EXTENDED" + } +] diff --git a/packages/server/tests/testnetAcceptance.env b/packages/server/tests/testnetAcceptance.env index 9927c8fe3..93f45cbdc 100644 --- a/packages/server/tests/testnetAcceptance.env +++ b/packages/server/tests/testnetAcceptance.env @@ -19,6 +19,7 @@ SERVER_REQUEST_TIMEOUT_MS=60000 MEMWATCH_ENABLED=false WRITE_SNAPSHOT_ON_MEMORY_LEAK=false TEST_INITIAL_ACCOUNT_STARTING_BALANCE=500 -HBAR_RATE_LIMIT_TINYBAR=5000000000# 50 HBARs -HBAR_RATE_LIMIT_DURATION=80000# 80 seconds +HBAR_RATE_LIMIT_TINYBAR=9000000000# 90 HBARs +HBAR_RATE_LIMIT_DURATION=180000# 180 seconds HBAR_RATE_LIMIT_BASIC=4000000000# 40 HBARs +HBAR_RATE_LIMIT_PRIVILEGED=5000000000# 50 HBARs