diff --git a/packages/relay/src/lib/clients/sdkClient.ts b/packages/relay/src/lib/clients/sdkClient.ts index e2d016bb0e..eec0e1a398 100644 --- a/packages/relay/src/lib/clients/sdkClient.ts +++ b/packages/relay/src/lib/clients/sdkClient.ts @@ -257,7 +257,7 @@ export class SDKClient { fileId = await this.createFile( ethereumTransactionData.callData, this.clientMain, - requestId, + requestId ?? 'create-file-from-submit-ethereum-transaction', callerName, interactingEntity, ); @@ -643,60 +643,111 @@ export class SDKClient { private createFile = async ( callData: Uint8Array, client: Client, - requestId?: string, - callerName?: string, - interactingEntity?: string, + requestId: string, + callerName: string, + interactingEntity: string, ) => { const requestIdPrefix = formatRequestIdMessage(requestId); const hexedCallData = Buffer.from(callData).toString('hex'); + const currentDateNow = Date.now(); + let createFileId; const fileCreateTx = await new FileCreateTransaction() .setContents(hexedCallData.substring(0, this.fileAppendChunkSize)) .setKeys(client.operatorPublicKey ? [client.operatorPublicKey] : []); - const fileCreateTxResponse = await fileCreateTx.execute(client); - const { fileId } = await fileCreateTxResponse.getReceipt(client); - - const createFileRecord = await fileCreateTxResponse.getRecord(this.clientMain); - this.captureMetrics( - SDKClient.transactionMode, - fileCreateTx.constructor.name, - Status.Success, - createFileRecord.transactionFee.toTinybars().toNumber(), - createFileRecord?.contractFunctionResult?.gasUsed, - callerName, - interactingEntity, - ); - if (fileId && callData.length > this.fileAppendChunkSize) { - const fileAppendTx = await new FileAppendTransaction() - .setFileId(fileId) - .setContents(hexedCallData.substring(this.fileAppendChunkSize, hexedCallData.length)) - .setChunkSize(this.fileAppendChunkSize) - .setMaxChunks(this.maxChunks); - await fileAppendTx.execute(client); + try { + const shouldLimit = this.hbarLimiter.shouldLimit(currentDateNow, SDKClient.transactionMode, callerName); + if (shouldLimit) { + throw predefined.HBAR_RATE_LIMIT_EXCEEDED; + } + const fileCreateTxResponse = await fileCreateTx.execute(client); + const { fileId } = await fileCreateTxResponse.getReceipt(client); + createFileId = fileId; + const createFileRecord = await fileCreateTxResponse.getRecord(this.clientMain); this.captureMetrics( SDKClient.transactionMode, - fileAppendTx.constructor.name, + fileCreateTx.constructor.name, Status.Success, - await this.calculateFileAppendTxTotalTinybarsCost(fileAppendTx), - 0, + createFileRecord.transactionFee.toTinybars().toNumber(), + createFileRecord?.contractFunctionResult?.gasUsed, callerName, interactingEntity, ); - } - // Ensure that the calldata file is not empty - if (fileId) { - const fileSize = await (await new FileInfoQuery().setFileId(fileId).execute(client)).size; + if (fileId && callData.length > this.fileAppendChunkSize) { + const fileAppendTx = await new FileAppendTransaction() + .setFileId(fileId) + .setContents(hexedCallData.substring(this.fileAppendChunkSize, hexedCallData.length)) + .setChunkSize(this.fileAppendChunkSize) + .setMaxChunks(this.maxChunks); + await fileAppendTx.execute(client); + + this.captureMetrics( + SDKClient.transactionMode, + fileAppendTx.constructor.name, + Status.Success, + await this.calculateFileAppendTxTotalTinybarsCost(fileAppendTx), + 0, + callerName, + interactingEntity, + ); + } + + // Ensure that the calldata file is not empty + if (fileId) { + const fileSize = await (await new FileInfoQuery().setFileId(fileId).execute(client)).size; + + if (callData.length > 0 && fileSize.isZero()) { + throw new SDKClientError({}, `${requestIdPrefix} Created file is empty. `); + } + this.logger.trace(`${requestIdPrefix} Created file with fileId: ${fileId} and file size ${fileSize}`); + } + } catch (error: any) { + const sdkClientError = new SDKClientError(error, error.message); + let transactionFee: number | Hbar = 0; + + // if valid network error utilize transaction id + if (sdkClientError.isValidNetworkError()) { + try { + const transactionRecord = await new TransactionRecordQuery() + .setTransactionId(fileCreateTx.transactionId!) + .setNodeAccountIds(fileCreateTx.nodeAccountIds!) + .setValidateReceiptStatus(false) + .execute(this.clientMain); + transactionFee = transactionRecord.transactionFee; + + this.captureMetrics( + SDKClient.transactionMode, + fileCreateTx.constructor.name, + sdkClientError.status, + transactionFee.toTinybars().toNumber(), + transactionRecord?.contractFunctionResult?.gasUsed, + callerName, + interactingEntity, + ); + + this.hbarLimiter.addExpense(transactionFee.toTinybars().toNumber(), currentDateNow); + } catch (err: any) { + const recordQueryError = new SDKClientError(err, err.message); + this.logger.error( + recordQueryError, + `${requestIdPrefix} Error raised during TransactionRecordQuery for ${fileCreateTx.transactionId}`, + ); + } + } - if (callData.length > 0 && fileSize.isZero()) { - throw new SDKClientError({}, `${requestIdPrefix} Created file is empty. `); + this.logger.trace( + `${requestIdPrefix} ${fileCreateTx.transactionId} ${callerName} ${fileCreateTx.constructor.name} status: ${sdkClientError.status} (${sdkClientError.status._code}), cost: ${transactionFee}`, + ); + if (error instanceof JsonRpcError) { + throw predefined.HBAR_RATE_LIMIT_EXCEEDED; } - this.logger.trace(`${requestIdPrefix} Created file with fileId: ${fileId} and file size ${fileSize}`); + throw sdkClientError; } - return fileId; + return createFileId; }; /**