Skip to content

Commit 6a9acb2

Browse files
quiet-nodeebadiere
andauthored
chore: cherry pick #3252 to release/0.60 (#3269)
fix: Allow the HBar Rate Limiter to be disabled. (#3252) * fix: Allow the HBar Rate Limiter to be disabled. * fix: Added acceptance test. * fix: Improved test by adding condition that would normally trigger rate limiting, and added isEnabled() method. * fix: Cleaned up and tightened test. * fix: Updated workflow for new tests. * fix: Updated comment. * fix: added the new hbarlimiter_batch_3 to the public_result in the workflow * fix: Test clean up. fix: clean up of experimental code. * fix: added isEanbled check to addExpense. * fix: Check totalBudget instead of remainingBudget in constructor. * fix: clean up. * fix: Test fix. Now that the addExpense is also skipped when the HBar Rate Limiter is disabled the test should not use the expenses aggregated to determine if the maxSpendingLimit has been passed, but simply use the relay operator's before and after balances. * fix: Added note around nullish coalescing operator. * fix: Added check for remaining budget at start of test and renamed flag to more meaningful name. --------- Signed-off-by: Eric Badiere <[email protected]> Co-authored-by: Eric Badiere <[email protected]>
1 parent 8648335 commit 6a9acb2

File tree

7 files changed

+143
-28
lines changed

7 files changed

+143
-28
lines changed

.github/workflows/acceptance.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ jobs:
5555
with:
5656
testfilter: hbarlimiter_batch2
5757

58+
hbarlimiter_batch_3:
59+
name: HBar Limiter Batch 3
60+
uses: ./.github/workflows/acceptance-workflow.yml
61+
with:
62+
testfilter: hbarlimiter_batch3
63+
5864
tokencreate:
5965
name: Token Create
6066
uses: ./.github/workflows/acceptance-workflow.yml
@@ -123,6 +129,7 @@ jobs:
123129
- ratelimiter
124130
- hbarlimiter_batch_1
125131
- hbarlimiter_batch_2
132+
- hbarlimiter_batch_3
126133
- tokencreate
127134
- tokenmanagement
128135
- htsprecompilev1

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"acceptancetest:ratelimiter": "nyc ts-mocha packages/ws-server/tests/acceptance/index.spec.ts -g '@web-socket-ratelimiter' --exit && ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@ratelimiter' --exit",
5151
"acceptancetest:hbarlimiter_batch1": "HBAR_RATE_LIMIT_BASIC=1000000000 HBAR_RATE_LIMIT_EXTENDED=1500000000 HBAR_RATE_LIMIT_PRIVILEGED=2000000000 nyc ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@hbarlimiter-batch1' --exit",
5252
"acceptancetest:hbarlimiter_batch2": "HBAR_RATE_LIMIT_BASIC=1000000000 HBAR_RATE_LIMIT_EXTENDED=1500000000 HBAR_RATE_LIMIT_PRIVILEGED=2000000000 nyc ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@hbarlimiter-batch2' --exit",
53+
"acceptancetest:hbarlimiter_batch3": "HBAR_RATE_LIMIT_TINYBAR=0 HBAR_RATE_LIMIT_BASIC=1000000000 HBAR_RATE_LIMIT_EXTENDED=1500000000 HBAR_RATE_LIMIT_PRIVILEGED=2000000000 nyc ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@hbarlimiter-batch3' --exit",
5354
"acceptancetest:tokencreate": "nyc ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@tokencreate' --exit",
5455
"acceptancetest:tokenmanagement": "nyc ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@tokenmanagement' --exit",
5556
"acceptancetest:htsprecompilev1": "nyc ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@htsprecompilev1' --exit",

packages/relay/src/lib/constants.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,9 @@ export default {
145145
// @ts-ignore
146146
HBAR_RATE_LIMIT_DURATION: parseInt(ConfigService.get('HBAR_RATE_LIMIT_DURATION') || '86400000'), // 1 day
147147
// @ts-ignore
148-
HBAR_RATE_LIMIT_TOTAL: BigNumber(ConfigService.get('HBAR_RATE_LIMIT_TINYBAR') || '800000000000'), // 8000 HBARs
148+
// The logical OR operator || returns the first truthy value and 0 is falsy.
149+
// The nullish coalescing operator ?? falls back to the default value when the left-hand operand is null or undefined, not when it's 0 or any other falsy value.
150+
HBAR_RATE_LIMIT_TOTAL: BigNumber(ConfigService.get('HBAR_RATE_LIMIT_TINYBAR') ?? '800000000000'), // 8000 HBARs
149151
// @ts-ignore
150152
HBAR_RATE_LIMIT_BASIC: BigNumber(ConfigService.get('HBAR_RATE_LIMIT_BASIC') || '1120000000'), // 11.2 HBARs
151153
// @ts-ignore

packages/relay/src/lib/services/hbarLimitService/IHbarLimitService.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,5 @@ export interface IHbarLimitService {
3131
estimatedTxFee?: number,
3232
): Promise<boolean>;
3333
addExpense(cost: number, ethAddress: string, requestDetails: RequestDetails, ipAddress?: string): Promise<void>;
34+
isEnabled(): boolean;
3435
}

packages/relay/src/lib/services/hbarLimitService/index.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ export class HbarLimitService implements IHbarLimitService {
3737
PRIVILEGED: Hbar.fromTinybars(constants.HBAR_RATE_LIMIT_PRIVILEGED),
3838
};
3939

40+
/**
41+
* Flag to turn off the HBarRateLimitService.
42+
* @private
43+
*/
44+
private readonly isHBarRateLimiterEnabled: boolean = true;
45+
4046
/**
4147
* Counts the number of times the rate limit has been reached.
4248
* @private
@@ -90,6 +96,10 @@ export class HbarLimitService implements IHbarLimitService {
9096
this.reset = this.getResetTimestamp();
9197
this.remainingBudget = this.totalBudget;
9298

99+
if (this.totalBudget.toTinybars().lte(0)) {
100+
this.isHBarRateLimiterEnabled = false;
101+
}
102+
93103
const metricCounterName = 'rpc_relay_hbar_rate_limit';
94104
this.register.removeSingleMetric(metricCounterName);
95105
this.hbarLimitCounter = new Counter({
@@ -142,6 +152,14 @@ export class HbarLimitService implements IHbarLimitService {
142152
);
143153
}
144154

155+
/**
156+
* Checks if the rate limiter is enabled.
157+
* returns {boolean} - `true` if the rate limiter is enabled, otherwise `false`.
158+
*/
159+
isEnabled(): boolean {
160+
return this.isHBarRateLimiterEnabled;
161+
}
162+
145163
/**
146164
* Resets the {@link HbarSpendingPlan#amountSpent} field for all existing plans.
147165
* @param {RequestDetails} requestDetails - The request details used for logging and tracking.
@@ -181,6 +199,10 @@ export class HbarLimitService implements IHbarLimitService {
181199
requestDetails: RequestDetails,
182200
estimatedTxFee: number = 0,
183201
): Promise<boolean> {
202+
if (!this.isEnabled()) {
203+
return false;
204+
}
205+
184206
const ipAddress = requestDetails.ipAddress;
185207
if (await this.isTotalBudgetExceeded(mode, methodName, txConstructorName, estimatedTxFee, requestDetails)) {
186208
return true;
@@ -235,6 +257,10 @@ export class HbarLimitService implements IHbarLimitService {
235257
* @returns {Promise<void>} - A promise that resolves when the expense has been added.
236258
*/
237259
async addExpense(cost: number, ethAddress: string, requestDetails: RequestDetails): Promise<void> {
260+
if (!this.isEnabled()) {
261+
return;
262+
}
263+
238264
const newRemainingBudget = this.remainingBudget.toTinybars().sub(cost);
239265
this.remainingBudget = Hbar.fromTinybars(newRemainingBudget);
240266
this.hbarLimitRemainingGauge.set(newRemainingBudget.toNumber());

packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,46 @@ describe('HBAR Rate Limit Service', function () {
494494
expect(result).to.be.false;
495495
});
496496
});
497+
498+
describe('disable the rate limiter', function () {
499+
const hbarLimitServiceDisabled = new HbarLimitService(
500+
hbarSpendingPlanRepositoryStub,
501+
ethAddressHbarSpendingPlanRepositoryStub,
502+
ipAddressHbarSpendingPlanRepositoryStub,
503+
logger,
504+
register,
505+
Hbar.fromTinybars(0),
506+
limitDuration,
507+
);
508+
509+
it('should return false if the rate limiter is disabled by setting HBAR_RATE_LIMIT_TINYBAR to zero', async function () {
510+
const spendingPlan = createSpendingPlan(
511+
mockPlanId,
512+
HbarLimitService.TIER_LIMITS[SubscriptionTier.BASIC].toTinybars().sub(mockEstimatedTxFee).add(1),
513+
);
514+
ethAddressHbarSpendingPlanRepositoryStub.findByAddress.resolves({
515+
ethAddress: mockEthAddress,
516+
planId: mockPlanId,
517+
});
518+
hbarSpendingPlanRepositoryStub.findByIdWithDetails.resolves(spendingPlan);
519+
520+
const result = await hbarLimitServiceDisabled.shouldLimit(
521+
mode,
522+
methodName,
523+
txConstructorName,
524+
mockEthAddress,
525+
requestDetails,
526+
mockEstimatedTxFee,
527+
);
528+
529+
// Rate limiter is disabled, so it should return false
530+
expect(result).to.be.false;
531+
});
532+
533+
it('should return undefined if the rate limiter is disabled and addExpense is called', async function () {
534+
expect(await hbarLimitServiceDisabled.addExpense(100, mockEthAddress, requestDetails)).to.be.undefined;
535+
});
536+
});
497537
});
498538

