Skip to content

Commit

Permalink
fix: Hbar usage tracking when transaction is larger than FILE_APPEND_…
Browse files Browse the repository at this point in the history
…CHUNK_SIZE (#2698)

* fix: use executeAll for fileAppend

Signed-off-by: Ivo Yankov <ivo@devlabs.bg>

* chore: code cleanup

Signed-off-by: Ivo Yankov <ivo@devlabs.bg>

* chore: fix typo

Signed-off-by: Ivo Yankov <ivo@devlabs.bg>

* nit: code smell

Signed-off-by: Ivo Yankov <ivo@devlabs.bg>

* fix: set lower hbar limit for acceptance tests

Signed-off-by: Ivo Yankov <ivo@devlabs.bg>

* fix: addressing comments

Signed-off-by: Ivo Yankov <ivo@devlabs.bg>

* fix: set operator key

Signed-off-by: Ivo Yankov <ivo@devlabs.bg>

* test: check if switching operator in relay restart breaks sdkClient

Signed-off-by: Ivo Yankov <ivo@devlabs.bg>

* chore: remove .only

Signed-off-by: Ivo Yankov <ivo@devlabs.bg>

* fix: change operator for all tests

Signed-off-by: Ivo Yankov <ivo@devlabs.bg>

* chore: reverted an unwanted change

Signed-off-by: Ivo Yankov <ivo@devlabs.bg>

---------

Signed-off-by: Ivo Yankov <ivo@devlabs.bg>
  • Loading branch information
Ivo-Yankov authored and quiet-node committed Jul 18, 2024
1 parent 8f380a2 commit 94efb89
Show file tree
Hide file tree
Showing 8 changed files with 243 additions and 102 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"acceptancetest:api_batch2": "ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@api-batch-2' --exit",
"acceptancetest:api_batch3": "ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@api-batch-3' --exit",
"acceptancetest:erc20": "ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@erc20' --exit",
"acceptancetest:ratelimiter": "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:ratelimiter": "ts-mocha packages/ws-server/tests/acceptance/index.spec.ts -g '@web-socket-ratelimiter' --exit && HBAR_RATE_LIMIT_TINYBAR=3000000000 ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@ratelimiter' --exit",
"acceptancetest:tokencreate": "ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@tokencreate' --exit",
"acceptancetest:tokenmanagement": "ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@tokenmanagement' --exit",
"acceptancetest:htsprecompilev1": "ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@htsprecompilev1' --exit",
Expand Down
45 changes: 18 additions & 27 deletions packages/relay/src/lib/clients/sdkClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -630,17 +630,6 @@ export class SDKClient {
return balance.hbars.to(HbarUnit.Tinybar).multipliedBy(constants.TINYBAR_TO_WEIBAR_COEF);
}

private async calculateFileAppendTxTotalTinybarsCost(fileAppendTx: FileAppendTransaction): Promise<number> {
// @ts-ignore
const fileAppendTxs = fileAppendTx._transactionIds.list.map((txId) =>
new TransactionRecordQuery().setTransactionId(txId).execute(this.clientMain),
);

return (await Promise.all(fileAppendTxs)).reduce((base, record) => {
return base + record.transactionFee.toTinybars().toNumber();
}, 0);
}

private createFile = async (
callData: Uint8Array,
client: Client,
Expand Down Expand Up @@ -687,22 +676,24 @@ export class SDKClient {
.setContents(hexedCallData.substring(this.fileAppendChunkSize, hexedCallData.length))
.setChunkSize(this.fileAppendChunkSize)
.setMaxChunks(this.maxChunks);
const fileAppendTxResponse = await fileAppendTx.execute(client);

// get transaction fee and add expense to limiter
const appendFileRecord = await fileAppendTxResponse.getRecord(this.clientMain);
transactionFee = appendFileRecord.transactionFee;
this.hbarLimiter.addExpense(transactionFee.toTinybars().toNumber(), currentDateNow);

this.captureMetrics(
SDKClient.transactionMode,
fileAppendTx.constructor.name,
Status.Success,
await this.calculateFileAppendTxTotalTinybarsCost(fileAppendTx),
0,
callerName,
interactingEntity,
);
const fileAppendTxResponses = await fileAppendTx.executeAll(client);

for (let fileAppendTxResponse of fileAppendTxResponses) {
// get transaction fee and add expense to limiter
const appendFileRecord = await fileAppendTxResponse.getRecord(this.clientMain);
const tinybarsCost = appendFileRecord.transactionFee.toTinybars().toNumber();

this.captureMetrics(
SDKClient.transactionMode,
fileAppendTx.constructor.name,
Status.Success,
tinybarsCost,
0,
callerName,
interactingEntity,
);
this.hbarLimiter.addExpense(tinybarsCost, currentDateNow);
}
}

// Ensure that the calldata file is not empty
Expand Down
33 changes: 24 additions & 9 deletions packages/server/tests/acceptance/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import fs from 'fs';
import ServicesClient from '../clients/servicesClient';
import MirrorClient from '../clients/mirrorClient';
import RelayClient from '../clients/relayClient';
import MetricsClient from '../clients/metricsClient';

// Server related
import app from '../../dist/server';
Expand Down Expand Up @@ -86,12 +87,22 @@ describe('RPC Server Acceptance Tests', function () {
logger.child({ name: `services-test-client` }),
);
global.mirrorNode = new MirrorClient(MIRROR_NODE_URL, logger.child({ name: `mirror-node-test-client` }));
global.metrics = new MetricsClient(RELAY_URL, logger.child({ name: `metrics-test-client` }));
global.relay = new RelayClient(RELAY_URL, logger.child({ name: `relay-test-client` }));
global.relayServer = relayServer;
global.socketServer = socketServer;
global.logger = logger;
global.initialBalance = INITIAL_BALANCE;

global.restartLocalRelay = async function () {
if (global.relayIsLocal) {
stopRelay();
await new Promise((r) => setTimeout(r, 5000)); // wait for server to shutdown

runLocalRelay();
}
};

before(async () => {
// configuration details
logger.info('Acceptance Tests Configurations successfully loaded');
Expand Down Expand Up @@ -152,15 +163,7 @@ describe('RPC Server Acceptance Tests', function () {
const cost = startOperatorBalance.toTinybars().subtract(endOperatorBalance.toTinybars());
logger.info(`Acceptance Tests spent ${Hbar.fromTinybars(cost)}`);

//stop relay
logger.info('Stop relay');
if (relayServer !== undefined) {
relayServer.close();
}

if (process.env.TEST_WS_SERVER === 'true' && socketServer !== undefined) {
socketServer.close();
}
stopRelay();
});

describe('Acceptance tests', async () => {
Expand All @@ -181,6 +184,18 @@ describe('RPC Server Acceptance Tests', function () {
}
}

function stopRelay() {
//stop relay
logger.info('Stop relay');
if (relayServer !== undefined) {
relayServer.close();
}

if (process.env.TEST_WS_SERVER === 'true' && global.socketServer !== undefined) {
global.socketServer.close();
}
}

function runLocalRelay() {
// start local relay, relay instance in local should not be running

Expand Down
175 changes: 113 additions & 62 deletions packages/server/tests/acceptance/rateLimiter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,17 @@ import relayConstants from '../../../../packages/relay/src/lib/constants';

// Local resources
import parentContractJson from '../contracts/Parent.json';
import largeContractJson from '../contracts/EstimatePrecompileContract.json';
import { Utils } from '../helpers/utils';
import { predefined } from '@hashgraph/json-rpc-relay';

describe('@ratelimiter Rate Limiters Acceptance Tests', function () {
this.timeout(480 * 1000); // 480 seconds

const accounts: AliasAccount[] = [];

// @ts-ignore
const { mirrorNode, relay, logger, initialBalance } = global;
const { mirrorNode, relay, logger, initialBalance, metrics } = global;

// cached entities
let parentContractAddress: string;
Expand Down Expand Up @@ -90,72 +92,121 @@ describe('@ratelimiter Rate Limiters Acceptance Tests', function () {
});
});

describe('HBAR Limiter Acceptance Tests', function () {
this.timeout(480 * 1000); // 480 seconds

this.beforeAll(async () => {
requestId = Utils.generateRequestId();
const requestIdPrefix = Utils.formatRequestIdMessage(requestId);

logger.info(`${requestIdPrefix} Creating accounts`);
logger.info(`${requestIdPrefix} HBAR_RATE_LIMIT_TINYBAR: ${process.env.HBAR_RATE_LIMIT_TINYBAR}`);

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

const neededAccounts: number = 2;
accounts.push(
...(await Utils.createMultipleAliasAccounts(
mirrorNode,
initialAccount,
neededAccounts,
initialBalance,
requestId,
)),
);
global.accounts.push(...accounts);

const parentContract = await Utils.deployContract(
parentContractJson.abi,
parentContractJson.bytecode,
accounts[0].wallet,
);

parentContractAddress = parentContract.target as string;
global.logger.trace(`${requestIdPrefix} Deploy parent contract on address ${parentContractAddress}`);
});

this.beforeEach(async () => {
requestId = Utils.generateRequestId();
});
// The following tests exhaust the hbar limit, so they should only be run against a local relay
if (global.relayIsLocal) {
describe('HBAR Limiter Acceptance Tests', function () {
before(async () => {
// Restart the relay to reset the limits
await global.restartLocalRelay();
});

describe('HBAR Rate Limit Tests', () => {
const defaultGasPrice = Assertions.defaultGasPrice;
const defaultGasLimit = 3_000_000;

const defaultLondonTransactionData = {
value: ONE_TINYBAR,
chainId: Number(CHAIN_ID),
maxPriorityFeePerGas: defaultGasPrice,
maxFeePerGas: defaultGasPrice,
gasLimit: defaultGasLimit,
type: 2,
};
this.timeout(480 * 1000); // 480 seconds

this.beforeAll(async () => {
requestId = Utils.generateRequestId();
const requestIdPrefix = Utils.formatRequestIdMessage(requestId);

logger.info(`${requestIdPrefix} Creating accounts`);
logger.info(`${requestIdPrefix} HBAR_RATE_LIMIT_TINYBAR: ${process.env.HBAR_RATE_LIMIT_TINYBAR}`);

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

const neededAccounts: number = 2;
accounts.push(
...(await Utils.createMultipleAliasAccounts(
mirrorNode,
initialAccount,
neededAccounts,
initialBalance,
requestId,
)),
);
global.accounts.push(...accounts);

const parentContract = await Utils.deployContract(
parentContractJson.abi,
parentContractJson.bytecode,
accounts[0].wallet,
);

parentContractAddress = parentContract.target as string;
global.logger.trace(`${requestIdPrefix} Deploy parent contract on address ${parentContractAddress}`);
});

it('should execute "eth_sendRawTransaction" without triggering HBAR rate limit exceeded ', async function () {
const gasPrice = await relay.gasPrice(requestId);
this.beforeEach(async () => {
requestId = Utils.generateRequestId();
});

const transaction = {
...defaultLondonTransactionData,
to: parentContractAddress,
nonce: await relay.getAccountNonce(accounts[1].address, requestId),
maxPriorityFeePerGas: gasPrice,
maxFeePerGas: gasPrice,
describe('HBAR Rate Limit Tests', () => {
const defaultGasPrice = Assertions.defaultGasPrice;
const defaultGasLimit = 3_000_000;

const defaultLondonTransactionData = {
value: ONE_TINYBAR,
chainId: Number(CHAIN_ID),
maxPriorityFeePerGas: defaultGasPrice,
maxFeePerGas: defaultGasPrice,
gasLimit: defaultGasLimit,
type: 2,
};
const signedTx = await accounts[1].wallet.signTransaction(transaction);

await expect(relay.call(testConstants.ETH_ENDPOINTS.ETH_SEND_RAW_TRANSACTION, [signedTx], requestId)).to.be
.fulfilled;
it('should execute "eth_sendRawTransaction" without triggering HBAR rate limit exceeded', async function () {
const gasPrice = await relay.gasPrice(requestId);
const remainingHbarsBefore = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT));

const transaction = {
...defaultLondonTransactionData,
to: parentContractAddress,
nonce: await relay.getAccountNonce(accounts[1].address, requestId),
maxPriorityFeePerGas: gasPrice,
maxFeePerGas: gasPrice,
};
const signedTx = await accounts[1].wallet.signTransaction(transaction);

await expect(relay.call(testConstants.ETH_ENDPOINTS.ETH_SEND_RAW_TRANSACTION, [signedTx], requestId)).to.be
.fulfilled;
const remainingHbarsAfter = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT));
expect(remainingHbarsAfter).to.be.eq(remainingHbarsBefore);
});

it('should deploy a large contract and decrease remaining HBAR in limiter when transaction data is large', async function () {
const remainingHbarsBefore = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT));
expect(remainingHbarsBefore).to.be.gt(0);

const largeContract = await Utils.deployContract(
largeContractJson.abi,
largeContractJson.bytecode,
accounts[0].wallet,
);
await largeContract.waitForDeployment();
const remainingHbarsAfter = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT));
expect(largeContract.target).to.not.be.null;
expect(remainingHbarsAfter).to.be.lt(remainingHbarsBefore);
});

it('multiple deployments of large contracts should eventually exhaust the remaining hbar limit', async function () {
const remainingHbarsBefore = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT));
expect(remainingHbarsBefore).to.be.gt(0);
try {
for (let i = 0; i < 50; i++) {
const largeContract = await Utils.deployContract(
largeContractJson.abi,
largeContractJson.bytecode,
accounts[0].wallet,
);
await largeContract.waitForDeployment();
expect(largeContract.target).to.not.be.null;
}

expect(true).to.be.false;
} catch (e: any) {
expect(e.message).to.contain(predefined.HBAR_RATE_LIMIT_EXCEEDED.message);
}

const remainingHbarsAfter = Number(await metrics.get(testConstants.METRICS.REMAINING_HBAR_LIMIT));
expect(remainingHbarsAfter).to.be.lte(0);
});
});
});
});
}
});
Loading

0 comments on commit 94efb89

Please sign in to comment.