Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Allow the HBar Rate Limiter to be disabled. #3252

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"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",
"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",
"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",
"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",
quiet-node marked this conversation as resolved.
Show resolved Hide resolved
"acceptancetest:tokencreate": "nyc ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@tokencreate' --exit",
"acceptancetest:tokenmanagement": "nyc ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@tokenmanagement' --exit",
"acceptancetest:htsprecompilev1": "nyc ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@htsprecompilev1' --exit",
Expand Down
2 changes: 1 addition & 1 deletion packages/relay/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ export default {
// @ts-ignore
HBAR_RATE_LIMIT_DURATION: parseInt(ConfigService.get('HBAR_RATE_LIMIT_DURATION') || '86400000'), // 1 day
// @ts-ignore
HBAR_RATE_LIMIT_TOTAL: BigNumber(ConfigService.get('HBAR_RATE_LIMIT_TINYBAR') || '800000000000'), // 8000 HBARs
HBAR_RATE_LIMIT_TOTAL: BigNumber(ConfigService.get('HBAR_RATE_LIMIT_TINYBAR') ?? '800000000000'), // 8000 HBARs
ebadiere marked this conversation as resolved.
Show resolved Hide resolved
// @ts-ignore
HBAR_RATE_LIMIT_BASIC: BigNumber(ConfigService.get('HBAR_RATE_LIMIT_BASIC') || '1120000000'), // 11.2 HBARs
// @ts-ignore
Expand Down
14 changes: 14 additions & 0 deletions packages/relay/src/lib/services/hbarLimitService/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ export class HbarLimitService implements IHbarLimitService {
PRIVILEGED: Hbar.fromTinybars(constants.HBAR_RATE_LIMIT_PRIVILEGED),
};

/**
* Disables the rate limiter.
quiet-node marked this conversation as resolved.
Show resolved Hide resolved
* @private
*/
private readonly disableRateLimiter: boolean = false;

/**
* Counts the number of times the rate limit has been reached.
* @private
Expand Down Expand Up @@ -90,6 +96,10 @@ export class HbarLimitService implements IHbarLimitService {
this.reset = this.getResetTimestamp();
this.remainingBudget = this.totalBudget;

if (this.remainingBudget.toTinybars().isZero()) {
ebadiere marked this conversation as resolved.
Show resolved Hide resolved
this.disableRateLimiter = true;
}

const metricCounterName = 'rpc_relay_hbar_rate_limit';
this.register.removeSingleMetric(metricCounterName);
this.hbarLimitCounter = new Counter({
Expand Down Expand Up @@ -181,6 +191,10 @@ export class HbarLimitService implements IHbarLimitService {
requestDetails: RequestDetails,
estimatedTxFee: number = 0,
): Promise<boolean> {
if (this.disableRateLimiter) {
ebadiere marked this conversation as resolved.
Show resolved Hide resolved
return false;
}

const ipAddress = requestDetails.ipAddress;
if (await this.isTotalBudgetExceeded(mode, methodName, txConstructorName, estimatedTxFee, requestDetails)) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,29 @@ describe('HBAR Rate Limit Service', function () {
expect(result).to.be.false;
});
});

describe('It should be able to disable the rate limiter', function () {
const hbarLimitServiceDisabled = new HbarLimitService(
hbarSpendingPlanRepositoryStub,
ethAddressHbarSpendingPlanRepositoryStub,
ipAddressHbarSpendingPlanRepositoryStub,
logger,
register,
Hbar.fromTinybars(0),
limitDuration,
);
it('should return false if the rate limiter is disabled by setting HBAR_RATE_LIMIT_TINYBAR to zero', async function () {
// hbarLimitServiceDisabled.disableRateLimiter();
const result = await hbarLimitServiceDisabled.shouldLimit(
ebadiere marked this conversation as resolved.
Show resolved Hide resolved
mode,
methodName,
txConstructorName,
'',
requestDetails,
);
expect(result).to.be.false;
});
});
});

describe('getSpendingPlan', function () {
Expand Down
153 changes: 126 additions & 27 deletions packages/server/tests/acceptance/hbarLimiter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,33 @@ import mediumSizeContract from '../contracts/hbarLimiterContracts/mediumSizeCont
config({ path: resolve(__dirname, '../localAcceptance.env') });
const DOT_ENV = dotenv.parse(fs.readFileSync(resolve(__dirname, '../localAcceptance.env')));

const pollForProperAmountSpent = async (
quiet-node marked this conversation as resolved.
Show resolved Hide resolved
hbarSpendingPlan: IDetailedHbarSpendingPlan,
deploymentCounts: number,
expectedTxCost: number,
) => {
let amountSpent = (await hbarSpendingPlanRepository.findByIdWithDetails(hbarSpendingPlan.id, requestDetails))
.amountSpent;

while (amountSpent < deploymentCounts * expectedTxCost) {
logger.warn(
`Fail to retrieve proper amount spent by the spending plan. Polling for the proper amount: deploymentCounts=${deploymentCounts}, expectedTxCost=${expectedTxCost}, amountSpent=${amountSpent}, properAmountSpent=${
deploymentCounts * expectedTxCost
}, planId=${hbarSpendingPlan.id}`,
);
await Utils.wait(3000);
amountSpent = (await hbarSpendingPlanRepository.findByIdWithDetails(hbarSpendingPlan.id, requestDetails))
.amountSpent;
}

logger.info(
`Successfully retrieve proper amount spent by hbarSpendingPlan: deploymentCounts=${deploymentCounts}, expectedTxCost=${expectedTxCost}, amountSpent=${amountSpent}, properAmountSpent=${
deploymentCounts * expectedTxCost
}, planId=${hbarSpendingPlan.id}`,
);
return amountSpent;
};

describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () {
// @ts-ignore
const {
Expand Down Expand Up @@ -91,6 +118,33 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () {
logger.child({ name: 'hbar-spending-plan-repository' }),
);

const pollForProperAmountSpent = async (
hbarSpendingPlan: IDetailedHbarSpendingPlan,
deploymentCounts: number,
expectedTxCost: number,
) => {
let amountSpent = (await hbarSpendingPlanRepository.findByIdWithDetails(hbarSpendingPlan.id, requestDetails))
.amountSpent;

while (amountSpent < deploymentCounts * expectedTxCost) {
logger.warn(
`Fail to retrieve proper amount spent by the spending plan. Polling for the proper amount: deploymentCounts=${deploymentCounts}, expectedTxCost=${expectedTxCost}, amountSpent=${amountSpent}, properAmountSpent=${
deploymentCounts * expectedTxCost
}, planId=${hbarSpendingPlan.id}`,
);
await Utils.wait(3000);
amountSpent = (await hbarSpendingPlanRepository.findByIdWithDetails(hbarSpendingPlan.id, requestDetails))
.amountSpent;
}

logger.info(
`Successfully retrieve proper amount spent by hbarSpendingPlan: deploymentCounts=${deploymentCounts}, expectedTxCost=${expectedTxCost}, amountSpent=${amountSpent}, properAmountSpent=${
deploymentCounts * expectedTxCost
}, planId=${hbarSpendingPlan.id}`,
);
return amountSpent;
};

// The following tests exhaust the hbar limit, so they should only be run against a local relay
if (relayIsLocal) {
const deployContract = async (contractJson: any, wallet: ethers.Wallet): Promise<ethers.Contract> => {
Expand Down Expand Up @@ -414,33 +468,6 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () {
return { aliasAccounts, hbarSpendingPlan };
};

const pollForProperAmountSpent = async (
hbarSpendingPlan: IDetailedHbarSpendingPlan,
deploymentCounts: number,
expectedTxCost: number,
) => {
let amountSpent = (await hbarSpendingPlanRepository.findByIdWithDetails(hbarSpendingPlan.id, requestDetails))
.amountSpent;

while (amountSpent < deploymentCounts * expectedTxCost) {
logger.warn(
`Fail to retrieve proper amount spent by the spending plan. Polling for the proper amount: deploymentCounts=${deploymentCounts}, expectedTxCost=${expectedTxCost}, amountSpent=${amountSpent}, properAmountSpent=${
deploymentCounts * expectedTxCost
}, planId=${hbarSpendingPlan.id}`,
);
await Utils.wait(3000);
amountSpent = (await hbarSpendingPlanRepository.findByIdWithDetails(hbarSpendingPlan.id, requestDetails))
.amountSpent;
}

logger.info(
`Successfully retrieve proper amount spent by hbarSpendingPlan: deploymentCounts=${deploymentCounts}, expectedTxCost=${expectedTxCost}, amountSpent=${amountSpent}, properAmountSpent=${
deploymentCounts * expectedTxCost
}, planId=${hbarSpendingPlan.id}`,
);
return amountSpent;
};

describe('@hbarlimiter-batch1 BASIC Tier', () => {
beforeEach(async function () {
const basicPlans = await hbarSpendingPlanRepository.findAllActiveBySubscriptionTier(
Expand Down Expand Up @@ -887,5 +914,77 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () {
});
});
});

describe('@hbarlimiter-batch3 BASIC Tier', () => {
ebadiere marked this conversation as resolved.
Show resolved Hide resolved
const accounts: AliasAccount[] = [];
ebadiere marked this conversation as resolved.
Show resolved Hide resolved

beforeEach(async function () {
ebadiere marked this conversation as resolved.
Show resolved Hide resolved
logger.info(`${requestDetails.formattedRequestId} Creating accounts`);
quiet-node marked this conversation as resolved.
Show resolved Hide resolved
logger.info(
`${requestDetails.formattedRequestId} HBAR_RATE_LIMIT_TINYBAR: ${ConfigService.get(
'HBAR_RATE_LIMIT_TINYBAR',
)}`,
);
quiet-node marked this conversation as resolved.
Show resolved Hide resolved

const initialAccount: AliasAccount = global.accounts[0];

const neededAccounts: number = 3;
quiet-node marked this conversation as resolved.
Show resolved Hide resolved
accounts.push(
...(await Utils.createMultipleAliasAccounts(
mirrorNode,
initialAccount,
neededAccounts,
initialBalance,
requestDetails,
)),
);
global.accounts.push(...accounts);
const basicPlans = await hbarSpendingPlanRepository.findAllActiveBySubscriptionTier(
[SubscriptionTier.BASIC],
requestDetails,
);
quiet-node marked this conversation as resolved.
Show resolved Hide resolved
for (const plan of basicPlans) {
await hbarSpendingPlanRepository.delete(plan.id, requestDetails);
await ethAddressSpendingPlanRepository.deleteAllByPlanId(plan.id, 'before', requestDetails);
await ipSpendingPlanRepository.deleteAllByPlanId(plan.id, 'before', requestDetails);
}
});
quiet-node marked this conversation as resolved.
Show resolved Hide resolved

it('should eventually exhaust the hbar limit for a BASIC user after multiple deployments of large contracts, and not throw an error', async function () {
let expectedTxCost = 0;
let deploymentCounts = 0;
let hbarSpendingPlan: IDetailedHbarSpendingPlan | null = null;

for (deploymentCounts = 0; deploymentCounts < 3; deploymentCounts++) {
const tx = await deployContract(largeContractJson, accounts[2].wallet);
await tx.waitForDeployment();

expectedTxCost ||= await getExpectedCostOfLastLargeTx(largeContractJson.bytecode);

if (!hbarSpendingPlan) {
const ethSpendingPlan = await ethAddressSpendingPlanRepository.findByAddress(
accounts[2].wallet.address,
requestDetails,
);
hbarSpendingPlan = await hbarSpendingPlanRepository.findByIdWithDetails(
ethSpendingPlan.planId,
requestDetails,
);
}

await pollForProperAmountSpent(hbarSpendingPlan, deploymentCounts + 1, expectedTxCost);
}
// Verify that the amount spent exceeds the HBAR limit
const amountSpent = await pollForProperAmountSpent(hbarSpendingPlan, deploymentCounts, expectedTxCost);
quiet-node marked this conversation as resolved.
Show resolved Hide resolved
expect(amountSpent).to.be.gte(maxBasicSpendingLimit);

// Verify that remaining HBAR limit is zero or negative
const remainingHbarsAfter = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT));
expect(remainingHbarsAfter).to.be.lte(0);

// Confirm that deployments continued even after the limit was exhausted
expect(deploymentCounts).to.be.gte(1);
quiet-node marked this conversation as resolved.
Show resolved Hide resolved
});
});
}
});
Loading