diff --git a/packages/relay/src/formatters.ts b/packages/relay/src/formatters.ts index 497639730a..393c763627 100644 --- a/packages/relay/src/formatters.ts +++ b/packages/relay/src/formatters.ts @@ -18,8 +18,17 @@ * */ +import constants from "./lib/constants"; + const hashNumber = (num) => { return '0x' + num.toString(16); }; -export { hashNumber }; + /** + * Format message prefix for logger. + */ +const formatRequestIdMessage = (requestId?: string): string => { + return requestId ? `[${constants.REQUEST_ID_STRING}${requestId}]` : ''; +} + +export { hashNumber, formatRequestIdMessage }; diff --git a/packages/relay/src/index.ts b/packages/relay/src/index.ts index 5635765fe8..328fc17bef 100644 --- a/packages/relay/src/index.ts +++ b/packages/relay/src/index.ts @@ -46,75 +46,75 @@ export interface Net { export interface Eth { - blockNumber(): Promise; + blockNumber(requestId?: string): Promise; - call(call: any, blockParam: string | null): Promise; + call(call: any, blockParam: string | null, requestId?: string): Promise; - coinbase(): JsonRpcError; + coinbase(requestId?: string): JsonRpcError; - estimateGas(transaction:any, blockParam: string| null): Promise; + estimateGas(transaction:any, blockParam: string| null, requestId?: string): Promise; - gasPrice(): Promise; + gasPrice(requestId?: string): Promise; - getBalance(account: string, blockNumber: string | null): Promise; + getBalance(account: string, blockNumber: string | null, requestId?: string): Promise; - getBlockByHash(hash: string, showDetails: boolean): Promise; + getBlockByHash(hash: string, showDetails: boolean, requestId?: string): Promise; - getBlockByNumber(blockNum: string, showDetails: boolean): Promise; + getBlockByNumber(blockNum: string, showDetails: boolean, requestId?: string): Promise; - getBlockTransactionCountByHash(hash: string): Promise; + getBlockTransactionCountByHash(hash: string, requestId?: string): Promise; - getBlockTransactionCountByNumber(blockNum: string): Promise + getBlockTransactionCountByNumber(blockNum: string, requestId?: string): Promise - getCode(address: string, blockNumber: string | null): Promise; + getCode(address: string, blockNumber: string | null, requestId?: string): Promise; - chainId(): string; + chainId(requestId?: string): string; - getLogs(blockHash: string|null, fromBlock: string|null, toBlock: string|null, address: string|null, topics: any[]|null): Promise; + getLogs(blockHash: string|null, fromBlock: string|null, toBlock: string|null, address: string|null, topics: any[]|null, requestId?: string): Promise; - getStorageAt(address: string, slot: string, blockNumber: string|null): JsonRpcError; + getStorageAt(address: string, slot: string, blockNumber: string|null, requestId?: string): Promise; - getTransactionByBlockHashAndIndex(hash: string, index: number): Promise; + getTransactionByBlockHashAndIndex(hash: string, index: number, requestId?: string): Promise; - getTransactionByBlockNumberAndIndex(blockNum: string, index: number): Promise; + getTransactionByBlockNumberAndIndex(blockNum: string, index: number, requestId?: string): Promise; - getTransactionByHash(hash: string): Promise; + getTransactionByHash(hash: string, requestId?: string): Promise; - getTransactionCount(address: string, blocknum: string): Promise; + getTransactionCount(address: string, blocknum: string, requestId?: string): Promise; - getTransactionReceipt(hash: string): Promise; + getTransactionReceipt(hash: string, requestId?: string): Promise; - getUncleByBlockHashAndIndex(): Promise; + getUncleByBlockHashAndIndex(requestId?: string): Promise; - getUncleByBlockNumberAndIndex(): Promise; + getUncleByBlockNumberAndIndex(requestId?: string): Promise; - getUncleCountByBlockHash(): Promise; + getUncleCountByBlockHash(requestId?: string): Promise; - getUncleCountByBlockNumber(): Promise; + getUncleCountByBlockNumber(requestId?: string): Promise; - getWork(): JsonRpcError; + getWork(requestId?: string): JsonRpcError; - feeHistory(blockCount: number, newestBlock: string, rewardPercentiles: Array|null): Promise; + feeHistory(blockCount: number, newestBlock: string, rewardPercentiles: Array|null, requestId?: string): Promise; - hashrate(): Promise; + hashrate(requestId?: string): Promise; - mining(): Promise; + mining(requestId?: string): Promise; - protocolVersion(): JsonRpcError; + protocolVersion(requestId?: string): JsonRpcError; - sendRawTransaction(transaction: string): Promise; + sendRawTransaction(transaction: string, requestId?: string): Promise; - sendTransaction(): JsonRpcError; + sendTransaction(requestId?: string): JsonRpcError; - sign(): JsonRpcError; + sign(requestId?: string): JsonRpcError; - signTransaction(): JsonRpcError; + signTransaction(requestId?: string): JsonRpcError; - submitHashrate(): JsonRpcError; + submitHashrate(requestId?: string): JsonRpcError; - submitWork(): Promise; + submitWork(requestId?: string): Promise; - syncing(): Promise; + syncing(requestId?: string): Promise; - accounts(): Array; + accounts(requestId?: string): Array; } diff --git a/packages/relay/src/lib/clients/mirrorNodeClient.ts b/packages/relay/src/lib/clients/mirrorNodeClient.ts index 85411f6c9b..66d68b243d 100644 --- a/packages/relay/src/lib/clients/mirrorNodeClient.ts +++ b/packages/relay/src/lib/clients/mirrorNodeClient.ts @@ -23,6 +23,7 @@ import { MirrorNodeClientError } from './../errors/MirrorNodeClientError'; import { Logger } from "pino"; import constants from './../constants'; import { Histogram, Registry } from 'prom-client'; +import { formatRequestIdMessage } from '../../formatters'; export interface ILimitOrderParams { limit?: number; @@ -140,55 +141,60 @@ export class MirrorNodeClient { this.logger.info(`Mirror Node client successfully configured to ${this.baseUrl}`); } - async request(path: string, pathLabel: string, allowedErrorStatuses?: number[]): Promise { + async request(path: string, pathLabel: string, allowedErrorStatuses?: number[], requestId?: string): Promise { const start = Date.now(); + const requestIdPrefix = formatRequestIdMessage(requestId); let ms; try { const response = await this.client.get(path); ms = Date.now() - start; - this.logger.debug(`[GET] ${path} ${response.status} ${ms} ms`); + this.logger.debug(`${requestIdPrefix} [GET] ${path} ${response.status} ${ms} ms`); this.mirrorResponseHistogram.labels(pathLabel, response.status).observe(ms); return response.data; } catch (error: any) { ms = Date.now() - start; const effectiveStatusCode = error.response !== undefined ? error.response.status : MirrorNodeClient.unknownServerErrorHttpStatusCode; this.mirrorResponseHistogram.labels(pathLabel, effectiveStatusCode).observe(ms); - this.handleError(error, path, effectiveStatusCode, allowedErrorStatuses); + this.handleError(error, path, effectiveStatusCode, allowedErrorStatuses, requestId); } return null; } - handleError(error: any, path: string, effectiveStatusCode: number, allowedErrorStatuses?: number[]) { + handleError(error: any, path: string, effectiveStatusCode: number, allowedErrorStatuses?: number[], requestId?: string) { + const requestIdPrefix = formatRequestIdMessage(requestId); if (allowedErrorStatuses && allowedErrorStatuses.length) { if (error.response && allowedErrorStatuses.indexOf(effectiveStatusCode) !== -1) { - this.logger.debug(`[GET] ${path} ${effectiveStatusCode} status`); + this.logger.debug(`${requestIdPrefix} [GET] ${path} ${effectiveStatusCode} status`); return null; } } - this.logger.error(new Error(error.message), `[GET] ${path} ${effectiveStatusCode} status`); + this.logger.error(new Error(error.message), `${requestIdPrefix} [GET] ${path} ${effectiveStatusCode} status`); throw new MirrorNodeClientError(error.message, effectiveStatusCode); } - public async getAccountLatestTransactionByAddress(idOrAliasOrEvmAddress: string): Promise { + public async getAccountLatestTransactionByAddress(idOrAliasOrEvmAddress: string, requestId?: string): Promise { return this.request(`${MirrorNodeClient.GET_ACCOUNTS_ENDPOINT}${idOrAliasOrEvmAddress}?order=desc&limit=1`, MirrorNodeClient.GET_ACCOUNTS_ENDPOINT, - [400]); + [400], + requestId); } - public async getAccount(idOrAliasOrEvmAddress: string): Promise { + public async getAccount(idOrAliasOrEvmAddress: string, requestId?: string): Promise { return this.request(`${MirrorNodeClient.GET_ACCOUNTS_ENDPOINT}${idOrAliasOrEvmAddress}`, MirrorNodeClient.GET_ACCOUNTS_ENDPOINT, - [400, 404]); + [400, 404], + requestId); } - public async getBlock(hashOrBlockNumber: string | number) { + public async getBlock(hashOrBlockNumber: string | number, requestId?: string) { return this.request(`${MirrorNodeClient.GET_BLOCK_ENDPOINT}${hashOrBlockNumber}`, MirrorNodeClient.GET_BLOCK_ENDPOINT, - [400]); + [400], + requestId); } - public async getBlocks(blockNumber?: number | string[], timestamp?: string, limitOrderParams?: ILimitOrderParams) { + public async getBlocks(blockNumber?: number | string[], timestamp?: string, limitOrderParams?: ILimitOrderParams, requestId?: string) { const queryParamObject = {}; this.setQueryParam(queryParamObject, 'block.number', blockNumber); this.setQueryParam(queryParamObject, 'timestamp', timestamp); @@ -196,54 +202,62 @@ export class MirrorNodeClient { const queryParams = this.getQueryParams(queryParamObject); return this.request(`${MirrorNodeClient.GET_BLOCKS_ENDPOINT}${queryParams}`, MirrorNodeClient.GET_BLOCKS_ENDPOINT, - [400, 404]); + [400, 404], + requestId); } - public async getContract(contractIdOrAddress: string) { + public async getContract(contractIdOrAddress: string, requestId?: string) { return this.request(`${MirrorNodeClient.GET_CONTRACT_ENDPOINT}${contractIdOrAddress}`, MirrorNodeClient.GET_CONTRACT_ENDPOINT, - [400, 404]); + [400, 404], + requestId); } - public async getContractResult(transactionIdOrHash: string) { + public async getContractResult(transactionIdOrHash: string, requestId?: string) { return this.request(`${MirrorNodeClient.GET_CONTRACT_RESULT_ENDPOINT}${transactionIdOrHash}`, MirrorNodeClient.GET_CONTRACT_RESULT_ENDPOINT, - [400, 404]); + [400, 404], + requestId); } - public async getContractResults(contractResultsParams?: IContractResultsParams, limitOrderParams?: ILimitOrderParams) { + public async getContractResults(contractResultsParams?: IContractResultsParams, limitOrderParams?: ILimitOrderParams, requestId?: string) { const queryParamObject = {}; this.setContractResultsParams(queryParamObject, contractResultsParams); this.setLimitOrderParams(queryParamObject, limitOrderParams); const queryParams = this.getQueryParams(queryParamObject); return this.request(`${MirrorNodeClient.GET_CONTRACT_RESULTS_ENDPOINT}${queryParams}`, MirrorNodeClient.GET_CONTRACT_RESULTS_ENDPOINT, - [400]); + [400], + requestId); } - public async getContractResultsDetails(contractId: string, timestamp: string) { + public async getContractResultsDetails(contractId: string, timestamp: string, requestId?: string) { return this.request(`${this.getContractResultsDetailsByContractIdAndTimestamp(contractId, timestamp)}`, MirrorNodeClient.GET_CONTRACT_RESULTS_DETAILS_BY_CONTRACT_ID_ENDPOINT, - [400]); + [400], + requestId); } public async getContractResultsByAddress( contractIdOrAddress: string, contractResultsParams?: IContractResultsParams, - limitOrderParams?: ILimitOrderParams) { + limitOrderParams?: ILimitOrderParams, + requestId?: string) { const queryParamObject = {}; this.setContractResultsParams(queryParamObject, contractResultsParams); this.setLimitOrderParams(queryParamObject, limitOrderParams); const queryParams = this.getQueryParams(queryParamObject); return this.request(`${MirrorNodeClient.getContractResultsByAddressPath(contractIdOrAddress)}${queryParams}`, MirrorNodeClient.GET_CONTRACT_RESULTS_BY_ADDRESS_ENDPOINT, - [400]); + [400], + requestId); } - public async getContractResultsByAddressAndTimestamp(contractIdOrAddress: string, timestamp: string) { + public async getContractResultsByAddressAndTimestamp(contractIdOrAddress: string, timestamp: string, requestId?: string) { return this.request(`${MirrorNodeClient.getContractResultsByAddressPath(contractIdOrAddress)}/${timestamp}`, MirrorNodeClient.GET_CONTRACT_RESULTS_BY_ADDRESS_ENDPOINT, - [206, 400, 404]); + [206, 400, 404], + requestId); } private prepareLogsParams( @@ -265,17 +279,20 @@ export class MirrorNodeClient { public async getContractResultsLogs( contractLogsResultsParams?: IContractLogsResultsParams, - limitOrderParams?: ILimitOrderParams) { + limitOrderParams?: ILimitOrderParams, + requestId?: string) { const queryParams = this.prepareLogsParams(contractLogsResultsParams, limitOrderParams); return this.request(`${MirrorNodeClient.GET_CONTRACT_RESULT_LOGS_ENDPOINT}${queryParams}`, MirrorNodeClient.GET_CONTRACT_RESULT_LOGS_ENDPOINT, - [400, 404]); + [400, 404], + requestId); } public async getContractResultsLogsByAddress( address: string, contractLogsResultsParams?: IContractLogsResultsParams, - limitOrderParams?: ILimitOrderParams + limitOrderParams?: ILimitOrderParams, + requestId?: string ) { const queryParams = this.prepareLogsParams(contractLogsResultsParams, limitOrderParams); const apiEndpoint = MirrorNodeClient.GET_CONTRACT_RESULT_LOGS_BY_ADDRESS_ENDPOINT.replace( @@ -284,34 +301,37 @@ export class MirrorNodeClient { ); return this.request(`${apiEndpoint}${queryParams}`, MirrorNodeClient.GET_CONTRACT_RESULT_LOGS_BY_ADDRESS_ENDPOINT, - [400, 404]); + [400, 404], + requestId); } - public async getLatestBlock() { - return this.getBlocks(undefined, undefined, this.getLimitOrderQueryParam(1, MirrorNodeClient.ORDER.DESC)); + public async getLatestBlock(requestId?: string) { + return this.getBlocks(undefined, undefined, this.getLimitOrderQueryParam(1, MirrorNodeClient.ORDER.DESC), requestId); } public getLimitOrderQueryParam(limit: number, order: string): ILimitOrderParams { return { limit: limit, order: order }; } - public async getNetworkExchangeRate(timestamp?: string) { + public async getNetworkExchangeRate(timestamp?: string, requestId?: string) { const queryParamObject = {}; this.setQueryParam(queryParamObject, 'timestamp', timestamp); const queryParams = this.getQueryParams(queryParamObject); return this.request(`${MirrorNodeClient.GET_NETWORK_EXCHANGERATE_ENDPOINT}${queryParams}`, MirrorNodeClient.GET_NETWORK_EXCHANGERATE_ENDPOINT, - [400, 404]); + [400, 404], + requestId); } - public async getNetworkFees(timestamp?: string, order?: string) { + public async getNetworkFees(timestamp?: string, order?: string, requestId?: string) { const queryParamObject = {}; this.setQueryParam(queryParamObject, 'timestamp', timestamp); this.setQueryParam(queryParamObject, 'order', order); const queryParams = this.getQueryParams(queryParamObject); return this.request(`${MirrorNodeClient.GET_NETWORK_FEES_ENDPOINT}${queryParams}`, MirrorNodeClient.GET_NETWORK_FEES_ENDPOINT, - [400, 404]); + [400, 404], + requestId); } private static getContractResultsByAddressPath(address: string) { @@ -324,6 +344,15 @@ export class MirrorNodeClient { .replace(MirrorNodeClient.TIMESTAMP_PLACEHOLDER, timestamp); } + public async getLatestContractResultsByAddress(address: string, blockEndTimestamp: string | undefined, limit: number) { + // retrieve the timestamp of the contract + const contractResultsParams: IContractResultsParams = blockEndTimestamp + ? { timestamp: `lte:${blockEndTimestamp}` } + : {}; + const limitOrderParams: ILimitOrderParams = this.getLimitOrderQueryParam(limit, 'desc'); + return this.getContractResultsByAddress(address, contractResultsParams, limitOrderParams); + } + getQueryParams(params: object) { let paramString = ''; for (const [key, value] of Object.entries(params)) { @@ -362,15 +391,15 @@ export class MirrorNodeClient { } } - public async resolveEntityType(entityIdentifier: string) { - const contractResult = await this.getContract(entityIdentifier); + public async resolveEntityType(entityIdentifier: string, requestId?: string) { + const contractResult = await this.getContract(entityIdentifier, requestId); if (contractResult) { return { type: constants.TYPE_CONTRACT, entity: contractResult }; } - const accountResult = await this.getAccount(entityIdentifier); + const accountResult = await this.getAccount(entityIdentifier, requestId); if (accountResult) { return { type: constants.TYPE_ACCOUNT, diff --git a/packages/relay/src/lib/clients/sdkClient.ts b/packages/relay/src/lib/clients/sdkClient.ts index 388fa5f7ab..4260e1000e 100644 --- a/packages/relay/src/lib/clients/sdkClient.ts +++ b/packages/relay/src/lib/clients/sdkClient.ts @@ -44,6 +44,7 @@ import { import { BigNumber } from '@hashgraph/sdk/lib/Transfer'; import { Logger } from "pino"; import { Gauge, Histogram, Registry } from 'prom-client'; +import { formatRequestIdMessage } from '../../formatters'; import constants from './../constants'; import { SDKClientError } from './../errors/SDKClientError'; @@ -116,55 +117,55 @@ export class SDKClient { }); } - async getAccountBalance(account: string, callerName: string): Promise { + async getAccountBalance(account: string, callerName: string, requestId?: string): Promise { return this.executeQuery(new AccountBalanceQuery() - .setAccountId(AccountId.fromString(account)), this.clientMain, callerName); + .setAccountId(AccountId.fromString(account)), this.clientMain, callerName, requestId); } - async getAccountBalanceInTinyBar(account: string, callerName: string): Promise { - const balance = await this.getAccountBalance(account, callerName); + async getAccountBalanceInTinyBar(account: string, callerName: string, requestId?: string): Promise { + const balance = await this.getAccountBalance(account, callerName, requestId); return balance.hbars.to(HbarUnit.Tinybar); } - async getAccountBalanceInWeiBar(account: string, callerName: string): Promise { - const balance = await this.getAccountBalance(account, callerName); + async getAccountBalanceInWeiBar(account: string, callerName: string, requestId?: string): Promise { + const balance = await this.getAccountBalance(account, callerName, requestId); return SDKClient.HbarToWeiBar(balance); } - async getAccountInfo(address: string, callerName: string): Promise { + async getAccountInfo(address: string, callerName: string, requestId?: string): Promise { return this.executeQuery(new AccountInfoQuery() - .setAccountId(AccountId.fromString(address)), this.clientMain, callerName); + .setAccountId(AccountId.fromString(address)), this.clientMain, callerName, requestId); } - async getContractByteCode(shard: number | Long, realm: number | Long, address: string, callerName: string): Promise { + async getContractByteCode(shard: number | Long, realm: number | Long, address: string, callerName: string, requestId?: string): Promise { return this.executeQuery(new ContractByteCodeQuery() - .setContractId(ContractId.fromEvmAddress(shard, realm, address)), this.clientMain, callerName); + .setContractId(ContractId.fromEvmAddress(shard, realm, address)), this.clientMain, callerName, requestId); } - async getContractBalance(contract: string, callerName: string): Promise { + async getContractBalance(contract: string, callerName: string, requestId?: string): Promise { return this.executeQuery(new AccountBalanceQuery() - .setContractId(ContractId.fromString(contract)), this.clientMain, callerName); + .setContractId(ContractId.fromString(contract)), this.clientMain, callerName, requestId); } - async getContractBalanceInWeiBar(account: string, callerName: string): Promise { - const balance = await this.getContractBalance(account, callerName); + async getContractBalanceInWeiBar(account: string, callerName: string, requestId?: string): Promise { + const balance = await this.getContractBalance(account, callerName, requestId); return SDKClient.HbarToWeiBar(balance); } - async getExchangeRate(callerName: string): Promise { - const exchangeFileBytes = await this.getFileIdBytes(constants.EXCHANGE_RATE_FILE_ID, callerName); + async getExchangeRate(callerName: string, requestId?: string): Promise { + const exchangeFileBytes = await this.getFileIdBytes(constants.EXCHANGE_RATE_FILE_ID, callerName, requestId); return ExchangeRates.fromBytes(exchangeFileBytes); } - async getFeeSchedule(callerName: string): Promise { - const feeSchedulesFileBytes = await this.getFileIdBytes(constants.FEE_SCHEDULE_FILE_ID, callerName); + async getFeeSchedule(callerName: string, requestId?: string): Promise { + const feeSchedulesFileBytes = await this.getFileIdBytes(constants.FEE_SCHEDULE_FILE_ID, callerName, requestId); return FeeSchedules.fromBytes(feeSchedulesFileBytes); } - async getTinyBarGasFee(callerName: string): Promise { - const feeSchedules = await this.getFeeSchedule(callerName); + async getTinyBarGasFee(callerName: string, requestId?: string): Promise { + const feeSchedules = await this.getFeeSchedule(callerName, requestId); if (_.isNil(feeSchedules.current) || feeSchedules.current?.transactionFeeSchedule === undefined) { throw new SDKClientError({}, 'Invalid FeeSchedules proto format'); } @@ -172,7 +173,7 @@ export class SDKClient { for (const schedule of feeSchedules.current?.transactionFeeSchedule) { if (schedule.hederaFunctionality?._code === constants.ETH_FUNCTIONALITY_CODE && schedule.fees !== undefined) { // get exchange rate & convert to tiny bar - const exchangeRates = await this.getExchangeRate(callerName); + const exchangeRates = await this.getExchangeRate(callerName, requestId); return this.convertGasPriceToTinyBars(schedule.fees[0].servicedata, exchangeRates); } @@ -181,21 +182,21 @@ export class SDKClient { throw new SDKClientError({}, `${constants.ETH_FUNCTIONALITY_CODE} code not found in feeSchedule`); } - async getFileIdBytes(address: string, callerName: string): Promise { + async getFileIdBytes(address: string, callerName: string, requestId?: string): Promise { return this.executeQuery(new FileContentsQuery() - .setFileId(address), this.clientMain, callerName); + .setFileId(address), this.clientMain, callerName, requestId); } async getRecord(transactionResponse: TransactionResponse) { return transactionResponse.getRecord(this.clientMain); } - async submitEthereumTransaction(transactionBuffer: Uint8Array, callerName: string): Promise { + async submitEthereumTransaction(transactionBuffer: Uint8Array, callerName: string, requestId?: string): Promise { return this.executeTransaction(new EthereumFlow() - .setEthereumData(transactionBuffer), callerName); + .setEthereumData(transactionBuffer), callerName, requestId); } - async submitContractCallQuery(to: string, data: string, gas: number, from: string, callerName: string): Promise { + async submitContractCallQuery(to: string, data: string, gas: number, from: string, callerName: string, requestId?: string): Promise { const contract = SDKClient.prune0x(to); const contractId = contract.startsWith("00000000000") ? ContractId.fromSolidityAddress(contract) @@ -222,7 +223,7 @@ export class SDKClient { const cost = await contractCallQuery .getCost(this.clientMain); return this.executeQuery(contractCallQuery - .setQueryPayment(cost), this.clientMain, callerName); + .setQueryPayment(cost), this.clientMain, callerName, requestId); } private convertGasPriceToTinyBars = (feeComponents: FeeComponents | undefined, exchangeRates: ExchangeRates) => { @@ -237,12 +238,13 @@ export class SDKClient { ); }; - private executeQuery = async (query: Query, client: Client, callerName: string) => { + private executeQuery = async (query: Query, client: Client, callerName: string, requestId?: string) => { + const requestIdPrefix = formatRequestIdMessage(requestId); try { const resp = await query.execute(client); - this.logger.info(`Consensus Node query response: ${query.constructor.name} ${Status.Success._code}`); + this.logger.info(`${requestIdPrefix} Consensus Node query response: ${query.constructor.name} ${Status.Success._code}`); // local free queries will have a '0.0.0' accountId on transactionId - this.logger.trace(`${query.paymentTransactionId} query cost ${query._queryPayment}`); + this.logger.trace(`${requestIdPrefix} ${query.paymentTransactionId} query cost ${query._queryPayment}`); this.captureMetrics( SDKClient.queryMode, @@ -255,7 +257,7 @@ export class SDKClient { catch (e: any) { const sdkClientError = new SDKClientError(e); if(sdkClientError.isValidNetworkError()) { - this.logger.debug(`Consensus Node query response: ${query.constructor.name} ${sdkClientError.statusCode}`); + this.logger.debug(`${requestIdPrefix} Consensus Node query response: ${query.constructor.name} ${sdkClientError.statusCode}`); this.captureMetrics( SDKClient.queryMode, query.constructor.name, @@ -268,18 +270,19 @@ export class SDKClient { } }; - private executeTransaction = async (transaction: Transaction | EthereumFlow, callerName: string): Promise => { + private executeTransaction = async (transaction: Transaction | EthereumFlow, callerName: string, requestId?: string): Promise => { const transactionType = transaction.constructor.name; + const requestIdPrefix = formatRequestIdMessage(requestId); try { - this.logger.info(`Execute ${transactionType} transaction`); + this.logger.info(`${requestIdPrefix} Execute ${transactionType} transaction`); const resp = await transaction.execute(this.clientMain); - this.logger.info(`Consensus Node ${transactionType} transaction response: ${resp.transactionId.toString()} ${Status.Success._code}`); + this.logger.info(`${requestIdPrefix} Consensus Node ${transactionType} transaction response: ${resp.transactionId.toString()} ${Status.Success._code}`); return resp; } catch (e: any) { const sdkClientError = new SDKClientError(e); if(sdkClientError.isValidNetworkError()) { - this.logger.info(`Consensus Node ${transactionType} transaction response: ${sdkClientError.statusCode}`); + this.logger.info(`${requestIdPrefix} Consensus Node ${transactionType} transaction response: ${sdkClientError.statusCode}`); this.captureMetrics( SDKClient.transactionMode, transactionType, @@ -292,14 +295,15 @@ export class SDKClient { } }; - executeGetTransactionRecord = async (resp: TransactionResponse, transactionName: string, callerName: string): Promise => { + executeGetTransactionRecord = async (resp: TransactionResponse, transactionName: string, callerName: string, requestId?: string): Promise => { + const requestIdPrefix = formatRequestIdMessage(requestId); try { if (!resp.getRecord) { - throw new SDKClientError({}, `Invalid response format, expected record availability: ${JSON.stringify(resp)}`); + throw new SDKClientError({}, `${requestIdPrefix} Invalid response format, expected record availability: ${JSON.stringify(resp)}`); } const transactionRecord: TransactionRecord = await resp.getRecord(this.clientMain); - this.logger.trace(`${resp.transactionId.toString()} transaction cost: ${transactionRecord.transactionFee}`); + this.logger.trace(`${requestIdPrefix} ${resp.transactionId.toString()} transaction cost: ${transactionRecord.transactionFee}`); this.captureMetrics( SDKClient.transactionMode, transactionName, diff --git a/packages/relay/src/lib/constants.ts b/packages/relay/src/lib/constants.ts index d319d6816f..8745c35123 100644 --- a/packages/relay/src/lib/constants.ts +++ b/packages/relay/src/lib/constants.ts @@ -53,4 +53,5 @@ export default { TX_DEFAULT_GAS: 400_000, TX_CREATE_EXTRA: 32_000, TX_DATA_ZERO_COST: 4, + REQUEST_ID_STRING: `Request ID: ` }; diff --git a/packages/relay/src/lib/errors/JsonRpcError.ts b/packages/relay/src/lib/errors/JsonRpcError.ts index 47f1d14d3a..cebf0f04d3 100644 --- a/packages/relay/src/lib/errors/JsonRpcError.ts +++ b/packages/relay/src/lib/errors/JsonRpcError.ts @@ -111,4 +111,9 @@ export const predefined = { code: -32010, message: 'Request timeout. Please try again.' }), + 'RESOURCE_NOT_FOUND': new JsonRpcError({ + name: 'Resource not found', + code: -32001, + message: 'Requested resource not found' + }), }; diff --git a/packages/relay/src/lib/eth.ts b/packages/relay/src/lib/eth.ts index 7bf9b77357..410c9d1301 100644 --- a/packages/relay/src/lib/eth.ts +++ b/packages/relay/src/lib/eth.ts @@ -29,6 +29,7 @@ import { SDKClientError } from './errors/SDKClientError'; import { MirrorNodeClientError } from './errors/MirrorNodeClientError'; import constants from './constants'; import { Precheck } from './precheck'; +import { formatRequestIdMessage } from '../formatters'; const _ = require('lodash'); const cache = require('js-cache'); @@ -68,6 +69,10 @@ export class EthImpl implements Eth { static ethGetTransactionCount = 'eth_getTransactionCount'; static ethSendRawTransaction = 'eth_sendRawTransaction'; + // block constants + static blockLatest = 'latest'; + static blockEarliest = 'earliest'; + static blockPending = 'pending'; /** * The sdk client use for connecting to both the consensus nodes and mirror node. The account @@ -125,19 +130,21 @@ export class EthImpl implements Eth { * This method is implemented to always return an empty array. This is in alignment * with the behavior of Infura. */ - accounts() { - this.logger.trace('accounts()'); + accounts(requestId?: string) { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} accounts()`); return []; } /** * Gets the fee history. */ - async feeHistory(blockCount: number, newestBlock: string, rewardPercentiles: Array | null) { - this.logger.trace(`feeHistory(blockCount=${blockCount}, newestBlock=${newestBlock}, rewardPercentiles=${rewardPercentiles})`); + async feeHistory(blockCount: number, newestBlock: string, rewardPercentiles: Array | null, requestId?: string) { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} feeHistory(blockCount=${blockCount}, newestBlock=${newestBlock}, rewardPercentiles=${rewardPercentiles})`); try { - const latestBlockNumber = await this.translateBlockTag('latest'); - const newestBlockNumber = await this.translateBlockTag(newestBlock); + const latestBlockNumber = await this.translateBlockTag(EthImpl.blockLatest, requestId); + const newestBlockNumber = await this.translateBlockTag(newestBlock, requestId); if (newestBlockNumber > latestBlockNumber) { return predefined.REQUEST_BEYOND_HEAD_BLOCK(newestBlockNumber, latestBlockNumber); @@ -152,33 +159,33 @@ export class EthImpl implements Eth { let feeHistory: object | undefined = cache.get(constants.CACHE_KEY.FEE_HISTORY); if (!feeHistory) { - feeHistory = await this.getFeeHistory(blockCount, newestBlockNumber, latestBlockNumber, rewardPercentiles); + feeHistory = await this.getFeeHistory(blockCount, newestBlockNumber, latestBlockNumber, rewardPercentiles, requestId); - this.logger.trace(`caching ${constants.CACHE_KEY.FEE_HISTORY} for ${constants.CACHE_TTL.ONE_HOUR} ms`); + this.logger.trace(`${requestIdPrefix} caching ${constants.CACHE_KEY.FEE_HISTORY} for ${constants.CACHE_TTL.ONE_HOUR} ms`); cache.set(constants.CACHE_KEY.FEE_HISTORY, feeHistory, constants.CACHE_TTL.ONE_HOUR); } return feeHistory; } catch (e) { - this.logger.error(e, 'Error constructing default feeHistory'); + this.logger.error(e, `${requestIdPrefix} Error constructing default feeHistory`); return EthImpl.feeHistoryEmptyResponse; } } - private async getFeeByBlockNumber(blockNumber: number): Promise { + private async getFeeByBlockNumber(blockNumber: number, requestId?: string): Promise { let fee = 0; - + const requestIdPrefix = formatRequestIdMessage(requestId); try { - const block = await this.mirrorNodeClient.getBlock(blockNumber); - fee = await this.getFeeWeibars(EthImpl.ethFeeHistory, `lte:${block.timestamp.to}`); + const block = await this.mirrorNodeClient.getBlock(blockNumber, requestId); + fee = await this.getFeeWeibars(EthImpl.ethFeeHistory, requestId, `lte:${block.timestamp.to}`); } catch (error) { - this.logger.warn(error, `Fee history cannot retrieve block or fee. Returning ${fee} fee for block ${blockNumber}`); + this.logger.warn(error, `${requestIdPrefix} Fee history cannot retrieve block or fee. Returning ${fee} fee for block ${blockNumber}`); } return EthImpl.numberTo0x(fee); } - private async getFeeHistory(blockCount: number, newestBlockNumber: number, latestBlockNumber: number, rewardPercentiles: Array | null) { + private async getFeeHistory(blockCount: number, newestBlockNumber: number, latestBlockNumber: number, rewardPercentiles: Array | null, requestId?: string) { // include newest block number in the total block count const oldestBlockNumber = Math.max(0, newestBlockNumber - blockCount + 1); const shouldIncludeRewards = Array.isArray(rewardPercentiles) && rewardPercentiles.length > 0; @@ -190,7 +197,7 @@ export class EthImpl implements Eth { // get fees from oldest to newest blocks for (let blockNumber = oldestBlockNumber; blockNumber <= newestBlockNumber; blockNumber++) { - const fee = await this.getFeeByBlockNumber(blockNumber); + const fee = await this.getFeeByBlockNumber(blockNumber, requestId); feeHistory.baseFeePerGas.push(fee); feeHistory.gasUsedRatio.push(EthImpl.defaultGasUsedRatio); @@ -201,7 +208,7 @@ export class EthImpl implements Eth { if (latestBlockNumber > newestBlockNumber) { // get next block fee if the newest block is not the latest - nextBaseFeePerGas = await this.getFeeByBlockNumber(newestBlockNumber + 1); + nextBaseFeePerGas = await this.getFeeByBlockNumber(newestBlockNumber + 1, requestId); } if (nextBaseFeePerGas) { @@ -215,23 +222,23 @@ export class EthImpl implements Eth { return feeHistory; } - private async getFeeWeibars(callerName: string, timestamp?: string) { + private async getFeeWeibars(callerName: string, requestId?: string, timestamp?: string) { let networkFees; - + const requestIdPrefix = formatRequestIdMessage(requestId); try { - networkFees = await this.mirrorNodeClient.getNetworkFees(timestamp); + networkFees = await this.mirrorNodeClient.getNetworkFees(timestamp,undefined, requestId); if (_.isNil(networkFees)) { - this.logger.debug(`Mirror Node returned no fees. Fallback to network`); + this.logger.debug(`${requestIdPrefix} Mirror Node returned no fees. Fallback to network`); } } catch (e: any) { - this.logger.warn(e, `Mirror Node threw an error retrieving fees. Fallback to network`); + this.logger.warn(e, `${requestIdPrefix} Mirror Node threw an error retrieving fees. Fallback to network`); } if (_.isNil(networkFees)) { networkFees = { fees: [ { - gas: await this.sdkClient.getTinyBarGasFee(callerName), + gas: await this.sdkClient.getTinyBarGasFee(callerName, requestId), 'transaction_type': EthImpl.ethTxType } ] @@ -257,16 +264,17 @@ export class EthImpl implements Eth { /** * Gets the most recent block number. */ - async blockNumber(): Promise { - this.logger.trace('blockNumber()'); + async blockNumber(requestId?: string): Promise { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} blockNumber()`); - const blocksResponse = await this.mirrorNodeClient.getLatestBlock(); + const blocksResponse = await this.mirrorNodeClient.getLatestBlock(requestId); const blocks = blocksResponse !== null ? blocksResponse.blocks : null; if (Array.isArray(blocks) && blocks.length > 0) { return EthImpl.numberTo0x(blocks[0].number); } - throw new Error('Error encountered retrieving latest block'); + throw new Error(`Error encountered retrieving latest block`); } /** @@ -274,8 +282,9 @@ export class EthImpl implements Eth { * the same value. This can be specified via an environment variable * `CHAIN_ID`. */ - chainId(): string { - this.logger.trace('chainId()'); + chainId(requestId?: string): string { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} chainId()`); return this.chain; } @@ -283,8 +292,9 @@ export class EthImpl implements Eth { * Estimates the amount of gas to execute a call. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - async estimateGas(transaction: any, _blockParam: string | null) { - this.logger.trace('estimateGas()'); + async estimateGas(transaction: any, _blockParam: string | null, requestId?: string) { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} estimateGas()`); if (!transaction || !transaction.data || transaction.data === '0x') { return EthImpl.gasTxBaseCost; } else { @@ -295,15 +305,16 @@ export class EthImpl implements Eth { /** * Gets the current gas price of the network. */ - async gasPrice() { - this.logger.trace('gasPrice()'); + async gasPrice(requestId?: string) { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} gasPrice()`); try { let gasPrice: number | undefined = cache.get(constants.CACHE_KEY.GAS_PRICE); if (!gasPrice) { - gasPrice = await this.getFeeWeibars(EthImpl.ethGasPrice); + gasPrice = await this.getFeeWeibars(EthImpl.ethGasPrice, requestId); - this.logger.trace(`caching ${constants.CACHE_KEY.GAS_PRICE} for ${constants.CACHE_TTL.ONE_HOUR} ms`); + this.logger.trace(`${requestIdPrefix} caching ${constants.CACHE_KEY.GAS_PRICE} for ${constants.CACHE_TTL.ONE_HOUR} ms`); cache.set(constants.CACHE_KEY.GAS_PRICE, gasPrice, constants.CACHE_TTL.ONE_HOUR); } @@ -317,111 +328,160 @@ export class EthImpl implements Eth { /** * Gets whether this "Ethereum client" is a miner. We don't mine, so this always returns false. */ - async mining() { - this.logger.trace('mining()'); + async mining(requestId?: string) { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} mining()`); return false; } /** * TODO Needs docs, or be removed? */ - async submitWork() { - this.logger.trace('submitWork()'); + async submitWork(requestId?: string) { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} submitWork()`); return false; } /** * TODO Needs docs, or be removed? */ - async syncing() { - this.logger.trace('syncing()'); + async syncing(requestId?: string) { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} syncing()`); return false; } /** * Always returns null. There are no uncles in Hedera. */ - async getUncleByBlockHashAndIndex() { - this.logger.trace('getUncleByBlockHashAndIndex()'); + async getUncleByBlockHashAndIndex(requestId?: string) { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} getUncleByBlockHashAndIndex()`); return null; } /** * Always returns null. There are no uncles in Hedera. */ - async getUncleByBlockNumberAndIndex() { - this.logger.trace('getUncleByBlockNumberAndIndex()'); + async getUncleByBlockNumberAndIndex(requestId?: string) { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} getUncleByBlockNumberAndIndex()`); return null; } /** * Always returns '0x0'. There are no uncles in Hedera. */ - async getUncleCountByBlockHash() { - this.logger.trace('getUncleCountByBlockHash()'); + async getUncleCountByBlockHash(requestId?: string) { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} getUncleCountByBlockHash()`); return EthImpl.zeroHex; } /** * Always returns '0x0'. There are no uncles in Hedera. */ - async getUncleCountByBlockNumber() { - this.logger.trace('getUncleCountByBlockNumber()'); + async getUncleCountByBlockNumber(requestId?: string) { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} getUncleCountByBlockNumber()`); return EthImpl.zeroHex; } /** * TODO Needs docs, or be removed? */ - async hashrate() { - this.logger.trace('hashrate()'); + async hashrate(requestId?: string) { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} hashrate()`); return EthImpl.zeroHex; } /** * Always returns UNSUPPORTED_METHOD error. */ - getWork(): JsonRpcError { - this.logger.trace('getWork()'); + getWork(requestId?: string): JsonRpcError { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} getWork()`); return predefined.UNSUPPORTED_METHOD; } /** * Unsupported methods always return UNSUPPORTED_METHOD error. */ - submitHashrate(): JsonRpcError { - this.logger.trace('submitHashrate()'); + submitHashrate(requestId?: string): JsonRpcError { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} submitHashrate()`); return predefined.UNSUPPORTED_METHOD; } - signTransaction(): JsonRpcError { - this.logger.trace('signTransaction()'); + signTransaction(requestId?: string): JsonRpcError { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} signTransaction()`); return predefined.UNSUPPORTED_METHOD; } - sign(): JsonRpcError { - this.logger.trace('sign()'); + sign(requestId?: string): JsonRpcError { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} sign()`); return predefined.UNSUPPORTED_METHOD; } - sendTransaction(): JsonRpcError { - this.logger.trace('sendTransaction()'); + sendTransaction(requestId?: string): JsonRpcError { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} sendTransaction()`); return predefined.UNSUPPORTED_METHOD; } - protocolVersion(): JsonRpcError { - this.logger.trace('protocolVersion()'); + protocolVersion(requestId?: string): JsonRpcError { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} protocolVersion()`); return predefined.UNSUPPORTED_METHOD; } - coinbase(): JsonRpcError { - this.logger.trace('coinbase()'); + coinbase(requestId?: string): JsonRpcError { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} coinbase()`); return predefined.UNSUPPORTED_METHOD; } - getStorageAt(address: string, slot: string, blockNumber: string | null): JsonRpcError { - this.logger.trace('getStorageAt(address=%s, slot=%s, blockNumber=%s)', address, slot, blockNumber); - return predefined.UNSUPPORTED_METHOD; + /** + * Gets the value from a storage position at the given Ethereum address. + * + * @param address + * @param slot + * @param blockNumberOrTag + */ + async getStorageAt(address: string, slot: string, blockNumberOrTag?: string | null, requestId?: string) : Promise { + const requestIdPrefix = formatRequestIdMessage(requestId); + let result = EthImpl.zeroHex32Byte; // if contract or slot not found then return 32 byte 0 + const blockResponse = await this.getHistoricalBlockResponse(blockNumberOrTag, false); + const blockEndTimestamp = blockResponse?.timestamp?.to; + const contractResult = await this.mirrorNodeClient.getLatestContractResultsByAddress(address, blockEndTimestamp, 1); + + if (contractResult?.results?.length > 0) { + // retrieve the contract result details + await this.mirrorNodeClient.getContractResultsDetails(address, contractResult.results[0].timestamp) + .then( contractResultDetails => { + if(contractResultDetails && contractResultDetails.state_changes) { + // loop through the state changes to match slot and return value + for (const stateChange of contractResultDetails.state_changes) { + if(stateChange.slot === slot) { + result = stateChange.value_written; + } + } + } + }) + .catch( (e: any) => { + this.logger.error( + e, + `${requestIdPrefix} Failed to retrieve contract result details for contract address ${address} at timestamp=${contractResult.results[0].timestamp}`, + ); + throw e; + }); + } + + return result; } /** @@ -430,13 +490,14 @@ export class EthImpl implements Eth { * @param account * @param blockNumberOrTag */ - async getBalance(account: string, blockNumberOrTag: string | null) { + async getBalance(account: string, blockNumberOrTag: string | null, requestId?: string) { // FIXME: This implementation should be replaced so that instead of going to the // consensus nodes we go to the mirror nodes instead. The problem is that // the mirror nodes need to have the ability to give me the **CURRENT** // account balance *and* the account balance for any given block. - this.logger.trace('getBalance(account=%s, blockNumberOrTag=%s)', account, blockNumberOrTag); - const blockNumber = await this.translateBlockTag(blockNumberOrTag); + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} getBalance(account=${account}, blockNumberOrTag=${blockNumberOrTag})`); + const blockNumber = await this.translateBlockTag(blockNumberOrTag, requestId); const cachedLabel = `getBalance.${account}.${blockNumberOrTag}`; const cachedResponse: string | undefined = cache.get(cachedLabel); @@ -446,12 +507,12 @@ export class EthImpl implements Eth { try { let weibars: BigNumber | number = 0; - const result = await this.mirrorNodeClient.resolveEntityType(account); + const result = await this.mirrorNodeClient.resolveEntityType(account, requestId); if (result?.type === constants.TYPE_ACCOUNT) { - weibars = await this.sdkClient.getAccountBalanceInWeiBar(result.entity.account, EthImpl.ethGetBalance); + weibars = await this.sdkClient.getAccountBalanceInWeiBar(result.entity.account, EthImpl.ethGetBalance, requestId); } else if (result?.type === constants.TYPE_CONTRACT) { - weibars = await this.sdkClient.getContractBalanceInWeiBar(result.entity.contract_id, EthImpl.ethGetBalance); + weibars = await this.sdkClient.getContractBalanceInWeiBar(result.entity.contract_id, EthImpl.ethGetBalance, requestId); } return EthImpl.numberTo0x(weibars); @@ -459,13 +520,13 @@ export class EthImpl implements Eth { if(e instanceof SDKClientError) { // handle INVALID_ACCOUNT_ID if (e.isInvalidAccountId()) { - this.logger.debug(`Unable to find account ${account} in block ${JSON.stringify(blockNumber)}(${blockNumberOrTag}), returning 0x0 balance`); + this.logger.debug(`${requestIdPrefix} Unable to find account ${account} in block ${JSON.stringify(blockNumber)}(${blockNumberOrTag}), returning 0x0 balance`); cache.set(cachedLabel, EthImpl.zeroHex, constants.CACHE_TTL.ONE_HOUR); return EthImpl.zeroHex; } } - this.logger.error(e, 'Error raised during getBalance for account %s', account); + this.logger.error(e, `${requestIdPrefix} Error raised during getBalance for account ${account}`); throw e; } } @@ -476,9 +537,10 @@ export class EthImpl implements Eth { * @param address * @param blockNumber */ - async getCode(address: string, blockNumber: string | null) { + async getCode(address: string, blockNumber: string | null, requestId?: string) { + const requestIdPrefix = formatRequestIdMessage(requestId); // FIXME: This has to be reimplemented to get the data from the mirror node. - this.logger.trace('getCode(address=%s, blockNumber=%s)', address, blockNumber); + this.logger.trace(`${requestIdPrefix} getCode(address=${address}, blockNumber=${blockNumber})`); const cachedLabel = `getCode.${address}.${blockNumber}`; const cachedResponse: string | undefined = cache.get(cachedLabel); @@ -487,25 +549,25 @@ export class EthImpl implements Eth { } try { - const contract = await this.mirrorNodeClient.getContract(address); + const contract = await this.mirrorNodeClient.getContract(address, requestId); if (contract && contract.runtime_bytecode && contract.runtime_bytecode !== EthImpl.emptyHex) { return contract.runtime_bytecode; } else { - const bytecode = await this.sdkClient.getContractByteCode(0, 0, address, EthImpl.ethGetCode); + const bytecode = await this.sdkClient.getContractByteCode(0, 0, address, EthImpl.ethGetCode, requestId); return EthImpl.prepend0x(Buffer.from(bytecode).toString('hex')); } } catch (e: any) { if(e instanceof SDKClientError) { // handle INVALID_CONTRACT_ID if (e.isInvalidContractId()) { - this.logger.debug('Unable to find code for contract %s in block "%s", returning 0x0, err code: %s', address, blockNumber, e.statusCode); + this.logger.debug(`${requestIdPrefix} Unable to find code for contract ${address} in block "${blockNumber}", returning 0x0, err code: ${e.statusCode}`); cache.set(cachedLabel, EthImpl.emptyHex, constants.CACHE_TTL.ONE_HOUR); return EthImpl.emptyHex; } - this.logger.error(e, 'Error raised during getCode for address %s, err code: %s', address, e.statusCode); + this.logger.error(e, `${requestIdPrefix} Error raised during getCode for address ${address}, err code: ${e.statusCode}`); } else { - this.logger.error(e, 'Error raised during getCode for address %s', address); + this.logger.error(e, `${requestIdPrefix} Error raised during getCode for address ${address}`); } throw e; @@ -518,10 +580,11 @@ export class EthImpl implements Eth { * @param hash * @param showDetails */ - async getBlockByHash(hash: string, showDetails: boolean): Promise { - this.logger.trace('getBlockByHash(hash=%s, showDetails=%o)', hash, showDetails); - return this.getBlock(hash, showDetails).catch((e: any) => { - this.logger.error(e, 'Failed to retrieve block for hash %s', hash); + async getBlockByHash(hash: string, showDetails: boolean, requestId?: string): Promise { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} getBlockByHash(hash=${hash}, showDetails=${showDetails})`); + return this.getBlock(hash, showDetails, requestId).catch((e: any) => { + this.logger.error(e, `${requestIdPrefix} Failed to retrieve block for hash ${hash}`); return null; }); } @@ -531,10 +594,11 @@ export class EthImpl implements Eth { * @param blockNumOrTag * @param showDetails */ - async getBlockByNumber(blockNumOrTag: string, showDetails: boolean): Promise { - this.logger.trace('getBlockByNumber(blockNum=%d, showDetails=%o)', blockNumOrTag); - return this.getBlock(blockNumOrTag, showDetails).catch((e: any) => { - this.logger.error(e, 'Failed to retrieve block for blockNum %s', blockNumOrTag); + async getBlockByNumber(blockNumOrTag: string, showDetails: boolean, requestId?: string): Promise { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} getBlockByNumber(blockNum=${blockNumOrTag}, showDetails=${showDetails})`); + return this.getBlock(blockNumOrTag, showDetails, requestId).catch((e: any) => { + this.logger.error(e, `${requestIdPrefix} Failed to retrieve block for blockNum ${blockNumOrTag}`); return null; }); } @@ -544,13 +608,14 @@ export class EthImpl implements Eth { * * @param hash */ - async getBlockTransactionCountByHash(hash: string): Promise { - this.logger.trace('getBlockTransactionCountByHash(hash=%s, showDetails=%o)', hash); + async getBlockTransactionCountByHash(hash: string, requestId?: string): Promise { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} getBlockTransactionCountByHash(hash=${hash}, showDetails=%o)`); return this.mirrorNodeClient - .getBlock(hash) + .getBlock(hash, requestId) .then((block) => EthImpl.getTransactionCountFromBlockResponse(block)) .catch((e: any) => { - this.logger.error(e, 'Failed to retrieve block for hash %s', hash); + this.logger.error(e, `${requestIdPrefix} Failed to retrieve block for hash ${hash}`); return null; }); } @@ -559,14 +624,15 @@ export class EthImpl implements Eth { * Gets the number of transaction in a block by its block number. * @param blockNumOrTag */ - async getBlockTransactionCountByNumber(blockNumOrTag: string): Promise { - this.logger.trace('getBlockTransactionCountByNumber(blockNum=%s, showDetails=%o)', blockNumOrTag); - const blockNum = await this.translateBlockTag(blockNumOrTag); + async getBlockTransactionCountByNumber(blockNumOrTag: string, requestId?: string): Promise { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} getBlockTransactionCountByNumber(blockNum=${blockNumOrTag}, showDetails=%o)`); + const blockNum = await this.translateBlockTag(blockNumOrTag, requestId); return this.mirrorNodeClient - .getBlock(blockNum) + .getBlock(blockNum, requestId) .then((block) => EthImpl.getTransactionCountFromBlockResponse(block)) .catch((e: any) => { - this.logger.error(e, 'Failed to retrieve block for blockNum %s', blockNum); + this.logger.error(e, `${requestIdPrefix} Failed to retrieve block for blockNum ${blockNum}`, blockNum); return null; }); } @@ -577,17 +643,16 @@ export class EthImpl implements Eth { * @param blockHash * @param transactionIndex */ - async getTransactionByBlockHashAndIndex(blockHash: string, transactionIndex: number): Promise { - this.logger.trace('getTransactionByBlockHashAndIndex(hash=%s, index=%d)', blockHash, transactionIndex); + async getTransactionByBlockHashAndIndex(blockHash: string, transactionIndex: number, requestId?: string): Promise { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} getTransactionByBlockHashAndIndex(hash=${blockHash}, index=${transactionIndex})`); return this.mirrorNodeClient - .getContractResults({ blockHash: blockHash, transactionIndex: transactionIndex }) - .then((contractResults) => this.getTransactionFromContractResults(contractResults)) + .getContractResults({ blockHash: blockHash, transactionIndex: transactionIndex },undefined, requestId) + .then((contractResults) => this.getTransactionFromContractResults(contractResults, requestId)) .catch((e: any) => { this.logger.error( e, - 'Failed to retrieve contract result for hash %s and index=%d', - blockHash, - transactionIndex + `${requestIdPrefix} Failed to retrieve contract result for hash ${blockHash} and index=${transactionIndex}` ); return null; }); @@ -601,19 +666,19 @@ export class EthImpl implements Eth { */ async getTransactionByBlockNumberAndIndex( blockNumOrTag: string, - transactionIndex: number + transactionIndex: number, + requestId?: string ): Promise { - this.logger.trace('getTransactionByBlockNumberAndIndex(blockNum=%s, index=%d)', blockNumOrTag, transactionIndex); - const blockNum = await this.translateBlockTag(blockNumOrTag); + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} getTransactionByBlockNumberAndIndex(blockNum=${blockNumOrTag}, index=${transactionIndex})`); + const blockNum = await this.translateBlockTag(blockNumOrTag, requestId); return this.mirrorNodeClient - .getContractResults({ blockNumber: blockNum, transactionIndex: transactionIndex }) - .then((contractResults) => this.getTransactionFromContractResults(contractResults)) + .getContractResults({ blockNumber: blockNum, transactionIndex: transactionIndex },undefined, requestId) + .then((contractResults) => this.getTransactionFromContractResults(contractResults, requestId)) .catch((e: any) => { this.logger.error( e, - 'Failed to retrieve contract result for blockNum %s and index=%d', - blockNum, - transactionIndex + `${requestIdPrefix} Failed to retrieve contract result for blockNum ${blockNum} and index=${transactionIndex}` ); return null; }); @@ -628,23 +693,24 @@ export class EthImpl implements Eth { * @param address * @param blockNumOrTag */ - async getTransactionCount(address: string, blockNumOrTag: string): Promise { - this.logger.trace('getTransactionCount(address=%s, blockNumOrTag=%s)', address, blockNumOrTag); - const blockNumber = await this.translateBlockTag(blockNumOrTag); + async getTransactionCount(address: string, blockNumOrTag: string, requestId?: string): Promise { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} getTransactionCount(address=${address}, blockNumOrTag=${blockNumOrTag})`); + const blockNumber = await this.translateBlockTag(blockNumOrTag, requestId); if (blockNumber === 0) { return '0x0'; } else { try { - const result = await this.mirrorNodeClient.resolveEntityType(address); + const result = await this.mirrorNodeClient.resolveEntityType(address, requestId); if (result?.type === constants.TYPE_ACCOUNT) { - const accountInfo = await this.sdkClient.getAccountInfo(result?.entity.account, EthImpl.ethGetTransactionCount); + const accountInfo = await this.sdkClient.getAccountInfo(result?.entity.account, EthImpl.ethGetTransactionCount, requestId); return EthImpl.numberTo0x(Number(accountInfo.ethereumNonce)); } else if (result?.type === constants.TYPE_CONTRACT) { return EthImpl.numberTo0x(1); } } catch (e: any) { - this.logger.error(e, 'Error raised during getTransactionCount for address %s, block number or tag %s', address, blockNumOrTag); + this.logger.error(e, `${requestIdPrefix} Error raised during getTransactionCount for address ${address}, block number or tag ${blockNumOrTag}`); return predefined.INTERNAL_ERROR; } @@ -657,12 +723,13 @@ export class EthImpl implements Eth { * * @param transaction */ - async sendRawTransaction(transaction: string): Promise { - this.logger.trace('sendRawTransaction(transaction=%s)', transaction); + async sendRawTransaction(transaction: string, requestId?: string): Promise { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} sendRawTransaction(transaction=${transaction})`); try { - const gasPrice = await this.getFeeWeibars(EthImpl.ethSendRawTransaction); - await this.precheck.sendRawTransactionCheck(transaction, gasPrice); + const gasPrice = await this.getFeeWeibars(EthImpl.ethSendRawTransaction, requestId); + await this.precheck.sendRawTransactionCheck(transaction, gasPrice, requestId); } catch (e: any) { if (e instanceof JsonRpcError) { return e; @@ -673,24 +740,24 @@ export class EthImpl implements Eth { const transactionBuffer = Buffer.from(EthImpl.prune0x(transaction), 'hex'); try { - const contractExecuteResponse = await this.sdkClient.submitEthereumTransaction(transactionBuffer, EthImpl.ethSendRawTransaction); + const contractExecuteResponse = await this.sdkClient.submitEthereumTransaction(transactionBuffer, EthImpl.ethSendRawTransaction, requestId); try { // Wait for the record from the execution. - const record = await this.sdkClient.executeGetTransactionRecord(contractExecuteResponse, EthereumTransaction.name, EthImpl.ethSendRawTransaction); + const record = await this.sdkClient.executeGetTransactionRecord(contractExecuteResponse, EthereumTransaction.name, EthImpl.ethSendRawTransaction, requestId); if (record.ethereumHash == null) { throw new Error('The ethereumHash can never be null for an ethereum transaction, and yet it was!!'); } return EthImpl.prepend0x(Buffer.from(record.ethereumHash).toString('hex')); } catch (e) { this.logger.error(e, - 'Failed sendRawTransaction during record retrieval for transaction %s, returning computed hash', transaction); + `${requestIdPrefix} Failed sendRawTransaction during record retrieval for transaction ${transaction}, returning computed hash`); //Return computed hash if unable to retrieve EthereumHash from record due to error return EthImpl.prepend0x(createHash('keccak256').update(transactionBuffer).digest('hex')); } } catch (e: any) { this.logger.error(e, - 'Failed to successfully submit sendRawTransaction for transaction %s', transaction); + `${requestIdPrefix} Failed to successfully submit sendRawTransaction for transaction ${transaction}`); return predefined.INTERNAL_ERROR; } } @@ -701,15 +768,15 @@ export class EthImpl implements Eth { * @param call * @param blockParam */ - async call(call: any, blockParam: string | null): Promise { + async call(call: any, blockParam: string | null, requestId?: string): Promise { // FIXME: In the future this will be implemented by making calls to the mirror node. For the // time being we'll eat the cost and ask the main consensus nodes instead. - - this.logger.trace('call(hash=%o, blockParam=%s)', call, blockParam); + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} call(hash=${call}, blockParam=${blockParam})`, call, blockParam); // The "to" address must always be 42 chars. if (call.to.length != 42) { - throw new Error( - "Invalid Contract Address: '" + call.to + "'. Expected length of 42 chars but was" + call.to.length + throw new Error(requestIdPrefix+ + " Invalid Contract Address: '" + call.to + "'. Expected length of 42 chars but was" + call.to.length ); } @@ -727,13 +794,13 @@ export class EthImpl implements Eth { } // Execute the call and get the response - this.logger.debug('Making eth_call on contract %o with gas %d and call data "%s" from "%s"', call.to, gas, call.data, call.from); - const contractCallResponse = await this.sdkClient.submitContractCallQuery(call.to, call.data, gas, call.from, EthImpl.ethCall); + this.logger.debug(`${requestIdPrefix} Making eth_call on contract ${call.to} with gas ${gas} and call data "${call.data}" from "${call.from}"`, call.to, gas, call.data, call.from); + const contractCallResponse = await this.sdkClient.submitContractCallQuery(call.to, call.data, gas, call.from, EthImpl.ethCall, requestId); // FIXME Is this right? Maybe so? return EthImpl.prepend0x(Buffer.from(contractCallResponse.asBytes()).toString('hex')); } catch (e: any) { - this.logger.error(e, 'Failed to successfully submit contractCallQuery'); + this.logger.error(e, `${requestIdPrefix} Failed to successfully submit contractCallQuery`); return predefined.INTERNAL_ERROR; } } @@ -743,9 +810,10 @@ export class EthImpl implements Eth { * * @param hash */ - async getTransactionByHash(hash: string) { - this.logger.trace('getTransactionByHash(hash=%s)', hash); - const contractResult = await this.mirrorNodeClient.getContractResult(hash); + async getTransactionByHash(hash: string, requestId?: string) { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} getTransactionByHash(hash=${hash})`, hash); + const contractResult = await this.mirrorNodeClient.getContractResult(hash, requestId); if (contractResult === null || contractResult.hash === undefined) { return null; } @@ -783,11 +851,12 @@ export class EthImpl implements Eth { * * @param hash */ - async getTransactionReceipt(hash: string) { - this.logger.trace(`getTransactionReceipt(${hash})`); - const receiptResponse = await this.mirrorNodeClient.getContractResult(hash); + async getTransactionReceipt(hash: string, requestId?: string) { + const requestIdPrefix = formatRequestIdMessage(requestId); + this.logger.trace(`${requestIdPrefix} getTransactionReceipt(${hash})`); + const receiptResponse = await this.mirrorNodeClient.getContractResult(hash, requestId); if (receiptResponse === null || receiptResponse.hash === undefined) { - this.logger.trace(`no receipt for ${hash}`); + this.logger.trace(`${requestIdPrefix} no receipt for ${hash}`); // block not found return null; } else { @@ -834,7 +903,7 @@ export class EthImpl implements Eth { }; - this.logger.trace(`receipt for ${hash} found in block ${receipt.blockNumber}`); + this.logger.trace(`${requestIdPrefix} receipt for ${hash} found in block ${receipt.blockNumber}`); return receipt; } } @@ -880,10 +949,10 @@ export class EthImpl implements Eth { * @param tag null, a number, or 'latest', 'pending', or 'earliest' * @private */ - private async translateBlockTag(tag: string | null): Promise { - if (tag === null || tag === 'latest' || tag === 'pending') { - return Number(await this.blockNumber()); - } else if (tag === 'earliest') { + private async translateBlockTag(tag: string | null, requestId?: string): Promise { + if (tag === null || tag === EthImpl.blockLatest || tag === EthImpl.blockPending) { + return Number(await this.blockNumber(requestId)); + } else if (tag === EthImpl.blockEarliest) { return 0; } else { return Number(tag); @@ -900,29 +969,12 @@ export class EthImpl implements Eth { * @param blockHashOrNumber * @param showDetails */ - private async getBlock(blockHashOrNumber: string, showDetails: boolean): Promise { - let blockResponse: any; - if (blockHashOrNumber == null || blockHashOrNumber == 'latest' || blockHashOrNumber == 'pending') { - const blockPromise = this.mirrorNodeClient.getLatestBlock(); - const blockAnswer = await blockPromise; - blockResponse = blockAnswer.blocks[0]; - } else if (blockHashOrNumber == 'earliest') { - blockResponse = await this.mirrorNodeClient.getBlock(0); - } else if (blockHashOrNumber.length < 32) { - // anything less than 32 characters is treated as a number - blockResponse = await this.mirrorNodeClient.getBlock(Number(blockHashOrNumber)); - } else { - blockResponse = await this.mirrorNodeClient.getBlock(blockHashOrNumber); - } - - if (_.isNil(blockResponse) || blockResponse.hash === undefined) { - // block not found - return null; - } + private async getBlock(blockHashOrNumber: string, showDetails: boolean, requestId?: string ): Promise { + const blockResponse = await this.getHistoricalBlockResponse(blockHashOrNumber, true); const timestampRange = blockResponse.timestamp; const timestampRangeParams = [`gte:${timestampRange.from}`, `lte:${timestampRange.to}`]; - const contractResults = await this.mirrorNodeClient.getContractResults({ timestamp: timestampRangeParams }); + const contractResults = await this.mirrorNodeClient.getContractResults({ timestamp: timestampRangeParams },undefined, requestId); const maxGasLimit = constants.BLOCK_GAS_LIMIT; const gasUsed = blockResponse.gas_used; @@ -939,7 +991,7 @@ export class EthImpl implements Eth { for (const result of contractResults.results) { // depending on stage of contract execution revert the result.to value may be null if (!_.isNil(result.to)) { - const transaction = await this.getTransactionFromContractResult(result.to, result.timestamp); + const transaction = await this.getTransactionFromContractResult(result.to, result.timestamp, requestId); if (transaction !== null) { if (showDetails) { transactionObjects.push(transaction); @@ -953,7 +1005,7 @@ export class EthImpl implements Eth { const blockHash = blockResponse.hash.substring(0, 66); const transactionArray = showDetails ? transactionObjects : transactionHashes; return new Block({ - baseFeePerGas: await this.gasPrice(), + baseFeePerGas: await this.gasPrice(requestId), difficulty: EthImpl.zeroHex, extraData: EthImpl.emptyHex, gasLimit: EthImpl.numberTo0x(maxGasLimit), @@ -977,6 +1029,41 @@ export class EthImpl implements Eth { }); } + /** + * returns the block response + * otherwise return undefined. + * + * @param blockNumberOrTag + * @param returnLatest + */ + private async getHistoricalBlockResponse(blockNumberOrTag?: string | null, returnLatest?: boolean): Promise { + let blockResponse: any; + // Determine if the latest block should be returned and if not then just return null + if (!returnLatest && + (blockNumberOrTag == null || blockNumberOrTag === EthImpl.blockLatest || blockNumberOrTag === EthImpl.blockPending)) { + return null; + } + + if (blockNumberOrTag == null || blockNumberOrTag === EthImpl.blockLatest || blockNumberOrTag === EthImpl.blockPending) { + const blockPromise = this.mirrorNodeClient.getLatestBlock(); + const blockAnswer = await blockPromise; + blockResponse = blockAnswer.blocks[0]; + } else if (blockNumberOrTag == EthImpl.blockEarliest) { + blockResponse = await this.mirrorNodeClient.getBlock(0); + } else if (blockNumberOrTag.length < 32) { + // anything less than 32 characters is treated as a number + blockResponse = await this.mirrorNodeClient.getBlock(Number(blockNumberOrTag)); + } else { + blockResponse = await this.mirrorNodeClient.getBlock(blockNumberOrTag); + } + if (_.isNil(blockResponse) || blockResponse.hash === undefined) { + // block not found. + throw predefined.RESOURCE_NOT_FOUND; + } + + return blockResponse; + } + private static getTransactionCountFromBlockResponse(block: any) { if (block === null || block.count === undefined) { // block not found @@ -986,7 +1073,7 @@ export class EthImpl implements Eth { return EthImpl.numberTo0x(block.count); } - private getTransactionFromContractResults(contractResults: any) { + private getTransactionFromContractResults(contractResults: any, requestId?: string) { if (contractResults.results === undefined) { // contract result not found return null; @@ -998,12 +1085,13 @@ export class EthImpl implements Eth { return null; } - return this.getTransactionFromContractResult(contractResult.to, contractResult.timestamp); + return this.getTransactionFromContractResult(contractResult.to, contractResult.timestamp, requestId); } - private async getTransactionFromContractResult(to: string, timestamp: string): Promise { + private async getTransactionFromContractResult(to: string, timestamp: string, requestId?: string): Promise { // call mirror node by id and timestamp for further details - return this.mirrorNodeClient.getContractResultsByAddressAndTimestamp(to, timestamp) + const requestIdPrefix = formatRequestIdMessage(requestId); + return this.mirrorNodeClient.getContractResultsByAddressAndTimestamp(to, timestamp, requestId) .then(contractResultDetails => { const rSig = contractResultDetails.r === null ? null : contractResultDetails.r.substring(0, 66); const sSig = contractResultDetails.s === null ? null : contractResultDetails.s.substring(0, 66); @@ -1032,19 +1120,17 @@ export class EthImpl implements Eth { .catch((e: any) => { this.logger.error( e, - 'Failed to retrieve contract result details for contract address %s at timestamp=%s', - to, - timestamp + `${requestIdPrefix} Failed to retrieve contract result details for contract address ${to} at timestamp=${timestamp}` ); return null; }); } - async getLogs(blockHash: string | null, fromBlock: string | null, toBlock: string | null, address: string | null, topics: any[] | null): Promise { + async getLogs(blockHash: string | null, fromBlock: string | null, toBlock: string | null, address: string | null, topics: any[] | null, requestId?: string): Promise { const params: any = {}; if (blockHash) { try { - const block = await this.mirrorNodeClient.getBlock(blockHash); + const block = await this.mirrorNodeClient.getBlock(blockHash, requestId); if (block) { params.timestamp = [ `gte:${block.timestamp.from}`, @@ -1072,7 +1158,7 @@ export class EthImpl implements Eth { filters.push(`gte:${parseInt(fromBlock)}`); order = constants.ORDER.ASC; } - const blocksResult = await this.mirrorNodeClient.getBlocks(filters, undefined, {order}); + const blocksResult = await this.mirrorNodeClient.getBlocks(filters, undefined, {order}, requestId); const blocks = blocksResult?.blocks; if (blocks?.length) { @@ -1093,10 +1179,10 @@ export class EthImpl implements Eth { let result; if (address) { - result = await this.mirrorNodeClient.getContractResultsLogsByAddress(address, params); + result = await this.mirrorNodeClient.getContractResultsLogsByAddress(address, params, undefined, requestId); } else { - result = await this.mirrorNodeClient.getContractResultsLogs(params); + result = await this.mirrorNodeClient.getContractResultsLogs(params, undefined, requestId); } if (!result || !result.logs) { @@ -1115,7 +1201,8 @@ export class EthImpl implements Eth { uniquePairs[timestamp] = [i]; promises.push(this.mirrorNodeClient.getContractResultsDetails( log.contract_id, - log.timestamp + log.timestamp, + requestId )); } else { diff --git a/packages/relay/src/lib/precheck.ts b/packages/relay/src/lib/precheck.ts index dcf0279b41..efa92858d4 100644 --- a/packages/relay/src/lib/precheck.ts +++ b/packages/relay/src/lib/precheck.ts @@ -25,6 +25,7 @@ import { EthImpl } from './eth'; import { Logger } from 'pino'; import constants from './constants'; import { Transaction } from 'ethers'; +import { formatRequestIdMessage } from '../formatters'; export class Precheck { private mirrorNodeClient: MirrorNodeClient; @@ -55,21 +56,21 @@ export class Precheck { * @param transaction * @param gasPrice */ - async sendRawTransactionCheck(transaction: string, gasPrice: number) { + async sendRawTransactionCheck(transaction: string, gasPrice: number, requestId?: string) { const parsedTx = Precheck.parseTxIfNeeded(transaction); - this.gasLimit(parsedTx); - await this.nonce(parsedTx); - this.chainId(parsedTx); + this.gasLimit(parsedTx, requestId); + await this.nonce(parsedTx, requestId); + this.chainId(parsedTx, requestId); this.value(parsedTx); - this.gasPrice(parsedTx, gasPrice); - await this.balance(parsedTx, EthImpl.ethSendRawTransaction); + this.gasPrice(parsedTx, gasPrice, requestId); + await this.balance(parsedTx, EthImpl.ethSendRawTransaction, requestId); } /** * @param tx */ - async nonce(tx: Transaction) { + async nonce(tx: Transaction, requestId?: string) { const rsTx = await ethers.utils.resolveProperties({ gasPrice: tx.gasPrice, gasLimit: tx.gasLimit, @@ -85,7 +86,7 @@ export class Precheck { // @ts-ignore ethers.utils.joinSignature({ 'r': tx.r, 's': tx.s, 'v': tx.v }) ); - const accountInfo = await this.mirrorNodeClient.getAccount(recoveredAddress); + const accountInfo = await this.mirrorNodeClient.getAccount(recoveredAddress, requestId); // @ts-ignore if (accountInfo && accountInfo.ethereum_nonce > tx.nonce) { @@ -96,11 +97,12 @@ export class Precheck { /** * @param tx */ - chainId(tx: Transaction) { + chainId(tx: Transaction, requestId?: string) { + const requestIdPrefix = formatRequestIdMessage(requestId); const txChainId = EthImpl.prepend0x(Number(tx.chainId).toString(16)); const passes = txChainId === this.chain; if (!passes) { - this.logger.trace('Failed chainId precheck for sendRawTransaction(transaction=%s, chainId=%s)', JSON.stringify(tx), txChainId); + this.logger.trace(`${requestIdPrefix} Failed chainId precheck for sendRawTransaction(transaction=%s, chainId=%s)`, JSON.stringify(tx), txChainId); throw predefined.UNSUPPORTED_CHAIN_ID(txChainId, this.chain); } } @@ -109,13 +111,14 @@ export class Precheck { * @param tx * @param gasPrice */ - gasPrice(tx: Transaction, gasPrice: number) { + gasPrice(tx: Transaction, gasPrice: number, requestId?: string) { + const requestIdPrefix = formatRequestIdMessage(requestId); const minGasPrice = ethers.ethers.BigNumber.from(gasPrice); const txGasPrice = tx.gasPrice || tx.maxFeePerGas!.add(tx.maxPriorityFeePerGas!); const passes = txGasPrice.gte(minGasPrice); if (!passes) { - this.logger.trace('Failed gas price precheck for sendRawTransaction(transaction=%s, gasPrice=%s, requiredGasPrice=%s)', JSON.stringify(tx), txGasPrice, minGasPrice); + this.logger.trace(`${requestIdPrefix} Failed gas price precheck for sendRawTransaction(transaction=%s, gasPrice=%s, requiredGasPrice=%s)`, JSON.stringify(tx), txGasPrice, minGasPrice); throw predefined.GAS_PRICE_TOO_LOW; } } @@ -124,7 +127,8 @@ export class Precheck { * @param tx * @param callerName */ - async balance(tx: Transaction, callerName: string) { + async balance(tx: Transaction, callerName: string, requestId?: string) { + const requestIdPrefix = formatRequestIdMessage(requestId); const result = { passes: false, error: predefined.INSUFFICIENT_ACCOUNT_BALANCE @@ -134,17 +138,17 @@ export class Precheck { let tinybars; try { - const { account }: any = await this.mirrorNodeClient.getAccount(tx.from!); - tinybars = await this.sdkClient.getAccountBalanceInTinyBar(account, callerName); + const { account }: any = await this.mirrorNodeClient.getAccount(tx.from!, requestId); + tinybars = await this.sdkClient.getAccountBalanceInTinyBar(account, callerName, requestId); result.passes = ethers.ethers.BigNumber.from(tinybars.toString()).mul(constants.TINYBAR_TO_WEIBAR_COEF).gte(txTotalValue); } catch (error: any) { - this.logger.trace('Error on balance precheck for sendRawTransaction(transaction=%s, totalValue=%s, error=%s)', JSON.stringify(tx), txTotalValue, error.message); + this.logger.trace(`${requestIdPrefix} Error on balance precheck for sendRawTransaction(transaction=%s, totalValue=%s, error=%s)`, JSON.stringify(tx), txTotalValue, error.message); throw predefined.INTERNAL_ERROR; } if (!result.passes) { - this.logger.trace('Failed balance precheck for sendRawTransaction(transaction=%s, totalValue=%s, accountTinyBarBalance=%s)', JSON.stringify(tx), txTotalValue, tinybars); + this.logger.trace(`${requestIdPrefix} Failed balance precheck for sendRawTransaction(transaction=%s, totalValue=%s, accountTinyBarBalance=%s)`, JSON.stringify(tx), txTotalValue, tinybars); throw predefined.INSUFFICIENT_ACCOUNT_BALANCE; } } @@ -152,7 +156,8 @@ export class Precheck { /** * @param tx */ - gasLimit(tx: Transaction) { + gasLimit(tx: Transaction, requestId?: string) { + const requestIdPrefix = formatRequestIdMessage(requestId); const gasLimit = tx.gasLimit.toNumber(); const failBaseLog = 'Failed gasLimit precheck for sendRawTransaction(transaction=%s).'; @@ -160,10 +165,10 @@ export class Precheck { if (gasLimit > constants.BLOCK_GAS_LIMIT) { - this.logger.trace(`${failBaseLog} Gas Limit was too high: %s, block gas limit: %s`, JSON.stringify(tx), gasLimit, constants.BLOCK_GAS_LIMIT); + this.logger.trace(`${requestIdPrefix} ${failBaseLog} Gas Limit was too high: %s, block gas limit: %s`, JSON.stringify(tx), gasLimit, constants.BLOCK_GAS_LIMIT); throw predefined.GAS_LIMIT_TOO_HIGH; } else if (gasLimit < intrinsicGasCost) { - this.logger.trace(`${failBaseLog} Gas Limit was too low: %s, intrinsic gas cost: %s`, JSON.stringify(tx), gasLimit, intrinsicGasCost); + this.logger.trace(`${requestIdPrefix} ${failBaseLog} Gas Limit was too low: %s, intrinsic gas cost: %s`, JSON.stringify(tx), gasLimit, intrinsicGasCost); throw predefined.GAS_LIMIT_TOO_LOW; } } diff --git a/packages/relay/tests/lib/eth.spec.ts b/packages/relay/tests/lib/eth.spec.ts index e5ed3ee26c..7d6ea58aff 100644 --- a/packages/relay/tests/lib/eth.spec.ts +++ b/packages/relay/tests/lib/eth.spec.ts @@ -1496,6 +1496,79 @@ describe('Eth calls using MirrorNode', async function () { expect(hasError).to.be.true; }); }); + + describe('eth_getStorageAt', async function() { + it('eth_getStorageAt with match with block', async function () { + // mirror node request mocks + mock.onGet(`blocks/${blockNumber}`).reply(200, defaultBlock); + mock.onGet(`contracts/${contractAddress1}/results?timestamp=lte:${defaultBlock.timestamp.to}&limit=1&order=desc`).reply(200, defaultContractResults); + mock.onGet(`contracts/${contractAddress1}/results/${contractTimestamp1}`).reply(200, defaultDetailedContractResults); + + const result = await ethImpl.getStorageAt(contractAddress1, defaultDetailedContractResults.state_changes[0].slot, EthImpl.numberTo0x(blockNumber)); + expect(result).to.exist; + if (result == null) return; + + // verify slot value + expect(result).equal(defaultDetailedContractResults.state_changes[0].value_written); + }); + + it('eth_getStorageAt with match with latest block', async function () { + // mirror node request mocks + mock.onGet(`contracts/${contractAddress1}/results?limit=1&order=desc`).reply(200, defaultContractResults); + mock.onGet(`contracts/${contractAddress1}/results/${contractTimestamp1}`).reply(200, defaultDetailedContractResults); + + const result = await ethImpl.getStorageAt(contractAddress1, defaultDetailedContractResults.state_changes[0].slot, "latest"); + expect(result).to.exist; + if (result == null) return; + + // verify slot value + expect(result).equal(defaultDetailedContractResults.state_changes[0].value_written); + }); + + it('eth_getStorageAt with match null block', async function () { + // mirror node request mocks + mock.onGet(`contracts/${contractAddress1}/results?limit=1&order=desc`).reply(200, defaultContractResults); + mock.onGet(`contracts/${contractAddress1}/results/${contractTimestamp1}`).reply(200, defaultDetailedContractResults); + + const result = await ethImpl.getStorageAt(contractAddress1, defaultDetailedContractResults.state_changes[0].slot); + expect(result).to.exist; + if (result == null) return; + + // verify slot value + expect(result).equal(defaultDetailedContractResults.state_changes[0].value_written); + }); + + it('eth_getStorageAt should throw a predefined NO_SUITABLE_PEERS when block not found', async function () { + + let hasError = false; + try { + mock.onGet(`blocks/${blockNumber}`).reply(200, null); + const result = await ethImpl.getStorageAt(contractAddress1, defaultDetailedContractResults.state_changes[0].slot, EthImpl.numberTo0x(blockNumber)); + } catch (e: any) { + hasError = true; + expect(e.code).to.equal(-32001); + expect(e.name).to.equal('Resource not found'); + } + expect(hasError).to.be.true; + }); + + it('eth_getStorageAt should throw error when contract not found', async function () { + // mirror node request mocks + mock.onGet(`blocks/${blockNumber}`).reply(200, defaultBlock); + mock.onGet(`contracts/${contractAddress1}/results?timestamp=lte:${defaultBlock.timestamp.to}&limit=1&order=desc`).reply(200, defaultContractResults); + mock.onGet(`contracts/${contractAddress1}/results/${contractTimestamp1}`).reply(404, detailedContractResultNotFound); + + let hasError = false; + try { + const result = await ethImpl.getStorageAt(contractAddress1, defaultDetailedContractResults.state_changes[0].slot, EthImpl.numberTo0x(blockNumber)); + } catch (e: any) { + hasError = true; + expect(e.statusCode).to.equal(404); + expect(e.message).to.equal("Request failed with status code 404"); + } + expect(hasError).to.be.true; + }); + }); }); describe('Eth', async function () { @@ -1682,7 +1755,6 @@ describe('Eth', async function () { 'sendTransaction', 'protocolVersion', 'coinbase', - 'getStorageAt', ]; unsupportedMethods.forEach(method => { diff --git a/packages/relay/tests/lib/mirrorNodeClient.spec.ts b/packages/relay/tests/lib/mirrorNodeClient.spec.ts index ca9b95ad3f..2111c17874 100644 --- a/packages/relay/tests/lib/mirrorNodeClient.spec.ts +++ b/packages/relay/tests/lib/mirrorNodeClient.spec.ts @@ -414,6 +414,36 @@ describe('MirrorNodeClient', async function () { expect(firstResult.to).equal(contractResult.to); }); + it('`getLatestContractResultsByAddress` by address no timestamp', async () => { + const address = '0x0000000000000000000000000000000000001f41'; + mock.onGet(`contracts/${address}/results?limit=1&order=desc`).reply(200, { results: [contractResult], links: { next: null } }); + + const result = await mirrorNodeInstance.getLatestContractResultsByAddress(address, undefined, 1); + expect(result).to.exist; + expect(result.links).to.exist; + expect(result.links.next).to.equal(null); + expect(result.results.length).to.gt(0); + const firstResult = result.results[0]; + expect(firstResult.contract_id).equal(detailedContractResult.contract_id); + expect(firstResult.function_parameters).equal(contractResult.function_parameters); + expect(firstResult.to).equal(contractResult.to); + }); + + it('`getLatestContractResultsByAddress` by address with timestamp, limit 2', async () => { + const address = '0x0000000000000000000000000000000000001f41'; + mock.onGet(`contracts/${address}/results?timestamp=lte:987654.000123456&limit=2&order=desc`).reply(200, { results: [contractResult], links: { next: null } }); + + const result = await mirrorNodeInstance.getLatestContractResultsByAddress(address, "987654.000123456", 2); + expect(result).to.exist; + expect(result.links).to.exist; + expect(result.links.next).to.equal(null); + expect(result.results.length).to.gt(0); + const firstResult = result.results[0]; + expect(firstResult.contract_id).equal(detailedContractResult.contract_id); + expect(firstResult.function_parameters).equal(contractResult.function_parameters); + expect(firstResult.to).equal(contractResult.to); + }); + const log = { 'address': '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', 'bloom': '0x549358c4c2e573e02410ef7b5a5ffa5f36dd7398', diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index bcf508e3d8..ca6f660606 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -23,6 +23,7 @@ import Koa from 'koa'; import koaJsonRpc from 'koa-jsonrpc'; import { collectDefaultMetrics, Histogram, Registry } from 'prom-client'; +import crypto from 'crypto'; import fs from 'fs'; import path from 'path'; import pino from 'pino'; @@ -46,6 +47,7 @@ const cors = require('koa-cors'); const app = new Koa(); const rpc = koaJsonRpc(); +const REQUEST_ID_STRING = `Request ID: `; const responseSuccessStatusCode = '200'; const responseInternalErrorCode = '-32603'; collectDefaultMetrics({ register, prefix: 'rpc_relay_' }); @@ -134,10 +136,12 @@ app.use(async (ctx, next) => { const logAndHandleResponse = async (methodName, methodFunction) => { const start = Date.now(); let ms; - logger.debug(methodName); - const messagePrefix = `[POST] ${methodName}:`; + const requestId = generateRequestId(); + const requestIdPrefix = requestId ? `[${REQUEST_ID_STRING}${requestId}]` : ''; + logger.debug(`${requestIdPrefix} ${methodName}`); + const messagePrefix = `${requestIdPrefix} [POST] ${methodName}:`; try { - const response = await methodFunction(); + const response = await methodFunction(requestId); const status = response instanceof JsonRpcError ? response.code.toString() : responseSuccessStatusCode; ms = Date.now() - start; methodResponseHistogram.labels(methodName, status).observe(ms); @@ -161,6 +165,15 @@ const logAndHandleResponse = async (methodName, methodFunction) => { } }; +/** + * Generates random trace id for requests. + * + * returns: string + */ + const generateRequestId = () :string => { + return crypto.randomUUID(); +}; + /** * returns: false */ @@ -181,7 +194,7 @@ rpc.use('net_version', async () => { * returns: Block number - hex encoded integer */ rpc.use('eth_blockNumber', async () => { - return logAndHandleResponse('eth_blockNumber', () => relay.eth().blockNumber()); + return logAndHandleResponse('eth_blockNumber', (requestId) => relay.eth().blockNumber(requestId)); }); /** @@ -191,7 +204,8 @@ rpc.use('eth_blockNumber', async () => { * returns: Gas used - hex encoded integer */ rpc.use('eth_estimateGas', async (params: any) => { - return logAndHandleResponse('eth_estimateGas', () => relay.eth().estimateGas(params?.[0], params?.[1])); + return logAndHandleResponse('eth_estimateGas', (requestId) => + relay.eth().estimateGas(params?.[0], params?.[1], requestId)); }); /** @@ -202,7 +216,8 @@ rpc.use('eth_estimateGas', async (params: any) => { * returns: Balance - hex encoded integer */ rpc.use('eth_getBalance', async (params: any) => { - return logAndHandleResponse("eth_getBalance", () => relay.eth().getBalance(params?.[0], params?.[1])); + return logAndHandleResponse("eth_getBalance", (requestId) => + relay.eth().getBalance(params?.[0], params?.[1], requestId)); }); /** @@ -213,7 +228,8 @@ rpc.use('eth_getBalance', async (params: any) => { * returns: Bytecode - hex encoded bytes */ rpc.use('eth_getCode', async (params: any) => { - return logAndHandleResponse("eth_getCode", () => relay.eth().getCode(params?.[0], params?.[1])); + return logAndHandleResponse("eth_getCode", (requestId) => + relay.eth().getCode(params?.[0], params?.[1], requestId)); }); /** @@ -222,7 +238,8 @@ rpc.use('eth_getCode', async (params: any) => { * returns: Chain ID - integer */ rpc.use('eth_chainId', async () => { - return logAndHandleResponse('eth_chainId', () => relay.eth().chainId()); + return logAndHandleResponse('eth_chainId', (requestId) => + relay.eth().chainId(requestId)); }); /** @@ -233,7 +250,8 @@ rpc.use('eth_chainId', async () => { * returns: Block object */ rpc.use('eth_getBlockByNumber', async (params: any) => { - return logAndHandleResponse('eth_getBlockByNumber', () => relay.eth().getBlockByNumber(params?.[0], Boolean(params?.[1]))); + return logAndHandleResponse('eth_getBlockByNumber', (requestId) => + relay.eth().getBlockByNumber(params?.[0], Boolean(params?.[1]), requestId)); }); /** @@ -244,7 +262,8 @@ rpc.use('eth_getBlockByNumber', async (params: any) => { * returns: Block object */ rpc.use('eth_getBlockByHash', async (params: any) => { - return logAndHandleResponse("eth_getBlockByHash", () => relay.eth().getBlockByHash(params?.[0], Boolean(params?.[1]))); + return logAndHandleResponse("eth_getBlockByHash", (requestId) => + relay.eth().getBlockByHash(params?.[0], Boolean(params?.[1]), requestId)); }); /** @@ -253,7 +272,7 @@ rpc.use('eth_getBlockByHash', async (params: any) => { * returns: Gas price - hex encoded integer */ rpc.use('eth_gasPrice', async () => { - return logAndHandleResponse('eth_gasPrice', () => relay.eth().gasPrice()); + return logAndHandleResponse('eth_gasPrice', (requestId) => relay.eth().gasPrice(requestId)); }); /** @@ -264,8 +283,8 @@ rpc.use('eth_gasPrice', async () => { * returns: Transaction count - hex encoded integer */ rpc.use('eth_getTransactionCount', async (params: any) => { - logger.debug("eth_getTransactionCount"); - return logAndHandleResponse('eth_getTransactionCount', () => relay.eth().getTransactionCount(params?.[0], params?.[1])); + return logAndHandleResponse('eth_getTransactionCount', (requestId) => + relay.eth().getTransactionCount(params?.[0], params?.[1], requestId)); }); /** @@ -275,7 +294,8 @@ rpc.use('eth_getTransactionCount', async (params: any) => { * returns: Value - hex encoded bytes */ rpc.use('eth_call', async (params: any) => { - return logAndHandleResponse("eth_call", () => relay.eth().call(params?.[0], params?.[1])); + return logAndHandleResponse("eth_call", (requestId) => + relay.eth().call(params?.[0], params?.[1], requestId)); }); /** @@ -285,7 +305,8 @@ rpc.use('eth_call', async (params: any) => { * returns: Transaction hash - 32 byte hex value */ rpc.use('eth_sendRawTransaction', async (params: any) => { - return logAndHandleResponse("eth_sendRawTransaction", () => relay.eth().sendRawTransaction(params?.[0])); + return logAndHandleResponse("eth_sendRawTransaction", (requestId) => + relay.eth().sendRawTransaction(params?.[0], requestId)); }); /** @@ -295,7 +316,8 @@ rpc.use('eth_sendRawTransaction', async (params: any) => { * returns: Transaction Receipt - object */ rpc.use('eth_getTransactionReceipt', async (params: any) => { - return logAndHandleResponse('eth_getTransactionReceipt', () => relay.eth().getTransactionReceipt(params?.[0])); + return logAndHandleResponse('eth_getTransactionReceipt', (requestId) => + relay.eth().getTransactionReceipt(params?.[0], requestId)); }); rpc.use('web3_clientVersion', async () => { @@ -308,7 +330,7 @@ rpc.use('web3_clientVersion', async () => { * returns: Accounts - hex encoded address */ rpc.use('eth_accounts', async () => { - return logAndHandleResponse("eth_accounts", () => relay.eth().accounts()); + return logAndHandleResponse("eth_accounts", (requestId) => relay.eth().accounts(requestId)); }); /** @@ -318,7 +340,8 @@ rpc.use('eth_accounts', async () => { * returns: Transaction Object */ rpc.use('eth_getTransactionByHash', async (params: any) => { - return logAndHandleResponse("eth_getTransactionByHash", () => relay.eth().getTransactionByHash(params[0])); + return logAndHandleResponse("eth_getTransactionByHash", (requestId) => + relay.eth().getTransactionByHash(params[0], requestId)); }); /** @@ -334,7 +357,8 @@ rpc.use('eth_getTransactionByHash', async (params: any) => { * - reward - Array of effective priority fee per gas data. */ rpc.use('eth_feeHistory', async (params: any) => { - return logAndHandleResponse("eth_feeHistory", () => relay.eth().feeHistory(Number(params?.[0]), params?.[1], params?.[2])); + return logAndHandleResponse("eth_feeHistory", (requestId) => + relay.eth().feeHistory(Number(params?.[0]), params?.[1], params?.[2], requestId)); }); @@ -345,7 +369,8 @@ rpc.use('eth_feeHistory', async (params: any) => { * returns: Block Transaction Count - Hex encoded integer */ rpc.use('eth_getBlockTransactionCountByHash', async (params: any) => { - return logAndHandleResponse("eth_getBlockTransactionCountByHash", () => relay.eth().getBlockTransactionCountByHash(params?.[0])); + return logAndHandleResponse("eth_getBlockTransactionCountByHash", (requestId) => + relay.eth().getBlockTransactionCountByHash(params?.[0], requestId)); }); /** @@ -355,7 +380,8 @@ rpc.use('eth_getBlockTransactionCountByHash', async (params: any) => { * returns: Block Transaction Count - Hex encoded integer */ rpc.use('eth_getBlockTransactionCountByNumber', async (params: any) => { - return logAndHandleResponse("eth_getBlockTransactionCountByNumber", () => relay.eth().getBlockTransactionCountByNumber(params?.[0])); + return logAndHandleResponse("eth_getBlockTransactionCountByNumber", (requestId) => + relay.eth().getBlockTransactionCountByNumber(params?.[0], requestId)); }); /** @@ -366,12 +392,13 @@ rpc.use('eth_getBlockTransactionCountByNumber', async (params: any) => { */ rpc.use('eth_getLogs', async (params: any) => { params = params[0] ?? []; - return logAndHandleResponse('eth_getLogs', () => relay.eth().getLogs( + return logAndHandleResponse('eth_getLogs', (requestId) => relay.eth().getLogs( params?.blockHash || null, params?.fromBlock || null, params?.toBlock || null, params?.address || null, - params?.topics || null + params?.topics || null, + requestId )); }); @@ -385,7 +412,8 @@ rpc.use('eth_getLogs', async (params: any) => { * returns: Value - The storage value */ rpc.use('eth_getStorageAt', async (params: any) => { - return logAndHandleResponse("eth_getStorageAt", () => relay.eth().getStorageAt(params?.[0], params?.[1], params?.[2])); + return logAndHandleResponse("eth_getStorageAt", (requestId) => + relay.eth().getStorageAt(params?.[0], params?.[1], params?.[2], requestId)); }); /** @@ -396,7 +424,8 @@ rpc.use('eth_getStorageAt', async (params: any) => { * returns: Transaction */ rpc.use('eth_getTransactionByBlockHashAndIndex', async (params: any) => { - return logAndHandleResponse("eth_getTransactionByBlockHashAndIndex", () => relay.eth().getTransactionByBlockHashAndIndex(params?.[0], params?.[1])); + return logAndHandleResponse("eth_getTransactionByBlockHashAndIndex", (requestId) => + relay.eth().getTransactionByBlockHashAndIndex(params?.[0], params?.[1], requestId)); }); /** @@ -407,7 +436,8 @@ rpc.use('eth_getTransactionByBlockHashAndIndex', async (params: any) => { * returns: Transaction */ rpc.use('eth_getTransactionByBlockNumberAndIndex', async (params: any) => { - return logAndHandleResponse("eth_getTransactionByBlockNumberAndIndex", () => relay.eth().getTransactionByBlockNumberAndIndex(params?.[0], params?.[1])); + return logAndHandleResponse("eth_getTransactionByBlockNumberAndIndex", (requestId) => + relay.eth().getTransactionByBlockNumberAndIndex(params?.[0], params?.[1], requestId)); }); /** @@ -420,7 +450,8 @@ rpc.use('eth_getTransactionByBlockNumberAndIndex', async (params: any) => { * returns: null */ rpc.use('eth_getUncleByBlockHashAndIndex', async () => { - return logAndHandleResponse("eth_getUncleByBlockHashAndIndex", () => relay.eth().getUncleByBlockHashAndIndex()); + return logAndHandleResponse("eth_getUncleByBlockHashAndIndex", (requestId) => + relay.eth().getUncleByBlockHashAndIndex(requestId)); }); /** @@ -432,7 +463,8 @@ rpc.use('eth_getUncleByBlockHashAndIndex', async () => { * returns: null */ rpc.use('eth_getUncleByBlockNumberAndIndex', async () => { - return logAndHandleResponse("eth_getUncleByBlockNumberAndIndex", () => relay.eth().getUncleByBlockNumberAndIndex()); + return logAndHandleResponse("eth_getUncleByBlockNumberAndIndex", (requestId) => + relay.eth().getUncleByBlockNumberAndIndex(requestId)); }); /** @@ -443,7 +475,8 @@ rpc.use('eth_getUncleByBlockNumberAndIndex', async () => { * returns: 0x0 */ rpc.use('eth_getUncleCountByBlockHash', async () => { - return logAndHandleResponse("eth_getUncleCountByBlockHash", () => relay.eth().getUncleCountByBlockHash()); + return logAndHandleResponse("eth_getUncleCountByBlockHash", (requestId) => + relay.eth().getUncleCountByBlockHash(requestId)); }); /** @@ -454,7 +487,8 @@ rpc.use('eth_getUncleCountByBlockHash', async () => { * returns: 0x0 */ rpc.use('eth_getUncleCountByBlockNumber', async () => { - return logAndHandleResponse("eth_getUncleCountByBlockNumber", () => relay.eth().getUncleCountByBlockNumber()); + return logAndHandleResponse("eth_getUncleCountByBlockNumber", (requestId) => + relay.eth().getUncleCountByBlockNumber(requestId)); }); /** @@ -464,7 +498,7 @@ rpc.use('eth_getUncleCountByBlockNumber', async () => { * returns: code: -32000 */ rpc.use('eth_getWork', async () => { - return logAndHandleResponse("eth_getWork", () => relay.eth().getWork()); + return logAndHandleResponse("eth_getWork", (requestId) => relay.eth().getWork(requestId)); }); /** @@ -475,7 +509,7 @@ rpc.use('eth_getWork', async () => { * returns: 0x0 */ rpc.use('eth_hashrate', async () => { - return logAndHandleResponse("eth_hashrate", () => relay.eth().hashrate()); + return logAndHandleResponse("eth_hashrate", (requestId) => relay.eth().hashrate(requestId)); }); /** @@ -486,7 +520,7 @@ rpc.use('eth_hashrate', async () => { * returns: false */ rpc.use('eth_mining', async () => { - return logAndHandleResponse("eth_mining", () => relay.eth().mining()); + return logAndHandleResponse("eth_mining", (requestId) => relay.eth().mining(requestId)); }); /** @@ -497,7 +531,7 @@ rpc.use('eth_mining', async () => { * returns: false */ rpc.use('eth_submitWork', async () => { - return logAndHandleResponse("eth_submitWork", () => relay.eth().submitWork()); + return logAndHandleResponse("eth_submitWork", (requestId) => relay.eth().submitWork(requestId)); }); /** @@ -507,7 +541,7 @@ rpc.use('eth_submitWork', async () => { * returns: false */ rpc.use('eth_syncing', async () => { - return logAndHandleResponse("eth_syncing", () => relay.eth().syncing()); + return logAndHandleResponse("eth_syncing", (requestId) => relay.eth().syncing(requestId)); }); /** @@ -523,27 +557,27 @@ rpc.use('web3_client_version', async () => { * Not supported */ rpc.use('eth_submitHashrate', async () => { - return logAndHandleResponse("eth_submitHashrate", () => relay.eth().submitHashrate()); + return logAndHandleResponse("eth_submitHashrate", (requestId) => relay.eth().submitHashrate(requestId)); }); rpc.use('eth_signTransaction', async () => { - return logAndHandleResponse("eth_signTransaction", () => relay.eth().signTransaction()); + return logAndHandleResponse("eth_signTransaction", (requestId) => relay.eth().signTransaction(requestId)); }); rpc.use('eth_sign', async () => { - return logAndHandleResponse("eth_sign", () => relay.eth().sign()); + return logAndHandleResponse("eth_sign", (requestId) => relay.eth().sign(requestId)); }); rpc.use('eth_sendTransaction', async () => { - return logAndHandleResponse("eth_sendTransaction", () => relay.eth().sendTransaction()); + return logAndHandleResponse("eth_sendTransaction", (requestId) => relay.eth().sendTransaction(requestId)); }); rpc.use('eth_protocolVersion', async () => { - return logAndHandleResponse("eth_protocolVersion", () => relay.eth().protocolVersion()); + return logAndHandleResponse("eth_protocolVersion", (requestId) => relay.eth().protocolVersion(requestId)); }); rpc.use('eth_coinbase', async () => { - return logAndHandleResponse("eth_coinbase", () => relay.eth().coinbase()); + return logAndHandleResponse("eth_coinbase", (requestId) => relay.eth().coinbase(requestId)); }); app.use(cors()); diff --git a/packages/server/tests/acceptance/index.spec.ts b/packages/server/tests/acceptance/index.spec.ts index e53716a3d1..70a56459ff 100644 --- a/packages/server/tests/acceptance/index.spec.ts +++ b/packages/server/tests/acceptance/index.spec.ts @@ -116,8 +116,8 @@ describe('RPC Server Acceptance Tests', function () { function runLocalHederaNetwork() { // set env variables for docker images until local-node is updated - process.env['NETWORK_NODE_IMAGE_TAG'] = '0.27.4'; - process.env['HAVEGED_IMAGE_TAG'] = '0.27.4'; + process.env['NETWORK_NODE_IMAGE_TAG'] = '0.29.0-alpha.1'; + process.env['HAVEGED_IMAGE_TAG'] = '0.29.0-alpha.1'; process.env['MIRROR_IMAGE_TAG'] = '0.62.0-rc1'; logger.trace(`Docker container versions, services: ${process.env['NETWORK_NODE_IMAGE_TAG']}, mirror: ${process.env['MIRROR_IMAGE_TAG']}`); diff --git a/packages/server/tests/acceptance/rpc.spec.ts b/packages/server/tests/acceptance/rpc.spec.ts index 76592a3753..c352a9bce8 100644 --- a/packages/server/tests/acceptance/rpc.spec.ts +++ b/packages/server/tests/acceptance/rpc.spec.ts @@ -1104,23 +1104,23 @@ describe('RPC Server Acceptance Tests', function () { Assertions.feeHistory(res, { resultCount: blockCountNumber, oldestBlock: oldestBlockNumberHex, - chechReward: true + checkReward: true }); - - expect(res.baseFeePerGas[0]).to.equal(datedGasPriceHex); + // We expect all values in the array to be from the mirror node. If there is discrepancy in the blocks, the first value is from the consensus node and it's different from expected. + expect(res.baseFeePerGas[1]).to.equal(datedGasPriceHex); expect(res.baseFeePerGas[res.baseFeePerGas.length - 2]).to.equal(updatedGasPriceHex); expect(res.baseFeePerGas[res.baseFeePerGas.length - 1]).to.equal(updatedGasPriceHex); }); it('should call eth_feeHistory with newest block > latest', async function () { let latestBlock; - const newestBlockNumber = lastBlockAfterUpdate.number + 10; - const newestBlockNumberHex = ethers.utils.hexValue(newestBlockNumber); + const blocksAhead = 10; try { latestBlock = (await mirrorNode.get(`/blocks?limit=1&order=desc`)).blocks[0]; + const newestBlockNumberHex = ethers.utils.hexValue(latestBlock.number + blocksAhead); await relay.call('eth_feeHistory', ['0x1', newestBlockNumberHex, null]); } catch (error) { - Assertions.jsonRpcError(error, predefined.REQUEST_BEYOND_HEAD_BLOCK(newestBlockNumber, latestBlock.number)); + Assertions.jsonRpcError(error, predefined.REQUEST_BEYOND_HEAD_BLOCK(latestBlock.number + blocksAhead, latestBlock.number)); } }); diff --git a/packages/server/tests/helpers/prerequisite.ts b/packages/server/tests/helpers/prerequisite.ts index 4b4ce77bcb..b096b9e411 100644 --- a/packages/server/tests/helpers/prerequisite.ts +++ b/packages/server/tests/helpers/prerequisite.ts @@ -11,9 +11,9 @@ const RELAY_URL = process.env.E2E_RELAY_HOST || LOCAL_RELAY_URL; (function () { if (USE_LOCAL_NODE) { - process.env['NETWORK_NODE_IMAGE_TAG'] = '0.26.2'; - process.env['HAVEGED_IMAGE_TAG'] = '0.26.2'; - process.env['MIRROR_IMAGE_TAG'] = '0.58.0'; + process.env['NETWORK_NODE_IMAGE_TAG'] = '0.29.0-alpha.1'; + process.env['HAVEGED_IMAGE_TAG'] = '0.29.0-alpha.1'; + process.env['MIRROR_IMAGE_TAG'] = '0.62.0-rc1'; console.log(`Docker container versions, services: ${process.env['NETWORK_NODE_IMAGE_TAG']}, mirror: ${process.env['MIRROR_IMAGE_TAG']}`); // start relay, stop relay instance in local-node diff --git a/packages/server/tests/integration/server.spec.ts b/packages/server/tests/integration/server.spec.ts index 05911b589a..8fd00adfe4 100644 --- a/packages/server/tests/integration/server.spec.ts +++ b/packages/server/tests/integration/server.spec.ts @@ -316,17 +316,6 @@ describe('RPC Server', async function() { BaseTest.unsupportedJsonRpcMethodChecks(res); }); - - it('should execute "eth_getStorageAt"', async function() { - const res = await this.testClient.post('/', { - 'id': '2', - 'jsonrpc': '2.0', - 'method': 'eth_getStorageAt', - 'params': [null] - }); - - BaseTest.unsupportedJsonRpcMethodChecks(res); - }); }); class BaseTest { diff --git a/tools/truffle-example/README.md b/tools/truffle-example/README.md new file mode 100644 index 0000000000..3c573a2430 --- /dev/null +++ b/tools/truffle-example/README.md @@ -0,0 +1,25 @@ +# Truffle example + +Simple scripts for basic operations like hbars transfer, balance fetching, and contract interactions (deployment and calls). + +## Prerequisite +You must have running: +- JSON-RPC Relay + +## Configuration + +Create `.env` file based on `.env.example` +``` +# Alias accounts keys +OPERATOR_PRIVATE_KEY= +RECEIVER_PRIVATE_KEY= +# Mostly like to be http://localhost:7546 +RELAY_ENDPOINT= +``` + +## Setup & Install + +In the project directory: + +1. Run `npm install` +2. Run `npx truffle test` diff --git a/tools/web3js-example/README.md b/tools/web3js-example/README.md new file mode 100644 index 0000000000..1bc513f244 --- /dev/null +++ b/tools/web3js-example/README.md @@ -0,0 +1,25 @@ +# Web3js example + +Simple scripts for basic operations like hbars transfer, balance fetching, and contract interactions (deployment and calls). + +## Prerequisite +You must have running: +- JSON-RPC Relay + +## Configuration + +Create `.env` file based on `.env.example` +``` +# Alias accounts keys +OPERATOR_PRIVATE_KEY= +RECEIVER_PRIVATE_KEY= +# Mostly like to be http://localhost:7546 +RELAY_ENDPOINT= +``` + +## Setup & Install + +In the project directory: + +1. Run `npm install` +2. Run `npx run test` \ No newline at end of file