Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enabled fast return on eth_sendRawTransaction #3273

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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",
quiet-node marked this conversation as resolved.
Show resolved Hide resolved
"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",
quiet-node marked this conversation as resolved.
Show resolved Hide resolved
"@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(
quiet-node marked this conversation as resolved.
Show resolved Hide resolved
`${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
Loading