diff --git a/packages/relay/src/lib/clients/mirrorNodeClient.ts b/packages/relay/src/lib/clients/mirrorNodeClient.ts index 997ba76891..2fa615301f 100644 --- a/packages/relay/src/lib/clients/mirrorNodeClient.ts +++ b/packages/relay/src/lib/clients/mirrorNodeClient.ts @@ -1,4 +1,4 @@ -/* - +/*- * * Hedera JSON RPC Relay * @@ -620,7 +620,10 @@ export class MirrorNodeClient { requestDetails, ); - await this.cacheService.set(cachedLabel, block, MirrorNodeClient.GET_BLOCK_ENDPOINT, requestDetails); + if (block) { + await this.cacheService.set(cachedLabel, block, MirrorNodeClient.GET_BLOCK_ENDPOINT, requestDetails); + } + return block; } @@ -754,21 +757,42 @@ export class MirrorNodeClient { * In some very rare cases the /contracts/results api is called before all the data is saved in * the mirror node DB and `transaction_index` or `block_number` is returned as `undefined` or `block_hash` as `0x`. * A single re-fetch is sufficient to resolve this problem. - * @param {string} transactionIdOrHash - The transaction ID or hash - * @param {RequestDetails} requestDetails - The request details for logging and tracking. + * + * @param {string} methodName - The name of the method used to fetch contract results. + * @param {any[]} args - The arguments to be passed to the specified method for fetching contract results. + * @param {RequestDetails} requestDetails - Details used for logging and tracking the request. + * @returns {Promise} - A promise resolving to the fetched contract result, either on the first attempt or after a retry. */ - public async getContractResultWithRetry(transactionIdOrHash: string, requestDetails: RequestDetails) { - const contractResult = await this.getContractResult(transactionIdOrHash, requestDetails); - if ( - contractResult && - !( - contractResult.transaction_index && - contractResult.block_number && - contractResult.block_hash != EthImpl.emptyHex - ) - ) { - return this.getContractResult(transactionIdOrHash, requestDetails); + public async getContractResultWithRetry( + methodName: string, + args: any[], + requestDetails: RequestDetails, + ): Promise { + const shortDelay = 500; + const contractResult = await this[methodName](...args); + + if (contractResult) { + const contractObjects = Array.isArray(contractResult) ? contractResult : [contractResult]; + for (const contractObject of contractObjects) { + if ( + contractObject && + (contractObject.transaction_index == null || + contractObject.block_number == null || + contractObject.block_hash == EthImpl.emptyHex) + ) { + if (this.logger.isLevelEnabled('debug')) { + this.logger.debug( + `${requestDetails.formattedRequestId} Contract result contains undefined transaction_index, block_number, or block_hash is an empty hex (0x): transaction_hash:${contractObject.hash}, transaction_index:${contractObject.transaction_index}, block_number=${contractObject.block_number}, block_hash=${contractObject.block_hash}. Retrying after a delay of ${shortDelay} ms `, + ); + } + + // Backoff before repeating request + await new Promise((r) => setTimeout(r, shortDelay)); + return await this[methodName](...args); + } + } } + return contractResult; } @@ -870,14 +894,25 @@ export class MirrorNodeClient { return this.getQueryParams(queryParamObject); } - public async getContractResultsLogs( + /** + * In some very rare cases the /contracts/results/logs api is called before all the data is saved in + * the mirror node DB and `transaction_index`, `block_number`, `index` is returned as `undefined`, or block_hash is an empty hex (0x). + * A single re-fetch is sufficient to resolve this problem. + * + * @param {RequestDetails} requestDetails - Details used for logging and tracking the request. + * @param {IContractLogsResultsParams} [contractLogsResultsParams] - Parameters for querying contract logs results. + * @param {ILimitOrderParams} [limitOrderParams] - Parameters for limit and order when fetching the logs. + * @returns {Promise} - A promise resolving to the paginated contract logs results. + */ + public async getContractResultsLogsWithRetry( requestDetails: RequestDetails, contractLogsResultsParams?: IContractLogsResultsParams, limitOrderParams?: ILimitOrderParams, - ) { + ): Promise { + const shortDelay = 500; const queryParams = this.prepareLogsParams(contractLogsResultsParams, limitOrderParams); - return this.getPaginatedResults( + const logResults = await this.getPaginatedResults( `${MirrorNodeClient.GET_CONTRACT_RESULT_LOGS_ENDPOINT}${queryParams}`, MirrorNodeClient.GET_CONTRACT_RESULT_LOGS_ENDPOINT, MirrorNodeClient.CONTRACT_RESULT_LOGS_PROPERTY, @@ -886,6 +921,38 @@ export class MirrorNodeClient { 1, MirrorNodeClient.mirrorNodeContractResultsLogsPageMax, ); + + if (logResults) { + for (const log of logResults) { + if ( + log && + (log.transaction_index == null || + log.block_number == null || + log.index == null || + log.block_hash === EthImpl.emptyHex) + ) { + if (this.logger.isLevelEnabled('debug')) { + this.logger.debug( + `${requestDetails.formattedRequestId} Contract result log contains undefined transaction_index, block_number, index, or block_hash is an empty hex (0x): transaction_hash:${log.transaction_hash}, transaction_index:${log.transaction_index}, block_number=${log.block_number}, log_index=${log.index}, block_hash=${log.block_hash}. Retrying after a delay of ${shortDelay} ms.`, + ); + } + + // Backoff before repeating request + await new Promise((r) => setTimeout(r, shortDelay)); + return await this.getPaginatedResults( + `${MirrorNodeClient.GET_CONTRACT_RESULT_LOGS_ENDPOINT}${queryParams}`, + MirrorNodeClient.GET_CONTRACT_RESULT_LOGS_ENDPOINT, + MirrorNodeClient.CONTRACT_RESULT_LOGS_PROPERTY, + requestDetails, + [], + 1, + MirrorNodeClient.mirrorNodeContractResultsLogsPageMax, + ); + } + } + } + + return logResults; } public async getContractResultsLogsByAddress( diff --git a/packages/relay/src/lib/clients/sdkClient.ts b/packages/relay/src/lib/clients/sdkClient.ts index 163d926652..2e0495181a 100644 --- a/packages/relay/src/lib/clients/sdkClient.ts +++ b/packages/relay/src/lib/clients/sdkClient.ts @@ -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}`, + ); + if (e instanceof JsonRpcError) { throw e; } diff --git a/packages/relay/src/lib/eth.ts b/packages/relay/src/lib/eth.ts index e3bc5f4a3b..ff9d39b968 100644 --- a/packages/relay/src/lib/eth.ts +++ b/packages/relay/src/lib/eth.ts @@ -1,4 +1,4 @@ -/* - +/*- * * Hedera JSON RPC Relay * @@ -1922,13 +1922,17 @@ export class EthImpl implements Eth { transactionIndex: string, requestDetails: RequestDetails, ): Promise { - 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( + this.mirrorNodeClient.getContractResults.name, + [requestDetails, { timestamp: timestampRangeParams }, undefined], requestDetails, - { timestamp: timestampRangeParams }, - undefined, ); const gasUsed = blockResponse.gas_used; const params = { timestamp: timestampRangeParams }; diff --git a/packages/relay/src/lib/services/debugService/index.ts b/packages/relay/src/lib/services/debugService/index.ts index 5ec317110b..a92e16f3bd 100644 --- a/packages/relay/src/lib/services/debugService/index.ts +++ b/packages/relay/src/lib/services/debugService/index.ts @@ -18,18 +18,19 @@ * */ +import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services'; import type { Logger } from 'pino'; -import type { MirrorNodeClient } from '../../clients'; -import type { IDebugService } from './IDebugService'; -import type { CommonService } from '../ethService'; + import { decodeErrorMessage, mapKeysAndValues, numberTo0x, strip0x } from '../../../formatters'; +import type { MirrorNodeClient } from '../../clients'; +import { IOpcode } from '../../clients/models/IOpcode'; +import { IOpcodesResponse } from '../../clients/models/IOpcodesResponse'; import constants, { CallType, TracerType } from '../../constants'; import { predefined } from '../../errors/JsonRpcError'; import { EthImpl } from '../../eth'; -import { IOpcodesResponse } from '../../clients/models/IOpcodesResponse'; -import { IOpcode } from '../../clients/models/IOpcode'; import { ICallTracerConfig, IOpcodeLoggerConfig, ITracerConfig, RequestDetails } from '../../types'; -import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services'; +import type { CommonService } from '../ethService'; +import type { IDebugService } from './IDebugService'; /** * Represents a DebugService for tracing and debugging transactions and debugging @@ -300,7 +301,11 @@ export class DebugService implements IDebugService { try { const [actionsResponse, transactionsResponse] = await Promise.all([ this.mirrorNodeClient.getContractsResultsActions(transactionHash, requestDetails), - this.mirrorNodeClient.getContractResultWithRetry(transactionHash, requestDetails), + this.mirrorNodeClient.getContractResultWithRetry( + this.mirrorNodeClient.getContractResult.name, + [transactionHash, requestDetails], + requestDetails, + ), ]); if (!actionsResponse || !transactionsResponse) { throw predefined.RESOURCE_NOT_FOUND(`Failed to retrieve contract results for transaction ${transactionHash}`); diff --git a/packages/relay/src/lib/services/ethService/ethCommonService/index.ts b/packages/relay/src/lib/services/ethService/ethCommonService/index.ts index bf4bad3502..4279c967ea 100644 --- a/packages/relay/src/lib/services/ethService/ethCommonService/index.ts +++ b/packages/relay/src/lib/services/ethService/ethCommonService/index.ts @@ -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 @@ export class CommonService implements ICommonService { returnLatest?: boolean, ): Promise { 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 @@ export class CommonService implements ICommonService { 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 @@ export class CommonService implements ICommonService { new Log({ address: log.address, blockHash: toHash32(log.block_hash), - blockNumber: numberTo0x(log.block_number), + blockNumber: nullableNumberTo0x(log.block_number), data: log.data, logIndex: nullableNumberTo0x(log.index), removed: false, diff --git a/packages/relay/src/receiptsRootUtils.ts b/packages/relay/src/receiptsRootUtils.ts index c9603de699..3f3415dac2 100644 --- a/packages/relay/src/receiptsRootUtils.ts +++ b/packages/relay/src/receiptsRootUtils.ts @@ -21,8 +21,9 @@ import { RLP } from '@ethereumjs/rlp'; import { Trie } from '@ethereumjs/trie'; import { bytesToInt, concatBytes, hexToBytes, intToBytes, intToHex } from '@ethereumjs/util'; -import { EthImpl } from './lib/eth'; + import { prepend0x } from './formatters'; +import { EthImpl } from './lib/eth'; import { Log } from './lib/model'; import { LogsBloomUtils } from './logsBloomUtils'; @@ -93,16 +94,29 @@ export class ReceiptsRootUtils { public static buildReceiptRootHashes(txHashes: string[], contractResults: any[], logs: Log[]): IReceiptRootHash[] { const receipts: IReceiptRootHash[] = []; - for (let i in txHashes) { + for (const i in txHashes) { const txHash: string = txHashes[i]; const logsPerTx: Log[] = logs.filter((log) => log.transactionHash == txHash); const crPerTx: any[] = contractResults.filter((cr) => cr.hash == txHash); + + // Determine the transaction index for the current transaction hash: + // - Prefer the `transaction_index` from the contract results (`crPerTx`) if available. + // - Fallback to the `transactionIndex` from logs (`logsPerTx`) if no valid `transaction_index` is found in `crPerTx`. + // - If neither source provides a valid value, `transactionIndex` remains `null`. + let transactionIndex: any = null; + if (crPerTx.length && crPerTx[0].transaction_index != null) { + transactionIndex = intToHex(crPerTx[0].transaction_index); + } else if (logsPerTx.length) { + transactionIndex = logsPerTx[0].transactionIndex; + } + receipts.push({ - transactionIndex: crPerTx.length ? intToHex(crPerTx[0].transaction_index) : logsPerTx[0].transactionIndex, + transactionIndex, type: crPerTx.length && crPerTx[0].type ? intToHex(crPerTx[0].type) : null, root: crPerTx.length ? crPerTx[0].root : EthImpl.zeroHex32Byte, status: crPerTx.length ? crPerTx[0].status : EthImpl.oneHex, - cumulativeGasUsed: crPerTx.length ? intToHex(crPerTx[0].block_gas_used) : EthImpl.zeroHex, + cumulativeGasUsed: + crPerTx.length && crPerTx[0].block_gas_used ? intToHex(crPerTx[0].block_gas_used) : EthImpl.zeroHex, logsBloom: crPerTx.length ? crPerTx[0].bloom : LogsBloomUtils.buildLogsBloom(logs[0].address, logsPerTx[0].topics), diff --git a/packages/relay/tests/lib/eth/eth_getBlockByHash.spec.ts b/packages/relay/tests/lib/eth/eth_getBlockByHash.spec.ts index f5f60fd7ba..2696d5b717 100644 --- a/packages/relay/tests/lib/eth/eth_getBlockByHash.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getBlockByHash.spec.ts @@ -19,20 +19,21 @@ */ import { expect, use } from 'chai'; -import sinon from 'sinon'; import chaiAsPromised from 'chai-as-promised'; +import sinon from 'sinon'; +import { ASCIIToHex, numberTo0x, prepend0x } from '../../../dist/formatters'; import { predefined } from '../../../src'; +import { SDKClient } from '../../../src/lib/clients'; import { EthImpl } from '../../../src/lib/eth'; +import { RequestDetails } from '../../../src/lib/types'; +import RelayAssertions from '../../assertions'; import { blockLogsBloom, defaultContractResults, defaultDetailedContractResults, overrideEnvsInMochaDescribe, } from '../../helpers'; -import { SDKClient } from '../../../src/lib/clients'; -import RelayAssertions from '../../assertions'; -import { ASCIIToHex, numberTo0x, prepend0x } from '../../../dist/formatters'; import { ACCOUNT_WITHOUT_TRANSACTIONS, BLOCK_HASH, @@ -54,13 +55,13 @@ import { DEFAULT_BLOCK_RECEIPTS_ROOT_HASH, DEFAULT_CONTRACT, DEFAULT_ETH_GET_BLOCK_BY_LOGS, + DEFAULT_LOGS, DEFAULT_NETWORK_FEES, LINKS_NEXT_RES, MOCK_ACCOUNT_WITHOUT_TRANSACTIONS, NO_SUCH_BLOCK_EXISTS_RES, } from './eth-config'; import { generateEthTestEnv } from './eth-helpers'; -import { RequestDetails } from '../../../src/lib/types'; use(chaiAsPromised); @@ -70,7 +71,7 @@ let ethImplLowTransactionCount: EthImpl; describe('@ethGetBlockByHash using MirrorNode', async function () { this.timeout(10000); - let { restMock, hapiServiceInstance, ethImpl, cacheService, mirrorNodeInstance, logger, registry } = + const { restMock, hapiServiceInstance, ethImpl, cacheService, mirrorNodeInstance, logger, registry } = generateEthTestEnv(true); const results = defaultContractResults.results; const TOTAL_GAS_USED = numberTo0x(results[0].gas_used + results[1].gas_used); @@ -365,4 +366,29 @@ describe('@ethGetBlockByHash using MirrorNode', async function () { ); }); }); + + it('eth_getBlockByHash should gracefully handle nulbale entities found in logs', async function () { + // mirror node request mocks + restMock.onGet(`blocks/${BLOCK_HASH}`).reply(200, DEFAULT_BLOCK); + restMock.onGet(CONTRACT_RESULTS_WITH_FILTER_URL).reply(200, defaultContractResults); + restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); + + const nullEntitiedLogs = { + logs: [{ ...DEFAULT_LOGS.logs[0], block_number: null, transaction_index: null, logIndex: null }], + }; + + restMock.onGet(CONTRACT_RESULTS_LOGS_WITH_FILTER_URL).reply(200, nullEntitiedLogs); + + const result = await ethImpl.getBlockByHash(BLOCK_HASH, false, requestDetails); + + RelayAssertions.assertBlock(result, { + hash: BLOCK_HASH_TRIMMED, + gasUsed: TOTAL_GAS_USED, + number: BLOCK_NUMBER_HEX, + parentHash: BLOCK_HASH_PREV_TRIMMED, + timestamp: BLOCK_TIMESTAMP_HEX, + transactions: [CONTRACT_HASH_1, CONTRACT_HASH_2], + receiptsRoot: DEFAULT_BLOCK_RECEIPTS_ROOT_HASH, + }); + }); }); diff --git a/packages/relay/tests/lib/eth/eth_getBlockByNumber.spec.ts b/packages/relay/tests/lib/eth/eth_getBlockByNumber.spec.ts index 3b3309f773..4242341ef2 100644 --- a/packages/relay/tests/lib/eth/eth_getBlockByNumber.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getBlockByNumber.spec.ts @@ -18,23 +18,30 @@ * */ +import { fail } from 'assert'; +import MockAdapter from 'axios-mock-adapter'; import { expect, use } from 'chai'; -import sinon from 'sinon'; import chaiAsPromised from 'chai-as-promised'; import { Logger } from 'pino'; +import { Registry } from 'prom-client'; +import sinon from 'sinon'; + +import { ASCIIToHex, hashNumber, numberTo0x, prepend0x } from '../../../dist/formatters'; import { predefined } from '../../../src'; +import { MirrorNodeClient, SDKClient } from '../../../src/lib/clients'; +import constants from '../../../src/lib/constants'; import { EthImpl } from '../../../src/lib/eth'; +import { Block, Transaction } from '../../../src/lib/model'; +import { CacheService } from '../../../src/lib/services/cacheService/cacheService'; +import HAPIService from '../../../src/lib/services/hapiService/hapiService'; +import { RequestDetails } from '../../../src/lib/types'; +import RelayAssertions from '../../assertions'; import { blockLogsBloom, defaultContractResults, defaultDetailedContractResults, overrideEnvsInMochaDescribe, } from '../../helpers'; -import { Block, Transaction } from '../../../src/lib/model'; -import { MirrorNodeClient, SDKClient } from '../../../src/lib/clients'; -import RelayAssertions from '../../assertions'; -import constants from '../../../src/lib/constants'; -import { ASCIIToHex, hashNumber, numberTo0x, prepend0x } from '../../../dist/formatters'; import { BLOCK_HASH, BLOCK_HASH_PREV_TRIMMED, @@ -63,6 +70,7 @@ import { DEFAULT_BLOCKS_RES, DEFAULT_CONTRACT_RES_REVERT, DEFAULT_ETH_GET_BLOCK_BY_LOGS, + DEFAULT_LOGS, DEFAULT_NETWORK_FEES, GAS_USED_1, GAS_USED_2, @@ -76,12 +84,6 @@ import { NOT_FOUND_RES, } from './eth-config'; import { generateEthTestEnv } from './eth-helpers'; -import { fail } from 'assert'; -import { RequestDetails } from '../../../src/lib/types'; -import MockAdapter from 'axios-mock-adapter'; -import HAPIService from '../../../src/lib/services/hapiService/hapiService'; -import { CacheService } from '../../../src/lib/services/cacheService/cacheService'; -import { Registry } from 'prom-client'; use(chaiAsPromised); @@ -609,4 +611,29 @@ describe('@ethGetBlockByNumber using MirrorNode', async function () { }); }); }); + + it('eth_getBlockByNumber should gracefully handle nulbale entities found in logs', async function () { + // mirror node request mocks + restMock.onGet(`blocks/${BLOCK_HASH}`).reply(200, DEFAULT_BLOCK); + restMock.onGet(CONTRACT_RESULTS_WITH_FILTER_URL).reply(200, defaultContractResults); + restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); + + const nullEntitiedLogs = { + logs: [{ ...DEFAULT_LOGS.logs[0], block_number: null, transaction_index: null, logIndex: null }], + }; + + restMock.onGet(CONTRACT_RESULTS_LOGS_WITH_FILTER_URL).reply(200, nullEntitiedLogs); + + const result = await ethImpl.getBlockByNumber(BLOCK_HASH, false, requestDetails); + + RelayAssertions.assertBlock(result, { + hash: BLOCK_HASH_TRIMMED, + gasUsed: TOTAL_GAS_USED, + number: BLOCK_NUMBER_HEX, + parentHash: BLOCK_HASH_PREV_TRIMMED, + timestamp: BLOCK_TIMESTAMP_HEX, + transactions: [CONTRACT_HASH_1, CONTRACT_HASH_2], + receiptsRoot: DEFAULT_BLOCK_RECEIPTS_ROOT_HASH, + }); + }); }); diff --git a/packages/relay/tests/lib/mirrorNodeClient.spec.ts b/packages/relay/tests/lib/mirrorNodeClient.spec.ts index 9e9d2fda21..1d168eb582 100644 --- a/packages/relay/tests/lib/mirrorNodeClient.spec.ts +++ b/packages/relay/tests/lib/mirrorNodeClient.spec.ts @@ -19,23 +19,25 @@ */ import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services'; -import { expect } from 'chai'; -import { Registry } from 'prom-client'; -import { MirrorNodeClient } from '../../src/lib/clients'; -import constants from '../../src/lib/constants'; import axios, { AxiosInstance } from 'axios'; import MockAdapter from 'axios-mock-adapter'; -import { getRequestId, mockData, random20BytesAddress, withOverriddenEnvsInMochaTest } from '../helpers'; -import pino from 'pino'; +import { expect } from 'chai'; import { ethers } from 'ethers'; +import pino from 'pino'; +import { Registry } from 'prom-client'; + import { MirrorNodeClientError, predefined } from '../../src'; +import { MirrorNodeClient } from '../../src/lib/clients'; +import constants from '../../src/lib/constants'; import { CacheService } from '../../src/lib/services/cacheService/cacheService'; +import { getRequestId, mockData, random20BytesAddress, withOverriddenEnvsInMochaTest } from '../helpers'; const registry = new Registry(); -import { MirrorNodeTransactionRecord, RequestDetails } from '../../src/lib/types'; -import { SDKClientError } from '../../src/lib/errors/SDKClientError'; import { BigNumber } from 'bignumber.js'; +import { SDKClientError } from '../../src/lib/errors/SDKClientError'; +import { MirrorNodeTransactionRecord, RequestDetails } from '../../src/lib/types'; + const logger = pino(); const noTransactions = '?transactions=false'; const requestDetails = new RequestDetails({ requestId: getRequestId(), ipAddress: '0.0.0.0' }); @@ -83,7 +85,7 @@ describe('MirrorNodeClient', async function () { for (const code of nullResponseCodes) { it(`returns null when ${code} is returned`, async () => { - let error = new Error('test error'); + const error = new Error('test error'); error['response'] = 'test error'; const result = mirrorNodeInstance.handleError( @@ -101,7 +103,7 @@ describe('MirrorNodeClient', async function () { for (const code of errorRepsonseCodes) { it(`throws an error when ${code} is returned`, async () => { try { - let error = new Error('test error'); + const error = new Error('test error'); error['response'] = 'test error'; mirrorNodeInstance.handleError( error, @@ -568,7 +570,11 @@ describe('MirrorNodeClient', async function () { const hash = '0x4a563af33c4871b51a8b108aa2fe1dd5280a30dfb7236170ae5e5e7957eb6399'; mock.onGet(`contracts/results/${hash}`).reply(200, detailedContractResult); - const result = await mirrorNodeInstance.getContractResultWithRetry(hash, requestDetails); + const result = await mirrorNodeInstance.getContractResultWithRetry( + mirrorNodeInstance.getContractResult.name, + [hash, requestDetails], + requestDetails, + ); expect(result).to.exist; expect(result.contract_id).equal(detailedContractResult.contract_id); expect(result.to).equal(detailedContractResult.to); @@ -585,7 +591,11 @@ describe('MirrorNodeClient', async function () { mock.onGet(`contracts/results/${hash}`).replyOnce(200, { ...detailedContractResult, transaction_index: undefined }); mock.onGet(`contracts/results/${hash}`).reply(200, detailedContractResult); - const result = await mirrorNodeInstance.getContractResultWithRetry(hash, requestDetails); + const result = await mirrorNodeInstance.getContractResultWithRetry( + mirrorNodeInstance.getContractResult.name, + [hash, requestDetails], + requestDetails, + ); expect(result).to.exist; expect(result.contract_id).equal(detailedContractResult.contract_id); expect(result.to).equal(detailedContractResult.to); @@ -601,7 +611,11 @@ describe('MirrorNodeClient', async function () { .replyOnce(200, { ...detailedContractResult, transaction_index: undefined, block_number: undefined }); mock.onGet(`contracts/results/${hash}`).reply(200, detailedContractResult); - const result = await mirrorNodeInstance.getContractResultWithRetry(hash, requestDetails); + const result = await mirrorNodeInstance.getContractResultWithRetry( + mirrorNodeInstance.getContractResult.name, + [hash, requestDetails], + requestDetails, + ); expect(result).to.exist; expect(result.contract_id).equal(detailedContractResult.contract_id); expect(result.to).equal(detailedContractResult.to); @@ -616,7 +630,11 @@ describe('MirrorNodeClient', async function () { mock.onGet(`contracts/results/${hash}`).replyOnce(200, { ...detailedContractResult, block_number: undefined }); mock.onGet(`contracts/results/${hash}`).reply(200, detailedContractResult); - const result = await mirrorNodeInstance.getContractResultWithRetry(hash, requestDetails); + const result = await mirrorNodeInstance.getContractResultWithRetry( + mirrorNodeInstance.getContractResult.name, + [hash, requestDetails], + requestDetails, + ); expect(result).to.exist; expect(result.contract_id).equal(detailedContractResult.contract_id); expect(result.to).equal(detailedContractResult.to); @@ -630,7 +648,11 @@ describe('MirrorNodeClient', async function () { mock.onGet(`contracts/results/${hash}`).replyOnce(200, { ...detailedContractResult, block_hash: '0x' }); mock.onGet(`contracts/results/${hash}`).reply(200, detailedContractResult); - const result = await mirrorNodeInstance.getContractResultWithRetry(hash, requestDetails); + const result = await mirrorNodeInstance.getContractResultWithRetry( + mirrorNodeInstance.getContractResult.name, + [hash, requestDetails], + requestDetails, + ); expect(result).to.exist; expect(result.block_hash).equal(detailedContractResult.block_hash); expect(mock.history.get.length).to.eq(2); @@ -646,7 +668,11 @@ describe('MirrorNodeClient', async function () { }); mock.onGet(`contracts/results/${hash}`).reply(200, detailedContractResult); - const result = await mirrorNodeInstance.getContractResultWithRetry(hash, requestDetails); + const result = await mirrorNodeInstance.getContractResultWithRetry( + mirrorNodeInstance.getContractResult.name, + [hash, requestDetails], + requestDetails, + ); expect(result).to.exist; expect(result.transaction_index).equal(detailedContractResult.transaction_index); expect(result.block_number).equal(detailedContractResult.block_number); @@ -769,7 +795,7 @@ describe('MirrorNodeClient', async function () { it('`getContractResultsLogs` ', async () => { mock.onGet(`contracts/results/logs?limit=100&order=asc`).reply(200, { logs: [log] }); - const results = await mirrorNodeInstance.getContractResultsLogs(requestDetails); + const results = await mirrorNodeInstance.getContractResultsLogsWithRetry(requestDetails); expect(results).to.exist; expect(results.length).to.gt(0); const firstResult = results[0]; @@ -1558,7 +1584,7 @@ describe('MirrorNodeClient', async function () { it('should fetch contract for existing contract from cache on additional calls', async () => { mock.onGet(contractPath).reply(200, mockData.contract); - let id = await mirrorNodeInstance.getContractId(evmAddress, requestDetails); + const id = await mirrorNodeInstance.getContractId(evmAddress, requestDetails); expect(id).to.exist; expect(id).to.be.equal(mockData.contract.contract_id); diff --git a/packages/server/tests/acceptance/rpc_batch1.spec.ts b/packages/server/tests/acceptance/rpc_batch1.spec.ts index 0c41b43478..ec8b8c6760 100644 --- a/packages/server/tests/acceptance/rpc_batch1.spec.ts +++ b/packages/server/tests/acceptance/rpc_batch1.spec.ts @@ -1672,6 +1672,8 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { const signedTx = await accounts[0].wallet.signTransaction(tx); const transactionHash = await relay.sendRawTransaction(signedTx, requestId); + await relay.pollForValidTransactionReceipt(transactionHash); + const info = await mirrorNode.get(`/contracts/results/${transactionHash}`, requestId); expect(info).to.exist;