Skip to content

Commit 56903ba

Browse files
authored
feat: enabled fast return on eth_sendRawTransaction (#3273)
* chore: added USE_ASYNC_TX_PROCESSING feature flag Signed-off-by: Logan Nguyen <[email protected]> * dep: installed keccak package Signed-off-by: Logan Nguyen <[email protected]> * chore: added computeTransactionHash utils method Signed-off-by: Logan Nguyen <[email protected]> * feat: enabled fast return on eth_sendRawTransaction Signed-off-by: Logan Nguyen <[email protected]> * test: fixed eth_sendRawTransaction UTs Signed-off-by: Logan Nguyen <[email protected]> * chore: added USE_ASYNC_TX_PROCESSING to test.env and localAcceptance.env Signed-off-by: Logan Nguyen <[email protected]> * feat: added pollForValidTransactionReceipt Signed-off-by: Logan Nguyen <[email protected]> * fix: fixed batch2 Signed-off-by: Logan Nguyen <[email protected]> * chore: skipped some tests when useAsyncTxProcessing is on (TBD) Signed-off-by: Logan Nguyen <[email protected]> * fix: fixed batch2 Signed-off-by: Logan Nguyen <[email protected]> * fix: fixed batch3 Signed-off-by: Logan Nguyen <[email protected]> * fix: fixed hbarLimiter Signed-off-by: Logan Nguyen <[email protected]> * fix: fixed ws_batch2 Signed-off-by: Logan Nguyen <[email protected]> * fix: fixed conformity Signed-off-by: Logan Nguyen <[email protected]> * fix: fixed getLogs test in batch1 Signed-off-by: Logan Nguyen <[email protected]> * fix: fixed typo Signed-off-by: Logan Nguyen <[email protected]> * fix: fixed typo for sendRawTransactionProcessor method Signed-off-by: Logan Nguyen <[email protected]> --------- Signed-off-by: Logan Nguyen <[email protected]>
1 parent 7accf1f commit 56903ba

File tree

18 files changed

+647
-373
lines changed

18 files changed

+647
-373
lines changed

docs/configuration.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ The following table lists the available properties along with their default valu
5050
Unless you need to set a non-default value, it is recommended to only populate overridden properties in the custom `.env`.
5151

5252
| Name | Default | Description |
53-
|---------------------------------------------|---------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
53+
| ------------------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
5454
| `CACHE_MAX` | "1000" | The maximum number (or size) of items that remain in the cache (assuming no TTL pruning or explicit deletions). |
5555
| `CACHE_TTL` | "3_600_000" | Max time to live in ms, for items before they are considered stale. Default is one hour in milliseconds |
5656
| `CLIENT_TRANSPORT_SECURITY` | "false" | Flag to enable or disable TLS for both networks. |
@@ -100,6 +100,7 @@ Unless you need to set a non-default value, it is recommended to only populate o
100100
| `TIER_2_RATE_LIMIT` | "800" | Maximum moderate request count limit used for non expensive endpoints. |
101101
| `TIER_3_RATE_LIMIT` | "1600" | Maximum relaxed request count limit used for static return endpoints. |
102102
| `TX_DEFAULT_GAS` | "400000" | Default gas for transactions that do not specify gas. |
103+
| `USE_ASYNC_TX_PROCESSING` | "false" | Set to `true` to enable `eth_sendRawTransaction` to return the transaction hash immediately after passing all prechecks, while processing the transaction asynchronously in the background. |
103104
| `FILE_APPEND_MAX_CHUNKS` | "20" | Default maximum number of chunks for the `HAPI` `FileAppendTransaction` to use during contract creation submissions to consensus nodes as part of `eth_sendRawTransactionsaction`. |
104105
| `FILE_APPEND_CHUNK_SIZE=5120` | "5120" | Size in bytes of file chunks for the `HAPI` `FileAppendTransaction` to use during contract creation submissions to consensus nodes as part of `eth_sendRawTransactionsaction`. |
105106
| `FILTER_API_ENABLED` | "false" | Enables all filter related methods: `eth_newFilter`, `eth_uninstallFilter`, `eth_getFilterChanges`, `eth_getFilterLogs`, `eth_newBlockFilter` |

package-lock.json

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"acceptancetest:api_batch3": "nyc ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@api-batch-3' --exit",
5050
"acceptancetest:erc20": "npm_package_version=0.0.1 nyc ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@erc20' --exit",
5151
"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",
52-
"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",
52+
"acceptancetest:hbarlimiter_batch1": "HBAR_RATE_LIMIT_TINYBAR=7000000000 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",
5353
"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",
5454
"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",
5555
"acceptancetest:tokencreate": "nyc ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@tokencreate' --exit",

packages/config-service/src/services/globalConfig.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,12 @@ export class GlobalConfig {
652652
required: false,
653653
defaultValue: null,
654654
},
655+
USE_ASYNC_TX_PROCESSING: {
656+
envName: 'USE_ASYNC_TX_PROCESSING',
657+
type: 'boolean',
658+
required: false,
659+
defaultValue: false,
660+
},
655661
WEB_SOCKET_HTTP_PORT: {
656662
envName: 'WEB_SOCKET_HTTP_PORT',
657663
type: 'number',

packages/relay/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"author": "Hedera Smart Contracts Team",
1212
"devDependencies": {
1313
"@types/chai": "^4.3.0",
14+
"@types/keccak": "^3.0.5",
1415
"@types/mocha": "^9.1.0",
1516
"@types/node": "^17.0.14",
1617
"chai": "^4.3.6",
@@ -48,9 +49,9 @@
4849
"test:eth-get-logs": "nyc ts-mocha --recursive './tests/**/*.spec.ts' './tests/**/**/*.spec.ts' -g '@ethGetLogs' --exit"
4950
},
5051
"dependencies": {
51-
"@hashgraph/json-rpc-config-service": "file:../config-service",
5252
"@ethersproject/asm": "^5.7.0",
5353
"@hashgraph/sdk": "^2.54.0-beta.1",
54+
"@hashgraph/json-rpc-config-service": "file:../config-service",
5455
"@keyvhq/core": "^1.6.9",
5556
"axios": "^1.4.0",
5657
"axios-retry": "^3.5.1",

packages/relay/src/lib/eth.ts

Lines changed: 78 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ import { IContractCallRequest, IContractCallResponse, IFeeHistory, ITransactionR
6262
import { IAccountInfo } from './types/mirrorNode';
6363

6464
const _ = require('lodash');
65-
const createHash = require('keccak');
6665
const asm = require('@ethersproject/asm');
6766

6867
interface LatestBlockNumberTimestamp {
@@ -1550,43 +1549,42 @@ export class EthImpl implements Eth {
15501549

15511550
async parseRawTxAndPrecheck(
15521551
transaction: string,
1553-
requestDetails: RequestDetails,
15541552
networkGasPriceInWeiBars: number,
1553+
requestDetails: RequestDetails,
15551554
): Promise<EthersTransaction> {
1556-
let interactingEntity = '';
1557-
let originatingAddress = '';
1555+
const parsedTx = Precheck.parseTxIfNeeded(transaction);
15581556
try {
1559-
this.precheck.checkSize(transaction);
1560-
const parsedTx = Precheck.parseTxIfNeeded(transaction);
1561-
interactingEntity = parsedTx.to?.toString() || '';
1562-
originatingAddress = parsedTx.from?.toString() || '';
15631557
if (this.logger.isLevelEnabled('trace')) {
15641558
this.logger.trace(
1565-
`${requestDetails.formattedRequestId} sendRawTransaction(from=${originatingAddress}, to=${interactingEntity}, transaction=${transaction})`,
1559+
`${requestDetails.formattedRequestId} Transaction undergoing prechecks: transaction=${JSON.stringify(
1560+
parsedTx,
1561+
)}`,
15661562
);
15671563
}
15681564

1565+
this.precheck.checkSize(transaction);
15691566
await this.precheck.sendRawTransactionCheck(parsedTx, networkGasPriceInWeiBars, requestDetails);
15701567
return parsedTx;
15711568
} catch (e: any) {
1572-
this.logger.warn(
1573-
`${requestDetails.formattedRequestId} Error on precheck sendRawTransaction(from=${originatingAddress}, to=${interactingEntity}, transaction=${transaction})`,
1569+
this.logger.error(
1570+
`${requestDetails.formattedRequestId} Precheck failed: transaction=${JSON.stringify(parsedTx)}`,
15741571
);
15751572
throw this.common.genericErrorHandler(e);
15761573
}
15771574
}
15781575

15791576
async sendRawTransactionErrorHandler(
1580-
e,
1581-
transaction,
1582-
transactionBuffer,
1583-
txSubmitted,
1584-
parsedTx,
1577+
e: any,
1578+
transactionBuffer: Buffer,
1579+
txSubmitted: boolean,
1580+
parsedTx: EthersTransaction,
15851581
requestDetails: RequestDetails,
15861582
): Promise<string | JsonRpcError> {
15871583
this.logger.error(
15881584
e,
1589-
`${requestDetails.formattedRequestId} Failed to successfully submit sendRawTransaction for transaction ${transaction}`,
1585+
`${
1586+
requestDetails.formattedRequestId
1587+
} Failed to successfully submit sendRawTransaction: transaction=${JSON.stringify(parsedTx)}`,
15901588
);
15911589
if (e instanceof JsonRpcError) {
15921590
return e;
@@ -1638,24 +1636,39 @@ export class EthImpl implements Eth {
16381636

16391637
this.logger.error(
16401638
e,
1641-
`${requestDetails.formattedRequestId} Failed sendRawTransaction during record retrieval for transaction ${transaction}, returning computed hash`,
1639+
`${
1640+
requestDetails.formattedRequestId
1641+
} Failed sendRawTransaction during record retrieval for transaction, returning computed hash: transaction=${JSON.stringify(
1642+
parsedTx,
1643+
)}`,
16421644
);
16431645
//Return computed hash if unable to retrieve EthereumHash from record due to error
1644-
return prepend0x(createHash('keccak256').update(transactionBuffer).digest('hex'));
1646+
return Utils.computeTransactionHash(transactionBuffer);
16451647
}
16461648

16471649
/**
1648-
* Submits a transaction to the network for execution.
1650+
* Asynchronously processes a raw transaction by submitting it to the network, managing HFS, polling the MN, handling errors, and returning the transaction hash.
16491651
*
1650-
* @param {string} transaction The raw transaction to submit
1651-
* @param {RequestDetails} requestDetails The request details for logging and tracking
1652+
* @async
1653+
* @param {Buffer} transactionBuffer - The raw transaction data as a buffer.
1654+
* @param {EthersTransaction} parsedTx - The parsed Ethereum transaction object.
1655+
* @param {number} networkGasPriceInWeiBars - The current network gas price in wei bars.
1656+
* @param {RequestDetails} requestDetails - Details of the request for logging and tracking purposes.
1657+
* @returns {Promise<string | JsonRpcError>} A promise that resolves to the transaction hash if successful, or a JsonRpcError if an error occurs.
1658+
* @throws {JsonRpcError} If there's an error during transaction processing.
16521659
*/
1653-
async sendRawTransaction(transaction: string, requestDetails: RequestDetails): Promise<string | JsonRpcError> {
1660+
async sendRawTransactionProcessor(
1661+
transactionBuffer: Buffer,
1662+
parsedTx: EthersTransaction,
1663+
networkGasPriceInWeiBars: number,
1664+
requestDetails: RequestDetails,
1665+
): Promise<string | JsonRpcError> {
1666+
let fileId: FileId | null = null;
1667+
let txSubmitted = false;
1668+
let submittedTransactionId: string = '';
1669+
let sendRawTransactionError: any;
1670+
16541671
const requestIdPrefix = requestDetails.formattedRequestId;
1655-
const networkGasPriceInWeiBars = Utils.addPercentageBufferToGasPrice(
1656-
await this.getFeeWeibars(EthImpl.ethGasPrice, requestDetails),
1657-
);
1658-
const parsedTx = await this.parseRawTxAndPrecheck(transaction, requestDetails, networkGasPriceInWeiBars);
16591672
const originalCallerAddress = parsedTx.from?.toString() || '';
16601673
const toAddress = parsedTx.to?.toString() || '';
16611674

@@ -1668,13 +1681,6 @@ export class EthImpl implements Eth {
16681681
)
16691682
.inc();
16701683

1671-
const transactionBuffer = Buffer.from(EthImpl.prune0x(transaction), 'hex');
1672-
1673-
let fileId: FileId | null = null;
1674-
let txSubmitted = false;
1675-
let submittedTransactionId: string = '';
1676-
let sendRawTransactionError: any;
1677-
16781684
try {
16791685
const sendRawTransactionResult = await this.hapiService
16801686
.getSDKClient()
@@ -1770,14 +1776,51 @@ export class EthImpl implements Eth {
17701776
// If this point is reached, it means that no valid transaction hash was returned. Therefore, an error must have occurred.
17711777
return await this.sendRawTransactionErrorHandler(
17721778
sendRawTransactionError,
1773-
transaction,
17741779
transactionBuffer,
17751780
txSubmitted,
17761781
parsedTx,
17771782
requestDetails,
17781783
);
17791784
}
17801785

1786+
/**
1787+
* Submits a transaction to the network for execution.
1788+
*
1789+
* @param {string} transaction - The raw transaction to submit.
1790+
* @param {RequestDetails} requestDetails - The request details for logging and tracking.
1791+
* @returns {Promise<string | JsonRpcError>} A promise that resolves to the transaction hash if successful, or a JsonRpcError if an error occurs.
1792+
*/
1793+
async sendRawTransaction(transaction: string, requestDetails: RequestDetails): Promise<string | JsonRpcError> {
1794+
const transactionBuffer = Buffer.from(EthImpl.prune0x(transaction), 'hex');
1795+
1796+
const networkGasPriceInWeiBars = Utils.addPercentageBufferToGasPrice(
1797+
await this.getFeeWeibars(EthImpl.ethGasPrice, requestDetails),
1798+
);
1799+
const parsedTx = await this.parseRawTxAndPrecheck(transaction, networkGasPriceInWeiBars, requestDetails);
1800+
1801+
/**
1802+
* Note: If the USE_ASYNC_TX_PROCESSING feature flag is enabled,
1803+
* the transaction hash is calculated and returned immediately after passing all prechecks.
1804+
* All transaction processing logic is then handled asynchronously in the background.
1805+
*/
1806+
const useAsyncTxProcessing = ConfigService.get('USE_ASYNC_TX_PROCESSING') as boolean;
1807+
if (useAsyncTxProcessing) {
1808+
this.sendRawTransactionProcessor(transactionBuffer, parsedTx, networkGasPriceInWeiBars, requestDetails);
1809+
return Utils.computeTransactionHash(transactionBuffer);
1810+
}
1811+
1812+
/**
1813+
* Note: If the USE_ASYNC_TX_PROCESSING feature flag is disabled,
1814+
* wait for all transaction processing logic to complete before returning the transaction hash.
1815+
*/
1816+
return await this.sendRawTransactionProcessor(
1817+
transactionBuffer,
1818+
parsedTx,
1819+
networkGasPriceInWeiBars,
1820+
requestDetails,
1821+
);
1822+
}
1823+
17811824
/**
17821825
* Execute a free contract call query.
17831826
*

packages/relay/src/utils.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818
*
1919
*/
2020

21-
import { PrivateKey } from '@hashgraph/sdk';
22-
import constants from './lib/constants';
2321
import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
22+
import { PrivateKey } from '@hashgraph/sdk';
2423
import crypto from 'crypto';
25-
import { hexToASCII, strip0x } from './formatters';
24+
import createHash from 'keccak';
25+
26+
import { hexToASCII, prepend0x, strip0x } from './formatters';
27+
import constants from './lib/constants';
2628

2729
export class Utils {
2830
public static readonly IP_ADDRESS_REGEX = /\b((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}\b/g;
@@ -125,4 +127,13 @@ export class Utils {
125127
statuses.includes(hexToASCII(strip0x(contractResult.error_message ?? '')))
126128
);
127129
}
130+
131+
/**
132+
* Computes the Keccak-256 hash of a transaction buffer and prepends '0x'
133+
* @param {Buffer} transactionBuffer - The raw transaction buffer to hash
134+
* @returns {string} The computed transaction hash with '0x' prefix
135+
*/
136+
public static computeTransactionHash(transactionBuffer: Buffer): string {
137+
return prepend0x(createHash('keccak256').update(transactionBuffer).digest('hex'));
138+
}
128139
}

0 commit comments

Comments
 (0)