Skip to content

Commit

Permalink
feat: enabled fast return on eth_sendRawTransaction (#3273)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
quiet-node authored Nov 25, 2024
1 parent 7accf1f commit 56903ba
Show file tree
Hide file tree
Showing 18 changed files with 647 additions and 373 deletions.
3 changes: 2 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ The following table lists the available properties along with their default valu
Unless you need to set a non-default value, it is recommended to only populate overridden properties in the custom `.env`.

| Name | Default | Description |
|---------------------------------------------|---------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ------------------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `CACHE_MAX` | "1000" | The maximum number (or size) of items that remain in the cache (assuming no TTL pruning or explicit deletions). |
| `CACHE_TTL` | "3_600_000" | Max time to live in ms, for items before they are considered stale. Default is one hour in milliseconds |
| `CLIENT_TRANSPORT_SECURITY` | "false" | Flag to enable or disable TLS for both networks. |
Expand Down Expand Up @@ -100,6 +100,7 @@ Unless you need to set a non-default value, it is recommended to only populate o
| `TIER_2_RATE_LIMIT` | "800" | Maximum moderate request count limit used for non expensive endpoints. |
| `TIER_3_RATE_LIMIT` | "1600" | Maximum relaxed request count limit used for static return endpoints. |
| `TX_DEFAULT_GAS` | "400000" | Default gas for transactions that do not specify gas. |
| `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. |
| `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`. |
| `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`. |
| `FILTER_API_ENABLED` | "false" | Enables all filter related methods: `eth_newFilter`, `eth_uninstallFilter`, `eth_getFilterChanges`, `eth_getFilterLogs`, `eth_newBlockFilter` |
Expand Down
21 changes: 21 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"acceptancetest:api_batch3": "nyc ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@api-batch-3' --exit",
"acceptancetest:erc20": "npm_package_version=0.0.1 nyc ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@erc20' --exit",
"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",
"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",
"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",
"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",
"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",
"acceptancetest:tokencreate": "nyc ts-mocha packages/server/tests/acceptance/index.spec.ts -g '@tokencreate' --exit",
Expand Down
6 changes: 6 additions & 0 deletions packages/config-service/src/services/globalConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,12 @@ export class GlobalConfig {
required: false,
defaultValue: null,
},
USE_ASYNC_TX_PROCESSING: {
envName: 'USE_ASYNC_TX_PROCESSING',
type: 'boolean',
required: false,
defaultValue: false,
},
WEB_SOCKET_HTTP_PORT: {
envName: 'WEB_SOCKET_HTTP_PORT',
type: 'number',
Expand Down
3 changes: 2 additions & 1 deletion packages/relay/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"author": "Hedera Smart Contracts Team",
"devDependencies": {
"@types/chai": "^4.3.0",
"@types/keccak": "^3.0.5",
"@types/mocha": "^9.1.0",
"@types/node": "^17.0.14",
"chai": "^4.3.6",
Expand Down Expand Up @@ -48,9 +49,9 @@
"test:eth-get-logs": "nyc ts-mocha --recursive './tests/**/*.spec.ts' './tests/**/**/*.spec.ts' -g '@ethGetLogs' --exit"
},
"dependencies": {
"@hashgraph/json-rpc-config-service": "file:../config-service",
"@ethersproject/asm": "^5.7.0",
"@hashgraph/sdk": "^2.54.0-beta.1",
"@hashgraph/json-rpc-config-service": "file:../config-service",
"@keyvhq/core": "^1.6.9",
"axios": "^1.4.0",
"axios-retry": "^3.5.1",
Expand Down
113 changes: 78 additions & 35 deletions packages/relay/src/lib/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ import { IContractCallRequest, IContractCallResponse, IFeeHistory, ITransactionR
import { IAccountInfo } from './types/mirrorNode';

const _ = require('lodash');
const createHash = require('keccak');
const asm = require('@ethersproject/asm');

interface LatestBlockNumberTimestamp {
Expand Down Expand Up @@ -1550,43 +1549,42 @@ export class EthImpl implements Eth {

async parseRawTxAndPrecheck(
transaction: string,
requestDetails: RequestDetails,
networkGasPriceInWeiBars: number,
requestDetails: RequestDetails,
): Promise<EthersTransaction> {
let interactingEntity = '';
let originatingAddress = '';
const parsedTx = Precheck.parseTxIfNeeded(transaction);
try {
this.precheck.checkSize(transaction);
const parsedTx = Precheck.parseTxIfNeeded(transaction);
interactingEntity = parsedTx.to?.toString() || '';
originatingAddress = parsedTx.from?.toString() || '';
if (this.logger.isLevelEnabled('trace')) {
this.logger.trace(
`${requestDetails.formattedRequestId} sendRawTransaction(from=${originatingAddress}, to=${interactingEntity}, transaction=${transaction})`,
`${requestDetails.formattedRequestId} Transaction undergoing prechecks: transaction=${JSON.stringify(
parsedTx,
)}`,
);
}

this.precheck.checkSize(transaction);
await this.precheck.sendRawTransactionCheck(parsedTx, networkGasPriceInWeiBars, requestDetails);
return parsedTx;
} catch (e: any) {
this.logger.warn(
`${requestDetails.formattedRequestId} Error on precheck sendRawTransaction(from=${originatingAddress}, to=${interactingEntity}, transaction=${transaction})`,
this.logger.error(
`${requestDetails.formattedRequestId} Precheck failed: transaction=${JSON.stringify(parsedTx)}`,
);
throw this.common.genericErrorHandler(e);
}
}

async sendRawTransactionErrorHandler(
e,
transaction,
transactionBuffer,
txSubmitted,
parsedTx,
e: any,
transactionBuffer: Buffer,
txSubmitted: boolean,
parsedTx: EthersTransaction,
requestDetails: RequestDetails,
): Promise<string | JsonRpcError> {
this.logger.error(
e,
`${requestDetails.formattedRequestId} Failed to successfully submit sendRawTransaction for transaction ${transaction}`,
`${
requestDetails.formattedRequestId
} Failed to successfully submit sendRawTransaction: transaction=${JSON.stringify(parsedTx)}`,
);
if (e instanceof JsonRpcError) {
return e;
Expand Down Expand Up @@ -1638,24 +1636,39 @@ export class EthImpl implements Eth {

this.logger.error(
e,
`${requestDetails.formattedRequestId} Failed sendRawTransaction during record retrieval for transaction ${transaction}, returning computed hash`,
`${
requestDetails.formattedRequestId
} Failed sendRawTransaction during record retrieval for transaction, returning computed hash: transaction=${JSON.stringify(
parsedTx,
)}`,
);
//Return computed hash if unable to retrieve EthereumHash from record due to error
return prepend0x(createHash('keccak256').update(transactionBuffer).digest('hex'));
return Utils.computeTransactionHash(transactionBuffer);
}

/**
* Submits a transaction to the network for execution.
* Asynchronously processes a raw transaction by submitting it to the network, managing HFS, polling the MN, handling errors, and returning the transaction hash.
*
* @param {string} transaction The raw transaction to submit
* @param {RequestDetails} requestDetails The request details for logging and tracking
* @async
* @param {Buffer} transactionBuffer - The raw transaction data as a buffer.
* @param {EthersTransaction} parsedTx - The parsed Ethereum transaction object.
* @param {number} networkGasPriceInWeiBars - The current network gas price in wei bars.
* @param {RequestDetails} requestDetails - Details of the request for logging and tracking purposes.
* @returns {Promise<string | JsonRpcError>} A promise that resolves to the transaction hash if successful, or a JsonRpcError if an error occurs.
* @throws {JsonRpcError} If there's an error during transaction processing.
*/
async sendRawTransaction(transaction: string, requestDetails: RequestDetails): Promise<string | JsonRpcError> {
async sendRawTransactionProcessor(
transactionBuffer: Buffer,
parsedTx: EthersTransaction,
networkGasPriceInWeiBars: number,
requestDetails: RequestDetails,
): Promise<string | JsonRpcError> {
let fileId: FileId | null = null;
let txSubmitted = false;
let submittedTransactionId: string = '';
let sendRawTransactionError: any;

const requestIdPrefix = requestDetails.formattedRequestId;
const networkGasPriceInWeiBars = Utils.addPercentageBufferToGasPrice(
await this.getFeeWeibars(EthImpl.ethGasPrice, requestDetails),
);
const parsedTx = await this.parseRawTxAndPrecheck(transaction, requestDetails, networkGasPriceInWeiBars);
const originalCallerAddress = parsedTx.from?.toString() || '';
const toAddress = parsedTx.to?.toString() || '';

Expand All @@ -1668,13 +1681,6 @@ export class EthImpl implements Eth {
)
.inc();

const transactionBuffer = Buffer.from(EthImpl.prune0x(transaction), 'hex');

let fileId: FileId | null = null;
let txSubmitted = false;
let submittedTransactionId: string = '';
let sendRawTransactionError: any;

try {
const sendRawTransactionResult = await this.hapiService
.getSDKClient()
Expand Down Expand Up @@ -1770,14 +1776,51 @@ export class EthImpl implements Eth {
// If this point is reached, it means that no valid transaction hash was returned. Therefore, an error must have occurred.
return await this.sendRawTransactionErrorHandler(
sendRawTransactionError,
transaction,
transactionBuffer,
txSubmitted,
parsedTx,
requestDetails,
);
}

/**
* Submits a transaction to the network for execution.
*
* @param {string} transaction - The raw transaction to submit.
* @param {RequestDetails} requestDetails - The request details for logging and tracking.
* @returns {Promise<string | JsonRpcError>} A promise that resolves to the transaction hash if successful, or a JsonRpcError if an error occurs.
*/
async sendRawTransaction(transaction: string, requestDetails: RequestDetails): Promise<string | JsonRpcError> {
const transactionBuffer = Buffer.from(EthImpl.prune0x(transaction), 'hex');

const networkGasPriceInWeiBars = Utils.addPercentageBufferToGasPrice(
await this.getFeeWeibars(EthImpl.ethGasPrice, requestDetails),
);
const parsedTx = await this.parseRawTxAndPrecheck(transaction, networkGasPriceInWeiBars, requestDetails);

/**
* Note: If the USE_ASYNC_TX_PROCESSING feature flag is enabled,
* the transaction hash is calculated and returned immediately after passing all prechecks.
* All transaction processing logic is then handled asynchronously in the background.
*/
const useAsyncTxProcessing = ConfigService.get('USE_ASYNC_TX_PROCESSING') as boolean;
if (useAsyncTxProcessing) {
this.sendRawTransactionProcessor(transactionBuffer, parsedTx, networkGasPriceInWeiBars, requestDetails);
return Utils.computeTransactionHash(transactionBuffer);
}

/**
* Note: If the USE_ASYNC_TX_PROCESSING feature flag is disabled,
* wait for all transaction processing logic to complete before returning the transaction hash.
*/
return await this.sendRawTransactionProcessor(
transactionBuffer,
parsedTx,
networkGasPriceInWeiBars,
requestDetails,
);
}

/**
* Execute a free contract call query.
*
Expand Down
17 changes: 14 additions & 3 deletions packages/relay/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
*
*/

import { PrivateKey } from '@hashgraph/sdk';
import constants from './lib/constants';
import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
import { PrivateKey } from '@hashgraph/sdk';
import crypto from 'crypto';
import { hexToASCII, strip0x } from './formatters';
import createHash from 'keccak';

import { hexToASCII, prepend0x, strip0x } from './formatters';
import constants from './lib/constants';

export class Utils {
public static readonly IP_ADDRESS_REGEX = /\b((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}\b/g;
Expand Down Expand Up @@ -125,4 +127,13 @@ export class Utils {
statuses.includes(hexToASCII(strip0x(contractResult.error_message ?? '')))
);
}

/**
* Computes the Keccak-256 hash of a transaction buffer and prepends '0x'
* @param {Buffer} transactionBuffer - The raw transaction buffer to hash
* @returns {string} The computed transaction hash with '0x' prefix
*/
public static computeTransactionHash(transactionBuffer: Buffer): string {
return prepend0x(createHash('keccak256').update(transactionBuffer).digest('hex'));
}
}
Loading

0 comments on commit 56903ba

Please sign in to comment.