499539
describe('getSpendingPlan', function () {

packages/server/tests/acceptance/hbarLimiter.spec.ts

Lines changed: 65 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,33 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () {
9191
logger.child({ name: 'hbar-spending-plan-repository' }),
9292
);
9393

94+
const pollForProperAmountSpent = async (
95+
hbarSpendingPlan: IDetailedHbarSpendingPlan,
96+
deploymentCounts: number,
97+
expectedTxCost: number,
98+
) => {
99+
let amountSpent = (await hbarSpendingPlanRepository.findByIdWithDetails(hbarSpendingPlan.id, requestDetails))
100+
.amountSpent;
101+
102+
while (amountSpent < deploymentCounts * expectedTxCost) {
103+
logger.warn(
104+
`Fail to retrieve proper amount spent by the spending plan. Polling for the proper amount: deploymentCounts=${deploymentCounts}, expectedTxCost=${expectedTxCost}, amountSpent=${amountSpent}, properAmountSpent=${
105+
deploymentCounts * expectedTxCost
106+
}, planId=${hbarSpendingPlan.id}`,
107+
);
108+
await Utils.wait(3000);
109+
amountSpent = (await hbarSpendingPlanRepository.findByIdWithDetails(hbarSpendingPlan.id, requestDetails))
110+
.amountSpent;
111+
}
112+
113+
logger.info(
114+
`Successfully retrieve proper amount spent by hbarSpendingPlan: deploymentCounts=${deploymentCounts}, expectedTxCost=${expectedTxCost}, amountSpent=${amountSpent}, properAmountSpent=${
115+
deploymentCounts * expectedTxCost
116+
}, planId=${hbarSpendingPlan.id}`,
117+
);
118+
return amountSpent;
119+
};
120+
94121
// The following tests exhaust the hbar limit, so they should only be run against a local relay
95122
if (relayIsLocal) {
96123
const deployContract = async (contractJson: any, wallet: ethers.Wallet): Promise<ethers.Contract> => {
@@ -414,33 +441,6 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () {
414441
return { aliasAccounts, hbarSpendingPlan };
415442
};
416443

417-
const pollForProperAmountSpent = async (
418-
hbarSpendingPlan: IDetailedHbarSpendingPlan,
419-
deploymentCounts: number,
420-
expectedTxCost: number,
421-
) => {
422-
let amountSpent = (await hbarSpendingPlanRepository.findByIdWithDetails(hbarSpendingPlan.id, requestDetails))
423-
.amountSpent;
424-
425-
while (amountSpent < deploymentCounts * expectedTxCost) {
426-
logger.warn(
427-
`Fail to retrieve proper amount spent by the spending plan. Polling for the proper amount: deploymentCounts=${deploymentCounts}, expectedTxCost=${expectedTxCost}, amountSpent=${amountSpent}, properAmountSpent=${
428-
deploymentCounts * expectedTxCost
429-
}, planId=${hbarSpendingPlan.id}`,
430-
);
431-
await Utils.wait(3000);
432-
amountSpent = (await hbarSpendingPlanRepository.findByIdWithDetails(hbarSpendingPlan.id, requestDetails))
433-
.amountSpent;
434-
}
435-
436-
logger.info(
437-
`Successfully retrieve proper amount spent by hbarSpendingPlan: deploymentCounts=${deploymentCounts}, expectedTxCost=${expectedTxCost}, amountSpent=${amountSpent}, properAmountSpent=${
438-
deploymentCounts * expectedTxCost
439-
}, planId=${hbarSpendingPlan.id}`,
440-
);
441-
return amountSpent;
442-
};
443-
444444
describe('@hbarlimiter-batch1 BASIC Tier', () => {
445445
beforeEach(async function () {
446446
const basicPlans = await hbarSpendingPlanRepository.findAllActiveBySubscriptionTier(
@@ -887,5 +887,43 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () {
887887
});
888888
});
889889
});
890+
891+
describe('@hbarlimiter-batch3 Unlimited', () => {
892+
before(async function () {
893+
logger.info(
894+
`${requestDetails.formattedRequestId} HBAR_RATE_LIMIT_TINYBAR: ${ConfigService.get(
895+
'HBAR_RATE_LIMIT_TINYBAR',
896+
)}`,
897+
);
898+
});
899+
900+
it('should eventually exhaust the hbar limit for a BASIC user after multiple deployments of large contracts, and not throw an error', async function () {
901+
// confirm that HBAR_RATE_LIMIT_TINYBAR is set to zero
902+
expect(ConfigService.get('HBAR_RATE_LIMIT_TINYBAR')).to.eq(0);
903+
// This should set the remaining HBAR limit to zero
904+
const remainingHbarsBefore = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT));
905+
expect(remainingHbarsBefore).to.eq(0);
906+
let deploymentCounts = 0;
907+
908+
const operatorBalanceBefore = (await mirrorNode.get(`/accounts/${operatorAccount}`, requestId)).balance.balance;
909+
910+
for (deploymentCounts = 0; deploymentCounts < 3; deploymentCounts++) {
911+
const tx = await deployContract(largeContractJson, global.accounts[0].wallet);
912+
await tx.waitForDeployment();
913+
}
914+
// Verify that the amount spent exceeds the HBAR limit
915+
const operatorBalanceAfter = (await mirrorNode.get(`/accounts/${operatorAccount}`, requestId)).balance.balance;
916+
const amountSpent = operatorBalanceBefore - operatorBalanceAfter;
917+
expect(amountSpent).to.be.gt(maxBasicSpendingLimit);
918+
919+
// Verify that remaining HBAR limit is zero
920+
const remainingHbarsAfter = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT));
921+
expect(remainingHbarsAfter).to.be.eq(0);
922+
923+
// Confirm that deployments continued even after the limit was exhausted
924+
expect(deploymentCounts).to.be.gte(1);
925+
await expect(deployContract(largeContractJson, global.accounts[0].wallet)).to.be.fulfilled;
926+
});
927+
});
890928
}
891929
});

0 commit comments

Comments
 (0)