Repository: hiero-ledger/hiero-json-rpc-relay
Pull request #3600: refactor: Create BlockService with block related classes
Author: login: konstantinabl
Labels: name: enhancement
description: New feature or request
Created: 2025-03-26T16:18:57Z | Merged: 2025-04-03T12:00:21Z

**Description**:
<!--
One or two line summary of what this PR does and why it is needed, followed by a list
of changes in imperative, present tense for use in the commit message or changelog. Example:

Add support for ...

* Add config property
* Change column name
* Remove ...
-->

This PR refactors the JSON-RPC relay by extracting block-related functionality from the eth.ts file into a dedicated BlockService class and moving shared code into a CommonService. This improves code organization, testability, and maintainability.

**Changes** 

- Created new BlockService to handle all block-related RPC methods
- Moved methods used only from the block related methods to the blockService - **_populateSyntheticTransactions, getTransactionCountFromBlockResponse_**
- Extracted common functionality into a CommonService class - **_formatContractResult_**
- Fixed type issues in getBlock method
- Made some calls to MN in getBlock method parallel to improve performance
- Added JSDoc where missing

**Moved methods moved to BlockService**:

Main methods:

- getBlockByNumber
- getBlockByHash
- getBlockTransactionCountByHash
- getBlockTransactionCountByNumber
- getBlockReceipts
- getUncleByBlockHashAndIndex
- getUncleByBlockNumberAndIndex
- getUncleCountByBlockHash
- getUncleCountByBlockNumber

Utility methods:

- getBlock
- getTransactionCountFromBlockResponse
- populateSyntheticTransactions

**Related issue(s)**:

Fixes #3569

**Checklist**

- [x] Documented (Code comments, README, etc.)
- [x] Tested (unit, integration, etc.)


Linked issue #3569: Refactor: Create BlockService class
Create a new BlockService class, extracting all methods related to blocks from EthImpl
New BlockService class should look something like that

