Skip to content

Commit 14955e9

Browse files
authored
feat: removed fallback predefined gas value for eth_estimateGas when failed to retrieve gas estimate from Mirror Node (#4678)
Signed-off-by: Logan Nguyen <[email protected]>
1 parent 1a6b3ca commit 14955e9

File tree

17 files changed

+608
-311
lines changed

17 files changed

+608
-311
lines changed

.env.http.example

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ OPERATOR_KEY_MAIN= # Operator private key used to sign transaction
2222
# DEBUG_API_ENABLED=false # Enables debug methods like debug_traceTransaction
2323
# TXPOOL_API_ENABLED=true # Enables txpool related methods
2424
# FILTER_API_ENABLED=true # Enables filter related methods
25-
# ESTIMATE_GAS_THROWS=true # If true, throws actual error reason during contract reverts
2625

2726
# ========== BATCH REQUESTS ==========
2827
# BATCH_REQUESTS_ENABLED=true # Enable or disable batch requests

charts/hedera-json-rpc-relay/values.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ config:
3131
# DEBUG_API_ENABLED:
3232
# TXPOOL_API_ENABLED:
3333
# FILTER_API_ENABLED:
34-
# ESTIMATE_GAS_THROWS:
3534
# SUBSCRIPTIONS_ENABLED:
3635

3736
# ========== BATCH REQUESTS ==========

docs/configuration.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ Unless you need to set a non-default value, it is recommended to only populate o
3838
| `DEBUG_API_ENABLED` | "false" | Enables all debug related methods: `debug_traceTransaction` |
3939
| `DEFAULT_RATE_LIMIT` | "200" | default fallback rate limit, if no other is configured. |
4040
| `ENABLE_TX_POOL` | "false" | Flag to determine if the system should use a tx pool or not. |
41-
| `ESTIMATE_GAS_THROWS` | "true" | Flag to determine if the system should throw an error with the actual reason during contract reverts instead of returning a predefined gas value. |
4241
| `ETH_BLOCK_NUMBER_CACHE_TTL_MS` | "1000" | Time in ms to cache response from mirror node |
4342
| `ETH_CALL_ACCEPTED_ERRORS` | "[]" | A list of acceptable error codes for eth_call requests. If an error code in this list is returned, the request will be retried. |
4443
| `ETH_CALL_CACHE_TTL` | "200" | Maximum time in ms to cache an eth_call response. |

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,6 @@ const _CONFIG = {
173173
required: false,
174174
defaultValue: false,
175175
},
176-
ESTIMATE_GAS_THROWS: {
177-
type: 'boolean',
178-
required: false,
179-
defaultValue: true,
180-
},
181176
ETH_BLOCK_NUMBER_CACHE_TTL_MS: {
182177
type: 'number',
183178
required: false,

packages/relay/src/lib/errors/JsonRpcError.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,12 @@ export const predefined = {
196196
message: message,
197197
});
198198
},
199-
COULD_NOT_ESTIMATE_GAS_PRICE: new JsonRpcError({
200-
code: -32604,
201-
message: 'Error encountered estimating the gas price',
202-
}),
199+
COULD_NOT_SIMULATE_TRANSACTION: (errMessage: string) => {
200+
return new JsonRpcError({
201+
code: -32000,
202+
message: `Error occurred during transaction simulation: ${errMessage}`,
203+
});
204+
},
203205
COULD_NOT_RETRIEVE_LATEST_BLOCK: new JsonRpcError({
204206
code: -32607,
205207
message: 'Error encountered retrieving latest block',

packages/relay/src/lib/errors/MirrorNodeClientError.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ export class MirrorNodeClientError extends Error {
99

1010
static ErrorCodes = {
1111
ECONNABORTED: 504,
12-
CONTRACT_REVERT_EXECUTED: 400,
1312
NOT_SUPPORTED: 501,
1413
};
1514

@@ -45,11 +44,7 @@ export class MirrorNodeClientError extends Error {
4544
return this.statusCode === MirrorNodeClientError.ErrorCodes.ECONNABORTED;
4645
}
4746

48-
public isContractReverted(): boolean {
49-
return this.statusCode === MirrorNodeClientError.ErrorCodes.CONTRACT_REVERT_EXECUTED;
50-
}
51-
52-
public isContractRevertOpcodeExecuted() {
47+
public isContractRevert() {
5348
return this.message === MirrorNodeClientError.messages.CONTRACT_REVERT_EXECUTED;
5449
}
5550

packages/relay/src/lib/services/ethService/contractService/ContractService.ts

Lines changed: 24 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -167,24 +167,26 @@ export class ContractService implements IContractService {
167167
try {
168168
const response = await this.estimateGasFromMirrorNode(transaction, requestDetails);
169169

170-
if (response?.result) {
171-
this.logger.info(`Returning gas: ${response.result}`);
172-
return prepend0x(trimPrecedingZeros(response.result));
173-
} else {
174-
this.logger.error(`No gas estimate returned from mirror-node: ${JSON.stringify(response)}`);
175-
return this.predefinedGasForTransaction(transaction, requestDetails);
170+
if (!response?.result) {
171+
if (this.logger.isLevelEnabled('debug')) {
172+
this.logger.debug(`No gas estimate returned from mirror-node: ${JSON.stringify(response)}`);
173+
}
174+
return predefined.INTERNAL_ERROR('Fail to retrieve gas estimate');
176175
}
176+
177+
return prepend0x(trimPrecedingZeros(response.result));
177178
} catch (e: any) {
178-
this.logger.error(`Error raised while fetching estimateGas from mirror-node: ${JSON.stringify(e)}`);
179-
// in case of contract revert, we don't want to return a predefined gas but the actual error with the reason
180-
if (
181-
ConfigService.get('ESTIMATE_GAS_THROWS') &&
182-
e instanceof MirrorNodeClientError &&
183-
e.isContractRevertOpcodeExecuted()
184-
) {
185-
return predefined.CONTRACT_REVERT(e.detail ?? e.message, e.data);
179+
if (e instanceof MirrorNodeClientError) {
180+
if (e.isContractRevert()) {
181+
throw predefined.CONTRACT_REVERT(e.detail || e.message, e.data);
182+
} else if (e.statusCode === 400) {
183+
throw predefined.COULD_NOT_SIMULATE_TRANSACTION(e.detail || e.message);
184+
}
186185
}
187-
return this.predefinedGasForTransaction(transaction, requestDetails, e);
186+
187+
// for any other error or Mirror Node upstream server errors (429, 500, 502, 503, 504, etc.),
188+
// preserve the original error and re-throw to the upper layer for further handling logic
189+
throw e;
188190
}
189191
}
190192

@@ -523,15 +525,14 @@ export class ContractService implements IContractService {
523525
return constants.EMPTY_HEX;
524526
}
525527

526-
if (e.isContractReverted()) {
527-
if (this.logger.isLevelEnabled('trace')) {
528-
this.logger.trace(
529-
`mirror node eth_call request encountered contract revert. message: ${e.message}, details: ${e.detail}, data: ${e.data}`,
530-
);
531-
}
532-
return predefined.CONTRACT_REVERT(e.detail || e.message, e.data);
528+
if (e.isContractRevert()) {
529+
throw predefined.CONTRACT_REVERT(e.detail || e.message, e.data);
530+
} else if (e.statusCode === 400) {
531+
throw predefined.COULD_NOT_SIMULATE_TRANSACTION(e.detail || e.message);
533532
}
534-
// for any other Mirror Node upstream server errors (429, 500, 502, 503, 504, etc.), preserve the original error and re-throw to the upper layer
533+
534+
// for any other error or Mirror Node upstream server errors (429, 500, 502, 503, 504, etc.),
535+
// preserve the original error and re-throw to the upper layer for further handling logic
535536
throw e;
536537
}
537538

@@ -555,68 +556,6 @@ export class ContractService implements IContractService {
555556
return predefined.INTERNAL_ERROR(e.message.toString());
556557
}
557558

558-
/**
559-
* Fallback calculations for the amount of gas to be used for a transaction.
560-
* This method is used when the mirror node fails to return a gas estimate.
561-
*
562-
* @param {IContractCallRequest} transaction The transaction data for the contract call.
563-
* @param {RequestDetails} requestDetails The request details for logging and tracking.
564-
* @param error (Optional) received error from the mirror-node contract call request.
565-
* @returns {Promise<string | JsonRpcError>} the calculated gas cost for the transaction
566-
*/
567-
private async predefinedGasForTransaction(
568-
transaction: IContractCallRequest,
569-
requestDetails: RequestDetails,
570-
error?: any,
571-
): Promise<string | JsonRpcError> {
572-
const isSimpleTransfer = !!transaction?.to && (!transaction.data || transaction.data === '0x');
573-
const isContractCall =
574-
!!transaction?.to && transaction?.data && transaction.data.length >= constants.FUNCTION_SELECTOR_CHAR_LENGTH;
575-
const isContractCreate = !transaction?.to && transaction?.data && transaction.data !== '0x';
576-
const contractCallAverageGas = numberTo0x(constants.TX_CONTRACT_CALL_AVERAGE_GAS);
577-
const gasTxBaseCost = numberTo0x(constants.TX_BASE_COST);
578-
579-
if (isSimpleTransfer) {
580-
// Handle Simple Transaction and Hollow Account creation
581-
const isZeroOrHigher = Number(transaction.value) >= 0;
582-
if (!isZeroOrHigher) {
583-
return predefined.INVALID_PARAMETER(
584-
0,
585-
`Invalid 'value' field in transaction param. Value must be greater than or equal to 0`,
586-
);
587-
}
588-
// when account exists return default base gas
589-
if (await this.common.getAccount(transaction.to!, requestDetails)) {
590-
this.logger.warn(`Returning predefined gas for simple transfer: ${gasTxBaseCost}`);
591-
return gasTxBaseCost;
592-
}
593-
const minGasTxHollowAccountCreation = numberTo0x(constants.MIN_TX_HOLLOW_ACCOUNT_CREATION_GAS);
594-
// otherwise, return the minimum amount of gas for hollow account creation
595-
this.logger.warn(`Returning predefined gas for hollow account creation: ${minGasTxHollowAccountCreation}`);
596-
return minGasTxHollowAccountCreation;
597-
} else if (isContractCreate) {
598-
// The size limit of the encoded contract posted to the mirror node can
599-
// cause contract deployment transactions to fail with a 400 response code.
600-
// The contract is actually deployed on the consensus node, so the contract will work.
601-
// In these cases, we don't want to return a CONTRACT_REVERT error.
602-
if (
603-
ConfigService.get('ESTIMATE_GAS_THROWS') &&
604-
error?.isContractReverted() &&
605-
error?.message !== MirrorNodeClientError.messages.INVALID_HEX
606-
) {
607-
return predefined.CONTRACT_REVERT(error.detail, error.data);
608-
}
609-
this.logger.warn(`Returning predefined gas for contract creation: ${gasTxBaseCost}`);
610-
return numberTo0x(Precheck.transactionIntrinsicGasCost(transaction.data!));
611-
} else if (isContractCall) {
612-
this.logger.warn(`Returning predefined gas for contract call: ${contractCallAverageGas}`);
613-
return contractCallAverageGas;
614-
} else {
615-
this.logger.warn(`Returning predefined gas for unknown transaction: ${this.defaultGas}`);
616-
return this.defaultGas;
617-
}
618-
}
619-
620559
/**
621560
* Prepares the call data for mirror node request.
622561
*

packages/relay/src/lib/services/ethService/ethCommonService/CommonService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ export class CommonService implements ICommonService {
458458
}
459459
}
460460

461-
throw predefined.COULD_NOT_ESTIMATE_GAS_PRICE;
461+
throw predefined.INTERNAL_ERROR('Failed to retrieve gas price from network fees');
462462
}
463463

464464
/**

packages/relay/tests/helpers.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,58 @@ const mockData = {
351351
],
352352
},
353353
},
354+
355+
genericBadRequest: {
356+
_status: {
357+
messages: [
358+
{
359+
message: 'Bad request',
360+
detail: 'Invalid request parameters',
361+
data: '',
362+
},
363+
],
364+
},
365+
},
366+
367+
internalServerError: {
368+
_status: {
369+
messages: [
370+
{
371+
message: 'Internal Server Error',
372+
},
373+
],
374+
},
375+
},
376+
377+
badGateway: {
378+
_status: {
379+
messages: [
380+
{
381+
message: 'Bad Gateway',
382+
},
383+
],
384+
},
385+
},
386+
387+
serviceUnavailable: {
388+
_status: {
389+
messages: [
390+
{
391+
message: 'Service Unavailable',
392+
},
393+
],
394+
},
395+
},
396+
397+
gatewayTimeout: {
398+
_status: {
399+
messages: [
400+
{
401+
message: 'Gateway Timeout',
402+
},
403+
],
404+
},
405+
},
354406
};
355407

356408
export { expectUnsupportedMethod, expectedError, signTransaction, mockData, random20BytesAddress, getQueryParams };

0 commit comments

Comments
 (0)