Skip to content

Commit

Permalink
fix: bugged metrics and wrong env vars (#3090)
Browse files Browse the repository at this point in the history
* fix: bugged metrics and wrong env vars

Signed-off-by: Victor Yanev <victor.yanev@limechain.tech>

* fix: jsdocs

Signed-off-by: Victor Yanev <victor.yanev@limechain.tech>

---------

Signed-off-by: Victor Yanev <victor.yanev@limechain.tech>
  • Loading branch information
victor-yanev authored and konstantinabl committed Oct 15, 2024
1 parent 2257cf4 commit a05fe80
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 56 deletions.
6 changes: 3 additions & 3 deletions packages/relay/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,9 @@ export default {
// TODO: Replace with actual values - https://github.com/hashgraph/hedera-json-rpc-relay/issues/2895
HBAR_RATE_LIMIT_DURATION: parseInt(process.env.HBAR_RATE_LIMIT_DURATION || '80000'), // 80 seconds
HBAR_RATE_LIMIT_TOTAL: BigNumber(process.env.HBAR_RATE_LIMIT_TINYBAR || '11000000000'), // 110 HBARs per 80 seconds
HBAR_RATE_LIMIT_BASIC: BigNumber(process.env.HBAR_DAILY_LIMIT_BASIC || '92592592'), // Equivalent of 1000 HBARs per day
HBAR_RATE_LIMIT_EXTENDED: BigNumber(process.env.HBAR_DAILY_LIMIT_EXTENDED || '925925925'), // Equivalent of 10000 HBARs per day
HBAR_RATE_LIMIT_PRIVILEGED: BigNumber(process.env.HBAR_DAILY_LIMIT_PRIVILEGED || '1851851850'), // Equivalent of 20000 HBARs per day
HBAR_RATE_LIMIT_BASIC: BigNumber(process.env.HBAR_RATE_LIMIT_BASIC || '92592592'), // Equivalent of 1000 HBARs per day
HBAR_RATE_LIMIT_EXTENDED: BigNumber(process.env.HBAR_RATE_LIMIT_EXTENDED || '925925925'), // Equivalent of 10000 HBARs per day
HBAR_RATE_LIMIT_PRIVILEGED: BigNumber(process.env.HBAR_RATE_LIMIT_PRIVILEGED || '1851851850'), // Equivalent of 20000 HBARs per day
GAS_PRICE_TINY_BAR_BUFFER: parseInt(process.env.GAS_PRICE_TINY_BAR_BUFFER || '10000000000'),
WEB_SOCKET_PORT: process.env.WEB_SOCKET_PORT || 8546,
WEB_SOCKET_HTTP_PORT: process.env.WEB_SOCKET_HTTP_PORT || 8547,
Expand Down
70 changes: 39 additions & 31 deletions packages/relay/src/lib/services/hbarLimitService/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,21 +50,21 @@ export class HbarLimitService implements IHbarLimitService {
private readonly hbarLimitRemainingGauge: Gauge;

/**
* Tracks the number of unique spending plans that have been utilized on a daily basis
* Tracks the number of unique spending plans that have been utilized during the limit duration.
* (i.e., plans that had expenses added to them).
*
* For basic spending plans, this equates to the number of unique users who have made requests on that day,
* For basic spending plans, this equates to the number of unique users who have made requests during that period,
* since each user has their own individual spending plan.
*
* @private
*/
private readonly dailyUniqueSpendingPlansCounter: Record<SubscriptionTier, Counter>;
private readonly uniqueSpendingPlansCounter: Record<SubscriptionTier, Counter>;

/**
* Tracks the average daily spending plan usages.
* Tracks the average amount of tinybars spent by spending plans per subscription tier
* @private
*/
private readonly averageDailySpendingPlanUsagesGauge: Record<SubscriptionTier, Gauge>;
private readonly averageSpendingPlanAmountSpentGauge: Record<SubscriptionTier, Gauge>;

/**
* The remaining budget for the rate limiter.
Expand Down Expand Up @@ -109,27 +109,27 @@ export class HbarLimitService implements IHbarLimitService {
});
this.hbarLimitRemainingGauge.set(this.remainingBudget.toTinybars().toNumber());

this.dailyUniqueSpendingPlansCounter = Object.values(SubscriptionTier).reduce(
(acc, type) => {
const dailyUniqueSpendingPlansCounterName = `daily_unique_spending_plans_counter_${type.toLowerCase()}`;
this.register.removeSingleMetric(dailyUniqueSpendingPlansCounterName);
acc[type] = new Counter({
name: dailyUniqueSpendingPlansCounterName,
help: `Tracks the number of unique spending plans used daily for ${type} subscription tier`,
this.uniqueSpendingPlansCounter = Object.values(SubscriptionTier).reduce(
(acc, tier) => {
const uniqueSpendingPlansCounterName = `unique_spending_plans_counter_${tier.toLowerCase()}`;
this.register.removeSingleMetric(uniqueSpendingPlansCounterName);
acc[tier] = new Counter({
name: uniqueSpendingPlansCounterName,
help: `Tracks the number of unique ${tier} spending plans used during the limit duration`,
registers: [register],
});
return acc;
},
{} as Record<SubscriptionTier, Counter>,
);

this.averageDailySpendingPlanUsagesGauge = Object.values(SubscriptionTier).reduce(
(acc, type) => {
const averageDailySpendingGaugeName = `average_daily_spending_plan_usages_gauge_${type.toLowerCase()}`;
this.register.removeSingleMetric(averageDailySpendingGaugeName);
acc[type] = new Gauge({
name: averageDailySpendingGaugeName,
help: `Tracks the average daily spending plan usages for ${type} subscription tier`,
this.averageSpendingPlanAmountSpentGauge = Object.values(SubscriptionTier).reduce(
(acc, tier) => {
const averageAmountSpentGaugeName = `average_spending_plan_amount_spent_gauge_${tier.toLowerCase()}`;
this.register.removeSingleMetric(averageAmountSpentGaugeName);
acc[tier] = new Gauge({
name: averageAmountSpentGaugeName,
help: `Tracks the average amount of tinybars spent by ${tier} spending plans`,
registers: [register],
});
return acc;
Expand All @@ -156,6 +156,7 @@ export class HbarLimitService implements IHbarLimitService {
this.logger.trace(`${requestDetails.formattedRequestId} Resetting HBAR rate limiter...`);
await this.hbarSpendingPlanRepository.resetAmountSpentOfAllPlans(requestDetails);
this.resetBudget();
this.resetTemporaryMetrics();
this.reset = this.getResetTimestamp();
this.logger.trace(
`${requestDetails.formattedRequestId} HBAR Rate Limit reset: remainingBudget=${this.remainingBudget}, newResetTimestamp=${this.reset}`,
Expand All @@ -182,8 +183,7 @@ export class HbarLimitService implements IHbarLimitService {
estimatedTxFee: number = 0,
): Promise<boolean> {
const ipAddress = requestDetails.ipAddress;

if (await this.isDailyBudgetExceeded(mode, methodName, txConstructorName, estimatedTxFee, requestDetails)) {
if (await this.isTotalBudgetExceeded(mode, methodName, estimatedTxFee, requestDetails)) {
return true;
}

Expand Down Expand Up @@ -264,32 +264,32 @@ export class HbarLimitService implements IHbarLimitService {

// Check if the spending plan is being used for the first time today
if (spendingPlan.amountSpent === 0) {
this.dailyUniqueSpendingPlansCounter[spendingPlan.subscriptionTier].inc(1);
this.uniqueSpendingPlansCounter[spendingPlan.subscriptionTier].inc(1);
}

await this.hbarSpendingPlanRepository.addToAmountSpent(spendingPlan.id, cost, requestDetails, this.limitDuration);
this.remainingBudget = Hbar.fromTinybars(this.remainingBudget.toTinybars().sub(cost));
this.hbarLimitRemainingGauge.set(this.remainingBudget.toTinybars().toNumber());

// Done asynchronously in the background
this.updateAverageDailyUsagePerSubscriptionTier(spendingPlan.subscriptionTier, requestDetails).then();
this.updateAverageAmountSpentPerSubscriptionTier(spendingPlan.subscriptionTier, requestDetails).then();

this.logger.trace(
`${requestDetails.formattedRequestId} HBAR rate limit expense update: cost=${cost} tℏ, remainingBudget=${this.remainingBudget}`,
);
}

/**
* Checks if the total daily budget has been exceeded.
* Checks if the total budget of the limiter has been exceeded.
* @param {string} mode - The mode of the transaction or request.
* @param {string} methodName - The name of the method being invoked.
* @param {string} txConstructorName - The name of the transaction constructor associated with the transaction.
* @param {number} estimatedTxFee - The total estimated transaction fee, default to 0.
* @param {RequestDetails} requestDetails The request details for logging and tracking
* @returns {Promise<boolean>} - Resolves `true` if the daily budget has been exceeded, otherwise `false`.
* @returns {Promise<boolean>} - Resolves `true` if the total budget has been exceeded, otherwise `false`.
* @private
*/
private async isDailyBudgetExceeded(
private async isTotalBudgetExceeded(
mode: string,
methodName: string,
txConstructorName: string,
Expand All @@ -314,12 +314,12 @@ export class HbarLimitService implements IHbarLimitService {
}

/**
* Updates the average daily usage per subscription tier.
* @param {SubscriptionTier} subscriptionTier - The subscription tier to update the average daily usage for.
* Updates the average amount of tinybars spent of spending plans per subscription tier.
* @param {SubscriptionTier} subscriptionTier - The subscription tier to update the average usage for.
* @param {RequestDetails} requestDetails - The request details for logging and tracking.
* @private {Promise<void>} - A promise that resolves when the average daily usage has been updated.
* @private {Promise<void>} - A promise that resolves when the average usage has been updated.
*/
private async updateAverageDailyUsagePerSubscriptionTier(
private async updateAverageAmountSpentPerSubscriptionTier(
subscriptionTier: SubscriptionTier,
requestDetails: RequestDetails,
): Promise<void> {
Expand All @@ -329,7 +329,7 @@ export class HbarLimitService implements IHbarLimitService {
);
const totalUsage = plans.reduce((total, plan) => total + plan.amountSpent, 0);
const averageUsage = Math.round(totalUsage / plans.length);
this.averageDailySpendingPlanUsagesGauge[subscriptionTier].set(averageUsage);
this.averageSpendingPlanAmountSpentGauge[subscriptionTier].set(averageUsage);
}

/**
Expand All @@ -350,6 +350,14 @@ export class HbarLimitService implements IHbarLimitService {
this.hbarLimitRemainingGauge.set(this.remainingBudget.toTinybars().toNumber());
}

/**
* Resets the metrics which are used to track the number of unique spending plans used during the limit duration.
* @private
*/
private resetTemporaryMetrics(): void {
Object.values(SubscriptionTier).forEach((tier) => this.uniqueSpendingPlansCounter[tier].reset());
}

/**
* Calculates the next reset timestamp for the rate limiter.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ describe('HBAR Rate Limit Service', function () {
expect(hbarLimitService['hbarLimitCounter']).to.be.instanceOf(Counter);
expect(hbarLimitService['hbarLimitRemainingGauge']).to.be.instanceOf(Gauge);
Object.values(SubscriptionTier).forEach((tier) => {
expect(hbarLimitService['dailyUniqueSpendingPlansCounter'][tier]).to.be.instanceOf(Counter);
expect(hbarLimitService['averageDailySpendingPlanUsagesGauge'][tier]).to.be.instanceOf(Gauge);
expect(hbarLimitService['uniqueSpendingPlansCounter'][tier]).to.be.instanceOf(Counter);
expect(hbarLimitService['averageSpendingPlanAmountSpentGauge'][tier]).to.be.instanceOf(Gauge);
});
});

Expand Down Expand Up @@ -176,7 +176,7 @@ describe('HBAR Rate Limit Service', function () {

describe('shouldLimit', function () {
describe('based on ethAddress', async function () {
it('should return true if the total daily budget is exceeded', async function () {
it('should return true if the total budget is exceeded', async function () {
// @ts-ignore
hbarLimitService.remainingBudget = 0;
const result = await hbarLimitService.shouldLimit(
Expand Down Expand Up @@ -367,7 +367,7 @@ describe('HBAR Rate Limit Service', function () {
});

describe('based on ipAddress', async function () {
it('should return true if the total daily budget is exceeded', async function () {
it('should return true if the total budget is exceeded', async function () {
// @ts-ignore
hbarLimitService.remainingBudget = 0;
const result = await hbarLimitService.shouldLimit(mode, methodName, txConstructorName, '', requestDetails);
Expand Down Expand Up @@ -686,17 +686,17 @@ describe('HBAR Rate Limit Service', function () {
spendingHistory: [{ amount: expense, timestamp: new Date() }],
},
]);
const incDailyUniqueSpendingPlansCounterSpy = sinon.spy(
hbarLimitService['dailyUniqueSpendingPlansCounter'][SubscriptionTier.BASIC],
const incUniqueSpendingPlansCounterSpy = sinon.spy(
hbarLimitService['uniqueSpendingPlansCounter'][SubscriptionTier.BASIC],
'inc',
);
const setAverageDailySpendingPlanUsagesGaugeSpy = sinon.spy(
hbarLimitService['averageDailySpendingPlanUsagesGauge'][SubscriptionTier.BASIC],
const setAverageSpendingPlanAmountSpentGaugeSpy = sinon.spy(
hbarLimitService['averageSpendingPlanAmountSpentGauge'][SubscriptionTier.BASIC],
'set',
);
const updateAverageDailyUsagePerSubscriptionTierSpy = sinon.spy(
const updateAverageAmountSpentPerSubscriptionTierSpy = sinon.spy(
hbarLimitService,
<any>'updateAverageDailyUsagePerSubscriptionTier',
'updateAverageAmountSpentPerSubscriptionTier' as any,
);

await hbarLimitService.addExpense(expense, ethAddress, requestDetails);
Expand All @@ -708,10 +708,10 @@ describe('HBAR Rate Limit Service', function () {
expect((await hbarLimitService['hbarLimitRemainingGauge'].get()).values[0].value).to.equal(
hbarLimitService['totalBudget'].toTinybars().sub(expense).toNumber(),
);
await Promise.all(updateAverageDailyUsagePerSubscriptionTierSpy.returnValues);
await Promise.all(updateAverageAmountSpentPerSubscriptionTierSpy.returnValues);
const expectedAverageUsage = Math.round((otherPlanOfTheSameTier.amountSpent + expense) / 2);
sinon.assert.calledOnceWithExactly(setAverageDailySpendingPlanUsagesGaugeSpy, expectedAverageUsage);
sinon.assert.calledOnceWithExactly(incDailyUniqueSpendingPlansCounterSpy, 1);
sinon.assert.calledOnceWithExactly(setAverageSpendingPlanAmountSpentGaugeSpy, expectedAverageUsage);
sinon.assert.calledOnceWithExactly(incUniqueSpendingPlansCounterSpy, 1);
};

it('should create a basic spending plan if none exists', async function () {
Expand Down Expand Up @@ -754,39 +754,39 @@ describe('HBAR Rate Limit Service', function () {
});
});

describe('isDailyBudgetExceeded', function () {
const testIsDailyBudgetExceeded = async (remainingBudget: number, expected: boolean) => {
describe('isTotalBudgetExceeded', function () {
const testIsTotalBudgetExceeded = async (remainingBudget: number, expected: boolean) => {
// @ts-ignore
hbarLimitService.remainingBudget = Hbar.fromTinybars(remainingBudget);
await expect(
hbarLimitService['isDailyBudgetExceeded'](mode, methodName, txConstructorName, 0, requestDetails),
hbarLimitService['isTotalBudgetExceeded'](mode, methodName, undefined, requestDetails),
).to.eventually.equal(expected);
};

it('should return true when the remaining budget is zero', async function () {
await testIsDailyBudgetExceeded(0, true);
await testIsTotalBudgetExceeded(0, true);
});

it('should return true when the remaining budget is negative', async function () {
await testIsDailyBudgetExceeded(-1, true);
await testIsTotalBudgetExceeded(-1, true);
});

it('should return false when the remaining budget is greater than zero', async function () {
await testIsDailyBudgetExceeded(100, false);
await testIsTotalBudgetExceeded(100, false);
});

it('should update the hbar limit counter when a method is called and the daily budget is exceeded', async function () {
it('should update the hbar limit counter when a method is called and the total budget is exceeded', async function () {
// @ts-ignore
const hbarLimitCounterSpy = sinon.spy(hbarLimitService.hbarLimitCounter, <any>'inc');
await testIsDailyBudgetExceeded(0, true);
await testIsTotalBudgetExceeded(0, true);
expect(hbarLimitCounterSpy.calledWithMatch({ mode, methodName }, 1)).to.be.true;
});

it('should reset the limiter when the reset date is reached', async function () {
// @ts-ignore
hbarLimitService.reset = new Date();
const resetLimiterSpy = sinon.spy(hbarLimitService, 'resetLimiter');
await testIsDailyBudgetExceeded(0, false);
await testIsTotalBudgetExceeded(0, false);
expect(resetLimiterSpy.calledOnce).to.be.true;
});
});
Expand Down

0 comments on commit a05fe80

Please sign in to comment.