diff --git a/packages/relay/src/lib/eth.ts b/packages/relay/src/lib/eth.ts index dd7049b57b..7e5a336153 100644 --- a/packages/relay/src/lib/eth.ts +++ b/packages/relay/src/lib/eth.ts @@ -50,6 +50,8 @@ import { CacheService } from './services/cacheService/cacheService'; import { IDebugService } from './services/debugService/IDebugService'; import { DebugService } from './services/debugService'; import { isValidEthereumAddress } from '../formatters'; +import { IFeeHistory } from './types/IFeeHistory'; +import { ITransactionReceipt } from './types/ITransactionReceipt'; const _ = require('lodash'); const createHash = require('keccak'); @@ -87,8 +89,17 @@ export class EthImpl implements Eth { static ethTxType = 'EthereumTransaction'; static ethEmptyTrie = '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421'; static defaultGasUsedRatio = 0.5; - static feeHistoryZeroBlockCountResponse = { gasUsedRatio: null, oldestBlock: EthImpl.zeroHex }; - static feeHistoryEmptyResponse = { baseFeePerGas: [], gasUsedRatio: [], reward: [], oldestBlock: EthImpl.zeroHex }; + static feeHistoryZeroBlockCountResponse: IFeeHistory = { + gasUsedRatio: null, + oldestBlock: EthImpl.zeroHex, + baseFeePerGas: undefined, + }; + static readonly feeHistoryEmptyResponse: IFeeHistory = { + baseFeePerGas: [], + gasUsedRatio: [], + reward: [], + oldestBlock: EthImpl.zeroHex, + }; static redirectBytecodePrefix = '6080604052348015600f57600080fd5b506000610167905077618dc65e'; static redirectBytecodePostfix = '600052366000602037600080366018016008845af43d806000803e8160008114605857816000f35b816000fdfea2646970667358221220d8378feed472ba49a0005514ef7087017f707b45fb9bf56bb81bb93ff19a238b64736f6c634300080b0033'; @@ -273,7 +284,7 @@ export class EthImpl implements Eth { return !CommonService.blockTagIsLatestOrPendingStrict(tag) && !CommonService.isDevMode; } - private initEthExecutionCounter(register: Registry) { + private initEthExecutionCounter(register: Registry): Counter { const metricCounterName = 'rpc_relay_eth_executions'; register.removeSingleMetric(metricCounterName); return new Counter({ @@ -296,12 +307,12 @@ 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(requestIdPrefix?: string) { + accounts(requestIdPrefix?: string): never[] { this.logger.trace(`${requestIdPrefix} accounts()`); return EthImpl.accounts; } - private getEthFeeHistoryFixedFee() { + private getEthFeeHistoryFixedFee(): boolean { if (process.env.ETH_FEE_HISTORY_FIXED === undefined) { return true; } @@ -316,7 +327,7 @@ export class EthImpl implements Eth { newestBlock: string, rewardPercentiles: Array | null, requestIdPrefix?: string, - ) { + ): Promise { const maxResults = process.env.TEST === 'true' ? constants.DEFAULT_FEE_HISTORY_MAX_RESULTS @@ -327,32 +338,21 @@ export class EthImpl implements Eth { ); try { - let newestBlockNumber; - let latestBlockNumber; - if (this.getEthFeeHistoryFixedFee()) { - newestBlockNumber = - newestBlock == EthImpl.blockLatest || newestBlock == EthImpl.blockPending - ? await this.translateBlockTag(EthImpl.blockLatest, requestIdPrefix) - : await this.translateBlockTag(newestBlock, requestIdPrefix); - } else { - // once we finish testing and refining Fixed Fee method, we can remove this else block to clean up code - latestBlockNumber = await this.translateBlockTag(EthImpl.blockLatest, requestIdPrefix); - newestBlockNumber = - newestBlock == EthImpl.blockLatest || newestBlock == EthImpl.blockPending - ? latestBlockNumber - : await this.translateBlockTag(newestBlock, requestIdPrefix); - - if (newestBlockNumber > latestBlockNumber) { - return predefined.REQUEST_BEYOND_HEAD_BLOCK(newestBlockNumber, latestBlockNumber); - } + const latestBlockNumber = await this.translateBlockTag(EthImpl.blockLatest, requestIdPrefix); + const newestBlockNumber = + newestBlock == EthImpl.blockLatest || newestBlock == EthImpl.blockPending + ? latestBlockNumber + : await this.translateBlockTag(newestBlock, requestIdPrefix); + + if (newestBlockNumber > latestBlockNumber) { + return predefined.REQUEST_BEYOND_HEAD_BLOCK(newestBlockNumber, latestBlockNumber); } - blockCount = blockCount > maxResults ? maxResults : blockCount; if (blockCount <= 0) { return EthImpl.feeHistoryZeroBlockCountResponse; } - let feeHistory: object | undefined; + let feeHistory: IFeeHistory; if (this.getEthFeeHistoryFixedFee()) { let oldestBlock = newestBlockNumber - blockCount + 1; @@ -364,12 +364,13 @@ export class EthImpl implements Eth { feeHistory = this.getRepeatedFeeHistory(blockCount, oldestBlock, rewardPercentiles, gasPriceFee); } else { // once we finish testing and refining Fixed Fee method, we can remove this else block to clean up code - const cacheKey = `${constants.CACHE_KEY.FEE_HISTORY}_${blockCount}_${newestBlock}_${rewardPercentiles?.join( '', )}`; - feeHistory = this.cacheService.get(cacheKey, EthImpl.ethFeeHistory, requestIdPrefix); - if (!feeHistory) { + const cachedFeeHistory = this.cacheService.get(cacheKey, EthImpl.ethFeeHistory, requestIdPrefix); + if (cachedFeeHistory) { + feeHistory = cachedFeeHistory; + } else { feeHistory = await this.getFeeHistory( blockCount, newestBlockNumber, @@ -377,9 +378,9 @@ export class EthImpl implements Eth { rewardPercentiles, requestIdPrefix, ); - if (newestBlock != EthImpl.blockLatest && newestBlock != EthImpl.blockPending) { - this.cacheService.set(cacheKey, feeHistory, EthImpl.ethFeeHistory, undefined, requestIdPrefix); - } + } + if (newestBlock != EthImpl.blockLatest && newestBlock != EthImpl.blockPending) { + this.cacheService.set(cacheKey, feeHistory, EthImpl.ethFeeHistory, undefined, requestIdPrefix); } } @@ -410,10 +411,10 @@ export class EthImpl implements Eth { oldestBlockNumber: number, rewardPercentiles: Array | null, fee: string, - ) { + ): IFeeHistory { const shouldIncludeRewards = Array.isArray(rewardPercentiles) && rewardPercentiles.length > 0; - const feeHistory = { + const feeHistory: IFeeHistory = { baseFeePerGas: Array(blockCount).fill(fee), gasUsedRatio: Array(blockCount).fill(EthImpl.defaultGasUsedRatio), oldestBlock: numberTo0x(oldestBlockNumber), @@ -421,7 +422,7 @@ export class EthImpl implements Eth { // next fee. Due to high block production rate and low fee change rate we add the next fee // since by the time a user utilizes the response there will be a next block likely with the same fee - feeHistory.baseFeePerGas.push(fee); + feeHistory.baseFeePerGas?.push(fee); if (shouldIncludeRewards) { feeHistory['reward'] = Array(blockCount).fill(Array(rewardPercentiles.length).fill(EthImpl.zeroHex)); @@ -436,11 +437,11 @@ export class EthImpl implements Eth { latestBlockNumber: number, rewardPercentiles: Array | null, requestIdPrefix?: string, - ) { + ): Promise { // 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; - const feeHistory = { + const feeHistory: IFeeHistory = { baseFeePerGas: [] as string[], gasUsedRatio: [] as number[], oldestBlock: numberTo0x(oldestBlockNumber), @@ -450,8 +451,8 @@ export class EthImpl implements Eth { for (let blockNumber = oldestBlockNumber; blockNumber <= newestBlockNumber; blockNumber++) { const fee = await this.getFeeByBlockNumber(blockNumber, requestIdPrefix); - feeHistory.baseFeePerGas.push(fee); - feeHistory.gasUsedRatio.push(EthImpl.defaultGasUsedRatio); + feeHistory.baseFeePerGas?.push(fee); + feeHistory.gasUsedRatio?.push(EthImpl.defaultGasUsedRatio); } // get latest block fee @@ -463,7 +464,7 @@ export class EthImpl implements Eth { } if (nextBaseFeePerGas) { - feeHistory.baseFeePerGas.push(nextBaseFeePerGas); + feeHistory.baseFeePerGas?.push(nextBaseFeePerGas); } if (shouldIncludeRewards) { @@ -473,7 +474,7 @@ export class EthImpl implements Eth { return feeHistory; } - private async getFeeWeibars(callerName: string, requestIdPrefix?: string, timestamp?: string) { + private async getFeeWeibars(callerName: string, requestIdPrefix?: string, timestamp?: string): Promise { let networkFees; try { networkFees = await this.mirrorNodeClient.getNetworkFees(timestamp, undefined, requestIdPrefix); @@ -513,7 +514,7 @@ export class EthImpl implements Eth { */ async blockNumber(requestIdPrefix?: string): Promise { this.logger.trace(`${requestIdPrefix} blockNumber()`); - return this.common.getLatestBlockNumber(requestIdPrefix); + return await this.common.getLatestBlockNumber(requestIdPrefix); } /** @@ -553,15 +554,20 @@ 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, requestIdPrefix?: string) { + async estimateGas( + transaction: any, + _blockParam: string | null, + requestIdPrefix?: string, + ): Promise { this.logger.trace( `${requestIdPrefix} estimateGas(transaction=${JSON.stringify(transaction)}, _blockParam=${_blockParam})`, ); - if (transaction?.data?.length >= constants.FUNCTION_SELECTOR_CHAR_LENGTH) + if (transaction?.data?.length >= constants.FUNCTION_SELECTOR_CHAR_LENGTH) { this.ethExecutionsCounter .labels(EthImpl.ethEstimateGas, transaction.data.substring(0, constants.FUNCTION_SELECTOR_CHAR_LENGTH)) .inc(); + } this.contractCallFormat(transaction); let gas = EthImpl.gasTxBaseCost; @@ -634,7 +640,7 @@ export class EthImpl implements Eth { * Perform value format precheck before making contract call towards the mirror node * @param transaction */ - contractCallFormat(transaction: any) { + contractCallFormat(transaction: any): void { if (transaction.value) { transaction.value = weibarHexToTinyBarInt(transaction.value); } @@ -663,7 +669,7 @@ export class EthImpl implements Eth { /** * Gets the current gas price of the network. */ - async gasPrice(requestIdPrefix?: string) { + async gasPrice(requestIdPrefix?: string): Promise { this.logger.trace(`${requestIdPrefix} gasPrice()`); try { let gasPrice: number | undefined = this.cacheService.get( @@ -692,7 +698,7 @@ export class EthImpl implements Eth { /** * Gets whether this "Ethereum client" is a miner. We don't mine, so this always returns false. */ - async mining(requestIdPrefix?: string) { + async mining(requestIdPrefix?: string): Promise { this.logger.trace(`${requestIdPrefix} mining()`); return false; } @@ -700,7 +706,7 @@ export class EthImpl implements Eth { /** * TODO Needs docs, or be removed? */ - async submitWork(requestIdPrefix?: string) { + async submitWork(requestIdPrefix?: string): Promise { this.logger.trace(`${requestIdPrefix} submitWork()`); return false; } @@ -708,7 +714,7 @@ export class EthImpl implements Eth { /** * TODO Needs docs, or be removed? */ - async syncing(requestIdPrefix?: string) { + async syncing(requestIdPrefix?: string): Promise { this.logger.trace(`${requestIdPrefix} syncing()`); return false; } @@ -716,7 +722,7 @@ export class EthImpl implements Eth { /** * Always returns null. There are no uncles in Hedera. */ - async getUncleByBlockHashAndIndex(requestIdPrefix?: string) { + async getUncleByBlockHashAndIndex(requestIdPrefix?: string): Promise { this.logger.trace(`${requestIdPrefix} getUncleByBlockHashAndIndex()`); return null; } @@ -724,7 +730,7 @@ export class EthImpl implements Eth { /** * Always returns null. There are no uncles in Hedera. */ - async getUncleByBlockNumberAndIndex(requestIdPrefix?: string) { + async getUncleByBlockNumberAndIndex(requestIdPrefix?: string): Promise { this.logger.trace(`${requestIdPrefix} getUncleByBlockNumberAndIndex()`); return null; } @@ -732,7 +738,7 @@ export class EthImpl implements Eth { /** * Always returns '0x0'. There are no uncles in Hedera. */ - async getUncleCountByBlockHash(requestIdPrefix?: string) { + async getUncleCountByBlockHash(requestIdPrefix?: string): Promise { this.logger.trace(`${requestIdPrefix} getUncleCountByBlockHash()`); return EthImpl.zeroHex; } @@ -740,7 +746,7 @@ export class EthImpl implements Eth { /** * Always returns '0x0'. There are no uncles in Hedera. */ - async getUncleCountByBlockNumber(requestIdPrefix?: string) { + async getUncleCountByBlockNumber(requestIdPrefix?: string): Promise { this.logger.trace(`${requestIdPrefix} getUncleCountByBlockNumber()`); return EthImpl.zeroHex; } @@ -748,7 +754,7 @@ export class EthImpl implements Eth { /** * TODO Needs docs, or be removed? */ - async hashrate(requestIdPrefix?: string) { + async hashrate(requestIdPrefix?: string): Promise { this.logger.trace(`${requestIdPrefix} hashrate()`); return EthImpl.zeroHex; } @@ -860,7 +866,7 @@ export class EthImpl implements Eth { * @param account * @param blockNumberOrTag */ - async getBalance(account: string, blockNumberOrTagOrHash: string | null, requestIdPrefix?: string) { + async getBalance(account: string, blockNumberOrTagOrHash: string | null, requestIdPrefix?: string): Promise { const latestBlockTolerance = 1; this.logger.trace(`${requestIdPrefix} getBalance(account=${account}, blockNumberOrTag=${blockNumberOrTagOrHash})`); @@ -1023,13 +1029,12 @@ export class EthImpl implements Eth { * @param address * @param blockNumber */ - async getCode(address: string, blockNumber: string | null, requestIdPrefix?: string) { + async getCode(address: string, blockNumber: string | null, requestIdPrefix?: string): Promise { if (!EthImpl.isBlockParamValid(blockNumber)) { throw predefined.UNKNOWN_BLOCK( `The value passed is not a valid blockHash/blockNumber/blockTag value: ${blockNumber}`, ); } - this.logger.trace(`${requestIdPrefix} getCode(address=${address}, blockNumber=${blockNumber})`); // check for static precompile cases first before consulting nodes @@ -1371,7 +1376,13 @@ export class EthImpl implements Eth { } } - async sendRawTransactionErrorHandler(e, transaction, transactionBuffer, txSubmitted, requestIdPrefix) { + async sendRawTransactionErrorHandler( + e, + transaction, + transactionBuffer, + txSubmitted, + requestIdPrefix, + ): Promise { this.logger.error( e, `${requestIdPrefix} Failed to successfully submit sendRawTransaction for transaction ${transaction}`, @@ -1818,7 +1829,7 @@ export class EthImpl implements Eth { * @param call * @param requestIdPrefix */ - async performCallChecks(call: any) { + async performCallChecks(call: any): Promise { // The "to" address must always be 42 chars. if (!call.to || call.to.length != 42) { throw predefined.INVALID_CONTRACT_ADDRESS(call.to); @@ -1829,7 +1840,7 @@ export class EthImpl implements Eth { address: string, requestIdPrefix?: string, searchableTypes = [constants.TYPE_CONTRACT, constants.TYPE_TOKEN, constants.TYPE_ACCOUNT], - ) { + ): Promise { if (!address) return address; const entity = await this.mirrorNodeClient.resolveEntityType( @@ -1856,7 +1867,7 @@ export class EthImpl implements Eth { * * @param hash */ - async getTransactionByHash(hash: string, requestIdPrefix?: string) { + async getTransactionByHash(hash: string, requestIdPrefix?: string): Promise { this.logger.trace(`${requestIdPrefix} getTransactionByHash(hash=${hash})`, hash); if (this.shouldPopulateSyntheticContractResults) { @@ -1869,7 +1880,7 @@ export class EthImpl implements Eth { ); if (cachedLog) { - const tx = this.createTransactionFromLog(cachedLog); + const tx: Transaction1559 = this.createTransactionFromLog(cachedLog); return tx; } } @@ -1900,7 +1911,7 @@ export class EthImpl implements Eth { * * @param hash */ - async getTransactionReceipt(hash: string, requestIdPrefix?: string) { + async getTransactionReceipt(hash: string, requestIdPrefix?: string): Promise { this.logger.trace(`${requestIdPrefix} getTransactionReceipt(${hash})`); const cacheKey = `${constants.CACHE_KEY.ETH_GET_TRANSACTION_RECEIPT}_${hash}`; @@ -1921,7 +1932,7 @@ export class EthImpl implements Eth { if (cachedLog) { const gasPriceForTimestamp = await this.getCurrentGasPriceForBlock(cachedLog.blockHash); - const receipt: any = { + const receipt: ITransactionReceipt = { blockHash: cachedLog.blockHash, blockNumber: cachedLog.blockNumber, contractAddress: cachedLog.address, @@ -1977,7 +1988,7 @@ export class EthImpl implements Eth { }); }); - const receipt: any = { + const receipt: ITransactionReceipt = { blockHash: toHash32(receiptResponse.block_hash), blockNumber: numberTo0x(receiptResponse.block_number), from: await this.resolveEvmAddress(receiptResponse.from, requestIdPrefix), @@ -2033,11 +2044,11 @@ export class EthImpl implements Eth { return input.startsWith(EthImpl.emptyHex) ? input.substring(2) : input; } - private static isBlockTagEarliest = (tag: string) => { + private static isBlockTagEarliest = (tag: string): boolean => { return tag === EthImpl.blockEarliest; }; - private static isBlockTagFinalized = (tag: string) => { + private static isBlockTagFinalized = (tag: string): boolean => { return ( tag === EthImpl.blockFinalized || tag === EthImpl.blockLatest || @@ -2054,7 +2065,7 @@ export class EthImpl implements Eth { return tag == null || this.isBlockTagEarliest(tag) || this.isBlockTagFinalized(tag) || this.isBlockNumValid(tag); }; - private static isBlockHash = (blockHash) => { + private static isBlockHash = (blockHash): boolean => { return new RegExp(constants.BLOCK_HASH_REGEX + '{64}$').test(blockHash); }; @@ -2200,7 +2211,7 @@ export class EthImpl implements Eth { (log) => !transactionArray.some((transaction) => transaction.hash === log.transactionHash), ); filteredLogs.forEach((log) => { - const transaction = this.createTransactionFromLog(log); + const transaction: Transaction1559 = this.createTransactionFromLog(log); transactionArray.push(transaction); const cacheKey = `${constants.CACHE_KEY.SYNTHETIC_LOG_TRANSACTION_HASH}${log.transactionHash}`; @@ -2237,7 +2248,7 @@ export class EthImpl implements Eth { * @param requestIdPrefix * @returns Transaction Object */ - private createTransactionFromLog(log: Log) { + private createTransactionFromLog(log: Log): Transaction1559 { return new Transaction1559({ accessList: undefined, // we don't support access lists for now blockHash: log.blockHash, @@ -2261,7 +2272,7 @@ export class EthImpl implements Eth { }); } - private static getTransactionCountFromBlockResponse(block: any) { + private static getTransactionCountFromBlockResponse(block: any): null | string { if (block === null || block.count === undefined) { // block not found return null; @@ -2270,7 +2281,7 @@ export class EthImpl implements Eth { return numberTo0x(block.count); } - private async getAccountLatestEthereumNonce(address: string, requestId?: string) { + private async getAccountLatestEthereumNonce(address: string, requestId?: string): Promise { const accountData = await this.mirrorNodeClient.getAccount(address, requestId); if (accountData) { // with HIP 729 ethereum_nonce should always be 0+ and null. Historical contracts may have a null value as the nonce was not tracked, return default EVM compliant 0x1 in this case @@ -2293,7 +2304,7 @@ export class EthImpl implements Eth { address: string, blockNumOrHash: any, requestIdPrefix: string | undefined, - ) { + ): Promise { // get block timestamp for blockNum const block = await this.mirrorNodeClient.getBlock(blockNumOrHash, requestIdPrefix); // consider caching error responses if (block == null) { diff --git a/packages/relay/src/lib/types/IFeeHistory.ts b/packages/relay/src/lib/types/IFeeHistory.ts new file mode 100644 index 0000000000..135b58cb46 --- /dev/null +++ b/packages/relay/src/lib/types/IFeeHistory.ts @@ -0,0 +1,6 @@ +export interface IFeeHistory { + baseFeePerGas: string[] | undefined; + gasUsedRatio: number[] | null; + oldestBlock: string; + reward?: string[][]; +} diff --git a/packages/relay/src/lib/types/ITransactionReceipt.ts b/packages/relay/src/lib/types/ITransactionReceipt.ts new file mode 100644 index 0000000000..248b402a91 --- /dev/null +++ b/packages/relay/src/lib/types/ITransactionReceipt.ts @@ -0,0 +1,20 @@ +import type { Log } from '../model'; + +export interface ITransactionReceipt { + blockHash: string; + blockNumber: string; + contractAddress: string; + cumulativeGasUsed: string; + effectiveGasPrice: string; + from: string; + gasUsed: string; + logs: Log[]; + logsBloom: string; + root: string; + status: string; + to: string; + transactionHash: string; + transactionIndex: string | null; + type: string | null; + revertReason?: string; +}