Skip to content

Commit 1dc6552

Browse files
ebadierequiet-nodeNana-EC
authored
fix: Added HBar rate limiter to createFile (#2634)
* fix: Added HBar rate limiter to createFile Signed-off-by: ebadiere <ebadiere@gmail.com> * fix: Build fix and applied feedback. Signed-off-by: ebadiere <ebadiere@gmail.com> * fix: removed duplicated query & added return statement Signed-off-by: Logan Nguyen <logan.nguyen@swirldslabs.com> * Update packages/relay/src/lib/clients/sdkClient.ts Co-authored-by: Nana Essilfie-Conduah <56320167+Nana-EC@users.noreply.github.com> Signed-off-by: Eric Badiere <ebadiere@gmail.com> * fix: removed unused variable Signed-off-by: Logan Nguyen <logan.nguyen@swirldslabs.com> * fix: Applied feedback Signed-off-by: ebadiere <ebadiere@gmail.com> * fix: Fixed scope. Signed-off-by: ebadiere <ebadiere@gmail.com> --------- Signed-off-by: ebadiere <ebadiere@gmail.com> Signed-off-by: Logan Nguyen <logan.nguyen@swirldslabs.com> Signed-off-by: Eric Badiere <ebadiere@gmail.com> Co-authored-by: Logan Nguyen <logan.nguyen@swirldslabs.com> Co-authored-by: Nana Essilfie-Conduah <56320167+Nana-EC@users.noreply.github.com>
1 parent c7e4099 commit 1dc6552

File tree

3 files changed

+1939
-40
lines changed

3 files changed

+1939
-40
lines changed

packages/relay/src/lib/clients/sdkClient.ts

Lines changed: 126 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ export class SDKClient {
243243
async submitEthereumTransaction(
244244
transactionBuffer: Uint8Array,
245245
callerName: string,
246-
requestId?: string,
246+
requestId: string,
247247
): Promise<{ txResponse: TransactionResponse; fileId: FileId | null }> {
248248
const ethereumTransactionData: EthereumTransactionData = EthereumTransactionData.fromBytes(transactionBuffer);
249249
const ethereumTransaction = new EthereumTransaction();
@@ -476,6 +476,7 @@ export class SDKClient {
476476

477477
this.logger.info(`${requestIdPrefix} Execute ${transactionType} transaction`);
478478
const resp = await transaction.execute(this.clientMain);
479+
479480
this.logger.info(
480481
`${requestIdPrefix} ${resp.transactionId} ${callerName} ${transactionType} status: ${Status.Success} (${Status.Success._code})`,
481482
);
@@ -643,60 +644,146 @@ export class SDKClient {
643644
private createFile = async (
644645
callData: Uint8Array,
645646
client: Client,
646-
requestId?: string,
647-
callerName?: string,
648-
interactingEntity?: string,
647+
requestId: string,
648+
callerName: string,
649+
interactingEntity: string,
649650
) => {
650651
const requestIdPrefix = formatRequestIdMessage(requestId);
651652
const hexedCallData = Buffer.from(callData).toString('hex');
653+
const currentDateNow = Date.now();
654+
let fileCreateTx, fileAppendTx;
652655

653-
const fileCreateTx = await new FileCreateTransaction()
654-
.setContents(hexedCallData.substring(0, this.fileAppendChunkSize))
655-
.setKeys(client.operatorPublicKey ? [client.operatorPublicKey] : []);
656-
const fileCreateTxResponse = await fileCreateTx.execute(client);
657-
const { fileId } = await fileCreateTxResponse.getReceipt(client);
658-
659-
const createFileRecord = await fileCreateTxResponse.getRecord(this.clientMain);
660-
this.captureMetrics(
661-
SDKClient.transactionMode,
662-
fileCreateTx.constructor.name,
663-
Status.Success,
664-
createFileRecord.transactionFee.toTinybars().toNumber(),
665-
createFileRecord?.contractFunctionResult?.gasUsed,
666-
callerName,
667-
interactingEntity,
668-
);
656+
try {
657+
const shouldLimit = this.hbarLimiter.shouldLimit(currentDateNow, SDKClient.transactionMode, callerName);
658+
if (shouldLimit) {
659+
throw predefined.HBAR_RATE_LIMIT_EXCEEDED;
660+
}
669661

670-
if (fileId && callData.length > this.fileAppendChunkSize) {
671-
const fileAppendTx = await new FileAppendTransaction()
672-
.setFileId(fileId)
673-
.setContents(hexedCallData.substring(this.fileAppendChunkSize, hexedCallData.length))
674-
.setChunkSize(this.fileAppendChunkSize)
675-
.setMaxChunks(this.maxChunks);
676-
await fileAppendTx.execute(client);
662+
fileCreateTx = await new FileCreateTransaction()
663+
.setContents(hexedCallData.substring(0, this.fileAppendChunkSize))
664+
.setKeys(client.operatorPublicKey ? [client.operatorPublicKey] : []);
665+
666+
const fileCreateTxResponse = await fileCreateTx.execute(client);
667+
const { fileId } = await fileCreateTxResponse.getReceipt(client);
668+
669+
// get transaction fee and add expense to limiter
670+
const createFileRecord = await fileCreateTxResponse.getRecord(this.clientMain);
671+
let transactionFee = createFileRecord.transactionFee;
672+
this.hbarLimiter.addExpense(transactionFee.toTinybars().toNumber(), currentDateNow);
677673

678674
this.captureMetrics(
679675
SDKClient.transactionMode,
680-
fileAppendTx.constructor.name,
676+
fileCreateTx.constructor.name,
681677
Status.Success,
682-
await this.calculateFileAppendTxTotalTinybarsCost(fileAppendTx),
683-
0,
678+
createFileRecord.transactionFee.toTinybars().toNumber(),
679+
createFileRecord?.contractFunctionResult?.gasUsed,
684680
callerName,
685681
interactingEntity,
686682
);
687-
}
688683

689-
// Ensure that the calldata file is not empty
690-
if (fileId) {
691-
const fileSize = await (await new FileInfoQuery().setFileId(fileId).execute(client)).size;
684+
if (fileId && callData.length > this.fileAppendChunkSize) {
685+
fileAppendTx = await new FileAppendTransaction()
686+
.setFileId(fileId)
687+
.setContents(hexedCallData.substring(this.fileAppendChunkSize, hexedCallData.length))
688+
.setChunkSize(this.fileAppendChunkSize)
689+
.setMaxChunks(this.maxChunks);
690+
const fileAppendTxResponse = await fileAppendTx.execute(client);
691+
692+
// get transaction fee and add expense to limiter
693+
const appendFileRecord = await fileAppendTxResponse.getRecord(this.clientMain);
694+
transactionFee = appendFileRecord.transactionFee;
695+
this.hbarLimiter.addExpense(transactionFee.toTinybars().toNumber(), currentDateNow);
696+
697+
this.captureMetrics(
698+
SDKClient.transactionMode,
699+
fileAppendTx.constructor.name,
700+
Status.Success,
701+
await this.calculateFileAppendTxTotalTinybarsCost(fileAppendTx),
702+
0,
703+
callerName,
704+
interactingEntity,
705+
);
706+
}
707+
708+
// Ensure that the calldata file is not empty
709+
if (fileId) {
710+
const fileSize = await (await new FileInfoQuery().setFileId(fileId).execute(client)).size;
692711

693-
if (callData.length > 0 && fileSize.isZero()) {
694-
throw new SDKClientError({}, `${requestIdPrefix} Created file is empty. `);
712+
if (callData.length > 0 && fileSize.isZero()) {
713+
throw new SDKClientError({}, `${requestIdPrefix} Created file is empty. `);
714+
}
715+
this.logger.trace(`${requestIdPrefix} Created file with fileId: ${fileId} and file size ${fileSize}`);
716+
}
717+
718+
return fileId;
719+
} catch (error: any) {
720+
const sdkClientError = new SDKClientError(error, error.message);
721+
let transactionFee: number | Hbar = 0;
722+
723+
// if valid network error utilize transaction id
724+
if (sdkClientError.isValidNetworkError()) {
725+
try {
726+
const transactionCreateRecord = await new TransactionRecordQuery()
727+
.setTransactionId(fileCreateTx.transactionId!)
728+
.setNodeAccountIds(fileCreateTx.nodeAccountIds!)
729+
.setValidateReceiptStatus(false)
730+
.execute(this.clientMain);
731+
transactionFee = transactionCreateRecord.transactionFee;
732+
this.hbarLimiter.addExpense(transactionFee.toTinybars().toNumber(), currentDateNow);
733+
734+
this.captureMetrics(
735+
SDKClient.transactionMode,
736+
fileCreateTx.constructor.name,
737+
sdkClientError.status,
738+
transactionFee.toTinybars().toNumber(),
739+
transactionCreateRecord?.contractFunctionResult?.gasUsed,
740+
callerName,
741+
interactingEntity,
742+
);
743+
744+
this.logger.info(
745+
`${requestIdPrefix} ${fileCreateTx.transactionId} ${callerName} ${fileCreateTx.constructor.name} status: ${sdkClientError.status} (${sdkClientError.status._code}), cost: ${transactionFee}`,
746+
);
747+
748+
if (fileAppendTx) {
749+
const transactionAppendRecord = await new TransactionRecordQuery()
750+
.setTransactionId(fileAppendTx.transactionId!)
751+
.setNodeAccountIds(fileAppendTx.nodeAccountIds!)
752+
.setValidateReceiptStatus(false)
753+
.execute(this.clientMain);
754+
transactionFee = transactionAppendRecord.transactionFee;
755+
this.hbarLimiter.addExpense(transactionFee.toTinybars().toNumber(), currentDateNow);
756+
757+
this.captureMetrics(
758+
SDKClient.transactionMode,
759+
fileCreateTx.constructor.name,
760+
sdkClientError.status,
761+
transactionFee.toTinybars().toNumber(),
762+
transactionCreateRecord?.contractFunctionResult?.gasUsed,
763+
callerName,
764+
interactingEntity,
765+
);
766+
767+
this.logger.info(
768+
`${requestIdPrefix} ${fileAppendTx.transactionId} ${callerName} ${fileCreateTx.constructor.name} status: ${sdkClientError.status} (${sdkClientError.status._code}), cost: ${transactionFee}`,
769+
);
770+
}
771+
} catch (err: any) {
772+
const recordQueryError = new SDKClientError(err, err.message);
773+
this.logger.error(
774+
recordQueryError,
775+
`${requestIdPrefix} Error raised during TransactionRecordQuery for ${fileCreateTx.transactionId}`,
776+
);
777+
}
695778
}
696-
this.logger.trace(`${requestIdPrefix} Created file with fileId: ${fileId} and file size ${fileSize}`);
697-
}
698779

699-
return fileId;
780+
this.logger.info(`${requestIdPrefix} HBAR_RATE_LIMIT_EXCEEDED cost: ${transactionFee}`);
781+
782+
if (error instanceof JsonRpcError) {
783+
throw predefined.HBAR_RATE_LIMIT_EXCEEDED;
784+
}
785+
throw sdkClientError;
786+
}
700787
};
701788

702789
/**

packages/relay/src/lib/eth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1412,7 +1412,7 @@ export class EthImpl implements Eth {
14121412
*
14131413
* @param transaction
14141414
*/
1415-
async sendRawTransaction(transaction: string, requestIdPrefix?: string): Promise<string | JsonRpcError> {
1415+
async sendRawTransaction(transaction: string, requestIdPrefix: string): Promise<string | JsonRpcError> {
14161416
if (transaction?.length >= constants.FUNCTION_SELECTOR_CHAR_LENGTH)
14171417
this.ethExecutionsCounter
14181418
.labels(EthImpl.ethSendRawTransaction, transaction.substring(0, constants.FUNCTION_SELECTOR_CHAR_LENGTH))

0 commit comments

Comments
 (0)