![Image](https://github.com/user-attachments/assets/2ec5cf5e-e7c4-407a-b081-f9bf7a51c412)

Diff:
diff --git a/packages/relay/src/formatters.ts b/packages/relay/src/formatters.ts
index 37a38b2f27..d86ec27efe 100644
--- a/packages/relay/src/formatters.ts
+++ b/packages/relay/src/formatters.ts
@@ -6,6 +6,7 @@ import { BigNumber as BN } from 'bignumber.js';
 import crypto from 'crypto';
 
 import constants from './lib/constants';
+import { Transaction } from './lib/model';
 import { TransactionFactory } from './lib/services/factories/transactionFactory';
 
 const EMPTY_HEX = '0x';
diff --git a/packages/relay/src/lib/clients/mirrorNodeClient.ts b/packages/relay/src/lib/clients/mirrorNodeClient.ts
index e6ddcf8bb1..d8238238da 100644
--- a/packages/relay/src/lib/clients/mirrorNodeClient.ts
+++ b/packages/relay/src/lib/clients/mirrorNodeClient.ts
@@ -16,7 +16,6 @@ import { predefined } from '../errors/JsonRpcError';
 import { MirrorNodeClientError } from '../errors/MirrorNodeClientError';
 import { SDKClientError } from '../errors/SDKClientError';
 import { EthImpl } from '../eth';
-import { IBlockMirrorNode } from '../services/blockService/IBlockService';
 import { CacheService } from '../services/cacheService/cacheService';
 import {
   IContractCallRequest,
@@ -29,6 +28,7 @@ import {
   MirrorNodeTransactionRecord,
   RequestDetails,
 } from '../types';
+import { IMirrorNodeBlock } from '../types/IMirrorNodeBlock';
 import constants from './../constants';
 import { IOpcodesResponse } from './models/IOpcodesResponse';
 
@@ -588,7 +588,7 @@ export class MirrorNodeClient {
     );
   }
 
-  public async getBlock(hashOrBlockNumber: string | number, requestDetails: RequestDetails): Promise<IBlockMirrorNode> {
+  public async getBlock(hashOrBlockNumber: string | number, requestDetails: RequestDetails): Promise<IMirrorNodeBlock> {
     const cachedLabel = `${constants.CACHE_KEY.GET_BLOCK}.${hashOrBlockNumber}`;
     const cachedResponse: any = await this.cacheService.getAsync(
       cachedLabel,
diff --git a/packages/relay/src/lib/debug.ts b/packages/relay/src/lib/debug.ts
index f194c8b8bb..0b07be868c 100644
--- a/packages/relay/src/lib/debug.ts
+++ b/packages/relay/src/lib/debug.ts
@@ -11,10 +11,10 @@ import { IOpcodesResponse } from './clients/models/IOpcodesResponse';
 import constants, { CallType, TracerType } from './constants';
 import { rpcMethod, rpcParamValidationRules } from './decorators';
 import { predefined } from './errors/JsonRpcError';
+import { CommonService } from './services';
 import { CacheService } from './services/cacheService/cacheService';
-import { CommonService } from './services/ethService/ethCommonService';
-import { ICallTracerConfig, IOpcodeLoggerConfig, ITracerConfig, ParamType, RequestDetails } from './types';
 import HAPIService from './services/hapiService/hapiService';
+import { ICallTracerConfig, IOpcodeLoggerConfig, ITracerConfig, ParamType, RequestDetails } from './types';
 
 /**
  * Represents a DebugService for tracing and debugging transactions.
@@ -53,7 +53,12 @@ export class DebugImpl implements Debug {
    * @param {CacheService} cacheService - Service for managing cached data.
    * @param {HAPIService} hapiService - Hapi service for making SDK calls.
    */
-  constructor(mirrorNodeClient: MirrorNodeClient, logger: Logger, cacheService: CacheService, hapiService: HAPIService) {
+  constructor(
+    mirrorNodeClient: MirrorNodeClient,
+    logger: Logger,
+    cacheService: CacheService,
+    hapiService: HAPIService,
+  ) {
     this.logger = logger;
     this.common = new CommonService(mirrorNodeClient, logger, cacheService, hapiService);
     this.mirrorNodeClient = mirrorNodeClient;
diff --git a/packages/relay/src/lib/eth.ts b/packages/relay/src/lib/eth.ts
index d51726bd3a..5e6935c880 100644
--- a/packages/relay/src/lib/eth.ts
+++ b/packages/relay/src/lib/eth.ts
@@ -25,7 +25,6 @@ import {
 } from '../formatters';
 import { Eth } from '../index';
 import { LogsBloomUtils } from '../logsBloomUtils';
-import { IReceiptRootHash, ReceiptsRootUtils } from '../receiptsRootUtils';
 import { Utils } from '../utils';
 import { MirrorNodeClient } from './clients';
 import constants from './constants';
@@ -35,21 +34,14 @@ import { MirrorNodeClientError } from './errors/MirrorNodeClientError';
 import { SDKClientError } from './errors/SDKClientError';
 import { Block, Log, Receipt, Transaction, Transaction1559 } from './model';
 import { Precheck } from './precheck';
+import { BlockService, CommonService, FilterService, IBlockService, ICommonService } from './services';
 import { CacheService } from './services/cacheService/cacheService';
-import { CommonService, FilterService } from './services/ethService';
-import HAPIService from './services/hapiService/hapiService';
-import {
-  IContractCallRequest,
-  IContractCallResponse,
-  IFeeHistory,
-  IGetLogsParams,
-  INewFilterParams,
-  ITransactionReceipt,
-  RequestDetails,
-} from './types';
-import { IAccountInfo, IContractResultsParams } from './types/mirrorNode';
 import { FeeService } from './services/feeService';
 import { AccountService } from './services/accountService';
+import HAPIService from './services/hapiService/hapiService';
+import { IGetLogsParams, INewFilterParams } from './types';
+import { IContractCallRequest, IContractCallResponse, IFeeHistory, ITransactionReceipt, RequestDetails } from './types';
+import { IAccountInfo, IContractResultsParams } from './types/mirrorNode';
 const _ = require('lodash');
 import { ParamType } from './types/validation';
 import { IAccountService } from './services/accountService/IAccountService';
@@ -184,13 +176,18 @@ export class EthImpl implements Eth {
   /**
    * The Common Service implementation that contains logic shared by other services.
    */
-  private readonly common: CommonService;
+  private readonly common: ICommonService;
 
   /**
    * The Filter Service implementation that takes care of all filter API operations.
    */
   private readonly filterService: FilterService;
 
+  /**
+   * The Block Service implementation that takes care of all block API operations.
+   */
+  private readonly blockService: IBlockService;
+
   /**
    * The Fee Service implementation that takes care of all fee API operations.
    */
@@ -235,6 +232,7 @@ export class EthImpl implements Eth {
     this.filterService = new FilterService(mirrorNodeClient, logger, cacheService, this.common);
     this.feeService = new FeeService(mirrorNodeClient, this.common, logger, cacheService);
     this.accountService = new AccountService(cacheService, this.common, logger, mirrorNodeClient);
+    this.blockService = new BlockService(cacheService, chain, this.common, mirrorNodeClient, logger);
   }
 
   private initCounter(metricCounterName: string, labelNames: string[], register: Registry): Counter {
@@ -732,10 +730,7 @@ export class EthImpl implements Eth {
   @rpcMethod
   @rpcParamLayoutConfig(RPC_LAYOUT.REQUEST_DETAILS_ONLY)
   async getUncleByBlockHashAndIndex(requestDetails: RequestDetails): Promise<null> {
-    if (this.logger.isLevelEnabled('trace')) {
-      this.logger.trace(`${requestDetails.formattedRequestId} getUncleByBlockHashAndIndex()`);
-    }
-    return null;
+    return this.blockService.getUncleByBlockHashAndIndex(requestDetails);
   }
 
   /**
@@ -750,10 +745,7 @@ export class EthImpl implements Eth {
   @rpcMethod
   @rpcParamLayoutConfig(RPC_LAYOUT.REQUEST_DETAILS_ONLY)
   async getUncleByBlockNumberAndIndex(requestDetails: RequestDetails): Promise<null> {
-    if (this.logger.isLevelEnabled('trace')) {
-      this.logger.trace(`${requestDetails.formattedRequestId} getUncleByBlockNumberAndIndex()`);
-    }
-    return null;
+    return this.blockService.getUncleByBlockNumberAndIndex(requestDetails);
   }
 
   /**
@@ -768,10 +760,7 @@ export class EthImpl implements Eth {
   @rpcMethod
   @rpcParamLayoutConfig(RPC_LAYOUT.REQUEST_DETAILS_ONLY)
   async getUncleCountByBlockHash(requestDetails: RequestDetails): Promise<string> {
-    if (this.logger.isLevelEnabled('trace')) {
-      this.logger.trace(`${requestDetails.formattedRequestId} getUncleCountByBlockHash()`);
-    }
-    return EthImpl.zeroHex;
+    return this.blockService.getUncleCountByBlockHash(requestDetails);
   }
 
   /**
@@ -786,10 +775,7 @@ export class EthImpl implements Eth {
   @rpcMethod
   @rpcParamLayoutConfig(RPC_LAYOUT.REQUEST_DETAILS_ONLY)
   async getUncleCountByBlockNumber(requestDetails: RequestDetails): Promise<string> {
-    if (this.logger.isLevelEnabled('trace')) {
-      this.logger.trace(`${requestDetails.formattedRequestId} getUncleCountByBlockNumber()`);
-    }
-    return EthImpl.zeroHex;
+    return this.blockService.getUncleCountByBlockNumber(requestDetails);
   }
 
   /**
@@ -1162,61 +1148,7 @@ export class EthImpl implements Eth {
     1: { type: ParamType.BOOLEAN, required: true },
   })
   async getBlockByHash(hash: string, showDetails: boolean, requestDetails: RequestDetails): Promise<Block | null> {
-    const requestIdPrefix = requestDetails.formattedRequestId;
-    this.logger.trace(`${requestIdPrefix} getBlockByHash(hash=${hash}, showDetails=${showDetails})`);
-
-    const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_BY_HASH}_${hash}_${showDetails}`;
-    let block = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetBlockByHash, requestDetails);
-    if (!block) {
-      block = await this.getBlock(hash, showDetails, requestDetails).catch((e: any) => {
-        throw this.common.genericErrorHandler(e, `${requestIdPrefix} Failed to retrieve block for hash ${hash}`);
-      });
-      await this.cacheService.set(cacheKey, block, EthImpl.ethGetBlockByHash, requestDetails);
-    }
-
-    return block;
-  }
-
-  /**
-   * Retrieves the block associated with the specified block number or tag.
-   *
-   * @rpcMethod Exposed as eth_getBlockByNumber RPC endpoint
-   * @rpcParamValidationRules Applies JSON-RPC parameter validation according to the API specification
-   *
-   * @param {string} blockNumOrTag - The block number or tag. Possible values include 'earliest', 'pending', 'latest', or a hexadecimal block number. This parameter cannot be null.
-   * @param {boolean} showDetails - Indicates whether to include detailed information about the block.
-   * @param {RequestDetails} requestDetails - The details of the request for logging and tracking purposes.
-   * @returns {Promise<Block | null>} A promise that resolves to the block object or null if the block is not found.
-   */
-  @rpcMethod
-  @rpcParamValidationRules({
-    0: { type: ParamType.BLOCK_NUMBER, required: true },
-    1: { type: ParamType.BOOLEAN, required: true },
-  })
-  async getBlockByNumber(
-    blockNumOrTag: string,
-    showDetails: boolean,
-    requestDetails: RequestDetails,
-  ): Promise<Block | null> {
-    const requestIdPrefix = requestDetails.formattedRequestId;
-    this.logger.trace(`${requestIdPrefix} getBlockByNumber(blockNum=${blockNumOrTag}, showDetails=${showDetails})`);
-
-    const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_BY_NUMBER}_${blockNumOrTag}_${showDetails}`;
-    let block = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetBlockByNumber, requestDetails);
-    if (!block) {
-      block = await this.getBlock(blockNumOrTag, showDetails, requestDetails).catch((e: any) => {
-        throw this.common.genericErrorHandler(
-          e,
-          `${requestIdPrefix} Failed to retrieve block for blockNum ${blockNumOrTag}`,
-        );
-      });
-
-      if (!this.common.blockTagIsLatestOrPending(blockNumOrTag)) {
-        await this.cacheService.set(cacheKey, block, EthImpl.ethGetBlockByNumber, requestDetails);
-      }
-    }
-
-    return block;
+    return this.blockService.getBlockByHash(hash, showDetails, requestDetails);
   }
 
   /**
@@ -1234,33 +1166,7 @@ export class EthImpl implements Eth {
     0: { type: ParamType.BLOCK_HASH, required: true },
   })
   async getBlockTransactionCountByHash(hash: string, requestDetails: RequestDetails): Promise<string | null> {
-    const requestIdPrefix = requestDetails.formattedRequestId;
-    this.logger.trace(`${requestIdPrefix} getBlockTransactionCountByHash(hash=${hash}, showDetails=%o)`);
-
-    const cacheKey = `${constants.CACHE_KEY.ETH_GET_TRANSACTION_COUNT_BY_HASH}_${hash}`;
-    const cachedResponse = await this.cacheService.getAsync(
-      cacheKey,
-      EthImpl.ethGetTransactionCountByHash,
-      requestDetails,
-    );
-    if (cachedResponse) {
-      if (this.logger.isLevelEnabled('debug')) {
-        this.logger.debug(
-          `${requestIdPrefix} getBlockTransactionCountByHash returned cached response: ${cachedResponse}`,
-        );
-      }
-      return cachedResponse;
-    }
-
-    const transactionCount = await this.mirrorNodeClient
-      .getBlock(hash, requestDetails)
-      .then((block) => EthImpl.getTransactionCountFromBlockResponse(block))
-      .catch((e: any) => {
-        throw this.common.genericErrorHandler(e, `${requestIdPrefix} Failed to retrieve block for hash ${hash}`);
-      });
-
-    await this.cacheService.set(cacheKey, transactionCount, EthImpl.ethGetTransactionCountByHash, requestDetails);
-    return transactionCount;
+    return this.blockService.getBlockTransactionCountByHash(hash, requestDetails);
   }
 
   /**
@@ -1281,41 +1187,7 @@ export class EthImpl implements Eth {
     blockNumOrTag: string,
     requestDetails: RequestDetails,
   ): Promise<string | null> {
-    const requestIdPrefix = requestDetails.formattedRequestId;
-    if (this.logger.isLevelEnabled('trace')) {
-      this.logger.trace(
-        `${requestIdPrefix} getBlockTransactionCountByNumber(blockNum=${blockNumOrTag}, showDetails=%o)`,
-      );
-    }
-    const blockNum = await this.common.translateBlockTag(blockNumOrTag, requestDetails);
-
-    const cacheKey = `${constants.CACHE_KEY.ETH_GET_TRANSACTION_COUNT_BY_NUMBER}_${blockNum}`;
-    const cachedResponse = await this.cacheService.getAsync(
-      cacheKey,
-      EthImpl.ethGetTransactionCountByNumber,
-      requestDetails,
-    );
-    if (cachedResponse) {
-      if (this.logger.isLevelEnabled('debug')) {
-        this.logger.debug(
-          `${requestIdPrefix} getBlockTransactionCountByNumber returned cached response: ${cachedResponse}`,
-        );
-      }
-      return cachedResponse;
-    }
-
-    const transactionCount = await this.mirrorNodeClient
-      .getBlock(blockNum, requestDetails)
-      .then((block) => EthImpl.getTransactionCountFromBlockResponse(block))
-      .catch((e: any) => {
-        throw this.common.genericErrorHandler(
-          e,
-          `${requestIdPrefix} Failed to retrieve block for blockNum ${blockNum}`,
-        );
-      });
-
-    await this.cacheService.set(cacheKey, transactionCount, EthImpl.ethGetTransactionCountByNumber, requestDetails);
-    return transactionCount;
+    return this.blockService.getBlockTransactionCountByNumber(blockNumOrTag, requestDetails);
   }
 
   /**
@@ -1403,6 +1275,30 @@ export class EthImpl implements Eth {
     }
   }
 
+  /**
+   * Retrieves the block associated with the specified block number or tag.
+   *
+   * @rpcMethod Exposed as eth_getBlockByNumber RPC endpoint
+   * @rpcParamValidationRules Applies JSON-RPC parameter validation according to the API specification
+   *
+   * @param {string} blockNumOrTag - The block number or tag. Possible values include 'earliest', 'pending', 'latest', or a hexadecimal block number. This parameter cannot be null.
+   * @param {boolean} showDetails - Indicates whether to include detailed information about the block.
+   * @param {RequestDetails} requestDetails - The details of the request for logging and tracking purposes.
+   * @returns {Promise<Block | null>} A promise that resolves to the block object or null if the block is not found.
+   */
+  @rpcMethod
+  @rpcParamValidationRules({
+    0: { type: ParamType.BLOCK_NUMBER, required: true },
+    1: { type: ParamType.BOOLEAN, required: true },
+  })
+  async getBlockByNumber(
+    blockNumOrTag: string,
+    showDetails: boolean,
+    requestDetails: RequestDetails,
+  ): Promise<Block | null> {
+    return this.blockService.getBlockByNumber(blockNumOrTag, showDetails, requestDetails);
+  }
+
   /**
    * Gets the number of transactions that have been executed for the given address.
    * This goes to the consensus nodes to determine the ethereumNonce.
@@ -1840,7 +1736,11 @@ export class EthImpl implements Eth {
       constants.TYPE_ACCOUNT,
     ]);
 
-    return CommonService.formatContractResult({ ...contractResults[0], from: resolvedFromAddress, to: resolvedToAddress });
+    return CommonService.formatContractResult({
+      ...contractResults[0],
+      from: resolvedFromAddress,
+      to: resolvedToAddress,
+    });
   }
 
   // according to EIP-1898 (https://eips.ethereum.org/EIPS/eip-1898) block param can either be a string (blockNumber or Block Tag) or an object (blockHash or blockNumber)
@@ -1881,9 +1781,9 @@ export class EthImpl implements Eth {
   }
 
   private async getBlockNumberFromHash(blockHash: string, requestDetails: RequestDetails): Promise<string> {
-    const block = await this.getBlockByHash(blockHash, false, requestDetails);
+    const block = await this.mirrorNodeClient.getBlock(blockHash, requestDetails);
     if (block != null) {
-      return block.number;
+      return numberTo0x(block.number);
     } else {
       throw predefined.RESOURCE_NOT_FOUND(`Block Hash: '${blockHash}'`);
     }
@@ -2204,7 +2104,10 @@ export class EthImpl implements Eth {
         return null;
       }
 
-      const gasPriceForTimestamp = await this.getCurrentGasPriceForBlock(syntheticLogs[0].blockHash, requestDetails);
+      const gasPriceForTimestamp = await this.common.getCurrentGasPriceForBlock(
+        syntheticLogs[0].blockHash,
+        requestDetails,
+      );
       const receipt: ITransactionReceipt = {
         blockHash: syntheticLogs[0].blockHash,
         blockNumber: syntheticLogs[0].blockNumber,
@@ -2236,7 +2139,7 @@ export class EthImpl implements Eth {
       );
       return receipt;
     } else {
-      const effectiveGas = await this.getCurrentGasPriceForBlock(receiptResponse.block_hash, requestDetails);
+      const effectiveGas = await this.common.getCurrentGasPriceForBlock(receiptResponse.block_hash, requestDetails);
       // support stricter go-eth client which requires the transaction hash property on logs
       const logs = receiptResponse.logs.map((log) => {
         return new Log({
@@ -2252,7 +2155,7 @@ export class EthImpl implements Eth {
         });
       });
 
-      const contractAddress = this.getContractAddressFromReceipt(receiptResponse);
+      const contractAddress = this.common.getContractAddressFromReceipt(receiptResponse);
       const receipt: ITransactionReceipt = {
         blockHash: toHash32(receiptResponse.block_hash),
         blockNumber: numberTo0x(receiptResponse.block_number),
@@ -2292,42 +2195,6 @@ export class EthImpl implements Eth {
     }
   }
 
-  /**
-   * This method retrieves the contract address from the receipt response.
-   * If the contract creation is via a system contract, it handles the system contract creation.
-   * If not, it returns the address from the receipt response.
-   *
-   * @param {any} receiptResponse - The receipt response object.
-   * @returns {string} The contract address.
-   */
-  private getContractAddressFromReceipt(receiptResponse: any): string {
-    const isCreationViaSystemContract = constants.HTS_CREATE_FUNCTIONS_SELECTORS.includes(
-      receiptResponse.function_parameters.substring(0, constants.FUNCTION_SELECTOR_CHAR_LENGTH),
-    );
-
-    if (!isCreationViaSystemContract) {
-      return receiptResponse.address;
-    }
-
-    // Handle system contract creation
-    // reason for substring is described in the design doc in this repo: docs/design/hts_address_tx_receipt.md
-    const tokenAddress = receiptResponse.call_result.substring(receiptResponse.call_result.length - 40);
-    return prepend0x(tokenAddress);
-  }
-
-  private async getCurrentGasPriceForBlock(blockHash: string, requestDetails: RequestDetails): Promise<string> {
-    const block = await this.getBlockByHash(blockHash, false, requestDetails);
-    const timestampDecimal = parseInt(block ? block.timestamp : '0', 16);
-    const timestampDecimalString = timestampDecimal > 0 ? timestampDecimal.toString() : '';
-    const gasPriceForTimestamp = await this.common.getFeeWeibars(
-      EthImpl.ethGetTransactionReceipt,
-      requestDetails,
-      timestampDecimalString,
-    );
-
-    return numberTo0x(gasPriceForTimestamp);
-  }
-
   private static redirectBytecodeAddressReplace(address: string): string {
     return `${this.redirectBytecodePrefix}${address.slice(2)}${this.redirectBytecodePostfix}`;
   }
@@ -2382,135 +2249,6 @@ export class EthImpl implements Eth {
     return gas;
   }
 
-  populateSyntheticTransactions(
-    showDetails: boolean,
-    logs: Log[],
-    transactionsArray: Array<any>,
-    requestDetails: RequestDetails,
-  ): Array<any> {
-    let filteredLogs: Log[];
-    if (showDetails) {
-      filteredLogs = logs.filter(
-        (log) => !transactionsArray.some((transaction) => transaction.hash === log.transactionHash),
-      );
-      filteredLogs.forEach((log) => {
-        const transaction: Transaction1559 = this.createTransactionFromLog(log);
-        transactionsArray.push(transaction);
-      });
-    } else {
-      filteredLogs = logs.filter((log) => !transactionsArray.includes(log.transactionHash));
-      filteredLogs.forEach((log) => {
-        transactionsArray.push(log.transactionHash);
-      });
-    }
-
-    if (this.logger.isLevelEnabled('trace')) {
-      this.logger.trace(
-        `${requestDetails.formattedRequestId} Synthetic transaction hashes will be populated in the block response`,
-      );
-    }
-
-    return transactionsArray;
-  }
-
-  /**
-   * Gets the block with the given hash.
-   * Given an ethereum transaction hash, call the mirror node to get the block info.
-   * Then using the block timerange get all contract results to get transaction details.
-   * If showDetails is set to true subsequently call mirror node for additional transaction details
-   *
-   * @param {string} blockHashOrNumber The block hash or block number
-   * @param {boolean} showDetails Whether to show transaction details
-   * @param {RequestDetails} requestDetails The request details for logging and tracking
-   */
-  private async getBlock(
-    blockHashOrNumber: string,
-    showDetails: boolean,
-    requestDetails: RequestDetails,
-  ): Promise<Block | null> {
-    const blockResponse = await this.common.getHistoricalBlockResponse(requestDetails, blockHashOrNumber, true);
-
-    if (blockResponse == null) return null;
-    const timestampRange = blockResponse.timestamp;
-    const timestampRangeParams = [`gte:${timestampRange.from}`, `lte:${timestampRange.to}`];
-
-    const contractResults = await this.mirrorNodeClient.getContractResultWithRetry(
-      this.mirrorNodeClient.getContractResults.name,
-      [requestDetails, { timestamp: timestampRangeParams }, undefined],
-      requestDetails,
-    );
-    const gasUsed = blockResponse.gas_used;
-    const params = { timestamp: timestampRangeParams };
-
-    // get contract results logs using block timestamp range
-    const logs = await this.common.getLogsWithParams(null, params, requestDetails);
-
-    if (contractResults == null && logs.length == 0) {
-      // contract result not found
-      return null;
-    }
-
-    // The consensus timestamp of the block, with the nanoseconds part omitted.
-    const timestamp = timestampRange.from.substring(0, timestampRange.from.indexOf('.'));
-    if (showDetails && contractResults.length >= this.ethGetTransactionCountMaxBlockRange) {
-      throw predefined.MAX_BLOCK_SIZE(blockResponse.count);
-    }
-
-    // prepare transactionArray
-    let transactionArray: any[] = [];
-    for (const contractResult of contractResults) {
-      // there are several hedera-specific validations that occur right before entering the evm
-      // if a transaction has reverted there, we should not include that tx in the block response
-      if (Utils.isRevertedDueToHederaSpecificValidation(contractResult)) {
-        if (this.logger.isLevelEnabled('debug')) {
-          this.logger.debug(
-            `${requestDetails.formattedRequestId} Transaction with hash ${contractResult.hash} is skipped due to hedera-specific validation failure (${contractResult.result})`,
-          );
-        }
-        continue;
-      }
-      contractResult.from = await this.resolveEvmAddress(contractResult.from, requestDetails, [constants.TYPE_ACCOUNT]);
-      contractResult.to = await this.resolveEvmAddress(contractResult.to, requestDetails);
-      contractResult.chain_id = contractResult.chain_id || this.chain;
-
-      transactionArray.push(showDetails ? CommonService.formatContractResult(contractResult) : contractResult.hash);
-    }
-
-    transactionArray = this.populateSyntheticTransactions(showDetails, logs, transactionArray, requestDetails);
-    transactionArray = showDetails ? transactionArray : _.uniq(transactionArray);
-
-    const formattedReceipts: IReceiptRootHash[] = ReceiptsRootUtils.buildReceiptRootHashes(
-      transactionArray.map((tx) => (showDetails ? tx.hash : tx)),
-      contractResults,
-      logs,
-    );
-
-    const blockHash = toHash32(blockResponse.hash);
-    return new Block({
-      baseFeePerGas: await this.gasPrice(requestDetails),
-      difficulty: EthImpl.zeroHex,
-      extraData: EthImpl.emptyHex,
-      gasLimit: numberTo0x(constants.BLOCK_GAS_LIMIT),
-      gasUsed: numberTo0x(gasUsed),
-      hash: blockHash,
-      logsBloom: blockResponse.logs_bloom === EthImpl.emptyHex ? EthImpl.emptyBloom : blockResponse.logs_bloom,
-      miner: EthImpl.zeroAddressHex,
-      mixHash: EthImpl.zeroHex32Byte,
-      nonce: EthImpl.zeroHex8Byte,
-      number: numberTo0x(blockResponse.number),
-      parentHash: blockResponse.previous_hash.substring(0, 66),
-      receiptsRoot: await ReceiptsRootUtils.getRootHash(formattedReceipts),
-      timestamp: numberTo0x(Number(timestamp)),
-      sha3Uncles: EthImpl.emptyArrayHex,
-      size: numberTo0x(blockResponse.size | 0),
-      stateRoot: constants.DEFAULT_ROOT_HASH,
-      totalDifficulty: EthImpl.zeroHex,
-      transactions: transactionArray,
-      transactionsRoot: transactionArray.length == 0 ? constants.DEFAULT_ROOT_HASH : blockHash,
-      uncles: [],
-    });
-  }
-
   private createTransactionFromLog(log: Log): Transaction1559 {
     return new Transaction1559({
       accessList: undefined, // we don't support access lists for now
@@ -2649,70 +2387,6 @@ export class EthImpl implements Eth {
     0: { type: ParamType.BLOCK_NUMBER_OR_HASH, required: true },
   })
   public async getBlockReceipts(blockHashOrBlockNumber: string, requestDetails: RequestDetails): Promise<Receipt[]> {
-    const requestIdPrefix = requestDetails.formattedRequestId;
-    if (this.logger.isLevelEnabled('trace')) {
-      this.logger.trace(`${requestIdPrefix} getBlockReceipt(${JSON.stringify(blockHashOrBlockNumber)})`);
-    }
-
-    const block = await this.common.getHistoricalBlockResponse(requestDetails, blockHashOrBlockNumber);
-    const blockNumber = block.number;
-
-    const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_RECEIPTS}_${blockNumber}`;
-    const cachedResponse = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetBlockReceipts, requestDetails);
-    if (cachedResponse) {
-      if (this.logger.isLevelEnabled('debug')) {
-        this.logger.debug(
-          `${requestIdPrefix} getBlockReceipts returned cached response: ${JSON.stringify(cachedResponse)}`,
-        );
-      }
-      return cachedResponse;
-    }
-
-    const paramTimestamp: IContractResultsParams = {
-      timestamp: [`lte:${block.timestamp.to}`, `gte:${block.timestamp.from}`],
-    };
-
-    const contractResults = await this.mirrorNodeClient.getContractResults(requestDetails, paramTimestamp);
-    if (!contractResults || contractResults.length === 0) {
-      return [];
-    }
-
-    const effectiveGas = await this.getCurrentGasPriceForBlock(block.hash, requestDetails);
-
-    const logs = await this.common.getLogsWithParams(null, paramTimestamp, requestDetails);
-    contractResults.forEach((contractResult) => {
-      contractResult.logs = logs.filter((log) => log.transactionHash === contractResult.hash);
-    });
-
-    const receipts: Receipt[] = [];
-
-    for (const contractResult of contractResults) {
-      const from = await this.resolveEvmAddress(contractResult.from, requestDetails);
-      const to = await this.resolveEvmAddress(contractResult.to, requestDetails);
-
-      const contractAddress = this.getContractAddressFromReceipt(contractResult);
-      const receipt = {
-        blockHash: toHash32(contractResult.block_hash),
-        blockNumber: numberTo0x(contractResult.block_number),
-        from: from,
-        to: to,
-        cumulativeGasUsed: numberTo0x(contractResult.block_gas_used),
-        gasUsed: nanOrNumberTo0x(contractResult.gas_used),
-        contractAddress: contractAddress,
-        logs: contractResult.logs,
-        logsBloom: contractResult.bloom === EthImpl.emptyHex ? EthImpl.emptyBloom : contractResult.bloom,
-        transactionHash: toHash32(contractResult.hash),
-        transactionIndex: numberTo0x(contractResult.transaction_index),
-        effectiveGasPrice: effectiveGas,
-        root: contractResult.root || constants.DEFAULT_ROOT_HASH,
-        status: contractResult.status,
-        type: nullableNumberTo0x(contractResult.type),
-      };
-
-      receipts.push(receipt);
-    }
-
-    await this.cacheService.set(cacheKey, receipts, EthImpl.ethGetBlockReceipts, requestDetails);
-    return receipts;
+    return await this.blockService.getBlockReceipts(blockHashOrBlockNumber, requestDetails);
   }
 }
diff --git a/packages/relay/src/lib/services/accountService/index.ts b/packages/relay/src/lib/services/accountService/index.ts
index afa3e1af01..0809d7910c 100644
--- a/packages/relay/src/lib/services/accountService/index.ts
+++ b/packages/relay/src/lib/services/accountService/index.ts
@@ -1,15 +1,16 @@
 // SPDX-License-Identifier: Apache-2.0
 
-import { IAccountService } from './IAccountService';
-import { RequestDetails } from '../../types';
-import { JsonRpcError, predefined } from '../../errors/JsonRpcError';
-import constants from '../../constants';
+import { Logger } from 'pino';
+
 import { numberTo0x, parseNumericEnvVar } from '../../../formatters';
-import { CommonService } from '../ethService';
 import { MirrorNodeClient } from '../../clients';
-import { Logger } from 'pino';
+import constants from '../../constants';
+import { JsonRpcError, predefined } from '../../errors/JsonRpcError';
+import { RequestDetails } from '../../types';
 import { CacheService } from '../cacheService/cacheService';
 import { LatestBlockNumberTimestamp } from '../ethService/ethCommonService/ITimestamp';
+import { CommonService, ICommonService } from '../index';
+import { IAccountService } from './IAccountService';
 
 export class AccountService implements IAccountService {
   /**
@@ -24,14 +25,14 @@ export class AccountService implements IAccountService {
    *
    * @private
    */
-  private readonly common: CommonService;
+  private readonly common: ICommonService;
 
   /**
    * @private
    */
   private readonly ethBlockNumberCacheTtlMs = parseNumericEnvVar(
     'ETH_BLOCK_NUMBER_CACHE_TTL_MS',
-    'ETH_BLOCK_NUMBER_CACHE_TTL_MS_DEFAULT'
+    'ETH_BLOCK_NUMBER_CACHE_TTL_MS_DEFAULT',
   );
 
   /**
@@ -39,7 +40,7 @@ export class AccountService implements IAccountService {
    */
   private readonly ethGetBalanceCacheTtlMs = parseNumericEnvVar(
     'ETH_GET_BALANCE_CACHE_TTL_MS',
-    'ETH_GET_BALANCE_CACHE_TTL_MS_DEFAULT'
+    'ETH_GET_BALANCE_CACHE_TTL_MS_DEFAULT',
   );
 
   /**
@@ -47,7 +48,7 @@ export class AccountService implements IAccountService {
    */
   private readonly ethGetTransactionCountCacheTtl = parseNumericEnvVar(
     'ETH_GET_TRANSACTION_COUNT_CACHE_TTL',
-    'ETH_GET_TRANSACTION_COUNT_CACHE_TTL'
+    'ETH_GET_TRANSACTION_COUNT_CACHE_TTL',
   );
 
   /**
@@ -76,7 +77,7 @@ export class AccountService implements IAccountService {
    * @param logger
    * @param mirrorNodeClient
    */
-  constructor(cacheService: CacheService, common: CommonService, logger: Logger, mirrorNodeClient: MirrorNodeClient) {
+  constructor(cacheService: CacheService, common: ICommonService, logger: Logger, mirrorNodeClient: MirrorNodeClient) {
     this.cacheService = cacheService;
     this.common = common;
     this.logger = logger;
@@ -94,12 +95,12 @@ export class AccountService implements IAccountService {
   public async getBalance(
     account: string,
     blockNumberOrTagOrHash: string | null,
-    requestDetails: RequestDetails
+    requestDetails: RequestDetails,
   ): Promise<string> {
     const requestIdPrefix = requestDetails.formattedRequestId;
     if (this.logger.isLevelEnabled('trace')) {
       this.logger.trace(
-        `${requestIdPrefix} getBalance(account=${account}, blockNumberOrTag=${blockNumberOrTagOrHash})`
+        `${requestIdPrefix} getBalance(account=${account}, blockNumberOrTag=${blockNumberOrTagOrHash})`,
       );
     }
 
@@ -107,10 +108,10 @@ export class AccountService implements IAccountService {
     // this check is required, because some tools like Metamask pass for parameter latest block, with a number (ex 0x30ea)
     // tolerance is needed, because there is a small delay between requesting latest block from blockNumber and passing it here
     if (!this.common.blockTagIsLatestOrPending(blockNumberOrTagOrHash)) {
-      ({
-        latestBlock,
-        blockNumberOrTagOrHash
-      } = await this.extractBlockNumberAndTimestamp(blockNumberOrTagOrHash, requestDetails));
+      ({ latestBlock, blockNumberOrTagOrHash } = await this.extractBlockNumberAndTimestamp(
+        blockNumberOrTagOrHash,
+        requestDetails,
+      ));
     }
 
     // check cache first
@@ -137,10 +138,12 @@ export class AccountService implements IAccountService {
           // A blockNumberOrTag has been provided. If it is `latest` or `pending` retrieve the balance from /accounts/{account.id}
           // If the parsed blockNumber is the same as the one from the latest block retrieve the balance from /accounts/{account.id}
           if (latestBlock && block.number !== latestBlock.blockNumber) {
-            ({
-              balanceFound,
-              weibars
-            } = await this.getBalanceAtBlockNumber(account, block, latestBlock, requestDetails));
+            ({ balanceFound, weibars } = await this.getBalanceAtBlockNumber(
+              account,
+              block,
+              latestBlock,
+              requestDetails,
+            ));
           }
         }
       }
@@ -159,8 +162,8 @@ export class AccountService implements IAccountService {
         if (this.logger.isLevelEnabled('debug')) {
           this.logger.debug(
             `${requestIdPrefix} Unable to find account ${account} in block ${JSON.stringify(
-              blockNumber
-            )}(${blockNumberOrTagOrHash}), returning 0x0 balance`
+              blockNumber,
+            )}(${blockNumberOrTagOrHash}), returning 0x0 balance`,
           );
         }
         return CommonService.zeroHex;
@@ -176,14 +179,14 @@ export class AccountService implements IAccountService {
         cachedBalance,
         CommonService.ethGetBalance,
         requestDetails,
-        this.ethGetBalanceCacheTtlMs
+        this.ethGetBalanceCacheTtlMs,
       );
 
       return cachedBalance;
     } catch (error: any) {
       throw this.common.genericErrorHandler(
         error,
-        `${requestIdPrefix} Error raised during getBalance for account ${account}`
+        `${requestIdPrefix} Error raised during getBalance for account ${account}`,
       );
     }
   }
@@ -203,7 +206,7 @@ export class AccountService implements IAccountService {
     if (blockNumberCached) {
       if (this.logger.isLevelEnabled('trace')) {
         this.logger.trace(
-          `${requestDetails.requestId} returning cached value ${cacheKey}:${JSON.stringify(blockNumberCached)}`
+          `${requestDetails.requestId} returning cached value ${cacheKey}:${JSON.stringify(blockNumberCached)}`,
         );
       }
       latestBlock = { blockNumber: blockNumberCached, timeStampTo: '0' };
@@ -269,11 +272,7 @@ export class AccountService implements IAccountService {
     const timeDiff = latestTimestamp - blockTimestamp;
     // The block is NOT from the last 15 minutes, use /balances rest API
     if (timeDiff > constants.BALANCES_UPDATE_INTERVAL) {
-      const balance = await this.mirrorNodeClient.getBalanceAtTimestamp(
-        account,
-        requestDetails,
-        block.timestamp.from
-      );
+      const balance = await this.mirrorNodeClient.getBalanceAtTimestamp(account, requestDetails, block.timestamp.from);
       balanceFound = true;
       if (balance?.balances?.length) {
         weibars = BigInt(balance.balances[0].balance) * BigInt(constants.TINYBAR_TO_WEIBAR_COEF);
@@ -294,14 +293,14 @@ export class AccountService implements IAccountService {
         const nextPage: string = mirrorAccount.links.next;
         if (nextPage) {
           mirrorAccount.transactions = mirrorAccount.transactions.concat(
-            await this.getPagedTransactions(nextPage, block, requestDetails)
+            await this.getPagedTransactions(nextPage, block, requestDetails),
           );
         }
 
         balanceFromTxs = this.getBalanceAtBlockTimestamp(
           mirrorAccount.account,
           mirrorAccount.transactions,
-          block.timestamp.to
+          block.timestamp.to,
         );
 
         balanceFound = true;
@@ -325,7 +324,7 @@ export class AccountService implements IAccountService {
   public async getTransactionCount(
     address: string,
     blockNumOrTag: string | null,
-    requestDetails: RequestDetails
+    requestDetails: RequestDetails,
   ): Promise<string | JsonRpcError> {
     const requestIdPrefix = requestDetails.formattedRequestId;
     if (this.logger.isLevelEnabled('trace')) {
@@ -354,7 +353,10 @@ export class AccountService implements IAccountService {
         nonceCount = await this.getAccountNonceForEarliestBlock(requestDetails);
       } else if (!isNaN(blockNum) && blockNumOrTag.length != CommonService.blockHashLength && blockNum > 0) {
         nonceCount = await this.getAccountNonceForHistoricBlock(address, blockNum, requestDetails);
-      } else if (blockNumOrTag.length == CommonService.blockHashLength && blockNumOrTag.startsWith(CommonService.emptyHex)) {
+      } else if (
+        blockNumOrTag.length == CommonService.blockHashLength &&
+        blockNumOrTag.startsWith(CommonService.emptyHex)
+      ) {
         nonceCount = await this.getAccountNonceForHistoricBlock(address, blockNumOrTag, requestDetails);
       } else {
         // return a '-39001: Unknown block' error per api-spec
@@ -387,7 +389,10 @@ export class AccountService implements IAccountService {
   /**
    * Gets the most recent block number and timestamp.to which represents the block finality.
    */
-  private async blockNumberTimestamp(caller: string, requestDetails: RequestDetails): Promise<LatestBlockNumberTimestamp> {
+  private async blockNumberTimestamp(
+    caller: string,
+    requestDetails: RequestDetails,
+  ): Promise<LatestBlockNumberTimestamp> {
     if (this.logger.isLevelEnabled('trace')) {
       this.logger.trace(`${requestDetails.formattedRequestId} blockNumber()`);
     }
@@ -444,7 +449,7 @@ export class AccountService implements IAccountService {
   private async getAccountNonceForHistoricBlock(
     address: string,
     blockNumOrHash: number | string,
-    requestDetails: RequestDetails
+    requestDetails: RequestDetails,
   ): Promise<string> {
     let getBlock;
     const isParamBlockNum = typeof blockNumOrHash === 'number';
@@ -495,7 +500,7 @@ export class AccountService implements IAccountService {
   private async getAccountNonceFromContractResult(
     address: string,
     blockNumOrHash: string | number,
-    requestDetails: RequestDetails
+    requestDetails: RequestDetails,
   ): Promise<string> {
     const requestIdPrefix = requestDetails.formattedRequestId;
     // get block timestamp for blockNum
@@ -509,7 +514,7 @@ export class AccountService implements IAccountService {
       address,
       block.timestamp.to,
       requestDetails,
-      2
+      2,
     );
     if (ethereumTransactions == null || ethereumTransactions.transactions.length === 0) {
       return CommonService.zeroHex;
@@ -524,11 +529,11 @@ export class AccountService implements IAccountService {
     // get the transaction result for the latest transaction
     const transactionResult = await this.mirrorNodeClient.getContractResult(
       ethereumTransactions.transactions[0].transaction_id,
-      requestDetails
+      requestDetails,
     );
     if (transactionResult == null) {
       throw predefined.RESOURCE_NOT_FOUND(
-        `Failed to retrieve contract results for transaction ${ethereumTransactions.transactions[0].transaction_id}`
+        `Failed to retrieve contract results for transaction ${ethereumTransactions.transactions[0].transaction_id}`,
       );
     }
 
@@ -536,7 +541,7 @@ export class AccountService implements IAccountService {
 
     if (accountResult.evm_address !== address.toLowerCase()) {
       this.logger.warn(
-        `${requestIdPrefix} eth_transactionCount for a historical block was requested where address: ${address} was not sender: ${transactionResult.address}, returning latest value as best effort.`
+        `${requestIdPrefix} eth_transactionCount for a historical block was requested where address: ${address} was not sender: ${transactionResult.address}, returning latest value as best effort.`,
       );
       return await this.getAccountLatestEthereumNonce(address, requestDetails);
     }
diff --git a/packages/relay/src/lib/services/blockService/BlockService.ts b/packages/relay/src/lib/services/blockService/BlockService.ts
new file mode 100644
index 0000000000..e5ab3449d8
--- /dev/null
+++ b/packages/relay/src/lib/services/blockService/BlockService.ts
@@ -0,0 +1,537 @@
+// SPDX-License-Identifier: Apache-2.0
+import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
+import _ from 'lodash';
+import { Logger } from 'pino';
+
+import { nanOrNumberTo0x, nullableNumberTo0x, numberTo0x, toHash32 } from '../../../formatters';
+import { IReceiptRootHash, ReceiptsRootUtils } from '../../../receiptsRootUtils';
+import { Utils } from '../../../utils';
+import { MirrorNodeClient } from '../../clients/mirrorNodeClient';
+import constants from '../../constants';
+import { predefined } from '../../errors/JsonRpcError';
+import { EthImpl } from '../../eth';
+import { Block, Log, Receipt, Transaction } from '../../model';
+import { IContractResultsParams, RequestDetails } from '../../types';
+import { IMirrorNodeBlock } from '../../types/IMirrorNodeBlock';
+import { CacheService } from '../cacheService/cacheService';
+import { CommonService } from '../ethService/ethCommonService/CommonService';
+import { BlockFactory } from '../factories/blockFactory';
+import { TransactionFactory } from '../factories/transactionFactory';
+import { IBlockService, ICommonService } from '../index';
+
+export class BlockService implements IBlockService {
+  /**
+   * The cache service used for caching all responses.
+   * @private
+   */
+  private readonly cacheService: CacheService;
+
+  /**
+   * The chain id.
+   * @private
+   */
+  private readonly chain: string;
+
+  /**
+   * The common service used for all common methods.
+   * @private
+   */
+  private readonly common: ICommonService;
+
+  /**
+   * The maximum block range for the transaction count.
+   */
+  private readonly ethGetTransactionCountMaxBlockRange = ConfigService.get('ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE');
+
+  /**
+   * The logger used for logging all output from this class.
+   * @private
+   */
+  private readonly logger: Logger;
+
+  /**
+   * The interface through which we interact with the mirror node
+   * @private
+   */
+  private readonly mirrorNodeClient: MirrorNodeClient;
+
+  /**
+   * The static method for the eth_getBlockByHash RPC call.
+   */
+  static ethGetBlockByHash = 'eth_GetBlockByHash';
+
+  /**
+   * The static method for the eth_getBlockByNumber RPC call.
+   */
+  static ethGetBlockByNumber = 'eth_GetBlockByNumber';
+
+  /** Constructor */
+  constructor(
+    cacheService: CacheService,
+    chain: string,
+    common: ICommonService,
+    mirrorNodeClient: MirrorNodeClient,
+    logger: Logger,
+  ) {
+    this.cacheService = cacheService;
+    this.chain = chain;
+    this.common = common;
+    this.mirrorNodeClient = mirrorNodeClient;
+    this.logger = logger;
+  }
+
+  /**
+   * Gets the block with the given hash.
+   *
+   * @param {string} hash the block hash
+   * @param {boolean} showDetails whether to show the details of the block
+   * @param {RequestDetails} requestDetails The request details for logging and tracking
+   * @returns {Promise<Block | null>} The block
+   */
+  public async getBlockByHash(
+    hash: string,
+    showDetails: boolean,
+    requestDetails: RequestDetails,
+  ): Promise<Block | null> {
+    const requestIdPrefix = requestDetails.formattedRequestId;
+    this.logger.trace(`${requestIdPrefix} getBlockByHash(hash=${hash}, showDetails=${showDetails})`);
+
+    const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_BY_HASH}_${hash}_${showDetails}`;
+    let block = await this.cacheService.getAsync(cacheKey, BlockService.ethGetBlockByHash, requestDetails);
+    if (!block) {
+      block = await this.getBlock(hash, showDetails, requestDetails).catch((e: any) => {
+        throw this.common.genericErrorHandler(e, `${requestIdPrefix} Failed to retrieve block for hash ${hash}`);
+      });
+      await this.cacheService.set(cacheKey, block, BlockService.ethGetBlockByHash, requestDetails);
+    }
+
+    return block;
+  }
+
+  /**
+   * Gets the block with the given number.
+   *
+   * @param {string} blockNumber The block number
+   * @param {boolean} showDetails Whether to show the details of the block
+   * @param {RequestDetails} requestDetails The request details for logging and tracking
+   * @returns {Promise<Block>} The block
+   */
+  public async getBlockByNumber(
+    blockNumber: string,
+    showDetails: boolean,
+    requestDetails: RequestDetails,
+  ): Promise<Block> {
+    const requestIdPrefix = requestDetails.formattedRequestId;
+    this.logger.trace(`${requestIdPrefix} getBlockByNumber(blockNumber=${blockNumber}, showDetails=${showDetails})`);
+
+    const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_BY_NUMBER}_${blockNumber}_${showDetails}`;
+    let block = await this.cacheService.getAsync(cacheKey, BlockService.ethGetBlockByNumber, requestDetails);
+    if (!block) {
+      block = await this.getBlock(blockNumber, showDetails, requestDetails).catch((e: any) => {
+        throw this.common.genericErrorHandler(
+          e,
+          `${requestIdPrefix} Failed to retrieve block for blockNumber ${blockNumber}`,
+        );
+      });
+
+      if (!this.common.blockTagIsLatestOrPending(blockNumber)) {
+        await this.cacheService.set(cacheKey, block, BlockService.ethGetBlockByNumber, requestDetails);
+      }
+    }
+
+    return block;
+  }
+
+  /**
+   * Gets all transaction receipts for a block by block hash or block number.
+   *
+   * @param {string} blockHashOrBlockNumber The block hash, block number, or block tag
+   * @param {RequestDetails} requestDetails The request details for logging and tracking
+   * @returns {Promise<Receipt[]>} Array of transaction receipts for the block
+   */
+  public async getBlockReceipts(blockHashOrBlockNumber: string, requestDetails: RequestDetails): Promise<Receipt[]> {
+    const requestIdPrefix = requestDetails.formattedRequestId;
+    if (this.logger.isLevelEnabled('trace')) {
+      this.logger.trace(`${requestIdPrefix} getBlockReceipt(${JSON.stringify(blockHashOrBlockNumber)})`);
+    }
+
+    const block = await this.common.getHistoricalBlockResponse(requestDetails, blockHashOrBlockNumber);
+    const blockNumber = block.number;
+
+    const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_RECEIPTS}_${blockNumber}`;
+    const cachedResponse = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetBlockReceipts, requestDetails);
+    if (cachedResponse) {
+      if (this.logger.isLevelEnabled('debug')) {
+        this.logger.debug(
+          `${requestIdPrefix} getBlockReceipts returned cached response: ${JSON.stringify(cachedResponse)}`,
+        );
+      }
+      return cachedResponse;
+    }
+
+    const paramTimestamp: IContractResultsParams = {
+      timestamp: [`lte:${block.timestamp.to}`, `gte:${block.timestamp.from}`],
+    };
+
+    const contractResults = await this.mirrorNodeClient.getContractResults(requestDetails, paramTimestamp);
+    if (!contractResults || contractResults.length === 0) {
+      return [];
+    }
+
+    const effectiveGas = await this.common.getCurrentGasPriceForBlock(block.hash, requestDetails);
+
+    const logs = await this.common.getLogsWithParams(null, paramTimestamp, requestDetails);
+    contractResults.forEach((contractResult) => {
+      contractResult.logs = logs.filter((log) => log.transactionHash === contractResult.hash);
+    });
+
+    const receipts: Receipt[] = [];
+
+    for (const contractResult of contractResults) {
+      const from = await this.common.resolveEvmAddress(contractResult.from, requestDetails);
+      const to = await this.common.resolveEvmAddress(contractResult.to, requestDetails);
+
+      const contractAddress = this.common.getContractAddressFromReceipt(contractResult);
+      const receipt = {
+        blockHash: toHash32(contractResult.block_hash),
+        blockNumber: numberTo0x(contractResult.block_number),
+        from: from,
+        to: to,
+        cumulativeGasUsed: numberTo0x(contractResult.block_gas_used),
+        gasUsed: nanOrNumberTo0x(contractResult.gas_used),
+        contractAddress: contractAddress,
+        logs: contractResult.logs,
+        logsBloom: contractResult.bloom === EthImpl.emptyHex ? EthImpl.emptyBloom : contractResult.bloom,
+        transactionHash: toHash32(contractResult.hash),
+        transactionIndex: numberTo0x(contractResult.transaction_index),
+        effectiveGasPrice: effectiveGas,
+        root: contractResult.root || constants.DEFAULT_ROOT_HASH,
+        status: contractResult.status,
+        type: nullableNumberTo0x(contractResult.type),
+      };
+
+      receipts.push(receipt);
+    }
+
+    await this.cacheService.set(cacheKey, receipts, EthImpl.ethGetBlockReceipts, requestDetails);
+    return receipts;
+  }
+
+  /**
+   * Gets the number of transaction in a block by its block hash.
+   *
+   * @param {string} hash The block hash
+   * @param {RequestDetails} requestDetails The request details for logging and tracking
+   * @returns {Promise<string | null>} The transaction count
+   */
+  async getBlockTransactionCountByHash(hash: string, requestDetails: RequestDetails): Promise<string | null> {
+    const requestIdPrefix = requestDetails.formattedRequestId;
+    this.logger.trace(`${requestIdPrefix} getBlockTransactionCountByHash(hash=${hash}, showDetails=%o)`);
+
+    const cacheKey = `${constants.CACHE_KEY.ETH_GET_TRANSACTION_COUNT_BY_HASH}_${hash}`;
+    const cachedResponse = await this.cacheService.getAsync(
+      cacheKey,
+      EthImpl.ethGetTransactionCountByHash,
+      requestDetails,
+    );
+    if (cachedResponse) {
+      if (this.logger.isLevelEnabled('debug')) {
+        this.logger.debug(
+          `${requestIdPrefix} getBlockTransactionCountByHash returned cached response: ${cachedResponse}`,
+        );
+      }
+      return cachedResponse;
+    }
+
+    try {
+      const block = await this.mirrorNodeClient.getBlock(hash, requestDetails);
+      const transactionCount = this.getTransactionCountFromBlockResponse(block);
+      await this.cacheService.set(cacheKey, transactionCount, EthImpl.ethGetTransactionCountByHash, requestDetails);
+
+      return transactionCount;
+    } catch (error: any) {
+      throw this.common.genericErrorHandler(error, `${requestIdPrefix} Failed to retrieve block for hash ${hash}`);
+    }
+  }
+
+  /**
+   * Gets the number of transaction in a block by its block number.
+   * @param {string} blockNumOrTag Possible values are earliest/pending/latest or hex
+   * @param {RequestDetails} requestDetails The request details for logging and tracking
+   * @returns {Promise<string | null>} The transaction count
+   */
+  async getBlockTransactionCountByNumber(
+    blockNumOrTag: string,
+    requestDetails: RequestDetails,
+  ): Promise<string | null> {
+    const requestIdPrefix = requestDetails.formattedRequestId;
+    if (this.logger.isLevelEnabled('trace')) {
+      this.logger.trace(
+        `${requestIdPrefix} getBlockTransactionCountByNumber(blockNum=${blockNumOrTag}, showDetails=%o)`,
+      );
+    }
+    const blockNum = await this.common.translateBlockTag(blockNumOrTag, requestDetails);
+
+    const cacheKey = `${constants.CACHE_KEY.ETH_GET_TRANSACTION_COUNT_BY_NUMBER}_${blockNum}`;
+    const cachedResponse = await this.cacheService.getAsync(
+      cacheKey,
+      EthImpl.ethGetTransactionCountByNumber,
+      requestDetails,
+    );
+    if (cachedResponse) {
+      if (this.logger.isLevelEnabled('debug')) {
+        this.logger.debug(
+          `${requestIdPrefix} getBlockTransactionCountByNumber returned cached response: ${cachedResponse}`,
+        );
+      }
+      return cachedResponse;
+    }
+
+    try {
+      const block = await this.mirrorNodeClient.getBlock(blockNum, requestDetails);
+      const transactionCount = this.getTransactionCountFromBlockResponse(block);
+      await this.cacheService.set(cacheKey, transactionCount, EthImpl.ethGetTransactionCountByNumber, requestDetails);
+      return transactionCount;
+    } catch (error: any) {
+      throw this.common.genericErrorHandler(
+        error,
+        `${requestIdPrefix} Failed to retrieve block for blockNum ${blockNum}`,
+      );
+    }
+  }
+
+  /**
+   * Always returns null. There are no uncles in Hedera.
+   * @param {RequestDetails} requestDetails The request details for logging and tracking
+   * @returns {Promise<null>} null
+   */
+  async getUncleByBlockHashAndIndex(requestDetails: RequestDetails): Promise<null> {
+    if (this.logger.isLevelEnabled('trace')) {
+      this.logger.trace(`${requestDetails.formattedRequestId} getUncleByBlockHashAndIndex()`);
+    }
+    return null;
+  }
+
+  /**
+   * Always returns null. There are no uncles in Hedera.
+   * @param {RequestDetails} requestDetails The request details for logging and tracking
+   * @returns {Promise<null>} null
+   */
+  async getUncleByBlockNumberAndIndex(requestDetails: RequestDetails): Promise<null> {
+    if (this.logger.isLevelEnabled('trace')) {
+      this.logger.trace(`${requestDetails.formattedRequestId} getUncleByBlockNumberAndIndex()`);
+    }
+    return null;
+  }
+
+  /**
+   * Always returns '0x0'. There are no uncles in Hedera.
+   * @param {RequestDetails} requestDetails The request details for logging and tracking
+   * @returns {Promise<string>} '0x0'
+   */
+  async getUncleCountByBlockHash(requestDetails: RequestDetails): Promise<string> {
+    if (this.logger.isLevelEnabled('trace')) {
+      this.logger.trace(`${requestDetails.formattedRequestId} getUncleCountByBlockHash()`);
+    }
+    return EthImpl.zeroHex;
+  }
+
+  /**
+   * Always returns '0x0'. There are no uncles in Hedera.
+   * @param {RequestDetails} requestDetails The request details for logging and tracking
+   * @returns {Promise<string>} '0x0'
+   */
+  async getUncleCountByBlockNumber(requestDetails: RequestDetails): Promise<string> {
+    if (this.logger.isLevelEnabled('trace')) {
+      this.logger.trace(`${requestDetails.formattedRequestId} getUncleCountByBlockNumber()`);
+    }
+    return EthImpl.zeroHex;
+  }
+
+  /**
+   * Gets the block with the given hash.
+   * Given an ethereum transaction hash, call the mirror node to get the block info.
+   * Then using the block timerange get all contract results to get transaction details.
+   * If showDetails is set to true subsequently call mirror node for additional transaction details
+   *
+   * @param {string} blockHashOrNumber The block hash or block number
+   * @param {boolean} showDetails Whether to show transaction details
+   * @param {RequestDetails} requestDetails The request details for logging and tracking
+   * @returns {Promise<Block | null>} The block
+   */
+  private async getBlock(
+    blockHashOrNumber: string,
+    showDetails: boolean,
+    requestDetails: RequestDetails,
+  ): Promise<Block | null> {
+    const blockResponse: IMirrorNodeBlock = await this.common.getHistoricalBlockResponse(
+      requestDetails,
+      blockHashOrNumber,
+      true,
+    );
+
+    if (blockResponse == null) return null;
+    const timestampRange = blockResponse.timestamp;
+    const timestampRangeParams = [`gte:${timestampRange.from}`, `lte:${timestampRange.to}`];
+    const params = { timestamp: timestampRangeParams };
+
+    const [contractResults, logs] = await Promise.all([
+      this.mirrorNodeClient.getContractResultWithRetry(
+        this.mirrorNodeClient.getContractResults.name,
+        [requestDetails, { timestamp: timestampRangeParams }, undefined],
+        requestDetails,
+      ),
+      this.common.getLogsWithParams(null, params, requestDetails),
+    ]);
+
+    if (contractResults == null && logs.length == 0) {
+      return null;
+    }
+
+    if (showDetails && contractResults.length >= this.ethGetTransactionCountMaxBlockRange) {
+      throw predefined.MAX_BLOCK_SIZE(blockResponse.count);
+    }
+
+    let txArray: Transaction[] | string[] = await this.prepareTransactionArray(
+      contractResults,
+      showDetails,
+      requestDetails,
+    );
+
+    txArray = this.populateSyntheticTransactions(showDetails, logs, txArray, requestDetails);
+    txArray = showDetails ? (txArray as Transaction[]) : _.uniq(txArray as string[]);
+
+    const receipts: IReceiptRootHash[] = ReceiptsRootUtils.buildReceiptRootHashes(
+      txArray.map((tx) => (showDetails ? tx.hash : tx)),
+      contractResults,
+      logs,
+    );
+
+    const gasPrice = await this.common.gasPrice(requestDetails);
+
+    try {
+      return await BlockFactory.createBlock({
+        blockResponse,
+        receipts,
+        txArray,
+        gasPrice,
+      });
+    } catch (error: any) {
+      this.logger.error(`Error creating Block: ${error.message}`);
+      return null;
+    }
+  }
+
+  /**
+   * Gets the transaction count from the block response.
+   * @param block The block response
+   * @returns The transaction count
+   */
+  private getTransactionCountFromBlockResponse(block: IMirrorNodeBlock): null | string {
+    if (block === null || block.count === undefined) {
+      // block not found
+      return null;
+    }
+
+    return numberTo0x(block.count);
+  }
+
+  /**
+   * Populates the synthetic transactions for the block.
+   * @param showDetails Whether to show transaction details
+   * @param logs[] The logs to populate the synthetic transactions from
+   * @param transactionsArray The array of transactions to populate
+   * @param requestDetails The request details for logging and tracking
+   * @returns {Array<Transaction | string>} The populated transactions
+   */
+  private populateSyntheticTransactions(
+    showDetails: boolean,
+    logs: Log[],
+    transactionsArray: Transaction[] | string[],
+    requestDetails: RequestDetails,
+  ): Transaction[] | string[] {
+    let filteredLogs: Log[];
+    if (showDetails) {
+      filteredLogs = logs.filter(
+        (log) => !(transactionsArray as Transaction[]).some((transaction) => transaction.hash === log.transactionHash),
+      );
+      filteredLogs.forEach((log) => {
+        const transaction: Transaction | null = TransactionFactory.createTransactionByType(2, {
+          accessList: undefined, // we don't support access lists for now
+          blockHash: log.blockHash,
+          blockNumber: log.blockNumber,
+          chainId: this.chain,
+          from: log.address,
+          gas: EthImpl.defaultTxGas,
+          gasPrice: EthImpl.invalidEVMInstruction,
+          hash: log.transactionHash,
+          input: EthImpl.zeroHex8Byte,
+          maxPriorityFeePerGas: EthImpl.zeroHex,
+          maxFeePerGas: EthImpl.zeroHex,
+          nonce: nanOrNumberTo0x(0),
+          r: EthImpl.zeroHex,
+          s: EthImpl.zeroHex,
+          to: log.address,
+          transactionIndex: log.transactionIndex,
+          type: EthImpl.twoHex, // 0x0 for legacy transactions, 0x1 for access list types, 0x2 for dynamic fees.
+          v: EthImpl.zeroHex,
+          value: EthImpl.oneTwoThreeFourHex,
+        });
+
+        if (transaction !== null) {
+          (transactionsArray as Transaction[]).push(transaction);
+        }
+      });
+    } else {
+      filteredLogs = logs.filter((log) => !(transactionsArray as string[]).includes(log.transactionHash));
+      filteredLogs.forEach((log) => {
+        (transactionsArray as string[]).push(log.transactionHash);
+      });
+    }
+
+    if (this.logger.isLevelEnabled('trace')) {
+      this.logger.trace(
+        `${requestDetails.formattedRequestId} Synthetic transaction hashes will be populated in the block response`,
+      );
+    }
+
+    return transactionsArray;
+  }
+
+  /**
+   * Prepares the transaction array for the block.
+   * @param contractResults The contract results to prepare the transaction array from
+   * @param showDetails Whether to show transaction details
+   * @param requestDetails The request details for logging and tracking
+   * @returns {Array<Transaction | string>} The prepared transaction array
+   */
+  private async prepareTransactionArray(
+    contractResults: any[],
+    showDetails: boolean,
+    requestDetails: RequestDetails,
+  ): Promise<Transaction[] | string[]> {
+    const txArray: Transaction[] | string[] = [];
+    for (const contractResult of contractResults) {
+      // there are several hedera-specific validations that occur right before entering the evm
+      // if a transaction has reverted there, we should not include that tx in the block response
+      if (Utils.isRevertedDueToHederaSpecificValidation(contractResult)) {
+        if (this.logger.isLevelEnabled('debug')) {
+          this.logger.debug(
+            `${requestDetails.formattedRequestId} Transaction with hash ${contractResult.hash} is skipped due to hedera-specific validation failure (${contractResult.result})`,
+          );
+        }
+        continue;
+      }
+
+      [contractResult.from, contractResult.to] = await Promise.all([
+        this.common.resolveEvmAddress(contractResult.from, requestDetails, [constants.TYPE_ACCOUNT]),
+        this.common.resolveEvmAddress(contractResult.to, requestDetails),
+      ]);
+
+      contractResult.chain_id = contractResult.chain_id || this.chain;
+      txArray.push(showDetails ? CommonService.formatContractResult(contractResult) : contractResult.hash);
+    }
+
+    return txArray;
+  }
+}
diff --git a/packages/relay/src/lib/services/blockService/IBlockService.ts b/packages/relay/src/lib/services/blockService/IBlockService.ts
index 71aaf449b6..4856ab0439 100644
--- a/packages/relay/src/lib/services/blockService/IBlockService.ts
+++ b/packages/relay/src/lib/services/blockService/IBlockService.ts
@@ -1,8 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
-import { Block } from '../../model';
+import { Block, Receipt } from '../../model';
 import { RequestDetails } from '../../types';
-import { ITimestamp } from '../ethService/ethCommonService/ITimestamp';
 
 export interface IBlockService {
   getBlockByNumber: (
@@ -11,17 +10,11 @@ export interface IBlockService {
     requestDetails: RequestDetails,
   ) => Promise<Block | null>;
   getBlockByHash: (hash: string, showDetails: boolean, requestDetails: RequestDetails) => Promise<Block | null>;
+  getBlockTransactionCountByHash: (hash: string, requestDetails: RequestDetails) => Promise<string | null>;
+  getBlockTransactionCountByNumber: (blockNum: string, requestDetails: RequestDetails) => Promise<string | null>;
+  getBlockReceipts: (blockHash: string, requestDetails: RequestDetails) => Promise<Receipt[]>;
+  getUncleByBlockHashAndIndex: (requestDetails: RequestDetails) => Promise<null>;
+  getUncleByBlockNumberAndIndex: (requestDetails: RequestDetails) => Promise<null>;
+  getUncleCountByBlockHash: (requestDetails: RequestDetails) => Promise<string>;
+  getUncleCountByBlockNumber: (requestDetails: RequestDetails) => Promise<string>;
 }
-
-export interface IBlockMirrorNode {
-  count: number;
-  gas_used: number;
-  hapi_version: string;
-  hash: string;
-  logs_bloom: string;
-  name: string;
-  number: number;
-  previous_hash: string;
-  size: number;
-  timestamp: ITimestamp;
-}
\ No newline at end of file
diff --git a/packages/relay/src/lib/services/blockService/index.ts b/packages/relay/src/lib/services/blockService/index.ts
deleted file mode 100644
index be1b13ba93..0000000000
--- a/packages/relay/src/lib/services/blockService/index.ts
+++ /dev/null
@@ -1,230 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
-import _ from 'lodash';
-import { Logger } from 'pino';
-
-import { nanOrNumberTo0x } from '../../../formatters';
-import { IReceiptRootHash, ReceiptsRootUtils } from '../../../receiptsRootUtils';
-import { Utils } from '../../../utils';
-import { MirrorNodeClient } from '../../clients/mirrorNodeClient';
-import constants from '../../constants';
-import { predefined } from '../../errors/JsonRpcError';
-import { EthImpl } from '../../eth';
-import { Block, Log, Transaction } from '../../model';
-import { RequestDetails } from '../../types';
-import { CacheService } from '../cacheService/cacheService';
-import { CommonService } from '../ethService/ethCommonService';
-import { BlockFactory } from '../factories/blockFactory';
-import { TransactionFactory } from '../factories/transactionFactory';
-import { IBlockMirrorNode, IBlockService } from './IBlockService';
-
-export class BlockService implements IBlockService {
-  private readonly common: CommonService;
-
-  private readonly mirrorNodeClient: MirrorNodeClient;
-
-  private readonly logger: Logger;
-
-  private readonly cacheService: CacheService;
-
-  static ethGetBlockByHash = 'eth_GetBlockByHash';
-
-  constructor(mirrorNodeClient: MirrorNodeClient, common: CommonService, logger: Logger, cacheService: CacheService) {
-    this.mirrorNodeClient = mirrorNodeClient;
-    this.common = common;
-    this.logger = logger;
-    this.cacheService = cacheService;
-  }
-
-  public async getBlockByNumber(
-    blockNumber: string,
-    showDetails: boolean,
-    requestDetails: RequestDetails,
-  ): Promise<Block> {
-    const requestIdPrefix = requestDetails.formattedRequestId;
-    this.logger.trace(`${requestIdPrefix} getBlockByNumber(blockNumber=${blockNumber}, showDetails=${showDetails})`);
-
-    const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_BY_NUMBER}_${blockNumber}_${showDetails}`;
-    let block = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetBlockByNumber, requestDetails);
-    if (!block) {
-      block = await this.getBlock(blockNumber, showDetails, requestDetails).catch((e: any) => {
-        throw this.common.genericErrorHandler(
-          e,
-          `${requestIdPrefix} Failed to retrieve block for blockNumber ${blockNumber}`,
-        );
-      });
-
-      if (!this.common.blockTagIsLatestOrPending(blockNumber)) {
-        await this.cacheService.set(cacheKey, block, EthImpl.ethGetBlockByNumber, requestDetails);
-      }
-    }
-
-    return block;
-  }
-
-  /**
-   * Gets the block with the given hash.
-   *
-   * @param {string} hash the block hash
-   * @param {boolean} showDetails whether to show the details of the block
-   * @param {RequestDetails} requestDetails The request details for logging and tracking
-   */
-  async getBlockByHash(hash: string, showDetails: boolean, requestDetails: RequestDetails): Promise<Block | null> {
-    const requestIdPrefix = requestDetails.formattedRequestId;
-    this.logger.trace(`${requestIdPrefix} getBlockByHash(hash=${hash}, showDetails=${showDetails})`);
-
-    const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_BY_HASH}_${hash}_${showDetails}`;
-    let block = await this.cacheService.getAsync(cacheKey, BlockService.ethGetBlockByHash, requestDetails);
-    if (!block) {
-      block = await this.getBlock(hash, showDetails, requestDetails).catch((e: any) => {
-        throw this.common.genericErrorHandler(e, `${requestIdPrefix} Failed to retrieve block for hash ${hash}`);
-      });
-      await this.cacheService.set(cacheKey, block, BlockService.ethGetBlockByHash, requestDetails);
-    }
-
-    return block;
-  }
-
-  /**
-   * Gets the block with the given hash.
-   * Given an ethereum transaction hash, call the mirror node to get the block info.
-   * Then using the block timerange get all contract results to get transaction details.
-   * If showDetails is set to true subsequently call mirror node for additional transaction details
-   *
-   * @param {string} blockHashOrNumber The block hash or block number
-   * @param {boolean} showDetails Whether to show transaction details
-   * @param {RequestDetails} requestDetails The request details for logging and tracking
-   */
-  private async getBlock(
-    blockHashOrNumber: string,
-    showDetails: boolean,
-    requestDetails: RequestDetails,
-  ): Promise<Block | null> {
-    const blockResponse: IBlockMirrorNode = await this.common.getHistoricalBlockResponse(
-      requestDetails,
-      blockHashOrNumber,
-      true,
-    );
-
-    if (blockResponse == null) return null;
-    const timestampRange = blockResponse.timestamp;
-    const timestampRangeParams = [`gte:${timestampRange.from}`, `lte:${timestampRange.to}`];
-
-    const contractResults = await this.mirrorNodeClient.getContractResultWithRetry(
-      this.mirrorNodeClient.getContractResults.name,
-      [requestDetails, { timestamp: timestampRangeParams }, undefined],
-      requestDetails,
-    );
-    const params = { timestamp: timestampRangeParams };
-
-    // get contract results logs using block timestamp range
-    const logs = await this.common.getLogsWithParams(null, params, requestDetails);
-
-    if (contractResults == null && logs.length == 0) {
-      // contract result not found
-      return null;
-    }
-
-    // The consensus timestamp of the block, with the nanoseconds part omitted.
-    const timestamp = timestampRange.from.substring(0, timestampRange.from.indexOf('.'));
-    if (showDetails && contractResults.length >= ConfigService.get('ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE')) {
-      throw predefined.MAX_BLOCK_SIZE(blockResponse.count);
-    }
-
-    // prepare transactionArray
-    let txArray: any[] = [];
-    for (const contractResult of contractResults) {
-      // there are several hedera-specific validations that occur right before entering the evm
-      // if a transaction has reverted there, we should not include that tx in the block response
-      if (Utils.isRevertedDueToHederaSpecificValidation(contractResult)) {
-        if (this.logger.isLevelEnabled('debug')) {
-          this.logger.debug(
-            `${requestDetails.formattedRequestId} Transaction with hash ${contractResult.hash} is skipped due to hedera-specific validation failure (${contractResult.result})`,
-          );
-        }
-        continue;
-      }
-      // make this promise.all ??
-      contractResult.from = await this.common.resolveEvmAddress(contractResult.from, requestDetails, [
-        constants.TYPE_ACCOUNT,
-      ]);
-      contractResult.to = await this.common.resolveEvmAddress(contractResult.to, requestDetails);
-
-      //contractResult.chain_id = contractResult.chain_id //|| this.chain;
-      txArray.push(showDetails ? CommonService.formatContractResult(contractResult) : contractResult.hash);
-    }
-
-    txArray = this.populateSyntheticTransactions(showDetails, logs, txArray, requestDetails);
-    txArray = showDetails ? txArray : _.uniq(txArray);
-
-    const receipts: IReceiptRootHash[] = ReceiptsRootUtils.buildReceiptRootHashes(
-      txArray.map((tx) => (showDetails ? tx.hash : tx)),
-      contractResults,
-      logs,
-    );
-
-    const gasPrice = '0x0';
-    try {
-      return await BlockFactory.createBlock({
-        blockResponse,
-        receipts,
-        txArray,
-        gasPrice,
-      });
-    } catch (error: any) {
-      this.logger.error(`Error creating Block: ${error.message}`);
-      return null;
-    }
-  }
-
-  private populateSyntheticTransactions(
-    showDetails: boolean,
-    logs: Log[],
-    transactionsArray: Array<any>,
-    requestDetails: RequestDetails,
-  ): Array<any> {
-    let filteredLogs: Log[];
-    if (showDetails) {
-      filteredLogs = logs.filter(
-        (log) => !transactionsArray.some((transaction) => transaction.hash === log.transactionHash),
-      );
-      filteredLogs.forEach((log) => {
-        const transaction: Transaction | null = TransactionFactory.createTransactionByType(2, {
-          accessList: undefined, // we don't support access lists for now
-          blockHash: log.blockHash,
-          blockNumber: log.blockNumber,
-          chainId: '0x12', //this.chain,
-          from: log.address,
-          gas: EthImpl.defaultTxGas,
-          gasPrice: EthImpl.invalidEVMInstruction,
-          hash: log.transactionHash,
-          input: EthImpl.zeroHex8Byte,
-          maxPriorityFeePerGas: EthImpl.zeroHex,
-          maxFeePerGas: EthImpl.zeroHex,
-          nonce: nanOrNumberTo0x(0),
-          r: EthImpl.zeroHex,
-          s: EthImpl.zeroHex,
-          to: log.address,
-          transactionIndex: log.transactionIndex,
-          type: EthImpl.twoHex, // 0x0 for legacy transactions, 0x1 for access list types, 0x2 for dynamic fees.
-          v: EthImpl.zeroHex,
-          value: EthImpl.oneTwoThreeFourHex,
-        });
-        transactionsArray.push(transaction);
-      });
-    } else {
-      filteredLogs = logs.filter((log) => !transactionsArray.includes(log.transactionHash));
-      filteredLogs.forEach((log) => {
-        transactionsArray.push(log.transactionHash);
-      });
-    }
-
-    if (this.logger.isLevelEnabled('trace')) {
-      this.logger.trace(
-        `${requestDetails.formattedRequestId} Synthetic transaction hashes will be populated in the block response`,
-      );
-    }
-
-    return transactionsArray;
-  }
-}
diff --git a/packages/relay/src/lib/services/ethService/ethCommonService/index.ts b/packages/relay/src/lib/services/ethService/ethCommonService/CommonService.ts
similarity index 88%
rename from packages/relay/src/lib/services/ethService/ethCommonService/index.ts
rename to packages/relay/src/lib/services/ethService/ethCommonService/CommonService.ts
index 3248c6d2a5..cd4c6c4c83 100644
--- a/packages/relay/src/lib/services/ethService/ethCommonService/index.ts
+++ b/packages/relay/src/lib/services/ethService/ethCommonService/CommonService.ts
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
 import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
+import { Hbar } from '@hashgraph/sdk';
 import * as _ from 'lodash';
 import { Logger } from 'pino';
 
@@ -16,20 +17,19 @@ import {
   tinybarsToWeibars,
   toHash32,
 } from '../../../../formatters';
+import { Utils } from '../../../../utils';
 import { MirrorNodeClient } from '../../../clients';
 import constants from '../../../constants';
 import { JsonRpcError, predefined } from '../../../errors/JsonRpcError';
 import { MirrorNodeClientError } from '../../../errors/MirrorNodeClientError';
 import { SDKClientError } from '../../../errors/SDKClientError';
 import { EthImpl } from '../../../eth';
-import { Log } from '../../../model';
+import { Log, Transaction } from '../../../model';
 import { RequestDetails } from '../../../types';
 import { CacheService } from '../../cacheService/cacheService';
-import { ICommonService } from './ICommonService';
-import { Utils } from '../../../../utils';
-import { Hbar } from '@hashgraph/sdk';
-import HAPIService from '../../hapiService/hapiService';
 import { TransactionFactory } from '../../factories/transactionFactory';
+import HAPIService from '../../hapiService/hapiService';
+import { ICommonService } from './ICommonService';
 
 /**
  * Create a new Common Service implementation.
@@ -89,7 +89,7 @@ export class CommonService implements ICommonService {
    */
   private readonly ethBlockNumberCacheTtlMs = parseNumericEnvVar(
     'ETH_BLOCK_NUMBER_CACHE_TTL_MS',
-    'ETH_BLOCK_NUMBER_CACHE_TTL_MS_DEFAULT'
+    'ETH_BLOCK_NUMBER_CACHE_TTL_MS_DEFAULT',
   );
   private readonly ethGasPriceCacheTtlMs = parseNumericEnvVar(
     'ETH_GET_GAS_PRICE_CACHE_TTL_MS',
@@ -97,6 +97,7 @@ export class CommonService implements ICommonService {
   );
   private readonly maxBlockRange = parseNumericEnvVar('MAX_BLOCK_RANGE', 'MAX_BLOCK_RANGE');
   private readonly maxTimestampParamRange = 604800; // 7 days
+  static EMPTY_HEX = '0x';
 
   /**
    * @private
@@ -105,7 +106,12 @@ export class CommonService implements ICommonService {
     return ConfigService.get('ETH_GET_LOGS_BLOCK_RANGE_LIMIT');
   }
 
-  constructor(mirrorNodeClient: MirrorNodeClient, logger: Logger, cacheService: CacheService, hapiService: HAPIService) {
+  constructor(
+    mirrorNodeClient: MirrorNodeClient,
+    logger: Logger,
+    cacheService: CacheService,
+    hapiService: HAPIService,
+  ) {
     this.mirrorNodeClient = mirrorNodeClient;
     this.logger = logger;
     this.cacheService = cacheService;
@@ -131,7 +137,7 @@ export class CommonService implements ICommonService {
     fromBlock: string,
     toBlock: string,
     requestDetails: RequestDetails,
-    address?: string | string[] | null
+    address?: string | string[] | null,
   ) {
     if (this.blockTagIsLatestOrPending(toBlock)) {
       toBlock = CommonService.blockLatest;
@@ -189,7 +195,7 @@ export class CommonService implements ICommonService {
           prepend0x(fromBlockNum.toString(16)),
           fromBlockResponse.timestamp.from,
           prepend0x(toBlockNum.toString(16)),
-          toBlockResponse.timestamp.to
+          toBlockResponse.timestamp.to,
         );
       }
 
@@ -274,7 +280,7 @@ export class CommonService implements ICommonService {
     if (!returnLatest && this.blockTagIsLatestOrPending(blockNumberOrTagOrHash)) {
       if (this.logger.isLevelEnabled('debug')) {
         this.logger.debug(
-          `${requestDetails.formattedRequestId} Detected a contradiction between blockNumberOrTagOrHash and returnLatest. The request does not target the latest block, yet blockNumberOrTagOrHash representing latest or pending: returnLatest=${returnLatest}, blockNumberOrTagOrHash=${blockNumberOrTagOrHash}`
+          `${requestDetails.formattedRequestId} Detected a contradiction between blockNumberOrTagOrHash and returnLatest. The request does not target the latest block, yet blockNumberOrTagOrHash representing latest or pending: returnLatest=${returnLatest}, blockNumberOrTagOrHash=${blockNumberOrTagOrHash}`,
         );
       }
       return null;
@@ -283,7 +289,7 @@ export class CommonService implements ICommonService {
     if (blockNumberOrTagOrHash === EthImpl.emptyHex) {
       if (this.logger.isLevelEnabled('debug')) {
         this.logger.debug(
-          `${requestDetails.formattedRequestId} Invalid input detected in getHistoricalBlockResponse(): blockNumberOrTagOrHash=${blockNumberOrTagOrHash}.`
+          `${requestDetails.formattedRequestId} Invalid input detected in getHistoricalBlockResponse(): blockNumberOrTagOrHash=${blockNumberOrTagOrHash}.`,
         );
       }
       return null;
@@ -323,15 +329,15 @@ export class CommonService implements ICommonService {
     const blockNumberCached = await this.cacheService.getAsync(
       cacheKey,
       CommonService.latestBlockNumber,
-      requestDetails
+      requestDetails,
     );
 
     if (blockNumberCached) {
       if (this.logger.isLevelEnabled('trace')) {
         this.logger.trace(
           `${requestDetails.formattedRequestId} returning cached value ${cacheKey}:${JSON.stringify(
-            blockNumberCached
-          )}`
+            blockNumberCached,
+          )}`,
         );
       }
       return blockNumberCached;
@@ -347,7 +353,7 @@ export class CommonService implements ICommonService {
         currentBlock,
         CommonService.latestBlockNumber,
         requestDetails,
-        this.ethBlockNumberCacheTtlMs
+        this.ethBlockNumberCacheTtlMs,
       );
 
       return currentBlock;
@@ -376,7 +382,7 @@ export class CommonService implements ICommonService {
   public async validateBlockHashAndAddTimestampToParams(
     params: any,
     blockHash: string,
-    requestDetails: RequestDetails
+    requestDetails: RequestDetails,
   ) {
     try {
       const block = await this.mirrorNodeClient.getBlock(blockHash, requestDetails);
@@ -409,7 +415,7 @@ export class CommonService implements ICommonService {
   public async getLogsByAddress(address: string | string[], params: any, requestDetails: RequestDetails) {
     const addresses = Array.isArray(address) ? address : [address];
     const logPromises = addresses.map((addr) =>
-      this.mirrorNodeClient.getContractResultsLogsByAddress(addr, requestDetails, params, undefined)
+      this.mirrorNodeClient.getContractResultsLogsByAddress(addr, requestDetails, params, undefined),
     );
 
     const logResults = await Promise.all(logPromises);
@@ -424,7 +430,7 @@ export class CommonService implements ICommonService {
   public async getLogsWithParams(
     address: string | string[] | null,
     params: any,
-    requestDetails: RequestDetails
+    requestDetails: RequestDetails,
   ): Promise<Log[]> {
     const EMPTY_RESPONSE = [];
 
@@ -451,8 +457,8 @@ export class CommonService implements ICommonService {
           removed: false,
           topics: log.topics,
           transactionHash: toHash32(log.transaction_hash),
-          transactionIndex: numberTo0x(log.transaction_index)
-        })
+          transactionIndex: numberTo0x(log.transaction_index),
+        }),
       );
     }
 
@@ -465,7 +471,7 @@ export class CommonService implements ICommonService {
     toBlock: string | 'latest',
     address: string | string[] | null,
     topics: any[] | null,
-    requestDetails: RequestDetails
+    requestDetails: RequestDetails,
   ): Promise<Log[]> {
     const EMPTY_RESPONSE = [];
     const params: any = {};
@@ -488,7 +494,7 @@ export class CommonService implements ICommonService {
   async resolveEvmAddress(
     address: string,
     requestDetails: RequestDetails,
-    searchableTypes = [constants.TYPE_CONTRACT, constants.TYPE_TOKEN, constants.TYPE_ACCOUNT]
+    searchableTypes = [constants.TYPE_CONTRACT, constants.TYPE_TOKEN, constants.TYPE_ACCOUNT],
   ): Promise<string> {
     if (!address) return address;
 
@@ -497,7 +503,7 @@ export class CommonService implements ICommonService {
       EthImpl.ethGetCode,
       requestDetails,
       searchableTypes,
-      0
+      0,
     );
     let resolvedAddress = address;
     if (
@@ -511,47 +517,6 @@ export class CommonService implements ICommonService {
     return resolvedAddress;
   }
 
-  public static formatContractResult = (cr: any) => {
-    if (cr === null) {
-      return null;
-    }
-
-    const gasPrice =
-      cr.gas_price === null || cr.gas_price === '0x'
-        ? '0x0'
-        : isHex(cr.gas_price)
-        ? numberTo0x(BigInt(cr.gas_price) * BigInt(constants.TINYBAR_TO_WEIBAR_COEF))
-        : nanOrNumberTo0x(cr.gas_price);
-
-    const commonFields = {
-      blockHash: toHash32(cr.block_hash),
-      blockNumber: nullableNumberTo0x(cr.block_number),
-      from: cr.from.substring(0, 42),
-      gas: nanOrNumberTo0x(cr.gas_used),
-      gasPrice,
-      hash: cr.hash.substring(0, 66),
-      input: cr.function_parameters,
-      nonce: nanOrNumberTo0x(cr.nonce),
-      r: cr.r === null ? '0x0' : stripLeadingZeroForSignatures(cr.r.substring(0, 66)),
-      s: cr.s === null ? '0x0' : stripLeadingZeroForSignatures(cr.s.substring(0, 66)),
-      to: cr.to?.substring(0, 42),
-      transactionIndex: nullableNumberTo0x(cr.transaction_index),
-      type: cr.type === null ? '0x0' : nanOrNumberTo0x(cr.type),
-      v: cr.v === null ? '0x0' : nanOrNumberTo0x(cr.v),
-      value: nanOrNumberInt64To0x(tinybarsToWeibars(cr.amount, true)),
-      // for legacy EIP155 with tx.chainId=0x0, mirror-node will return a '0x' (EMPTY_HEX) value for contract result's chain_id
-      //   which is incompatibile with certain tools (i.e. foundry). By setting this field, chainId, to undefined, the end jsonrpc
-      //   object will leave out this field, which is the proper behavior for other tools to be compatible with.
-      chainId: cr.chain_id === '0x' ? undefined : cr.chain_id
-    };
-
-    return TransactionFactory.createTransactionByType(cr.type, {
-      ...commonFields,
-      maxPriorityFeePerGas: cr.max_priority_fee_per_gas,
-      maxFeePerGas: cr.max_fee_per_gas
-    });
-  };
-
   /**
    * Returns the current network fee in denominated in weibars.
    *
@@ -567,23 +532,23 @@ export class CommonService implements ICommonService {
     } catch (e: any) {
       this.logger.warn(
         e,
-        `${requestDetails.formattedRequestId} Mirror Node threw an error while retrieving fees. Fallback to consensus node.`
+        `${requestDetails.formattedRequestId} Mirror Node threw an error while retrieving fees. Fallback to consensus node.`,
       );
     }
 
     if (_.isNil(networkFees)) {
       if (this.logger.isLevelEnabled('debug')) {
         this.logger.debug(
-          `${requestDetails.formattedRequestId} Mirror Node returned no network fees. Fallback to consensus node.`
+          `${requestDetails.formattedRequestId} Mirror Node returned no network fees. Fallback to consensus node.`,
         );
       }
       networkFees = {
         fees: [
           {
             gas: await this.hapiService.getSDKClient().getTinyBarGasFee(callerName, requestDetails),
-            transaction_type: EthImpl.ethTxType
-          }
-        ]
+            transaction_type: EthImpl.ethTxType,
+          },
+        ],
       };
     }
 
@@ -615,7 +580,7 @@ export class CommonService implements ICommonService {
       let gasPrice: number | undefined = await this.cacheService.getAsync(
         constants.CACHE_KEY.GAS_PRICE,
         EthImpl.ethGasPrice,
-        requestDetails
+        requestDetails,
       );
 
       if (!gasPrice) {
@@ -626,7 +591,7 @@ export class CommonService implements ICommonService {
           gasPrice,
           EthImpl.ethGasPrice,
           requestDetails,
-          this.ethGasPriceCacheTtlMs
+          this.ethGasPriceCacheTtlMs,
         );
       }
 
@@ -652,4 +617,80 @@ export class CommonService implements ICommonService {
       return Number(tag);
     }
   }
+
+  /**
+   * This method retrieves the contract address from the receipt response.
+   * If the contract creation is via a system contract, it handles the system contract creation.
+   * If not, it returns the address from the receipt response.
+   *
+   * @param {any} receiptResponse - The receipt response object.
+   * @returns {string} The contract address.
+   */
+  public getContractAddressFromReceipt(receiptResponse: any): string {
+    const isCreationViaSystemContract = constants.HTS_CREATE_FUNCTIONS_SELECTORS.includes(
+      receiptResponse.function_parameters.substring(0, constants.FUNCTION_SELECTOR_CHAR_LENGTH),
+    );
+
+    if (!isCreationViaSystemContract) {
+      return receiptResponse.address;
+    }
+
+    // Handle system contract creation
+    // reason for substring is described in the design doc in this repo: docs/design/hts_address_tx_receipt.md
+    const tokenAddress = receiptResponse.call_result.substring(receiptResponse.call_result.length - 40);
+    return prepend0x(tokenAddress);
+  }
+
+  public async getCurrentGasPriceForBlock(blockHash: string, requestDetails: RequestDetails): Promise<string> {
+    const block = await this.mirrorNodeClient.getBlock(blockHash, requestDetails);
+    const timestampDecimalString = block ? block.timestamp.from.split('.')[0] : '';
+    const gasPriceForTimestamp = await this.getFeeWeibars(
+      EthImpl.ethGetTransactionReceipt,
+      requestDetails,
+      timestampDecimalString,
+    );
+
+    return numberTo0x(gasPriceForTimestamp);
+  }
+
+  public static formatContractResult(cr: any): Transaction | null {
+    if (cr === null) {
+      return null;
+    }
+
+    const gasPrice =
+      cr.gas_price === null || cr.gas_price === '0x'
+        ? '0x0'
+        : isHex(cr.gas_price)
+        ? numberTo0x(BigInt(cr.gas_price) * BigInt(constants.TINYBAR_TO_WEIBAR_COEF))
+        : nanOrNumberTo0x(cr.gas_price);
+
+    const commonFields = {
+      blockHash: toHash32(cr.block_hash),
+      blockNumber: nullableNumberTo0x(cr.block_number),
+      from: cr.from.substring(0, 42),
+      gas: nanOrNumberTo0x(cr.gas_used),
+      gasPrice,
+      hash: cr.hash.substring(0, 66),
+      input: cr.function_parameters,
+      nonce: nanOrNumberTo0x(cr.nonce),
+      r: cr.r === null ? '0x0' : stripLeadingZeroForSignatures(cr.r.substring(0, 66)),
+      s: cr.s === null ? '0x0' : stripLeadingZeroForSignatures(cr.s.substring(0, 66)),
+      to: cr.to?.substring(0, 42),
+      transactionIndex: nullableNumberTo0x(cr.transaction_index),
+      type: cr.type === null ? '0x0' : nanOrNumberTo0x(cr.type),
+      v: cr.v === null ? '0x0' : nanOrNumberTo0x(cr.v),
+      value: nanOrNumberInt64To0x(tinybarsToWeibars(cr.amount, true)),
+      // for legacy EIP155 with tx.chainId=0x0, mirror-node will return a '0x' (EMPTY_HEX) value for contract result's chain_id
+      //   which is incompatibile with certain tools (i.e. foundry). By setting this field, chainId, to undefined, the end jsonrpc
+      //   object will leave out this field, which is the proper behavior for other tools to be compatible with.
+      chainId: cr.chain_id === this.EMPTY_HEX ? undefined : cr.chain_id,
+    };
+
+    return TransactionFactory.createTransactionByType(cr.type, {
+      ...commonFields,
+      maxPriorityFeePerGas: cr.max_priority_fee_per_gas,
+      maxFeePerGas: cr.max_fee_per_gas,
+    });
+  }
 }
diff --git a/packages/relay/src/lib/services/ethService/ethCommonService/ICommonService.ts b/packages/relay/src/lib/services/ethService/ethCommonService/ICommonService.ts
index d6e0d13bbd..cd9b1f8c3c 100644
--- a/packages/relay/src/lib/services/ethService/ethCommonService/ICommonService.ts
+++ b/packages/relay/src/lib/services/ethService/ethCommonService/ICommonService.ts
@@ -4,15 +4,19 @@ import { Log } from '../../../model';
 import { RequestDetails } from '../../../types';
 
 export interface ICommonService {
+  addTopicsToParams(params: any, topics: any[] | null): void;
+
   blockTagIsLatestOrPending(tag: any): boolean;
 
-  validateBlockRangeAndAddTimestampToParams(
-    params: any,
-    fromBlock: string,
-    toBlock: string,
-    requestDetails: RequestDetails,
-    address?: string | string[] | null,
-  ): Promise<boolean>;
+  gasPrice(requestDetails: RequestDetails): Promise<string>;
+
+  genericErrorHandler(error: any, logMessage?: string): void;
+
+  getContractAddressFromReceipt(contractResult: any): string;
+
+  getCurrentGasPriceForBlock(block: string, requestDetails: RequestDetails): Promise<string>;
+
+  getFeeWeibars(callerName: string, requestDetails: RequestDetails, timestamp?: string): Promise<number>;
 
   getHistoricalBlockResponse(
     requestDetails: RequestDetails,
@@ -22,32 +26,36 @@ export interface ICommonService {
 
   getLatestBlockNumber(requestDetails: RequestDetails): Promise<string>;
 
-  genericErrorHandler(error: any, logMessage?: string): void;
-
-  validateBlockHashAndAddTimestampToParams(
-    params: any,
-    blockHash: string,
-    requestDetails: RequestDetails,
-  ): Promise<boolean>;
-
-  addTopicsToParams(params: any, topics: any[] | null): void;
-
-  getLogsByAddress(address: string | [string], params: any, requestDetails: RequestDetails): Promise<any>;
-
-  getLogsWithParams(address: string | [string] | null, params: any, requestDetails: RequestDetails): Promise<Log[]>;
-
   getLogs(
     blockHash: string | null,
     fromBlock: string | 'latest',
     toBlock: string | 'latest',
-    address: string | [string] | null,
+    address: string | string[] | null,
     topics: any[] | null,
     requestDetails: RequestDetails,
   ): Promise<Log[]>;
 
-  getFeeWeibars(callerName: string, requestDetails: RequestDetails, timestamp?: string): Promise<number>;
+  getLogsByAddress(address: string | [string], params: any, requestDetails: RequestDetails): Promise<any>;
 
-  gasPrice(requestDetails: RequestDetails): Promise<string>;
+  getLogsWithParams(address: string | [string] | null, params: any, requestDetails: RequestDetails): Promise<Log[]>;
+
+  resolveEvmAddress(address: string, requestDetails: RequestDetails, types?: string[]): Promise<string>;
 
   translateBlockTag(tag: string | null, requestDetails: RequestDetails): Promise<number>;
+
+  validateBlockHashAndAddTimestampToParams(
+    params: any,
+    blockHash: string,
+    requestDetails: RequestDetails,
+  ): Promise<boolean>;
+
+  validateBlockRange(fromBlock: string, toBlock: string, requestDetails: RequestDetails): Promise<boolean>;
+
+  validateBlockRangeAndAddTimestampToParams(
+    params: any,
+    fromBlock: string,
+    toBlock: string,
+    requestDetails: RequestDetails,
+    address?: string | string[] | null,
+  ): Promise<boolean>;
 }
diff --git a/packages/relay/src/lib/services/ethService/ethFilterService/index.ts b/packages/relay/src/lib/services/ethService/ethFilterService/index.ts
index 52e927c590..5be813116a 100644
--- a/packages/relay/src/lib/services/ethService/ethFilterService/index.ts
+++ b/packages/relay/src/lib/services/ethService/ethFilterService/index.ts
@@ -11,7 +11,7 @@ import { Log } from '../../../model';
 import { RequestDetails } from '../../../types';
 import { INewFilterParams } from '../../../types/requestParams';
 import { CacheService } from '../../cacheService/cacheService';
-import { CommonService } from '../ethCommonService';
+import { CommonService, ICommonService } from '../../index';
 import { IFilterService } from './IFilterService';
 
 /**
@@ -41,15 +41,19 @@ export class FilterService implements IFilterService {
    * @private
    */
   private readonly cacheService: CacheService;
+
+  /**
+   * The Common Service implementation that contains logic shared by other services.
+   */
+  private readonly common: ICommonService;
+
   public readonly ethNewFilter = 'eth_newFilter';
   public readonly ethUninstallFilter = 'eth_uninstallFilter';
   public readonly ethGetFilterLogs = 'eth_getFilterLogs';
   public readonly ethGetFilterChanges = 'eth_getFilterChanges';
-
-  private readonly common: CommonService;
   private readonly supportedTypes: string[];
 
-  constructor(mirrorNodeClient: MirrorNodeClient, logger: Logger, cacheService: CacheService, common: CommonService) {
+  constructor(mirrorNodeClient: MirrorNodeClient, logger: Logger, cacheService: CacheService, common: ICommonService) {
     this.mirrorNodeClient = mirrorNodeClient;
     this.logger = logger;
     this.cacheService = cacheService;
diff --git a/packages/relay/src/lib/services/ethService/index.ts b/packages/relay/src/lib/services/ethService/index.ts
deleted file mode 100644
index 8c8fde1f30..0000000000
--- a/packages/relay/src/lib/services/ethService/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-
-export * from './ethCommonService';
-export * from './ethFilterService';
diff --git a/packages/relay/src/lib/services/factories/blockFactory.ts b/packages/relay/src/lib/services/factories/blockFactory.ts
index e2219568c0..5029a203e5 100644
--- a/packages/relay/src/lib/services/factories/blockFactory.ts
+++ b/packages/relay/src/lib/services/factories/blockFactory.ts
@@ -5,10 +5,10 @@ import { IReceiptRootHash, ReceiptsRootUtils } from '../../../receiptsRootUtils'
 import constants from '../../constants';
 import { EthImpl } from '../../eth';
 import { Block } from '../../model';
-import { IBlockMirrorNode } from '../blockService/IBlockService';
+import { IMirrorNodeBlock } from '../../types/IMirrorNodeBlock';
 
 interface BlockFactoryParams {
-  blockResponse: IBlockMirrorNode;
+  blockResponse: IMirrorNodeBlock;
   receipts: IReceiptRootHash[];
   txArray: any[];
   gasPrice: string;
diff --git a/packages/relay/src/lib/services/factories/transactionFactory.ts b/packages/relay/src/lib/services/factories/transactionFactory.ts
index cfeed3d8ce..b8f4c38cd5 100644
--- a/packages/relay/src/lib/services/factories/transactionFactory.ts
+++ b/packages/relay/src/lib/services/factories/transactionFactory.ts
@@ -5,7 +5,7 @@ import { Transaction, Transaction1559, Transaction2930 } from '../../model';
 
 // TransactionFactory is a factory class that creates a Transaction object based on the type of transaction.
 export class TransactionFactory {
-  static createTransactionByType(type: number, fields: any): Transaction | Transaction2930 | Transaction1559 | null {
+  static createTransactionByType(type: number, fields: any): Transaction | null {
     switch (type) {
       case 0:
         return new Transaction(fields); // eip 155 fields
diff --git a/packages/relay/src/lib/services/feeService/index.ts b/packages/relay/src/lib/services/feeService/index.ts
index 9705e24fc0..11f6e717af 100644
--- a/packages/relay/src/lib/services/feeService/index.ts
+++ b/packages/relay/src/lib/services/feeService/index.ts
@@ -1,17 +1,18 @@
 // SPDX-License-Identifier: Apache-2.0
 
-import { IFeeService } from '../feeService/IFeeService';
 import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
+import _ from 'lodash';
+import { Logger } from 'pino';
+
+import { numberTo0x } from '../../../formatters';
+import { MirrorNodeClient } from '../../clients';
 import constants from '../../constants';
 import { JsonRpcError, predefined } from '../../errors/JsonRpcError';
+import { EthImpl } from '../../eth';
 import { IFeeHistory, RequestDetails } from '../../types';
-import { CommonService } from '../ethService';
-import { MirrorNodeClient } from '../../clients';
-import { Logger } from 'pino';
 import { CacheService } from '../cacheService/cacheService';
-import { EthImpl } from '../../eth';
-import { numberTo0x } from '../../../formatters';
-import _ from 'lodash';
+import { IFeeService } from '../feeService/IFeeService';
+import { ICommonService } from '../index';
 
 export class FeeService implements IFeeService {
   /**
@@ -19,7 +20,7 @@ export class FeeService implements IFeeService {
    *
    * @private
    */
-  private readonly common: CommonService;
+  private readonly common: ICommonService;
 
   /**
    * The interface through which we interact with the mirror node.
@@ -50,7 +51,7 @@ export class FeeService implements IFeeService {
    * @param logger
    * @param cacheService
    */
-  constructor(mirrorNodeClient: MirrorNodeClient, common: CommonService, logger: Logger, cacheService: CacheService) {
+  constructor(mirrorNodeClient: MirrorNodeClient, common: ICommonService, logger: Logger, cacheService: CacheService) {
     this.mirrorNodeClient = mirrorNodeClient;
     this.common = common;
     this.logger = logger;
@@ -65,7 +66,12 @@ export class FeeService implements IFeeService {
    * @param rewardPercentiles
    * @param requestDetails
    */
-  public async feeHistory(blockCount: number, newestBlock: string, rewardPercentiles: Array<number> | null, requestDetails: RequestDetails): Promise<IFeeHistory | JsonRpcError> {
+  public async feeHistory(
+    blockCount: number,
+    newestBlock: string,
+    rewardPercentiles: Array<number> | null,
+    requestDetails: RequestDetails,
+  ): Promise<IFeeHistory | JsonRpcError> {
     const requestIdPrefix = requestDetails.formattedRequestId;
     const maxResults = ConfigService.get('TEST')
       ? constants.DEFAULT_FEE_HISTORY_MAX_RESULTS
@@ -113,7 +119,7 @@ export class FeeService implements IFeeService {
       } 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(
-          ''
+          '',
         )}`;
         const cachedFeeHistory = await this.cacheService.getAsync(cacheKey, EthImpl.ethFeeHistory, requestDetails);
 
@@ -125,7 +131,7 @@ export class FeeService implements IFeeService {
             newestBlockNumber,
             latestBlockNumber,
             rewardPercentiles,
-            requestDetails
+            requestDetails,
           );
         }
         if (newestBlock != EthImpl.blockLatest && newestBlock != EthImpl.blockPending) {
@@ -134,7 +140,7 @@ export class FeeService implements IFeeService {
             feeHistory,
             EthImpl.ethFeeHistory,
             requestDetails,
-            parseInt(constants.ETH_FEE_HISTORY_TTL)
+            parseInt(constants.ETH_FEE_HISTORY_TTL),
           );
         }
       }
@@ -166,13 +172,18 @@ export class FeeService implements IFeeService {
    * @param fee
    * @private
    */
-  private static getRepeatedFeeHistory(blockCount: number, oldestBlockNumber: number, rewardPercentiles: Array<number> | null, fee: string): IFeeHistory {
+  private static getRepeatedFeeHistory(
+    blockCount: number,
+    oldestBlockNumber: number,
+    rewardPercentiles: Array<number> | null,
+    fee: string,
+  ): IFeeHistory {
     const shouldIncludeRewards = Array.isArray(rewardPercentiles) && rewardPercentiles.length > 0;
 
     const feeHistory: IFeeHistory = {
       baseFeePerGas: Array(blockCount).fill(fee),
       gasUsedRatio: Array(blockCount).fill(EthImpl.defaultGasUsedRatio),
-      oldestBlock: numberTo0x(oldestBlockNumber)
+      oldestBlock: numberTo0x(oldestBlockNumber),
     };
 
     // next fee. Due to high block production rate and low fee change rate we add the next fee
@@ -194,14 +205,20 @@ export class FeeService implements IFeeService {
    * @param requestDetails
    * @private
    */
-  private async getFeeHistory(blockCount: number, newestBlockNumber: number, latestBlockNumber: number, rewardPercentiles: Array<number> | null, requestDetails: RequestDetails): Promise<IFeeHistory> {
+  private async getFeeHistory(
+    blockCount: number,
+    newestBlockNumber: number,
+    latestBlockNumber: number,
+    rewardPercentiles: Array<number> | null,
+    requestDetails: RequestDetails,
+  ): Promise<IFeeHistory> {
     // include the 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: IFeeHistory = {
       baseFeePerGas: [] as string[],
       gasUsedRatio: [] as number[],
-      oldestBlock: numberTo0x(oldestBlockNumber)
+      oldestBlock: numberTo0x(oldestBlockNumber),
     };
 
     // get fees from oldest to newest blocks
@@ -245,7 +262,7 @@ export class FeeService implements IFeeService {
     } catch (error) {
       this.logger.warn(
         error,
-        `${requestDetails.formattedRequestId} Fee history cannot retrieve block or fee. Returning ${fee} fee for block ${blockNumber}`
+        `${requestDetails.formattedRequestId} Fee history cannot retrieve block or fee. Returning ${fee} fee for block ${blockNumber}`,
       );
     }
 
diff --git a/packages/relay/src/lib/services/index.ts b/packages/relay/src/lib/services/index.ts
new file mode 100644
index 0000000000..65fdc8fcf5
--- /dev/null
+++ b/packages/relay/src/lib/services/index.ts
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: Apache-2.0
+import { BlockService } from './blockService/BlockService';
+import { IBlockService } from './blockService/IBlockService';
+import { CommonService } from './ethService/ethCommonService/CommonService';
+import { ICommonService } from './ethService/ethCommonService/ICommonService';
+import { FilterService } from './ethService/ethFilterService';
+import { IFilterService } from './ethService/ethFilterService/IFilterService';
+
+export { BlockService, IBlockService, ICommonService, CommonService, IFilterService, FilterService };
diff --git a/packages/relay/src/lib/services/transactionService/ITransactionService.ts b/packages/relay/src/lib/services/transactionService/ITransactionService.ts
deleted file mode 100644
index cdc649ac06..0000000000
--- a/packages/relay/src/lib/services/transactionService/ITransactionService.ts
+++ /dev/null
@@ -1 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
diff --git a/packages/relay/tests/lib/debug.spec.ts b/packages/relay/tests/lib/debug.spec.ts
index a8021f5c73..02abeb8f57 100644
--- a/packages/relay/tests/lib/debug.spec.ts
+++ b/packages/relay/tests/lib/debug.spec.ts
@@ -4,17 +4,23 @@ import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services'
 import MockAdapter from 'axios-mock-adapter';
 import chai, { expect } from 'chai';
 import chaiAsPromised from 'chai-as-promised';
+import { EventEmitter } from 'events';
 import pino from 'pino';
-import { Registry } from 'prom-client';
+import { register, Registry } from 'prom-client';
 
 import { predefined } from '../../src';
 import { strip0x } from '../../src/formatters';
 import { MirrorNodeClient } from '../../src/lib/clients';
 import { IOpcodesResponse } from '../../src/lib/clients/models/IOpcodesResponse';
-import { TracerType } from '../../src/lib/constants';
+import constants, { TracerType } from '../../src/lib/constants';
+import { EvmAddressHbarSpendingPlanRepository } from '../../src/lib/db/repositories/hbarLimiter/evmAddressHbarSpendingPlanRepository';
+import { HbarSpendingPlanRepository } from '../../src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository';
+import { IPAddressHbarSpendingPlanRepository } from '../../src/lib/db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository';
 import { DebugImpl } from '../../src/lib/debug';
+import { CommonService } from '../../src/lib/services';
 import { CacheService } from '../../src/lib/services/cacheService/cacheService';
-import { CommonService } from '../../src/lib/services/ethService';
+import HAPIService from '../../src/lib/services/hapiService/hapiService';
+import { HbarLimitService } from '../../src/lib/services/hbarLimitService';
 import { RequestDetails } from '../../src/lib/types';
 import RelayAssertions from '../assertions';
 import { getQueryParams, withOverriddenEnvsInMochaTest } from '../helpers';
@@ -23,12 +29,12 @@ chai.use(chaiAsPromised);
 const logger = pino({ level: 'silent' });
 const registry = new Registry();
 
-let restMock: typeof MockAdapter;
-let web3Mock: typeof MockAdapter;
+let restMock: MockAdapter;
+let web3Mock: MockAdapter;
 let mirrorNodeInstance: MirrorNodeClient;
 let debugService: DebugImpl;
 let cacheService: CacheService;
-
+let hapiServiceInstance: HAPIService;
 describe('Debug API Test Suite', async function () {
   this.timeout(10000);
 
@@ -253,12 +259,28 @@ describe('Debug API Test Suite', async function () {
       registry,
       cacheService,
     );
+    const duration = constants.HBAR_RATE_LIMIT_DURATION;
+    const eventEmitter = new EventEmitter();
+
+    const hbarSpendingPlanRepository = new HbarSpendingPlanRepository(cacheService, logger);
+    const evmAddressHbarSpendingPlanRepository = new EvmAddressHbarSpendingPlanRepository(cacheService, logger);
+    const ipAddressHbarSpendingPlanRepository = new IPAddressHbarSpendingPlanRepository(cacheService, logger);
+    const hbarLimitService = new HbarLimitService(
+      hbarSpendingPlanRepository,
+      evmAddressHbarSpendingPlanRepository,
+      ipAddressHbarSpendingPlanRepository,
+      logger,
+      register,
+      duration,
+    );
+    hapiServiceInstance = new HAPIService(logger, registry, cacheService, eventEmitter, hbarLimitService);
 
     restMock = new MockAdapter(mirrorNodeInstance.getMirrorNodeRestInstance(), { onNoMatch: 'throwException' });
+
     web3Mock = new MockAdapter(mirrorNodeInstance.getMirrorNodeWeb3Instance(), { onNoMatch: 'throwException' });
 
-    const common = new CommonService(mirrorNodeInstance, logger, cacheService);
-    debugService = new DebugImpl(mirrorNodeInstance, logger, common);
+    const common = new CommonService(mirrorNodeInstance, logger, cacheService, hapiServiceInstance);
+    debugService = new DebugImpl(mirrorNodeInstance, logger, cacheService, hapiServiceInstance);
   });
 
   describe('debug_traceTransaction', async function () {
diff --git a/packages/relay/tests/lib/eth/eth_getBlockReceipts.spec.ts b/packages/relay/tests/lib/eth/eth_getBlockReceipts.spec.ts
index a54ebb4dbb..1e5c141fb6 100644
--- a/packages/relay/tests/lib/eth/eth_getBlockReceipts.spec.ts
+++ b/packages/relay/tests/lib/eth/eth_getBlockReceipts.spec.ts
@@ -53,7 +53,7 @@ describe('@ethGetBlockReceipts using MirrorNode', async function () {
   this.beforeEach(async () => {
     // reset cache and restMock
     await cacheService.clear(requestDetails);
-    currentGasPriceStub = sinon.stub(ethImpl, 'getCurrentGasPriceForBlock').resolves('0x25');
+    currentGasPriceStub = sinon.stub(ethImpl.common, 'getCurrentGasPriceForBlock').resolves('0x25');
     extractBlockNumberOrTagStub = sinon.stub(ethImpl, 'extractBlockNumberOrTag').resolves(BLOCK_NUMBER);
     sdkClientStub = sinon.createStubInstance(SDKClient);
     getSdkClientStub = sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub);
diff --git a/packages/relay/tests/lib/eth/eth_getTransactionReceipt.spec.ts b/packages/relay/tests/lib/eth/eth_getTransactionReceipt.spec.ts
index 37a788e8ee..83449efea8 100644
--- a/packages/relay/tests/lib/eth/eth_getTransactionReceipt.spec.ts
+++ b/packages/relay/tests/lib/eth/eth_getTransactionReceipt.spec.ts
@@ -4,6 +4,7 @@ import { expect, use } from 'chai';
 import chaiAsPromised from 'chai-as-promised';
 import sinon, { createSandbox } from 'sinon';
 
+import { predefined } from '../../../src';
 import constants from '../../../src/lib/constants';
 import { EthImpl } from '../../../src/lib/eth';
 import { RequestDetails } from '../../../src/lib/types';
@@ -11,7 +12,6 @@ import RelayAssertions from '../../assertions';
 import { defaultErrorMessageHex } from '../../helpers';
 import { DEFAULT_BLOCK, EMPTY_LOGS_RESPONSE } from './eth-config';
 import { generateEthTestEnv } from './eth-helpers';
-import { predefined } from '../../../src';
 
 use(chaiAsPromised);
 
@@ -116,7 +116,7 @@ describe('@ethGetTransactionReceipt eth_getTransactionReceipt tests', async func
 
   const stubBlockAndFeesFunc = (sandbox: sinon.SinonSandbox) => {
     const gasPrice = 12500000000000000000;
-    sandbox.stub(ethImpl, <any>'getCurrentGasPriceForBlock').resolves('0xad78ebc5ac620000');
+    sandbox.stub(ethImpl.common, <any>'getCurrentGasPriceForBlock').resolves('0xad78ebc5ac620000');
     sandbox.stub(ethImpl, <any>'getBlockByHash').resolves(DEFAULT_BLOCK);
     sandbox.stub(ethImpl.common, <any>'getFeeWeibars').resolves(gasPrice);
   };
@@ -129,15 +129,18 @@ describe('@ethGetTransactionReceipt eth_getTransactionReceipt tests', async func
 
   it('returns `null` for non-existent hash', async function () {
     const txHash = '0x0000000000000000000000000000000000000000000000000000000000000001';
-    restMock.onGet(`contracts/results/${txHash}`).reply(404, JSON.stringify({
-      _status: {
-        messages: [
-          {
-            message: 'No correlating transaction',
-          },
-        ],
-      },
-    }));
+    restMock.onGet(`contracts/results/${txHash}`).reply(
+      404,
+      JSON.stringify({
+        _status: {
+          messages: [
+            {
+              message: 'No correlating transaction',
+            },
+          ],
+        },
+      }),
+    );
     restMock
       .onGet(`contracts/results/logs?transaction.hash=${txHash}&limit=100&order=asc`)
       .reply(200, JSON.stringify(EMPTY_LOGS_RESPONSE));
@@ -155,7 +158,9 @@ describe('@ethGetTransactionReceipt eth_getTransactionReceipt tests', async func
     restMock.onGet(`tokens/${defaultDetailedContractResultByHash.contract_id}`).reply(200);
     restMock.onGet(`tokens/${defaultDetailedContractResultByHash.contract_id}`).reply(200);
     // mirror node request mocks
-    restMock.onGet(`contracts/results/${defaultTxHash}`).reply(200, JSON.stringify(defaultDetailedContractResultByHash));
+    restMock
+      .onGet(`contracts/results/${defaultTxHash}`)
+      .reply(200, JSON.stringify(defaultDetailedContractResultByHash));
     restMock.onGet(`contracts/${defaultDetailedContractResultByHash.created_contract_ids[0]}`).reply(404);
     stubBlockAndFeesFunc(sandbox);
     const receipt = await ethImpl.getTransactionReceipt(defaultTxHash, requestDetails);
@@ -169,7 +174,9 @@ describe('@ethGetTransactionReceipt eth_getTransactionReceipt tests', async func
   });
 
   it('valid receipt on match should hit cache', async function () {
-    restMock.onGet(`contracts/results/${defaultTxHash}`).replyOnce(200, JSON.stringify(defaultDetailedContractResultByHash));
+    restMock
+      .onGet(`contracts/results/${defaultTxHash}`)
+      .replyOnce(200, JSON.stringify(defaultDetailedContractResultByHash));
     restMock.onGet(`contracts/${defaultDetailedContractResultByHash.created_contract_ids[0]}`).replyOnce(404);
     stubBlockAndFeesFunc(sandbox);
     for (let i = 0; i < 3; i++) {
@@ -186,10 +193,15 @@ describe('@ethGetTransactionReceipt eth_getTransactionReceipt tests', async func
 
   it('valid receipt with evm address on match', async function () {
     // mirror node request mocks
-    restMock.onGet(`contracts/results/${defaultTxHash}`).reply(200, JSON.stringify(defaultDetailedContractResultByHash));
-    restMock.onGet(`contracts/${defaultDetailedContractResultByHash.created_contract_ids[0]}`).reply(200, JSON.stringify({
-      evm_address: contractEvmAddress,
-    }));
+    restMock
+      .onGet(`contracts/results/${defaultTxHash}`)
+      .reply(200, JSON.stringify(defaultDetailedContractResultByHash));
+    restMock.onGet(`contracts/${defaultDetailedContractResultByHash.created_contract_ids[0]}`).reply(
+      200,
+      JSON.stringify({
+        evm_address: contractEvmAddress,
+      }),
+    );
     stubBlockAndFeesFunc(sandbox);
     const receipt = await ethImpl.getTransactionReceipt(defaultTxHash, requestDetails);
 
@@ -280,15 +292,21 @@ describe('@ethGetTransactionReceipt eth_getTransactionReceipt tests', async func
     const uniqueTxHash = '0x17cad7b827375d12d73af57b6a3e84353645fd31305ea58ff52dda53ec640533';
 
     // mirror node request mocks
-    restMock.onGet(`contracts/results/${uniqueTxHash}`).reply(200, JSON.stringify({
-      ...defaultDetailedContractResultByHash,
-      ...{
-        transaction_index: undefined,
-      },
-    }));
-    restMock.onGet(`contracts/${defaultDetailedContractResultByHash.created_contract_ids[0]}`).reply(200, JSON.stringify({
-      evm_address: contractEvmAddress,
-    }));
+    restMock.onGet(`contracts/results/${uniqueTxHash}`).reply(
+      200,
+      JSON.stringify({
+        ...defaultDetailedContractResultByHash,
+        ...{
+          transaction_index: undefined,
+        },
+      }),
+    );
+    restMock.onGet(`contracts/${defaultDetailedContractResultByHash.created_contract_ids[0]}`).reply(
+      200,
+      JSON.stringify({
+        evm_address: contractEvmAddress,
+      }),
+    );
     stubBlockAndFeesFunc(sandbox);
 
     try {
diff --git a/packages/relay/tests/lib/ethGetBlockBy.spec.ts b/packages/relay/tests/lib/ethGetBlockBy.spec.ts
index 9e8ff974e5..597cbeffd2 100644
--- a/packages/relay/tests/lib/ethGetBlockBy.spec.ts
+++ b/packages/relay/tests/lib/ethGetBlockBy.spec.ts
@@ -16,6 +16,8 @@ import { HbarSpendingPlanRepository } from '../../src/lib/db/repositories/hbarLi
 import { IPAddressHbarSpendingPlanRepository } from '../../src/lib/db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository';
 import { EthImpl } from '../../src/lib/eth';
 import { Log, Transaction } from '../../src/lib/model';
+import { CommonService } from '../../src/lib/services';
+import { BlockService } from '../../src/lib/services/blockService/BlockService';
 import { CacheService } from '../../src/lib/services/cacheService/cacheService';
 import HAPIService from '../../src/lib/services/hapiService/hapiService';
 import { HbarLimitService } from '../../src/lib/services/hbarLimitService';
@@ -99,6 +101,7 @@ const defaultLogs1 = [
 describe('eth_getBlockBy', async function () {
   this.timeout(10000);
   let ethImpl: EthImpl;
+  let blockService: BlockService;
 
   const requestDetails = new RequestDetails({ requestId: 'ethGetBlockByTest', ipAddress: '0.0.0.0' });
 
@@ -138,6 +141,8 @@ describe('eth_getBlockBy', async function () {
 
     // @ts-ignore
     ethImpl = new EthImpl(hapiServiceInstance, mirrorNodeInstance, logger, '0x12a', registry, cacheService);
+    const common = new CommonService(mirrorNodeInstance, logger, cacheService, hapiServiceInstance);
+    blockService = new BlockService(cacheService, '0x12a', common, mirrorNodeInstance, logger);
   });
 
   this.beforeEach(async () => {
@@ -171,7 +176,7 @@ describe('eth_getBlockBy', async function () {
 
     it('populateSyntheticTransactions with no dupes in empty transactionHashes', async function () {
       const initHashes = [];
-      ethImpl.populateSyntheticTransactions(showDetails, referenceLogs, initHashes, '1');
+      blockService.populateSyntheticTransactions(showDetails, referenceLogs, initHashes, requestDetails);
       expect(initHashes.length).to.equal(defaultLogs1.length);
       expect(initHashes[0]).to.equal(modelLog1.transactionHash);
       expect(initHashes[1]).to.equal(modelLog2.transactionHash);
@@ -181,7 +186,7 @@ describe('eth_getBlockBy', async function () {
     it('populateSyntheticTransactions with no dupes in non empty transactionHashes', async function () {
       const initHashes = ['txHash1', 'txHash2'];
       const txHashes = initHashes.slice();
-      ethImpl.populateSyntheticTransactions(showDetails, referenceLogs, txHashes, '1');
+      blockService.populateSyntheticTransactions(showDetails, referenceLogs, txHashes, requestDetails);
       expect(txHashes.length).to.equal(initHashes.length + defaultLogs1.length);
       expect(txHashes[initHashes.length + 0]).to.equal(modelLog1.transactionHash);
       expect(txHashes[initHashes.length + 1]).to.equal(modelLog2.transactionHash);
@@ -191,7 +196,7 @@ describe('eth_getBlockBy', async function () {
     it('populateSyntheticTransactions with 1 transaction dupes in transactionHashes', async function () {
       const initHashes = [modelLog2.transactionHash];
       const txHashes = initHashes.slice();
-      ethImpl.populateSyntheticTransactions(showDetails, referenceLogs, txHashes, '1');
+      blockService.populateSyntheticTransactions(showDetails, referenceLogs, txHashes, requestDetails);
       expect(txHashes.length).to.equal(referenceLogs.length);
       expect(txHashes[0]).to.equal(contractHash2);
       expect(txHashes[1]).to.equal(modelLog1.transactionHash);
@@ -201,7 +206,7 @@ describe('eth_getBlockBy', async function () {
     it('populateSyntheticTransactions with all dupes in transactionHashes', async function () {
       const initHashes = [modelLog1.transactionHash, modelLog2.transactionHash, modelLog3.transactionHash];
       const txHashes = initHashes.slice();
-      ethImpl.populateSyntheticTransactions(showDetails, referenceLogs, txHashes, '1');
+      blockService.populateSyntheticTransactions(showDetails, referenceLogs, txHashes, requestDetails);
       expect(txHashes.length).to.equal(referenceLogs.length);
       expect(txHashes[0]).to.equal(modelLog1.transactionHash);
       expect(txHashes[1]).to.equal(modelLog2.transactionHash);
@@ -237,7 +242,7 @@ describe('eth_getBlockBy', async function () {
     const showDetails = true;
     it('populateSyntheticTransactions with no dupes in empty txObjects', async function () {
       const initTxObjects: Transaction[] = [];
-      ethImpl.populateSyntheticTransactions(showDetails, referenceLogs, initTxObjects, '1');
+      blockService.populateSyntheticTransactions(showDetails, referenceLogs, initTxObjects, requestDetails);
       expect(initTxObjects.length).to.equal(defaultLogs1.length);
       expect(initTxObjects[0].hash).to.equal(modelLog1.transactionHash);
       expect(initTxObjects[1].hash).to.equal(modelLog2.transactionHash);
@@ -247,7 +252,7 @@ describe('eth_getBlockBy', async function () {
     it('populateSyntheticTransactions with no dupes in non empty txObjects', async function () {
       const initTxObjects = [getTransactionModel('txHash1'), getTransactionModel('txHash2')];
       const txObjects = initTxObjects.slice();
-      ethImpl.populateSyntheticTransactions(showDetails, referenceLogs, txObjects, '1');
+      blockService.populateSyntheticTransactions(showDetails, referenceLogs, txObjects, requestDetails);
       expect(txObjects.length).to.equal(initTxObjects.length + defaultLogs1.length);
       expect(txObjects[initTxObjects.length + 0].hash).to.equal(modelLog1.transactionHash);
       expect(txObjects[initTxObjects.length + 1].hash).to.equal(modelLog2.transactionHash);
@@ -257,7 +262,7 @@ describe('eth_getBlockBy', async function () {
     it('populateSyntheticTransactions with 1 transaction dupes in txObjects', async function () {
       const initTxObjects = [getTransactionModel(modelLog2.transactionHash)];
       const txObjects = initTxObjects.slice();
-      ethImpl.populateSyntheticTransactions(showDetails, referenceLogs, txObjects, '1');
+      blockService.populateSyntheticTransactions(showDetails, referenceLogs, txObjects, requestDetails);
       expect(txObjects.length).to.equal(referenceLogs.length);
       expect(txObjects[0].hash).to.equal(contractHash2);
       expect(txObjects[1].hash).to.equal(modelLog1.transactionHash);
@@ -271,7 +276,7 @@ describe('eth_getBlockBy', async function () {
         getTransactionModel(modelLog3.transactionHash),
       ];
       const txObjects = initTxObjects.slice();
-      ethImpl.populateSyntheticTransactions(showDetails, referenceLogs, txObjects, '1');
+      blockService.populateSyntheticTransactions(showDetails, referenceLogs, txObjects, requestDetails);
       expect(txObjects.length).to.equal(referenceLogs.length);
       expect(txObjects[0].hash).to.equal(modelLog1.transactionHash);
       expect(txObjects[1].hash).to.equal(modelLog2.transactionHash);
diff --git a/packages/relay/tests/lib/formatters.spec.ts b/packages/relay/tests/lib/formatters.spec.ts
index d431e99459..447a3462fe 100644
--- a/packages/relay/tests/lib/formatters.spec.ts
+++ b/packages/relay/tests/lib/formatters.spec.ts
@@ -31,8 +31,8 @@ import {
   weibarHexToTinyBarInt,
 } from '../../src/formatters';
 import constants from '../../src/lib/constants';
+import { CommonService } from '../../src/lib/services';
 import { overrideEnvsInMochaDescribe } from '../helpers';
-import { CommonService } from '../../dist/lib/services/ethService';
 
 describe('Formatters', () => {
   describe('formatRequestIdMessage', () => {
diff --git a/packages/relay/tests/lib/openrpc.spec.ts b/packages/relay/tests/lib/openrpc.spec.ts
index 027c6492c9..cb4eaed746 100644
--- a/packages/relay/tests/lib/openrpc.spec.ts
+++ b/packages/relay/tests/lib/openrpc.spec.ts
@@ -446,7 +446,7 @@ describe('Open RPC Specification', function () {
   it('should execute "eth_getTransactionReceipt"', async function () {
     mock.onGet(`contracts/${defaultDetailedContractResultByHash.created_contract_ids[0]}`).reply(404);
 
-    sinon.stub(ethImpl, <any>'getCurrentGasPriceForBlock').resolves('0xad78ebc5ac620000');
+    sinon.stub(ethImpl.common, <any>'getCurrentGasPriceForBlock').resolves('0xad78ebc5ac620000');
     const response = await ethImpl.getTransactionReceipt(defaultTxHash, requestDetails);
 
     validateResponseSchema(methodsResponseSchema.eth_getTransactionReceipt, response);
diff --git a/packages/relay/tests/lib/services/eth/filter.spec.ts b/packages/relay/tests/lib/services/eth/filter.spec.ts
index 78fb926b94..847a11ea15 100644
--- a/packages/relay/tests/lib/services/eth/filter.spec.ts
+++ b/packages/relay/tests/lib/services/eth/filter.spec.ts
@@ -10,8 +10,8 @@ import { v4 as uuid } from 'uuid';
 import { predefined } from '../../../../src';
 import { MirrorNodeClient } from '../../../../src/lib/clients';
 import constants from '../../../../src/lib/constants';
+import { CommonService, FilterService } from '../../../../src/lib/services';
 import { CacheService } from '../../../../src/lib/services/cacheService/cacheService';
-import { CommonService, FilterService } from '../../../../src/lib/services/ethService';
 import { RequestDetails } from '../../../../src/lib/types';
 import RelayAssertions from '../../../assertions';
 import {


Reviews:
user: login: Ferparishuertas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T07:35:02Z
user: login: Ferparishuertas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T07:35:39Z
user: login: konstantinabl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T08:39:48Z
user: login: konstantinabl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T11:48:04Z
user: login: konstantinabl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T12:34:10Z
user: login: simzzz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T08:37:13Z
user: login: simzzz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T08:59:11Z
user: login: natanasow
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T12:01:10Z
user: login: konstantinabl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T07:57:02Z
user: login: konstantinabl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T07:57:15Z
user: login: konstantinabl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T08:43:50Z
user: login: konstantinabl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T08:44:38Z
user: login: konstantinabl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T08:47:11Z
user: login: konstantinabl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T08:48:57Z
user: login: konstantinabl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T11:00:21Z
user: login: konstantinabl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T11:00:32Z
user: login: konstantinabl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T11:00:47Z
user: login: Ferparishuertas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T11:22:25Z
user: login: Ferparishuertas
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T11:23:36Z
user: login: konstantinabl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T11:26:27Z
user: login: simzzz
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T11:28:31Z
user: login: natanasow
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-03T11:58:50Z

Review comments:
diff_hunk: @@ -10,6 +10,12 @@ export interface IBlockService {
     requestDetails: RequestDetails,
   ) => Promise<Block | null>;
   getBlockByHash: (hash: string, showDetails: boolean, requestDetails: RequestDetails) => Promise<Block | null>;
+  getBlockTransactionCountByHash: (hash: string, requestDetails: RequestDetails) => Promise<string | null>;
+  getBlockTransactionCountByNumber: (blockNum: string, requestDetails: RequestDetails) => Promise<string | null>;
+  getUncleByBlockHashAndIndex: (requestDetails: RequestDetails) => Promise<null>;
+  getUncleByBlockNumberAndIndex: (requestDetails: RequestDetails) => Promise<null>;
+  getUncleCountByBlockHash: (requestDetails: RequestDetails) => Promise<string>;
+  getUncleCountByBlockNumber: (requestDetails: RequestDetails) => Promise<string>;
 }
 
 export interface ITimestamp {
path: packages/relay/src/lib/services/blockService/IBlockService.ts
user: login: Ferparishuertas
body: Why do we add more than 1 interface in the same file?
created_at: 2025-04-01T07:08:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -19,19 +19,57 @@ import { TransactionFactory } from '../factories/transactionFactory';
 import { IBlockMirrorNode, IBlockService } from './IBlockService';
 
 export class BlockService implements IBlockService {
path: packages/relay/src/lib/services/blockService/index.ts
user: login: Ferparishuertas
body: Why are we calling this file index.ts when it is not really a module, and visibility is across the relay?
created_at: 2025-04-01T07:20:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -16,20 +17,19 @@ import {
   tinybarsToWeibars,
   toHash32,
 } from '../../../../formatters';
+import { Utils } from '../../../../utils';
 import { MirrorNodeClient } from '../../../clients';
 import constants from '../../../constants';
 import { JsonRpcError, predefined } from '../../../errors/JsonRpcError';
 import { MirrorNodeClientError } from '../../../errors/MirrorNodeClientError';
 import { SDKClientError } from '../../../errors/SDKClientError';
 import { EthImpl } from '../../../eth';
-import { Log } from '../../../model';
+import { Log, Transaction } from '../../../model';
 import { RequestDetails } from '../../../types';
 import { CacheService } from '../../cacheService/cacheService';
-import { ICommonService } from './ICommonService';
-import { Utils } from '../../../../utils';
-import { Hbar } from '@hashgraph/sdk';
-import HAPIService from '../../hapiService/hapiService';
 import { TransactionFactory } from '../../factories/transactionFactory';
+import HAPIService from '../../hapiService/hapiService';
+import { ICommonService } from './ICommonService';
path: packages/relay/src/lib/services/ethService/ethCommonService/index.ts
user: login: Ferparishuertas
body: Why are we calling this file index.ts when it is not really a module, and visibility is across the relay?

and why the package is ethervice/ethCommonService?
Why not simply CommonService?
If we want to distinguish eth services why not. eth.services, and the block, common and others?
created_at: 2025-04-01T07:22:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -6,6 +6,7 @@ import { BigNumber as BN } from 'bignumber.js';
 import crypto from 'crypto';
 
 import constants from './lib/constants';
+import { Transaction } from './lib/model';
 import { TransactionFactory } from './lib/services/factories/transactionFactory';
path: packages/relay/src/formatters.ts
user: login: Ferparishuertas
body: Why this file is at the main level, and why is it lowerCase and others Uppercase?
created_at: 2025-04-01T07:23:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -25,7 +25,6 @@ import {
 } from '../formatters';
path: packages/relay/src/lib/eth.ts
user: login: Ferparishuertas
body: Why eth is inside a lib package?
created_at: 2025-04-01T07:35:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -10,6 +10,12 @@ export interface IBlockService {
     requestDetails: RequestDetails,
   ) => Promise<Block | null>;
   getBlockByHash: (hash: string, showDetails: boolean, requestDetails: RequestDetails) => Promise<Block | null>;
+  getBlockTransactionCountByHash: (hash: string, requestDetails: RequestDetails) => Promise<string | null>;
+  getBlockTransactionCountByNumber: (blockNum: string, requestDetails: RequestDetails) => Promise<string | null>;
+  getUncleByBlockHashAndIndex: (requestDetails: RequestDetails) => Promise<null>;
+  getUncleByBlockNumberAndIndex: (requestDetails: RequestDetails) => Promise<null>;
+  getUncleCountByBlockHash: (requestDetails: RequestDetails) => Promise<string>;
+  getUncleCountByBlockNumber: (requestDetails: RequestDetails) => Promise<string>;
 }
 
 export interface ITimestamp {
path: packages/relay/src/lib/services/blockService/IBlockService.ts
user: login: konstantinabl
body: Thanks for pointing this out, this is not my change actually and I didnt see it was introduced. It comes from an upstream branch, which I merged yesterday. I will fix it
created_at: 2025-04-01T08:39:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -25,7 +25,6 @@ import {
 } from '../formatters';
path: packages/relay/src/lib/eth.ts
user: login: konstantinabl
body: I dont know. It has always been like this and there was never a discussion about moving it
created_at: 2025-04-01T11:48:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -6,6 +6,7 @@ import { BigNumber as BN } from 'bignumber.js';
 import crypto from 'crypto';
 
 import constants from './lib/constants';
+import { Transaction } from './lib/model';
 import { TransactionFactory } from './lib/services/factories/transactionFactory';
path: packages/relay/src/formatters.ts
user: login: konstantinabl
body: Not sure what you mean by the "main level"
created_at: 2025-04-01T12:34:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,424 @@
+// SPDX-License-Identifier: Apache-2.0
+import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
+import _ from 'lodash';
+import { Logger } from 'pino';
+
+import { nanOrNumberTo0x, numberTo0x } from '../../../formatters';
+import { IReceiptRootHash, ReceiptsRootUtils } from '../../../receiptsRootUtils';
+import { Utils } from '../../../utils';
+import { MirrorNodeClient } from '../../clients/mirrorNodeClient';
+import constants from '../../constants';
+import { predefined } from '../../errors/JsonRpcError';
+import { EthImpl } from '../../eth';
+import { Block, Log, Transaction } from '../../model';
+import { RequestDetails } from '../../types';
+import { CacheService } from '../cacheService/cacheService';
+import { CommonService } from '../ethService/ethCommonService';
+import { BlockFactory } from '../factories/blockFactory';
+import { TransactionFactory } from '../factories/transactionFactory';
+import { IBlockMirrorNode, IBlockService } from './IBlockService';
+
+export class BlockService implements IBlockService {
+  /**
+   * The cache service used for caching all responses.
+   * @private
+   */
+  private readonly cacheService: CacheService;
+
+  /**
+   * The chain id.
+   * @private
+   */
+  private readonly chain: string;
+
+  /**
+   * The common service used for all common methods.
+   * @private
+   */
+  private readonly common: CommonService;
+
+  /**
+   * The maximum block range for the transaction count.
+   */
+  private readonly ethGetTransactionCountMaxBlockRange = ConfigService.get('ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE');
+
+  /**
+   * The logger used for logging all output from this class.
+   * @private
+   */
+  private readonly logger: Logger;
+
+  /**
+   * The interface through which we interact with the mirror node
+   * @private
+   */
+  private readonly mirrorNodeClient: MirrorNodeClient;
+
+  /**
+   * The static method for the eth_getBlockByHash RPC call.
+   */
+  static ethGetBlockByHash = 'eth_GetBlockByHash';
+
+  /** Constructor */
+  constructor(
+    cacheService: CacheService,
+    chain: string,
+    common: CommonService,
+    mirrorNodeClient: MirrorNodeClient,
+    logger: Logger,
+  ) {
+    this.common = common;
+    this.chain = chain;
+    this.mirrorNodeClient = mirrorNodeClient;
+    this.logger = logger;
+    this.cacheService = cacheService;
+  }
+
+  public async getBlockByNumber(
path: packages/relay/src/lib/services/blockService/BlockService.ts
user: login: simzzz
body: Since we have separate handlers for `getBlockByNumber` and `getBlockByHash` in eth.ts wouldn't it be cleaner to have one function for both here since they are very similar in order to reduce code repetition and just pass some extra properties. 

An example signature could be
```
private async getBlockByNumberOrHash(
  type: "number" | "hash",
  identifier: string,
  showDetails: boolean,
  requestDetails: RequestDetails,
  cacheKeyPrefix: string,
  cacheMethod: string,
): Promise<Block | null> 
```
created_at: 2025-04-02T08:37:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,424 @@
+// SPDX-License-Identifier: Apache-2.0
+import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
+import _ from 'lodash';
+import { Logger } from 'pino';
+
+import { nanOrNumberTo0x, numberTo0x } from '../../../formatters';
+import { IReceiptRootHash, ReceiptsRootUtils } from '../../../receiptsRootUtils';
+import { Utils } from '../../../utils';
+import { MirrorNodeClient } from '../../clients/mirrorNodeClient';
+import constants from '../../constants';
+import { predefined } from '../../errors/JsonRpcError';
+import { EthImpl } from '../../eth';
+import { Block, Log, Transaction } from '../../model';
+import { RequestDetails } from '../../types';
+import { CacheService } from '../cacheService/cacheService';
+import { CommonService } from '../ethService/ethCommonService';
+import { BlockFactory } from '../factories/blockFactory';
+import { TransactionFactory } from '../factories/transactionFactory';
+import { IBlockMirrorNode, IBlockService } from './IBlockService';
+
+export class BlockService implements IBlockService {
+  /**
+   * The cache service used for caching all responses.
+   * @private
+   */
+  private readonly cacheService: CacheService;
+
+  /**
+   * The chain id.
+   * @private
+   */
+  private readonly chain: string;
+
+  /**
+   * The common service used for all common methods.
+   * @private
+   */
+  private readonly common: CommonService;
+
+  /**
+   * The maximum block range for the transaction count.
+   */
+  private readonly ethGetTransactionCountMaxBlockRange = ConfigService.get('ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE');
+
+  /**
+   * The logger used for logging all output from this class.
+   * @private
+   */
+  private readonly logger: Logger;
+
+  /**
+   * The interface through which we interact with the mirror node
+   * @private
+   */
+  private readonly mirrorNodeClient: MirrorNodeClient;
+
+  /**
+   * The static method for the eth_getBlockByHash RPC call.
+   */
+  static ethGetBlockByHash = 'eth_GetBlockByHash';
+
+  /** Constructor */
+  constructor(
+    cacheService: CacheService,
+    chain: string,
+    common: CommonService,
+    mirrorNodeClient: MirrorNodeClient,
+    logger: Logger,
+  ) {
+    this.common = common;
+    this.chain = chain;
+    this.mirrorNodeClient = mirrorNodeClient;
+    this.logger = logger;
+    this.cacheService = cacheService;
+  }
+
+  public async getBlockByNumber(
+    blockNumber: string,
+    showDetails: boolean,
+    requestDetails: RequestDetails,
+  ): Promise<Block> {
+    const requestIdPrefix = requestDetails.formattedRequestId;
+    this.logger.trace(`${requestIdPrefix} getBlockByNumber(blockNumber=${blockNumber}, showDetails=${showDetails})`);
+
+    const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_BY_NUMBER}_${blockNumber}_${showDetails}`;
+    let block = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetBlockByNumber, requestDetails);
path: packages/relay/src/lib/services/blockService/BlockService.ts
user: login: simzzz
body: Shouldn't `EthImpl.ethGetBlockByNumber` be moved to BlockService along with other statics like in `getBlockByHash`?
created_at: 2025-04-02T08:39:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2132,9 +2031,9 @@ export class EthImpl implements Eth {
   }
 
   private async getBlockNumberFromHash(blockHash: string, requestDetails: RequestDetails): Promise<string> {
-    const block = await this.getBlockByHash(blockHash, false, requestDetails);
+    const block = await this.mirrorNodeClient.getBlock(blockHash, requestDetails);
     if (block != null) {
-      return block.number;
+      return numberTo0x(block.number);
path: packages/relay/src/lib/eth.ts
user: login: natanasow
body: q: why do we add `numberTo0x` here?
created_at: 2025-04-02T11:51:48Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,532 @@
+// SPDX-License-Identifier: Apache-2.0
+import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
+import _ from 'lodash';
+import { Logger } from 'pino';
+
+import { nanOrNumberTo0x, nullableNumberTo0x, numberTo0x, toHash32 } from '../../../formatters';
+import { IReceiptRootHash, ReceiptsRootUtils } from '../../../receiptsRootUtils';
+import { Utils } from '../../../utils';
+import { MirrorNodeClient } from '../../clients/mirrorNodeClient';
+import constants from '../../constants';
+import { predefined } from '../../errors/JsonRpcError';
+import { EthImpl } from '../../eth';
+import { Block, Log, Receipt, Transaction } from '../../model';
+import { IContractResultsParams, RequestDetails } from '../../types';
+import { CacheService } from '../cacheService/cacheService';
+import { CommonService } from '../ethService/ethCommonService/CommonService';
+import { BlockFactory } from '../factories/blockFactory';
+import { TransactionFactory } from '../factories/transactionFactory';
+import { IBlockService, ICommonService } from '../index';
+import { IBlockMirrorNode } from './IBlockService';
+
+export class BlockService implements IBlockService {
+  /**
+   * The cache service used for caching all responses.
+   * @private
+   */
+  private readonly cacheService: CacheService;
+
+  /**
+   * The chain id.
+   * @private
+   */
+  private readonly chain: string;
+
+  /**
+   * The common service used for all common methods.
+   * @private
+   */
+  private readonly common: ICommonService;
+
+  /**
+   * The maximum block range for the transaction count.
+   */
+  private readonly ethGetTransactionCountMaxBlockRange = ConfigService.get('ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE');
+
+  /**
+   * The logger used for logging all output from this class.
+   * @private
+   */
+  private readonly logger: Logger;
+
+  /**
+   * The interface through which we interact with the mirror node
+   * @private
+   */
+  private readonly mirrorNodeClient: MirrorNodeClient;
+
+  /**
+   * The static method for the eth_getBlockByHash RPC call.
+   */
+  static ethGetBlockByHash = 'eth_GetBlockByHash';
+
+  /** Constructor */
+  constructor(
+    cacheService: CacheService,
+    chain: string,
+    common: ICommonService,
+    mirrorNodeClient: MirrorNodeClient,
+    logger: Logger,
+  ) {
+    this.common = common;
path: packages/relay/src/lib/services/blockService/BlockService.ts
user: login: natanasow
body: nit: we could reorder them as they are passed in the constructor
created_at: 2025-04-02T11:53:29Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,532 @@
+// SPDX-License-Identifier: Apache-2.0
+import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
+import _ from 'lodash';
+import { Logger } from 'pino';
+
+import { nanOrNumberTo0x, nullableNumberTo0x, numberTo0x, toHash32 } from '../../../formatters';
+import { IReceiptRootHash, ReceiptsRootUtils } from '../../../receiptsRootUtils';
+import { Utils } from '../../../utils';
+import { MirrorNodeClient } from '../../clients/mirrorNodeClient';
+import constants from '../../constants';
+import { predefined } from '../../errors/JsonRpcError';
+import { EthImpl } from '../../eth';
+import { Block, Log, Receipt, Transaction } from '../../model';
+import { IContractResultsParams, RequestDetails } from '../../types';
+import { CacheService } from '../cacheService/cacheService';
+import { CommonService } from '../ethService/ethCommonService/CommonService';
+import { BlockFactory } from '../factories/blockFactory';
+import { TransactionFactory } from '../factories/transactionFactory';
+import { IBlockService, ICommonService } from '../index';
+import { IBlockMirrorNode } from './IBlockService';
+
+export class BlockService implements IBlockService {
+  /**
+   * The cache service used for caching all responses.
+   * @private
+   */
+  private readonly cacheService: CacheService;
+
+  /**
+   * The chain id.
+   * @private
+   */
+  private readonly chain: string;
+
+  /**
+   * The common service used for all common methods.
+   * @private
+   */
+  private readonly common: ICommonService;
+
+  /**
+   * The maximum block range for the transaction count.
+   */
+  private readonly ethGetTransactionCountMaxBlockRange = ConfigService.get('ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE');
+
+  /**
+   * The logger used for logging all output from this class.
+   * @private
+   */
+  private readonly logger: Logger;
+
+  /**
+   * The interface through which we interact with the mirror node
+   * @private
+   */
+  private readonly mirrorNodeClient: MirrorNodeClient;
+
+  /**
+   * The static method for the eth_getBlockByHash RPC call.
+   */
+  static ethGetBlockByHash = 'eth_GetBlockByHash';
+
+  /** Constructor */
+  constructor(
+    cacheService: CacheService,
+    chain: string,
+    common: ICommonService,
+    mirrorNodeClient: MirrorNodeClient,
+    logger: Logger,
+  ) {
+    this.common = common;
+    this.chain = chain;
+    this.mirrorNodeClient = mirrorNodeClient;
+    this.logger = logger;
+    this.cacheService = cacheService;
+  }
+
+  /**
+   * Gets the block with the given hash.
+   *
+   * @param {string} hash the block hash
+   * @param {boolean} showDetails whether to show the details of the block
+   * @param {RequestDetails} requestDetails The request details for logging and tracking
+   * @returns {Promise<Block | null>} The block
+   */
+  public async getBlockByHash(
+    hash: string,
+    showDetails: boolean,
+    requestDetails: RequestDetails,
+  ): Promise<Block | null> {
+    const requestIdPrefix = requestDetails.formattedRequestId;
+    this.logger.trace(`${requestIdPrefix} getBlockByHash(hash=${hash}, showDetails=${showDetails})`);
+
+    const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_BY_HASH}_${hash}_${showDetails}`;
+    let block = await this.cacheService.getAsync(cacheKey, BlockService.ethGetBlockByHash, requestDetails);
+    if (!block) {
+      block = await this.getBlock(hash, showDetails, requestDetails).catch((e: any) => {
+        throw this.common.genericErrorHandler(e, `${requestIdPrefix} Failed to retrieve block for hash ${hash}`);
+      });
+      await this.cacheService.set(cacheKey, block, BlockService.ethGetBlockByHash, requestDetails);
+    }
+
+    return block;
+  }
+
+  /**
+   * Gets the block with the given number.
+   *
+   * @param {string} blockNumber The block number
+   * @param {boolean} showDetails Whether to show the details of the block
+   * @param {RequestDetails} requestDetails The request details for logging and tracking
+   * @returns {Promise<Block>} The block
+   */
+  public async getBlockByNumber(
+    blockNumber: string,
+    showDetails: boolean,
+    requestDetails: RequestDetails,
+  ): Promise<Block> {
+    const requestIdPrefix = requestDetails.formattedRequestId;
+    this.logger.trace(`${requestIdPrefix} getBlockByNumber(blockNumber=${blockNumber}, showDetails=${showDetails})`);
+
+    const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_BY_NUMBER}_${blockNumber}_${showDetails}`;
+    let block = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetBlockByNumber, requestDetails);
+    if (!block) {
+      block = await this.getBlock(blockNumber, showDetails, requestDetails).catch((e: any) => {
+        throw this.common.genericErrorHandler(
+          e,
+          `${requestIdPrefix} Failed to retrieve block for blockNumber ${blockNumber}`,
+        );
+      });
+
+      if (!this.common.blockTagIsLatestOrPending(blockNumber)) {
+        await this.cacheService.set(cacheKey, block, EthImpl.ethGetBlockByNumber, requestDetails);
+      }
+    }
+
+    return block;
+  }
+
+  /**
+   * Gets all transaction receipts for a block by block hash or block number.
+   *
+   * @param {string} blockHashOrBlockNumber The block hash, block number, or block tag
+   * @param {RequestDetails} requestDetails The request details for logging and tracking
+   * @returns {Promise<Receipt[]>} Array of transaction receipts for the block
+   */
+  public async getBlockReceipts(blockHashOrBlockNumber: string, requestDetails: RequestDetails): Promise<Receipt[]> {
+    const requestIdPrefix = requestDetails.formattedRequestId;
+    if (this.logger.isLevelEnabled('trace')) {
+      this.logger.trace(`${requestIdPrefix} getBlockReceipt(${JSON.stringify(blockHashOrBlockNumber)})`);
+    }
+
+    const block = await this.common.getHistoricalBlockResponse(requestDetails, blockHashOrBlockNumber);
+    const blockNumber = block.number;
+
+    const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_RECEIPTS}_${blockNumber}`;
+    const cachedResponse = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetBlockReceipts, requestDetails);
+    if (cachedResponse) {
+      if (this.logger.isLevelEnabled('debug')) {
+        this.logger.debug(
+          `${requestIdPrefix} getBlockReceipts returned cached response: ${JSON.stringify(cachedResponse)}`,
+        );
+      }
+      return cachedResponse;
+    }
+
+    const paramTimestamp: IContractResultsParams = {
+      timestamp: [`lte:${block.timestamp.to}`, `gte:${block.timestamp.from}`],
+    };
+
+    const contractResults = await this.mirrorNodeClient.getContractResults(requestDetails, paramTimestamp);
+    if (!contractResults || contractResults.length === 0) {
+      return [];
+    }
+
+    const effectiveGas = await this.common.getCurrentGasPriceForBlock(block.hash, requestDetails);
+
+    const logs = await this.common.getLogsWithParams(null, paramTimestamp, requestDetails);
+    contractResults.forEach((contractResult) => {
+      contractResult.logs = logs.filter((log) => log.transactionHash === contractResult.hash);
+    });
+
+    const receipts: Receipt[] = [];
+
+    for (const contractResult of contractResults) {
+      const from = await this.common.resolveEvmAddress(contractResult.from, requestDetails);
+      const to = await this.common.resolveEvmAddress(contractResult.to, requestDetails);
+
+      const contractAddress = this.common.getContractAddressFromReceipt(contractResult);
+      const receipt = {
path: packages/relay/src/lib/services/blockService/BlockService.ts
user: login: natanasow
body: future: we could add ReceiptFactory here
created_at: 2025-04-02T11:55:15Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,541 @@
+//contract results
path: packages/relay/src/lib/sth.json
user: login: natanasow
body: q: what's the purpose of these files `sth.json` and `sth2.json`, some comment might be useful
created_at: 2025-04-02T12:00:44Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,541 @@
+//contract results
path: packages/relay/src/lib/sth.json
user: login: konstantinabl
body: accidentally added. removed now : ) 
created_at: 2025-04-03T07:57:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,532 @@
+// SPDX-License-Identifier: Apache-2.0
+import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
+import _ from 'lodash';
+import { Logger } from 'pino';
+
+import { nanOrNumberTo0x, nullableNumberTo0x, numberTo0x, toHash32 } from '../../../formatters';
+import { IReceiptRootHash, ReceiptsRootUtils } from '../../../receiptsRootUtils';
+import { Utils } from '../../../utils';
+import { MirrorNodeClient } from '../../clients/mirrorNodeClient';
+import constants from '../../constants';
+import { predefined } from '../../errors/JsonRpcError';
+import { EthImpl } from '../../eth';
+import { Block, Log, Receipt, Transaction } from '../../model';
+import { IContractResultsParams, RequestDetails } from '../../types';
+import { CacheService } from '../cacheService/cacheService';
+import { CommonService } from '../ethService/ethCommonService/CommonService';
+import { BlockFactory } from '../factories/blockFactory';
+import { TransactionFactory } from '../factories/transactionFactory';
+import { IBlockService, ICommonService } from '../index';
+import { IBlockMirrorNode } from './IBlockService';
+
+export class BlockService implements IBlockService {
+  /**
+   * The cache service used for caching all responses.
+   * @private
+   */
+  private readonly cacheService: CacheService;
+
+  /**
+   * The chain id.
+   * @private
+   */
+  private readonly chain: string;
+
+  /**
+   * The common service used for all common methods.
+   * @private
+   */
+  private readonly common: ICommonService;
+
+  /**
+   * The maximum block range for the transaction count.
+   */
+  private readonly ethGetTransactionCountMaxBlockRange = ConfigService.get('ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE');
+
+  /**
+   * The logger used for logging all output from this class.
+   * @private
+   */
+  private readonly logger: Logger;
+
+  /**
+   * The interface through which we interact with the mirror node
+   * @private
+   */
+  private readonly mirrorNodeClient: MirrorNodeClient;
+
+  /**
+   * The static method for the eth_getBlockByHash RPC call.
+   */
+  static ethGetBlockByHash = 'eth_GetBlockByHash';
+
+  /** Constructor */
+  constructor(
+    cacheService: CacheService,
+    chain: string,
+    common: ICommonService,
+    mirrorNodeClient: MirrorNodeClient,
+    logger: Logger,
+  ) {
+    this.common = common;
+    this.chain = chain;
+    this.mirrorNodeClient = mirrorNodeClient;
+    this.logger = logger;
+    this.cacheService = cacheService;
+  }
+
+  /**
+   * Gets the block with the given hash.
+   *
+   * @param {string} hash the block hash
+   * @param {boolean} showDetails whether to show the details of the block
+   * @param {RequestDetails} requestDetails The request details for logging and tracking
+   * @returns {Promise<Block | null>} The block
+   */
+  public async getBlockByHash(
+    hash: string,
+    showDetails: boolean,
+    requestDetails: RequestDetails,
+  ): Promise<Block | null> {
+    const requestIdPrefix = requestDetails.formattedRequestId;
+    this.logger.trace(`${requestIdPrefix} getBlockByHash(hash=${hash}, showDetails=${showDetails})`);
+
+    const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_BY_HASH}_${hash}_${showDetails}`;
+    let block = await this.cacheService.getAsync(cacheKey, BlockService.ethGetBlockByHash, requestDetails);
+    if (!block) {
+      block = await this.getBlock(hash, showDetails, requestDetails).catch((e: any) => {
+        throw this.common.genericErrorHandler(e, `${requestIdPrefix} Failed to retrieve block for hash ${hash}`);
+      });
+      await this.cacheService.set(cacheKey, block, BlockService.ethGetBlockByHash, requestDetails);
+    }
+
+    return block;
+  }
+
+  /**
+   * Gets the block with the given number.
+   *
+   * @param {string} blockNumber The block number
+   * @param {boolean} showDetails Whether to show the details of the block
+   * @param {RequestDetails} requestDetails The request details for logging and tracking
+   * @returns {Promise<Block>} The block
+   */
+  public async getBlockByNumber(
+    blockNumber: string,
+    showDetails: boolean,
+    requestDetails: RequestDetails,
+  ): Promise<Block> {
+    const requestIdPrefix = requestDetails.formattedRequestId;
+    this.logger.trace(`${requestIdPrefix} getBlockByNumber(blockNumber=${blockNumber}, showDetails=${showDetails})`);
+
+    const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_BY_NUMBER}_${blockNumber}_${showDetails}`;
+    let block = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetBlockByNumber, requestDetails);
+    if (!block) {
+      block = await this.getBlock(blockNumber, showDetails, requestDetails).catch((e: any) => {
+        throw this.common.genericErrorHandler(
+          e,
+          `${requestIdPrefix} Failed to retrieve block for blockNumber ${blockNumber}`,
+        );
+      });
+
+      if (!this.common.blockTagIsLatestOrPending(blockNumber)) {
+        await this.cacheService.set(cacheKey, block, EthImpl.ethGetBlockByNumber, requestDetails);
+      }
+    }
+
+    return block;
+  }
+
+  /**
+   * Gets all transaction receipts for a block by block hash or block number.
+   *
+   * @param {string} blockHashOrBlockNumber The block hash, block number, or block tag
+   * @param {RequestDetails} requestDetails The request details for logging and tracking
+   * @returns {Promise<Receipt[]>} Array of transaction receipts for the block
+   */
+  public async getBlockReceipts(blockHashOrBlockNumber: string, requestDetails: RequestDetails): Promise<Receipt[]> {
+    const requestIdPrefix = requestDetails.formattedRequestId;
+    if (this.logger.isLevelEnabled('trace')) {
+      this.logger.trace(`${requestIdPrefix} getBlockReceipt(${JSON.stringify(blockHashOrBlockNumber)})`);
+    }
+
+    const block = await this.common.getHistoricalBlockResponse(requestDetails, blockHashOrBlockNumber);
+    const blockNumber = block.number;
+
+    const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_RECEIPTS}_${blockNumber}`;
+    const cachedResponse = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetBlockReceipts, requestDetails);
+    if (cachedResponse) {
+      if (this.logger.isLevelEnabled('debug')) {
+        this.logger.debug(
+          `${requestIdPrefix} getBlockReceipts returned cached response: ${JSON.stringify(cachedResponse)}`,
+        );
+      }
+      return cachedResponse;
+    }
+
+    const paramTimestamp: IContractResultsParams = {
+      timestamp: [`lte:${block.timestamp.to}`, `gte:${block.timestamp.from}`],
+    };
+
+    const contractResults = await this.mirrorNodeClient.getContractResults(requestDetails, paramTimestamp);
+    if (!contractResults || contractResults.length === 0) {
+      return [];
+    }
+
+    const effectiveGas = await this.common.getCurrentGasPriceForBlock(block.hash, requestDetails);
+
+    const logs = await this.common.getLogsWithParams(null, paramTimestamp, requestDetails);
+    contractResults.forEach((contractResult) => {
+      contractResult.logs = logs.filter((log) => log.transactionHash === contractResult.hash);
+    });
+
+    const receipts: Receipt[] = [];
+
+    for (const contractResult of contractResults) {
+      const from = await this.common.resolveEvmAddress(contractResult.from, requestDetails);
+      const to = await this.common.resolveEvmAddress(contractResult.to, requestDetails);
+
+      const contractAddress = this.common.getContractAddressFromReceipt(contractResult);
+      const receipt = {
path: packages/relay/src/lib/services/blockService/BlockService.ts
user: login: konstantinabl
body: Yes, I was thinking the same, I will add an issue to the refactor epic
created_at: 2025-04-03T07:57:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,532 @@
+// SPDX-License-Identifier: Apache-2.0
+import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
+import _ from 'lodash';
+import { Logger } from 'pino';
+
+import { nanOrNumberTo0x, nullableNumberTo0x, numberTo0x, toHash32 } from '../../../formatters';
+import { IReceiptRootHash, ReceiptsRootUtils } from '../../../receiptsRootUtils';
+import { Utils } from '../../../utils';
+import { MirrorNodeClient } from '../../clients/mirrorNodeClient';
+import constants from '../../constants';
+import { predefined } from '../../errors/JsonRpcError';
+import { EthImpl } from '../../eth';
+import { Block, Log, Receipt, Transaction } from '../../model';
+import { IContractResultsParams, RequestDetails } from '../../types';
+import { CacheService } from '../cacheService/cacheService';
+import { CommonService } from '../ethService/ethCommonService/CommonService';
+import { BlockFactory } from '../factories/blockFactory';
+import { TransactionFactory } from '../factories/transactionFactory';
+import { IBlockService, ICommonService } from '../index';
+import { IBlockMirrorNode } from './IBlockService';
+
+export class BlockService implements IBlockService {
+  /**
+   * The cache service used for caching all responses.
+   * @private
+   */
+  private readonly cacheService: CacheService;
+
+  /**
+   * The chain id.
+   * @private
+   */
+  private readonly chain: string;
+
+  /**
+   * The common service used for all common methods.
+   * @private
+   */
+  private readonly common: ICommonService;
+
+  /**
+   * The maximum block range for the transaction count.
+   */
+  private readonly ethGetTransactionCountMaxBlockRange = ConfigService.get('ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE');
+
+  /**
+   * The logger used for logging all output from this class.
+   * @private
+   */
+  private readonly logger: Logger;
+
+  /**
+   * The interface through which we interact with the mirror node
+   * @private
+   */
+  private readonly mirrorNodeClient: MirrorNodeClient;
+
+  /**
+   * The static method for the eth_getBlockByHash RPC call.
+   */
+  static ethGetBlockByHash = 'eth_GetBlockByHash';
+
+  /** Constructor */
+  constructor(
+    cacheService: CacheService,
+    chain: string,
+    common: ICommonService,
+    mirrorNodeClient: MirrorNodeClient,
+    logger: Logger,
+  ) {
+    this.common = common;
+    this.chain = chain;
+    this.mirrorNodeClient = mirrorNodeClient;
+    this.logger = logger;
+    this.cacheService = cacheService;
+  }
+
+  /**
+   * Gets the block with the given hash.
+   *
+   * @param {string} hash the block hash
+   * @param {boolean} showDetails whether to show the details of the block
+   * @param {RequestDetails} requestDetails The request details for logging and tracking
+   * @returns {Promise<Block | null>} The block
+   */
+  public async getBlockByHash(
+    hash: string,
+    showDetails: boolean,
+    requestDetails: RequestDetails,
+  ): Promise<Block | null> {
+    const requestIdPrefix = requestDetails.formattedRequestId;
+    this.logger.trace(`${requestIdPrefix} getBlockByHash(hash=${hash}, showDetails=${showDetails})`);
+
+    const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_BY_HASH}_${hash}_${showDetails}`;
+    let block = await this.cacheService.getAsync(cacheKey, BlockService.ethGetBlockByHash, requestDetails);
+    if (!block) {
+      block = await this.getBlock(hash, showDetails, requestDetails).catch((e: any) => {
+        throw this.common.genericErrorHandler(e, `${requestIdPrefix} Failed to retrieve block for hash ${hash}`);
+      });
+      await this.cacheService.set(cacheKey, block, BlockService.ethGetBlockByHash, requestDetails);
+    }
+
+    return block;
+  }
+
+  /**
+   * Gets the block with the given number.
+   *
+   * @param {string} blockNumber The block number
+   * @param {boolean} showDetails Whether to show the details of the block
+   * @param {RequestDetails} requestDetails The request details for logging and tracking
+   * @returns {Promise<Block>} The block
+   */
+  public async getBlockByNumber(
+    blockNumber: string,
+    showDetails: boolean,
+    requestDetails: RequestDetails,
+  ): Promise<Block> {
+    const requestIdPrefix = requestDetails.formattedRequestId;
+    this.logger.trace(`${requestIdPrefix} getBlockByNumber(blockNumber=${blockNumber}, showDetails=${showDetails})`);
+
+    const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_BY_NUMBER}_${blockNumber}_${showDetails}`;
+    let block = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetBlockByNumber, requestDetails);
+    if (!block) {
+      block = await this.getBlock(blockNumber, showDetails, requestDetails).catch((e: any) => {
+        throw this.common.genericErrorHandler(
+          e,
+          `${requestIdPrefix} Failed to retrieve block for blockNumber ${blockNumber}`,
+        );
+      });
+
+      if (!this.common.blockTagIsLatestOrPending(blockNumber)) {
+        await this.cacheService.set(cacheKey, block, EthImpl.ethGetBlockByNumber, requestDetails);
+      }
+    }
+
+    return block;
+  }
+
+  /**
+   * Gets all transaction receipts for a block by block hash or block number.
+   *
+   * @param {string} blockHashOrBlockNumber The block hash, block number, or block tag
+   * @param {RequestDetails} requestDetails The request details for logging and tracking
+   * @returns {Promise<Receipt[]>} Array of transaction receipts for the block
+   */
+  public async getBlockReceipts(blockHashOrBlockNumber: string, requestDetails: RequestDetails): Promise<Receipt[]> {
+    const requestIdPrefix = requestDetails.formattedRequestId;
+    if (this.logger.isLevelEnabled('trace')) {
+      this.logger.trace(`${requestIdPrefix} getBlockReceipt(${JSON.stringify(blockHashOrBlockNumber)})`);
+    }
+
+    const block = await this.common.getHistoricalBlockResponse(requestDetails, blockHashOrBlockNumber);
+    const blockNumber = block.number;
+
+    const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_RECEIPTS}_${blockNumber}`;
+    const cachedResponse = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetBlockReceipts, requestDetails);
+    if (cachedResponse) {
+      if (this.logger.isLevelEnabled('debug')) {
+        this.logger.debug(
+          `${requestIdPrefix} getBlockReceipts returned cached response: ${JSON.stringify(cachedResponse)}`,
+        );
+      }
+      return cachedResponse;
+    }
+
+    const paramTimestamp: IContractResultsParams = {
+      timestamp: [`lte:${block.timestamp.to}`, `gte:${block.timestamp.from}`],
+    };
+
+    const contractResults = await this.mirrorNodeClient.getContractResults(requestDetails, paramTimestamp);
+    if (!contractResults || contractResults.length === 0) {
+      return [];
+    }
+
+    const effectiveGas = await this.common.getCurrentGasPriceForBlock(block.hash, requestDetails);
+
+    const logs = await this.common.getLogsWithParams(null, paramTimestamp, requestDetails);
+    contractResults.forEach((contractResult) => {
+      contractResult.logs = logs.filter((log) => log.transactionHash === contractResult.hash);
+    });
+
+    const receipts: Receipt[] = [];
+
+    for (const contractResult of contractResults) {
+      const from = await this.common.resolveEvmAddress(contractResult.from, requestDetails);
+      const to = await this.common.resolveEvmAddress(contractResult.to, requestDetails);
+
+      const contractAddress = this.common.getContractAddressFromReceipt(contractResult);
+      const receipt = {
path: packages/relay/src/lib/services/blockService/BlockService.ts
user: login: konstantinabl
body: https://github.com/hiero-ledger/hiero-json-rpc-relay/issues/3622
created_at: 2025-04-03T08:43:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,532 @@
+// SPDX-License-Identifier: Apache-2.0
+import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
+import _ from 'lodash';
+import { Logger } from 'pino';
+
+import { nanOrNumberTo0x, nullableNumberTo0x, numberTo0x, toHash32 } from '../../../formatters';
+import { IReceiptRootHash, ReceiptsRootUtils } from '../../../receiptsRootUtils';
+import { Utils } from '../../../utils';
+import { MirrorNodeClient } from '../../clients/mirrorNodeClient';
+import constants from '../../constants';
+import { predefined } from '../../errors/JsonRpcError';
+import { EthImpl } from '../../eth';
+import { Block, Log, Receipt, Transaction } from '../../model';
+import { IContractResultsParams, RequestDetails } from '../../types';
+import { CacheService } from '../cacheService/cacheService';
+import { CommonService } from '../ethService/ethCommonService/CommonService';
+import { BlockFactory } from '../factories/blockFactory';
+import { TransactionFactory } from '../factories/transactionFactory';
+import { IBlockService, ICommonService } from '../index';
+import { IBlockMirrorNode } from './IBlockService';
+
+export class BlockService implements IBlockService {
+  /**
+   * The cache service used for caching all responses.
+   * @private
+   */
+  private readonly cacheService: CacheService;
+
+  /**
+   * The chain id.
+   * @private
+   */
+  private readonly chain: string;
+
+  /**
+   * The common service used for all common methods.
+   * @private
+   */
+  private readonly common: ICommonService;
+
+  /**
+   * The maximum block range for the transaction count.
+   */
+  private readonly ethGetTransactionCountMaxBlockRange = ConfigService.get('ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE');
+
+  /**
+   * The logger used for logging all output from this class.
+   * @private
+   */
+  private readonly logger: Logger;
+
+  /**
+   * The interface through which we interact with the mirror node
+   * @private
+   */
+  private readonly mirrorNodeClient: MirrorNodeClient;
+
+  /**
+   * The static method for the eth_getBlockByHash RPC call.
+   */
+  static ethGetBlockByHash = 'eth_GetBlockByHash';
+
+  /** Constructor */
+  constructor(
+    cacheService: CacheService,
+    chain: string,
+    common: ICommonService,
+    mirrorNodeClient: MirrorNodeClient,
+    logger: Logger,
+  ) {
+    this.common = common;
path: packages/relay/src/lib/services/blockService/BlockService.ts
user: login: konstantinabl
body: fixed.
created_at: 2025-04-03T08:44:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2132,9 +2031,9 @@ export class EthImpl implements Eth {
   }
 
   private async getBlockNumberFromHash(blockHash: string, requestDetails: RequestDetails): Promise<string> {
-    const block = await this.getBlockByHash(blockHash, false, requestDetails);
+    const block = await this.mirrorNodeClient.getBlock(blockHash, requestDetails);
     if (block != null) {
-      return block.number;
+      return numberTo0x(block.number);
path: packages/relay/src/lib/eth.ts
user: login: konstantinabl
body: https://github.com/hiero-ledger/hiero-json-rpc-relay/pull/3600/files/81982e90829842889d034f9a3beab6ce63aa0fb3#diff-dd8c7990f7764960d05dcb4c1b5ae7eee48b425b1b83d6dcc7e7ccc442330ef0R2034

Because of the change on line 2034 above. We dont call the whole getBlockByHash flow anymore, just the mirrorNode, so the MN response contains the block.number, but not in hex format
created_at: 2025-04-03T08:47:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,424 @@
+// SPDX-License-Identifier: Apache-2.0
+import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
+import _ from 'lodash';
+import { Logger } from 'pino';
+
+import { nanOrNumberTo0x, numberTo0x } from '../../../formatters';
+import { IReceiptRootHash, ReceiptsRootUtils } from '../../../receiptsRootUtils';
+import { Utils } from '../../../utils';
+import { MirrorNodeClient } from '../../clients/mirrorNodeClient';
+import constants from '../../constants';
+import { predefined } from '../../errors/JsonRpcError';
+import { EthImpl } from '../../eth';
+import { Block, Log, Transaction } from '../../model';
+import { RequestDetails } from '../../types';
+import { CacheService } from '../cacheService/cacheService';
+import { CommonService } from '../ethService/ethCommonService';
+import { BlockFactory } from '../factories/blockFactory';
+import { TransactionFactory } from '../factories/transactionFactory';
+import { IBlockMirrorNode, IBlockService } from './IBlockService';
+
+export class BlockService implements IBlockService {
+  /**
+   * The cache service used for caching all responses.
+   * @private
+   */
+  private readonly cacheService: CacheService;
+
+  /**
+   * The chain id.
+   * @private
+   */
+  private readonly chain: string;
+
+  /**
+   * The common service used for all common methods.
+   * @private
+   */
+  private readonly common: CommonService;
+
+  /**
+   * The maximum block range for the transaction count.
+   */
+  private readonly ethGetTransactionCountMaxBlockRange = ConfigService.get('ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE');
+
+  /**
+   * The logger used for logging all output from this class.
+   * @private
+   */
+  private readonly logger: Logger;
+
+  /**
+   * The interface through which we interact with the mirror node
+   * @private
+   */
+  private readonly mirrorNodeClient: MirrorNodeClient;
+
+  /**
+   * The static method for the eth_getBlockByHash RPC call.
+   */
+  static ethGetBlockByHash = 'eth_GetBlockByHash';
+
+  /** Constructor */
+  constructor(
+    cacheService: CacheService,
+    chain: string,
+    common: CommonService,
+    mirrorNodeClient: MirrorNodeClient,
+    logger: Logger,
+  ) {
+    this.common = common;
+    this.chain = chain;
+    this.mirrorNodeClient = mirrorNodeClient;
+    this.logger = logger;
+    this.cacheService = cacheService;
+  }
+
+  public async getBlockByNumber(
+    blockNumber: string,
+    showDetails: boolean,
+    requestDetails: RequestDetails,
+  ): Promise<Block> {
+    const requestIdPrefix = requestDetails.formattedRequestId;
+    this.logger.trace(`${requestIdPrefix} getBlockByNumber(blockNumber=${blockNumber}, showDetails=${showDetails})`);
+
+    const cacheKey = `${constants.CACHE_KEY.ETH_GET_BLOCK_BY_NUMBER}_${blockNumber}_${showDetails}`;
+    let block = await this.cacheService.getAsync(cacheKey, EthImpl.ethGetBlockByNumber, requestDetails);
path: packages/relay/src/lib/services/blockService/BlockService.ts
user: login: konstantinabl
body: fixed
created_at: 2025-04-03T08:48:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -10,6 +10,12 @@ export interface IBlockService {
     requestDetails: RequestDetails,
   ) => Promise<Block | null>;
   getBlockByHash: (hash: string, showDetails: boolean, requestDetails: RequestDetails) => Promise<Block | null>;
+  getBlockTransactionCountByHash: (hash: string, requestDetails: RequestDetails) => Promise<string | null>;
+  getBlockTransactionCountByNumber: (blockNum: string, requestDetails: RequestDetails) => Promise<string | null>;
+  getUncleByBlockHashAndIndex: (requestDetails: RequestDetails) => Promise<null>;
+  getUncleByBlockNumberAndIndex: (requestDetails: RequestDetails) => Promise<null>;
+  getUncleCountByBlockHash: (requestDetails: RequestDetails) => Promise<string>;
+  getUncleCountByBlockNumber: (requestDetails: RequestDetails) => Promise<string>;
 }
 
 export interface ITimestamp {
path: packages/relay/src/lib/services/blockService/IBlockService.ts
user: login: konstantinabl
body: Fixed now
created_at: 2025-04-03T11:00:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -19,19 +19,57 @@ import { TransactionFactory } from '../factories/transactionFactory';
 import { IBlockMirrorNode, IBlockService } from './IBlockService';
 
 export class BlockService implements IBlockService {
path: packages/relay/src/lib/services/blockService/index.ts
user: login: konstantinabl
body: Fixed it : )
created_at: 2025-04-03T11:00:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -16,20 +17,19 @@ import {
   tinybarsToWeibars,
   toHash32,
 } from '../../../../formatters';
+import { Utils } from '../../../../utils';
 import { MirrorNodeClient } from '../../../clients';
 import constants from '../../../constants';
 import { JsonRpcError, predefined } from '../../../errors/JsonRpcError';
 import { MirrorNodeClientError } from '../../../errors/MirrorNodeClientError';
 import { SDKClientError } from '../../../errors/SDKClientError';
 import { EthImpl } from '../../../eth';
-import { Log } from '../../../model';
+import { Log, Transaction } from '../../../model';
 import { RequestDetails } from '../../../types';
 import { CacheService } from '../../cacheService/cacheService';
-import { ICommonService } from './ICommonService';
-import { Utils } from '../../../../utils';
-import { Hbar } from '@hashgraph/sdk';
-import HAPIService from '../../hapiService/hapiService';
 import { TransactionFactory } from '../../factories/transactionFactory';
+import HAPIService from '../../hapiService/hapiService';
+import { ICommonService } from './ICommonService';
path: packages/relay/src/lib/services/ethService/ethCommonService/index.ts
user: login: konstantinabl
body: Changed the name, its CommonService.ts now
created_at: 2025-04-03T11:00:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -6,6 +6,7 @@ import { BigNumber as BN } from 'bignumber.js';
 import crypto from 'crypto';
 
 import constants from './lib/constants';
+import { Transaction } from './lib/model';
 import { TransactionFactory } from './lib/services/factories/transactionFactory';
path: packages/relay/src/formatters.ts
user: login: Ferparishuertas
body:  I mean at src level. We wiil review it outside of the PR
created_at: 2025-04-03T11:22:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,424 @@
+// SPDX-License-Identifier: Apache-2.0
+import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
+import _ from 'lodash';
+import { Logger } from 'pino';
+
+import { nanOrNumberTo0x, numberTo0x } from '../../../formatters';
+import { IReceiptRootHash, ReceiptsRootUtils } from '../../../receiptsRootUtils';
+import { Utils } from '../../../utils';
+import { MirrorNodeClient } from '../../clients/mirrorNodeClient';
+import constants from '../../constants';
+import { predefined } from '../../errors/JsonRpcError';
+import { EthImpl } from '../../eth';
+import { Block, Log, Transaction } from '../../model';
+import { RequestDetails } from '../../types';
+import { CacheService } from '../cacheService/cacheService';
+import { CommonService } from '../ethService/ethCommonService';
+import { BlockFactory } from '../factories/blockFactory';
+import { TransactionFactory } from '../factories/transactionFactory';
+import { IBlockMirrorNode, IBlockService } from './IBlockService';
+
+export class BlockService implements IBlockService {
+  /**
+   * The cache service used for caching all responses.
+   * @private
+   */
+  private readonly cacheService: CacheService;
+
+  /**
+   * The chain id.
+   * @private
+   */
+  private readonly chain: string;
+
+  /**
+   * The common service used for all common methods.
+   * @private
+   */
+  private readonly common: CommonService;
+
+  /**
+   * The maximum block range for the transaction count.
+   */
+  private readonly ethGetTransactionCountMaxBlockRange = ConfigService.get('ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE');
+
+  /**
+   * The logger used for logging all output from this class.
+   * @private
+   */
+  private readonly logger: Logger;
+
+  /**
+   * The interface through which we interact with the mirror node
+   * @private
+   */
+  private readonly mirrorNodeClient: MirrorNodeClient;
+
+  /**
+   * The static method for the eth_getBlockByHash RPC call.
+   */
+  static ethGetBlockByHash = 'eth_GetBlockByHash';
+
+  /** Constructor */
+  constructor(
+    cacheService: CacheService,
+    chain: string,
+    common: CommonService,
+    mirrorNodeClient: MirrorNodeClient,
+    logger: Logger,
+  ) {
+    this.common = common;
+    this.chain = chain;
+    this.mirrorNodeClient = mirrorNodeClient;
+    this.logger = logger;
+    this.cacheService = cacheService;
+  }
+
+  public async getBlockByNumber(
path: packages/relay/src/lib/services/blockService/BlockService.ts
user: login: konstantinabl
body: Will resolve this in a separate issue, so we can discuss the logging
created_at: 2025-04-03T11:26:26Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-31T14:42:42Z
body: ## Test Results
 18 files  ±0  236 suites  ±0   30m 45s :stopwatch: -23s
627 tests ±0  623 :white_check_mark: ±0  4 :zzz: ±0  0 :x: ±0 
643 runs  ±0  639 :white_check_mark: ±0  4 :zzz: ±0  0 :x: ±0 

Results for commit 3ea140bc. ± Comparison against base commit e3859b7b.

[test-results]:data:application/gzip;base64,H4sIAPyu6mcC/03MSw6DIBSF4a0Yxh2AXKF0Mw0CNyFVaXiMTPdeSiw6/L+TnJ2gX1wij4HdbwNJxedWIxc1bYk6+7C1GaYqdc2/XYzyX89UjGnET3r5dyXogNovFWgHF2OIh8SytU/gR/RLrrqcj60vh62vfyasq881CHeaAZ0NWglCWZgUpVYpnMFpigoNo9JKi+TzBRXMQmgJAQAA

:recycle: This comment has been updated with latest results.
author_association: NONE
user: login: Ferparishuertas
created_at: 2025-04-01T07:34:36Z
body: Can you enumerate which methods are extracted and why?
I do see missing getBlockReceiptsm and other private methods, that are block related
author_association: CONTRIBUTOR
user: login: konstantinabl
created_at: 2025-04-03T11:08:13Z
body: > Can you enumerate which methods are extracted and why? I do see missing getBlockReceiptsm and other private methods, that are block related

@Ferparishuertas I have moved getBlockReceipts now and listed the methods i have moved. Not sure how to explain why, without making it too long. I moved the block related methods to BlockService and any methods used by those methods to the blockService as well. Also moved any utility methods used by the block-related methods and other methods from eth.ts to CommonService, so eth.ts doesn't include any business logic and utility methods. 
What other private methods are missing?
author_association: CONTRIBUTOR