-
Notifications
You must be signed in to change notification settings - Fork 75
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
fix: enhanced reliability of eth RPC methods with null checks and retry mechanisms #3349
base: main
Are you sure you want to change the base?
Changes from all commits
152b18c
bc26b45
548cdeb
d97250d
ae23d06
a99288f
a410b15
8687322
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -724,6 +724,11 @@ export class SDKClient { | |
); | ||
return transactionResponse; | ||
} catch (e: any) { | ||
this.logger.warn( | ||
e, | ||
`${requestDetails.formattedRequestId} Transaction failed while executing transaction via the SDK: transactionId=${transaction.transactionId}, callerName=${callerName}, txConstructorName=${txConstructorName}`, | ||
); | ||
|
||
Comment on lines
+727
to
+731
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note for Rereviewers: |
||
if (e instanceof JsonRpcError) { | ||
throw e; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
/* - | ||
/*- | ||
* | ||
* Hedera JSON RPC Relay | ||
* | ||
|
@@ -1922,13 +1922,17 @@ export class EthImpl implements Eth { | |
transactionIndex: string, | ||
requestDetails: RequestDetails, | ||
): Promise<Transaction | null> { | ||
const contractResults = await this.mirrorNodeClient.getContractResults( | ||
const contractResults = await this.mirrorNodeClient.getContractResultWithRetry( | ||
this.mirrorNodeClient.getContractResults.name, | ||
[ | ||
requestDetails, | ||
{ | ||
[blockParam.title]: blockParam.value, | ||
transactionIndex: Number(transactionIndex), | ||
}, | ||
undefined, | ||
], | ||
requestDetails, | ||
{ | ||
[blockParam.title]: blockParam.value, | ||
transactionIndex: Number(transactionIndex), | ||
}, | ||
undefined, | ||
); | ||
|
||
if (!contractResults[0]) return null; | ||
|
@@ -2201,7 +2205,11 @@ export class EthImpl implements Eth { | |
this.logger.trace(`${requestIdPrefix} getTransactionByHash(hash=${hash})`, hash); | ||
} | ||
|
||
const contractResult = await this.mirrorNodeClient.getContractResultWithRetry(hash, requestDetails); | ||
const contractResult = await this.mirrorNodeClient.getContractResultWithRetry( | ||
this.mirrorNodeClient.getContractResult.name, | ||
[hash, requestDetails], | ||
requestDetails, | ||
); | ||
if (contractResult === null || contractResult.hash === undefined) { | ||
// handle synthetic transactions | ||
const syntheticLogs = await this.common.getLogsWithParams( | ||
|
@@ -2265,7 +2273,12 @@ export class EthImpl implements Eth { | |
return cachedResponse; | ||
} | ||
|
||
const receiptResponse = await this.mirrorNodeClient.getContractResultWithRetry(hash, requestDetails); | ||
const receiptResponse = await this.mirrorNodeClient.getContractResultWithRetry( | ||
this.mirrorNodeClient.getContractResult.name, | ||
[hash, requestDetails], | ||
requestDetails, | ||
); | ||
|
||
if (receiptResponse === null || receiptResponse.hash === undefined) { | ||
// handle synthetic transactions | ||
const syntheticLogs = await this.common.getLogsWithParams( | ||
|
@@ -2531,10 +2544,11 @@ export class EthImpl implements Eth { | |
if (blockResponse == null) return null; | ||
const timestampRange = blockResponse.timestamp; | ||
const timestampRangeParams = [`gte:${timestampRange.from}`, `lte:${timestampRange.to}`]; | ||
const contractResults = await this.mirrorNodeClient.getContractResults( | ||
|
||
const contractResults = await this.mirrorNodeClient.getContractResultWithRetry( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we just being cautious or does this case actually need to have retry support? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since these cases occur only on the mainnet and at the time of the transaction, I plan to release these updates that include additional debugging information for improved analysis. If the issues persist, it will be easier to pinpoint the root causes and escalate them to the mainnet team more effectively. |
||
this.mirrorNodeClient.getContractResults.name, | ||
[requestDetails, { timestamp: timestampRangeParams }, undefined], | ||
requestDetails, | ||
{ timestamp: timestampRangeParams }, | ||
undefined, | ||
); | ||
const gasUsed = blockResponse.gas_used; | ||
const params = { timestamp: timestampRangeParams }; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,19 +18,21 @@ | |
* | ||
*/ | ||
|
||
import constants from '../../../constants'; | ||
import { JsonRpcError, predefined } from '../../../errors/JsonRpcError'; | ||
import { ICommonService } from './ICommonService'; | ||
import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services'; | ||
import * as _ from 'lodash'; | ||
import { Logger } from 'pino'; | ||
import { MirrorNodeClient } from '../../../clients'; | ||
|
||
import { nullableNumberTo0x, numberTo0x, parseNumericEnvVar, toHash32 } from '../../../../formatters'; | ||
import { SDKClientError } from '../../../errors/SDKClientError'; | ||
import { MirrorNodeClient } from '../../../clients'; | ||
import constants from '../../../constants'; | ||
import { JsonRpcError, predefined } from '../../../errors/JsonRpcError'; | ||
import { MirrorNodeClientError } from '../../../errors/MirrorNodeClientError'; | ||
import { SDKClientError } from '../../../errors/SDKClientError'; | ||
import { EthImpl } from '../../../eth'; | ||
import { Log } from '../../../model'; | ||
import * as _ from 'lodash'; | ||
import { CacheService } from '../../cacheService/cacheService'; | ||
import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services'; | ||
import { RequestDetails } from '../../../types'; | ||
import { CacheService } from '../../cacheService/cacheService'; | ||
import { ICommonService } from './ICommonService'; | ||
|
||
/** | ||
* Create a new Common Service implementation. | ||
|
@@ -175,6 +177,20 @@ | |
returnLatest?: boolean, | ||
): Promise<any> { | ||
if (!returnLatest && this.blockTagIsLatestOrPending(blockNumberOrTagOrHash)) { | ||
if (this.logger.isLevelEnabled('debug')) { | ||
this.logger.debug( | ||
`${requestDetails.formattedRequestId} Detected a contradiction between blockNumberOrTagOrHash and returnLatest. The request does not target the latest block, yet blockNumberOrTagOrHash representing latest or pending: returnLatest=${returnLatest}, blockNumberOrTagOrHash=${blockNumberOrTagOrHash}`, | ||
); | ||
} | ||
return null; | ||
} | ||
|
||
if (blockNumberOrTagOrHash === EthImpl.emptyHex) { | ||
if (this.logger.isLevelEnabled('debug')) { | ||
this.logger.debug( | ||
`${requestDetails.formattedRequestId} Invalid input detected in getHistoricalBlockResponse(): blockNumberOrTagOrHash=${blockNumberOrTagOrHash}.`, | ||
); | ||
} | ||
return null; | ||
} | ||
|
||
|
@@ -321,7 +337,7 @@ | |
if (address) { | ||
logResults = await this.getLogsByAddress(address, params, requestDetails); | ||
} else { | ||
logResults = await this.mirrorNodeClient.getContractResultsLogs(requestDetails, params); | ||
logResults = await this.mirrorNodeClient.getContractResultsLogsWithRetry(requestDetails, params); | ||
} | ||
|
||
if (!logResults) { | ||
|
@@ -334,7 +350,7 @@ | |
new Log({ | ||
address: log.address, | ||
blockHash: toHash32(log.block_hash), | ||
blockNumber: numberTo0x(log.block_number), | ||
blockNumber: nullableNumberTo0x(log.block_number), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Q: Why would a block number be null? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is one of the strange cases where There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmmm, if a retry is ensured to resolve the issue then maybe we can manage one iteration in the short term. |
||
data: log.data, | ||
logIndex: nullableNumberTo0x(log.index), | ||
removed: false, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure if I understand correctly, but the errors described in the issue were because of caching null blocks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an issue I noticed and an attempt securing the code. While it may or may not be directly related to the problem, we should ensure the block is not null before storing it in the cache. Otherwise, we risk repeatedly retrieving null from the cache, which likely has a TTL.